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