File: svg_button_editor.py

package info (click to toggle)
python-enable 4.3.0-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 7,280 kB
  • ctags: 13,899
  • sloc: cpp: 48,447; python: 28,502; ansic: 9,004; makefile: 315; sh: 44
file content (255 lines) | stat: -rw-r--r-- 9,366 bytes parent folder | download | duplicates (3)
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