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
.SetBestFittingSize(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 SetBestSize(self
, size
=None):
97 Given the current font and bezel width settings, calculate
101 size
= wx
.DefaultSize
102 wx
.PyControl
.SetBestFittingSize(self
, size
)
105 def DoGetBestSize(self
):
107 Overridden base class virtual. Determines the best size of the
108 button based on the label and bezel size.
110 w
, h
, useMin
= self
._GetLabelSize
()
111 defSize
= wx
.Button
.GetDefaultSize()
113 if useMin
and width
< defSize
.width
:
114 width
= defSize
.width
116 if useMin
and height
< defSize
.height
:
117 height
= defSize
.height
118 width
= width
+ self
.bezelWidth
- 1
119 height
= height
+ self
.bezelWidth
- 1
120 return (width
, height
)
123 def AcceptsFocus(self
):
124 """Overridden base class virtual."""
125 return self
.IsShown() and self
.IsEnabled()
128 def GetDefaultAttributes(self
):
130 Overridden base class virtual. By default we should use
131 the same font/colour attributes as the native Button.
133 return wx
.Button
.GetClassDefaultAttributes()
136 def ShouldInheritColours(self
):
138 Overridden base class virtual. Buttons usually don't inherit
139 the parent's colours.
144 def Enable(self
, enable
=True):
145 wx
.PyControl
.Enable(self
, enable
)
149 def SetBezelWidth(self
, width
):
150 """Set the width of the 3D effect"""
151 self
.bezelWidth
= width
153 def GetBezelWidth(self
):
154 """Return the width of the 3D effect"""
155 return self
.bezelWidth
157 def SetUseFocusIndicator(self
, flag
):
158 """Specifiy if a focus indicator (dotted line) should be used"""
159 self
.useFocusInd
= flag
161 def GetUseFocusIndicator(self
):
162 """Return focus indicator flag"""
163 return self
.useFocusInd
166 def InitColours(self
):
168 Calculate a new set of highlight and shadow colours based on
169 the background colour. Works okay if the colour is dark...
171 faceClr
= self
.GetBackgroundColour()
172 r
, g
, b
= faceClr
.Get()
173 fr
, fg
, fb
= min(255,r
+32), min(255,g
+32), min(255,b
+32)
174 self
.faceDnClr
= wx
.Colour(fr
, fg
, fb
)
175 sr
, sg
, sb
= max(0,r
-32), max(0,g
-32), max(0,b
-32)
176 self
.shadowPen
= wx
.Pen(wx
.Colour(sr
,sg
,sb
), 1, wx
.SOLID
)
177 hr
, hg
, hb
= min(255,r
+64), min(255,g
+64), min(255,b
+64)
178 self
.highlightPen
= wx
.Pen(wx
.Colour(hr
,hg
,hb
), 1, wx
.SOLID
)
179 self
.focusClr
= wx
.Colour(hr
, hg
, hb
)
181 textClr
= self
.GetForegroundColour()
182 if wx
.Platform
== "__WXMAC__":
183 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.SOLID
)
185 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.USER_DASH
)
186 self
.focusIndPen
.SetDashes([1,1])
187 self
.focusIndPen
.SetCap(wx
.CAP_BUTT
)
190 def SetBackgroundColour(self
, colour
):
191 wx
.PyControl
.SetBackgroundColour(self
, colour
)
195 def SetForegroundColour(self
, colour
):
196 wx
.PyControl
.SetForegroundColour(self
, colour
)
199 def SetDefault(self
):
200 self
.GetParent().SetDefaultItem(self
)
202 def _GetLabelSize(self
):
203 """ used internally """
204 w
, h
= self
.GetTextExtent(self
.GetLabel())
209 evt
= GenButtonEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
.GetId())
210 evt
.SetIsDown(not self
.up
)
211 evt
.SetButtonObj(self
)
212 evt
.SetEventObject(self
)
213 self
.GetEventHandler().ProcessEvent(evt
)
216 def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
):
217 # draw the upper left sides
219 dc
.SetPen(self
.highlightPen
)
221 dc
.SetPen(self
.shadowPen
)
222 for i
in range(self
.bezelWidth
):
223 dc
.DrawLine(x1
+i
, y1
, x1
+i
, y2
-i
)
224 dc
.DrawLine(x1
, y1
+i
, x2
-i
, y1
+i
)
226 # draw the lower right sides
228 dc
.SetPen(self
.shadowPen
)
230 dc
.SetPen(self
.highlightPen
)
231 for i
in range(self
.bezelWidth
):
232 dc
.DrawLine(x1
+i
, y2
-i
, x2
+1, y2
-i
)
233 dc
.DrawLine(x2
-i
, y1
+i
, x2
-i
, y2
)
236 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
237 dc
.SetFont(self
.GetFont())
239 dc
.SetTextForeground(self
.GetForegroundColour())
241 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
242 label
= self
.GetLabel()
243 tw
, th
= dc
.GetTextExtent(label
)
245 dw
= dy
= self
.labelDelta
246 dc
.DrawText(label
, (width
-tw
)/2+dw
, (height
-th
)/2+dy
)
249 def DrawFocusIndicator(self
, dc
, w
, h
):
251 self
.focusIndPen
.SetColour(self
.focusClr
)
252 dc
.SetLogicalFunction(wx
.INVERT
)
253 dc
.SetPen(self
.focusIndPen
)
254 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
255 dc
.DrawRectangle(bw
+2,bw
+2, w
-bw
*2-4, h
-bw
*2-4)
256 dc
.SetLogicalFunction(wx
.COPY
)
259 def OnPaint(self
, event
):
260 (width
, height
) = self
.GetClientSizeTuple()
265 dc
= wx
.PaintDC(self
)
269 colBg
= self
.GetBackgroundColour()
270 brush
= wx
.Brush(colBg
, wx
.SOLID
)
271 if self
.style
& wx
.BORDER_NONE
:
272 myAttr
= self
.GetDefaultAttributes()
273 parAttr
= self
.GetParent().GetDefaultAttributes()
274 myDef
= colBg
== myAttr
.colBg
275 parDef
= self
.GetParent().GetBackgroundColour() == parAttr
.colBg
277 if wx
.Platform
== "__WXMAC__":
278 brush
.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
279 elif wx
.Platform
== "__WXMSW__":
280 if self
.DoEraseBackground(dc
):
282 elif myDef
and not parDef
:
283 colBg
= self
.GetParent().GetBackgroundColour()
284 brush
= wx
.Brush(colBg
, wx
.SOLID
)
286 brush
= wx
.Brush(self
.faceDnClr
, wx
.SOLID
)
287 if brush
is not None:
288 dc
.SetBackground(brush
)
291 self
.DrawBezel(dc
, x1
, y1
, x2
, y2
)
292 self
.DrawLabel(dc
, width
, height
)
293 if self
.hasFocus
and self
.useFocusInd
:
294 self
.DrawFocusIndicator(dc
, width
, height
)
297 def OnLeftDown(self
, event
):
298 if not self
.IsEnabled():
307 def OnLeftUp(self
, event
):
308 if not self
.IsEnabled() or not self
.HasCapture():
310 if self
.HasCapture():
312 if not self
.up
: # if the button was down when the mouse was released...
315 if self
: # in case the button was destroyed in the eventhandler
320 def OnMotion(self
, event
):
321 if not self
.IsEnabled() or not self
.HasCapture():
323 if event
.LeftIsDown() and self
.HasCapture():
324 x
,y
= event
.GetPositionTuple()
325 w
,h
= self
.GetClientSizeTuple()
326 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
330 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
337 def OnGainFocus(self
, event
):
343 def OnLoseFocus(self
, event
):
344 self
.hasFocus
= False
349 def OnKeyDown(self
, event
):
350 if self
.hasFocus
and event
.KeyCode() == ord(" "):
356 def OnKeyUp(self
, event
):
357 if self
.hasFocus
and event
.KeyCode() == ord(" "):
364 #----------------------------------------------------------------------
366 class GenBitmapButton(GenButton
):
367 """A generic bitmap button."""
369 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
,
370 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
371 style
= 0, validator
= wx
.DefaultValidator
,
373 self
.bmpDisabled
= None
375 self
.bmpSelected
= None
376 self
.SetBitmapLabel(bitmap
)
377 GenButton
.__init
__(self
, parent
, id, "", pos
, size
, style
, validator
, name
)
380 def GetBitmapLabel(self
):
382 def GetBitmapDisabled(self
):
383 return self
.bmpDisabled
384 def GetBitmapFocus(self
):
386 def GetBitmapSelected(self
):
387 return self
.bmpSelected
390 def SetBitmapDisabled(self
, bitmap
):
391 """Set bitmap to display when the button is disabled"""
392 self
.bmpDisabled
= bitmap
394 def SetBitmapFocus(self
, bitmap
):
395 """Set bitmap to display when the button has the focus"""
396 self
.bmpFocus
= bitmap
397 self
.SetUseFocusIndicator(False)
399 def SetBitmapSelected(self
, bitmap
):
400 """Set bitmap to display when the button is selected (pressed down)"""
401 self
.bmpSelected
= bitmap
403 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
405 Set the bitmap to display normally.
406 This is the only one that is required. If
407 createOthers is True, then the other bitmaps
408 will be generated on the fly. Currently,
409 only the disabled bitmap is generated.
411 self
.bmpLabel
= bitmap
412 if bitmap
is not None and createOthers
:
413 image
= wx
.ImageFromBitmap(bitmap
)
414 imageutils
.grayOut(image
)
415 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
418 def _GetLabelSize(self
):
419 """ used internally """
420 if not self
.bmpLabel
:
422 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
424 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
426 if self
.bmpDisabled
and not self
.IsEnabled():
427 bmp
= self
.bmpDisabled
428 if self
.bmpFocus
and self
.hasFocus
:
430 if self
.bmpSelected
and not self
.up
:
431 bmp
= self
.bmpSelected
432 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
434 dw
= dy
= self
.labelDelta
435 hasMask
= bmp
.GetMask() != None
436 dc
.DrawBitmap(bmp
, (width
-bw
)/2+dw
, (height
-bh
)/2+dy
, hasMask
)
439 #----------------------------------------------------------------------
442 class GenBitmapTextButton(GenBitmapButton
):
443 """A generic bitmapped button with text label"""
444 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
, label
='',
445 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
446 style
= 0, validator
= wx
.DefaultValidator
,
448 GenBitmapButton
.__init
__(self
, parent
, id, bitmap
, pos
, size
, style
, validator
, name
)
452 def _GetLabelSize(self
):
453 """ used internally """
454 w
, h
= self
.GetTextExtent(self
.GetLabel())
455 if not self
.bmpLabel
:
456 return w
, h
, True # if there isn't a bitmap use the size of the text
458 w_bmp
= self
.bmpLabel
.GetWidth()+2
459 h_bmp
= self
.bmpLabel
.GetHeight()+2
465 return width
, height
, True
468 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
470 if bmp
!= None: # if the bitmap is used
471 if self
.bmpDisabled
and not self
.IsEnabled():
472 bmp
= self
.bmpDisabled
473 if self
.bmpFocus
and self
.hasFocus
:
475 if self
.bmpSelected
and not self
.up
:
476 bmp
= self
.bmpSelected
477 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
479 dw
= dy
= self
.labelDelta
480 hasMask
= bmp
.GetMask() != None
482 bw
= bh
= 0 # no bitmap -> size is zero
484 dc
.SetFont(self
.GetFont())
486 dc
.SetTextForeground(self
.GetForegroundColour())
488 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
490 label
= self
.GetLabel()
491 tw
, th
= dc
.GetTextExtent(label
) # size of text
493 dw
= dy
= self
.labelDelta
495 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
497 dc
.DrawBitmap(bmp
, pos_x
, (height
-bh
)/2+dy
, hasMask
) # draw bitmap if available
498 pos_x
= pos_x
+ 2 # extra spacing from bitmap
500 dc
.DrawText(label
, pos_x
+ dw
+bw
, (height
-th
)/2+dy
) # draw the text
503 #----------------------------------------------------------------------
507 def SetToggle(self
, flag
):
516 def OnLeftDown(self
, event
):
517 if not self
.IsEnabled():
519 self
.saveUp
= self
.up
520 self
.up
= not self
.up
525 def OnLeftUp(self
, event
):
526 if not self
.IsEnabled() or not self
.HasCapture():
528 if self
.HasCapture():
531 if self
.up
!= self
.saveUp
:
534 def OnKeyDown(self
, event
):
537 def OnMotion(self
, event
):
538 if not self
.IsEnabled():
540 if event
.LeftIsDown() and self
.HasCapture():
541 x
,y
= event
.GetPositionTuple()
542 w
,h
= self
.GetClientSizeTuple()
543 if x
<w
and x
>=0 and y
<h
and y
>=0:
544 self
.up
= not self
.saveUp
547 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
548 self
.up
= self
.saveUp
553 def OnKeyUp(self
, event
):
554 if self
.hasFocus
and event
.KeyCode() == ord(" "):
555 self
.up
= not self
.up
563 class GenToggleButton(__ToggleMixin
, GenButton
):
564 """A generic toggle button"""
567 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
568 """A generic toggle bitmap button"""
571 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
572 """A generic toggle bitmap button with text label"""
575 #----------------------------------------------------------------------