]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/buttonpanel.py
updates from Andrea, plus some fixes
[wxWidgets.git] / wxPython / wx / lib / buttonpanel.py
1 # --------------------------------------------------------------------------- #
2 # FANCYBUTTONPANEL Widget wxPython IMPLEMENTATION
3 #
4 # Original C++ Code From Eran. You Can Find It At:
5 #
6 # http://wxforum.shadonet.com/viewtopic.php?t=6619
7 #
8 # License: wxWidgets license
9 #
10 #
11 # Python Code By:
12 #
13 # Andrea Gavana, @ 02 Oct 2006
14 # Latest Revision: 16 Oct 2006, 17.00 GMT
15 #
16 #
17 # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
18 # Write To Me At:
19 #
20 # andrea.gavana@gmail.com
21 # gavana@kpo.kz
22 #
23 # Or, Obviously, To The wxPython Mailing List!!!
24 #
25 #
26 # End Of Comments
27 # --------------------------------------------------------------------------- #
28
29 """
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.
36
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.
39
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
42 classic look).
43
44
45 Usage
46 -----
47
48 The following example shows a simple implementation that uses ButtonPanel
49 inside a very simple frame::
50
51 class MyFrame(wx.Frame):
52
53 def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition,
54 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
55
56 wx.Frame.__init__(self, parent, id, title, pos, size, style)
57
58 mainPanel = wx.Panel(self, -1)
59 self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE)
60
61 vSizer = wx.BoxSizer(wx.VERTICAL)
62 mainPanel.SetSizer(vSizer)
63
64 alignment = BP_ALIGN_RIGHT
65
66 titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo")
67
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)
71
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)
75
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)
79
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)
83
84 vSizer.Add(titleBar, 0, wx.EXPAND)
85 vSizer.Add((20, 20))
86 vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5)
87
88 vSizer.Layout()
89
90 # our normal wxApp-derived class, as usual
91
92 app = wx.PySimpleApp()
93
94 frame = MyFrame(None)
95 app.SetTopWindow(frame)
96 frame.Show()
97
98 app.MainLoop()
99
100
101 License And Version:
102
103 ButtonPanel Is Freeware And Distributed Under The wxPython License.
104
105 Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT
106 Version 0.3.
107
108 """
109
110
111 import wx
112
113 # Some constants to tune the BPArt class
114 BP_BACKGROUND_COLOR = 0
115 """ Background brush colour when no gradient shading exists. """
116 BP_GRADIENT_COLOR_FROM = 1
117 """ Starting gradient colour, used only when BP_USE_GRADIENT style is applied. """
118 BP_GRADIENT_COLOR_TO = 2
119 """ Ending gradient colour, used only when BP_USE_GRADIENT style is applied. """
120 BP_BORDER_COLOR = 3
121 """ Pen colour to paint the border of ButtonPanel. """
122 BP_TEXT_COLOR = 4
123 """ Main ButtonPanel caption colour. """
124 BP_BUTTONTEXT_COLOR = 5
125 """ Text colour for buttons with text. """
126 BP_BUTTONTEXT_INACTIVE_COLOR = 6
127 """ Text colour for inactive buttons with text. """
128 BP_SELECTION_BRUSH_COLOR = 7
129 """ Brush colour to be used when hovering or selecting a button. """
130 BP_SELECTION_PEN_COLOR = 8
131 """ Pen colour to be used when hovering or selecting a button. """
132 BP_SEPARATOR_COLOR = 9
133 """ Pen colour used to paint the separators. """
134 BP_TEXT_FONT = 10
135 """ Font of the ButtonPanel main caption. """
136 BP_BUTTONTEXT_FONT = 11
137 """ Text font for the buttons with text. """
138
139 BP_BUTTONTEXT_ALIGN_BOTTOM = 12
140 """ Flag that indicates the image and text in buttons is stacked. """
141 BP_BUTTONTEXT_ALIGN_RIGHT = 13
142 """ Flag that indicates the text is shown alongside the image in buttons with text. """
143
144 BP_SEPARATOR_SIZE = 14
145 """
146 Separator size. NB: This is not the line width, but the sum of the space before
147 and after the separator line plus the width of the line.
148 """
149 BP_MARGINS_SIZE = 15
150 """
151 Size of the left/right margins in ButtonPanel (top/bottom for vertically
152 aligned ButtonPanels).
153 """
154 BP_BORDER_SIZE = 16
155 """ Size of the border. """
156 BP_PADDING_SIZE = 17
157 """ Inter-tool separator size. """
158
159 # Caption Gradient Type
160 BP_GRADIENT_NONE = 0
161 """ No gradient shading should be used to paint the background. """
162 BP_GRADIENT_VERTICAL = 1
163 """ Vertical gradient shading should be used to paint the background. """
164 BP_GRADIENT_HORIZONTAL = 2
165 """ Horizontal gradient shading should be used to paint the background. """
166
167 # Flags for HitTest() method
168 BP_HT_BUTTON = 200
169 BP_HT_NONE = 201
170
171 # Alignment of buttons in the panel
172 BP_ALIGN_RIGHT = 1
173 BP_ALIGN_LEFT = 2
174 BP_ALIGN_TOP = 4
175 BP_ALIGN_BOTTOM = 8
176
177 # ButtonPanel styles
178 BP_DEFAULT_STYLE = 1
179 BP_USE_GRADIENT = 2
180
181
182 # Check for the new method in 2.7 (not present in 2.6.3.3)
183 if wx.VERSION_STRING < "2.7":
184 wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point)
185
186
187 def BrightenColour(color, factor):
188 """ Bright the input colour by a factor."""
189
190 val = color.Red()*factor
191 if val > 255:
192 red = 255
193 else:
194 red = val
195
196 val = color.Green()*factor
197 if val > 255:
198 green = 255
199 else:
200 green = val
201
202 val = color.Blue()*factor
203 if val > 255:
204 blue = 255
205 else:
206 blue = val
207
208 return wx.Color(red, green, blue)
209
210
211 def GrayOut(anImage):
212 """
213 Convert the given image (in place) to a grayed-out version,
214 appropriate for a 'Disabled' appearance.
215 """
216
217 factor = 0.7 # 0 < f < 1. Higher Is Grayer
218
219 anImage = anImage.ConvertToImage()
220 if anImage.HasAlpha():
221 anImage.ConvertAlphaToMask(1)
222
223 if anImage.HasMask():
224 maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
225 else:
226 maskColor = None
227
228 data = map(ord, list(anImage.GetData()))
229
230 for i in range(0, len(data), 3):
231
232 pixel = (data[i], data[i+1], data[i+2])
233 pixel = MakeGray(pixel, factor, maskColor)
234
235 for x in range(3):
236 data[i+x] = pixel[x]
237
238 anImage.SetData(''.join(map(chr, data)))
239
240 anImage = anImage.ConvertToBitmap()
241
242 return anImage
243
244
245 def MakeGray((r,g,b), factor, maskColor):
246 """
247 Make a pixel grayed-out. If the pixel matches the maskColor, it won't be
248 changed.
249 """
250
251 if (r,g,b) != maskColor:
252 return map(lambda x: int((230 - x) * factor) + x, (r,g,b))
253 else:
254 return (r,g,b)
255
256
257 # ---------------------------------------------------------------------------- #
258 # Class BPArt
259 # Handles all the drawings for buttons, separators and text and allows the
260 # programmer to set colours, sizes and gradient shadings for ButtonPanel
261 # ---------------------------------------------------------------------------- #
262
263 class BPArt:
264 """
265 BPArt is an art provider class which does all of the drawing for ButtonPanel.
266 This allows the library caller to customize the BPArt or to completely replace
267 all drawing with custom BPArts.
268 """
269
270 def __init__(self, parentStyle):
271 """ Default class constructor. """
272
273 base_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)
274
275 self._background_brush = wx.Brush(base_color, wx.SOLID)
276 self._gradient_color_to = wx.WHITE
277 self._gradient_color_from = wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
278
279 if parentStyle & BP_USE_GRADIENT:
280 self._border_pen = wx.Pen(wx.WHITE, 3)
281 self._caption_text_color = wx.WHITE
282 self._buttontext_color = wx.Colour(70, 143, 255)
283 self._separator_pen = wx.Pen(BrightenColour(self._gradient_color_from, 1.4))
284 self._gradient_type = BP_GRADIENT_VERTICAL
285 else:
286 self._border_pen = wx.Pen(BrightenColour(base_color, 0.9), 3)
287 self._caption_text_color = wx.BLACK
288 self._buttontext_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNTEXT)
289 self._separator_pen = wx.Pen(BrightenColour(base_color, 0.9))
290 self._gradient_type = BP_GRADIENT_NONE
291
292 self._buttontext_inactive_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT)
293 self._selection_brush = wx.Brush(wx.Color(225, 225, 255))
294 self._selection_pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION))
295
296 sysfont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
297 self._caption_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.BOLD,
298 False, sysfont.GetFaceName())
299 self._buttontext_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.NORMAL,
300 False, sysfont.GetFaceName())
301
302 self._separator_size = 7
303 self._margins_size = wx.Size(6, 6)
304 self._caption_border_size = 3
305 self._padding_size = wx.Size(6, 6)
306
307
308 def GetMetric(self, id):
309 """ Returns sizes of customizable options. """
310
311 if id == BP_SEPARATOR_SIZE:
312 return self._separator_size
313 elif id == BP_MARGINS_SIZE:
314 return self._margins_size
315 elif id == BP_BORDER_SIZE:
316 return self._caption_border_size
317 elif id == BP_PADDING_SIZE:
318 return self._padding_size
319 else:
320 raise "\nERROR: Invalid Metric Ordinal. "
321
322
323 def SetMetric(self, id, new_val):
324 """ Sets sizes for customizable options. """
325
326 if id == BP_SEPARATOR_SIZE:
327 self._separator_size = new_val
328 elif id == BP_MARGINS_SIZE:
329 self._margins_size = new_val
330 elif id == BP_BORDER_SIZE:
331 self._caption_border_size = new_val
332 self._border_pen.SetWidth(new_val)
333 elif id == BP_PADDING_SIZE:
334 self._padding_size = new_val
335 else:
336 raise "\nERROR: Invalid Metric Ordinal. "
337
338
339 def GetColor(self, id):
340 """ Returns colours of customizable options. """
341
342 if id == BP_BACKGROUND_COLOR:
343 return self._background_brush.GetColour()
344 elif id == BP_GRADIENT_COLOR_FROM:
345 return self._gradient_color_from
346 elif id == BP_GRADIENT_COLOR_TO:
347 return self._gradient_color_to
348 elif id == BP_BORDER_COLOR:
349 return self._border_pen.GetColour()
350 elif id == BP_TEXT_COLOR:
351 return self._caption_text_color
352 elif id == BP_BUTTONTEXT_COLOR:
353 return self._buttontext_color
354 elif id == BP_BUTTONTEXT_INACTIVE_COLOR:
355 return self._buttontext_inactive_color
356 elif id == BP_SELECTION_BRUSH_COLOR:
357 return self._selection_brush.GetColour()
358 elif id == BP_SELECTION_PEN_COLOR:
359 return self._selection_pen.GetColour()
360 elif id == BP_SEPARATOR_COLOR:
361 return self._separator_pen.GetColour()
362 else:
363 raise "\nERROR: Invalid Colour Ordinal. "
364
365
366 def SetColor(self, id, colour):
367 """ Sets colours for customizable options. """
368
369 if id == BP_BACKGROUND_COLOR:
370 self._background_brush.SetColour(colour)
371 elif id == BP_GRADIENT_COLOR_FROM:
372 self._gradient_color_from = colour
373 elif id == BP_GRADIENT_COLOR_TO:
374 self._gradient_color_to = colour
375 elif id == BP_BORDER_COLOR:
376 self._border_pen.SetColour(colour)
377 elif id == BP_TEXT_COLOR:
378 self._caption_text_color = colour
379 elif id == BP_BUTTONTEXT_COLOR:
380 self._buttontext_color = colour
381 elif id == BP_BUTTONTEXT_INACTIVE_COLOR:
382 self._buttontext_inactive_color = colour
383 elif id == BP_SELECTION_BRUSH_COLOR:
384 self._selection_brush.SetColour(colour)
385 elif id == BP_SELECTION_PEN_COLOR:
386 self._selection_pen.SetColour(colour)
387 elif id == BP_SEPARATOR_COLOR:
388 self._separator_pen.SetColour(colour)
389 else:
390 raise "\nERROR: Invalid Colour Ordinal. "
391
392
393 GetColour = GetColor
394 SetColour = SetColor
395
396
397 def SetFont(self, id, font):
398 """ Sets font for customizable options. """
399
400 if id == BP_TEXT_FONT:
401 self._caption_font = font
402 elif id == BP_BUTTONTEXT_FONT:
403 self._buttontext_font = font
404
405
406 def GetFont(self, id):
407 """ Returns font of customizable options. """
408
409 if id == BP_TEXT_FONT:
410 return self._caption_font
411 elif id == BP_BUTTONTEXT_FONT:
412 return self._buttontext_font
413
414 return wx.NoneFont
415
416
417 def SetGradientType(self, gradient):
418 """ Sets the gradient type for BPArt drawings. """
419
420 self._gradient_type = gradient
421
422
423 def GetGradientType(self):
424 """ Returns the gradient type for BPArt drawings. """
425
426 return self._gradient_type
427
428
429 def DrawSeparator(self, dc, rect, isVertical):
430 """ Draws a separator in ButtonPanel. """
431
432 dc.SetPen(self._separator_pen)
433
434 if isVertical:
435 ystart = yend = rect.y + rect.height/2
436 xstart = int(rect.x + 1.5*self._caption_border_size)
437 xend = int(rect.x + rect.width - 1.5*self._caption_border_size)
438 dc.DrawLine(xstart, ystart, xend, yend)
439 else:
440 xstart = xend = rect.x + rect.width/2
441 ystart = int(rect.y + 1.5*self._caption_border_size)
442 yend = int(rect.y + rect.height - 1.5*self._caption_border_size)
443 dc.DrawLine(xstart, ystart, xend, yend)
444
445
446 def DrawCaption(self, dc, rect, captionText):
447 """ Draws the main caption text in ButtonPanel. """
448
449 textColour = self._caption_text_color
450 textFont = self._caption_font
451 padding = self._padding_size
452
453 dc.SetTextForeground(textColour)
454 dc.SetFont(textFont)
455
456 dc.DrawText(captionText, rect.x + padding.x, rect.y+padding.y)
457
458
459 def DrawButton(self, dc, rect, parentSize, buttonBitmap, isVertical,
460 buttonStatus, isToggled, textAlignment, text=""):
461 """ Draws a button in ButtonPanel, together with its text (if any). """
462
463 bmpxsize, bmpysize = buttonBitmap.GetWidth(), buttonBitmap.GetHeight()
464 dx = dy = focus = 0
465
466 borderw = self._caption_border_size
467 padding = self._padding_size
468
469 buttonFont = self._buttontext_font
470 dc.SetFont(buttonFont)
471
472 if isVertical:
473
474 rect = wx.Rect(borderw, rect.y, rect.width-2*borderw, rect.height)
475
476 if text != "":
477
478 textW, textH = dc.GetTextExtent(text)
479
480 if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT:
481 fullExtent = bmpxsize + padding.x/2 + textW
482 bmpypos = rect.y + (rect.height - bmpysize)/2
483 bmpxpos = rect.x + (rect.width - fullExtent)/2
484 textxpos = bmpxpos + padding.x/2 + bmpxsize
485 textypos = bmpypos + (bmpysize - textH)/2
486 else:
487 bmpxpos = rect.x + (rect.width - bmpxsize)/2
488 bmpypos = rect.y + padding.y
489 textxpos = rect.x + (rect.width - textW)/2
490 textypos = bmpypos + bmpysize + padding.y/2
491 else:
492 bmpxpos = rect.x + (rect.width - bmpxsize)/2
493 bmpypos = rect.y + (rect.height - bmpysize)/2
494
495
496 else:
497
498 rect = wx.Rect(rect.x, borderw, rect.width, rect.height-2*borderw)
499
500 if text != "":
501
502 textW, textH = dc.GetTextExtent(text)
503
504 if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT:
505 fullExtent = bmpxsize + padding.x/2 + textW
506 bmpypos = rect.y + (rect.height - bmpysize)/2
507 bmpxpos = rect.x + (rect.width - fullExtent)/2
508 textxpos = bmpxpos + padding.x/2 + bmpxsize
509 textypos = bmpypos + (bmpysize - textH)/2
510 else:
511 fullExtent = bmpysize + padding.y/2 + textH
512 bmpxpos = rect.x + (rect.width - bmpxsize)/2
513 bmpypos = rect.y + (rect.height - fullExtent)/2
514 textxpos = rect.x + (rect.width - textW)/2
515 textypos = bmpypos + bmpysize + padding.y/2
516 else:
517 bmpxpos = rect.x + (rect.width - bmpxsize)/2
518 bmpypos = rect.y + (rect.height - bmpysize)/2
519
520 # Draw a button
521 # [ Padding | Text | .. Buttons .. | Padding ]
522
523 if buttonStatus in ["Pressed", "Toggled", "Hover"]:
524 dc.SetBrush(self._selection_brush)
525 dc.SetPen(self._selection_pen)
526 dc.DrawRoundedRectangleRect(rect, 4)
527
528 if buttonStatus == "Pressed" or isToggled:
529 dx = dy = 1
530
531 dc.DrawBitmap(buttonBitmap, bmpxpos+dx, bmpypos+dy, True)
532
533 if text != "":
534 isEnabled = buttonStatus != "Disabled"
535 self.DrawLabel(dc, text, isEnabled, textxpos+dx, textypos+dy)
536
537
538 def DrawLabel(self, dc, text, isEnabled, xpos, ypos):
539 """ Draws the label for a button. """
540
541 if not isEnabled:
542 dc.SetTextForeground(self._buttontext_inactive_color)
543 else:
544 dc.SetTextForeground(self._buttontext_color)
545
546 dc.DrawText(text, xpos, ypos)
547
548
549 def DrawButtonPanel(self, dc, rect, style):
550 """ Paint the ButtonPanel's background. """
551
552 if style & BP_USE_GRADIENT:
553 # Draw gradient color in the backgroud of the panel
554 self.FillGradientColor(dc, rect)
555
556 # Draw a rectangle around the panel
557 backBrush = (style & BP_USE_GRADIENT and [wx.TRANSPARENT_BRUSH] or \
558 [self._background_brush])[0]
559
560 dc.SetBrush(backBrush)
561 dc.SetPen(self._border_pen)
562 dc.DrawRectangleRect(rect)
563
564
565 def FillGradientColor(self, dc, rect):
566 """ Gradient fill from colour 1 to colour 2 with top to bottom or left to right. """
567
568 if rect.height < 1 or rect.width < 1:
569 return
570
571 isVertical = self._gradient_type == BP_GRADIENT_VERTICAL
572 size = (isVertical and [rect.height] or [rect.width])[0]
573 start = (isVertical and [rect.y] or [rect.x])[0]
574
575 # calculate gradient coefficients
576
577 col2 = self._gradient_color_from
578 col1 = self._gradient_color_to
579
580 rf, gf, bf = 0, 0, 0
581 rstep = float((col2.Red() - col1.Red()))/float(size)
582 gstep = float((col2.Green() - col1.Green()))/float(size)
583 bstep = float((col2.Blue() - col1.Blue()))/float(size)
584
585 for coord in xrange(start, start + size):
586
587 currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf)
588 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
589 dc.SetPen(wx.Pen(currCol))
590 if isVertical:
591 dc.DrawLine(rect.x, coord, rect.x + rect.width, coord)
592 else:
593 dc.DrawLine(coord, rect.y, coord, rect.y + rect.width)
594
595 rf += rstep
596 gf += gstep
597 bf += bstep
598
599
600 class Control(wx.EvtHandler):
601
602 def __init__(self, parent, size=wx.Size(-1, -1)):
603 """
604 Default class constructor.
605
606 Base class for all pseudo controls
607 parent = parent object
608 size = (width, height)
609 """
610
611 wx.EvtHandler.__init__(self)
612
613 self._parent = parent
614 self._id = wx.NewId()
615 self._size = size
616 self._isshown = True
617 self._focus = False
618
619
620 def Show(self, show=True):
621 """ Shows or hide the control. """
622
623 self._isshown = show
624
625
626 def Hide(self):
627 """ Hides the control. """
628
629 self.Show(False)
630
631
632 def IsShown(self):
633 """ Returns whether the control is shown or not. """
634
635 return self._isshown
636
637
638 def GetId(self):
639 """ Returns the control id. """
640
641 return self._id
642
643
644 def GetBestSize(self):
645 """ Returns the control best size. """
646
647 return self._size
648
649
650 def Disable(self):
651 """ Disables the control. """
652
653 self.Enable(False)
654
655
656 def Enable(self, value=True):
657 """ Enables or disables the control. """
658
659 self.disabled = not value
660
661
662 def SetFocus(self, focus=True):
663 """ Sets or kills the focus on the control. """
664
665 self._focus = focus
666
667
668 def HasFocus(self):
669 """ Returns whether the control has the focus or not. """
670
671 return self._focus
672
673
674 def OnMouseEvent(self, x, y, event):
675 pass
676
677 def Draw(self, rect):
678 pass
679
680
681
682 class Sizer(object):
683 """
684 Sizer
685
686 This is a mix-in class to add pseudo support to a wx sizer. Just create
687 a new class that derives from this class and the wx sizer and intercepts
688 any methods that add to the wx sizer.
689 """
690 def __init__(self):
691 self.children = [] # list of child Pseudo Controls
692
693 # Sizer doesn't use the x1,y1,x2,y2 so allow it to
694 # be called with or without the coordinates
695 def Draw(self, dc, x1=0, y1=0, x2=0, y2=0):
696 for item in self.children:
697 # use sizer coordinates rather than
698 # what is passed in
699 c = item.GetUserData()
700 c.Draw(dc, item.GetRect())
701
702 def GetBestSize(self):
703 # this should be handled by the wx.Sizer based class
704 return self.GetMinSize()
705
706
707 # Pseudo BoxSizer
708 class BoxSizer(Sizer, wx.BoxSizer):
709 def __init__(self, orient=wx.HORIZONTAL):
710 wx.BoxSizer.__init__(self, orient)
711 Sizer.__init__(self)
712
713 #-------------------------------------------
714 # sizer overrides (only called from Python)
715 #-------------------------------------------
716 # no support for user data if it's a pseudocontrol
717 # since that is already used
718 def Add(self, item, proportion=0, flag=0, border=0, userData=None):
719 # check to see if it's a pseudo object or sizer
720 if isinstance(item, Sizer):
721 szitem = wx.BoxSizer.Add(self, item, proportion, flag, border, item)
722 self.children.append(szitem)
723 elif isinstance(item, Control): # Control should be what ever class your controls come from
724 sz = item.GetBestSize()
725 # add a spacer to track this object
726 szitem = wx.BoxSizer.Add(self, sz, proportion, flag, border, item)
727 self.children.append(szitem)
728 else:
729 wx.BoxSizer.Add(self, item, proportion, flag, border, userData)
730
731 def Prepend(self, item, proportion=0, flag=0, border=0, userData=None):
732 # check to see if it's a pseudo object or sizer
733 if isinstance(item, Sizer):
734 szitem = wx.BoxSizer.Prepend(self, item, proportion, flag, border, item)
735 self.children.append(szitem)
736 elif isinstance(item, Control): # Control should be what ever class your controls come from
737 sz = item.GetBestSize()
738 # add a spacer to track this object
739 szitem = wx.BoxSizer.Prepend(self, sz, proportion, flag, border, item)
740 self.children.insert(0,szitem)
741 else:
742 wx.BoxSizer.Prepend(self, item, proportion, flag, border, userData)
743
744 def Insert(self, before, item, proportion=0, flag=0, border=0, userData=None, realIndex=None):
745 # check to see if it's a pseudo object or sizer
746 if isinstance(item, Sizer):
747 szitem = wx.BoxSizer.Insert(self, before, item, proportion, flag, border, item)
748 self.children.append(szitem)
749 elif isinstance(item, Control): # Control should be what ever class your controls come from
750 sz = item.GetBestSize()
751 # add a spacer to track this object
752 szitem = wx.BoxSizer.Insert(self, before, sz, proportion, flag, border, item)
753 if realIndex is not None:
754 self.children.insert(realIndex,szitem)
755 else:
756 self.children.insert(before,szitem)
757
758 else:
759 wx.BoxSizer.Insert(self, before, item, proportion, flag, border, userData)
760
761
762 def Remove(self, indx, pop=-1):
763
764 if pop >= 0:
765 self.children.pop(pop)
766
767 wx.BoxSizer.Remove(self, indx)
768
769
770 def Layout(self):
771
772 for ii, child in enumerate(self.GetChildren()):
773 item = child.GetUserData()
774 if item and child.IsShown():
775 self.SetItemMinSize(ii, *item.GetBestSize())
776
777 wx.BoxSizer.Layout(self)
778
779
780 def Show(self, item, show=True):
781
782 child = self.GetChildren()[item]
783 if child and child.GetUserData():
784 child.GetUserData().Show(show)
785
786 wx.BoxSizer.Show(self, item, show)
787
788
789 # ---------------------------------------------------------------------------- #
790 # Class Separator
791 # This class holds all the information to size and draw a separator inside
792 # ButtonPanel
793 # ---------------------------------------------------------------------------- #
794
795 class Separator(Control):
796
797 def __init__(self, parent):
798 """ Default class constructor. """
799
800 self._isshown = True
801 self._parent = parent
802 Control.__init__(self, parent)
803
804
805 def GetBestSize(self):
806 """ Returns the separator best size. """
807
808 # 10 is completely arbitrary, but it works anyhow
809 if self._parent.IsVertical():
810 return wx.Size(10, self._parent._art.GetMetric(BP_SEPARATOR_SIZE))
811 else:
812 return wx.Size(self._parent._art.GetMetric(BP_SEPARATOR_SIZE), 10)
813
814
815 def Draw(self, dc, rect):
816 """ Draws the separator. Actually the drawing is done in BPArt. """
817
818 if not self.IsShown():
819 return
820
821 isVertical = self._parent.IsVertical()
822 self._parent._art.DrawSeparator(dc, rect, isVertical)
823
824
825 # ---------------------------------------------------------------------------- #
826 # Class ButtonPanelText
827 # This class is used to hold data about the main caption in ButtonPanel
828 # ---------------------------------------------------------------------------- #
829
830 class ButtonPanelText(Control):
831
832 def __init__(self, parent, text=""):
833 """ Default class constructor. """
834
835 self._text = text
836 self._isshown = True
837 self._parent = parent
838
839 Control.__init__(self, parent)
840
841
842 def GetText(self):
843 """ Returns the caption text. """
844
845 return self._text
846
847
848 def SetText(self, text=""):
849 """ Sets the caption text. """
850
851 self._text = text
852
853
854 def CreateDC(self):
855 """ Convenience function to create a DC. """
856
857 dc = wx.ClientDC(self._parent)
858 textFont = self._parent._art.GetFont(BP_TEXT_FONT)
859 dc.SetFont(textFont)
860
861 return dc
862
863
864 def GetBestSize(self):
865 """ Returns the best size for the main caption in ButtonPanel. """
866
867 if self._text == "":
868 return wx.Size(0, 0)
869
870 dc = self.CreateDC()
871 rect = self._parent.GetClientRect()
872
873 tw, th = dc.GetTextExtent(self._text)
874 padding = self._parent._art.GetMetric(BP_PADDING_SIZE)
875 self._size = wx.Size(tw+2*padding.x, th+2*padding.y)
876
877 return self._size
878
879
880 def Draw(self, dc, rect):
881 """ Draws the main caption. Actually the drawing is done in BPArt. """
882
883 if not self.IsShown():
884 return
885
886 captionText = self.GetText()
887 self._parent._art.DrawCaption(dc, rect, captionText)
888
889
890 # -- ButtonInfo class implementation ----------------------------------------
891 # This class holds information about every button that is added to
892 # ButtonPanel. It is an auxiliary class that you should use
893 # every time you add a button.
894
895 class ButtonInfo(Control):
896
897 def __init__(self, parent, id=wx.ID_ANY, bmp=wx.NullBitmap,
898 status="Normal", text="", kind=wx.ITEM_NORMAL):
899 """
900 Default class constructor.
901
902 Parameters:
903 - parent: the parent window (ButtonPanel);
904 - id: the button id;
905 - bmp: the associated bitmap;
906 - status: button status (pressed, hovered, None).
907 - text to be displayed either below of to the right of the button
908 """
909
910 if id == wx.ID_ANY:
911 id = wx.NewId()
912
913 self._status = status
914 self._rect = wx.Rect()
915 self._text = text
916 self._kind = kind
917 self._toggle = False
918 self._textAlignment = BP_BUTTONTEXT_ALIGN_BOTTOM
919
920 disabledbmp = GrayOut(bmp)
921
922 self._bitmaps = {"Normal": bmp, "Toggled": None, "Disabled": disabledbmp,
923 "Hover": None, "Pressed": None}
924
925 Control.__init__(self, parent)
926
927
928 def GetBestSize(self):
929 """ Returns the best size for the button. """
930
931 xsize = self.GetBitmap().GetWidth()
932 ysize = self.GetBitmap().GetHeight()
933
934 if self.HasText():
935 # We have text in the button
936 dc = wx.ClientDC(self._parent)
937 normalFont = self._parent._art.GetFont(BP_BUTTONTEXT_FONT)
938 dc.SetFont(normalFont)
939 tw, th = dc.GetTextExtent(self.GetText())
940
941 if self.GetTextAlignment() == BP_BUTTONTEXT_ALIGN_BOTTOM:
942 xsize = max(xsize, tw)
943 ysize = ysize + th
944 else:
945 xsize = xsize + tw
946 ysize = max(ysize, th)
947
948 border = self._parent._art.GetMetric(BP_BORDER_SIZE)
949 padding = self._parent._art.GetMetric(BP_PADDING_SIZE)
950
951 if self._parent.IsVertical():
952 xsize = xsize + 2*border
953 else:
954 ysize = ysize + 2*border
955
956 self._size = wx.Size(xsize+2*padding.x, ysize+2*padding.y)
957
958 return self._size
959
960
961 def Draw(self, dc, rect):
962 """ Draws the button on ButtonPanel. Actually the drawing is done in BPArt. """
963
964 if not self.IsShown():
965 return
966
967 buttonBitmap = self.GetBitmap()
968 isVertical = self._parent.IsVertical()
969 text = self.GetText()
970 parentSize = self._parent.GetSize()[not isVertical]
971 buttonStatus = self.GetStatus()
972 isToggled = self.GetToggled()
973 textAlignment = self.GetTextAlignment()
974
975 self._parent._art.DrawButton(dc, rect, parentSize, buttonBitmap, isVertical,
976 buttonStatus, isToggled, textAlignment, text)
977
978 self.SetRect(rect)
979
980
981 def CheckRefresh(self, status):
982 """ Checks whether a ButtonPanel repaint is needed or not. Convenience function. """
983
984 if status == self._status:
985 self._parent.Refresh()
986
987
988 def SetBitmap(self, bmp, status="Normal"):
989 """ Sets the associated bitmap. """
990
991 self._bitmaps[status] = bmp
992 self.CheckRefresh(status)
993
994
995 def GetBitmap(self, status=None):
996 """ Returns the associated bitmap. """
997
998 if status is None:
999 status = self._status
1000
1001 if not self.IsEnabled():
1002 status = "Disabled"
1003
1004 if self._bitmaps[status] is None:
1005 return self._bitmaps["Normal"]
1006
1007 return self._bitmaps[status]
1008
1009
1010 def GetRect(self):
1011 """ Returns the button rect. """
1012
1013 return self._rect
1014
1015
1016 def GetStatus(self):
1017 """ Returns the button status. """
1018
1019 return self._status
1020
1021
1022 def GetId(self):
1023 """ Returns the button id. """
1024
1025 return self._id
1026
1027
1028 def SetRect(self, rect):
1029 """ Sets the button rect. """
1030
1031 self._rect = rect
1032
1033
1034 def SetStatus(self, status):
1035 """ Sets the button status. """
1036
1037 if status == self._status:
1038 return
1039
1040 if self.GetToggled() and status == "Normal":
1041 status = "Toggled"
1042
1043 self._status = status
1044 self._parent.Refresh()
1045
1046
1047 def GetTextAlignment(self):
1048 """ Returns the text alignment in the button (bottom or right). """
1049
1050 return self._textAlignment
1051
1052
1053 def SetTextAlignment(self, alignment):
1054 """ Sets the text alignment in the button (bottom or right). """
1055
1056 if alignment == self._textAlignment:
1057 return
1058
1059 self._alignment = alignment
1060
1061
1062 def GetToggled(self):
1063 """ Returns whether a wx.ITEM_CHECK button is toggled or not. """
1064
1065 if self._kind == wx.ITEM_NORMAL:
1066 return False
1067
1068 return self._toggle
1069
1070
1071 def SetToggled(self, toggle=True):
1072 """ Sets a wx.ITEM_CHECK button toggled/not toggled. """
1073
1074 if self._kind == wx.ITEM_NORMAL:
1075 return
1076
1077 self._toggle = toggle
1078
1079
1080 def SetId(self, id):
1081 """ Sets the button id. """
1082
1083 self._id = id
1084
1085
1086 def AddStatus(self, name="Custom", bmp=wx.NullBitmap):
1087 """
1088 Add a programmer-defined status in addition to the 5 default status:
1089 - Normal;
1090 - Disabled;
1091 - Hover;
1092 - Pressed;
1093 - Toggled.
1094 """
1095
1096 self._bitmaps.update({name: bmp})
1097
1098
1099 def Enable(self, enable=True):
1100
1101 if enable:
1102 self._status = "Normal"
1103 else:
1104 self._status = "Disabled"
1105
1106
1107 def IsEnabled(self):
1108
1109 return self._status != "Disabled"
1110
1111
1112 def SetText(self, text=""):
1113 """ Sets the text of the button. """
1114
1115 self._text = text
1116
1117
1118 def GetText(self):
1119 """ Returns the text associated to the button. """
1120
1121 return self._text
1122
1123
1124 def HasText(self):
1125 """ Returns whether the button has text or not. """
1126
1127 return self._text != ""
1128
1129
1130 def SetKind(self, kind=wx.ITEM_NORMAL):
1131 """ Sets the button type (standard or toggle). """
1132
1133 self._kind = kind
1134
1135
1136 def GetKind(self):
1137 """ Returns the button type (standard or toggle). """
1138
1139 return self._kind
1140
1141
1142 Bitmap = property(GetBitmap, SetBitmap)
1143 Id = property(GetId, SetId)
1144 Rect = property(GetRect, SetRect)
1145 Status = property(GetStatus, SetStatus)
1146
1147
1148 # -- ButtonPanel class implementation ----------------------------------
1149 # This is the main class.
1150
1151 class ButtonPanel(wx.PyPanel):
1152
1153 def __init__(self, parent, id=wx.ID_ANY, text="", style=BP_DEFAULT_STYLE,
1154 alignment=BP_ALIGN_LEFT, name="buttonPanel"):
1155 """
1156 Default class constructor.
1157
1158 - parent: parent window
1159 - id: window ID
1160 - text: text to draw
1161 - style: window style
1162 - alignment: alignment of buttons (left or right)
1163 - name: window class name
1164 """
1165
1166 wx.PyPanel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize,
1167 wx.NO_BORDER, name=name)
1168
1169 self._vButtons = []
1170 self._vSeparators = []
1171
1172 self._nStyle = style
1173 self._alignment = alignment
1174
1175 self._art = BPArt(style)
1176
1177 self._controlCreated = False
1178
1179 direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0]
1180 self._mainsizer = BoxSizer(direction)
1181 self.SetSizer(self._mainsizer)
1182
1183 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1184
1185 # First spacer to create some room before the first text/button/control
1186 self._mainsizer.Add((margins.x, margins.y), 0)
1187
1188 # Last spacer to create some room before the last text/button/control
1189 self._mainsizer.Add((margins.x, margins.y), 0)
1190
1191 self.Bind(wx.EVT_SIZE, self.OnSize)
1192 self.Bind(wx.EVT_PAINT, self.OnPaint)
1193 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1194 self.Bind(wx.EVT_MOTION, self.OnMouseMove)
1195 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
1196 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
1197 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
1198 self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow)
1199
1200 self.SetBarText(text)
1201 self.LayoutItems()
1202
1203
1204 def SetBarText(self, text):
1205 """ Sets the main caption text (leave text="" for no text). """
1206
1207 self.Freeze()
1208
1209 text = text.strip()
1210
1211 if self._controlCreated:
1212 self.RemoveText()
1213
1214 self._text = ButtonPanelText(self, text)
1215 lenChildren = len(self._mainsizer.GetChildren())
1216
1217 if text == "":
1218 # Even if we have no text, we insert it an empty spacer anyway
1219 # it is easier to handle if you have to recreate the sizer after.
1220 if self.IsStandard():
1221 self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER,
1222 userData=self._text, realIndex=0)
1223 else:
1224 self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER,
1225 userData=self._text, realIndex=lenChildren)
1226
1227 return
1228
1229 # We have text, so insert the text and an expandable spacer
1230 # alongside it. "Standard" ButtonPanel are left or top aligned.
1231 if self.IsStandard():
1232 self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER,
1233 userData=self._text, realIndex=0)
1234 self._mainsizer.Insert(2, (0, 0), 1, wx.EXPAND)
1235
1236 else:
1237 self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER,
1238 userData=self._text, realIndex=lenChildren)
1239 self._mainsizer.Insert(lenChildren-1, (0, 0), 1, wx.EXPAND)
1240
1241
1242 def RemoveText(self):
1243 """ Removes the main caption text. """
1244
1245 lenChildren = len(self._mainsizer.GetChildren())
1246 lenCustom = len(self._vButtons) + len(self._vSeparators) + 1
1247
1248 if self.IsStandard():
1249 # Detach the text
1250 self._mainsizer.Remove(1, 0)
1251 if self.HasBarText():
1252 # Detach the expandable spacer
1253 self._mainsizer.Remove(1, -1)
1254 else:
1255 # Detach the text
1256 self._mainsizer.Remove(lenChildren-2, lenCustom-1)
1257 if self.HasBarText():
1258 # Detach the expandable spacer
1259 self._mainsizer.Remove(lenChildren-3, -1)
1260
1261
1262 def GetBarText(self):
1263 """ Returns the main caption text. """
1264
1265 return self._text.GetText()
1266
1267
1268 def HasBarText(self):
1269 """ Returns whether ButtonPanel has a main caption text or not. """
1270
1271 return hasattr(self, "_text") and self._text.GetText() != ""
1272
1273
1274 def AddButton(self, btnInfo):
1275 """
1276 Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
1277 this method. See the demo for details.
1278 """
1279
1280 lenChildren = len(self._mainsizer.GetChildren())
1281 self._mainsizer.Insert(lenChildren-1, btnInfo, 0, wx.ALIGN_CENTER|wx.EXPAND, userData=btnInfo)
1282
1283 self._vButtons.append(btnInfo)
1284
1285
1286 def AddSpacer(self, size=(0, 0), proportion=1, flag=wx.EXPAND):
1287 """ Adds a spacer (stretchable or fixed-size) to ButtonPanel. """
1288
1289 lenChildren = len(self._mainsizer.GetChildren())
1290 self._mainsizer.Insert(lenChildren-1, size, proportion, flag)
1291
1292
1293 def AddControl(self, control, proportion=0, flag=wx.ALIGN_CENTER|wx.ALL, border=None):
1294 """ Adds a wxPython control to ButtonPanel. """
1295
1296 lenChildren = len(self._mainsizer.GetChildren())
1297
1298 if border is None:
1299 border = self._art.GetMetric(BP_PADDING_SIZE)
1300 border = max(border.x, border.y)
1301
1302 self._mainsizer.Insert(lenChildren-1, control, proportion, flag, border)
1303
1304
1305 def AddSeparator(self):
1306 """ Adds a separator line to ButtonPanel. """
1307
1308 lenChildren = len(self._mainsizer.GetChildren())
1309 separator = Separator(self)
1310
1311 self._mainsizer.Insert(lenChildren-1, separator, 0, wx.EXPAND)
1312 self._vSeparators.append(separator)
1313
1314
1315 def RemoveAllButtons(self):
1316 """ Remove all the buttons from ButtonPanel. """
1317
1318 self._vButtons = []
1319
1320
1321 def RemoveAllSeparators(self):
1322 """ Remove all the separators from ButtonPanel. """
1323
1324 self._vSeparators = []
1325
1326
1327 def GetAlignment(self):
1328 """ Returns the button alignment (left, right, top, bottom). """
1329
1330 return self._alignment
1331
1332
1333 def SetAlignment(self, alignment):
1334 """ Sets the button alignment (left, right, top, bottom). """
1335
1336 if alignment == self._alignment:
1337 return
1338
1339 self.Freeze()
1340
1341 text = self.GetBarText()
1342
1343 # Remove the text in any case
1344 self.RemoveText()
1345
1346 # Remove the first and last spacers
1347 self._mainsizer.Remove(0, -1)
1348 self._mainsizer.Remove(len(self._mainsizer.GetChildren())-1, -1)
1349
1350 self._alignment = alignment
1351
1352 # Recreate the sizer accordingly to the new alignment
1353 self.ReCreateSizer(text)
1354
1355
1356 def IsVertical(self):
1357 """ Returns whether ButtonPanel is vertically aligned or not. """
1358
1359 return self._alignment not in [BP_ALIGN_RIGHT, BP_ALIGN_LEFT]
1360
1361
1362 def IsStandard(self):
1363 """ Returns whether ButtonPanel is aligned "Standard" (left/top) or not. """
1364
1365 return self._alignment in [BP_ALIGN_LEFT, BP_ALIGN_TOP]
1366
1367
1368 def DoLayout(self):
1369 """
1370 Do the Layout for ButtonPanel.
1371 NB: Call this method every time you make a modification to the layout
1372 or to the customizable sizes of the pseudo controls.
1373 """
1374
1375 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1376 lenChildren = len(self._mainsizer.GetChildren())
1377
1378 self._mainsizer.SetItemMinSize(0, (margins.x, margins.y))
1379 self._mainsizer.SetItemMinSize(lenChildren-1, (margins.x, margins.y))
1380
1381 self._controlCreated = True
1382 self.LayoutItems()
1383
1384 # *VERY* WEIRD: the sizer seems not to respond to any layout until I
1385 # change the ButtonPanel size and restore it back
1386 size = self.GetSize()
1387 self.SetSize((size.x+1, size.y+1))
1388 self.SetSize((size.x, size.y))
1389
1390 if self.IsFrozen():
1391 self.Thaw()
1392
1393
1394 def ReCreateSizer(self, text):
1395 """ Recreates the ButtonPanel sizer accordingly to the alignment specified. """
1396
1397 children = self._mainsizer.GetChildren()
1398 self.RemoveAllButtons()
1399 self.RemoveAllSeparators()
1400
1401 # Create a new sizer depending on the alignment chosen
1402 direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0]
1403 self._mainsizer = BoxSizer(direction)
1404
1405 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1406 # First spacer to create some room before the first text/button/control
1407 self._mainsizer.Add((margins.x, margins.y), 0)
1408
1409 # Last spacer to create some room before the last text/button/control
1410 self._mainsizer.Add((margins.x, margins.y), 0)
1411
1412 # This is needed otherwise SetBarText goes mad
1413 self._controlCreated = False
1414
1415 for child in children:
1416 userData = child.GetUserData()
1417 if userData:
1418 if isinstance(userData, ButtonInfo):
1419 # It is a ButtonInfo, can't be anything else
1420 self.AddButton(child.GetUserData())
1421 elif isinstance(userData, Separator):
1422 self.AddSeparator()
1423
1424 else:
1425 if child.IsSpacer():
1426 # This is a spacer, expandable or not
1427 self.AddSpacer(child.GetSize(), child.GetProportion(),
1428 child.GetFlag())
1429 else:
1430 # This is a wxPython control
1431 self.AddControl(child.GetWindow(), child.GetProportion(),
1432 child.GetFlag(), child.GetBorder())
1433
1434 self.SetSizer(self._mainsizer)
1435
1436 # Now add the text. It doesn't matter if there is no text
1437 self.SetBarText(text)
1438
1439 self.DoLayout()
1440
1441 self.Thaw()
1442
1443
1444 def DoGetBestSize(self):
1445 """ Returns the best size of ButtonPanel. """
1446
1447 w = h = btnWidth = btnHeight = 0
1448 isVertical = self.IsVertical()
1449
1450 padding = self._art.GetMetric(BP_PADDING_SIZE)
1451 border = self._art.GetMetric(BP_BORDER_SIZE)
1452 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1453 separator_size = self._art.GetMetric(BP_SEPARATOR_SIZE)
1454
1455 # Add the space required for the main caption
1456 if self.HasBarText():
1457 w, h = self._text.GetBestSize()
1458 if isVertical:
1459 h += padding.y
1460 else:
1461 w += padding.x
1462 else:
1463 w = h = border
1464
1465 # Add the button's sizes
1466 for btn in self._vButtons:
1467
1468 bw, bh = btn.GetBestSize()
1469 btnWidth = max(btnWidth, bw)
1470 btnHeight = max(btnHeight, bh)
1471
1472 if isVertical:
1473 w = max(w, btnWidth)
1474 h += bh
1475 else:
1476 h = max(h, btnHeight)
1477 w += bw
1478
1479 # Add the control's sizes
1480 for control in self.GetControls():
1481 cw, ch = control.GetSize()
1482 if isVertical:
1483 h += ch
1484 w = max(w, cw)
1485 else:
1486 w += cw
1487 h = max(h, ch)
1488
1489 # Add the separator's sizes and the 2 SizerItems at the beginning
1490 # and at the end
1491 if self.IsVertical():
1492 h += 2*margins.y + len(self._vSeparators)*separator_size
1493 else:
1494 w += 2*margins.x + len(self._vSeparators)*separator_size
1495
1496 return wx.Size(w, h)
1497
1498
1499 def OnPaint(self, event):
1500 """ Handles the wx.EVT_PAINT event for ButtonPanel. """
1501
1502 dc = wx.BufferedPaintDC(self)
1503 rect = self.GetClientRect()
1504
1505 self._art.DrawButtonPanel(dc, rect, self._nStyle)
1506 self._mainsizer.Draw(dc)
1507
1508
1509 def OnEraseBackground(self, event):
1510 """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
1511
1512 pass
1513
1514
1515 def OnSize(self, event):
1516 """ Handles the wx.EVT_SIZE event for ButtonPanel. """
1517
1518 # NOTE: It seems like LayoutItems number of calls can be optimized in some way.
1519 # Currently every DoLayout (or every parent Layout()) calls about 3 times
1520 # the LayoutItems method. Any idea on how to improve it?
1521 self.LayoutItems()
1522 self.Refresh()
1523
1524 event.Skip()
1525
1526
1527 def LayoutItems(self):
1528 """
1529 Layout the items using a different algorithm depending on the existance
1530 of the main caption.
1531 """
1532
1533 nonspacers, allchildren = self.GetNonFlexibleChildren()
1534
1535 if self.HasBarText():
1536 self.FlexibleLayout(nonspacers, allchildren)
1537 else:
1538 self.SizeLayout(nonspacers, allchildren)
1539
1540 self._mainsizer.Layout()
1541
1542
1543 def SizeLayout(self, nonspacers, children):
1544 """ Layout the items when no main caption exists. """
1545
1546 size = self.GetSize()
1547 isVertical = self.IsVertical()
1548
1549 corner = 0
1550 indx1 = len(nonspacers)
1551
1552 for item in nonspacers:
1553 corner += self.GetItemSize(item, isVertical)
1554 if corner > size[isVertical]:
1555 indx1 = nonspacers.index(item)
1556 break
1557
1558 # Leave out the last spacer, it has to be there always
1559 for ii in xrange(len(nonspacers)-1):
1560 indx = children.index(nonspacers[ii])
1561 self._mainsizer.Show(indx, ii < indx1)
1562
1563
1564 def GetItemSize(self, item, isVertical):
1565 """ Returns the size of an item in the main ButtonPanel sizer. """
1566
1567 if item.GetUserData():
1568 return item.GetUserData().GetBestSize()[isVertical]
1569 else:
1570 return item.GetSize()[isVertical]
1571
1572
1573 def FlexibleLayout(self, nonspacers, allchildren):
1574 """ Layout the items when the main caption exists. """
1575
1576 if len(nonspacers) < 2:
1577 return
1578
1579 isVertical = self.IsVertical()
1580 isStandard = self.IsStandard()
1581
1582 size = self.GetSize()[isVertical]
1583 padding = self._art.GetMetric(BP_PADDING_SIZE)
1584
1585 fixed = (isStandard and [nonspacers[1]] or [nonspacers[-2]])[0]
1586
1587 if isStandard:
1588 nonspacers.reverse()
1589 leftendx = fixed.GetSize()[isVertical] + padding.x
1590 else:
1591 rightstartx = size - fixed.GetSize()[isVertical]
1592 size = 0
1593
1594 count = lennonspacers = len(nonspacers)
1595
1596 for item in nonspacers:
1597 if isStandard:
1598 size -= self.GetItemSize(item, isVertical)
1599 if size < leftendx:
1600 break
1601 else:
1602 size += self.GetItemSize(item, isVertical)
1603 if size > rightstartx:
1604 break
1605
1606 count = count - 1
1607
1608 nonspacers.reverse()
1609
1610 for jj in xrange(2, lennonspacers):
1611 indx = allchildren.index(nonspacers[jj])
1612 self._mainsizer.Show(indx, jj >= count)
1613
1614
1615 def GetNonFlexibleChildren(self):
1616 """
1617 Returns all the ButtonPanel main sizer's children that are not
1618 flexible spacers.
1619 """
1620
1621 children1 = []
1622 children2 = self._mainsizer.GetChildren()
1623
1624 for child in children2:
1625 if child.IsSpacer():
1626 if child.GetUserData() or child.GetProportion() == 0:
1627 children1.append(child)
1628 else:
1629 children1.append(child)
1630
1631 return children1, children2
1632
1633
1634 def GetControls(self):
1635 """ Returns the wxPython controls that belongs to ButtonPanel. """
1636
1637 children2 = self._mainsizer.GetChildren()
1638 children1 = [child for child in children2 if not child.IsSpacer()]
1639
1640 return children1
1641
1642
1643 def SetStyle(self, style):
1644 """ Sets ButtonPanel style. """
1645
1646 if style == self._nStyle:
1647 return
1648
1649 self._nStyle = style
1650 self.Refresh()
1651
1652
1653 def GetStyle(self):
1654 """ Returns the ButtonPanel style. """
1655
1656 return self._nStyle
1657
1658
1659 def OnMouseMove(self, event):
1660 """ Handles the wx.EVT_MOTION event for ButtonPanel. """
1661
1662 # Check to see if we are hovering a button
1663 for btn in self._vButtons:
1664
1665 if not btn.IsEnabled():
1666 continue
1667
1668 if btn.GetRect().Contains(event.GetPosition()):
1669 btn.SetStatus("Hover")
1670 else:
1671 btn.SetStatus("Normal")
1672
1673 self.Refresh()
1674 event.Skip()
1675
1676
1677 def OnLeftDown(self, event):
1678 """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
1679
1680 tabId, hit = self.HitTest(event.GetPosition())
1681
1682 if hit == BP_HT_BUTTON and self._vButtons[tabId].IsEnabled():
1683
1684 self._vButtons[tabId].SetStatus("Pressed")
1685 self.Refresh()
1686
1687
1688 def OnLeftUp(self, event):
1689 """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
1690
1691 tabId, flags = self.HitTest(event.GetPosition())
1692 hit = self._vButtons[tabId]
1693
1694 if flags == BP_HT_BUTTON:
1695
1696 if hit.GetStatus() == "Disabled":
1697 return
1698
1699 for btn in self._vButtons:
1700 if btn != hit:
1701 btn.SetFocus(False)
1702
1703 if hit.GetStatus() == "Pressed":
1704 # Fire a button click event
1705 btnEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, hit.GetId())
1706 self.GetEventHandler().ProcessEvent(btnEvent)
1707
1708 hit.SetToggled(not hit.GetToggled())
1709
1710 # Update the button status to be hovered
1711 hit.SetStatus("Hover")
1712 hit.SetFocus()
1713
1714 self.Refresh()
1715
1716
1717 def OnMouseLeave(self, event):
1718 """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
1719
1720 # Reset all buttons statuses
1721 for btn in self._vButtons:
1722 if not btn.IsEnabled():
1723 continue
1724 btn.SetStatus("Normal")
1725
1726 self.Refresh()
1727 event.Skip()
1728
1729
1730 def OnMouseEnterWindow(self, event):
1731 """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
1732
1733 event.Skip()
1734
1735
1736 def HitTest(self, pt):
1737 """
1738 HitTest method for ButtonPanel. Returns the button (if any) and
1739 a flag (if any).
1740 """
1741
1742 btnIdx = -1
1743
1744 for ii in xrange(len(self._vButtons)):
1745 if not self._vButtons[ii].IsEnabled():
1746 continue
1747 if self._vButtons[ii].GetRect().Contains(pt):
1748 return ii, BP_HT_BUTTON
1749
1750 return -1, BP_HT_NONE
1751
1752
1753 def GetBPArt(self):
1754 """ Returns the associated BPArt art provider. """
1755
1756 return self._art
1757