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