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.
23 The GenButton is the base. It acts like a normal button but you
24 are able to better control how it looks, bevel width, colours, etc.
26 GenBitmapButton is a button with one or more bitmaps that show
27 the various states the button can be in.
29 GenToggleButton stays depressed when clicked, until clicked again.
31 GenBitmapToggleButton the same but with bitmaps.
39 #----------------------------------------------------------------------
41 class GenButtonEvent(wx
.PyCommandEvent
):
42 def __init__(self
, eventType
, ID
):
43 wx
.PyCommandEvent
.__init
__(self
, eventType
, ID
)
47 def SetIsDown(self
, isDown
):
53 def SetButtonObj(self
, btn
):
56 def GetButtonObj(self
):
60 #----------------------------------------------------------------------
62 class GenButton(wx
.PyControl
):
65 def __init__(self
, parent
, ID
, label
,
66 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
67 style
= 0, validator
= wx
.DefaultValidator
,
71 wx
.PyControl
.__init
__(self
, parent
, ID
, pos
, size
, style
, validator
, name
)
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_ERASE_BACKGROUND
, self
.OnEraseBackground
)
93 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
96 def SetBestSize(self
, size
=None):
98 Given the current font and bezel width settings, calculate
102 size
= wx
.DefaultSize
103 wx
.PyControl
.SetBestFittingSize(self
, size
)
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
)
201 def _GetLabelSize(self
):
202 """ used internally """
203 w
, h
= self
.GetTextExtent(self
.GetLabel())
208 evt
= GenButtonEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
.GetId())
209 evt
.SetIsDown(not self
.up
)
210 evt
.SetButtonObj(self
)
211 evt
.SetEventObject(self
)
212 self
.GetEventHandler().ProcessEvent(evt
)
215 def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
):
216 # draw the upper left sides
218 dc
.SetPen(self
.highlightPen
)
220 dc
.SetPen(self
.shadowPen
)
221 for i
in range(self
.bezelWidth
):
222 dc
.DrawLine(x1
+i
, y1
, x1
+i
, y2
-i
)
223 dc
.DrawLine(x1
, y1
+i
, x2
-i
, y1
+i
)
225 # draw the lower right sides
227 dc
.SetPen(self
.shadowPen
)
229 dc
.SetPen(self
.highlightPen
)
230 for i
in range(self
.bezelWidth
):
231 dc
.DrawLine(x1
+i
, y2
-i
, x2
+1, y2
-i
)
232 dc
.DrawLine(x2
-i
, y1
+i
, x2
-i
, y2
)
235 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
236 dc
.SetFont(self
.GetFont())
238 dc
.SetTextForeground(self
.GetForegroundColour())
240 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
241 label
= self
.GetLabel()
242 tw
, th
= dc
.GetTextExtent(label
)
244 dw
= dy
= self
.labelDelta
245 dc
.DrawText(label
, (width
-tw
)/2+dw
, (height
-th
)/2+dy
)
248 def DrawFocusIndicator(self
, dc
, w
, h
):
250 self
.focusIndPen
.SetColour(self
.focusClr
)
251 dc
.SetLogicalFunction(wx
.INVERT
)
252 dc
.SetPen(self
.focusIndPen
)
253 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
254 dc
.DrawRectangle(bw
+2,bw
+2, w
-bw
*2-4, h
-bw
*2-4)
255 dc
.SetLogicalFunction(wx
.COPY
)
258 def OnPaint(self
, event
):
259 (width
, height
) = self
.GetClientSizeTuple()
263 dc
= wx
.BufferedPaintDC(self
)
265 dc
.SetBackground(wx
.Brush(self
.GetBackgroundColour(), wx
.SOLID
))
267 dc
.SetBackground(wx
.Brush(self
.faceDnClr
, wx
.SOLID
))
269 self
.DrawBezel(dc
, x1
, y1
, x2
, y2
)
270 self
.DrawLabel(dc
, width
, height
)
271 if self
.hasFocus
and self
.useFocusInd
:
272 self
.DrawFocusIndicator(dc
, width
, height
)
275 def OnEraseBackground(self
, event
):
279 def OnLeftDown(self
, event
):
280 if not self
.IsEnabled():
289 def OnLeftUp(self
, event
):
290 if not self
.IsEnabled() or not self
.HasCapture():
292 if self
.HasCapture():
294 if not self
.up
: # if the button was down when the mouse was released...
301 def OnMotion(self
, event
):
302 if not self
.IsEnabled() or not self
.HasCapture():
304 if event
.LeftIsDown() and self
.HasCapture():
305 x
,y
= event
.GetPositionTuple()
306 w
,h
= self
.GetClientSizeTuple()
307 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
311 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
318 def OnGainFocus(self
, event
):
320 dc
= wx
.ClientDC(self
)
321 w
, h
= self
.GetClientSizeTuple()
323 self
.DrawFocusIndicator(dc
, w
, h
)
326 def OnLoseFocus(self
, event
):
327 self
.hasFocus
= False
328 dc
= wx
.ClientDC(self
)
329 w
, h
= self
.GetClientSizeTuple()
331 self
.DrawFocusIndicator(dc
, w
, h
)
334 def OnKeyDown(self
, event
):
335 if self
.hasFocus
and event
.KeyCode() == ord(" "):
341 def OnKeyUp(self
, event
):
342 if self
.hasFocus
and event
.KeyCode() == ord(" "):
349 #----------------------------------------------------------------------
351 class GenBitmapButton(GenButton
):
352 def __init__(self
, parent
, ID
, bitmap
,
353 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
354 style
= 0, validator
= wx
.DefaultValidator
,
356 self
.bmpDisabled
= None
358 self
.bmpSelected
= None
359 self
.SetBitmapLabel(bitmap
)
360 GenButton
.__init
__(self
, parent
, ID
, "", pos
, size
, style
, validator
, name
)
363 def GetBitmapLabel(self
):
365 def GetBitmapDisabled(self
):
366 return self
.bmpDisabled
367 def GetBitmapFocus(self
):
369 def GetBitmapSelected(self
):
370 return self
.bmpSelected
373 def SetBitmapDisabled(self
, bitmap
):
374 """Set bitmap to display when the button is disabled"""
375 self
.bmpDisabled
= bitmap
377 def SetBitmapFocus(self
, bitmap
):
378 """Set bitmap to display when the button has the focus"""
379 self
.bmpFocus
= bitmap
380 self
.SetUseFocusIndicator(False)
382 def SetBitmapSelected(self
, bitmap
):
383 """Set bitmap to display when the button is selected (pressed down)"""
384 self
.bmpSelected
= bitmap
386 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
388 Set the bitmap to display normally.
389 This is the only one that is required. If
390 createOthers is True, then the other bitmaps
391 will be generated on the fly. Currently,
392 only the disabled bitmap is generated.
394 self
.bmpLabel
= bitmap
395 if bitmap
is not None and createOthers
:
396 image
= wx
.ImageFromBitmap(bitmap
)
397 imageutils
.grayOut(image
)
398 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
401 def _GetLabelSize(self
):
402 """ used internally """
403 if not self
.bmpLabel
:
405 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
407 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
409 if self
.bmpDisabled
and not self
.IsEnabled():
410 bmp
= self
.bmpDisabled
411 if self
.bmpFocus
and self
.hasFocus
:
413 if self
.bmpSelected
and not self
.up
:
414 bmp
= self
.bmpSelected
415 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
417 dw
= dy
= self
.labelDelta
418 hasMask
= bmp
.GetMask() != None
419 dc
.DrawBitmap(bmp
, (width
-bw
)/2+dw
, (height
-bh
)/2+dy
, hasMask
)
422 #----------------------------------------------------------------------
425 class GenBitmapTextButton(GenBitmapButton
): # generic bitmapped button with Text Label
426 def __init__(self
, parent
, ID
, bitmap
, label
,
427 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
428 style
= 0, validator
= wx
.DefaultValidator
,
430 GenBitmapButton
.__init
__(self
, parent
, ID
, bitmap
, pos
, size
, style
, validator
, name
)
434 def _GetLabelSize(self
):
435 """ used internally """
436 w
, h
= self
.GetTextExtent(self
.GetLabel())
437 if not self
.bmpLabel
:
438 return w
, h
, True # if there isn't a bitmap use the size of the text
440 w_bmp
= self
.bmpLabel
.GetWidth()+2
441 h_bmp
= self
.bmpLabel
.GetHeight()+2
447 return width
, height
, True
450 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
452 if bmp
!= None: # if the bitmap is used
453 if self
.bmpDisabled
and not self
.IsEnabled():
454 bmp
= self
.bmpDisabled
455 if self
.bmpFocus
and self
.hasFocus
:
457 if self
.bmpSelected
and not self
.up
:
458 bmp
= self
.bmpSelected
459 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
461 dw
= dy
= self
.labelDelta
462 hasMask
= bmp
.GetMask() != None
464 bw
= bh
= 0 # no bitmap -> size is zero
466 dc
.SetFont(self
.GetFont())
468 dc
.SetTextForeground(self
.GetForegroundColour())
470 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
472 label
= self
.GetLabel()
473 tw
, th
= dc
.GetTextExtent(label
) # size of text
475 dw
= dy
= self
.labelDelta
477 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
479 dc
.DrawBitmap(bmp
, pos_x
, (height
-bh
)/2+dy
, hasMask
) # draw bitmap if available
480 pos_x
= pos_x
+ 2 # extra spacing from bitmap
482 dc
.DrawText(label
, pos_x
+ dw
+bw
, (height
-th
)/2+dy
) # draw the text
485 #----------------------------------------------------------------------
489 def SetToggle(self
, flag
):
498 def OnLeftDown(self
, event
):
499 if not self
.IsEnabled():
501 self
.saveUp
= self
.up
502 self
.up
= not self
.up
507 def OnLeftUp(self
, event
):
508 if not self
.IsEnabled() or not self
.HasCapture():
510 if self
.HasCapture():
511 if self
.up
!= self
.saveUp
:
516 def OnKeyDown(self
, event
):
519 def OnMotion(self
, event
):
520 if not self
.IsEnabled():
522 if event
.LeftIsDown() and self
.HasCapture():
523 x
,y
= event
.GetPositionTuple()
524 w
,h
= self
.GetClientSizeTuple()
525 if x
<w
and x
>=0 and y
<h
and y
>=0:
526 self
.up
= not self
.saveUp
529 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
530 self
.up
= self
.saveUp
535 def OnKeyUp(self
, event
):
536 if self
.hasFocus
and event
.KeyCode() == ord(" "):
537 self
.up
= not self
.up
545 class GenToggleButton(__ToggleMixin
, GenButton
):
548 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
551 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
554 #----------------------------------------------------------------------