Mercurial > traipse_dev
view orpg/tools/ButtonPanel.py @ 196:0bc44a57ae6c alpha
Traipse Alpha 'OpenRPG' {100425-00}
Traipse is a distribution of OpenRPG that is designed to be easy to setup and go. Traipse also makes it easy for
developers to work on code without fear of sacrifice. 'Ornery-Orc' continues the trend of 'Grumpy' and adds fixes to
the code. 'Ornery-Orc's main goal is to offer more advanced features and enhance the productivity of the user.
Update Summary (Patch-2)
New Features:
New Namespace method with two new syntaxes
New Namespace Internal is context sensitive, always!
New Namespace External is 'as narrow as you make it'
New PluginDB access for URL2Link plugin
New to Forms, they now show their content in Design Mode
Fixes:
Fix to Server GUI startup errors
Fix to Server GUI Rooms tab updating
Fix to Chat and Settings if non existant die roller is picked
Fix to Dieroller and .open() used with .vs(). Successes are correctly calculated
Fix to Alias Lib's Export to Tree, Open, Save features
Fix to alias node, now works properly
Fix to Splitter node, minor GUI cleanup
Fix to Backgrounds not loading through remote loader
Fix to Node name errors
Fix to rolling dice in chat Whispers
Fix to Splitters Sizing issues
author | sirebral |
---|---|
date | Sun, 25 Apr 2010 23:26:55 -0500 |
parents | 4385a7d0efd1 |
children |
line wrap: on
line source
# --------------------------------------------------------------------------- # # FANCYBUTTONPANEL Widget wxPython IMPLEMENTATION # # Original C++ Code From Eran. You Can Find It At: # # http://wxforum.shadonet.com/viewtopic.php?t=6619 # # License: wxWidgets license # # # Python Code By: # # Andrea Gavana, @ 02 Oct 2006 # Latest Revision: 17 Oct 2006, 17.00 GMT # # # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please # Write To Me At: # # andrea.gavana@gmail.com # gavana@kpo.kz # # Or, Obviously, To The wxPython Mailing List!!! # # # End Of Comments # --------------------------------------------------------------------------- # """ With `ButtonPanel` class you have a panel with gradient coloring on it and with the possibility to place some buttons on it. Using a standard panel with normal wx.Buttons leads to an ugly result: the buttons are placed correctly on the panel - but with grey area around them. Gradient coloring is kept behind the images - this was achieved due to the PNG format and the transparency of the bitmaps. The image are functioning like a buttons and can be caught in your code using the usual self.Bind(wx.EVT_BUTTON, self.OnButton) method. The control is generic, and support theming (well, I tested it under Windows with the three defauls themes: grey, blue, silver and the classic look). Usage ----- ButtonPanel supports 4 alignments: left, right, top, bottom, which have a different meaning and behavior wrt wx.Toolbar. The easiest thing is to try the demo to understand, but I'll try to explain how it works. CASE 1: ButtonPanel has a main caption text Left alignment means ButtonPanel is horizontal, with the text aligned to the left. When you shrink the demo frame, if there is not enough room for all the controls to be shown, the controls closest to the text are hidden; Right alignment means ButtonPanel is horizontal, with the text aligned to the right. Item layout as above; Top alignment means ButtonPanel is vertical, with the text aligned to the top. Item layout as above; Bottom alignment means ButtonPanel is vertical, with the text aligned to the bottom. Item layout as above. CASE 2: ButtonPanel has *no* main caption text In this case, left and right alignment are the same (as top and bottom are the same), but the layout strategy changes: now if there is not enough room for all the controls to be shown, the last added items are hidden ("last" means on the far right for horizontal ButtonPanels and far bottom for vertical ButtonPanels). The following example shows a simple implementation that uses ButtonPanel inside a very simple frame:: class MyFrame(wx.Frame): def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition, size=(800, 600), style=wx.DEFAULT_FRAME_STYLE): wx.Frame.__init__(self, parent, id, title, pos, size, style) mainPanel = wx.Panel(self, -1) self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE) vSizer = wx.BoxSizer(wx.VERTICAL) mainPanel.SetSizer(vSizer) alignment = BP_ALIGN_RIGHT titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo") btn1 = ButtonInfo(wx.NewId(), wx.Bitmap("png4.png", wx.BITMAP_TYPE_PNG)) titleBar.AddButton(btn1) self.Bind(wx.EVT_BUTTON, self.OnButton, btn1) btn2 = ButtonInfo(wx.NewId(), wx.Bitmap("png3.png", wx.BITMAP_TYPE_PNG)) titleBar.AddButton(btn2) self.Bind(wx.EVT_BUTTON, self.OnButton, btn2) btn3 = ButtonInfo(wx.NewId(), wx.Bitmap("png2.png", wx.BITMAP_TYPE_PNG)) titleBar.AddButton(btn3) self.Bind(wx.EVT_BUTTON, self.OnButton, btn3) btn4 = ButtonInfo(wx.NewId(), wx.Bitmap("png1.png", wx.BITMAP_TYPE_PNG)) titleBar.AddButton(btn4) self.Bind(wx.EVT_BUTTON, self.OnButton, btn4) vSizer.Add(titleBar, 0, wx.EXPAND) vSizer.Add((20, 20)) vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5) vSizer.Layout() # our normal wxApp-derived class, as usual app = wx.PySimpleApp() frame = MyFrame(None) app.SetTopWindow(frame) frame.Show() app.MainLoop() License And Version: ButtonPanel Is Freeware And Distributed Under The wxPython License. Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT Version 0.3. """ import wx # Some constants to tune the BPArt class BP_BACKGROUND_COLOR = 0 """ Background brush colour when no gradient shading exists. """ BP_GRADIENT_COLOR_FROM = 1 """ Starting gradient colour, used only when BP_USE_GRADIENT style is applied. """ BP_GRADIENT_COLOR_TO = 2 """ Ending gradient colour, used only when BP_USE_GRADIENT style is applied. """ BP_BORDER_COLOR = 3 """ Pen colour to paint the border of ButtonPanel. """ BP_TEXT_COLOR = 4 """ Main ButtonPanel caption colour. """ BP_BUTTONTEXT_COLOR = 5 """ Text colour for buttons with text. """ BP_BUTTONTEXT_INACTIVE_COLOR = 6 """ Text colour for inactive buttons with text. """ BP_SELECTION_BRUSH_COLOR = 7 """ Brush colour to be used when hovering or selecting a button. """ BP_SELECTION_PEN_COLOR = 8 """ Pen colour to be used when hovering or selecting a button. """ BP_SEPARATOR_COLOR = 9 """ Pen colour used to paint the separators. """ BP_TEXT_FONT = 10 """ Font of the ButtonPanel main caption. """ BP_BUTTONTEXT_FONT = 11 """ Text font for the buttons with text. """ BP_BUTTONTEXT_ALIGN_BOTTOM = 12 """ Flag that indicates the image and text in buttons is stacked. """ BP_BUTTONTEXT_ALIGN_RIGHT = 13 """ Flag that indicates the text is shown alongside the image in buttons with text. """ BP_SEPARATOR_SIZE = 14 """ Separator size. NB: This is not the line width, but the sum of the space before and after the separator line plus the width of the line. """ BP_MARGINS_SIZE = 15 """ Size of the left/right margins in ButtonPanel (top/bottom for vertically aligned ButtonPanels). """ BP_BORDER_SIZE = 16 """ Size of the border. """ BP_PADDING_SIZE = 17 """ Inter-tool separator size. """ # Caption Gradient Type BP_GRADIENT_NONE = 0 """ No gradient shading should be used to paint the background. """ BP_GRADIENT_VERTICAL = 1 """ Vertical gradient shading should be used to paint the background. """ BP_GRADIENT_HORIZONTAL = 2 """ Horizontal gradient shading should be used to paint the background. """ # Flags for HitTest() method BP_HT_BUTTON = 200 BP_HT_NONE = 201 # Alignment of buttons in the panel BP_ALIGN_RIGHT = 1 BP_ALIGN_LEFT = 2 BP_ALIGN_TOP = 4 BP_ALIGN_BOTTOM = 8 # ButtonPanel styles BP_DEFAULT_STYLE = 1 BP_USE_GRADIENT = 2 # Delay used to cancel the longHelp in the statusbar field _DELAY = 3000 # Check for the new method in 2.7 (not present in 2.6.3.3) if wx.VERSION_STRING < "2.7": wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point) def BrightenColour(color, factor): """ Bright the input colour by a factor.""" val = color.Red()*factor if val > 255: red = 255 else: red = val val = color.Green()*factor if val > 255: green = 255 else: green = val val = color.Blue()*factor if val > 255: blue = 255 else: blue = val return wx.Color(red, green, blue) def GrayOut(anImage): """ Convert the given image (in place) to a grayed-out version, appropriate for a 'Disabled' appearance. """ factor = 0.7 # 0 < f < 1. Higher Is Grayer anImage = anImage.ConvertToImage() if anImage.HasAlpha(): anImage.ConvertAlphaToMask(1) if anImage.HasMask(): maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue()) else: maskColor = None data = map(ord, list(anImage.GetData())) for i in range(0, len(data), 3): pixel = (data[i], data[i+1], data[i+2]) pixel = MakeGray(pixel, factor, maskColor) for x in range(3): data[i+x] = pixel[x] anImage.SetData(''.join(map(chr, data))) anImage = anImage.ConvertToBitmap() return anImage def MakeGray((r,g,b), factor, maskColor): """ Make a pixel grayed-out. If the pixel matches the maskColor, it won't be changed. """ if (r,g,b) != maskColor: return map(lambda x: int((230 - x) * factor) + x, (r,g,b)) else: return (r,g,b) # ---------------------------------------------------------------------------- # # Class BPArt # Handles all the drawings for buttons, separators and text and allows the # programmer to set colours, sizes and gradient shadings for ButtonPanel # ---------------------------------------------------------------------------- # class BPArt: """ BPArt is an art provider class which does all of the drawing for ButtonPanel. This allows the library caller to customize the BPArt or to completely replace all drawing with custom BPArts. """ def __init__(self, parentStyle): """ Default class constructor. """ base_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE) self._background_brush = wx.Brush(base_color, wx.SOLID) self._gradient_color_to = wx.WHITE self._gradient_color_from = wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION) if parentStyle & BP_USE_GRADIENT: self._border_pen = wx.Pen(wx.WHITE, 3) self._caption_text_color = wx.WHITE self._buttontext_color = wx.Colour(70, 143, 255) self._separator_pen = wx.Pen(BrightenColour(self._gradient_color_from, 1.4)) self._gradient_type = BP_GRADIENT_VERTICAL else: self._border_pen = wx.Pen(BrightenColour(base_color, 0.9), 3) self._caption_text_color = wx.BLACK self._buttontext_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNTEXT) self._separator_pen = wx.Pen(BrightenColour(base_color, 0.9)) self._gradient_type = BP_GRADIENT_NONE self._buttontext_inactive_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT) self._selection_brush = wx.Brush(wx.Color(225, 225, 255)) self._selection_pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION)) sysfont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) self._caption_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.BOLD, False, sysfont.GetFaceName()) self._buttontext_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, sysfont.GetFaceName()) self._separator_size = 7 self._margins_size = wx.Size(6, 6) self._caption_border_size = 3 self._padding_size = wx.Size(6, 6) def GetMetric(self, id): """ Returns sizes of customizable options. """ if id == BP_SEPARATOR_SIZE: return self._separator_size elif id == BP_MARGINS_SIZE: return self._margins_size elif id == BP_BORDER_SIZE: return self._caption_border_size elif id == BP_PADDING_SIZE: return self._padding_size else: raise "\nERROR: Invalid Metric Ordinal. " def SetMetric(self, id, new_val): """ Sets sizes for customizable options. """ if id == BP_SEPARATOR_SIZE: self._separator_size = new_val elif id == BP_MARGINS_SIZE: self._margins_size = new_val elif id == BP_BORDER_SIZE: self._caption_border_size = new_val self._border_pen.SetWidth(new_val) elif id == BP_PADDING_SIZE: self._padding_size = new_val else: raise "\nERROR: Invalid Metric Ordinal. " def GetColor(self, id): """ Returns colours of customizable options. """ if id == BP_BACKGROUND_COLOR: return self._background_brush.GetColour() elif id == BP_GRADIENT_COLOR_FROM: return self._gradient_color_from elif id == BP_GRADIENT_COLOR_TO: return self._gradient_color_to elif id == BP_BORDER_COLOR: return self._border_pen.GetColour() elif id == BP_TEXT_COLOR: return self._caption_text_color elif id == BP_BUTTONTEXT_COLOR: return self._buttontext_color elif id == BP_BUTTONTEXT_INACTIVE_COLOR: return self._buttontext_inactive_color elif id == BP_SELECTION_BRUSH_COLOR: return self._selection_brush.GetColour() elif id == BP_SELECTION_PEN_COLOR: return self._selection_pen.GetColour() elif id == BP_SEPARATOR_COLOR: return self._separator_pen.GetColour() else: raise "\nERROR: Invalid Colour Ordinal. " def SetColor(self, id, colour): """ Sets colours for customizable options. """ if id == BP_BACKGROUND_COLOR: self._background_brush.SetColour(colour) elif id == BP_GRADIENT_COLOR_FROM: self._gradient_color_from = colour elif id == BP_GRADIENT_COLOR_TO: self._gradient_color_to = colour elif id == BP_BORDER_COLOR: self._border_pen.SetColour(colour) elif id == BP_TEXT_COLOR: self._caption_text_color = colour elif id == BP_BUTTONTEXT_COLOR: self._buttontext_color = colour elif id == BP_BUTTONTEXT_INACTIVE_COLOR: self._buttontext_inactive_color = colour elif id == BP_SELECTION_BRUSH_COLOR: self._selection_brush.SetColour(colour) elif id == BP_SELECTION_PEN_COLOR: self._selection_pen.SetColour(colour) elif id == BP_SEPARATOR_COLOR: self._separator_pen.SetColour(colour) else: raise "\nERROR: Invalid Colour Ordinal. " GetColour = GetColor SetColour = SetColor def SetFont(self, id, font): """ Sets font for customizable options. """ if id == BP_TEXT_FONT: self._caption_font = font elif id == BP_BUTTONTEXT_FONT: self._buttontext_font = font def GetFont(self, id): """ Returns font of customizable options. """ if id == BP_TEXT_FONT: return self._caption_font elif id == BP_BUTTONTEXT_FONT: return self._buttontext_font return wx.NoneFont def SetGradientType(self, gradient): """ Sets the gradient type for BPArt drawings. """ self._gradient_type = gradient def GetGradientType(self): """ Returns the gradient type for BPArt drawings. """ return self._gradient_type def DrawSeparator(self, dc, rect, isVertical): """ Draws a separator in ButtonPanel. """ dc.SetPen(self._separator_pen) if isVertical: ystart = yend = rect.y + rect.height/2 xstart = int(rect.x + 1.5*self._caption_border_size) xend = int(rect.x + rect.width - 1.5*self._caption_border_size) dc.DrawLine(xstart, ystart, xend, yend) else: xstart = xend = rect.x + rect.width/2 ystart = int(rect.y + 1.5*self._caption_border_size) yend = int(rect.y + rect.height - 1.5*self._caption_border_size) dc.DrawLine(xstart, ystart, xend, yend) def DrawCaption(self, dc, rect, captionText): """ Draws the main caption text in ButtonPanel. """ textColour = self._caption_text_color textFont = self._caption_font padding = self._padding_size dc.SetTextForeground(textColour) dc.SetFont(textFont) dc.DrawText(captionText, rect.x + padding.x, rect.y+padding.y) def DrawButton(self, dc, rect, parentSize, buttonBitmap, isVertical, buttonStatus, isToggled, textAlignment, text=""): """ Draws a button in ButtonPanel, together with its text (if any). """ bmpxsize, bmpysize = buttonBitmap.GetWidth(), buttonBitmap.GetHeight() dx = dy = focus = 0 borderw = self._caption_border_size padding = self._padding_size buttonFont = self._buttontext_font dc.SetFont(buttonFont) if isVertical: rect = wx.Rect(borderw, rect.y, rect.width-2*borderw, rect.height) if text != "": textW, textH = dc.GetTextExtent(text) if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT: fullExtent = bmpxsize + padding.x/2 + textW bmpypos = rect.y + (rect.height - bmpysize)/2 bmpxpos = rect.x + (rect.width - fullExtent)/2 textxpos = bmpxpos + padding.x/2 + bmpxsize textypos = bmpypos + (bmpysize - textH)/2 else: bmpxpos = rect.x + (rect.width - bmpxsize)/2 bmpypos = rect.y + padding.y textxpos = rect.x + (rect.width - textW)/2 textypos = bmpypos + bmpysize + padding.y/2 else: bmpxpos = rect.x + (rect.width - bmpxsize)/2 bmpypos = rect.y + (rect.height - bmpysize)/2 else: rect = wx.Rect(rect.x, borderw, rect.width, rect.height-2*borderw) if text != "": textW, textH = dc.GetTextExtent(text) if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT: fullExtent = bmpxsize + padding.x/2 + textW bmpypos = rect.y + (rect.height - bmpysize)/2 bmpxpos = rect.x + (rect.width - fullExtent)/2 textxpos = bmpxpos + padding.x/2 + bmpxsize textypos = bmpypos + (bmpysize - textH)/2 else: fullExtent = bmpysize + padding.y/2 + textH bmpxpos = rect.x + (rect.width - bmpxsize)/2 bmpypos = rect.y + (rect.height - fullExtent)/2 textxpos = rect.x + (rect.width - textW)/2 textypos = bmpypos + bmpysize + padding.y/2 else: bmpxpos = rect.x + (rect.width - bmpxsize)/2 bmpypos = rect.y + (rect.height - bmpysize)/2 # Draw a button # [ Padding | Text | .. Buttons .. | Padding ] if buttonStatus in ["Pressed", "Toggled", "Hover"]: dc.SetBrush(self._selection_brush) dc.SetPen(self._selection_pen) dc.DrawRoundedRectangleRect(rect, 4) if buttonStatus == "Pressed" or isToggled: dx = dy = 1 dc.DrawBitmap(buttonBitmap, bmpxpos+dx, bmpypos+dy, True) if text != "": isEnabled = buttonStatus != "Disabled" self.DrawLabel(dc, text, isEnabled, textxpos+dx, textypos+dy) def DrawLabel(self, dc, text, isEnabled, xpos, ypos): """ Draws the label for a button. """ if not isEnabled: dc.SetTextForeground(self._buttontext_inactive_color) else: dc.SetTextForeground(self._buttontext_color) dc.DrawText(text, xpos, ypos) def DrawButtonPanel(self, dc, rect, style): """ Paint the ButtonPanel's background. """ if style & BP_USE_GRADIENT: # Draw gradient color in the backgroud of the panel self.FillGradientColor(dc, rect) # Draw a rectangle around the panel backBrush = (style & BP_USE_GRADIENT and [wx.TRANSPARENT_BRUSH] or \ [self._background_brush])[0] dc.SetBrush(backBrush) dc.SetPen(self._border_pen) dc.DrawRectangleRect(rect) def FillGradientColor(self, dc, rect): """ Gradient fill from colour 1 to colour 2 with top to bottom or left to right. """ if rect.height < 1 or rect.width < 1: return isVertical = self._gradient_type == BP_GRADIENT_VERTICAL size = (isVertical and [rect.height] or [rect.width])[0] start = (isVertical and [rect.y] or [rect.x])[0] # calculate gradient coefficients col2 = self._gradient_color_from col1 = self._gradient_color_to rf, gf, bf = 0, 0, 0 rstep = float((col2.Red() - col1.Red()))/float(size) gstep = float((col2.Green() - col1.Green()))/float(size) bstep = float((col2.Blue() - col1.Blue()))/float(size) for coord in xrange(start, start + size): currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf) dc.SetBrush(wx.Brush(currCol, wx.SOLID)) dc.SetPen(wx.Pen(currCol)) if isVertical: dc.DrawLine(rect.x, coord, rect.x + rect.width, coord) else: dc.DrawLine(coord, rect.y, coord, rect.y + rect.height) rf += rstep gf += gstep bf += bstep class StatusBarTimer(wx.Timer): """Timer used for deleting StatusBar long help after _DELAY seconds.""" def __init__(self, owner): """ Default class constructor. For internal use: do not call it in your code! """ wx.Timer.__init__(self) self._owner = owner def Notify(self): """The timer has expired.""" self._owner.OnStatusBarTimer() class Control(wx.EvtHandler): def __init__(self, parent, size=wx.Size(-1, -1)): """ Default class constructor. Base class for all pseudo controls parent = parent object size = (width, height) """ wx.EvtHandler.__init__(self) self._parent = parent self._id = wx.NewId() self._size = size self._isshown = True self._focus = False def Show(self, show=True): """ Shows or hide the control. """ self._isshown = show def Hide(self): """ Hides the control. """ self.Show(False) def IsShown(self): """ Returns whether the control is shown or not. """ return self._isshown def GetId(self): """ Returns the control id. """ return self._id def GetBestSize(self): """ Returns the control best size. """ return self._size def Disable(self): """ Disables the control. """ self.Enable(False) def Enable(self, value=True): """ Enables or disables the control. """ self.disabled = not value def SetFocus(self, focus=True): """ Sets or kills the focus on the control. """ self._focus = focus def HasFocus(self): """ Returns whether the control has the focus or not. """ return self._focus def OnMouseEvent(self, x, y, event): pass def Draw(self, rect): pass class Sizer(object): """ Sizer This is a mix-in class to add pseudo support to a wx sizer. Just create a new class that derives from this class and the wx sizer and intercepts any methods that add to the wx sizer. """ def __init__(self): self.children = [] # list of child Pseudo Controls # Sizer doesn't use the x1,y1,x2,y2 so allow it to # be called with or without the coordinates def Draw(self, dc, x1=0, y1=0, x2=0, y2=0): for item in self.children: # use sizer coordinates rather than # what is passed in c = item.GetUserData() c.Draw(dc, item.GetRect()) def GetBestSize(self): # this should be handled by the wx.Sizer based class return self.GetMinSize() # Pseudo BoxSizer class BoxSizer(Sizer, wx.BoxSizer): def __init__(self, orient=wx.HORIZONTAL): wx.BoxSizer.__init__(self, orient) Sizer.__init__(self) #------------------------------------------- # sizer overrides (only called from Python) #------------------------------------------- # no support for user data if it's a pseudocontrol # since that is already used def Add(self, item, proportion=0, flag=0, border=0, userData=None): # check to see if it's a pseudo object or sizer if isinstance(item, Sizer): szitem = wx.BoxSizer.Add(self, item, proportion, flag, border, item) self.children.append(szitem) elif isinstance(item, Control): # Control should be what ever class your controls come from sz = item.GetBestSize() # add a spacer to track this object szitem = wx.BoxSizer.Add(self, sz, proportion, flag, border, item) self.children.append(szitem) else: wx.BoxSizer.Add(self, item, proportion, flag, border, userData) def Prepend(self, item, proportion=0, flag=0, border=0, userData=None): # check to see if it's a pseudo object or sizer if isinstance(item, Sizer): szitem = wx.BoxSizer.Prepend(self, item, proportion, flag, border, item) self.children.append(szitem) elif isinstance(item, Control): # Control should be what ever class your controls come from sz = item.GetBestSize() # add a spacer to track this object szitem = wx.BoxSizer.Prepend(self, sz, proportion, flag, border, item) self.children.insert(0,szitem) else: wx.BoxSizer.Prepend(self, item, proportion, flag, border, userData) def Insert(self, before, item, proportion=0, flag=0, border=0, userData=None, realIndex=None): # check to see if it's a pseudo object or sizer if isinstance(item, Sizer): szitem = wx.BoxSizer.Insert(self, before, item, proportion, flag, border, item) self.children.append(szitem) elif isinstance(item, Control): # Control should be what ever class your controls come from sz = item.GetBestSize() # add a spacer to track this object szitem = wx.BoxSizer.Insert(self, before, sz, proportion, flag, border, item) if realIndex is not None: self.children.insert(realIndex,szitem) else: self.children.insert(before,szitem) else: wx.BoxSizer.Insert(self, before, item, proportion, flag, border, userData) def Remove(self, indx, pop=-1): if pop >= 0: self.children.pop(pop) wx.BoxSizer.Remove(self, indx) def Layout(self): for ii, child in enumerate(self.GetChildren()): item = child.GetUserData() if item and child.IsShown(): self.SetItemMinSize(ii, *item.GetBestSize()) wx.BoxSizer.Layout(self) def Show(self, item, show=True): child = self.GetChildren()[item] if child and child.GetUserData(): child.GetUserData().Show(show) wx.BoxSizer.Show(self, item, show) # ---------------------------------------------------------------------------- # # Class Separator # This class holds all the information to size and draw a separator inside # ButtonPanel # ---------------------------------------------------------------------------- # class Separator(Control): def __init__(self, parent): """ Default class constructor. """ self._isshown = True self._parent = parent Control.__init__(self, parent) def GetBestSize(self): """ Returns the separator best size. """ # 10 is completely arbitrary, but it works anyhow if self._parent.IsVertical(): return wx.Size(10, self._parent._art.GetMetric(BP_SEPARATOR_SIZE)) else: return wx.Size(self._parent._art.GetMetric(BP_SEPARATOR_SIZE), 10) def Draw(self, dc, rect): """ Draws the separator. Actually the drawing is done in BPArt. """ if not self.IsShown(): return isVertical = self._parent.IsVertical() self._parent._art.DrawSeparator(dc, rect, isVertical) # ---------------------------------------------------------------------------- # # Class ButtonPanelText # This class is used to hold data about the main caption in ButtonPanel # ---------------------------------------------------------------------------- # class ButtonPanelText(Control): def __init__(self, parent, text=""): """ Default class constructor. """ self._text = text self._isshown = True self._parent = parent Control.__init__(self, parent) def GetText(self): """ Returns the caption text. """ return self._text def SetText(self, text=""): """ Sets the caption text. """ self._text = text def CreateDC(self): """ Convenience function to create a DC. """ dc = wx.ClientDC(self._parent) textFont = self._parent._art.GetFont(BP_TEXT_FONT) dc.SetFont(textFont) return dc def GetBestSize(self): """ Returns the best size for the main caption in ButtonPanel. """ if self._text == "": return wx.Size(0, 0) dc = self.CreateDC() rect = self._parent.GetClientRect() tw, th = dc.GetTextExtent(self._text) padding = self._parent._art.GetMetric(BP_PADDING_SIZE) self._size = wx.Size(tw+2*padding.x, th+2*padding.y) return self._size def Draw(self, dc, rect): """ Draws the main caption. Actually the drawing is done in BPArt. """ if not self.IsShown(): return captionText = self.GetText() self._parent._art.DrawCaption(dc, rect, captionText) # -- ButtonInfo class implementation ---------------------------------------- # This class holds information about every button that is added to # ButtonPanel. It is an auxiliary class that you should use # every time you add a button. class ButtonInfo(Control): def __init__(self, parent, id=wx.ID_ANY, bmp=wx.NullBitmap, status="Normal", text="", kind=wx.ITEM_NORMAL, shortHelp="", longHelp=""): """ Default class constructor. Parameters: - parent: the parent window (ButtonPanel); - id: the button id; - bmp: the associated bitmap; - status: button status (pressed, hovered, normal). - text: text to be displayed either below of to the right of the button - kind: button kind, may be wx.ITEM_NORMAL for standard buttons or wx.ITEM_CHECK for toggle buttons; - shortHelp: a short help to be shown in the button tooltip; - longHelp: this string is shown in the statusbar (if any) of the parent frame when the mouse pointer is inside the button. """ if id == wx.ID_ANY: id = wx.NewId() self._status = status self._rect = wx.Rect() self._text = text self._kind = kind self._toggle = False self._textAlignment = BP_BUTTONTEXT_ALIGN_BOTTOM self._shortHelp = shortHelp self._longHelp = longHelp self._menu = None disabledbmp = GrayOut(bmp) self._bitmaps = {"Normal": bmp, "Toggled": None, "Disabled": disabledbmp, "Hover": None, "Pressed": None} Control.__init__(self, parent) def GetBestSize(self): """ Returns the best size for the button. """ xsize = self.GetBitmap().GetWidth() ysize = self.GetBitmap().GetHeight() if self.HasText(): # We have text in the button dc = wx.ClientDC(self._parent) normalFont = self._parent._art.GetFont(BP_BUTTONTEXT_FONT) dc.SetFont(normalFont) tw, th = dc.GetTextExtent(self.GetText()) if self.GetTextAlignment() == BP_BUTTONTEXT_ALIGN_BOTTOM: xsize = max(xsize, tw) ysize = ysize + th else: xsize = xsize + tw ysize = max(ysize, th) border = self._parent._art.GetMetric(BP_BORDER_SIZE) padding = self._parent._art.GetMetric(BP_PADDING_SIZE) if self._parent.IsVertical(): xsize = xsize + 2*border else: ysize = ysize + 2*border self._size = wx.Size(xsize+2*padding.x, ysize+2*padding.y) return self._size def Draw(self, dc, rect): """ Draws the button on ButtonPanel. Actually the drawing is done in BPArt. """ if not self.IsShown(): return buttonBitmap = self.GetBitmap() isVertical = self._parent.IsVertical() text = self.GetText() parentSize = self._parent.GetSize()[not isVertical] buttonStatus = self.GetStatus() isToggled = self.GetToggled() textAlignment = self.GetTextAlignment() self._parent._art.DrawButton(dc, rect, parentSize, buttonBitmap, isVertical, buttonStatus, isToggled, textAlignment, text) self.SetRect(rect) def CheckRefresh(self, status): """ Checks whether a ButtonPanel repaint is needed or not. Convenience function. """ if status == self._status: self._parent.RefreshRect(self.GetRect()) def SetBitmap(self, bmp, status="Normal"): """ Sets the associated bitmap. """ self._bitmaps[status] = bmp self.CheckRefresh(status) def GetBitmap(self, status=None): """ Returns the associated bitmap. """ if status is None: status = self._status if not self.IsEnabled(): status = "Disabled" if self._bitmaps[status] is None: return self._bitmaps["Normal"] return self._bitmaps[status] def GetRect(self): """ Returns the button rect. """ return self._rect def GetStatus(self): """ Returns the button status. """ return self._status def GetId(self): """ Returns the button id. """ return self._id def SetRect(self, rect): """ Sets the button rect. """ self._rect = rect def SetStatus(self, status): """ Sets the button status. """ if status == self._status: return if self.GetToggled() and status == "Normal": status = "Toggled" self._status = status self._parent.RefreshRect(self.GetRect()) def GetTextAlignment(self): """ Returns the text alignment in the button (bottom or right). """ return self._textAlignment def SetTextAlignment(self, alignment): """ Sets the text alignment in the button (bottom or right). """ if alignment == self._textAlignment: return self._textAlignment = alignment def GetToggled(self): """ Returns whether a wx.ITEM_CHECK button is toggled or not. """ if self._kind == wx.ITEM_NORMAL: return False return self._toggle def SetToggled(self, toggle=True): """ Sets a wx.ITEM_CHECK button toggled/not toggled. """ if self._kind == wx.ITEM_NORMAL: return self._toggle = toggle if toggle: self.SetStatus("Toggled") else: self.SetStatus("Normal") def SetId(self, id): """ Sets the button id. """ self._id = id def AddStatus(self, name="Custom", bmp=wx.NullBitmap): """ Add a programmer-defined status in addition to the 5 default status: - Normal; - Disabled; - Hover; - Pressed; - Toggled. """ self._bitmaps.update({name: bmp}) def Enable(self, enable=True): if enable: self._status = "Normal" else: self._status = "Disabled" def IsEnabled(self): return self._status != "Disabled" def SetText(self, text=""): """ Sets the text of the button. """ self._text = text def GetText(self): """ Returns the text associated to the button. """ return self._text def HasText(self): """ Returns whether the button has text or not. """ return self._text != "" def SetKind(self, kind=wx.ITEM_NORMAL): """ Sets the button type (standard or toggle). """ self._kind = kind def GetKind(self): """ Returns the button type (standard or toggle). """ return self._kind def SetShortHelp(self, help=""): """ Sets the help string to be shown in a tootip. """ self._shortHelp = help def GetShortHelp(self): """ Returns the help string shown in a tootip. """ return self._shortHelp def SetLongHelp(self, help=""): """ Sets the help string to be shown in the statusbar. """ self._longHelp = help def GetLongHelp(self): """ Returns the help string shown in the statusbar. """ return self._longHelp def GetMenu(self): return self._menu def SetMenu(self, Menu): self._menu = Menu Bitmap = property(GetBitmap, SetBitmap) Id = property(GetId, SetId) Rect = property(GetRect, SetRect) Status = property(GetStatus, SetStatus) # -- ButtonPanel class implementation ---------------------------------- # This is the main class. class ButtonPanel(wx.PyPanel): def __init__(self, parent, id=wx.ID_ANY, text="", style=BP_DEFAULT_STYLE, alignment=BP_ALIGN_LEFT, name="buttonPanel"): """ Default class constructor. - parent: parent window - id: window ID - text: text to draw - style: window style - alignment: alignment of buttons (left or right) - name: window class name """ wx.PyPanel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, wx.NO_BORDER, name=name) self._vButtons = [] self._vControls = [] self._vSeparators = [] self._nStyle = style self._alignment = alignment self._statusTimer = None self._useHelp = True self._freezeCount = 0 self._currentButton = -1 self._haveTip = False self._art = BPArt(style) self._controlCreated = False direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0] self._mainsizer = BoxSizer(direction) self.SetSizer(self._mainsizer) margins = self._art.GetMetric(BP_MARGINS_SIZE) # First spacer to create some room before the first text/button/control self._mainsizer.Add((margins.x, margins.y), 0) # Last spacer to create some room before the last text/button/control self._mainsizer.Add((margins.x, margins.y), 0) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_MOTION, self.OnMouseMove) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow) self.SetBarText(text) self.LayoutItems() def SetBarText(self, text): """ Sets the main caption text (leave text="" for no text). """ self.Freeze() text = text.strip() if self._controlCreated: self.RemoveText() self._text = ButtonPanelText(self, text) lenChildren = len(self._mainsizer.GetChildren()) if text == "": # Even if we have no text, we insert it an empty spacer anyway # it is easier to handle if you have to recreate the sizer after. if self.IsStandard(): self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER, userData=self._text, realIndex=0) else: self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER, userData=self._text, realIndex=lenChildren) return # We have text, so insert the text and an expandable spacer # alongside it. "Standard" ButtonPanel are left or top aligned. if self.IsStandard(): self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER, userData=self._text, realIndex=0) self._mainsizer.Insert(2, (0, 0), 1, wx.EXPAND) else: self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER, userData=self._text, realIndex=lenChildren) self._mainsizer.Insert(lenChildren-1, (0, 0), 1, wx.EXPAND) def RemoveText(self): """ Removes the main caption text. """ lenChildren = len(self._mainsizer.GetChildren()) lenCustom = len(self._vButtons) + len(self._vSeparators) + 1 if self.IsStandard(): # Detach the text self._mainsizer.Remove(1, 0) if self.HasBarText(): # Detach the expandable spacer self._mainsizer.Remove(1, -1) else: # Detach the text self._mainsizer.Remove(lenChildren-2, lenCustom-1) if self.HasBarText(): # Detach the expandable spacer self._mainsizer.Remove(lenChildren-3, -1) def GetBarText(self): """ Returns the main caption text. """ return self._text.GetText() def HasBarText(self): """ Returns whether ButtonPanel has a main caption text or not. """ return hasattr(self, "_text") and self._text.GetText() != "" def AddButton(self, btnInfo): """ Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to this method. See the demo for details. """ lenChildren = len(self._mainsizer.GetChildren()) self._mainsizer.Insert(lenChildren-1, btnInfo, 0, wx.ALIGN_CENTER|wx.EXPAND, userData=btnInfo) self._vButtons.append(btnInfo) def AddSpacer(self, size=(0, 0), proportion=1, flag=wx.EXPAND): """ Adds a spacer (stretchable or fixed-size) to ButtonPanel. """ lenChildren = len(self._mainsizer.GetChildren()) self._mainsizer.Insert(lenChildren-1, size, proportion, flag) def AddControl(self, control, proportion=0, flag=wx.ALIGN_CENTER|wx.ALL, border=None): """ Adds a wxPython control to ButtonPanel. """ lenChildren = len(self._mainsizer.GetChildren()) if border is None: border = self._art.GetMetric(BP_PADDING_SIZE) border = max(border.x, border.y) self._mainsizer.Insert(lenChildren-1, control, proportion, flag, border) self._vControls.append(control) def AddSeparator(self): """ Adds a separator line to ButtonPanel. """ lenChildren = len(self._mainsizer.GetChildren()) separator = Separator(self) self._mainsizer.Insert(lenChildren-1, separator, 0, wx.EXPAND) self._vSeparators.append(separator) def DisableAll(self): """ Disable all buttons and controls """ for btn in self._vButtons: btn.Enable(False) for ctrl in self._vControls: ctrl.Disable() def EnableAll(self): """ Enable all buttons and controls """ for btn in self._vButtons: btn.Enable(True) for ctrl in self._vControls: ctrl.Enable() def RemoveAllButtons(self): """ Remove all the buttons from ButtonPanel. """ self._vButtons = [] self._vControls = [] def RemoveAllSeparators(self): """ Remove all the separators from ButtonPanel. """ self._vSeparators = [] def GetAlignment(self): """ Returns the button alignment (left, right, top, bottom). """ return self._alignment def SetAlignment(self, alignment): """ Sets the button alignment (left, right, top, bottom). """ if alignment == self._alignment: return self.Freeze() text = self.GetBarText() # Remove the text in any case self.RemoveText() # Remove the first and last spacers self._mainsizer.Remove(0, -1) self._mainsizer.Remove(len(self._mainsizer.GetChildren())-1, -1) self._alignment = alignment # Recreate the sizer accordingly to the new alignment self.ReCreateSizer(text) def IsVertical(self): """ Returns whether ButtonPanel is vertically aligned or not. """ return self._alignment not in [BP_ALIGN_RIGHT, BP_ALIGN_LEFT] def IsStandard(self): """ Returns whether ButtonPanel is aligned "Standard" (left/top) or not. """ return self._alignment in [BP_ALIGN_LEFT, BP_ALIGN_TOP] def DoLayout(self): """ Do the Layout for ButtonPanel. NB: Call this method every time you make a modification to the layout or to the customizable sizes of the pseudo controls. """ margins = self._art.GetMetric(BP_MARGINS_SIZE) lenChildren = len(self._mainsizer.GetChildren()) self._mainsizer.SetItemMinSize(0, (margins.x, margins.y)) self._mainsizer.SetItemMinSize(lenChildren-1, (margins.x, margins.y)) self._controlCreated = True self.LayoutItems() # *VERY* WEIRD: the sizer seems not to respond to any layout until I # change the ButtonPanel size and restore it back size = self.GetSize() self.SetSize((size.x+1, size.y+1)) self.SetSize((size.x, size.y)) if self.IsFrozen(): self.Thaw() def ReCreateSizer(self, text): """ Recreates the ButtonPanel sizer accordingly to the alignment specified. """ children = self._mainsizer.GetChildren() self.RemoveAllButtons() self.RemoveAllSeparators() # Create a new sizer depending on the alignment chosen direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0] self._mainsizer = BoxSizer(direction) margins = self._art.GetMetric(BP_MARGINS_SIZE) # First spacer to create some room before the first text/button/control self._mainsizer.Add((margins.x, margins.y), 0) # Last spacer to create some room before the last text/button/control self._mainsizer.Add((margins.x, margins.y), 0) # This is needed otherwise SetBarText goes mad self._controlCreated = False for child in children: userData = child.GetUserData() if userData: if isinstance(userData, ButtonInfo): # It is a ButtonInfo, can't be anything else self.AddButton(child.GetUserData()) elif isinstance(userData, Separator): self.AddSeparator() else: if child.IsSpacer(): # This is a spacer, expandable or not self.AddSpacer(child.GetSize(), child.GetProportion(), child.GetFlag()) else: # This is a wxPython control self.AddControl(child.GetWindow(), child.GetProportion(), child.GetFlag(), child.GetBorder()) self.SetSizer(self._mainsizer) # Now add the text. It doesn't matter if there is no text self.SetBarText(text) self.DoLayout() self.Thaw() def DoGetBestSize(self): """ Returns the best size of ButtonPanel. """ w = h = btnWidth = btnHeight = 0 isVertical = self.IsVertical() padding = self._art.GetMetric(BP_PADDING_SIZE) border = self._art.GetMetric(BP_BORDER_SIZE) margins = self._art.GetMetric(BP_MARGINS_SIZE) separator_size = self._art.GetMetric(BP_SEPARATOR_SIZE) # Add the space required for the main caption if self.HasBarText(): w, h = self._text.GetBestSize() if isVertical: h += padding.y else: w += padding.x else: w = h = border # Add the button's sizes for btn in self._vButtons: bw, bh = btn.GetBestSize() btnWidth = max(btnWidth, bw) btnHeight = max(btnHeight, bh) if isVertical: w = max(w, btnWidth) h += bh else: h = max(h, btnHeight) w += bw # Add the control's sizes for control in self.GetControls(): cw, ch = control.GetSize() if isVertical: h += ch w = max(w, cw) else: w += cw h = max(h, ch) # Add the separator's sizes and the 2 SizerItems at the beginning # and at the end if self.IsVertical(): h += 2*margins.y + len(self._vSeparators)*separator_size else: w += 2*margins.x + len(self._vSeparators)*separator_size return wx.Size(w, h) def OnPaint(self, event): """ Handles the wx.EVT_PAINT event for ButtonPanel. """ dc = wx.BufferedPaintDC(self) rect = self.GetClientRect() self._art.DrawButtonPanel(dc, rect, self._nStyle) self._mainsizer.Draw(dc) def OnEraseBackground(self, event): """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """ pass def OnSize(self, event): """ Handles the wx.EVT_SIZE event for ButtonPanel. """ # NOTE: It seems like LayoutItems number of calls can be optimized in some way. # Currently every DoLayout (or every parent Layout()) calls about 3 times # the LayoutItems method. Any idea on how to improve it? self.LayoutItems() self.Refresh() event.Skip() def LayoutItems(self): """ Layout the items using a different algorithm depending on the existance of the main caption. """ nonspacers, allchildren = self.GetNonFlexibleChildren() if self.HasBarText(): self.FlexibleLayout(nonspacers, allchildren) else: self.SizeLayout(nonspacers, allchildren) self._mainsizer.Layout() def SizeLayout(self, nonspacers, children): """ Layout the items when no main caption exists. """ size = self.GetSize() isVertical = self.IsVertical() corner = 0 indx1 = len(nonspacers) for item in nonspacers: corner += self.GetItemSize(item, isVertical) if corner > size[isVertical]: indx1 = nonspacers.index(item) break # Leave out the last spacer, it has to be there always for ii in xrange(len(nonspacers)-1): indx = children.index(nonspacers[ii]) self._mainsizer.Show(indx, ii < indx1) def GetItemSize(self, item, isVertical): """ Returns the size of an item in the main ButtonPanel sizer. """ if item.GetUserData(): return item.GetUserData().GetBestSize()[isVertical] else: return item.GetSize()[isVertical] def FlexibleLayout(self, nonspacers, allchildren): """ Layout the items when the main caption exists. """ if len(nonspacers) < 2: return isVertical = self.IsVertical() isStandard = self.IsStandard() size = self.GetSize()[isVertical] padding = self._art.GetMetric(BP_PADDING_SIZE) fixed = (isStandard and [nonspacers[1]] or [nonspacers[-2]])[0] if isStandard: nonspacers.reverse() leftendx = fixed.GetSize()[isVertical] + padding.x else: rightstartx = size - fixed.GetSize()[isVertical] size = 0 count = lennonspacers = len(nonspacers) for item in nonspacers: if isStandard: size -= self.GetItemSize(item, isVertical) if size < leftendx: break else: size += self.GetItemSize(item, isVertical) if size > rightstartx: break count = count - 1 nonspacers.reverse() for jj in xrange(2, lennonspacers): indx = allchildren.index(nonspacers[jj]) self._mainsizer.Show(indx, jj >= count) def GetNonFlexibleChildren(self): """ Returns all the ButtonPanel main sizer's children that are not flexible spacers. """ children1 = [] children2 = self._mainsizer.GetChildren() for child in children2: if child.IsSpacer(): if child.GetUserData() or child.GetProportion() == 0: children1.append(child) else: children1.append(child) return children1, children2 def GetControls(self): """ Returns the wxPython controls that belongs to ButtonPanel. """ children2 = self._mainsizer.GetChildren() children1 = [child for child in children2 if not child.IsSpacer()] return children1 def SetStyle(self, style): """ Sets ButtonPanel style. """ if style == self._nStyle: return self._nStyle = style self.Refresh() def GetStyle(self): """ Returns the ButtonPanel style. """ return self._nStyle def OnMouseMove(self, event): """ Handles the wx.EVT_MOTION event for ButtonPanel. """ # Check to see if we are hovering a button tabId, flags = self.HitTest(event.GetPosition()) if flags != BP_HT_BUTTON: self.RemoveHelp() self.RepaintOldSelection() self._currentButton = -1 return btn = self._vButtons[tabId] if not btn.IsEnabled(): self.RemoveHelp() self.RepaintOldSelection() return if tabId != self._currentButton: self.RepaintOldSelection() if btn.GetRect().Contains(event.GetPosition()): btn.SetStatus("Hover") else: btn.SetStatus("Normal") if tabId != self._currentButton: self.RemoveHelp() self.DoGiveHelp(btn) self._currentButton = tabId event.Skip() def OnLeftDown(self, event): """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """ tabId, hit = self.HitTest(event.GetPosition()) if hit == BP_HT_BUTTON: btn = self._vButtons[tabId] if btn.IsEnabled(): btn.SetStatus("Pressed") self._currentButton = tabId def OnRightDown(self, event): """ Handles the wx.EVT_RIGHT_DOWN event for ButtonPanel. """ tabId, hit = self.HitTest(event.GetPosition()) if hit == BP_HT_BUTTON: btn = self._vButtons[tabId] if btn.IsEnabled() and btn.GetMenu() != None: self.PopupMenu(btn.GetMenu()) def OnLeftUp(self, event): """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """ tabId, flags = self.HitTest(event.GetPosition()) if flags != BP_HT_BUTTON: return hit = self._vButtons[tabId] if hit.GetStatus() == "Disabled": return for btn in self._vButtons: if btn != hit: btn.SetFocus(False) if hit.GetStatus() == "Pressed": # Fire a button click event btnEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, hit.GetId()) self.GetEventHandler().ProcessEvent(btnEvent) hit.SetToggled(not hit.GetToggled()) # Update the button status to be hovered hit.SetStatus("Hover") hit.SetFocus() self._currentButton = tabId def OnMouseLeave(self, event): """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """ # Reset all buttons statuses for btn in self._vButtons: if not btn.IsEnabled(): continue btn.SetStatus("Normal") self.RemoveHelp() event.Skip() def OnMouseEnterWindow(self, event): """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """ tabId, flags = self.HitTest(event.GetPosition()) if flags == BP_HT_BUTTON: hit = self._vButtons[tabId] if hit.GetStatus() == "Disabled": event.Skip() return self.DoGiveHelp(hit) self._currentButton = tabId event.Skip() def DoGiveHelp(self, hit): """ Gives tooltips and help in StatusBar. """ if not self.GetUseHelp(): return shortHelp = hit.GetShortHelp() if shortHelp: self.SetToolTipString(shortHelp) self._haveTip = True longHelp = hit.GetLongHelp() if not longHelp: return topLevel = wx.GetTopLevelParent(self) if isinstance(topLevel, wx.Frame) and topLevel.GetStatusBar(): statusBar = topLevel.GetStatusBar() if self._statusTimer and self._statusTimer.IsRunning(): self._statusTimer.Stop() statusBar.PopStatusText(0) statusBar.PushStatusText(longHelp, 0) self._statusTimer = StatusBarTimer(self) self._statusTimer.Start(_DELAY, wx.TIMER_ONE_SHOT) def RemoveHelp(self): """ Removes the tooltips and statusbar help (if any) for a button. """ if not self.GetUseHelp(): return if self._haveTip: self.SetToolTipString("") self._haveTip = False if self._statusTimer and self._statusTimer.IsRunning(): topLevel = wx.GetTopLevelParent(self) statusBar = topLevel.GetStatusBar() self._statusTimer.Stop() statusBar.PopStatusText(0) self._statusTimer = None def RepaintOldSelection(self): """ Repaints the old selected/hovered button. """ current = self._currentButton if current == -1: return btn = self._vButtons[current] if not btn.IsEnabled(): return btn.SetStatus("Normal") def OnStatusBarTimer(self): """ Handles the timer expiring to delete the longHelp in the StatusBar. """ topLevel = wx.GetTopLevelParent(self) statusBar = topLevel.GetStatusBar() statusBar.PopStatusText(0) def SetUseHelp(self, useHelp=True): """ Sets whether or not shortHelp and longHelp should be displayed. """ self._useHelp = useHelp def GetUseHelp(self): """ Returns whether or not shortHelp and longHelp should be displayed. """ return self._useHelp def HitTest(self, pt): """ HitTest method for ButtonPanel. Returns the button (if any) and a flag (if any). """ for ii in xrange(len(self._vButtons)): if not self._vButtons[ii].IsEnabled(): continue if self._vButtons[ii].GetRect().Contains(pt): return ii, BP_HT_BUTTON return -1, BP_HT_NONE def GetBPArt(self): """ Returns the associated BPArt art provider. """ return self._art def SetBPArt(self, art): """ Sets a new BPArt to ButtonPanel. Useful only if another BPArt class is used. """ self._art = art self.Refresh() if wx.VERSION < (2,7,1,1): def Freeze(self): """Freeze ButtonPanel.""" self._freezeCount = self._freezeCount + 1 wx.PyPanel.Freeze(self) def Thaw(self): """Thaw ButtonPanel.""" if self._freezeCount == 0: raise "\nERROR: Thawing Unfrozen ButtonPanel?" self._freezeCount = self._freezeCount - 1 wx.PyPanel.Thaw(self) def IsFrozen(self): """ Returns whether a call to Freeze() has been done. """ return self._freezeCount != 0