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
)
258 def OnPaint(self
, event
):
259 (width
, height
) = self
.GetClientSizeTuple()
264 dc
= wx
.PaintDC(self
)
265 brush
= self
.GetBackgroundBrush(dc
)
266 if brush
is not None:
267 dc
.SetBackground(brush
)
270 self
.DrawBezel(dc
, x1
, y1
, x2
, y2
)
271 self
.DrawLabel(dc
, width
, height
)
272 if self
.hasFocus
and self
.useFocusInd
:
273 self
.DrawFocusIndicator(dc
, width
, height
)
276 def GetBackgroundBrush(self
, dc
):
278 colBg
= self
.GetBackgroundColour()
279 brush
= wx
.Brush(colBg
, wx
.SOLID
)
280 if self
.style
& wx
.BORDER_NONE
:
281 myAttr
= self
.GetDefaultAttributes()
282 parAttr
= self
.GetParent().GetDefaultAttributes()
283 myDef
= colBg
== myAttr
.colBg
284 parDef
= self
.GetParent().GetBackgroundColour() == parAttr
.colBg
286 if wx
.Platform
== "__WXMAC__":
287 brush
.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
288 elif wx
.Platform
== "__WXMSW__":
289 if self
.DoEraseBackground(dc
):
291 elif myDef
and not parDef
:
292 colBg
= self
.GetParent().GetBackgroundColour()
293 brush
= wx
.Brush(colBg
, wx
.SOLID
)
295 # this line assumes that a pressed button should be hilighted with
296 # a solid colour even if the background is supposed to be transparent
297 brush
= wx
.Brush(self
.faceDnClr
, wx
.SOLID
)
301 def OnLeftDown(self
, event
):
302 if not self
.IsEnabled():
311 def OnLeftUp(self
, event
):
312 if not self
.IsEnabled() or not self
.HasCapture():
314 if self
.HasCapture():
316 if not self
.up
: # if the button was down when the mouse was released...
319 if self
: # in case the button was destroyed in the eventhandler
324 def OnMotion(self
, event
):
325 if not self
.IsEnabled() or not self
.HasCapture():
327 if event
.LeftIsDown() and self
.HasCapture():
328 x
,y
= event
.GetPositionTuple()
329 w
,h
= self
.GetClientSizeTuple()
330 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
334 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
341 def OnGainFocus(self
, event
):
347 def OnLoseFocus(self
, event
):
348 self
.hasFocus
= False
353 def OnKeyDown(self
, event
):
354 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
360 def OnKeyUp(self
, event
):
361 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
368 #----------------------------------------------------------------------
370 class GenBitmapButton(GenButton
):
371 """A generic bitmap button."""
373 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
,
374 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
375 style
= 0, validator
= wx
.DefaultValidator
,
377 self
.bmpDisabled
= None
379 self
.bmpSelected
= None
380 self
.SetBitmapLabel(bitmap
)
381 GenButton
.__init
__(self
, parent
, id, "", pos
, size
, style
, validator
, name
)
384 def GetBitmapLabel(self
):
386 def GetBitmapDisabled(self
):
387 return self
.bmpDisabled
388 def GetBitmapFocus(self
):
390 def GetBitmapSelected(self
):
391 return self
.bmpSelected
394 def SetBitmapDisabled(self
, bitmap
):
395 """Set bitmap to display when the button is disabled"""
396 self
.bmpDisabled
= bitmap
398 def SetBitmapFocus(self
, bitmap
):
399 """Set bitmap to display when the button has the focus"""
400 self
.bmpFocus
= bitmap
401 self
.SetUseFocusIndicator(False)
403 def SetBitmapSelected(self
, bitmap
):
404 """Set bitmap to display when the button is selected (pressed down)"""
405 self
.bmpSelected
= bitmap
407 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
409 Set the bitmap to display normally.
410 This is the only one that is required. If
411 createOthers is True, then the other bitmaps
412 will be generated on the fly. Currently,
413 only the disabled bitmap is generated.
415 self
.bmpLabel
= bitmap
416 if bitmap
is not None and createOthers
:
417 image
= wx
.ImageFromBitmap(bitmap
)
418 imageutils
.grayOut(image
)
419 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
422 def _GetLabelSize(self
):
423 """ used internally """
424 if not self
.bmpLabel
:
426 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
428 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
430 if self
.bmpDisabled
and not self
.IsEnabled():
431 bmp
= self
.bmpDisabled
432 if self
.bmpFocus
and self
.hasFocus
:
434 if self
.bmpSelected
and not self
.up
:
435 bmp
= self
.bmpSelected
436 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
438 dw
= dy
= self
.labelDelta
439 hasMask
= bmp
.GetMask() != None
440 dc
.DrawBitmap(bmp
, (width
-bw
)/2+dw
, (height
-bh
)/2+dy
, hasMask
)
443 #----------------------------------------------------------------------
446 class GenBitmapTextButton(GenBitmapButton
):
447 """A generic bitmapped button with text label"""
448 def __init__(self
, parent
, id=-1, bitmap
=wx
.NullBitmap
, label
='',
449 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
450 style
= 0, validator
= wx
.DefaultValidator
,
452 GenBitmapButton
.__init
__(self
, parent
, id, bitmap
, pos
, size
, style
, validator
, name
)
456 def _GetLabelSize(self
):
457 """ used internally """
458 w
, h
= self
.GetTextExtent(self
.GetLabel())
459 if not self
.bmpLabel
:
460 return w
, h
, True # if there isn't a bitmap use the size of the text
462 w_bmp
= self
.bmpLabel
.GetWidth()+2
463 h_bmp
= self
.bmpLabel
.GetHeight()+2
469 return width
, height
, True
472 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
474 if bmp
!= None: # if the bitmap is used
475 if self
.bmpDisabled
and not self
.IsEnabled():
476 bmp
= self
.bmpDisabled
477 if self
.bmpFocus
and self
.hasFocus
:
479 if self
.bmpSelected
and not self
.up
:
480 bmp
= self
.bmpSelected
481 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
483 dw
= dy
= self
.labelDelta
484 hasMask
= bmp
.GetMask() != None
486 bw
= bh
= 0 # no bitmap -> size is zero
488 dc
.SetFont(self
.GetFont())
490 dc
.SetTextForeground(self
.GetForegroundColour())
492 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
494 label
= self
.GetLabel()
495 tw
, th
= dc
.GetTextExtent(label
) # size of text
497 dw
= dy
= self
.labelDelta
499 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
501 dc
.DrawBitmap(bmp
, pos_x
, (height
-bh
)/2+dy
, hasMask
) # draw bitmap if available
502 pos_x
= pos_x
+ 2 # extra spacing from bitmap
504 dc
.DrawText(label
, pos_x
+ dw
+bw
, (height
-th
)/2+dy
) # draw the text
507 #----------------------------------------------------------------------
511 def SetToggle(self
, flag
):
520 def OnLeftDown(self
, event
):
521 if not self
.IsEnabled():
523 self
.saveUp
= self
.up
524 self
.up
= not self
.up
529 def OnLeftUp(self
, event
):
530 if not self
.IsEnabled() or not self
.HasCapture():
532 if self
.HasCapture():
535 if self
.up
!= self
.saveUp
:
538 def OnKeyDown(self
, event
):
541 def OnMotion(self
, event
):
542 if not self
.IsEnabled():
544 if event
.LeftIsDown() and self
.HasCapture():
545 x
,y
= event
.GetPositionTuple()
546 w
,h
= self
.GetClientSizeTuple()
547 if x
<w
and x
>=0 and y
<h
and y
>=0:
548 self
.up
= not self
.saveUp
551 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
552 self
.up
= self
.saveUp
557 def OnKeyUp(self
, event
):
558 if self
.hasFocus
and event
.GetKeyCode() == ord(" "):
559 self
.up
= not self
.up
567 class GenToggleButton(__ToggleMixin
, GenButton
):
568 """A generic toggle button"""
571 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
572 """A generic toggle bitmap button"""
575 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
576 """A generic toggle bitmap button with text label"""
579 #----------------------------------------------------------------------