Mercurial > traipse_dev
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 |