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