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