Package pyjamas :: Package ui :: Module CustomButton
[hide private]
[frames] | no frames]

Source Code for Module pyjamas.ui.CustomButton

  1  # Copyright Pyjamas Team 
  2  # Copyright (C) 2009 Luke Kenneth Casson Leighton <lkcl@lkcl.net> 
  3  # 
  4  # Licensed under the Apache License, Version 2.0 (the "License"); you may not 
  5  # use this file except in compliance with the License. You may obtain a copy of 
  6  # the License at 
  7  # 
  8  # http:/www.apache.org/licenses/LICENSE-2.0 
  9  # 
 10  # Unless required by applicable law or agreed to in writing, software 
 11  # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 12  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 13  # License for the specific language governing permissions and limitations under 
 14  # the License. 
 15   
 16  """ 
 17  Custom Button is a base button class with built in support for a set number 
 18  of button faces. 
 19   
 20  Each face has its own style modifier. For example, the state for down and 
 21  hovering is assigned the CSS modifier down-hovering. So, if the 
 22  button's overall style name is gwt-PushButton then when showing the 
 23  down-hovering face, the button's style is 
 24  gwt-PushButton-down-hovering. The overall style name can be used to 
 25  change the style of the button irrespective of the current face. 
 26   
 27  Each button face can be assigned is own image, text, or html contents. If no 
 28  content is defined for a face, then the face will use the contents of another 
 29  face. For example, if down-hovering does not have defined 
 30  contents, it will use the contents defined by the down face. 
 31   
 32  The supported faces are defined below: 
 33   
 34  CSS          | Getter method       | description of face       | delegateTo 
 35  -------------+---------------------+---------------------------+--------- 
 36  up           |getUpFace()          |button is up               |none 
 37  down         |getDownFace()        |button is down             | up 
 38  up-hovering  |getUpHoveringFace()  |button is up and hovering  | up 
 39  up-disabled  |getUpDisabledFace()  |button is up and disabled  | up 
 40  down-hovering|getDownHoveringFace()|button is down and hovering|down 
 41  down-disabled|getDownDisabledFace()|button is down and disabled|down 
 42   
 43  """ 
 44   
 45  from pyjamas    import DOM 
 46  from pyjamas import Factory 
 47  from pyjamas.ui import Event 
 48  from ButtonBase import ButtonBase 
 49  from pyjamas.ui import Focus 
 50  from UIObject import UIObject 
 51   
 52   
53 -class Face:
54 STYLENAME_HTML_FACE = "html-face" 55
56 - def __init__(self, button, delegateTo = None):
57 """ 58 Constructor for Face. Creates a face that delegates to 59 the supplied face. 60 61 @param delegateTo: default content provider 62 """ 63 self.button = button 64 self.delegateTo = delegateTo 65 self.face = None 66 self.id = None 67 self.name = "fazom" # TODO
68
69 - def getName(self): # FIXME
70 """Returns with a known face's name""" 71 return self.name
72
73 - def getFaceID(self): # FIXME
74 """Returns with the face's id""" 75 return self.id 76
77 - def setName(self, name): # FIXME
78 """Sets the face's name""" 79 self.name = name 80 return 81
82 - def setFaceID(self, face_id): # FIXME
83 """Sets the face's id""" 84 self.id = face_id 85 return 86
87 - def getHTML(self):
88 """Gets the face's contents as html.""" 89 return DOM.getInnerHTML(self.getFace())
90
91 - def getText(self):
92 """Gets the face's contents as text.""" 93 return DOM.getInnerText(self.getFace())
94
95 - def setHTML(self, html):
96 """Set the face's contents as html.""" 97 self.face = DOM.createDiv() 98 UIObject.setStyleName(self.button, self.face, self.STYLENAME_HTML_FACE, 99 True) 100 DOM.setInnerHTML(self.face, html) 101 self.button.updateButtonFace()
102
103 - def setImage(self, image):
104 """ 105 Set the face's contents as an image. 106 @param image: image to set as face contents 107 """ 108 self.face = image.getElement() 109 self.button.updateButtonFace()
110
111 - def setText(self, text):
112 """ 113 Sets the face's contents as text. 114 @param text: text to set as face's contents 115 """ 116 self.face = DOM.createDiv() 117 UIObject.setStyleName(self.button, self.face, self.STYLENAME_HTML_FACE, 118 True) 119 DOM.setInnerText(self.face, text) 120 self.button.updateButtonFace()
121
122 - def toString(self):
123 return self.getName()
124
125 - def getFace(self):
126 """Gets the contents associated with this face.""" 127 if self.face is not None: 128 return self.face 129 if self.delegateTo is not None: 130 return self.delegateTo.getFace() 131 # provide a default face as none was supplied. 132 self.face = DOM.createDiv() 133 return self.face
134 135
136 -class CustomButton (ButtonBase):
137 """ 138 Represents a button's face. Each face is associated with its own style 139 modifier and, optionally, its own contents html, text, or image. 140 """ 141 142 STYLENAME_DEFAULT = "gwt-CustomButton" 143 DOWN_ATTRIBUTE = 1 # Pressed Attribute bit. 144 HOVERING_ATTRIBUTE = 2 # Hovering Attribute bit. 145 DISABLED_ATTRIBUTE = 4 # Disabled Attribute bit. 146 UP = 0 # ID for up face. 147 DOWN = DOWN_ATTRIBUTE # 1 ID for down face. 148 UP_HOVERING = HOVERING_ATTRIBUTE # 2 ID for upHovering face. 149 DOWN_HOVERING = DOWN_ATTRIBUTE | HOVERING_ATTRIBUTE # 3 ID for downHovering. 150 UP_DISABLED = DISABLED_ATTRIBUTE # 4 ID for upDisabled face. 151 DOWN_DISABLED = DOWN | DISABLED_ATTRIBUTE # 5 ID for downDisabled face. 152 153 154 155 """ Calling possibilities: 156 def __init__(self, upImage): 157 def __init__(self, upImage, listener): 158 def __init__(self, upImage, downImage): 159 def __init__(self, upImage, downImage, listener): 160 def __init__(self, upText): 161 def __init__(self, upText, listener): 162 def __init__(self, upText, downText): 163 def __init__(self, upText, downText, listener): 164 def __init__(self): 165 166 167 TODO: I do not know how to handle the following cases: 168 def __init__(self, upImage, listener): 169 def __init__(self, upText, listener): 170 171 So how can I make difference between listener and downImage/downText ? 172 """ 173
174 - def __init__(self, upImageText=None, downImageText=None, listener=None, 175 **kwargs):
176 """Constructor for CustomButton.""" 177 178 if not kwargs.has_key('StyleName'): 179 kwargs['StyleName']=self.STYLENAME_DEFAULT 180 if kwargs.has_key('Element'): 181 # XXX FIXME: createFocusable is used for a reason... 182 element = kwargs.pop('Element') 183 else: 184 element = Focus.createFocusable() 185 ButtonBase.__init__(self, element, **kwargs) 186 187 self.curFace = None # The button's current face. 188 self.curFaceElement = None # No "undefined" anymore 189 self.up = None # Face for up. 190 self.down = None # Face for down. 191 self.downHovering = None # Face for downHover. 192 self.upHovering = None # Face for upHover. 193 self.upDisabled = None # Face for upDisabled. 194 self.downDisabled = None # Face for downDisabled. 195 self.isCapturing = False # If True, this widget is capturing with 196 # the mouse held down. 197 self.isFocusing = False # If True, widget has focus with space down. 198 self.allowClick = False # Used to decide whether to allow clicks to 199 # propagate up to the superclass or container 200 # elements. 201 202 self.setUpFace(self.createFace(None, "up", self.UP)) 203 #self.getUpFace().setText("Not initialized yet:)") 204 #self.setCurrentFace(self.getUpFace()) 205 206 # Add a11y role "button" 207 # XXX: TODO Accessibility 208 209 # TODO: pyjslib.isinstance 210 if downImageText is None and listener is None: 211 listener = upImageText 212 upImageText = None 213 214 if upImageText and isinstance(upImageText, basestring): 215 upText = upImageText 216 upImage = None 217 else: 218 upImage = upImageText 219 upText = None 220 221 if downImageText and isinstance(downImageText, basestring): 222 downText = downImageText 223 downImage = None 224 else: 225 downImage = downImageText 226 downText = None 227 228 #self.getUpFace().setText("Just a test") 229 if upImage is not None: 230 self.getUpFace().setImage(upImage) 231 if upText is not None: 232 self.getUpFace().setText(upText) 233 if downImage is not None: 234 self.getDownFace().setImage(downImage) 235 if downText is not None: 236 self.getDownFace().setText(downText) 237 238 # set the face DOWN 239 #self.setCurrentFace(self.getDownFace()) 240 241 # set the face UP 242 #self.setCurrentFace(self.getUpFace()) 243 244 self.sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS 245 | Event.KEYEVENTS) 246 if listener is not None: 247 self.addClickListener(listener)
248
249 - def updateButtonFace(self):
250 if self.curFace is not None and \ 251 self.curFace.getFace() == self.getFace(): 252 self.setCurrentFaceElement(self.face)
253
254 - def getDownDisabledFace(self):
255 """Gets the downDisabled face of the button.""" 256 if self.downDisabled is None: 257 self.setDownDisabledFace(self.createFace(self.getDownFace(), 258 "down-disabled", self.DOWN_DISABLED)) 259 260 return self.downDisabled
261
262 - def getDownFace(self):
263 """Gets the down face of the button.""" 264 if self.down is None: 265 self.setDownFace(self.createFace(self.getUpFace(), 266 "down", self.DOWN)) 267 return self.down
268
269 - def getDownHoveringFace(self):
270 """Gets the downHovering face of the button.""" 271 if self.downHovering is None: 272 self.setDownHoveringFace(self.createFace(self.getDownFace(), 273 "down-hovering", self.DOWN_HOVERING)) 274 return self.downHovering
275
276 - def getHTML(self):
277 """Gets the current face's html.""" 278 return self.getCurrentFace().getHTML()
279
280 - def getTabIndex(self):
281 return Focus.getTabIndex(self.getElement())
282
283 - def getText(self):
284 """Gets the current face's text.""" 285 return self.getCurrentFace().getText()
286
287 - def getUpDisabledFace(self):
288 """Gets the upDisabled face of the button.""" 289 if self.upDisabled is None: 290 self.setUpDisabledFace(self.createFace(self.getUpFace(), 291 "up-disabled", self.UP_DISABLED)) 292 return self.upDisabled
293
294 - def getUpFace(self):
295 """Gets the up face of the button.""" 296 return self.up # self.up must be always initialized
297
298 - def getUpHoveringFace(self):
299 """Gets the upHovering face of the button.""" 300 if self.upHovering is None: 301 self.setUpHoveringFace(self.createFace(self.getUpFace(), 302 "up-hovering", self.UP_HOVERING)) 303 return self.upHovering
304
305 - def onBrowserEvent(self, event):
306 # Should not act on button if disabled. 307 if not self.isEnabled(): 308 # This can happen when events are bubbled up from 309 # non-disabled children 310 return 311 312 event_type = DOM.eventGetType(event) 313 314 if event_type == "click": 315 # If clicks are currently disallowed, keep it from bubbling or 316 # being passed to the superclass. 317 if not self.allowClick: 318 DOM.eventStopPropagation(event) 319 return 320 321 elif event_type == "mousedown": 322 if DOM.eventGetButton(event) == Event.BUTTON_LEFT: 323 self.setFocus(True) 324 self.onClickStart() 325 DOM.setCapture(self.getElement()) 326 self.isCapturing = True 327 # Prevent dragging (on some browsers) 328 DOM.eventPreventDefault(event) 329 330 elif event_type == "mouseup": 331 if self.isCapturing: 332 self.isCapturing = False 333 DOM.releaseCapture(self.getElement()) 334 if self.isHovering() and \ 335 DOM.eventGetButton(event) == Event.BUTTON_LEFT: 336 self.onClick() 337 338 elif event_type == "mousemove": 339 if self.isCapturing: 340 # Prevent dragging (on other browsers) 341 DOM.eventPreventDefault(event) 342 343 elif event_type == "mouseout": 344 to = DOM.eventGetToElement(event) 345 if (DOM.isOrHasChild(self.getElement(), DOM.eventGetTarget(event)) 346 and (to is None or not DOM.isOrHasChild(self.getElement(), to))): 347 if self.isCapturing: 348 self.onClickCancel() 349 self.setHovering(False) 350 351 elif event_type == "mouseover": 352 if DOM.isOrHasChild(self.getElement(), DOM.eventGetTarget(event)): 353 self.setHovering(True) 354 if self.isCapturing: 355 self.onClickStart() 356 357 elif event_type == "blur": 358 if self.isFocusing: 359 self.isFocusing = False 360 self.onClickCancel() 361 362 elif event_type == "losecapture": 363 if self.isCapturing: 364 self.isCapturing = False 365 self.onClickCancel() 366 367 ButtonBase.onBrowserEvent(self, event) 368 369 # Synthesize clicks based on keyboard events AFTER the normal 370 # key handling. 371 if (DOM.eventGetTypeInt(event) & Event.KEYEVENTS) == 0: 372 return 373 374 keyCode = DOM.eventGetKeyCode(event) 375 if event_type == "keydown": 376 if keyCode == ' ': 377 self.isFocusing = True 378 self.onClickStart() 379 380 elif event_type == "keyup": 381 if self.isFocusing and keyCode == ' ': 382 self.isFocusing = False 383 self.onClick() 384 385 elif event_type == "keypress": 386 if keyCode == '\n' or keyCode == '\r': 387 self.onClickStart() 388 self.onClick()
389
390 - def setAccessKey(self, key):
391 # TODO: accessibility 392 # Focus.setAccessKey(self.getElement(), key) 393 pass
394
395 - def setEnabled(self, enabled):
396 """Sets whether this button is enabled.""" 397 if self.isEnabled() == enabled: 398 return 399 self.toggleDisabled() 400 ButtonBase.setEnabled(self, enabled) 401 if enabled: 402 self.setAriaPressed(self.getCurrentFace()) 403 else: 404 self.cleanupCaptureState()
405 # XXX - TODO: Accessibility 406
407 - def setFocus(self, focused):
408 if focused: 409 Focus.focus(self.getElement()) 410 else: 411 Focus.blur(self.getElement())
412
413 - def setHTML(self, html):
414 """Sets the current face's html.""" 415 self.getCurrentFace().setHTML(html)
416
417 - def setTabIndex(self, index):
418 Focus.setTabIndex(self.getElement(), index)
419
420 - def setText(self, text):
421 """Sets the current face's text.""" 422 self.getCurrentFace().setText(text)
423
424 - def isDown(self):
425 """Is this button down?""" 426 # 0->0, 1->1, 2->0, 3->1, 4->0, 5->1 427 return (self.DOWN_ATTRIBUTE & self.getCurrentFace().getFaceID()) > 0
428
429 - def onAttach(self):
430 """ 431 Overridden on attach to ensure that a button face has been chosen before 432 the button is displayed. 433 """ 434 self.finishSetup() 435 ButtonBase.onAttach(self)
436
437 - def onClick(self, sender=None):
438 """ 439 Called when the user finishes clicking on this button. 440 The default behavior is to fire the click event to 441 listeners. Subclasses that override onClickStart() should 442 override this method to restore the normal widget display. 443 """ 444 # Allow the click we're about to synthesize to pass through to the 445 # superclass and containing elements. Element.dispatchEvent() is 446 # synchronous, so we simply set and clear the flag within this method. 447 self.allowClick = True 448 449 # Mouse coordinates are not always available (e.g., when the click is 450 # caused by a keyboard event). 451 evt = None # we NEED to initialize evt, to be in the same namespace 452 # as the evt *inside* of JS block 453 454 # We disallow setting the button here, because IE doesn't provide the 455 # button property for click events. 456 457 # there is a good explanation about all the arguments of initMouseEvent 458 # at: https://developer.mozilla.org/En/DOM:event.initMouseEvent 459 460 DOM.buttonClick(self.getElement()) 461 self.allowClick = False
462
463 - def onClickCancel(self):
464 """ 465 Called when the user aborts a click in progress; for example, by 466 dragging the mouse outside of the button before releasing the mouse 467 button. Subclasses that override onClickStart() should override this 468 method to restore the normal widget display. 469 """ 470 pass
471
472 - def onClickStart(self):
473 """ 474 Called when the user begins to click on this button. Subclasses may 475 override this method to display the start of the click visually; such 476 subclasses should also override onClick() and onClickCancel() to 477 restore normal visual state. Each onClickStart will eventually be 478 followed by either onClick or onClickCancel, depending on whether 479 the click is completed. 480 """ 481 pass
482
483 - def onDetach(self):
486
487 - def setDown(self, down):
488 """Sets whether this button is down.""" 489 if down != self.isDown(): 490 self.toggleDown()
491
492 - def finishSetup(self): #default
493 """Common setup between constructors.""" 494 if self.curFace is None: 495 self.setCurrentFace(self.getUpFace())
496
497 - def fireClickListeners(self, nativeEvent):
498 # TODO(ecc) Once event triggering is committed, should fire a 499 # click event instead. 500 self.fireEvent(ClickEvent()) # TODO: ???
501
502 - def fireEvent(self):
503 # TODO: there is no standard mechanism in pyjamas? 504 pass
505
506 - def getCurrentFace(self):
507 """ 508 Gets the current face of the button. 509 Implementation note: Package so we can use it when testing the 510 button. 511 """ 512 self.finishSetup() 513 return self.curFace
514
515 - def isHovering(self):
516 """Is the mouse hovering over this button? Returns True""" 517 return (self.HOVERING_ATTRIBUTE & self.getCurrentFace().getFaceID()) > 0
518
519 - def setHovering(self, hovering):
520 """Sets whether this button is hovering.""" 521 if hovering != self.isHovering(): # TODO 522 self.toggleHover()
523
524 - def toggleDown(self):
525 """Toggle the up/down attribute.""" 526 newFaceID = self.getCurrentFace().getFaceID() ^ self.DOWN_ATTRIBUTE 527 self.setCurrentFaceFromID(newFaceID) # newFaceId: 0,1,2,3,4,5
528
529 - def cleanupCaptureState(self):
530 """ 531 Resets internal state if this button can no longer service events. 532 This can occur when the widget becomes detached or disabled. 533 """ 534 if not self.isCapturing and not self.isFocusing: 535 return 536 DOM.releaseCapture(self.getElement()) 537 self.isCapturing = False 538 self.isFocusing = False 539 self.onClickCancel()
540
541 - def createFace(self, delegateTo, name, faceID):
542 # TODO: name and faceID 543 # TODO: maybe no need to break it into this pieces 544 face = Face(self, delegateTo) 545 face.setName(name) 546 face.setFaceID(faceID) 547 return face
548
549 - def getFaceFromID(self, face_id):
550 if (face_id == self.DOWN): 551 return self.getDownFace() 552 elif(face_id == self.UP): 553 return self.getUpFace() 554 elif (face_id == self.DOWN_HOVERING): 555 return self.getDownHoveringFace() 556 elif (face_id == self.UP_HOVERING): 557 return self.getUpHoveringFace() 558 elif (face_id == self.UP_DISABLED): 559 return self.getUpDisabledFace() 560 elif (face_id == self.DOWN_DISABLED): 561 return self.getDownDisabledFace() 562 else: 563 raise Exception("%s is not a known face id." % str(face_id))
564
565 - def setAriaPressed(self, newFace):
566 pressed = (newFace.getFaceID() & self.DOWN_ATTRIBUTE) == 1
567 # XXX: TODO Accessibility 568
569 - def setCurrentFace(self, newFace):
570 """Implementation note: default access for testing.""" 571 if self.curFace == newFace: 572 return 573 if self.curFace is not None: 574 self.removeStyleDependentName(self.curFace.getName()) 575 576 self.curFace = newFace 577 self.setCurrentFaceElement(newFace.getFace()); 578 self.addStyleDependentName(self.curFace.getName()) 579 580 if self.isEnabled: 581 self.setAriaPressed(newFace) 582 #self.updateButtonFace() # TODO: should we comment out? 583 self.style_name = self.getStyleName()
584
585 - def setCurrentFaceFromID(self, faceID):
586 """Sets the current face based on the faceID.""" 587 # this is a new method compared by gwt. Likely to be removed. 588 newFace = self.getFaceFromID(faceID) 589 self.setCurrentFace(newFace)
590
591 - def setCurrentFaceElement(self, newFaceElement):
592 # XXX: TODO 593 if self.curFaceElement == newFaceElement: 594 return 595 if self.curFaceElement is not None: 596 DOM.removeChild(self.getElement(), self.curFaceElement) 597 598 self.curFaceElement = newFaceElement 599 DOM.appendChild(self.getElement(), self.curFaceElement)
600
601 - def setDownDisabledFace(self, downDisabled):
602 """Sets the downDisabled face of the button.""" 603 self.downDisabled = downDisabled
604
605 - def setDownFace(self, down):
606 """Sets the down face of the button.""" 607 self.down = down
608
609 - def setDownHoveringFace(self, downHovering):
610 """Sets the downHovering face of the button.""" 611 self.downHovering = downHovering
612
613 - def setUpDisabledFace(self, upDisabled):
614 """Sets the upDisabled face of the button.""" 615 self.upDisabled = upDisabled
616
617 - def setUpFace(self, up):
618 """Sets the up face of the button.""" 619 self.up = up
620
621 - def setUpHoveringFace(self, upHovering):
622 """Sets the upHovering face of the button.""" 623 self.upHovering = upHovering
624
625 - def toggleDisabled(self):
626 """Toggle the disabled attribute.""" 627 # Toggle disabled. 628 newFaceID = self.getCurrentFace().getFaceID() ^ self.DISABLED_ATTRIBUTE 629 630 # Remove hovering. 631 newFaceID &= ~self.HOVERING_ATTRIBUTE 632 633 # Sets the current face. 634 self.setCurrentFaceFromID(newFaceID)
635
636 - def toggleHover(self):
637 """Toggle the hovering attribute.""" 638 639 # Toggle hovering. 640 newFaceID = self.getCurrentFace().getFaceID() ^ self.HOVERING_ATTRIBUTE 641 642 # Remove disabled. 643 newFaceID &= ~self.DISABLED_ATTRIBUTE 644 self.setCurrentFaceFromID(newFaceID)
645 646 647 Factory.registerClass('pyjamas.ui.CustomButton', 'CustomButton', CustomButton) 648