2356118e69
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@42321 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1989 lines
60 KiB
Python
1989 lines
60 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: 17 Oct 2006, 17.00 GMT
|
|
#
|
|
#
|
|
# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
|
|
# Write To Me At:
|
|
#
|
|
# andrea.gavana@gmail.com
|
|
# gavana@kpo.kz
|
|
#
|
|
# Or, Obviously, To The wxPython Mailing List!!!
|
|
#
|
|
#
|
|
# End Of Comments
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
"""
|
|
With `ButtonPanel` class you have a panel with gradient coloring
|
|
on it and with the possibility to place some buttons on it. Using a
|
|
standard panel with normal wx.Buttons leads to an ugly result: the
|
|
buttons are placed correctly on the panel - but with grey area around
|
|
them. Gradient coloring is kept behind the images - this was achieved
|
|
due to the PNG format and the transparency of the bitmaps.
|
|
|
|
The image are functioning like a buttons and can be caught in your
|
|
code using the usual self.Bind(wx.EVT_BUTTON, self.OnButton) method.
|
|
|
|
The control is generic, and support theming (well, I tested it under
|
|
Windows with the three defauls themes: grey, blue, silver and the
|
|
classic look).
|
|
|
|
|
|
Usage
|
|
-----
|
|
|
|
ButtonPanel supports 4 alignments: left, right, top, bottom, which have a
|
|
different meaning and behavior wrt wx.Toolbar. The easiest thing is to try
|
|
the demo to understand, but I'll try to explain how it works.
|
|
|
|
CASE 1: ButtonPanel has a main caption text
|
|
|
|
Left alignment means ButtonPanel is horizontal, with the text aligned to the
|
|
left. When you shrink the demo frame, if there is not enough room for all
|
|
the controls to be shown, the controls closest to the text are hidden;
|
|
|
|
Right alignment means ButtonPanel is horizontal, with the text aligned to the
|
|
right. Item layout as above;
|
|
|
|
Top alignment means ButtonPanel is vertical, with the text aligned to the top.
|
|
Item layout as above;
|
|
|
|
Bottom alignment means ButtonPanel is vertical, with the text aligned to the
|
|
bottom. Item layout as above.
|
|
|
|
|
|
CASE 2: ButtonPanel has *no* main caption text
|
|
In this case, left and right alignment are the same (as top and bottom are the same),
|
|
but the layout strategy changes: now if there is not enough room for all the controls
|
|
to be shown, the last added items are hidden ("last" means on the far right for
|
|
horizontal ButtonPanels and far bottom for vertical ButtonPanels).
|
|
|
|
|
|
The following example shows a simple implementation that uses ButtonPanel
|
|
inside a very simple frame::
|
|
|
|
class MyFrame(wx.Frame):
|
|
|
|
def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition,
|
|
size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
|
|
|
|
wx.Frame.__init__(self, parent, id, title, pos, size, style)
|
|
|
|
mainPanel = wx.Panel(self, -1)
|
|
self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE)
|
|
|
|
vSizer = wx.BoxSizer(wx.VERTICAL)
|
|
mainPanel.SetSizer(vSizer)
|
|
|
|
alignment = BP_ALIGN_RIGHT
|
|
|
|
titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo")
|
|
|
|
btn1 = ButtonInfo(wx.NewId(), wx.Bitmap("png4.png", wx.BITMAP_TYPE_PNG))
|
|
titleBar.AddButton(btn1)
|
|
self.Bind(wx.EVT_BUTTON, self.OnButton, btn1)
|
|
|
|
btn2 = ButtonInfo(wx.NewId(), wx.Bitmap("png3.png", wx.BITMAP_TYPE_PNG))
|
|
titleBar.AddButton(btn2)
|
|
self.Bind(wx.EVT_BUTTON, self.OnButton, btn2)
|
|
|
|
btn3 = ButtonInfo(wx.NewId(), wx.Bitmap("png2.png", wx.BITMAP_TYPE_PNG))
|
|
titleBar.AddButton(btn3)
|
|
self.Bind(wx.EVT_BUTTON, self.OnButton, btn3)
|
|
|
|
btn4 = ButtonInfo(wx.NewId(), wx.Bitmap("png1.png", wx.BITMAP_TYPE_PNG))
|
|
titleBar.AddButton(btn4)
|
|
self.Bind(wx.EVT_BUTTON, self.OnButton, btn4)
|
|
|
|
vSizer.Add(titleBar, 0, wx.EXPAND)
|
|
vSizer.Add((20, 20))
|
|
vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5)
|
|
|
|
vSizer.Layout()
|
|
|
|
# our normal wxApp-derived class, as usual
|
|
|
|
app = wx.PySimpleApp()
|
|
|
|
frame = MyFrame(None)
|
|
app.SetTopWindow(frame)
|
|
frame.Show()
|
|
|
|
app.MainLoop()
|
|
|
|
|
|
License And Version:
|
|
|
|
ButtonPanel Is Freeware And Distributed Under The wxPython License.
|
|
|
|
Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT
|
|
Version 0.3.
|
|
|
|
"""
|
|
|
|
|
|
import wx
|
|
|
|
# Some constants to tune the BPArt class
|
|
BP_BACKGROUND_COLOR = 0
|
|
""" Background brush colour when no gradient shading exists. """
|
|
BP_GRADIENT_COLOR_FROM = 1
|
|
""" Starting gradient colour, used only when BP_USE_GRADIENT style is applied. """
|
|
BP_GRADIENT_COLOR_TO = 2
|
|
""" Ending gradient colour, used only when BP_USE_GRADIENT style is applied. """
|
|
BP_BORDER_COLOR = 3
|
|
""" Pen colour to paint the border of ButtonPanel. """
|
|
BP_TEXT_COLOR = 4
|
|
""" Main ButtonPanel caption colour. """
|
|
BP_BUTTONTEXT_COLOR = 5
|
|
""" Text colour for buttons with text. """
|
|
BP_BUTTONTEXT_INACTIVE_COLOR = 6
|
|
""" Text colour for inactive buttons with text. """
|
|
BP_SELECTION_BRUSH_COLOR = 7
|
|
""" Brush colour to be used when hovering or selecting a button. """
|
|
BP_SELECTION_PEN_COLOR = 8
|
|
""" Pen colour to be used when hovering or selecting a button. """
|
|
BP_SEPARATOR_COLOR = 9
|
|
""" Pen colour used to paint the separators. """
|
|
BP_TEXT_FONT = 10
|
|
""" Font of the ButtonPanel main caption. """
|
|
BP_BUTTONTEXT_FONT = 11
|
|
""" Text font for the buttons with text. """
|
|
|
|
BP_BUTTONTEXT_ALIGN_BOTTOM = 12
|
|
""" Flag that indicates the image and text in buttons is stacked. """
|
|
BP_BUTTONTEXT_ALIGN_RIGHT = 13
|
|
""" Flag that indicates the text is shown alongside the image in buttons with text. """
|
|
|
|
BP_SEPARATOR_SIZE = 14
|
|
"""
|
|
Separator size. NB: This is not the line width, but the sum of the space before
|
|
and after the separator line plus the width of the line.
|
|
"""
|
|
BP_MARGINS_SIZE = 15
|
|
"""
|
|
Size of the left/right margins in ButtonPanel (top/bottom for vertically
|
|
aligned ButtonPanels).
|
|
"""
|
|
BP_BORDER_SIZE = 16
|
|
""" Size of the border. """
|
|
BP_PADDING_SIZE = 17
|
|
""" Inter-tool separator size. """
|
|
|
|
# Caption Gradient Type
|
|
BP_GRADIENT_NONE = 0
|
|
""" No gradient shading should be used to paint the background. """
|
|
BP_GRADIENT_VERTICAL = 1
|
|
""" Vertical gradient shading should be used to paint the background. """
|
|
BP_GRADIENT_HORIZONTAL = 2
|
|
""" Horizontal gradient shading should be used to paint the background. """
|
|
|
|
# Flags for HitTest() method
|
|
BP_HT_BUTTON = 200
|
|
BP_HT_NONE = 201
|
|
|
|
# Alignment of buttons in the panel
|
|
BP_ALIGN_RIGHT = 1
|
|
BP_ALIGN_LEFT = 2
|
|
BP_ALIGN_TOP = 4
|
|
BP_ALIGN_BOTTOM = 8
|
|
|
|
# ButtonPanel styles
|
|
BP_DEFAULT_STYLE = 1
|
|
BP_USE_GRADIENT = 2
|
|
|
|
# Delay used to cancel the longHelp in the statusbar field
|
|
_DELAY = 3000
|
|
|
|
|
|
# Check for the new method in 2.7 (not present in 2.6.3.3)
|
|
if wx.VERSION_STRING < "2.7":
|
|
wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point)
|
|
|
|
|
|
def BrightenColour(color, factor):
|
|
""" Bright the input colour by a factor."""
|
|
|
|
val = color.Red()*factor
|
|
if val > 255:
|
|
red = 255
|
|
else:
|
|
red = val
|
|
|
|
val = color.Green()*factor
|
|
if val > 255:
|
|
green = 255
|
|
else:
|
|
green = val
|
|
|
|
val = color.Blue()*factor
|
|
if val > 255:
|
|
blue = 255
|
|
else:
|
|
blue = val
|
|
|
|
return wx.Color(red, green, blue)
|
|
|
|
|
|
def GrayOut(anImage):
|
|
"""
|
|
Convert the given image (in place) to a grayed-out version,
|
|
appropriate for a 'Disabled' appearance.
|
|
"""
|
|
|
|
factor = 0.7 # 0 < f < 1. Higher Is Grayer
|
|
|
|
anImage = anImage.ConvertToImage()
|
|
if anImage.HasAlpha():
|
|
anImage.ConvertAlphaToMask(1)
|
|
|
|
if anImage.HasMask():
|
|
maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
|
|
else:
|
|
maskColor = None
|
|
|
|
data = map(ord, list(anImage.GetData()))
|
|
|
|
for i in range(0, len(data), 3):
|
|
|
|
pixel = (data[i], data[i+1], data[i+2])
|
|
pixel = MakeGray(pixel, factor, maskColor)
|
|
|
|
for x in range(3):
|
|
data[i+x] = pixel[x]
|
|
|
|
anImage.SetData(''.join(map(chr, data)))
|
|
|
|
anImage = anImage.ConvertToBitmap()
|
|
|
|
return anImage
|
|
|
|
|
|
def MakeGray((r,g,b), factor, maskColor):
|
|
"""
|
|
Make a pixel grayed-out. If the pixel matches the maskColor, it won't be
|
|
changed.
|
|
"""
|
|
|
|
if (r,g,b) != maskColor:
|
|
return map(lambda x: int((230 - x) * factor) + x, (r,g,b))
|
|
else:
|
|
return (r,g,b)
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# Class BPArt
|
|
# Handles all the drawings for buttons, separators and text and allows the
|
|
# programmer to set colours, sizes and gradient shadings for ButtonPanel
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class BPArt:
|
|
"""
|
|
BPArt is an art provider class which does all of the drawing for ButtonPanel.
|
|
This allows the library caller to customize the BPArt or to completely replace
|
|
all drawing with custom BPArts.
|
|
"""
|
|
|
|
def __init__(self, parentStyle):
|
|
""" Default class constructor. """
|
|
|
|
base_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)
|
|
|
|
self._background_brush = wx.Brush(base_color, wx.SOLID)
|
|
self._gradient_color_to = wx.WHITE
|
|
self._gradient_color_from = wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
|
|
|
|
if parentStyle & BP_USE_GRADIENT:
|
|
self._border_pen = wx.Pen(wx.WHITE, 3)
|
|
self._caption_text_color = wx.WHITE
|
|
self._buttontext_color = wx.Colour(70, 143, 255)
|
|
self._separator_pen = wx.Pen(BrightenColour(self._gradient_color_from, 1.4))
|
|
self._gradient_type = BP_GRADIENT_VERTICAL
|
|
else:
|
|
self._border_pen = wx.Pen(BrightenColour(base_color, 0.9), 3)
|
|
self._caption_text_color = wx.BLACK
|
|
self._buttontext_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNTEXT)
|
|
self._separator_pen = wx.Pen(BrightenColour(base_color, 0.9))
|
|
self._gradient_type = BP_GRADIENT_NONE
|
|
|
|
self._buttontext_inactive_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT)
|
|
self._selection_brush = wx.Brush(wx.Color(225, 225, 255))
|
|
self._selection_pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION))
|
|
|
|
sysfont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
|
|
self._caption_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.BOLD,
|
|
False, sysfont.GetFaceName())
|
|
self._buttontext_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.NORMAL,
|
|
False, sysfont.GetFaceName())
|
|
|
|
self._separator_size = 7
|
|
self._margins_size = wx.Size(6, 6)
|
|
self._caption_border_size = 3
|
|
self._padding_size = wx.Size(6, 6)
|
|
|
|
|
|
def GetMetric(self, id):
|
|
""" Returns sizes of customizable options. """
|
|
|
|
if id == BP_SEPARATOR_SIZE:
|
|
return self._separator_size
|
|
elif id == BP_MARGINS_SIZE:
|
|
return self._margins_size
|
|
elif id == BP_BORDER_SIZE:
|
|
return self._caption_border_size
|
|
elif id == BP_PADDING_SIZE:
|
|
return self._padding_size
|
|
else:
|
|
raise "\nERROR: Invalid Metric Ordinal. "
|
|
|
|
|
|
def SetMetric(self, id, new_val):
|
|
""" Sets sizes for customizable options. """
|
|
|
|
if id == BP_SEPARATOR_SIZE:
|
|
self._separator_size = new_val
|
|
elif id == BP_MARGINS_SIZE:
|
|
self._margins_size = new_val
|
|
elif id == BP_BORDER_SIZE:
|
|
self._caption_border_size = new_val
|
|
self._border_pen.SetWidth(new_val)
|
|
elif id == BP_PADDING_SIZE:
|
|
self._padding_size = new_val
|
|
else:
|
|
raise "\nERROR: Invalid Metric Ordinal. "
|
|
|
|
|
|
def GetColor(self, id):
|
|
""" Returns colours of customizable options. """
|
|
|
|
if id == BP_BACKGROUND_COLOR:
|
|
return self._background_brush.GetColour()
|
|
elif id == BP_GRADIENT_COLOR_FROM:
|
|
return self._gradient_color_from
|
|
elif id == BP_GRADIENT_COLOR_TO:
|
|
return self._gradient_color_to
|
|
elif id == BP_BORDER_COLOR:
|
|
return self._border_pen.GetColour()
|
|
elif id == BP_TEXT_COLOR:
|
|
return self._caption_text_color
|
|
elif id == BP_BUTTONTEXT_COLOR:
|
|
return self._buttontext_color
|
|
elif id == BP_BUTTONTEXT_INACTIVE_COLOR:
|
|
return self._buttontext_inactive_color
|
|
elif id == BP_SELECTION_BRUSH_COLOR:
|
|
return self._selection_brush.GetColour()
|
|
elif id == BP_SELECTION_PEN_COLOR:
|
|
return self._selection_pen.GetColour()
|
|
elif id == BP_SEPARATOR_COLOR:
|
|
return self._separator_pen.GetColour()
|
|
else:
|
|
raise "\nERROR: Invalid Colour Ordinal. "
|
|
|
|
|
|
def SetColor(self, id, colour):
|
|
""" Sets colours for customizable options. """
|
|
|
|
if id == BP_BACKGROUND_COLOR:
|
|
self._background_brush.SetColour(colour)
|
|
elif id == BP_GRADIENT_COLOR_FROM:
|
|
self._gradient_color_from = colour
|
|
elif id == BP_GRADIENT_COLOR_TO:
|
|
self._gradient_color_to = colour
|
|
elif id == BP_BORDER_COLOR:
|
|
self._border_pen.SetColour(colour)
|
|
elif id == BP_TEXT_COLOR:
|
|
self._caption_text_color = colour
|
|
elif id == BP_BUTTONTEXT_COLOR:
|
|
self._buttontext_color = colour
|
|
elif id == BP_BUTTONTEXT_INACTIVE_COLOR:
|
|
self._buttontext_inactive_color = colour
|
|
elif id == BP_SELECTION_BRUSH_COLOR:
|
|
self._selection_brush.SetColour(colour)
|
|
elif id == BP_SELECTION_PEN_COLOR:
|
|
self._selection_pen.SetColour(colour)
|
|
elif id == BP_SEPARATOR_COLOR:
|
|
self._separator_pen.SetColour(colour)
|
|
else:
|
|
raise "\nERROR: Invalid Colour Ordinal. "
|
|
|
|
|
|
GetColour = GetColor
|
|
SetColour = SetColor
|
|
|
|
|
|
def SetFont(self, id, font):
|
|
""" Sets font for customizable options. """
|
|
|
|
if id == BP_TEXT_FONT:
|
|
self._caption_font = font
|
|
elif id == BP_BUTTONTEXT_FONT:
|
|
self._buttontext_font = font
|
|
|
|
|
|
def GetFont(self, id):
|
|
""" Returns font of customizable options. """
|
|
|
|
if id == BP_TEXT_FONT:
|
|
return self._caption_font
|
|
elif id == BP_BUTTONTEXT_FONT:
|
|
return self._buttontext_font
|
|
|
|
return wx.NoneFont
|
|
|
|
|
|
def SetGradientType(self, gradient):
|
|
""" Sets the gradient type for BPArt drawings. """
|
|
|
|
self._gradient_type = gradient
|
|
|
|
|
|
def GetGradientType(self):
|
|
""" Returns the gradient type for BPArt drawings. """
|
|
|
|
return self._gradient_type
|
|
|
|
|
|
def DrawSeparator(self, dc, rect, isVertical):
|
|
""" Draws a separator in ButtonPanel. """
|
|
|
|
dc.SetPen(self._separator_pen)
|
|
|
|
if isVertical:
|
|
ystart = yend = rect.y + rect.height/2
|
|
xstart = int(rect.x + 1.5*self._caption_border_size)
|
|
xend = int(rect.x + rect.width - 1.5*self._caption_border_size)
|
|
dc.DrawLine(xstart, ystart, xend, yend)
|
|
else:
|
|
xstart = xend = rect.x + rect.width/2
|
|
ystart = int(rect.y + 1.5*self._caption_border_size)
|
|
yend = int(rect.y + rect.height - 1.5*self._caption_border_size)
|
|
dc.DrawLine(xstart, ystart, xend, yend)
|
|
|
|
|
|
def DrawCaption(self, dc, rect, captionText):
|
|
""" Draws the main caption text in ButtonPanel. """
|
|
|
|
textColour = self._caption_text_color
|
|
textFont = self._caption_font
|
|
padding = self._padding_size
|
|
|
|
dc.SetTextForeground(textColour)
|
|
dc.SetFont(textFont)
|
|
|
|
dc.DrawText(captionText, rect.x + padding.x, rect.y+padding.y)
|
|
|
|
|
|
def DrawButton(self, dc, rect, parentSize, buttonBitmap, isVertical,
|
|
buttonStatus, isToggled, textAlignment, text=""):
|
|
""" Draws a button in ButtonPanel, together with its text (if any). """
|
|
|
|
bmpxsize, bmpysize = buttonBitmap.GetWidth(), buttonBitmap.GetHeight()
|
|
dx = dy = focus = 0
|
|
|
|
borderw = self._caption_border_size
|
|
padding = self._padding_size
|
|
|
|
buttonFont = self._buttontext_font
|
|
dc.SetFont(buttonFont)
|
|
|
|
if isVertical:
|
|
|
|
rect = wx.Rect(borderw, rect.y, rect.width-2*borderw, rect.height)
|
|
|
|
if text != "":
|
|
|
|
textW, textH = dc.GetTextExtent(text)
|
|
|
|
if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT:
|
|
fullExtent = bmpxsize + padding.x/2 + textW
|
|
bmpypos = rect.y + (rect.height - bmpysize)/2
|
|
bmpxpos = rect.x + (rect.width - fullExtent)/2
|
|
textxpos = bmpxpos + padding.x/2 + bmpxsize
|
|
textypos = bmpypos + (bmpysize - textH)/2
|
|
else:
|
|
bmpxpos = rect.x + (rect.width - bmpxsize)/2
|
|
bmpypos = rect.y + padding.y
|
|
textxpos = rect.x + (rect.width - textW)/2
|
|
textypos = bmpypos + bmpysize + padding.y/2
|
|
else:
|
|
bmpxpos = rect.x + (rect.width - bmpxsize)/2
|
|
bmpypos = rect.y + (rect.height - bmpysize)/2
|
|
|
|
|
|
else:
|
|
|
|
rect = wx.Rect(rect.x, borderw, rect.width, rect.height-2*borderw)
|
|
|
|
if text != "":
|
|
|
|
textW, textH = dc.GetTextExtent(text)
|
|
|
|
if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT:
|
|
fullExtent = bmpxsize + padding.x/2 + textW
|
|
bmpypos = rect.y + (rect.height - bmpysize)/2
|
|
bmpxpos = rect.x + (rect.width - fullExtent)/2
|
|
textxpos = bmpxpos + padding.x/2 + bmpxsize
|
|
textypos = bmpypos + (bmpysize - textH)/2
|
|
else:
|
|
fullExtent = bmpysize + padding.y/2 + textH
|
|
bmpxpos = rect.x + (rect.width - bmpxsize)/2
|
|
bmpypos = rect.y + (rect.height - fullExtent)/2
|
|
textxpos = rect.x + (rect.width - textW)/2
|
|
textypos = bmpypos + bmpysize + padding.y/2
|
|
else:
|
|
bmpxpos = rect.x + (rect.width - bmpxsize)/2
|
|
bmpypos = rect.y + (rect.height - bmpysize)/2
|
|
|
|
# Draw a button
|
|
# [ Padding | Text | .. Buttons .. | Padding ]
|
|
|
|
if buttonStatus in ["Pressed", "Toggled", "Hover"]:
|
|
dc.SetBrush(self._selection_brush)
|
|
dc.SetPen(self._selection_pen)
|
|
dc.DrawRoundedRectangleRect(rect, 4)
|
|
|
|
if buttonStatus == "Pressed" or isToggled:
|
|
dx = dy = 1
|
|
|
|
dc.DrawBitmap(buttonBitmap, bmpxpos+dx, bmpypos+dy, True)
|
|
|
|
if text != "":
|
|
isEnabled = buttonStatus != "Disabled"
|
|
self.DrawLabel(dc, text, isEnabled, textxpos+dx, textypos+dy)
|
|
|
|
|
|
def DrawLabel(self, dc, text, isEnabled, xpos, ypos):
|
|
""" Draws the label for a button. """
|
|
|
|
if not isEnabled:
|
|
dc.SetTextForeground(self._buttontext_inactive_color)
|
|
else:
|
|
dc.SetTextForeground(self._buttontext_color)
|
|
|
|
dc.DrawText(text, xpos, ypos)
|
|
|
|
|
|
def DrawButtonPanel(self, dc, rect, style):
|
|
""" Paint the ButtonPanel's background. """
|
|
|
|
if style & BP_USE_GRADIENT:
|
|
# Draw gradient color in the backgroud of the panel
|
|
self.FillGradientColor(dc, rect)
|
|
|
|
# Draw a rectangle around the panel
|
|
backBrush = (style & BP_USE_GRADIENT and [wx.TRANSPARENT_BRUSH] or \
|
|
[self._background_brush])[0]
|
|
|
|
dc.SetBrush(backBrush)
|
|
dc.SetPen(self._border_pen)
|
|
dc.DrawRectangleRect(rect)
|
|
|
|
|
|
def FillGradientColor(self, dc, rect):
|
|
""" Gradient fill from colour 1 to colour 2 with top to bottom or left to right. """
|
|
|
|
if rect.height < 1 or rect.width < 1:
|
|
return
|
|
|
|
isVertical = self._gradient_type == BP_GRADIENT_VERTICAL
|
|
size = (isVertical and [rect.height] or [rect.width])[0]
|
|
start = (isVertical and [rect.y] or [rect.x])[0]
|
|
|
|
# calculate gradient coefficients
|
|
|
|
col2 = self._gradient_color_from
|
|
col1 = self._gradient_color_to
|
|
|
|
rf, gf, bf = 0, 0, 0
|
|
rstep = float((col2.Red() - col1.Red()))/float(size)
|
|
gstep = float((col2.Green() - col1.Green()))/float(size)
|
|
bstep = float((col2.Blue() - col1.Blue()))/float(size)
|
|
|
|
for coord in xrange(start, start + size):
|
|
|
|
currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf)
|
|
dc.SetBrush(wx.Brush(currCol, wx.SOLID))
|
|
dc.SetPen(wx.Pen(currCol))
|
|
if isVertical:
|
|
dc.DrawLine(rect.x, coord, rect.x + rect.width, coord)
|
|
else:
|
|
dc.DrawLine(coord, rect.y, coord, rect.y + rect.width)
|
|
|
|
rf += rstep
|
|
gf += gstep
|
|
bf += bstep
|
|
|
|
|
|
class StatusBarTimer(wx.Timer):
|
|
"""Timer used for deleting StatusBar long help after _DELAY seconds."""
|
|
|
|
def __init__(self, owner):
|
|
"""
|
|
Default class constructor.
|
|
For internal use: do not call it in your code!
|
|
"""
|
|
|
|
wx.Timer.__init__(self)
|
|
self._owner = owner
|
|
|
|
|
|
def Notify(self):
|
|
"""The timer has expired."""
|
|
|
|
self._owner.OnStatusBarTimer()
|
|
|
|
|
|
class Control(wx.EvtHandler):
|
|
|
|
def __init__(self, parent, size=wx.Size(-1, -1)):
|
|
"""
|
|
Default class constructor.
|
|
|
|
Base class for all pseudo controls
|
|
parent = parent object
|
|
size = (width, height)
|
|
"""
|
|
|
|
wx.EvtHandler.__init__(self)
|
|
|
|
self._parent = parent
|
|
self._id = wx.NewId()
|
|
self._size = size
|
|
self._isshown = True
|
|
self._focus = False
|
|
|
|
|
|
def Show(self, show=True):
|
|
""" Shows or hide the control. """
|
|
|
|
self._isshown = show
|
|
|
|
|
|
def Hide(self):
|
|
""" Hides the control. """
|
|
|
|
self.Show(False)
|
|
|
|
|
|
def IsShown(self):
|
|
""" Returns whether the control is shown or not. """
|
|
|
|
return self._isshown
|
|
|
|
|
|
def GetId(self):
|
|
""" Returns the control id. """
|
|
|
|
return self._id
|
|
|
|
|
|
def GetBestSize(self):
|
|
""" Returns the control best size. """
|
|
|
|
return self._size
|
|
|
|
|
|
def Disable(self):
|
|
""" Disables the control. """
|
|
|
|
self.Enable(False)
|
|
|
|
|
|
def Enable(self, value=True):
|
|
""" Enables or disables the control. """
|
|
|
|
self.disabled = not value
|
|
|
|
|
|
def SetFocus(self, focus=True):
|
|
""" Sets or kills the focus on the control. """
|
|
|
|
self._focus = focus
|
|
|
|
|
|
def HasFocus(self):
|
|
""" Returns whether the control has the focus or not. """
|
|
|
|
return self._focus
|
|
|
|
|
|
def OnMouseEvent(self, x, y, event):
|
|
pass
|
|
|
|
def Draw(self, rect):
|
|
pass
|
|
|
|
|
|
|
|
class Sizer(object):
|
|
"""
|
|
Sizer
|
|
|
|
This is a mix-in class to add pseudo support to a wx sizer. Just create
|
|
a new class that derives from this class and the wx sizer and intercepts
|
|
any methods that add to the wx sizer.
|
|
"""
|
|
def __init__(self):
|
|
self.children = [] # list of child Pseudo Controls
|
|
|
|
# Sizer doesn't use the x1,y1,x2,y2 so allow it to
|
|
# be called with or without the coordinates
|
|
def Draw(self, dc, x1=0, y1=0, x2=0, y2=0):
|
|
for item in self.children:
|
|
# use sizer coordinates rather than
|
|
# what is passed in
|
|
c = item.GetUserData()
|
|
c.Draw(dc, item.GetRect())
|
|
|
|
def GetBestSize(self):
|
|
# this should be handled by the wx.Sizer based class
|
|
return self.GetMinSize()
|
|
|
|
|
|
# Pseudo BoxSizer
|
|
class BoxSizer(Sizer, wx.BoxSizer):
|
|
def __init__(self, orient=wx.HORIZONTAL):
|
|
wx.BoxSizer.__init__(self, orient)
|
|
Sizer.__init__(self)
|
|
|
|
#-------------------------------------------
|
|
# sizer overrides (only called from Python)
|
|
#-------------------------------------------
|
|
# no support for user data if it's a pseudocontrol
|
|
# since that is already used
|
|
def Add(self, item, proportion=0, flag=0, border=0, userData=None):
|
|
# check to see if it's a pseudo object or sizer
|
|
if isinstance(item, Sizer):
|
|
szitem = wx.BoxSizer.Add(self, item, proportion, flag, border, item)
|
|
self.children.append(szitem)
|
|
elif isinstance(item, Control): # Control should be what ever class your controls come from
|
|
sz = item.GetBestSize()
|
|
# add a spacer to track this object
|
|
szitem = wx.BoxSizer.Add(self, sz, proportion, flag, border, item)
|
|
self.children.append(szitem)
|
|
else:
|
|
wx.BoxSizer.Add(self, item, proportion, flag, border, userData)
|
|
|
|
def Prepend(self, item, proportion=0, flag=0, border=0, userData=None):
|
|
# check to see if it's a pseudo object or sizer
|
|
if isinstance(item, Sizer):
|
|
szitem = wx.BoxSizer.Prepend(self, item, proportion, flag, border, item)
|
|
self.children.append(szitem)
|
|
elif isinstance(item, Control): # Control should be what ever class your controls come from
|
|
sz = item.GetBestSize()
|
|
# add a spacer to track this object
|
|
szitem = wx.BoxSizer.Prepend(self, sz, proportion, flag, border, item)
|
|
self.children.insert(0,szitem)
|
|
else:
|
|
wx.BoxSizer.Prepend(self, item, proportion, flag, border, userData)
|
|
|
|
def Insert(self, before, item, proportion=0, flag=0, border=0, userData=None, realIndex=None):
|
|
# check to see if it's a pseudo object or sizer
|
|
if isinstance(item, Sizer):
|
|
szitem = wx.BoxSizer.Insert(self, before, item, proportion, flag, border, item)
|
|
self.children.append(szitem)
|
|
elif isinstance(item, Control): # Control should be what ever class your controls come from
|
|
sz = item.GetBestSize()
|
|
# add a spacer to track this object
|
|
szitem = wx.BoxSizer.Insert(self, before, sz, proportion, flag, border, item)
|
|
if realIndex is not None:
|
|
self.children.insert(realIndex,szitem)
|
|
else:
|
|
self.children.insert(before,szitem)
|
|
|
|
else:
|
|
wx.BoxSizer.Insert(self, before, item, proportion, flag, border, userData)
|
|
|
|
|
|
def Remove(self, indx, pop=-1):
|
|
|
|
if pop >= 0:
|
|
self.children.pop(pop)
|
|
|
|
wx.BoxSizer.Remove(self, indx)
|
|
|
|
|
|
def Layout(self):
|
|
|
|
for ii, child in enumerate(self.GetChildren()):
|
|
item = child.GetUserData()
|
|
if item and child.IsShown():
|
|
self.SetItemMinSize(ii, *item.GetBestSize())
|
|
|
|
wx.BoxSizer.Layout(self)
|
|
|
|
|
|
def Show(self, item, show=True):
|
|
|
|
child = self.GetChildren()[item]
|
|
if child and child.GetUserData():
|
|
child.GetUserData().Show(show)
|
|
|
|
wx.BoxSizer.Show(self, item, show)
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# Class Separator
|
|
# This class holds all the information to size and draw a separator inside
|
|
# ButtonPanel
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class Separator(Control):
|
|
|
|
def __init__(self, parent):
|
|
""" Default class constructor. """
|
|
|
|
self._isshown = True
|
|
self._parent = parent
|
|
Control.__init__(self, parent)
|
|
|
|
|
|
def GetBestSize(self):
|
|
""" Returns the separator best size. """
|
|
|
|
# 10 is completely arbitrary, but it works anyhow
|
|
if self._parent.IsVertical():
|
|
return wx.Size(10, self._parent._art.GetMetric(BP_SEPARATOR_SIZE))
|
|
else:
|
|
return wx.Size(self._parent._art.GetMetric(BP_SEPARATOR_SIZE), 10)
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
""" Draws the separator. Actually the drawing is done in BPArt. """
|
|
|
|
if not self.IsShown():
|
|
return
|
|
|
|
isVertical = self._parent.IsVertical()
|
|
self._parent._art.DrawSeparator(dc, rect, isVertical)
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
# Class ButtonPanelText
|
|
# This class is used to hold data about the main caption in ButtonPanel
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
class ButtonPanelText(Control):
|
|
|
|
def __init__(self, parent, text=""):
|
|
""" Default class constructor. """
|
|
|
|
self._text = text
|
|
self._isshown = True
|
|
self._parent = parent
|
|
|
|
Control.__init__(self, parent)
|
|
|
|
|
|
def GetText(self):
|
|
""" Returns the caption text. """
|
|
|
|
return self._text
|
|
|
|
|
|
def SetText(self, text=""):
|
|
""" Sets the caption text. """
|
|
|
|
self._text = text
|
|
|
|
|
|
def CreateDC(self):
|
|
""" Convenience function to create a DC. """
|
|
|
|
dc = wx.ClientDC(self._parent)
|
|
textFont = self._parent._art.GetFont(BP_TEXT_FONT)
|
|
dc.SetFont(textFont)
|
|
|
|
return dc
|
|
|
|
|
|
def GetBestSize(self):
|
|
""" Returns the best size for the main caption in ButtonPanel. """
|
|
|
|
if self._text == "":
|
|
return wx.Size(0, 0)
|
|
|
|
dc = self.CreateDC()
|
|
rect = self._parent.GetClientRect()
|
|
|
|
tw, th = dc.GetTextExtent(self._text)
|
|
padding = self._parent._art.GetMetric(BP_PADDING_SIZE)
|
|
self._size = wx.Size(tw+2*padding.x, th+2*padding.y)
|
|
|
|
return self._size
|
|
|
|
|
|
def Draw(self, dc, rect):
|
|
""" Draws the main caption. Actually the drawing is done in BPArt. """
|
|
|
|
if not self.IsShown():
|
|
return
|
|
|
|
captionText = self.GetText()
|
|
self._parent._art.DrawCaption(dc, rect, captionText)
|
|
|
|
|
|
# -- ButtonInfo class implementation ----------------------------------------
|
|
# This class holds information about every button that is added to
|
|
# ButtonPanel. It is an auxiliary class that you should use
|
|
# every time you add a button.
|
|
|
|
class ButtonInfo(Control):
|
|
|
|
def __init__(self, parent, id=wx.ID_ANY, bmp=wx.NullBitmap,
|
|
status="Normal", text="", kind=wx.ITEM_NORMAL,
|
|
shortHelp="", longHelp=""):
|
|
"""
|
|
Default class constructor.
|
|
|
|
Parameters:
|
|
- parent: the parent window (ButtonPanel);
|
|
- id: the button id;
|
|
- bmp: the associated bitmap;
|
|
- status: button status (pressed, hovered, normal).
|
|
- text: text to be displayed either below of to the right of the button
|
|
- kind: button kind, may be wx.ITEM_NORMAL for standard buttons or
|
|
wx.ITEM_CHECK for toggle buttons;
|
|
- shortHelp: a short help to be shown in the button tooltip;
|
|
- longHelp: this string is shown in the statusbar (if any) of the parent
|
|
frame when the mouse pointer is inside the button.
|
|
"""
|
|
|
|
if id == wx.ID_ANY:
|
|
id = wx.NewId()
|
|
|
|
self._status = status
|
|
self._rect = wx.Rect()
|
|
self._text = text
|
|
self._kind = kind
|
|
self._toggle = False
|
|
self._textAlignment = BP_BUTTONTEXT_ALIGN_BOTTOM
|
|
self._shortHelp = shortHelp
|
|
self._longHelp = longHelp
|
|
|
|
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
|
|
|
|
|