]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/buttons.py
wx.lib.pubsub updates from Oliver Schoenborn:
[wxWidgets.git] / wxPython / wx / lib / buttons.py
CommitLineData
d14a1e28
RD
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#----------------------------------------------------------------------
b881fc78
RD
13# 11/30/2003 - Jeff Grimmett (grimmtooth@softhome.net)
14#
15# o Updated for wx namespace
16# o Tested with updated demo
17#
d14a1e28
RD
18
19"""
20This module implements various forms of generic buttons, meaning that
4e5d278c
RD
21they are not built on native controls but are self-drawn. They act
22like normal buttons but you are able to better control how they look,
23bevel width, colours, etc.
d14a1e28
RD
24"""
25
26import wx
27import imageutils
28
29
30#----------------------------------------------------------------------
31
32class GenButtonEvent(wx.PyCommandEvent):
4e5d278c 33 """Event sent from the generic buttons when the button is activated. """
2f643d06
RD
34 def __init__(self, eventType, id):
35 wx.PyCommandEvent.__init__(self, eventType, id)
d14a1e28
RD
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
54class GenButton(wx.PyControl):
4e5d278c
RD
55 """A generic button, and base class for the other generic buttons."""
56
d14a1e28
RD
57 labelDelta = 1
58
2f643d06 59 def __init__(self, parent, id=-1, label='',
d14a1e28
RD
60 pos = wx.DefaultPosition, size = wx.DefaultSize,
61 style = 0, validator = wx.DefaultValidator,
62 name = "genbutton"):
fdf907ad
RD
63 cstyle = style
64 if cstyle == 0:
d167fc51 65 cstyle = wx.BORDER_NONE
2f643d06 66 wx.PyControl.__init__(self, parent, id, pos, size, cstyle, validator, name)
d14a1e28
RD
67
68 self.up = True
d14a1e28 69 self.hasFocus = False
d167fc51
RD
70 self.style = style
71 if style & wx.BORDER_NONE:
fdf907ad
RD
72 self.bezelWidth = 0
73 self.useFocusInd = False
74 else:
75 self.bezelWidth = 2
76 self.useFocusInd = True
d14a1e28
RD
77
78 self.SetLabel(label)
969d9b6f 79 self.InheritAttributes()
5193b348 80 self.SetBestFittingSize(size)
d14a1e28
RD
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)
d14a1e28
RD
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:
969d9b6f 101 size = wx.DefaultSize
5193b348 102 wx.PyControl.SetBestFittingSize(self, size)
d14a1e28
RD
103
104
105 def DoGetBestSize(self):
969d9b6f
RD
106 """
107 Overridden base class virtual. Determines the best size of the
108 button based on the label and bezel size.
109 """
d14a1e28
RD
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
969d9b6f
RD
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
d14a1e28
RD
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):
969d9b6f
RD
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()
d14a1e28
RD
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)
969d9b6f
RD
188
189
d14a1e28
RD
190 def SetBackgroundColour(self, colour):
191 wx.PyControl.SetBackgroundColour(self, colour)
969d9b6f 192 self.InitColours()
d14a1e28 193
969d9b6f
RD
194
195 def SetForegroundColour(self, colour):
196 wx.PyControl.SetForegroundColour(self, colour)
197 self.InitColours()
d14a1e28 198
fb113b16
RD
199 def SetDefault(self):
200 self.GetParent().SetDefaultItem(self)
201
d14a1e28
RD
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):
d7403ad2
RD
223 dc.DrawLine(x1+i, y1, x1+i, y2-i)
224 dc.DrawLine(x1, y1+i, x2-i, y1+i)
d14a1e28
RD
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):
d7403ad2
RD
232 dc.DrawLine(x1+i, y2-i, x2+1, y2-i)
233 dc.DrawLine(x2-i, y1+i, x2-i, y2)
d14a1e28
RD
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
d7403ad2 246 dc.DrawText(label, (width-tw)/2+dw, (height-th)/2+dy)
d14a1e28
RD
247
248
249 def DrawFocusIndicator(self, dc, w, h):
250 bw = self.bezelWidth
d14a1e28
RD
251 self.focusIndPen.SetColour(self.focusClr)
252 dc.SetLogicalFunction(wx.INVERT)
253 dc.SetPen(self.focusIndPen)
254 dc.SetBrush(wx.TRANSPARENT_BRUSH)
d7403ad2 255 dc.DrawRectangle(bw+2,bw+2, w-bw*2-4, h-bw*2-4)
d14a1e28
RD
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
d167fc51 264
d14a1e28 265 dc = wx.BufferedPaintDC(self)
d167fc51
RD
266 brush = None
267
d14a1e28 268 if self.up:
d167fc51
RD
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)
d14a1e28 285 else:
d167fc51
RD
286 brush = wx.Brush(self.faceDnClr, wx.SOLID)
287 if brush is not None:
288 dc.SetBackground(brush)
289 dc.Clear()
290
d14a1e28
RD
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
d14a1e28
RD
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 self.Refresh()
316 event.Skip()
317
318
319 def OnMotion(self, event):
320 if not self.IsEnabled() or not self.HasCapture():
321 return
322 if event.LeftIsDown() and self.HasCapture():
323 x,y = event.GetPositionTuple()
324 w,h = self.GetClientSizeTuple()
325 if self.up and x<w and x>=0 and y<h and y>=0:
326 self.up = False
327 self.Refresh()
328 return
329 if not self.up and (x<0 or y<0 or x>=w or y>=h):
330 self.up = True
331 self.Refresh()
332 return
333 event.Skip()
334
335
336 def OnGainFocus(self, event):
337 self.hasFocus = True
2f643d06
RD
338 self.Refresh()
339 self.Update()
d14a1e28
RD
340
341
342 def OnLoseFocus(self, event):
343 self.hasFocus = False
2f643d06
RD
344 self.Refresh()
345 self.Update()
d14a1e28
RD
346
347
348 def OnKeyDown(self, event):
349 if self.hasFocus and event.KeyCode() == ord(" "):
350 self.up = False
351 self.Refresh()
352 event.Skip()
353
354
355 def OnKeyUp(self, event):
356 if self.hasFocus and event.KeyCode() == ord(" "):
357 self.up = True
358 self.Notify()
359 self.Refresh()
360 event.Skip()
361
362
363#----------------------------------------------------------------------
364
365class GenBitmapButton(GenButton):
4e5d278c
RD
366 """A generic bitmap button."""
367
2f643d06 368 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap,
d14a1e28
RD
369 pos = wx.DefaultPosition, size = wx.DefaultSize,
370 style = 0, validator = wx.DefaultValidator,
371 name = "genbutton"):
372 self.bmpDisabled = None
373 self.bmpFocus = None
374 self.bmpSelected = None
375 self.SetBitmapLabel(bitmap)
2f643d06 376 GenButton.__init__(self, parent, id, "", pos, size, style, validator, name)
d14a1e28
RD
377
378
379 def GetBitmapLabel(self):
380 return self.bmpLabel
381 def GetBitmapDisabled(self):
382 return self.bmpDisabled
383 def GetBitmapFocus(self):
384 return self.bmpFocus
385 def GetBitmapSelected(self):
386 return self.bmpSelected
387
388
389 def SetBitmapDisabled(self, bitmap):
390 """Set bitmap to display when the button is disabled"""
391 self.bmpDisabled = bitmap
392
393 def SetBitmapFocus(self, bitmap):
394 """Set bitmap to display when the button has the focus"""
395 self.bmpFocus = bitmap
396 self.SetUseFocusIndicator(False)
397
398 def SetBitmapSelected(self, bitmap):
399 """Set bitmap to display when the button is selected (pressed down)"""
400 self.bmpSelected = bitmap
401
402 def SetBitmapLabel(self, bitmap, createOthers=True):
403 """
404 Set the bitmap to display normally.
405 This is the only one that is required. If
406 createOthers is True, then the other bitmaps
407 will be generated on the fly. Currently,
408 only the disabled bitmap is generated.
409 """
410 self.bmpLabel = bitmap
411 if bitmap is not None and createOthers:
412 image = wx.ImageFromBitmap(bitmap)
413 imageutils.grayOut(image)
414 self.SetBitmapDisabled(wx.BitmapFromImage(image))
415
416
417 def _GetLabelSize(self):
418 """ used internally """
419 if not self.bmpLabel:
420 return -1, -1, False
421 return self.bmpLabel.GetWidth()+2, self.bmpLabel.GetHeight()+2, False
422
423 def DrawLabel(self, dc, width, height, dw=0, dy=0):
424 bmp = self.bmpLabel
425 if self.bmpDisabled and not self.IsEnabled():
426 bmp = self.bmpDisabled
427 if self.bmpFocus and self.hasFocus:
428 bmp = self.bmpFocus
429 if self.bmpSelected and not self.up:
430 bmp = self.bmpSelected
431 bw,bh = bmp.GetWidth(), bmp.GetHeight()
432 if not self.up:
433 dw = dy = self.labelDelta
434 hasMask = bmp.GetMask() != None
d7403ad2 435 dc.DrawBitmap(bmp, (width-bw)/2+dw, (height-bh)/2+dy, hasMask)
d14a1e28
RD
436
437
438#----------------------------------------------------------------------
439
440
4e5d278c
RD
441class GenBitmapTextButton(GenBitmapButton):
442 """A generic bitmapped button with text label"""
2f643d06 443 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap, label='',
d14a1e28
RD
444 pos = wx.DefaultPosition, size = wx.DefaultSize,
445 style = 0, validator = wx.DefaultValidator,
446 name = "genbutton"):
2f643d06 447 GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style, validator, name)
d14a1e28
RD
448 self.SetLabel(label)
449
450
451 def _GetLabelSize(self):
452 """ used internally """
453 w, h = self.GetTextExtent(self.GetLabel())
454 if not self.bmpLabel:
455 return w, h, True # if there isn't a bitmap use the size of the text
456
457 w_bmp = self.bmpLabel.GetWidth()+2
458 h_bmp = self.bmpLabel.GetHeight()+2
459 width = w + w_bmp
460 if h_bmp > h:
461 height = h_bmp
462 else:
463 height = h
464 return width, height, True
465
466
467 def DrawLabel(self, dc, width, height, dw=0, dy=0):
468 bmp = self.bmpLabel
469 if bmp != None: # if the bitmap is used
470 if self.bmpDisabled and not self.IsEnabled():
471 bmp = self.bmpDisabled
472 if self.bmpFocus and self.hasFocus:
473 bmp = self.bmpFocus
474 if self.bmpSelected and not self.up:
475 bmp = self.bmpSelected
476 bw,bh = bmp.GetWidth(), bmp.GetHeight()
477 if not self.up:
478 dw = dy = self.labelDelta
479 hasMask = bmp.GetMask() != None
480 else:
481 bw = bh = 0 # no bitmap -> size is zero
482
483 dc.SetFont(self.GetFont())
484 if self.IsEnabled():
485 dc.SetTextForeground(self.GetForegroundColour())
486 else:
487 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
488
489 label = self.GetLabel()
490 tw, th = dc.GetTextExtent(label) # size of text
491 if not self.up:
492 dw = dy = self.labelDelta
493
494 pos_x = (width-bw-tw)/2+dw # adjust for bitmap and text to centre
495 if bmp !=None:
d7403ad2 496 dc.DrawBitmap(bmp, pos_x, (height-bh)/2+dy, hasMask) # draw bitmap if available
d14a1e28
RD
497 pos_x = pos_x + 2 # extra spacing from bitmap
498
d7403ad2 499 dc.DrawText(label, pos_x + dw+bw, (height-th)/2+dy) # draw the text
d14a1e28
RD
500
501
502#----------------------------------------------------------------------
503
504
505class __ToggleMixin:
506 def SetToggle(self, flag):
507 self.up = not flag
508 self.Refresh()
509 SetValue = SetToggle
510
511 def GetToggle(self):
512 return not self.up
513 GetValue = GetToggle
514
515 def OnLeftDown(self, event):
516 if not self.IsEnabled():
517 return
518 self.saveUp = self.up
519 self.up = not self.up
520 self.CaptureMouse()
521 self.SetFocus()
522 self.Refresh()
523
524 def OnLeftUp(self, event):
525 if not self.IsEnabled() or not self.HasCapture():
526 return
527 if self.HasCapture():
d14a1e28
RD
528 self.ReleaseMouse()
529 self.Refresh()
4617be08
RD
530 if self.up != self.saveUp:
531 self.Notify()
d14a1e28
RD
532
533 def OnKeyDown(self, event):
534 event.Skip()
535
536 def OnMotion(self, event):
537 if not self.IsEnabled():
538 return
539 if event.LeftIsDown() and self.HasCapture():
540 x,y = event.GetPositionTuple()
541 w,h = self.GetClientSizeTuple()
542 if x<w and x>=0 and y<h and y>=0:
543 self.up = not self.saveUp
544 self.Refresh()
545 return
546 if (x<0 or y<0 or x>=w or y>=h):
547 self.up = self.saveUp
548 self.Refresh()
549 return
550 event.Skip()
551
552 def OnKeyUp(self, event):
553 if self.hasFocus and event.KeyCode() == ord(" "):
554 self.up = not self.up
555 self.Notify()
556 self.Refresh()
557 event.Skip()
558
559
560
561
562class GenToggleButton(__ToggleMixin, GenButton):
4e5d278c 563 """A generic toggle button"""
d14a1e28
RD
564 pass
565
566class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton):
4e5d278c 567 """A generic toggle bitmap button"""
d14a1e28
RD
568 pass
569
570class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton):
4e5d278c 571 """A generic toggle bitmap button with text label"""
d14a1e28
RD
572 pass
573
574#----------------------------------------------------------------------
1fded56b 575
1fded56b 576