1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
|
#------------------------------------------------------------------------------
#
# Copyright (c) 2009, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
#
#------------------------------------------------------------------------------
""" Traits UI button editor for SVG images.
"""
# Standard library imports
import copy
import sys
import xml.etree.cElementTree as etree
import os.path
# System library imports
import wx
# ETS imports
from enable.savage.svg.document import SVGDocument
from enable.savage.svg.backends.wx.renderer import Renderer
from traits.api import Instance
from traitsui.wx.constants import WindowColor
from traitsui.wx.editor import Editor
# Local imports
from wx_render_panel import RenderPanel
class ButtonRenderPanel(RenderPanel):
def __init__(self, parent, button, padding=(8,8)):
self.button = button
self.document = button.document
self.state = 'up'
self.toggle_document = button.toggle_document
self.toggle_state = button.factory.toggle_state
self.padding = padding
super(ButtonRenderPanel, self).__init__(parent, document=self.document)
def DoGetBestSize(self):
label = self.button.factory.label
if len(label):
dc = wx.ScreenDC()
dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT))
label_size = dc.GetTextExtent(label)
else:
label_size = (0, 0)
width = max(self.button.factory.width, label_size[0])
height = self.button.factory.height + label_size[1]
return wx.Size(width + self.padding[0], height + self.padding[1])
def GetBackgroundColour(self):
bgcolor = copy.copy(WindowColor)
if self.state == 'down':
red, green, blue = bgcolor.Get()
red -= 15
green -= 15
blue -= 15
bgcolor.Set(red, green, blue, 255)
return bgcolor
def OnPaint(self, evt):
dc = wx.BufferedPaintDC(self)
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT))
gc = wx.GraphicsContext_Create(dc)
if self.toggle_state and self.button.factory.toggle and \
not self.button.factory.toggle_filename:
self._draw_toggle(gc)
# Put the icon in the middle of the alotted space. If the text is wider
# than the icon, then the best_size will be wider, in which case we
# want to put the icon in a little bit more towards the center,
# otherwise, the icon will be drawn starting after the left padding.
best_size = self.DoGetBestSize()
x_offset = (best_size.width - self.button.factory.width)/2.0
y_offset = self.padding[1] / 2.0
gc.Translate(x_offset, y_offset)
gc.Scale(float(self.zoom_x) / 100, float(self.zoom_y) / 100)
if self.toggle_state and self.button.factory.toggle and \
self.button.factory.toggle_filename:
self.toggle_document.render(gc)
label_text = self.button.factory.toggle_label
else:
self.document.render(gc)
label_text = self.button.factory.label
# Reset the translation and zoom, then draw the text at an offset
# based on the text width. There is a minor gotcha for supporting
# multiple platforms here, Translate and DrawText behave differently
# on different platforms.
# It would be nice is a cross platform library actually worked the
# same across platforms...
text_width = dc.GetTextExtent(label_text)[0]
text_x = (best_size.width - text_width)/2.0
text_y = self.button.factory.height
gc.Scale(100/float(self.zoom_x), 100/float(self.zoom_y))
if sys.platform == 'darwin':
gc.Translate(-x_offset + text_x, -y_offset + text_y)
dc.DrawText(label_text, 0, 0)
else:
gc.Translate(-x_offset, -y_offset)
dc.DrawText(label_text, text_x, text_y)
if not self.button.enabled:
self._draw_disable_mask(gc)
def OnLeftDown(self, evt):
# if the button is supposed to toggle, set the toggle_state
# to the opposite of what it currently is
if self.button.factory.toggle:
self.toggle_state = not self.toggle_state
if self.toggle_state:
tooltip = wx.ToolTip(self.button.factory.toggle_tooltip)
else:
tooltip = wx.ToolTip(self.button.factory.tooltip)
self.button.control.SetToolTip(tooltip)
self.state = 'down'
evt.Skip()
self.Refresh()
def OnLeftUp(self, evt):
self.state = 'up'
self.button.update_editor()
evt.Skip()
self.Refresh()
def OnEnterWindow(self, evt):
self.hover = True
self.Refresh()
def OnLeaveWindow(self, evt):
self.hover = False
self.Refresh()
def OnWheel(self, evt):
pass
def _draw_disable_mask(self, gc):
""" Draws a mask using the background color with the alpha
set to about 33%
"""
best_size = self.DoGetBestSize()
path = gc.CreatePath()
path.AddRectangle(0, 0, best_size.width, best_size.height)
bgcolor = self.GetBackgroundColour()
bgcolor.Set(bgcolor.red, bgcolor.green, bgcolor.blue, 175)
gc.SetBrush(wx.Brush(bgcolor))
gc.FillPath(path)
def _draw_toggle(self, gc):
# the toggle doc and button doc may not be the same
# size, so calculate the scaling factor. Byt using the padding
# to lie about the size of the toggle button, we can grow the
# toggle a bit to use some of the padding. This is good for icons
# which use all of their available space
zoom_scale_x = float(self.zoom_x) / 100
zoom_scale_y = float(self.zoom_y) / 100
doc_size = self.document.getSize()
toggle_doc_size = self.toggle_document.getSize()
w_scale = zoom_scale_x * doc_size[0] / (toggle_doc_size[0]-self.padding[0]-1)
h_scale = zoom_scale_y * doc_size[1] / (toggle_doc_size[1]-self.padding[1]-1)
# move to the center of the allotted area
best_size = self.DoGetBestSize()
x_offset = (best_size.width - self.button.factory.width)/2.0
y_offset = self.padding[1] / 2.0
gc.Translate(x_offset, y_offset)
# Now scale the gc and render
gc.Scale(w_scale, h_scale)
self.toggle_document.render(gc)
# And return the scaling factor back to what it originally was
# and return to the origial location
gc.Scale(1/w_scale, 1/h_scale)
gc.Translate(-x_offset, -y_offset)
class SVGButtonEditor ( Editor ):
""" Traits UI 'display only' image editor.
"""
document = Instance(SVGDocument)
toggle_document = Instance(SVGDocument)
#---------------------------------------------------------------------------
# Editor API
#---------------------------------------------------------------------------
def init ( self, parent ):
""" Finishes initializing the editor by creating the underlying toolkit
widget.
"""
self.document = SVGDocument.createFromFile(self.factory.filename, renderer=Renderer)
# load the button toggle document which will be displayed when the
# button is toggled.
if self.factory.toggle_filename:
self.toggle_document = SVGDocument.createFromFile(self.factory.toggle_filename, renderer=Renderer)
else:
tree = etree.parse(os.path.join(os.path.dirname(__file__), 'data', 'button_toggle.svg'))
self.toggle_document = SVGDocument(tree.getroot(), renderer=Renderer)
padding = (self.factory.width_padding, self.factory.height_padding)
self.control = ButtonRenderPanel( parent, self, padding=padding )
if self.factory.tooltip != '':
self.control.SetToolTip(wx.ToolTip(self.factory.tooltip))
svg_w, svg_h = self.control.GetBestSize()
self.control.zoom_x /= float(svg_w) / self.factory.width
self.control.zoom_y /= float(svg_h) / self.factory.height
self.control.Refresh()
def prepare ( self, parent ):
""" Finishes setting up the editor. This differs from the base class
in that self.update_editor() is not called at the end, which
would fire an event.
"""
name = self.extended_name
if name != 'None':
self.context_object.on_trait_change( self._update_editor, name,
dispatch = 'ui' )
self.init( parent )
self._sync_values()
def update_editor ( self ):
""" Updates the editor when the object trait changes externally to the
editor.
"""
factory = self.factory
self.value = factory.value
|