Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control; 027 028import java.util.Collections; 029import java.util.List; 030 031import javafx.beans.property.BooleanProperty; 032import javafx.beans.property.BooleanPropertyBase; 033import javafx.beans.property.ObjectProperty; 034import javafx.beans.property.ObjectPropertyBase; 035import javafx.collections.FXCollections; 036import javafx.collections.ListChangeListener; 037import javafx.collections.ObservableList; 038import javafx.event.Event; 039import javafx.event.EventDispatchChain; 040import javafx.event.EventHandler; 041import javafx.event.EventTarget; 042import javafx.event.EventType; 043import javafx.scene.Node; 044 045import com.sun.javafx.event.EventHandlerManager; 046import java.util.Comparator; 047import javafx.beans.property.ReadOnlyBooleanProperty; 048import javafx.beans.property.ReadOnlyBooleanWrapper; 049import javafx.beans.property.ReadOnlyObjectProperty; 050import javafx.beans.property.ReadOnlyObjectWrapper; 051import javafx.scene.control.TreeSortMode; 052 053import static javafx.scene.control.TreeSortMode.*; 054 055/** 056 * The model for a single node supplying a hierarchy of values to a control such 057 * as TreeView. The model may be implemented such that values may be loaded in 058 * memory as they are needed. 059 * <p> 060 * The model allows registration of listeners which will be notified as the 061 * number of items changes, their position or if the values themselves change. 062 * Note however that a TreeItem is <b>not</b> a Node, and therefore no visual 063 * events will be fired on the TreeItem. To get these events, it is necessary to 064 * add relevant observers to the TreeCell instances (via a custom cell factory - 065 * see the {@link Cell} class documentation for more details). 066 * 067 * <p>In the simplest case, TreeItem instances may be created in memory as such: 068 * <pre><code> 069 * TreeItem<String> root = new TreeItem<String>("Root Node"); 070 * root.setExpanded(true); 071 * root.getChildren().addAll( 072 * new TreeItem<String>("Item 1"), 073 * new TreeItem<String>("Item 2"), 074 * new TreeItem<String>("Item 3") 075 * ); 076 * TreeView<String> treeView = new TreeView<String>(root); 077 * </code></pre> 078 * 079 * This approach works well for simple tree structures, or when the data is not 080 * excessive (so that it can easily fit in memory). In situations where the size 081 * of the tree structure is unknown (and therefore potentially huge), there is 082 * the option of creating TreeItem instances on-demand in a memory-efficient way. 083 * To demonstrate this, the code below creates a file system browser: 084 * 085 * <pre><code> 086 * private TreeView buildFileSystemBrowser() { 087 * TreeItem<File> root = createNode(new File("/")); 088 * return new TreeView<File>(root); 089 * } 090 * 091 * // This method creates a TreeItem to represent the given File. It does this 092 * // by overriding the TreeItem.getChildren() and TreeItem.isLeaf() methods 093 * // anonymously, but this could be better abstracted by creating a 094 * // 'FileTreeItem' subclass of TreeItem. However, this is left as an exercise 095 * // for the reader. 096 * private TreeItem<File> createNode(final File f) { 097 * return new TreeItem<File>(f) { 098 * // We cache whether the File is a leaf or not. A File is a leaf if 099 * // 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 */ 202public class TreeItem<T> implements EventTarget { //, Comparable<TreeItem<T>> { 203 204 /*************************************************************************** 205 * * 206 * Static properties and methods * 207 * * 208 **************************************************************************/ 209 210 /** 211 * The base EventType used to indicate that an event has occurred within a 212 * TreeItem. When an event occurs in a TreeItem, the event is fired to any 213 * listeners on the TreeItem that the event occurs, before it 'bubbles' up the 214 * TreeItem chain by following the TreeItem parent property. This repeats 215 * until a TreeItem whose parent TreeItem is null is reached At this point 216 * the event stops 'bubbling' and goes no further. This means that events 217 * that occur on a TreeItem can be relatively cheap, as a listener needs only 218 * be installed on the TreeView root node to be alerted of events happening 219 * at any point in the tree. 220 * 221 * @param <T> The type of the value contained within the TreeItem. 222 */ 223 @SuppressWarnings("unchecked") 224 public static <T> EventType<TreeModificationEvent<T>> treeNotificationEvent() { 225 return (EventType<TreeModificationEvent<T>>) TREE_NOTIFICATION_EVENT; 226 } 227 private static final EventType TREE_NOTIFICATION_EVENT 228 = new EventType(Event.ANY, "TreeNotificationEvent"); 229 230 /** 231 * The general EventType used when the TreeItem receives a modification that 232 * results in the number of children being visible changes. 233 * This is normally achieved via one of the sub-types of this 234 * EventType (see {@link #branchExpandedEvent()}, 235 * {@link #branchCollapsedEvent()} and {@link #childrenModificationEvent()} 236 * for the three sub-types). 237 * 238 * @param <T> The type of the value contained within the TreeItem. 239 */ 240 @SuppressWarnings("unchecked") 241 public static <T> EventType<TreeModificationEvent<T>> expandedItemCountChangeEvent() { 242 return (EventType<TreeModificationEvent<T>>) EXPANDED_ITEM_COUNT_CHANGE_EVENT; 243 } 244 private static final EventType EXPANDED_ITEM_COUNT_CHANGE_EVENT 245 = new EventType(treeNotificationEvent(), "ExpandedItemCountChangeEvent"); 246 247 /** 248 * An EventType used when the TreeItem receives a modification to its 249 * expanded property, such that the TreeItem is now in the expanded state. 250 * 251 * @param <T> The type of the value contained within the TreeItem. 252 */ 253 @SuppressWarnings("unchecked") 254 public static <T> EventType<TreeModificationEvent<T>> branchExpandedEvent() { 255 return (EventType<TreeModificationEvent<T>>) BRANCH_EXPANDED_EVENT; 256 } 257 private static final EventType<?> BRANCH_EXPANDED_EVENT 258 = new EventType(expandedItemCountChangeEvent(), "BranchExpandedEvent"); 259 260 /** 261 * An EventType used when the TreeItem receives a modification to its 262 * expanded property, such that the TreeItem is now in the collapsed state. 263 * 264 * @param <T> The type of the value contained within the TreeItem. 265 */ 266 @SuppressWarnings("unchecked") 267 public static <T> EventType<TreeModificationEvent<T>> branchCollapsedEvent() { 268 return (EventType<TreeModificationEvent<T>>) BRANCH_COLLAPSED_EVENT; 269 } 270 private static final EventType<?> BRANCH_COLLAPSED_EVENT 271 = new EventType(expandedItemCountChangeEvent(), "BranchCollapsedEvent"); 272 273 /** 274 * An EventType used when the TreeItem receives a direct modification to its 275 * children list. 276 * 277 * @param <T> The type of the value contained within the TreeItem. 278 */ 279 @SuppressWarnings("unchecked") 280 public static <T> EventType<TreeModificationEvent<T>> childrenModificationEvent() { 281 return (EventType<TreeModificationEvent<T>>) CHILDREN_MODIFICATION_EVENT; 282 } 283 private static final EventType<?> CHILDREN_MODIFICATION_EVENT 284 = new EventType(expandedItemCountChangeEvent(), "ChildrenModificationEvent"); 285 286 /** 287 * An EventType used when the TreeItem receives a modification to its 288 * value property. 289 * 290 * @param <T> The type of the value contained within the TreeItem. 291 */ 292 @SuppressWarnings("unchecked") 293 public static <T> EventType<TreeModificationEvent<T>> valueChangedEvent() { 294 return (EventType<TreeModificationEvent<T>>) VALUE_CHANGED_EVENT; 295 } 296 private static final EventType<?> VALUE_CHANGED_EVENT 297 = new EventType(treeNotificationEvent(), "ValueChangedEvent"); 298 299 /** 300 * An EventType used when the TreeItem receives a modification to its 301 * graphic property. 302 * 303 * @param <T> The type of the value contained within the TreeItem. 304 */ 305 @SuppressWarnings("unchecked") 306 public static <T> EventType<TreeModificationEvent<T>> graphicChangedEvent() { 307 return (EventType<TreeModificationEvent<T>>) GRAPHIC_CHANGED_EVENT; 308 } 309 private static final EventType<?> GRAPHIC_CHANGED_EVENT 310 = new EventType(treeNotificationEvent(), "GraphicChangedEvent"); 311 312 313 314 /*************************************************************************** 315 * * 316 * Constructors * 317 * * 318 **************************************************************************/ 319 320 /** 321 * Creates an empty TreeItem. 322 */ 323 public TreeItem() { 324 this(null); 325 } 326 327 /** 328 * Creates a TreeItem with the value property set to the provided object. 329 * 330 * @param value The object to be stored as the value of this TreeItem. 331 */ 332 public TreeItem(final T value) { 333 this(value, (Node)null); 334 } 335 336 /** 337 * Creates a TreeItem with the value property set to the provided object, and 338 * the graphic set to the provided Node. 339 * 340 * @param value The object to be stored as the value of this TreeItem. 341 * @param graphic The Node to show in the TreeView next to this TreeItem. 342 */ 343 public TreeItem(final T value, final Node graphic) { 344 setValue(value); 345 setGraphic(graphic); 346 347 addEventHandler(TreeItem.<Object>expandedItemCountChangeEvent(), itemListener); 348 } 349 350 private final EventHandler<TreeModificationEvent<Object>> itemListener = 351 new EventHandler<TreeModificationEvent<Object>>() { 352 @Override public void handle(TreeModificationEvent<Object> event) { 353 expandedDescendentCountDirty = true; 354 } 355 }; 356 357 358 /*************************************************************************** 359 * * 360 * Instance Variables * 361 * * 362 **************************************************************************/ 363 364 private boolean expandedDescendentCountDirty = true; 365 366 // The ObservableList containing all children belonging to this TreeItem. 367 // It is important that interactions with this list go directly into the 368 // children property, rather than via getChildren(), as this may be 369 // a very expensive call. 370 private ObservableList<TreeItem<T>> children; 371 372 // Made static based on findings of RT-18344 - EventHandlerManager is an 373 // expensive class and should be reused amongst classes if at all possible. 374 private final EventHandlerManager eventHandlerManager = 375 new EventHandlerManager(this); 376 377 378 // Rather than have the TreeView need to (pretty well) constantly determine 379 // the expanded descendent count of a TreeItem, we instead cache it locally 380 // based on tree item modification events. 381 private int expandedDescendentCount = 1; 382 383 // we record the previous value also, so that we can easily determine how 384 // many items just disappeared on a TreeItem collapse event. Note that the 385 // actual number of items that disappeared is one less than this value, 386 // because we obviously are also counting this node, which hasn't disappeared 387 // when all children are collapsed. 388 int previousExpandedDescendentCount = 1; 389 390 Comparator<TreeItem<T>> lastComparator = null; 391 TreeSortMode lastSortMode = null; 392 393 // Refer to the TreeItem.updateChildrenParent method below for more context 394 // and a description of this field 395 private int parentLinkCount = 0; 396 397 398 399 /*************************************************************************** 400 * * 401 * Callbacks and events * 402 * * 403 **************************************************************************/ 404 405 // called whenever the contents of the children sequence changes 406 private ListChangeListener<TreeItem<T>> childrenListener = new ListChangeListener<TreeItem<T>>() { 407 @Override public void onChanged(Change<? extends TreeItem<T>> c) { 408 expandedDescendentCountDirty = true; 409 while (c.next()) { 410 updateChildren(c.getAddedSubList(), c.getRemoved()); 411 } 412 } 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 EventType<?> evtType = isExpanded() ? 540 BRANCH_EXPANDED_EVENT : BRANCH_COLLAPSED_EVENT; 541 542 fireEvent(new TreeModificationEvent<T>(evtType, TreeItem.this, isExpanded())); 543 } 544 545 @Override 546 public Object getBean() { 547 return TreeItem.this; 548 } 549 550 @Override 551 public String getName() { 552 return "expanded"; 553 } 554 }; 555 } 556 return expanded; 557 } 558 559 560 // --- Leaf 561 private ReadOnlyBooleanWrapper leaf; 562 private void setLeaf(boolean value) { 563 if (value && leaf == null) { 564 return; 565 } else if (leaf == null) { 566 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true); 567 } 568 leaf.setValue(value); 569 } 570 571 /** 572 * A TreeItem is a leaf if it has no children. The isLeaf method may of 573 * course be overridden by subclasses to support alternate means of defining 574 * how a TreeItem may be a leaf, but the general premise is the same: a 575 * leaf can not be expanded by the user, and as such will not show a 576 * disclosure node or respond to expansion requests. 577 */ 578 public boolean isLeaf() { return leaf == null ? true : leaf.getValue(); } 579 580 /** 581 * Represents the TreeItem leaf property, which is true if the TreeItem has no children. 582 */ 583 public final ReadOnlyBooleanProperty leafProperty() { 584 if (leaf == null) { 585 leaf = new ReadOnlyBooleanWrapper(this, "leaf", true); 586 } 587 return leaf.getReadOnlyProperty(); 588 } 589 590 591 // --- Parent 592 private ReadOnlyObjectWrapper<TreeItem<T>> parent = new ReadOnlyObjectWrapper<TreeItem<T>>(this, "parent"); 593 private void setParent(TreeItem<T> value) { parent.setValue(value); } 594 595 /** 596 * The parent of this TreeItem. Each TreeItem can have no more than one 597 * parent. If a TreeItem has no parent, it represents a root in the tree model. 598 * 599 * @return The parent of this TreeItem, or null if the TreeItem has no parent. 600 */ 601 public final TreeItem<T> getParent() { return parent == null ? null : parent.getValue(); } 602 603 /** 604 * A property that represents the parent of this TreeItem. 605 */ 606 public final ReadOnlyObjectProperty<TreeItem<T>> parentProperty() { return parent.getReadOnlyProperty(); } 607 608 609 610 /*********************************************************************** 611 * * 612 * TreeItem API * 613 * * 614 **********************************************************************/ 615 616 /** 617 * The children of this TreeItem. This method is called frequently, and 618 * it is therefore recommended that the returned list be cached by 619 * any TreeItem implementations. 620 * 621 * @return a list that contains the child TreeItems belonging to the TreeItem. 622 */ 623 public ObservableList<TreeItem<T>> getChildren() { 624 if (children == null) { 625 children = FXCollections.observableArrayList(); 626 children.addListener(childrenListener); 627 } 628 629 // we need to check if this TreeItem needs to have its children sorted. 630 // There are two different ways that this could be possible. 631 if (children.isEmpty()) return children; 632 633 checkSortState(); 634 635 return children; 636 } 637 638 639 640 /*************************************************************************** 641 * * 642 * Public API * 643 * * 644 **************************************************************************/ 645 646 /** 647 * Returns the previous sibling of the TreeItem. Ordering is based on the 648 * position of the TreeItem relative to its siblings in the children 649 * list belonging to the parent of the TreeItem. 650 * 651 * @return A TreeItem that is the previous sibling of the current TreeItem, 652 * or null if no such sibling can be found. 653 */ 654 public TreeItem<T> previousSibling() { 655 return previousSibling(this); 656 } 657 658 /** 659 * Returns the previous sibling after the given node. Ordering is based on the 660 * position of the given TreeItem relative to its siblings in the children 661 * list belonging to the parent of the TreeItem. 662 * 663 * @param beforeNode The TreeItem for which the previous sibling is being 664 * sought. 665 * @return A TreeItem that is the previous sibling of the given TreeItem, 666 * or null if no such sibling can be found. 667 */ 668 public TreeItem<T> previousSibling(final TreeItem<T> beforeNode) { 669 if (getParent() == null || beforeNode == null) { 670 return null; 671 } 672 673 List<TreeItem<T>> parentChildren = getParent().getChildren(); 674 final int childCount = parentChildren.size(); 675 int pos = -1; 676 for (int i = 0; i < childCount; i++) { 677 if (beforeNode.equals(parentChildren.get(i))) { 678 pos = i - 1; 679 return pos < 0 ? null : parentChildren.get(pos); 680 } 681 } 682 return null; 683 } 684 685 /** 686 * Returns the next sibling of the TreeItem. Ordering is based on the 687 * position of the TreeItem relative to its siblings in the children 688 * list belonging to the parent of the TreeItem. 689 * 690 * @return A TreeItem that is the next sibling of the current TreeItem, 691 * or null if no such sibling can be found. 692 */ 693 public TreeItem<T> nextSibling() { 694 return nextSibling(this); 695 } 696 697 /** 698 * Returns the next sibling after the given node. Ordering is based on the 699 * position of the given TreeItem relative to its siblings in the children 700 * list belonging to the parent of the TreeItem. 701 * 702 * @param afterNode The TreeItem for which the next sibling is being 703 * sought. 704 * @return A TreeItem that is the next sibling of the given TreeItem, 705 * or null if no such sibling can be found. 706 */ 707 public TreeItem<T> nextSibling(final TreeItem<T> afterNode) { 708 if (getParent() == null || afterNode == null) { 709 return null; 710 } 711 712 List<TreeItem<T>> parentChildren = getParent().getChildren(); 713 final int childCount = parentChildren.size(); 714 int pos = -1; 715 for (int i = 0; i < childCount; i++) { 716 if (afterNode.equals(parentChildren.get(i))) { 717 pos = i + 1; 718 return pos >= childCount ? null : parentChildren.get(pos); 719 } 720 } 721 return null; 722 } 723 724 /** 725 * Returns a string representation of this {@code TreeItem} object. 726 * @return a string representation of this {@code TreeItem} object. 727 */ 728 @Override public String toString() { 729 return "TreeItem [ value: " + getValue() + " ]"; 730 } 731 732 private void fireEvent(TreeModificationEvent<T> evt) { 733 Event.fireEvent(this, evt); 734 } 735 736 737 738 739 /*************************************************************************** 740 * * 741 * Event Target Implementation / API * 742 * * 743 **************************************************************************/ 744 745 /** {@inheritDoc} */ 746 @Override public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { 747 // To allow for a TreeView (and its skin) to be notified of changes in the 748 // tree, this method recursively calls up to the root node, at which point 749 // it fires a ROOT_NOTIFICATION_EVENT, which the TreeView may be watching for. 750 if (getParent() != null) { 751 getParent().buildEventDispatchChain(tail); 752 } 753 return tail.append(eventHandlerManager); 754 } 755 756 /** 757 * Registers an event handler to this TreeItem. The TreeItem class allows 758 * registration of listeners which will be notified as the 759 * number of items changes, their position or if the values themselves change. 760 * Note however that a TreeItem is <b>not</b> a Node, and therefore no visual 761 * events will be fired on the TreeItem. To get these events, it is necessary to 762 * add relevant observers to the TreeCell instances (via a custom cell factory - 763 * see the {@link Cell} class documentation for more details). 764 * 765 * @param eventType the type of the events to receive by the handler 766 * @param eventHandler the handler to register 767 * @throws NullPointerException if the event type or handler is null 768 */ 769 public <E extends Event> void addEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { 770 eventHandlerManager.addEventHandler(eventType, eventHandler); 771 } 772 773 /** 774 * Unregisters a previously registered event handler from this TreeItem. One 775 * handler might have been registered for different event types, so the 776 * caller needs to specify the particular event type from which to 777 * unregister the handler. 778 * 779 * @param eventType the event type from which to unregister 780 * @param eventHandler the handler to unregister 781 * @throws NullPointerException if the event type or handler is null 782 */ 783 public <E extends Event> void removeEventHandler(EventType<E> eventType, EventHandler<E> eventHandler) { 784 eventHandlerManager.removeEventHandler(eventType, eventHandler); 785 } 786 787 788 789 /*************************************************************************** 790 * * 791 * private methods * 792 * * 793 **************************************************************************/ 794 795 void sort() { 796 sort(children, lastComparator, lastSortMode); 797 } 798 799 private void sort(final ObservableList<TreeItem<T>> children, 800 final Comparator<TreeItem<T>> comparator, 801 final TreeSortMode sortMode) { 802 803 if (comparator == null) return; 804 805 runSort(children, comparator, sortMode); 806 807 // if we're at the root node, we'll fire an event so that the control 808 // can update its display 809 if (getParent() == null) { 810 fireEvent(new TreeItem.TreeModificationEvent<T>(TreeItem.childrenModificationEvent(), this)); 811 } 812 } 813 814 private void checkSortState() { 815 TreeItem<T> rootNode = getRoot(); 816 817 TreeSortMode sortMode = rootNode.lastSortMode; 818 Comparator<TreeItem<T>> comparator = rootNode.lastComparator; 819 820 if (comparator != null && comparator != lastComparator) { 821 lastComparator = comparator; 822 runSort(children, comparator, sortMode); 823 } 824 } 825 826 private void runSort(ObservableList<TreeItem<T>> children, Comparator<TreeItem<T>> comparator, TreeSortMode sortMode) { 827 if (sortMode == ALL_DESCENDANTS) { 828 doSort(children, comparator); 829 } else if (sortMode == ONLY_FIRST_LEVEL) { 830 // if we are here we presume that the current node is the root node 831 // (but we can test to see if getParent() returns null to be sure). 832 // We also know that ONLY_FIRST_LEVEL only applies to the children 833 // of the root, so we return straight after we sort these children. 834 if (getParent() == null) { 835 doSort(children, comparator); 836 } 837// } else if (sortMode == ONLY_LEAVES) { 838// if (isLeaf()) { 839// // sort the parent once 840// } 841// } else if (sortMode == ALL_BUT_LEAVES) { 842// 843 } else { 844 // Unknown sort mode 845 } 846 } 847 848 private TreeItem<T> getRoot() { 849 TreeItem<T> parent = getParent(); 850 if (parent == null) return this; 851 852 while (true) { 853 TreeItem<T> newParent = parent.getParent(); 854 if (newParent == null) return parent; 855 parent = newParent; 856 } 857 } 858 859 private void doSort(ObservableList<TreeItem<T>> children, final Comparator<TreeItem<T>> comparator) { 860 if (!isLeaf() && isExpanded()) { 861 FXCollections.sort(children, comparator); 862 } 863 } 864 865 // This value is package accessible so that it may be retrieved from TreeView. 866 int getExpandedDescendentCount(boolean reset) { 867 if (reset || expandedDescendentCountDirty) { 868 updateExpandedDescendentCount(reset); 869 expandedDescendentCountDirty = false; 870 } 871 return expandedDescendentCount; 872 } 873 874 private void updateExpandedDescendentCount(boolean reset) { 875 previousExpandedDescendentCount = expandedDescendentCount; 876 expandedDescendentCount = 1; 877 878 if (!isLeaf() && isExpanded()) { 879 for (TreeItem<T> child : getChildren()) { 880 if (child == null) continue; 881 expandedDescendentCount += child.isExpanded() ? child.getExpandedDescendentCount(reset) : 1; 882 } 883 } 884 } 885 886 private void updateChildren(List<? extends TreeItem<T>> added, List<? extends TreeItem<T>> removed) { 887 setLeaf(children.isEmpty()); 888 889 // update the relationships such that all added children point to 890 // this node as the parent (and all removed children point to null) 891 updateChildrenParent(removed, null); 892 updateChildrenParent(added, this); 893 894 // fire an event up the parent hierarchy such that any listening 895 // TreeViews (which only listen to their root node) can redraw 896 fireEvent(new TreeModificationEvent<T>( 897 CHILDREN_MODIFICATION_EVENT, this, added, removed)); 898 } 899 900 // Convenience method to set the parent of all children in the given list to 901 // the given parent TreeItem 902 private static <T> void updateChildrenParent(List<? extends TreeItem<T>> treeItems, final TreeItem<T> newParent) { 903 if (treeItems == null) return; 904 for (final TreeItem<T> treeItem : treeItems) { 905 if (treeItem == null) continue; 906 907 TreeItem<T> currentParent = treeItem.getParent(); 908 909 // We only replace the parent if the parentLinkCount of the given 910 // TreeItem is zero (which indicates that this TreeItem has not been 911 // 'linked' to its parent multiple times). This can happen in 912 // situations such as what is shown in RT-28668 (and tested for in 913 // TreeViewTest.test_rt28556()). Specifically, when a sort is applied 914 // to the children of a TreeItem, it is possible for them to be 915 // sorted in such a way that the element is considered to be 916 // added in multiple places in the child list and then removed from 917 // one of those places subsequently. In doing this final removal, 918 // the parent of that TreeItem is set to null when it should in fact 919 // remain with the parent that it belongs to. 920 if (treeItem.parentLinkCount == 0) { 921 treeItem.setParent(newParent); 922 } 923 924 boolean parentMatch = currentParent != null && currentParent.equals(newParent); 925 if (parentMatch) { 926 if (newParent == null) { 927 treeItem.parentLinkCount--; 928 } else { 929 treeItem.parentLinkCount++; 930 } 931 } 932 } 933 } 934 935 /** 936 * An {@link Event} that contains relevant information for all forms of 937 * TreeItem modifications. 938 */ 939 public static class TreeModificationEvent<T> extends Event { 940 private static final long serialVersionUID = 4741889985221719579L; 941 942 /** 943 * Common supertype for all tree modification event types. 944 */ 945 public static final EventType<?> ANY = TREE_NOTIFICATION_EVENT; 946 947 private transient final TreeItem<T> treeItem; 948 private final T newValue; 949 950 private final List<? extends TreeItem<T>> added; 951 private final List<? extends TreeItem<T>> removed; 952 953 private final boolean wasExpanded; 954 private final boolean wasCollapsed; 955 956 /** 957 * Constructs a basic TreeModificationEvent - this is useful in situations 958 * where the tree item has not received a new value, has not changed 959 * between expanded/collapsed states, and whose children has not changed. 960 * An example of when this constructor is used is when the TreeItem 961 * graphic property changes. 962 * 963 * @param eventType The type of the event that has occurred. 964 * @param treeItem The TreeItem on which this event occurred. 965 */ 966 public TreeModificationEvent(EventType<? extends Event> eventType, TreeItem<T> treeItem) { 967 this (eventType, treeItem, null); 968 } 969 970 /** 971 * Constructs a TreeModificationEvent for when the TreeItem has had its 972 * {@link TreeItem#valueProperty()} changed. 973 * 974 * @param eventType The type of the event that has occurred. 975 * @param treeItem The TreeItem on which this event occurred. 976 * @param newValue The new value that has been put into the 977 * {@link TreeItem#valueProperty()}. 978 */ 979 public TreeModificationEvent(EventType<? extends Event> eventType, 980 TreeItem<T> treeItem, T newValue) { 981 super(eventType); 982 this.treeItem = treeItem; 983 this.newValue = newValue; 984 this.added = null; 985 this.removed = null; 986 this.wasExpanded = false; 987 this.wasCollapsed = false; 988 } 989 990 /** 991 * Constructs a TreeModificationEvent for when the TreeItem has had its 992 * {@link TreeItem#expandedProperty()} changed. 993 * 994 * @param eventType The type of the event that has occurred. 995 * @param treeItem The TreeItem on which this event occurred. 996 * @param expanded A boolean to represent the current expanded 997 * state of the TreeItem. 998 */ 999 public TreeModificationEvent(EventType<? extends Event> eventType, 1000 TreeItem<T> treeItem, boolean expanded) { 1001 super(eventType); 1002 this.treeItem = treeItem; 1003 this.newValue = null; 1004 this.added = null; 1005 this.removed = null; 1006 this.wasExpanded = expanded; 1007 this.wasCollapsed = ! expanded; 1008 } 1009 1010 /** 1011 * Constructs a TreeModificationEvent for when the TreeItem has had its 1012 * children list changed. 1013 * 1014 * @param eventType The type of the event that has occurred. 1015 * @param treeItem The TreeItem on which this event occurred. 1016 * @param added A list of the items added to the children list of the 1017 * given TreeItem. 1018 * @param removed A list of the items removed from the children list of 1019 * the given TreeItem. 1020 */ 1021 public TreeModificationEvent(EventType<? extends Event> eventType, 1022 TreeItem<T> treeItem, List<? extends TreeItem<T>> added, 1023 List<? extends TreeItem<T>> removed) { 1024 super(eventType); 1025 this.treeItem = treeItem; 1026 this.newValue = null; 1027 this.added = added; 1028 this.removed = removed; 1029 this.wasExpanded = false; 1030 this.wasCollapsed = false; 1031 } 1032 1033 /** 1034 * Returns the TreeItem upon which this event occurred. 1035 */ 1036 @Override public TreeItem<T> getSource() { 1037 return this.treeItem; 1038 } 1039 1040 /** 1041 * Returns the TreeItem that this event occurred upon. 1042 * @return The TreeItem that this event occurred upon. 1043 */ 1044 public TreeItem<T> getTreeItem() { 1045 return treeItem; 1046 } 1047 1048 /** 1049 * If the value of the TreeItem changed, this method will return the new 1050 * value. If it did not change, this method will return null. 1051 * @return The new value of the TreeItem if it changed, null otherwise. 1052 */ 1053 public T getNewValue() { 1054 return newValue; 1055 } 1056 1057 /** 1058 * Returns the children added to the TreeItem in this event, or an empty 1059 * list if no children were added. 1060 * @return The newly added children, or an empty list if no children 1061 * were added. 1062 */ 1063 public List<? extends TreeItem<T>> getAddedChildren() { 1064 return added == null ? Collections.<TreeItem<T>>emptyList() : added; 1065 } 1066 1067 /** 1068 * Returns the children removed from the TreeItem in this event, or an 1069 * empty list if no children were added. 1070 * @return The removed children, or an empty list if no children 1071 * were removed. 1072 */ 1073 public List<? extends TreeItem<T>> getRemovedChildren() { 1074 return removed == null ? Collections.<TreeItem<T>>emptyList() : removed; 1075 } 1076 1077 /** 1078 * Returns the number of children items that were removed in this event, 1079 * or zero if no children were removed. 1080 * @return The number of removed children items, or zero if no children 1081 * were removed. 1082 */ 1083 public int getRemovedSize() { 1084 return getRemovedChildren().size(); 1085 } 1086 1087 /** 1088 * Returns the number of children items that were added in this event, 1089 * or zero if no children were added. 1090 * @return The number of added children items, or zero if no children 1091 * were added. 1092 */ 1093 public int getAddedSize() { 1094 return getAddedChildren().size(); 1095 } 1096 1097 /** 1098 * Returns true if this event represents a TreeItem expansion event, 1099 * and false if the TreeItem was not expanded. 1100 */ 1101 public boolean wasExpanded() { return wasExpanded; } 1102 1103 /** 1104 * Returns true if this event represents a TreeItem collapse event, 1105 * and false if the TreeItem was not collapsed. 1106 */ 1107 public boolean wasCollapsed() { return wasCollapsed; } 1108 1109 /** 1110 * Returns true if this event represents a TreeItem event where children 1111 * TreeItems were added. 1112 */ 1113 public boolean wasAdded() { return getAddedSize() > 0; } 1114 1115 /** 1116 * Returns true if this event represents a TreeItem event where children 1117 * TreeItems were removed. 1118 */ 1119 public boolean wasRemoved() { return getRemovedSize() > 0; } 1120 } 1121}