1 /* 2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 32 import javafx.beans.property.BooleanProperty; 33 import javafx.beans.property.BooleanPropertyBase; 34 import javafx.beans.property.ObjectProperty; 35 import javafx.beans.property.ObjectPropertyBase; 36 import javafx.collections.FXCollections; 37 import javafx.collections.ListChangeListener; 38 import javafx.collections.ObservableList; 39 import javafx.event.Event; 40 import javafx.event.EventDispatchChain; 41 import javafx.event.EventHandler; 42 import javafx.event.EventTarget; 43 import javafx.event.EventType; 44 import javafx.scene.Node; 45 46 import com.sun.javafx.event.EventHandlerManager; 47 import java.util.Comparator; 48 import javafx.beans.property.ReadOnlyBooleanProperty; 49 import javafx.beans.property.ReadOnlyBooleanWrapper; 50 import javafx.beans.property.ReadOnlyObjectProperty; 51 import javafx.beans.property.ReadOnlyObjectWrapper; 52 53 import static javafx.scene.control.TreeSortMode.*; 54 55 /** 56 * The model for a single node supplying a hierarchy of values to a control such 57 * as TreeView. The model may be implemented such that values may be loaded in 58 * memory as they are needed. 59 * <p> 60 * The model allows registration of listeners which will be notified as the 61 * number of items changes, their position or if the values themselves change. 62 * Note however that a TreeItem is <b>not</b> a Node, and therefore no visual 63 * events will be fired on the TreeItem. To get these events, it is necessary to 64 * add relevant observers to the TreeCell instances (via a custom cell factory - 65 * see the {@link Cell} class documentation for more details). 66 * 67 * <p>In the simplest case, TreeItem instances may be created in memory as such: 68 * <pre><code> 69 * TreeItem<String> root = new TreeItem<String>("Root Node"); 70 * root.setExpanded(true); 71 * root.getChildren().addAll( 72 * new TreeItem<String>("Item 1"), 73 * new TreeItem<String>("Item 2"), 74 * new TreeItem<String>("Item 3") 75 * ); 76 * TreeView<String> treeView = new TreeView<String>(root); 77 * </code></pre> 78 * 79 * This approach works well for simple tree structures, or when the data is not 80 * excessive (so that it can easily fit in memory). In situations where the size 81 * of the tree structure is unknown (and therefore potentially huge), there is 82 * the option of creating TreeItem instances on-demand in a memory-efficient way. 83 * To demonstrate this, the code below creates a file system browser: 84 * 85 * <pre><code> 86 * private TreeView buildFileSystemBrowser() { 87 * TreeItem<File> root = createNode(new File("/")); 88 * return new TreeView<File>(root); 89 * } 90 * 91 * // This method creates a TreeItem to represent the given File. It does this 92 * // by overriding the TreeItem.getChildren() and TreeItem.isLeaf() methods 93 * // anonymously, but this could be better abstracted by creating a 94 * // 'FileTreeItem' subclass of TreeItem. However, this is left as an exercise 95 * // for the reader. 96 * private TreeItem<File> createNode(final File f) { 97 * return new TreeItem<File>(f) { 98 * // We cache whether the File is a leaf or not. A File is a leaf if 99 * // it is not a directory and does not have any files contained within 100 * // it. We cache this as isLeaf() is called often, and doing the 101 * // actual check on File is expensive. 102 * private boolean isLeaf; 103 * 104 * // We do the children and leaf testing only once, and then set these 105 * // booleans to false so that we do not check again during this 106 * // run. A more complete implementation may need to handle more 107 * // dynamic file system situations (such as where a folder has files 108 * // added after the TreeView is shown). Again, this is left as an 109 * // exercise for the reader. 110 * private boolean isFirstTimeChildren = true; 111 * private boolean isFirstTimeLeaf = true; 112 * 113 * @Override public ObservableList<TreeItem<File>> getChildren() { 114 * if (isFirstTimeChildren) { 115 * isFirstTimeChildren = false; 116 * 117 * // First getChildren() call, so we actually go off and 118 * // determine the children of the File contained in this TreeItem. 119 * super.getChildren().setAll(buildChildren(this)); 120 * } 121 * return super.getChildren(); 122 * } 123 * 124 * @Override public boolean isLeaf() { 125 * if (isFirstTimeLeaf) { 126 * isFirstTimeLeaf = false; 127 * File f = (File) getValue(); 128 * isLeaf = f.isFile(); 129 * } 130 * 131 * return isLeaf; 132 * } 133 * 134 * private ObservableList<TreeItem<File>> buildChildren(TreeItem<File> TreeItem) { 135 * File f = TreeItem.getValue(); 136 * if (f != null && f.isDirectory()) { 137 * File[] files = f.listFiles(); 138 * if (files != null) { 139 * ObservableList<TreeItem<File>> children = FXCollections.observableArrayList(); 140 * 141 * for (File childFile : files) { 142 * children.add(createNode(childFile)); 143 * } 144 * 145 * return children; 146 * } 147 * } 148 * 149 * return FXCollections.emptyObservableList(); 150 * } 151 * }; 152 * }</code></pre> 153 * 154 * <strong>TreeItem Events</strong> 155 * <p>TreeItem supports the same event bubbling concept as elsewhere in the 156 * scenegraph. This means that it is not necessary to listen for events on all 157 * TreeItems (and this is certainly not encouraged!). A better, and far more low 158 * cost solution is to instead attach event listeners to the TreeView 159 * {@link TreeView#rootProperty() root} item. As long as there is a path between 160 * where the event occurs and the root TreeItem, the event will be bubbled to the 161 * root item. 162 * 163 * <p>It is important to note however that a TreeItem is <strong>not</strong> a 164 * Node, which means that only the event types defined in TreeItem will be 165 * delivered. To listen to general events (for example mouse interactions), it is 166 * necessary to add the necessary listeners to the {@link Cell cells} contained 167 * within the TreeView (by providing a {@link TreeView#cellFactoryProperty() 168 * cell factory}). 169 * 170 * <p>The TreeItem class defines a number of events, with a defined hierarchy. These 171 * are shown below (follow the links to learn more about each event type): 172 * 173 * <ul> 174 * <li>{@link TreeItem#treeNotificationEvent() TreeItem.treeNotificationEvent()}</li> 175 * <ul> 176 * <li>{@link TreeItem#valueChangedEvent() TreeItem.valueChangedEvent()}</li> 177 * <li>{@link TreeItem#graphicChangedEvent() TreeItem.graphicChangedEvent()}</li> 178 * <li>{@link TreeItem#treeItemCountChangeEvent() TreeItem.treeItemCountChangeEvent()}</li> 179 * <ul> 180 * <li>{@link TreeItem#branchExpandedEvent() TreeItem.branchExpandedEvent()}</li> 181 * <li>{@link TreeItem#branchCollapsedEvent() TreeItem.branchCollapsedEvent()}</li> 182 * <li>{@link TreeItem#childrenModificationEvent() TreeItem.childrenModificationEvent()}</li> 183 * </ul> 184 * </ul> 185 * </ul> 186 * 187 * <p>The indentation shown above signifies the relationship between event types. 188 * For example, all TreeItem event types have 189 * {@link TreeItem#treeNotificationEvent() treeNotificationEvent()} as their 190 * parent event type, and the branch 191 * {@link TreeItem#branchExpandedEvent() expand} / 192 * {@link TreeItem#branchCollapsedEvent() collapse} event types are both 193 * {@link TreeItem#treeNotificationEvent() treeNotificationEvent()}. For 194 * performance reasons, it is encouraged to listen 195 * to only the events you need to listen to. This means that it is encouraged 196 * that it is better to listen to, for example, 197 * {@link TreeItem#valueChangedEvent() TreeItem.valueChangedEvent()}, 198 * rather than {@link TreeItem#treeNotificationEvent() TreeItem.treeNotificationEvent()}. 199 * 200 * @param <T> The type of the {@link #getValue() value} property within TreeItem. 201 * @since JavaFX 2.0 202 */ 203 public class TreeItem<T> implements EventTarget { //, Comparable<TreeItem<T>> { 204 205 /*************************************************************************** 206 * * 207 * Static properties and methods * 208 * * 209 **************************************************************************/ 210 211 /** 212 * The base EventType used to indicate that an event has occurred within a 213 * TreeItem. When an event occurs in a TreeItem, the event is fired to any 214 * listeners on the TreeItem that the event occurs, before it 'bubbles' up the 215 * TreeItem chain by following the TreeItem parent property. This repeats 216 * until a TreeItem whose parent TreeItem is null is reached At this point 217 * the event stops 'bubbling' and goes no further. This means that events 218 * that occur on a TreeItem can be relatively cheap, as a listener needs only 219 * be installed on the TreeView root node to be alerted of events happening 220 * at any point in the tree. 221 * 222 * @param <T> The type of the value contained within the TreeItem. 223 */ 224 @SuppressWarnings("unchecked") 225 public static <T> EventType<TreeModificationEvent<T>> treeNotificationEvent() { 226 return (EventType<TreeModificationEvent<T>>) TREE_NOTIFICATION_EVENT; 227 } 228 private static final EventType<?> TREE_NOTIFICATION_EVENT 229 = new EventType<>(Event.ANY, "TreeNotificationEvent"); 230 231 /** 232 * The general EventType used when the TreeItem receives a modification that 233 * results in the number of children being visible changes. 234 * This is normally achieved via one of the sub-types of this 235 * EventType (see {@link #branchExpandedEvent()}, 236 * {@link #branchCollapsedEvent()} and {@link #childrenModificationEvent()} 237 * for the three sub-types). 238 * 239 * @param <T> The type of the value contained within the TreeItem. 240 * @since JavaFX 8.0 241 */ 242 @SuppressWarnings("unchecked") 243 public static <T> EventType<TreeModificationEvent<T>> expandedItemCountChangeEvent() { 244 return (EventType<TreeModificationEvent<T>>) EXPANDED_ITEM_COUNT_CHANGE_EVENT; 245 } 246 private static final EventType<?> EXPANDED_ITEM_COUNT_CHANGE_EVENT 247 = new EventType<>(treeNotificationEvent(), "ExpandedItemCountChangeEvent"); 248 249 /** 250 * An EventType used when the TreeItem receives a modification to its 251 * expanded property, such that the TreeItem is now in the expanded state. 252 * 253 * @param <T> The type of the value contained within the TreeItem. 254 */ 255 @SuppressWarnings("unchecked") 256 public static <T> EventType<TreeModificationEvent<T>> branchExpandedEvent() { 257 return (EventType<TreeModificationEvent<T>>) BRANCH_EXPANDED_EVENT; 258 } 259 private static final EventType<?> BRANCH_EXPANDED_EVENT 260 = new EventType<>(expandedItemCountChangeEvent(), "BranchExpandedEvent"); 261 262 /** 263 * An EventType used when the TreeItem receives a modification to its 264 * expanded property, such that the TreeItem is now in the collapsed state. 265 * 266 * @param <T> The type of the value contained within the TreeItem. 267 */ 268 @SuppressWarnings("unchecked") 269 public static <T> EventType<TreeModificationEvent<T>> branchCollapsedEvent() { 270 return (EventType<TreeModificationEvent<T>>) BRANCH_COLLAPSED_EVENT; 271 } 272 private static final EventType<?> BRANCH_COLLAPSED_EVENT 273 = new EventType<>(expandedItemCountChangeEvent(), "BranchCollapsedEvent"); 274 275 /** 276 * An EventType used when the TreeItem receives a direct modification to its 277 * children list. 278 * 279 * @param <T> The type of the value contained within the TreeItem. 280 */ 281 @SuppressWarnings("unchecked") 282 public static <T> EventType<TreeModificationEvent<T>> childrenModificationEvent() { 283 return (EventType<TreeModificationEvent<T>>) CHILDREN_MODIFICATION_EVENT; 284 } 285 private static final EventType<?> CHILDREN_MODIFICATION_EVENT 286 = new EventType<>(expandedItemCountChangeEvent(), "ChildrenModificationEvent"); 287 288 /** 289 * An EventType used when the TreeItem receives a modification to its 290 * value property. 291 * 292 * @param <T> The type of the value contained within the TreeItem. 293 */ 294 @SuppressWarnings("unchecked") 295 public static <T> EventType<TreeModificationEvent<T>> valueChangedEvent() { 296 return (EventType<TreeModificationEvent<T>>) VALUE_CHANGED_EVENT; 297 } 298 private static final EventType<?> VALUE_CHANGED_EVENT 299 = new EventType<>(treeNotificationEvent(), "ValueChangedEvent"); 300 301 /** 302 * An EventType used when the TreeItem receives a modification to its 303 * graphic property. 304 * 305 * @param <T> The type of the value contained within the TreeItem. 306 */ 307 @SuppressWarnings("unchecked") 308 public static <T> EventType<TreeModificationEvent<T>> graphicChangedEvent() { 309 return (EventType<TreeModificationEvent<T>>) GRAPHIC_CHANGED_EVENT; 310 } 311 private static final EventType<?> GRAPHIC_CHANGED_EVENT 312 = new EventType<>(treeNotificationEvent(), "GraphicChangedEvent"); 313 314 315 316 /*************************************************************************** 317 * * 318 * Constructors * 319 * * 320 **************************************************************************/ 321 322 /** 323 * Creates an empty TreeItem. 324 */ 325 public TreeItem() { 326 this(null); 327 } 328 329 /** 330 * Creates a TreeItem with the value property set to the provided object. 331 * 332 * @param value The object to be stored as the value of this TreeItem. 333 */ 334 public TreeItem(final T value) { 335 this(value, (Node)null); 336 } 337 338 /** 339 * Creates a TreeItem with the value property set to the provided object, and 340 * the graphic set to the provided Node. 341 * 342 * @param value The object to be stored as the value of this TreeItem. 343 * @param graphic The Node to show in the TreeView next to this TreeItem. 344 */ 345 public TreeItem(final T value, final Node graphic) { 346 setValue(value); 347 setGraphic(graphic); 348 349 addEventHandler(TreeItem.<Object>expandedItemCountChangeEvent(), itemListener); 350 } 351 352 private final EventHandler<TreeModificationEvent<Object>> itemListener = 353 new EventHandler<TreeModificationEvent<Object>>() { 354 @Override public void handle(TreeModificationEvent<Object> event) { 355 expandedDescendentCountDirty = true; 356 } 357 }; 358 359 360 /*************************************************************************** 361 * * 362 * Instance Variables * 363 * * 364 **************************************************************************/ 365 366 private boolean ignoreSortUpdate = false; 367 368 private boolean expandedDescendentCountDirty = true; 369 370 // The ObservableList containing all children belonging to this TreeItem. 371 // It is important that interactions with this list go directly into the 372 // children property, rather than via getChildren(), as this may be 373 // a very expensive call. 374 ObservableList<TreeItem<T>> children; 375 376 // Made static based on findings of RT-18344 - EventHandlerManager is an 377 // expensive class and should be reused amongst classes if at all possible. 378 private final EventHandlerManager eventHandlerManager = 379 new EventHandlerManager(this); 380 381 382 // Rather than have the TreeView need to (pretty well) constantly determine 383 // the expanded descendent count of a TreeItem, we instead cache it locally 384 // based on tree item modification events. 385 private int expandedDescendentCount = 1; 386 387 // we record the previous value also, so that we can easily determine how 388 // many items just disappeared on a TreeItem collapse event. Note that the 389 // actual number of items that disappeared is one less than this value, 390 // because we obviously are also counting this node, which hasn't disappeared 391 // when all children are collapsed. 392 int previousExpandedDescendentCount = 1; 393 394 Comparator<TreeItem<T>> lastComparator = null; 395 TreeSortMode lastSortMode = null; 396 397 // Refer to the TreeItem.updateChildrenParent method below for more context 398 // and a description of this field 399 private int parentLinkCount = 0; 400 401 402 403 /*************************************************************************** 404 * * 405 * Callbacks and events * 406 * * 407 **************************************************************************/ 408 409 // called whenever the contents of the children sequence changes 410 private ListChangeListener<TreeItem<T>> childrenListener = c -> { 411 expandedDescendentCountDirty = true; 412 updateChildren(c); 413 }; 414 415 416 417 /*************************************************************************** 418 * * 419 * Properties * 420 * * 421 **************************************************************************/ 422 423 // --- Value 424 private ObjectProperty<T> value; 425 426 /** 427 * Sets the application-specific data represented by this TreeItem. 428 */ 429 public final void setValue(T value) { valueProperty().setValue(value); } 430 431 /** 432 * Returns the application-specific data represented by this TreeItem. 433 * @return the data represented by this TreeItem 434 */ 435 public final T getValue() { return value == null ? null : value.getValue(); } 436 437 /** 438 * A property representing the application-specific data contained within 439 * this TreeItem. 440 */ 441 public final ObjectProperty<T> valueProperty() { 442 if (value == null) { 443 value = new ObjectPropertyBase<T>() { 444 @Override protected void invalidated() { 445 fireEvent(new TreeModificationEvent<T>(VALUE_CHANGED_EVENT, TreeItem.this, get())); 446 } 447 448 @Override public Object getBean() { 449 return TreeItem.this; 450 } 451 452 @Override public String getName() { 453 return "value"; 454 } 455 }; 456 } 457 return value; 458 } 459 460 461 // --- Graphic 462 private ObjectProperty<Node> graphic; 463 464 /** 465 * Sets the node that is generally shown to the left of the value property. 466 * For best effect, this tends to be a 16x16 image. 467 * 468 * @param value The graphic node that will be displayed to the user. 469 */ 470 public final void setGraphic(Node value) { graphicProperty().setValue(value); } 471 472 /** 473 * Returns the node that is generally shown to the left of the value property. 474 * For best effect, this tends to be a 16x16 image. 475 * 476 * @return The graphic node that will be displayed to the user. 477 */ 478 public final Node getGraphic() { return graphic == null ? null : graphic.getValue(); } 479 480 /** 481 * The node that is generally shown to the left of the value property. For 482 * best effect, this tends to be a 16x16 image. 483 */ 484 public final ObjectProperty<Node> graphicProperty() { 485 if (graphic == null) { 486 graphic = new ObjectPropertyBase<Node>() { 487 @Override protected void invalidated() { 488 fireEvent(new TreeModificationEvent<T>(GRAPHIC_CHANGED_EVENT, TreeItem.this)); 489 } 490 491 @Override 492 public Object getBean() { 493 return TreeItem.this; 494 } 495 496 @Override 497 public String getName() { 498 return "graphic"; 499 } 500 }; 501 } 502 return graphic; 503 } 504 505 506 // --- Expanded 507 private BooleanProperty expanded; 508 509 /** 510 * Sets the expanded state of this TreeItem. This has no effect on a TreeItem 511 * with no children. On a TreeItem with children however, the result of 512 * toggling this property is that visually the children will either become 513 * visible or hidden, based on whether expanded is set to true or false. 514 * 515 * @param value If this TreeItem has children, calling setExpanded with 516 * <code>true</code> will result in the children becoming visible. 517 * Calling setExpanded with <code>false</code> will hide any children 518 * belonging to the TreeItem. 519 */ 520 public final void setExpanded(boolean value) { 521 if (! value && expanded == null) return; 522 expandedProperty().setValue(value); 523 } 524 525 /** 526 * Returns the expanded state of this TreeItem. 527 * 528 * @return Returns the expanded state of this TreeItem. 529 */ 530 public final boolean isExpanded() { return expanded == null ? false : expanded.getValue(); } 531 532 /** 533 * The expanded state of this TreeItem. 534 */ 535 public final BooleanProperty expandedProperty() { 536 if (expanded == null) { 537 expanded = new BooleanPropertyBase() { 538 @Override protected void invalidated() { 539 // We don't fire expanded events for leaf nodes (RT-32620) 540 if (isLeaf()) return; 541 542 EventType<?> evtType = isExpanded() ? 543 BRANCH_EXPANDED_EVENT : BRANCH_COLLAPSED_EVENT; 544 545 fireEvent(new TreeModificationEvent<T>(evtType, TreeItem.this, isExpanded())); 546 } 547 548 @Override 549 public Object getBean() { 550 return TreeItem.this; 551 } 552 553 @Override 554 public String getName() { 555 return "expanded"; 556 } 557 }; 558 } 559 return expanded; 560 } 561 562 563 // --- Leaf 564 private ReadOnlyBooleanWrapper leaf; 565 private void setLeaf(boolean value) { 566 if (value && leaf == null) { 567 return; 568 } else if (leaf == null) { 569 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true); 570 } 571 leaf.setValue(value); 572 } 573 574 /** 575 * A TreeItem is a leaf if it has no children. The isLeaf method may of 576 * course be overridden by subclasses to support alternate means of defining 577 * how a TreeItem may be a leaf, but the general premise is the same: a 578 * leaf can not be expanded by the user, and as such will not show a 579 * disclosure node or respond to expansion requests. 580 */ 581 public boolean isLeaf() { return leaf == null ? true : leaf.getValue(); } 582 583 /** 584 * Represents the TreeItem leaf property, which is true if the TreeItem has no children. 585 */ 586 public final ReadOnlyBooleanProperty leafProperty() { 587 if (leaf == null) { 588 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true); 589 } 590 return leaf.getReadOnlyProperty(); 591 } 592 593 594 // --- Parent 595 private ReadOnlyObjectWrapper<TreeItem<T>> parent = new ReadOnlyObjectWrapper<TreeItem<T>>(this, "parent"); 596 private void setParent(TreeItem<T> value) { parent.setValue(value); } 597 598 /** 599 * The parent of this TreeItem. Each TreeItem can have no more than one 600 * parent. If a TreeItem has no parent, it represents a root in the tree model. 601 * 602 * @return The parent of this TreeItem, or null if the TreeItem has no parent. 603 */ 604 public final TreeItem<T> getParent() { return parent == null ? null : parent.getValue(); } 605 606 /** 607 * A property that represents the parent of this TreeItem. 608 */ 609 public final ReadOnlyObjectProperty<TreeItem<T>> parentProperty() { return parent.getReadOnlyProperty(); } 610 611 612 613 /*********************************************************************** 614 * * 615 * TreeItem API * 616 * * 617 **********************************************************************/ 618 619 /** 620 * The children of this TreeItem. This method is called frequently, and 621 * it is therefore recommended that the returned list be cached by 622 * any TreeItem implementations. 623 * 624 * @return a list that contains the child TreeItems belonging to the TreeItem. 625 */ 626 public ObservableList<TreeItem<T>> getChildren() { 627 if (children == null) { 628 children = FXCollections.observableArrayList(); 629 children.addListener(childrenListener); 630 } 631 632 // we need to check if this TreeItem needs to have its children sorted. 633 // There are two different ways that this could be possible. 634 if (children.isEmpty()) return children; 635 636 // checkSortState should in almost all instances be called, but there 637 // are situations where checking the sort state will result in 638 // unwanted permutation events being fired (if a sort is applied). To 639 // avoid this (which resolves RT-37593), we set the ignoreSortUpdate 640 // to true (and of course, we're careful to set it back to false again) 641 if (!ignoreSortUpdate) { 642 checkSortState(); 643 } 644 645 return children; 646 } 647 648 649 650 /*************************************************************************** 651 * * 652 * Public API * 653 * * 654 **************************************************************************/ 655 656 /** 657 * Returns the previous sibling of the TreeItem. Ordering is based on the 658 * position of the TreeItem relative to its siblings in the children 659 * list belonging to the parent of the TreeItem. 660 * 661 * @return A TreeItem that is the previous sibling of the current TreeItem, 662 * or null if no such sibling can be found. 663 */ 664 public TreeItem<T> previousSibling() { 665 return previousSibling(this); 666 } 667 668 /** 669 * Returns the previous sibling after the given node. Ordering is based on the 670 * position of the given TreeItem relative to its siblings in the children 671 * list belonging to the parent of the TreeItem. 672 * 673 * @param beforeNode The TreeItem for which the previous sibling is being 674 * sought. 675 * @return A TreeItem that is the previous sibling of the given TreeItem, 676 * or null if no such sibling can be found. 677 */ 678 public TreeItem<T> previousSibling(final TreeItem<T> beforeNode) { 679 if (getParent() == null || beforeNode == null) { 680 return null; 681 } 682 683 List<TreeItem<T>> parentChildren = getParent().getChildren(); 684 final int childCount = parentChildren.size(); 685 int pos = -1; 686 for (int i = 0; i < childCount; i++) { 687 if (beforeNode.equals(parentChildren.get(i))) { 688 pos = i - 1; 689 return pos < 0 ? null : parentChildren.get(pos); 690 } 691 } 692 return null; 693 } 694 695 /** 696 * Returns the next sibling of the TreeItem. Ordering is based on the 697 * position of the TreeItem relative to its siblings in the children 698 * list belonging to the parent of the TreeItem. 699 * 700 * @return A TreeItem that is the next sibling of the current TreeItem, 701 * or null if no such sibling can be found. 702 */ 703 public TreeItem<T> nextSibling() { 704 return nextSibling(this); 705 } 706 707 /** 708 * Returns the next sibling after the given node. Ordering is based on the 709 * position of the given TreeItem relative to its siblings in the children 710 * list belonging to the parent of the TreeItem. 711 * 712 * @param afterNode The TreeItem for which the next sibling is being 713 * sought. 714 * @return A TreeItem that is the next sibling of the given TreeItem, 715 * or null if no such sibling can be found. 716 */ 717 public TreeItem<T> nextSibling(final TreeItem<T> afterNode) { 718 if (getParent() == null || afterNode == null) { 719 return null; 720 } 721 722 List<TreeItem<T>> parentChildren = getParent().getChildren(); 723 final int childCount = parentChildren.size(); 724 int pos = -1; 725 for (int i = 0; i < childCount; i++) { 726 if (afterNode.equals(parentChildren.get(i))) { 727 pos = i + 1; 728 return pos >= childCount ? null : parentChildren.get(pos); 729 } 730 } 731 return null; 732 } 733 734 /** 735 * Returns a string representation of this {@code TreeItem} object. 736 * @return a string representation of this {@code TreeItem} object. 737 */ 738 @Override public String toString() { 739 return "TreeItem [ value: " + getValue() + " ]"; 740 } 741 742 private void fireEvent(TreeModificationEvent<T> evt) { 743 Event.fireEvent(this, evt); 744 } 745 746 747 748 749 /*************************************************************************** 750 * * 751 * Event Target Implementation / API * 752 * * 753 **************************************************************************/ 754 755 /** {@inheritDoc} */ 756 @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { 757 // To allow for a TreeView (and its skin) to be notified of changes in the 758 // tree, this method recursively calls up to the root node, at which point 759 // it fires a ROOT_NOTIFICATION_EVENT, which the TreeView may be watching for. 760 if (getParent() != null) { 761 getParent().buildEventDispatchChain(tail); 762 } 763 return tail.append(eventHandlerManager); 764 } 765 766 /** 767 * Registers an event handler to this TreeItem. The TreeItem class allows 768 * registration of listeners which will be notified as the 769 * number of items changes, their position or if the values themselves change. 770 * Note however that a TreeItem is <b>not</b> a Node, and therefore no visual 771 * events will be fired on the TreeItem. To get these events, it is necessary to 772 * add relevant observers to the TreeCell instances (via a custom cell factory - 773 * see the {@link Cell} class documentation for more details). 774 * 775 * @param eventType the type of the events to receive by the handler 776 * @param eventHandler the handler to register 777 * @throws NullPointerException if the event type or handler is null 778 */ 779 public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { 780 eventHandlerManager.addEventHandler(eventType, eventHandler); 781 } 782 783 /** 784 * Unregisters a previously registered event handler from this TreeItem. One 785 * handler might have been registered for different event types, so the 786 * caller needs to specify the particular event type from which to 787 * unregister the handler. 788 * 789 * @param eventType the event type from which to unregister 790 * @param eventHandler the handler to unregister 791 * @throws NullPointerException if the event type or handler is null 792 */ 793 public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { 794 eventHandlerManager.removeEventHandler(eventType, eventHandler); 795 } 796 797 798 799 /*************************************************************************** 800 * * 801 * private methods * 802 * * 803 **************************************************************************/ 804 805 void sort() { 806 sort(children, lastComparator, lastSortMode); 807 } 808 809 private void sort(final ObservableList<TreeItem<T>> children, 810 final Comparator<TreeItem<T>> comparator, 811 final TreeSortMode sortMode) { 812 813 if (comparator == null) return; 814 815 runSort(children, comparator, sortMode); 816 817 // if we're at the root node, we'll fire an event so that the control 818 // can update its display 819 if (getParent() == null) { 820 TreeModificationEvent<T> e = new TreeModificationEvent<T>(TreeItem.childrenModificationEvent(), this); 821 e.wasPermutated = true; 822 fireEvent(e); 823 } 824 } 825 826 private void checkSortState() { 827 TreeItem<T> rootNode = getRoot(); 828 829 TreeSortMode sortMode = rootNode.lastSortMode; 830 Comparator<TreeItem<T>> comparator = rootNode.lastComparator; 831 832 if (comparator != null && comparator != lastComparator) { 833 lastComparator = comparator; 834 runSort(children, comparator, sortMode); 835 } 836 } 837 838 private void runSort(ObservableList<TreeItem<T>> children, Comparator<TreeItem<T>> comparator, TreeSortMode sortMode) { 839 if (sortMode == ALL_DESCENDANTS) { 840 doSort(children, comparator); 841 } else if (sortMode == ONLY_FIRST_LEVEL) { 842 // if we are here we presume that the current node is the root node 843 // (but we can test to see if getParent() returns null to be sure). 844 // We also know that ONLY_FIRST_LEVEL only applies to the children 845 // of the root, so we return straight after we sort these children. 846 if (getParent() == null) { 847 doSort(children, comparator); 848 } 849 // } else if (sortMode == ONLY_LEAVES) { 850 // if (isLeaf()) { 851 // // sort the parent once 852 // } 853 // } else if (sortMode == ALL_BUT_LEAVES) { 854 // 855 } else { 856 // Unknown sort mode 857 } 858 } 859 860 private TreeItem<T> getRoot() { 861 TreeItem<T> parent = getParent(); 862 if (parent == null) return this; 863 864 while (true) { 865 TreeItem<T> newParent = parent.getParent(); 866 if (newParent == null) return parent; 867 parent = newParent; 868 } 869 } 870 871 private void doSort(ObservableList<TreeItem<T>> children, final Comparator<TreeItem<T>> comparator) { 872 if (!isLeaf() && isExpanded()) { 873 FXCollections.sort(children, comparator); 874 } 875 } 876 877 // This value is package accessible so that it may be retrieved from TreeView. 878 int getExpandedDescendentCount(boolean reset) { 879 if (reset || expandedDescendentCountDirty) { 880 updateExpandedDescendentCount(reset); 881 expandedDescendentCountDirty = false; 882 } 883 return expandedDescendentCount; 884 } 885 886 private void updateExpandedDescendentCount(boolean reset) { 887 previousExpandedDescendentCount = expandedDescendentCount; 888 expandedDescendentCount = 1; 889 890 ignoreSortUpdate = true; 891 if (!isLeaf() && isExpanded()) { 892 for (TreeItem<T> child : getChildren()) { 893 if (child == null) continue; 894 expandedDescendentCount += child.isExpanded() ? child.getExpandedDescendentCount(reset) : 1; 895 } 896 } 897 ignoreSortUpdate = false; 898 } 899 900 private void updateChildren(ListChangeListener.Change<? extends TreeItem<T>> c) { 901 setLeaf(children.isEmpty()); 902 903 final List<TreeItem<T>> added = new ArrayList<>(); 904 final List<TreeItem<T>> removed = new ArrayList<>(); 905 906 while (c.next()) { 907 added.addAll(c.getAddedSubList()); 908 removed.addAll(c.getRemoved()); 909 } 910 911 // update the relationships such that all added children point to 912 // this node as the parent (and all removed children point to null) 913 updateChildrenParent(removed, null); 914 updateChildrenParent(added, this); 915 916 c.reset(); 917 918 // fire an event up the parent hierarchy such that any listening 919 // TreeViews (which only listen to their root node) can redraw 920 fireEvent(new TreeModificationEvent<T>( 921 CHILDREN_MODIFICATION_EVENT, this, added, removed, c)); 922 } 923 924 // Convenience method to set the parent of all children in the given list to 925 // the given parent TreeItem 926 private static <T> void updateChildrenParent(List<? extends TreeItem<T>> treeItems, final TreeItem<T> newParent) { 927 if (treeItems == null) return; 928 for (final TreeItem<T> treeItem : treeItems) { 929 if (treeItem == null) continue; 930 931 TreeItem<T> currentParent = treeItem.getParent(); 932 933 // We only replace the parent if the parentLinkCount of the given 934 // TreeItem is zero (which indicates that this TreeItem has not been 935 // 'linked' to its parent multiple times). This can happen in 936 // situations such as what is shown in RT-28668 (and tested for in 937 // TreeViewTest.test_rt28556()). Specifically, when a sort is applied 938 // to the children of a TreeItem, it is possible for them to be 939 // sorted in such a way that the element is considered to be 940 // added in multiple places in the child list and then removed from 941 // one of those places subsequently. In doing this final removal, 942 // the parent of that TreeItem is set to null when it should in fact 943 // remain with the parent that it belongs to. 944 if (treeItem.parentLinkCount == 0) { 945 treeItem.setParent(newParent); 946 } 947 948 boolean parentMatch = currentParent != null && currentParent.equals(newParent); 949 if (parentMatch) { 950 if (newParent == null) { 951 treeItem.parentLinkCount--; 952 } else { 953 treeItem.parentLinkCount++; 954 } 955 } 956 } 957 } 958 959 /** 960 * An {@link Event} that contains relevant information for all forms of 961 * TreeItem modifications. 962 * @since JavaFX 2.0 963 */ 964 public static class TreeModificationEvent<T> extends Event { 965 private static final long serialVersionUID = 4741889985221719579L; 966 967 /** 968 * Common supertype for all tree modification event types. 969 * @since JavaFX 8.0 970 */ 971 public static final EventType<?> ANY = TREE_NOTIFICATION_EVENT; 972 973 private transient final TreeItem<T> treeItem; 974 private final T newValue; 975 976 private final List<? extends TreeItem<T>> added; 977 private final List<? extends TreeItem<T>> removed; 978 private final ListChangeListener.Change<? extends TreeItem<T>> change; 979 980 private final boolean wasExpanded; 981 private final boolean wasCollapsed; 982 private boolean wasPermutated; 983 984 /** 985 * Constructs a basic TreeModificationEvent - this is useful in situations 986 * where the tree item has not received a new value, has not changed 987 * between expanded/collapsed states, and whose children has not changed. 988 * An example of when this constructor is used is when the TreeItem 989 * graphic property changes. 990 * 991 * @param eventType The type of the event that has occurred. 992 * @param treeItem The TreeItem on which this event occurred. 993 */ 994 public TreeModificationEvent(EventType<? extends Event> eventType, TreeItem<T> treeItem) { 995 this (eventType, treeItem, null); 996 } 997 998 /** 999 * Constructs a TreeModificationEvent for when the TreeItem has had its 1000 * {@link TreeItem#valueProperty()} changed. 1001 * 1002 * @param eventType The type of the event that has occurred. 1003 * @param treeItem The TreeItem on which this event occurred. 1004 * @param newValue The new value that has been put into the 1005 * {@link TreeItem#valueProperty()}. 1006 */ 1007 public TreeModificationEvent(EventType<? extends Event> eventType, 1008 TreeItem<T> treeItem, T newValue) { 1009 super(eventType); 1010 this.treeItem = treeItem; 1011 this.newValue = newValue; 1012 this.added = null; 1013 this.removed = null; 1014 this.change = null; 1015 this.wasExpanded = false; 1016 this.wasCollapsed = false; 1017 } 1018 1019 /** 1020 * Constructs a TreeModificationEvent for when the TreeItem has had its 1021 * {@link TreeItem#expandedProperty()} changed. 1022 * 1023 * @param eventType The type of the event that has occurred. 1024 * @param treeItem The TreeItem on which this event occurred. 1025 * @param expanded A boolean to represent the current expanded 1026 * state of the TreeItem. 1027 */ 1028 public TreeModificationEvent(EventType<? extends Event> eventType, 1029 TreeItem<T> treeItem, boolean expanded) { 1030 super(eventType); 1031 this.treeItem = treeItem; 1032 this.newValue = null; 1033 this.added = null; 1034 this.removed = null; 1035 this.change = null; 1036 this.wasExpanded = expanded; 1037 this.wasCollapsed = ! expanded; 1038 } 1039 1040 /** 1041 * Constructs a TreeModificationEvent for when the TreeItem has had its 1042 * children list changed. 1043 * 1044 * @param eventType The type of the event that has occurred. 1045 * @param treeItem The TreeItem on which this event occurred. 1046 * @param added A list of the items added to the children list of the 1047 * given TreeItem. 1048 * @param removed A list of the items removed from the children list of 1049 * the given TreeItem. 1050 */ 1051 public TreeModificationEvent(EventType<? extends Event> eventType, 1052 TreeItem<T> treeItem, 1053 List<? extends TreeItem<T>> added, 1054 List<? extends TreeItem<T>> removed) { 1055 this(eventType, treeItem, added, removed, null); 1056 } 1057 1058 /** 1059 * Constructs a TreeModificationEvent for when the TreeItem has had its 1060 * children list changed, including the 1061 * {@link javafx.collections.ListChangeListener.Change} that has taken place. 1062 * 1063 * @param eventType The type of the event that has occurred. 1064 * @param treeItem The TreeItem on which this event occurred. 1065 * @param added A list of the items added to the children list of the 1066 * given TreeItem. 1067 * @param removed A list of the items removed from the children list of 1068 * the given TreeItem. 1069 * @param change The actual change that has taken place on the children list. 1070 */ 1071 private TreeModificationEvent(EventType<? extends Event> eventType, 1072 TreeItem<T> treeItem, 1073 List<? extends TreeItem<T>> added, 1074 List<? extends TreeItem<T>> removed, 1075 ListChangeListener.Change<? extends TreeItem<T>> change) { 1076 super(eventType); 1077 this.treeItem = treeItem; 1078 this.newValue = null; 1079 this.added = added; 1080 this.removed = removed; 1081 this.change = change; 1082 this.wasExpanded = false; 1083 this.wasCollapsed = false; 1084 1085 this.wasPermutated = added != null && removed != null && 1086 added.size() == removed.size() && 1087 added.containsAll(removed); 1088 } 1089 1090 /** 1091 * Returns the TreeItem upon which this event occurred. 1092 * @since JavaFX 2.1 1093 */ 1094 @Override public TreeItem<T> getSource() { 1095 return this.treeItem; 1096 } 1097 1098 /** 1099 * Returns the TreeItem that this event occurred upon. 1100 * @return The TreeItem that this event occurred upon. 1101 */ 1102 public TreeItem<T> getTreeItem() { 1103 return treeItem; 1104 } 1105 1106 /** 1107 * If the value of the TreeItem changed, this method will return the new 1108 * value. If it did not change, this method will return null. 1109 * @return The new value of the TreeItem if it changed, null otherwise. 1110 */ 1111 public T getNewValue() { 1112 return newValue; 1113 } 1114 1115 /** 1116 * Returns the children added to the TreeItem in this event, or an empty 1117 * list if no children were added. 1118 * @return The newly added children, or an empty list if no children 1119 * were added. 1120 */ 1121 public List<? extends TreeItem<T>> getAddedChildren() { 1122 return added == null ? Collections.<TreeItem<T>>emptyList() : added; 1123 } 1124 1125 /** 1126 * Returns the children removed from the TreeItem in this event, or an 1127 * empty list if no children were added. 1128 * @return The removed children, or an empty list if no children 1129 * were removed. 1130 */ 1131 public List<? extends TreeItem<T>> getRemovedChildren() { 1132 return removed == null ? Collections.<TreeItem<T>>emptyList() : removed; 1133 } 1134 1135 /** 1136 * Returns the number of children items that were removed in this event, 1137 * or zero if no children were removed. 1138 * @return The number of removed children items, or zero if no children 1139 * were removed. 1140 */ 1141 public int getRemovedSize() { 1142 return getRemovedChildren().size(); 1143 } 1144 1145 /** 1146 * Returns the number of children items that were added in this event, 1147 * or zero if no children were added. 1148 * @return The number of added children items, or zero if no children 1149 * were added. 1150 */ 1151 public int getAddedSize() { 1152 return getAddedChildren().size(); 1153 } 1154 1155 /** 1156 * Returns true if this event represents a TreeItem expansion event, 1157 * and false if the TreeItem was not expanded. 1158 */ 1159 public boolean wasExpanded() { return wasExpanded; } 1160 1161 /** 1162 * Returns true if this event represents a TreeItem collapse event, 1163 * and false if the TreeItem was not collapsed. 1164 */ 1165 public boolean wasCollapsed() { return wasCollapsed; } 1166 1167 /** 1168 * Returns true if this event represents a TreeItem event where children 1169 * TreeItems were added. 1170 */ 1171 public boolean wasAdded() { return getAddedSize() > 0; } 1172 1173 /** 1174 * Returns true if this event represents a TreeItem event where children 1175 * TreeItems were removed. 1176 */ 1177 public boolean wasRemoved() { return getRemovedSize() > 0; } 1178 1179 /** 1180 * Returns true if the order of the TreeItem children list has changed, 1181 * but that there have been no additions or removals. 1182 */ 1183 public boolean wasPermutated() { return wasPermutated; } 1184 1185 int getFrom() { return change == null ? -1 : change.getFrom(); } 1186 int getTo() { return change == null ? -1 : change.getTo(); } 1187 ListChangeListener.Change<? extends TreeItem<T>> getChange() { return change; } 1188 } 1189 }