]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/buttonpanel.py
added missing button state
[wxWidgets.git] / wxPython / wx / lib / buttonpanel.py
CommitLineData
66dae888
RD
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
61b35490 14# Latest Revision: 17 Oct 2006, 17.00 GMT
66dae888
RD
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"""
30With `ButtonPanel` class you have a panel with gradient coloring
31on it and with the possibility to place some buttons on it. Using a
32standard panel with normal wx.Buttons leads to an ugly result: the
33buttons are placed correctly on the panel - but with grey area around
34them. Gradient coloring is kept behind the images - this was achieved
35due to the PNG format and the transparency of the bitmaps.
36
37The image are functioning like a buttons and can be caught in your
38code using the usual self.Bind(wx.EVT_BUTTON, self.OnButton) method.
39
40The control is generic, and support theming (well, I tested it under
41Windows with the three defauls themes: grey, blue, silver and the
42classic look).
43
44
45Usage
46-----
47
61b35490
RD
48ButtonPanel supports 4 alignments: left, right, top, bottom, which have a
49different meaning and behavior wrt wx.Toolbar. The easiest thing is to try
50the demo to understand, but I'll try to explain how it works.
51
52CASE 1: ButtonPanel has a main caption text
53
54Left alignment means ButtonPanel is horizontal, with the text aligned to the
55left. When you shrink the demo frame, if there is not enough room for all
56the controls to be shown, the controls closest to the text are hidden;
57
58Right alignment means ButtonPanel is horizontal, with the text aligned to the
59right. Item layout as above;
60
61Top alignment means ButtonPanel is vertical, with the text aligned to the top.
62Item layout as above;
63
64Bottom alignment means ButtonPanel is vertical, with the text aligned to the
65bottom. Item layout as above.
66
67
68CASE 2: ButtonPanel has *no* main caption text
69In this case, left and right alignment are the same (as top and bottom are the same),
70but the layout strategy changes: now if there is not enough room for all the controls
71to be shown, the last added items are hidden ("last" means on the far right for
72horizontal ButtonPanels and far bottom for vertical ButtonPanels).
73
74
66dae888
RD
75The following example shows a simple implementation that uses ButtonPanel
76inside 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
66dae888
RD
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
128License And Version:
129
130ButtonPanel Is Freeware And Distributed Under The wxPython License.
131
37840b8b
RD
132Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT
133Version 0.3.
66dae888
RD
134
135"""
136
137
138import wx
139
37840b8b
RD
140# Some constants to tune the BPArt class
141BP_BACKGROUND_COLOR = 0
142""" Background brush colour when no gradient shading exists. """
143BP_GRADIENT_COLOR_FROM = 1
144""" Starting gradient colour, used only when BP_USE_GRADIENT style is applied. """
145BP_GRADIENT_COLOR_TO = 2
146""" Ending gradient colour, used only when BP_USE_GRADIENT style is applied. """
147BP_BORDER_COLOR = 3
148""" Pen colour to paint the border of ButtonPanel. """
149BP_TEXT_COLOR = 4
150""" Main ButtonPanel caption colour. """
151BP_BUTTONTEXT_COLOR = 5
152""" Text colour for buttons with text. """
153BP_BUTTONTEXT_INACTIVE_COLOR = 6
154""" Text colour for inactive buttons with text. """
155BP_SELECTION_BRUSH_COLOR = 7
156""" Brush colour to be used when hovering or selecting a button. """
157BP_SELECTION_PEN_COLOR = 8
158""" Pen colour to be used when hovering or selecting a button. """
159BP_SEPARATOR_COLOR = 9
160""" Pen colour used to paint the separators. """
161BP_TEXT_FONT = 10
162""" Font of the ButtonPanel main caption. """
163BP_BUTTONTEXT_FONT = 11
164""" Text font for the buttons with text. """
165
166BP_BUTTONTEXT_ALIGN_BOTTOM = 12
167""" Flag that indicates the image and text in buttons is stacked. """
168BP_BUTTONTEXT_ALIGN_RIGHT = 13
169""" Flag that indicates the text is shown alongside the image in buttons with text. """
170
171BP_SEPARATOR_SIZE = 14
172"""
173Separator size. NB: This is not the line width, but the sum of the space before
174and after the separator line plus the width of the line.
175"""
176BP_MARGINS_SIZE = 15
177"""
178Size of the left/right margins in ButtonPanel (top/bottom for vertically
179aligned ButtonPanels).
180"""
181BP_BORDER_SIZE = 16
182""" Size of the border. """
183BP_PADDING_SIZE = 17
184""" Inter-tool separator size. """
185
186# Caption Gradient Type
187BP_GRADIENT_NONE = 0
188""" No gradient shading should be used to paint the background. """
189BP_GRADIENT_VERTICAL = 1
190""" Vertical gradient shading should be used to paint the background. """
191BP_GRADIENT_HORIZONTAL = 2
192""" Horizontal gradient shading should be used to paint the background. """
66dae888
RD
193
194# Flags for HitTest() method
195BP_HT_BUTTON = 200
196BP_HT_NONE = 201
197
198# Alignment of buttons in the panel
37840b8b
RD
199BP_ALIGN_RIGHT = 1
200BP_ALIGN_LEFT = 2
201BP_ALIGN_TOP = 4
202BP_ALIGN_BOTTOM = 8
203
204# ButtonPanel styles
205BP_DEFAULT_STYLE = 1
206BP_USE_GRADIENT = 2
66dae888 207
61b35490
RD
208# Delay used to cancel the longHelp in the statusbar field
209_DELAY = 3000
210
37840b8b
RD
211
212# Check for the new method in 2.7 (not present in 2.6.3.3)
213if wx.VERSION_STRING < "2.7":
214 wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point)
66dae888
RD
215
216
37840b8b 217def BrightenColour(color, factor):
66dae888
RD
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
37840b8b
RD
241def 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
275def 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
293class 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))
61b35490 325
37840b8b
RD
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())
61b35490 331
37840b8b
RD
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:
170acdc9 623 dc.DrawLine(coord, rect.y, coord, rect.y + rect.height)
37840b8b
RD
624
625 rf += rstep
626 gf += gstep
627 bf += bstep
628
61b35490
RD
629
630class 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
37840b8b
RD
648
649class 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
731class 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
757class 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
844class 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
879class 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
66dae888
RD
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
37840b8b 944class ButtonInfo(Control):
66dae888 945
37840b8b 946 def __init__(self, parent, id=wx.ID_ANY, bmp=wx.NullBitmap,
61b35490
RD
947 status="Normal", text="", kind=wx.ITEM_NORMAL,
948 shortHelp="", longHelp=""):
66dae888
RD
949 """
950 Default class constructor.
951
952 Parameters:
37840b8b 953 - parent: the parent window (ButtonPanel);
66dae888
RD
954 - id: the button id;
955 - bmp: the associated bitmap;
61b35490
RD
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.
66dae888 963 """
37840b8b 964
66dae888
RD
965 if id == wx.ID_ANY:
966 id = wx.NewId()
37840b8b 967
66dae888
RD
968 self._status = status
969 self._rect = wx.Rect()
37840b8b
RD
970 self._text = text
971 self._kind = kind
972 self._toggle = False
973 self._textAlignment = BP_BUTTONTEXT_ALIGN_BOTTOM
61b35490
RD
974 self._shortHelp = shortHelp
975 self._longHelp = longHelp
37840b8b
RD
976
977 disabledbmp = GrayOut(bmp)
978
979 self._bitmaps = {"Normal": bmp, "Toggled": None, "Disabled": disabledbmp,
980 "Hover": None, "Pressed": None}
981
982 Control.__init__(self, parent)
66dae888
RD
983
984
37840b8b
RD
985 def GetBestSize(self):
986 """ Returns the best size for the button. """
987
988 xsize = self.GetBitmap().GetWidth()
989 ysize = self.GetBitmap().GetHeight()
990
991 if self.HasText():
992 # We have text in the button
993 dc = wx.ClientDC(self._parent)
994 normalFont = self._parent._art.GetFont(BP_BUTTONTEXT_FONT)
995 dc.SetFont(normalFont)
996 tw, th = dc.GetTextExtent(self.GetText())
997
998 if self.GetTextAlignment() == BP_BUTTONTEXT_ALIGN_BOTTOM:
999 xsize = max(xsize, tw)
1000 ysize = ysize + th
1001 else:
1002 xsize = xsize + tw
1003 ysize = max(ysize, th)
1004
1005 border = self._parent._art.GetMetric(BP_BORDER_SIZE)
1006 padding = self._parent._art.GetMetric(BP_PADDING_SIZE)
1007
1008 if self._parent.IsVertical():
1009 xsize = xsize + 2*border
1010 else:
1011 ysize = ysize + 2*border
1012
1013 self._size = wx.Size(xsize+2*padding.x, ysize+2*padding.y)
1014
1015 return self._size
1016
1017
1018 def Draw(self, dc, rect):
1019 """ Draws the button on ButtonPanel. Actually the drawing is done in BPArt. """
1020
1021 if not self.IsShown():
1022 return
1023
1024 buttonBitmap = self.GetBitmap()
1025 isVertical = self._parent.IsVertical()
1026 text = self.GetText()
1027 parentSize = self._parent.GetSize()[not isVertical]
1028 buttonStatus = self.GetStatus()
1029 isToggled = self.GetToggled()
1030 textAlignment = self.GetTextAlignment()
1031
1032 self._parent._art.DrawButton(dc, rect, parentSize, buttonBitmap, isVertical,
1033 buttonStatus, isToggled, textAlignment, text)
1034
1035 self.SetRect(rect)
1036
1037
1038 def CheckRefresh(self, status):
1039 """ Checks whether a ButtonPanel repaint is needed or not. Convenience function. """
1040
1041 if status == self._status:
61b35490 1042 self._parent.RefreshRect(self.GetRect())
37840b8b
RD
1043
1044
1045 def SetBitmap(self, bmp, status="Normal"):
1046 """ Sets the associated bitmap. """
1047
1048 self._bitmaps[status] = bmp
1049 self.CheckRefresh(status)
1050
1051
1052 def GetBitmap(self, status=None):
66dae888
RD
1053 """ Returns the associated bitmap. """
1054
37840b8b
RD
1055 if status is None:
1056 status = self._status
1057
1058 if not self.IsEnabled():
1059 status = "Disabled"
1060
1061 if self._bitmaps[status] is None:
1062 return self._bitmaps["Normal"]
1063
1064 return self._bitmaps[status]
66dae888
RD
1065
1066
1067 def GetRect(self):
1068 """ Returns the button rect. """
1069
1070 return self._rect
1071
1072
1073 def GetStatus(self):
1074 """ Returns the button status. """
1075
1076 return self._status
1077
1078
1079 def GetId(self):
1080 """ Returns the button id. """
1081
1082 return self._id
1083
1084
1085 def SetRect(self, rect):
1086 """ Sets the button rect. """
1087
1088 self._rect = rect
66dae888
RD
1089
1090
1091 def SetStatus(self, status):
1092 """ Sets the button status. """
1093
37840b8b
RD
1094 if status == self._status:
1095 return
1096
1097 if self.GetToggled() and status == "Normal":
1098 status = "Toggled"
1099
66dae888 1100 self._status = status
61b35490 1101 self._parent.RefreshRect(self.GetRect())
37840b8b
RD
1102
1103
1104 def GetTextAlignment(self):
1105 """ Returns the text alignment in the button (bottom or right). """
1106
1107 return self._textAlignment
1108
1109
1110 def SetTextAlignment(self, alignment):
1111 """ Sets the text alignment in the button (bottom or right). """
1112
1113 if alignment == self._textAlignment:
1114 return
1115
2356118e 1116 self._textAlignment = alignment
37840b8b
RD
1117
1118
1119 def GetToggled(self):
1120 """ Returns whether a wx.ITEM_CHECK button is toggled or not. """
1121
1122 if self._kind == wx.ITEM_NORMAL:
1123 return False
1124
1125 return self._toggle
1126
1127
1128 def SetToggled(self, toggle=True):
1129 """ Sets a wx.ITEM_CHECK button toggled/not toggled. """
1130
1131 if self._kind == wx.ITEM_NORMAL:
1132 return
1133
1134 self._toggle = toggle
66dae888
RD
1135
1136
1137 def SetId(self, id):
1138 """ Sets the button id. """
1139
1140 self._id = id
1141
37840b8b
RD
1142
1143 def AddStatus(self, name="Custom", bmp=wx.NullBitmap):
1144 """
1145 Add a programmer-defined status in addition to the 5 default status:
1146 - Normal;
1147 - Disabled;
1148 - Hover;
1149 - Pressed;
1150 - Toggled.
1151 """
1152
1153 self._bitmaps.update({name: bmp})
1154
1155
1156 def Enable(self, enable=True):
1157
1158 if enable:
1159 self._status = "Normal"
1160 else:
1161 self._status = "Disabled"
1162
1163
1164 def IsEnabled(self):
1165
1166 return self._status != "Disabled"
1167
1168
1169 def SetText(self, text=""):
1170 """ Sets the text of the button. """
1171
1172 self._text = text
1173
1174
1175 def GetText(self):
1176 """ Returns the text associated to the button. """
1177
1178 return self._text
1179
1180
1181 def HasText(self):
1182 """ Returns whether the button has text or not. """
1183
1184 return self._text != ""
1185
1186
1187 def SetKind(self, kind=wx.ITEM_NORMAL):
1188 """ Sets the button type (standard or toggle). """
1189
1190 self._kind = kind
1191
1192
1193 def GetKind(self):
1194 """ Returns the button type (standard or toggle). """
1195
1196 return self._kind
61b35490
RD
1197
1198
1199 def SetShortHelp(self, help=""):
1200 """ Sets the help string to be shown in a tootip. """
1201
1202 self._shortHelp = help
1203
1204
1205 def GetShortHelp(self):
1206 """ Returns the help string shown in a tootip. """
1207
1208 return self._shortHelp
1209
1210
1211 def SetLongHelp(self, help=""):
1212 """ Sets the help string to be shown in the statusbar. """
1213
1214 self._longHelp = help
1215
1216
1217 def GetLongHelp(self):
1218 """ Returns the help string shown in the statusbar. """
1219
1220 return self._longHelp
37840b8b
RD
1221
1222
66dae888
RD
1223 Bitmap = property(GetBitmap, SetBitmap)
1224 Id = property(GetId, SetId)
1225 Rect = property(GetRect, SetRect)
1226 Status = property(GetStatus, SetStatus)
1227
1228
66dae888
RD
1229# -- ButtonPanel class implementation ----------------------------------
1230# This is the main class.
1231
9d45af36 1232class ButtonPanel(wx.PyPanel):
66dae888
RD
1233
1234 def __init__(self, parent, id=wx.ID_ANY, text="", style=BP_DEFAULT_STYLE,
37840b8b 1235 alignment=BP_ALIGN_LEFT, name="buttonPanel"):
66dae888
RD
1236 """
1237 Default class constructor.
1238
1239 - parent: parent window
1240 - id: window ID
1241 - text: text to draw
1242 - style: window style
1243 - alignment: alignment of buttons (left or right)
1244 - name: window class name
1245 """
1246
9d45af36 1247 wx.PyPanel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize,
61b35490 1248 wx.NO_BORDER, name=name)
37840b8b 1249
66dae888 1250 self._vButtons = []
37840b8b 1251 self._vSeparators = []
66dae888 1252
66dae888
RD
1253 self._nStyle = style
1254 self._alignment = alignment
61b35490
RD
1255 self._statusTimer = None
1256 self._useHelp = True
1257 self._freezeCount = 0
1258 self._currentButton = -1
122c2bae 1259 self._haveTip = False
37840b8b
RD
1260
1261 self._art = BPArt(style)
1262
1263 self._controlCreated = False
1264
1265 direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0]
1266 self._mainsizer = BoxSizer(direction)
1267 self.SetSizer(self._mainsizer)
1268
1269 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1270
1271 # First spacer to create some room before the first text/button/control
1272 self._mainsizer.Add((margins.x, margins.y), 0)
66dae888 1273
37840b8b
RD
1274 # Last spacer to create some room before the last text/button/control
1275 self._mainsizer.Add((margins.x, margins.y), 0)
1276
1277 self.Bind(wx.EVT_SIZE, self.OnSize)
66dae888 1278 self.Bind(wx.EVT_PAINT, self.OnPaint)
66dae888
RD
1279 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1280 self.Bind(wx.EVT_MOTION, self.OnMouseMove)
1281 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
1282 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
1283 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
1284 self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow)
1285
37840b8b
RD
1286 self.SetBarText(text)
1287 self.LayoutItems()
1288
1289
1290 def SetBarText(self, text):
1291 """ Sets the main caption text (leave text="" for no text). """
1292
1293 self.Freeze()
1294
1295 text = text.strip()
1296
1297 if self._controlCreated:
1298 self.RemoveText()
1299
1300 self._text = ButtonPanelText(self, text)
1301 lenChildren = len(self._mainsizer.GetChildren())
1302
1303 if text == "":
1304 # Even if we have no text, we insert it an empty spacer anyway
1305 # it is easier to handle if you have to recreate the sizer after.
1306 if self.IsStandard():
1307 self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER,
1308 userData=self._text, realIndex=0)
1309 else:
1310 self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER,
1311 userData=self._text, realIndex=lenChildren)
1312
1313 return
1314
1315 # We have text, so insert the text and an expandable spacer
1316 # alongside it. "Standard" ButtonPanel are left or top aligned.
1317 if self.IsStandard():
1318 self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER,
1319 userData=self._text, realIndex=0)
1320 self._mainsizer.Insert(2, (0, 0), 1, wx.EXPAND)
1321
1322 else:
1323 self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER,
1324 userData=self._text, realIndex=lenChildren)
1325 self._mainsizer.Insert(lenChildren-1, (0, 0), 1, wx.EXPAND)
1326
1327
1328 def RemoveText(self):
1329 """ Removes the main caption text. """
1330
1331 lenChildren = len(self._mainsizer.GetChildren())
1332 lenCustom = len(self._vButtons) + len(self._vSeparators) + 1
1333
1334 if self.IsStandard():
1335 # Detach the text
1336 self._mainsizer.Remove(1, 0)
1337 if self.HasBarText():
1338 # Detach the expandable spacer
1339 self._mainsizer.Remove(1, -1)
1340 else:
1341 # Detach the text
1342 self._mainsizer.Remove(lenChildren-2, lenCustom-1)
1343 if self.HasBarText():
1344 # Detach the expandable spacer
1345 self._mainsizer.Remove(lenChildren-3, -1)
66dae888 1346
37840b8b
RD
1347
1348 def GetBarText(self):
1349 """ Returns the main caption text. """
1350
1351 return self._text.GetText()
1352
1353
1354 def HasBarText(self):
1355 """ Returns whether ButtonPanel has a main caption text or not. """
1356
1357 return hasattr(self, "_text") and self._text.GetText() != ""
1358
1359
66dae888
RD
1360 def AddButton(self, btnInfo):
1361 """
1362 Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
1363 this method. See the demo for details.
1364 """
1365
37840b8b
RD
1366 lenChildren = len(self._mainsizer.GetChildren())
1367 self._mainsizer.Insert(lenChildren-1, btnInfo, 0, wx.ALIGN_CENTER|wx.EXPAND, userData=btnInfo)
1368
66dae888 1369 self._vButtons.append(btnInfo)
37840b8b
RD
1370
1371
1372 def AddSpacer(self, size=(0, 0), proportion=1, flag=wx.EXPAND):
1373 """ Adds a spacer (stretchable or fixed-size) to ButtonPanel. """
1374
1375 lenChildren = len(self._mainsizer.GetChildren())
1376 self._mainsizer.Insert(lenChildren-1, size, proportion, flag)
1377
1378
1379 def AddControl(self, control, proportion=0, flag=wx.ALIGN_CENTER|wx.ALL, border=None):
1380 """ Adds a wxPython control to ButtonPanel. """
1381
1382 lenChildren = len(self._mainsizer.GetChildren())
1383
1384 if border is None:
1385 border = self._art.GetMetric(BP_PADDING_SIZE)
1386 border = max(border.x, border.y)
1387
1388 self._mainsizer.Insert(lenChildren-1, control, proportion, flag, border)
1389
1390
1391 def AddSeparator(self):
1392 """ Adds a separator line to ButtonPanel. """
1393
1394 lenChildren = len(self._mainsizer.GetChildren())
1395 separator = Separator(self)
1396
1397 self._mainsizer.Insert(lenChildren-1, separator, 0, wx.EXPAND)
1398 self._vSeparators.append(separator)
1399
66dae888
RD
1400
1401 def RemoveAllButtons(self):
1402 """ Remove all the buttons from ButtonPanel. """
1403
1404 self._vButtons = []
37840b8b 1405
66dae888 1406
37840b8b
RD
1407 def RemoveAllSeparators(self):
1408 """ Remove all the separators from ButtonPanel. """
66dae888 1409
37840b8b
RD
1410 self._vSeparators = []
1411
1412
66dae888 1413 def GetAlignment(self):
37840b8b 1414 """ Returns the button alignment (left, right, top, bottom). """
66dae888
RD
1415
1416 return self._alignment
1417
1418
1419 def SetAlignment(self, alignment):
37840b8b 1420 """ Sets the button alignment (left, right, top, bottom). """
66dae888 1421
37840b8b
RD
1422 if alignment == self._alignment:
1423 return
66dae888 1424
37840b8b
RD
1425 self.Freeze()
1426
1427 text = self.GetBarText()
1428
1429 # Remove the text in any case
1430 self.RemoveText()
66dae888 1431
37840b8b
RD
1432 # Remove the first and last spacers
1433 self._mainsizer.Remove(0, -1)
1434 self._mainsizer.Remove(len(self._mainsizer.GetChildren())-1, -1)
1435
1436 self._alignment = alignment
66dae888 1437
37840b8b
RD
1438 # Recreate the sizer accordingly to the new alignment
1439 self.ReCreateSizer(text)
66dae888
RD
1440
1441
37840b8b
RD
1442 def IsVertical(self):
1443 """ Returns whether ButtonPanel is vertically aligned or not. """
66dae888 1444
37840b8b 1445 return self._alignment not in [BP_ALIGN_RIGHT, BP_ALIGN_LEFT]
66dae888 1446
66dae888 1447
37840b8b
RD
1448 def IsStandard(self):
1449 """ Returns whether ButtonPanel is aligned "Standard" (left/top) or not. """
1450
1451 return self._alignment in [BP_ALIGN_LEFT, BP_ALIGN_TOP]
1452
1453
1454 def DoLayout(self):
1455 """
1456 Do the Layout for ButtonPanel.
1457 NB: Call this method every time you make a modification to the layout
1458 or to the customizable sizes of the pseudo controls.
1459 """
1460
1461 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1462 lenChildren = len(self._mainsizer.GetChildren())
1463
1464 self._mainsizer.SetItemMinSize(0, (margins.x, margins.y))
1465 self._mainsizer.SetItemMinSize(lenChildren-1, (margins.x, margins.y))
1466
1467 self._controlCreated = True
1468 self.LayoutItems()
1469
1470 # *VERY* WEIRD: the sizer seems not to respond to any layout until I
1471 # change the ButtonPanel size and restore it back
1472 size = self.GetSize()
1473 self.SetSize((size.x+1, size.y+1))
1474 self.SetSize((size.x, size.y))
1475
1476 if self.IsFrozen():
1477 self.Thaw()
66dae888 1478
66dae888 1479
37840b8b
RD
1480 def ReCreateSizer(self, text):
1481 """ Recreates the ButtonPanel sizer accordingly to the alignment specified. """
1482
1483 children = self._mainsizer.GetChildren()
1484 self.RemoveAllButtons()
1485 self.RemoveAllSeparators()
66dae888 1486
37840b8b
RD
1487 # Create a new sizer depending on the alignment chosen
1488 direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0]
1489 self._mainsizer = BoxSizer(direction)
66dae888 1490
37840b8b
RD
1491 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1492 # First spacer to create some room before the first text/button/control
1493 self._mainsizer.Add((margins.x, margins.y), 0)
1494
1495 # Last spacer to create some room before the last text/button/control
1496 self._mainsizer.Add((margins.x, margins.y), 0)
1497
1498 # This is needed otherwise SetBarText goes mad
1499 self._controlCreated = False
1500
1501 for child in children:
1502 userData = child.GetUserData()
1503 if userData:
1504 if isinstance(userData, ButtonInfo):
1505 # It is a ButtonInfo, can't be anything else
1506 self.AddButton(child.GetUserData())
1507 elif isinstance(userData, Separator):
1508 self.AddSeparator()
1509
1510 else:
1511 if child.IsSpacer():
1512 # This is a spacer, expandable or not
1513 self.AddSpacer(child.GetSize(), child.GetProportion(),
1514 child.GetFlag())
1515 else:
1516 # This is a wxPython control
1517 self.AddControl(child.GetWindow(), child.GetProportion(),
1518 child.GetFlag(), child.GetBorder())
1519
1520 self.SetSizer(self._mainsizer)
1521
1522 # Now add the text. It doesn't matter if there is no text
1523 self.SetBarText(text)
1524
1525 self.DoLayout()
1526
1527 self.Thaw()
66dae888 1528
66dae888 1529
37840b8b
RD
1530 def DoGetBestSize(self):
1531 """ Returns the best size of ButtonPanel. """
1532
1533 w = h = btnWidth = btnHeight = 0
1534 isVertical = self.IsVertical()
1535
1536 padding = self._art.GetMetric(BP_PADDING_SIZE)
1537 border = self._art.GetMetric(BP_BORDER_SIZE)
1538 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1539 separator_size = self._art.GetMetric(BP_SEPARATOR_SIZE)
1540
1541 # Add the space required for the main caption
1542 if self.HasBarText():
1543 w, h = self._text.GetBestSize()
1544 if isVertical:
1545 h += padding.y
66dae888 1546 else:
37840b8b
RD
1547 w += padding.x
1548 else:
1549 w = h = border
66dae888 1550
37840b8b
RD
1551 # Add the button's sizes
1552 for btn in self._vButtons:
66dae888 1553
37840b8b
RD
1554 bw, bh = btn.GetBestSize()
1555 btnWidth = max(btnWidth, bw)
1556 btnHeight = max(btnHeight, bh)
66dae888 1557
37840b8b
RD
1558 if isVertical:
1559 w = max(w, btnWidth)
1560 h += bh
1561 else:
1562 h = max(h, btnHeight)
1563 w += bw
1564
1565 # Add the control's sizes
1566 for control in self.GetControls():
1567 cw, ch = control.GetSize()
1568 if isVertical:
1569 h += ch
1570 w = max(w, cw)
1571 else:
1572 w += cw
1573 h = max(h, ch)
66dae888 1574
37840b8b
RD
1575 # Add the separator's sizes and the 2 SizerItems at the beginning
1576 # and at the end
1577 if self.IsVertical():
1578 h += 2*margins.y + len(self._vSeparators)*separator_size
1579 else:
1580 w += 2*margins.x + len(self._vSeparators)*separator_size
61b35490 1581
37840b8b 1582 return wx.Size(w, h)
66dae888 1583
66dae888 1584
37840b8b
RD
1585 def OnPaint(self, event):
1586 """ Handles the wx.EVT_PAINT event for ButtonPanel. """
1587
1588 dc = wx.BufferedPaintDC(self)
1589 rect = self.GetClientRect()
66dae888 1590
37840b8b
RD
1591 self._art.DrawButtonPanel(dc, rect, self._nStyle)
1592 self._mainsizer.Draw(dc)
1593
66dae888
RD
1594
1595 def OnEraseBackground(self, event):
1596 """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
37840b8b 1597
66dae888
RD
1598 pass
1599
1600
1601 def OnSize(self, event):
1602 """ Handles the wx.EVT_SIZE event for ButtonPanel. """
37840b8b
RD
1603
1604 # NOTE: It seems like LayoutItems number of calls can be optimized in some way.
1605 # Currently every DoLayout (or every parent Layout()) calls about 3 times
1606 # the LayoutItems method. Any idea on how to improve it?
1607 self.LayoutItems()
1608 self.Refresh()
1609
66dae888 1610 event.Skip()
66dae888 1611
37840b8b
RD
1612
1613 def LayoutItems(self):
1614 """
1615 Layout the items using a different algorithm depending on the existance
1616 of the main caption.
66dae888 1617 """
66dae888 1618
37840b8b
RD
1619 nonspacers, allchildren = self.GetNonFlexibleChildren()
1620
1621 if self.HasBarText():
1622 self.FlexibleLayout(nonspacers, allchildren)
1623 else:
1624 self.SizeLayout(nonspacers, allchildren)
1625
1626 self._mainsizer.Layout()
66dae888 1627
66dae888 1628
37840b8b
RD
1629 def SizeLayout(self, nonspacers, children):
1630 """ Layout the items when no main caption exists. """
66dae888 1631
37840b8b
RD
1632 size = self.GetSize()
1633 isVertical = self.IsVertical()
1634
1635 corner = 0
1636 indx1 = len(nonspacers)
1637
1638 for item in nonspacers:
1639 corner += self.GetItemSize(item, isVertical)
1640 if corner > size[isVertical]:
1641 indx1 = nonspacers.index(item)
1642 break
1643
1644 # Leave out the last spacer, it has to be there always
1645 for ii in xrange(len(nonspacers)-1):
1646 indx = children.index(nonspacers[ii])
1647 self._mainsizer.Show(indx, ii < indx1)
1648
66dae888 1649
37840b8b
RD
1650 def GetItemSize(self, item, isVertical):
1651 """ Returns the size of an item in the main ButtonPanel sizer. """
1652
1653 if item.GetUserData():
1654 return item.GetUserData().GetBestSize()[isVertical]
1655 else:
1656 return item.GetSize()[isVertical]
66dae888 1657
37840b8b
RD
1658
1659 def FlexibleLayout(self, nonspacers, allchildren):
1660 """ Layout the items when the main caption exists. """
1661
1662 if len(nonspacers) < 2:
1663 return
1664
1665 isVertical = self.IsVertical()
1666 isStandard = self.IsStandard()
1667
1668 size = self.GetSize()[isVertical]
1669 padding = self._art.GetMetric(BP_PADDING_SIZE)
1670
1671 fixed = (isStandard and [nonspacers[1]] or [nonspacers[-2]])[0]
1672
1673 if isStandard:
1674 nonspacers.reverse()
1675 leftendx = fixed.GetSize()[isVertical] + padding.x
1676 else:
1677 rightstartx = size - fixed.GetSize()[isVertical]
1678 size = 0
1679
1680 count = lennonspacers = len(nonspacers)
1681
1682 for item in nonspacers:
1683 if isStandard:
1684 size -= self.GetItemSize(item, isVertical)
1685 if size < leftendx:
1686 break
1687 else:
1688 size += self.GetItemSize(item, isVertical)
1689 if size > rightstartx:
1690 break
1691
1692 count = count - 1
1693
1694 nonspacers.reverse()
1695
1696 for jj in xrange(2, lennonspacers):
1697 indx = allchildren.index(nonspacers[jj])
1698 self._mainsizer.Show(indx, jj >= count)
1699
1700
1701 def GetNonFlexibleChildren(self):
1702 """
1703 Returns all the ButtonPanel main sizer's children that are not
1704 flexible spacers.
1705 """
1706
1707 children1 = []
1708 children2 = self._mainsizer.GetChildren()
1709
1710 for child in children2:
1711 if child.IsSpacer():
1712 if child.GetUserData() or child.GetProportion() == 0:
1713 children1.append(child)
1714 else:
1715 children1.append(child)
1716
1717 return children1, children2
1718
1719
1720 def GetControls(self):
1721 """ Returns the wxPython controls that belongs to ButtonPanel. """
1722
1723 children2 = self._mainsizer.GetChildren()
1724 children1 = [child for child in children2 if not child.IsSpacer()]
1725
1726 return children1
1727
1728
1729 def SetStyle(self, style):
1730 """ Sets ButtonPanel style. """
1731
1732 if style == self._nStyle:
1733 return
1734
1735 self._nStyle = style
1736 self.Refresh()
1737
1738
1739 def GetStyle(self):
1740 """ Returns the ButtonPanel style. """
1741
1742 return self._nStyle
1743
1744
66dae888
RD
1745 def OnMouseMove(self, event):
1746 """ Handles the wx.EVT_MOTION event for ButtonPanel. """
1747
1748 # Check to see if we are hovering a button
61b35490 1749 tabId, flags = self.HitTest(event.GetPosition())
37840b8b 1750
61b35490
RD
1751 if flags != BP_HT_BUTTON:
1752 self.RemoveHelp()
1753 self.RepaintOldSelection()
1754 self._currentButton = -1
1755 return
1756
1757 btn = self._vButtons[tabId]
37840b8b 1758
61b35490
RD
1759 if not btn.IsEnabled():
1760 self.RemoveHelp()
1761 self.RepaintOldSelection()
1762 return
1763
1764 if tabId != self._currentButton:
1765 self.RepaintOldSelection()
1766
1767 if btn.GetRect().Contains(event.GetPosition()):
1768 btn.SetStatus("Hover")
1769 else:
1770 btn.SetStatus("Normal")
1771
1772 if tabId != self._currentButton:
1773 self.RemoveHelp()
1774 self.DoGiveHelp(btn)
1775
1776 self._currentButton = tabId
66dae888 1777
66dae888
RD
1778 event.Skip()
1779
1780
1781 def OnLeftDown(self, event):
1782 """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
1783
1784 tabId, hit = self.HitTest(event.GetPosition())
1785
61b35490
RD
1786 if hit == BP_HT_BUTTON:
1787 btn = self._vButtons[tabId]
1788 if btn.IsEnabled():
1789 btn.SetStatus("Pressed")
1790 self._currentButton = tabId
1791
66dae888
RD
1792
1793 def OnLeftUp(self, event):
1794 """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
1795
37840b8b 1796 tabId, flags = self.HitTest(event.GetPosition())
66dae888 1797
61b35490
RD
1798 if flags != BP_HT_BUTTON:
1799 return
1800
1801 hit = self._vButtons[tabId]
37840b8b 1802
61b35490
RD
1803 if hit.GetStatus() == "Disabled":
1804 return
66dae888 1805
61b35490
RD
1806 for btn in self._vButtons:
1807 if btn != hit:
1808 btn.SetFocus(False)
37840b8b 1809
61b35490
RD
1810 if hit.GetStatus() == "Pressed":
1811 # Fire a button click event
1812 btnEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, hit.GetId())
1813 self.GetEventHandler().ProcessEvent(btnEvent)
37840b8b 1814
61b35490
RD
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
66dae888
RD
1822
1823 def OnMouseLeave(self, event):
1824 """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
1825
37840b8b
RD
1826 # Reset all buttons statuses
1827 for btn in self._vButtons:
1828 if not btn.IsEnabled():
1829 continue
1830 btn.SetStatus("Normal")
61b35490
RD
1831
1832 self.RemoveHelp()
1833
66dae888
RD
1834 event.Skip()
1835
1836
1837 def OnMouseEnterWindow(self, event):
1838 """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
61b35490
RD
1839
1840 tabId, flags = self.HitTest(event.GetPosition())
1841
1842 if flags == BP_HT_BUTTON:
1843
1844 hit = self._vButtons[tabId]
1845
1846 if hit.GetStatus() == "Disabled":
1847 event.Skip()
1848 return
1849
1850 self.DoGiveHelp(hit)
1851 self._currentButton = tabId
1852
66dae888
RD
1853 event.Skip()
1854
1855
61b35490
RD
1856 def DoGiveHelp(self, hit):
1857 """ Gives tooltips and help in StatusBar. """
1858
1859 if not self.GetUseHelp():
1860 return
122c2bae 1861
61b35490
RD
1862 shortHelp = hit.GetShortHelp()
1863 if shortHelp:
1864 self.SetToolTipString(shortHelp)
122c2bae 1865 self._haveTip = True
61b35490
RD
1866
1867 longHelp = hit.GetLongHelp()
1868 if not longHelp:
1869 return
1870
1871 topLevel = wx.GetTopLevelParent(self)
1872
1873 if isinstance(topLevel, wx.Frame) and topLevel.GetStatusBar():
1874 statusBar = topLevel.GetStatusBar()
1875
1876 if self._statusTimer and self._statusTimer.IsRunning():
1877 self._statusTimer.Stop()
1878 statusBar.PopStatusText(0)
1879
1880 statusBar.PushStatusText(longHelp, 0)
1881 self._statusTimer = StatusBarTimer(self)
1882 self._statusTimer.Start(_DELAY, wx.TIMER_ONE_SHOT)
1883
1884
1885 def RemoveHelp(self):
1886 """ Removes the tooltips and statusbar help (if any) for a button. """
1887
1888 if not self.GetUseHelp():
1889 return
1890
122c2bae
RD
1891 if self._haveTip:
1892 self.SetToolTipString("")
1893 self._haveTip = False
61b35490
RD
1894
1895 if self._statusTimer and self._statusTimer.IsRunning():
1896 topLevel = wx.GetTopLevelParent(self)
1897 statusBar = topLevel.GetStatusBar()
1898 self._statusTimer.Stop()
1899 statusBar.PopStatusText(0)
1900 self._statusTimer = None
1901
1902
1903 def RepaintOldSelection(self):
1904 """ Repaints the old selected/hovered button. """
1905
1906 current = self._currentButton
1907
1908 if current == -1:
1909 return
1910
1911 btn = self._vButtons[current]
1912 if not btn.IsEnabled():
1913 return
1914
1915 btn.SetStatus("Normal")
1916
1917
1918 def OnStatusBarTimer(self):
1919 """ Handles the timer expiring to delete the longHelp in the StatusBar. """
1920
1921 topLevel = wx.GetTopLevelParent(self)
1922 statusBar = topLevel.GetStatusBar()
1923 statusBar.PopStatusText(0)
1924
1925
1926 def SetUseHelp(self, useHelp=True):
1927 """ Sets whether or not shortHelp and longHelp should be displayed. """
1928
1929 self._useHelp = useHelp
1930
1931
1932 def GetUseHelp(self):
1933 """ Returns whether or not shortHelp and longHelp should be displayed. """
1934
1935 return self._useHelp
1936
1937
66dae888
RD
1938 def HitTest(self, pt):
1939 """
1940 HitTest method for ButtonPanel. Returns the button (if any) and
1941 a flag (if any).
1942 """
1943
66dae888 1944 for ii in xrange(len(self._vButtons)):
37840b8b
RD
1945 if not self._vButtons[ii].IsEnabled():
1946 continue
1947 if self._vButtons[ii].GetRect().Contains(pt):
66dae888
RD
1948 return ii, BP_HT_BUTTON
1949
1950 return -1, BP_HT_NONE
1951
1952
37840b8b
RD
1953 def GetBPArt(self):
1954 """ Returns the associated BPArt art provider. """
66dae888 1955
37840b8b
RD
1956 return self._art
1957
61b35490
RD
1958
1959 def SetBPArt(self, art):
1960 """ Sets a new BPArt to ButtonPanel. Useful only if another BPArt class is used. """
1961
1962 self._art = art
1963 self.Refresh()
1964
1965 if wx.VERSION < (2,7,1,1):
1966 def Freeze(self):
1967 """Freeze ButtonPanel."""
1968
1969 self._freezeCount = self._freezeCount + 1
1970 wx.PyPanel.Freeze(self)
1971
1972
1973 def Thaw(self):
1974 """Thaw ButtonPanel."""
1975
1976 if self._freezeCount == 0:
1977 raise "\nERROR: Thawing Unfrozen ButtonPanel?"
1978
1979 self._freezeCount = self._freezeCount - 1
1980 wx.PyPanel.Thaw(self)
1981
1982
1983 def IsFrozen(self):
1984 """ Returns whether a call to Freeze() has been done. """
1985
1986 return self._freezeCount != 0
1987
1988