1 #----------------------------------------------------------------------
3 # Purpose: Various kinds of generic buttons, (not native controls but
10 # Copyright: (c) 1999 by Total Control Software
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------
13 # 11/30/2003 - Jeff Grimmett (grimmtooth@softhome.net)
15 # o Updated for wx namespace
16 # o Tested with updated demo
20 This module implements various forms of generic buttons, meaning that
21 they are not built on native controls but are self-drawn.
23 The GenButton is the base. It acts like a normal button but you
24 are able to better control how it looks, bevel width, colours, etc.
26 GenBitmapButton is a button with one or more bitmaps that show
27 the various states the button can be in.
29 GenToggleButton stays depressed when clicked, until clicked again.
31 GenBitmapToggleButton the same but with bitmaps.
39 #----------------------------------------------------------------------
41 class GenButtonEvent(wx
.PyCommandEvent
):
42 def __init__(self
, eventType
, ID
):
43 wx
.PyCommandEvent
.__init
__(self
, eventType
, ID
)
47 def SetIsDown(self
, isDown
):
53 def SetButtonObj(self
, btn
):
56 def GetButtonObj(self
):
60 #----------------------------------------------------------------------
62 class GenButton(wx
.PyControl
):
65 def __init__(self
, parent
, ID
, label
,
66 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
67 style
= 0, validator
= wx
.DefaultValidator
,
71 wx
.PyControl
.__init
__(self
, parent
, ID
, pos
, size
, style
, validator
, name
)
76 self
.useFocusInd
= True
80 font
= parent
.GetFont()
82 font
= wx
.SystemSettings
.GetSystemFont(wx
.SYS_DEFAULT_GUI_FONT
)
84 self
.SetBestSize(size
)
87 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
88 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnLeftUp
)
89 if wx
.Platform
== '__WXMSW__':
90 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnLeftDown
)
91 self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
92 self
.Bind(wx
.EVT_SET_FOCUS
, self
.OnGainFocus
)
93 self
.Bind(wx
.EVT_KILL_FOCUS
, self
.OnLoseFocus
)
94 self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
)
95 self
.Bind(wx
.EVT_KEY_UP
, self
.OnKeyUp
)
96 self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
)
97 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
100 def SetBestSize(self
, size
=None):
102 Given the current font and bezel width settings, calculate
106 size
= wx
.Size(-1,-1)
107 if type(size
) == type(()):
108 size
= wx
.Size(size
[0], size
[1])
109 size
= wx
.Size(size
.width
, size
.height
) # make a copy
111 best
= self
.GetBestSize()
113 size
.width
= best
.width
114 if size
.height
== -1:
115 size
.height
= best
.height
120 def DoGetBestSize(self
):
121 """Overridden base class virtual. Determines the best size of the
122 button based on the label and bezel size."""
123 w
, h
, useMin
= self
._GetLabelSize
()
124 defSize
= wx
.Button
.GetDefaultSize()
126 if useMin
and width
< defSize
.width
:
127 width
= defSize
.width
129 if useMin
and height
< defSize
.height
:
130 height
= defSize
.height
131 width
= width
+ self
.bezelWidth
- 1
132 height
= height
+ self
.bezelWidth
- 1
133 return (width
, height
)
136 def AcceptsFocus(self
):
137 """Overridden base class virtual."""
138 return self
.IsShown() and self
.IsEnabled()
141 def Enable(self
, enable
=True):
142 wx
.PyControl
.Enable(self
, enable
)
146 def SetBezelWidth(self
, width
):
147 """Set the width of the 3D effect"""
148 self
.bezelWidth
= width
150 def GetBezelWidth(self
):
151 """Return the width of the 3D effect"""
152 return self
.bezelWidth
154 def SetUseFocusIndicator(self
, flag
):
155 """Specifiy if a focus indicator (dotted line) should be used"""
156 self
.useFocusInd
= flag
158 def GetUseFocusIndicator(self
):
159 """Return focus indicator flag"""
160 return self
.useFocusInd
163 def InitColours(self
):
164 faceClr
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNFACE
)
165 textClr
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNTEXT
)
166 self
.faceDnClr
= faceClr
167 self
.SetBackgroundColour(faceClr
)
168 self
.SetForegroundColour(textClr
)
170 shadowClr
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNSHADOW
)
171 highlightClr
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNHIGHLIGHT
)
172 self
.shadowPen
= wx
.Pen(shadowClr
, 1, wx
.SOLID
)
173 self
.highlightPen
= wx
.Pen(highlightClr
, 1, wx
.SOLID
)
174 if wx
.Platform
== "__WXMAC__":
175 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.SOLID
)
177 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.USER_DASH
)
178 self
.focusIndPen
.SetDashes([1,1])
179 self
.focusIndPen
.SetCap(wx
.CAP_BUTT
)
180 self
.focusClr
= highlightClr
183 def SetBackgroundColour(self
, colour
):
184 wx
.PyControl
.SetBackgroundColour(self
, colour
)
185 colour
= self
.GetBackgroundColour()
187 # Calculate a new set of highlight and shadow colours based on
188 # the new background colour. Works okay if the colour is dark...
189 r
, g
, b
= colour
.Get()
190 fr
, fg
, fb
= min(255,r
+32), min(255,g
+32), min(255,b
+32)
191 self
.faceDnClr
= wx
.Colour(fr
, fg
, fb
)
192 sr
, sg
, sb
= max(0,r
-32), max(0,g
-32), max(0,b
-32)
193 self
.shadowPen
= wx
.Pen(wx
.Colour(sr
,sg
,sb
), 1, wx
.SOLID
)
194 hr
, hg
, hb
= min(255,r
+64), min(255,g
+64), min(255,b
+64)
195 self
.highlightPen
= wx
.Pen(wx
.Colour(hr
,hg
,hb
), 1, wx
.SOLID
)
196 self
.focusClr
= wx
.Colour(hr
, hg
, hb
)
199 def _GetLabelSize(self
):
200 """ used internally """
201 w
, h
= self
.GetTextExtent(self
.GetLabel())
206 evt
= GenButtonEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
.GetId())
207 evt
.SetIsDown(not self
.up
)
208 evt
.SetButtonObj(self
)
209 evt
.SetEventObject(self
)
210 self
.GetEventHandler().ProcessEvent(evt
)
213 def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
):
214 # draw the upper left sides
216 dc
.SetPen(self
.highlightPen
)
218 dc
.SetPen(self
.shadowPen
)
219 for i
in range(self
.bezelWidth
):
220 dc
.DrawLine((x1
+i
, y1
), (x1
+i
, y2
-i
))
221 dc
.DrawLine((x1
, y1
+i
), (x2
-i
, y1
+i
))
223 # draw the lower right sides
225 dc
.SetPen(self
.shadowPen
)
227 dc
.SetPen(self
.highlightPen
)
228 for i
in range(self
.bezelWidth
):
229 dc
.DrawLine((x1
+i
, y2
-i
), (x2
+1, y2
-i
))
230 dc
.DrawLine((x2
-i
, y1
+i
), (x2
-i
, y2
))
233 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
234 dc
.SetFont(self
.GetFont())
236 dc
.SetTextForeground(self
.GetForegroundColour())
238 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
239 label
= self
.GetLabel()
240 tw
, th
= dc
.GetTextExtent(label
)
242 dw
= dy
= self
.labelDelta
243 dc
.DrawText(label
, ((width
-tw
)/2+dw
, (height
-th
)/2+dy
))
246 def DrawFocusIndicator(self
, dc
, w
, h
):
249 ## self.focusIndPen.SetColour(self.GetForegroundColour())
251 ## #self.focusIndPen.SetColour(self.GetBackgroundColour())
252 ## self.focusIndPen.SetColour(self.GetForegroundColour())
253 self
.focusIndPen
.SetColour(self
.focusClr
)
254 dc
.SetLogicalFunction(wx
.INVERT
)
255 dc
.SetPen(self
.focusIndPen
)
256 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
257 dc
.DrawRectangle((bw
+2,bw
+2), (w
-bw
*2-4, h
-bw
*2-4))
258 dc
.SetLogicalFunction(wx
.COPY
)
261 def OnPaint(self
, event
):
262 (width
, height
) = self
.GetClientSizeTuple()
266 dc
= wx
.BufferedPaintDC(self
)
268 dc
.SetBackground(wx
.Brush(self
.GetBackgroundColour(), wx
.SOLID
))
270 dc
.SetBackground(wx
.Brush(self
.faceDnClr
, wx
.SOLID
))
272 self
.DrawBezel(dc
, x1
, y1
, x2
, y2
)
273 self
.DrawLabel(dc
, width
, height
)
274 if self
.hasFocus
and self
.useFocusInd
:
275 self
.DrawFocusIndicator(dc
, width
, height
)
278 def OnEraseBackground(self
, event
):
282 def OnLeftDown(self
, event
):
283 if not self
.IsEnabled():
292 def OnLeftUp(self
, event
):
293 if not self
.IsEnabled() or not self
.HasCapture():
295 if self
.HasCapture():
297 if not self
.up
: # if the button was down when the mouse was released...
304 def OnMotion(self
, event
):
305 if not self
.IsEnabled() or not self
.HasCapture():
307 if event
.LeftIsDown() and self
.HasCapture():
308 x
,y
= event
.GetPositionTuple()
309 w
,h
= self
.GetClientSizeTuple()
310 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
314 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
321 def OnGainFocus(self
, event
):
323 dc
= wx
.ClientDC(self
)
324 w
, h
= self
.GetClientSizeTuple()
326 self
.DrawFocusIndicator(dc
, w
, h
)
329 def OnLoseFocus(self
, event
):
330 self
.hasFocus
= False
331 dc
= wx
.ClientDC(self
)
332 w
, h
= self
.GetClientSizeTuple()
334 self
.DrawFocusIndicator(dc
, w
, h
)
337 def OnKeyDown(self
, event
):
338 if self
.hasFocus
and event
.KeyCode() == ord(" "):
344 def OnKeyUp(self
, event
):
345 if self
.hasFocus
and event
.KeyCode() == ord(" "):
352 #----------------------------------------------------------------------
354 class GenBitmapButton(GenButton
):
355 def __init__(self
, parent
, ID
, bitmap
,
356 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
357 style
= 0, validator
= wx
.DefaultValidator
,
359 self
.bmpDisabled
= None
361 self
.bmpSelected
= None
362 self
.SetBitmapLabel(bitmap
)
363 GenButton
.__init
__(self
, parent
, ID
, "", pos
, size
, style
, validator
, name
)
366 def GetBitmapLabel(self
):
368 def GetBitmapDisabled(self
):
369 return self
.bmpDisabled
370 def GetBitmapFocus(self
):
372 def GetBitmapSelected(self
):
373 return self
.bmpSelected
376 def SetBitmapDisabled(self
, bitmap
):
377 """Set bitmap to display when the button is disabled"""
378 self
.bmpDisabled
= bitmap
380 def SetBitmapFocus(self
, bitmap
):
381 """Set bitmap to display when the button has the focus"""
382 self
.bmpFocus
= bitmap
383 self
.SetUseFocusIndicator(False)
385 def SetBitmapSelected(self
, bitmap
):
386 """Set bitmap to display when the button is selected (pressed down)"""
387 self
.bmpSelected
= bitmap
389 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
391 Set the bitmap to display normally.
392 This is the only one that is required. If
393 createOthers is True, then the other bitmaps
394 will be generated on the fly. Currently,
395 only the disabled bitmap is generated.
397 self
.bmpLabel
= bitmap
398 if bitmap
is not None and createOthers
:
399 image
= wx
.ImageFromBitmap(bitmap
)
400 imageutils
.grayOut(image
)
401 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
404 def _GetLabelSize(self
):
405 """ used internally """
406 if not self
.bmpLabel
:
408 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
410 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
412 if self
.bmpDisabled
and not self
.IsEnabled():
413 bmp
= self
.bmpDisabled
414 if self
.bmpFocus
and self
.hasFocus
:
416 if self
.bmpSelected
and not self
.up
:
417 bmp
= self
.bmpSelected
418 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
420 dw
= dy
= self
.labelDelta
421 hasMask
= bmp
.GetMask() != None
422 dc
.DrawBitmap(bmp
, ((width
-bw
)/2+dw
, (height
-bh
)/2+dy
), hasMask
)
425 #----------------------------------------------------------------------
428 class GenBitmapTextButton(GenBitmapButton
): # generic bitmapped button with Text Label
429 def __init__(self
, parent
, ID
, bitmap
, label
,
430 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
431 style
= 0, validator
= wx
.DefaultValidator
,
433 GenBitmapButton
.__init
__(self
, parent
, ID
, bitmap
, pos
, size
, style
, validator
, name
)
437 def _GetLabelSize(self
):
438 """ used internally """
439 w
, h
= self
.GetTextExtent(self
.GetLabel())
440 if not self
.bmpLabel
:
441 return w
, h
, True # if there isn't a bitmap use the size of the text
443 w_bmp
= self
.bmpLabel
.GetWidth()+2
444 h_bmp
= self
.bmpLabel
.GetHeight()+2
450 return width
, height
, True
453 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
455 if bmp
!= None: # if the bitmap is used
456 if self
.bmpDisabled
and not self
.IsEnabled():
457 bmp
= self
.bmpDisabled
458 if self
.bmpFocus
and self
.hasFocus
:
460 if self
.bmpSelected
and not self
.up
:
461 bmp
= self
.bmpSelected
462 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
464 dw
= dy
= self
.labelDelta
465 hasMask
= bmp
.GetMask() != None
467 bw
= bh
= 0 # no bitmap -> size is zero
469 dc
.SetFont(self
.GetFont())
471 dc
.SetTextForeground(self
.GetForegroundColour())
473 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
475 label
= self
.GetLabel()
476 tw
, th
= dc
.GetTextExtent(label
) # size of text
478 dw
= dy
= self
.labelDelta
480 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
482 dc
.DrawBitmap(bmp
, (pos_x
, (height
-bh
)/2+dy
), hasMask
) # draw bitmap if available
483 pos_x
= pos_x
+ 2 # extra spacing from bitmap
485 dc
.DrawText(label
, (pos_x
+ dw
+bw
, (height
-th
)/2+dy
)) # draw the text
488 #----------------------------------------------------------------------
492 def SetToggle(self
, flag
):
501 def OnLeftDown(self
, event
):
502 if not self
.IsEnabled():
504 self
.saveUp
= self
.up
505 self
.up
= not self
.up
510 def OnLeftUp(self
, event
):
511 if not self
.IsEnabled() or not self
.HasCapture():
513 if self
.HasCapture():
514 if self
.up
!= self
.saveUp
:
519 def OnKeyDown(self
, event
):
522 def OnMotion(self
, event
):
523 if not self
.IsEnabled():
525 if event
.LeftIsDown() and self
.HasCapture():
526 x
,y
= event
.GetPositionTuple()
527 w
,h
= self
.GetClientSizeTuple()
528 if x
<w
and x
>=0 and y
<h
and y
>=0:
529 self
.up
= not self
.saveUp
532 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
533 self
.up
= self
.saveUp
538 def OnKeyUp(self
, event
):
539 if self
.hasFocus
and event
.KeyCode() == ord(" "):
540 self
.up
= not self
.up
548 class GenToggleButton(__ToggleMixin
, GenButton
):
551 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
554 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
557 #----------------------------------------------------------------------