]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/buttons.py
Patch from Andrea that fixes the following problems/issues:
[wxWidgets.git] / wxPython / wx / lib / buttons.py
1 #----------------------------------------------------------------------
2 # Name: wx.lib.buttons
3 # Purpose: Various kinds of generic buttons, (not native controls but
4 # self-drawn.)
5 #
6 # Author: Robin Dunn
7 #
8 # Created: 9-Dec-1999
9 # RCS-ID: $Id$
10 # Copyright: (c) 1999 by Total Control Software
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------
13 # 11/30/2003 - Jeff Grimmett (grimmtooth@softhome.net)
14 #
15 # o Updated for wx namespace
16 # o Tested with updated demo
17 #
18
19 """
20 This module implements various forms of generic buttons, meaning that
21 they are not built on native controls but are self-drawn. They act
22 like normal buttons but you are able to better control how they look,
23 bevel width, colours, etc.
24 """
25
26 import wx
27 import imageutils
28
29
30 #----------------------------------------------------------------------
31
32 class GenButtonEvent(wx.PyCommandEvent):
33 """Event sent from the generic buttons when the button is activated. """
34 def __init__(self, eventType, id):
35 wx.PyCommandEvent.__init__(self, eventType, id)
36 self.isDown = False
37 self.theButton = None
38
39 def SetIsDown(self, isDown):
40 self.isDown = isDown
41
42 def GetIsDown(self):
43 return self.isDown
44
45 def SetButtonObj(self, btn):
46 self.theButton = btn
47
48 def GetButtonObj(self):
49 return self.theButton
50
51
52 #----------------------------------------------------------------------
53
54 class GenButton(wx.PyControl):
55 """A generic button, and base class for the other generic buttons."""
56
57 labelDelta = 1
58
59 def __init__(self, parent, id=-1, label='',
60 pos = wx.DefaultPosition, size = wx.DefaultSize,
61 style = 0, validator = wx.DefaultValidator,
62 name = "genbutton"):
63 cstyle = style
64 if cstyle == 0:
65 cstyle = wx.BORDER_NONE
66 wx.PyControl.__init__(self, parent, id, pos, size, cstyle, validator, name)
67
68 self.up = True
69 self.hasFocus = False
70 self.style = style
71 if style & wx.BORDER_NONE:
72 self.bezelWidth = 0
73 self.useFocusInd = False
74 else:
75 self.bezelWidth = 2
76 self.useFocusInd = True
77
78 self.SetLabel(label)
79 self.InheritAttributes()
80 self.SetInitialSize(size)
81 self.InitColours()
82
83 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
84 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
85 if wx.Platform == '__WXMSW__':
86 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
87 self.Bind(wx.EVT_MOTION, self.OnMotion)
88 self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus)
89 self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
90 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
91 self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
92 self.Bind(wx.EVT_PAINT, self.OnPaint)
93
94
95 def SetInitialSize(self, size=None):
96 """
97 Given the current font and bezel width settings, calculate
98 and set a good size.
99 """
100 if size is None:
101 size = wx.DefaultSize
102 wx.PyControl.SetInitialSize(self, size)
103 SetBestSize = SetInitialSize
104
105
106 def DoGetBestSize(self):
107 """
108 Overridden base class virtual. Determines the best size of the
109 button based on the label and bezel size.
110 """
111 w, h, useMin = self._GetLabelSize()
112 defSize = wx.Button.GetDefaultSize()
113 width = 12 + w
114 if useMin and width < defSize.width:
115 width = defSize.width
116 height = 11 + h
117 if useMin and height < defSize.height:
118 height = defSize.height
119 width = width + self.bezelWidth - 1
120 height = height + self.bezelWidth - 1
121 return (width, height)
122
123
124 def AcceptsFocus(self):
125 """Overridden base class virtual."""
126 return self.IsShown() and self.IsEnabled()
127
128
129 def GetDefaultAttributes(self):
130 """
131 Overridden base class virtual. By default we should use
132 the same font/colour attributes as the native Button.
133 """
134 return wx.Button.GetClassDefaultAttributes()
135
136
137 def ShouldInheritColours(self):
138 """
139 Overridden base class virtual. Buttons usually don't inherit
140 the parent's colours.
141 """
142 return False
143
144
145 def Enable(self, enable=True):
146 wx.PyControl.Enable(self, enable)
147 self.Refresh()
148
149
150 def SetBezelWidth(self, width):
151 """Set the width of the 3D effect"""
152 self.bezelWidth = width
153
154 def GetBezelWidth(self):
155 """Return the width of the 3D effect"""
156 return self.bezelWidth
157
158 def SetUseFocusIndicator(self, flag):
159 """Specifiy if a focus indicator (dotted line) should be used"""
160 self.useFocusInd = flag
161
162 def GetUseFocusIndicator(self):
163 """Return focus indicator flag"""
164 return self.useFocusInd
165
166
167 def InitColours(self):
168 """
169 Calculate a new set of highlight and shadow colours based on
170 the background colour. Works okay if the colour is dark...
171 """
172 faceClr = self.GetBackgroundColour()
173 r, g, b = faceClr.Get()
174 fr, fg, fb = min(255,r+32), min(255,g+32), min(255,b+32)
175 self.faceDnClr = wx.Colour(fr, fg, fb)
176 sr, sg, sb = max(0,r-32), max(0,g-32), max(0,b-32)
177 self.shadowPen = wx.Pen(wx.Colour(sr,sg,sb), 1, wx.SOLID)
178 hr, hg, hb = min(255,r+64), min(255,g+64), min(255,b+64)
179 self.highlightPen = wx.Pen(wx.Colour(hr,hg,hb), 1, wx.SOLID)
180 self.focusClr = wx.Colour(hr, hg, hb)
181
182 textClr = self.GetForegroundColour()
183 if wx.Platform == "__WXMAC__":
184 self.focusIndPen = wx.Pen(textClr, 1, wx.SOLID)
185 else:
186 self.focusIndPen = wx.Pen(textClr, 1, wx.USER_DASH)
187 self.focusIndPen.SetDashes([1,1])
188 self.focusIndPen.SetCap(wx.CAP_BUTT)
189
190
191 def SetBackgroundColour(self, colour):
192 wx.PyControl.SetBackgroundColour(self, colour)
193 self.InitColours()
194
195
196 def SetForegroundColour(self, colour):
197 wx.PyControl.SetForegroundColour(self, colour)
198 self.InitColours()
199
200 def SetDefault(self):
201 tlw = wx.GetTopLevelParent(self)
202 if hasattr(tlw, 'SetDefaultItem'):
203 tlw.SetDefaultItem(self)
204
205 def _GetLabelSize(self):
206 """ used internally """
207 w, h = self.GetTextExtent(self.GetLabel())
208 return w, h, True
209
210
211 def Notify(self):
212 evt = GenButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
213 evt.SetIsDown(not self.up)
214 evt.SetButtonObj(self)
215 evt.SetEventObject(self)
216 self.GetEventHandler().ProcessEvent(evt)
217
218
219 def DrawBezel(self, dc, x1, y1, x2, y2):
220 # draw the upper left sides
221 if self.up:
222 dc.SetPen(self.highlightPen)
223 else:
224 dc.SetPen(self.shadowPen)
225 for i in range(self.bezelWidth):
226 dc.DrawLine(x1+i, y1, x1+i, y2-i)
227 dc.DrawLine(x1, y1+i, x2-i, y1+i)
228
229 # draw the lower right sides
230 if self.up:
231 dc.SetPen(self.shadowPen)
232 else:
233 dc.SetPen(self.highlightPen)
234 for i in range(self.bezelWidth):
235 dc.DrawLine(x1+i, y2-i, x2+1, y2-i)
236 dc.DrawLine(x2-i, y1+i, x2-i, y2)
237
238
239 def DrawLabel(self, dc, width, height, dw=0, dy=0):
240 dc.SetFont(self.GetFont())
241 if self.IsEnabled():
242 dc.SetTextForeground(self.GetForegroundColour())
243 else:
244 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
245 label = self.GetLabel()
246 tw, th = dc.GetTextExtent(label)
247 if not self.up:
248 dw = dy = self.labelDelta
249 dc.DrawText(label, (width-tw)/2+dw, (height-th)/2+dy)
250
251
252 def DrawFocusIndicator(self, dc, w, h):
253 bw = self.bezelWidth
254 self.focusIndPen.SetColour(self.focusClr)
255 dc.SetLogicalFunction(wx.INVERT)
256 dc.SetPen(self.focusIndPen)
257 dc.SetBrush(wx.TRANSPARENT_BRUSH)
258 dc.DrawRectangle(bw+2,bw+2, w-bw*2-4, h-bw*2-4)
259 dc.SetLogicalFunction(wx.COPY)
260
261 def OnPaint(self, event):
262 (width, height) = self.GetClientSizeTuple()
263 x1 = y1 = 0
264 x2 = width-1
265 y2 = height-1
266
267 dc = wx.PaintDC(self)
268 brush = self.GetBackgroundBrush(dc)
269 if brush is not None:
270 dc.SetBackground(brush)
271 dc.Clear()
272
273 self.DrawBezel(dc, x1, y1, x2, y2)
274 self.DrawLabel(dc, width, height)
275 if self.hasFocus and self.useFocusInd:
276 self.DrawFocusIndicator(dc, width, height)
277
278
279 def GetBackgroundBrush(self, dc):
280 if self.up:
281 colBg = self.GetBackgroundColour()
282 brush = wx.Brush(colBg, wx.SOLID)
283 if self.style & wx.BORDER_NONE:
284 myAttr = self.GetDefaultAttributes()
285 parAttr = self.GetParent().GetDefaultAttributes()
286 myDef = colBg == myAttr.colBg
287 parDef = self.GetParent().GetBackgroundColour() == parAttr.colBg
288 if myDef and parDef:
289 if wx.Platform == "__WXMAC__":
290 brush.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
291 elif wx.Platform == "__WXMSW__":
292 if self.DoEraseBackground(dc):
293 brush = None
294 elif myDef and not parDef:
295 colBg = self.GetParent().GetBackgroundColour()
296 brush = wx.Brush(colBg, wx.SOLID)
297 else:
298 # this line assumes that a pressed button should be hilighted with
299 # a solid colour even if the background is supposed to be transparent
300 brush = wx.Brush(self.faceDnClr, wx.SOLID)
301 return brush
302
303
304 def OnLeftDown(self, event):
305 if not self.IsEnabled():
306 return
307 self.up = False
308 self.CaptureMouse()
309 self.SetFocus()
310 self.Refresh()
311 event.Skip()
312
313
314 def OnLeftUp(self, event):
315 if not self.IsEnabled() or not self.HasCapture():
316 return
317 if self.HasCapture():
318 self.ReleaseMouse()
319 if not self.up: # if the button was down when the mouse was released...
320 self.Notify()
321 self.up = True
322 if self: # in case the button was destroyed in the eventhandler
323 self.Refresh()
324 event.Skip()
325
326
327 def OnMotion(self, event):
328 if not self.IsEnabled() or not self.HasCapture():
329 return
330 if event.LeftIsDown() and self.HasCapture():
331 x,y = event.GetPositionTuple()
332 w,h = self.GetClientSizeTuple()
333 if self.up and x<w and x>=0 and y<h and y>=0:
334 self.up = False
335 self.Refresh()
336 return
337 if not self.up and (x<0 or y<0 or x>=w or y>=h):
338 self.up = True
339 self.Refresh()
340 return
341 event.Skip()
342
343
344 def OnGainFocus(self, event):
345 self.hasFocus = True
346 self.Refresh()
347 self.Update()
348
349
350 def OnLoseFocus(self, event):
351 self.hasFocus = False
352 self.Refresh()
353 self.Update()
354
355
356 def OnKeyDown(self, event):
357 if self.hasFocus and event.GetKeyCode() == ord(" "):
358 self.up = False
359 self.Refresh()
360 event.Skip()
361
362
363 def OnKeyUp(self, event):
364 if self.hasFocus and event.GetKeyCode() == ord(" "):
365 self.up = True
366 self.Notify()
367 self.Refresh()
368 event.Skip()
369
370
371 #----------------------------------------------------------------------
372
373 class GenBitmapButton(GenButton):
374 """A generic bitmap button."""
375
376 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap,
377 pos = wx.DefaultPosition, size = wx.DefaultSize,
378 style = 0, validator = wx.DefaultValidator,
379 name = "genbutton"):
380 self.bmpDisabled = None
381 self.bmpFocus = None
382 self.bmpSelected = None
383 self.SetBitmapLabel(bitmap)
384 GenButton.__init__(self, parent, id, "", pos, size, style, validator, name)
385
386
387 def GetBitmapLabel(self):
388 return self.bmpLabel
389 def GetBitmapDisabled(self):
390 return self.bmpDisabled
391 def GetBitmapFocus(self):
392 return self.bmpFocus
393 def GetBitmapSelected(self):
394 return self.bmpSelected
395
396
397 def SetBitmapDisabled(self, bitmap):
398 """Set bitmap to display when the button is disabled"""
399 self.bmpDisabled = bitmap
400
401 def SetBitmapFocus(self, bitmap):
402 """Set bitmap to display when the button has the focus"""
403 self.bmpFocus = bitmap
404 self.SetUseFocusIndicator(False)
405
406 def SetBitmapSelected(self, bitmap):
407 """Set bitmap to display when the button is selected (pressed down)"""
408 self.bmpSelected = bitmap
409
410 def SetBitmapLabel(self, bitmap, createOthers=True):
411 """
412 Set the bitmap to display normally.
413 This is the only one that is required. If
414 createOthers is True, then the other bitmaps
415 will be generated on the fly. Currently,
416 only the disabled bitmap is generated.
417 """
418 self.bmpLabel = bitmap
419 if bitmap is not None and createOthers:
420 image = wx.ImageFromBitmap(bitmap)
421 imageutils.grayOut(image)
422 self.SetBitmapDisabled(wx.BitmapFromImage(image))
423
424
425 def _GetLabelSize(self):
426 """ used internally """
427 if not self.bmpLabel:
428 return -1, -1, False
429 return self.bmpLabel.GetWidth()+2, self.bmpLabel.GetHeight()+2, False
430
431 def DrawLabel(self, dc, width, height, dw=0, dy=0):
432 bmp = self.bmpLabel
433 if self.bmpDisabled and not self.IsEnabled():
434 bmp = self.bmpDisabled
435 if self.bmpFocus and self.hasFocus:
436 bmp = self.bmpFocus
437 if self.bmpSelected and not self.up:
438 bmp = self.bmpSelected
439 bw,bh = bmp.GetWidth(), bmp.GetHeight()
440 if not self.up:
441 dw = dy = self.labelDelta
442 hasMask = bmp.GetMask() != None
443 dc.DrawBitmap(bmp, (width-bw)/2+dw, (height-bh)/2+dy, hasMask)
444
445
446 #----------------------------------------------------------------------
447
448
449 class GenBitmapTextButton(GenBitmapButton):
450 """A generic bitmapped button with text label"""
451 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap, label='',
452 pos = wx.DefaultPosition, size = wx.DefaultSize,
453 style = 0, validator = wx.DefaultValidator,
454 name = "genbutton"):
455 GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style, validator, name)
456 self.SetLabel(label)
457
458
459 def _GetLabelSize(self):
460 """ used internally """
461 w, h = self.GetTextExtent(self.GetLabel())
462 if not self.bmpLabel:
463 return w, h, True # if there isn't a bitmap use the size of the text
464
465 w_bmp = self.bmpLabel.GetWidth()+2
466 h_bmp = self.bmpLabel.GetHeight()+2
467 width = w + w_bmp
468 if h_bmp > h:
469 height = h_bmp
470 else:
471 height = h
472 return width, height, True
473
474
475 def DrawLabel(self, dc, width, height, dw=0, dy=0):
476 bmp = self.bmpLabel
477 if bmp != None: # if the bitmap is used
478 if self.bmpDisabled and not self.IsEnabled():
479 bmp = self.bmpDisabled
480 if self.bmpFocus and self.hasFocus:
481 bmp = self.bmpFocus
482 if self.bmpSelected and not self.up:
483 bmp = self.bmpSelected
484 bw,bh = bmp.GetWidth(), bmp.GetHeight()
485 if not self.up:
486 dw = dy = self.labelDelta
487 hasMask = bmp.GetMask() != None
488 else:
489 bw = bh = 0 # no bitmap -> size is zero
490
491 dc.SetFont(self.GetFont())
492 if self.IsEnabled():
493 dc.SetTextForeground(self.GetForegroundColour())
494 else:
495 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
496
497 label = self.GetLabel()
498 tw, th = dc.GetTextExtent(label) # size of text
499 if not self.up:
500 dw = dy = self.labelDelta
501
502 pos_x = (width-bw-tw)/2+dw # adjust for bitmap and text to centre
503 if bmp !=None:
504 dc.DrawBitmap(bmp, pos_x, (height-bh)/2+dy, hasMask) # draw bitmap if available
505 pos_x = pos_x + 2 # extra spacing from bitmap
506
507 dc.DrawText(label, pos_x + dw+bw, (height-th)/2+dy) # draw the text
508
509
510 #----------------------------------------------------------------------
511
512
513 class __ToggleMixin:
514 def SetToggle(self, flag):
515 self.up = not flag
516 self.Refresh()
517 SetValue = SetToggle
518
519 def GetToggle(self):
520 return not self.up
521 GetValue = GetToggle
522
523 def OnLeftDown(self, event):
524 if not self.IsEnabled():
525 return
526 self.saveUp = self.up
527 self.up = not self.up
528 self.CaptureMouse()
529 self.SetFocus()
530 self.Refresh()
531
532 def OnLeftUp(self, event):
533 if not self.IsEnabled() or not self.HasCapture():
534 return
535 if self.HasCapture():
536 self.ReleaseMouse()
537 self.Refresh()
538 if self.up != self.saveUp:
539 self.Notify()
540
541 def OnKeyDown(self, event):
542 event.Skip()
543
544 def OnMotion(self, event):
545 if not self.IsEnabled():
546 return
547 if event.LeftIsDown() and self.HasCapture():
548 x,y = event.GetPositionTuple()
549 w,h = self.GetClientSizeTuple()
550 if x<w and x>=0 and y<h and y>=0:
551 self.up = not self.saveUp
552 self.Refresh()
553 return
554 if (x<0 or y<0 or x>=w or y>=h):
555 self.up = self.saveUp
556 self.Refresh()
557 return
558 event.Skip()
559
560 def OnKeyUp(self, event):
561 if self.hasFocus and event.GetKeyCode() == ord(" "):
562 self.up = not self.up
563 self.Notify()
564 self.Refresh()
565 event.Skip()
566
567
568
569
570 class GenToggleButton(__ToggleMixin, GenButton):
571 """A generic toggle button"""
572 pass
573
574 class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton):
575 """A generic toggle bitmap button"""
576 pass
577
578 class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton):
579 """A generic toggle bitmap button with text label"""
580 pass
581
582 #----------------------------------------------------------------------
583
584 class ThemedGenButton(GenButton):
585 " A themed generic button, and base class for the other themed buttons "
586 def DrawBezel(self, dc, x1, y1, x2, y2):
587 rect = wx.Rect(x1, y1, x2, y2)
588 if self.up:
589 state = 0
590 else:
591 state = wx.CONTROL_PRESSED
592 if not self.IsEnabled():
593 state = wx.CONTROL_DISABLED
594 wx.RendererNative.Get().DrawPushButton(self, dc, rect, state)
595
596 class ThemedGenBitmapButton(ThemedGenButton, GenBitmapButton):
597 """A themed generic bitmap button."""
598 pass
599
600 class ThemedGenBitmapTextButton(ThemedGenButton, GenBitmapTextButton):
601 """A themed generic bitmapped button with text label"""
602 pass
603
604 class ThemedGenToggleButton(ThemedGenButton, GenToggleButton):
605 """A themed generic toggle button"""
606 pass
607
608 class ThemedGenBitmapToggleButton(ThemedGenButton, GenBitmapToggleButton):
609 """A themed generic toggle bitmap button"""
610 pass
611
612 class ThemedGenBitmapTextToggleButton(ThemedGenButton, GenBitmapTextToggleButton):
613 """A themed generic toggle bitmap button with text label"""
614 pass
615
616
617 #----------------------------------------------------------------------