]>
Commit | Line | Data |
---|---|---|
1 | # --------------------------------------------------------------------------- # | |
2 | # FANCYBUTTONPANEL Widget wxPython IMPLEMENTATION | |
3 | # | |
4 | # Original C++ Code From Eran. You Can Find It At: | |
5 | # | |
6 | # http://wxforum.shadonet.com/viewtopic.php?t=6619 | |
7 | # | |
8 | # License: wxWidgets license | |
9 | # | |
10 | # | |
11 | # Python Code By: | |
12 | # | |
13 | # Andrea Gavana, @ 02 Oct 2006 | |
14 | # Latest Revision: 17 Oct 2006, 17.00 GMT | |
15 | # | |
16 | # | |
17 | # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please | |
18 | # Write To Me At: | |
19 | # | |
20 | # andrea.gavana@gmail.com | |
21 | # gavana@kpo.kz | |
22 | # | |
23 | # Or, Obviously, To The wxPython Mailing List!!! | |
24 | # | |
25 | # | |
26 | # End Of Comments | |
27 | # --------------------------------------------------------------------------- # | |
28 | ||
29 | """ | |
30 | With `ButtonPanel` class you have a panel with gradient coloring | |
31 | on it and with the possibility to place some buttons on it. Using a | |
32 | standard panel with normal wx.Buttons leads to an ugly result: the | |
33 | buttons are placed correctly on the panel - but with grey area around | |
34 | them. Gradient coloring is kept behind the images - this was achieved | |
35 | due to the PNG format and the transparency of the bitmaps. | |
36 | ||
37 | The image are functioning like a buttons and can be caught in your | |
38 | code using the usual self.Bind(wx.EVT_BUTTON, self.OnButton) method. | |
39 | ||
40 | The control is generic, and support theming (well, I tested it under | |
41 | Windows with the three defauls themes: grey, blue, silver and the | |
42 | classic look). | |
43 | ||
44 | ||
45 | Usage | |
46 | ----- | |
47 | ||
48 | ButtonPanel supports 4 alignments: left, right, top, bottom, which have a | |
49 | different meaning and behavior wrt wx.Toolbar. The easiest thing is to try | |
50 | the demo to understand, but I'll try to explain how it works. | |
51 | ||
52 | CASE 1: ButtonPanel has a main caption text | |
53 | ||
54 | Left alignment means ButtonPanel is horizontal, with the text aligned to the | |
55 | left. When you shrink the demo frame, if there is not enough room for all | |
56 | the controls to be shown, the controls closest to the text are hidden; | |
57 | ||
58 | Right alignment means ButtonPanel is horizontal, with the text aligned to the | |
59 | right. Item layout as above; | |
60 | ||
61 | Top alignment means ButtonPanel is vertical, with the text aligned to the top. | |
62 | Item layout as above; | |
63 | ||
64 | Bottom alignment means ButtonPanel is vertical, with the text aligned to the | |
65 | bottom. Item layout as above. | |
66 | ||
67 | ||
68 | CASE 2: ButtonPanel has *no* main caption text | |
69 | In this case, left and right alignment are the same (as top and bottom are the same), | |
70 | but the layout strategy changes: now if there is not enough room for all the controls | |
71 | to be shown, the last added items are hidden ("last" means on the far right for | |
72 | horizontal ButtonPanels and far bottom for vertical ButtonPanels). | |
73 | ||
74 | ||
75 | The following example shows a simple implementation that uses ButtonPanel | |
76 | inside a very simple frame:: | |
77 | ||
78 | class MyFrame(wx.Frame): | |
79 | ||
80 | def __init__(self, parent, id=-1, title="ButtonPanel", pos=wx.DefaultPosition, | |
81 | size=(800, 600), style=wx.DEFAULT_FRAME_STYLE): | |
82 | ||
83 | wx.Frame.__init__(self, parent, id, title, pos, size, style) | |
84 | ||
85 | mainPanel = wx.Panel(self, -1) | |
86 | self.logtext = wx.TextCtrl(mainPanel, -1, "", style=wx.TE_MULTILINE) | |
87 | ||
88 | vSizer = wx.BoxSizer(wx.VERTICAL) | |
89 | mainPanel.SetSizer(vSizer) | |
90 | ||
91 | alignment = BP_ALIGN_RIGHT | |
92 | ||
93 | titleBar = ButtonPanel(mainPanel, -1, "A Simple Test & Demo") | |
94 | ||
95 | btn1 = ButtonInfo(wx.NewId(), wx.Bitmap("png4.png", wx.BITMAP_TYPE_PNG)) | |
96 | titleBar.AddButton(btn1) | |
97 | self.Bind(wx.EVT_BUTTON, self.OnButton, btn1) | |
98 | ||
99 | btn2 = ButtonInfo(wx.NewId(), wx.Bitmap("png3.png", wx.BITMAP_TYPE_PNG)) | |
100 | titleBar.AddButton(btn2) | |
101 | self.Bind(wx.EVT_BUTTON, self.OnButton, btn2) | |
102 | ||
103 | btn3 = ButtonInfo(wx.NewId(), wx.Bitmap("png2.png", wx.BITMAP_TYPE_PNG)) | |
104 | titleBar.AddButton(btn3) | |
105 | self.Bind(wx.EVT_BUTTON, self.OnButton, btn3) | |
106 | ||
107 | btn4 = ButtonInfo(wx.NewId(), wx.Bitmap("png1.png", wx.BITMAP_TYPE_PNG)) | |
108 | titleBar.AddButton(btn4) | |
109 | self.Bind(wx.EVT_BUTTON, self.OnButton, btn4) | |
110 | ||
111 | vSizer.Add(titleBar, 0, wx.EXPAND) | |
112 | vSizer.Add((20, 20)) | |
113 | vSizer.Add(self.logtext, 1, wx.EXPAND|wx.ALL, 5) | |
114 | ||
115 | vSizer.Layout() | |
116 | ||
117 | # our normal wxApp-derived class, as usual | |
118 | ||
119 | app = wx.PySimpleApp() | |
120 | ||
121 | frame = MyFrame(None) | |
122 | app.SetTopWindow(frame) | |
123 | frame.Show() | |
124 | ||
125 | app.MainLoop() | |
126 | ||
127 | ||
128 | License And Version: | |
129 | ||
130 | ButtonPanel Is Freeware And Distributed Under The wxPython License. | |
131 | ||
132 | Latest Revision: Andrea Gavana @ 12 Oct 2006, 17.00 GMT | |
133 | Version 0.3. | |
134 | ||
135 | """ | |
136 | ||
137 | ||
138 | import wx | |
139 | ||
140 | # Some constants to tune the BPArt class | |
141 | BP_BACKGROUND_COLOR = 0 | |
142 | """ Background brush colour when no gradient shading exists. """ | |
143 | BP_GRADIENT_COLOR_FROM = 1 | |
144 | """ Starting gradient colour, used only when BP_USE_GRADIENT style is applied. """ | |
145 | BP_GRADIENT_COLOR_TO = 2 | |
146 | """ Ending gradient colour, used only when BP_USE_GRADIENT style is applied. """ | |
147 | BP_BORDER_COLOR = 3 | |
148 | """ Pen colour to paint the border of ButtonPanel. """ | |
149 | BP_TEXT_COLOR = 4 | |
150 | """ Main ButtonPanel caption colour. """ | |
151 | BP_BUTTONTEXT_COLOR = 5 | |
152 | """ Text colour for buttons with text. """ | |
153 | BP_BUTTONTEXT_INACTIVE_COLOR = 6 | |
154 | """ Text colour for inactive buttons with text. """ | |
155 | BP_SELECTION_BRUSH_COLOR = 7 | |
156 | """ Brush colour to be used when hovering or selecting a button. """ | |
157 | BP_SELECTION_PEN_COLOR = 8 | |
158 | """ Pen colour to be used when hovering or selecting a button. """ | |
159 | BP_SEPARATOR_COLOR = 9 | |
160 | """ Pen colour used to paint the separators. """ | |
161 | BP_TEXT_FONT = 10 | |
162 | """ Font of the ButtonPanel main caption. """ | |
163 | BP_BUTTONTEXT_FONT = 11 | |
164 | """ Text font for the buttons with text. """ | |
165 | ||
166 | BP_BUTTONTEXT_ALIGN_BOTTOM = 12 | |
167 | """ Flag that indicates the image and text in buttons is stacked. """ | |
168 | BP_BUTTONTEXT_ALIGN_RIGHT = 13 | |
169 | """ Flag that indicates the text is shown alongside the image in buttons with text. """ | |
170 | ||
171 | BP_SEPARATOR_SIZE = 14 | |
172 | """ | |
173 | Separator size. NB: This is not the line width, but the sum of the space before | |
174 | and after the separator line plus the width of the line. | |
175 | """ | |
176 | BP_MARGINS_SIZE = 15 | |
177 | """ | |
178 | Size of the left/right margins in ButtonPanel (top/bottom for vertically | |
179 | aligned ButtonPanels). | |
180 | """ | |
181 | BP_BORDER_SIZE = 16 | |
182 | """ Size of the border. """ | |
183 | BP_PADDING_SIZE = 17 | |
184 | """ Inter-tool separator size. """ | |
185 | ||
186 | # Caption Gradient Type | |
187 | BP_GRADIENT_NONE = 0 | |
188 | """ No gradient shading should be used to paint the background. """ | |
189 | BP_GRADIENT_VERTICAL = 1 | |
190 | """ Vertical gradient shading should be used to paint the background. """ | |
191 | BP_GRADIENT_HORIZONTAL = 2 | |
192 | """ Horizontal gradient shading should be used to paint the background. """ | |
193 | ||
194 | # Flags for HitTest() method | |
195 | BP_HT_BUTTON = 200 | |
196 | BP_HT_NONE = 201 | |
197 | ||
198 | # Alignment of buttons in the panel | |
199 | BP_ALIGN_RIGHT = 1 | |
200 | BP_ALIGN_LEFT = 2 | |
201 | BP_ALIGN_TOP = 4 | |
202 | BP_ALIGN_BOTTOM = 8 | |
203 | ||
204 | # ButtonPanel styles | |
205 | BP_DEFAULT_STYLE = 1 | |
206 | BP_USE_GRADIENT = 2 | |
207 | ||
208 | # Delay used to cancel the longHelp in the statusbar field | |
209 | _DELAY = 3000 | |
210 | ||
211 | ||
212 | # Check for the new method in 2.7 (not present in 2.6.3.3) | |
213 | if wx.VERSION_STRING < "2.7": | |
214 | wx.Rect.Contains = lambda self, point: wx.Rect.Inside(self, point) | |
215 | ||
216 | ||
217 | def BrightenColour(color, factor): | |
218 | """ Bright the input colour by a factor.""" | |
219 | ||
220 | val = color.Red()*factor | |
221 | if val > 255: | |
222 | red = 255 | |
223 | else: | |
224 | red = val | |
225 | ||
226 | val = color.Green()*factor | |
227 | if val > 255: | |
228 | green = 255 | |
229 | else: | |
230 | green = val | |
231 | ||
232 | val = color.Blue()*factor | |
233 | if val > 255: | |
234 | blue = 255 | |
235 | else: | |
236 | blue = val | |
237 | ||
238 | return wx.Color(red, green, blue) | |
239 | ||
240 | ||
241 | def GrayOut(anImage): | |
242 | """ | |
243 | Convert the given image (in place) to a grayed-out version, | |
244 | appropriate for a 'Disabled' appearance. | |
245 | """ | |
246 | ||
247 | factor = 0.7 # 0 < f < 1. Higher Is Grayer | |
248 | ||
249 | anImage = anImage.ConvertToImage() | |
250 | if anImage.HasAlpha(): | |
251 | anImage.ConvertAlphaToMask(1) | |
252 | ||
253 | if anImage.HasMask(): | |
254 | maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue()) | |
255 | else: | |
256 | maskColor = None | |
257 | ||
258 | data = map(ord, list(anImage.GetData())) | |
259 | ||
260 | for i in range(0, len(data), 3): | |
261 | ||
262 | pixel = (data[i], data[i+1], data[i+2]) | |
263 | pixel = MakeGray(pixel, factor, maskColor) | |
264 | ||
265 | for x in range(3): | |
266 | data[i+x] = pixel[x] | |
267 | ||
268 | anImage.SetData(''.join(map(chr, data))) | |
269 | ||
270 | anImage = anImage.ConvertToBitmap() | |
271 | ||
272 | return anImage | |
273 | ||
274 | ||
275 | def MakeGray((r,g,b), factor, maskColor): | |
276 | """ | |
277 | Make a pixel grayed-out. If the pixel matches the maskColor, it won't be | |
278 | changed. | |
279 | """ | |
280 | ||
281 | if (r,g,b) != maskColor: | |
282 | return map(lambda x: int((230 - x) * factor) + x, (r,g,b)) | |
283 | else: | |
284 | return (r,g,b) | |
285 | ||
286 | ||
287 | # ---------------------------------------------------------------------------- # | |
288 | # Class BPArt | |
289 | # Handles all the drawings for buttons, separators and text and allows the | |
290 | # programmer to set colours, sizes and gradient shadings for ButtonPanel | |
291 | # ---------------------------------------------------------------------------- # | |
292 | ||
293 | class BPArt: | |
294 | """ | |
295 | BPArt is an art provider class which does all of the drawing for ButtonPanel. | |
296 | This allows the library caller to customize the BPArt or to completely replace | |
297 | all drawing with custom BPArts. | |
298 | """ | |
299 | ||
300 | def __init__(self, parentStyle): | |
301 | """ Default class constructor. """ | |
302 | ||
303 | base_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE) | |
304 | ||
305 | self._background_brush = wx.Brush(base_color, wx.SOLID) | |
306 | self._gradient_color_to = wx.WHITE | |
307 | self._gradient_color_from = wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION) | |
308 | ||
309 | if parentStyle & BP_USE_GRADIENT: | |
310 | self._border_pen = wx.Pen(wx.WHITE, 3) | |
311 | self._caption_text_color = wx.WHITE | |
312 | self._buttontext_color = wx.Colour(70, 143, 255) | |
313 | self._separator_pen = wx.Pen(BrightenColour(self._gradient_color_from, 1.4)) | |
314 | self._gradient_type = BP_GRADIENT_VERTICAL | |
315 | else: | |
316 | self._border_pen = wx.Pen(BrightenColour(base_color, 0.9), 3) | |
317 | self._caption_text_color = wx.BLACK | |
318 | self._buttontext_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNTEXT) | |
319 | self._separator_pen = wx.Pen(BrightenColour(base_color, 0.9)) | |
320 | self._gradient_type = BP_GRADIENT_NONE | |
321 | ||
322 | self._buttontext_inactive_color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_GRAYTEXT) | |
323 | self._selection_brush = wx.Brush(wx.Color(225, 225, 255)) | |
324 | self._selection_pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION)) | |
325 | ||
326 | sysfont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) | |
327 | self._caption_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.BOLD, | |
328 | False, sysfont.GetFaceName()) | |
329 | self._buttontext_font = wx.Font(sysfont.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.NORMAL, | |
330 | False, sysfont.GetFaceName()) | |
331 | ||
332 | self._separator_size = 7 | |
333 | self._margins_size = wx.Size(6, 6) | |
334 | self._caption_border_size = 3 | |
335 | self._padding_size = wx.Size(6, 6) | |
336 | ||
337 | ||
338 | def GetMetric(self, id): | |
339 | """ Returns sizes of customizable options. """ | |
340 | ||
341 | if id == BP_SEPARATOR_SIZE: | |
342 | return self._separator_size | |
343 | elif id == BP_MARGINS_SIZE: | |
344 | return self._margins_size | |
345 | elif id == BP_BORDER_SIZE: | |
346 | return self._caption_border_size | |
347 | elif id == BP_PADDING_SIZE: | |
348 | return self._padding_size | |
349 | else: | |
350 | raise "\nERROR: Invalid Metric Ordinal. " | |
351 | ||
352 | ||
353 | def SetMetric(self, id, new_val): | |
354 | """ Sets sizes for customizable options. """ | |
355 | ||
356 | if id == BP_SEPARATOR_SIZE: | |
357 | self._separator_size = new_val | |
358 | elif id == BP_MARGINS_SIZE: | |
359 | self._margins_size = new_val | |
360 | elif id == BP_BORDER_SIZE: | |
361 | self._caption_border_size = new_val | |
362 | self._border_pen.SetWidth(new_val) | |
363 | elif id == BP_PADDING_SIZE: | |
364 | self._padding_size = new_val | |
365 | else: | |
366 | raise "\nERROR: Invalid Metric Ordinal. " | |
367 | ||
368 | ||
369 | def GetColor(self, id): | |
370 | """ Returns colours of customizable options. """ | |
371 | ||
372 | if id == BP_BACKGROUND_COLOR: | |
373 | return self._background_brush.GetColour() | |
374 | elif id == BP_GRADIENT_COLOR_FROM: | |
375 | return self._gradient_color_from | |
376 | elif id == BP_GRADIENT_COLOR_TO: | |
377 | return self._gradient_color_to | |
378 | elif id == BP_BORDER_COLOR: | |
379 | return self._border_pen.GetColour() | |
380 | elif id == BP_TEXT_COLOR: | |
381 | return self._caption_text_color | |
382 | elif id == BP_BUTTONTEXT_COLOR: | |
383 | return self._buttontext_color | |
384 | elif id == BP_BUTTONTEXT_INACTIVE_COLOR: | |
385 | return self._buttontext_inactive_color | |
386 | elif id == BP_SELECTION_BRUSH_COLOR: | |
387 | return self._selection_brush.GetColour() | |
388 | elif id == BP_SELECTION_PEN_COLOR: | |
389 | return self._selection_pen.GetColour() | |
390 | elif id == BP_SEPARATOR_COLOR: | |
391 | return self._separator_pen.GetColour() | |
392 | else: | |
393 | raise "\nERROR: Invalid Colour Ordinal. " | |
394 | ||
395 | ||
396 | def SetColor(self, id, colour): | |
397 | """ Sets colours for customizable options. """ | |
398 | ||
399 | if id == BP_BACKGROUND_COLOR: | |
400 | self._background_brush.SetColour(colour) | |
401 | elif id == BP_GRADIENT_COLOR_FROM: | |
402 | self._gradient_color_from = colour | |
403 | elif id == BP_GRADIENT_COLOR_TO: | |
404 | self._gradient_color_to = colour | |
405 | elif id == BP_BORDER_COLOR: | |
406 | self._border_pen.SetColour(colour) | |
407 | elif id == BP_TEXT_COLOR: | |
408 | self._caption_text_color = colour | |
409 | elif id == BP_BUTTONTEXT_COLOR: | |
410 | self._buttontext_color = colour | |
411 | elif id == BP_BUTTONTEXT_INACTIVE_COLOR: | |
412 | self._buttontext_inactive_color = colour | |
413 | elif id == BP_SELECTION_BRUSH_COLOR: | |
414 | self._selection_brush.SetColour(colour) | |
415 | elif id == BP_SELECTION_PEN_COLOR: | |
416 | self._selection_pen.SetColour(colour) | |
417 | elif id == BP_SEPARATOR_COLOR: | |
418 | self._separator_pen.SetColour(colour) | |
419 | else: | |
420 | raise "\nERROR: Invalid Colour Ordinal. " | |
421 | ||
422 | ||
423 | GetColour = GetColor | |
424 | SetColour = SetColor | |
425 | ||
426 | ||
427 | def SetFont(self, id, font): | |
428 | """ Sets font for customizable options. """ | |
429 | ||
430 | if id == BP_TEXT_FONT: | |
431 | self._caption_font = font | |
432 | elif id == BP_BUTTONTEXT_FONT: | |
433 | self._buttontext_font = font | |
434 | ||
435 | ||
436 | def GetFont(self, id): | |
437 | """ Returns font of customizable options. """ | |
438 | ||
439 | if id == BP_TEXT_FONT: | |
440 | return self._caption_font | |
441 | elif id == BP_BUTTONTEXT_FONT: | |
442 | return self._buttontext_font | |
443 | ||
444 | return wx.NoneFont | |
445 | ||
446 | ||
447 | def SetGradientType(self, gradient): | |
448 | """ Sets the gradient type for BPArt drawings. """ | |
449 | ||
450 | self._gradient_type = gradient | |
451 | ||
452 | ||
453 | def GetGradientType(self): | |
454 | """ Returns the gradient type for BPArt drawings. """ | |
455 | ||
456 | return self._gradient_type | |
457 | ||
458 | ||
459 | def DrawSeparator(self, dc, rect, isVertical): | |
460 | """ Draws a separator in ButtonPanel. """ | |
461 | ||
462 | dc.SetPen(self._separator_pen) | |
463 | ||
464 | if isVertical: | |
465 | ystart = yend = rect.y + rect.height/2 | |
466 | xstart = int(rect.x + 1.5*self._caption_border_size) | |
467 | xend = int(rect.x + rect.width - 1.5*self._caption_border_size) | |
468 | dc.DrawLine(xstart, ystart, xend, yend) | |
469 | else: | |
470 | xstart = xend = rect.x + rect.width/2 | |
471 | ystart = int(rect.y + 1.5*self._caption_border_size) | |
472 | yend = int(rect.y + rect.height - 1.5*self._caption_border_size) | |
473 | dc.DrawLine(xstart, ystart, xend, yend) | |
474 | ||
475 | ||
476 | def DrawCaption(self, dc, rect, captionText): | |
477 | """ Draws the main caption text in ButtonPanel. """ | |
478 | ||
479 | textColour = self._caption_text_color | |
480 | textFont = self._caption_font | |
481 | padding = self._padding_size | |
482 | ||
483 | dc.SetTextForeground(textColour) | |
484 | dc.SetFont(textFont) | |
485 | ||
486 | dc.DrawText(captionText, rect.x + padding.x, rect.y+padding.y) | |
487 | ||
488 | ||
489 | def DrawButton(self, dc, rect, parentSize, buttonBitmap, isVertical, | |
490 | buttonStatus, isToggled, textAlignment, text=""): | |
491 | """ Draws a button in ButtonPanel, together with its text (if any). """ | |
492 | ||
493 | bmpxsize, bmpysize = buttonBitmap.GetWidth(), buttonBitmap.GetHeight() | |
494 | dx = dy = focus = 0 | |
495 | ||
496 | borderw = self._caption_border_size | |
497 | padding = self._padding_size | |
498 | ||
499 | buttonFont = self._buttontext_font | |
500 | dc.SetFont(buttonFont) | |
501 | ||
502 | if isVertical: | |
503 | ||
504 | rect = wx.Rect(borderw, rect.y, rect.width-2*borderw, rect.height) | |
505 | ||
506 | if text != "": | |
507 | ||
508 | textW, textH = dc.GetTextExtent(text) | |
509 | ||
510 | if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT: | |
511 | fullExtent = bmpxsize + padding.x/2 + textW | |
512 | bmpypos = rect.y + (rect.height - bmpysize)/2 | |
513 | bmpxpos = rect.x + (rect.width - fullExtent)/2 | |
514 | textxpos = bmpxpos + padding.x/2 + bmpxsize | |
515 | textypos = bmpypos + (bmpysize - textH)/2 | |
516 | else: | |
517 | bmpxpos = rect.x + (rect.width - bmpxsize)/2 | |
518 | bmpypos = rect.y + padding.y | |
519 | textxpos = rect.x + (rect.width - textW)/2 | |
520 | textypos = bmpypos + bmpysize + padding.y/2 | |
521 | else: | |
522 | bmpxpos = rect.x + (rect.width - bmpxsize)/2 | |
523 | bmpypos = rect.y + (rect.height - bmpysize)/2 | |
524 | ||
525 | ||
526 | else: | |
527 | ||
528 | rect = wx.Rect(rect.x, borderw, rect.width, rect.height-2*borderw) | |
529 | ||
530 | if text != "": | |
531 | ||
532 | textW, textH = dc.GetTextExtent(text) | |
533 | ||
534 | if textAlignment == BP_BUTTONTEXT_ALIGN_RIGHT: | |
535 | fullExtent = bmpxsize + padding.x/2 + textW | |
536 | bmpypos = rect.y + (rect.height - bmpysize)/2 | |
537 | bmpxpos = rect.x + (rect.width - fullExtent)/2 | |
538 | textxpos = bmpxpos + padding.x/2 + bmpxsize | |
539 | textypos = bmpypos + (bmpysize - textH)/2 | |
540 | else: | |
541 | fullExtent = bmpysize + padding.y/2 + textH | |
542 | bmpxpos = rect.x + (rect.width - bmpxsize)/2 | |
543 | bmpypos = rect.y + (rect.height - fullExtent)/2 | |
544 | textxpos = rect.x + (rect.width - textW)/2 | |
545 | textypos = bmpypos + bmpysize + padding.y/2 | |
546 | else: | |
547 | bmpxpos = rect.x + (rect.width - bmpxsize)/2 | |
548 | bmpypos = rect.y + (rect.height - bmpysize)/2 | |
549 | ||
550 | # Draw a button | |
551 | # [ Padding | Text | .. Buttons .. | Padding ] | |
552 | ||
553 | if buttonStatus in ["Pressed", "Toggled", "Hover"]: | |
554 | dc.SetBrush(self._selection_brush) | |
555 | dc.SetPen(self._selection_pen) | |
556 | dc.DrawRoundedRectangleRect(rect, 4) | |
557 | ||
558 | if buttonStatus == "Pressed" or isToggled: | |
559 | dx = dy = 1 | |
560 | ||
561 | dc.DrawBitmap(buttonBitmap, bmpxpos+dx, bmpypos+dy, True) | |
562 | ||
563 | if text != "": | |
564 | isEnabled = buttonStatus != "Disabled" | |
565 | self.DrawLabel(dc, text, isEnabled, textxpos+dx, textypos+dy) | |
566 | ||
567 | ||
568 | def DrawLabel(self, dc, text, isEnabled, xpos, ypos): | |
569 | """ Draws the label for a button. """ | |
570 | ||
571 | if not isEnabled: | |
572 | dc.SetTextForeground(self._buttontext_inactive_color) | |
573 | else: | |
574 | dc.SetTextForeground(self._buttontext_color) | |
575 | ||
576 | dc.DrawText(text, xpos, ypos) | |
577 | ||
578 | ||
579 | def DrawButtonPanel(self, dc, rect, style): | |
580 | """ Paint the ButtonPanel's background. """ | |
581 | ||
582 | if style & BP_USE_GRADIENT: | |
583 | # Draw gradient color in the backgroud of the panel | |
584 | self.FillGradientColor(dc, rect) | |
585 | ||
586 | # Draw a rectangle around the panel | |
587 | backBrush = (style & BP_USE_GRADIENT and [wx.TRANSPARENT_BRUSH] or \ | |
588 | [self._background_brush])[0] | |
589 | ||
590 | dc.SetBrush(backBrush) | |
591 | dc.SetPen(self._border_pen) | |
592 | dc.DrawRectangleRect(rect) | |
593 | ||
594 | ||
595 | def FillGradientColor(self, dc, rect): | |
596 | """ Gradient fill from colour 1 to colour 2 with top to bottom or left to right. """ | |
597 | ||
598 | if rect.height < 1 or rect.width < 1: | |
599 | return | |
600 | ||
601 | isVertical = self._gradient_type == BP_GRADIENT_VERTICAL | |
602 | size = (isVertical and [rect.height] or [rect.width])[0] | |
603 | start = (isVertical and [rect.y] or [rect.x])[0] | |
604 | ||
605 | # calculate gradient coefficients | |
606 | ||
607 | col2 = self._gradient_color_from | |
608 | col1 = self._gradient_color_to | |
609 | ||
610 | rf, gf, bf = 0, 0, 0 | |
611 | rstep = float((col2.Red() - col1.Red()))/float(size) | |
612 | gstep = float((col2.Green() - col1.Green()))/float(size) | |
613 | bstep = float((col2.Blue() - col1.Blue()))/float(size) | |
614 | ||
615 | for coord in xrange(start, start + size): | |
616 | ||
617 | currCol = wx.Colour(col1.Red() + rf, col1.Green() + gf, col1.Blue() + bf) | |
618 | dc.SetBrush(wx.Brush(currCol, wx.SOLID)) | |
619 | dc.SetPen(wx.Pen(currCol)) | |
620 | if isVertical: | |
621 | dc.DrawLine(rect.x, coord, rect.x + rect.width, coord) | |
622 | else: | |
623 | dc.DrawLine(coord, rect.y, coord, rect.y + rect.height) | |
624 | ||
625 | rf += rstep | |
626 | gf += gstep | |
627 | bf += bstep | |
628 | ||
629 | ||
630 | class StatusBarTimer(wx.Timer): | |
631 | """Timer used for deleting StatusBar long help after _DELAY seconds.""" | |
632 | ||
633 | def __init__(self, owner): | |
634 | """ | |
635 | Default class constructor. | |
636 | For internal use: do not call it in your code! | |
637 | """ | |
638 | ||
639 | wx.Timer.__init__(self) | |
640 | self._owner = owner | |
641 | ||
642 | ||
643 | def Notify(self): | |
644 | """The timer has expired.""" | |
645 | ||
646 | self._owner.OnStatusBarTimer() | |
647 | ||
648 | ||
649 | class Control(wx.EvtHandler): | |
650 | ||
651 | def __init__(self, parent, size=wx.Size(-1, -1)): | |
652 | """ | |
653 | Default class constructor. | |
654 | ||
655 | Base class for all pseudo controls | |
656 | parent = parent object | |
657 | size = (width, height) | |
658 | """ | |
659 | ||
660 | wx.EvtHandler.__init__(self) | |
661 | ||
662 | self._parent = parent | |
663 | self._id = wx.NewId() | |
664 | self._size = size | |
665 | self._isshown = True | |
666 | self._focus = False | |
667 | ||
668 | ||
669 | def Show(self, show=True): | |
670 | """ Shows or hide the control. """ | |
671 | ||
672 | self._isshown = show | |
673 | ||
674 | ||
675 | def Hide(self): | |
676 | """ Hides the control. """ | |
677 | ||
678 | self.Show(False) | |
679 | ||
680 | ||
681 | def IsShown(self): | |
682 | """ Returns whether the control is shown or not. """ | |
683 | ||
684 | return self._isshown | |
685 | ||
686 | ||
687 | def GetId(self): | |
688 | """ Returns the control id. """ | |
689 | ||
690 | return self._id | |
691 | ||
692 | ||
693 | def GetBestSize(self): | |
694 | """ Returns the control best size. """ | |
695 | ||
696 | return self._size | |
697 | ||
698 | ||
699 | def Disable(self): | |
700 | """ Disables the control. """ | |
701 | ||
702 | self.Enable(False) | |
703 | ||
704 | ||
705 | def Enable(self, value=True): | |
706 | """ Enables or disables the control. """ | |
707 | ||
708 | self.disabled = not value | |
709 | ||
710 | ||
711 | def SetFocus(self, focus=True): | |
712 | """ Sets or kills the focus on the control. """ | |
713 | ||
714 | self._focus = focus | |
715 | ||
716 | ||
717 | def HasFocus(self): | |
718 | """ Returns whether the control has the focus or not. """ | |
719 | ||
720 | return self._focus | |
721 | ||
722 | ||
723 | def OnMouseEvent(self, x, y, event): | |
724 | pass | |
725 | ||
726 | def Draw(self, rect): | |
727 | pass | |
728 | ||
729 | ||
730 | ||
731 | class Sizer(object): | |
732 | """ | |
733 | Sizer | |
734 | ||
735 | This is a mix-in class to add pseudo support to a wx sizer. Just create | |
736 | a new class that derives from this class and the wx sizer and intercepts | |
737 | any methods that add to the wx sizer. | |
738 | """ | |
739 | def __init__(self): | |
740 | self.children = [] # list of child Pseudo Controls | |
741 | ||
742 | # Sizer doesn't use the x1,y1,x2,y2 so allow it to | |
743 | # be called with or without the coordinates | |
744 | def Draw(self, dc, x1=0, y1=0, x2=0, y2=0): | |
745 | for item in self.children: | |
746 | # use sizer coordinates rather than | |
747 | # what is passed in | |
748 | c = item.GetUserData() | |
749 | c.Draw(dc, item.GetRect()) | |
750 | ||
751 | def GetBestSize(self): | |
752 | # this should be handled by the wx.Sizer based class | |
753 | return self.GetMinSize() | |
754 | ||
755 | ||
756 | # Pseudo BoxSizer | |
757 | class BoxSizer(Sizer, wx.BoxSizer): | |
758 | def __init__(self, orient=wx.HORIZONTAL): | |
759 | wx.BoxSizer.__init__(self, orient) | |
760 | Sizer.__init__(self) | |
761 | ||
762 | #------------------------------------------- | |
763 | # sizer overrides (only called from Python) | |
764 | #------------------------------------------- | |
765 | # no support for user data if it's a pseudocontrol | |
766 | # since that is already used | |
767 | def Add(self, item, proportion=0, flag=0, border=0, userData=None): | |
768 | # check to see if it's a pseudo object or sizer | |
769 | if isinstance(item, Sizer): | |
770 | szitem = wx.BoxSizer.Add(self, item, proportion, flag, border, item) | |
771 | self.children.append(szitem) | |
772 | elif isinstance(item, Control): # Control should be what ever class your controls come from | |
773 | sz = item.GetBestSize() | |
774 | # add a spacer to track this object | |
775 | szitem = wx.BoxSizer.Add(self, sz, proportion, flag, border, item) | |
776 | self.children.append(szitem) | |
777 | else: | |
778 | wx.BoxSizer.Add(self, item, proportion, flag, border, userData) | |
779 | ||
780 | def Prepend(self, item, proportion=0, flag=0, border=0, userData=None): | |
781 | # check to see if it's a pseudo object or sizer | |
782 | if isinstance(item, Sizer): | |
783 | szitem = wx.BoxSizer.Prepend(self, item, proportion, flag, border, item) | |
784 | self.children.append(szitem) | |
785 | elif isinstance(item, Control): # Control should be what ever class your controls come from | |
786 | sz = item.GetBestSize() | |
787 | # add a spacer to track this object | |
788 | szitem = wx.BoxSizer.Prepend(self, sz, proportion, flag, border, item) | |
789 | self.children.insert(0,szitem) | |
790 | else: | |
791 | wx.BoxSizer.Prepend(self, item, proportion, flag, border, userData) | |
792 | ||
793 | def Insert(self, before, item, proportion=0, flag=0, border=0, userData=None, realIndex=None): | |
794 | # check to see if it's a pseudo object or sizer | |
795 | if isinstance(item, Sizer): | |
796 | szitem = wx.BoxSizer.Insert(self, before, item, proportion, flag, border, item) | |
797 | self.children.append(szitem) | |
798 | elif isinstance(item, Control): # Control should be what ever class your controls come from | |
799 | sz = item.GetBestSize() | |
800 | # add a spacer to track this object | |
801 | szitem = wx.BoxSizer.Insert(self, before, sz, proportion, flag, border, item) | |
802 | if realIndex is not None: | |
803 | self.children.insert(realIndex,szitem) | |
804 | else: | |
805 | self.children.insert(before,szitem) | |
806 | ||
807 | else: | |
808 | wx.BoxSizer.Insert(self, before, item, proportion, flag, border, userData) | |
809 | ||
810 | ||
811 | def Remove(self, indx, pop=-1): | |
812 | ||
813 | if pop >= 0: | |
814 | self.children.pop(pop) | |
815 | ||
816 | wx.BoxSizer.Remove(self, indx) | |
817 | ||
818 | ||
819 | def Layout(self): | |
820 | ||
821 | for ii, child in enumerate(self.GetChildren()): | |
822 | item = child.GetUserData() | |
823 | if item and child.IsShown(): | |
824 | self.SetItemMinSize(ii, *item.GetBestSize()) | |
825 | ||
826 | wx.BoxSizer.Layout(self) | |
827 | ||
828 | ||
829 | def Show(self, item, show=True): | |
830 | ||
831 | child = self.GetChildren()[item] | |
832 | if child and child.GetUserData(): | |
833 | child.GetUserData().Show(show) | |
834 | ||
835 | wx.BoxSizer.Show(self, item, show) | |
836 | ||
837 | ||
838 | # ---------------------------------------------------------------------------- # | |
839 | # Class Separator | |
840 | # This class holds all the information to size and draw a separator inside | |
841 | # ButtonPanel | |
842 | # ---------------------------------------------------------------------------- # | |
843 | ||
844 | class Separator(Control): | |
845 | ||
846 | def __init__(self, parent): | |
847 | """ Default class constructor. """ | |
848 | ||
849 | self._isshown = True | |
850 | self._parent = parent | |
851 | Control.__init__(self, parent) | |
852 | ||
853 | ||
854 | def GetBestSize(self): | |
855 | """ Returns the separator best size. """ | |
856 | ||
857 | # 10 is completely arbitrary, but it works anyhow | |
858 | if self._parent.IsVertical(): | |
859 | return wx.Size(10, self._parent._art.GetMetric(BP_SEPARATOR_SIZE)) | |
860 | else: | |
861 | return wx.Size(self._parent._art.GetMetric(BP_SEPARATOR_SIZE), 10) | |
862 | ||
863 | ||
864 | def Draw(self, dc, rect): | |
865 | """ Draws the separator. Actually the drawing is done in BPArt. """ | |
866 | ||
867 | if not self.IsShown(): | |
868 | return | |
869 | ||
870 | isVertical = self._parent.IsVertical() | |
871 | self._parent._art.DrawSeparator(dc, rect, isVertical) | |
872 | ||
873 | ||
874 | # ---------------------------------------------------------------------------- # | |
875 | # Class ButtonPanelText | |
876 | # This class is used to hold data about the main caption in ButtonPanel | |
877 | # ---------------------------------------------------------------------------- # | |
878 | ||
879 | class ButtonPanelText(Control): | |
880 | ||
881 | def __init__(self, parent, text=""): | |
882 | """ Default class constructor. """ | |
883 | ||
884 | self._text = text | |
885 | self._isshown = True | |
886 | self._parent = parent | |
887 | ||
888 | Control.__init__(self, parent) | |
889 | ||
890 | ||
891 | def GetText(self): | |
892 | """ Returns the caption text. """ | |
893 | ||
894 | return self._text | |
895 | ||
896 | ||
897 | def SetText(self, text=""): | |
898 | """ Sets the caption text. """ | |
899 | ||
900 | self._text = text | |
901 | ||
902 | ||
903 | def CreateDC(self): | |
904 | """ Convenience function to create a DC. """ | |
905 | ||
906 | dc = wx.ClientDC(self._parent) | |
907 | textFont = self._parent._art.GetFont(BP_TEXT_FONT) | |
908 | dc.SetFont(textFont) | |
909 | ||
910 | return dc | |
911 | ||
912 | ||
913 | def GetBestSize(self): | |
914 | """ Returns the best size for the main caption in ButtonPanel. """ | |
915 | ||
916 | if self._text == "": | |
917 | return wx.Size(0, 0) | |
918 | ||
919 | dc = self.CreateDC() | |
920 | rect = self._parent.GetClientRect() | |
921 | ||
922 | tw, th = dc.GetTextExtent(self._text) | |
923 | padding = self._parent._art.GetMetric(BP_PADDING_SIZE) | |
924 | self._size = wx.Size(tw+2*padding.x, th+2*padding.y) | |
925 | ||
926 | return self._size | |
927 | ||
928 | ||
929 | def Draw(self, dc, rect): | |
930 | """ Draws the main caption. Actually the drawing is done in BPArt. """ | |
931 | ||
932 | if not self.IsShown(): | |
933 | return | |
934 | ||
935 | captionText = self.GetText() | |
936 | self._parent._art.DrawCaption(dc, rect, captionText) | |
937 | ||
938 | ||
939 | # -- ButtonInfo class implementation ---------------------------------------- | |
940 | # This class holds information about every button that is added to | |
941 | # ButtonPanel. It is an auxiliary class that you should use | |
942 | # every time you add a button. | |
943 | ||
944 | class ButtonInfo(Control): | |
945 | ||
946 | def __init__(self, parent, id=wx.ID_ANY, bmp=wx.NullBitmap, | |
947 | status="Normal", text="", kind=wx.ITEM_NORMAL, | |
948 | shortHelp="", longHelp=""): | |
949 | """ | |
950 | Default class constructor. | |
951 | ||
952 | Parameters: | |
953 | - parent: the parent window (ButtonPanel); | |
954 | - id: the button id; | |
955 | - bmp: the associated bitmap; | |
956 | - status: button status (pressed, hovered, normal). | |
957 | - text: text to be displayed either below of to the right of the button | |
958 | - kind: button kind, may be wx.ITEM_NORMAL for standard buttons or | |
959 | wx.ITEM_CHECK for toggle buttons; | |
960 | - shortHelp: a short help to be shown in the button tooltip; | |
961 | - longHelp: this string is shown in the statusbar (if any) of the parent | |
962 | frame when the mouse pointer is inside the button. | |
963 | """ | |
964 | ||
965 | if id == wx.ID_ANY: | |
966 | id = wx.NewId() | |
967 | ||
968 | self._status = status | |
969 | self._rect = wx.Rect() | |
970 | self._text = text | |
971 | self._kind = kind | |
972 | self._toggle = False | |
973 | self._textAlignment = BP_BUTTONTEXT_ALIGN_BOTTOM | |
974 | self._shortHelp = shortHelp | |
975 | self._longHelp = longHelp | |
976 | ||
977 | disabledbmp = GrayOut(bmp) | |
978 | ||
979 | self._bitmaps = {"Normal": bmp, "Toggled": None, "Disabled": disabledbmp, | |
980 | "Hover": None, "Pressed": None} | |
981 | ||
982 | Control.__init__(self, parent) | |
983 | ||
984 | ||
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: | |
1042 | self._parent.RefreshRect(self.GetRect()) | |
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): | |
1053 | """ Returns the associated bitmap. """ | |
1054 | ||
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] | |
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 | |
1089 | ||
1090 | ||
1091 | def SetStatus(self, status): | |
1092 | """ Sets the button status. """ | |
1093 | ||
1094 | if status == self._status: | |
1095 | return | |
1096 | ||
1097 | if self.GetToggled() and status == "Normal": | |
1098 | status = "Toggled" | |
1099 | ||
1100 | self._status = status | |
1101 | self._parent.RefreshRect(self.GetRect()) | |
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 | ||
1116 | self._textAlignment = alignment | |
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 | |
1135 | ||
1136 | ||
1137 | def SetId(self, id): | |
1138 | """ Sets the button id. """ | |
1139 | ||
1140 | self._id = id | |
1141 | ||
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 | |
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 | |
1221 | ||
1222 | ||
1223 | Bitmap = property(GetBitmap, SetBitmap) | |
1224 | Id = property(GetId, SetId) | |
1225 | Rect = property(GetRect, SetRect) | |
1226 | Status = property(GetStatus, SetStatus) | |
1227 | ||
1228 | ||
1229 | # -- ButtonPanel class implementation ---------------------------------- | |
1230 | # This is the main class. | |
1231 | ||
1232 | class ButtonPanel(wx.PyPanel): | |
1233 | ||
1234 | def __init__(self, parent, id=wx.ID_ANY, text="", style=BP_DEFAULT_STYLE, | |
1235 | alignment=BP_ALIGN_LEFT, name="buttonPanel"): | |
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 | ||
1247 | wx.PyPanel.__init__(self, parent, id, wx.DefaultPosition, wx.DefaultSize, | |
1248 | wx.NO_BORDER, name=name) | |
1249 | ||
1250 | self._vButtons = [] | |
1251 | self._vSeparators = [] | |
1252 | ||
1253 | self._nStyle = style | |
1254 | self._alignment = alignment | |
1255 | self._statusTimer = None | |
1256 | self._useHelp = True | |
1257 | self._freezeCount = 0 | |
1258 | self._currentButton = -1 | |
1259 | self._haveTip = False | |
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) | |
1273 | ||
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) | |
1278 | self.Bind(wx.EVT_PAINT, self.OnPaint) | |
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 | ||
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) | |
1346 | ||
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 | ||
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 | ||
1366 | lenChildren = len(self._mainsizer.GetChildren()) | |
1367 | self._mainsizer.Insert(lenChildren-1, btnInfo, 0, wx.ALIGN_CENTER|wx.EXPAND, userData=btnInfo) | |
1368 | ||
1369 | self._vButtons.append(btnInfo) | |
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 | ||
1400 | ||
1401 | def RemoveAllButtons(self): | |
1402 | """ Remove all the buttons from ButtonPanel. """ | |
1403 | ||
1404 | self._vButtons = [] | |
1405 | ||
1406 | ||
1407 | def RemoveAllSeparators(self): | |
1408 | """ Remove all the separators from ButtonPanel. """ | |
1409 | ||
1410 | self._vSeparators = [] | |
1411 | ||
1412 | ||
1413 | def GetAlignment(self): | |
1414 | """ Returns the button alignment (left, right, top, bottom). """ | |
1415 | ||
1416 | return self._alignment | |
1417 | ||
1418 | ||
1419 | def SetAlignment(self, alignment): | |
1420 | """ Sets the button alignment (left, right, top, bottom). """ | |
1421 | ||
1422 | if alignment == self._alignment: | |
1423 | return | |
1424 | ||
1425 | self.Freeze() | |
1426 | ||
1427 | text = self.GetBarText() | |
1428 | ||
1429 | # Remove the text in any case | |
1430 | self.RemoveText() | |
1431 | ||
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 | |
1437 | ||
1438 | # Recreate the sizer accordingly to the new alignment | |
1439 | self.ReCreateSizer(text) | |
1440 | ||
1441 | ||
1442 | def IsVertical(self): | |
1443 | """ Returns whether ButtonPanel is vertically aligned or not. """ | |
1444 | ||
1445 | return self._alignment not in [BP_ALIGN_RIGHT, BP_ALIGN_LEFT] | |
1446 | ||
1447 | ||
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() | |
1478 | ||
1479 | ||
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() | |
1486 | ||
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) | |
1490 | ||
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() | |
1528 | ||
1529 | ||
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 | |
1546 | else: | |
1547 | w += padding.x | |
1548 | else: | |
1549 | w = h = border | |
1550 | ||
1551 | # Add the button's sizes | |
1552 | for btn in self._vButtons: | |
1553 | ||
1554 | bw, bh = btn.GetBestSize() | |
1555 | btnWidth = max(btnWidth, bw) | |
1556 | btnHeight = max(btnHeight, bh) | |
1557 | ||
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) | |
1574 | ||
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 | |
1581 | ||
1582 | return wx.Size(w, h) | |
1583 | ||
1584 | ||
1585 | def OnPaint(self, event): | |
1586 | """ Handles the wx.EVT_PAINT event for ButtonPanel. """ | |
1587 | ||
1588 | dc = wx.BufferedPaintDC(self) | |
1589 | rect = self.GetClientRect() | |
1590 | ||
1591 | self._art.DrawButtonPanel(dc, rect, self._nStyle) | |
1592 | self._mainsizer.Draw(dc) | |
1593 | ||
1594 | ||
1595 | def OnEraseBackground(self, event): | |
1596 | """ Handles the wx.EVT_ERASE_BACKGROUND event for ButtonPanel (does nothing). """ | |
1597 | ||
1598 | pass | |
1599 | ||
1600 | ||
1601 | def OnSize(self, event): | |
1602 | """ Handles the wx.EVT_SIZE event for ButtonPanel. """ | |
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 | ||
1610 | event.Skip() | |
1611 | ||
1612 | ||
1613 | def LayoutItems(self): | |
1614 | """ | |
1615 | Layout the items using a different algorithm depending on the existance | |
1616 | of the main caption. | |
1617 | """ | |
1618 | ||
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() | |
1627 | ||
1628 | ||
1629 | def SizeLayout(self, nonspacers, children): | |
1630 | """ Layout the items when no main caption exists. """ | |
1631 | ||
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 | ||
1649 | ||
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] | |
1657 | ||
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 | ||
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 | |
1749 | tabId, flags = self.HitTest(event.GetPosition()) | |
1750 | ||
1751 | if flags != BP_HT_BUTTON: | |
1752 | self.RemoveHelp() | |
1753 | self.RepaintOldSelection() | |
1754 | self._currentButton = -1 | |
1755 | return | |
1756 | ||
1757 | btn = self._vButtons[tabId] | |
1758 | ||
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 | |
1777 | ||
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 | ||
1786 | if hit == BP_HT_BUTTON: | |
1787 | btn = self._vButtons[tabId] | |
1788 | if btn.IsEnabled(): | |
1789 | btn.SetStatus("Pressed") | |
1790 | self._currentButton = tabId | |
1791 | ||
1792 | ||
1793 | def OnLeftUp(self, event): | |
1794 | """ Handles the wx.EVT_LEFT_UP event for ButtonPanel. """ | |
1795 | ||
1796 | tabId, flags = self.HitTest(event.GetPosition()) | |
1797 | ||
1798 | if flags != BP_HT_BUTTON: | |
1799 | return | |
1800 | ||
1801 | hit = self._vButtons[tabId] | |
1802 | ||
1803 | if hit.GetStatus() == "Disabled": | |
1804 | return | |
1805 | ||
1806 | for btn in self._vButtons: | |
1807 | if btn != hit: | |
1808 | btn.SetFocus(False) | |
1809 | ||
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) | |
1814 | ||
1815 | hit.SetToggled(not hit.GetToggled()) | |
1816 | ||
1817 | # Update the button status to be hovered | |
1818 | hit.SetStatus("Hover") | |
1819 | hit.SetFocus() | |
1820 | self._currentButton = tabId | |
1821 | ||
1822 | ||
1823 | def OnMouseLeave(self, event): | |
1824 | """ Handles the wx.EVT_LEAVE_WINDOW event for ButtonPanel. """ | |
1825 | ||
1826 | # Reset all buttons statuses | |
1827 | for btn in self._vButtons: | |
1828 | if not btn.IsEnabled(): | |
1829 | continue | |
1830 | btn.SetStatus("Normal") | |
1831 | ||
1832 | self.RemoveHelp() | |
1833 | ||
1834 | event.Skip() | |
1835 | ||
1836 | ||
1837 | def OnMouseEnterWindow(self, event): | |
1838 | """ Handles the wx.EVT_ENTER_WINDOW event for ButtonPanel. """ | |
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 | ||
1853 | event.Skip() | |
1854 | ||
1855 | ||
1856 | def DoGiveHelp(self, hit): | |
1857 | """ Gives tooltips and help in StatusBar. """ | |
1858 | ||
1859 | if not self.GetUseHelp(): | |
1860 | return | |
1861 | ||
1862 | shortHelp = hit.GetShortHelp() | |
1863 | if shortHelp: | |
1864 | self.SetToolTipString(shortHelp) | |
1865 | self._haveTip = True | |
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 | ||
1891 | if self._haveTip: | |
1892 | self.SetToolTipString("") | |
1893 | self._haveTip = False | |
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 | ||
1938 | def HitTest(self, pt): | |
1939 | """ | |
1940 | HitTest method for ButtonPanel. Returns the button (if any) and | |
1941 | a flag (if any). | |
1942 | """ | |
1943 | ||
1944 | for ii in xrange(len(self._vButtons)): | |
1945 | if not self._vButtons[ii].IsEnabled(): | |
1946 | continue | |
1947 | if self._vButtons[ii].GetRect().Contains(pt): | |
1948 | return ii, BP_HT_BUTTON | |
1949 | ||
1950 | return -1, BP_HT_NONE | |
1951 | ||
1952 | ||
1953 | def GetBPArt(self): | |
1954 | """ Returns the associated BPArt art provider. """ | |
1955 | ||
1956 | return self._art | |
1957 | ||
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 |