]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/buttonpanel.py
more cvs to svn script changes
[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: 17 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 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.
51
52 CASE 1: ButtonPanel has a main caption text
53
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;
57
58 Right alignment means ButtonPanel is horizontal, with the text aligned to the
59 right. Item layout as above;
60
61 Top alignment means ButtonPanel is vertical, with the text aligned to the top.
62 Item layout as above;
63
64 Bottom alignment means ButtonPanel is vertical, with the text aligned to the
65 bottom. Item layout as above.
66
67
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).
73
74
75 The following example shows a simple implementation that uses ButtonPanel
76 inside a very simple frame::
77
78 class MyFrame(wx.Frame):
79
80 def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition,
81 size=(800, 600), style=wx.DEFAULT_FRAME_STYLE):
82
83 wx.Frame.__init__(self, parent, id, title, pos, size, style)
84
85 mainPanel = wx.Panel(self, -1)
86 self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE)
87
88 vSizer = wx.BoxSizer(wx.VERTICAL)
89 mainPanel.SetSizer(vSizer)
90
91 alignment = BP_ALIGN_RIGHT
92
93 titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo")
94
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)
98
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)
102
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)
106
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)
110
111 vSizer.Add(titleBar, 0, wx.EXPAND)
112 vSizer.Add((20, 20))
113 vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5)
114
115 vSizer.Layout()
116
117 # our normal wxApp-derived class, as usual
118
119 app = wx.PySimpleApp()
120
121 frame = MyFrame(None)
122 app.SetTopWindow(frame)
123 frame.Show()
124
125 app.MainLoop()
126
127
128 License And Version:
129
130 ButtonPanel Is Freeware And Distributed Under The wxPython License.
131
132 Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT
133 Version 0.3.
134
135 """
136
137
138 import wx
139
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. """
147 BP_BORDER_COLOR = 3
148 """ Pen colour to paint the border of ButtonPanel. """
149 BP_TEXT_COLOR = 4
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. """
161 BP_TEXT_FONT = 10
162 """ Font of the ButtonPanel main caption. """
163 BP_BUTTONTEXT_FONT = 11
164 """ Text font for the buttons with text. """
165
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. """
170
171 BP_SEPARATOR_SIZE = 14
172 """
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.
175 """
176 BP_MARGINS_SIZE = 15
177 """
178 Size of the left/right margins in ButtonPanel (top/bottom for vertically
179 aligned ButtonPanels).
180 """
181 BP_BORDER_SIZE = 16
182 """ Size of the border. """
183 BP_PADDING_SIZE = 17
184 """ Inter-tool separator size. """
185
186 # Caption Gradient Type
187 BP_GRADIENT_NONE = 0
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. """
193
194 # Flags for HitTest() method
195 BP_HT_BUTTON = 200
196 BP_HT_NONE = 201
197
198 # Alignment of buttons in the panel
199 BP_ALIGN_RIGHT = 1
200 BP_ALIGN_LEFT = 2
201 BP_ALIGN_TOP = 4
202 BP_ALIGN_BOTTOM = 8
203
204 # ButtonPanel styles
205 BP_DEFAULT_STYLE = 1
206 BP_USE_GRADIENT = 2
207
208 # Delay used to cancel the longHelp in the statusbar field
209 _DELAY = 3000
210
211
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)
215
216
217 def BrightenColour(color, factor):
218 """ Bright the input colour by a factor."""
219
220 val = color.Red()*factor
221 if val > 255:
222 red = 255
223 else:
224 red = val
225
226 val = color.Green()*factor
227 if val > 255:
228 green = 255
229 else:
230 green = val
231
232 val = color.Blue()*factor
233 if val > 255:
234 blue = 255
235 else:
236 blue = val
237
238 return wx.Color(red, green, blue)
239
240
241 def GrayOut(anImage):
242 """
243 Convert the given image (in place) to a grayed-out version,
244 appropriate for a 'Disabled' appearance.
245 """
246
247 factor = 0.7 # 0 < f < 1. Higher Is Grayer
248
249 anImage = anImage.ConvertToImage()
250 if anImage.HasAlpha():
251 anImage.ConvertAlphaToMask(1)
252
253 if anImage.HasMask():
254 maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
255 else:
256 maskColor = None
257
258 data = map(ord, list(anImage.GetData()))
259
260 for i in range(0, len(data), 3):
261
262 pixel = (data[i], data[i+1], data[i+2])
263 pixel = MakeGray(pixel, factor, maskColor)
264
265 for x in range(3):
266 data[i+x] = pixel[x]
267
268 anImage.SetData(''.join(map(chr, data)))
269
270 anImage = anImage.ConvertToBitmap()
271
272 return anImage
273
274
275 def MakeGray((r,g,b), factor, maskColor):
276 """
277 Make a pixel grayed-out. If the pixel matches the maskColor, it won't be
278 changed.
279 """
280
281 if (r,g,b) != maskColor:
282 return map(lambda x: int((230 - x) * factor) + x, (r,g,b))
283 else:
284 return (r,g,b)
285
286
287 # ---------------------------------------------------------------------------- #
288 # Class BPArt
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 # ---------------------------------------------------------------------------- #
292
293 class BPArt:
294 """
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.
298 """
299
300 def __init__(self, parentStyle):
301 """ Default class constructor. """
302
303 base_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)
304
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)
308
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
315 else:
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
321
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))
325
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())
331
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)
336
337
338 def GetMetric(self, id):
339 """ Returns sizes of customizable options. """
340
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
349 else:
350 raise "\nERROR: Invalid Metric Ordinal. "
351
352
353 def SetMetric(self, id, new_val):
354 """ Sets sizes for customizable options. """
355
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
365 else:
366 raise "\nERROR: Invalid Metric Ordinal. "
367
368
369 def GetColor(self, id):
370 """ Returns colours of customizable options. """
371
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()
392 else:
393 raise "\nERROR: Invalid Colour Ordinal. "
394
395
396 def SetColor(self, id, colour):
397 """ Sets colours for customizable options. """
398
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)
419 else:
420 raise "\nERROR: Invalid Colour Ordinal. "
421
422
423 GetColour = GetColor
424 SetColour = SetColor
425
426
427 def SetFont(self, id, font):
428 """ Sets font for customizable options. """
429
430 if id == BP_TEXT_FONT:
431 self._caption_font = font
432 elif id == BP_BUTTONTEXT_FONT:
433 self._buttontext_font = font
434
435
436 def GetFont(self, id):
437 """ Returns font of customizable options. """
438
439 if id == BP_TEXT_FONT:
440 return self._caption_font
441 elif id == BP_BUTTONTEXT_FONT:
442 return self._buttontext_font
443
444 return wx.NoneFont
445
446
447 def SetGradientType(self, gradient):
448 """ Sets the gradient type for BPArt drawings. """
449
450 self._gradient_type = gradient
451
452
453 def GetGradientType(self):
454 """ Returns the gradient type for BPArt drawings. """
455
456 return self._gradient_type
457
458
459 def DrawSeparator(self, dc, rect, isVertical):
460 """ Draws a separator in ButtonPanel. """
461
462 dc.SetPen(self._separator_pen)
463
464 if isVertical:
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)
469 else:
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)
474
475
476 def DrawCaption(self, dc, rect, captionText):
477 """ Draws the main caption text in ButtonPanel. """
478
479 textColour = self._caption_text_color
480 textFont = self._caption_font
481 padding = self._padding_size
482
483 dc.SetTextForeground(textColour)
484 dc.SetFont(textFont)
485
486 dc.DrawText(captionText, rect.x + padding.x, rect.y+padding.y)
487
488
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). """
492
493 bmpxsize, bmpysize = buttonBitmap.GetWidth(), buttonBitmap.GetHeight()
494 dx = dy = focus = 0
495
496 borderw = self._caption_border_size
497 padding = self._padding_size
498
499 buttonFont = self._buttontext_font
500 dc.SetFont(buttonFont)
501
502 if isVertical:
503
504 rect = wx.Rect(borderw, rect.y, rect.width-2*borderw, rect.height)
505
506 if text != "":
507
508 textW, textH = dc.GetTextExtent(text)
509
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
516 else:
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
521 else:
522 bmpxpos = rect.x + (rect.width - bmpxsize)/2
523 bmpypos = rect.y + (rect.height - bmpysize)/2
524
525
526 else:
527
528 rect = wx.Rect(rect.x, borderw, rect.width, rect.height-2*borderw)
529
530 if text != "":
531
532 textW, textH = dc.GetTextExtent(text)
533
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
540 else:
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
546 else:
547 bmpxpos = rect.x + (rect.width - bmpxsize)/2
548 bmpypos = rect.y + (rect.height - bmpysize)/2
549
550 # Draw a button
551 # [ Padding | Text | .. Buttons .. | Padding ]
552
553 if buttonStatus in ["Pressed", "Toggled", "Hover"]:
554 dc.SetBrush(self._selection_brush)
555 dc.SetPen(self._selection_pen)
556 dc.DrawRoundedRectangleRect(rect, 4)
557
558 if buttonStatus == "Pressed" or isToggled:
559 dx = dy = 1
560
561 dc.DrawBitmap(buttonBitmap, bmpxpos+dx, bmpypos+dy, True)
562
563 if text != "":
564 isEnabled = buttonStatus != "Disabled"
565 self.DrawLabel(dc, text, isEnabled, textxpos+dx, textypos+dy)
566
567
568 def DrawLabel(self, dc, text, isEnabled, xpos, ypos):
569 """ Draws the label for a button. """
570
571 if not isEnabled:
572 dc.SetTextForeground(self._buttontext_inactive_color)
573 else:
574 dc.SetTextForeground(self._buttontext_color)
575
576 dc.DrawText(text, xpos, ypos)
577
578
579 def DrawButtonPanel(self, dc, rect, style):
580 """ Paint the ButtonPanel's background. """
581
582 if style & BP_USE_GRADIENT:
583 # Draw gradient color in the backgroud of the panel
584 self.FillGradientColor(dc, rect)
585
586 # Draw a rectangle around the panel
587 backBrush = (style & BP_USE_GRADIENT and [wx.TRANSPARENT_BRUSH] or \
588 [self._background_brush])[0]
589
590 dc.SetBrush(backBrush)
591 dc.SetPen(self._border_pen)
592 dc.DrawRectangleRect(rect)
593
594
595 def FillGradientColor(self, dc, rect):
596 """ Gradient fill from colour 1 to colour 2 with top to bottom or left to right. """
597
598 if rect.height < 1 or rect.width < 1:
599 return
600
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]
604
605 # calculate gradient coefficients
606
607 col2 = self._gradient_color_from
608 col1 = self._gradient_color_to
609
610 rf, gf, bf = 0, 0, 0
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)
614
615 for coord in xrange(start, start + size):
616
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))
620 if isVertical:
621 dc.DrawLine(rect.x, coord, rect.x + rect.width, coord)
622 else:
623 dc.DrawLine(coord, rect.y, coord, rect.y + rect.height)
624
625 rf += rstep
626 gf += gstep
627 bf += bstep
628
629
630 class StatusBarTimer(wx.Timer):
631 """Timer used for deleting StatusBar long help after _DELAY seconds."""
632
633 def __init__(self, owner):
634 """
635 Default class constructor.
636 For internal use: do not call it in your code!
637 """
638
639 wx.Timer.__init__(self)
640 self._owner = owner
641
642
643 def Notify(self):
644 """The timer has expired."""
645
646 self._owner.OnStatusBarTimer()
647
648
649 class Control(wx.EvtHandler):
650
651 def __init__(self, parent, size=wx.Size(-1, -1)):
652 """
653 Default class constructor.
654
655 Base class for all pseudo controls
656 parent = parent object
657 size = (width, height)
658 """
659
660 wx.EvtHandler.__init__(self)
661
662 self._parent = parent
663 self._id = wx.NewId()
664 self._size = size
665 self._isshown = True
666 self._focus = False
667
668
669 def Show(self, show=True):
670 """ Shows or hide the control. """
671
672 self._isshown = show
673
674
675 def Hide(self):
676 """ Hides the control. """
677
678 self.Show(False)
679
680
681 def IsShown(self):
682 """ Returns whether the control is shown or not. """
683
684 return self._isshown
685
686
687 def GetId(self):
688 """ Returns the control id. """
689
690 return self._id
691
692
693 def GetBestSize(self):
694 """ Returns the control best size. """
695
696 return self._size
697
698
699 def Disable(self):
700 """ Disables the control. """
701
702 self.Enable(False)
703
704
705 def Enable(self, value=True):
706 """ Enables or disables the control. """
707
708 self.disabled = not value
709
710
711 def SetFocus(self, focus=True):
712 """ Sets or kills the focus on the control. """
713
714 self._focus = focus
715
716
717 def HasFocus(self):
718 """ Returns whether the control has the focus or not. """
719
720 return self._focus
721
722
723 def OnMouseEvent(self, x, y, event):
724 pass
725
726 def Draw(self, rect):
727 pass
728
729
730
731 class Sizer(object):
732 """
733 Sizer
734
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.
738 """
739 def __init__(self):
740 self.children = [] # list of child Pseudo Controls
741
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
747 # what is passed in
748 c = item.GetUserData()
749 c.Draw(dc, item.GetRect())
750
751 def GetBestSize(self):
752 # this should be handled by the wx.Sizer based class
753 return self.GetMinSize()
754
755
756 # Pseudo BoxSizer
757 class BoxSizer(Sizer, wx.BoxSizer):
758 def __init__(self, orient=wx.HORIZONTAL):
759 wx.BoxSizer.__init__(self, orient)
760 Sizer.__init__(self)
761
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)
777 else:
778 wx.BoxSizer.Add(self, item, proportion, flag, border, userData)
779
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)
790 else:
791 wx.BoxSizer.Prepend(self, item, proportion, flag, border, userData)
792
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)
804 else:
805 self.children.insert(before,szitem)
806
807 else:
808 wx.BoxSizer.Insert(self, before, item, proportion, flag, border, userData)
809
810
811 def Remove(self, indx, pop=-1):
812
813 if pop >= 0:
814 self.children.pop(pop)
815
816 wx.BoxSizer.Remove(self, indx)
817
818
819 def Layout(self):
820
821 for ii, child in enumerate(self.GetChildren()):
822 item = child.GetUserData()
823 if item and child.IsShown():
824 self.SetItemMinSize(ii, *item.GetBestSize())
825
826 wx.BoxSizer.Layout(self)
827
828
829 def Show(self, item, show=True):
830
831 child = self.GetChildren()[item]
832 if child and child.GetUserData():
833 child.GetUserData().Show(show)
834
835 wx.BoxSizer.Show(self, item, show)
836
837
838 # ---------------------------------------------------------------------------- #
839 # Class Separator
840 # This class holds all the information to size and draw a separator inside
841 # ButtonPanel
842 # ---------------------------------------------------------------------------- #
843
844 class Separator(Control):
845
846 def __init__(self, parent):
847 """ Default class constructor. """
848
849 self._isshown = True
850 self._parent = parent
851 Control.__init__(self, parent)
852
853
854 def GetBestSize(self):
855 """ Returns the separator best size. """
856
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))
860 else:
861 return wx.Size(self._parent._art.GetMetric(BP_SEPARATOR_SIZE), 10)
862
863
864 def Draw(self, dc, rect):
865 """ Draws the separator. Actually the drawing is done in BPArt. """
866
867 if not self.IsShown():
868 return
869
870 isVertical = self._parent.IsVertical()
871 self._parent._art.DrawSeparator(dc, rect, isVertical)
872
873
874 # ---------------------------------------------------------------------------- #
875 # Class ButtonPanelText
876 # This class is used to hold data about the main caption in ButtonPanel
877 # ---------------------------------------------------------------------------- #
878
879 class ButtonPanelText(Control):
880
881 def __init__(self, parent, text=""):
882 """ Default class constructor. """
883
884 self._text = text
885 self._isshown = True
886 self._parent = parent
887
888 Control.__init__(self, parent)
889
890
891 def GetText(self):
892 """ Returns the caption text. """
893
894 return self._text
895
896
897 def SetText(self, text=""):
898 """ Sets the caption text. """
899
900 self._text = text
901
902
903 def CreateDC(self):
904 """ Convenience function to create a DC. """
905
906 dc = wx.ClientDC(self._parent)
907 textFont = self._parent._art.GetFont(BP_TEXT_FONT)
908 dc.SetFont(textFont)
909
910 return dc
911
912
913 def GetBestSize(self):
914 """ Returns the best size for the main caption in ButtonPanel. """
915
916 if self._text == "":
917 return wx.Size(0, 0)
918
919 dc = self.CreateDC()
920 rect = self._parent.GetClientRect()
921
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)
925
926 return self._size
927
928
929 def Draw(self, dc, rect):
930 """ Draws the main caption. Actually the drawing is done in BPArt. """
931
932 if not self.IsShown():
933 return
934
935 captionText = self.GetText()
936 self._parent._art.DrawCaption(dc, rect, captionText)
937
938
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.
943
944 class ButtonInfo(Control):
945
946 def __init__(self, parent, id=wx.ID_ANY, bmp=wx.NullBitmap,
947 status="Normal", text="", kind=wx.ITEM_NORMAL,
948 shortHelp="", longHelp=""):
949 """
950 Default class constructor.
951
952 Parameters:
953 - parent: the parent window (ButtonPanel);
954 - id: the button id;
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.
963 """
964
965 if id == wx.ID_ANY:
966 id = wx.NewId()
967
968 self._status = status
969 self._rect = wx.Rect()
970 self._text = text
971 self._kind = kind
972 self._toggle = False
973 self._textAlignment = BP_BUTTONTEXT_ALIGN_BOTTOM
974 self._shortHelp = shortHelp
975 self._longHelp = longHelp
976
977 if bmp:
978 disabledbmp = GrayOut(bmp)
979 else:
980 disabledbmp = wx.NullBitmap
981
982 self._bitmaps = {"Normal": bmp, "Toggled": None, "Disabled": disabledbmp,
983 "Hover": None, "Pressed": None}
984
985 Control.__init__(self, parent)
986
987
988 def GetBestSize(self):
989 """ Returns the best size for the button. """
990
991 xsize = self.GetBitmap().GetWidth()
992 ysize = self.GetBitmap().GetHeight()
993
994 if self.HasText():
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())
1000
1001 if self.GetTextAlignment() == BP_BUTTONTEXT_ALIGN_BOTTOM:
1002 xsize = max(xsize, tw)
1003 ysize = ysize + th
1004 else:
1005 xsize = xsize + tw
1006 ysize = max(ysize, th)
1007
1008 border = self._parent._art.GetMetric(BP_BORDER_SIZE)
1009 padding = self._parent._art.GetMetric(BP_PADDING_SIZE)
1010
1011 if self._parent.IsVertical():
1012 xsize = xsize + 2*border
1013 else:
1014 ysize = ysize + 2*border
1015
1016 self._size = wx.Size(xsize+2*padding.x, ysize+2*padding.y)
1017
1018 return self._size
1019
1020
1021 def Draw(self, dc, rect):
1022 """ Draws the button on ButtonPanel. Actually the drawing is done in BPArt. """
1023
1024 if not self.IsShown():
1025 return
1026
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()
1034
1035 self._parent._art.DrawButton(dc, rect, parentSize, buttonBitmap, isVertical,
1036 buttonStatus, isToggled, textAlignment, text)
1037
1038 self.SetRect(rect)
1039
1040
1041 def CheckRefresh(self, status):
1042 """ Checks whether a ButtonPanel repaint is needed or not. Convenience function. """
1043
1044 if status == self._status:
1045 self._parent.RefreshRect(self.GetRect())
1046
1047
1048 def SetBitmap(self, bmp, status="Normal"):
1049 """ Sets the associated bitmap. """
1050
1051 self._bitmaps[status] = bmp
1052 self.CheckRefresh(status)
1053
1054
1055 def GetBitmap(self, status=None):
1056 """ Returns the associated bitmap. """
1057
1058 if status is None:
1059 status = self._status
1060
1061 if not self.IsEnabled():
1062 status = "Disabled"
1063
1064 if self._bitmaps[status] is None:
1065 return self._bitmaps["Normal"]
1066
1067 return self._bitmaps[status]
1068
1069
1070 def GetRect(self):
1071 """ Returns the button rect. """
1072
1073 return self._rect
1074
1075
1076 def GetStatus(self):
1077 """ Returns the button status. """
1078
1079 return self._status
1080
1081
1082 def GetId(self):
1083 """ Returns the button id. """
1084
1085 return self._id
1086
1087
1088 def SetRect(self, rect):
1089 """ Sets the button rect. """
1090
1091 self._rect = rect
1092
1093
1094 def SetStatus(self, status):
1095 """ Sets the button status. """
1096
1097 if status == self._status:
1098 return
1099
1100 if self.GetToggled() and status == "Normal":
1101 status = "Toggled"
1102
1103 self._status = status
1104 self._parent.RefreshRect(self.GetRect())
1105
1106
1107 def GetTextAlignment(self):
1108 """ Returns the text alignment in the button (bottom or right). """
1109
1110 return self._textAlignment
1111
1112
1113 def SetTextAlignment(self, alignment):
1114 """ Sets the text alignment in the button (bottom or right). """
1115
1116 if alignment == self._textAlignment:
1117 return
1118
1119 self._textAlignment = alignment
1120
1121
1122 def GetToggled(self):
1123 """ Returns whether a wx.ITEM_CHECK button is toggled or not. """
1124
1125 if self._kind == wx.ITEM_NORMAL:
1126 return False
1127
1128 return self._toggle
1129
1130
1131 def SetToggled(self, toggle=True):
1132 """ Sets a wx.ITEM_CHECK button toggled/not toggled. """
1133
1134 if self._kind == wx.ITEM_NORMAL:
1135 return
1136
1137 self._toggle = toggle
1138
1139
1140 def SetId(self, id):
1141 """ Sets the button id. """
1142
1143 self._id = id
1144
1145
1146 def AddStatus(self, name="Custom", bmp=wx.NullBitmap):
1147 """
1148 Add a programmer-defined status in addition to the 5 default status:
1149 - Normal;
1150 - Disabled;
1151 - Hover;
1152 - Pressed;
1153 - Toggled.
1154 """
1155
1156 self._bitmaps.update({name: bmp})
1157
1158
1159 def Enable(self, enable=True):
1160
1161 if enable:
1162 self._status = "Normal"
1163 else:
1164 self._status = "Disabled"
1165
1166
1167 def IsEnabled(self):
1168
1169 return self._status != "Disabled"
1170
1171
1172 def SetText(self, text=""):
1173 """ Sets the text of the button. """
1174
1175 self._text = text
1176
1177
1178 def GetText(self):
1179 """ Returns the text associated to the button. """
1180
1181 return self._text
1182
1183
1184 def HasText(self):
1185 """ Returns whether the button has text or not. """
1186
1187 return self._text != ""
1188
1189
1190 def SetKind(self, kind=wx.ITEM_NORMAL):
1191 """ Sets the button type (standard or toggle). """
1192
1193 self._kind = kind
1194
1195
1196 def GetKind(self):
1197 """ Returns the button type (standard or toggle). """
1198
1199 return self._kind
1200
1201
1202 def SetShortHelp(self, help=""):
1203 """ Sets the help string to be shown in a tootip. """
1204
1205 self._shortHelp = help
1206
1207
1208 def GetShortHelp(self):
1209 """ Returns the help string shown in a tootip. """
1210
1211 return self._shortHelp
1212
1213
1214 def SetLongHelp(self, help=""):
1215 """ Sets the help string to be shown in the statusbar. """
1216
1217 self._longHelp = help
1218
1219
1220 def GetLongHelp(self):
1221 """ Returns the help string shown in the statusbar. """
1222
1223 return self._longHelp
1224
1225
1226 Bitmap = property(GetBitmap, SetBitmap)
1227 Id = property(GetId, SetId)
1228 Rect = property(GetRect, SetRect)
1229 Status = property(GetStatus, SetStatus)
1230
1231
1232 # -- ButtonPanel class implementation ----------------------------------
1233 # This is the main class.
1234
1235 class ButtonPanel(wx.PyPanel):
1236
1237 def __init__(self, parent, id=wx.ID_ANY, text="", style=BP_DEFAULT_STYLE,
1238 alignment=BP_ALIGN_LEFT, name="buttonPanel"):
1239 """
1240 Default class constructor.
1241
1242 - parent: parent window
1243 - id: window ID
1244 - text: text to draw
1245 - style: window style
1246 - alignment: alignment of buttons (left or right)
1247 - name: window class name
1248 """
1249
1250 wx.PyPanel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize,
1251 wx.NO_BORDER, name=name)
1252
1253 self._vButtons = []
1254 self._vSeparators = []
1255
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
1263
1264 self._art = BPArt(style)
1265
1266 self._controlCreated = False
1267
1268 direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0]
1269 self._mainsizer = BoxSizer(direction)
1270 self.SetSizer(self._mainsizer)
1271
1272 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1273
1274 # First spacer to create some room before the first text/button/control
1275 self._mainsizer.Add((margins.x, margins.y), 0)
1276
1277 # Last spacer to create some room before the last text/button/control
1278 self._mainsizer.Add((margins.x, margins.y), 0)
1279
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)
1288
1289 self.SetBarText(text)
1290 self.LayoutItems()
1291
1292
1293 def SetBarText(self, text):
1294 """ Sets the main caption text (leave text="" for no text). """
1295
1296 self.Freeze()
1297
1298 text = text.strip()
1299
1300 if self._controlCreated:
1301 self.RemoveText()
1302
1303 self._text = ButtonPanelText(self, text)
1304 lenChildren = len(self._mainsizer.GetChildren())
1305
1306 if text == "":
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)
1312 else:
1313 self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER,
1314 userData=self._text, realIndex=lenChildren)
1315
1316 return
1317
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)
1324
1325 else:
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)
1329
1330
1331 def RemoveText(self):
1332 """ Removes the main caption text. """
1333
1334 lenChildren = len(self._mainsizer.GetChildren())
1335 lenCustom = len(self._vButtons) + len(self._vSeparators) + 1
1336
1337 if self.IsStandard():
1338 # Detach the text
1339 self._mainsizer.Remove(1, 0)
1340 if self.HasBarText():
1341 # Detach the expandable spacer
1342 self._mainsizer.Remove(1, -1)
1343 else:
1344 # Detach the text
1345 self._mainsizer.Remove(lenChildren-2, lenCustom-1)
1346 if self.HasBarText():
1347 # Detach the expandable spacer
1348 self._mainsizer.Remove(lenChildren-3, -1)
1349
1350
1351 def GetBarText(self):
1352 """ Returns the main caption text. """
1353
1354 return self._text.GetText()
1355
1356
1357 def HasBarText(self):
1358 """ Returns whether ButtonPanel has a main caption text or not. """
1359
1360 return hasattr(self, "_text") and self._text.GetText() != ""
1361
1362
1363 def AddButton(self, btnInfo):
1364 """
1365 Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
1366 this method. See the demo for details.
1367 """
1368
1369 lenChildren = len(self._mainsizer.GetChildren())
1370 self._mainsizer.Insert(lenChildren-1, btnInfo, 0, wx.ALIGN_CENTER|wx.EXPAND, userData=btnInfo)
1371
1372 self._vButtons.append(btnInfo)
1373
1374
1375 def AddSpacer(self, size=(0, 0), proportion=1, flag=wx.EXPAND):
1376 """ Adds a spacer (stretchable or fixed-size) to ButtonPanel. """
1377
1378 lenChildren = len(self._mainsizer.GetChildren())
1379 self._mainsizer.Insert(lenChildren-1, size, proportion, flag)
1380
1381
1382 def AddControl(self, control, proportion=0, flag=wx.ALIGN_CENTER|wx.ALL, border=None):
1383 """ Adds a wxPython control to ButtonPanel. """
1384
1385 lenChildren = len(self._mainsizer.GetChildren())
1386
1387 if border is None:
1388 border = self._art.GetMetric(BP_PADDING_SIZE)
1389 border = max(border.x, border.y)
1390
1391 self._mainsizer.Insert(lenChildren-1, control, proportion, flag, border)
1392
1393
1394 def AddSeparator(self):
1395 """ Adds a separator line to ButtonPanel. """
1396
1397 lenChildren = len(self._mainsizer.GetChildren())
1398 separator = Separator(self)
1399
1400 self._mainsizer.Insert(lenChildren-1, separator, 0, wx.EXPAND)
1401 self._vSeparators.append(separator)
1402
1403
1404 def RemoveAllButtons(self):
1405 """ Remove all the buttons from ButtonPanel. """
1406
1407 self._vButtons = []
1408
1409
1410 def RemoveAllSeparators(self):
1411 """ Remove all the separators from ButtonPanel. """
1412
1413 self._vSeparators = []
1414
1415
1416 def GetAlignment(self):
1417 """ Returns the button alignment (left, right, top, bottom). """
1418
1419 return self._alignment
1420
1421
1422 def SetAlignment(self, alignment):
1423 """ Sets the button alignment (left, right, top, bottom). """
1424
1425 if alignment == self._alignment:
1426 return
1427
1428 self.Freeze()
1429
1430 text = self.GetBarText()
1431
1432 # Remove the text in any case
1433 self.RemoveText()
1434
1435 # Remove the first and last spacers
1436 self._mainsizer.Remove(0, -1)
1437 self._mainsizer.Remove(len(self._mainsizer.GetChildren())-1, -1)
1438
1439 self._alignment = alignment
1440
1441 # Recreate the sizer accordingly to the new alignment
1442 self.ReCreateSizer(text)
1443
1444
1445 def IsVertical(self):
1446 """ Returns whether ButtonPanel is vertically aligned or not. """
1447
1448 return self._alignment not in [BP_ALIGN_RIGHT, BP_ALIGN_LEFT]
1449
1450
1451 def IsStandard(self):
1452 """ Returns whether ButtonPanel is aligned "Standard" (left/top) or not. """
1453
1454 return self._alignment in [BP_ALIGN_LEFT, BP_ALIGN_TOP]
1455
1456
1457 def DoLayout(self):
1458 """
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.
1462 """
1463
1464 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1465 lenChildren = len(self._mainsizer.GetChildren())
1466
1467 self._mainsizer.SetItemMinSize(0, (margins.x, margins.y))
1468 self._mainsizer.SetItemMinSize(lenChildren-1, (margins.x, margins.y))
1469
1470 self._controlCreated = True
1471 self.LayoutItems()
1472
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))
1478
1479 if self.IsFrozen():
1480 self.Thaw()
1481
1482
1483 def ReCreateSizer(self, text):
1484 """ Recreates the ButtonPanel sizer accordingly to the alignment specified. """
1485
1486 children = self._mainsizer.GetChildren()
1487 self.RemoveAllButtons()
1488 self.RemoveAllSeparators()
1489
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)
1493
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)
1497
1498 # Last spacer to create some room before the last text/button/control
1499 self._mainsizer.Add((margins.x, margins.y), 0)
1500
1501 # This is needed otherwise SetBarText goes mad
1502 self._controlCreated = False
1503
1504 for child in children:
1505 userData = child.GetUserData()
1506 if userData:
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):
1511 self.AddSeparator()
1512
1513 else:
1514 if child.IsSpacer():
1515 # This is a spacer, expandable or not
1516 self.AddSpacer(child.GetSize(), child.GetProportion(),
1517 child.GetFlag())
1518 else:
1519 # This is a wxPython control
1520 self.AddControl(child.GetWindow(), child.GetProportion(),
1521 child.GetFlag(), child.GetBorder())
1522
1523 self.SetSizer(self._mainsizer)
1524
1525 # Now add the text. It doesn't matter if there is no text
1526 self.SetBarText(text)
1527
1528 self.DoLayout()
1529
1530 self.Thaw()
1531
1532
1533 def DoGetBestSize(self):
1534 """ Returns the best size of ButtonPanel. """
1535
1536 w = h = btnWidth = btnHeight = 0
1537 isVertical = self.IsVertical()
1538
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)
1543
1544 # Add the space required for the main caption
1545 if self.HasBarText():
1546 w, h = self._text.GetBestSize()
1547 if isVertical:
1548 h += padding.y
1549 else:
1550 w += padding.x
1551 else:
1552 w = h = border
1553
1554 # Add the button's sizes
1555 for btn in self._vButtons:
1556
1557 bw, bh = btn.GetBestSize()
1558 btnWidth = max(btnWidth, bw)
1559 btnHeight = max(btnHeight, bh)
1560
1561 if isVertical:
1562 w = max(w, btnWidth)
1563 h += bh
1564 else:
1565 h = max(h, btnHeight)
1566 w += bw
1567
1568 # Add the control's sizes
1569 for control in self.GetControls():
1570 cw, ch = control.GetSize()
1571 if isVertical:
1572 h += ch
1573 w = max(w, cw)
1574 else:
1575 w += cw
1576 h = max(h, ch)
1577
1578 # Add the separator's sizes and the 2 SizerItems at the beginning
1579 # and at the end
1580 if self.IsVertical():
1581 h += 2*margins.y + len(self._vSeparators)*separator_size
1582 else:
1583 w += 2*margins.x + len(self._vSeparators)*separator_size
1584
1585 return wx.Size(w, h)
1586
1587
1588 def OnPaint(self, event):
1589 """ Handles the wx.EVT_PAINT event for ButtonPanel. """
1590
1591 dc = wx.BufferedPaintDC(self)
1592 rect = self.GetClientRect()
1593
1594 self._art.DrawButtonPanel(dc, rect, self._nStyle)
1595 self._mainsizer.Draw(dc)
1596
1597
1598 def OnEraseBackground(self, event):
1599 """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
1600
1601 pass
1602
1603
1604 def OnSize(self, event):
1605 """ Handles the wx.EVT_SIZE event for ButtonPanel. """
1606
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?
1610 self.LayoutItems()
1611 self.Refresh()
1612
1613 event.Skip()
1614
1615
1616 def LayoutItems(self):
1617 """
1618 Layout the items using a different algorithm depending on the existance
1619 of the main caption.
1620 """
1621
1622 nonspacers, allchildren = self.GetNonFlexibleChildren()
1623
1624 if self.HasBarText():
1625 self.FlexibleLayout(nonspacers, allchildren)
1626 else:
1627 self.SizeLayout(nonspacers, allchildren)
1628
1629 self._mainsizer.Layout()
1630
1631
1632 def SizeLayout(self, nonspacers, children):
1633 """ Layout the items when no main caption exists. """
1634
1635 size = self.GetSize()
1636 isVertical = self.IsVertical()
1637
1638 corner = 0
1639 indx1 = len(nonspacers)
1640
1641 for item in nonspacers:
1642 corner += self.GetItemSize(item, isVertical)
1643 if corner > size[isVertical]:
1644 indx1 = nonspacers.index(item)
1645 break
1646
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)
1651
1652
1653 def GetItemSize(self, item, isVertical):
1654 """ Returns the size of an item in the main ButtonPanel sizer. """
1655
1656 if item.GetUserData():
1657 return item.GetUserData().GetBestSize()[isVertical]
1658 else:
1659 return item.GetSize()[isVertical]
1660
1661
1662 def FlexibleLayout(self, nonspacers, allchildren):
1663 """ Layout the items when the main caption exists. """
1664
1665 if len(nonspacers) < 2:
1666 return
1667
1668 isVertical = self.IsVertical()
1669 isStandard = self.IsStandard()
1670
1671 size = self.GetSize()[isVertical]
1672 padding = self._art.GetMetric(BP_PADDING_SIZE)
1673
1674 fixed = (isStandard and [nonspacers[1]] or [nonspacers[-2]])[0]
1675
1676 if isStandard:
1677 nonspacers.reverse()
1678 leftendx = fixed.GetSize()[isVertical] + padding.x
1679 else:
1680 rightstartx = size - fixed.GetSize()[isVertical]
1681 size = 0
1682
1683 count = lennonspacers = len(nonspacers)
1684
1685 for item in nonspacers:
1686 if isStandard:
1687 size -= self.GetItemSize(item, isVertical)
1688 if size < leftendx:
1689 break
1690 else:
1691 size += self.GetItemSize(item, isVertical)
1692 if size > rightstartx:
1693 break
1694
1695 count = count - 1
1696
1697 nonspacers.reverse()
1698
1699 for jj in xrange(2, lennonspacers):
1700 indx = allchildren.index(nonspacers[jj])
1701 self._mainsizer.Show(indx, jj >= count)
1702
1703
1704 def GetNonFlexibleChildren(self):
1705 """
1706 Returns all the ButtonPanel main sizer's children that are not
1707 flexible spacers.
1708 """
1709
1710 children1 = []
1711 children2 = self._mainsizer.GetChildren()
1712
1713 for child in children2:
1714 if child.IsSpacer():
1715 if child.GetUserData() or child.GetProportion() == 0:
1716 children1.append(child)
1717 else:
1718 children1.append(child)
1719
1720 return children1, children2
1721
1722
1723 def GetControls(self):
1724 """ Returns the wxPython controls that belongs to ButtonPanel. """
1725
1726 children2 = self._mainsizer.GetChildren()
1727 children1 = [child for child in children2 if not child.IsSpacer()]
1728
1729 return children1
1730
1731
1732 def SetStyle(self, style):
1733 """ Sets ButtonPanel style. """
1734
1735 if style == self._nStyle:
1736 return
1737
1738 self._nStyle = style
1739 self.Refresh()
1740
1741
1742 def GetStyle(self):
1743 """ Returns the ButtonPanel style. """
1744
1745 return self._nStyle
1746
1747
1748 def OnMouseMove(self, event):
1749 """ Handles the wx.EVT_MOTION event for ButtonPanel. """
1750
1751 # Check to see if we are hovering a button
1752 tabId, flags = self.HitTest(event.GetPosition())
1753
1754 if flags != BP_HT_BUTTON:
1755 self.RemoveHelp()
1756 self.RepaintOldSelection()
1757 self._currentButton = -1
1758 return
1759
1760 btn = self._vButtons[tabId]
1761
1762 if not btn.IsEnabled():
1763 self.RemoveHelp()
1764 self.RepaintOldSelection()
1765 return
1766
1767 if tabId != self._currentButton:
1768 self.RepaintOldSelection()
1769
1770 if btn.GetRect().Contains(event.GetPosition()):
1771 if btn.GetStatus() != "Pressed":
1772 btn.SetStatus("Hover")
1773 else:
1774 btn.SetStatus("Normal")
1775
1776 if tabId != self._currentButton:
1777 self.RemoveHelp()
1778 self.DoGiveHelp(btn)
1779
1780 self._currentButton = tabId
1781
1782 event.Skip()
1783
1784
1785 def OnLeftDown(self, event):
1786 """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
1787
1788 tabId, hit = self.HitTest(event.GetPosition())
1789
1790 if hit == BP_HT_BUTTON:
1791 btn = self._vButtons[tabId]
1792 if btn.IsEnabled():
1793 btn.SetStatus("Pressed")
1794 self._currentButton = tabId
1795
1796
1797 def OnLeftUp(self, event):
1798 """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
1799
1800 tabId, flags = self.HitTest(event.GetPosition())
1801
1802 if flags != BP_HT_BUTTON:
1803 return
1804
1805 hit = self._vButtons[tabId]
1806
1807 if hit.GetStatus() == "Disabled":
1808 return
1809
1810 for btn in self._vButtons:
1811 if btn != hit:
1812 btn.SetFocus(False)
1813
1814 if hit.GetStatus() == "Pressed":
1815 hit.SetToggled(not hit.GetToggled())
1816
1817 # Update the button status to be hovered
1818 hit.SetStatus("Hover")
1819 hit.SetFocus()
1820 self._currentButton = tabId
1821
1822 # Fire a button click event
1823 btnEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, hit.GetId())
1824 self.GetEventHandler().ProcessEvent(btnEvent)
1825
1826
1827 def OnMouseLeave(self, event):
1828 """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
1829
1830 # Reset all buttons statuses
1831 for btn in self._vButtons:
1832 if not btn.IsEnabled():
1833 continue
1834 btn.SetStatus("Normal")
1835
1836 self.RemoveHelp()
1837
1838 event.Skip()
1839
1840
1841 def OnMouseEnterWindow(self, event):
1842 """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
1843
1844 tabId, flags = self.HitTest(event.GetPosition())
1845
1846 if flags == BP_HT_BUTTON:
1847
1848 hit = self._vButtons[tabId]
1849
1850 if hit.GetStatus() == "Disabled":
1851 event.Skip()
1852 return
1853
1854 self.DoGiveHelp(hit)
1855 self._currentButton = tabId
1856
1857 event.Skip()
1858
1859
1860 def DoGiveHelp(self, hit):
1861 """ Gives tooltips and help in StatusBar. """
1862
1863 if not self.GetUseHelp():
1864 return
1865
1866 shortHelp = hit.GetShortHelp()
1867 if shortHelp:
1868 self.SetToolTipString(shortHelp)
1869 self._haveTip = True
1870
1871 longHelp = hit.GetLongHelp()
1872 if not longHelp:
1873 return
1874
1875 topLevel = wx.GetTopLevelParent(self)
1876
1877 if isinstance(topLevel, wx.Frame) and topLevel.GetStatusBar():
1878 statusBar = topLevel.GetStatusBar()
1879
1880 if self._statusTimer and self._statusTimer.IsRunning():
1881 self._statusTimer.Stop()
1882 statusBar.PopStatusText(0)
1883
1884 statusBar.PushStatusText(longHelp, 0)
1885 self._statusTimer = StatusBarTimer(self)
1886 self._statusTimer.Start(_DELAY, wx.TIMER_ONE_SHOT)
1887
1888
1889 def RemoveHelp(self):
1890 """ Removes the tooltips and statusbar help (if any) for a button. """
1891
1892 if not self.GetUseHelp():
1893 return
1894
1895 if self._haveTip:
1896 self.SetToolTipString("")
1897 self._haveTip = False
1898
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
1905
1906
1907 def RepaintOldSelection(self):
1908 """ Repaints the old selected/hovered button. """
1909
1910 current = self._currentButton
1911
1912 if current == -1:
1913 return
1914
1915 btn = self._vButtons[current]
1916 if not btn.IsEnabled():
1917 return
1918
1919 btn.SetStatus("Normal")
1920
1921
1922 def OnStatusBarTimer(self):
1923 """ Handles the timer expiring to delete the longHelp in the StatusBar. """
1924
1925 topLevel = wx.GetTopLevelParent(self)
1926 statusBar = topLevel.GetStatusBar()
1927 statusBar.PopStatusText(0)
1928
1929
1930 def SetUseHelp(self, useHelp=True):
1931 """ Sets whether or not shortHelp and longHelp should be displayed. """
1932
1933 self._useHelp = useHelp
1934
1935
1936 def GetUseHelp(self):
1937 """ Returns whether or not shortHelp and longHelp should be displayed. """
1938
1939 return self._useHelp
1940
1941
1942 def HitTest(self, pt):
1943 """
1944 HitTest method for ButtonPanel. Returns the button (if any) and
1945 a flag (if any).
1946 """
1947
1948 for ii in xrange(len(self._vButtons)):
1949 if not self._vButtons[ii].IsEnabled():
1950 continue
1951 if self._vButtons[ii].GetRect().Contains(pt):
1952 return ii, BP_HT_BUTTON
1953
1954 return -1, BP_HT_NONE
1955
1956
1957 def GetBPArt(self):
1958 """ Returns the associated BPArt art provider. """
1959
1960 return self._art
1961
1962
1963 def SetBPArt(self, art):
1964 """ Sets a new BPArt to ButtonPanel. Useful only if another BPArt class is used. """
1965
1966 self._art = art
1967 self.Refresh()
1968
1969 if wx.VERSION < (2,7,1,1):
1970 def Freeze(self):
1971 """Freeze ButtonPanel."""
1972
1973 self._freezeCount = self._freezeCount + 1
1974 wx.PyPanel.Freeze(self)
1975
1976
1977 def Thaw(self):
1978 """Thaw ButtonPanel."""
1979
1980 if self._freezeCount == 0:
1981 raise "\nERROR: Thawing Unfrozen ButtonPanel?"
1982
1983 self._freezeCount = self._freezeCount - 1
1984 wx.PyPanel.Thaw(self)
1985
1986
1987 def IsFrozen(self):
1988 """ Returns whether a call to Freeze() has been done. """
1989
1990 return self._freezeCount != 0
1991
1992