Module GradientButton
[hide private]
[frames] | no frames]

Source Code for Module GradientButton

  1  # --------------------------------------------------------------------------------- # 
  2  # GRADIENTBUTTON wxPython IMPLEMENTATION 
  3  # 
  4  # Andrea Gavana, @ 07 October 2008 
  5  # Latest Revision: 07 October 2008, 22.00 GMT 
  6  # 
  7  # 
  8  # TODO List 
  9  # 
 10  # 1) Anything to do? 
 11  # 
 12  # 
 13  # For all kind of problems, requests of enhancements and bug reports, please 
 14  # write to me at: 
 15  # 
 16  # andrea.gavana@gmail.com 
 17  # gavana@kpo.kz 
 18  # 
 19  # Or, obviously, to the wxPython mailing list!!! 
 20  # 
 21  # 
 22  # End Of Comments 
 23  # --------------------------------------------------------------------------------- # 
 24   
 25  """ 
 26  Description 
 27  =========== 
 28   
 29  GradientButton is another custom-drawn button class which mimics Windows CE mobile 
 30  gradient buttons, using a tri-vertex blended gradient plus some ClearType bold 
 31  font (best effect with Tahoma Bold). GradientButton supports: 
 32   
 33  * Triple blended gradient background, with customizable colours; 
 34  * Custom colours for the "pressed" state; 
 35  * Rounded-corners buttons; 
 36  * Text-only or image+text buttons. 
 37   
 38  And a lot more. Check the demo for an almost complete review of the functionalities. 
 39   
 40   
 41  Supported Platforms 
 42  =================== 
 43   
 44  GradientButton has been tested on the following platforms: 
 45    * Windows (Windows XP). 
 46   
 47   
 48  Latest Revision: Andrea Gavana @ 07 October 2008, 22.00 GMT 
 49  Version 0.1 
 50   
 51  """ 
 52   
 53  import wx 
 54   
 55   
 56  HOVER = 1 
 57  CLICK = 2 
 58   
59 -class GradientButtonEvent(wx.PyCommandEvent):
60 """ Event sent from the Gradient buttons when the button is activated. """ 61
62 - def __init__(self, eventType, id):
63 """ 64 Default class constructor. 65 66 @param eventType: the event type; 67 @param id: the event id. 68 """ 69 70 wx.PyCommandEvent.__init__(self, eventType, id) 71 self.isDown = False 72 self.theButton = None
73 74
75 - def SetButtonObj(self, btn):
76 """ 77 Sets the event object for the event. 78 79 @param btn: the button object. 80 """ 81 82 self.theButton = btn
83 84
85 - def GetButtonObj(self):
86 """ Returns the object associated with this event. """ 87 88 return self.theButton
89 90
91 -class GradientButton(wx.PyControl):
92 """ This is the main class implementation of L{GradientButton}. """ 93
94 - def __init__(self, parent, id=wx.ID_ANY, bitmap=None, label="", pos=wx.DefaultPosition, 95 size=wx.DefaultSize, style=wx.NO_BORDER, validator=wx.DefaultValidator, 96 name="gradientbutton"):
97 """ 98 Default class constructor. 99 100 @param parent: the AquaButton parent. 101 @param id: the button id; 102 @param bitmap: the button bitmap (if any); 103 @param label: the button text label; 104 @param pos: the button position; 105 @param size: the button size; 106 @param style: the button style (unused); 107 @param validator: the validator associated to the button; 108 @param name: the button name. 109 """ 110 111 wx.PyControl.__init__(self, parent, id, pos, size, style, validator, name) 112 113 self.Bind(wx.EVT_PAINT, self.OnPaint) 114 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda event: None) 115 self.Bind(wx.EVT_SIZE, self.OnSize) 116 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 117 self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) 118 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) 119 self.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter) 120 self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus) 121 self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus) 122 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) 123 self.Bind(wx.EVT_KEY_UP, self.OnKeyUp) 124 125 if "__WXMSW__" in wx.PlatformInfo: 126 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown) 127 128 self._mouseAction = None 129 self._bitmap = bitmap 130 self._hasFocus = False 131 132 self.SetLabel(label) 133 self.InheritAttributes() 134 self.SetInitialSize(size) 135 136 # The following defaults are better suited to draw the text outline 137 self._bottomStartColour = wx.BLACK 138 rgba = self._bottomStartColour.Red(), self._bottomStartColour.Green(), \ 139 self._bottomStartColour.Blue(), self._bottomStartColour.Alpha() 140 self._bottomEndColour = self.LightColour(self._bottomStartColour, 20) 141 self._topStartColour = self.LightColour(self._bottomStartColour, 40) 142 self._topEndColour = self.LightColour(self._bottomStartColour, 25) 143 self._pressedTopColour = self.LightColour(self._bottomStartColour, 20) 144 self._pressedBottomColour = wx.Colour(*rgba) 145 self.SetForegroundColour(wx.WHITE) 146 147 for method in dir(self): 148 if method.endswith("Colour"): 149 newMethod = method[0:-6] + "Color" 150 if not hasattr(self, newMethod): 151 setattr(self, newMethod, method)
152 153
154 - def LightColour(self, color, percent):
155 """ 156 Return light contrast of color. The color returned is from the scale of 157 color -> white. The percent determines how light the color will be. 158 Percent = 100 return white, percent = 0 returns color. 159 """ 160 161 end_color = wx.WHITE 162 rd = end_color.Red() - color.Red() 163 gd = end_color.Green() - color.Green() 164 bd = end_color.Blue() - color.Blue() 165 high = 100 166 167 # We take the percent way of the color from color -. white 168 i = percent 169 r = color.Red() + ((i*rd*100)/high)/100 170 g = color.Green() + ((i*gd*100)/high)/100 171 b = color.Blue() + ((i*bd*100)/high)/100 172 173 return wx.Color(r, g, b)
174 175
176 - def OnSize(self, event):
177 """ Handles the wx.EVT_SIZE event for L{GradientButton}. """ 178 179 event.Skip() 180 self.Refresh()
181 182
183 - def OnLeftDown(self, event):
184 """ Handles the wx.EVT_LEFT_DOWN event for L{GradientButton}. """ 185 186 if not self.IsEnabled(): 187 return 188 189 self._mouseAction = CLICK 190 self.CaptureMouse() 191 self.Refresh() 192 event.Skip()
193 194
195 - def OnLeftUp(self, event):
196 """ Handles the wx.EVT_LEFT_UP event for L{GradientButton}. """ 197 198 if not self.IsEnabled() or not self.HasCapture(): 199 return 200 201 pos = event.GetPosition() 202 rect = self.GetClientRect() 203 204 if self.HasCapture(): 205 self.ReleaseMouse() 206 207 if rect.Contains(pos): 208 self._mouseAction = HOVER 209 self.Notify() 210 else: 211 self._mouseAction = None 212 213 self.Refresh() 214 event.Skip()
215 216
217 - def OnMouseEnter(self, event):
218 """ Handles the wx.EVT_ENTER_WINDOW event for L{GradientButton}. """ 219 220 if not self.IsEnabled(): 221 return 222 223 self._mouseAction = HOVER 224 self.Refresh() 225 event.Skip()
226 227
228 - def OnMouseLeave(self, event):
229 """ Handles the wx.EVT_LEAVE_WINDOW event for L{GradientButton}. """ 230 231 self._mouseAction = None 232 self.Refresh() 233 event.Skip()
234 235
236 - def OnGainFocus(self, event):
237 """ Handles the wx.EVT_SET_FOCUS event for L{GradientButton}. """ 238 239 self._hasFocus = True 240 self.Refresh() 241 self.Update()
242 243
244 - def OnLoseFocus(self, event):
245 """ Handles the wx.EVT_KILL_FOCUS event for L{GradientButton}. """ 246 247 self._hasFocus = False 248 self.Refresh() 249 self.Update()
250 251
252 - def OnKeyDown(self, event):
253 """ Handles the wx.EVT_KEY_DOWN event for L{GradientButton}. """ 254 255 if self._hasFocus and event.GetKeyCode() == ord(" "): 256 self._mouseAction = HOVER 257 self.Refresh() 258 event.Skip()
259 260
261 - def OnKeyUp(self, event):
262 """ Handles the wx.EVT_KEY_UP event for L{GradientButton}. """ 263 264 if self._hasFocus and event.GetKeyCode() == ord(" "): 265 self._mouseAction = HOVER 266 self.Notify() 267 self.Refresh() 268 event.Skip()
269 270
271 - def OnPaint(self, event):
272 """ Handles the wx.EVT_PAINT event for L{GradientButton}. """ 273 274 dc = wx.BufferedPaintDC(self) 275 gc = wx.GraphicsContext.Create(dc) 276 dc.SetBackground(wx.Brush(self.GetParent().GetBackgroundColour())) 277 dc.Clear() 278 279 clientRect = self.GetClientRect() 280 gradientRect = wx.Rect(*clientRect) 281 capture = wx.Window.GetCapture() 282 283 x, y, width, height = clientRect 284 285 gradientRect.SetHeight(gradientRect.GetHeight()/2 + ((capture==self and [1] or [0])[0])) 286 if capture != self: 287 if self._mouseAction == HOVER: 288 topStart, topEnd = self.LightColour(self._topStartColour, 10), self.LightColour(self._topEndColour, 10) 289 else: 290 topStart, topEnd = self._topStartColour, self._topEndColour 291 292 rc1 = wx.Rect(x, y, width, height/2) 293 path1 = self.GetPath(gc, rc1, 8) 294 br1 = gc.CreateLinearGradientBrush(x, y, x, y+height/2, topStart, topEnd) 295 gc.SetBrush(br1) 296 gc.FillPath(path1) #draw main 297 298 path4 = gc.CreatePath() 299 path4.AddRectangle(x, y+height/2-8, width, 8) 300 path4.CloseSubpath() 301 gc.SetBrush(br1) 302 gc.FillPath(path4) 303 304 else: 305 306 rc1 = wx.Rect(x, y, width, height) 307 path1 = self.GetPath(gc, rc1, 8) 308 gc.SetPen(wx.Pen(self._pressedTopColour)) 309 gc.SetBrush(wx.Brush(self._pressedTopColour)) 310 gc.FillPath(path1) 311 312 gradientRect.Offset((0, gradientRect.GetHeight())) 313 314 if capture != self: 315 316 if self._mouseAction == HOVER: 317 bottomStart, bottomEnd = self.LightColour(self._bottomStartColour, 10), self.LightColour(self._bottomEndColour, 10) 318 else: 319 bottomStart, bottomEnd = self._bottomStartColour, self._bottomEndColour 320 321 rc3 = wx.Rect(x, y+height/2, width, height/2) 322 path3 = self.GetPath(gc, rc3, 8) 323 br3 = gc.CreateLinearGradientBrush(x, y+height/2, x, y+height, bottomStart, bottomEnd) 324 gc.SetBrush(br3) 325 gc.FillPath(path3) #draw main 326 327 path4 = gc.CreatePath() 328 path4.AddRectangle(x, y+height/2, width, 8) 329 path4.CloseSubpath() 330 gc.SetBrush(br3) 331 gc.FillPath(path4) 332 333 shadowOffset = 0 334 else: 335 336 rc2 = wx.Rect(x+1, gradientRect.height/2, gradientRect.width, gradientRect.height) 337 path2 = self.GetPath(gc, rc2, 8) 338 gc.SetPen(wx.Pen(self._pressedBottomColour)) 339 gc.SetBrush(wx.Brush(self._pressedBottomColour)) 340 gc.FillPath(path2) 341 shadowOffset = 1 342 343 font = gc.CreateFont(self.GetFont(), self.GetForegroundColour()) 344 gc.SetFont(font) 345 label = self.GetLabel() 346 tw, th = gc.GetTextExtent(label) 347 348 if self._bitmap: 349 bw, bh = self._bitmap.GetWidth(), self._bitmap.GetHeight() 350 else: 351 bw = bh = 0 352 353 pos_x = (width-bw-tw)/2+shadowOffset # adjust for bitmap and text to centre 354 if self._bitmap: 355 pos_y = (height-bh)/2+shadowOffset 356 gc.DrawBitmap(self._bitmap, pos_x, pos_y, bw, bh) # draw bitmap if available 357 pos_x = pos_x + 2 # extra spacing from bitmap 358 359 gc.DrawText(label, pos_x + bw + shadowOffset, (height-th)/2+shadowOffset)
360 361
362 - def GetPath(self, gc, rc, r):
363 """ Returns a rounded GraphicsPath. """ 364 365 x, y, w, h = rc 366 path = gc.CreatePath() 367 path.AddRoundedRectangle(x, y, w, h, r) 368 path.CloseSubpath() 369 return path
370 371
372 - def SetInitialSize(self, size=None):
373 """ 374 Given the current font and bezel width settings, calculate 375 and set a good size. 376 """ 377 378 if size is None: 379 size = wx.DefaultSize 380 wx.PyControl.SetInitialSize(self, size)
381 382 SetBestSize = SetInitialSize 383 384
385 - def AcceptsFocus(self):
386 """Overridden base class virtual.""" 387 388 return self.IsShown() and self.IsEnabled()
389 390
391 - def GetDefaultAttributes(self):
392 """ 393 Overridden base class virtual. By default we should use 394 the same font/colour attributes as the native Button. 395 """ 396 397 return wx.Button.GetClassDefaultAttributes()
398 399
400 - def ShouldInheritColours(self):
401 """ 402 Overridden base class virtual. Buttons usually don't inherit 403 the parent's colours. 404 """ 405 406 return False
407 408
409 - def Enable(self, enable=True):
410 """ Enables/disables the button. """ 411 412 wx.PyControl.Enable(self, enable) 413 self.Refresh()
414 415
416 - def SetTopStartColour(self, colour):
417 """ Sets the top start colour for the gradient shading. """ 418 419 self._topStartColour = colour 420 self.Refresh()
421 422
423 - def GetTopStartColour(self):
424 """ Returns the top start colour for the gradient shading. """ 425 426 return self._topStartColour
427 428
429 - def SetTopEndColour(self, colour):
430 """ Sets the top end colour for the gradient shading. """ 431 432 self._topEndColour = colour 433 self.Refresh()
434 435
436 - def GetTopEndColour(self):
437 """ Returns the top end colour for the gradient shading. """ 438 439 return self._topEndColour
440 441
442 - def SetBottomStartColour(self, colour):
443 """ Sets the top bottom colour for the gradient shading. """ 444 445 self._bottomStartColour = colour 446 self.Refresh()
447 448
449 - def GetBottomStartColour(self):
450 """ Returns the bottom start colour for the gradient shading. """ 451 452 return self._bottomStartColour
453 454
455 - def SetBottomEndColour(self, colour):
456 """ Sets the bottom end colour for the gradient shading. """ 457 458 self._bottomEndColour = colour 459 self.Refresh()
460 461
462 - def GetBottomEndColour(self):
463 """ Returns the bottom end colour for the gradient shading. """ 464 465 return self._bottomEndColour
466 467
468 - def SetPressedTopColour(self, colour):
469 """ Sets the pressed top start colour for the gradient shading. """ 470 471 self._pressedTopColour = colour 472 self.Refresh()
473 474
475 - def GetPressedTopColour(self):
476 """ Returns the pressed top start colour for the gradient shading. """ 477 478 return self._pressedTopColour
479 480
481 - def SetPressedBottomColour(self, colour):
482 """ Sets the pressed bottom start colour for the gradient shading. """ 483 484 self._pressedBottomColour = colour 485 self.Refresh()
486 487
488 - def GetPressedBottomColour(self):
489 """ Returns the pressed bottom start colour for the gradient shading. """ 490 491 return self._pressedBottomColour
492 493
494 - def SetForegroundColour(self, colour):
495 """ Sets the L{GradientButton} foreground (text) colour. """ 496 497 wx.PyControl.SetForegroundColour(self, colour) 498 self.Refresh()
499 500
501 - def DoGetBestSize(self):
502 """ 503 Overridden base class virtual. Determines the best size of the 504 button based on the label and bezel size. 505 """ 506 507 label = self.GetLabel() 508 if not label: 509 return wx.Size(112, 48) 510 511 dc = wx.ClientDC(self) 512 dc.SetFont(self.GetFont()) 513 retWidth, retHeight = dc.GetTextExtent(label) 514 515 bmpWidth = bmpHeight = 0 516 constant = 15 517 if self._bitmap: 518 bmpWidth, bmpHeight = self._bitmap.GetWidth()+10, self._bitmap.GetHeight() 519 retWidth += bmpWidth 520 retHeight = max(bmpHeight, retHeight) 521 constant = 15 522 523 return wx.Size(retWidth+constant, retHeight+constant)
524 525
526 - def SetDefault(self):
527 """ Sets the default button. """ 528 529 tlw = wx.GetTopLevelParent(self) 530 if hasattr(tlw, 'SetDefaultItem'): 531 tlw.SetDefaultItem(self)
532 533
534 - def Notify(self):
535 """ Actually sends a wx.EVT_BUTTON event to the listener (if any). """ 536 537 evt = GradientButtonEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId()) 538 evt.SetButtonObj(self) 539 evt.SetEventObject(self) 540 self.GetEventHandler().ProcessEvent(evt)
541