File: themed_control.py

package info (click to toggle)
python-traitsui 4.4.0-1.3
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,680 kB
  • ctags: 6,394
  • sloc: python: 32,786; makefile: 16; sh: 5
file content (307 lines) | stat: -rw-r--r-- 9,982 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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#-------------------------------------------------------------------------------
#
#  Copyright (c) 2007, 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!
#
#  Author: David C. Morrill
#  Date:   07/03/2007
#
#-------------------------------------------------------------------------------

""" Defines 'ThemedControl, a themed control based class. A 'themed' control is
    a control (optionally) supporting a stretchable background image.
"""

#-------------------------------------------------------------------------------
#  Imports:
#-------------------------------------------------------------------------------

import wx

from traits.api \
    import HasTraits, Str, Int, Enum, Bool, Property, Event, Tuple, Instance, \
           cached_property, on_trait_change

from traitsui.api \
    import default_theme

from traitsui.ui_traits \
    import Image, Position, Alignment, Spacing

from image_slice import ImageSlice

from themed_window \
    import ThemedWindow

#-------------------------------------------------------------------------------
#  Constants:
#-------------------------------------------------------------------------------

# The size of an empty text string:
ZeroTextSize = ( 0, 0, 0, 0 )

# An empty position and size bounds:
EmptyBounds = ( 0, 0, 0, 0 )

# Targets for '_get_bounds_for' method:
TheText    = 0
TheBitmap  = 1
TheControl = 2

#-------------------------------------------------------------------------------
#  'ThemedControl' class:
#-------------------------------------------------------------------------------

class ThemedControl ( ThemedWindow ):

    #-- Public Traits ----------------------------------------------------------

    # An (optional) image to be drawn inside the control:
    image = Image( event = 'updated' )

    # The (optional) text to be displayed inside the control:
    text = Str( event = 'updated' )

    # The position of the image relative to the text:
    position = Position( event = 'updated' )

    # The amount of spacing between the image and the text:
    spacing = Spacing( event = 'updated' )

    # Is the text value private (like a password):
    password = Bool( False, event = 'updated' )

    # An additional, optional offset to apply to the text/image position:
    offset = Tuple( Int, Int )

    # Minimum default size for the control:
    min_size = Tuple( Int, Int )

    # Is the control enabled:
    enabled = Bool( True )

    #-- Private Traits ---------------------------------------------------------

    # An event fired when any display related value changes:
    updated = Event

    # The underlying wx.Window control:
    control = Instance( wx.Window )

    # The current text value to display:
    current_text = Property( depends_on = 'text, password' )

    # The best size for the control:
    best_size = Property

    # The size of the current text:
    text_size = Property( depends_on = 'current_text, control' )

    # The position and size of the current text within the control:
    text_bounds = Property( depends_on = 'updated' )

    # The position and size of the current bitmap within the control:
    bitmap_bounds = Property( depends_on = 'updated' )

    #-- Public Methods ---------------------------------------------------------

    def create_control ( self, parent ):
        """ Creates the underlying wx.Window object.
        """
        self.control = control = wx.Window( parent, -1,
                            size  = wx.Size( 70, 20 ),
                            style = wx.FULL_REPAINT_ON_RESIZE | wx.WANTS_CHARS )

        # Initialize the control (set-up event handlers, ...):
        self.init_control()

        # Make sure all internal state gets initialized:
        self._image_changed( self.image )

        # Make sure the control is sized correctly:
        size = self.best_size
        control.SetMinSize( size )
        control.SetSize( size )

        return control

    #-- Property Implementations -----------------------------------------------

    @cached_property
    def _get_current_text ( self ):
        """ Returns the current text to display.
        """
        if self.password:
            return '*' * len( self.text )

        return self.text

    def _get_best_size ( self ):
        """ Returns the 'best' size for the control.
        """
        cx, cy, cdx, cdy = self._get_bounds_for( TheControl )
        mdx, mdy         = self.min_size
        return wx.Size( max( cdx, mdx ), max( cdy, mdy ) )

    @cached_property
    def _get_text_size ( self ):
        """ Returns the text size information for the window.
        """
        text = self.current_text
        if (text == '') or (self.control is None):
            return ZeroTextSize

        return self.control.GetFullTextExtent( text )

    @cached_property
    def _get_text_bounds ( self ):
        """ Returns the position and size of the text within the window.
        """
        return self._get_bounds_for( TheText )

    @cached_property
    def _get_bitmap_bounds ( self ):
        """ Returns the size and position of the bitmap within the window.
        """
        return self._get_bounds_for( TheBitmap )

    #-- Event Handlers ---------------------------------------------------------

    @on_trait_change( 'theme.+, image' )
    def _updated_changed ( self ):
        """ Handles any update related trait being changed.
        """
        if self.control is not None:
            self.control.Refresh()

    def _image_changed ( self, image ):
        """ Handles the image being changed by updating the corresponding
            bitmap.
        """
        if image is None:
            self._bitmap = None
        else:
            self._bitmap = image.create_image().ConvertToBitmap()

    #-- wxPython Event Handlers ------------------------------------------------

    def _paint_fg ( self, dc ):
        """ Paints the foreground into the specified device context.
        """
        self.enabled = self.control.IsEnabled()

        # Get the text and image offset to use:
        theme    = self.theme or default_theme
        label    = theme.label
        ox, oy   = label.left, label.top
        ox2, oy2 = self.offset
        ox      += ox2
        oy      += oy2

        # Draw the bitmap (if any):
        ix, iy, idx, idy = self.bitmap_bounds
        if idx != 0:
            dc.DrawBitmap( self._bitmap, ix + ox, iy + oy, True )

        # Draw the text (if any):
        tx, ty, tdx, tdy = self.text_bounds
        if tdx != 0:
            dc.SetBackgroundMode( wx.TRANSPARENT )
            dc.SetTextForeground( theme.content_color )
            dc.SetFont( self.control.GetFont() )
            dc.DrawText( self.current_text, tx + ox, ty + oy )

    def _size ( self, event ):
        """ Handles the control being resized.
        """
        super( ThemedControl, self )._size( event )
        self.updated = True

    #-- Private Methods --------------------------------------------------------

    def _get_bounds_for ( self, item ):
        """ Returns all text and image related position and size information.
        """
        control = self.control
        if control is None:
            return EmptyBounds

        tdx, tdy, descent, leading = self.text_size
        bitmap = self._bitmap
        if bitmap is None:
            bdx, bdy = 0, 0
        else:
            bdx, bdy = bitmap.GetWidth(), bitmap.GetHeight()
        if (tdx + bdx) == 0:
            return EmptyBounds

        wdx, wdy  = control.GetClientSizeTuple()
        spacing   = (tdx != 0) * (bdx != 0) * self.spacing
        theme     = self.theme or default_theme
        slice     = theme.image_slice or ImageSlice()
        content   = theme.content

        position = self.position
        if position in ( 'above', 'below' ):
            cdx = max( tdx, bdx )
            cdy = tdy + spacing + bdy
        else:
            cdx = tdx + spacing + bdx
            cdy = max( tdy, bdy )

        if item == TheControl:
            cdx += content.left + content.right
            cdy += content.top  + content.bottom

            return ( 0, 0, max( slice.left  + slice.right,
                                slice.xleft + slice.xright  + cdx ),
                           max( slice.top   + slice.bottom,
                                slice.xtop  + slice.xbottom + cdy ) )

        alignment = theme.alignment
        if alignment == 'default':
            alignment = self.default_alignment
        if alignment == 'left':
            x = slice.xleft + content.left
        elif alignment == 'center':
            x = slice.xleft + content.left + ((wdx - slice.xleft - slice.xright
                            - content.left - content.right - cdx) / 2)
        else:
            x = wdx - slice.xright - content.right - cdx

        if position == 'left':
            bx = x
            tx = bx + bdx + spacing
        elif position == 'right':
            tx = x
            bx = tx + tdx + spacing
        else:
            x += (max( tdx, bdx ) / 2)
            tx = x - (tdx / 2 )
            bx = x - (bdx / 2 )

        y = slice.xtop + content.top + ((wdy - slice.xtop - slice.xbottom -
                         content.top - content.bottom - cdy) / 2)
        if position == 'above':
            by = y
            ty = by + bdy + spacing
        elif position == 'below':
            ty = y
            by = ty + tdy + spacing
        else:
            y += (max( tdy, bdy ) / 2)
            ty = y - ((tdy + 1) / 2)
            by = y - (bdy / 2)

        if item == TheText:
            return ( tx, ty, tdx, tdy )

        return ( bx, by, bdx, bdy )