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.
244 class ButtonPanel(wx
.PyPanel
):
246 def __init__(self
, parent
, id=wx
.ID_ANY
, text
="", style
=BP_DEFAULT_STYLE
,
247 alignment
=BP_ALIGN_RIGHT
, name
="buttonPanel"):
249 Default class constructor.
251 - parent: parent window
254 - style: window style
255 - alignment: alignment of buttons (left or right)
256 - name: window class name
259 wx
.PyPanel
.__init
__(self
, parent
, id, wx
.DefaultPosition
, wx
.DefaultSize
,
260 wx
.NO_BORDER
, name
=name
)
265 self
._alignment
= alignment
268 self
._colorFrom
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)
269 self
._colorTo
= wx
.WHITE
270 self
._colorBorder
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_3DFACE
)
271 self
._colorText
= wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_WINDOWTEXT
)
272 self
._firsttime
= True
273 self
._borderPenWidth
= 3
275 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
276 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
277 self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
)
278 self
.Bind(wx
.EVT_MOTION
, self
.OnMouseMove
)
279 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
280 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnLeftUp
)
281 self
.Bind(wx
.EVT_LEAVE_WINDOW
, self
.OnMouseLeave
)
282 self
.Bind(wx
.EVT_ENTER_WINDOW
, self
.OnMouseEnterWindow
)
285 def AddButton(self
, btnInfo
):
287 Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
288 this method. See the demo for details.
291 self
._vButtons
.append(btnInfo
)
295 def RemoveAllButtons(self
):
296 """ Remove all the buttons from ButtonPanel. """
302 def GetAlignment(self
):
303 """ Returns the button alignment (left, right). """
305 return self
._alignment
308 def SetAlignment(self
, alignment
):
309 """ Sets the button alignment (left, right). """
311 self
._alignment
= alignment
314 def DoGetBestSize(self
):
317 dc
= wx
.ClientDC(self
)
318 boldFont
= wx
.SystemSettings
.GetFont(wx
.SYS_DEFAULT_GUI_FONT
)
319 boldFont
.SetWeight(wx
.FONTWEIGHT_BOLD
)
321 tw
, th
= dc
.GetTextExtent(self
._text
)
323 w
+= tw
+ self
._nPadding
326 bh
= self
._vButtons
[0].GetBitmap().GetHeight()
327 bw
= self
._vButtons
[0].GetBitmap().GetWidth()
329 bh
+= 2*self
._nPadding
+ 2*self
._borderPenWidth
332 bw
= (len(self
._vButtons
)+1) * (bw
+ 2*self
._nPadding
)
339 def OnPaint(self
, event
):
340 """ Handles the wx.EVT_PAINT event for ButtonPanel. """
342 dc
= wx
.BufferedPaintDC(self
)
343 rect
= self
.GetClientRect()
345 ##print rect, self.GetRect(), self.GetBestSize(), self.GetMinSize()
347 # Draw gradient color in the background of the panel
348 self
.FillGradientColor(dc
, rect
)
350 backBrush
= wx
.TRANSPARENT_BRUSH
351 borderPen
= wx
.Pen(self
._colorBorder
)
352 size
= self
.GetSize()
353 borderPen
.SetWidth(self
._borderPenWidth
)
355 # Draw a rectangle around the panel
356 dc
.SetBrush(backBrush
)
358 dc
.DrawRectangleRect(rect
)
361 textWidth
, textHeight
= 0, 0
365 dc
.SetTextForeground(self
._colorText
)
366 borderPen
.SetWidth(2)
367 boldFont
= wx
.SystemSettings
.GetFont(wx
.SYS_DEFAULT_GUI_FONT
)
368 boldFont
.SetWeight(wx
.FONTWEIGHT_BOLD
)
371 textWidth
, textHeight
= dc
.GetTextExtent(self
._text
)
373 if self
._alignment
== BP_ALIGN_RIGHT
:
374 textX
= self
._nPadding
376 textX
= rect
.width
- textWidth
- self
._nPadding
378 textY
= (rect
.GetHeight() - textHeight
)/2
379 dc
.DrawText(self
._text
, textX
, textY
)
383 height
= self
._vButtons
[0].GetBitmap().GetHeight()
384 self
._nBmpSize
= self
._vButtons
[0].GetBitmap().GetWidth()
385 height
+= 2*self
._nPadding
+ 2*self
._borderPenWidth
387 if self
._firsttime
: # this probably isn't needed anymore now that DoGetBestSize is implemented...
388 self
.GetContainingSizer().Layout()
389 self
._firsttime
= False
392 # [ Padding | Text | .. Buttons .. | Padding ]
394 totalWidth
= rect
.width
- self
._nPadding
*2 - textWidth
396 # The button is drawn inside a circle with padding of self._nPadding around it
397 # so the width of each image = imageWidth + 2 * self._nPadding
398 nImageWidth
= self
._nBmpSize
+ 2*self
._nPadding
400 if self
._alignment
== BP_ALIGN_RIGHT
:
402 leftEndX
= self
._nPadding
+ textWidth
403 posx
= rect
.width
- nImageWidth
- self
._nPadding
405 for ii
in xrange(len(self
._vButtons
)):
407 # Make sure we can keep drawing
411 # Draw a rectangle around the buttons
412 if self
._vButtons
[ii
].GetStatus() == BP_BTN_HOVER
:
414 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
415 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
416 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
417 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
419 elif self
._vButtons
[ii
].GetStatus() == BP_BTN_PRESSED
:
421 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
422 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
423 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
424 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
+ 1, True)
428 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
430 self
._vButtons
[ii
].SetRect(wx
.Rect(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
))
435 rightStartX
= textX
- self
._nPadding
- nImageWidth
436 posx
= self
._nPadding
438 for ii
in xrange(len(self
._vButtons
)):
440 # Make sure we can keep drawing
441 if posx
> rightStartX
:
444 # Draw a rectangle around the buttons
445 if self
._vButtons
[ii
].GetStatus() == BP_BTN_HOVER
:
447 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
448 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
449 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
450 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
452 elif self
._vButtons
[ii
].GetStatus() == BP_BTN_PRESSED
:
454 dc
.SetBrush(wx
.Brush(wx
.Color(225, 225, 255)))
455 dc
.SetPen(wx
.Pen(wx
.SystemSettings
.GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)))
456 dc
.DrawRectangle(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
)
457 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
+ 1, True)
461 dc
.DrawBitmap(self
._vButtons
[ii
].GetBitmap(), posx
+ self
._nPadding
, self
._nPadding
+ self
._borderPenWidth
, True)
463 self
._vButtons
[ii
].SetRect(wx
.Rect(posx
, self
._borderPenWidth
, nImageWidth
, nImageWidth
))
466 # Update all other buttons that they are not drawn (by setting the rect to 0)
467 for cur
in xrange(ii
+1, len(self
._vButtons
)):
468 self
._vButtons
[cur
].SetRect(wx
.Rect(0, 0, 0, 0))
471 def OnEraseBackground(self
, event
):
472 """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
477 def OnSize(self
, event
):
478 """ Handles the wx.EVT_SIZE event for ButtonPanel. """
484 def SetColor(self
, switch
, color
):
486 Sets the color depending on switch:
487 - BP_CAPTION_COLOR: caption color;
488 - BP_CAPTION_GRADIENT_COLOR: gradient color;
489 - BP_CAPTION_BORDER_COLOR; border color;
490 - BP_TEXT_COLOR: text color.
493 if switch
== BP_CAPTION_COLOR
:
494 self
._colorFrom
= color
495 elif switch
== BP_CAPTION_GRADIENT_COLOR
:
496 self
._colorTo
= color
497 elif switch
== BP_CAPTION_BORDER_COLOR
:
498 self
._colorBorder
= color
499 elif switch
== BP_TEXT_COLOR
:
500 self
._colorText
= color
503 def FillGradientColor(self
, dc
, rect
):
504 """ Gradient fill from colour 1 to colour 2 with top to bottom. """
506 if rect
.height
< 1 or rect
.width
< 1:
511 # calculate gradient coefficients
512 style
= self
.GetParent().GetWindowStyleFlag()
513 col2
= self
._colorFrom
517 rstep
= float((col2
.Red() - col1
.Red()))/float(size
)
518 gstep
= float((col2
.Green() - col1
.Green()))/float(size
)
519 bstep
= float((col2
.Blue() - col1
.Blue()))/float(size
)
521 for y
in xrange(rect
.y
, rect
.y
+ size
):
523 currCol
= wx
.Colour(col1
.Red() + rf
, col1
.Green() + gf
, col1
.Blue() + bf
)
524 dc
.SetBrush(wx
.Brush(currCol
, wx
.SOLID
))
525 dc
.SetPen(wx
.Pen(currCol
))
526 dc
.DrawLine(rect
.x
, y
, rect
.x
+ rect
.width
, y
)
532 def OnMouseMove(self
, event
):
533 """ Handles the wx.EVT_MOTION event for ButtonPanel. """
535 # Check to see if we are hovering a button
536 for ii
in xrange(len(self
._vButtons
)):
537 if self
._vButtons
[ii
].GetRect().Inside(event
.GetPosition()):
538 self
._vButtons
[ii
].SetStatus(BP_BTN_HOVER
)
540 self
._vButtons
[ii
].SetStatus(BP_BTN_NONE
)
546 def OnLeftDown(self
, event
):
547 """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
549 tabId
, hit
= self
.HitTest(event
.GetPosition())
551 if hit
== BP_HT_BUTTON
:
553 self
._vButtons
[tabId
].SetStatus(BP_BTN_PRESSED
)
557 def OnLeftUp(self
, event
):
558 """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
560 tabId
, hit
= self
.HitTest(event
.GetPosition())
562 if hit
== BP_HT_BUTTON
:
563 if self
._vButtons
[tabId
].GetStatus() == BP_BTN_PRESSED
:
564 # Fire a button click event
565 btnEvent
= wx
.CommandEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, self
._vButtons
[tabId
].GetId())
566 self
.GetEventHandler().ProcessEvent(btnEvent
)
568 # Update the button status to be hovered
569 self
._vButtons
[tabId
].SetStatus(BP_BTN_HOVER
)
573 def OnMouseLeave(self
, event
):
574 """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
576 # Reset all buttons statuses
577 for ii
in xrange(len(self
._vButtons
)):
578 self
._vButtons
[ii
].SetStatus(BP_BTN_NONE
)
584 def OnMouseEnterWindow(self
, event
):
585 """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
590 def HitTest(self
, pt
):
592 HitTest method for ButtonPanel. Returns the button (if any) and
598 for ii
in xrange(len(self
._vButtons
)):
599 if self
._vButtons
[ii
].GetRect().Inside(pt
):
600 return ii
, BP_HT_BUTTON
602 return -1, BP_HT_NONE