]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/buttons.py
f1e2f737a8454d0948ff161b82bd6c31b230306d
[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.SetBestFittingSize(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 SetBestSize(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.SetBestFittingSize(self, size)
103
104
105 def DoGetBestSize(self):
106 """
107 Overridden base class virtual. Determines the best size of the
108 button based on the label and bezel size.
109 """
110 w, h, useMin = self._GetLabelSize()
111 defSize = wx.Button.GetDefaultSize()
112 width = 12 + w
113 if useMin and width < defSize.width:
114 width = defSize.width
115 height = 11 + h
116 if useMin and height < defSize.height:
117 height = defSize.height
118 width = width + self.bezelWidth - 1
119 height = height + self.bezelWidth - 1
120 return (width, height)
121
122
123 def AcceptsFocus(self):
124 """Overridden base class virtual."""
125 return self.IsShown() and self.IsEnabled()
126
127
128 def GetDefaultAttributes(self):
129 """
130 Overridden base class virtual. By default we should use
131 the same font/colour attributes as the native Button.
132 """
133 return wx.Button.GetClassDefaultAttributes()
134
135
136 def ShouldInheritColours(self):
137 """
138 Overridden base class virtual. Buttons usually don't inherit
139 the parent's colours.
140 """
141 return False
142
143
144 def Enable(self, enable=True):
145 wx.PyControl.Enable(self, enable)
146 self.Refresh()
147
148
149 def SetBezelWidth(self, width):
150 """Set the width of the 3D effect"""
151 self.bezelWidth = width
152
153 def GetBezelWidth(self):
154 """Return the width of the 3D effect"""
155 return self.bezelWidth
156
157 def SetUseFocusIndicator(self, flag):
158 """Specifiy if a focus indicator (dotted line) should be used"""
159 self.useFocusInd = flag
160
161 def GetUseFocusIndicator(self):
162 """Return focus indicator flag"""
163 return self.useFocusInd
164
165
166 def InitColours(self):
167 """
168 Calculate a new set of highlight and shadow colours based on
169 the background colour. Works okay if the colour is dark...
170 """
171 faceClr = self.GetBackgroundColour()
172 r, g, b = faceClr.Get()
173 fr, fg, fb = min(255,r+32), min(255,g+32), min(255,b+32)
174 self.faceDnClr = wx.Colour(fr, fg, fb)
175 sr, sg, sb = max(0,r-32), max(0,g-32), max(0,b-32)
176 self.shadowPen = wx.Pen(wx.Colour(sr,sg,sb), 1, wx.SOLID)
177 hr, hg, hb = min(255,r+64), min(255,g+64), min(255,b+64)
178 self.highlightPen = wx.Pen(wx.Colour(hr,hg,hb), 1, wx.SOLID)
179 self.focusClr = wx.Colour(hr, hg, hb)
180
181 textClr = self.GetForegroundColour()
182 if wx.Platform == "__WXMAC__":
183 self.focusIndPen = wx.Pen(textClr, 1, wx.SOLID)
184 else:
185 self.focusIndPen = wx.Pen(textClr, 1, wx.USER_DASH)
186 self.focusIndPen.SetDashes([1,1])
187 self.focusIndPen.SetCap(wx.CAP_BUTT)
188
189
190 def SetBackgroundColour(self, colour):
191 wx.PyControl.SetBackgroundColour(self, colour)
192 self.InitColours()
193
194
195 def SetForegroundColour(self, colour):
196 wx.PyControl.SetForegroundColour(self, colour)
197 self.InitColours()
198
199 def SetDefault(self):
200 self.GetParent().SetDefaultItem(self)
201
202 def _GetLabelSize(self):
203 """ used internally """
204 w, h = self.GetTextExtent(self.GetLabel())
205 return w, h, True
206
207
208 def Notify(self):
209 evt = GenButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
210 evt.SetIsDown(not self.up)
211 evt.SetButtonObj(self)
212 evt.SetEventObject(self)
213 self.GetEventHandler().ProcessEvent(evt)
214
215
216 def DrawBezel(self, dc, x1, y1, x2, y2):
217 # draw the upper left sides
218 if self.up:
219 dc.SetPen(self.highlightPen)
220 else:
221 dc.SetPen(self.shadowPen)
222 for i in range(self.bezelWidth):
223 dc.DrawLine(x1+i, y1, x1+i, y2-i)
224 dc.DrawLine(x1, y1+i, x2-i, y1+i)
225
226 # draw the lower right sides
227 if self.up:
228 dc.SetPen(self.shadowPen)
229 else:
230 dc.SetPen(self.highlightPen)
231 for i in range(self.bezelWidth):
232 dc.DrawLine(x1+i, y2-i, x2+1, y2-i)
233 dc.DrawLine(x2-i, y1+i, x2-i, y2)
234
235
236 def DrawLabel(self, dc, width, height, dw=0, dy=0):
237 dc.SetFont(self.GetFont())
238 if self.IsEnabled():
239 dc.SetTextForeground(self.GetForegroundColour())
240 else:
241 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
242 label = self.GetLabel()
243 tw, th = dc.GetTextExtent(label)
244 if not self.up:
245 dw = dy = self.labelDelta
246 dc.DrawText(label, (width-tw)/2+dw, (height-th)/2+dy)
247
248
249 def DrawFocusIndicator(self, dc, w, h):
250 bw = self.bezelWidth
251 self.focusIndPen.SetColour(self.focusClr)
252 dc.SetLogicalFunction(wx.INVERT)
253 dc.SetPen(self.focusIndPen)
254 dc.SetBrush(wx.TRANSPARENT_BRUSH)
255 dc.DrawRectangle(bw+2,bw+2, w-bw*2-4, h-bw*2-4)
256 dc.SetLogicalFunction(wx.COPY)
257
258 def OnPaint(self, event):
259 (width, height) = self.GetClientSizeTuple()
260 x1 = y1 = 0
261 x2 = width-1
262 y2 = height-1
263
264 dc = wx.PaintDC(self)
265 brush = self.GetBackgroundBrush(dc)
266 if brush is not None:
267 dc.SetBackground(brush)
268 dc.Clear()
269
270 self.DrawBezel(dc, x1, y1, x2, y2)
271 self.DrawLabel(dc, width, height)
272 if self.hasFocus and self.useFocusInd:
273 self.DrawFocusIndicator(dc, width, height)
274
275
276 def GetBackgroundBrush(self, dc):
277 if self.up:
278 colBg = self.GetBackgroundColour()
279 brush = wx.Brush(colBg, wx.SOLID)
280 if self.style & wx.BORDER_NONE:
281 myAttr = self.GetDefaultAttributes()
282 parAttr = self.GetParent().GetDefaultAttributes()
283 myDef = colBg == myAttr.colBg
284 parDef = self.GetParent().GetBackgroundColour() == parAttr.colBg
285 if myDef and parDef:
286 if wx.Platform == "__WXMAC__":
287 brush.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
288 elif wx.Platform == "__WXMSW__":
289 if self.DoEraseBackground(dc):
290 brush = None
291 elif myDef and not parDef:
292 colBg = self.GetParent().GetBackgroundColour()
293 brush = wx.Brush(colBg, wx.SOLID)
294 else:
295 # this line assumes that a pressed button should be hilighted with
296 # a solid colour even if the background is supposed to be transparent
297 brush = wx.Brush(self.faceDnClr, wx.SOLID)
298 return brush
299
300
301 def OnLeftDown(self, event):
302 if not self.IsEnabled():
303 return
304 self.up = False
305 self.CaptureMouse()
306 self.SetFocus()
307 self.Refresh()
308 event.Skip()
309
310
311 def OnLeftUp(self, event):
312 if not self.IsEnabled() or not self.HasCapture():
313 return
314 if self.HasCapture():
315 self.ReleaseMouse()
316 if not self.up: # if the button was down when the mouse was released...
317 self.Notify()
318 self.up = True
319 if self: # in case the button was destroyed in the eventhandler
320 self.Refresh()
321 event.Skip()
322
323
324 def OnMotion(self, event):
325 if not self.IsEnabled() or not self.HasCapture():
326 return
327 if event.LeftIsDown() and self.HasCapture():
328 x,y = event.GetPositionTuple()
329 w,h = self.GetClientSizeTuple()
330 if self.up and x<w and x>=0 and y<h and y>=0:
331 self.up = False
332 self.Refresh()
333 return
334 if not self.up and (x<0 or y<0 or x>=w or y>=h):
335 self.up = True
336 self.Refresh()
337 return
338 event.Skip()
339
340
341 def OnGainFocus(self, event):
342 self.hasFocus = True
343 self.Refresh()
344 self.Update()
345
346
347 def OnLoseFocus(self, event):
348 self.hasFocus = False
349 self.Refresh()
350 self.Update()
351
352
353 def OnKeyDown(self, event):
354 if self.hasFocus and event.GetKeyCode() == ord(" "):
355 self.up = False
356 self.Refresh()
357 event.Skip()
358
359
360 def OnKeyUp(self, event):
361 if self.hasFocus and event.GetKeyCode() == ord(" "):
362 self.up = True
363 self.Notify()
364 self.Refresh()
365 event.Skip()
366
367
368 #----------------------------------------------------------------------
369
370 class GenBitmapButton(GenButton):
371 """A generic bitmap button."""
372
373 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap,
374 pos = wx.DefaultPosition, size = wx.DefaultSize,
375 style = 0, validator = wx.DefaultValidator,
376 name = "genbutton"):
377 self.bmpDisabled = None
378 self.bmpFocus = None
379 self.bmpSelected = None
380 self.SetBitmapLabel(bitmap)
381 GenButton.__init__(self, parent, id, "", pos, size, style, validator, name)
382
383
384 def GetBitmapLabel(self):
385 return self.bmpLabel
386 def GetBitmapDisabled(self):
387 return self.bmpDisabled
388 def GetBitmapFocus(self):
389 return self.bmpFocus
390 def GetBitmapSelected(self):
391 return self.bmpSelected
392
393
394 def SetBitmapDisabled(self, bitmap):
395 """Set bitmap to display when the button is disabled"""
396 self.bmpDisabled = bitmap
397
398 def SetBitmapFocus(self, bitmap):
399 """Set bitmap to display when the button has the focus"""
400 self.bmpFocus = bitmap
401 self.SetUseFocusIndicator(False)
402
403 def SetBitmapSelected(self, bitmap):
404 """Set bitmap to display when the button is selected (pressed down)"""
405 self.bmpSelected = bitmap
406
407 def SetBitmapLabel(self, bitmap, createOthers=True):
408 """
409 Set the bitmap to display normally.
410 This is the only one that is required. If
411 createOthers is True, then the other bitmaps
412 will be generated on the fly. Currently,
413 only the disabled bitmap is generated.
414 """
415 self.bmpLabel = bitmap
416 if bitmap is not None and createOthers:
417 image = wx.ImageFromBitmap(bitmap)
418 imageutils.grayOut(image)
419 self.SetBitmapDisabled(wx.BitmapFromImage(image))
420
421
422 def _GetLabelSize(self):
423 """ used internally """
424 if not self.bmpLabel:
425 return -1, -1, False
426 return self.bmpLabel.GetWidth()+2, self.bmpLabel.GetHeight()+2, False
427
428 def DrawLabel(self, dc, width, height, dw=0, dy=0):
429 bmp = self.bmpLabel
430 if self.bmpDisabled and not self.IsEnabled():
431 bmp = self.bmpDisabled
432 if self.bmpFocus and self.hasFocus:
433 bmp = self.bmpFocus
434 if self.bmpSelected and not self.up:
435 bmp = self.bmpSelected
436 bw,bh = bmp.GetWidth(), bmp.GetHeight()
437 if not self.up:
438 dw = dy = self.labelDelta
439 hasMask = bmp.GetMask() != None
440 dc.DrawBitmap(bmp, (width-bw)/2+dw, (height-bh)/2+dy, hasMask)
441
442
443 #----------------------------------------------------------------------
444
445
446 class GenBitmapTextButton(GenBitmapButton):
447 """A generic bitmapped button with text label"""
448 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap, label='',
449 pos = wx.DefaultPosition, size = wx.DefaultSize,
450 style = 0, validator = wx.DefaultValidator,
451 name = "genbutton"):
452 GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style, validator, name)
453 self.SetLabel(label)
454
455
456 def _GetLabelSize(self):
457 """ used internally """
458 w, h = self.GetTextExtent(self.GetLabel())
459 if not self.bmpLabel:
460 return w, h, True # if there isn't a bitmap use the size of the text
461
462 w_bmp = self.bmpLabel.GetWidth()+2
463 h_bmp = self.bmpLabel.GetHeight()+2
464 width = w + w_bmp
465 if h_bmp > h:
466 height = h_bmp
467 else:
468 height = h
469 return width, height, True
470
471
472 def DrawLabel(self, dc, width, height, dw=0, dy=0):
473 bmp = self.bmpLabel
474 if bmp != None: # if the bitmap is used
475 if self.bmpDisabled and not self.IsEnabled():
476 bmp = self.bmpDisabled
477 if self.bmpFocus and self.hasFocus:
478 bmp = self.bmpFocus
479 if self.bmpSelected and not self.up:
480 bmp = self.bmpSelected
481 bw,bh = bmp.GetWidth(), bmp.GetHeight()
482 if not self.up:
483 dw = dy = self.labelDelta
484 hasMask = bmp.GetMask() != None
485 else:
486 bw = bh = 0 # no bitmap -> size is zero
487
488 dc.SetFont(self.GetFont())
489 if self.IsEnabled():
490 dc.SetTextForeground(self.GetForegroundColour())
491 else:
492 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
493
494 label = self.GetLabel()
495 tw, th = dc.GetTextExtent(label) # size of text
496 if not self.up:
497 dw = dy = self.labelDelta
498
499 pos_x = (width-bw-tw)/2+dw # adjust for bitmap and text to centre
500 if bmp !=None:
501 dc.DrawBitmap(bmp, pos_x, (height-bh)/2+dy, hasMask) # draw bitmap if available
502 pos_x = pos_x + 2 # extra spacing from bitmap
503
504 dc.DrawText(label, pos_x + dw+bw, (height-th)/2+dy) # draw the text
505
506
507 #----------------------------------------------------------------------
508
509
510 class __ToggleMixin:
511 def SetToggle(self, flag):
512 self.up = not flag
513 self.Refresh()
514 SetValue = SetToggle
515
516 def GetToggle(self):
517 return not self.up
518 GetValue = GetToggle
519
520 def OnLeftDown(self, event):
521 if not self.IsEnabled():
522 return
523 self.saveUp = self.up
524 self.up = not self.up
525 self.CaptureMouse()
526 self.SetFocus()
527 self.Refresh()
528
529 def OnLeftUp(self, event):
530 if not self.IsEnabled() or not self.HasCapture():
531 return
532 if self.HasCapture():
533 self.ReleaseMouse()
534 self.Refresh()
535 if self.up != self.saveUp:
536 self.Notify()
537
538 def OnKeyDown(self, event):
539 event.Skip()
540
541 def OnMotion(self, event):
542 if not self.IsEnabled():
543 return
544 if event.LeftIsDown() and self.HasCapture():
545 x,y = event.GetPositionTuple()
546 w,h = self.GetClientSizeTuple()
547 if x<w and x>=0 and y<h and y>=0:
548 self.up = not self.saveUp
549 self.Refresh()
550 return
551 if (x<0 or y<0 or x>=w or y>=h):
552 self.up = self.saveUp
553 self.Refresh()
554 return
555 event.Skip()
556
557 def OnKeyUp(self, event):
558 if self.hasFocus and event.GetKeyCode() == ord(" "):
559 self.up = not self.up
560 self.Notify()
561 self.Refresh()
562 event.Skip()
563
564
565
566
567 class GenToggleButton(__ToggleMixin, GenButton):
568 """A generic toggle button"""
569 pass
570
571 class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton):
572 """A generic toggle bitmap button"""
573 pass
574
575 class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton):
576 """A generic toggle bitmap button with text label"""
577 pass
578
579 #----------------------------------------------------------------------
580
581