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