1 /*
   2  * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import com.sun.javafx.collections.NonIterableChange;
  29 import com.sun.javafx.css.converters.SizeConverter;
  30 import com.sun.javafx.scene.control.behavior.TreeCellBehavior;
  31 import com.sun.javafx.scene.control.skin.ListViewSkin;
  32 import com.sun.javafx.scene.control.skin.TreeViewSkin;
  33 
  34 import javafx.application.Platform;
  35 import javafx.beans.DefaultProperty;
  36 import javafx.beans.property.BooleanProperty;
  37 import javafx.beans.property.DoubleProperty;
  38 import javafx.beans.property.ObjectProperty;
  39 import javafx.beans.property.ObjectPropertyBase;
  40 import javafx.beans.property.ReadOnlyIntegerProperty;
  41 import javafx.beans.property.ReadOnlyIntegerWrapper;
  42 import javafx.beans.property.ReadOnlyObjectProperty;
  43 import javafx.beans.property.ReadOnlyObjectWrapper;
  44 import javafx.beans.property.SimpleBooleanProperty;
  45 import javafx.beans.property.SimpleObjectProperty;
  46 import javafx.beans.value.ChangeListener;
  47 import javafx.beans.value.WeakChangeListener;
  48 import javafx.beans.value.WritableValue;
  49 import javafx.collections.ListChangeListener;
  50 import javafx.css.CssMetaData;
  51 import javafx.css.Styleable;
  52 import javafx.css.StyleableDoubleProperty;
  53 import javafx.css.StyleableProperty;
  54 import javafx.event.Event;
  55 import javafx.event.EventHandler;
  56 import javafx.event.EventType;
  57 import javafx.event.WeakEventHandler;
  58 import javafx.scene.AccessibleAttribute;
  59 import javafx.scene.AccessibleRole;
  60 import javafx.scene.control.TreeItem.TreeModificationEvent;
  61 import javafx.scene.layout.Region;
  62 import javafx.util.Callback;
  63 
  64 import java.lang.ref.SoftReference;
  65 import java.lang.ref.WeakReference;
  66 import java.util.ArrayList;
  67 import java.util.Collections;
  68 import java.util.HashMap;
  69 import java.util.List;
  70 import java.util.Map;
  71 
  72 /**
  73  * The TreeView control provides a view on to a tree root (of type 
  74  * {@link TreeItem}). By using a TreeView, it is possible to drill down into the
  75  * children of a TreeItem, recursively until a TreeItem has no children (that is,
  76  * it is a <i>leaf</i> node in the tree). To facilitate this, unlike controls
  77  * like {@link ListView}, in TreeView it is necessary to <strong>only</strong> 
  78  * specify the {@link #rootProperty() root} node. 
  79  *
  80  * <p>
  81  * For more information on building up a tree using this approach, refer to the 
  82  * {@link TreeItem} class documentation. Briefly however, to create a TreeView, 
  83  * you should do something along the lines of the following:
  84  * <pre><code>
  85  * TreeItem&lt;String&gt; root = new TreeItem&lt;String&gt;("Root Node");
  86  * root.setExpanded(true);
  87  * root.getChildren().addAll(
  88  *     new TreeItem&lt;String&gt;("Item 1"),
  89  *     new TreeItem&lt;String&gt;("Item 2"),
  90  *     new TreeItem&lt;String&gt;("Item 3")
  91  * );
  92  * TreeView&lt;String&gt; treeView = new TreeView&lt;String&gt;(root);
  93  * </code></pre>
  94  * 
  95  * <p>
  96  * A TreeView may be configured to optionally hide the root node by setting the
  97  * {@link #setShowRoot(boolean) showRoot} property to {@code false}. If the root
  98  * node is hidden, there is one less level of indentation, and all children
  99  * nodes of the root node are shown. By default, the root node is shown in the
 100  * TreeView.
 101  * 
 102  * <h3>TreeView Selection / Focus APIs</h3>
 103  * <p>To track selection and focus, it is necessary to become familiar with the
 104  * {@link SelectionModel} and {@link FocusModel} classes. A TreeView has at most
 105  * one instance of each of these classes, available from 
 106  * {@link #selectionModelProperty() selectionModel} and 
 107  * {@link #focusModelProperty() focusModel} properties respectively.
 108  * Whilst it is possible to use this API to set a new selection model, in
 109  * most circumstances this is not necessary - the default selection and focus
 110  * models should work in most circumstances.
 111  * 
 112  * <p>The default {@link SelectionModel} used when instantiating a TreeView is
 113  * an implementation of the {@link MultipleSelectionModel} abstract class. 
 114  * However, as noted in the API documentation for
 115  * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode}
 116  * property, the default value is {@link SelectionMode#SINGLE}. To enable 
 117  * multiple selection in a default TreeView instance, it is therefore necessary
 118  * to do the following:
 119  * 
 120  * <pre>
 121  * {@code 
 122  * treeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre>
 123  * 
 124  * <h3>Customizing TreeView Visuals</h3>
 125  * <p>The visuals of the TreeView can be entirely customized by replacing the 
 126  * default {@link #cellFactoryProperty() cell factory}. A cell factory is used to
 127  * generate {@link TreeCell} instances, which are used to represent an item in the
 128  * TreeView. See the {@link Cell} class documentation for a more complete
 129  * description of how to write custom Cells.
 130  *
 131  * <h3>Editing</h3>
 132  * <p>This control supports inline editing of values, and this section attempts to
 133  * give an overview of the available APIs and how you should use them.</p>
 134  *
 135  * <p>Firstly, cell editing most commonly requires a different user interface
 136  * than when a cell is not being edited. This is the responsibility of the
 137  * {@link Cell} implementation being used. For TreeView, this is the responsibility
 138  * of the {@link #cellFactoryProperty() cell factory}. It is your choice whether the cell is
 139  * permanently in an editing state (e.g. this is common for {@link CheckBox} cells),
 140  * or to switch to a different UI when editing begins (e.g. when a double-click
 141  * is received on a cell).</p>
 142  *
 143  * <p>To know when editing has been requested on a cell,
 144  * simply override the {@link javafx.scene.control.Cell#startEdit()} method, and
 145  * update the cell {@link javafx.scene.control.Cell#textProperty() text} and
 146  * {@link javafx.scene.control.Cell#graphicProperty() graphic} properties as
 147  * appropriate (e.g. set the text to null and set the graphic to be a
 148  * {@link TextField}). Additionally, you should also override
 149  * {@link Cell#cancelEdit()} to reset the UI back to its original visual state
 150  * when the editing concludes. In both cases it is important that you also
 151  * ensure that you call the super method to have the cell perform all duties it
 152  * must do to enter or exit its editing mode.</p>
 153  *
 154  * <p>Once your cell is in an editing state, the next thing you are most probably
 155  * interested in is how to commit or cancel the editing that is taking place. This is your
 156  * responsibility as the cell factory provider. Your cell implementation will know
 157  * when the editing is over, based on the user input (e.g. when the user presses
 158  * the Enter or ESC keys on their keyboard). When this happens, it is your
 159  * responsibility to call {@link Cell#commitEdit(Object)} or
 160  * {@link Cell#cancelEdit()}, as appropriate.</p>
 161  *
 162  * <p>When you call {@link Cell#commitEdit(Object)} an event is fired to the
 163  * TreeView, which you can observe by adding an {@link EventHandler} via
 164  * {@link TreeView#setOnEditCommit(javafx.event.EventHandler)}. Similarly,
 165  * you can also observe edit events for
 166  * {@link TreeView#setOnEditStart(javafx.event.EventHandler) edit start}
 167  * and {@link TreeView#setOnEditCancel(javafx.event.EventHandler) edit cancel}.</p>
 168  *
 169  * <p>By default the TreeView edit commit handler is non-null, with a default
 170  * handler that attempts to overwrite the property value for the
 171  * item in the currently-being-edited row. It is able to do this as the
 172  * {@link Cell#commitEdit(Object)} method is passed in the new value, and this
 173  * is passed along to the edit commit handler via the
 174  * {@link EditEvent} that is fired. It is simply a matter of calling
 175  * {@link EditEvent#getNewValue()} to retrieve this value.
 176  *
 177  * <p>It is very important to note that if you call
 178  * {@link TreeView#setOnEditCommit(javafx.event.EventHandler)} with your own
 179  * {@link EventHandler}, then you will be removing the default handler. Unless
 180  * you then handle the writeback to the property (or the relevant data source),
 181  * nothing will happen. You can work around this by using the
 182  * {@link TreeView#addEventHandler(javafx.event.EventType, javafx.event.EventHandler)}
 183  * method to add a {@link TreeView#EDIT_COMMIT_EVENT} {@link EventType} with
 184  * your desired {@link EventHandler} as the second argument. Using this method,
 185  * you will not replace the default implementation, but you will be notified when
 186  * an edit commit has occurred.</p>
 187  *
 188  * <p>Hopefully this summary answers some of the commonly asked questions.
 189  * Fortunately, JavaFX ships with a number of pre-built cell factories that
 190  * handle all the editing requirements on your behalf. You can find these
 191  * pre-built cell factories in the javafx.scene.control.cell package.</p>
 192  *
 193  * @see TreeItem
 194  * @see TreeCell
 195  * @param <T> The type of the item contained within the {@link TreeItem} value
 196  *      property for all tree items in this TreeView.
 197  * @since JavaFX 2.0
 198  */
 199 @DefaultProperty("root")
 200 public class TreeView<T> extends Control {
 201     
 202     /***************************************************************************
 203      *                                                                         *
 204      * Static properties and methods                                           *
 205      *                                                                         *
 206      **************************************************************************/
 207     
 208     /** 
 209      * An EventType that indicates some edit event has occurred. It is the parent
 210      * type of all other edit events: {@link #editStartEvent},
 211      *  {@link #editCommitEvent} and {@link #editCancelEvent}.
 212      * 
 213      * @return An EventType that indicates some edit event has occurred.
 214      */
 215     @SuppressWarnings("unchecked")
 216     public static <T> EventType<EditEvent<T>> editAnyEvent() {
 217         return (EventType<EditEvent<T>>) EDIT_ANY_EVENT;
 218     }
 219     private static final EventType<?> EDIT_ANY_EVENT =
 220             new EventType<>(Event.ANY, "TREE_VIEW_EDIT");
 221 
 222     /**
 223      * An EventType used to indicate that an edit event has started within the
 224      * TreeView upon which the event was fired.
 225      * 
 226      * @return An EventType used to indicate that an edit event has started.
 227      */
 228     @SuppressWarnings("unchecked")
 229     public static <T> EventType<EditEvent<T>> editStartEvent() {
 230         return (EventType<EditEvent<T>>) EDIT_START_EVENT;
 231     }
 232     private static final EventType<?> EDIT_START_EVENT =
 233             new EventType<>(editAnyEvent(), "EDIT_START");
 234 
 235     /**
 236      * An EventType used to indicate that an edit event has just been canceled
 237      * within the TreeView upon which the event was fired.
 238      * 
 239      * @return An EventType used to indicate that an edit event has just been
 240      *      canceled.
 241      */
 242     @SuppressWarnings("unchecked")
 243     public static <T> EventType<EditEvent<T>> editCancelEvent() {
 244         return (EventType<EditEvent<T>>) EDIT_CANCEL_EVENT;
 245     }
 246     private static final EventType<?> EDIT_CANCEL_EVENT =
 247             new EventType<>(editAnyEvent(), "EDIT_CANCEL");
 248 
 249     /**
 250      * An EventType that is used to indicate that an edit in a TreeView has been
 251      * committed. This means that user has made changes to the data of a
 252      * TreeItem, and that the UI should be updated.
 253      * 
 254      * @return An EventType that is used to indicate that an edit in a TreeView
 255      *      has been committed.
 256      */
 257     @SuppressWarnings("unchecked")
 258     public static <T> EventType<EditEvent<T>> editCommitEvent() {
 259         return (EventType<EditEvent<T>>) EDIT_COMMIT_EVENT;
 260     }
 261     private static final EventType<?> EDIT_COMMIT_EVENT =
 262             new EventType<>(editAnyEvent(), "EDIT_COMMIT");
 263     
 264     /**
 265      * Returns the number of levels of 'indentation' of the given TreeItem, 
 266      * based on how many times {@link javafx.scene.control.TreeItem#getParent()}
 267      * can be recursively called. If the TreeItem does not have any parent set,
 268      * the returned value will be zero. For each time getParent() is recursively
 269      * called, the returned value is incremented by one.
 270      *
 271      * <p><strong>Important note: </strong>This method is deprecated as it does
 272      * not consider the root node. This means that this method will iterate
 273      * past the root node of the TreeView control, if the root node has a parent.
 274      * If this is important, call {@link TreeView#getTreeItemLevel(TreeItem)}
 275      * instead.
 276      * 
 277      * @param node The TreeItem for which the level is needed.
 278      * @return An integer representing the number of parents above the given node,
 279      *          or -1 if the given TreeItem is null.
 280      * @deprecated This method does not correctly calculate the distance from the
 281      *          given TreeItem to the root of the TreeView. As of JavaFX 8.0_20,
 282      *          the proper way to do this is via
 283      *          {@link TreeView#getTreeItemLevel(TreeItem)}
 284      */
 285     @Deprecated
 286     public static int getNodeLevel(TreeItem<?> node) {
 287         if (node == null) return -1;
 288 
 289         int level = 0;
 290         TreeItem<?> parent = node.getParent();
 291         while (parent != null) {
 292             level++;
 293             parent = parent.getParent();
 294         }
 295 
 296         return level;
 297     }
 298 
 299 
 300     /***************************************************************************
 301      *                                                                         *
 302      * Constructors                                                            *
 303      *                                                                         *
 304      **************************************************************************/
 305 
 306     /**
 307      * Creates an empty TreeView.
 308      * 
 309      * <p>Refer to the {@link TreeView} class documentation for details on the
 310      * default state of other properties.
 311      */
 312     public TreeView() {
 313         this(null);
 314     }
 315 
 316     /**
 317      * Creates a TreeView with the provided root node.
 318      * 
 319      * <p>Refer to the {@link TreeView} class documentation for details on the
 320      * default state of other properties.
 321      * 
 322      * @param root The node to be the root in this TreeView.
 323      */
 324     public TreeView(TreeItem<T> root) {
 325         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
 326         setAccessibleRole(AccessibleRole.TREE_VIEW);
 327 
 328         setRoot(root);
 329         updateExpandedItemCount(root);
 330 
 331         // install default selection and focus models - it's unlikely this will be changed
 332         // by many users.
 333         MultipleSelectionModel<TreeItem<T>> sm = new TreeViewBitSetSelectionModel<T>(this);
 334         setSelectionModel(sm);
 335         setFocusModel(new TreeViewFocusModel<T>(this));
 336     }
 337     
 338     
 339     
 340     /***************************************************************************
 341      *                                                                         *
 342      * Instance Variables                                                      *
 343      *                                                                         *
 344      **************************************************************************/ 
 345     
 346     // used in the tree item modification event listener. Used by the 
 347     // layoutChildren method to determine whether the tree item count should
 348     // be recalculated.
 349     private boolean expandedItemCountDirty = true;
 350 
 351     // Used in the getTreeItem(int row) method to act as a cache.
 352     // See RT-26716 for the justification and performance gains.
 353     private Map<Integer, SoftReference<TreeItem<T>>> treeItemCacheMap = new HashMap<>();
 354     
 355     
 356     /***************************************************************************
 357      *                                                                         *
 358      * Callbacks and Events                                                    *
 359      *                                                                         *
 360      **************************************************************************/
 361     
 362     // we use this to forward events that have bubbled up TreeItem instances
 363     // to the TreeViewSkin, to force it to recalculate teh item count and redraw
 364     // if necessary
 365     private final EventHandler<TreeModificationEvent<T>> rootEvent = e -> {
 366         // this forces layoutChildren at the next pulse, and therefore
 367         // updates the item count if necessary
 368         EventType<?> eventType = e.getEventType();
 369         boolean match = false;
 370         while (eventType != null) {
 371             if (eventType.equals(TreeItem.<T>expandedItemCountChangeEvent())) {
 372                 match = true;
 373                 break;
 374             }
 375             eventType = eventType.getSuperType();
 376         }
 377 
 378         if (match) {
 379             expandedItemCountDirty = true;
 380             requestLayout();
 381         }
 382     };
 383     
 384     private WeakEventHandler<TreeModificationEvent<T>> weakRootEventListener;
 385 
 386 
 387     
 388     /***************************************************************************
 389      *                                                                         *
 390      * Properties                                                              *
 391      *                                                                         *
 392      **************************************************************************/
 393     
 394 
 395     // --- Cell Factory
 396     private ObjectProperty<Callback<TreeView<T>, TreeCell<T>>> cellFactory;
 397     
 398     /**
 399      * Sets the cell factory that will be used for creating TreeCells,
 400      * which are used to represent items in the
 401      * TreeView. The factory works identically to the cellFactory in ListView
 402      * and other complex composite controls. It is called to create a new
 403      * TreeCell only when the system has determined that it doesn't have enough
 404      * cells to represent the currently visible items. The TreeCell is reused
 405      * by the system to represent different items in the tree when possible.
 406      *
 407      * <p>Refer to the {@link Cell} class documentation for more details.
 408      * 
 409      * @param value The {@link Callback} to use for generating TreeCell instances,
 410      *      or null if the default cell factory should be used.
 411      */
 412     public final void setCellFactory(Callback<TreeView<T>, TreeCell<T>> value) { 
 413         cellFactoryProperty().set(value); 
 414     }
 415     
 416     /**
 417      * <p>Returns the cell factory that will be used for creating TreeCells,
 418      * which are used to represent items in the TreeView, or null if no custom
 419      * cell factory has been set.
 420      */
 421     public final Callback<TreeView<T>, TreeCell<T>> getCellFactory() { 
 422         return cellFactory == null ? null : cellFactory.get(); 
 423     }
 424     
 425     /**
 426      * Represents the cell factory that will be used for creating TreeCells,
 427      * which are used to represent items in the TreeView. 
 428      */
 429     public final ObjectProperty<Callback<TreeView<T>, TreeCell<T>>> cellFactoryProperty() {
 430         if (cellFactory == null) {
 431             cellFactory = new SimpleObjectProperty<Callback<TreeView<T>, TreeCell<T>>>(this, "cellFactory");
 432         }
 433         return cellFactory;
 434     }
 435 
 436     
 437     // --- Root
 438     private ObjectProperty<TreeItem<T>> root = new SimpleObjectProperty<TreeItem<T>>(this, "root") {
 439         private WeakReference<TreeItem<T>> weakOldItem;
 440 
 441         @Override protected void invalidated() {
 442             TreeItem<T> oldTreeItem = weakOldItem == null ? null : weakOldItem.get();
 443             if (oldTreeItem != null && weakRootEventListener != null) {
 444                 oldTreeItem.removeEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootEventListener);
 445             }
 446 
 447             TreeItem<T> root = getRoot();
 448             if (root != null) {
 449                 weakRootEventListener = new WeakEventHandler<>(rootEvent);
 450                 getRoot().addEventHandler(TreeItem.<T>treeNotificationEvent(), weakRootEventListener);
 451                 weakOldItem = new WeakReference<>(root);
 452             }
 453 
 454             // Fix for RT-37853
 455             edit(null);
 456 
 457             expandedItemCountDirty = true;
 458             updateRootExpanded();
 459         }
 460     };
 461     
 462     /**
 463      * Sets the root node in this TreeView. See the {@link TreeItem} class level
 464      * documentation for more details.
 465      * 
 466      * @param value The {@link TreeItem} that will be placed at the root of the
 467      *      TreeView.
 468      */
 469     public final void setRoot(TreeItem<T> value) {
 470         rootProperty().set(value);
 471     }
 472 
 473     /**
 474      * Returns the current root node of this TreeView, or null if no root node
 475      * is specified.
 476      * @return The current root node, or null if no root node exists.
 477      */
 478     public final TreeItem<T> getRoot() {
 479         return root == null ? null : root.get();
 480     }
 481 
 482     /**
 483      * Property representing the root node of the TreeView.
 484      */
 485     public final ObjectProperty<TreeItem<T>> rootProperty() {
 486         return root;
 487     }
 488 
 489     
 490     
 491     // --- Show Root
 492     private BooleanProperty showRoot;
 493     
 494     /**
 495      * Specifies whether the root {@code TreeItem} should be shown within this 
 496      * TreeView.
 497      * 
 498      * @param value If true, the root TreeItem will be shown, and if false it
 499      *      will be hidden.
 500      */
 501     public final void setShowRoot(boolean value) {
 502         showRootProperty().set(value);
 503     }
 504 
 505     /**
 506      * Returns true if the root of the TreeView should be shown, and false if
 507      * it should not. By default, the root TreeItem is visible in the TreeView.
 508      */
 509     public final boolean isShowRoot() {
 510         return showRoot == null ? true : showRoot.get();
 511     }
 512 
 513     /**
 514      * Property that represents whether or not the TreeView root node is visible.
 515      */
 516     public final BooleanProperty showRootProperty() {
 517         if (showRoot == null) {
 518             showRoot = new SimpleBooleanProperty(this, "showRoot", true) {
 519                 @Override protected void invalidated() {
 520                     updateRootExpanded();
 521                     updateExpandedItemCount(getRoot());
 522                 }
 523             };
 524         }
 525         return showRoot;
 526     }
 527     
 528     
 529     // --- Selection Model
 530     private ObjectProperty<MultipleSelectionModel<TreeItem<T>>> selectionModel;
 531 
 532     /**
 533      * Sets the {@link MultipleSelectionModel} to be used in the TreeView. 
 534      * Despite a TreeView requiring a <code><b>Multiple</b>SelectionModel</code>,
 535      * it is possible to configure it to only allow single selection (see 
 536      * {@link MultipleSelectionModel#setSelectionMode(javafx.scene.control.SelectionMode)}
 537      * for more information).
 538      */
 539     public final void setSelectionModel(MultipleSelectionModel<TreeItem<T>> value) {
 540         selectionModelProperty().set(value);
 541     }
 542 
 543     /**
 544      * Returns the currently installed selection model.
 545      */
 546     public final MultipleSelectionModel<TreeItem<T>> getSelectionModel() {
 547         return selectionModel == null ? null : selectionModel.get();
 548     }
 549 
 550     /**
 551      * The SelectionModel provides the API through which it is possible
 552      * to select single or multiple items within a TreeView, as  well as inspect
 553      * which rows have been selected by the user. Note that it has a generic
 554      * type that must match the type of the TreeView itself.
 555      */
 556     public final ObjectProperty<MultipleSelectionModel<TreeItem<T>>> selectionModelProperty() {
 557         if (selectionModel == null) {
 558             selectionModel = new SimpleObjectProperty<MultipleSelectionModel<TreeItem<T>>>(this, "selectionModel");
 559         }
 560         return selectionModel;
 561     }
 562     
 563     
 564     // --- Focus Model
 565     private ObjectProperty<FocusModel<TreeItem<T>>> focusModel;
 566 
 567     /**
 568      * Sets the {@link FocusModel} to be used in the TreeView. 
 569      */
 570     public final void setFocusModel(FocusModel<TreeItem<T>> value) {
 571         focusModelProperty().set(value);
 572     }
 573 
 574     /**
 575      * Returns the currently installed {@link FocusModel}.
 576      */
 577     public final FocusModel<TreeItem<T>> getFocusModel() {
 578         return focusModel == null ? null : focusModel.get();
 579     }
 580 
 581     /**
 582      * The FocusModel provides the API through which it is possible
 583      * to control focus on zero or one rows of the TreeView. Generally the
 584      * default implementation should be more than sufficient.
 585      */
 586     public final ObjectProperty<FocusModel<TreeItem<T>>> focusModelProperty() {
 587         if (focusModel == null) {
 588             focusModel = new SimpleObjectProperty<FocusModel<TreeItem<T>>>(this, "focusModel");
 589         }
 590         return focusModel;
 591     }
 592     
 593     
 594     // --- Expanded node count
 595     /**
 596      * <p>Represents the number of tree nodes presently able to be visible in the
 597      * TreeView. This is essentially the count of all expanded tree items, and
 598      * their children.
 599      *
 600      * <p>For example, if just the root node is visible, the expandedItemCount will
 601      * be one. If the root had three children and the root was expanded, the value
 602      * will be four.
 603      * @since JavaFX 8.0
 604      */
 605     private ReadOnlyIntegerWrapper expandedItemCount = new ReadOnlyIntegerWrapper(this, "expandedItemCount", 0);
 606     public final ReadOnlyIntegerProperty expandedItemCountProperty() {
 607         return expandedItemCount.getReadOnlyProperty();
 608     }
 609     private void setExpandedItemCount(int value) {
 610         expandedItemCount.set(value);
 611     }
 612     public final int getExpandedItemCount() {
 613         if (expandedItemCountDirty) {
 614             updateExpandedItemCount(getRoot());
 615         }
 616         return expandedItemCount.get();
 617     }
 618 
 619 
 620     // --- Fixed cell size
 621     private DoubleProperty fixedCellSize;
 622 
 623     /**
 624      * Sets the new fixed cell size for this control. Any value greater than
 625      * zero will enable fixed cell size mode, whereas a zero or negative value
 626      * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size
 627      * mode.
 628      *
 629      * @param value The new fixed cell size value, or a value less than or equal
 630      *              to zero (or Region.USE_COMPUTED_SIZE) to disable.
 631      * @since JavaFX 8.0
 632      */
 633     public final void setFixedCellSize(double value) {
 634         fixedCellSizeProperty().set(value);
 635     }
 636 
 637     /**
 638      * Returns the fixed cell size value. A value less than or equal to zero is
 639      * used to represent that fixed cell size mode is disabled, and a value
 640      * greater than zero represents the size of all cells in this control.
 641      *
 642      * @return A double representing the fixed cell size of this control, or a
 643      *      value less than or equal to zero if fixed cell size mode is disabled.
 644      * @since JavaFX 8.0
 645      */
 646     public final double getFixedCellSize() {
 647         return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get();
 648     }
 649     /**
 650      * Specifies whether this control has cells that are a fixed height (of the
 651      * specified value). If this value is less than or equal to zero,
 652      * then all cells are individually sized and positioned. This is a slow
 653      * operation. Therefore, when performance matters and developers are not
 654      * dependent on variable cell sizes it is a good idea to set the fixed cell
 655      * size value. Generally cells are around 24px, so setting a fixed cell size
 656      * of 24 is likely to result in very little difference in visuals, but a
 657      * improvement to performance.
 658      *
 659      * <p>To set this property via CSS, use the -fx-fixed-cell-size property.
 660      * This should not be confused with the -fx-cell-size property. The difference
 661      * between these two CSS properties is that -fx-cell-size will size all
 662      * cells to the specified size, but it will not enforce that this is the
 663      * only size (thus allowing for variable cell sizes, and preventing the
 664      * performance gains from being possible). Therefore, when performance matters
 665      * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are
 666      * specified in CSS, -fx-fixed-cell-size takes precedence.</p>
 667      *
 668      * @since JavaFX 8.0
 669      */
 670     public final DoubleProperty fixedCellSizeProperty() {
 671         if (fixedCellSize == null) {
 672             fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) {
 673                 @Override public CssMetaData<TreeView<?>,Number> getCssMetaData() {
 674                     return StyleableProperties.FIXED_CELL_SIZE;
 675                 }
 676 
 677                 @Override public Object getBean() {
 678                     return TreeView.this;
 679                 }
 680 
 681                 @Override public String getName() {
 682                     return "fixedCellSize";
 683                 }
 684             };
 685         }
 686         return fixedCellSize;
 687     }
 688 
 689     
 690     // --- Editable
 691     private BooleanProperty editable;
 692     public final void setEditable(boolean value) {
 693         editableProperty().set(value);
 694     }
 695     public final boolean isEditable() {
 696         return editable == null ? false : editable.get();
 697     }
 698     /**
 699      * Specifies whether this TreeView is editable - only if the TreeView and
 700      * the TreeCells within it are both editable will a TreeCell be able to go
 701      * into their editing state.
 702      */
 703     public final BooleanProperty editableProperty() {
 704         if (editable == null) {
 705             editable = new SimpleBooleanProperty(this, "editable", false);
 706         }
 707         return editable;
 708     }
 709     
 710     
 711     // --- Editing Item
 712     private ReadOnlyObjectWrapper<TreeItem<T>> editingItem;
 713 
 714     private void setEditingItem(TreeItem<T> value) {
 715         editingItemPropertyImpl().set(value);
 716     }
 717 
 718     /**
 719      * Returns the TreeItem that is currently being edited in the TreeView,
 720      * or null if no item is being edited.
 721      */
 722     public final TreeItem<T> getEditingItem() {
 723         return editingItem == null ? null : editingItem.get();
 724     }
 725 
 726     /**
 727      * <p>A property used to represent the TreeItem currently being edited
 728      * in the TreeView, if editing is taking place, or null if no item is being edited.
 729      * 
 730      * <p>It is not possible to set the editing item, instead it is required that
 731      * you call {@link #edit(javafx.scene.control.TreeItem)}.
 732      */
 733     public final ReadOnlyObjectProperty<TreeItem<T>> editingItemProperty() {
 734         return editingItemPropertyImpl().getReadOnlyProperty();
 735     }
 736 
 737     private ReadOnlyObjectWrapper<TreeItem<T>> editingItemPropertyImpl() {
 738         if (editingItem == null) {
 739             editingItem = new ReadOnlyObjectWrapper<TreeItem<T>>(this, "editingItem");
 740         }
 741         return editingItem;
 742     }
 743     
 744     
 745     // --- On Edit Start
 746     private ObjectProperty<EventHandler<EditEvent<T>>> onEditStart;
 747 
 748     /**
 749      * Sets the {@link EventHandler} that will be called when the user begins
 750      * an edit. 
 751      */
 752     public final void setOnEditStart(EventHandler<EditEvent<T>> value) {
 753         onEditStartProperty().set(value);
 754     }
 755 
 756     /**
 757      * Returns the {@link EventHandler} that will be called when the user begins
 758      * an edit.
 759      */
 760     public final EventHandler<EditEvent<T>> getOnEditStart() {
 761         return onEditStart == null ? null : onEditStart.get();
 762     }
 763 
 764     /**
 765      * This event handler will be fired when the user successfully initiates
 766      * editing.
 767      */
 768     public final ObjectProperty<EventHandler<EditEvent<T>>> onEditStartProperty() {
 769         if (onEditStart == null) {
 770             onEditStart = new SimpleObjectProperty<EventHandler<EditEvent<T>>>(this, "onEditStart") {
 771                 @Override protected void invalidated() {
 772                     setEventHandler(TreeView.<T>editStartEvent(), get());
 773                 }
 774             };
 775         }
 776         return onEditStart;
 777     }
 778 
 779 
 780     // --- On Edit Commit
 781     private ObjectProperty<EventHandler<EditEvent<T>>> onEditCommit;
 782 
 783     /**
 784      * Sets the {@link EventHandler} that will be called when the user commits
 785      * an edit. 
 786      */
 787     public final void setOnEditCommit(EventHandler<EditEvent<T>> value) {
 788         onEditCommitProperty().set(value);
 789     }
 790 
 791     /**
 792      * Returns the {@link EventHandler} that will be called when the user commits
 793      * an edit.
 794      */
 795     public final EventHandler<EditEvent<T>> getOnEditCommit() {
 796         return onEditCommit == null ? null : onEditCommit.get();
 797     }
 798 
 799     /**
 800      * <p>This property is used when the user performs an action that should
 801      * result in their editing input being persisted.</p>
 802      *
 803      * <p>The EventHandler in this property should not be called directly - 
 804      * instead call {@link TreeCell#commitEdit(java.lang.Object)} from within
 805      * your custom TreeCell. This will handle firing this event, updating the 
 806      * view, and switching out of the editing state.</p>
 807      */
 808     public final ObjectProperty<EventHandler<EditEvent<T>>> onEditCommitProperty() {
 809         if (onEditCommit == null) {
 810             onEditCommit = new SimpleObjectProperty<EventHandler<EditEvent<T>>>(this, "onEditCommit") {
 811                 @Override protected void invalidated() {
 812                     setEventHandler(TreeView.<T>editCommitEvent(), get());
 813                 }
 814             };
 815         }
 816         return onEditCommit;
 817     }
 818 
 819 
 820     // --- On Edit Cancel
 821     private ObjectProperty<EventHandler<EditEvent<T>>> onEditCancel;
 822 
 823     /**
 824      * Sets the {@link EventHandler} that will be called when the user cancels
 825      * an edit.
 826      */
 827     public final void setOnEditCancel(EventHandler<EditEvent<T>> value) {
 828         onEditCancelProperty().set(value);
 829     }
 830 
 831     /**
 832      * Returns the {@link EventHandler} that will be called when the user cancels
 833      * an edit.
 834      */
 835     public final EventHandler<EditEvent<T>> getOnEditCancel() {
 836         return onEditCancel == null ? null : onEditCancel.get();
 837     }
 838 
 839     /**
 840      * This event handler will be fired when the user cancels editing a cell.
 841      */
 842     public final ObjectProperty<EventHandler<EditEvent<T>>> onEditCancelProperty() {
 843         if (onEditCancel == null) {
 844             onEditCancel = new SimpleObjectProperty<EventHandler<EditEvent<T>>>(this, "onEditCancel") {
 845                 @Override protected void invalidated() {
 846                     setEventHandler(TreeView.<T>editCancelEvent(), get());
 847                 }
 848             };
 849         }
 850         return onEditCancel;
 851     }
 852 
 853 
 854     
 855     /***************************************************************************
 856      *                                                                         *
 857      * Public API                                                              *
 858      *                                                                         *
 859      **************************************************************************/
 860     
 861     
 862     /** {@inheritDoc} */
 863     @Override protected void layoutChildren() {
 864         if (expandedItemCountDirty) {
 865             updateExpandedItemCount(getRoot());
 866         }
 867         
 868         super.layoutChildren();
 869     }
 870     
 871     
 872     /**
 873      * Instructs the TreeView to begin editing the given TreeItem, if 
 874      * the TreeView is {@link #editableProperty() editable}. Once
 875      * this method is called, if the current 
 876      * {@link #cellFactoryProperty() cell factory} is set up to support editing,
 877      * the Cell will switch its visual state to enable the user input to take place.
 878      * 
 879      * @param item The TreeItem in the TreeView that should be edited.
 880      */
 881     public void edit(TreeItem<T> item) {
 882         if (!isEditable()) return;
 883         setEditingItem(item);
 884     }
 885     
 886 
 887     /**
 888      * Scrolls the TreeView such that the item in the given index is visible to
 889      * the end user.
 890      * 
 891      * @param index The index that should be made visible to the user, assuming
 892      *      of course that it is greater than, or equal to 0, and less than the
 893      *      number of the visible items in the TreeView.
 894      */
 895     public void scrollTo(int index) {
 896         ControlUtils.scrollToIndex(this, index);
 897     }
 898 
 899     /**
 900      * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
 901      * @since JavaFX 8.0
 902      */
 903     private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
 904     
 905     public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
 906         onScrollToProperty().set(value);
 907     }
 908     
 909     public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
 910         if( onScrollTo != null ) {
 911             return onScrollTo.get();
 912         }
 913         return null;
 914     }
 915     
 916     public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
 917         if( onScrollTo == null ) {
 918             onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
 919                 @Override
 920                 protected void invalidated() {
 921                     setEventHandler(ScrollToEvent.scrollToTopIndex(), get());
 922                 }
 923                 @Override
 924                 public Object getBean() {
 925                     return TreeView.this;
 926                 }
 927 
 928                 @Override
 929                 public String getName() {
 930                     return "onScrollTo";
 931                 }
 932             };
 933         }
 934         return onScrollTo;
 935     }
 936     
 937     /**
 938      * Returns the index position of the given TreeItem, assuming that it is
 939      * currently accessible through the tree hierarchy (most notably, that all
 940      * parent tree items are expanded). If a parent tree item is collapsed,
 941      * the result is that this method will return -1 to indicate that the
 942      * given tree item is not accessible in the tree.
 943      * 
 944      * @param item The TreeItem for which the index is sought.
 945      * @return An integer representing the location in the current TreeView of the
 946      *      first instance of the given TreeItem, or -1 if it is null or can not 
 947      *      be found (for example, if a parent (all the way up to the root) is
 948      *      collapsed).
 949      */
 950     public int getRow(TreeItem<T> item) {
 951         return TreeUtil.getRow(item, getRoot(), expandedItemCountDirty, isShowRoot());
 952     }
 953 
 954     /**
 955      * Returns the TreeItem in the given index, or null if it is out of bounds.
 956      * 
 957      * @param row The index of the TreeItem being sought.
 958      * @return The TreeItem in the given index, or null if it is out of bounds.
 959      */
 960     public TreeItem<T> getTreeItem(int row) {
 961         if (row < 0) return null;
 962 
 963         // normalize the requested row based on whether showRoot is set
 964         final int _row = isShowRoot() ? row : (row + 1);
 965 
 966         if (expandedItemCountDirty) {
 967             updateExpandedItemCount(getRoot());
 968         } else {
 969             if (treeItemCacheMap.containsKey(_row)) {
 970                 SoftReference<TreeItem<T>> treeItemRef = treeItemCacheMap.get(_row);
 971                 TreeItem<T> treeItem = treeItemRef.get();
 972                 if (treeItem != null) {
 973                     return treeItem;
 974                 }
 975             }
 976         }
 977 
 978         TreeItem<T> treeItem = TreeUtil.getItem(getRoot(), _row, expandedItemCountDirty);
 979         treeItemCacheMap.put(_row, new SoftReference<>(treeItem));
 980         return treeItem;
 981     }
 982 
 983     /**
 984      * Returns the number of levels of 'indentation' of the given TreeItem,
 985      * based on how many times getParent() can be recursively called. If the
 986      * given TreeItem is the root node of this TreeView, or if the TreeItem does
 987      * not have any parent set, the returned value will be zero. For each time
 988      * getParent() is recursively called, the returned value is incremented by one.
 989      *
 990      * @param node The TreeItem for which the level is needed.
 991      * @return An integer representing the number of parents above the given node,
 992      *         or -1 if the given TreeItem is null.
 993      */
 994     public int getTreeItemLevel(TreeItem<?> node) {
 995         final TreeItem<?> root = getRoot();
 996 
 997         if (node == null) return -1;
 998         if (node == root) return 0;
 999 
1000         int level = 0;
1001         TreeItem<?> parent = node.getParent();
1002         while (parent != null) {
1003             level++;
1004 
1005             if (parent == root) {
1006                 break;
1007             }
1008 
1009             parent = parent.getParent();
1010         }
1011 
1012         return level;
1013     }
1014 
1015     /** {@inheritDoc} */
1016     @Override protected Skin<?> createDefaultSkin() {
1017         return new TreeViewSkin<T>(this);
1018     }
1019 
1020     /**
1021      * Calling {@code refresh()} forces the TreeView control to recreate and
1022      * repopulate the cells necessary to populate the visual bounds of the control.
1023      * In other words, this forces the TreeView to update what it is showing to
1024      * the user. This is useful in cases where the underlying data source has
1025      * changed in a way that is not observed by the TreeView itself.
1026      *
1027      * @since JavaFX 8u60
1028      */
1029     public void refresh() {
1030         getProperties().put(TreeViewSkin.RECREATE, Boolean.TRUE);
1031     }
1032 
1033 
1034     
1035     /***************************************************************************
1036      *                                                                         *
1037      * Private Implementation                                                  *
1038      *                                                                         *
1039      **************************************************************************/  
1040     
1041     private void updateExpandedItemCount(TreeItem<T> treeItem) {
1042         setExpandedItemCount(TreeUtil.updateExpandedItemCount(treeItem, expandedItemCountDirty, isShowRoot()));
1043 
1044         if (expandedItemCountDirty) {
1045             // this is a very inefficient thing to do, but for now having a cache
1046             // is better than nothing at all...
1047             treeItemCacheMap.clear();
1048         }
1049 
1050         expandedItemCountDirty = false;
1051     }
1052 
1053     private void updateRootExpanded() {
1054         // if we aren't showing the root, and the root isn't expanded, we expand
1055         // it now so that something is shown.
1056         if (!isShowRoot() && getRoot() != null && ! getRoot().isExpanded()) {
1057             getRoot().setExpanded(true);
1058         }
1059     }
1060 
1061 
1062     
1063     /***************************************************************************
1064      *                                                                         *
1065      * Stylesheet Handling                                                     *
1066      *                                                                         *
1067      **************************************************************************/
1068 
1069     private static final String DEFAULT_STYLE_CLASS = "tree-view";
1070 
1071     /** @treatAsPrivate */
1072     private static class StyleableProperties {
1073         private static final CssMetaData<TreeView<?>,Number> FIXED_CELL_SIZE =
1074                 new CssMetaData<TreeView<?>,Number>("-fx-fixed-cell-size",
1075                                                      SizeConverter.getInstance(),
1076                                                      Region.USE_COMPUTED_SIZE) {
1077 
1078                     @Override public Double getInitialValue(TreeView<?> node) {
1079                         return node.getFixedCellSize();
1080                     }
1081 
1082                     @Override public boolean isSettable(TreeView<?> n) {
1083                         return n.fixedCellSize == null || !n.fixedCellSize.isBound();
1084                     }
1085 
1086                     @Override public StyleableProperty<Number> getStyleableProperty(TreeView<?> n) {
1087                         return (StyleableProperty<Number>)(WritableValue<Number>) n.fixedCellSizeProperty();
1088                     }
1089                 };
1090 
1091         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1092         static {
1093             final List<CssMetaData<? extends Styleable, ?>> styleables =
1094                     new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
1095             styleables.add(FIXED_CELL_SIZE);
1096             STYLEABLES = Collections.unmodifiableList(styleables);
1097         }
1098     }
1099 
1100     /**
1101      * @return The CssMetaData associated with this class, which may include the
1102      * CssMetaData of its super classes.
1103      * @since JavaFX 8.0
1104      */
1105     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1106         return StyleableProperties.STYLEABLES;
1107     }
1108 
1109     /**
1110      * {@inheritDoc}
1111      * @since JavaFX 8.0
1112      */
1113     @Override
1114     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
1115         return getClassCssMetaData();
1116     }
1117 
1118 
1119 
1120     /***************************************************************************
1121      *                                                                         *
1122      * Accessibility handling                                                  *
1123      *                                                                         *
1124      **************************************************************************/
1125 
1126     @Override
1127     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1128         switch (attribute) {
1129             case MULTIPLE_SELECTION: {
1130                 MultipleSelectionModel<TreeItem<T>> sm = getSelectionModel();
1131                 return sm != null && sm.getSelectionMode() == SelectionMode.MULTIPLE;
1132             }
1133             case ROW_COUNT: return getExpandedItemCount();
1134             default: return super.queryAccessibleAttribute(attribute, parameters);
1135         }
1136     }
1137 
1138 
1139 
1140     /***************************************************************************
1141      *                                                                         *
1142      * Support Interfaces                                                      *
1143      *                                                                         *
1144      **************************************************************************/
1145 
1146 
1147 
1148     /***************************************************************************
1149      *                                                                         *
1150      * Support Classes                                                         *
1151      *                                                                         *
1152      **************************************************************************/
1153 
1154 
1155     /**
1156      * An {@link Event} subclass used specifically in TreeView for representing
1157      * edit-related events. It provides additional API to easily access the 
1158      * TreeItem that the edit event took place on, as well as the input provided
1159      * by the end user.
1160      * 
1161      * @param <T> The type of the input, which is the same type as the TreeView 
1162      *      itself.
1163      * @since JavaFX 2.0
1164      */
1165     public static class EditEvent<T> extends Event {
1166         private static final long serialVersionUID = -4437033058917528976L;
1167 
1168         /**
1169          * Common supertype for all edit event types.
1170          * @since JavaFX 8.0
1171          */
1172         public static final EventType<?> ANY = EDIT_ANY_EVENT;
1173 
1174         private final T oldValue;
1175         private final T newValue;
1176         private transient final TreeItem<T> treeItem;
1177         
1178         /**
1179          * Creates a new EditEvent instance to represent an edit event. This 
1180          * event is used for {@link #EDIT_START_EVENT}, 
1181          * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT} types.
1182          */
1183         public EditEvent(TreeView<T> source,
1184                          EventType<? extends EditEvent> eventType,
1185                          TreeItem<T> treeItem, T oldValue, T newValue) {
1186             super(source, Event.NULL_SOURCE_TARGET, eventType);
1187             this.oldValue = oldValue;
1188             this.newValue = newValue;
1189             this.treeItem = treeItem;
1190         }
1191 
1192         /**
1193          * Returns the TreeView upon which the edit took place.
1194          */
1195         @Override public TreeView<T> getSource() {
1196             return (TreeView<T>) super.getSource();
1197         }
1198 
1199         /**
1200          * Returns the {@link TreeItem} upon which the edit took place.
1201          */
1202         public TreeItem<T> getTreeItem() {
1203             return treeItem;
1204         }
1205         
1206         /**
1207          * Returns the new value input into the TreeItem by the end user.
1208          */
1209         public T getNewValue() {
1210             return newValue;
1211         }
1212         
1213         /**
1214          * Returns the old value that existed in the TreeItem prior to the current
1215          * edit event.
1216          */
1217         public T getOldValue() {
1218             return oldValue;
1219         }
1220     }
1221     
1222     
1223     
1224     
1225 
1226 
1227 
1228     // package for testing
1229     static class TreeViewBitSetSelectionModel<T> extends MultipleSelectionModelBase<TreeItem<T>> {
1230 
1231         /***********************************************************************
1232          *                                                                     *
1233          * Internal fields                                                     *
1234          *                                                                     *
1235          **********************************************************************/
1236 
1237         private TreeView<T> treeView = null;
1238 
1239 
1240 
1241         /***********************************************************************
1242          *                                                                     *
1243          * Constructors                                                        *
1244          *                                                                     *
1245          **********************************************************************/
1246 
1247         public TreeViewBitSetSelectionModel(final TreeView<T> treeView) {
1248             if (treeView == null) {
1249                 throw new IllegalArgumentException("TreeView can not be null");
1250             }
1251 
1252             this.treeView = treeView;
1253             this.treeView.rootProperty().addListener(weakRootPropertyListener);
1254                     
1255             updateTreeEventListener(null, treeView.getRoot());
1256 
1257             updateDefaultSelection();
1258         }
1259         
1260         private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
1261             if (oldRoot != null && weakTreeItemListener != null) {
1262                 oldRoot.removeEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1263             }
1264             
1265             if (newRoot != null) {
1266                 weakTreeItemListener = new WeakEventHandler<>(treeItemListener);
1267                 newRoot.addEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1268             }
1269         }
1270         
1271         private ChangeListener<TreeItem<T>> rootPropertyListener = (observable, oldValue, newValue) -> {
1272             updateDefaultSelection();
1273             updateTreeEventListener(oldValue, newValue);
1274         };
1275 
1276         private EventHandler<TreeModificationEvent<T>> treeItemListener = e -> {
1277             if (getSelectedIndex() == -1 && getSelectedItem() == null) return;
1278 
1279             final TreeItem<T> treeItem = e.getTreeItem();
1280             if (treeItem == null) return;
1281 
1282             treeView.expandedItemCountDirty = true;
1283 
1284             // we only shift selection from this row - everything before it
1285             // is safe. We might change this below based on certain criteria
1286             int startRow = treeView.getRow(treeItem);
1287 
1288             int shift = 0;
1289             ListChangeListener.Change<? extends TreeItem<?>> change = e.getChange();
1290             if (change != null) {
1291                 change.next();
1292             }
1293 
1294             do {
1295                 final int addedSize = change == null ? 0 : change.getAddedSize();
1296                 final int removedSize = change == null ? 0 : change.getRemovedSize();
1297 
1298                 if (e.wasExpanded()) {
1299                     // need to shuffle selection by the number of visible children
1300                     shift += treeItem.getExpandedDescendentCount(false) - 1;
1301                     startRow++;
1302                 } else if (e.wasCollapsed()) {
1303                     // remove selection from any child treeItem, and also determine
1304                     // if any child item was selected (in which case the parent
1305                     // takes the selection on collapse)
1306                     treeItem.getExpandedDescendentCount(false);
1307                     final int count = treeItem.previousExpandedDescendentCount;
1308 
1309                     final int selectedIndex = getSelectedIndex();
1310                     final boolean wasPrimarySelectionInChild =
1311                             selectedIndex >= (startRow + 1) &&
1312                                     selectedIndex < (startRow + count);
1313 
1314                     boolean wasAnyChildSelected = false;
1315 
1316                     startAtomic();
1317                     final int from = startRow + 1;
1318                     final int to = startRow + count;
1319                     final List<Integer> removed = new ArrayList<>();
1320                     for (int i = from; i < to; i++) {
1321                         if (isSelected(i)) {
1322                             wasAnyChildSelected = true;
1323                             clearSelection(i);
1324                             removed.add(i);
1325                         }
1326                     }
1327                     stopAtomic();
1328 
1329                     // put selection onto the newly-collapsed tree item
1330                     if (wasPrimarySelectionInChild && wasAnyChildSelected) {
1331                         select(startRow);
1332                     } else {
1333                         // we pass in (index, index) here to represent that nothing was added
1334                         // in this change.
1335                         ListChangeListener.Change newChange = new NonIterableChange.GenericAddRemoveChange<>(from, from,
1336                                 removed, selectedIndicesSeq);
1337                         selectedIndicesSeq.callObservers(newChange);
1338                     }
1339 
1340                     shift += -count + 1;
1341                     startRow++;
1342                 } else if (e.wasPermutated()) {
1343                     // no-op
1344                 } else if (e.wasAdded()) {
1345                     // shuffle selection by the number of added items
1346                     shift += treeItem.isExpanded() ? addedSize : 0;
1347 
1348                     // RT-32963: We were taking the startRow from the TreeItem
1349                     // in which the children were added, rather than from the
1350                     // actual position of the new child. This led to selection
1351                     // being moved off the parent TreeItem by mistake.
1352                     // The 'if (e.getAddedSize() == 1)' condition here was
1353                     // subsequently commented out due to RT-33894.
1354                     startRow = treeView.getRow(e.getChange().getAddedSubList().get(0));
1355                 } else if (e.wasRemoved()) {
1356                     // shuffle selection by the number of removed items
1357                     shift += treeItem.isExpanded() ? -removedSize : 0;
1358 
1359                     // the start row is incorrect - it is _not_ the index of the
1360                     // TreeItem in which the children were removed from (which is
1361                     // what it currently represents). We need to take the 'from'
1362                     // value out of the event and make use of that to understand
1363                     // what actually changed inside the children list.
1364                     startRow += e.getFrom() + 1;
1365 
1366                     // whilst we are here, we should check if the removed items
1367                     // are part of the selectedItems list - and remove them
1368                     // from selection if they are (as per RT-15446)
1369                     final List<Integer> selectedIndices1 = getSelectedIndices();
1370                     final int selectedIndex = getSelectedIndex();
1371                     final List<TreeItem<T>> selectedItems = getSelectedItems();
1372                     final TreeItem<T> selectedItem = getSelectedItem();
1373                     final List<? extends TreeItem<T>> removedChildren = e.getChange().getRemoved();
1374 
1375                     for (int i = 0; i < selectedIndices1.size() && !selectedItems.isEmpty(); i++) {
1376                         int index = selectedIndices1.get(i);
1377                         if (index > selectedItems.size()) break;
1378 
1379                         if (removedChildren.size() == 1 &&
1380                                 selectedItems.size() == 1 &&
1381                                 selectedItem != null &&
1382                                 selectedItem.equals(removedChildren.get(0))) {
1383                             // Bug fix for RT-28637
1384                             if (selectedIndex < getItemCount()) {
1385                                 final int previousRow = selectedIndex == 0 ? 0 : selectedIndex - 1;
1386                                 TreeItem<T> newSelectedItem = getModelItem(previousRow);
1387                                 if (!selectedItem.equals(newSelectedItem)) {
1388                                     select(newSelectedItem);
1389                                 }
1390                             }
1391                         }
1392                     }
1393                 }
1394             } while (e.getChange() != null && e.getChange().next());
1395 
1396             shiftSelection(startRow, shift, null);
1397 
1398             if (e.wasAdded() || e.wasRemoved()) {
1399                 Integer anchor = TreeCellBehavior.getAnchor(treeView, null);
1400                 if (anchor != null && isSelected(anchor + shift)) {
1401                     TreeCellBehavior.setAnchor(treeView, anchor + shift, false);
1402                 }
1403             }
1404         };
1405         
1406         private WeakChangeListener<TreeItem<T>> weakRootPropertyListener =
1407                 new WeakChangeListener<>(rootPropertyListener);
1408         
1409         private WeakEventHandler<TreeModificationEvent<T>> weakTreeItemListener;
1410 
1411 
1412 
1413         /***********************************************************************
1414          *                                                                     *
1415          * Public selection API                                                *
1416          *                                                                     *
1417          **********************************************************************/
1418 
1419         /** {@inheritDoc} */
1420         @Override public void selectAll() {
1421             // when a selectAll happens, the anchor should not change, so we store it
1422             // before, and restore it afterwards
1423             final int anchor = TreeCellBehavior.getAnchor(treeView, -1);
1424             super.selectAll();
1425             TreeCellBehavior.setAnchor(treeView, anchor, false);
1426         }
1427 
1428         /** {@inheritDoc} */
1429         @Override public void select(TreeItem<T> obj) {
1430 //        if (getRowCount() <= 0) return;
1431             
1432             if (obj == null && getSelectionMode() == SelectionMode.SINGLE) {
1433                 clearSelection();
1434                 return;
1435             }
1436             
1437             // we firstly expand the path down such that the given object is
1438             // visible. This fixes RT-14456, where selection was not happening
1439             // correctly on TreeItems that are not visible.
1440 
1441             if (obj != null) {
1442                 TreeItem<?> item = obj.getParent();
1443                 while (item != null) {
1444                     item.setExpanded(true);
1445                     item = item.getParent();
1446                 }
1447             }
1448             
1449             // Fix for RT-15419. We eagerly update the tree item count, such that
1450             // selection occurs on the row
1451             treeView.updateExpandedItemCount(treeView.getRoot());
1452             
1453             // We have no option but to iterate through the model and select the
1454             // first occurrence of the given object. Once we find the first one, we
1455             // don't proceed to select any others.
1456             int row = treeView.getRow(obj);
1457             
1458             if (row == -1) {
1459                 // if we are here, we did not find the item in the entire data model.
1460                 // Even still, we allow for this item to be set to the give object.
1461                 // We expect that in concrete subclasses of this class we observe the
1462                 // data model such that we check to see if the given item exists in it,
1463                 // whilst SelectedIndex == -1 && SelectedItem != null.
1464                 setSelectedIndex(-1);
1465                 setSelectedItem(obj);
1466             } else {
1467                 select(row);
1468             }
1469         }
1470 
1471         /** {@inheritDoc} */
1472         @Override public void clearAndSelect(int row) {
1473             TreeCellBehavior.setAnchor(treeView, row, false);
1474             super.clearAndSelect(row);
1475         }
1476 
1477 
1478         /***********************************************************************
1479          *                                                                     *
1480          * Support code                                                        *
1481          *                                                                     *
1482          **********************************************************************/
1483         
1484         /** {@inheritDoc} */
1485         @Override protected void focus(int itemIndex) {
1486             if (treeView.getFocusModel() != null) {
1487                 treeView.getFocusModel().focus(itemIndex);
1488             }
1489 
1490             // FIXME this is not the correct location for fire selection events (and does not take into account multiple selection)
1491             treeView.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
1492         }
1493 
1494         /** {@inheritDoc} */
1495         @Override protected int getFocusedIndex() {
1496             if (treeView.getFocusModel() == null) return -1;
1497             return treeView.getFocusModel().getFocusedIndex();
1498         }
1499         
1500         /** {@inheritDoc} */
1501         @Override protected int getItemCount() {
1502             return treeView == null ? 0 : treeView.getExpandedItemCount();
1503         }
1504 
1505         /** {@inheritDoc} */
1506         @Override public TreeItem<T> getModelItem(int index) {
1507             if (treeView == null) return null;
1508 
1509             if (index < 0 || index >= treeView.getExpandedItemCount()) return null;
1510 
1511             return treeView.getTreeItem(index);
1512         }
1513 
1514 
1515 
1516         /***********************************************************************
1517          *                                                                     *
1518          * Private implementation                                              *
1519          *                                                                     *
1520          **********************************************************************/
1521 
1522         private void updateDefaultSelection() {
1523             clearSelection();
1524 
1525             // we put focus onto the first item, if there is at least
1526             // one item in the list
1527             focus(getItemCount() > 0 ? 0 : -1);
1528         }
1529     }
1530     
1531     
1532 
1533     /**
1534      * 
1535      * @param <T> 
1536      */
1537     static class TreeViewFocusModel<T> extends FocusModel<TreeItem<T>> {
1538 
1539         private final TreeView<T> treeView;
1540 
1541         public TreeViewFocusModel(final TreeView<T> treeView) {
1542             this.treeView = treeView;
1543             this.treeView.rootProperty().addListener(weakRootPropertyListener);
1544             updateTreeEventListener(null, treeView.getRoot());
1545 
1546             if (treeView.getExpandedItemCount() > 0) {
1547                 focus(0);
1548             }
1549 
1550             treeView.showRootProperty().addListener(o -> {
1551                 if (isFocused(0)) {
1552                     focus(-1);
1553                     focus(0);
1554                 }
1555             });
1556         }
1557         
1558         private final ChangeListener<TreeItem<T>> rootPropertyListener = (observable, oldValue, newValue) -> {
1559             updateTreeEventListener(oldValue, newValue);
1560         };
1561                 
1562         private final WeakChangeListener<TreeItem<T>> weakRootPropertyListener =
1563                 new WeakChangeListener<>(rootPropertyListener);
1564         
1565         private void updateTreeEventListener(TreeItem<T> oldRoot, TreeItem<T> newRoot) {
1566             if (oldRoot != null && weakTreeItemListener != null) {
1567                 oldRoot.removeEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1568             }
1569             
1570             if (newRoot != null) {
1571                 weakTreeItemListener = new WeakEventHandler<>(treeItemListener);
1572                 newRoot.addEventHandler(TreeItem.<T>expandedItemCountChangeEvent(), weakTreeItemListener);
1573             }
1574         }
1575         
1576         private EventHandler<TreeModificationEvent<T>> treeItemListener = new EventHandler<TreeModificationEvent<T>>() {
1577             @Override public void handle(TreeModificationEvent<T> e) {
1578                 // don't shift focus if the event occurred on a tree item after
1579                 // the focused row, or if there is no focus index at present
1580                 if (getFocusedIndex() == -1) return;
1581 
1582                 int row = treeView.getRow(e.getTreeItem());
1583 
1584                 int shift = 0;
1585                 if (e.getChange() != null) {
1586                     e.getChange().next();
1587                 }
1588 
1589                 do {
1590                     if (e.wasExpanded()) {
1591                         if (row < getFocusedIndex()) {
1592                             // need to shuffle selection by the number of visible children
1593                             shift += e.getTreeItem().getExpandedDescendentCount(false) - 1;
1594                         }
1595                     } else if (e.wasCollapsed()) {
1596                         if (row < getFocusedIndex()) {
1597                             // need to shuffle selection by the number of visible children
1598                             // that were just hidden
1599                             shift += -e.getTreeItem().previousExpandedDescendentCount + 1;
1600                         }
1601                     } else if (e.wasAdded()) {
1602                         // get the TreeItem the event occurred on - we only need to
1603                         // shift if the tree item is expanded
1604                         TreeItem<T> eventTreeItem = e.getTreeItem();
1605                         if (eventTreeItem.isExpanded()) {
1606                             for (int i = 0; i < e.getAddedChildren().size(); i++) {
1607                                 // get the added item and determine the row it is in
1608                                 TreeItem<T> item = e.getAddedChildren().get(i);
1609                                 row = treeView.getRow(item);
1610 
1611                                 if (item != null && row <= getFocusedIndex()) {
1612                                     shift += item.getExpandedDescendentCount(false);
1613                                 }
1614                             }
1615                         }
1616                     } else if (e.wasRemoved()) {
1617                         row += e.getFrom() + 1;
1618 
1619                         for (int i = 0; i < e.getRemovedChildren().size(); i++) {
1620                             TreeItem<T> item = e.getRemovedChildren().get(i);
1621                             if (item != null && item.equals(getFocusedItem())) {
1622                                 focus(Math.max(0, getFocusedIndex() - 1));
1623                                 return;
1624                             }
1625                         }
1626 
1627                         if (row <= getFocusedIndex()) {
1628                             // shuffle selection by the number of removed items
1629                             shift += e.getTreeItem().isExpanded() ? -e.getRemovedSize() : 0;
1630                         }
1631                     }
1632                 } while (e.getChange() != null && e.getChange().next());
1633                 
1634                 if(shift != 0) {
1635                     final int newFocus = getFocusedIndex() + shift;
1636                     if (newFocus >= 0) {
1637                         Platform.runLater(() -> focus(newFocus));
1638                     }
1639                 } 
1640             }
1641         };
1642         
1643         private WeakEventHandler<TreeModificationEvent<T>> weakTreeItemListener;
1644 
1645         @Override protected int getItemCount() {
1646             return treeView == null ? -1 : treeView.getExpandedItemCount();
1647         }
1648 
1649         @Override protected TreeItem<T> getModelItem(int index) {
1650             if (treeView == null) return null;
1651 
1652             if (index < 0 || index >= treeView.getExpandedItemCount()) return null;
1653 
1654             return treeView.getTreeItem(index);
1655         }
1656 
1657         /** {@inheritDoc} */
1658         @Override public void focus(int index) {
1659             if (treeView.expandedItemCountDirty) {
1660                 treeView.updateExpandedItemCount(treeView.getRoot());
1661             }
1662             
1663             super.focus(index);
1664         }
1665     }
1666 }