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 #----------------------------------------------------------------------