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 
  80         font 
= parent
.GetFont() 
  82             font 
= wx
.SystemSettings
.GetSystemFont(wx
.SYS_DEFAULT_GUI_FONT
) 
  84         self
.SetBestSize(size
) 
  87         self
.Bind(wx
.EVT_LEFT_DOWN
,        self
.OnLeftDown
) 
  88         self
.Bind(wx
.EVT_LEFT_UP
,          self
.OnLeftUp
) 
  89         if wx
.Platform 
== '__WXMSW__': 
  90             self
.Bind(wx
.EVT_LEFT_DCLICK
,  self
.OnLeftDown
) 
  91         self
.Bind(wx
.EVT_MOTION
,           self
.OnMotion
) 
  92         self
.Bind(wx
.EVT_SET_FOCUS
,        self
.OnGainFocus
) 
  93         self
.Bind(wx
.EVT_KILL_FOCUS
,       self
.OnLoseFocus
) 
  94         self
.Bind(wx
.EVT_KEY_DOWN
,         self
.OnKeyDown
) 
  95         self
.Bind(wx
.EVT_KEY_UP
,           self
.OnKeyUp
) 
  96         self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
) 
  97         self
.Bind(wx
.EVT_PAINT
,            self
.OnPaint
) 
 100     def SetBestSize(self
, size
=None): 
 102         Given the current font and bezel width settings, calculate 
 106             size 
= wx
.Size(-1,-1) 
 107         if type(size
) == type(()): 
 108             size 
= wx
.Size(size
[0], size
[1]) 
 109         size 
= wx
.Size(size
.width
, size
.height
)  # make a copy 
 111         best 
= self
.GetBestSize() 
 113             size
.width 
= best
.width
 
 114         if size
.height 
== -1: 
 115             size
.height 
= best
.height
 
 120     def DoGetBestSize(self
): 
 121         """Overridden base class virtual.  Determines the best size of the 
 122         button based on the label and bezel size.""" 
 123         w
, h
, useMin 
= self
._GetLabelSize
() 
 124         defSize 
= wx
.Button
.GetDefaultSize() 
 126         if useMin 
and width 
< defSize
.width
: 
 127            width 
= defSize
.width
 
 129         if useMin 
and height 
< defSize
.height
: 
 130             height 
= defSize
.height
 
 131         width 
= width 
+ self
.bezelWidth 
- 1 
 132         height 
= height 
+ self
.bezelWidth 
- 1 
 133         return (width
, height
) 
 136     def AcceptsFocus(self
): 
 137         """Overridden base class virtual.""" 
 138         return self
.IsShown() and self
.IsEnabled() 
 141     def Enable(self
, enable
=True): 
 142         wx
.PyControl
.Enable(self
, enable
) 
 146     def SetBezelWidth(self
, width
): 
 147         """Set the width of the 3D effect""" 
 148         self
.bezelWidth 
= width
 
 150     def GetBezelWidth(self
): 
 151         """Return the width of the 3D effect""" 
 152         return self
.bezelWidth
 
 154     def SetUseFocusIndicator(self
, flag
): 
 155         """Specifiy if a focus indicator (dotted line) should be used""" 
 156         self
.useFocusInd 
= flag
 
 158     def GetUseFocusIndicator(self
): 
 159         """Return focus indicator flag""" 
 160         return self
.useFocusInd
 
 163     def InitColours(self
): 
 164         faceClr      
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNFACE
) 
 165         textClr      
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNTEXT
) 
 166         self
.faceDnClr 
= faceClr
 
 167         self
.SetBackgroundColour(faceClr
) 
 168         self
.SetForegroundColour(textClr
) 
 170         shadowClr    
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNSHADOW
) 
 171         highlightClr 
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_BTNHIGHLIGHT
) 
 172         self
.shadowPen    
= wx
.Pen(shadowClr
, 1, wx
.SOLID
) 
 173         self
.highlightPen 
= wx
.Pen(highlightClr
, 1, wx
.SOLID
) 
 174         if wx
.Platform 
== "__WXMAC__": 
 175             self
.focusIndPen 
= wx
.Pen(textClr
, 1, wx
.SOLID
) 
 177             self
.focusIndPen  
= wx
.Pen(textClr
, 1, wx
.USER_DASH
) 
 178             self
.focusIndPen
.SetDashes([1,1]) 
 179             self
.focusIndPen
.SetCap(wx
.CAP_BUTT
) 
 180         self
.focusClr 
= highlightClr
 
 183     def SetBackgroundColour(self
, colour
): 
 184         wx
.PyControl
.SetBackgroundColour(self
, colour
) 
 185         colour 
= self
.GetBackgroundColour() 
 187         # Calculate a new set of highlight and shadow colours based on 
 188         # the new background colour.  Works okay if the colour is dark... 
 189         r
, g
, b 
= colour
.Get() 
 190         fr
, fg
, fb 
= min(255,r
+32), min(255,g
+32), min(255,b
+32) 
 191         self
.faceDnClr 
= wx
.Colour(fr
, fg
, fb
) 
 192         sr
, sg
, sb 
= max(0,r
-32), max(0,g
-32), max(0,b
-32) 
 193         self
.shadowPen 
= wx
.Pen(wx
.Colour(sr
,sg
,sb
), 1, wx
.SOLID
) 
 194         hr
, hg
, hb 
= min(255,r
+64), min(255,g
+64), min(255,b
+64) 
 195         self
.highlightPen 
= wx
.Pen(wx
.Colour(hr
,hg
,hb
), 1, wx
.SOLID
) 
 196         self
.focusClr 
= wx
.Colour(hr
, hg
, hb
) 
 199     def _GetLabelSize(self
): 
 200         """ used internally """ 
 201         w
, h 
= self
.GetTextExtent(self
.GetLabel()) 
 206         evt 
= GenButtonEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
.GetId()) 
 207         evt
.SetIsDown(not self
.up
) 
 208         evt
.SetButtonObj(self
) 
 209         evt
.SetEventObject(self
) 
 210         self
.GetEventHandler().ProcessEvent(evt
) 
 213     def DrawBezel(self
, dc
, x1
, y1
, x2
, y2
): 
 214         # draw the upper left sides 
 216             dc
.SetPen(self
.highlightPen
) 
 218             dc
.SetPen(self
.shadowPen
) 
 219         for i 
in range(self
.bezelWidth
): 
 220             dc
.DrawLine((x1
+i
, y1
), (x1
+i
, y2
-i
)) 
 221             dc
.DrawLine((x1
, y1
+i
), (x2
-i
, y1
+i
)) 
 223         # draw the lower right sides 
 225             dc
.SetPen(self
.shadowPen
) 
 227             dc
.SetPen(self
.highlightPen
) 
 228         for i 
in range(self
.bezelWidth
): 
 229             dc
.DrawLine((x1
+i
, y2
-i
), (x2
+1, y2
-i
)) 
 230             dc
.DrawLine((x2
-i
, y1
+i
), (x2
-i
, y2
)) 
 233     def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0): 
 234         dc
.SetFont(self
.GetFont()) 
 236             dc
.SetTextForeground(self
.GetForegroundColour()) 
 238             dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
)) 
 239         label 
= self
.GetLabel() 
 240         tw
, th 
= dc
.GetTextExtent(label
) 
 242             dw 
= dy 
= self
.labelDelta
 
 243         dc
.DrawText(label
, ((width
-tw
)/2+dw
, (height
-th
)/2+dy
)) 
 246     def DrawFocusIndicator(self
, dc
, w
, h
): 
 249 ##             self.focusIndPen.SetColour(self.GetForegroundColour()) 
 251 ##             #self.focusIndPen.SetColour(self.GetBackgroundColour()) 
 252 ##             self.focusIndPen.SetColour(self.GetForegroundColour()) 
 253         self
.focusIndPen
.SetColour(self
.focusClr
) 
 254         dc
.SetLogicalFunction(wx
.INVERT
) 
 255         dc
.SetPen(self
.focusIndPen
) 
 256         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 257         dc
.DrawRectangle((bw
+2,bw
+2), (w
-bw
*2-4, h
-bw
*2-4)) 
 258         dc
.SetLogicalFunction(wx
.COPY
) 
 261     def OnPaint(self
, event
): 
 262         (width
, height
) = self
.GetClientSizeTuple() 
 266         dc 
= wx
.BufferedPaintDC(self
) 
 268             dc
.SetBackground(wx
.Brush(self
.GetBackgroundColour(), wx
.SOLID
)) 
 270             dc
.SetBackground(wx
.Brush(self
.faceDnClr
, wx
.SOLID
)) 
 272         self
.DrawBezel(dc
, x1
, y1
, x2
, y2
) 
 273         self
.DrawLabel(dc
, width
, height
) 
 274         if self
.hasFocus 
and self
.useFocusInd
: 
 275             self
.DrawFocusIndicator(dc
, width
, height
) 
 278     def OnEraseBackground(self
, event
): 
 282     def OnLeftDown(self
, event
): 
 283         if not self
.IsEnabled(): 
 292     def OnLeftUp(self
, event
): 
 293         if not self
.IsEnabled() or not self
.HasCapture(): 
 295         if self
.HasCapture(): 
 297             if not self
.up
:    # if the button was down when the mouse was released... 
 304     def OnMotion(self
, event
): 
 305         if not self
.IsEnabled() or not self
.HasCapture(): 
 307         if event
.LeftIsDown() and self
.HasCapture(): 
 308             x
,y 
= event
.GetPositionTuple() 
 309             w
,h 
= self
.GetClientSizeTuple() 
 310             if self
.up 
and x
<w 
and x
>=0 and y
<h 
and y
>=0: 
 314             if not self
.up 
and (x
<0 or y
<0 or x
>=w 
or y
>=h
): 
 321     def OnGainFocus(self
, event
): 
 323         dc 
= wx
.ClientDC(self
) 
 324         w
, h 
= self
.GetClientSizeTuple() 
 326             self
.DrawFocusIndicator(dc
, w
, h
) 
 329     def OnLoseFocus(self
, event
): 
 330         self
.hasFocus 
= False 
 331         dc 
= wx
.ClientDC(self
) 
 332         w
, h 
= self
.GetClientSizeTuple() 
 334             self
.DrawFocusIndicator(dc
, w
, h
) 
 337     def OnKeyDown(self
, event
): 
 338         if self
.hasFocus 
and event
.KeyCode() == ord(" "): 
 344     def OnKeyUp(self
, event
): 
 345         if self
.hasFocus 
and event
.KeyCode() == ord(" "): 
 352 #---------------------------------------------------------------------- 
 354 class GenBitmapButton(GenButton
): 
 355     def __init__(self
, parent
, ID
, bitmap
, 
 356                  pos 
= wx
.DefaultPosition
, size 
= wx
.DefaultSize
, 
 357                  style 
= 0, validator 
= wx
.DefaultValidator
, 
 359         self
.bmpDisabled 
= None 
 361         self
.bmpSelected 
= None 
 362         self
.SetBitmapLabel(bitmap
) 
 363         GenButton
.__init
__(self
, parent
, ID
, "", pos
, size
, style
, validator
, name
) 
 366     def GetBitmapLabel(self
): 
 368     def GetBitmapDisabled(self
): 
 369         return self
.bmpDisabled
 
 370     def GetBitmapFocus(self
): 
 372     def GetBitmapSelected(self
): 
 373         return self
.bmpSelected
 
 376     def SetBitmapDisabled(self
, bitmap
): 
 377         """Set bitmap to display when the button is disabled""" 
 378         self
.bmpDisabled 
= bitmap
 
 380     def SetBitmapFocus(self
, bitmap
): 
 381         """Set bitmap to display when the button has the focus""" 
 382         self
.bmpFocus 
= bitmap
 
 383         self
.SetUseFocusIndicator(False) 
 385     def SetBitmapSelected(self
, bitmap
): 
 386         """Set bitmap to display when the button is selected (pressed down)""" 
 387         self
.bmpSelected 
= bitmap
 
 389     def SetBitmapLabel(self
, bitmap
, createOthers
=True): 
 391         Set the bitmap to display normally. 
 392         This is the only one that is required. If 
 393         createOthers is True, then the other bitmaps 
 394         will be generated on the fly.  Currently, 
 395         only the disabled bitmap is generated. 
 397         self
.bmpLabel 
= bitmap
 
 398         if bitmap 
is not None and createOthers
: 
 399             image 
= wx
.ImageFromBitmap(bitmap
) 
 400             imageutils
.grayOut(image
) 
 401             self
.SetBitmapDisabled(wx
.BitmapFromImage(image
)) 
 404     def _GetLabelSize(self
): 
 405         """ used internally """ 
 406         if not self
.bmpLabel
: 
 408         return self
.bmpLabel
.GetWidth()+2, self
.bmpLabel
.GetHeight()+2, False 
 410     def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0): 
 412         if self
.bmpDisabled 
and not self
.IsEnabled(): 
 413             bmp 
= self
.bmpDisabled
 
 414         if self
.bmpFocus 
and self
.hasFocus
: 
 416         if self
.bmpSelected 
and not self
.up
: 
 417             bmp 
= self
.bmpSelected
 
 418         bw
,bh 
= bmp
.GetWidth(), bmp
.GetHeight() 
 420             dw 
= dy 
= self
.labelDelta
 
 421         hasMask 
= bmp
.GetMask() != None 
 422         dc
.DrawBitmap(bmp
, ((width
-bw
)/2+dw
, (height
-bh
)/2+dy
), hasMask
) 
 425 #---------------------------------------------------------------------- 
 428 class GenBitmapTextButton(GenBitmapButton
):     # generic bitmapped button with Text Label 
 429     def __init__(self
, parent
, ID
, bitmap
, label
, 
 430                  pos 
= wx
.DefaultPosition
, size 
= wx
.DefaultSize
, 
 431                  style 
= 0, validator 
= wx
.DefaultValidator
, 
 433         GenBitmapButton
.__init
__(self
, parent
, ID
, bitmap
, pos
, size
, style
, validator
, name
) 
 437     def _GetLabelSize(self
): 
 438         """ used internally """ 
 439         w
, h 
= self
.GetTextExtent(self
.GetLabel()) 
 440         if not self
.bmpLabel
: 
 441             return w
, h
, True       # if there isn't a bitmap use the size of the text 
 443         w_bmp 
= self
.bmpLabel
.GetWidth()+2 
 444         h_bmp 
= self
.bmpLabel
.GetHeight()+2 
 450         return width
, height
, True 
 453     def DrawLabel(self
, dc
, width
, height
, dw
=0, dy
=0): 
 455         if bmp 
!= None:     # if the bitmap is used 
 456             if self
.bmpDisabled 
and not self
.IsEnabled(): 
 457                 bmp 
= self
.bmpDisabled
 
 458             if self
.bmpFocus 
and self
.hasFocus
: 
 460             if self
.bmpSelected 
and not self
.up
: 
 461                 bmp 
= self
.bmpSelected
 
 462             bw
,bh 
= bmp
.GetWidth(), bmp
.GetHeight() 
 464                 dw 
= dy 
= self
.labelDelta
 
 465             hasMask 
= bmp
.GetMask() != None 
 467             bw 
= bh 
= 0     # no bitmap -> size is zero 
 469         dc
.SetFont(self
.GetFont()) 
 471             dc
.SetTextForeground(self
.GetForegroundColour()) 
 473             dc
.SetTextForeground(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_GRAYTEXT
)) 
 475         label 
= self
.GetLabel() 
 476         tw
, th 
= dc
.GetTextExtent(label
)        # size of text 
 478             dw 
= dy 
= self
.labelDelta
 
 480         pos_x 
= (width
-bw
-tw
)/2+dw      
# adjust for bitmap and text to centre 
 482             dc
.DrawBitmap(bmp
, (pos_x
, (height
-bh
)/2+dy
), hasMask
) # draw bitmap if available 
 483             pos_x 
= pos_x 
+ 2   # extra spacing from bitmap 
 485         dc
.DrawText(label
, (pos_x 
+ dw
+bw
, (height
-th
)/2+dy
))      # draw the text 
 488 #---------------------------------------------------------------------- 
 492     def SetToggle(self
, flag
): 
 501     def OnLeftDown(self
, event
): 
 502         if not self
.IsEnabled(): 
 504         self
.saveUp 
= self
.up
 
 505         self
.up 
= not self
.up
 
 510     def OnLeftUp(self
, event
): 
 511         if not self
.IsEnabled() or not self
.HasCapture(): 
 513         if self
.HasCapture(): 
 514             if self
.up 
!= self
.saveUp
: 
 519     def OnKeyDown(self
, event
): 
 522     def OnMotion(self
, event
): 
 523         if not self
.IsEnabled(): 
 525         if event
.LeftIsDown() and self
.HasCapture(): 
 526             x
,y 
= event
.GetPositionTuple() 
 527             w
,h 
= self
.GetClientSizeTuple() 
 528             if x
<w 
and x
>=0 and y
<h 
and y
>=0: 
 529                 self
.up 
= not self
.saveUp
 
 532             if (x
<0 or y
<0 or x
>=w 
or y
>=h
): 
 533                 self
.up 
= self
.saveUp
 
 538     def OnKeyUp(self
, event
): 
 539         if self
.hasFocus 
and event
.KeyCode() == ord(" "): 
 540             self
.up 
= not self
.up
 
 548 class GenToggleButton(__ToggleMixin
, GenButton
): 
 551 class GenBitmapToggleButton(__ToggleMixin
, GenBitmapButton
): 
 554 class GenBitmapTextToggleButton(__ToggleMixin
, GenBitmapTextButton
): 
 557 #----------------------------------------------------------------------