+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 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)
+
+