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