comparison orpg/tools/ButtonPanel.py @ 0:4385a7d0efd1 grumpy-goblin

Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author sirebral
date Tue, 14 Jul 2009 16:41:58 -0500
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4385a7d0efd1
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 # Check for the new method in 2.7 (not present in 2.6.3.3)
212 if wx.VERSION_STRING < "2.7":
213 wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point)
214
215 def BrightenColour(color, factor):
216 """ Bright the input colour by a factor."""
217
218 val = color.Red()*factor
219 if val > 255:
220 red = 255
221 else:
222 red = val
223 val = color.Green()*factor
224 if val > 255:
225 green = 255
226 else:
227 green = val
228 val = color.Blue()*factor
229 if val > 255:
230 blue = 255
231 else:
232 blue = val
233 return wx.Color(red, green, blue)
234
235 def GrayOut(anImage):
236 """
237 Convert the given image (in place) to a grayed-out version,
238 appropriate for a 'Disabled' appearance.
239 """
240 factor = 0.7 # 0 < f < 1. Higher Is Grayer
241 anImage = anImage.ConvertToImage()
242 if anImage.HasAlpha():
243 anImage.ConvertAlphaToMask(1)
244 if anImage.HasMask():
245 maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
246 else:
247 maskColor = None
248 data = map(ord, list(anImage.GetData()))
249 for i in range(0, len(data), 3):
250 pixel = (data[i], data[i+1], data[i+2])
251 pixel = MakeGray(pixel, factor, maskColor)
252 for x in range(3):
253 data[i+x] = pixel[x]
254 anImage.SetData(''.join(map(chr, data)))
255 anImage = anImage.ConvertToBitmap()
256 return anImage
257
258 def MakeGray((r,g,b), factor, maskColor):
259 """
260 Make a pixel grayed-out. If the pixel matches the maskColor, it won't be
261 changed.
262 """
263 if (r,g,b) != maskColor:
264 return map(lambda x: int((230 - x) * factor) + x, (r,g,b))
265 else:
266 return (r,g,b)
267
268 # ---------------------------------------------------------------------------- #
269 # Class BPArt
270 # Handles all the drawings for buttons, separators and text and allows the
271 # programmer to set colours, sizes and gradient shadings for ButtonPanel
272 # ---------------------------------------------------------------------------- #
273
274 class BPArt:
275 """
276 BPArt is an art provider class which does all of the drawing for ButtonPanel.
277 This allows the library caller to customize the BPArt or to completely replace
278 all drawing with custom BPArts.
279 """
280
281 def __init__(self, parentStyle):
282 """ Default class constructor. """
283 base_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE)
284 self._background_brush = wx.Brush(base_color, wx.SOLID)
285 self._gradient_color_to = wx.WHITE
286 self._gradient_color_from = wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
287 if parentStyle & BP_USE_GRADIENT:
288 self._border_pen = wx.Pen(wx.WHITE, 3)
289 self._caption_text_color = wx.WHITE
290 self._buttontext_color = wx.Colour(70, 143, 255)
291 self._separator_pen = wx.Pen(BrightenColour(self._gradient_color_from, 1.4))
292 self._gradient_type = BP_GRADIENT_VERTICAL
293 else:
294 self._border_pen = wx.Pen(BrightenColour(base_color, 0.9), 3)
295 self._caption_text_color = wx.BLACK
296 self._buttontext_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNTEXT)
297 self._separator_pen = wx.Pen(BrightenColour(base_color, 0.9))
298 self._gradient_type = BP_GRADIENT_NONE
299 self._buttontext_inactive_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT)
300 self._selection_brush = wx.Brush(wx.Color(225, 225, 255))
301 self._selection_pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION))
302 sysfont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
303 self._caption_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.BOLD,
304 False, sysfont.GetFaceName())
305 self._buttontext_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.NORMAL,
306 False, sysfont.GetFaceName())
307 self._separator_size = 7
308 self._margins_size = wx.Size(6, 6)
309 self._caption_border_size = 3
310 self._padding_size = wx.Size(6, 6)
311
312 def GetMetric(self, id):
313 """ Returns sizes of customizable options. """
314 if id == BP_SEPARATOR_SIZE:
315 return self._separator_size
316 elif id == BP_MARGINS_SIZE:
317 return self._margins_size
318 elif id == BP_BORDER_SIZE:
319 return self._caption_border_size
320 elif id == BP_PADDING_SIZE:
321 return self._padding_size
322 else:
323 raise "\nERROR: Invalid Metric Ordinal. "
324
325 def SetMetric(self, id, new_val):
326 """ Sets sizes for customizable options. """
327 if id == BP_SEPARATOR_SIZE:
328 self._separator_size = new_val
329 elif id == BP_MARGINS_SIZE:
330 self._margins_size = new_val
331 elif id == BP_BORDER_SIZE:
332 self._caption_border_size = new_val
333 self._border_pen.SetWidth(new_val)
334 elif id == BP_PADDING_SIZE:
335 self._padding_size = new_val
336 else:
337 raise "\nERROR: Invalid Metric Ordinal. "
338
339 def GetColor(self, id):
340 """ Returns colours of customizable options. """
341 if id == BP_BACKGROUND_COLOR:
342 return self._background_brush.GetColour()
343 elif id == BP_GRADIENT_COLOR_FROM:
344 return self._gradient_color_from
345 elif id == BP_GRADIENT_COLOR_TO:
346 return self._gradient_color_to
347 elif id == BP_BORDER_COLOR:
348 return self._border_pen.GetColour()
349 elif id == BP_TEXT_COLOR:
350 return self._caption_text_color
351 elif id == BP_BUTTONTEXT_COLOR:
352 return self._buttontext_color
353 elif id == BP_BUTTONTEXT_INACTIVE_COLOR:
354 return self._buttontext_inactive_color
355 elif id == BP_SELECTION_BRUSH_COLOR:
356 return self._selection_brush.GetColour()
357 elif id == BP_SELECTION_PEN_COLOR:
358 return self._selection_pen.GetColour()
359 elif id == BP_SEPARATOR_COLOR:
360 return self._separator_pen.GetColour()
361 else:
362 raise "\nERROR: Invalid Colour Ordinal. "
363
364 def SetColor(self, id, colour):
365 """ Sets colours for customizable options. """
366 if id == BP_BACKGROUND_COLOR:
367 self._background_brush.SetColour(colour)
368 elif id == BP_GRADIENT_COLOR_FROM:
369 self._gradient_color_from = colour
370 elif id == BP_GRADIENT_COLOR_TO:
371 self._gradient_color_to = colour
372 elif id == BP_BORDER_COLOR:
373 self._border_pen.SetColour(colour)
374 elif id == BP_TEXT_COLOR:
375 self._caption_text_color = colour
376 elif id == BP_BUTTONTEXT_COLOR:
377 self._buttontext_color = colour
378 elif id == BP_BUTTONTEXT_INACTIVE_COLOR:
379 self._buttontext_inactive_color = colour
380 elif id == BP_SELECTION_BRUSH_COLOR:
381 self._selection_brush.SetColour(colour)
382 elif id == BP_SELECTION_PEN_COLOR:
383 self._selection_pen.SetColour(colour)
384 elif id == BP_SEPARATOR_COLOR:
385 self._separator_pen.SetColour(colour)
386 else:
387 raise "\nERROR: Invalid Colour Ordinal. "
388 GetColour = GetColor
389 SetColour = SetColor
390
391 def SetFont(self, id, font):
392 """ Sets font for customizable options. """
393 if id == BP_TEXT_FONT:
394 self._caption_font = font
395 elif id == BP_BUTTONTEXT_FONT:
396 self._buttontext_font = font
397
398 def GetFont(self, id):
399 """ Returns font of customizable options. """
400 if id == BP_TEXT_FONT:
401 return self._caption_font
402 elif id == BP_BUTTONTEXT_FONT:
403 return self._buttontext_font
404 return wx.NoneFont
405
406 def SetGradientType(self, gradient):
407 """ Sets the gradient type for BPArt drawings. """
408 self._gradient_type = gradient
409
410 def GetGradientType(self):
411 """ Returns the gradient type for BPArt drawings. """
412 return self._gradient_type
413
414 def DrawSeparator(self, dc, rect, isVertical):
415 """ Draws a separator in ButtonPanel. """
416 dc.SetPen(self._separator_pen)
417 if isVertical:
418 ystart = yend = rect.y + rect.height/2
419 xstart = int(rect.x + 1.5*self._caption_border_size)
420 xend = int(rect.x + rect.width - 1.5*self._caption_border_size)
421 dc.DrawLine(xstart, ystart, xend, yend)
422 else:
423 xstart = xend = rect.x + rect.width/2
424 ystart = int(rect.y + 1.5*self._caption_border_size)
425 yend = int(rect.y + rect.height - 1.5*self._caption_border_size)
426 dc.DrawLine(xstart, ystart, xend, yend)
427
428 def DrawCaption(self, dc, rect, captionText):
429 """ Draws the main caption text in ButtonPanel. """
430 textColour = self._caption_text_color
431 textFont = self._caption_font
432 padding = self._padding_size
433 dc.SetTextForeground(textColour)
434 dc.SetFont(textFont)
435 dc.DrawText(captionText, rect.x + padding.x, rect.y+padding.y)
436
437 def DrawButton(self, dc, rect, parentSize, buttonBitmap, isVertical,
438 buttonStatus, isToggled, textAlignment, text=""):
439 """ Draws a button in ButtonPanel, together with its text (if any). """
440 bmpxsize, bmpysize = buttonBitmap.GetWidth(), buttonBitmap.GetHeight()
441 dx = dy = focus = 0
442 borderw = self._caption_border_size
443 padding = self._padding_size
444 buttonFont = self._buttontext_font
445 dc.SetFont(buttonFont)
446 if isVertical:
447 rect = wx.Rect(borderw, rect.y, rect.width-2*borderw, rect.height)
448 if text != "":
449 textW, textH = dc.GetTextExtent(text)
450 if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT:
451 fullExtent = bmpxsize + padding.x/2 + textW
452 bmpypos = rect.y + (rect.height - bmpysize)/2
453 bmpxpos = rect.x + (rect.width - fullExtent)/2
454 textxpos = bmpxpos + padding.x/2 + bmpxsize
455 textypos = bmpypos + (bmpysize - textH)/2
456 else:
457 bmpxpos = rect.x + (rect.width - bmpxsize)/2
458 bmpypos = rect.y + padding.y
459 textxpos = rect.x + (rect.width - textW)/2
460 textypos = bmpypos + bmpysize + padding.y/2
461 else:
462 bmpxpos = rect.x + (rect.width - bmpxsize)/2
463 bmpypos = rect.y + (rect.height - bmpysize)/2
464 else:
465 rect = wx.Rect(rect.x, borderw, rect.width, rect.height-2*borderw)
466 if text != "":
467 textW, textH = dc.GetTextExtent(text)
468 if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT:
469 fullExtent = bmpxsize + padding.x/2 + textW
470 bmpypos = rect.y + (rect.height - bmpysize)/2
471 bmpxpos = rect.x + (rect.width - fullExtent)/2
472 textxpos = bmpxpos + padding.x/2 + bmpxsize
473 textypos = bmpypos + (bmpysize - textH)/2
474 else:
475 fullExtent = bmpysize + padding.y/2 + textH
476 bmpxpos = rect.x + (rect.width - bmpxsize)/2
477 bmpypos = rect.y + (rect.height - fullExtent)/2
478 textxpos = rect.x + (rect.width - textW)/2
479 textypos = bmpypos + bmpysize + padding.y/2
480 else:
481 bmpxpos = rect.x + (rect.width - bmpxsize)/2
482 bmpypos = rect.y + (rect.height - bmpysize)/2
483
484 # Draw a button
485 # [ Padding | Text | .. Buttons .. | Padding ]
486
487 if buttonStatus in ["Pressed", "Toggled", "Hover"]:
488 dc.SetBrush(self._selection_brush)
489 dc.SetPen(self._selection_pen)
490 dc.DrawRoundedRectangleRect(rect, 4)
491 if buttonStatus == "Pressed" or isToggled:
492 dx = dy = 1
493 dc.DrawBitmap(buttonBitmap, bmpxpos+dx, bmpypos+dy, True)
494 if text != "":
495 isEnabled = buttonStatus != "Disabled"
496 self.DrawLabel(dc, text, isEnabled, textxpos+dx, textypos+dy)
497
498 def DrawLabel(self, dc, text, isEnabled, xpos, ypos):
499 """ Draws the label for a button. """
500 if not isEnabled:
501 dc.SetTextForeground(self._buttontext_inactive_color)
502 else:
503 dc.SetTextForeground(self._buttontext_color)
504 dc.DrawText(text, xpos, ypos)
505
506 def DrawButtonPanel(self, dc, rect, style):
507 """ Paint the ButtonPanel's background. """
508
509 if style & BP_USE_GRADIENT:
510 # Draw gradient color in the backgroud of the panel
511 self.FillGradientColor(dc, rect)
512 # Draw a rectangle around the panel
513 backBrush = (style & BP_USE_GRADIENT and [wx.TRANSPARENT_BRUSH] or \
514 [self._background_brush])[0]
515 dc.SetBrush(backBrush)
516 dc.SetPen(self._border_pen)
517 dc.DrawRectangleRect(rect)
518
519 def FillGradientColor(self, dc, rect):
520 """ Gradient fill from colour 1 to colour 2 with top to bottom or left to right. """
521 if rect.height < 1 or rect.width < 1:
522 return
523 isVertical = self._gradient_type == BP_GRADIENT_VERTICAL
524 size = (isVertical and [rect.height] or [rect.width])[0]
525 start = (isVertical and [rect.y] or [rect.x])[0]
526
527 # calculate gradient coefficients
528 col2 = self._gradient_color_from
529 col1 = self._gradient_color_to
530 rf, gf, bf = 0, 0, 0
531 rstep = float((col2.Red() - col1.Red()))/float(size)
532 gstep = float((col2.Green() - col1.Green()))/float(size)
533 bstep = float((col2.Blue() - col1.Blue()))/float(size)
534 for coord in xrange(start, start + size):
535 currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf)
536 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
537 dc.SetPen(wx.Pen(currCol))
538 if isVertical:
539 dc.DrawLine(rect.x, coord, rect.x + rect.width, coord)
540 else:
541 dc.DrawLine(coord, rect.y, coord, rect.y + rect.height)
542 rf += rstep
543 gf += gstep
544 bf += bstep
545
546 class StatusBarTimer(wx.Timer):
547 """Timer used for deleting StatusBar long help after _DELAY seconds."""
548
549 def __init__(self, owner):
550 """
551 Default class constructor.
552 For internal use: do not call it in your code!
553 """
554 wx.Timer.__init__(self)
555 self._owner = owner
556
557 def Notify(self):
558 """The timer has expired."""
559 self._owner.OnStatusBarTimer()
560
561 class Control(wx.EvtHandler):
562
563 def __init__(self, parent, size=wx.Size(-1, -1)):
564 """
565 Default class constructor.
566
567 Base class for all pseudo controls
568 parent = parent object
569 size = (width, height)
570 """
571 wx.EvtHandler.__init__(self)
572 self._parent = parent
573 self._id = wx.NewId()
574 self._size = size
575 self._isshown = True
576 self._focus = False
577
578 def Show(self, show=True):
579 """ Shows or hide the control. """
580 self._isshown = show
581
582 def Hide(self):
583 """ Hides the control. """
584 self.Show(False)
585
586 def IsShown(self):
587 """ Returns whether the control is shown or not. """
588 return self._isshown
589
590 def GetId(self):
591 """ Returns the control id. """
592 return self._id
593
594 def GetBestSize(self):
595 """ Returns the control best size. """
596 return self._size
597
598 def Disable(self):
599 """ Disables the control. """
600 self.Enable(False)
601
602 def Enable(self, value=True):
603 """ Enables or disables the control. """
604 self.disabled = not value
605
606 def SetFocus(self, focus=True):
607 """ Sets or kills the focus on the control. """
608 self._focus = focus
609
610 def HasFocus(self):
611 """ Returns whether the control has the focus or not. """
612 return self._focus
613
614 def OnMouseEvent(self, x, y, event):
615 pass
616
617 def Draw(self, rect):
618 pass
619
620 class Sizer(object):
621 """
622 Sizer
623
624 This is a mix-in class to add pseudo support to a wx sizer. Just create
625 a new class that derives from this class and the wx sizer and intercepts
626 any methods that add to the wx sizer.
627 """
628 def __init__(self):
629 self.children = [] # list of child Pseudo Controls
630 # Sizer doesn't use the x1,y1,x2,y2 so allow it to
631 # be called with or without the coordinates
632 def Draw(self, dc, x1=0, y1=0, x2=0, y2=0):
633 for item in self.children:
634 # use sizer coordinates rather than
635 # what is passed in
636 c = item.GetUserData()
637 c.Draw(dc, item.GetRect())
638
639 def GetBestSize(self):
640 # this should be handled by the wx.Sizer based class
641 return self.GetMinSize()
642
643 # Pseudo BoxSizer
644 class BoxSizer(Sizer, wx.BoxSizer):
645 def __init__(self, orient=wx.HORIZONTAL):
646 wx.BoxSizer.__init__(self, orient)
647 Sizer.__init__(self)
648
649 #-------------------------------------------
650 # sizer overrides (only called from Python)
651 #-------------------------------------------
652 # no support for user data if it's a pseudocontrol
653 # since that is already used
654 def Add(self, item, proportion=0, flag=0, border=0, userData=None):
655 # check to see if it's a pseudo object or sizer
656 if isinstance(item, Sizer):
657 szitem = wx.BoxSizer.Add(self, item, proportion, flag, border, item)
658 self.children.append(szitem)
659 elif isinstance(item, Control): # Control should be what ever class your controls come from
660 sz = item.GetBestSize()
661 # add a spacer to track this object
662 szitem = wx.BoxSizer.Add(self, sz, proportion, flag, border, item)
663 self.children.append(szitem)
664 else:
665 wx.BoxSizer.Add(self, item, proportion, flag, border, userData)
666
667 def Prepend(self, item, proportion=0, flag=0, border=0, userData=None):
668 # check to see if it's a pseudo object or sizer
669 if isinstance(item, Sizer):
670 szitem = wx.BoxSizer.Prepend(self, item, proportion, flag, border, item)
671 self.children.append(szitem)
672 elif isinstance(item, Control): # Control should be what ever class your controls come from
673 sz = item.GetBestSize()
674 # add a spacer to track this object
675 szitem = wx.BoxSizer.Prepend(self, sz, proportion, flag, border, item)
676 self.children.insert(0,szitem)
677 else:
678 wx.BoxSizer.Prepend(self, item, proportion, flag, border, userData)
679
680 def Insert(self, before, item, proportion=0, flag=0, border=0, userData=None, realIndex=None):
681 # check to see if it's a pseudo object or sizer
682 if isinstance(item, Sizer):
683 szitem = wx.BoxSizer.Insert(self, before, item, proportion, flag, border, item)
684 self.children.append(szitem)
685 elif isinstance(item, Control): # Control should be what ever class your controls come from
686 sz = item.GetBestSize()
687 # add a spacer to track this object
688 szitem = wx.BoxSizer.Insert(self, before, sz, proportion, flag, border, item)
689 if realIndex is not None:
690 self.children.insert(realIndex,szitem)
691 else:
692 self.children.insert(before,szitem)
693 else:
694 wx.BoxSizer.Insert(self, before, item, proportion, flag, border, userData)
695
696 def Remove(self, indx, pop=-1):
697 if pop >= 0:
698 self.children.pop(pop)
699 wx.BoxSizer.Remove(self, indx)
700
701 def Layout(self):
702 for ii, child in enumerate(self.GetChildren()):
703 item = child.GetUserData()
704 if item and child.IsShown():
705 self.SetItemMinSize(ii, *item.GetBestSize())
706 wx.BoxSizer.Layout(self)
707
708 def Show(self, item, show=True):
709 child = self.GetChildren()[item]
710 if child and child.GetUserData():
711 child.GetUserData().Show(show)
712 wx.BoxSizer.Show(self, item, show)
713
714 # ---------------------------------------------------------------------------- #
715 # Class Separator
716 # This class holds all the information to size and draw a separator inside
717 # ButtonPanel
718 # ---------------------------------------------------------------------------- #
719
720 class Separator(Control):
721
722 def __init__(self, parent):
723 """ Default class constructor. """
724 self._isshown = True
725 self._parent = parent
726 Control.__init__(self, parent)
727
728 def GetBestSize(self):
729 """ Returns the separator best size. """
730 # 10 is completely arbitrary, but it works anyhow
731 if self._parent.IsVertical():
732 return wx.Size(10, self._parent._art.GetMetric(BP_SEPARATOR_SIZE))
733 else:
734 return wx.Size(self._parent._art.GetMetric(BP_SEPARATOR_SIZE), 10)
735
736 def Draw(self, dc, rect):
737 """ Draws the separator. Actually the drawing is done in BPArt. """
738 if not self.IsShown():
739 return
740 isVertical = self._parent.IsVertical()
741 self._parent._art.DrawSeparator(dc, rect, isVertical)
742
743 # ---------------------------------------------------------------------------- #
744 # Class ButtonPanelText
745 # This class is used to hold data about the main caption in ButtonPanel
746 # ---------------------------------------------------------------------------- #
747
748 class ButtonPanelText(Control):
749
750 def __init__(self, parent, text=""):
751 """ Default class constructor. """
752 self._text = text
753 self._isshown = True
754 self._parent = parent
755 Control.__init__(self, parent)
756
757 def GetText(self):
758 """ Returns the caption text. """
759 return self._text
760
761 def SetText(self, text=""):
762 """ Sets the caption text. """
763 self._text = text
764
765 def CreateDC(self):
766 """ Convenience function to create a DC. """
767 dc = wx.ClientDC(self._parent)
768 textFont = self._parent._art.GetFont(BP_TEXT_FONT)
769 dc.SetFont(textFont)
770 return dc
771
772 def GetBestSize(self):
773 """ Returns the best size for the main caption in ButtonPanel. """
774 if self._text == "":
775 return wx.Size(0, 0)
776 dc = self.CreateDC()
777 rect = self._parent.GetClientRect()
778 tw, th = dc.GetTextExtent(self._text)
779 padding = self._parent._art.GetMetric(BP_PADDING_SIZE)
780 self._size = wx.Size(tw+2*padding.x, th+2*padding.y)
781 return self._size
782
783 def Draw(self, dc, rect):
784 """ Draws the main caption. Actually the drawing is done in BPArt. """
785 if not self.IsShown():
786 return
787 captionText = self.GetText()
788 self._parent._art.DrawCaption(dc, rect, captionText)
789
790 # -- ButtonInfo class implementation ----------------------------------------
791 # This class holds information about every button that is added to
792 # ButtonPanel. It is an auxiliary class that you should use
793 # every time you add a button.
794
795 class ButtonInfo(Control):
796
797 def __init__(self, parent, id=wx.ID_ANY, bmp=wx.NullBitmap,
798 status="Normal", text="", kind=wx.ITEM_NORMAL,
799 shortHelp="", longHelp=""):
800 """
801 Default class constructor.
802
803 Parameters:
804 - parent: the parent window (ButtonPanel);
805 - id: the button id;
806 - bmp: the associated bitmap;
807 - status: button status (pressed, hovered, normal).
808 - text: text to be displayed either below of to the right of the button
809 - kind: button kind, may be wx.ITEM_NORMAL for standard buttons or
810 wx.ITEM_CHECK for toggle buttons;
811 - shortHelp: a short help to be shown in the button tooltip;
812 - longHelp: this string is shown in the statusbar (if any) of the parent
813 frame when the mouse pointer is inside the button.
814 """
815
816 if id == wx.ID_ANY:
817 id = wx.NewId()
818 self._status = status
819 self._rect = wx.Rect()
820 self._text = text
821 self._kind = kind
822 self._toggle = False
823 self._textAlignment = BP_BUTTONTEXT_ALIGN_BOTTOM
824 self._shortHelp = shortHelp
825 self._longHelp = longHelp
826 self._menu = None
827 disabledbmp = GrayOut(bmp)
828 self._bitmaps = {"Normal": bmp, "Toggled": None, "Disabled": disabledbmp,
829 "Hover": None, "Pressed": None}
830 Control.__init__(self, parent)
831
832 def GetBestSize(self):
833 """ Returns the best size for the button. """
834 xsize = self.GetBitmap().GetWidth()
835 ysize = self.GetBitmap().GetHeight()
836 if self.HasText():
837 # We have text in the button
838 dc = wx.ClientDC(self._parent)
839 normalFont = self._parent._art.GetFont(BP_BUTTONTEXT_FONT)
840 dc.SetFont(normalFont)
841 tw, th = dc.GetTextExtent(self.GetText())
842 if self.GetTextAlignment() == BP_BUTTONTEXT_ALIGN_BOTTOM:
843 xsize = max(xsize, tw)
844 ysize = ysize + th
845 else:
846 xsize = xsize + tw
847 ysize = max(ysize, th)
848 border = self._parent._art.GetMetric(BP_BORDER_SIZE)
849 padding = self._parent._art.GetMetric(BP_PADDING_SIZE)
850 if self._parent.IsVertical():
851 xsize = xsize + 2*border
852 else:
853 ysize = ysize + 2*border
854 self._size = wx.Size(xsize+2*padding.x, ysize+2*padding.y)
855 return self._size
856
857 def Draw(self, dc, rect):
858 """ Draws the button on ButtonPanel. Actually the drawing is done in BPArt. """
859 if not self.IsShown():
860 return
861 buttonBitmap = self.GetBitmap()
862 isVertical = self._parent.IsVertical()
863 text = self.GetText()
864 parentSize = self._parent.GetSize()[not isVertical]
865 buttonStatus = self.GetStatus()
866 isToggled = self.GetToggled()
867 textAlignment = self.GetTextAlignment()
868 self._parent._art.DrawButton(dc, rect, parentSize, buttonBitmap, isVertical,
869 buttonStatus, isToggled, textAlignment, text)
870 self.SetRect(rect)
871
872 def CheckRefresh(self, status):
873 """ Checks whether a ButtonPanel repaint is needed or not. Convenience function. """
874 if status == self._status:
875 self._parent.RefreshRect(self.GetRect())
876
877 def SetBitmap(self, bmp, status="Normal"):
878 """ Sets the associated bitmap. """
879 self._bitmaps[status] = bmp
880 self.CheckRefresh(status)
881
882 def GetBitmap(self, status=None):
883 """ Returns the associated bitmap. """
884 if status is None:
885 status = self._status
886 if not self.IsEnabled():
887 status = "Disabled"
888 if self._bitmaps[status] is None:
889 return self._bitmaps["Normal"]
890 return self._bitmaps[status]
891
892 def GetRect(self):
893 """ Returns the button rect. """
894 return self._rect
895
896 def GetStatus(self):
897 """ Returns the button status. """
898 return self._status
899
900 def GetId(self):
901 """ Returns the button id. """
902 return self._id
903
904 def SetRect(self, rect):
905 """ Sets the button rect. """
906 self._rect = rect
907
908 def SetStatus(self, status):
909 """ Sets the button status. """
910 if status == self._status:
911 return
912 if self.GetToggled() and status == "Normal":
913 status = "Toggled"
914 self._status = status
915 self._parent.RefreshRect(self.GetRect())
916
917 def GetTextAlignment(self):
918 """ Returns the text alignment in the button (bottom or right). """
919 return self._textAlignment
920
921 def SetTextAlignment(self, alignment):
922 """ Sets the text alignment in the button (bottom or right). """
923 if alignment == self._textAlignment:
924 return
925 self._textAlignment = alignment
926
927 def GetToggled(self):
928 """ Returns whether a wx.ITEM_CHECK button is toggled or not. """
929 if self._kind == wx.ITEM_NORMAL:
930 return False
931 return self._toggle
932
933 def SetToggled(self, toggle=True):
934 """ Sets a wx.ITEM_CHECK button toggled/not toggled. """
935 if self._kind == wx.ITEM_NORMAL:
936 return
937 self._toggle = toggle
938 if toggle:
939 self.SetStatus("Toggled")
940 else:
941 self.SetStatus("Normal")
942
943 def SetId(self, id):
944 """ Sets the button id. """
945 self._id = id
946
947 def AddStatus(self, name="Custom", bmp=wx.NullBitmap):
948 """
949 Add a programmer-defined status in addition to the 5 default status:
950 - Normal;
951 - Disabled;
952 - Hover;
953 - Pressed;
954 - Toggled.
955 """
956 self._bitmaps.update({name: bmp})
957
958 def Enable(self, enable=True):
959 if enable:
960 self._status = "Normal"
961 else:
962 self._status = "Disabled"
963
964 def IsEnabled(self):
965 return self._status != "Disabled"
966
967 def SetText(self, text=""):
968 """ Sets the text of the button. """
969 self._text = text
970
971 def GetText(self):
972 """ Returns the text associated to the button. """
973 return self._text
974
975 def HasText(self):
976 """ Returns whether the button has text or not. """
977 return self._text != ""
978
979 def SetKind(self, kind=wx.ITEM_NORMAL):
980 """ Sets the button type (standard or toggle). """
981 self._kind = kind
982
983 def GetKind(self):
984 """ Returns the button type (standard or toggle). """
985 return self._kind
986
987 def SetShortHelp(self, help=""):
988 """ Sets the help string to be shown in a tootip. """
989 self._shortHelp = help
990
991 def GetShortHelp(self):
992 """ Returns the help string shown in a tootip. """
993 return self._shortHelp
994
995 def SetLongHelp(self, help=""):
996 """ Sets the help string to be shown in the statusbar. """
997 self._longHelp = help
998
999 def GetLongHelp(self):
1000 """ Returns the help string shown in the statusbar. """
1001 return self._longHelp
1002
1003 def GetMenu(self):
1004 return self._menu
1005
1006 def SetMenu(self, Menu):
1007 self._menu = Menu
1008 Bitmap = property(GetBitmap, SetBitmap)
1009 Id = property(GetId, SetId)
1010 Rect = property(GetRect, SetRect)
1011 Status = property(GetStatus, SetStatus)
1012
1013 # -- ButtonPanel class implementation ----------------------------------
1014 # This is the main class.
1015
1016 class ButtonPanel(wx.PyPanel):
1017
1018 def __init__(self, parent, id=wx.ID_ANY, text="", style=BP_DEFAULT_STYLE,
1019 alignment=BP_ALIGN_LEFT, name="buttonPanel"):
1020 """
1021 Default class constructor.
1022
1023 - parent: parent window
1024 - id: window ID
1025 - text: text to draw
1026 - style: window style
1027 - alignment: alignment of buttons (left or right)
1028 - name: window class name
1029 """
1030 wx.PyPanel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize,
1031 wx.NO_BORDER, name=name)
1032 self._vButtons = []
1033 self._vControls = []
1034 self._vSeparators = []
1035 self._nStyle = style
1036 self._alignment = alignment
1037 self._statusTimer = None
1038 self._useHelp = True
1039 self._freezeCount = 0
1040 self._currentButton = -1
1041 self._haveTip = False
1042 self._art = BPArt(style)
1043 self._controlCreated = False
1044 direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0]
1045 self._mainsizer = BoxSizer(direction)
1046 self.SetSizer(self._mainsizer)
1047 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1048 # First spacer to create some room before the first text/button/control
1049 self._mainsizer.Add((margins.x, margins.y), 0)
1050 # Last spacer to create some room before the last text/button/control
1051 self._mainsizer.Add((margins.x, margins.y), 0)
1052 self.Bind(wx.EVT_SIZE, self.OnSize)
1053 self.Bind(wx.EVT_PAINT, self.OnPaint)
1054 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1055 self.Bind(wx.EVT_MOTION, self.OnMouseMove)
1056 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
1057 self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
1058 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
1059 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave)
1060 self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnterWindow)
1061 self.SetBarText(text)
1062 self.LayoutItems()
1063
1064 def SetBarText(self, text):
1065 """ Sets the main caption text (leave text="" for no text). """
1066 self.Freeze()
1067 text = text.strip()
1068 if self._controlCreated:
1069 self.RemoveText()
1070 self._text = ButtonPanelText(self, text)
1071 lenChildren = len(self._mainsizer.GetChildren())
1072 if text == "":
1073 # Even if we have no text, we insert it an empty spacer anyway
1074 # it is easier to handle if you have to recreate the sizer after.
1075 if self.IsStandard():
1076 self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER,
1077 userData=self._text, realIndex=0)
1078 else:
1079 self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER,
1080 userData=self._text, realIndex=lenChildren)
1081 return
1082 # We have text, so insert the text and an expandable spacer
1083 # alongside it. "Standard" ButtonPanel are left or top aligned.
1084 if self.IsStandard():
1085 self._mainsizer.Insert(1, self._text, 0, wx.ALIGN_CENTER,
1086 userData=self._text, realIndex=0)
1087 self._mainsizer.Insert(2, (0, 0), 1, wx.EXPAND)
1088 else:
1089 self._mainsizer.Insert(lenChildren-1, self._text, 0, wx.ALIGN_CENTER,
1090 userData=self._text, realIndex=lenChildren)
1091 self._mainsizer.Insert(lenChildren-1, (0, 0), 1, wx.EXPAND)
1092
1093 def RemoveText(self):
1094 """ Removes the main caption text. """
1095 lenChildren = len(self._mainsizer.GetChildren())
1096 lenCustom = len(self._vButtons) + len(self._vSeparators) + 1
1097 if self.IsStandard():
1098 # Detach the text
1099 self._mainsizer.Remove(1, 0)
1100 if self.HasBarText():
1101 # Detach the expandable spacer
1102 self._mainsizer.Remove(1, -1)
1103 else:
1104 # Detach the text
1105 self._mainsizer.Remove(lenChildren-2, lenCustom-1)
1106 if self.HasBarText():
1107 # Detach the expandable spacer
1108 self._mainsizer.Remove(lenChildren-3, -1)
1109
1110 def GetBarText(self):
1111 """ Returns the main caption text. """
1112 return self._text.GetText()
1113
1114 def HasBarText(self):
1115 """ Returns whether ButtonPanel has a main caption text or not. """
1116 return hasattr(self, "_text") and self._text.GetText() != ""
1117
1118 def AddButton(self, btnInfo):
1119 """
1120 Adds a button to ButtonPanel. Remember to pass a ButtonInfo instance to
1121 this method. See the demo for details.
1122 """
1123 lenChildren = len(self._mainsizer.GetChildren())
1124 self._mainsizer.Insert(lenChildren-1, btnInfo, 0, wx.ALIGN_CENTER|wx.EXPAND, userData=btnInfo)
1125 self._vButtons.append(btnInfo)
1126
1127 def AddSpacer(self, size=(0, 0), proportion=1, flag=wx.EXPAND):
1128 """ Adds a spacer (stretchable or fixed-size) to ButtonPanel. """
1129 lenChildren = len(self._mainsizer.GetChildren())
1130 self._mainsizer.Insert(lenChildren-1, size, proportion, flag)
1131
1132 def AddControl(self, control, proportion=0, flag=wx.ALIGN_CENTER|wx.ALL, border=None):
1133 """ Adds a wxPython control to ButtonPanel. """
1134 lenChildren = len(self._mainsizer.GetChildren())
1135 if border is None:
1136 border = self._art.GetMetric(BP_PADDING_SIZE)
1137 border = max(border.x, border.y)
1138 self._mainsizer.Insert(lenChildren-1, control, proportion, flag, border)
1139 self._vControls.append(control)
1140
1141 def AddSeparator(self):
1142 """ Adds a separator line to ButtonPanel. """
1143 lenChildren = len(self._mainsizer.GetChildren())
1144 separator = Separator(self)
1145 self._mainsizer.Insert(lenChildren-1, separator, 0, wx.EXPAND)
1146 self._vSeparators.append(separator)
1147
1148 def DisableAll(self):
1149 """ Disable all buttons and controls """
1150 for btn in self._vButtons:
1151 btn.Enable(False)
1152 for ctrl in self._vControls:
1153 ctrl.Disable()
1154
1155 def EnableAll(self):
1156 """ Enable all buttons and controls """
1157 for btn in self._vButtons:
1158 btn.Enable(True)
1159 for ctrl in self._vControls:
1160 ctrl.Enable()
1161
1162 def RemoveAllButtons(self):
1163 """ Remove all the buttons from ButtonPanel. """
1164 self._vButtons = []
1165 self._vControls = []
1166
1167 def RemoveAllSeparators(self):
1168 """ Remove all the separators from ButtonPanel. """
1169 self._vSeparators = []
1170
1171 def GetAlignment(self):
1172 """ Returns the button alignment (left, right, top, bottom). """
1173 return self._alignment
1174
1175 def SetAlignment(self, alignment):
1176 """ Sets the button alignment (left, right, top, bottom). """
1177 if alignment == self._alignment:
1178 return
1179 self.Freeze()
1180 text = self.GetBarText()
1181 # Remove the text in any case
1182 self.RemoveText()
1183 # Remove the first and last spacers
1184 self._mainsizer.Remove(0, -1)
1185 self._mainsizer.Remove(len(self._mainsizer.GetChildren())-1, -1)
1186 self._alignment = alignment
1187 # Recreate the sizer accordingly to the new alignment
1188 self.ReCreateSizer(text)
1189
1190 def IsVertical(self):
1191 """ Returns whether ButtonPanel is vertically aligned or not. """
1192 return self._alignment not in [BP_ALIGN_RIGHT, BP_ALIGN_LEFT]
1193
1194 def IsStandard(self):
1195 """ Returns whether ButtonPanel is aligned "Standard" (left/top) or not. """
1196 return self._alignment in [BP_ALIGN_LEFT, BP_ALIGN_TOP]
1197
1198 def DoLayout(self):
1199 """
1200 Do the Layout for ButtonPanel.
1201 NB: Call this method every time you make a modification to the layout
1202 or to the customizable sizes of the pseudo controls.
1203 """
1204 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1205 lenChildren = len(self._mainsizer.GetChildren())
1206 self._mainsizer.SetItemMinSize(0, (margins.x, margins.y))
1207 self._mainsizer.SetItemMinSize(lenChildren-1, (margins.x, margins.y))
1208 self._controlCreated = True
1209 self.LayoutItems()
1210
1211 # *VERY* WEIRD: the sizer seems not to respond to any layout until I
1212 # change the ButtonPanel size and restore it back
1213 size = self.GetSize()
1214 self.SetSize((size.x+1, size.y+1))
1215 self.SetSize((size.x, size.y))
1216 if self.IsFrozen():
1217 self.Thaw()
1218
1219 def ReCreateSizer(self, text):
1220 """ Recreates the ButtonPanel sizer accordingly to the alignment specified. """
1221 children = self._mainsizer.GetChildren()
1222 self.RemoveAllButtons()
1223 self.RemoveAllSeparators()
1224 # Create a new sizer depending on the alignment chosen
1225 direction = (self.IsVertical() and [wx.VERTICAL] or [wx.HORIZONTAL])[0]
1226 self._mainsizer = BoxSizer(direction)
1227 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1228 # First spacer to create some room before the first text/button/control
1229 self._mainsizer.Add((margins.x, margins.y), 0)
1230 # Last spacer to create some room before the last text/button/control
1231 self._mainsizer.Add((margins.x, margins.y), 0)
1232 # This is needed otherwise SetBarText goes mad
1233 self._controlCreated = False
1234 for child in children:
1235 userData = child.GetUserData()
1236 if userData:
1237 if isinstance(userData, ButtonInfo):
1238 # It is a ButtonInfo, can't be anything else
1239 self.AddButton(child.GetUserData())
1240 elif isinstance(userData, Separator):
1241 self.AddSeparator()
1242 else:
1243 if child.IsSpacer():
1244 # This is a spacer, expandable or not
1245 self.AddSpacer(child.GetSize(), child.GetProportion(),
1246 child.GetFlag())
1247 else:
1248 # This is a wxPython control
1249 self.AddControl(child.GetWindow(), child.GetProportion(),
1250 child.GetFlag(), child.GetBorder())
1251 self.SetSizer(self._mainsizer)
1252 # Now add the text. It doesn't matter if there is no text
1253 self.SetBarText(text)
1254 self.DoLayout()
1255 self.Thaw()
1256
1257 def DoGetBestSize(self):
1258 """ Returns the best size of ButtonPanel. """
1259 w = h = btnWidth = btnHeight = 0
1260 isVertical = self.IsVertical()
1261 padding = self._art.GetMetric(BP_PADDING_SIZE)
1262 border = self._art.GetMetric(BP_BORDER_SIZE)
1263 margins = self._art.GetMetric(BP_MARGINS_SIZE)
1264 separator_size = self._art.GetMetric(BP_SEPARATOR_SIZE)
1265 # Add the space required for the main caption
1266 if self.HasBarText():
1267 w, h = self._text.GetBestSize()
1268 if isVertical:
1269 h += padding.y
1270 else:
1271 w += padding.x
1272 else:
1273 w = h = border
1274 # Add the button's sizes
1275 for btn in self._vButtons:
1276 bw, bh = btn.GetBestSize()
1277 btnWidth = max(btnWidth, bw)
1278 btnHeight = max(btnHeight, bh)
1279 if isVertical:
1280 w = max(w, btnWidth)
1281 h += bh
1282 else:
1283 h = max(h, btnHeight)
1284 w += bw
1285
1286 # Add the control's sizes
1287 for control in self.GetControls():
1288 cw, ch = control.GetSize()
1289 if isVertical:
1290 h += ch
1291 w = max(w, cw)
1292 else:
1293 w += cw
1294 h = max(h, ch)
1295 # Add the separator's sizes and the 2 SizerItems at the beginning
1296 # and at the end
1297 if self.IsVertical():
1298 h += 2*margins.y + len(self._vSeparators)*separator_size
1299 else:
1300 w += 2*margins.x + len(self._vSeparators)*separator_size
1301 return wx.Size(w, h)
1302
1303 def OnPaint(self, event):
1304 """ Handles the wx.EVT_PAINT event for ButtonPanel. """
1305 dc = wx.BufferedPaintDC(self)
1306 rect = self.GetClientRect()
1307 self._art.DrawButtonPanel(dc, rect, self._nStyle)
1308 self._mainsizer.Draw(dc)
1309
1310 def OnEraseBackground(self, event):
1311 """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """
1312 pass
1313
1314 def OnSize(self, event):
1315 """ Handles the wx.EVT_SIZE event for ButtonPanel. """
1316 # NOTE: It seems like LayoutItems number of calls can be optimized in some way.
1317 # Currently every DoLayout (or every parent Layout()) calls about 3 times
1318 # the LayoutItems method. Any idea on how to improve it?
1319 self.LayoutItems()
1320 self.Refresh()
1321 event.Skip()
1322
1323 def LayoutItems(self):
1324 """
1325 Layout the items using a different algorithm depending on the existance
1326 of the main caption.
1327 """
1328 nonspacers, allchildren = self.GetNonFlexibleChildren()
1329 if self.HasBarText():
1330 self.FlexibleLayout(nonspacers, allchildren)
1331 else:
1332 self.SizeLayout(nonspacers, allchildren)
1333 self._mainsizer.Layout()
1334
1335 def SizeLayout(self, nonspacers, children):
1336 """ Layout the items when no main caption exists. """
1337 size = self.GetSize()
1338 isVertical = self.IsVertical()
1339 corner = 0
1340 indx1 = len(nonspacers)
1341 for item in nonspacers:
1342 corner += self.GetItemSize(item, isVertical)
1343 if corner > size[isVertical]:
1344 indx1 = nonspacers.index(item)
1345 break
1346 # Leave out the last spacer, it has to be there always
1347 for ii in xrange(len(nonspacers)-1):
1348 indx = children.index(nonspacers[ii])
1349 self._mainsizer.Show(indx, ii < indx1)
1350
1351 def GetItemSize(self, item, isVertical):
1352 """ Returns the size of an item in the main ButtonPanel sizer. """
1353 if item.GetUserData():
1354 return item.GetUserData().GetBestSize()[isVertical]
1355 else:
1356 return item.GetSize()[isVertical]
1357
1358 def FlexibleLayout(self, nonspacers, allchildren):
1359 """ Layout the items when the main caption exists. """
1360 if len(nonspacers) < 2:
1361 return
1362 isVertical = self.IsVertical()
1363 isStandard = self.IsStandard()
1364 size = self.GetSize()[isVertical]
1365 padding = self._art.GetMetric(BP_PADDING_SIZE)
1366 fixed = (isStandard and [nonspacers[1]] or [nonspacers[-2]])[0]
1367 if isStandard:
1368 nonspacers.reverse()
1369 leftendx = fixed.GetSize()[isVertical] + padding.x
1370 else:
1371 rightstartx = size - fixed.GetSize()[isVertical]
1372 size = 0
1373 count = lennonspacers = len(nonspacers)
1374 for item in nonspacers:
1375 if isStandard:
1376 size -= self.GetItemSize(item, isVertical)
1377 if size < leftendx:
1378 break
1379 else:
1380 size += self.GetItemSize(item, isVertical)
1381 if size > rightstartx:
1382 break
1383 count = count - 1
1384 nonspacers.reverse()
1385 for jj in xrange(2, lennonspacers):
1386 indx = allchildren.index(nonspacers[jj])
1387 self._mainsizer.Show(indx, jj >= count)
1388
1389 def GetNonFlexibleChildren(self):
1390 """
1391 Returns all the ButtonPanel main sizer's children that are not
1392 flexible spacers.
1393 """
1394 children1 = []
1395 children2 = self._mainsizer.GetChildren()
1396 for child in children2:
1397 if child.IsSpacer():
1398 if child.GetUserData() or child.GetProportion() == 0:
1399 children1.append(child)
1400 else:
1401 children1.append(child)
1402 return children1, children2
1403
1404 def GetControls(self):
1405 """ Returns the wxPython controls that belongs to ButtonPanel. """
1406 children2 = self._mainsizer.GetChildren()
1407 children1 = [child for child in children2 if not child.IsSpacer()]
1408 return children1
1409
1410 def SetStyle(self, style):
1411 """ Sets ButtonPanel style. """
1412 if style == self._nStyle:
1413 return
1414 self._nStyle = style
1415 self.Refresh()
1416
1417 def GetStyle(self):
1418 """ Returns the ButtonPanel style. """
1419 return self._nStyle
1420
1421 def OnMouseMove(self, event):
1422 """ Handles the wx.EVT_MOTION event for ButtonPanel. """
1423 # Check to see if we are hovering a button
1424 tabId, flags = self.HitTest(event.GetPosition())
1425 if flags != BP_HT_BUTTON:
1426 self.RemoveHelp()
1427 self.RepaintOldSelection()
1428 self._currentButton = -1
1429 return
1430 btn = self._vButtons[tabId]
1431 if not btn.IsEnabled():
1432 self.RemoveHelp()
1433 self.RepaintOldSelection()
1434 return
1435 if tabId != self._currentButton:
1436 self.RepaintOldSelection()
1437 if btn.GetRect().Contains(event.GetPosition()):
1438 btn.SetStatus("Hover")
1439 else:
1440 btn.SetStatus("Normal")
1441 if tabId != self._currentButton:
1442 self.RemoveHelp()
1443 self.DoGiveHelp(btn)
1444 self._currentButton = tabId
1445 event.Skip()
1446
1447 def OnLeftDown(self, event):
1448 """ Handles the wx.EVT_LEFT_DOWN event for ButtonPanel. """
1449 tabId, hit = self.HitTest(event.GetPosition())
1450 if hit == BP_HT_BUTTON:
1451 btn = self._vButtons[tabId]
1452 if btn.IsEnabled():
1453 btn.SetStatus("Pressed")
1454 self._currentButton = tabId
1455
1456 def OnRightDown(self, event):
1457 """ Handles the wx.EVT_RIGHT_DOWN event for ButtonPanel. """
1458 tabId, hit = self.HitTest(event.GetPosition())
1459 if hit == BP_HT_BUTTON:
1460 btn = self._vButtons[tabId]
1461 if btn.IsEnabled() and btn.GetMenu() != None:
1462 self.PopupMenu(btn.GetMenu())
1463
1464 def OnLeftUp(self, event):
1465 """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """
1466 tabId, flags = self.HitTest(event.GetPosition())
1467 if flags != BP_HT_BUTTON:
1468 return
1469 hit = self._vButtons[tabId]
1470 if hit.GetStatus() == "Disabled":
1471 return
1472 for btn in self._vButtons:
1473 if btn != hit:
1474 btn.SetFocus(False)
1475 if hit.GetStatus() == "Pressed":
1476 # Fire a button click event
1477 btnEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, hit.GetId())
1478 self.GetEventHandler().ProcessEvent(btnEvent)
1479 hit.SetToggled(not hit.GetToggled())
1480 # Update the button status to be hovered
1481 hit.SetStatus("Hover")
1482 hit.SetFocus()
1483 self._currentButton = tabId
1484
1485 def OnMouseLeave(self, event):
1486 """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """
1487 # Reset all buttons statuses
1488 for btn in self._vButtons:
1489 if not btn.IsEnabled():
1490 continue
1491 btn.SetStatus("Normal")
1492 self.RemoveHelp()
1493 event.Skip()
1494
1495 def OnMouseEnterWindow(self, event):
1496 """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """
1497 tabId, flags = self.HitTest(event.GetPosition())
1498 if flags == BP_HT_BUTTON:
1499 hit = self._vButtons[tabId]
1500 if hit.GetStatus() == "Disabled":
1501 event.Skip()
1502 return
1503 self.DoGiveHelp(hit)
1504 self._currentButton = tabId
1505 event.Skip()
1506
1507 def DoGiveHelp(self, hit):
1508 """ Gives tooltips and help in StatusBar. """
1509 if not self.GetUseHelp():
1510 return
1511 shortHelp = hit.GetShortHelp()
1512 if shortHelp:
1513 self.SetToolTipString(shortHelp)
1514 self._haveTip = True
1515 longHelp = hit.GetLongHelp()
1516 if not longHelp:
1517 return
1518 topLevel = wx.GetTopLevelParent(self)
1519 if isinstance(topLevel, wx.Frame) and topLevel.GetStatusBar():
1520 statusBar = topLevel.GetStatusBar()
1521 if self._statusTimer and self._statusTimer.IsRunning():
1522 self._statusTimer.Stop()
1523 statusBar.PopStatusText(0)
1524 statusBar.PushStatusText(longHelp, 0)
1525 self._statusTimer = StatusBarTimer(self)
1526 self._statusTimer.Start(_DELAY, wx.TIMER_ONE_SHOT)
1527
1528 def RemoveHelp(self):
1529 """ Removes the tooltips and statusbar help (if any) for a button. """
1530 if not self.GetUseHelp():
1531 return
1532 if self._haveTip:
1533 self.SetToolTipString("")
1534 self._haveTip = False
1535 if self._statusTimer and self._statusTimer.IsRunning():
1536 topLevel = wx.GetTopLevelParent(self)
1537 statusBar = topLevel.GetStatusBar()
1538 self._statusTimer.Stop()
1539 statusBar.PopStatusText(0)
1540 self._statusTimer = None
1541
1542 def RepaintOldSelection(self):
1543 """ Repaints the old selected/hovered button. """
1544 current = self._currentButton
1545 if current == -1:
1546 return
1547 btn = self._vButtons[current]
1548 if not btn.IsEnabled():
1549 return
1550 btn.SetStatus("Normal")
1551
1552 def OnStatusBarTimer(self):
1553 """ Handles the timer expiring to delete the longHelp in the StatusBar. """
1554 topLevel = wx.GetTopLevelParent(self)
1555 statusBar = topLevel.GetStatusBar()
1556 statusBar.PopStatusText(0)
1557
1558 def SetUseHelp(self, useHelp=True):
1559 """ Sets whether or not shortHelp and longHelp should be displayed. """
1560 self._useHelp = useHelp
1561
1562 def GetUseHelp(self):
1563 """ Returns whether or not shortHelp and longHelp should be displayed. """
1564 return self._useHelp
1565
1566 def HitTest(self, pt):
1567 """
1568 HitTest method for ButtonPanel. Returns the button (if any) and
1569 a flag (if any).
1570 """
1571 for ii in xrange(len(self._vButtons)):
1572 if not self._vButtons[ii].IsEnabled():
1573 continue
1574 if self._vButtons[ii].GetRect().Contains(pt):
1575 return ii, BP_HT_BUTTON
1576 return -1, BP_HT_NONE
1577
1578 def GetBPArt(self):
1579 """ Returns the associated BPArt art provider. """
1580 return self._art
1581
1582 def SetBPArt(self, art):
1583 """ Sets a new BPArt to ButtonPanel. Useful only if another BPArt class is used. """
1584 self._art = art
1585 self.Refresh()
1586
1587 if wx.VERSION < (2,7,1,1):
1588 def Freeze(self):
1589 """Freeze ButtonPanel."""
1590 self._freezeCount = self._freezeCount + 1
1591 wx.PyPanel.Freeze(self)
1592
1593 def Thaw(self):
1594 """Thaw ButtonPanel."""
1595 if self._freezeCount == 0:
1596 raise "\nERROR: Thawing Unfrozen ButtonPanel?"
1597 self._freezeCount = self._freezeCount - 1
1598 wx.PyPanel.Thaw(self)
1599
1600 def IsFrozen(self):
1601 """ Returns whether a call to Freeze() has been done. """
1602 return self._freezeCount != 0