wxWidgets/wxPython/wx/lib/buttonpanel.py
Robin Dunn 66dae888d1 Added the wx.lib.buttonpanel module, which is a tweaked version of
Andrea Gavana's FancyButtonPanel module.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@41605 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2006-10-03 21:23:17 +00:00

608 lines
19 KiB
Python

# --------------------------------------------------------------------------- #
# 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