Mercurial > traipse_dev
diff orpg/tools/ButtonPanel.py @ 0:4385a7d0efd1 grumpy-goblin
Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author | sirebral |
---|---|
date | Tue, 14 Jul 2009 16:41:58 -0500 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/orpg/tools/ButtonPanel.py Tue Jul 14 16:41:58 2009 -0500 @@ -0,0 +1,1602 @@ +# --------------------------------------------------------------------------- # +# 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