1 # --------------------------------------------------------------------------- #
2 # FANCYBUTTONPANEL Widget wxPython IMPLEMENTATION
4 # Original C++ Code From Eran. You Can Find It At:
6 # http://wxforum.shadonet.com/viewtopic.php?t=6619
8 # License: wxWidgets license
13 # Andrea Gavana, @ 02 Oct 2006
14 # Latest Revision: 02 Oct 2006, 17.00 GMT
17 # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
20 # andrea.gavana@gmail.com
23 # Or, Obviously, To The wxPython Mailing List!!!
27 # --------------------------------------------------------------------------- #
30 With `ButtonPanel` class you have a panel with gradient coloring
31 on it and with the possibility to place some buttons on it. Using a
32 standard panel with normal wx.Buttons leads to an ugly result: the
33 buttons are placed correctly on the panel - but with grey area around
34 them. Gradient coloring is kept behind the images - this was achieved
35 due to the PNG format and the transparency of the bitmaps.
37 The image are functioning like a buttons and can be caught in your
38 code using the usual self.Bind(wx.EVT_BUTTON, self.OnButton) method.
40 The control is generic, and support theming (well, I tested it under
41 Windows with the three defauls themes: grey, blue, silver and the
48 The following example shows a simple implementation that uses ButtonPanel
49 inside a very simple frame::
51 class MyFrame(wx.Frame):
53 def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition,
54 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
56 wx.Frame.__init__(self, parent, id, title, pos, size, style)
58 mainPanel = wx.Panel(self, -1)
59 self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE)
61 vSizer = wx.BoxSizer(wx.VERTICAL)
62 mainPanel.SetSizer(vSizer)
64 alignment = BP_ALIGN_RIGHT
66 titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo")
68 btn1 = ButtonInfo(wx.NewId(), wx.Bitmap("png4.png", wx.BITMAP_TYPE_PNG))
69 titleBar.AddButton(btn1)
70 self.Bind(wx.EVT_BUTTON, self.OnButton, btn1)
72 btn2 = ButtonInfo(wx.NewId(), wx.Bitmap("png3.png", wx.BITMAP_TYPE_PNG))
73 titleBar.AddButton(btn2)
74 self.Bind(wx.EVT_BUTTON, self.OnButton, btn2)
76 btn3 = ButtonInfo(wx.NewId(), wx.Bitmap("png2.png", wx.BITMAP_TYPE_PNG))
77 titleBar.AddButton(btn3)
78 self.Bind(wx.EVT_BUTTON, self.OnButton, btn3)
80 btn4 = ButtonInfo(wx.NewId(), wx.Bitmap("png1.png", wx.BITMAP_TYPE_PNG))
81 titleBar.AddButton(btn4)
82 self.Bind(wx.EVT_BUTTON, self.OnButton, btn4)
84 titleBar.SetColor(BP_TEXT_COLOR, wx.WHITE)
85 titleBar.SetColor(BP_CAPTION_BORDER_COLOR, wx.WHITE)
86 vSizer.Add(titleBar, 0, wx.EXPAND)
88 vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5)
92 # our normal wxApp-derived class, as usual
94 app = wx.PySimpleApp()
97 app.SetTopWindow(frame)
105 ButtonPanel Is Freeware And Distributed Under The wxPython License.
107 Latest Revision: Andrea Gavana @ 02 Oct 2006, 17.00 GMT
116 BP_CAPTION_COLOR
= 0,
117 BP_CAPTION_GRADIENT_COLOR
= 1
118 BP_CAPTION_BORDER_COLOR
= 2
126 # Flags for HitTest() method
130 # Alignment of buttons in the panel
134 # ButtonPanel default style
138 def BrightenColor(color
, factor
):
139 """ Bright the input colour by a factor."""
141 val
= color
.Red()*factor
147 val
= color
.Green()*factor
153 val
= color
.Blue()*factor
159 return wx
.Color(red
, green
, blue
)
162 # -- ButtonInfo class implementation ----------------------------------------
163 # This class holds information about every button that is added to
164 # ButtonPanel. It is an auxiliary class that you should use
165 # every time you add a button.
169 def __init__(self
, id=wx
.ID_ANY
, bmp
=wx
.NullBitmap
, status
=-1):
171 Default class constructor.
175 - bmp: the associated bitmap;
176 - status: button status (pressed, hovered, None).
182 self
._status
= status
183 self
._rect
= wx
.Rect()
187 """ Returns the associated bitmap. """
193 """ Returns the button rect. """
199 """ Returns the button status. """
205 """ Returns the button id. """
210 def SetRect(self
, rect
):
211 """ Sets the button rect. """
216 def SetBitmap(self
, bmp
):
217 """ Sets the associated bitmap. """
222 def SetStatus(self
, status
):
223 """ Sets the button status. """
225 self
._status
= status
229 """ Sets the button id. """
233 Bitmap
= property(GetBitmap
, SetBitmap
)
234 Id
= property(GetId
, SetId
)
235 Rect
= property(GetRect
, SetRect
)
236 Status
= property(GetStatus
, SetStatus
)
241 # -- ButtonPanel class implementation ----------------------------------
242 # This is the main class.
246 class ButtonPanel(BASE
):
248 def __init__(self
, parent
, id=wx
.ID_ANY
, text
="", style
=BP_DEFAULT_STYLE
,
249 alignment
=BP_ALIGN_RIGHT
, name
="buttonPanel"):
251 Default class constructor.
253 - parent: parent window
256 - style: window style
257 - alignment: alignment of buttons (left or right)
258 - name: window class name
261 BASE
.__init
__(self
, parent
, id, wx
.DefaultPosition
, wx
.DefaultSize
,
262 wx
.NO_BORDER
, name
=name
)
267 self
._alignment
= alignment
270 self
._colorFrom
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)
271 self
._colorTo
= wx
.WHITE
272 self
._colorBorder
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_3DFACE
)
273 self
._colorText
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_WINDOWTEXT
)
274 self
._firsttime
= True
275 self
._borderPenWidth
= 3
277 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
278 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
279 self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
)
280 self
.Bind(wx
.EVT_MOTION
, self
.OnMouseMove
)
281 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
282 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnLeftUp
)
283 self
.Bind(wx
.EVT_LEAVE_WINDOW
, self
.OnMouseLeave
)
284 self
.Bind(wx
.EVT_ENTER_WINDOW
, self
.OnMouseEnterWindow
)
287 def AddButton(self
, btnInfo
):
289 Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
290 this method. See the demo for details.
293 self
._vButtons
.append(btnInfo
)
297 def RemoveAllButtons(self
):
298 """ Remove all the buttons from ButtonPanel. """
304 def GetAlignment(self
):
305 """ Returns the button alignment (left, right). """
307 return self
._alignment
310 def SetAlignment(self
, alignment
):
311 """ Sets the button alignment (left, right). """
313 self
._alignment
= alignment
316 def DoGetBestSize(self
):
319 dc
= wx
.ClientDC(self
)
320 boldFont
= wx
.SystemSettings
.GetFont(wx
.SYS_DEFAULT_GUI_FONT
)
321 boldFont
.SetWeight(wx
.FONTWEIGHT_BOLD
)
323 tw
, th
= dc
.GetTextExtent(self
._text
)
325 w
+= tw
+ self
._nPadding
328 bh
= self
._vButtons
[0].GetBitmap().GetHeight()
329 bw
= self
._vButtons
[0].GetBitmap().GetWidth()
331 bh
+= 2*self
._nPadding
+ 2*self
._borderPenWidth
334 bw
= (len(self
._vButtons
)+1) * (bw
+ 2*self
._nPadding
)
341 def OnPaint(self
, event
):
342 """ Handles the wx.EVT_PAINT event for ButtonPanel. """
344 dc
= wx
.BufferedPaintDC(self
)
345 rect
= self
.GetClientRect()
347 ##print rect, self.GetRect(), self.GetBestSize(), self.GetMinSize()
349 # Draw gradient color in the background of the panel
350 self
.FillGradientColor(dc
, rect
)
352 backBrush
= wx
.TRANSPARENT_BRUSH
353 borderPen
= wx
.Pen(self
._colorBorder
)
354 size
= self
.GetSize()
355 borderPen
.SetWidth(self
._borderPenWidth
)
357 # Draw a rectangle around the panel
358 dc
.SetBrush(backBrush
)
360 dc
.DrawRectangleRect(rect
)
363 textWidth
, textHeight
= 0, 0
367 dc
.SetTextForeground(self
._colorText
)
368 borderPen
.SetWidth(2)
369 boldFont
= wx
.SystemSettings
.GetFont(wx
.SYS_DEFAULT_GUI_FONT
)
370 boldFont
.SetWeight(wx
.FONTWEIGHT_BOLD
)
373 textWidth
, textHeight
= dc
.GetTextExtent(self
._text
)
375 if self
._alignment
== BP_ALIGN_RIGHT
:
376 textX
= self
._nPadding
378 textX
= rect
.width
- textWidth
- self
._nPadding
380 textY
= (rect
.GetHeight() - textHeight
)/2
381 dc
.DrawText(self
._text
, textX
, textY
)
385 height
= self
._vButtons
[0].GetBitmap().GetHeight()
386 self
._nBmpSize
= self
._vButtons
[0].GetBitmap().GetWidth()
387 height
+= 2*self
._nPadding
+ 2*self
._borderPenWidth
389 if self
._firsttime
: # this probably isn't needed anymore now that DoGetBestSize is implemented...
390 self
.GetContainingSizer().Layout()
391 self
._firsttime
= False
394 # [ Padding | Text | .. Buttons .. | Padding ]
396 totalWidth
= rect
.width
- self
._nPadding
*2 - textWidth
398 # The button is drawn inside a circle with padding of self._nPadding around it
399 # so the width of each image = imageWidth + 2 * self._nPadding
400 nImageWidth
= self
._nBmpSize
+ 2*self
._nPadding
402 if self
._alignment
== BP_ALIGN_RIGHT
:
404 leftEndX
= self
._nPadding
+ textWidth
405 posx
= rect
.width
- nImageWidth
- self
._nPadding
407 for ii
in xrange(len(self
._vButtons
)):
409 # Make sure we can keep drawing
413 # Draw a rectangle around the buttons
414 if self
._vButtons
[ii
].GetStatus() == BP_BTN_HOVER
:
416 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
417 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
418 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
419 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
421 elif self
._vButtons
[ii
].GetStatus() == BP_BTN_PRESSED
:
423 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
424 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
425 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
426 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
+ 1, True)
430 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
432 self
._vButtons
[ii
].SetRect(wx
.Rect(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
))
437 rightStartX
= textX
- self
._nPadding
- nImageWidth
438 posx
= self
._nPadding
440 for ii
in xrange(len(self
._vButtons
)):
442 # Make sure we can keep drawing
443 if posx
> rightStartX
:
446 # Draw a rectangle around the buttons
447 if self
._vButtons
[ii
].GetStatus() == BP_BTN_HOVER
:
449 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
450 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
451 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
452 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
454 elif self
._vButtons
[ii
].GetStatus() == BP_BTN_PRESSED
:
456 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
457 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
458 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
459 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
+ 1, True)
463 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
465 self
._vButtons
[ii
].SetRect(wx
.Rect(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
))
468 # Update all other buttons that they are not drawn (by setting the rect to 0)
469 for cur
in xrange(ii
+1, len(self
._vButtons
)):
470 self
._vButtons
[cur
].SetRect(wx
.Rect(0, 0, 0, 0))
473 def OnEraseBackground(self
, event
):
474 """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
479 def OnSize(self
, event
):
480 """ Handles the wx.EVT_SIZE event for ButtonPanel. """
486 def SetColor(self
, switch
, color
):
488 Sets the color depending on switch:
489 - BP_CAPTION_COLOR: caption color;
490 - BP_CAPTION_GRADIENT_COLOR: gradient color;
491 - BP_CAPTION_BORDER_COLOR; border color;
492 - BP_TEXT_COLOR: text color.
495 if switch
== BP_CAPTION_COLOR
:
496 self
._colorFrom
= color
497 elif switch
== BP_CAPTION_GRADIENT_COLOR
:
498 self
._colorTo
= color
499 elif switch
== BP_CAPTION_BORDER_COLOR
:
500 self
._colorBorder
= color
501 elif switch
== BP_TEXT_COLOR
:
502 self
._colorText
= color
505 def FillGradientColor(self
, dc
, rect
):
506 """ Gradient fill from colour 1 to colour 2 with top to bottom. """
508 if rect
.height
< 1 or rect
.width
< 1:
513 # calculate gradient coefficients
514 style
= self
.GetParent().GetWindowStyleFlag()
515 col2
= self
._colorFrom
519 rstep
= float((col2
.Red() - col1
.Red()))/float(size
)
520 gstep
= float((col2
.Green() - col1
.Green()))/float(size
)
521 bstep
= float((col2
.Blue() - col1
.Blue()))/float(size
)
523 for y
in xrange(rect
.y
, rect
.y
+ size
):
525 currCol
= wx
.Colour(col1
.Red() + rf
, col1
.Green() + gf
, col1
.Blue() + bf
)
526 dc
.SetBrush(wx
.Brush(currCol
, wx
.SOLID
))
527 dc
.SetPen(wx
.Pen(currCol
))
528 dc
.DrawLine(rect
.x
, y
, rect
.x
+ rect
.width
, y
)
534 def OnMouseMove(self
, event
):
535 """ Handles the wx.EVT_MOTION event for ButtonPanel. """
537 # Check to see if we are hovering a button
538 for ii
in xrange(len(self
._vButtons
)):
539 if self
._vButtons
[ii
].GetRect().Inside(event
.GetPosition()):
540 self
._vButtons
[ii
].SetStatus(BP_BTN_HOVER
)
542 self
._vButtons
[ii
].SetStatus(BP_BTN_NONE
)
548 def OnLeftDown(self
, event
):
549 """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
551 tabId
, hit
= self
.HitTest(event
.GetPosition())
553 if hit
== BP_HT_BUTTON
:
555 self
._vButtons
[tabId
].SetStatus(BP_BTN_PRESSED
)
559 def OnLeftUp(self
, event
):
560 """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
562 tabId
, hit
= self
.HitTest(event
.GetPosition())
564 if hit
== BP_HT_BUTTON
:
565 if self
._vButtons
[tabId
].GetStatus() == BP_BTN_PRESSED
:
566 # Fire a button click event
567 btnEvent
= wx
.CommandEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
._vButtons
[tabId
].GetId())
568 self
.GetEventHandler().ProcessEvent(btnEvent
)
570 # Update the button status to be hovered
571 self
._vButtons
[tabId
].SetStatus(BP_BTN_HOVER
)
575 def OnMouseLeave(self
, event
):
576 """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
578 # Reset all buttons statuses
579 for ii
in xrange(len(self
._vButtons
)):
580 self
._vButtons
[ii
].SetStatus(BP_BTN_NONE
)
586 def OnMouseEnterWindow(self
, event
):
587 """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
592 def HitTest(self
, pt
):
594 HitTest method for ButtonPanel. Returns the button (if any) and
600 for ii
in xrange(len(self
._vButtons
)):
601 if self
._vButtons
[ii
].GetRect().Inside(pt
):
602 return ii
, BP_HT_BUTTON
604 return -1, BP_HT_NONE