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
.BufferedPaintDC(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...
319 def OnMotion(self
, event
):
320 if not self
.IsEnabled() or not self
.HasCapture():
322 if event
.LeftIsDown() and self
.HasCapture():
323 x
,y
= event
.GetPositionTuple()
324 w
,h
= self
.GetClientSizeTuple()
325 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
329 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
336 def OnGainFocus(self
, event
):
342 def OnLoseFocus(self
, event
):
343 self
.hasFocus
= False
348 def OnKeyDown(self
, event
):
349 if self
.hasFocus
and event
.KeyCode() == ord(" "):
355 def OnKeyUp(self
, event
):
356 if self
.hasFocus
and event
.KeyCode() == ord(" "):
363 #----------------------------------------------------------------------
365 class GenBitmapButton(GenButton
):
366 """A generic bitmap button."""
368 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
,
369 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
370 style
= 0, validator
= wx
.DefaultValidator
,
372 self
.bmpDisabled
= None
374 self
.bmpSelected
= None
375 self
.SetBitmapLabel(bitmap
)
376 GenButton
.__init
__(self
, parent
, id, "", pos
, size
, style
, validator
, name
)
379 def GetBitmapLabel(self
):
381 def GetBitmapDisabled(self
):
382 return self
.bmpDisabled
383 def GetBitmapFocus(self
):
385 def GetBitmapSelected(self
):
386 return self
.bmpSelected
389 def SetBitmapDisabled(self
, bitmap
):
390 """Set bitmap to display when the button is disabled"""
391 self
.bmpDisabled
= bitmap
393 def SetBitmapFocus(self
, bitmap
):
394 """Set bitmap to display when the button has the focus"""
395 self
.bmpFocus
= bitmap
396 self
.SetUseFocusIndicator(False)
398 def SetBitmapSelected(self
, bitmap
):
399 """Set bitmap to display when the button is selected (pressed down)"""
400 self
.bmpSelected
= bitmap
402 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
404 Set the bitmap to display normally.
405 This is the only one that is required. If
406 createOthers is True, then the other bitmaps
407 will be generated on the fly. Currently,
408 only the disabled bitmap is generated.
410 self
.bmpLabel
= bitmap
411 if bitmap
is not None and createOthers
:
412 image
= wx
.ImageFromBitmap(bitmap
)
413 imageutils
.grayOut(image
)
414 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
417 def _GetLabelSize(self
):
418 """ used internally """
419 if not self
.bmpLabel
:
421 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
423 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
425 if self
.bmpDisabled
and not self
.IsEnabled():
426 bmp
= self
.bmpDisabled
427 if self
.bmpFocus
and self
.hasFocus
:
429 if self
.bmpSelected
and not self
.up
:
430 bmp
= self
.bmpSelected
431 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
433 dw
= dy
= self
.labelDelta
434 hasMask
= bmp
.GetMask() != None
435 dc
.DrawBitmap(bmp
, (width
-bw
)/2+dw
, (height
-bh
)/2+dy
, hasMask
)
438 #----------------------------------------------------------------------
441 class GenBitmapTextButton(GenBitmapButton
):
442 """A generic bitmapped button with text label"""
443 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
, label
='',
444 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
445 style
= 0, validator
= wx
.DefaultValidator
,
447 GenBitmapButton
.__init
__(self
, parent
, id, bitmap
, pos
, size
, style
, validator
, name
)
451 def _GetLabelSize(self
):
452 """ used internally """
453 w
, h
= self
.GetTextExtent(self
.GetLabel())
454 if not self
.bmpLabel
:
455 return w
, h
, True # if there isn't a bitmap use the size of the text
457 w_bmp
= self
.bmpLabel
.GetWidth()+2
458 h_bmp
= self
.bmpLabel
.GetHeight()+2
464 return width
, height
, True
467 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
469 if bmp
!= None: # if the bitmap is used
470 if self
.bmpDisabled
and not self
.IsEnabled():
471 bmp
= self
.bmpDisabled
472 if self
.bmpFocus
and self
.hasFocus
:
474 if self
.bmpSelected
and not self
.up
:
475 bmp
= self
.bmpSelected
476 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
478 dw
= dy
= self
.labelDelta
479 hasMask
= bmp
.GetMask() != None
481 bw
= bh
= 0 # no bitmap -> size is zero
483 dc
.SetFont(self
.GetFont())
485 dc
.SetTextForeground(self
.GetForegroundColour())
487 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
489 label
= self
.GetLabel()
490 tw
, th
= dc
.GetTextExtent(label
) # size of text
492 dw
= dy
= self
.labelDelta
494 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
496 dc
.DrawBitmap(bmp
, pos_x
, (height
-bh
)/2+dy
, hasMask
) # draw bitmap if available
497 pos_x
= pos_x
+ 2 # extra spacing from bitmap
499 dc
.DrawText(label
, pos_x
+ dw
+bw
, (height
-th
)/2+dy
) # draw the text
502 #----------------------------------------------------------------------
506 def SetToggle(self
, flag
):
515 def OnLeftDown(self
, event
):
516 if not self
.IsEnabled():
518 self
.saveUp
= self
.up
519 self
.up
= not self
.up
524 def OnLeftUp(self
, event
):
525 if not self
.IsEnabled() or not self
.HasCapture():
527 if self
.HasCapture():
530 if self
.up
!= self
.saveUp
:
533 def OnKeyDown(self
, event
):
536 def OnMotion(self
, event
):
537 if not self
.IsEnabled():
539 if event
.LeftIsDown() and self
.HasCapture():
540 x
,y
= event
.GetPositionTuple()
541 w
,h
= self
.GetClientSizeTuple()
542 if x
<w
and x
>=0 and y
<h
and y
>=0:
543 self
.up
= not self
.saveUp
546 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
547 self
.up
= self
.saveUp
552 def OnKeyUp(self
, event
):
553 if self
.hasFocus
and event
.KeyCode() == ord(" "):
554 self
.up
= not self
.up
562 class GenToggleButton(__ToggleMixin
, GenButton
):
563 """A generic toggle button"""
566 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
567 """A generic toggle bitmap button"""
570 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
571 """A generic toggle bitmap button with text label"""
574 #----------------------------------------------------------------------