]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/buttons.py
some tweaks
[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
259 def OnPaint(self, event):
260 (width, height) = self.GetClientSizeTuple()
261 x1 = y1 = 0
262 x2 = width-1
263 y2 = height-1
264
265 dc = wx.PaintDC(self)
266 brush = None
267
268 if self.up:
269 colBg = self.GetBackgroundColour()
270 brush = wx.Brush(colBg, wx.SOLID)
271 if self.style & wx.BORDER_NONE:
272 myAttr = self.GetDefaultAttributes()
273 parAttr = self.GetParent().GetDefaultAttributes()
274 myDef = colBg == myAttr.colBg
275 parDef = self.GetParent().GetBackgroundColour() == parAttr.colBg
276 if myDef and parDef:
277 if wx.Platform == "__WXMAC__":
278 brush.MacSetTheme(1) # 1 == kThemeBrushDialogBackgroundActive
279 elif wx.Platform == "__WXMSW__":
280 if self.DoEraseBackground(dc):
281 brush = None
282 elif myDef and not parDef:
283 colBg = self.GetParent().GetBackgroundColour()
284 brush = wx.Brush(colBg, wx.SOLID)
285 else:
286 brush = wx.Brush(self.faceDnClr, wx.SOLID)
287 if brush is not None:
288 dc.SetBackground(brush)
289 dc.Clear()
290
291 self.DrawBezel(dc, x1, y1, x2, y2)
292 self.DrawLabel(dc, width, height)
293 if self.hasFocus and self.useFocusInd:
294 self.DrawFocusIndicator(dc, width, height)
295
296
297 def OnLeftDown(self, event):
298 if not self.IsEnabled():
299 return
300 self.up = False
301 self.CaptureMouse()
302 self.SetFocus()
303 self.Refresh()
304 event.Skip()
305
306
307 def OnLeftUp(self, event):
308 if not self.IsEnabled() or not self.HasCapture():
309 return
310 if self.HasCapture():
311 self.ReleaseMouse()
312 if not self.up: # if the button was down when the mouse was released...
313 self.Notify()
314 self.up = True
315 if self: # in case the button was destroyed in the eventhandler
316 self.Refresh()
317 event.Skip()
318
319
320 def OnMotion(self, event):
321 if not self.IsEnabled() or not self.HasCapture():
322 return
323 if event.LeftIsDown() and self.HasCapture():
324 x,y = event.GetPositionTuple()
325 w,h = self.GetClientSizeTuple()
326 if self.up and x<w and x>=0 and y<h and y>=0:
327 self.up = False
328 self.Refresh()
329 return
330 if not self.up and (x<0 or y<0 or x>=w or y>=h):
331 self.up = True
332 self.Refresh()
333 return
334 event.Skip()
335
336
337 def OnGainFocus(self, event):
338 self.hasFocus = True
339 self.Refresh()
340 self.Update()
341
342
343 def OnLoseFocus(self, event):
344 self.hasFocus = False
345 self.Refresh()
346 self.Update()
347
348
349 def OnKeyDown(self, event):
350 if self.hasFocus and event.GetKeyCode() == ord(" "):
351 self.up = False
352 self.Refresh()
353 event.Skip()
354
355
356 def OnKeyUp(self, event):
357 if self.hasFocus and event.GetKeyCode() == ord(" "):
358 self.up = True
359 self.Notify()
360 self.Refresh()
361 event.Skip()
362
363
364 #----------------------------------------------------------------------
365
366 class GenBitmapButton(GenButton):
367 """A generic bitmap button."""
368
369 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap,
370 pos = wx.DefaultPosition, size = wx.DefaultSize,
371 style = 0, validator = wx.DefaultValidator,
372 name = "genbutton"):
373 self.bmpDisabled = None
374 self.bmpFocus = None
375 self.bmpSelected = None
376 self.SetBitmapLabel(bitmap)
377 GenButton.__init__(self, parent, id, "", pos, size, style, validator, name)
378
379
380 def GetBitmapLabel(self):
381 return self.bmpLabel
382 def GetBitmapDisabled(self):
383 return self.bmpDisabled
384 def GetBitmapFocus(self):
385 return self.bmpFocus
386 def GetBitmapSelected(self):
387 return self.bmpSelected
388
389
390 def SetBitmapDisabled(self, bitmap):
391 """Set bitmap to display when the button is disabled"""
392 self.bmpDisabled = bitmap
393
394 def SetBitmapFocus(self, bitmap):
395 """Set bitmap to display when the button has the focus"""
396 self.bmpFocus = bitmap
397 self.SetUseFocusIndicator(False)
398
399 def SetBitmapSelected(self, bitmap):
400 """Set bitmap to display when the button is selected (pressed down)"""
401 self.bmpSelected = bitmap
402
403 def SetBitmapLabel(self, bitmap, createOthers=True):
404 """
405 Set the bitmap to display normally.
406 This is the only one that is required. If
407 createOthers is True, then the other bitmaps
408 will be generated on the fly. Currently,
409 only the disabled bitmap is generated.
410 """
411 self.bmpLabel = bitmap
412 if bitmap is not None and createOthers:
413 image = wx.ImageFromBitmap(bitmap)
414 imageutils.grayOut(image)
415 self.SetBitmapDisabled(wx.BitmapFromImage(image))
416
417
418 def _GetLabelSize(self):
419 """ used internally """
420 if not self.bmpLabel:
421 return -1, -1, False
422 return self.bmpLabel.GetWidth()+2, self.bmpLabel.GetHeight()+2, False
423
424 def DrawLabel(self, dc, width, height, dw=0, dy=0):
425 bmp = self.bmpLabel
426 if self.bmpDisabled and not self.IsEnabled():
427 bmp = self.bmpDisabled
428 if self.bmpFocus and self.hasFocus:
429 bmp = self.bmpFocus
430 if self.bmpSelected and not self.up:
431 bmp = self.bmpSelected
432 bw,bh = bmp.GetWidth(), bmp.GetHeight()
433 if not self.up:
434 dw = dy = self.labelDelta
435 hasMask = bmp.GetMask() != None
436 dc.DrawBitmap(bmp, (width-bw)/2+dw, (height-bh)/2+dy, hasMask)
437
438
439 #----------------------------------------------------------------------
440
441
442 class GenBitmapTextButton(GenBitmapButton):
443 """A generic bitmapped button with text label"""
444 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap, label='',
445 pos = wx.DefaultPosition, size = wx.DefaultSize,
446 style = 0, validator = wx.DefaultValidator,
447 name = "genbutton"):
448 GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style, validator, name)
449 self.SetLabel(label)
450
451
452 def _GetLabelSize(self):
453 """ used internally """
454 w, h = self.GetTextExtent(self.GetLabel())
455 if not self.bmpLabel:
456 return w, h, True # if there isn't a bitmap use the size of the text
457
458 w_bmp = self.bmpLabel.GetWidth()+2
459 h_bmp = self.bmpLabel.GetHeight()+2
460 width = w + w_bmp
461 if h_bmp > h:
462 height = h_bmp
463 else:
464 height = h
465 return width, height, True
466
467
468 def DrawLabel(self, dc, width, height, dw=0, dy=0):
469 bmp = self.bmpLabel
470 if bmp != None: # if the bitmap is used
471 if self.bmpDisabled and not self.IsEnabled():
472 bmp = self.bmpDisabled
473 if self.bmpFocus and self.hasFocus:
474 bmp = self.bmpFocus
475 if self.bmpSelected and not self.up:
476 bmp = self.bmpSelected
477 bw,bh = bmp.GetWidth(), bmp.GetHeight()
478 if not self.up:
479 dw = dy = self.labelDelta
480 hasMask = bmp.GetMask() != None
481 else:
482 bw = bh = 0 # no bitmap -> size is zero
483
484 dc.SetFont(self.GetFont())
485 if self.IsEnabled():
486 dc.SetTextForeground(self.GetForegroundColour())
487 else:
488 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
489
490 label = self.GetLabel()
491 tw, th = dc.GetTextExtent(label) # size of text
492 if not self.up:
493 dw = dy = self.labelDelta
494
495 pos_x = (width-bw-tw)/2+dw # adjust for bitmap and text to centre
496 if bmp !=None:
497 dc.DrawBitmap(bmp, pos_x, (height-bh)/2+dy, hasMask) # draw bitmap if available
498 pos_x = pos_x + 2 # extra spacing from bitmap
499
500 dc.DrawText(label, pos_x + dw+bw, (height-th)/2+dy) # draw the text
501
502
503 #----------------------------------------------------------------------
504
505
506 class __ToggleMixin:
507 def SetToggle(self, flag):
508 self.up = not flag
509 self.Refresh()
510 SetValue = SetToggle
511
512 def GetToggle(self):
513 return not self.up
514 GetValue = GetToggle
515
516 def OnLeftDown(self, event):
517 if not self.IsEnabled():
518 return
519 self.saveUp = self.up
520 self.up = not self.up
521 self.CaptureMouse()
522 self.SetFocus()
523 self.Refresh()
524
525 def OnLeftUp(self, event):
526 if not self.IsEnabled() or not self.HasCapture():
527 return
528 if self.HasCapture():
529 self.ReleaseMouse()
530 self.Refresh()
531 if self.up != self.saveUp:
532 self.Notify()
533
534 def OnKeyDown(self, event):
535 event.Skip()
536
537 def OnMotion(self, event):
538 if not self.IsEnabled():
539 return
540 if event.LeftIsDown() and self.HasCapture():
541 x,y = event.GetPositionTuple()
542 w,h = self.GetClientSizeTuple()
543 if x<w and x>=0 and y<h and y>=0:
544 self.up = not self.saveUp
545 self.Refresh()
546 return
547 if (x<0 or y<0 or x>=w or y>=h):
548 self.up = self.saveUp
549 self.Refresh()
550 return
551 event.Skip()
552
553 def OnKeyUp(self, event):
554 if self.hasFocus and event.GetKeyCode() == ord(" "):
555 self.up = not self.up
556 self.Notify()
557 self.Refresh()
558 event.Skip()
559
560
561
562
563 class GenToggleButton(__ToggleMixin, GenButton):
564 """A generic toggle button"""
565 pass
566
567 class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton):
568 """A generic toggle bitmap button"""
569 pass
570
571 class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton):
572 """A generic toggle bitmap button with text label"""
573 pass
574
575 #----------------------------------------------------------------------
576
577