]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/buttons.py
Set the DC font before drawing the label
[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.
22
23 The GenButton is the base. It acts like a normal button but you
24 are able to better control how it looks, bevel width, colours, etc.
25
26 GenBitmapButton is a button with one or more bitmaps that show
27 the various states the button can be in.
28
29 GenToggleButton stays depressed when clicked, until clicked again.
30
31 GenBitmapToggleButton the same but with bitmaps.
32
33 """
34
35 import wx
36 import imageutils
37
38
39 #----------------------------------------------------------------------
40
41 class GenButtonEvent(wx.PyCommandEvent):
42 def __init__(self, eventType, ID):
43 wx.PyCommandEvent.__init__(self, eventType, ID)
44 self.isDown = False
45 self.theButton = None
46
47 def SetIsDown(self, isDown):
48 self.isDown = isDown
49
50 def GetIsDown(self):
51 return self.isDown
52
53 def SetButtonObj(self, btn):
54 self.theButton = btn
55
56 def GetButtonObj(self):
57 return self.theButton
58
59
60 #----------------------------------------------------------------------
61
62 class GenButton(wx.PyControl):
63 labelDelta = 1
64
65 def __init__(self, parent, ID, label,
66 pos = wx.DefaultPosition, size = wx.DefaultSize,
67 style = 0, validator = wx.DefaultValidator,
68 name = "genbutton"):
69 if style == 0:
70 style = wx.NO_BORDER
71 wx.PyControl.__init__(self, parent, ID, pos, size, style, validator, name)
72
73 self.up = True
74 self.bezelWidth = 2
75 self.hasFocus = False
76 self.useFocusInd = True
77
78 self.SetLabel(label)
79 self.SetPosition(pos)
80 font = parent.GetFont()
81 if not font.Ok():
82 font = wx.SystemSettings.GetSystemFont(wx.SYS_DEFAULT_GUI_FONT)
83 self.SetFont(font)
84 self.SetBestSize(size)
85 self.InitColours()
86
87 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
88 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
89 if wx.Platform == '__WXMSW__':
90 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
91 self.Bind(wx.EVT_MOTION, self.OnMotion)
92 self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus)
93 self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
94 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
95 self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
96 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
97 self.Bind(wx.EVT_PAINT, self.OnPaint)
98
99
100 def SetBestSize(self, size=None):
101 """
102 Given the current font and bezel width settings, calculate
103 and set a good size.
104 """
105 if size is None:
106 size = wx.Size(-1,-1)
107 if type(size) == type(()):
108 size = wx.Size(size[0], size[1])
109 size = wx.Size(size.width, size.height) # make a copy
110
111 best = self.GetBestSize()
112 if size.width == -1:
113 size.width = best.width
114 if size.height == -1:
115 size.height = best.height
116
117 self.SetSize(size)
118
119
120 def DoGetBestSize(self):
121 """Overridden base class virtual. Determines the best size of the
122 button based on the label and bezel size."""
123 w, h, useMin = self._GetLabelSize()
124 defSize = wx.Button.GetDefaultSize()
125 width = 12 + w
126 if useMin and width < defSize.width:
127 width = defSize.width
128 height = 11 + h
129 if useMin and height < defSize.height:
130 height = defSize.height
131 width = width + self.bezelWidth - 1
132 height = height + self.bezelWidth - 1
133 return (width, height)
134
135
136 def AcceptsFocus(self):
137 """Overridden base class virtual."""
138 return self.IsShown() and self.IsEnabled()
139
140
141 def Enable(self, enable=True):
142 wx.PyControl.Enable(self, enable)
143 self.Refresh()
144
145
146 def SetBezelWidth(self, width):
147 """Set the width of the 3D effect"""
148 self.bezelWidth = width
149
150 def GetBezelWidth(self):
151 """Return the width of the 3D effect"""
152 return self.bezelWidth
153
154 def SetUseFocusIndicator(self, flag):
155 """Specifiy if a focus indicator (dotted line) should be used"""
156 self.useFocusInd = flag
157
158 def GetUseFocusIndicator(self):
159 """Return focus indicator flag"""
160 return self.useFocusInd
161
162
163 def InitColours(self):
164 faceClr = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)
165 textClr = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNTEXT)
166 self.faceDnClr = faceClr
167 self.SetBackgroundColour(faceClr)
168 self.SetForegroundColour(textClr)
169
170 shadowClr = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNSHADOW)
171 highlightClr = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNHIGHLIGHT)
172 self.shadowPen = wx.Pen(shadowClr, 1, wx.SOLID)
173 self.highlightPen = wx.Pen(highlightClr, 1, wx.SOLID)
174 if wx.Platform == "__WXMAC__":
175 self.focusIndPen = wx.Pen(textClr, 1, wx.SOLID)
176 else:
177 self.focusIndPen = wx.Pen(textClr, 1, wx.USER_DASH)
178 self.focusIndPen.SetDashes([1,1])
179 self.focusIndPen.SetCap(wx.CAP_BUTT)
180 self.focusClr = highlightClr
181
182
183 def SetBackgroundColour(self, colour):
184 wx.PyControl.SetBackgroundColour(self, colour)
185 colour = self.GetBackgroundColour()
186
187 # Calculate a new set of highlight and shadow colours based on
188 # the new background colour. Works okay if the colour is dark...
189 r, g, b = colour.Get()
190 fr, fg, fb = min(255,r+32), min(255,g+32), min(255,b+32)
191 self.faceDnClr = wx.Colour(fr, fg, fb)
192 sr, sg, sb = max(0,r-32), max(0,g-32), max(0,b-32)
193 self.shadowPen = wx.Pen(wx.Colour(sr,sg,sb), 1, wx.SOLID)
194 hr, hg, hb = min(255,r+64), min(255,g+64), min(255,b+64)
195 self.highlightPen = wx.Pen(wx.Colour(hr,hg,hb), 1, wx.SOLID)
196 self.focusClr = wx.Colour(hr, hg, hb)
197
198
199 def _GetLabelSize(self):
200 """ used internally """
201 w, h = self.GetTextExtent(self.GetLabel())
202 return w, h, True
203
204
205 def Notify(self):
206 evt = GenButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
207 evt.SetIsDown(not self.up)
208 evt.SetButtonObj(self)
209 evt.SetEventObject(self)
210 self.GetEventHandler().ProcessEvent(evt)
211
212
213 def DrawBezel(self, dc, x1, y1, x2, y2):
214 # draw the upper left sides
215 if self.up:
216 dc.SetPen(self.highlightPen)
217 else:
218 dc.SetPen(self.shadowPen)
219 for i in range(self.bezelWidth):
220 dc.DrawLine((x1+i, y1), (x1+i, y2-i))
221 dc.DrawLine((x1, y1+i), (x2-i, y1+i))
222
223 # draw the lower right sides
224 if self.up:
225 dc.SetPen(self.shadowPen)
226 else:
227 dc.SetPen(self.highlightPen)
228 for i in range(self.bezelWidth):
229 dc.DrawLine((x1+i, y2-i), (x2+1, y2-i))
230 dc.DrawLine((x2-i, y1+i), (x2-i, y2))
231
232
233 def DrawLabel(self, dc, width, height, dw=0, dy=0):
234 dc.SetFont(self.GetFont())
235 if self.IsEnabled():
236 dc.SetTextForeground(self.GetForegroundColour())
237 else:
238 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
239 label = self.GetLabel()
240 tw, th = dc.GetTextExtent(label)
241 if not self.up:
242 dw = dy = self.labelDelta
243 dc.DrawText(label, ((width-tw)/2+dw, (height-th)/2+dy))
244
245
246 def DrawFocusIndicator(self, dc, w, h):
247 bw = self.bezelWidth
248 ## if self.hasFocus:
249 ## self.focusIndPen.SetColour(self.GetForegroundColour())
250 ## else:
251 ## #self.focusIndPen.SetColour(self.GetBackgroundColour())
252 ## self.focusIndPen.SetColour(self.GetForegroundColour())
253 self.focusIndPen.SetColour(self.focusClr)
254 dc.SetLogicalFunction(wx.INVERT)
255 dc.SetPen(self.focusIndPen)
256 dc.SetBrush(wx.TRANSPARENT_BRUSH)
257 dc.DrawRectangle((bw+2,bw+2), (w-bw*2-4, h-bw*2-4))
258 dc.SetLogicalFunction(wx.COPY)
259
260
261 def OnPaint(self, event):
262 (width, height) = self.GetClientSizeTuple()
263 x1 = y1 = 0
264 x2 = width-1
265 y2 = height-1
266 dc = wx.BufferedPaintDC(self)
267 if self.up:
268 dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.SOLID))
269 else:
270 dc.SetBackground(wx.Brush(self.faceDnClr, wx.SOLID))
271 dc.Clear()
272 self.DrawBezel(dc, x1, y1, x2, y2)
273 self.DrawLabel(dc, width, height)
274 if self.hasFocus and self.useFocusInd:
275 self.DrawFocusIndicator(dc, width, height)
276
277
278 def OnEraseBackground(self, event):
279 pass
280
281
282 def OnLeftDown(self, event):
283 if not self.IsEnabled():
284 return
285 self.up = False
286 self.CaptureMouse()
287 self.SetFocus()
288 self.Refresh()
289 event.Skip()
290
291
292 def OnLeftUp(self, event):
293 if not self.IsEnabled() or not self.HasCapture():
294 return
295 if self.HasCapture():
296 self.ReleaseMouse()
297 if not self.up: # if the button was down when the mouse was released...
298 self.Notify()
299 self.up = True
300 self.Refresh()
301 event.Skip()
302
303
304 def OnMotion(self, event):
305 if not self.IsEnabled() or not self.HasCapture():
306 return
307 if event.LeftIsDown() and self.HasCapture():
308 x,y = event.GetPositionTuple()
309 w,h = self.GetClientSizeTuple()
310 if self.up and x<w and x>=0 and y<h and y>=0:
311 self.up = False
312 self.Refresh()
313 return
314 if not self.up and (x<0 or y<0 or x>=w or y>=h):
315 self.up = True
316 self.Refresh()
317 return
318 event.Skip()
319
320
321 def OnGainFocus(self, event):
322 self.hasFocus = True
323 dc = wx.ClientDC(self)
324 w, h = self.GetClientSizeTuple()
325 if self.useFocusInd:
326 self.DrawFocusIndicator(dc, w, h)
327
328
329 def OnLoseFocus(self, event):
330 self.hasFocus = False
331 dc = wx.ClientDC(self)
332 w, h = self.GetClientSizeTuple()
333 if self.useFocusInd:
334 self.DrawFocusIndicator(dc, w, h)
335
336
337 def OnKeyDown(self, event):
338 if self.hasFocus and event.KeyCode() == ord(" "):
339 self.up = False
340 self.Refresh()
341 event.Skip()
342
343
344 def OnKeyUp(self, event):
345 if self.hasFocus and event.KeyCode() == ord(" "):
346 self.up = True
347 self.Notify()
348 self.Refresh()
349 event.Skip()
350
351
352 #----------------------------------------------------------------------
353
354 class GenBitmapButton(GenButton):
355 def __init__(self, parent, ID, bitmap,
356 pos = wx.DefaultPosition, size = wx.DefaultSize,
357 style = 0, validator = wx.DefaultValidator,
358 name = "genbutton"):
359 self.bmpDisabled = None
360 self.bmpFocus = None
361 self.bmpSelected = None
362 self.SetBitmapLabel(bitmap)
363 GenButton.__init__(self, parent, ID, "", pos, size, style, validator, name)
364
365
366 def GetBitmapLabel(self):
367 return self.bmpLabel
368 def GetBitmapDisabled(self):
369 return self.bmpDisabled
370 def GetBitmapFocus(self):
371 return self.bmpFocus
372 def GetBitmapSelected(self):
373 return self.bmpSelected
374
375
376 def SetBitmapDisabled(self, bitmap):
377 """Set bitmap to display when the button is disabled"""
378 self.bmpDisabled = bitmap
379
380 def SetBitmapFocus(self, bitmap):
381 """Set bitmap to display when the button has the focus"""
382 self.bmpFocus = bitmap
383 self.SetUseFocusIndicator(False)
384
385 def SetBitmapSelected(self, bitmap):
386 """Set bitmap to display when the button is selected (pressed down)"""
387 self.bmpSelected = bitmap
388
389 def SetBitmapLabel(self, bitmap, createOthers=True):
390 """
391 Set the bitmap to display normally.
392 This is the only one that is required. If
393 createOthers is True, then the other bitmaps
394 will be generated on the fly. Currently,
395 only the disabled bitmap is generated.
396 """
397 self.bmpLabel = bitmap
398 if bitmap is not None and createOthers:
399 image = wx.ImageFromBitmap(bitmap)
400 imageutils.grayOut(image)
401 self.SetBitmapDisabled(wx.BitmapFromImage(image))
402
403
404 def _GetLabelSize(self):
405 """ used internally """
406 if not self.bmpLabel:
407 return -1, -1, False
408 return self.bmpLabel.GetWidth()+2, self.bmpLabel.GetHeight()+2, False
409
410 def DrawLabel(self, dc, width, height, dw=0, dy=0):
411 bmp = self.bmpLabel
412 if self.bmpDisabled and not self.IsEnabled():
413 bmp = self.bmpDisabled
414 if self.bmpFocus and self.hasFocus:
415 bmp = self.bmpFocus
416 if self.bmpSelected and not self.up:
417 bmp = self.bmpSelected
418 bw,bh = bmp.GetWidth(), bmp.GetHeight()
419 if not self.up:
420 dw = dy = self.labelDelta
421 hasMask = bmp.GetMask() != None
422 dc.DrawBitmap(bmp, ((width-bw)/2+dw, (height-bh)/2+dy), hasMask)
423
424
425 #----------------------------------------------------------------------
426
427
428 class GenBitmapTextButton(GenBitmapButton): # generic bitmapped button with Text Label
429 def __init__(self, parent, ID, bitmap, label,
430 pos = wx.DefaultPosition, size = wx.DefaultSize,
431 style = 0, validator = wx.DefaultValidator,
432 name = "genbutton"):
433 GenBitmapButton.__init__(self, parent, ID, bitmap, pos, size, style, validator, name)
434 self.SetLabel(label)
435
436
437 def _GetLabelSize(self):
438 """ used internally """
439 w, h = self.GetTextExtent(self.GetLabel())
440 if not self.bmpLabel:
441 return w, h, True # if there isn't a bitmap use the size of the text
442
443 w_bmp = self.bmpLabel.GetWidth()+2
444 h_bmp = self.bmpLabel.GetHeight()+2
445 width = w + w_bmp
446 if h_bmp > h:
447 height = h_bmp
448 else:
449 height = h
450 return width, height, True
451
452
453 def DrawLabel(self, dc, width, height, dw=0, dy=0):
454 bmp = self.bmpLabel
455 if bmp != None: # if the bitmap is used
456 if self.bmpDisabled and not self.IsEnabled():
457 bmp = self.bmpDisabled
458 if self.bmpFocus and self.hasFocus:
459 bmp = self.bmpFocus
460 if self.bmpSelected and not self.up:
461 bmp = self.bmpSelected
462 bw,bh = bmp.GetWidth(), bmp.GetHeight()
463 if not self.up:
464 dw = dy = self.labelDelta
465 hasMask = bmp.GetMask() != None
466 else:
467 bw = bh = 0 # no bitmap -> size is zero
468
469 dc.SetFont(self.GetFont())
470 if self.IsEnabled():
471 dc.SetTextForeground(self.GetForegroundColour())
472 else:
473 dc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT))
474
475 label = self.GetLabel()
476 tw, th = dc.GetTextExtent(label) # size of text
477 if not self.up:
478 dw = dy = self.labelDelta
479
480 pos_x = (width-bw-tw)/2+dw # adjust for bitmap and text to centre
481 if bmp !=None:
482 dc.DrawBitmap(bmp, (pos_x, (height-bh)/2+dy), hasMask) # draw bitmap if available
483 pos_x = pos_x + 2 # extra spacing from bitmap
484
485 dc.DrawText(label, (pos_x + dw+bw, (height-th)/2+dy)) # draw the text
486
487
488 #----------------------------------------------------------------------
489
490
491 class __ToggleMixin:
492 def SetToggle(self, flag):
493 self.up = not flag
494 self.Refresh()
495 SetValue = SetToggle
496
497 def GetToggle(self):
498 return not self.up
499 GetValue = GetToggle
500
501 def OnLeftDown(self, event):
502 if not self.IsEnabled():
503 return
504 self.saveUp = self.up
505 self.up = not self.up
506 self.CaptureMouse()
507 self.SetFocus()
508 self.Refresh()
509
510 def OnLeftUp(self, event):
511 if not self.IsEnabled() or not self.HasCapture():
512 return
513 if self.HasCapture():
514 if self.up != self.saveUp:
515 self.Notify()
516 self.ReleaseMouse()
517 self.Refresh()
518
519 def OnKeyDown(self, event):
520 event.Skip()
521
522 def OnMotion(self, event):
523 if not self.IsEnabled():
524 return
525 if event.LeftIsDown() and self.HasCapture():
526 x,y = event.GetPositionTuple()
527 w,h = self.GetClientSizeTuple()
528 if x<w and x>=0 and y<h and y>=0:
529 self.up = not self.saveUp
530 self.Refresh()
531 return
532 if (x<0 or y<0 or x>=w or y>=h):
533 self.up = self.saveUp
534 self.Refresh()
535 return
536 event.Skip()
537
538 def OnKeyUp(self, event):
539 if self.hasFocus and event.KeyCode() == ord(" "):
540 self.up = not self.up
541 self.Notify()
542 self.Refresh()
543 event.Skip()
544
545
546
547
548 class GenToggleButton(__ToggleMixin, GenButton):
549 pass
550
551 class GenBitmapToggleButton(__ToggleMixin, GenBitmapButton):
552 pass
553
554 class GenBitmapTextToggleButton(__ToggleMixin, GenBitmapTextButton):
555 pass
556
557 #----------------------------------------------------------------------
558
559