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 tlw
= wx
.GetTopLevelParent(self
)
202 if hasattr(tlw
, 'SetDefaultItem'):
203 tlw
.SetDefaultItem(self
)
205 def _GetLabelSize(self
):
206 """ used internally """
207 w
, h
= self
.GetTextExtent(self
.GetLabel())
212 evt
= GenButtonEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
.GetId())
213 evt
.SetIsDown(not self
.up
)
214 evt
.SetButtonObj(self
)
215 evt
.SetEventObject(self
)
216 self
.GetEventHandler().ProcessEvent(evt
)
219 def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
):
220 # draw the upper left sides
222 dc
.SetPen(self
.highlightPen
)
224 dc
.SetPen(self
.shadowPen
)
225 for i
in range(self
.bezelWidth
):
226 dc
.DrawLine(x1
+i
, y1
, x1
+i
, y2
-i
)
227 dc
.DrawLine(x1
, y1
+i
, x2
-i
, y1
+i
)
229 # draw the lower right sides
231 dc
.SetPen(self
.shadowPen
)
233 dc
.SetPen(self
.highlightPen
)
234 for i
in range(self
.bezelWidth
):
235 dc
.DrawLine(x1
+i
, y2
-i
, x2
+1, y2
-i
)
236 dc
.DrawLine(x2
-i
, y1
+i
, x2
-i
, y2
)
239 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
240 dc
.SetFont(self
.GetFont())
242 dc
.SetTextForeground(self
.GetForegroundColour())
244 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
245 label
= self
.GetLabel()
246 tw
, th
= dc
.GetTextExtent(label
)
248 dw
= dy
= self
.labelDelta
249 dc
.DrawText(label
, (width
-tw
)/2+dw
, (height
-th
)/2+dy
)
252 def DrawFocusIndicator(self
, dc
, w
, h
):
254 self
.focusIndPen
.SetColour(self
.focusClr
)
255 dc
.SetLogicalFunction(wx
.INVERT
)
256 dc
.SetPen(self
.focusIndPen
)
257 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
258 dc
.DrawRectangle(bw
+2,bw
+2, w
-bw
*2-4, h
-bw
*2-4)
259 dc
.SetLogicalFunction(wx
.COPY
)
261 def OnPaint(self
, event
):
262 (width
, height
) = self
.GetClientSizeTuple()
267 dc
= wx
.PaintDC(self
)
268 brush
= self
.GetBackgroundBrush(dc
)
269 if brush
is not None:
270 dc
.SetBackground(brush
)
273 self
.DrawBezel(dc
, x1
, y1
, x2
, y2
)
274 self
.DrawLabel(dc
, width
, height
)
275 if self
.hasFocus
and self
.useFocusInd
:
276 self
.DrawFocusIndicator(dc
, width
, height
)
279 def GetBackgroundBrush(self
, dc
):
281 colBg
= self
.GetBackgroundColour()
282 brush
= wx
.Brush(colBg
, wx
.SOLID
)
283 if self
.style
& wx
.BORDER_NONE
:
284 myAttr
= self
.GetDefaultAttributes()
285 parAttr
= self
.GetParent().GetDefaultAttributes()
286 myDef
= colBg
== myAttr
.colBg
287 parDef
= self
.GetParent().GetBackgroundColour() == parAttr
.colBg
289 if wx
.Platform
== "__WXMAC__":
290 brush
.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
291 elif wx
.Platform
== "__WXMSW__":
292 if self
.DoEraseBackground(dc
):
294 elif myDef
and not parDef
:
295 colBg
= self
.GetParent().GetBackgroundColour()
296 brush
= wx
.Brush(colBg
, wx
.SOLID
)
298 # this line assumes that a pressed button should be hilighted with
299 # a solid colour even if the background is supposed to be transparent
300 brush
= wx
.Brush(self
.faceDnClr
, wx
.SOLID
)
304 def OnLeftDown(self
, event
):
305 if not self
.IsEnabled():
314 def OnLeftUp(self
, event
):
315 if not self
.IsEnabled() or not self
.HasCapture():
317 if self
.HasCapture():
319 if not self
.up
: # if the button was down when the mouse was released...
322 if self
: # in case the button was destroyed in the eventhandler
327 def OnMotion(self
, event
):
328 if not self
.IsEnabled() or not self
.HasCapture():
330 if event
.LeftIsDown() and self
.HasCapture():
331 x
,y
= event
.GetPositionTuple()
332 w
,h
= self
.GetClientSizeTuple()
333 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
337 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
344 def OnGainFocus(self
, event
):
350 def OnLoseFocus(self
, event
):
351 self
.hasFocus
= False
356 def OnKeyDown(self
, event
):
357 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
363 def OnKeyUp(self
, event
):
364 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
371 #----------------------------------------------------------------------
373 class GenBitmapButton(GenButton
):
374 """A generic bitmap button."""
376 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
,
377 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
378 style
= 0, validator
= wx
.DefaultValidator
,
380 self
.bmpDisabled
= None
382 self
.bmpSelected
= None
383 self
.SetBitmapLabel(bitmap
)
384 GenButton
.__init
__(self
, parent
, id, "", pos
, size
, style
, validator
, name
)
387 def GetBitmapLabel(self
):
389 def GetBitmapDisabled(self
):
390 return self
.bmpDisabled
391 def GetBitmapFocus(self
):
393 def GetBitmapSelected(self
):
394 return self
.bmpSelected
397 def SetBitmapDisabled(self
, bitmap
):
398 """Set bitmap to display when the button is disabled"""
399 self
.bmpDisabled
= bitmap
401 def SetBitmapFocus(self
, bitmap
):
402 """Set bitmap to display when the button has the focus"""
403 self
.bmpFocus
= bitmap
404 self
.SetUseFocusIndicator(False)
406 def SetBitmapSelected(self
, bitmap
):
407 """Set bitmap to display when the button is selected (pressed down)"""
408 self
.bmpSelected
= bitmap
410 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
412 Set the bitmap to display normally.
413 This is the only one that is required. If
414 createOthers is True, then the other bitmaps
415 will be generated on the fly. Currently,
416 only the disabled bitmap is generated.
418 self
.bmpLabel
= bitmap
419 if bitmap
is not None and createOthers
:
420 image
= wx
.ImageFromBitmap(bitmap
)
421 imageutils
.grayOut(image
)
422 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
425 def _GetLabelSize(self
):
426 """ used internally """
427 if not self
.bmpLabel
:
429 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
431 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
433 if self
.bmpDisabled
and not self
.IsEnabled():
434 bmp
= self
.bmpDisabled
435 if self
.bmpFocus
and self
.hasFocus
:
437 if self
.bmpSelected
and not self
.up
:
438 bmp
= self
.bmpSelected
439 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
441 dw
= dy
= self
.labelDelta
442 hasMask
= bmp
.GetMask() != None
443 dc
.DrawBitmap(bmp
, (width
-bw
)/2+dw
, (height
-bh
)/2+dy
, hasMask
)
446 #----------------------------------------------------------------------
449 class GenBitmapTextButton(GenBitmapButton
):
450 """A generic bitmapped button with text label"""
451 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
, label
='',
452 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
453 style
= 0, validator
= wx
.DefaultValidator
,
455 GenBitmapButton
.__init
__(self
, parent
, id, bitmap
, pos
, size
, style
, validator
, name
)
459 def _GetLabelSize(self
):
460 """ used internally """
461 w
, h
= self
.GetTextExtent(self
.GetLabel())
462 if not self
.bmpLabel
:
463 return w
, h
, True # if there isn't a bitmap use the size of the text
465 w_bmp
= self
.bmpLabel
.GetWidth()+2
466 h_bmp
= self
.bmpLabel
.GetHeight()+2
472 return width
, height
, True
475 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
477 if bmp
!= None: # if the bitmap is used
478 if self
.bmpDisabled
and not self
.IsEnabled():
479 bmp
= self
.bmpDisabled
480 if self
.bmpFocus
and self
.hasFocus
:
482 if self
.bmpSelected
and not self
.up
:
483 bmp
= self
.bmpSelected
484 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
486 dw
= dy
= self
.labelDelta
487 hasMask
= bmp
.GetMask() != None
489 bw
= bh
= 0 # no bitmap -> size is zero
491 dc
.SetFont(self
.GetFont())
493 dc
.SetTextForeground(self
.GetForegroundColour())
495 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
497 label
= self
.GetLabel()
498 tw
, th
= dc
.GetTextExtent(label
) # size of text
500 dw
= dy
= self
.labelDelta
502 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
504 dc
.DrawBitmap(bmp
, pos_x
, (height
-bh
)/2+dy
, hasMask
) # draw bitmap if available
505 pos_x
= pos_x
+ 2 # extra spacing from bitmap
507 dc
.DrawText(label
, pos_x
+ dw
+bw
, (height
-th
)/2+dy
) # draw the text
510 #----------------------------------------------------------------------
514 def SetToggle(self
, flag
):
523 def OnLeftDown(self
, event
):
524 if not self
.IsEnabled():
526 self
.saveUp
= self
.up
527 self
.up
= not self
.up
532 def OnLeftUp(self
, event
):
533 if not self
.IsEnabled() or not self
.HasCapture():
535 if self
.HasCapture():
538 if self
.up
!= self
.saveUp
:
541 def OnKeyDown(self
, event
):
544 def OnMotion(self
, event
):
545 if not self
.IsEnabled():
547 if event
.LeftIsDown() and self
.HasCapture():
548 x
,y
= event
.GetPositionTuple()
549 w
,h
= self
.GetClientSizeTuple()
550 if x
<w
and x
>=0 and y
<h
and y
>=0:
551 self
.up
= not self
.saveUp
554 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
555 self
.up
= self
.saveUp
560 def OnKeyUp(self
, event
):
561 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
562 self
.up
= not self
.up
570 class GenToggleButton(__ToggleMixin
, GenButton
):
571 """A generic toggle button"""
574 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
575 """A generic toggle bitmap button"""
578 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
579 """A generic toggle bitmap button with text label"""
582 #----------------------------------------------------------------------
584 class ThemedGenButton(GenButton
):
585 " A themed generic button, and base class for the other themed buttons "
586 def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
):
587 rect
= wx
.Rect(x1
, y1
, x2
, y2
)
591 state
= wx
.CONTROL_PRESSED
592 if not self
.IsEnabled():
593 state
= wx
.CONTROL_DISABLED
594 wx
.RendererNative
.Get().DrawPushButton(self
, dc
, rect
, state
)
596 class ThemedGenBitmapButton(ThemedGenButton
, GenBitmapButton
):
597 """A themed generic bitmap button."""
600 class ThemedGenBitmapTextButton(ThemedGenButton
, GenBitmapTextButton
):
601 """A themed generic bitmapped button with text label"""
604 class ThemedGenToggleButton(ThemedGenButton
, GenToggleButton
):
605 """A themed generic toggle button"""
608 class ThemedGenBitmapToggleButton(ThemedGenButton
, GenBitmapToggleButton
):
609 """A themed generic toggle bitmap button"""
612 class ThemedGenBitmapTextToggleButton(ThemedGenButton
, GenBitmapTextToggleButton
):
613 """A themed generic toggle bitmap button with text label"""
617 #----------------------------------------------------------------------