1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 35 import com.oracle.javafx.scenebuilder.kit.editor.drag.DragController; 36 import com.oracle.javafx.scenebuilder.kit.editor.drag.source.DocumentDragSource; 37 import com.oracle.javafx.scenebuilder.kit.editor.drag.source.ExternalDragSource; 38 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N; 39 import static com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy.treeview.HierarchyTreeCell.HIERARCHY_FIRST_CELL; 40 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.AbstractFxmlPanelController; 41 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup; 42 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup; 43 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection; 44 import com.oracle.javafx.scenebuilder.kit.editor.util.ContextMenuController; 45 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 46 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 47 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 48 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask.Accessory; 49 50 import java.net.URL; 51 import java.util.ArrayList; 52 import java.util.HashMap; 53 import java.util.HashSet; 54 import java.util.Iterator; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Set; 58 59 import javafx.beans.property.ObjectProperty; 60 import javafx.beans.property.SimpleObjectProperty; 61 import javafx.beans.value.ObservableValue; 62 import javafx.collections.ListChangeListener; 63 import javafx.collections.ObservableList; 64 import javafx.event.EventTarget; 65 import javafx.geometry.Insets; 66 import javafx.geometry.Orientation; 67 import javafx.scene.Node; 68 import javafx.scene.Parent; 69 import javafx.scene.control.Cell; 70 import javafx.scene.control.Control; 71 import javafx.scene.control.Label; 72 import javafx.scene.control.ScrollBar; 73 import javafx.scene.control.Skin; 74 import javafx.scene.control.SkinBase; 75 import javafx.scene.control.TreeItem; 76 import javafx.scene.input.DragEvent; 77 import javafx.scene.input.Dragboard; 78 import javafx.scene.input.KeyEvent; 79 import javafx.scene.input.MouseButton; 80 import javafx.scene.input.MouseEvent; 81 import javafx.scene.input.TransferMode; 82 import javafx.scene.layout.Border; 83 import javafx.scene.layout.BorderStroke; 84 import javafx.scene.layout.BorderStrokeStyle; 85 import javafx.scene.layout.BorderWidths; 86 import javafx.scene.layout.CornerRadii; 87 import javafx.scene.layout.Pane; 88 import javafx.scene.paint.Color; 89 import javafx.scene.paint.Paint; 90 import javafx.stage.Window; 91 92 /** 93 * This class creates and controls the <b>Hierarchy Panel</b> of Scene Builder 94 * Kit. 95 * 96 * 97 */ 98 public abstract class AbstractHierarchyPanelController extends AbstractFxmlPanelController { 99 100 public enum BorderSide { 101 102 BOTTOM, RIGHT_BOTTOM_LEFT, RIGHT_LEFT, TOP_RIGHT_BOTTOM_LEFT, TOP_RIGHT_LEFT 103 } 104 105 private final HierarchyDNDController dndController = new HierarchyDNDController(this); 106 private final HierarchyAnimationScheduler animationScheduler = new HierarchyAnimationScheduler(); 107 private final ObjectProperty<DisplayOption> displayOptionProperty 108 = new SimpleObjectProperty<>(DisplayOption.INFO); 109 /** 110 * @treatAsPrivate 111 */ 112 protected TreeItem<HierarchyItem> rootTreeItem; 113 private boolean parentRingEnabled = true; 114 private Paint parentRingColor; 115 private final Map<FXOMObject, Boolean> treeItemsExpandedMapProperty = new HashMap<>(); 116 private boolean shouldEndOnExit; 117 private Label promptLabel; 118 119 // When DND few pixels of the top or bottom of the Hierarchy 120 // the user can cause it to auto-scroll until the desired target node 121 private static final double AUTO_SCROLLING_ZONE_HEIGHT = 40.0; 122 123 private Border bottomBorder; 124 private Border rightBottomLeftBorder; 125 private Border rightLeftBorder; 126 private Border topRightBottomLeftBorder; 127 private Border topRightLeftBorder; 128 // First cell : top insets = 0 instead of -2 129 private Border firstCellBottomBorder; 130 private Border firstCellRightBottomLeftBorder; 131 private Border firstCellRightLeftBorder; 132 private Border firstCellTopRightBottomLeftBorder; 133 private Border firstCellTopRightLeftBorder; 134 135 private final Border transparentBorder; 136 private final Border firstCellTransparentBorder; 137 138 private final BorderWidths cellBorderWidths = new BorderWidths(2); 139 private final Insets cellInsets = new Insets(-2, 0, -2, 0); 140 private final Insets firstCellInsets = new Insets(0, 0, -2, 0); 141 142 private static final Color DEFAULT_PARENT_RING_COLOR = Color.rgb(238, 168, 47); 143 144 /** 145 * @treatAsPrivate 146 */ 147 protected final ListChangeListener<TreeItem<HierarchyItem>> treeItemSelectionListener = change -> treeItemSelectionDidChange(); 148 149 /** 150 * Used to define the type of information displayed in the hierarchy. 151 */ 152 public enum DisplayOption { 153 154 INFO { 155 156 @Override 157 public String toString() { 158 return I18N.getString("hierarchy.displayoption.info"); 159 } 160 }, 161 FXID { 162 163 @Override 164 public String toString() { 165 return I18N.getString("hierarchy.displayoption.fxid"); 166 } 167 }, 168 NODEID { 169 170 @Override 171 public String toString() { 172 return I18N.getString("hierarchy.displayoption.nodeid"); 173 } 174 } 175 } 176 177 /* 178 * Public 179 */ 180 public AbstractHierarchyPanelController(URL fxmlURL, EditorController editorController) { 181 super(fxmlURL, I18N.getBundle(), editorController); 182 183 final BorderStroke bs = new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.SOLID, 184 CornerRadii.EMPTY, cellBorderWidths, cellInsets); 185 transparentBorder = new Border(bs); 186 final BorderStroke firstbs = new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.SOLID, 187 CornerRadii.EMPTY, cellBorderWidths, firstCellInsets); 188 firstCellTransparentBorder = new Border(firstbs); 189 } 190 191 private Label getPromptLabel() { 192 if (promptLabel == null) { 193 promptLabel = new Label(); 194 promptLabel.getStyleClass().add("hierarchy-prompt-label"); 195 promptLabel.setMouseTransparent(true); 196 } 197 return promptLabel; 198 } 199 200 /** 201 * Returns the root TreeItem. 202 * 203 * @return the root TreeItem. 204 */ 205 public final TreeItem<HierarchyItem> getRoot() { 206 return rootTreeItem; 207 } 208 209 /** 210 * Returns the display option property. 211 * 212 * @return the display option property. 213 */ 214 public ObservableValue<DisplayOption> displayOptionProperty() { 215 return displayOptionProperty; 216 } 217 218 /** 219 * Returns the type of information displayed in the hierarchy. 220 * 221 * @return the type of information displayed in the hierarchy. 222 */ 223 public final DisplayOption getDisplayOption() { 224 return displayOptionProperty.getValue(); 225 } 226 227 /** 228 * Sets the type of information displayed in the hierarchy. 229 * 230 * @param displayOption the type of information displayed in the hierarchy. 231 */ 232 public final void setDisplayOption(DisplayOption displayOption) { 233 displayOptionProperty.setValue(displayOption); 234 } 235 236 /** 237 * @return the DND controller 238 * @treatAsPrivate 239 */ 240 public final HierarchyDNDController getDNDController() { 241 return dndController; 242 } 243 244 /** 245 * @return true if the timeline is running 246 * @treatAsPrivate 247 */ 248 public final boolean isTimelineRunning() { 249 return animationScheduler.isTimelineRunning(); 250 } 251 252 /** 253 * @return true if the parent ring is enabled 254 * @treatAsPrivate 255 */ 256 public boolean isParentRingEnabled() { 257 return parentRingEnabled; 258 } 259 260 /** 261 * @param enabled the enabled value 262 * @treatAsPrivate 263 */ 264 public void setParentRingEnabled(boolean enabled) { 265 parentRingEnabled = enabled; 266 } 267 268 public Paint getParentRingColor() { 269 return parentRingColor; 270 } 271 272 public void setParentRingColor(Paint value) { 273 parentRingColor = value; 274 updateParentRingColor(); 275 } 276 277 public void setBorder(Cell<?> cell, BorderSide side) { 278 assert cell != null; 279 boolean isFirstCell = cell.getStyleClass() != null 280 && cell.getStyleClass().contains(HIERARCHY_FIRST_CELL); 281 final Border border; 282 switch (side) { 283 case BOTTOM: 284 border = isFirstCell ? firstCellBottomBorder : bottomBorder; 285 break; 286 case RIGHT_BOTTOM_LEFT: 287 border = isFirstCell ? firstCellRightBottomLeftBorder : rightBottomLeftBorder; 288 break; 289 case RIGHT_LEFT: 290 border = isFirstCell ? firstCellRightLeftBorder : rightLeftBorder; 291 break; 292 case TOP_RIGHT_BOTTOM_LEFT: 293 border = isFirstCell ? firstCellTopRightBottomLeftBorder : topRightBottomLeftBorder; 294 break; 295 case TOP_RIGHT_LEFT: 296 border = isFirstCell ? firstCellTopRightLeftBorder : topRightLeftBorder; 297 break; 298 default: 299 border = null; 300 assert false; 301 break; 302 } 303 assert border != null; 304 cell.setBorder(border); 305 } 306 307 private void setTransparentBorder(Cell<?> cell) { 308 assert cell != null; 309 boolean isFirstCell = cell.getStyleClass() != null 310 && cell.getStyleClass().contains(HIERARCHY_FIRST_CELL); 311 final Border border = isFirstCell ? firstCellTransparentBorder : transparentBorder; 312 cell.setBorder(border); 313 } 314 315 /** 316 * Returns the control used to represent the hierarchy. Either a TreeView or 317 * a TreeTableView. 318 * 319 * @return the control used to represent the hierarchy. 320 */ 321 public abstract Control getPanelControl(); 322 323 /** 324 * Returns the selected TreeItems. 325 * 326 * @return the selected TreeItems. 327 */ 328 public abstract ObservableList<TreeItem<HierarchyItem>> getSelectedItems(); 329 330 /** 331 * Returns the panel control scrollbar for the specified orientation. 332 * 333 * @param orientation the scrollbar orientation 334 * @return the panel control scrollbar for the specified orientation 335 * @treatAsPrivate 336 */ 337 public ScrollBar getScrollBar(final Orientation orientation) { 338 final Control panelControl = getPanelControl(); 339 final Set<Node> scrollBars = panelControl.lookupAll(".scroll-bar"); //NOI18N 340 for (Node node : scrollBars) { 341 if (node instanceof ScrollBar) { 342 final ScrollBar scrollBar = (ScrollBar) node; 343 if (scrollBar.getOrientation() == orientation) { 344 return scrollBar; 345 } 346 } 347 } 348 return null; 349 } 350 351 /** 352 * @treatAsPrivate 353 */ 354 protected abstract void startEditingDisplayInfo(); 355 356 /** 357 * @treatAsPrivate 358 */ 359 protected abstract void updatePanel(); 360 361 /** 362 * @treatAsPrivate 363 */ 364 protected abstract void clearSelection(); 365 366 /** 367 * @param treeItem the TreeItem 368 * @treatAsPrivate 369 */ 370 protected abstract void select(final TreeItem<HierarchyItem> treeItem); 371 372 /** 373 * @param treeItems the TreeItems 374 * @treatAsPrivate 375 */ 376 protected void select(final List<TreeItem<HierarchyItem>> treeItems) { 377 for (TreeItem<HierarchyItem> treeItem : treeItems) { 378 select(treeItem); 379 } 380 } 381 382 /** 383 * @param treeItem the TreeItem 384 * @treatAsPrivate 385 */ 386 public abstract void scrollTo(final TreeItem<HierarchyItem> treeItem); 387 388 /** 389 * @param treeItem the TreeItem 390 * @return true if visible 391 * @treatAsPrivate 392 */ 393 protected boolean isVisible(final TreeItem<HierarchyItem> treeItem) { 394 final Cell<?> cell = getCell(treeItem); 395 return (cell == null ? false : cell.isVisible()); 396 } 397 398 /** 399 * @param treeItem the TreeItem 400 * @return the cell corresponding to the specified TreeItem 401 * @treatAsPrivate 402 */ 403 public abstract Cell<?> getCell(final TreeItem<?> treeItem); 404 405 /** 406 * Returns the Y coordinate of the panel content TOP. Used to define the 407 * zone for auto scrolling. 408 * 409 * @return the Y coordinate of the panel content TOP 410 * @treatAsPrivate 411 */ 412 public abstract double getContentTopY(); 413 414 /** 415 * Returns the Y coordinate of the panel content BOTTOM. Used to define the 416 * zone for auto scrolling. 417 * 418 * @return the Y coordinate of the panel content BOTTOM 419 * @treatAsPrivate 420 */ 421 public abstract double getContentBottomY(); 422 423 /** 424 * @treatAsPrivate 425 */ 426 public abstract void updateParentRing(); 427 428 /** 429 * @treatAsPrivate 430 */ 431 public abstract void updatePlaceHolder(); 432 433 /** 434 * @treatAsPrivate 435 */ 436 public abstract void clearBorderColor(); 437 438 /** 439 * @param cell the cell 440 * @treatAsPrivate 441 */ 442 public void clearBorderColor(final Cell<?> cell) { 443 assert cell != null; 444 setTransparentBorder(cell); 445 } 446 447 /** 448 * @param node the node 449 * @treatAsPrivate 450 */ 451 public void addToPanelControlSkin(final Node node) { 452 final Skin<?> skin = getPanelControl().getSkin(); 453 assert skin instanceof SkinBase; 454 final SkinBase<?> skinbase = (SkinBase<?>) skin; 455 skinbase.getChildren().add(node); 456 } 457 458 /** 459 * @param node the node 460 * @treatAsPrivate 461 */ 462 public void removeFromPanelControlSkin(final Node node) { 463 final Skin<?> skin = getPanelControl().getSkin(); 464 assert skin instanceof SkinBase; 465 final SkinBase<?> skinbase = (SkinBase<?>) skin; 466 skinbase.getChildren().remove(node); 467 } 468 469 /** 470 * @treatAsPrivate 471 */ 472 protected abstract void startListeningToTreeItemSelection(); 473 474 /** 475 * @treatAsPrivate 476 */ 477 protected abstract void stopListeningToTreeItemSelection(); 478 479 /* 480 * AbstractPanelController 481 * @treatAsPrivate 482 */ 483 @Override 484 protected void fxomDocumentDidChange(FXOMDocument oldDocument) { 485 // Clear the map containing the TreeItems expanded property values 486 treeItemsExpandedMapProperty.clear(); 487 updatePanel(); 488 } 489 490 /** 491 * @treatAsPrivate 492 */ 493 @Override 494 protected void sceneGraphRevisionDidChange() { 495 if (getPanelControl() != null) { 496 // Update the map containing the TreeItems expanded property values 497 // This map will be used after rebuilding the tree, 498 // in order to update the TreeItems expanded property to their previous value 499 if (rootTreeItem != null) { // Root TreeItem may be null 500 updateTreeItemsExpandedMap(rootTreeItem); 501 } 502 // FXOM document has rebuilt the scene graph. Tree items must all 503 // be updated because: 504 // - classes of scene graph objects may have mutated 505 // - infos displayed in the tree items may be obsoletes 506 updatePanel(); 507 editorSelectionDidChange(); 508 } 509 } 510 511 /** 512 * @treatAsPrivate 513 */ 514 @Override 515 protected void cssRevisionDidChange() { 516 sceneGraphRevisionDidChange(); 517 } 518 519 private void updateTreeItemsExpandedMap(TreeItem<HierarchyItem> treeItem) { 520 assert treeItem != null; 521 final HierarchyItem item = treeItem.getValue(); 522 if (!item.isEmpty()) { 523 final FXOMObject fxomObject = item.getFxomObject(); 524 assert fxomObject != null; 525 treeItemsExpandedMapProperty.put(fxomObject, treeItem.isExpanded()); 526 // Inspect TreeItem chidren 527 for (TreeItem<HierarchyItem> treeItemChild : treeItem.getChildren()) { 528 updateTreeItemsExpandedMap(treeItemChild); 529 } 530 } 531 } 532 533 /** 534 * @treatAsPrivate 535 */ 536 @Override 537 protected void jobManagerRevisionDidChange() { 538 // FXOMDocument has been modified by a job. 539 // Tree items must all be updated. 540 sceneGraphRevisionDidChange(); 541 } 542 543 /** 544 * @treatAsPrivate 545 */ 546 @Override 547 protected void controllerDidLoadFxml() { 548 assert getPanelControl() != null; 549 550 // Initialize and configure the hierarchy panel 551 initializePanel(); 552 553 // Add listener on the selection 554 // Used to update global selection + update parent ring 555 startListeningToTreeItemSelection(); 556 557 // Populate panel 558 updatePanel(); 559 } 560 561 /** 562 * @treatAsPrivate 563 */ 564 @Override 565 protected void editorSelectionDidChange() { 566 final Selection selection = getEditorController().getSelection(); 567 final List<FXOMObject> selectedFxomObjects = new ArrayList<>(); 568 569 if (getPanelControl() != null) { 570 if (selection.getGroup() instanceof ObjectSelectionGroup) { 571 final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup(); 572 selectedFxomObjects.addAll(osg.getItems()); 573 } else if (selection.getGroup() instanceof GridSelectionGroup) { 574 final GridSelectionGroup gsg = (GridSelectionGroup) selection.getGroup(); 575 selectedFxomObjects.add(gsg.getParentObject()); 576 } 577 578 // Update selected items 579 stopListeningToTreeItemSelection(); 580 clearSelection(); 581 // Root TreeItem may be null 582 if (getRoot() != null && selectedFxomObjects.isEmpty() == false) { 583 final List<TreeItem<HierarchyItem>> selectedTreeItems 584 = lookupTreeItem(selectedFxomObjects); 585 if (selectedTreeItems.isEmpty() == false) { 586 select(selectedTreeItems); 587 // Scroll to the last TreeItem 588 final TreeItem<HierarchyItem> lastTreeItem 589 = selectedTreeItems.get(selectedTreeItems.size() - 1); 590 // Call scrollTo only if the item is not visible. 591 // This avoid unexpected scrolling to occur in the hierarchy 592 // TreeView / TreeTableView while changing some property in the inspector. 593 if (isVisible(lastTreeItem) == false) { 594 scrollTo(lastTreeItem); 595 } 596 } 597 } 598 startListeningToTreeItemSelection(); 599 600 // Update parent ring when selection did change 601 updateParentRing(); 602 } 603 } 604 605 private void treeItemSelectionDidChange() { 606 607 /* 608 * Before updating the selection, we test if a text session is on-going 609 * and can be completed cleanly. If not, we do not update the selection. 610 */ 611 if (getEditorController().canGetFxmlText()) { 612 final Set<FXOMObject> selectedFxomObjects = new HashSet<>(); 613 for (TreeItem<HierarchyItem> selectedItem : getSelectedItems()) { 614 // TreeItems may be null when selection is updating 615 if (selectedItem != null) { 616 final FXOMObject fxomObject = selectedItem.getValue().getFxomObject(); 617 // Placeholders may have a null fxom object 618 if (fxomObject != null) { 619 selectedFxomObjects.add(fxomObject); 620 } 621 } 622 } 623 624 // Update selection 625 stopListeningToEditorSelection(); 626 getEditorController().getSelection().select(selectedFxomObjects); 627 startListeningToEditorSelection(); 628 629 // Update parent ring when selection did change 630 updateParentRing(); 631 } /* 632 * If a text session is on-going and cannot be completed cleanly, 633 * we go back to previous TreeItem selection. 634 */ else { 635 editorSelectionDidChange(); 636 } 637 } 638 639 private TreeItem<HierarchyItem> makeTreeItem(final FXOMObject fxomObject) { 640 final HierarchyItem item = new HierarchyItem(fxomObject); 641 final TreeItem<HierarchyItem> treeItem = new TreeItem<>(item); 642 // Set back the TreeItem expanded property if any 643 Boolean expanded = treeItemsExpandedMapProperty.get(fxomObject); 644 if (expanded != null) { 645 treeItem.setExpanded(expanded); 646 } 647 updateTreeItem(treeItem); 648 return treeItem; 649 } 650 651 private TreeItem<HierarchyItem> makeTreeItemBorderPane( 652 final DesignHierarchyMask owner, 653 final FXOMObject fxomObject, 654 final Accessory accessory) { 655 final HierarchyItemBorderPane item 656 = new HierarchyItemBorderPane(owner, fxomObject, accessory); 657 final TreeItem<HierarchyItem> treeItem = new TreeItem<>(item); 658 // Set back the TreeItem expanded property if any 659 Boolean expanded = treeItemsExpandedMapProperty.get(fxomObject); 660 if (expanded != null) { 661 treeItem.setExpanded(expanded); 662 } 663 // Mask may be null for empty place holder 664 if (item.getMask() != null) { 665 updateTreeItem(treeItem); 666 } 667 return treeItem; 668 } 669 670 private TreeItem<HierarchyItem> makeTreeItemDialogPane( 671 final DesignHierarchyMask owner, 672 final FXOMObject fxomObject, 673 final Accessory accessory) { 674 final HierarchyItemDialogPane item 675 = new HierarchyItemDialogPane(owner, fxomObject, accessory); 676 final TreeItem<HierarchyItem> treeItem = new TreeItem<>(item); 677 // Set back the TreeItem expanded property if any 678 Boolean expanded = treeItemsExpandedMapProperty.get(fxomObject); 679 if (expanded != null) { 680 treeItem.setExpanded(expanded); 681 } 682 // Mask may be null for empty place holder 683 if (item.getMask() != null) { 684 updateTreeItem(treeItem); 685 } 686 return treeItem; 687 } 688 689 /** 690 * @param owner the mask owner 691 * @param fxomObject the FXOMObject 692 * @return the new TreeItem 693 * @treatAsPrivate 694 */ 695 protected TreeItem<HierarchyItem> makeTreeItemGraphic( 696 final DesignHierarchyMask owner, 697 final FXOMObject fxomObject) { 698 final HierarchyItemGraphic item 699 = new HierarchyItemGraphic(owner, fxomObject); 700 final TreeItem<HierarchyItem> treeItem = new TreeItem<>(item); 701 Boolean expanded = treeItemsExpandedMapProperty.get(fxomObject); 702 if (expanded != null) { 703 treeItem.setExpanded(expanded); 704 } 705 // Mask may be null for empty place holder 706 if (item.getMask() != null) { 707 updateTreeItem(treeItem); 708 } 709 return treeItem; 710 } 711 712 protected void updateTreeItems() { 713 assert getPanelControl() != null; 714 final Parent parent = getPanelControl().getParent(); 715 assert parent instanceof Pane; 716 final Pane pane = (Pane) parent; 717 final FXOMDocument fxomDocument = getEditorController().getFxomDocument(); 718 719 final Label label = getPromptLabel(); 720 if (fxomDocument == null || fxomDocument.getFxomRoot() == null) { 721 rootTreeItem = null; 722 // Add place holder to the parent 723 if (fxomDocument == null) { 724 label.setText(I18N.getString("contant.label.status.fxomdocument.null")); 725 } else { 726 label.setText(I18N.getString("content.label.status.invitation")); 727 } 728 if (pane.getChildren().contains(label) == false) { 729 // This may occur when closing en empty document 730 // => we switch from null FXOM root to null FXOM document 731 pane.getChildren().add(label); 732 } 733 } else { 734 rootTreeItem = makeTreeItem(fxomDocument.getFxomRoot()); 735 rootTreeItem.setExpanded(true); 736 // Remove place holder from the parent 737 ((Pane) parent).getChildren().remove(label); 738 } 739 } 740 741 protected void updateParentRingColor() { 742 // Update border items used to build the hierarchy parent ring 743 BorderStroke bs; 744 // bottom border 745 bs = new BorderStroke(Color.TRANSPARENT, Color.TRANSPARENT, parentRingColor, Color.TRANSPARENT, 746 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 747 CornerRadii.EMPTY, cellBorderWidths, cellInsets); 748 bottomBorder = new Border(bs); 749 bs = new BorderStroke(Color.TRANSPARENT, Color.TRANSPARENT, parentRingColor, Color.TRANSPARENT, 750 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 751 CornerRadii.EMPTY, cellBorderWidths, firstCellInsets); 752 firstCellBottomBorder = new Border(bs); 753 // right bottom and left border 754 bs = new BorderStroke(Color.TRANSPARENT, parentRingColor, parentRingColor, parentRingColor, 755 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 756 CornerRadii.EMPTY, cellBorderWidths, cellInsets); 757 rightBottomLeftBorder = new Border(bs); 758 bs = new BorderStroke(Color.TRANSPARENT, parentRingColor, parentRingColor, parentRingColor, 759 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 760 CornerRadii.EMPTY, cellBorderWidths, firstCellInsets); 761 firstCellRightBottomLeftBorder = new Border(bs); 762 // right and left border 763 bs = new BorderStroke(Color.TRANSPARENT, parentRingColor, Color.TRANSPARENT, parentRingColor, 764 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 765 CornerRadii.EMPTY, cellBorderWidths, cellInsets); 766 rightLeftBorder = new Border(bs); 767 bs = new BorderStroke(Color.TRANSPARENT, parentRingColor, Color.TRANSPARENT, parentRingColor, 768 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 769 CornerRadii.EMPTY, cellBorderWidths, firstCellInsets); 770 firstCellRightLeftBorder = new Border(bs); 771 // top right bottom and left border 772 bs = new BorderStroke(parentRingColor, BorderStrokeStyle.SOLID, 773 CornerRadii.EMPTY, cellBorderWidths, cellInsets); 774 topRightBottomLeftBorder = new Border(bs); 775 bs = new BorderStroke(parentRingColor, BorderStrokeStyle.SOLID, 776 CornerRadii.EMPTY, cellBorderWidths, firstCellInsets); 777 firstCellTopRightBottomLeftBorder = new Border(bs); 778 // top right and left border 779 bs = new BorderStroke(parentRingColor, parentRingColor, Color.TRANSPARENT, parentRingColor, 780 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 781 CornerRadii.EMPTY, cellBorderWidths, cellInsets); 782 topRightLeftBorder = new Border(bs); 783 bs = new BorderStroke(parentRingColor, parentRingColor, Color.TRANSPARENT, parentRingColor, 784 BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, 785 CornerRadii.EMPTY, cellBorderWidths, firstCellInsets); 786 firstCellTopRightLeftBorder = new Border(bs); 787 788 updateParentRing(); 789 updatePlaceHolder(); 790 } 791 792 private void updateTreeItem(final TreeItem<HierarchyItem> treeItem) { 793 794 final DesignHierarchyMask mask = treeItem.getValue().getMask(); 795 assert mask != null; 796 797 // Graphic (displayed at first position) 798 //--------------------------------- 799 if (mask.isAcceptingAccessory(Accessory.GRAPHIC)) { 800 final FXOMObject value = mask.getAccessory(Accessory.GRAPHIC); 801 if (value != null) { 802 treeItem.getChildren().add(makeTreeItemGraphic(mask, value)); 803 } 804 } 805 806 // Tooltip (displayed at second position) 807 //--------------------------------- 808 if (mask.isAcceptingAccessory(Accessory.TOOLTIP)) { 809 final FXOMObject value = mask.getAccessory(Accessory.TOOLTIP); 810 if (value != null) { 811 treeItem.getChildren().add(makeTreeItem(value)); 812 } 813 } 814 815 // Context menu (displayed at third position) 816 //--------------------------------- 817 if (mask.isAcceptingAccessory(Accessory.CONTEXT_MENU)) { 818 final FXOMObject value = mask.getAccessory(Accessory.CONTEXT_MENU); 819 if (value != null) { 820 treeItem.getChildren().add(makeTreeItem(value)); 821 } 822 } 823 824 // Axis (chart) 825 //--------------------------------- 826 if (mask.isAcceptingAccessory(Accessory.XAXIS)) { 827 final FXOMObject value = mask.getAccessory(Accessory.XAXIS); 828 if (value != null) { 829 treeItem.getChildren().add(makeTreeItem(value)); 830 } 831 } 832 if (mask.isAcceptingAccessory(Accessory.YAXIS)) { 833 final FXOMObject value = mask.getAccessory(Accessory.YAXIS); 834 if (value != null) { 835 treeItem.getChildren().add(makeTreeItem(value)); 836 } 837 } 838 839 // Content (ScrollPane, Tab...) 840 //--------------------------------- 841 if (mask.isAcceptingAccessory(Accessory.CONTENT)) { 842 final FXOMObject value = mask.getAccessory(Accessory.CONTENT); 843 if (value != null) { 844 treeItem.getChildren().add(makeTreeItem(value)); 845 } 846 } 847 848 // Positionning 849 //--------------------------------- 850 for (Accessory accessory : new Accessory[]{ 851 Accessory.TOP, 852 Accessory.LEFT, 853 Accessory.CENTER, 854 Accessory.RIGHT, 855 Accessory.BOTTOM}) { 856 if (mask.isAcceptingAccessory(accessory)) { 857 final FXOMObject value = mask.getAccessory(accessory); 858 treeItem.getChildren().add(makeTreeItemBorderPane(mask, value, accessory)); 859 } 860 } 861 862 // DialogPane 863 //--------------------------------- 864 for (Accessory accessory : new Accessory[]{ 865 Accessory.HEADER, 866 Accessory.DP_GRAPHIC, 867 Accessory.DP_CONTENT, 868 Accessory.EXPANDABLE_CONTENT}) { 869 if (mask.isAcceptingAccessory(accessory)) { 870 final FXOMObject value = mask.getAccessory(accessory); 871 treeItem.getChildren().add(makeTreeItemDialogPane(mask, value, accessory)); 872 } 873 } 874 875 // Sub components 876 //--------------------------------- 877 if (mask.isAcceptingSubComponent()) { 878 for (int i = 0, count = mask.getSubComponentCount(); i < count; i++) { 879 final FXOMObject value = mask.getSubComponentAtIndex(i); 880 treeItem.getChildren().add(makeTreeItem(value)); 881 } 882 } 883 } 884 885 private List<TreeItem<HierarchyItem>> lookupTreeItem(List<FXOMObject> fxomObjects) { 886 final List<TreeItem<HierarchyItem>> result = new ArrayList<>(); 887 for (FXOMObject fxomObject : fxomObjects) { 888 final TreeItem<HierarchyItem> treeItem = lookupTreeItem(fxomObject); 889 // TreeItem may be null when selecting a GridPane column/row 890 // constraint in content panel 891 if (treeItem != null) { 892 result.add(treeItem); 893 } 894 } 895 return result; 896 } 897 898 /** 899 * @param fxomObject the FXOMObject 900 * @return the TreeItem corresponding to the specified FXOMObject 901 * @treatAsPrivate 902 */ 903 public TreeItem<HierarchyItem> lookupTreeItem(FXOMObject fxomObject) { 904 return lookupTreeItem(fxomObject, getRoot()); 905 } 906 907 private TreeItem<HierarchyItem> lookupTreeItem(FXOMObject fxomObject, TreeItem<HierarchyItem> fromTreeItem) { 908 TreeItem<HierarchyItem> result; 909 assert fxomObject != null; 910 911 // ROOT TreeItem may be null when no document is loaded 912 if (fromTreeItem != null) { 913 assert fromTreeItem.getValue() != null; 914 if (fromTreeItem.getValue().getFxomObject() == fxomObject) { 915 result = fromTreeItem; 916 } else { 917 Iterator<TreeItem<HierarchyItem>> it = fromTreeItem.getChildren().iterator(); 918 result = null; 919 while ((result == null) && it.hasNext()) { 920 TreeItem<HierarchyItem> childItem = it.next(); 921 result = lookupTreeItem(fxomObject, childItem); 922 } 923 } 924 } else { 925 result = null; 926 } 927 return result; 928 } 929 930 /** 931 * Returns the list of all descendant from the specified parent TreeItem. 932 * The specified parent TreeItem is excluded from the returned list. 933 * 934 * @param <T> type 935 * @param parentTreeItem the parent TreeItem 936 * @return the list of all descendant 937 */ 938 private <T> List<TreeItem<T>> getAllTreeItems(final TreeItem<T> parentTreeItem) { 939 assert parentTreeItem != null; 940 final List<TreeItem<T>> treeItems = new ArrayList<>(); 941 for (TreeItem<T> child : parentTreeItem.getChildren()) { 942 treeItems.add(child); 943 treeItems.addAll(getAllTreeItems(child)); 944 } 945 return treeItems; 946 } 947 948 /** 949 * Returns the last visible TreeItem descendant of the specified parent 950 * TreeItem. 951 * 952 * @param <T> type 953 * @param parentTreeItem the parent TreeItem 954 * @return the last visible TreeItem 955 * @treatAsPrivate 956 */ 957 public <T> TreeItem<T> getLastVisibleTreeItem(final TreeItem<T> parentTreeItem) { 958 assert parentTreeItem != null; 959 TreeItem<T> result = parentTreeItem; 960 int size = result.getChildren().size(); 961 while (size != 0) { 962 if (result.isExpanded()) { 963 result = result.getChildren().get(size - 1); 964 size = result.getChildren().size(); 965 } else { 966 size = 0; 967 } 968 } 969 return result; 970 } 971 972 /** 973 * Returns the next visible TreeItem of the specified TreeItem. 974 * 975 * @param <T> type 976 * @param treeItem the TreeItem 977 * @return the next visible TreeItem 978 * @treatAsPrivate 979 */ 980 public <T> TreeItem<T> getNextVisibleTreeItem(final TreeItem<T> treeItem) { 981 assert treeItem != null; 982 if (treeItem == getRoot()) { 983 // Root TreeItem has no next TreeItem 984 return null; 985 } else if (treeItem.isExpanded() && !treeItem.getChildren().isEmpty()) { 986 // Return first child 987 return treeItem.getChildren().get(0); 988 } else { 989 TreeItem<T> parentTreeItem = treeItem.getParent(); 990 TreeItem<T> result = treeItem.nextSibling(); 991 while (result == null && parentTreeItem != getRoot()) { 992 result = parentTreeItem.nextSibling(); 993 parentTreeItem = parentTreeItem.getParent(); 994 } 995 return result; 996 } 997 } 998 999 /** 1000 * Returns the previous visible TreeItem of the specified TreeItem. 1001 * 1002 * @param <T> type 1003 * @param treeItem the TreeItem 1004 * @return the previous visible TreeItem 1005 * @treatAsPrivate 1006 */ 1007 public <T> TreeItem<T> getPreviousVisibleTreeItem(final TreeItem<T> treeItem) { 1008 assert treeItem != null; 1009 if (treeItem == getRoot()) { 1010 // Root TreeItem has no previous TreeItem 1011 return null; 1012 } else { 1013 TreeItem<T> parentTreeItem = treeItem.getParent(); 1014 TreeItem<T> result = treeItem.previousSibling(); 1015 while (result == null && parentTreeItem != getRoot()) { 1016 result = parentTreeItem.previousSibling(); 1017 parentTreeItem = parentTreeItem.getParent(); 1018 } 1019 return result; 1020 } 1021 } 1022 1023 /** 1024 * @treatAsPrivate 1025 */ 1026 protected void initializePanel() { 1027 // Panel may be either a TreeView or a TreeTableView 1028 assert getPanelControl() != null; 1029 1030 // Drag events 1031 //---------------------------------------------------------------------- 1032 // DRAG_DONE event received when drag gesture 1033 // started from the hierarchy panel ends 1034 getPanelControl().setOnDragDone(event -> handleOnDragDone(event)); 1035 getPanelControl().setOnDragDropped(event -> handleOnDragDropped(event)); 1036 getPanelControl().setOnDragEntered(event -> handleOnDragEntered(event)); 1037 getPanelControl().setOnDragExited(event -> handleOnDragExited(event)); 1038 getPanelControl().setOnDragOver(event -> handleOnDragOver(event)); 1039 1040 // Key events 1041 //---------------------------------------------------------------------- 1042 getPanelControl().setOnKeyPressed(event -> handleOnKeyPressed(event)); 1043 1044 // Mouse events 1045 //---------------------------------------------------------------------- 1046 // DRAG_DETECTED event received when drag gesture 1047 // starts from the hierarchy panel 1048 getPanelControl().setOnDragDetected(event -> handleOnDragDetected(event)); 1049 getPanelControl().setOnMousePressed(event -> handleOnMousePressed(event)); 1050 1051 // Setup the context menu 1052 final ContextMenuController contextMenuController 1053 = getEditorController().getContextMenuController(); 1054 getPanelControl().setContextMenu(contextMenuController.getContextMenu()); 1055 1056 // Set default parent ring color 1057 setParentRingColor(DEFAULT_PARENT_RING_COLOR); 1058 } 1059 1060 private void handleOnDragDetected(final MouseEvent event) { 1061 final ObservableList<TreeItem<HierarchyItem>> selectedTreeItems = getSelectedItems(); 1062 1063 // Do not start a DND gesture if there is an editing session on-going 1064 if (!getEditorController().canGetFxmlText()) { 1065 return; 1066 } 1067 1068 final Selection selection = getEditorController().getSelection(); 1069 if (selection.isEmpty() == false) { // (1) 1070 if (selection.getGroup() instanceof ObjectSelectionGroup) { 1071 // A set of regular component (ie fxom objects) are selected 1072 final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup(); 1073 1074 // Abort dragging an empty place holder 1075 for (TreeItem<HierarchyItem> selectedTreeItem : selectedTreeItems) { 1076 final HierarchyItem item = selectedTreeItem.getValue(); 1077 if (item.isEmpty()) { 1078 return; 1079 } 1080 } 1081 // Retrieve the hit object 1082 final Cell<?> cell = lookupCell(event.getTarget()); 1083 final Object item = cell.getItem(); 1084 assert item instanceof HierarchyItem; 1085 final HierarchyItem hierarchyItem = (HierarchyItem) item; 1086 final FXOMObject hitObject = hierarchyItem.getFxomObject(); 1087 assert (hitObject != null); // Because we cannot drag placeholders 1088 // Build drag source 1089 final Window ownerWindow = getPanelRoot().getScene().getWindow(); 1090 final DocumentDragSource dragSource = new DocumentDragSource( 1091 osg.getSortedItems(), hitObject, ownerWindow); 1092 if (dragSource.isAcceptable()) { 1093 // Start drag and drop 1094 final Dragboard db = getPanelControl().startDragAndDrop(TransferMode.COPY_OR_MOVE); 1095 db.setContent(dragSource.makeClipboardContent()); 1096 db.setDragView(dragSource.makeDragView()); 1097 // DragController.begin 1098 assert getEditorController().getDragController().getDragSource() == null; 1099 getEditorController().getDragController().begin(dragSource); 1100 } 1101 1102 } else { 1103 // Emergency code : a new type of AbstractSelectionGroup 1104 // exists but is not managed by this code yet. 1105 assert false : "Add implementation for " + selection.getGroup().getClass(); 1106 } 1107 } 1108 } 1109 1110 private void handleOnDragDone(final DragEvent event) { 1111 // DragController update 1112 final DragController dragController 1113 = getEditorController().getDragController(); 1114 assert shouldEndOnExit == false; 1115 dragController.end(); 1116 event.getDragboard().clear(); 1117 } 1118 1119 private void handleOnDragDropped(final DragEvent event) { 1120 // If there is no document loaded 1121 // Should we allow to start with empty document in SB 2.0 ? 1122 if (getEditorController().getFxomDocument() == null) { 1123 return; 1124 } 1125 1126 // DragController update 1127 final DragController dragController 1128 = getEditorController().getDragController(); 1129 dragController.commit(); 1130 // Do not invoke dragController.end here because we always receive a 1131 // DRAG_EXITED event which will perform the termination 1132 event.setDropCompleted(true); 1133 1134 // Give the focus to the hierarchy 1135 getPanelControl().requestFocus(); 1136 } 1137 1138 private void handleOnDragEntered(final DragEvent event) { 1139 // CSS 1140 clearBorderColor(); 1141 // When starting a DND gesture, disable parent ring updates 1142 setParentRingEnabled(false); 1143 1144 // DragController update 1145 // The drag source is null if the drag gesture 1146 // has been started from outside (from the explorer / finder) 1147 final DragController dragController 1148 = getEditorController().getDragController(); 1149 if (dragController.getDragSource() == null) { // Drag started externally 1150 final FXOMDocument fxomDocument 1151 = getEditorController().getFxomDocument(); 1152 // Build drag source 1153 final Window ownerWindow = getPanelRoot().getScene().getWindow(); 1154 final ExternalDragSource dragSource = new ExternalDragSource( 1155 event.getDragboard(), fxomDocument, ownerWindow); 1156 assert dragSource.isAcceptable(); 1157 dragController.begin(dragSource); 1158 shouldEndOnExit = true; 1159 } 1160 } 1161 1162 private void handleOnDragExited(final DragEvent event) { 1163 // CSS 1164 clearBorderColor(); 1165 // When ending a DND gesture, enable parent ring updates 1166 setParentRingEnabled(true); 1167 1168 // Cancel timeline animation if any 1169 animationScheduler.stopTimeline(); 1170 1171 // Retrieve the vertical scroll bar value before updating the TreeItems 1172 double verticalScrollBarValue = 0.0; 1173 final ScrollBar scrollBar = getScrollBar(Orientation.VERTICAL); 1174 if (scrollBar != null) { 1175 verticalScrollBarValue = scrollBar.getValue(); 1176 } 1177 1178 // DragController update 1179 final DragController dragController 1180 = getEditorController().getDragController(); 1181 dragController.setDropTarget(null); 1182 if (shouldEndOnExit) { 1183 dragController.end(); 1184 shouldEndOnExit = false; 1185 } 1186 // Set back the vertical scroll bar value after the TreeItems have been updated 1187 if (scrollBar != null) { 1188 scrollBar.setValue(verticalScrollBarValue); 1189 } 1190 } 1191 1192 private void handleOnDragOver(final DragEvent event) { 1193 final ScrollBar verticalScrollBar = getScrollBar(Orientation.VERTICAL); 1194 1195 // By dragging and hovering the cell within a few pixels 1196 // of the top or bottom of the Hierarchy, 1197 // the user can cause it to auto-scroll until the desired cell is in view. 1198 if (verticalScrollBar != null && verticalScrollBar.isVisible()) { 1199 final double eventY = event.getY(); 1200 final double topY = getContentTopY(); 1201 final double bottomY = getContentBottomY(); 1202 1203 // TOP auto scrolling zone 1204 if (topY <= eventY && eventY < topY + AUTO_SCROLLING_ZONE_HEIGHT) { 1205 // Start the timeline if not already playing 1206 if (!animationScheduler.isTimelineRunning()) { 1207 animationScheduler.playDecrementAnimation(verticalScrollBar); 1208 } 1209 } // BOTTOM auto scrolling zone 1210 else if (bottomY >= eventY && eventY > bottomY - AUTO_SCROLLING_ZONE_HEIGHT) { 1211 // Start the timeline if not already playing 1212 if (!animationScheduler.isTimelineRunning()) { 1213 animationScheduler.playIncrementAnimation(verticalScrollBar); 1214 } 1215 } else if (animationScheduler.isTimelineRunning()) { 1216 animationScheduler.stopTimeline(); 1217 } 1218 } 1219 } 1220 1221 private void handleOnKeyPressed(final KeyEvent event) { 1222 switch (event.getCode()) { 1223 1224 // Handle Inline editing 1225 case ENTER: 1226 startEditingDisplayInfo(); 1227 break; 1228 1229 // Handle collapse all 1230 case LEFT: 1231 if (event.isAltDown()) { 1232 final List<TreeItem<HierarchyItem>> treeItems = getSelectedItems(); 1233 if (!treeItems.isEmpty()) { 1234 for (TreeItem<HierarchyItem> treeItem : treeItems) { 1235 collapseAllTreeItems(treeItem); 1236 } 1237 } 1238 } 1239 break; 1240 1241 // Handle expand all 1242 case RIGHT: 1243 if (event.isAltDown()) { 1244 final List<TreeItem<HierarchyItem>> treeItems = getSelectedItems(); 1245 if (!treeItems.isEmpty()) { 1246 for (TreeItem<HierarchyItem> treeItem : treeItems) { 1247 expandAllTreeItems(treeItem); 1248 } 1249 } 1250 } 1251 break; 1252 1253 default: 1254 break; 1255 } 1256 } 1257 1258 private void handleOnMousePressed(final MouseEvent event) { 1259 1260 if (event.getButton() == MouseButton.SECONDARY) { 1261 final ContextMenuController contextMenuController 1262 = getEditorController().getContextMenuController(); 1263 // The context menu items depend on the selection so 1264 // we need to rebuild it each time it is invoked. 1265 contextMenuController.updateContextMenuItems(); 1266 } 1267 } 1268 1269 private <T> void expandAllTreeItems(final TreeItem<T> parentTreeItem) { 1270 assert parentTreeItem != null; 1271 parentTreeItem.setExpanded(true); 1272 final List<TreeItem<T>> treeItems = getAllTreeItems(parentTreeItem); 1273 assert treeItems != null; 1274 for (TreeItem<T> treeItem : treeItems) { 1275 treeItem.setExpanded(true); 1276 } 1277 } 1278 1279 private <T> void collapseAllTreeItems(final TreeItem<T> parentTreeItem) { 1280 assert parentTreeItem != null; 1281 parentTreeItem.setExpanded(false); 1282 final List<TreeItem<T>> treeItems = getAllTreeItems(parentTreeItem); 1283 assert treeItems != null; 1284 for (TreeItem<T> treeItem : treeItems) { 1285 treeItem.setExpanded(false); 1286 } 1287 } 1288 1289 /** 1290 * Returns the cell ancestor of the specified event target. Indeed, 1291 * depending on the mouse click position, the event target may be the cell 1292 * node itself, the cell graphic or the cell labeled text. 1293 * 1294 * @param target 1295 * @return 1296 */ 1297 private Cell<?> lookupCell(EventTarget target) { 1298 assert target instanceof Node; 1299 Node node = (Node) target; 1300 while ((node instanceof Cell) == false) { 1301 node = node.getParent(); 1302 } 1303 return (Cell<?>) node; 1304 } 1305 }