AGW Logo

Source code for gradientbutton

# --------------------------------------------------------------------------------- #
# GRADIENTBUTTON wxPython IMPLEMENTATION
#
# Andrea Gavana, @ 07 October 2008
# Latest Revision: 09 Feb 2012, 21.00 GMT
#
#
# TODO List
#
# 1) Anything to do?
#
#
# For all kind of problems, requests of enhancements and bug reports, please
# write to me at:
#
# andrea.gavana@gmail.com
# andrea.gavana@maerskoil.com
#
# Or, obviously, to the wxPython mailing list!!!
#
#
# End Of Comments
# --------------------------------------------------------------------------------- #

"""
L{GradientButton} is another custom-drawn button class which mimics Windows CE mobile
gradient buttons.


Description
===========

L{GradientButton} is another custom-drawn button class which mimics Windows CE mobile
gradient buttons, using a tri-vertex blended gradient plus some ClearType bold
font (best effect with Tahoma Bold). L{GradientButton} supports:

* Triple blended gradient background, with customizable colours;
* Custom colours for the "pressed" state;
* Rounded-corners buttons;
* Text-only or image+text buttons.

And a lot more. Check the demo for an almost complete review of the functionalities.


Usage
=====

Usage example::

    import wx
    import wx.lib.agw.gradientbutton as GB

    class MyFrame(wx.Frame):

        def __init__(self, parent):
        
            wx.Frame.__init__(self, parent, -1, "GradientButton Demo")

            panel = wx.Panel(self, -1)

            # One button without bitmap            
            button_1 = GB.GradientButton(panel, -1, None, "Hello World", (100, 50))

            # One button with bitmap
            my_bitmap = wx.Bitmap("my_bitmap.png", wx.BITMAP_TYPE_PNG)
            button_2 = GB.GradientButton(panel, -1, my_bitmap, "GradientButton", (100, 150))


    # our normal wxApp-derived class, as usual

    app = wx.PySimpleApp()

    frame = MyFrame(None)
    app.SetTopWindow(frame)
    frame.Show()

    app.MainLoop()



Supported Platforms
===================

L{GradientButton} has been tested on the following platforms:
  * Windows (Windows XP).


Window Styles
=============

`No particular window styles are available for this class.`


Events Processing
=================

This class processes the following events:

================= ==================================================
Event Name        Description
================= ==================================================
``wx.EVT_BUTTON`` Process a `wx.wxEVT_COMMAND_BUTTON_CLICKED` event, when the button is clicked. 
================= ==================================================


License And Version
===================

L{GradientButton} is distributed under the wxPython license.

Latest Revision: Andrea Gavana @ 09 Feb 2012, 21.00 GMT

Version 0.3

"""

import wx


HOVER = 1
CLICK = 2

[docs]class GradientButtonEvent(wx.PyCommandEvent): """ Event sent from L{GradientButton} when the button is activated. """
[docs] def __init__(self, eventType, eventId): """ Default class constructor. :param `eventType`: the event type; :param `eventId`: the event identifier. """ wx.PyCommandEvent.__init__(self, eventType, eventId) self.isDown = False self.theButton = None
[docs] def SetButtonObj(self, btn): """ Sets the event object for the event. :param `btn`: the button object, an instance of L{GradientButton}. """ self.theButton = btn
[docs] def GetButtonObj(self): """ Returns the object associated with this event. """ return self.theButton
[docs]class GradientButton(wx.PyControl): """ This is the main class implementation of L{GradientButton}. """
[docs] def __init__(self, parent, id=wx.ID_ANY, bitmap=None, label="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.NO_BORDER, validator=wx.DefaultValidator, name="gradientbutton"): """ Default class constructor. :param `parent`: the L{GradientButton} parent; :param `id`: window identifier. A value of -1 indicates a default value; :param `bitmap`: the button bitmap (if any); :param `label`: the button text label; :param `pos`: the control position. A value of (-1, -1) indicates a default position, chosen by either the windowing system or wxPython, depending on platform; :param `size`: the control size. A value of (-1, -1) indicates a default size, chosen by either the windowing system or wxPython, depending on platform; :param `style`: the button style (unused); :param `validator`: the validator associated to the button; :param `name`: the button name. """ wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name) self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None) self.Bind(wx.EVT_SIZE, self.OnSize) 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.OnMouseEnter) self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus) self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.Bind(wx.EVT_KEY_UP, self.OnKeyUp) self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) self._mouseAction = None self._bitmap = bitmap self._hasFocus = False self.SetLabel(label) self.InheritAttributes() self.SetInitialSize(size) self.SetBaseColours()
[docs] def SetBaseColours(self, startcolour=wx.BLACK, foregroundcolour=wx.WHITE): """Sets the bottom, top, pressed and foreground colour :param startcolour: based colour to be used for bottom, top and pressed :param foregroundcolour: colour used for the text """ self._bottomStartColour = startcolour rgba = self._bottomStartColour.Red(), self._bottomStartColour.Green(), \ self._bottomStartColour.Blue(), self._bottomStartColour.Alpha() self._bottomEndColour = self.LightColour(self._bottomStartColour, 20) self._topStartColour = self.LightColour(self._bottomStartColour, 40) self._topEndColour = self.LightColour(self._bottomStartColour, 25) self._pressedTopColour = self.LightColour(self._bottomStartColour, 20) self._pressedBottomColour = wx.Colour(*rgba) self.SetForegroundColour(foregroundcolour)
[docs] def LightColour(self, colour, percent): """ Return light contrast of `colour`. The colour returned is from the scale of `colour` ==> white. :param `colour`: the input colour to be brightened; :param `percent`: determines how light the colour will be. `percent` = 100 returns white, `percent` = 0 returns `colour`. """ end_colour = wx.WHITE rd = end_colour.Red() - colour.Red() gd = end_colour.Green() - colour.Green() bd = end_colour.Blue() - colour.Blue() high = 100 # We take the percent way of the colour from colour -. white i = percent r = colour.Red() + ((i*rd*100)/high)/100 g = colour.Green() + ((i*gd*100)/high)/100 b = colour.Blue() + ((i*bd*100)/high)/100 return wx.Colour(r, g, b)
[docs] def OnSize(self, event): """ Handles the ``wx.EVT_SIZE`` event for L{GradientButton}. :param `event`: a `wx.SizeEvent` event to be processed. """ event.Skip() self.Refresh()
[docs] def OnLeftDown(self, event): """ Handles the ``wx.EVT_LEFT_DOWN`` event for L{GradientButton}. :param `event`: a `wx.MouseEvent` event to be processed. """ if not self.IsEnabled(): return self._mouseAction = CLICK self.CaptureMouse() self.Refresh() event.Skip()
[docs] def OnLeftUp(self, event): """ Handles the ``wx.EVT_LEFT_UP`` event for L{GradientButton}. :param `event`: a `wx.MouseEvent` event to be processed. """ if not self.IsEnabled() or not self.HasCapture(): return pos = event.GetPosition() rect = self.GetClientRect() if self.HasCapture(): self.ReleaseMouse() if rect.Contains(pos): self._mouseAction = HOVER self.Notify() else: self._mouseAction = None self.Refresh() event.Skip()
[docs] def OnMouseEnter(self, event): """ Handles the ``wx.EVT_ENTER_WINDOW`` event for L{GradientButton}. :param `event`: a `wx.MouseEvent` event to be processed. """ if not self.IsEnabled(): return self._mouseAction = HOVER self.Refresh() event.Skip()
[docs] def OnMouseLeave(self, event): """ Handles the ``wx.EVT_LEAVE_WINDOW`` event for L{GradientButton}. :param `event`: a `wx.MouseEvent` event to be processed. """ self._mouseAction = None self.Refresh() event.Skip()
[docs] def OnGainFocus(self, event): """ Handles the ``wx.EVT_SET_FOCUS`` event for L{GradientButton}. :param `event`: a `wx.FocusEvent` event to be processed. """ self._hasFocus = True self.Refresh() self.Update()
[docs] def OnLoseFocus(self, event): """ Handles the ``wx.EVT_KILL_FOCUS`` event for L{GradientButton}. :param `event`: a `wx.FocusEvent` event to be processed. """ self._hasFocus = False self.Refresh() self.Update()
[docs] def OnKeyDown(self, event): """ Handles the ``wx.EVT_KEY_DOWN`` event for L{GradientButton}. :param `event`: a `wx.KeyEvent` event to be processed. """ if self._hasFocus and event.GetKeyCode() == ord(" "): self._mouseAction = HOVER self.Refresh() event.Skip()
[docs] def OnKeyUp(self, event): """ Handles the ``wx.EVT_KEY_UP`` event for L{GradientButton}. :param `event`: a `wx.KeyEvent` event to be processed. """ if self._hasFocus and event.GetKeyCode() == ord(" "): self._mouseAction = HOVER self.Notify() self.Refresh() event.Skip()
[docs] def OnPaint(self, event): """ Handles the ``wx.EVT_PAINT`` event for L{GradientButton}. :param `event`: a `wx.PaintEvent` event to be processed. """ dc = wx.BufferedPaintDC(self) gc = wx.GraphicsContext.Create(dc) dc.SetBackground(wx.Brush(self.GetParent().GetBackgroundColour())) dc.Clear() clientRect = self.GetClientRect() gradientRect = wx.Rect(*clientRect) capture = wx.Window.GetCapture() x, y, width, height = clientRect gradientRect.SetHeight(gradientRect.GetHeight()/2 + ((capture==self and [1] or [0])[0])) if capture != self: if self._mouseAction == HOVER: topStart, topEnd = self.LightColour(self._topStartColour, 10), self.LightColour(self._topEndColour, 10) else: topStart, topEnd = self._topStartColour, self._topEndColour rc1 = wx.Rect(x, y, width, height/2) path1 = self.GetPath(gc, rc1, 8) br1 = gc.CreateLinearGradientBrush(x, y, x, y+height/2, topStart, topEnd) gc.SetBrush(br1) gc.FillPath(path1) #draw main path4 = gc.CreatePath() path4.AddRectangle(x, y+height/2-8, width, 8) path4.CloseSubpath() gc.SetBrush(br1) gc.FillPath(path4) else: rc1 = wx.Rect(x, y, width, height) path1 = self.GetPath(gc, rc1, 8) gc.SetPen(wx.Pen(self._pressedTopColour)) gc.SetBrush(wx.Brush(self._pressedTopColour)) gc.FillPath(path1) gradientRect.Offset((0, gradientRect.GetHeight())) if capture != self: if self._mouseAction == HOVER: bottomStart, bottomEnd = self.LightColour(self._bottomStartColour, 10), self.LightColour(self._bottomEndColour, 10) else: bottomStart, bottomEnd = self._bottomStartColour, self._bottomEndColour rc3 = wx.Rect(x, y+height/2, width, height/2) path3 = self.GetPath(gc, rc3, 8) br3 = gc.CreateLinearGradientBrush(x, y+height/2, x, y+height, bottomStart, bottomEnd) gc.SetBrush(br3) gc.FillPath(path3) #draw main path4 = gc.CreatePath() path4.AddRectangle(x, y+height/2, width, 8) path4.CloseSubpath() gc.SetBrush(br3) gc.FillPath(path4) shadowOffset = 0 else: rc2 = wx.Rect(x+1, gradientRect.height/2, gradientRect.width, gradientRect.height) path2 = self.GetPath(gc, rc2, 8) gc.SetPen(wx.Pen(self._pressedBottomColour)) gc.SetBrush(wx.Brush(self._pressedBottomColour)) gc.FillPath(path2) shadowOffset = 1 font = gc.CreateFont(self.GetFont(), self.GetForegroundColour()) gc.SetFont(font) label = self.GetLabel() tw, th = gc.GetTextExtent(label) if self._bitmap: bw, bh = self._bitmap.GetWidth(), self._bitmap.GetHeight() else: bw = bh = 0 pos_x = (width-bw-tw)/2+shadowOffset # adjust for bitmap and text to centre if self._bitmap: pos_y = (height-bh)/2+shadowOffset gc.DrawBitmap(self._bitmap, pos_x, pos_y, bw, bh) # draw bitmap if available pos_x = pos_x + 2 # extra spacing from bitmap gc.DrawText(label, pos_x + bw + shadowOffset, (height-th)/2+shadowOffset)
[docs] def GetPath(self, gc, rc, r): """ Returns a rounded `wx.GraphicsPath` rectangle. :param `gc`: an instance of `wx.GraphicsContext`; :param `rc`: a client rectangle; :param `r`: the radious of the rounded part of the rectangle. """ x, y, w, h = rc path = gc.CreatePath() path.AddRoundedRectangle(x, y, w, h, r) path.CloseSubpath() return path
[docs] def SetInitialSize(self, size=None): """ Given the current font and bezel width settings, calculate and set a good size. :param `size`: an instance of `wx.Size`. """ if size is None: size = wx.DefaultSize wx.PyControl.SetInitialSize(self, size)
SetBestSize = SetInitialSize
[docs] def AcceptsFocus(self): """ Can this window be given focus by mouse click? :note: Overridden from `wx.PyControl`. """ return self.IsShown() and self.IsEnabled()
[docs] def GetDefaultAttributes(self): """ Overridden base class virtual. By default we should use the same font/colour attributes as the native `wx.Button`. """ return wx.Button.GetClassDefaultAttributes()
[docs] def ShouldInheritColours(self): """ Overridden base class virtual. Buttons usually don't inherit the parent's colours. :note: Overridden from `wx.PyControl`. """ return False
[docs] def Enable(self, enable=True): """ Enables/disables the button. :param `enable`: ``True`` to enable the button, ``False`` to disable it. :note: Overridden from `wx.PyControl`. """ wx.PyControl.Enable(self, enable) self.Refresh()
[docs] def SetTopStartColour(self, colour): """ Sets the top start colour for the gradient shading. :param `colour`: a valid `wx.Colour` object. """ self._topStartColour = colour self.Refresh()
[docs] def GetTopStartColour(self): """ Returns the top start colour for the gradient shading. """ return self._topStartColour
[docs] def SetTopEndColour(self, colour): """ Sets the top end colour for the gradient shading. :param `colour`: a valid `wx.Colour` object. """ self._topEndColour = colour self.Refresh()
[docs] def GetTopEndColour(self): """ Returns the top end colour for the gradient shading. """ return self._topEndColour
[docs] def SetBottomStartColour(self, colour): """ Sets the top bottom colour for the gradient shading. :param `colour`: a valid `wx.Colour` object. """ self._bottomStartColour = colour self.Refresh()
[docs] def GetBottomStartColour(self): """ Returns the bottom start colour for the gradient shading. """ return self._bottomStartColour
[docs] def SetBottomEndColour(self, colour): """ Sets the bottom end colour for the gradient shading. :param `colour`: a valid `wx.Colour` object. """ self._bottomEndColour = colour self.Refresh()
[docs] def GetBottomEndColour(self): """ Returns the bottom end colour for the gradient shading. """ return self._bottomEndColour
[docs] def SetPressedTopColour(self, colour): """ Sets the pressed top start colour for the gradient shading. :param `colour`: a valid `wx.Colour` object. """ self._pressedTopColour = colour self.Refresh()
[docs] def GetPressedTopColour(self): """ Returns the pressed top start colour for the gradient shading. """ return self._pressedTopColour
[docs] def SetPressedBottomColour(self, colour): """ Sets the pressed bottom start colour for the gradient shading. :param `colour`: a valid `wx.Colour` object. """ self._pressedBottomColour = colour self.Refresh()
[docs] def GetPressedBottomColour(self): """ Returns the pressed bottom start colour for the gradient shading. """ return self._pressedBottomColour
[docs] def SetForegroundColour(self, colour): """ Sets the L{GradientButton} foreground (text) colour. :param `colour`: a valid `wx.Colour` object. :note: Overridden from `wx.PyControl`. """ wx.PyControl.SetForegroundColour(self, colour) self.Refresh()
[docs] def DoGetBestSize(self): """ Overridden base class virtual. Determines the best size of the button based on the label and bezel size. :note: Overridden from `wx.PyControl`. """ label = self.GetLabel() if not label: return wx.Size(112, 48) dc = wx.ClientDC(self) dc.SetFont(self.GetFont()) retWidth, retHeight = dc.GetTextExtent(label) bmpWidth = bmpHeight = 0 constant = 15 if self._bitmap: bmpWidth, bmpHeight = self._bitmap.GetWidth()+10, self._bitmap.GetHeight() retWidth += bmpWidth retHeight = max(bmpHeight, retHeight) constant = 15 return wx.Size(retWidth+constant, retHeight+constant)
[docs] def SetDefault(self): """ Sets the default button. """ tlw = wx.GetTopLevelParent(self) if hasattr(tlw, 'SetDefaultItem'): tlw.SetDefaultItem(self)
[docs] def Notify(self): """ Actually sends a ``wx.EVT_BUTTON`` event to the listener (if any). """ evt = GradientButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId()) evt.SetButtonObj(self) evt.SetEventObject(self) self.GetEventHandler().ProcessEvent(evt)