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&lt;String&gt; root = new TreeItem&lt;String&gt;("Root Node");
070 * root.setExpanded(true);
071 * root.getChildren().addAll(
072 *     new TreeItem&lt;String&gt;("Item 1"),
073 *     new TreeItem&lt;String&gt;("Item 2"),
074 *     new TreeItem&lt;String&gt;("Item 3")
075 * );
076 * TreeView&lt;String&gt; treeView = new TreeView&lt;String&gt;(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&lt;File&gt; root = createNode(new File("/"));
088 *      return new TreeView&lt;File&gt;(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&lt;File&gt; createNode(final File f) {
097 *      return new TreeItem&lt;File&gt;(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 *          &#064;Override public ObservableList&lt;TreeItem&lt;File&gt;&gt; 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 *          &#064;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&lt;TreeItem&lt;File&gt;&gt; buildChildren(TreeItem&lt;File&gt; TreeItem) {
135 *              File f = TreeItem.getValue();
136 *              if (f != null && f.isDirectory()) {
137 *                  File[] files = f.listFiles();
138 *                  if (files != null) {
139 *                      ObservableList&lt;TreeItem&lt;File&gt;&gt; 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}