Package muntjac :: Package ui :: Module tree
[hide private]
[frames] | no frames]

Source Code for Module muntjac.ui.tree

   1  # Copyright (C) 2012 Vaadin Ltd.  
   2  # Copyright (C) 2012 Richard Lincoln 
   3  #  
   4  # Licensed under the Apache License, Version 2.0 (the "License");  
   5  # you may not use this file except in compliance with the License.  
   6  # You may obtain a copy of 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,  
  12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13  # See the License for the specific language governing permissions and  
  14  # limitations under the License. 
  15   
  16  """Used to select an item (or multiple items) from a hierarchical set of 
  17  items.""" 
  18   
  19  from collections import deque 
  20   
  21  from muntjac.util import clsname, OrderedSet 
  22  from muntjac.terminal.key_mapper import KeyMapper 
  23  from muntjac.terminal.gwt.client.mouse_event_details import MouseEventDetails 
  24   
  25  from muntjac.data import container 
  26   
  27  from muntjac.event import action 
  28  from muntjac.event.data_bound_transferable import DataBoundTransferable 
  29  from muntjac.event.dd.acceptcriteria.target_detail_is import TargetDetailIs 
  30  from muntjac.event.dd.drop_target import IDropTarget 
  31  from muntjac.event.dd.drag_source import IDragSource 
  32   
  33  from muntjac.event.dd.acceptcriteria.server_side_criterion import \ 
  34      ServerSideCriterion 
  35   
  36  from muntjac.event.dd.acceptcriteria.client_side_criterion import \ 
  37      ClientSideCriterion 
  38   
  39  from muntjac.event.item_click_event import \ 
  40      ItemClickEvent, IItemClickNotifier, IItemClickSource, ITEM_CLICK_METHOD,\ 
  41      IItemClickListener 
  42   
  43  from muntjac.ui.component import Event as ComponentEvent 
  44   
  45  from muntjac.ui.abstract_select import \ 
  46      AbstractSelect, MultiSelectMode, AbstractSelectTargetDetails 
  47   
  48  from muntjac.terminal.gwt.client.ui.v_tree import \ 
  49      VTree 
  50   
  51  from muntjac.terminal.gwt.client.ui.dd.vertical_drop_location import \ 
  52      VerticalDropLocation 
  53   
  54  from muntjac.data.util.container_hierarchical_wrapper import \ 
  55      ContainerHierarchicalWrapper 
  56   
  57  from muntjac.data.util.indexed_container import IndexedContainer 
58 59 60 -class Tree(AbstractSelect, container.IHierarchical, action.IContainer, 61 IItemClickSource, IItemClickNotifier, IDragSource, IDropTarget):
62 """Tree component. A Tree can be used to select an item (or multiple 63 items) from a hierarchical set of items. 64 65 @author: Vaadin Ltd. 66 @author: Richard Lincoln 67 @version: 1.1.2 68 """ 69 70 CLIENT_WIDGET = None #ClientWidget(VTree, LoadStyle.EAGER) 71
72 - def __init__(self, caption=None, dataSource=None):
73 """Creates a new tree with caption and connect it to a IContainer. 74 """ 75 #: Set of expanded nodes. 76 self._expanded = set() 77 78 #: List of action handlers. 79 self._actionHandlers = None 80 81 #: Action mapper. 82 self._actionMapper = None 83 84 #: Is the tree selectable on the client side. 85 self._selectable = True 86 87 #: Flag to indicate sub-tree loading 88 self._partialUpdate = False 89 90 #: Holds a itemId which was recently expanded 91 self._expandedItemId = None 92 93 #: a flag which indicates initial paint. After this flag set 94 # true partial updates are allowed. 95 self._initialPaint = True 96 97 #: Item tooltip generator 98 self._itemDescriptionGenerator = None 99 100 self._dragMode = TreeDragMode.NONE 101 self._multiSelectMode = MultiSelectMode.DEFAULT 102 103 super(Tree, self).__init__() 104 105 if caption is not None: 106 self.setCaption(caption) 107 108 if dataSource is not None: 109 self.setContainerDataSource(dataSource) 110 111 self._itemStyleGenerator = None 112 self._dropHandler = None
113 114 # Expanding and collapsing 115
116 - def isExpanded(self, itemId):
117 """Check is an item is expanded 118 119 @param itemId: 120 the item id. 121 @return: true iff the item is expanded. 122 """ 123 return itemId in self._expanded
124 125
126 - def expandItem(self, itemId, sendChildTree=None):
127 """Expands an item. 128 129 @param itemId: 130 the item id. 131 @param sendChildTree: 132 flag to indicate if client needs subtree or not (may 133 be cached) 134 @return: True iff the expand operation succeeded 135 """ 136 if sendChildTree is None: 137 success = self.expandItem(itemId, True) 138 self.requestRepaint() 139 return success 140 else: 141 # Succeeds if the node is already expanded 142 if self.isExpanded(itemId): 143 return True 144 145 # Nodes that can not have children are not expandable 146 if not self.areChildrenAllowed(itemId): 147 return False 148 149 # Expands 150 self._expanded.add(itemId) 151 152 self._expandedItemId = itemId 153 if self._initialPaint: 154 self.requestRepaint() 155 elif sendChildTree: 156 self.requestPartialRepaint() 157 158 self.fireExpandEvent(itemId) 159 160 return True
161 162
163 - def requestRepaint(self):
164 super(Tree, self).requestRepaint() 165 self._partialUpdate = False
166 167
168 - def requestPartialRepaint(self):
169 super(Tree, self).requestRepaint() 170 self._partialUpdate = True
171 172
173 - def expandItemsRecursively(self, startItemId):
174 """Expands the items recursively 175 176 Expands all the children recursively starting from an item. 177 Operation succeeds only if all expandable items are expanded. 178 179 @return: True iff the expand operation succeeded 180 """ 181 result = True 182 183 # Initial stack 184 todo = deque() 185 todo.append(startItemId) 186 # Expands recursively 187 while len(todo) > 0: 188 idd = todo.pop() 189 if (self.areChildrenAllowed(idd) 190 and not self.expandItem(idd, False)): 191 result = False 192 if self.hasChildren(idd): 193 for c in self.getChildren(idd): 194 todo.append(c) 195 196 self.requestRepaint() 197 return result
198 199
200 - def collapseItem(self, itemId):
201 """Collapses an item. 202 203 @param itemId: 204 the item id. 205 @return: True iff the collapse operation succeeded 206 """ 207 # Succeeds if the node is already collapsed 208 if not self.isExpanded(itemId): 209 return True 210 211 # Collapse 212 self._expanded.remove(itemId) 213 self.requestRepaint() 214 self.fireCollapseEvent(itemId) 215 return True
216 217
218 - def collapseItemsRecursively(self, startItemId):
219 """Collapses the items recursively. 220 221 Collapse all the children recursively starting from an item. 222 Operation succeeds only if all expandable items are collapsed. 223 224 @return: True iff the collapse operation succeeded 225 """ 226 result = True 227 228 # Initial stack 229 todo = deque() 230 todo.append(startItemId) 231 232 # Collapse recursively 233 while len(todo) > 0: 234 idd = todo.pop() 235 if self.areChildrenAllowed(idd) and not self.collapseItem(idd): 236 result = False 237 if self.hasChildren(idd): 238 for c in self.getChildren(idd): 239 todo.append(c) 240 241 return result
242 243
244 - def isSelectable(self):
245 """Returns the current selectable state. Selectable determines if the 246 a node can be selected on the client side. Selectable does not affect 247 L{setValue} or L{select}. 248 249 The tree is selectable by default. 250 251 @return: the current selectable state. 252 """ 253 return self._selectable
254 255
256 - def setSelectable(self, selectable):
257 """Sets the selectable state. Selectable determines if the a node can 258 be selected on the client side. Selectable does not affect L{setValue} 259 or L{select}. 260 261 The tree is selectable by default. 262 263 @param selectable: 264 The new selectable state. 265 """ 266 if self._selectable != selectable: 267 self._selectable = selectable 268 self.requestRepaint()
269 270
271 - def setMultiselectMode(self, mode):
272 """Sets the behavior of the multiselect mode 273 274 @param mode: 275 The mode to set 276 """ 277 if self._multiSelectMode != mode and mode is not None: 278 self._multiSelectMode = mode 279 self.requestRepaint()
280 281
282 - def getMultiselectMode(self):
283 """Returns the mode the multiselect is in. The mode controls 284 how multiselection can be done. 285 286 @return: The mode 287 """ 288 return self._multiSelectMode
289 290
291 - def changeVariables(self, source, variables):
292 if 'clickedKey' in variables: 293 key = variables.get('clickedKey') 294 295 idd = self.itemIdMapper.get(key) 296 evt = variables.get('clickEvent') 297 details = MouseEventDetails.deSerialize(evt) 298 item = self.getItem(idd) 299 if item is not None: 300 event = ItemClickEvent(self, item, idd, None, details) 301 self.fireEvent(event) 302 303 if not self.isSelectable() and 'selected' in variables: 304 # Not-selectable is a special case, AbstractSelect does not 305 # support. TODO: could be optimized. 306 variables = dict(variables) 307 del variables['selected'] 308 309 # Collapses the nodes 310 if 'collapse' in variables: 311 keys = variables.get('collapse') 312 for key in keys: 313 idd = self.itemIdMapper.get(key) 314 if idd is not None and self.isExpanded(idd): 315 self._expanded.remove(idd) 316 self.fireCollapseEvent(idd) 317 318 # Expands the nodes 319 if 'expand' in variables: 320 sendChildTree = False 321 if 'requestChildTree' in variables: 322 sendChildTree = True 323 324 keys = variables.get('expand') 325 for key in keys: 326 idd = self.itemIdMapper.get(key) 327 if idd is not None: 328 self.expandItem(idd, sendChildTree) 329 330 # AbstractSelect cannot handle multiselection so we 331 # handle it ourself 332 if ('selected' in variables 333 and self.isMultiSelect() 334 and self._multiSelectMode == MultiSelectMode.DEFAULT): 335 self.handleSelectedItems(variables) 336 variables = dict(variables) 337 del variables['selected'] 338 339 # Selections are handled by the select component 340 super(Tree, self).changeVariables(source, variables) 341 342 # Actions 343 if 'action' in variables: 344 st = variables.get('action').split(',') # FIXME: StringTokenizer 345 if len(st) == 2: 346 itemId = self.itemIdMapper.get(st[0].strip()) 347 action = self._actionMapper.get(st[1].strip()) 348 if (action is not None 349 and ((itemId is None) or self.containsId(itemId)) 350 and self._actionHandlers is not None): 351 for ah in self._actionHandlers: 352 ah.handleAction(action, self, itemId)
353 354
355 - def handleSelectedItems(self, variables):
356 """Handles the selection 357 358 @param variables: 359 The variables sent to the server from the client 360 """ 361 ka = variables.get('selected') 362 363 # Converts the key-array to id-set 364 s = list() 365 for i in range(len(ka)): 366 idd = self.itemIdMapper.get(ka[i]) 367 if (not self.isNullSelectionAllowed() 368 and (idd is None) 369 or (idd == self.getNullSelectionItemId())): 370 # skip empty selection if null selection is not allowed 371 self.requestRepaint() 372 elif idd is not None and self.containsId(idd): 373 s.append(idd) 374 375 if not self.isNullSelectionAllowed() and len(s) < 1: 376 # empty selection not allowed, keep old value 377 self.requestRepaint() 378 return 379 380 self.setValue(s, True)
381 382
383 - def paintContent(self, target):
384 """Paints any needed component-specific things to the given UIDL 385 stream. 386 387 @see: L{AbstractComponent.paintContent} 388 """ 389 self._initialPaint = False 390 391 if self._partialUpdate: 392 target.addAttribute('partialUpdate', True) 393 target.addAttribute('rootKey', 394 self.itemIdMapper.key(self._expandedItemId)) 395 else: 396 self.getCaptionChangeListener().clear() 397 398 # The tab ordering number 399 if self.getTabIndex() > 0: 400 target.addAttribute('tabindex', self.getTabIndex()) 401 402 # Paint tree attributes 403 if self.isSelectable(): 404 if self.isMultiSelect(): 405 target.addAttribute('selectmode', 'multi') 406 else: 407 target.addAttribute('selectmode', 'single') 408 409 if self.isMultiSelect(): 410 try: 411 idx = MultiSelectMode.values().index( 412 self._multiSelectMode) 413 except ValueError: 414 idx = -1 415 target.addAttribute('multiselectmode', idx) 416 else: 417 target.addAttribute('selectmode', 'none') 418 419 if self.isNewItemsAllowed(): 420 target.addAttribute('allownewitem', True) 421 422 if self.isNullSelectionAllowed(): 423 target.addAttribute('nullselect', True) 424 425 if self._dragMode != TreeDragMode.NONE: 426 target.addAttribute('dragMode', 427 TreeDragMode.ordinal(self._dragMode)) 428 429 # Initialize variables 430 actionSet = OrderedSet() 431 432 # rendered selectedKeys 433 selectedKeys = list() 434 expandedKeys = list() 435 436 # Iterates through hierarchical tree using a stack of iterators 437 iteratorStack = deque() 438 if self._partialUpdate: 439 ids = self.getChildren(self._expandedItemId) 440 else: 441 ids = self.rootItemIds() 442 443 if ids is not None: 444 iteratorStack.append( iter(ids) ) 445 446 # Body actions - Actions which has the target null and can be invoked 447 # by right clicking on the Tree body 448 if self._actionHandlers is not None: 449 keys = list() 450 for ah in self._actionHandlers: 451 452 # Getting action for the null item, which in this case 453 # means the body item 454 aa = ah.getActions(None, self) 455 if aa is not None: 456 for ai in range(len(aa)): 457 akey = self._actionMapper.key(aa[ai]) 458 actionSet.add(aa[ai]) 459 keys.append(akey) 460 461 target.addAttribute('alb', keys) 462 463 while len(iteratorStack) > 0: 464 465 # Gets the iterator for current tree level 466 i = iteratorStack[-1] # peek 467 468 try: 469 # Adds the item on current level 470 itemId = i.next() 471 472 # Starts the item / node 473 isNode = self.areChildrenAllowed(itemId) 474 if isNode: 475 target.startTag('node') 476 else: 477 target.startTag('leaf') 478 479 if self._itemStyleGenerator is not None: 480 stylename = self._itemStyleGenerator.getStyle(itemId) 481 if stylename is not None: 482 target.addAttribute('style', stylename) 483 484 if self._itemDescriptionGenerator is not None: 485 description = self._itemDescriptionGenerator\ 486 .generateDescription(self, itemId, None) 487 if description is not None and description != "": 488 target.addAttribute("descr", description) 489 490 # Adds the attributes 491 target.addAttribute('caption', self.getItemCaption(itemId)) 492 icon = self.getItemIcon(itemId) 493 if icon is not None: 494 target.addAttribute('icon', self.getItemIcon(itemId)) 495 496 key = self.itemIdMapper.key(itemId) 497 target.addAttribute('key', key) 498 if self.isSelected(itemId): 499 target.addAttribute('selected', True) 500 selectedKeys.append(key) 501 502 if self.areChildrenAllowed(itemId) and self.isExpanded(itemId): 503 target.addAttribute('expanded', True) 504 expandedKeys.append(key) 505 506 # Add caption change listener 507 self.getCaptionChangeListener().addNotifierForItem(itemId) 508 509 # Actions 510 if self._actionHandlers is not None: 511 keys = list() 512 ahi = iter(self._actionHandlers) 513 while True: 514 try: 515 aa = ahi.next().getActions(itemId, self) 516 if aa is not None: 517 for ai in range(len(aa)): 518 akey = self._actionMapper.key(aa[ai]) 519 actionSet.add(aa[ai]) 520 keys.append(akey) 521 except StopIteration: 522 break 523 target.addAttribute('al', keys) 524 525 # Adds the children if expanded, or close the tag 526 if (self.isExpanded(itemId) 527 and self.hasChildren(itemId) 528 and self.areChildrenAllowed(itemId)): 529 iteratorStack.append( iter(self.getChildren(itemId)) ) 530 elif isNode: 531 target.endTag('node') 532 else: 533 target.endTag('leaf') 534 535 # If the level is finished, back to previous tree level 536 except StopIteration: 537 # Removes used iterator from the stack 538 iteratorStack.pop() 539 540 # Closes node 541 if len(iteratorStack) > 0: 542 target.endTag('node') 543 544 # Actions 545 if len(actionSet) > 0: 546 target.addVariable(self, 'action', '') 547 target.startTag('actions') 548 i = actionSet 549 for a in actionSet: 550 target.startTag('action') 551 if a.getCaption() is not None: 552 target.addAttribute('caption', a.getCaption()) 553 554 if a.getIcon() is not None: 555 target.addAttribute('icon', a.getIcon()) 556 557 target.addAttribute('key', self._actionMapper.key(a)) 558 target.endTag('action') 559 560 target.endTag('actions') 561 562 if self._partialUpdate: 563 self._partialUpdate = False 564 else: 565 # Selected 566 target.addVariable(self, 'selected', selectedKeys) 567 568 # Expand and collapse 569 target.addVariable(self, 'expand', list()) 570 target.addVariable(self, 'collapse', list()) 571 572 # New items 573 target.addVariable(self, 'newitem', list()) 574 575 if self._dropHandler is not None: 576 self._dropHandler.getAcceptCriterion().paint(target)
577 578
579 - def areChildrenAllowed(self, itemId):
580 """Tests if the Item with given ID can have any children. 581 582 @see: L{IHierarchical.areChildrenAllowed} 583 """ 584 return self.items.areChildrenAllowed(itemId)
585 586
587 - def getChildren(self, itemId):
588 """Gets the IDs of all Items that are children of the specified Item. 589 590 @see: L{IHierarchical.getChildren} 591 """ 592 return self.items.getChildren(itemId)
593 594
595 - def getParent(self, itemId=None):
596 """Gets the ID of the parent Item of the specified Item. 597 598 @see: L{IHierarchical.getParent} 599 """ 600 if itemId is not None: 601 return self.items.getParent(itemId) 602 else: 603 return super(Tree, self).getParent()
604 605
606 - def hasChildren(self, itemId):
607 """Tests if the Item specified with C{itemId} has child 608 Items. 609 610 @see: L{IHierarchical.hasChildren} 611 """ 612 return self.items.hasChildren(itemId)
613 614
615 - def isRoot(self, itemId):
616 """Tests if the Item specified with C{itemId} is a root 617 Item. 618 619 @see: L{IHierarchical.isRoot} 620 """ 621 return self.items.isRoot(itemId)
622 623
624 - def rootItemIds(self):
625 """Gets the IDs of all Items in the container that don't have a 626 parent. 627 628 @see: L{IHierarchical.rootItemIds} 629 """ 630 return self.items.rootItemIds()
631 632
633 - def setChildrenAllowed(self, itemId, areChildrenAllowed):
634 """Sets the given Item's capability to have children. 635 636 @see: L{IHierarchical.setChildrenAllowed} 637 """ 638 success = self.items.setChildrenAllowed(itemId, areChildrenAllowed) 639 if success: 640 self.requestRepaint() 641 642 return success
643 644
645 - def setParent(self, itemId, newParentId=None):
646 if newParentId is not None: 647 success = self.items.setParent(itemId, newParentId) 648 if success: 649 self.requestRepaint() 650 651 return success 652 else: 653 parent = itemId 654 super(Tree, self).setParent(parent)
655 656
657 - def setContainerDataSource(self, newDataSource):
658 """Sets the IContainer that serves as the data source of the viewer. 659 660 @see: L{container.Viewer.setContainerDataSource} 661 """ 662 if newDataSource is None: 663 # Note: using wrapped IndexedContainer to match constructor 664 # (super creates an IndexedContainer, which is then wrapped). 665 newDataSource = ContainerHierarchicalWrapper(IndexedContainer()) 666 667 # Assure that the data source is ordered by making unordered 668 # containers ordered by wrapping them 669 if issubclass(newDataSource.__class__, container.IHierarchical): 670 super(Tree, self).setContainerDataSource(newDataSource) 671 else: 672 super(Tree, self).setContainerDataSource( 673 ContainerHierarchicalWrapper(newDataSource))
674 675
676 - def addListener(self, listener, iface):
677 """Adds the expand/collapse listener. 678 679 @param listener: 680 the listener to be added. 681 """ 682 if (isinstance(listener, ICollapseListener) and 683 (iface is None or issubclass(iface, ICollapseListener))): 684 self.registerListener(CollapseEvent, 685 listener, COLLAPSE_METHOD) 686 687 if (isinstance(listener, IExpandListener) and 688 (iface is None or issubclass(iface, IExpandListener))): 689 self.registerListener(ExpandEvent, 690 listener, EXPAND_METHOD) 691 692 if (isinstance(listener, IItemClickListener) and 693 (iface is None or issubclass(iface, IItemClickListener))): 694 self.registerListener(VTree.ITEM_CLICK_EVENT_ID, 695 ItemClickEvent, listener, ITEM_CLICK_METHOD) 696 697 super(Tree, self).addListener(listener, iface)
698 699
700 - def addCallback(self, callback, eventType=None, *args):
701 if eventType is None: 702 eventType = callback._eventType 703 704 if issubclass(eventType, CollapseEvent): 705 self.registerCallback(CollapseEvent, callback, None, *args) 706 707 elif issubclass(eventType, ExpandEvent): 708 self.registerCallback(ExpandEvent, callback, None, *args) 709 710 elif issubclass(eventType, ItemClickEvent): 711 self.registerCallback(ItemClickEvent, callback, 712 VTree.ITEM_CLICK_EVENT_ID, *args) 713 714 else: 715 super(Tree, self).addCallback(callback, eventType, *args)
716 717
718 - def removeListener(self, listener, iface=None):
719 """Removes the expand/collapse listener. 720 721 @param listener: 722 the listener to be removed. 723 """ 724 if (isinstance(listener, ICollapseListener) and 725 (iface is None or issubclass(iface, ICollapseListener))): 726 self.withdrawListener(CollapseEvent, 727 listener, COLLAPSE_METHOD) 728 729 if (isinstance(listener, IExpandListener) and 730 (iface is None or issubclass(iface, IExpandListener))): 731 self.withdrawListener(ExpandEvent, 732 listener, EXPAND_METHOD) 733 734 if (isinstance(listener, IItemClickListener) and 735 (iface is None or issubclass(iface, IItemClickListener))): 736 self.withdrawListener(VTree.ITEM_CLICK_EVENT_ID, 737 ItemClickEvent, listener) 738 739 super(Tree, self).removeListener(listener, iface)
740 741
742 - def removeCallback(self, callback, eventType=None):
743 if eventType is None: 744 eventType = callback._eventType 745 746 if issubclass(eventType, CollapseEvent): 747 self.withdrawCallback(CollapseEvent, callback) 748 749 elif issubclass(eventType, ExpandEvent): 750 self.withdrawCallback(ExpandEvent, callback) 751 752 elif issubclass(eventType, ItemClickEvent): 753 self.withdrawCallback(ItemClickEvent, callback, 754 VTree.ITEM_CLICK_EVENT_ID) 755 756 else: 757 super(Tree, self).removeCallback(callback, eventType)
758 759
760 - def fireExpandEvent(self, itemId):
761 """Emits the expand event. 762 763 @param itemId: 764 the item id. 765 """ 766 event = ExpandEvent(self, itemId) 767 self.fireEvent(event)
768 769
770 - def fireCollapseEvent(self, itemId):
771 """Emits collapse event. 772 773 @param itemId: 774 the item id. 775 """ 776 event = CollapseEvent(self, itemId) 777 self.fireEvent(event)
778 779
780 - def addActionHandler(self, actionHandler):
781 """Adds an action handler. 782 783 @see: L{IContainer.addActionHandler} 784 """ 785 if actionHandler is not None: 786 if self._actionHandlers is None: 787 self._actionHandlers = list() 788 self._actionMapper = KeyMapper() 789 790 if actionHandler not in self._actionHandlers: 791 self._actionHandlers.append(actionHandler) 792 self.requestRepaint()
793 794
795 - def removeActionHandler(self, actionHandler):
796 """Removes an action handler. 797 798 @see: L{IContainer.removeActionHandler} 799 """ 800 if (self._actionHandlers is not None 801 and actionHandler in self._actionHandlers): 802 self._actionHandlers.remove(actionHandler) 803 804 if len(self._actionHandlers) > 0: 805 self._actionHandlers = None 806 self._actionMapper = None 807 808 self.requestRepaint()
809 810
811 - def removeAllActionHandlers(self):
812 """Removes all action handlers""" 813 self._actionHandlers = None 814 self._actionMapper = None 815 self.requestRepaint()
816 817
818 - def getVisibleItemIds(self):
819 """Gets the visible item ids. 820 821 @see: L{Select.getVisibleItemIds} 822 """ 823 visible = list() 824 # Iterates trough hierarchical tree using a stack of iterators 825 iteratorStack = deque() 826 ids = self.rootItemIds() 827 if ids is not None: 828 iteratorStack.append(ids) 829 while len(iteratorStack) > 0: 830 # Gets the iterator for current tree level 831 i = iter( iteratorStack[-1] ) 832 833 # If the level is finished, back to previous tree level 834 try: 835 itemId = i.next() 836 visible.append(itemId) 837 # Adds children if expanded, or close the tag 838 if self.isExpanded(itemId) and self.hasChildren(itemId): 839 iteratorStack.append( self.getChildren(itemId) ) 840 except StopIteration: 841 # Removes used iterator from the stack 842 # Adds the item on current level 843 iteratorStack.pop() 844 845 return visible
846 847
848 - def setNullSelectionItemId(self, nullSelectionItemId):
849 """Tree does not support C{setNullSelectionItemId}. 850 851 @see: L{AbstractSelect.setNullSelectionItemId} 852 """ 853 if nullSelectionItemId is not None: 854 raise NotImplementedError
855 856
857 - def setNewItemsAllowed(self, allowNewOptions):
858 """Adding new items is not supported. 859 860 @raise NotImplementedError: 861 if set to true. 862 @see: L{Select.setNewItemsAllowed} 863 """ 864 if allowNewOptions: 865 raise NotImplementedError
866 867
868 - def setLazyLoading(self, useLazyLoading):
869 """Tree does not support lazy options loading mode. Setting this 870 true will throw NotImplementedError. 871 872 @see: L{Select.setLazyLoading} 873 """ 874 if useLazyLoading: 875 raise NotImplementedError, \ 876 'Lazy options loading is not supported by Tree.'
877 878
879 - def setItemStyleGenerator(self, itemStyleGenerator):
880 """Sets the L{IItemStyleGenerator} to be used with this tree. 881 882 @param itemStyleGenerator: 883 item style generator or null to remove generator 884 """ 885 if self._itemStyleGenerator != itemStyleGenerator: 886 self._itemStyleGenerator = itemStyleGenerator 887 self.requestRepaint()
888 889
890 - def getItemStyleGenerator(self):
891 """@return: the current L{IItemStyleGenerator} for this tree. 892 C{None} if L{IItemStyleGenerator} is not set. 893 """ 894 return self._itemStyleGenerator
895 896
897 - def removeItem(self, itemId):
898 return super(Tree, self).removeItem(itemId)
899 900
901 - def getDropHandler(self):
902 return self._dropHandler
903 904
905 - def setDropHandler(self, dropHandler):
906 self._dropHandler = dropHandler
907 908
909 - def translateDropTargetDetails(self, clientVariables):
910 return TreeTargetDetails(clientVariables, self)
911 912
913 - def key(self, itemId):
914 """Helper API for L{TreeDropCriterion} 915 """ 916 return self.itemIdMapper.key(itemId)
917 918
919 - def setDragMode(self, dragMode):
920 """Sets the drag mode that controls how Tree behaves as a 921 L{IDragSource}. 922 """ 923 self._dragMode = dragMode 924 self.requestRepaint()
925 926
927 - def getDragMode(self):
928 """@return: the drag mode that controls how Tree behaves as a 929 L{IDragSource}. 930 931 @see: L{TreeDragMode} 932 """ 933 return self._dragMode
934 935
936 - def getTransferable(self, payload):
937 transferable = TreeTransferable(self, payload) 938 # updating drag source variables 939 obj = payload.get('itemId') 940 941 if obj is not None: 942 transferable.setData('itemId', self.itemIdMapper.get(obj)) 943 944 return transferable
945 946
947 - def setItemDescriptionGenerator(self, generator):
948 """Set the item description generator which generates tooltips for 949 the tree items 950 951 @param generator: 952 The generator to use or null to disable 953 """ 954 if generator != self._itemDescriptionGenerator: 955 self._itemDescriptionGenerator = generator 956 self.requestRepaint()
957 958
960 """Get the item description generator which generates tooltips for 961 tree items. 962 """ 963 return self._itemDescriptionGenerator
964
965 966 -class TreeDragMode(object):
967 """Supported drag modes for Tree.""" 968 969 #: When drag mode is NONE, dragging from Tree is not supported. Browsers 970 # may still support selecting text/icons from Tree which can initiate 971 # HTML 5 style drag and drop operation. 972 NONE = 'NONE' 973 974 #: When drag mode is NODE, users can initiate drag from Tree nodes that 975 # represent L{Item}s in from the backed L{IContainer}. 976 NODE = 'NODE' 977 978 _values = [NONE, NODE] 979 980 @classmethod
981 - def values(cls):
982 return cls._enum_values[:]
983 984 @classmethod
985 - def ordinal(cls, val):
986 return cls._values.index(val)
987
988 989 -class ExpandEvent(ComponentEvent):
990 """Event to fired when a node is expanded. ExapandEvent is fired when a 991 node is to be expanded. it can me used to dynamically fill the sub-nodes 992 of the node. 993 994 @author: Vaadin Ltd. 995 @author: Richard Lincoln 996 @version: 1.1.2 997 """ 998
999 - def __init__(self, source, expandedItemId):
1000 """New instance of options change event 1001 1002 @param source: 1003 the Source of the event. 1004 @param expandedItemId: 1005 """ 1006 super(ExpandEvent, self).__init__(source) 1007 self._expandedItemId = expandedItemId
1008 1009
1010 - def getItemId(self):
1011 """Node where the event occurred. 1012 1013 @return: the source of the event. 1014 """ 1015 return self._expandedItemId
1016
1017 1018 -class IExpandListener(object):
1019 """Expand event listener. 1020 1021 @author: Vaadin Ltd. 1022 @author: Richard Lincoln 1023 @version: 1.1.2 1024 """ 1025
1026 - def nodeExpand(self, event):
1027 """A node has been expanded. 1028 1029 @param event: 1030 the expand event. 1031 """ 1032 raise NotImplementedError
1033 1034 1035 EXPAND_METHOD = IExpandListener.nodeExpand
1036 1037 1038 -class CollapseEvent(ComponentEvent):
1039 """Collapse event 1040 1041 @author: Vaadin Ltd. 1042 @author: Richard Lincoln 1043 @version: 1.1.2 1044 """ 1045
1046 - def __init__(self, source, collapsedItemId):
1047 """New instance of options change event. 1048 1049 @param source: 1050 the Source of the event. 1051 @param collapsedItemId: 1052 """ 1053 super(CollapseEvent, self).__init__(source) 1054 self._collapsedItemId = collapsedItemId
1055 1056
1057 - def getItemId(self):
1058 """Gets the collapsed item id. 1059 1060 @return: the collapsed item id. 1061 """ 1062 return self._collapsedItemId
1063
1064 1065 -class ICollapseListener(object):
1066 """Collapse event listener. 1067 1068 @author: Vaadin Ltd. 1069 @author: Richard Lincoln 1070 @version: 1.1.2 1071 """ 1072
1073 - def nodeCollapse(self, event):
1074 """A node has been collapsed. 1075 1076 @param event: 1077 the collapse event. 1078 """ 1079 raise NotImplementedError
1080 1081 1082 COLLAPSE_METHOD = ICollapseListener.nodeCollapse
1083 1084 1085 -class IItemStyleGenerator(object):
1086 """IItemStyleGenerator can be used to add custom styles to tree items. 1087 The CSS class name that will be added to the cell content is 1088 C{v-tree-node-[style name]}. 1089 """ 1090
1091 - def getStyle(self, itemId):
1092 """Called by Tree when an item is painted. 1093 1094 @param itemId: 1095 The itemId of the item to be painted 1096 @return: The style name to add to this item. (the CSS class name 1097 will be v-tree-node-[style name] 1098 """ 1099 raise NotImplementedError
1100
1101 1102 -class TreeTargetDetails(AbstractSelectTargetDetails):
1103 """A L{TargetDetails} implementation with Tree specific api. 1104 """ 1105
1106 - def __init__(self, rawVariables, tree):
1107 super(TreeTargetDetails, self).__init__(rawVariables, tree)
1108 1109
1110 - def getTarget(self):
1111 return super(TreeTargetDetails, self).getTarget()
1112 1113
1114 - def getItemIdInto(self):
1115 """If the event is on a node that can not have children (see 1116 L{Tree.areChildrenAllowed}), this method returns the 1117 parent item id of the target item (see L{getItemIdOver} ). 1118 The identifier of the parent node is also returned if the cursor is 1119 on the top part of node. Else this method returns the same as 1120 L{getItemIdOver}. 1121 1122 In other words this method returns the identifier of the "folder" 1123 into the drag operation is targeted. 1124 1125 If the method returns null, the current target is on a root node or 1126 on other undefined area over the tree component. 1127 1128 The default Tree implementation marks the targetted tree node with 1129 CSS classnames v-tree-node-dragfolder and 1130 v-tree-node-caption-dragfolder (for the caption element). 1131 """ 1132 itemIdOver = self.getItemIdOver() 1133 1134 if (self.areChildrenAllowed(itemIdOver) 1135 and self.getDropLocation() == VerticalDropLocation.MIDDLE): 1136 return itemIdOver 1137 1138 return self.getParent(itemIdOver)
1139 1140
1141 - def getItemIdAfter(self):
1142 """If drop is targeted into "folder node" (see L{getItemIdInto}), this 1143 method returns the item id of the node after 1144 the drag was targeted. This method is useful when implementing drop 1145 into specific location (between specific nodes) in tree. 1146 1147 @return: the id of the item after the user targets the drop or null if 1148 "target" is a first item in node list (or the first in root 1149 node list) 1150 """ 1151 itemIdOver = self.getItemIdOver() 1152 itemIdInto2 = self.getItemIdInto() 1153 1154 if itemIdOver == itemIdInto2: 1155 return None 1156 1157 dropLocation = self.getDropLocation() 1158 1159 if VerticalDropLocation.TOP == dropLocation: 1160 # if on top of the caption area, add before 1161 itemIdInto = self.getItemIdInto() 1162 if itemIdInto is not None: 1163 # seek the previous from child list 1164 children = self.getChildren(itemIdInto) 1165 else: 1166 children = self.rootItemIds() 1167 ref = None 1168 for obj in children: 1169 if obj == itemIdOver: 1170 return ref 1171 ref = obj 1172 1173 return itemIdOver
1174
1175 1176 -class TreeTransferable(DataBoundTransferable):
1177 """Concrete implementation of L{DataBoundTransferable} for data 1178 transferred from a tree. 1179 1180 @see: L{DataBoundTransferable}. 1181 """ 1182
1183 - def __init__(self, sourceComponent, rawVariables):
1184 super(TreeTransferable, self).__init__(sourceComponent, rawVariables)
1185 1186
1187 - def getItemId(self):
1188 return self.getData('itemId')
1189 1190
1191 - def getPropertyId(self):
1192 return self.getItemCaptionPropertyId()
1193
1194 1195 1196 -class TreeDropCriterion(ServerSideCriterion):
1197 """Lazy loading accept criterion for Tree. Accepted target nodes are 1198 loaded from server once per drag and drop operation. Developer must 1199 override one method that decides accepted tree nodes for the whole Tree. 1200 1201 Initially pretty much no data is sent to client. On first required 1202 criterion check (per drag request) the client side data structure is 1203 initialized from server and no subsequent requests requests are needed 1204 during that drag and drop operation. 1205 """ 1206
1207 - def __init__(self):
1208 self._tree = None 1209 self._allowedItemIds = None
1210 1211
1212 - def getIdentifier(self):
1214 1215
1216 - def accept(self, dragEvent):
1217 dropTargetData = dragEvent.getTargetDetails() 1218 self._tree = dragEvent.getTargetDetails().getTarget() 1219 self._allowedItemIds = self.getAllowedItemIds(dragEvent, self._tree) 1220 return dropTargetData.getItemIdOver() in self._allowedItemIds
1221 1222
1223 - def paintResponse(self, target):
1224 # send allowed nodes to client so subsequent requests 1225 # can be avoided 1226 arry = list(self._allowedItemIds) 1227 for i in range(len(arry)): 1228 key = self._tree.key(arry[i]) 1229 arry[i] = key 1230 target.addAttribute('allowedIds', arry)
1231 1232
1233 - def getAllowedItemIds(self, dragEvent, tree):
1234 pass
1235
1236 1237 -class TargetItemAllowsChildren(TargetDetailIs):
1238 """A criterion that accepts L{Transferable} only directly on a tree 1239 node that can have children. 1240 1241 Class is singleton, use L{TargetItemAllowsChildren.get} to get the 1242 instance. 1243 1244 @see: Tree.setChildrenAllowed 1245 """ 1246 1247 INSTANCE = None 1248 1249 @classmethod
1250 - def get(cls):
1251 return cls.INSTANCE
1252 1253
1254 - def __init__(self):
1255 # Uses enhanced server side check 1256 super(TargetItemAllowsChildren, self).__init__('itemIdOverIsNode', 1257 True)
1258 1259
1260 - def accept(self, dragEvent):
1261 try: 1262 # must be over tree node and in the middle of it (not top or 1263 # bottom part) 1264 eventDetails = dragEvent.getTargetDetails() 1265 1266 itemIdOver = eventDetails.getItemIdOver() 1267 if not eventDetails.getTarget().areChildrenAllowed(itemIdOver): 1268 return False 1269 1270 # return true if directly over 1271 return (eventDetails.getDropLocation() 1272 == VerticalDropLocation.MIDDLE) 1273 except Exception: 1274 return False
1275 1276 TargetItemAllowsChildren.INSTANCE = TargetItemAllowsChildren()
1277 1278 1279 -class TargetInSubtree(ClientSideCriterion):
1280 """An accept criterion that checks the parent node (or parent hierarchy) 1281 for the item identifier given in constructor. If the parent is found, 1282 content is accepted. Criterion can be used to accepts drags on a specific 1283 sub tree only. 1284 1285 The root items is also consider to be valid target. 1286 """ 1287
1288 - def __init__(self, rootId, depthToCheck=None):
1289 """Constructs a criteria that accepts the drag if the targeted Item 1290 is a descendant of Item identified by given id 1291 1292 Alternatively, constructs a criteria that accepts drops within given 1293 level below the subtree root identified by given id. 1294 1295 @param rootId: 1296 the item identifier of the parent node or the node to be 1297 sought for 1298 @param depthToCheck: 1299 the depth that tree is traversed upwards to seek for the 1300 parent, -1 means that the whole structure should be 1301 checked 1302 """ 1303 self._rootId = rootId 1304 self._depthToCheck = -1 1305 1306 if depthToCheck is not None: 1307 self._depthToCheck = depthToCheck
1308 1309
1310 - def accept(self, dragEvent):
1311 try: 1312 eventDetails = dragEvent.getTargetDetails() 1313 1314 if eventDetails.getItemIdOver() is not None: 1315 itemId = eventDetails.getItemIdOver() 1316 i = 0 1317 while (itemId is not None 1318 and (self._depthToCheck == -1) 1319 or (i <= self._depthToCheck)): 1320 if itemId == self._rootId: 1321 return True 1322 itemId = self.getParent(itemId) 1323 i += 1 1324 1325 return False 1326 except Exception: 1327 return False
1328 1329
1330 - def paintContent(self, target):
1331 super(TargetInSubtree, self).paintContent(target) 1332 target.addAttribute('depth', self._depthToCheck) 1333 target.addAttribute('key', self.key(self._rootId))
1334