+# --------------------------------------------------------------------------- #
+# FANCYBUTTONPANEL Widget wxPython IMPLEMENTATION
+#
+# Original C++ Code From Eran. You Can Find It At:
+#
+# http://wxforum.shadonet.com/viewtopic.php?t=6619
+#
+# License: wxWidgets license
+#
+#
+# Python Code By:
+#
+# Andrea Gavana, @ 02 Oct 2006
+# Latest Revision: 02 Oct 2006, 17.00 GMT
+#
+#
+# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
+# Write To Me At:
+#
+# andrea.gavana@gmail.com
+# gavana@kpo.kz
+#
+# Or, Obviously, To The wxPython Mailing List!!!
+#
+#
+# End Of Comments
+# --------------------------------------------------------------------------- #
+
+"""
+With `ButtonPanel` class you have a panel with gradient coloring
+on it and with the possibility to place some buttons on it. Using a
+standard panel with normal wx.Buttons leads to an ugly result: the
+buttons are placed correctly on the panel - but with grey area around
+them. Gradient coloring is kept behind the images - this was achieved
+due to the PNG format and the transparency of the bitmaps.
+
+The image are functioning like a buttons and can be caught in your
+code using the usual self.Bind(wx.EVT_BUTTON, self.OnButton) method.
+
+The control is generic, and support theming (well, I tested it under
+Windows with the three defauls themes: grey, blue, silver and the
+classic look).
+
+
+Usage
+-----
+
+The following example shows a simple implementation that uses ButtonPanel
+inside a very simple frame::
+
+ class MyFrame(wx.Frame):
+
+ def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition,
+ size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
+
+ wx.Frame.__init__(self, parent, id, title, pos, size, style)
+
+ mainPanel = wx.Panel(self, -1)
+ self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE)
+
+ vSizer = wx.BoxSizer(wx.VERTICAL)
+ mainPanel.SetSizer(vSizer)
+
+ alignment = BP_ALIGN_RIGHT
+
+ titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo")
+
+ btn1 = ButtonInfo(wx.NewId(), wx.Bitmap("png4.png", wx.BITMAP_TYPE_PNG))
+ titleBar.AddButton(btn1)
+ self.Bind(wx.EVT_BUTTON, self.OnButton, btn1)
+
+ btn2 = ButtonInfo(wx.NewId(), wx.Bitmap("png3.png", wx.BITMAP_TYPE_PNG))
+ titleBar.AddButton(btn2)
+ self.Bind(wx.EVT_BUTTON, self.OnButton, btn2)
+
+ btn3 = ButtonInfo(wx.NewId(), wx.Bitmap("png2.png", wx.BITMAP_TYPE_PNG))
+ titleBar.AddButton(btn3)
+ self.Bind(wx.EVT_BUTTON, self.OnButton, btn3)
+
+ btn4 = ButtonInfo(wx.NewId(), wx.Bitmap("png1.png", wx.BITMAP_TYPE_PNG))
+ titleBar.AddButton(btn4)
+ self.Bind(wx.EVT_BUTTON, self.OnButton, btn4)
+
+ 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.Layout()
+
+ # our normal wxApp-derived class, as usual
+
+ app = wx.PySimpleApp()
+
+ frame = MyFrame(None)
+ app.SetTopWindow(frame)
+ frame.Show()
+
+ app.MainLoop()
+
+
+License And Version:
+
+ButtonPanel Is Freeware And Distributed Under The wxPython License.
+
+Latest Revision: Andrea Gavana @ 02 Oct 2006, 17.00 GMT
+Version 0.1.
+
+"""
+
+
+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
+
+# 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
+
+# ButtonPanel default style
+BP_DEFAULT_STYLE = 2
+
+
+def BrightenColor(color, factor):
+ """ Bright the input colour by a factor."""
+
+ val = color.Red()*factor
+ if val > 255:
+ red = 255
+ else:
+ red = val
+
+ val = color.Green()*factor
+ if val > 255:
+ green = 255
+ else:
+ green = val
+
+ val = color.Blue()*factor
+ if val > 255:
+ blue = 255
+ else:
+ blue = val
+
+ return wx.Color(red, green, blue)
+
+
+# -- 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.
+
+ 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()
+
+
+ def GetBitmap(self):
+ """ Returns the associated bitmap. """
+
+ return self._bmp
+
+
+ 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 SetBitmap(self, bmp):
+ """ Sets the associated bitmap. """
+
+ self._bmp = bmp
+
+
+ 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)
+
+
+
+
+# -- ButtonPanel class implementation ----------------------------------
+# This is the main class.
+
+BASE = wx.PyPanel
+
+class ButtonPanel(BASE):
+
+ 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
+ """
+
+ BASE.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize,
+ wx.NO_BORDER, name=name)
+ self._vButtons = []
+
+ 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)
+
+
+ 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. """
+
+ self._vButtons = []
+ self.Refresh()
+
+
+ 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 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()
+
+ bh += 2*self._nPadding + 2*self._borderPenWidth
+ h = max(h, bh)
+
+ bw = (len(self._vButtons)+1) * (bw + 2*self._nPadding)
+ w += bw
+
+ return (w, h)
+
+
+
+ def OnPaint(self, event):
+ """ Handles the wx.EVT_PAINT event for ButtonPanel. """
+
+ dc = wx.BufferedPaintDC(self)
+ rect = self.GetClientRect()
+
+ ##print rect, self.GetRect(), self.GetBestSize(), self.GetMinSize()
+
+ # 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)
+
+ # Draw a rectangle around the panel
+ dc.SetBrush(backBrush)
+ dc.SetPen(borderPen)
+ dc.DrawRectangleRect(rect)
+
+ # Draw the text
+ textWidth, textHeight = 0, 0
+
+ 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)
+
+ textWidth, textHeight = dc.GetTextExtent(self._text)
+
+ if self._alignment == BP_ALIGN_RIGHT:
+ textX = self._nPadding
+ else:
+ textX = rect.width - textWidth - self._nPadding
+
+ 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
+
+ # 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
+
+ if self._alignment == BP_ALIGN_RIGHT:
+
+ leftEndX = self._nPadding + textWidth
+ posx = rect.width - nImageWidth - self._nPadding
+
+ for ii in xrange(len(self._vButtons)):
+
+ # Make sure we can keep drawing
+ if posx < leftEndX:
+ 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
+
+ else:
+
+ 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 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. """
+
+ 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):
+ """ Gradient fill from colour 1 to colour 2 with top to bottom. """
+
+ if rect.height < 1 or rect.width < 1:
+ return
+
+ size = rect.height
+
+ # calculate gradient coefficients
+ style = self.GetParent().GetWindowStyleFlag()
+ col2 = self._colorFrom
+ col1 = self._colorTo
+
+ 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):
+
+ 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)
+ rf += rstep
+ gf += gstep
+ bf += bstep
+
+
+ def OnMouseMove(self, event):
+ """ Handles the wx.EVT_MOTION event for ButtonPanel. """
+
+ # 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()
+
+
+ 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 OnLeftUp(self, event):
+ """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
+
+ tabId, hit = self.HitTest(event.GetPosition())
+
+ 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()
+
+
+ def OnMouseLeave(self, event):
+ """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
+
+ # 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 HitTest(self, pt):
+ """
+ HitTest method for ButtonPanel. Returns the button (if any) and
+ a flag (if any).
+ """
+
+ btnIdx = -1
+
+ for ii in xrange(len(self._vButtons)):
+ if self._vButtons[ii].GetRect().Inside(pt):
+ return ii, BP_HT_BUTTON
+
+ return -1, BP_HT_NONE
+
+
+