#---------------------------------------------------------------------- # Name: wx.lib.buttons # Purpose: Various kinds of generic buttons, (not native controls but # self-drawn.) # # Author: Robin Dunn # # Created: 9-Dec-1999 # RCS-ID: $Id$ # Copyright: (c) 1999 by Total Control Software # Licence: wxWindows license #---------------------------------------------------------------------- # 11/30/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o Updated for wx namespace # o Tested with updated demo # """ This module implements various forms of generic buttons, meaning that they are not built on native controls but are self-drawn. The GenButton is the base. It acts like a normal button but you are able to better control how it looks, bevel width, colours, etc. GenBitmapButton is a button with one or more bitmaps that show the various states the button can be in. GenToggleButton stays depressed when clicked, until clicked again. GenBitmapToggleButton the same but with bitmaps. """ import wx import imageutils #---------------------------------------------------------------------- class GenButtonEvent(wx.PyCommandEvent): def __init__(self, eventType, ID): wx.PyCommandEvent.__init__(self, eventType, ID) self.isDown = False self.theButton = None def SetIsDown(self, isDown): self.isDown = isDown def GetIsDown(self): return self.isDown def SetButtonObj(self, btn): self.theButton = btn def GetButtonObj(self): return self.theButton #---------------------------------------------------------------------- class GenButton(wx.PyControl): labelDelta = 1 def __init__(self, parent, ID, label, pos = wx.DefaultPosition, size = wx.DefaultSize, style = 0, validator = wx.DefaultValidator, name = "genbutton"): cstyle = style if cstyle == 0: cstyle = wx.NO_BORDER wx.PyControl.__init__(self, parent, ID, pos, size, cstyle, validator, name) self.up = True self.hasFocus = False if style & wx.NO_BORDER: self.bezelWidth = 0 self.useFocusInd = False else: self.bezelWidth = 2 self.useFocusInd = True self.SetLabel(label) self.InheritAttributes() self.SetBestFittingSize(size) self.InitColours() self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) if wx.Platform == '__WXMSW__': self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) self.Bind(wx.EVT_MOTION, self.OnMotion) self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus) self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.Bind(wx.EVT_KEY_UP, self.OnKeyUp) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_PAINT, self.OnPaint) def SetBestSize(self, size=None): """ Given the current font and bezel width settings, calculate and set a good size. """ if size is None: size = wx.DefaultSize wx.PyControl.SetBestFittingSize(self, size) def DoGetBestSize(self): """ Overridden base class virtual. Determines the best size of the button based on the label and bezel size. """ w, h, useMin = self._GetLabelSize() defSize = wx.Button.GetDefaultSize() width = 12 + w if useMin and width < defSize.width: width = defSize.width height = 11 + h if useMin and height < defSize.height: height = defSize.height width = width + self.bezelWidth - 1 height = height + self.bezelWidth - 1 return (width, height) def AcceptsFocus(self): """Overridden base class virtual.""" return self.IsShown() and self.IsEnabled() def GetDefaultAttributes(self): """ Overridden base class virtual. By default we should use the same font/colour attributes as the native Button. """ return wx.Button.GetClassDefaultAttributes() def ShouldInheritColours(self): """ Overridden base class virtual. Buttons usually don't inherit the parent's colours. """ return False def Enable(self, enable=True): wx.PyControl.Enable(self, enable) self.Refresh() def SetBezelWidth(self, width): """Set the width of the 3D effect""" self.bezelWidth = width def GetBezelWidth(self): """Return the width of the 3D effect""" return self.bezelWidth def SetUseFocusIndicator(self, flag): """Specifiy if a focus indicator (dotted line) should be used""" self.useFocusInd = flag def GetUseFocusIndicator(self): """Return focus indicator flag""" return self.useFocusInd def InitColours(self): """ Calculate a new set of highlight and shadow colours based on the background colour. Works okay if the colour is dark... """ faceClr = self.GetBackgroundColour() r, g, b = faceClr.Get() fr, fg, fb = min(255,r+32), min(255,g+32), min(255,b+32) self.faceDnClr = wx.Colour(fr, fg, fb) sr, sg, sb = max(0,r-32), max(0,g-32), max(0,b-32) self.shadowPen = wx.Pen(wx.Colour(sr,sg,sb), 1, wx.SOLID) hr, hg, hb = min(255,r+64), min(255,g+64), min(255,b+64) self.highlightPen = wx.Pen(wx.Colour(hr,hg,hb), 1, wx.SOLID) self.focusClr = wx.Colour(hr, hg, hb) textClr = self.GetForegroundColour() if wx.Platform == "__WXMAC__": self.focusIndPen = wx.Pen(textClr, 1, wx.SOLID) else: self.focusIndPen = wx.Pen(textClr, 1, wx.USER_DASH) self.focusIndPen.SetDashes([1,1]) self.focusIndPen.SetCap(wx.CAP_BUTT) def SetBackgroundColour(self, colour): wx.PyControl.SetBackgroundColour(self, colour) self.InitColours() def SetForegroundColour(self, colour): wx.PyControl.SetForegroundColour(self, colour) self.InitColours() def _GetLabelSize(self): """ used internally """ w, h = self.GetTextExtent(self.GetLabel()) return w, h, True def Notify(self): evt = GenButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId()) evt.SetIsDown(not self.up) evt.SetButtonObj(self) evt.SetEventObject(self) self.GetEventHandler().ProcessEvent(evt) def DrawBezel(self, dc, x1, y1, x2, y2): # draw the upper left sides if self.up: dc.SetPen(self.highlightPen) else: dc.SetPen(self.shadowPen) for i in range(self.bezelWidth): dc.DrawLine(x1+i, y1, x1+i, y2-i) dc.DrawLine(x1, y1+i, x2-i, y1+i) # draw the lower right sides if self.up: dc.SetPen(self.shadowPen) else: dc.SetPen(self.highlightPen) for i in range(self.bezelWidth): dc.DrawLine(x1+i, y2-i, x2+1, y2-i) dc.DrawLine(x2-i, y1+i, x2-i, y2) def DrawLabel(self, dc, width, height, dw=0, dy=0): dc.SetFont(self.GetFont()) if self.IsEnabled(): dc.SetTextForeground(self.GetForegroundColour()) else: dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) label = self.GetLabel() tw, th = dc.GetTextExtent(label) if not self.up: dw = dy = self.labelDelta dc.DrawText(label, (width-tw)/2+dw, (height-th)/2+dy) def DrawFocusIndicator(self, dc, w, h): bw = self.bezelWidth self.focusIndPen.SetColour(self.focusClr) dc.SetLogicalFunction(wx.INVERT) dc.SetPen(self.focusIndPen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangle(bw+2,bw+2, w-bw*2-4, h-bw*2-4) dc.SetLogicalFunction(wx.COPY) def OnPaint(self, event): (width, height) = self.GetClientSizeTuple() x1 = y1 = 0 x2 = width-1 y2 = height-1 dc = wx.BufferedPaintDC(self) if self.up: dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.SOLID)) else: dc.SetBackground(wx.Brush(self.faceDnClr, wx.SOLID)) dc.Clear() self.DrawBezel(dc, x1, y1, x2, y2) self.DrawLabel(dc, width, height) if self.hasFocus and self.useFocusInd: self.DrawFocusIndicator(dc, width, height) def OnEraseBackground(self, event): pass def OnLeftDown(self, event): if not self.IsEnabled(): return self.up = False self.CaptureMouse() self.SetFocus() self.Refresh() event.Skip() def OnLeftUp(self, event): if not self.IsEnabled() or not self.HasCapture(): return if self.HasCapture(): self.ReleaseMouse() if not self.up: # if the button was down when the mouse was released... self.Notify() self.up = True self.Refresh() event.Skip() def OnMotion(self, event): if not self.IsEnabled() or not self.HasCapture(): return if event.LeftIsDown() and self.HasCapture(): x,y = event.GetPositionTuple() w,h = self.GetClientSizeTuple() if self.up and x=0 and y=0: self.up = False self.Refresh() return if not self.up and (x<0 or y<0 or x>=w or y>=h): self.up = True self.Refresh() return event.Skip() def OnGainFocus(self, event): self.hasFocus = True dc = wx.ClientDC(self) w, h = self.GetClientSizeTuple() if self.useFocusInd: self.DrawFocusIndicator(dc, w, h) def OnLoseFocus(self, event): self.hasFocus = False dc = wx.ClientDC(self) w, h = self.GetClientSizeTuple() if self.useFocusInd: self.DrawFocusIndicator(dc, w, h) def OnKeyDown(self, event): if self.hasFocus and event.KeyCode() == ord(" "): self.up = False self.Refresh() event.Skip() def OnKeyUp(self, event): if self.hasFocus and event.KeyCode() == ord(" "): self.up = True self.Notify() self.Refresh() event.Skip() #---------------------------------------------------------------------- class GenBitmapButton(GenButton): def __init__(self, parent, ID, bitmap, pos = wx.DefaultPosition, size = wx.DefaultSize, style = 0, validator = wx.DefaultValidator, name = "genbutton"): self.bmpDisabled = None self.bmpFocus = None self.bmpSelected = None self.SetBitmapLabel(bitmap) GenButton.__init__(self, parent, ID, "", pos, size, style, validator, name) def GetBitmapLabel(self): return self.bmpLabel def GetBitmapDisabled(self): return self.bmpDisabled def GetBitmapFocus(self): return self.bmpFocus def GetBitmapSelected(self): return self.bmpSelected def SetBitmapDisabled(self, bitmap): """Set bitmap to display when the button is disabled""" self.bmpDisabled = bitmap def SetBitmapFocus(self, bitmap): """Set bitmap to display when the button has the focus""" self.bmpFocus = bitmap self.SetUseFocusIndicator(False) def SetBitmapSelected(self, bitmap): """Set bitmap to display when the button is selected (pressed down)""" self.bmpSelected = bitmap def SetBitmapLabel(self, bitmap, createOthers=True): """ Set the bitmap to display normally. This is the only one that is required. If createOthers is True, then the other bitmaps will be generated on the fly. Currently, only the disabled bitmap is generated. """ self.bmpLabel = bitmap if bitmap is not None and createOthers: image = wx.ImageFromBitmap(bitmap) imageutils.grayOut(image) self.SetBitmapDisabled(wx.BitmapFromImage(image)) def _GetLabelSize(self): """ used internally """ if not self.bmpLabel: return -1, -1, False return self.bmpLabel.GetWidth()+2, self.bmpLabel.GetHeight()+2, False def DrawLabel(self, dc, width, height, dw=0, dy=0): bmp = self.bmpLabel if self.bmpDisabled and not self.IsEnabled(): bmp = self.bmpDisabled if self.bmpFocus and self.hasFocus: bmp = self.bmpFocus if self.bmpSelected and not self.up: bmp = self.bmpSelected bw,bh = bmp.GetWidth(), bmp.GetHeight() if not self.up: dw = dy = self.labelDelta hasMask = bmp.GetMask() != None dc.DrawBitmap(bmp, (width-bw)/2+dw, (height-bh)/2+dy, hasMask) #---------------------------------------------------------------------- class GenBitmapTextButton(GenBitmapButton): # generic bitmapped button with Text Label def __init__(self, parent, ID, bitmap, label, pos = wx.DefaultPosition, size = wx.DefaultSize, style = 0, validator = wx.DefaultValidator, name = "genbutton"): GenBitmapButton.__init__(self, parent, ID, bitmap, pos, size, style, validator, name) self.SetLabel(label) def _GetLabelSize(self): """ used internally """ w, h = self.GetTextExtent(self.GetLabel()) if not self.bmpLabel: return w, h, True # if there isn't a bitmap use the size of the text w_bmp = self.bmpLabel.GetWidth()+2 h_bmp = self.bmpLabel.GetHeight()+2 width = w + w_bmp if h_bmp > h: height = h_bmp else: height = h return width, height, True def DrawLabel(self, dc, width, height, dw=0, dy=0): bmp = self.bmpLabel if bmp != None: # if the bitmap is used if self.bmpDisabled and not self.IsEnabled(): bmp = self.bmpDisabled if self.bmpFocus and self.hasFocus: bmp = self.bmpFocus if self.bmpSelected and not self.up: bmp = self.bmpSelected bw,bh = bmp.GetWidth(), bmp.GetHeight() if not self.up: dw = dy = self.labelDelta hasMask = bmp.GetMask() != None else: bw = bh = 0 # no bitmap -> size is zero dc.SetFont(self.GetFont()) if self.IsEnabled(): dc.SetTextForeground(self.GetForegroundColour()) else: dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) label = self.GetLabel() tw, th = dc.GetTextExtent(label) # size of text if not self.up: dw = dy = self.labelDelta pos_x = (width-bw-tw)/2+dw # adjust for bitmap and text to centre if bmp !=None: dc.DrawBitmap(bmp, pos_x, (height-bh)/2+dy, hasMask) # draw bitmap if available pos_x = pos_x + 2 # extra spacing from bitmap dc.DrawText(label, pos_x + dw+bw, (height-th)/2+dy) # draw the text #---------------------------------------------------------------------- class __ToggleMixin: def SetToggle(self, flag): self.up = not flag self.Refresh() SetValue = SetToggle def GetToggle(self): return not self.up GetValue = GetToggle def OnLeftDown(self, event): if not self.IsEnabled(): return self.saveUp = self.up self.up = not self.up self.CaptureMouse() self.SetFocus() self.Refresh() def OnLeftUp(self, event): if not self.IsEnabled() or not self.HasCapture(): return if self.HasCapture(): if self.up != self.saveUp: self.Notify() self.ReleaseMouse() self.Refresh() def OnKeyDown(self, event): event.Skip() def OnMotion(self, event): if not self.IsEnabled(): return if event.LeftIsDown() and self.HasCapture(): x,y = event.GetPositionTuple() w,h = self.GetClientSizeTuple() if x=0 and y=0: self.up = not self.saveUp self.Refresh() return if (x<0 or y<0 or x>=w or y>=h): self.up = self.saveUp self.Refresh() return event.Skip() def OnKeyUp(self, event): if self.hasFocus and event.KeyCode() == ord(" "): self.up = not self.up self.Notify() self.Refresh() event.Skip() class GenToggleButton(__ToggleMixin, GenButton): pass class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton): pass class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton): pass #----------------------------------------------------------------------