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
,
72 wx
.PyControl
.__init
__(self
, parent
, ID
, pos
, size
, cstyle
, validator
, name
)
76 if style
& wx
.NO_BORDER
:
78 self
.useFocusInd
= False
81 self
.useFocusInd
= True
84 self
.InheritAttributes()
85 self
.SetBestFittingSize(size
)
88 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
89 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnLeftUp
)
90 if wx
.Platform
== '__WXMSW__':
91 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnLeftDown
)
92 self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
93 self
.Bind(wx
.EVT_SET_FOCUS
, self
.OnGainFocus
)
94 self
.Bind(wx
.EVT_KILL_FOCUS
, self
.OnLoseFocus
)
95 self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
)
96 self
.Bind(wx
.EVT_KEY_UP
, self
.OnKeyUp
)
97 self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
)
98 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
101 def SetBestSize(self
, size
=None):
103 Given the current font and bezel width settings, calculate
107 size
= wx
.DefaultSize
108 wx
.PyControl
.SetBestFittingSize(self
, size
)
111 def DoGetBestSize(self
):
113 Overridden base class virtual. Determines the best size of the
114 button based on the label and bezel size.
116 w
, h
, useMin
= self
._GetLabelSize
()
117 defSize
= wx
.Button
.GetDefaultSize()
119 if useMin
and width
< defSize
.width
:
120 width
= defSize
.width
122 if useMin
and height
< defSize
.height
:
123 height
= defSize
.height
124 width
= width
+ self
.bezelWidth
- 1
125 height
= height
+ self
.bezelWidth
- 1
126 return (width
, height
)
129 def AcceptsFocus(self
):
130 """Overridden base class virtual."""
131 return self
.IsShown() and self
.IsEnabled()
134 def GetDefaultAttributes(self
):
136 Overridden base class virtual. By default we should use
137 the same font/colour attributes as the native Button.
139 return wx
.Button
.GetClassDefaultAttributes()
142 def ShouldInheritColours(self
):
144 Overridden base class virtual. Buttons usually don't inherit
145 the parent's colours.
150 def Enable(self
, enable
=True):
151 wx
.PyControl
.Enable(self
, enable
)
155 def SetBezelWidth(self
, width
):
156 """Set the width of the 3D effect"""
157 self
.bezelWidth
= width
159 def GetBezelWidth(self
):
160 """Return the width of the 3D effect"""
161 return self
.bezelWidth
163 def SetUseFocusIndicator(self
, flag
):
164 """Specifiy if a focus indicator (dotted line) should be used"""
165 self
.useFocusInd
= flag
167 def GetUseFocusIndicator(self
):
168 """Return focus indicator flag"""
169 return self
.useFocusInd
172 def InitColours(self
):
174 Calculate a new set of highlight and shadow colours based on
175 the background colour. Works okay if the colour is dark...
177 faceClr
= self
.GetBackgroundColour()
178 r
, g
, b
= faceClr
.Get()
179 fr
, fg
, fb
= min(255,r
+32), min(255,g
+32), min(255,b
+32)
180 self
.faceDnClr
= wx
.Colour(fr
, fg
, fb
)
181 sr
, sg
, sb
= max(0,r
-32), max(0,g
-32), max(0,b
-32)
182 self
.shadowPen
= wx
.Pen(wx
.Colour(sr
,sg
,sb
), 1, wx
.SOLID
)
183 hr
, hg
, hb
= min(255,r
+64), min(255,g
+64), min(255,b
+64)
184 self
.highlightPen
= wx
.Pen(wx
.Colour(hr
,hg
,hb
), 1, wx
.SOLID
)
185 self
.focusClr
= wx
.Colour(hr
, hg
, hb
)
187 textClr
= self
.GetForegroundColour()
188 if wx
.Platform
== "__WXMAC__":
189 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.SOLID
)
191 self
.focusIndPen
= wx
.Pen(textClr
, 1, wx
.USER_DASH
)
192 self
.focusIndPen
.SetDashes([1,1])
193 self
.focusIndPen
.SetCap(wx
.CAP_BUTT
)
196 def SetBackgroundColour(self
, colour
):
197 wx
.PyControl
.SetBackgroundColour(self
, colour
)
201 def SetForegroundColour(self
, colour
):
202 wx
.PyControl
.SetForegroundColour(self
, colour
)
206 def _GetLabelSize(self
):
207 """ used internally """
208 w
, h
= self
.GetTextExtent(self
.GetLabel())
213 evt
= GenButtonEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
.GetId())
214 evt
.SetIsDown(not self
.up
)
215 evt
.SetButtonObj(self
)
216 evt
.SetEventObject(self
)
217 self
.GetEventHandler().ProcessEvent(evt
)
220 def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
):
221 # draw the upper left sides
223 dc
.SetPen(self
.highlightPen
)
225 dc
.SetPen(self
.shadowPen
)
226 for i
in range(self
.bezelWidth
):
227 dc
.DrawLine(x1
+i
, y1
, x1
+i
, y2
-i
)
228 dc
.DrawLine(x1
, y1
+i
, x2
-i
, y1
+i
)
230 # draw the lower right sides
232 dc
.SetPen(self
.shadowPen
)
234 dc
.SetPen(self
.highlightPen
)
235 for i
in range(self
.bezelWidth
):
236 dc
.DrawLine(x1
+i
, y2
-i
, x2
+1, y2
-i
)
237 dc
.DrawLine(x2
-i
, y1
+i
, x2
-i
, y2
)
240 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
241 dc
.SetFont(self
.GetFont())
243 dc
.SetTextForeground(self
.GetForegroundColour())
245 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
246 label
= self
.GetLabel()
247 tw
, th
= dc
.GetTextExtent(label
)
249 dw
= dy
= self
.labelDelta
250 dc
.DrawText(label
, (width
-tw
)/2+dw
, (height
-th
)/2+dy
)
253 def DrawFocusIndicator(self
, dc
, w
, h
):
255 self
.focusIndPen
.SetColour(self
.focusClr
)
256 dc
.SetLogicalFunction(wx
.INVERT
)
257 dc
.SetPen(self
.focusIndPen
)
258 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
259 dc
.DrawRectangle(bw
+2,bw
+2, w
-bw
*2-4, h
-bw
*2-4)
260 dc
.SetLogicalFunction(wx
.COPY
)
263 def OnPaint(self
, event
):
264 (width
, height
) = self
.GetClientSizeTuple()
268 dc
= wx
.BufferedPaintDC(self
)
270 dc
.SetBackground(wx
.Brush(self
.GetBackgroundColour(), wx
.SOLID
))
272 dc
.SetBackground(wx
.Brush(self
.faceDnClr
, wx
.SOLID
))
274 self
.DrawBezel(dc
, x1
, y1
, x2
, y2
)
275 self
.DrawLabel(dc
, width
, height
)
276 if self
.hasFocus
and self
.useFocusInd
:
277 self
.DrawFocusIndicator(dc
, width
, height
)
280 def OnEraseBackground(self
, event
):
284 def OnLeftDown(self
, event
):
285 if not self
.IsEnabled():
294 def OnLeftUp(self
, event
):
295 if not self
.IsEnabled() or not self
.HasCapture():
297 if self
.HasCapture():
299 if not self
.up
: # if the button was down when the mouse was released...
306 def OnMotion(self
, event
):
307 if not self
.IsEnabled() or not self
.HasCapture():
309 if event
.LeftIsDown() and self
.HasCapture():
310 x
,y
= event
.GetPositionTuple()
311 w
,h
= self
.GetClientSizeTuple()
312 if self
.up
and x
<w
and x
>=0 and y
<h
and y
>=0:
316 if not self
.up
and (x
<0 or y
<0 or x
>=w
or y
>=h
):
323 def OnGainFocus(self
, event
):
325 dc
= wx
.ClientDC(self
)
326 w
, h
= self
.GetClientSizeTuple()
328 self
.DrawFocusIndicator(dc
, w
, h
)
331 def OnLoseFocus(self
, event
):
332 self
.hasFocus
= False
333 dc
= wx
.ClientDC(self
)
334 w
, h
= self
.GetClientSizeTuple()
336 self
.DrawFocusIndicator(dc
, w
, h
)
339 def OnKeyDown(self
, event
):
340 if self
.hasFocus
and event
.KeyCode() == ord(" "):
346 def OnKeyUp(self
, event
):
347 if self
.hasFocus
and event
.KeyCode() == ord(" "):
354 #----------------------------------------------------------------------
356 class GenBitmapButton(GenButton
):
357 def __init__(self
, parent
, ID
, bitmap
,
358 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
359 style
= 0, validator
= wx
.DefaultValidator
,
361 self
.bmpDisabled
= None
363 self
.bmpSelected
= None
364 self
.SetBitmapLabel(bitmap
)
365 GenButton
.__init
__(self
, parent
, ID
, "", pos
, size
, style
, validator
, name
)
368 def GetBitmapLabel(self
):
370 def GetBitmapDisabled(self
):
371 return self
.bmpDisabled
372 def GetBitmapFocus(self
):
374 def GetBitmapSelected(self
):
375 return self
.bmpSelected
378 def SetBitmapDisabled(self
, bitmap
):
379 """Set bitmap to display when the button is disabled"""
380 self
.bmpDisabled
= bitmap
382 def SetBitmapFocus(self
, bitmap
):
383 """Set bitmap to display when the button has the focus"""
384 self
.bmpFocus
= bitmap
385 self
.SetUseFocusIndicator(False)
387 def SetBitmapSelected(self
, bitmap
):
388 """Set bitmap to display when the button is selected (pressed down)"""
389 self
.bmpSelected
= bitmap
391 def SetBitmapLabel(self
, bitmap
, createOthers
=True):
393 Set the bitmap to display normally.
394 This is the only one that is required. If
395 createOthers is True, then the other bitmaps
396 will be generated on the fly. Currently,
397 only the disabled bitmap is generated.
399 self
.bmpLabel
= bitmap
400 if bitmap
is not None and createOthers
:
401 image
= wx
.ImageFromBitmap(bitmap
)
402 imageutils
.grayOut(image
)
403 self
.SetBitmapDisabled(wx
.BitmapFromImage(image
))
406 def _GetLabelSize(self
):
407 """ used internally """
408 if not self
.bmpLabel
:
410 return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False
412 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
414 if self
.bmpDisabled
and not self
.IsEnabled():
415 bmp
= self
.bmpDisabled
416 if self
.bmpFocus
and self
.hasFocus
:
418 if self
.bmpSelected
and not self
.up
:
419 bmp
= self
.bmpSelected
420 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
422 dw
= dy
= self
.labelDelta
423 hasMask
= bmp
.GetMask() != None
424 dc
.DrawBitmap(bmp
, (width
-bw
)/2+dw
, (height
-bh
)/2+dy
, hasMask
)
427 #----------------------------------------------------------------------
430 class GenBitmapTextButton(GenBitmapButton
): # generic bitmapped button with Text Label
431 def __init__(self
, parent
, ID
, bitmap
, label
,
432 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
433 style
= 0, validator
= wx
.DefaultValidator
,
435 GenBitmapButton
.__init
__(self
, parent
, ID
, bitmap
, pos
, size
, style
, validator
, name
)
439 def _GetLabelSize(self
):
440 """ used internally """
441 w
, h
= self
.GetTextExtent(self
.GetLabel())
442 if not self
.bmpLabel
:
443 return w
, h
, True # if there isn't a bitmap use the size of the text
445 w_bmp
= self
.bmpLabel
.GetWidth()+2
446 h_bmp
= self
.bmpLabel
.GetHeight()+2
452 return width
, height
, True
455 def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0):
457 if bmp
!= None: # if the bitmap is used
458 if self
.bmpDisabled
and not self
.IsEnabled():
459 bmp
= self
.bmpDisabled
460 if self
.bmpFocus
and self
.hasFocus
:
462 if self
.bmpSelected
and not self
.up
:
463 bmp
= self
.bmpSelected
464 bw
,bh
= bmp
.GetWidth(), bmp
.GetHeight()
466 dw
= dy
= self
.labelDelta
467 hasMask
= bmp
.GetMask() != None
469 bw
= bh
= 0 # no bitmap -> size is zero
471 dc
.SetFont(self
.GetFont())
473 dc
.SetTextForeground(self
.GetForegroundColour())
475 dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
))
477 label
= self
.GetLabel()
478 tw
, th
= dc
.GetTextExtent(label
) # size of text
480 dw
= dy
= self
.labelDelta
482 pos_x
= (width
-bw
-tw
)/2+dw
# adjust for bitmap and text to centre
484 dc
.DrawBitmap(bmp
, pos_x
, (height
-bh
)/2+dy
, hasMask
) # draw bitmap if available
485 pos_x
= pos_x
+ 2 # extra spacing from bitmap
487 dc
.DrawText(label
, pos_x
+ dw
+bw
, (height
-th
)/2+dy
) # draw the text
490 #----------------------------------------------------------------------
494 def SetToggle(self
, flag
):
503 def OnLeftDown(self
, event
):
504 if not self
.IsEnabled():
506 self
.saveUp
= self
.up
507 self
.up
= not self
.up
512 def OnLeftUp(self
, event
):
513 if not self
.IsEnabled() or not self
.HasCapture():
515 if self
.HasCapture():
516 if self
.up
!= self
.saveUp
:
521 def OnKeyDown(self
, event
):
524 def OnMotion(self
, event
):
525 if not self
.IsEnabled():
527 if event
.LeftIsDown() and self
.HasCapture():
528 x
,y
= event
.GetPositionTuple()
529 w
,h
= self
.GetClientSizeTuple()
530 if x
<w
and x
>=0 and y
<h
and y
>=0:
531 self
.up
= not self
.saveUp
534 if (x
<0 or y
<0 or x
>=w
or y
>=h
):
535 self
.up
= self
.saveUp
540 def OnKeyUp(self
, event
):
541 if self
.hasFocus
and event
.KeyCode() == ord(" "):
542 self
.up
= not self
.up
550 class GenToggleButton(__ToggleMixin
, GenButton
):
553 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
):
556 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
):
559 #----------------------------------------------------------------------