+ 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
+
+ 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
+
+
+ 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
+
+
+ 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._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_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)
+
+
+ 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 RemoveAllButtons(self):
+ """ Remove all the buttons from ButtonPanel. """
+
+ self._vButtons = []
+
+
+ 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 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
+
+