]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/buttons.py
Patches from Will Sadkin:
[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.
22
23 The GenButton is the base. It acts like a normal button but you
24 are able to better control how it looks, bevel width, colours, etc.
25
26 GenBitmapButton is a button with one or more bitmaps that show
27 the various states the button can be in.
28
29 GenToggleButton stays depressed when clicked, until clicked again.
30
31 GenBitmapToggleButton the same but with bitmaps.
32
33 """
34
35 import wx
36 import imageutils
37
38
39 #----------------------------------------------------------------------
40
41 class GenButtonEvent(wx.PyCommandEvent):
42 def __init__(self, eventType, ID):
43 wx.PyCommandEvent.__init__(self, eventType, ID)
44 self.isDown = False
45 self.theButton = None
46
47 def SetIsDown(self, isDown):
48 self.isDown = isDown
49
50 def GetIsDown(self):
51 return self.isDown
52
53 def SetButtonObj(self, btn):
54 self.theButton = btn
55
56 def GetButtonObj(self):
57 return self.theButton
58
59
60 #----------------------------------------------------------------------
61
62 class GenButton(wx.PyControl):
63 labelDelta = 1
64
65 def __init__(self, parent, ID, label,
66 pos = wx.DefaultPosition, size = wx.DefaultSize,
67 style = 0, validator = wx.DefaultValidator,
68 name = "genbutton"):
69 if style == 0:
70 style = wx.NO_BORDER
71 wx.PyControl.__init__(self, parent, ID, pos, size, style, validator, name)
72
73 self.up = True
74 self.bezelWidth = 2
75 self.hasFocus = False
76 self.useFocusInd = True
77
78 self.SetLabel(label)
79 self.InheritAttributes()
80 self.SetBestSize(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_ERASE_BACKGROUND, self.OnEraseBackground)
93 self.Bind(wx.EVT_PAINT, self.OnPaint)
94
95
96 def SetBestSize(self, size=None):
97 """
98 Given the current font and bezel width settings, calculate
99 and set a good size.
100 """
101 if size is None:
102 size = wx.DefaultSize
103 wx.PyControl.SetBestSize(self, size)
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
201 def _GetLabelSize(self):
202 """ used internally """
203 w, h = self.GetTextExtent(self.GetLabel())
204 return w, h, True
205
206
207 def Notify(self):
208 evt = GenButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
209 evt.SetIsDown(not self.up)
210 evt.SetButtonObj(self)
211 evt.SetEventObject(self)
212 self.GetEventHandler().ProcessEvent(evt)
213
214
215 def DrawBezel(self, dc, x1, y1, x2, y2):
216 # draw the upper left sides
217 if self.up:
218 dc.SetPen(self.highlightPen)
219 else:
220 dc.SetPen(self.shadowPen)
221 for i in range(self.bezelWidth):
222 dc.DrawLine(x1+i, y1, x1+i, y2-i)
223 dc.DrawLine(x1, y1+i, x2-i, y1+i)
224
225 # draw the lower right sides
226 if self.up:
227 dc.SetPen(self.shadowPen)
228 else:
229 dc.SetPen(self.highlightPen)
230 for i in range(self.bezelWidth):
231 dc.DrawLine(x1+i, y2-i, x2+1, y2-i)
232 dc.DrawLine(x2-i, y1+i, x2-i, y2)
233
234
235 def DrawLabel(self, dc, width, height, dw=0, dy=0):
236 dc.SetFont(self.GetFont())
237 if self.IsEnabled():
238 dc.SetTextForeground(self.GetForegroundColour())
239 else:
240 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
241 label = self.GetLabel()
242 tw, th = dc.GetTextExtent(label)
243 if not self.up:
244 dw = dy = self.labelDelta
245 dc.DrawText(label, (width-tw)/2+dw, (height-th)/2+dy)
246
247
248 def DrawFocusIndicator(self, dc, w, h):
249 bw = self.bezelWidth
250 self.focusIndPen.SetColour(self.focusClr)
251 dc.SetLogicalFunction(wx.INVERT)
252 dc.SetPen(self.focusIndPen)
253 dc.SetBrush(wx.TRANSPARENT_BRUSH)
254 dc.DrawRectangle(bw+2,bw+2, w-bw*2-4, h-bw*2-4)
255 dc.SetLogicalFunction(wx.COPY)
256
257
258 def OnPaint(self, event):
259 (width, height) = self.GetClientSizeTuple()
260 x1 = y1 = 0
261 x2 = width-1
262 y2 = height-1
263 dc = wx.BufferedPaintDC(self)
264 if self.up:
265 dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.SOLID))
266 else:
267 dc.SetBackground(wx.Brush(self.faceDnClr, wx.SOLID))
268 dc.Clear()
269 self.DrawBezel(dc, x1, y1, x2, y2)
270 self.DrawLabel(dc, width, height)
271 if self.hasFocus and self.useFocusInd:
272 self.DrawFocusIndicator(dc, width, height)
273
274
275 def OnEraseBackground(self, event):
276 pass
277
278
279 def OnLeftDown(self, event):
280 if not self.IsEnabled():
281 return
282 self.up = False
283 self.CaptureMouse()
284 self.SetFocus()
285 self.Refresh()
286 event.Skip()
287
288
289 def OnLeftUp(self, event):
290 if not self.IsEnabled() or not self.HasCapture():
291 return
292 if self.HasCapture():
293 self.ReleaseMouse()
294 if not self.up: # if the button was down when the mouse was released...
295 self.Notify()
296 self.up = True
297 self.Refresh()
298 event.Skip()
299
300
301 def OnMotion(self, event):
302 if not self.IsEnabled() or not self.HasCapture():
303 return
304 if event.LeftIsDown() and self.HasCapture():
305 x,y = event.GetPositionTuple()
306 w,h = self.GetClientSizeTuple()
307 if self.up and x<w and x>=0 and y<h and y>=0:
308 self.up = False
309 self.Refresh()
310 return
311 if not self.up and (x<0 or y<0 or x>=w or y>=h):
312 self.up = True
313 self.Refresh()
314 return
315 event.Skip()
316
317
318 def OnGainFocus(self, event):
319 self.hasFocus = True
320 dc = wx.ClientDC(self)
321 w, h = self.GetClientSizeTuple()
322 if self.useFocusInd:
323 self.DrawFocusIndicator(dc, w, h)
324
325
326 def OnLoseFocus(self, event):
327 self.hasFocus = False
328 dc = wx.ClientDC(self)
329 w, h = self.GetClientSizeTuple()
330 if self.useFocusInd:
331 self.DrawFocusIndicator(dc, w, h)
332
333
334 def OnKeyDown(self, event):
335 if self.hasFocus and event.KeyCode() == ord(" "):
336 self.up = False
337 self.Refresh()
338 event.Skip()
339
340
341 def OnKeyUp(self, event):
342 if self.hasFocus and event.KeyCode() == ord(" "):
343 self.up = True
344 self.Notify()
345 self.Refresh()
346 event.Skip()
347
348
349 #----------------------------------------------------------------------
350
351 class GenBitmapButton(GenButton):
352 def __init__(self, parent, ID, bitmap,
353 pos = wx.DefaultPosition, size = wx.DefaultSize,
354 style = 0, validator = wx.DefaultValidator,
355 name = "genbutton"):
356 self.bmpDisabled = None
357 self.bmpFocus = None
358 self.bmpSelected = None
359 self.SetBitmapLabel(bitmap)
360 GenButton.__init__(self, parent, ID, "", pos, size, style, validator, name)
361
362
363 def GetBitmapLabel(self):
364 return self.bmpLabel
365 def GetBitmapDisabled(self):
366 return self.bmpDisabled
367 def GetBitmapFocus(self):
368 return self.bmpFocus
369 def GetBitmapSelected(self):
370 return self.bmpSelected
371
372
373 def SetBitmapDisabled(self, bitmap):
374 """Set bitmap to display when the button is disabled"""
375 self.bmpDisabled = bitmap
376
377 def SetBitmapFocus(self, bitmap):
378 """Set bitmap to display when the button has the focus"""
379 self.bmpFocus = bitmap
380 self.SetUseFocusIndicator(False)
381
382 def SetBitmapSelected(self, bitmap):
383 """Set bitmap to display when the button is selected (pressed down)"""
384 self.bmpSelected = bitmap
385
386 def SetBitmapLabel(self, bitmap, createOthers=True):
387 """
388 Set the bitmap to display normally.
389 This is the only one that is required. If
390 createOthers is True, then the other bitmaps
391 will be generated on the fly. Currently,
392 only the disabled bitmap is generated.
393 """
394 self.bmpLabel = bitmap
395 if bitmap is not None and createOthers:
396 image = wx.ImageFromBitmap(bitmap)
397 imageutils.grayOut(image)
398 self.SetBitmapDisabled(wx.BitmapFromImage(image))
399
400
401 def _GetLabelSize(self):
402 """ used internally """
403 if not self.bmpLabel:
404 return -1, -1, False
405 return self.bmpLabel.GetWidth()+2, self.bmpLabel.GetHeight()+2, False
406
407 def DrawLabel(self, dc, width, height, dw=0, dy=0):
408 bmp = self.bmpLabel
409 if self.bmpDisabled and not self.IsEnabled():
410 bmp = self.bmpDisabled
411 if self.bmpFocus and self.hasFocus:
412 bmp = self.bmpFocus
413 if self.bmpSelected and not self.up:
414 bmp = self.bmpSelected
415 bw,bh = bmp.GetWidth(), bmp.GetHeight()
416 if not self.up:
417 dw = dy = self.labelDelta
418 hasMask = bmp.GetMask() != None
419 dc.DrawBitmap(bmp, (width-bw)/2+dw, (height-bh)/2+dy, hasMask)
420
421
422 #----------------------------------------------------------------------
423
424
425 class GenBitmapTextButton(GenBitmapButton): # generic bitmapped button with Text Label
426 def __init__(self, parent, ID, bitmap, label,
427 pos = wx.DefaultPosition, size = wx.DefaultSize,
428 style = 0, validator = wx.DefaultValidator,
429 name = "genbutton"):
430 GenBitmapButton.__init__(self, parent, ID, bitmap, pos, size, style, validator, name)
431 self.SetLabel(label)
432
433
434 def _GetLabelSize(self):
435 """ used internally """
436 w, h = self.GetTextExtent(self.GetLabel())
437 if not self.bmpLabel:
438 return w, h, True # if there isn't a bitmap use the size of the text
439
440 w_bmp = self.bmpLabel.GetWidth()+2
441 h_bmp = self.bmpLabel.GetHeight()+2
442 width = w + w_bmp
443 if h_bmp > h:
444 height = h_bmp
445 else:
446 height = h
447 return width, height, True
448
449
450 def DrawLabel(self, dc, width, height, dw=0, dy=0):
451 bmp = self.bmpLabel
452 if bmp != None: # if the bitmap is used
453 if self.bmpDisabled and not self.IsEnabled():
454 bmp = self.bmpDisabled
455 if self.bmpFocus and self.hasFocus:
456 bmp = self.bmpFocus
457 if self.bmpSelected and not self.up:
458 bmp = self.bmpSelected
459 bw,bh = bmp.GetWidth(), bmp.GetHeight()
460 if not self.up:
461 dw = dy = self.labelDelta
462 hasMask = bmp.GetMask() != None
463 else:
464 bw = bh = 0 # no bitmap -> size is zero
465
466 dc.SetFont(self.GetFont())
467 if self.IsEnabled():
468 dc.SetTextForeground(self.GetForegroundColour())
469 else:
470 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
471
472 label = self.GetLabel()
473 tw, th = dc.GetTextExtent(label) # size of text
474 if not self.up:
475 dw = dy = self.labelDelta
476
477 pos_x = (width-bw-tw)/2+dw # adjust for bitmap and text to centre
478 if bmp !=None:
479 dc.DrawBitmap(bmp, pos_x, (height-bh)/2+dy, hasMask) # draw bitmap if available
480 pos_x = pos_x + 2 # extra spacing from bitmap
481
482 dc.DrawText(label, pos_x + dw+bw, (height-th)/2+dy) # draw the text
483
484
485 #----------------------------------------------------------------------
486
487
488 class __ToggleMixin:
489 def SetToggle(self, flag):
490 self.up = not flag
491 self.Refresh()
492 SetValue = SetToggle
493
494 def GetToggle(self):
495 return not self.up
496 GetValue = GetToggle
497
498 def OnLeftDown(self, event):
499 if not self.IsEnabled():
500 return
501 self.saveUp = self.up
502 self.up = not self.up
503 self.CaptureMouse()
504 self.SetFocus()
505 self.Refresh()
506
507 def OnLeftUp(self, event):
508 if not self.IsEnabled() or not self.HasCapture():
509 return
510 if self.HasCapture():
511 if self.up != self.saveUp:
512 self.Notify()
513 self.ReleaseMouse()
514 self.Refresh()
515
516 def OnKeyDown(self, event):
517 event.Skip()
518
519 def OnMotion(self, event):
520 if not self.IsEnabled():
521 return
522 if event.LeftIsDown() and self.HasCapture():
523 x,y = event.GetPositionTuple()
524 w,h = self.GetClientSizeTuple()
525 if x<w and x>=0 and y<h and y>=0:
526 self.up = not self.saveUp
527 self.Refresh()
528 return
529 if (x<0 or y<0 or x>=w or y>=h):
530 self.up = self.saveUp
531 self.Refresh()
532 return
533 event.Skip()
534
535 def OnKeyUp(self, event):
536 if self.hasFocus and event.KeyCode() == ord(" "):
537 self.up = not self.up
538 self.Notify()
539 self.Refresh()
540 event.Skip()
541
542
543
544
545 class GenToggleButton(__ToggleMixin, GenButton):
546 pass
547
548 class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton):
549 pass
550
551 class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton):
552 pass
553
554 #----------------------------------------------------------------------
555
556