]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/buttons.py
Migate to the new wxRendererNative::DrawItemSelectionRect function where available...
[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
RD
200 def SetDefault(self):
201 self.GetParent().SetDefaultItem(self)
202
d14a1e28
RD
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):
d7403ad2
RD
224 dc.DrawLine(x1+i, y1, x1+i, y2-i)
225 dc.DrawLine(x1, y1+i, x2-i, y1+i)
d14a1e28
RD
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):
d7403ad2
RD
233 dc.DrawLine(x1+i, y2-i, x2+1, y2-i)
234 dc.DrawLine(x2-i, y1+i, x2-i, y2)
d14a1e28
RD
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
d7403ad2 247 dc.DrawText(label, (width-tw)/2+dw, (height-th)/2+dy)
d14a1e28
RD
248
249
250 def DrawFocusIndicator(self, dc, w, h):
251 bw = self.bezelWidth
d14a1e28
RD
252 self.focusIndPen.SetColour(self.focusClr)
253 dc.SetLogicalFunction(wx.INVERT)
254 dc.SetPen(self.focusIndPen)
255 dc.SetBrush(wx.TRANSPARENT_BRUSH)
d7403ad2 256 dc.DrawRectangle(bw+2,bw+2, w-bw*2-4, h-bw*2-4)
d14a1e28
RD
257 dc.SetLogicalFunction(wx.COPY)
258
d14a1e28
RD
259 def OnPaint(self, event):
260 (width, height) = self.GetClientSizeTuple()
261 x1 = y1 = 0
262 x2 = width-1
263 y2 = height-1
01732ba1 264
fad16503 265 dc = wx.PaintDC(self)
01732ba1
RD
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):
d14a1e28 278 if self.up:
d167fc51
RD
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)
d14a1e28 295 else:
01732ba1
RD
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
d167fc51 298 brush = wx.Brush(self.faceDnClr, wx.SOLID)
01732ba1 299 return brush
d14a1e28
RD
300
301
d14a1e28
RD
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
4e20fcc5
RD
320 if self: # in case the button was destroyed in the eventhandler
321 self.Refresh()
322 event.Skip()
d14a1e28
RD
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
2f643d06
RD
344 self.Refresh()
345 self.Update()
d14a1e28
RD
346
347
348 def OnLoseFocus(self, event):
349 self.hasFocus = False
2f643d06
RD
350 self.Refresh()
351 self.Update()
d14a1e28
RD
352
353
354 def OnKeyDown(self, event):
a3cee65e 355 if self.hasFocus and event.GetKeyCode() == ord(" "):
d14a1e28
RD
356 self.up = False
357 self.Refresh()
358 event.Skip()
359
360
361 def OnKeyUp(self, event):
a3cee65e 362 if self.hasFocus and event.GetKeyCode() == ord(" "):
d14a1e28
RD
363 self.up = True
364 self.Notify()
365 self.Refresh()
366 event.Skip()
367
368
369#----------------------------------------------------------------------
370
371class GenBitmapButton(GenButton):
4e5d278c
RD
372 """A generic bitmap button."""
373
2f643d06 374 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap,
d14a1e28
RD
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)
2f643d06 382 GenButton.__init__(self, parent, id, "", pos, size, style, validator, name)
d14a1e28
RD
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
d7403ad2 441 dc.DrawBitmap(bmp, (width-bw)/2+dw, (height-bh)/2+dy, hasMask)
d14a1e28
RD
442
443
444#----------------------------------------------------------------------
445
446
4e5d278c
RD
447class GenBitmapTextButton(GenBitmapButton):
448 """A generic bitmapped button with text label"""
2f643d06 449 def __init__(self, parent, id=-1, bitmap=wx.NullBitmap, label='',
d14a1e28
RD
450 pos = wx.DefaultPosition, size = wx.DefaultSize,
451 style = 0, validator = wx.DefaultValidator,
452 name = "genbutton"):
2f643d06 453 GenBitmapButton.__init__(self, parent, id, bitmap, pos, size, style, validator, name)
d14a1e28
RD
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:
d7403ad2 502 dc.DrawBitmap(bmp, pos_x, (height-bh)/2+dy, hasMask) # draw bitmap if available
d14a1e28
RD
503 pos_x = pos_x + 2 # extra spacing from bitmap
504
d7403ad2 505 dc.DrawText(label, pos_x + dw+bw, (height-th)/2+dy) # draw the text
d14a1e28
RD
506
507
508#----------------------------------------------------------------------
509
510
511class __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():
d14a1e28
RD
534 self.ReleaseMouse()
535 self.Refresh()
4617be08
RD
536 if self.up != self.saveUp:
537 self.Notify()
d14a1e28
RD
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):
a3cee65e 559 if self.hasFocus and event.GetKeyCode() == ord(" "):
d14a1e28
RD
560 self.up = not self.up
561 self.Notify()
562 self.Refresh()
563 event.Skip()
564
565
566
567
568class GenToggleButton(__ToggleMixin, GenButton):
4e5d278c 569 """A generic toggle button"""
d14a1e28
RD
570 pass
571
572class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton):
4e5d278c 573 """A generic toggle bitmap button"""
d14a1e28
RD
574 pass
575
576class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton):
4e5d278c 577 """A generic toggle bitmap button with text label"""
d14a1e28
RD
578 pass
579
580#----------------------------------------------------------------------
1fded56b 581
1fded56b 582