1
#-------------------------------------------------------------------------------
3
# Copyright (c) 2007, Enthought, Inc.
6
# This software is provided without warranty under the terms of the BSD
7
# license included in enthought/LICENSE.txt and may be redistributed only
8
# under the conditions described in the aforementioned license. The license
9
# is also available online at http://www.enthought.com/licenses/BSD.txt
11
# Thanks for using Enthought open source!
13
# Author: David C. Morrill
16
#-------------------------------------------------------------------------------
18
""" Defines a themed panel that wraps itself around a single child widget.
21
#-------------------------------------------------------------------------------
23
#-------------------------------------------------------------------------------
28
import Str, Property, Instance, Bool, cached_property
33
#-------------------------------------------------------------------------------
35
#-------------------------------------------------------------------------------
37
# Size of an empty text string:
38
ZeroTextSize = ( 0, 0, 0, 0 )
40
#-------------------------------------------------------------------------------
42
#-------------------------------------------------------------------------------
44
class ImagePanel ( ThemedWindow ):
46
# The optional text to display in the top or bottom of the image slice:
47
text = Str( event = 'updated' )
49
# Can the application change the theme contents?
50
mutable_theme = Bool( False )
52
# Is the image panel capable of displaying text?
53
can_show_text = Property
55
# The adjusted size of the panel, taking into account the size of its
56
# current children and the image border:
57
adjusted_size = Property
59
# The best size of the panel, taking into account the best size of its
60
# children and the image border:
63
# The underlying wx control:
64
control = Instance( wx.Window )
66
#-- Private Traits ---------------------------------------------------------
68
# The size of the current text:
69
text_size = Property( depends_on = 'text, control' )
71
#-- Public Methods ---------------------------------------------------------
73
def create_control ( self, parent ):
74
""" Creates the underlying wx.Panel control.
76
self.control = control = wx.Panel( parent, -1,
77
style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE )
79
# Set up the sizer for the control:
80
control.SetSizer( ImageSizer( self.theme ) )
82
# Initialize the control (set-up event handlers, ...)
85
# Attach the image slice to the control:
86
control._image_slice = self.theme.image_slice
88
# Set the panel's background colour to the image slice bg_color:
89
control.SetBackgroundColour( control._image_slice.bg_color )
94
""" Lays out the contents of the image panel.
97
self.control.Refresh()
99
#-- Property Implementations -----------------------------------------------
101
def _get_adjusted_size ( self ):
102
""" Returns the adjusted size of the panel taking into account the
103
size of its current children and the image border.
105
control = self.control
107
for child in control.GetChildren():
108
dx, dy = child.GetSizeTuple()
110
size = self._adjusted_size_of( dx, dy )
111
control.SetSize( size )
115
def _get_best_size ( self ):
116
""" Returns the best size of the panel taking into account the
117
best size of its current children and the image border.
119
control = self.control
121
for child in control.GetChildren():
122
dx, dy = child.GetBestSize()
124
return self._adjusted_size_of( dx, dy )
127
def _get_can_show_text ( self ):
128
""" Returns whether or not the image panel is capable of displaying
131
tdx, tdy, descent, leading = self.control.GetFullTextExtent( 'Myj' )
132
slice = self.theme.image_slice
134
return ((tdy <= slice.xtop) or (tdy <= slice.xbottom) or
135
(slice.xleft >= 40) or (slice.xright >= 40))
138
def _get_text_size ( self ):
139
""" Returns the text size information for the window.
141
if (self.text == '') or (self.control is None):
144
return self.control.GetFullTextExtent( self.text )
146
#-- Trait Event Handlers ---------------------------------------------------
148
def _updated_changed ( self ):
149
""" Handles a change that requires the control to be updated.
151
if self.control is not None:
152
self.control.Refresh()
154
def _mutable_theme_changed ( self, state ):
155
""" Handles a change to the 'mutable_theme' trait.
157
self.on_trait_change( self._theme_modified,
158
'theme.[border.-,content.-,label.-,alignment,content_color,'
159
'label_color]', remove = not state )
161
def _theme_modified ( self ):
162
if self.control is not None:
165
def _theme_changed ( self, theme ):
166
""" Handles the 'theme' trait being changed.
168
super( ImagePanel, self )._theme_changed()
170
control = self.control
171
if (control is not None) and (theme is not None):
172
# Attach the image slice to the control:
173
control._image_slice = theme.image_slice
175
# Set the panel's background colour to the image slice bg_color:
176
control.SetBackgroundColour( control._image_slice.bg_color )
178
#-- wx.Python Event Handlers -----------------------------------------------
180
def _paint_fg ( self, dc ):
181
""" Paints the foreground into the specified device context.
183
# If we have text and have room to draw it, then do so:
185
if (text != '') and self.can_show_text:
187
dc.SetBackgroundMode( wx.TRANSPARENT )
188
dc.SetTextForeground( theme.label_color )
189
dc.SetFont( self.control.GetFont() )
191
alignment = theme.alignment
193
wdx, wdy = self.control.GetClientSizeTuple()
194
tdx, tdy, descent, leading = self.text_size
196
slice = theme.image_slice
198
xright = slice.xright
200
xbottom = slice.xbottom
202
lbottom = label.bottom
203
tdyp = tdy + ltop + lbottom
204
cl = xleft + label.left
205
cr = wdx - xright - label.right
206
if (tdyp <= xtop) and (xtop >= xbottom):
207
ty = ((ltop + xtop - lbottom - tdy) / 2) + 1
209
ty = wdy + ((ltop - xbottom - lbottom - tdy) / 2)
211
ty = (wdy + xtop + label.top - xbottom - label.bottom - tdy) / 2
214
cr = xleft - label.right
216
cl = wdx - xright + label.left
217
cr = wdx - label.right
219
# Calculate the x coordinate for the specified alignment type:
220
if alignment == 'left':
222
elif alignment == 'right':
225
tx = (cl + cr - tdx) / 2
227
# Draw the (clipped) text string:
228
dc.SetClippingRegion( cl, ty, cr - cl, tdy )
229
dc.DrawText( text, tx, ty )
230
dc.DestroyClippingRegion()
232
#-- Private Methods --------------------------------------------------------
234
def _adjusted_size_of ( self, dx, dy ):
235
""" Returns the adjusted size of its children, taking into account the
238
slice = self.theme.image_slice
239
content = self.theme.content
240
sizer = self.control.GetSizer()
241
return wx.Size( dx + min( slice.left, slice.xleft ) +
242
min( slice.right, slice.xright ) +
243
content.left + content.right,
244
dy + min( slice.top, slice.xtop ) +
245
min( slice.bottom, slice.xbottom ) +
246
content.top + content.bottom )
248
#-------------------------------------------------------------------------------
249
# 'ImageSizer' class:
250
#-------------------------------------------------------------------------------
252
class ImageSizer ( wx.PySizer ):
253
""" Defines a sizer that correctly sizes a window's children to fit within
254
the borders implicitly defined by a background ImageSlice object,
257
#---------------------------------------------------------------------------
258
# Initializes the object:
259
#---------------------------------------------------------------------------
261
def __init__ ( self, theme ):
262
""" Initializes the object.
264
super( ImageSizer, self ).__init__()
266
# Save a reference to the theme:
269
# Save the ImageSlice object which determines the inset border size:
270
self._image_slice = theme.image_slice
272
#---------------------------------------------------------------------------
273
# Calculates the minimum size needed by the sizer:
274
#---------------------------------------------------------------------------
276
def CalcMin ( self ):
277
""" Calculates the minimum size of the control by adding its contents
278
minimum size to the ImageSlice object's border size.
281
for item in self.GetChildren():
283
dx, dy = item.GetSizer().CalcMin()
285
dx, dy = item.GetWindow().GetBestSize()
287
slice = self._image_slice
288
content = self._theme.content
290
return wx.Size( max( slice.left + slice.right,
291
slice.xleft + slice.xright +
292
content.left + content.right + dx ),
293
max( slice.top + slice.bottom,
294
slice.xtop + slice.xbottom +
295
content.top + content.bottom + dy ) )
297
#---------------------------------------------------------------------------
298
# Layout the contents of the sizer based on the sizer's current size and
300
#---------------------------------------------------------------------------
302
def RecalcSizes ( self ):
303
""" Layout the contents of the sizer based on the sizer's current size
306
x, y = self.GetPositionTuple()
307
dx, dy = self.GetSizeTuple()
308
slice = self._image_slice
309
content = self._theme.content
310
left = slice.xleft + content.left
311
top = slice.xtop + content.top
312
ix, iy, idx, idy = ( x + left,
314
dx - left - slice.xright - content.right,
315
dy - top - slice.xbottom - content.bottom )
317
for item in self.GetChildren():
319
item.GetSizer().SetDimension( ix, iy, idx, idy )
321
item.GetWindow().SetDimensions( ix, iy, idx, idy )