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: 17 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 ButtonPanel supports 4 alignments: left, right, top, bottom, which have a
49 different meaning and behavior wrt wx.Toolbar. The easiest thing is to try
50 the demo to understand, but I'll try to explain how it works.
52 CASE 1: ButtonPanel has a main caption text
54 Left alignment means ButtonPanel is horizontal, with the text aligned to the
55 left. When you shrink the demo frame, if there is not enough room for all
56 the controls to be shown, the controls closest to the text are hidden;
58 Right alignment means ButtonPanel is horizontal, with the text aligned to the
59 right. Item layout as above;
61 Top alignment means ButtonPanel is vertical, with the text aligned to the top.
64 Bottom alignment means ButtonPanel is vertical, with the text aligned to the
65 bottom. Item layout as above.
68 CASE 2: ButtonPanel has *no* main caption text
69 In this case, left and right alignment are the same (as top and bottom are the same),
70 but the layout strategy changes: now if there is not enough room for all the controls
71 to be shown, the last added items are hidden ("last" means on the far right for
72 horizontal ButtonPanels and far bottom for vertical ButtonPanels).
75 The following example shows a simple implementation that uses ButtonPanel
76 inside a very simple frame::
78 class MyFrame(wx.Frame):
80 def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition,
81 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
83 wx.Frame.__init__(self, parent, id, title, pos, size, style)
85 mainPanel = wx.Panel(self, -1)
86 self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE)
88 vSizer = wx.BoxSizer(wx.VERTICAL)
89 mainPanel.SetSizer(vSizer)
91 alignment = BP_ALIGN_RIGHT
93 titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo")
95 btn1 = ButtonInfo(wx.NewId(), wx.Bitmap("png4.png", wx.BITMAP_TYPE_PNG))
96 titleBar.AddButton(btn1)
97 self.Bind(wx.EVT_BUTTON, self.OnButton, btn1)
99 btn2 = ButtonInfo(wx.NewId(), wx.Bitmap("png3.png", wx.BITMAP_TYPE_PNG))
100 titleBar.AddButton(btn2)
101 self.Bind(wx.EVT_BUTTON, self.OnButton, btn2)
103 btn3 = ButtonInfo(wx.NewId(), wx.Bitmap("png2.png", wx.BITMAP_TYPE_PNG))
104 titleBar.AddButton(btn3)
105 self.Bind(wx.EVT_BUTTON, self.OnButton, btn3)
107 btn4 = ButtonInfo(wx.NewId(), wx.Bitmap("png1.png", wx.BITMAP_TYPE_PNG))
108 titleBar.AddButton(btn4)
109 self.Bind(wx.EVT_BUTTON, self.OnButton, btn4)
111 vSizer.Add(titleBar, 0, wx.EXPAND)
113 vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5)
117 # our normal wxApp-derived class, as usual
119 app = wx.PySimpleApp()
121 frame = MyFrame(None)
122 app.SetTopWindow(frame)
130 ButtonPanel Is Freeware And Distributed Under The wxPython License.
132 Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT
140 # Some constants to tune the BPArt class
141 BP_BACKGROUND_COLOR
= 0
142 """ Background brush colour when no gradient shading exists. """
143 BP_GRADIENT_COLOR_FROM
= 1
144 """ Starting gradient colour, used only when BP_USE_GRADIENT style is applied. """
145 BP_GRADIENT_COLOR_TO
= 2
146 """ Ending gradient colour, used only when BP_USE_GRADIENT style is applied. """
148 """ Pen colour to paint the border of ButtonPanel. """
150 """ Main ButtonPanel caption colour. """
151 BP_BUTTONTEXT_COLOR
= 5
152 """ Text colour for buttons with text. """
153 BP_BUTTONTEXT_INACTIVE_COLOR
= 6
154 """ Text colour for inactive buttons with text. """
155 BP_SELECTION_BRUSH_COLOR
= 7
156 """ Brush colour to be used when hovering or selecting a button. """
157 BP_SELECTION_PEN_COLOR
= 8
158 """ Pen colour to be used when hovering or selecting a button. """
159 BP_SEPARATOR_COLOR
= 9
160 """ Pen colour used to paint the separators. """
162 """ Font of the ButtonPanel main caption. """
163 BP_BUTTONTEXT_FONT
= 11
164 """ Text font for the buttons with text. """
166 BP_BUTTONTEXT_ALIGN_BOTTOM
= 12
167 """ Flag that indicates the image and text in buttons is stacked. """
168 BP_BUTTONTEXT_ALIGN_RIGHT
= 13
169 """ Flag that indicates the text is shown alongside the image in buttons with text. """
171 BP_SEPARATOR_SIZE
= 14
173 Separator size. NB: This is not the line width, but the sum of the space before
174 and after the separator line plus the width of the line.
178 Size of the left/right margins in ButtonPanel (top/bottom for vertically
179 aligned ButtonPanels).
182 """ Size of the border. """
184 """ Inter-tool separator size. """
186 # Caption Gradient Type
188 """ No gradient shading should be used to paint the background. """
189 BP_GRADIENT_VERTICAL
= 1
190 """ Vertical gradient shading should be used to paint the background. """
191 BP_GRADIENT_HORIZONTAL
= 2
192 """ Horizontal gradient shading should be used to paint the background. """
194 # Flags for HitTest() method
198 # Alignment of buttons in the panel
208 # Delay used to cancel the longHelp in the statusbar field
212 # Check for the new method in 2.7 (not present in 2.6.3.3)
213 if wx
.VERSION_STRING
< "2.7":
214 wx
.Rect
.Contains
= lambda self
, point
: wx
.Rect
.Inside(self
, point
)
217 def BrightenColour(color
, factor
):
218 """ Bright the input colour by a factor."""
220 val
= color
.Red()*factor
226 val
= color
.Green()*factor
232 val
= color
.Blue()*factor
238 return wx
.Color(red
, green
, blue
)
241 def GrayOut(anImage
):
243 Convert the given image (in place) to a grayed-out version,
244 appropriate for a 'Disabled' appearance.
247 factor
= 0.7 # 0 < f < 1. Higher Is Grayer
249 anImage
= anImage
.ConvertToImage()
250 if anImage
.HasAlpha():
251 anImage
.ConvertAlphaToMask(1)
253 if anImage
.HasMask():
254 maskColor
= (anImage
.GetMaskRed(), anImage
.GetMaskGreen(), anImage
.GetMaskBlue())
258 data
= map(ord, list(anImage
.GetData()))
260 for i
in range(0, len(data
), 3):
262 pixel
= (data
[i
], data
[i
+1], data
[i
+2])
263 pixel
= MakeGray(pixel
, factor
, maskColor
)
268 anImage
.SetData(''.join(map(chr, data
)))
270 anImage
= anImage
.ConvertToBitmap()
275 def MakeGray((r
,g
,b
), factor
, maskColor
):
277 Make a pixel grayed-out. If the pixel matches the maskColor, it won't be
281 if (r
,g
,b
) != maskColor
:
282 return map(lambda x
: int((230 - x
) * factor
) + x
, (r
,g
,b
))
287 # ---------------------------------------------------------------------------- #
289 # Handles all the drawings for buttons, separators and text and allows the
290 # programmer to set colours, sizes and gradient shadings for ButtonPanel
291 # ---------------------------------------------------------------------------- #
295 BPArt is an art provider class which does all of the drawing for ButtonPanel.
296 This allows the library caller to customize the BPArt or to completely replace
297 all drawing with custom BPArts.
300 def __init__(self
, parentStyle
):
301 """ Default class constructor. """
303 base_color
= wx
.SystemSettings_GetColour(wx
.SYS_COLOUR_3DFACE
)
305 self
._background
_brush
= wx
.Brush(base_color
, wx
.SOLID
)
306 self
._gradient
_color
_to
= wx
.WHITE
307 self
._gradient
_color
_from
= wx
.SystemSettings_GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
)
309 if parentStyle
& BP_USE_GRADIENT
:
310 self
._border
_pen
= wx
.Pen(wx
.WHITE
, 3)
311 self
._caption
_text
_color
= wx
.WHITE
312 self
._buttontext
_color
= wx
.Colour(70, 143, 255)
313 self
._separator
_pen
= wx
.Pen(BrightenColour(self
._gradient
_color
_from
, 1.4))
314 self
._gradient
_type
= BP_GRADIENT_VERTICAL
316 self
._border
_pen
= wx
.Pen(BrightenColour(base_color
, 0.9), 3)
317 self
._caption
_text
_color
= wx
.BLACK
318 self
._buttontext
_color
= wx
.SystemSettings_GetColour(wx
.SYS_COLOUR_BTNTEXT
)
319 self
._separator
_pen
= wx
.Pen(BrightenColour(base_color
, 0.9))
320 self
._gradient
_type
= BP_GRADIENT_NONE
322 self
._buttontext
_inactive
_color
= wx
.SystemSettings_GetColour(wx
.SYS_COLOUR_GRAYTEXT
)
323 self
._selection
_brush
= wx
.Brush(wx
.Color(225, 225, 255))
324 self
._selection
_pen
= wx
.Pen(wx
.SystemSettings_GetColour(wx
.SYS_COLOUR_ACTIVECAPTION
))
326 sysfont
= wx
.SystemSettings
.GetFont(wx
.SYS_DEFAULT_GUI_FONT
)
327 self
._caption
_font
= wx
.Font(sysfont
.GetPointSize(), wx
.DEFAULT
, wx
.NORMAL
, wx
.BOLD
,
328 False, sysfont
.GetFaceName())
329 self
._buttontext
_font
= wx
.Font(sysfont
.GetPointSize(), wx
.DEFAULT
, wx
.NORMAL
, wx
.NORMAL
,
330 False, sysfont
.GetFaceName())
332 self
._separator
_size
= 7
333 self
._margins
_size
= wx
.Size(6, 6)
334 self
._caption
_border
_size
= 3
335 self
._padding
_size
= wx
.Size(6, 6)
338 def GetMetric(self
, id):
339 """ Returns sizes of customizable options. """
341 if id == BP_SEPARATOR_SIZE
:
342 return self
._separator
_size
343 elif id == BP_MARGINS_SIZE
:
344 return self
._margins
_size
345 elif id == BP_BORDER_SIZE
:
346 return self
._caption
_border
_size
347 elif id == BP_PADDING_SIZE
:
348 return self
._padding
_size
350 raise "\nERROR: Invalid Metric Ordinal. "
353 def SetMetric(self
, id, new_val
):
354 """ Sets sizes for customizable options. """
356 if id == BP_SEPARATOR_SIZE
:
357 self
._separator
_size
= new_val
358 elif id == BP_MARGINS_SIZE
:
359 self
._margins
_size
= new_val
360 elif id == BP_BORDER_SIZE
:
361 self
._caption
_border
_size
= new_val
362 self
._border
_pen
.SetWidth(new_val
)
363 elif id == BP_PADDING_SIZE
:
364 self
._padding
_size
= new_val
366 raise "\nERROR: Invalid Metric Ordinal. "
369 def GetColor(self
, id):
370 """ Returns colours of customizable options. """
372 if id == BP_BACKGROUND_COLOR
:
373 return self
._background
_brush
.GetColour()
374 elif id == BP_GRADIENT_COLOR_FROM
:
375 return self
._gradient
_color
_from
376 elif id == BP_GRADIENT_COLOR_TO
:
377 return self
._gradient
_color
_to
378 elif id == BP_BORDER_COLOR
:
379 return self
._border
_pen
.GetColour()
380 elif id == BP_TEXT_COLOR
:
381 return self
._caption
_text
_color
382 elif id == BP_BUTTONTEXT_COLOR
:
383 return self
._buttontext
_color
384 elif id == BP_BUTTONTEXT_INACTIVE_COLOR
:
385 return self
._buttontext
_inactive
_color
386 elif id == BP_SELECTION_BRUSH_COLOR
:
387 return self
._selection
_brush
.GetColour()
388 elif id == BP_SELECTION_PEN_COLOR
:
389 return self
._selection
_pen
.GetColour()
390 elif id == BP_SEPARATOR_COLOR
:
391 return self
._separator
_pen
.GetColour()
393 raise "\nERROR: Invalid Colour Ordinal. "
396 def SetColor(self
, id, colour
):
397 """ Sets colours for customizable options. """
399 if id == BP_BACKGROUND_COLOR
:
400 self
._background
_brush
.SetColour(colour
)
401 elif id == BP_GRADIENT_COLOR_FROM
:
402 self
._gradient
_color
_from
= colour
403 elif id == BP_GRADIENT_COLOR_TO
:
404 self
._gradient
_color
_to
= colour
405 elif id == BP_BORDER_COLOR
:
406 self
._border
_pen
.SetColour(colour
)
407 elif id == BP_TEXT_COLOR
:
408 self
._caption
_text
_color
= colour
409 elif id == BP_BUTTONTEXT_COLOR
:
410 self
._buttontext
_color
= colour
411 elif id == BP_BUTTONTEXT_INACTIVE_COLOR
:
412 self
._buttontext
_inactive
_color
= colour
413 elif id == BP_SELECTION_BRUSH_COLOR
:
414 self
._selection
_brush
.SetColour(colour
)
415 elif id == BP_SELECTION_PEN_COLOR
:
416 self
._selection
_pen
.SetColour(colour
)
417 elif id == BP_SEPARATOR_COLOR
:
418 self
._separator
_pen
.SetColour(colour
)
420 raise "\nERROR: Invalid Colour Ordinal. "
427 def SetFont(self
, id, font
):
428 """ Sets font for customizable options. """
430 if id == BP_TEXT_FONT
:
431 self
._caption
_font
= font
432 elif id == BP_BUTTONTEXT_FONT
:
433 self
._buttontext
_font
= font
436 def GetFont(self
, id):
437 """ Returns font of customizable options. """
439 if id == BP_TEXT_FONT
:
440 return self
._caption
_font
441 elif id == BP_BUTTONTEXT_FONT
:
442 return self
._buttontext
_font
447 def SetGradientType(self
, gradient
):
448 """ Sets the gradient type for BPArt drawings. """
450 self
._gradient
_type
= gradient
453 def GetGradientType(self
):
454 """ Returns the gradient type for BPArt drawings. """
456 return self
._gradient
_type
459 def DrawSeparator(self
, dc
, rect
, isVertical
):
460 """ Draws a separator in ButtonPanel. """
462 dc
.SetPen(self
._separator
_pen
)
465 ystart
= yend
= rect
.y
+ rect
.height
/2
466 xstart
= int(rect
.x
+ 1.5*self
._caption
_border
_size
)
467 xend
= int(rect
.x
+ rect
.width
- 1.5*self
._caption
_border
_size
)
468 dc
.DrawLine(xstart
, ystart
, xend
, yend
)
470 xstart
= xend
= rect
.x
+ rect
.width
/2
471 ystart
= int(rect
.y
+ 1.5*self
._caption
_border
_size
)
472 yend
= int(rect
.y
+ rect
.height
- 1.5*self
._caption
_border
_size
)
473 dc
.DrawLine(xstart
, ystart
, xend
, yend
)
476 def DrawCaption(self
, dc
, rect
, captionText
):
477 """ Draws the main caption text in ButtonPanel. """
479 textColour
= self
._caption
_text
_color
480 textFont
= self
._caption
_font
481 padding
= self
._padding
_size
483 dc
.SetTextForeground(textColour
)
486 dc
.DrawText(captionText
, rect
.x
+ padding
.x
, rect
.y
+padding
.y
)
489 def DrawButton(self
, dc
, rect
, parentSize
, buttonBitmap
, isVertical
,
490 buttonStatus
, isToggled
, textAlignment
, text
=""):
491 """ Draws a button in ButtonPanel, together with its text (if any). """
493 bmpxsize
, bmpysize
= buttonBitmap
.GetWidth(), buttonBitmap
.GetHeight()
496 borderw
= self
._caption
_border
_size
497 padding
= self
._padding
_size
499 buttonFont
= self
._buttontext
_font
500 dc
.SetFont(buttonFont
)
504 rect
= wx
.Rect(borderw
, rect
.y
, rect
.width
-2*borderw
, rect
.height
)
508 textW
, textH
= dc
.GetTextExtent(text
)
510 if textAlignment
== BP_BUTTONTEXT_ALIGN_RIGHT
:
511 fullExtent
= bmpxsize
+ padding
.x
/2 + textW
512 bmpypos
= rect
.y
+ (rect
.height
- bmpysize
)/2
513 bmpxpos
= rect
.x
+ (rect
.width
- fullExtent
)/2
514 textxpos
= bmpxpos
+ padding
.x
/2 + bmpxsize
515 textypos
= bmpypos
+ (bmpysize
- textH
)/2
517 bmpxpos
= rect
.x
+ (rect
.width
- bmpxsize
)/2
518 bmpypos
= rect
.y
+ padding
.y
519 textxpos
= rect
.x
+ (rect
.width
- textW
)/2
520 textypos
= bmpypos
+ bmpysize
+ padding
.y
/2
522 bmpxpos
= rect
.x
+ (rect
.width
- bmpxsize
)/2
523 bmpypos
= rect
.y
+ (rect
.height
- bmpysize
)/2
528 rect
= wx
.Rect(rect
.x
, borderw
, rect
.width
, rect
.height
-2*borderw
)
532 textW
, textH
= dc
.GetTextExtent(text
)
534 if textAlignment
== BP_BUTTONTEXT_ALIGN_RIGHT
:
535 fullExtent
= bmpxsize
+ padding
.x
/2 + textW
536 bmpypos
= rect
.y
+ (rect
.height
- bmpysize
)/2
537 bmpxpos
= rect
.x
+ (rect
.width
- fullExtent
)/2
538 textxpos
= bmpxpos
+ padding
.x
/2 + bmpxsize
539 textypos
= bmpypos
+ (bmpysize
- textH
)/2
541 fullExtent
= bmpysize
+ padding
.y
/2 + textH
542 bmpxpos
= rect
.x
+ (rect
.width
- bmpxsize
)/2
543 bmpypos
= rect
.y
+ (rect
.height
- fullExtent
)/2
544 textxpos
= rect
.x
+ (rect
.width
- textW
)/2
545 textypos
= bmpypos
+ bmpysize
+ padding
.y
/2
547 bmpxpos
= rect
.x
+ (rect
.width
- bmpxsize
)/2
548 bmpypos
= rect
.y
+ (rect
.height
- bmpysize
)/2
551 # [ Padding | Text | .. Buttons .. | Padding ]
553 if buttonStatus
in ["Pressed", "Toggled", "Hover"]:
554 dc
.SetBrush(self
._selection
_brush
)
555 dc
.SetPen(self
._selection
_pen
)
556 dc
.DrawRoundedRectangleRect(rect
, 4)
558 if buttonStatus
== "Pressed" or isToggled
:
561 dc
.DrawBitmap(buttonBitmap
, bmpxpos
+dx
, bmpypos
+dy
, True)
564 isEnabled
= buttonStatus
!= "Disabled"
565 self
.DrawLabel(dc
, text
, isEnabled
, textxpos
+dx
, textypos
+dy
)
568 def DrawLabel(self
, dc
, text
, isEnabled
, xpos
, ypos
):
569 """ Draws the label for a button. """
572 dc
.SetTextForeground(self
._buttontext
_inactive
_color
)
574 dc
.SetTextForeground(self
._buttontext
_color
)
576 dc
.DrawText(text
, xpos
, ypos
)
579 def DrawButtonPanel(self
, dc
, rect
, style
):
580 """ Paint the ButtonPanel's background. """
582 if style
& BP_USE_GRADIENT
:
583 # Draw gradient color in the backgroud of the panel
584 self
.FillGradientColor(dc
, rect
)
586 # Draw a rectangle around the panel
587 backBrush
= (style
& BP_USE_GRADIENT
and [wx
.TRANSPARENT_BRUSH
] or \
588 [self
._background
_brush
])[0]
590 dc
.SetBrush(backBrush
)
591 dc
.SetPen(self
._border
_pen
)
592 dc
.DrawRectangleRect(rect
)
595 def FillGradientColor(self
, dc
, rect
):
596 """ Gradient fill from colour 1 to colour 2 with top to bottom or left to right. """
598 if rect
.height
< 1 or rect
.width
< 1:
601 isVertical
= self
._gradient
_type
== BP_GRADIENT_VERTICAL
602 size
= (isVertical
and [rect
.height
] or [rect
.width
])[0]
603 start
= (isVertical
and [rect
.y
] or [rect
.x
])[0]
605 # calculate gradient coefficients
607 col2
= self
._gradient
_color
_from
608 col1
= self
._gradient
_color
_to
611 rstep
= float((col2
.Red() - col1
.Red()))/float(size
)
612 gstep
= float((col2
.Green() - col1
.Green()))/float(size
)
613 bstep
= float((col2
.Blue() - col1
.Blue()))/float(size
)
615 for coord
in xrange(start
, start
+ size
):
617 currCol
= wx
.Colour(col1
.Red() + rf
, col1
.Green() + gf
, col1
.Blue() + bf
)
618 dc
.SetBrush(wx
.Brush(currCol
, wx
.SOLID
))
619 dc
.SetPen(wx
.Pen(currCol
))
621 dc
.DrawLine(rect
.x
, coord
, rect
.x
+ rect
.width
, coord
)
623 dc
.DrawLine(coord
, rect
.y
, coord
, rect
.y
+ rect
.height
)
630 class StatusBarTimer(wx
.Timer
):
631 """Timer used for deleting StatusBar long help after _DELAY seconds."""
633 def __init__(self
, owner
):
635 Default class constructor.
636 For internal use: do not call it in your code!
639 wx
.Timer
.__init
__(self
)
644 """The timer has expired."""
646 self
._owner
.OnStatusBarTimer()
649 class Control(wx
.EvtHandler
):
651 def __init__(self
, parent
, size
=wx
.Size(-1, -1)):
653 Default class constructor.
655 Base class for all pseudo controls
656 parent = parent object
657 size = (width, height)
660 wx
.EvtHandler
.__init
__(self
)
662 self
._parent
= parent
663 self
._id
= wx
.NewId()
669 def Show(self
, show
=True):
670 """ Shows or hide the control. """
676 """ Hides the control. """
682 """ Returns whether the control is shown or not. """
688 """ Returns the control id. """
693 def GetBestSize(self
):
694 """ Returns the control best size. """
700 """ Disables the control. """
705 def Enable(self
, value
=True):
706 """ Enables or disables the control. """
708 self
.disabled
= not value
711 def SetFocus(self
, focus
=True):
712 """ Sets or kills the focus on the control. """
718 """ Returns whether the control has the focus or not. """
723 def OnMouseEvent(self
, x
, y
, event
):
726 def Draw(self
, rect
):
735 This is a mix-in class to add pseudo support to a wx sizer. Just create
736 a new class that derives from this class and the wx sizer and intercepts
737 any methods that add to the wx sizer.
740 self
.children
= [] # list of child Pseudo Controls
742 # Sizer doesn't use the x1,y1,x2,y2 so allow it to
743 # be called with or without the coordinates
744 def Draw(self
, dc
, x1
=0, y1
=0, x2
=0, y2
=0):
745 for item
in self
.children
:
746 # use sizer coordinates rather than
748 c
= item
.GetUserData()
749 c
.Draw(dc
, item
.GetRect())
751 def GetBestSize(self
):
752 # this should be handled by the wx.Sizer based class
753 return self
.GetMinSize()
757 class BoxSizer(Sizer
, wx
.BoxSizer
):
758 def __init__(self
, orient
=wx
.HORIZONTAL
):
759 wx
.BoxSizer
.__init
__(self
, orient
)
762 #-------------------------------------------
763 # sizer overrides (only called from Python)
764 #-------------------------------------------
765 # no support for user data if it's a pseudocontrol
766 # since that is already used
767 def Add(self
, item
, proportion
=0, flag
=0, border
=0, userData
=None):
768 # check to see if it's a pseudo object or sizer
769 if isinstance(item
, Sizer
):
770 szitem
= wx
.BoxSizer
.Add(self
, item
, proportion
, flag
, border
, item
)
771 self
.children
.append(szitem
)
772 elif isinstance(item
, Control
): # Control should be what ever class your controls come from
773 sz
= item
.GetBestSize()
774 # add a spacer to track this object
775 szitem
= wx
.BoxSizer
.Add(self
, sz
, proportion
, flag
, border
, item
)
776 self
.children
.append(szitem
)
778 wx
.BoxSizer
.Add(self
, item
, proportion
, flag
, border
, userData
)
780 def Prepend(self
, item
, proportion
=0, flag
=0, border
=0, userData
=None):
781 # check to see if it's a pseudo object or sizer
782 if isinstance(item
, Sizer
):
783 szitem
= wx
.BoxSizer
.Prepend(self
, item
, proportion
, flag
, border
, item
)
784 self
.children
.append(szitem
)
785 elif isinstance(item
, Control
): # Control should be what ever class your controls come from
786 sz
= item
.GetBestSize()
787 # add a spacer to track this object
788 szitem
= wx
.BoxSizer
.Prepend(self
, sz
, proportion
, flag
, border
, item
)
789 self
.children
.insert(0,szitem
)
791 wx
.BoxSizer
.Prepend(self
, item
, proportion
, flag
, border
, userData
)
793 def Insert(self
, before
, item
, proportion
=0, flag
=0, border
=0, userData
=None, realIndex
=None):
794 # check to see if it's a pseudo object or sizer
795 if isinstance(item
, Sizer
):
796 szitem
= wx
.BoxSizer
.Insert(self
, before
, item
, proportion
, flag
, border
, item
)
797 self
.children
.append(szitem
)
798 elif isinstance(item
, Control
): # Control should be what ever class your controls come from
799 sz
= item
.GetBestSize()
800 # add a spacer to track this object
801 szitem
= wx
.BoxSizer
.Insert(self
, before
, sz
, proportion
, flag
, border
, item
)
802 if realIndex
is not None:
803 self
.children
.insert(realIndex
,szitem
)
805 self
.children
.insert(before
,szitem
)
808 wx
.BoxSizer
.Insert(self
, before
, item
, proportion
, flag
, border
, userData
)
811 def Remove(self
, indx
, pop
=-1):
814 self
.children
.pop(pop
)
816 wx
.BoxSizer
.Remove(self
, indx
)
821 for ii
, child
in enumerate(self
.GetChildren()):
822 item
= child
.GetUserData()
823 if item
and child
.IsShown():
824 self
.SetItemMinSize(ii
, *item
.GetBestSize())
826 wx
.BoxSizer
.Layout(self
)
829 def Show(self
, item
, show
=True):
831 child
= self
.GetChildren()[item
]
832 if child
and child
.GetUserData():
833 child
.GetUserData().Show(show
)
835 wx
.BoxSizer
.Show(self
, item
, show
)
838 # ---------------------------------------------------------------------------- #
840 # This class holds all the information to size and draw a separator inside
842 # ---------------------------------------------------------------------------- #
844 class Separator(Control
):
846 def __init__(self
, parent
):
847 """ Default class constructor. """
850 self
._parent
= parent
851 Control
.__init
__(self
, parent
)
854 def GetBestSize(self
):
855 """ Returns the separator best size. """
857 # 10 is completely arbitrary, but it works anyhow
858 if self
._parent
.IsVertical():
859 return wx
.Size(10, self
._parent
._art
.GetMetric(BP_SEPARATOR_SIZE
))
861 return wx
.Size(self
._parent
._art
.GetMetric(BP_SEPARATOR_SIZE
), 10)
864 def Draw(self
, dc
, rect
):
865 """ Draws the separator. Actually the drawing is done in BPArt. """
867 if not self
.IsShown():
870 isVertical
= self
._parent
.IsVertical()
871 self
._parent
._art
.DrawSeparator(dc
, rect
, isVertical
)
874 # ---------------------------------------------------------------------------- #
875 # Class ButtonPanelText
876 # This class is used to hold data about the main caption in ButtonPanel
877 # ---------------------------------------------------------------------------- #
879 class ButtonPanelText(Control
):
881 def __init__(self
, parent
, text
=""):
882 """ Default class constructor. """
886 self
._parent
= parent
888 Control
.__init
__(self
, parent
)
892 """ Returns the caption text. """
897 def SetText(self
, text
=""):
898 """ Sets the caption text. """
904 """ Convenience function to create a DC. """
906 dc
= wx
.ClientDC(self
._parent
)
907 textFont
= self
._parent
._art
.GetFont(BP_TEXT_FONT
)
913 def GetBestSize(self
):
914 """ Returns the best size for the main caption in ButtonPanel. """
920 rect
= self
._parent
.GetClientRect()
922 tw
, th
= dc
.GetTextExtent(self
._text
)
923 padding
= self
._parent
._art
.GetMetric(BP_PADDING_SIZE
)
924 self
._size
= wx
.Size(tw
+2*padding
.x
, th
+2*padding
.y
)
929 def Draw(self
, dc
, rect
):
930 """ Draws the main caption. Actually the drawing is done in BPArt. """
932 if not self
.IsShown():
935 captionText
= self
.GetText()
936 self
._parent
._art
.DrawCaption(dc
, rect
, captionText
)
939 # -- ButtonInfo class implementation ----------------------------------------
940 # This class holds information about every button that is added to
941 # ButtonPanel. It is an auxiliary class that you should use
942 # every time you add a button.
944 class ButtonInfo(Control
):
946 def __init__(self
, parent
, id=wx
.ID_ANY
, bmp
=wx
.NullBitmap
,
947 status
="Normal", text
="", kind
=wx
.ITEM_NORMAL
,
948 shortHelp
="", longHelp
=""):
950 Default class constructor.
953 - parent: the parent window (ButtonPanel);
955 - bmp: the associated bitmap;
956 - status: button status (pressed, hovered, normal).
957 - text: text to be displayed either below of to the right of the button
958 - kind: button kind, may be wx.ITEM_NORMAL for standard buttons or
959 wx.ITEM_CHECK for toggle buttons;
960 - shortHelp: a short help to be shown in the button tooltip;
961 - longHelp: this string is shown in the statusbar (if any) of the parent
962 frame when the mouse pointer is inside the button.
968 self
._status
= status
969 self
._rect
= wx
.Rect()
973 self
._textAlignment
= BP_BUTTONTEXT_ALIGN_BOTTOM
974 self
._shortHelp
= shortHelp
975 self
._longHelp
= longHelp
978 disabledbmp
= GrayOut(bmp
)
980 disabledbmp
= wx
.NullBitmap
982 self
._bitmaps
= {"Normal": bmp
, "Toggled": None, "Disabled": disabledbmp
,
983 "Hover": None, "Pressed": None}
985 Control
.__init
__(self
, parent
)
988 def GetBestSize(self
):
989 """ Returns the best size for the button. """
991 xsize
= self
.GetBitmap().GetWidth()
992 ysize
= self
.GetBitmap().GetHeight()
995 # We have text in the button
996 dc
= wx
.ClientDC(self
._parent
)
997 normalFont
= self
._parent
._art
.GetFont(BP_BUTTONTEXT_FONT
)
998 dc
.SetFont(normalFont
)
999 tw
, th
= dc
.GetTextExtent(self
.GetText())
1001 if self
.GetTextAlignment() == BP_BUTTONTEXT_ALIGN_BOTTOM
:
1002 xsize
= max(xsize
, tw
)
1006 ysize
= max(ysize
, th
)
1008 border
= self
._parent
._art
.GetMetric(BP_BORDER_SIZE
)
1009 padding
= self
._parent
._art
.GetMetric(BP_PADDING_SIZE
)
1011 if self
._parent
.IsVertical():
1012 xsize
= xsize
+ 2*border
1014 ysize
= ysize
+ 2*border
1016 self
._size
= wx
.Size(xsize
+2*padding
.x
, ysize
+2*padding
.y
)
1021 def Draw(self
, dc
, rect
):
1022 """ Draws the button on ButtonPanel. Actually the drawing is done in BPArt. """
1024 if not self
.IsShown():
1027 buttonBitmap
= self
.GetBitmap()
1028 isVertical
= self
._parent
.IsVertical()
1029 text
= self
.GetText()
1030 parentSize
= self
._parent
.GetSize()[not isVertical
]
1031 buttonStatus
= self
.GetStatus()
1032 isToggled
= self
.GetToggled()
1033 textAlignment
= self
.GetTextAlignment()
1035 self
._parent
._art
.DrawButton(dc
, rect
, parentSize
, buttonBitmap
, isVertical
,
1036 buttonStatus
, isToggled
, textAlignment
, text
)
1041 def CheckRefresh(self
, status
):
1042 """ Checks whether a ButtonPanel repaint is needed or not. Convenience function. """
1044 if status
== self
._status
:
1045 self
._parent
.RefreshRect(self
.GetRect())
1048 def SetBitmap(self
, bmp
, status
="Normal"):
1049 """ Sets the associated bitmap. """
1051 self
._bitmaps
[status
] = bmp
1052 self
.CheckRefresh(status
)
1055 def GetBitmap(self
, status
=None):
1056 """ Returns the associated bitmap. """
1059 status
= self
._status
1061 if not self
.IsEnabled():
1064 if self
._bitmaps
[status
] is None:
1065 return self
._bitmaps
["Normal"]
1067 return self
._bitmaps
[status
]
1071 """ Returns the button rect. """
1076 def GetStatus(self
):
1077 """ Returns the button status. """
1083 """ Returns the button id. """
1088 def SetRect(self
, rect
):
1089 """ Sets the button rect. """
1094 def SetStatus(self
, status
):
1095 """ Sets the button status. """
1097 if status
== self
._status
:
1100 if self
.GetToggled() and status
== "Normal":
1103 self
._status
= status
1104 self
._parent
.RefreshRect(self
.GetRect())
1107 def GetTextAlignment(self
):
1108 """ Returns the text alignment in the button (bottom or right). """
1110 return self
._textAlignment
1113 def SetTextAlignment(self
, alignment
):
1114 """ Sets the text alignment in the button (bottom or right). """
1116 if alignment
== self
._textAlignment
:
1119 self
._textAlignment
= alignment
1122 def GetToggled(self
):
1123 """ Returns whether a wx.ITEM_CHECK button is toggled or not. """
1125 if self
._kind
== wx
.ITEM_NORMAL
:
1131 def SetToggled(self
, toggle
=True):
1132 """ Sets a wx.ITEM_CHECK button toggled/not toggled. """
1134 if self
._kind
== wx
.ITEM_NORMAL
:
1137 self
._toggle
= toggle
1140 def SetId(self
, id):
1141 """ Sets the button id. """
1146 def AddStatus(self
, name
="Custom", bmp
=wx
.NullBitmap
):
1148 Add a programmer-defined status in addition to the 5 default status:
1156 self
._bitmaps
.update({name: bmp}
)
1159 def Enable(self
, enable
=True):
1162 self
._status
= "Normal"
1164 self
._status
= "Disabled"
1167 def IsEnabled(self
):
1169 return self
._status
!= "Disabled"
1172 def SetText(self
, text
=""):
1173 """ Sets the text of the button. """
1179 """ Returns the text associated to the button. """
1185 """ Returns whether the button has text or not. """
1187 return self
._text
!= ""
1190 def SetKind(self
, kind
=wx
.ITEM_NORMAL
):
1191 """ Sets the button type (standard or toggle). """
1197 """ Returns the button type (standard or toggle). """
1202 def SetShortHelp(self
, help=""):
1203 """ Sets the help string to be shown in a tootip. """
1205 self
._shortHelp
= help
1208 def GetShortHelp(self
):
1209 """ Returns the help string shown in a tootip. """
1211 return self
._shortHelp
1214 def SetLongHelp(self
, help=""):
1215 """ Sets the help string to be shown in the statusbar. """
1217 self
._longHelp
= help
1220 def GetLongHelp(self
):
1221 """ Returns the help string shown in the statusbar. """
1223 return self
._longHelp
1226 Bitmap
= property(GetBitmap
, SetBitmap
)
1227 Id
= property(GetId
, SetId
)
1228 Rect
= property(GetRect
, SetRect
)
1229 Status
= property(GetStatus
, SetStatus
)
1232 # -- ButtonPanel class implementation ----------------------------------
1233 # This is the main class.
1235 class ButtonPanel(wx
.PyPanel
):
1237 def __init__(self
, parent
, id=wx
.ID_ANY
, text
="", style
=BP_DEFAULT_STYLE
,
1238 alignment
=BP_ALIGN_LEFT
, name
="buttonPanel"):
1240 Default class constructor.
1242 - parent: parent window
1244 - text: text to draw
1245 - style: window style
1246 - alignment: alignment of buttons (left or right)
1247 - name: window class name
1250 wx
.PyPanel
.__init
__(self
, parent
, id, wx
.DefaultPosition
, wx
.DefaultSize
,
1251 wx
.NO_BORDER
, name
=name
)
1254 self
._vSeparators
= []
1256 self
._nStyle
= style
1257 self
._alignment
= alignment
1258 self
._statusTimer
= None
1259 self
._useHelp
= True
1260 self
._freezeCount
= 0
1261 self
._currentButton
= -1
1262 self
._haveTip
= False
1264 self
._art
= BPArt(style
)
1266 self
._controlCreated
= False
1268 direction
= (self
.IsVertical() and [wx
.VERTICAL
] or [wx
.HORIZONTAL
])[0]
1269 self
._mainsizer
= BoxSizer(direction
)
1270 self
.SetSizer(self
._mainsizer
)
1272 margins
= self
._art
.GetMetric(BP_MARGINS_SIZE
)
1274 # First spacer to create some room before the first text/button/control
1275 self
._mainsizer
.Add((margins
.x
, margins
.y
), 0)
1277 # Last spacer to create some room before the last text/button/control
1278 self
._mainsizer
.Add((margins
.x
, margins
.y
), 0)
1280 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
1281 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
1282 self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
)
1283 self
.Bind(wx
.EVT_MOTION
, self
.OnMouseMove
)
1284 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
)
1285 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnLeftUp
)
1286 self
.Bind(wx
.EVT_LEAVE_WINDOW
, self
.OnMouseLeave
)
1287 self
.Bind(wx
.EVT_ENTER_WINDOW
, self
.OnMouseEnterWindow
)
1289 self
.SetBarText(text
)
1293 def SetBarText(self
, text
):
1294 """ Sets the main caption text (leave text="" for no text). """
1300 if self
._controlCreated
:
1303 self
._text
= ButtonPanelText(self
, text
)
1304 lenChildren
= len(self
._mainsizer
.GetChildren())
1307 # Even if we have no text, we insert it an empty spacer anyway
1308 # it is easier to handle if you have to recreate the sizer after.
1309 if self
.IsStandard():
1310 self
._mainsizer
.Insert(1, self
._text
, 0, wx
.ALIGN_CENTER
,
1311 userData
=self
._text
, realIndex
=0)
1313 self
._mainsizer
.Insert(lenChildren
-1, self
._text
, 0, wx
.ALIGN_CENTER
,
1314 userData
=self
._text
, realIndex
=lenChildren
)
1318 # We have text, so insert the text and an expandable spacer
1319 # alongside it. "Standard" ButtonPanel are left or top aligned.
1320 if self
.IsStandard():
1321 self
._mainsizer
.Insert(1, self
._text
, 0, wx
.ALIGN_CENTER
,
1322 userData
=self
._text
, realIndex
=0)
1323 self
._mainsizer
.Insert(2, (0, 0), 1, wx
.EXPAND
)
1326 self
._mainsizer
.Insert(lenChildren
-1, self
._text
, 0, wx
.ALIGN_CENTER
,
1327 userData
=self
._text
, realIndex
=lenChildren
)
1328 self
._mainsizer
.Insert(lenChildren
-1, (0, 0), 1, wx
.EXPAND
)
1331 def RemoveText(self
):
1332 """ Removes the main caption text. """
1334 lenChildren
= len(self
._mainsizer
.GetChildren())
1335 lenCustom
= len(self
._vButtons
) + len(self
._vSeparators
) + 1
1337 if self
.IsStandard():
1339 self
._mainsizer
.Remove(1, 0)
1340 if self
.HasBarText():
1341 # Detach the expandable spacer
1342 self
._mainsizer
.Remove(1, -1)
1345 self
._mainsizer
.Remove(lenChildren
-2, lenCustom
-1)
1346 if self
.HasBarText():
1347 # Detach the expandable spacer
1348 self
._mainsizer
.Remove(lenChildren
-3, -1)
1351 def GetBarText(self
):
1352 """ Returns the main caption text. """
1354 return self
._text
.GetText()
1357 def HasBarText(self
):
1358 """ Returns whether ButtonPanel has a main caption text or not. """
1360 return hasattr(self
, "_text") and self
._text
.GetText() != ""
1363 def AddButton(self
, btnInfo
):
1365 Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
1366 this method. See the demo for details.
1369 lenChildren
= len(self
._mainsizer
.GetChildren())
1370 self
._mainsizer
.Insert(lenChildren
-1, btnInfo
, 0, wx
.ALIGN_CENTER|wx
.EXPAND
, userData
=btnInfo
)
1372 self
._vButtons
.append(btnInfo
)
1375 def AddSpacer(self
, size
=(0, 0), proportion
=1, flag
=wx
.EXPAND
):
1376 """ Adds a spacer (stretchable or fixed-size) to ButtonPanel. """
1378 lenChildren
= len(self
._mainsizer
.GetChildren())
1379 self
._mainsizer
.Insert(lenChildren
-1, size
, proportion
, flag
)
1382 def AddControl(self
, control
, proportion
=0, flag
=wx
.ALIGN_CENTER|wx
.ALL
, border
=None):
1383 """ Adds a wxPython control to ButtonPanel. """
1385 lenChildren
= len(self
._mainsizer
.GetChildren())
1388 border
= self
._art
.GetMetric(BP_PADDING_SIZE
)
1389 border
= max(border
.x
, border
.y
)
1391 self
._mainsizer
.Insert(lenChildren
-1, control
, proportion
, flag
, border
)
1394 def AddSeparator(self
):
1395 """ Adds a separator line to ButtonPanel. """
1397 lenChildren
= len(self
._mainsizer
.GetChildren())
1398 separator
= Separator(self
)
1400 self
._mainsizer
.Insert(lenChildren
-1, separator
, 0, wx
.EXPAND
)
1401 self
._vSeparators
.append(separator
)
1404 def RemoveAllButtons(self
):
1405 """ Remove all the buttons from ButtonPanel. """
1410 def RemoveAllSeparators(self
):
1411 """ Remove all the separators from ButtonPanel. """
1413 self
._vSeparators
= []
1416 def GetAlignment(self
):
1417 """ Returns the button alignment (left, right, top, bottom). """
1419 return self
._alignment
1422 def SetAlignment(self
, alignment
):
1423 """ Sets the button alignment (left, right, top, bottom). """
1425 if alignment
== self
._alignment
:
1430 text
= self
.GetBarText()
1432 # Remove the text in any case
1435 # Remove the first and last spacers
1436 self
._mainsizer
.Remove(0, -1)
1437 self
._mainsizer
.Remove(len(self
._mainsizer
.GetChildren())-1, -1)
1439 self
._alignment
= alignment
1441 # Recreate the sizer accordingly to the new alignment
1442 self
.ReCreateSizer(text
)
1445 def IsVertical(self
):
1446 """ Returns whether ButtonPanel is vertically aligned or not. """
1448 return self
._alignment
not in [BP_ALIGN_RIGHT
, BP_ALIGN_LEFT
]
1451 def IsStandard(self
):
1452 """ Returns whether ButtonPanel is aligned "Standard" (left/top) or not. """
1454 return self
._alignment
in [BP_ALIGN_LEFT
, BP_ALIGN_TOP
]
1459 Do the Layout for ButtonPanel.
1460 NB: Call this method every time you make a modification to the layout
1461 or to the customizable sizes of the pseudo controls.
1464 margins
= self
._art
.GetMetric(BP_MARGINS_SIZE
)
1465 lenChildren
= len(self
._mainsizer
.GetChildren())
1467 self
._mainsizer
.SetItemMinSize(0, (margins
.x
, margins
.y
))
1468 self
._mainsizer
.SetItemMinSize(lenChildren
-1, (margins
.x
, margins
.y
))
1470 self
._controlCreated
= True
1473 # *VERY* WEIRD: the sizer seems not to respond to any layout until I
1474 # change the ButtonPanel size and restore it back
1475 size
= self
.GetSize()
1476 self
.SetSize((size
.x
+1, size
.y
+1))
1477 self
.SetSize((size
.x
, size
.y
))
1483 def ReCreateSizer(self
, text
):
1484 """ Recreates the ButtonPanel sizer accordingly to the alignment specified. """
1486 children
= self
._mainsizer
.GetChildren()
1487 self
.RemoveAllButtons()
1488 self
.RemoveAllSeparators()
1490 # Create a new sizer depending on the alignment chosen
1491 direction
= (self
.IsVertical() and [wx
.VERTICAL
] or [wx
.HORIZONTAL
])[0]
1492 self
._mainsizer
= BoxSizer(direction
)
1494 margins
= self
._art
.GetMetric(BP_MARGINS_SIZE
)
1495 # First spacer to create some room before the first text/button/control
1496 self
._mainsizer
.Add((margins
.x
, margins
.y
), 0)
1498 # Last spacer to create some room before the last text/button/control
1499 self
._mainsizer
.Add((margins
.x
, margins
.y
), 0)
1501 # This is needed otherwise SetBarText goes mad
1502 self
._controlCreated
= False
1504 for child
in children
:
1505 userData
= child
.GetUserData()
1507 if isinstance(userData
, ButtonInfo
):
1508 # It is a ButtonInfo, can't be anything else
1509 self
.AddButton(child
.GetUserData())
1510 elif isinstance(userData
, Separator
):
1514 if child
.IsSpacer():
1515 # This is a spacer, expandable or not
1516 self
.AddSpacer(child
.GetSize(), child
.GetProportion(),
1519 # This is a wxPython control
1520 self
.AddControl(child
.GetWindow(), child
.GetProportion(),
1521 child
.GetFlag(), child
.GetBorder())
1523 self
.SetSizer(self
._mainsizer
)
1525 # Now add the text. It doesn't matter if there is no text
1526 self
.SetBarText(text
)
1533 def DoGetBestSize(self
):
1534 """ Returns the best size of ButtonPanel. """
1536 w
= h
= btnWidth
= btnHeight
= 0
1537 isVertical
= self
.IsVertical()
1539 padding
= self
._art
.GetMetric(BP_PADDING_SIZE
)
1540 border
= self
._art
.GetMetric(BP_BORDER_SIZE
)
1541 margins
= self
._art
.GetMetric(BP_MARGINS_SIZE
)
1542 separator_size
= self
._art
.GetMetric(BP_SEPARATOR_SIZE
)
1544 # Add the space required for the main caption
1545 if self
.HasBarText():
1546 w
, h
= self
._text
.GetBestSize()
1554 # Add the button's sizes
1555 for btn
in self
._vButtons
:
1557 bw
, bh
= btn
.GetBestSize()
1558 btnWidth
= max(btnWidth
, bw
)
1559 btnHeight
= max(btnHeight
, bh
)
1562 w
= max(w
, btnWidth
)
1565 h
= max(h
, btnHeight
)
1568 # Add the control's sizes
1569 for control
in self
.GetControls():
1570 cw
, ch
= control
.GetSize()
1578 # Add the separator's sizes and the 2 SizerItems at the beginning
1580 if self
.IsVertical():
1581 h
+= 2*margins
.y
+ len(self
._vSeparators
)*separator_size
1583 w
+= 2*margins
.x
+ len(self
._vSeparators
)*separator_size
1585 return wx
.Size(w
, h
)
1588 def OnPaint(self
, event
):
1589 """ Handles the wx.EVT_PAINT event for ButtonPanel. """
1591 dc
= wx
.BufferedPaintDC(self
)
1592 rect
= self
.GetClientRect()
1594 self
._art
.DrawButtonPanel(dc
, rect
, self
._nStyle
)
1595 self
._mainsizer
.Draw(dc
)
1598 def OnEraseBackground(self
, event
):
1599 """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
1604 def OnSize(self
, event
):
1605 """ Handles the wx.EVT_SIZE event for ButtonPanel. """
1607 # NOTE: It seems like LayoutItems number of calls can be optimized in some way.
1608 # Currently every DoLayout (or every parent Layout()) calls about 3 times
1609 # the LayoutItems method. Any idea on how to improve it?
1616 def LayoutItems(self
):
1618 Layout the items using a different algorithm depending on the existance
1619 of the main caption.
1622 nonspacers
, allchildren
= self
.GetNonFlexibleChildren()
1624 if self
.HasBarText():
1625 self
.FlexibleLayout(nonspacers
, allchildren
)
1627 self
.SizeLayout(nonspacers
, allchildren
)
1629 self
._mainsizer
.Layout()
1632 def SizeLayout(self
, nonspacers
, children
):
1633 """ Layout the items when no main caption exists. """
1635 size
= self
.GetSize()
1636 isVertical
= self
.IsVertical()
1639 indx1
= len(nonspacers
)
1641 for item
in nonspacers
:
1642 corner
+= self
.GetItemSize(item
, isVertical
)
1643 if corner
> size
[isVertical
]:
1644 indx1
= nonspacers
.index(item
)
1647 # Leave out the last spacer, it has to be there always
1648 for ii
in xrange(len(nonspacers
)-1):
1649 indx
= children
.index(nonspacers
[ii
])
1650 self
._mainsizer
.Show(indx
, ii
< indx1
)
1653 def GetItemSize(self
, item
, isVertical
):
1654 """ Returns the size of an item in the main ButtonPanel sizer. """
1656 if item
.GetUserData():
1657 return item
.GetUserData().GetBestSize()[isVertical
]
1659 return item
.GetSize()[isVertical
]
1662 def FlexibleLayout(self
, nonspacers
, allchildren
):
1663 """ Layout the items when the main caption exists. """
1665 if len(nonspacers
) < 2:
1668 isVertical
= self
.IsVertical()
1669 isStandard
= self
.IsStandard()
1671 size
= self
.GetSize()[isVertical
]
1672 padding
= self
._art
.GetMetric(BP_PADDING_SIZE
)
1674 fixed
= (isStandard
and [nonspacers
[1]] or [nonspacers
[-2]])[0]
1677 nonspacers
.reverse()
1678 leftendx
= fixed
.GetSize()[isVertical
] + padding
.x
1680 rightstartx
= size
- fixed
.GetSize()[isVertical
]
1683 count
= lennonspacers
= len(nonspacers
)
1685 for item
in nonspacers
:
1687 size
-= self
.GetItemSize(item
, isVertical
)
1691 size
+= self
.GetItemSize(item
, isVertical
)
1692 if size
> rightstartx
:
1697 nonspacers
.reverse()
1699 for jj
in xrange(2, lennonspacers
):
1700 indx
= allchildren
.index(nonspacers
[jj
])
1701 self
._mainsizer
.Show(indx
, jj
>= count
)
1704 def GetNonFlexibleChildren(self
):
1706 Returns all the ButtonPanel main sizer's children that are not
1711 children2
= self
._mainsizer
.GetChildren()
1713 for child
in children2
:
1714 if child
.IsSpacer():
1715 if child
.GetUserData() or child
.GetProportion() == 0:
1716 children1
.append(child
)
1718 children1
.append(child
)
1720 return children1
, children2
1723 def GetControls(self
):
1724 """ Returns the wxPython controls that belongs to ButtonPanel. """
1726 children2
= self
._mainsizer
.GetChildren()
1727 children1
= [child
for child
in children2
if not child
.IsSpacer()]
1732 def SetStyle(self
, style
):
1733 """ Sets ButtonPanel style. """
1735 if style
== self
._nStyle
:
1738 self
._nStyle
= style
1743 """ Returns the ButtonPanel style. """
1748 def OnMouseMove(self
, event
):
1749 """ Handles the wx.EVT_MOTION event for ButtonPanel. """
1751 # Check to see if we are hovering a button
1752 tabId
, flags
= self
.HitTest(event
.GetPosition())
1754 if flags
!= BP_HT_BUTTON
:
1756 self
.RepaintOldSelection()
1757 self
._currentButton
= -1
1760 btn
= self
._vButtons
[tabId
]
1762 if not btn
.IsEnabled():
1764 self
.RepaintOldSelection()
1767 if tabId
!= self
._currentButton
:
1768 self
.RepaintOldSelection()
1770 if btn
.GetRect().Contains(event
.GetPosition()):
1771 if btn
.GetStatus() != "Pressed":
1772 btn
.SetStatus("Hover")
1774 btn
.SetStatus("Normal")
1776 if tabId
!= self
._currentButton
:
1778 self
.DoGiveHelp(btn
)
1780 self
._currentButton
= tabId
1785 def OnLeftDown(self
, event
):
1786 """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
1788 tabId
, hit
= self
.HitTest(event
.GetPosition())
1790 if hit
== BP_HT_BUTTON
:
1791 btn
= self
._vButtons
[tabId
]
1793 btn
.SetStatus("Pressed")
1794 self
._currentButton
= tabId
1797 def OnLeftUp(self
, event
):
1798 """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
1800 tabId
, flags
= self
.HitTest(event
.GetPosition())
1802 if flags
!= BP_HT_BUTTON
:
1805 hit
= self
._vButtons
[tabId
]
1807 if hit
.GetStatus() == "Disabled":
1810 for btn
in self
._vButtons
:
1814 if hit
.GetStatus() == "Pressed":
1815 hit
.SetToggled(not hit
.GetToggled())
1817 # Update the button status to be hovered
1818 hit
.SetStatus("Hover")
1820 self
._currentButton
= tabId
1822 # Fire a button click event
1823 btnEvent
= wx
.CommandEvent(wx
.wxEVT_COMMAND_BUTTON_CLICKED
, hit
.GetId())
1824 self
.GetEventHandler().ProcessEvent(btnEvent
)
1827 def OnMouseLeave(self
, event
):
1828 """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
1830 # Reset all buttons statuses
1831 for btn
in self
._vButtons
:
1832 if not btn
.IsEnabled():
1834 btn
.SetStatus("Normal")
1841 def OnMouseEnterWindow(self
, event
):
1842 """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
1844 tabId
, flags
= self
.HitTest(event
.GetPosition())
1846 if flags
== BP_HT_BUTTON
:
1848 hit
= self
._vButtons
[tabId
]
1850 if hit
.GetStatus() == "Disabled":
1854 self
.DoGiveHelp(hit
)
1855 self
._currentButton
= tabId
1860 def DoGiveHelp(self
, hit
):
1861 """ Gives tooltips and help in StatusBar. """
1863 if not self
.GetUseHelp():
1866 shortHelp
= hit
.GetShortHelp()
1868 self
.SetToolTipString(shortHelp
)
1869 self
._haveTip
= True
1871 longHelp
= hit
.GetLongHelp()
1875 topLevel
= wx
.GetTopLevelParent(self
)
1877 if isinstance(topLevel
, wx
.Frame
) and topLevel
.GetStatusBar():
1878 statusBar
= topLevel
.GetStatusBar()
1880 if self
._statusTimer
and self
._statusTimer
.IsRunning():
1881 self
._statusTimer
.Stop()
1882 statusBar
.PopStatusText(0)
1884 statusBar
.PushStatusText(longHelp
, 0)
1885 self
._statusTimer
= StatusBarTimer(self
)
1886 self
._statusTimer
.Start(_DELAY
, wx
.TIMER_ONE_SHOT
)
1889 def RemoveHelp(self
):
1890 """ Removes the tooltips and statusbar help (if any) for a button. """
1892 if not self
.GetUseHelp():
1896 self
.SetToolTipString("")
1897 self
._haveTip
= False
1899 if self
._statusTimer
and self
._statusTimer
.IsRunning():
1900 topLevel
= wx
.GetTopLevelParent(self
)
1901 statusBar
= topLevel
.GetStatusBar()
1902 self
._statusTimer
.Stop()
1903 statusBar
.PopStatusText(0)
1904 self
._statusTimer
= None
1907 def RepaintOldSelection(self
):
1908 """ Repaints the old selected/hovered button. """
1910 current
= self
._currentButton
1915 btn
= self
._vButtons
[current
]
1916 if not btn
.IsEnabled():
1919 btn
.SetStatus("Normal")
1922 def OnStatusBarTimer(self
):
1923 """ Handles the timer expiring to delete the longHelp in the StatusBar. """
1925 topLevel
= wx
.GetTopLevelParent(self
)
1926 statusBar
= topLevel
.GetStatusBar()
1927 statusBar
.PopStatusText(0)
1930 def SetUseHelp(self
, useHelp
=True):
1931 """ Sets whether or not shortHelp and longHelp should be displayed. """
1933 self
._useHelp
= useHelp
1936 def GetUseHelp(self
):
1937 """ Returns whether or not shortHelp and longHelp should be displayed. """
1939 return self
._useHelp
1942 def HitTest(self
, pt
):
1944 HitTest method for ButtonPanel. Returns the button (if any) and
1948 for ii
in xrange(len(self
._vButtons
)):
1949 if not self
._vButtons
[ii
].IsEnabled():
1951 if self
._vButtons
[ii
].GetRect().Contains(pt
):
1952 return ii
, BP_HT_BUTTON
1954 return -1, BP_HT_NONE
1958 """ Returns the associated BPArt art provider. """
1963 def SetBPArt(self
, art
):
1964 """ Sets a new BPArt to ButtonPanel. Useful only if another BPArt class is used. """
1969 if wx
.VERSION
< (2,7,1,1):
1971 """Freeze ButtonPanel."""
1973 self
._freezeCount
= self
._freezeCount
+ 1
1974 wx
.PyPanel
.Freeze(self
)
1978 """Thaw ButtonPanel."""
1980 if self
._freezeCount
== 0:
1981 raise "\nERROR: Thawing Unfrozen ButtonPanel?"
1983 self
._freezeCount
= self
._freezeCount
- 1
1984 wx
.PyPanel
.Thaw(self
)
1988 """ Returns whether a call to Freeze() has been done. """
1990 return self
._freezeCount
!= 0