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