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. They act
22 like normal buttons but you are able to better control how they look,
23 bevel width, colours, etc.
30 #----------------------------------------------------------------------
32 class GenButtonEvent(wx
.PyCommandEvent
):
33 """Event sent from the generic buttons when the button is activated. """
34 def __init__(self
, eventType
, id):
35 wx
.PyCommandEvent
.__init
__(self
, eventType
, id)
39 def SetIsDown(self
, isDown
):
45 def SetButtonObj(self
, btn
):
48 def GetButtonObj(self
):
52 #----------------------------------------------------------------------
54 class GenButton(wx
.PyControl
):
55 """A generic button, and base class for the other generic buttons."""
59 def __init__(self
, parent
, id=-1, label
='',
60 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
61 style
= 0, validator
= wx
.DefaultValidator
,
65 cstyle
= wx
.BORDER_NONE
66 wx
.PyControl
.__init
__(self
, parent
, id, pos
, size
, cstyle
, validator
, name
)
71 if style
& wx
.BORDER_NONE
:
73 self
.useFocusInd
= False
76 self
.useFocusInd
= True
79 self
.InheritAttributes()
80 self
.SetInitialSize(size
)
83 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
84 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnLeftUp
)
85 if wx
.Platform
== '__WXMSW__':
86 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnLeftDown
)
87 self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
88 self
.Bind(wx
.EVT_SET_FOCUS
, self
.OnGainFocus
)
89 self
.Bind(wx
.EVT_KILL_FOCUS
, self
.OnLoseFocus
)
90 self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
)
91 self
.Bind(wx
.EVT_KEY_UP
, self
.OnKeyUp
)
92 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
95 def SetInitialSize(self
, size
=None):
97 Given the current font and bezel width settings, calculate
101 size
= wx
.DefaultSize
102 wx
.PyControl
.SetInitialSize(self
, size
)
103 SetBestSize
= SetInitialSize
106 def DoGetBestSize(self
):
108 Overridden base class virtual. Determines the best size of the
109 button based on the label and bezel size.
111 w
, h
, useMin
= self
._GetLabelSize
()
112 defSize
= wx
.Button
.GetDefaultSize()
114 if useMin
and width
< defSize
.width
:
115 width
= defSize
.width
117 if useMin
and height
< defSize
.height
:
118 height
= defSize
.height
119 width
= width
+ self
.bezelWidth
- 1
120 height
= height
+ self
.bezelWidth
- 1
121 return (width
, height
)
124 def AcceptsFocus(self
):
125 """Overridden base class virtual."""
126 return self
.IsShown() and self
.IsEnabled()
129 def GetDefaultAttributes(self
):
131 Overridden base class virtual. By default we should use
132 the same font/colour attributes as the native Button.
134 return wx
.Button
.GetClassDefaultAttributes()
137 def ShouldInheritColours(self
):
139 Overridden base class virtual. Buttons usually don't inherit
140 the parent's colours.
145 def Enable(self
, enable
=True):
146 wx
.PyControl
.Enable(self
, enable
)
150 def SetBezelWidth(self
, width
):
151 """Set the width of the 3D effect"""
152 self
.bezelWidth
= width
154 def GetBezelWidth(self
):
155 """Return the width of the 3D effect"""
156 return self
.bezelWidth
158 def SetUseFocusIndicator(self
, flag
):
159 """Specifiy if a focus indicator (dotted line) should be used"""
160 self
.useFocusInd
= flag
162 def GetUseFocusIndicator(self
):
163 """Return focus indicator flag"""
164 return self
.useFocusInd
167 def InitColours(self
):
169 Calculate a new set of highlight and shadow colours based on
170 the background colour. Works okay if the colour is dark...
172 faceClr
= self
.GetBackgroundColour()
173 r
, g
, b
= faceClr
.Get()
174 fr
, fg
, fb
= min(255,r
+32), min(255,g
+32), min(255,b
+32)
175 self
.faceDnClr
= wx
.Colour(fr
, fg
, fb
)
176 sr
, sg
, sb
= max(0,r
-32), max(0,g
-32), max(0,b
-32)
177 self
.shadowPen
= wx
.Pen(wx
.Colour(sr
,sg
,sb
), 1, wx
.SOLID
)
178 hr
, hg
, hb
= min(255,r
+64), min(255,g
+64), min(255,b
+64)
179 self
.highlightPen
= wx
.Pen(wx
.Colour(hr
,hg
,hb
), 1, wx
.SOLID
)
180 self
.focusClr
= wx
.Colour(hr
, hg
, hb
)
182 textClr
= self
.GetForegroundColour()
183 if wx
.Platform
== "__WXMAC__":
184 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.SOLID
)
186 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.USER_DASH
)
187 self
.focusIndPen
.SetDashes([1,1])
188 self
.focusIndPen
.SetCap(wx
.CAP_BUTT
)
191 def SetBackgroundColour(self
, colour
):
192 wx
.PyControl
.SetBackgroundColour(self
, colour
)
196 def SetForegroundColour(self
, colour
):
197 wx
.PyControl
.SetForegroundColour(self
, colour
)
200 def SetDefault(self
):
201 self
.GetParent().SetDefaultItem(self
)
203 def _GetLabelSize(self
):
204 """ used internally """
205 w
, h
= self
.GetTextExtent(self
.GetLabel())
210 evt
= GenButtonEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
.GetId())
211 evt
.SetIsDown(not self
.up
)
212 evt
.SetButtonObj(self
)
213 evt
.SetEventObject(self
)
214 self
.GetEventHandler().ProcessEvent(evt
)
217 def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
):
218 # draw the upper left sides
220 dc
.SetPen(self
.highlightPen
)
222 dc
.SetPen(self
.shadowPen
)
223 for i
in range(self
.bezelWidth
):
224 dc
.DrawLine(x1
+i
, y1
, x1
+i
, y2
-i
)
225 dc
.DrawLine(x1
, y1
+i
, x2
-i
, y1
+i
)
227 # draw the lower right sides
229 dc
.SetPen(self
.shadowPen
)
231 dc
.SetPen(self
.highlightPen
)
232 for i
in range(self
.bezelWidth
):
233 dc
.DrawLine(x1
+i
, y2
-i
, x2
+1, y2
-i
)
234 dc
.DrawLine(x2
-i
, y1
+i
, x2
-i
, y2
)
237 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
238 dc
.SetFont(self
.GetFont())
240 dc
.SetTextForeground(self
.GetForegroundColour())
242 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
243 label
= self
.GetLabel()
244 tw
, th
= dc
.GetTextExtent(label
)
246 dw
= dy
= self
.labelDelta
247 dc
.DrawText(label
, (width
-tw
)/2+dw
, (height
-th
)/2+dy
)
250 def DrawFocusIndicator(self
, dc
, w
, h
):
252 self
.focusIndPen
.SetColour(self
.focusClr
)
253 dc
.SetLogicalFunction(wx
.INVERT
)
254 dc
.SetPen(self
.focusIndPen
)
255 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
256 dc
.DrawRectangle(bw
+2,bw
+2, w
-bw
*2-4, h
-bw
*2-4)
257 dc
.SetLogicalFunction(wx
.COPY
)
259 def OnPaint(self
, event
):
260 (width
, height
) = self
.GetClientSizeTuple()
265 dc
= wx
.PaintDC(self
)
266 brush
= self
.GetBackgroundBrush(dc
)
267 if brush
is not None:
268 dc
.SetBackground(brush
)
271 self
.DrawBezel(dc
, x1
, y1
, x2
, y2
)
272 self
.DrawLabel(dc
, width
, height
)
273 if self
.hasFocus
and self
.useFocusInd
:
274 self
.DrawFocusIndicator(dc
, width
, height
)
277 def GetBackgroundBrush(self
, dc
):
279 colBg
= self
.GetBackgroundColour()
280 brush
= wx
.Brush(colBg
, wx
.SOLID
)
281 if self
.style
& wx
.BORDER_NONE
:
282 myAttr
= self
.GetDefaultAttributes()
283 parAttr
= self
.GetParent().GetDefaultAttributes()
284 myDef
= colBg
== myAttr
.colBg
285 parDef
= self
.GetParent().GetBackgroundColour() == parAttr
.colBg
287 if wx
.Platform
== "__WXMAC__":
288 brush
.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
289 elif wx
.Platform
== "__WXMSW__":
290 if self
.DoEraseBackground(dc
):
292 elif myDef
and not parDef
:
293 colBg
= self
.GetParent().GetBackgroundColour()
294 brush
= wx
.Brush(colBg
, wx
.SOLID
)
296 # this line assumes that a pressed button should be hilighted with
297 # a solid colour even if the background is supposed to be transparent
298 brush
= wx
.Brush(self
.faceDnClr
, wx
.SOLID
)
302 def OnLeftDown(self
, event
):
303 if not self
.IsEnabled():
312 def OnLeftUp(self
, event
):
313 if not self
.IsEnabled() or not self
.HasCapture():
315 if self
.HasCapture():
317 if not self
.up
: # if the button was down when the mouse was released...
320 if self
: # in case the button was destroyed in the eventhandler
325 def OnMotion(self
, event
):
326 if not self
.IsEnabled() or not self
.HasCapture():
328 if event
.LeftIsDown() and self
.HasCapture():
329 x
,y
= event
.GetPositionTuple()
330 w
,h
= self
.GetClientSizeTuple()
331 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
335 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
342 def OnGainFocus(self
, event
):
348 def OnLoseFocus(self
, event
):
349 self
.hasFocus
= False
354 def OnKeyDown(self
, event
):
355 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
361 def OnKeyUp(self
, event
):
362 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
369 #----------------------------------------------------------------------
371 class GenBitmapButton(GenButton
):
372 """A generic bitmap button."""
374 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
,
375 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
376 style
= 0, validator
= wx
.DefaultValidator
,
378 self
.bmpDisabled
= None
380 self
.bmpSelected
= None
381 self
.SetBitmapLabel(bitmap
)
382 GenButton
.__init
__(self
, parent
, id, "", pos
, size
, style
, validator
, name
)
385 def GetBitmapLabel(self
):
387 def GetBitmapDisabled(self
):
388 return self
.bmpDisabled
389 def GetBitmapFocus(self
):
391 def GetBitmapSelected(self
):
392 return self
.bmpSelected
395 def SetBitmapDisabled(self
, bitmap
):
396 """Set bitmap to display when the button is disabled"""
397 self
.bmpDisabled
= bitmap
399 def SetBitmapFocus(self
, bitmap
):
400 """Set bitmap to display when the button has the focus"""
401 self
.bmpFocus
= bitmap
402 self
.SetUseFocusIndicator(False)
404 def SetBitmapSelected(self
, bitmap
):
405 """Set bitmap to display when the button is selected (pressed down)"""
406 self
.bmpSelected
= bitmap
408 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
410 Set the bitmap to display normally.
411 This is the only one that is required. If
412 createOthers is True, then the other bitmaps
413 will be generated on the fly. Currently,
414 only the disabled bitmap is generated.
416 self
.bmpLabel
= bitmap
417 if bitmap
is not None and createOthers
:
418 image
= wx
.ImageFromBitmap(bitmap
)
419 imageutils
.grayOut(image
)
420 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
423 def _GetLabelSize(self
):
424 """ used internally """
425 if not self
.bmpLabel
:
427 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
429 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
431 if self
.bmpDisabled
and not self
.IsEnabled():
432 bmp
= self
.bmpDisabled
433 if self
.bmpFocus
and self
.hasFocus
:
435 if self
.bmpSelected
and not self
.up
:
436 bmp
= self
.bmpSelected
437 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
439 dw
= dy
= self
.labelDelta
440 hasMask
= bmp
.GetMask() != None
441 dc
.DrawBitmap(bmp
, (width
-bw
)/2+dw
, (height
-bh
)/2+dy
, hasMask
)
444 #----------------------------------------------------------------------
447 class GenBitmapTextButton(GenBitmapButton
):
448 """A generic bitmapped button with text label"""
449 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
, label
='',
450 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
451 style
= 0, validator
= wx
.DefaultValidator
,
453 GenBitmapButton
.__init
__(self
, parent
, id, bitmap
, pos
, size
, style
, validator
, name
)
457 def _GetLabelSize(self
):
458 """ used internally """
459 w
, h
= self
.GetTextExtent(self
.GetLabel())
460 if not self
.bmpLabel
:
461 return w
, h
, True # if there isn't a bitmap use the size of the text
463 w_bmp
= self
.bmpLabel
.GetWidth()+2
464 h_bmp
= self
.bmpLabel
.GetHeight()+2
470 return width
, height
, True
473 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
475 if bmp
!= None: # if the bitmap is used
476 if self
.bmpDisabled
and not self
.IsEnabled():
477 bmp
= self
.bmpDisabled
478 if self
.bmpFocus
and self
.hasFocus
:
480 if self
.bmpSelected
and not self
.up
:
481 bmp
= self
.bmpSelected
482 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
484 dw
= dy
= self
.labelDelta
485 hasMask
= bmp
.GetMask() != None
487 bw
= bh
= 0 # no bitmap -> size is zero
489 dc
.SetFont(self
.GetFont())
491 dc
.SetTextForeground(self
.GetForegroundColour())
493 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
495 label
= self
.GetLabel()
496 tw
, th
= dc
.GetTextExtent(label
) # size of text
498 dw
= dy
= self
.labelDelta
500 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
502 dc
.DrawBitmap(bmp
, pos_x
, (height
-bh
)/2+dy
, hasMask
) # draw bitmap if available
503 pos_x
= pos_x
+ 2 # extra spacing from bitmap
505 dc
.DrawText(label
, pos_x
+ dw
+bw
, (height
-th
)/2+dy
) # draw the text
508 #----------------------------------------------------------------------
512 def SetToggle(self
, flag
):
521 def OnLeftDown(self
, event
):
522 if not self
.IsEnabled():
524 self
.saveUp
= self
.up
525 self
.up
= not self
.up
530 def OnLeftUp(self
, event
):
531 if not self
.IsEnabled() or not self
.HasCapture():
533 if self
.HasCapture():
536 if self
.up
!= self
.saveUp
:
539 def OnKeyDown(self
, event
):
542 def OnMotion(self
, event
):
543 if not self
.IsEnabled():
545 if event
.LeftIsDown() and self
.HasCapture():
546 x
,y
= event
.GetPositionTuple()
547 w
,h
= self
.GetClientSizeTuple()
548 if x
<w
and x
>=0 and y
<h
and y
>=0:
549 self
.up
= not self
.saveUp
552 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
553 self
.up
= self
.saveUp
558 def OnKeyUp(self
, event
):
559 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
560 self
.up
= not self
.up
568 class GenToggleButton(__ToggleMixin
, GenButton
):
569 """A generic toggle button"""
572 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
573 """A generic toggle bitmap button"""
576 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
577 """A generic toggle bitmap button with text label"""
580 #----------------------------------------------------------------------