]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/buttonpanel.py
Added a set of button classes to wx.lib.buttons from David Hughes that
[wxWidgets.git] / wxPython / wx / lib / buttonpanel.py
index 0b3a26a8b16e834c19ec6c457d8971777ebb3b93..7df01da87d63a3578b3f61796cc7bb31fb267346 100644 (file)
@@ -11,7 +11,7 @@
 # Python Code By:
 #
 # Andrea Gavana, @ 02 Oct 2006
 # Python Code By:
 #
 # Andrea Gavana, @ 02 Oct 2006
-# Latest Revision: 02 Oct 2006, 17.00 GMT
+# Latest Revision: 17 Oct 2006, 17.00 GMT
 #
 #
 # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
 #
 #
 # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
@@ -45,6 +45,33 @@ classic look).
 Usage
 -----
 
 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::
 
 The following example shows a simple implementation that uses ButtonPanel
 inside a very simple frame::
 
@@ -81,8 +108,6 @@ inside a very simple frame::
           titleBar.AddButton(btn4)
           self.Bind(wx.EVT_BUTTON, self.OnButton, btn4)
 
           titleBar.AddButton(btn4)
           self.Bind(wx.EVT_BUTTON, self.OnButton, btn4)
 
-          titleBar.SetColor(BP_TEXT_COLOR, wx.WHITE) 
-          titleBar.SetColor(BP_CAPTION_BORDER_COLOR, wx.WHITE) 
           vSizer.Add(titleBar, 0, wx.EXPAND)
           vSizer.Add((20, 20))
           vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5)
           vSizer.Add(titleBar, 0, wx.EXPAND)
           vSizer.Add((20, 20))
           vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5)
@@ -104,38 +129,92 @@ License And Version:
 
 ButtonPanel Is Freeware And Distributed Under The wxPython License. 
 
 
 ButtonPanel Is Freeware And Distributed Under The wxPython License. 
 
-Latest Revision: Andrea Gavana @ 02 Oct 2006, 17.00 GMT
-Version 0.1.
+Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT
+Version 0.3.
 
 """
 
 
 import wx
 
 
 """
 
 
 import wx
 
-# Some constants
-BP_CAPTION_COLOR = 0, 
-BP_CAPTION_GRADIENT_COLOR = 1 
-BP_CAPTION_BORDER_COLOR = 2
-BP_TEXT_COLOR = 3
-
-# Buttons states 
-BP_BTN_NONE = 100
-BP_BTN_PRESSED = 101
-BP_BTN_HOVER = 102
+# 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
 
 # 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_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
 
 
-# ButtonPanel default style
-BP_DEFAULT_STYLE = 2 
+
+# 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 BrightenColor(color, factor): 
+def BrightenColour(color, factor): 
     """ Bright the input colour by a factor."""
 
     val = color.Red()*factor
     """ Bright the input colour by a factor."""
 
     val = color.Red()*factor
@@ -159,447 +238,1751 @@ def BrightenColor(color, factor):
     return wx.Color(red, green, blue) 
 
 
     return wx.Color(red, green, blue) 
 
 
-# -- 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:
-
-    def __init__(self, id=wx.ID_ANY, bmp=wx.NullBitmap, status=-1):
-        """
-        Default class constructor.
+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
 
 
-        Parameters:
-        - id: the button id;
-        - bmp: the associated bitmap;
-        - status: button status (pressed, hovered, None).
-        """
-        if id == wx.ID_ANY:
-            id = wx.NewId()
-        self._id = id
-        self._bmp = bmp
-        self._status = status
-        self._rect = wx.Rect()
+    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):
 
 
-    def GetBitmap(self):
-        """ Returns the associated bitmap. """
-
-        return self._bmp
-
-
-    def GetRect(self):
-        """ Returns the button rect. """
+        pixel = (data[i], data[i+1], data[i+2])
+        pixel = MakeGray(pixel, factor, maskColor)
 
 
-        return self._rect
+        for x in range(3):
+            data[i+x] = pixel[x]
 
 
+    anImage.SetData(''.join(map(chr, data)))
 
 
-    def GetStatus(self):
-        """ Returns the button status. """
+    anImage = anImage.ConvertToBitmap()
 
 
-        return self._status
+    return anImage
 
 
 
 
-    def GetId(self):
-        """ Returns the button id. """
+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))
         
         
-        return self._id
-
-
-    def SetRect(self, rect):
-        """ Sets the button rect. """
-
-        self._rect = rect
-
-
-    def SetBitmap(self, bmp):
-        """ Sets the associated bitmap. """
-
-        self._bmp = bmp
+        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. "
         
 
         
 
-    def SetStatus(self, status):
-        """ Sets the button status. """
-
-        self._status = status
-
-
-    def SetId(self, id):
-        """ Sets the button id. """
-
-        self._id = id
-
-    Bitmap = property(GetBitmap, SetBitmap)
-    Id     = property(GetId, SetId)
-    Rect   = property(GetRect, SetRect)
-    Status = property(GetStatus, SetStatus)
-    
+    GetColour = GetColor
+    SetColour = SetColor
 
 
-    
 
 
-# -- ButtonPanel class implementation ----------------------------------
-# This is the main class.
+    def SetFont(self, id, font):
+        """ Sets font for customizable options. """
 
 
-class ButtonPanel(wx.PyPanel):
+        if id == BP_TEXT_FONT:
+            self._caption_font = font
+        elif id == BP_BUTTONTEXT_FONT:
+            self._buttontext_font = font
 
 
-    def __init__(self, parent, id=wx.ID_ANY, text="", style=BP_DEFAULT_STYLE,
-                 alignment=BP_ALIGN_RIGHT, 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 = []
+    def GetFont(self, id):
+        """ Returns font of customizable options. """
 
 
-        self._text = text
-        self._nStyle = style
-        self._alignment = alignment
-        self._nPadding = 6
-        self._nBmpSize = 16
-        self._colorFrom = wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
-        self._colorTo = wx.WHITE
-        self._colorBorder = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
-        self._colorText = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
-        self._firsttime = True
-        self._borderPenWidth = 3
-        
-        self.Bind(wx.EVT_PAINT, self.OnPaint)
-        self.Bind(wx.EVT_SIZE, self.OnSize)
-        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)
+        if id == BP_TEXT_FONT:
+            return self._caption_font
+        elif id == BP_BUTTONTEXT_FONT:
+            return self._buttontext_font
         
         
+        return wx.NoneFont
 
 
-    def AddButton(self, btnInfo):
-        """
-        Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
-        this method. See the demo for details.
-        """
-
-        self._vButtons.append(btnInfo)
-        self.Refresh()
 
 
-    def RemoveAllButtons(self):
-        """ Remove all the buttons from ButtonPanel. """
+    def SetGradientType(self, gradient):
+        """ Sets the gradient type for BPArt drawings. """
 
 
-        self._vButtons = []
-        self.Refresh()
+        self._gradient_type = gradient
         
 
         
 
-    def GetAlignment(self):
-        """ Returns the button alignment (left, right). """
-
-        return self._alignment
-    
-
-    def SetAlignment(self, alignment):
-        """ Sets the button alignment (left, right). """
-
-        self._alignment = alignment
+    def GetGradientType(self):
+        """ Returns the gradient type for BPArt drawings. """
 
 
+        return self._gradient_type        
 
 
-    def DoGetBestSize(self):
-        w = h = 0
-        if self._text:
-            dc = wx.ClientDC(self)
-            boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) 
-            boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
-            dc.SetFont(boldFont)
-            tw, th = dc.GetTextExtent(self._text)
-            h = max(h, th)
-            w += tw + self._nPadding
-
-        if self._vButtons:
-            bh = self._vButtons[0].GetBitmap().GetHeight()
-            bw = self._vButtons[0].GetBitmap().GetWidth()
+        
+    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
             
             
-            bh += 2*self._nPadding + 2*self._borderPenWidth
-            h = max(h, bh)
+        dc.SetTextForeground(textColour) 
+        dc.SetFont(textFont)
 
 
-            bw = (len(self._vButtons)+1) * (bw + 2*self._nPadding)
-            w += bw
+        dc.DrawText(captionText, rect.x + padding.x, rect.y+padding.y)
             
             
-        return (w, h)
 
 
-
-
-    def OnPaint(self, event):
-        """ Handles the wx.EVT_PAINT event for ButtonPanel. """
+    def DrawButton(self, dc, rect, parentSize, buttonBitmap, isVertical,
+                   buttonStatus, isToggled, textAlignment, text=""):
+        """ Draws a button in ButtonPanel, together with its text (if any). """
         
         
-        dc = wx.BufferedPaintDC(self) 
-        rect = self.GetClientRect()
-
-        ##print rect, self.GetRect(), self.GetBestSize(), self.GetMinSize()
+        bmpxsize, bmpysize = buttonBitmap.GetWidth(), buttonBitmap.GetHeight()
+        dx = dy = focus = 0
         
         
-        # Draw gradient color in the background of the panel 
-        self.FillGradientColor(dc, rect) 
-
-        backBrush = wx.TRANSPARENT_BRUSH
-        borderPen = wx.Pen(self._colorBorder) 
-        size = self.GetSize() 
-        borderPen.SetWidth(self._borderPenWidth) 
+        borderw = self._caption_border_size
+        padding = self._padding_size
 
 
-        # Draw a rectangle around the panel 
-        dc.SetBrush(backBrush) 
-        dc.SetPen(borderPen) 
-        dc.DrawRectangleRect(rect) 
-
-        # Draw the text 
-        textWidth, textHeight = 0, 0
+        buttonFont = self._buttontext_font
+        dc.SetFont(buttonFont)
         
         
-        if self._text != "":
-         
-            dc.SetTextForeground(self._colorText) 
-            borderPen.SetWidth(2) 
-            boldFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) 
-            boldFont.SetWeight(wx.FONTWEIGHT_BOLD)
-            dc.SetFont(boldFont)
+        if isVertical:
             
             
-            textWidth, textHeight = dc.GetTextExtent(self._text)
+            rect = wx.Rect(borderw, rect.y, rect.width-2*borderw, rect.height)
+            
+            if text != "":
 
 
-            if self._alignment == BP_ALIGN_RIGHT:
-                textX = self._nPadding
+                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:
             else:
-                textX = rect.width - textWidth - self._nPadding
+                bmpxpos = rect.x + (rect.width - bmpxsize)/2
+                bmpypos = rect.y + (rect.height - bmpysize)/2
                 
                 
-            textY = (rect.GetHeight() - textHeight)/2 
-            dc.DrawText(self._text, textX, textY) 
-         
-        if self._vButtons:
-         
-            height = self._vButtons[0].GetBitmap().GetHeight() 
-            self._nBmpSize = self._vButtons[0].GetBitmap().GetWidth() 
-            height += 2*self._nPadding + 2*self._borderPenWidth
-
-            if self._firsttime:  # this probably isn't needed anymore now that DoGetBestSize is implemented...
-                self.GetContainingSizer().Layout()
-                self._firsttime = False
-            
-            # Draw all buttons 
-            # [ Padding | Text | .. Buttons .. | Padding ]
-            
-            totalWidth = rect.width - self._nPadding*2 - textWidth 
+                
+        else:
 
 
-            # The button is drawn inside a circle with padding of self._nPadding around it 
-            # so the width of each image = imageWidth + 2 * self._nPadding 
-            nImageWidth = self._nBmpSize + 2*self._nPadding 
+            rect = wx.Rect(rect.x, borderw, rect.width, rect.height-2*borderw)
 
 
-            if self._alignment == BP_ALIGN_RIGHT:
+            if text != "":
 
 
-                leftEndX = self._nPadding + textWidth 
-                posx = rect.width - nImageWidth - self._nPadding
+                textW, textH = dc.GetTextExtent(text)
                 
                 
-                for ii in xrange(len(self._vButtons)):
-                 
-                    # Make sure we can keep drawing 
-                    if posx < leftEndX:
-                        break 
+                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 rectangle around the buttons 
-                    if self._vButtons[ii].GetStatus() == BP_BTN_HOVER:
-                     
-                        dc.SetBrush(wx.Brush(wx.Color(225, 225, 255))) 
-                        dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION))) 
-                        dc.DrawRectangle(posx, self._borderPenWidth, nImageWidth, nImageWidth) 
-                        dc.DrawBitmap(self._vButtons[ii].GetBitmap(), posx + self._nPadding, self._nPadding + self._borderPenWidth, True) 
-                     
-                    elif self._vButtons[ii].GetStatus() == BP_BTN_PRESSED:
-                     
-                        dc.SetBrush(wx.Brush(wx.Color(225, 225, 255))) 
-                        dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION))) 
-                        dc.DrawRectangle(posx, self._borderPenWidth, nImageWidth, nImageWidth) 
-                        dc.DrawBitmap(self._vButtons[ii].GetBitmap(), posx + self._nPadding, self._nPadding + self._borderPenWidth + 1, True) 
-                     
-                    else:
-                     
-                        dc.DrawBitmap(self._vButtons[ii].GetBitmap(), posx + self._nPadding, self._nPadding + self._borderPenWidth, True) 
-                 
-                    self._vButtons[ii].SetRect(wx.Rect(posx, self._borderPenWidth, nImageWidth, nImageWidth)) 
-                    posx -= nImageWidth 
+        # Draw a button 
+        # [ Padding | Text | .. Buttons .. | Padding ]
 
 
-            else:
+        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)
 
 
-                rightStartX = textX - self._nPadding - nImageWidth
-                posx = self._nPadding
                 
                 
-                for ii in xrange(len(self._vButtons)):
-                 
-                    # Make sure we can keep drawing 
-                    if posx > rightStartX:
-                        break 
-                    
-                    # Draw a rectangle around the buttons 
-                    if self._vButtons[ii].GetStatus() == BP_BTN_HOVER:
-                     
-                        dc.SetBrush(wx.Brush(wx.Color(225, 225, 255))) 
-                        dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION))) 
-                        dc.DrawRectangle(posx, self._borderPenWidth, nImageWidth, nImageWidth) 
-                        dc.DrawBitmap(self._vButtons[ii].GetBitmap(), posx + self._nPadding, self._nPadding + self._borderPenWidth, True) 
-                     
-                    elif self._vButtons[ii].GetStatus() == BP_BTN_PRESSED:
-                     
-                        dc.SetBrush(wx.Brush(wx.Color(225, 225, 255))) 
-                        dc.SetPen(wx.Pen(wx.SystemSettings.GetColour(wx.SYS_COLOUR_ACTIVECAPTION))) 
-                        dc.DrawRectangle(posx, self._borderPenWidth, nImageWidth, nImageWidth) 
-                        dc.DrawBitmap(self._vButtons[ii].GetBitmap(), posx + self._nPadding, self._nPadding + self._borderPenWidth + 1, True) 
-                     
-                    else:
-                     
-                        dc.DrawBitmap(self._vButtons[ii].GetBitmap(), posx + self._nPadding, self._nPadding + self._borderPenWidth, True) 
-                 
-                    self._vButtons[ii].SetRect(wx.Rect(posx, self._borderPenWidth, nImageWidth, nImageWidth)) 
-                    posx += nImageWidth 
-               
-            # Update all other buttons that they are not drawn (by setting the rect to 0) 
-            for cur in xrange(ii+1, len(self._vButtons)):
-                self._vButtons[cur].SetRect(wx.Rect(0, 0, 0, 0)) 
+    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 OnEraseBackground(self, event):
-        """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
+
+    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) 
         
         
-        pass
-    
-    def OnSize(self, event):
-        """ Handles the wx.EVT_SIZE event for ButtonPanel. """
-        self.Refresh() 
-        event.Skip() 
 
 
-    def SetColor(self, switch, color):
-        """
-        Sets the color depending on switch:
-        - BP_CAPTION_COLOR: caption color;
-        - BP_CAPTION_GRADIENT_COLOR: gradient color; 
-        - BP_CAPTION_BORDER_COLOR; border color;
-        - BP_TEXT_COLOR: text color. 
-         """
-         
-        if switch == BP_CAPTION_COLOR:
-            self._colorFrom = color  
-        elif switch == BP_CAPTION_GRADIENT_COLOR:
-            self._colorTo = color 
-        elif switch == BP_CAPTION_BORDER_COLOR:
-            self._colorBorder = color 
-        elif switch == BP_TEXT_COLOR:
-            self._colorText = color 
-         
     def FillGradientColor(self, dc, rect):
     def FillGradientColor(self, dc, rect):
-        """ Gradient fill from colour 1 to colour 2 with top to bottom. """
+        """ 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 
 
 
         if rect.height < 1 or rect.width < 1: 
             return 
 
-        size = rect.height 
+        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
 
 
-        # calculate gradient coefficients 
-        style = self.GetParent().GetWindowStyleFlag() 
-        col2 = self._colorFrom 
-        col1 = self._colorTo 
+        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)
 
 
         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 y in xrange(rect.y, rect.y + 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)) 
          
             currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf)
             dc.SetBrush(wx.Brush(currCol, wx.SOLID)) 
-            dc.SetPen(wx.Pen(currCol)) 
-            dc.DrawLine(rect.x, y, rect.x + rect.width, y) 
+            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 
             rf += rstep
             gf += gstep
             bf += bstep 
+                
 
 
-         
-    def OnMouseMove(self, event):
-        """ Handles the wx.EVT_MOTION event for ButtonPanel. """
+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!
+        """
         
         
-        # Check to see if we are hovering a button
-        for ii in xrange(len(self._vButtons)):
-            if self._vButtons[ii].GetRect().Inside(event.GetPosition()):
-                self._vButtons[ii].SetStatus(BP_BTN_HOVER) 
-            else:
-                self._vButtons[ii].SetStatus(BP_BTN_NONE) 
-                 
-        self.Refresh() 
-        event.Skip() 
+        wx.Timer.__init__(self)
+        self._owner = owner        
 
 
-    def OnLeftDown(self, event):
-        """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
-        tabId, hit = self.HitTest(event.GetPosition())
 
 
-        if hit == BP_HT_BUTTON:
-         
-            self._vButtons[tabId].SetStatus(BP_BTN_PRESSED) 
-            self.Refresh() 
+    def Notify(self):
+        """The timer has expired."""
 
 
+        self._owner.OnStatusBarTimer()
 
 
-    def OnLeftUp(self, event):
-        """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
         
         
-        tabId, hit = self.HitTest(event.GetPosition())
+class Control(wx.EvtHandler):
+
+    def __init__(self, parent, size=wx.Size(-1, -1)):
+        """
+        Default class constructor.
         
         
-        if hit == BP_HT_BUTTON:
-            if self._vButtons[tabId].GetStatus() == BP_BTN_PRESSED:                 
-                # Fire a button click event 
-                btnEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self._vButtons[tabId].GetId())
-                self.GetEventHandler().ProcessEvent(btnEvent) 
-
-                # Update the button status to be hovered 
-                self._vButtons[tabId].SetStatus(BP_BTN_HOVER) 
-                self.Refresh() 
-                 
+        Base class for all pseudo controls
+        parent = parent object
+        size = (width, height)
+        """
 
 
-    def OnMouseLeave(self, event):
-        """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
+        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        
         
         
-        # Reset all buttons statuses 
-        for ii in xrange(len(self._vButtons)):
-            self._vButtons[ii].SetStatus(BP_BTN_NONE)
-            
-        self.Refresh() 
-        event.Skip() 
 
 
-    def OnMouseEnterWindow(self, event):
-        """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
-         
-        event.Skip() 
+    def GetId(self):
+        """ Returns the control id. """
 
 
-    def HitTest(self, pt):
-        """
-        HitTest method for ButtonPanel. Returns the button (if any) and
-        a flag (if any).
-        """
-         
-        btnIdx = -1 
+        return self._id
+    
 
 
-        for ii in xrange(len(self._vButtons)):
-            if self._vButtons[ii].GetRect().Inside(pt):
-                return ii, BP_HT_BUTTON
+    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
 
 
-        return -1, BP_HT_NONE
 
 
+    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
+
+        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
+
+