1 /*
   2  * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import com.sun.javafx.collections.MappingChange;
  29 import com.sun.javafx.collections.NonIterableChange;
  30 import com.sun.javafx.scene.control.Properties;
  31 import com.sun.javafx.scene.control.SelectedCellsMap;
  32 
  33 import com.sun.javafx.scene.control.behavior.TableCellBehavior;
  34 import com.sun.javafx.scene.control.behavior.TableCellBehaviorBase;
  35 import com.sun.javafx.scene.control.behavior.TreeTableCellBehavior;
  36 import javafx.beans.property.DoubleProperty;
  37 import javafx.css.CssMetaData;
  38 import javafx.css.PseudoClass;
  39 
  40 import javafx.css.converter.SizeConverter;
  41 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
  42 import com.sun.javafx.scene.control.TableColumnComparatorBase;
  43 
  44 import javafx.css.Styleable;
  45 import javafx.css.StyleableDoubleProperty;
  46 import javafx.css.StyleableProperty;
  47 import javafx.event.WeakEventHandler;
  48 
  49 import javafx.scene.control.skin.TreeTableViewSkin;
  50 
  51 import java.lang.ref.SoftReference;
  52 import java.lang.ref.WeakReference;
  53 import java.util.ArrayList;
  54 import java.util.Collection;
  55 import java.util.Collections;
  56 import java.util.Comparator;
  57 import java.util.HashMap;
  58 import java.util.LinkedHashSet;
  59 import java.util.List;
  60 import java.util.Map;
  61 import java.util.Set;
  62 import java.util.WeakHashMap;
  63 
  64 import javafx.application.Platform;
  65 import javafx.beans.DefaultProperty;
  66 import javafx.beans.InvalidationListener;
  67 import javafx.beans.WeakInvalidationListener;
  68 import javafx.beans.property.BooleanProperty;
  69 import javafx.beans.property.ObjectProperty;
  70 import javafx.beans.property.ObjectPropertyBase;
  71 import javafx.beans.property.ReadOnlyIntegerProperty;
  72 import javafx.beans.property.ReadOnlyIntegerWrapper;
  73 import javafx.beans.property.ReadOnlyObjectProperty;
  74 import javafx.beans.property.ReadOnlyObjectWrapper;
  75 import javafx.beans.property.SimpleBooleanProperty;
  76 import javafx.beans.property.SimpleObjectProperty;
  77 import javafx.beans.value.ChangeListener;
  78 import javafx.beans.value.WeakChangeListener;
  79 import javafx.beans.value.WritableValue;
  80 import javafx.collections.FXCollections;
  81 import javafx.collections.ListChangeListener;
  82 import javafx.collections.MapChangeListener;
  83 import javafx.collections.ObservableList;
  84 import javafx.collections.WeakListChangeListener;
  85 import javafx.event.Event;
  86 import javafx.event.EventHandler;
  87 import javafx.event.EventType;
  88 import javafx.scene.AccessibleAttribute;
  89 import javafx.scene.AccessibleRole;
  90 import javafx.scene.Node;
  91 import javafx.scene.layout.Region;
  92 import javafx.util.Callback;
  93 
  94 /**
  95  * The TreeTableView control is designed to visualize an unlimited number of rows
  96  * of data, broken out into columns. The TreeTableView control is conceptually
  97  * very similar to the {@link TreeView} and {@link TableView} controls,
  98  * and as you read on you'll come to see the APIs are largely the same.
  99  * However, to give a high-level overview, you'll note that the TreeTableView
 100  * uses the same {@link TreeItem} API as {@link TreeView},
 101  * and that you therefore are required to simply set the
 102  * {@link #rootProperty() root node} in the TreeTableView. Similarly, the
 103  * TreeTableView control makes use of the same TableColumn-based approach that
 104  * the {@link TableView} control uses, except instead of using the
 105  * TableView-specific {@link TableColumn} class, you should instead use the
 106  * TreeTableView-specific {@link TreeTableColumn} class instead. For an
 107  * example on how to create a TreeTableView instance, refer to the 'Creating a
 108  * TreeTableView' control section below.
 109  *
 110  * <p>As with the {@link TableView} control, the TreeTableView control has a
 111  * number of features, including:
 112  * <ul>
 113  * <li>Powerful {@link TreeTableColumn} API:
 114  *   <ul>
 115  *   <li>Support for {@link TreeTableColumn#cellFactoryProperty() cell factories} to
 116  *      easily customize {@link Cell cell} contents in both rendering and editing
 117  *      states.
 118  *   <li>Specification of {@link TreeTableColumn#minWidthProperty() minWidth}/
 119  *      {@link TreeTableColumn#prefWidthProperty() prefWidth}/
 120  *      {@link TreeTableColumn#maxWidthProperty() maxWidth},
 121  *      and also {@link TreeTableColumn#resizableProperty() fixed width columns}.
 122  *   <li>Width resizing by the user at runtime.
 123  *   <li>Column reordering by the user at runtime.
 124  *   <li>Built-in support for {@link TreeTableColumn#getColumns() column nesting}
 125  *   </ul>
 126  * <li>Different {@link #columnResizePolicyProperty() resizing policies} to
 127  *      dictate what happens when the user resizes columns.
 128  * <li>Support for {@link #getSortOrder() multiple column sorting} by clicking
 129  *      the column header (hold down Shift keyboard key whilst clicking on a
 130  *      header to sort by multiple columns).
 131  * </ul>
 132  *
 133  * <h2>Creating a TreeTableView</h2>
 134  *
 135  * <p>Creating a TreeTableView is a multi-step process, and also depends on the
 136  * underlying data model needing to be represented. For this example we'll use
 137  * the TreeTableView to visualise a file system, and will therefore make use
 138  * of an imaginary (and vastly simplified) File class as defined below:
 139  *
 140  * <pre>{@code
 141  * public class File {
 142  *     private StringProperty name;
 143  *     public void setName(String value) { nameProperty().set(value); }
 144  *     public String getName() { return nameProperty().get(); }
 145  *     public StringProperty nameProperty() {
 146  *         if (name == null) name = new SimpleStringProperty(this, "name");
 147  *         return name;
 148  *     }
 149  *
 150  *     private LongProperty lastModified;
 151  *     public void setLastModified(long value) { lastModifiedProperty().set(value); }
 152  *     public long getLastModified() { return lastModifiedProperty().get(); }
 153  *     public LongProperty lastModifiedProperty() {
 154  *         if (lastModified == null) lastModified = new SimpleLongProperty(this, "lastModified");
 155  *         return lastModified;
 156  *     }
 157  * }}</pre>
 158  *
 159  * <p>Firstly, a TreeTableView instance needs to be defined, as such:
 160  *
 161  * <pre>{@code
 162  * TreeTableView<File> treeTable = new TreeTableView<>();}</pre>
 163  *
 164  * <p>With the basic TreeTableView instantiated, we next focus on the data model.
 165  * As mentioned, for this example, we'll be representing a file system using File
 166  * instances. To do this, we need to define the root node of the tree table, as such:
 167  *
 168  * <pre>{@code
 169  * TreeItem<File> root = new TreeItem<>(new File("/"));
 170  * treeTable.setRoot(root);}</pre>
 171  *
 172  * <p>With the root set as such, the TreeTableView will automatically update whenever
 173  * the {@link TreeItem#getChildren() children} of the root changes.
 174  *
 175  * <p>At this point we now have a TreeTableView hooked up to observe the root
 176  * TreeItem instance. The missing ingredient
 177  * now is the means of splitting out the data contained within the model and
 178  * representing it in one or more {@link TreeTableColumn} instances. To
 179  * create a two-column TreeTableView to show the file name and last modified
 180  * properties, we extend the code shown above as follows:
 181  *
 182  * <pre>{@code
 183  * TreeTableColumns<File,String> fileNameCol = new TreeTableColumn<>("Filename");
 184  * TreeTableColumns<File,Long> lastModifiedCol = new TreeTableColumn<>("Size");
 185  *
 186  * table.getColumns().setAll(fileNameCol, lastModifiedCol);}</pre>
 187  *
 188  * <p>With the code shown above we have nearly fully defined the minimum properties
 189  * required to create a TreeTableView instance. The only thing missing is the
 190  * {@link javafx.scene.control.TreeTableColumn#cellValueFactoryProperty() cell value factories}
 191  * for the two columns - it is these that are responsible for determining the value
 192  * of a cell in a given row. Commonly these can be specified using the
 193  * {@link javafx.scene.control.cell.TreeItemPropertyValueFactory} class, but
 194  * failing that you can also create an anonymous inner class and do whatever is
 195  * necessary. For example, using {@link javafx.scene.control.cell.TreeItemPropertyValueFactory}
 196  * you would do the following:
 197  *
 198  * <pre>{@code
 199  * fileNameCol.setCellValueFactory(new TreeItemPropertyValueFactory("name"));
 200  * lastModifiedCol.setCellValueFactory(new TreeItemPropertyValueFactory("lastModified"));}</pre>
 201  *
 202  * Running this code (assuming the file system structure is probably built up in
 203  * memory) will result in a TreeTableView being shown with two columns for name
 204  * and lastModified. Any other properties of the File class will not be shown, as
 205  * no TreeTableColumns are defined for them.
 206  *
 207  * <h3>TreeTableView support for classes that don't contain properties</h3>
 208  *
 209  * <p>The code shown above is the shortest possible code for creating a TreeTableView
 210  * when the domain objects are designed with JavaFX properties in mind
 211  * (additionally, {@link javafx.scene.control.cell.TreeItemPropertyValueFactory} supports
 212  * normal JavaBean properties too, although there is a caveat to this, so refer
 213  * to the class documentation for more information). When this is not the case,
 214  * it is necessary to provide a custom cell value factory. More information
 215  * about cell value factories can be found in the {@link TreeTableColumn} API
 216  * documentation, but briefly, here is how a TreeTableColumns could be specified:
 217  *
 218  * <pre>{@code
 219  * firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
 220  *     public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
 221  *         // p.getValue() returns the TreeItem<Person> instance for a particular TreeTableView row,
 222  *         // p.getValue().getValue() returns the Person instance inside the TreeItem<Person>
 223  *         return p.getValue().getValue().firstNameProperty();
 224  *     }
 225  *  });
 226  * }}</pre>
 227  *
 228  * <h3>TreeTableView Selection / Focus APIs</h3>
 229  * <p>To track selection and focus, it is necessary to become familiar with the
 230  * {@link SelectionModel} and {@link FocusModel} classes. A TreeTableView has at most
 231  * one instance of each of these classes, available from
 232  * {@link #selectionModelProperty() selectionModel} and
 233  * {@link #focusModelProperty() focusModel} properties, respectively.
 234  * Whilst it is possible to use this API to set a new selection model, in
 235  * most circumstances this is not necessary - the default selection and focus
 236  * models should work in most circumstances.
 237  *
 238  * <p>The default {@link SelectionModel} used when instantiating a TreeTableView is
 239  * an implementation of the {@link MultipleSelectionModel} abstract class.
 240  * However, as noted in the API documentation for
 241  * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode}
 242  * property, the default value is {@link SelectionMode#SINGLE}. To enable
 243  * multiple selection in a default TreeTableView instance, it is therefore necessary
 244  * to do the following:
 245  *
 246  * <pre>
 247  * {@code
 248  * treeTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre>
 249  *
 250  * <h3>Customizing TreeTableView Visuals</h3>
 251  * <p>The visuals of the TreeTableView can be entirely customized by replacing the
 252  * default {@link #rowFactoryProperty() row factory}. A row factory is used to
 253  * generate {@link TreeTableRow} instances, which are used to represent an entire
 254  * row in the TreeTableView.
 255  *
 256  * <p>In many cases, this is not what is desired however, as it is more commonly
 257  * the case that cells be customized on a per-column basis, not a per-row basis.
 258  * It is therefore important to note that a {@link TreeTableRow} is not a
 259  * {@link TreeTableCell}. A  {@link TreeTableRow} is simply a container for zero or more
 260  * {@link TreeTableCell}, and in most circumstances it is more likely that you'll
 261  * want to create custom TreeTableCells, rather than TreeTableRows. The primary use case
 262  * for creating custom TreeTableRow instances would most probably be to introduce
 263  * some form of column spanning support.
 264  *
 265  * <p>You can create custom {@link TreeTableCell} instances per column by assigning
 266  * the appropriate function to the TreeTableColumns
 267  * {@link TreeTableColumn#cellFactoryProperty() cell factory} property.
 268  *
 269  * <p>See the {@link Cell} class documentation for a more complete
 270  * description of how to write custom Cells.
 271  *
 272  * <h3>Editing</h3>
 273  * <p>This control supports inline editing of values, and this section attempts to
 274  * give an overview of the available APIs and how you should use them.</p>
 275  *
 276  * <p>Firstly, cell editing most commonly requires a different user interface
 277  * than when a cell is not being edited. This is the responsibility of the
 278  * {@link Cell} implementation being used. For TreeTableView, it is highly
 279  * recommended that editing be
 280  * {@link javafx.scene.control.TreeTableColumn#cellFactoryProperty() per-TreeTableColumn},
 281  * rather than {@link #rowFactoryProperty() per row}, as more often than not
 282  * you want users to edit each column value differently, and this approach allows
 283  * for editors specific to each column. It is your choice whether the cell is
 284  * permanently in an editing state (e.g. this is common for {@link CheckBox} cells),
 285  * or to switch to a different UI when editing begins (e.g. when a double-click
 286  * is received on a cell).</p>
 287  *
 288  * <p>To know when editing has been requested on a cell,
 289  * simply override the {@link javafx.scene.control.Cell#startEdit()} method, and
 290  * update the cell {@link javafx.scene.control.Cell#textProperty() text} and
 291  * {@link javafx.scene.control.Cell#graphicProperty() graphic} properties as
 292  * appropriate (e.g. set the text to null and set the graphic to be a
 293  * {@link TextField}). Additionally, you should also override
 294  * {@link Cell#cancelEdit()} to reset the UI back to its original visual state
 295  * when the editing concludes. In both cases it is important that you also
 296  * ensure that you call the super method to have the cell perform all duties it
 297  * must do to enter or exit its editing mode.</p>
 298  *
 299  * <p>Once your cell is in an editing state, the next thing you are most probably
 300  * interested in is how to commit or cancel the editing that is taking place. This is your
 301  * responsibility as the cell factory provider. Your cell implementation will know
 302  * when the editing is over, based on the user input (e.g. when the user presses
 303  * the Enter or ESC keys on their keyboard). When this happens, it is your
 304  * responsibility to call {@link Cell#commitEdit(Object)} or
 305  * {@link Cell#cancelEdit()}, as appropriate.</p>
 306  *
 307  * <p>When you call {@link Cell#commitEdit(Object)} an event is fired to the
 308  * TreeTableView, which you can observe by adding an {@link EventHandler} via
 309  * {@link TreeTableColumn#setOnEditCommit(javafx.event.EventHandler)}. Similarly,
 310  * you can also observe edit events for
 311  * {@link TreeTableColumn#setOnEditStart(javafx.event.EventHandler) edit start}
 312  * and {@link TreeTableColumn#setOnEditCancel(javafx.event.EventHandler) edit cancel}.</p>
 313  *
 314  * <p>By default the TreeTableColumn edit commit handler is non-null, with a default
 315  * handler that attempts to overwrite the property value for the
 316  * item in the currently-being-edited row. It is able to do this as the
 317  * {@link Cell#commitEdit(Object)} method is passed in the new value, and this
 318  * is passed along to the edit commit handler via the
 319  * {@link javafx.scene.control.TreeTableColumn.CellEditEvent CellEditEvent} that is
 320  * fired. It is simply a matter of calling
 321  * {@link javafx.scene.control.TreeTableColumn.CellEditEvent#getNewValue()} to
 322  * retrieve this value.
 323  *
 324  * <p>It is very important to note that if you call
 325  * {@link TreeTableColumn#setOnEditCommit(javafx.event.EventHandler)} with your own
 326  * {@link EventHandler}, then you will be removing the default handler. Unless
 327  * you then handle the writeback to the property (or the relevant data source),
 328  * nothing will happen. You can work around this by using the
 329  * {@link TreeTableColumn#addEventHandler(javafx.event.EventType, javafx.event.EventHandler)}
 330  * method to add a {@link TreeTableColumn#EDIT_COMMIT_EVENT} {@link EventType} with
 331  * your desired {@link EventHandler} as the second argument. Using this method,
 332  * you will not replace the default implementation, but you will be notified when
 333  * an edit commit has occurred.</p>
 334  *
 335  * <p>Hopefully this summary answers some of the commonly asked questions.
 336  * Fortunately, JavaFX ships with a number of pre-built cell factories that
 337  * handle all the editing requirements on your behalf. You can find these
 338  * pre-built cell factories in the javafx.scene.control.cell package.</p>
 339  *
 340  * @see TreeTableColumn
 341  * @see TreeTablePosition
 342  * @param <S> The type of the TreeItem instances used in this TreeTableView.
 343  * @since JavaFX 8.0
 344  */
 345 @DefaultProperty("root")
 346 public class TreeTableView<S> extends Control {
 347 
 348     /***************************************************************************
 349      *                                                                         *
 350      * Constructors                                                            *
 351      *                                                                         *
 352      **************************************************************************/
 353 
 354     /**
 355      * Creates an empty TreeTableView.
 356      *
 357      * <p>Refer to the {@link TreeTableView} class documentation for details on the
 358      * default state of other properties.
 359      */
 360     public TreeTableView() {
 361         this(null);
 362     }
 363 
 364     /**
 365      * Creates a TreeTableView with the provided root node.
 366      *
 367      * <p>Refer to the {@link TreeTableView} class documentation for details on the
 368      * default state of other properties.
 369      *
 370      * @param root The node to be the root in this TreeTableView.
 371      */
 372     public TreeTableView(TreeItem<S> root) {
 373         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
 374         setAccessibleRole(AccessibleRole.TREE_TABLE_VIEW);
 375 
 376         setRoot(root);
 377         updateExpandedItemCount(root);
 378 
 379         // install default selection and focus models - it's unlikely this will be changed
 380         // by many users.
 381         setSelectionModel(new TreeTableViewArrayListSelectionModel<S>(this));
 382         setFocusModel(new TreeTableViewFocusModel<S>(this));
 383 
 384         // we watch the columns list, such that when it changes we can update
 385         // the leaf columns and visible leaf columns lists (which are read-only).
 386         getColumns().addListener(weakColumnsObserver);
 387 
 388         // watch for changes to the sort order list - and when it changes run
 389         // the sort method.
 390         getSortOrder().addListener((ListChangeListener.Change<? extends TreeTableColumn<S, ?>> c) -> {
 391             doSort(TableUtil.SortEventType.SORT_ORDER_CHANGE, c);
 392         });
 393 
 394         // We're watching for changes to the content width such
 395         // that the resize policy can be run if necessary. This comes from
 396         // TreeTableViewSkin.
 397         getProperties().addListener((MapChangeListener<Object, Object>) c -> {
 398             if (c.wasAdded() && TableView.SET_CONTENT_WIDTH.equals(c.getKey())) {
 399                 if (c.getValueAdded() instanceof Number) {
 400                     setContentWidth((Double) c.getValueAdded());
 401                 }
 402                 getProperties().remove(TableView.SET_CONTENT_WIDTH);
 403             }
 404         });
 405 
 406         sceneProperty().addListener((o, oldScene, newScene) -> {
 407             if (oldScene != null) {
 408                 oldScene.focusOwnerProperty().removeListener(weakFocusOwnerListener);
 409             }
 410             if (newScene != null) {
 411                 newScene.focusOwnerProperty().addListener(weakFocusOwnerListener);
 412             }
 413         });
 414 
 415         isInited = true;
 416     }
 417 
 418 
 419 
 420     /***************************************************************************
 421      *                                                                         *
 422      * Static properties and methods                                           *
 423      *                                                                         *
 424      **************************************************************************/
 425 
 426     /**
 427      * An EventType that indicates some edit event has occurred. It is the parent
 428      * type of all other edit events: {@link #editStartEvent},
 429      *  {@link #editCommitEvent} and {@link #editCancelEvent}.
 430      *
 431      * @param <S> The type of the TreeItem instances used in this TreeTableView
 432      * @return An EventType that indicates some edit event has occurred
 433      */
 434     @SuppressWarnings("unchecked")
 435     public static <S> EventType<TreeTableView.EditEvent<S>> editAnyEvent() {
 436         return (EventType<TreeTableView.EditEvent<S>>) EDIT_ANY_EVENT;
 437     }
 438     private static final EventType<?> EDIT_ANY_EVENT =
 439             new EventType<>(Event.ANY, "TREE_TABLE_VIEW_EDIT");
 440 
 441     /**
 442      * An EventType used to indicate that an edit event has started within the
 443      * TreeTableView upon which the event was fired.
 444      *
 445      * @param <S> The type of the TreeItem instances used in this TreeTableView
 446      * @return An EventType used to indicate that an edit event has started
 447      */
 448     @SuppressWarnings("unchecked")
 449     public static <S> EventType<TreeTableView.EditEvent<S>> editStartEvent() {
 450         return (EventType<TreeTableView.EditEvent<S>>) EDIT_START_EVENT;
 451     }
 452     private static final EventType<?> EDIT_START_EVENT =
 453             new EventType<>(editAnyEvent(), "EDIT_START");
 454 
 455     /**
 456      * An EventType used to indicate that an edit event has just been canceled
 457      * within the TreeTableView upon which the event was fired.
 458      *
 459      * @param <S> The type of the TreeItem instances used in this TreeTableView
 460      * @return An EventType used to indicate that an edit event has just been
 461      *      canceled
 462      */
 463     @SuppressWarnings("unchecked")
 464     public static <S> EventType<TreeTableView.EditEvent<S>> editCancelEvent() {
 465         return (EventType<TreeTableView.EditEvent<S>>) EDIT_CANCEL_EVENT;
 466     }
 467     private static final EventType<?> EDIT_CANCEL_EVENT =
 468             new EventType<>(editAnyEvent(), "EDIT_CANCEL");
 469 
 470     /**
 471      * An EventType that is used to indicate that an edit in a TreeTableView has been
 472      * committed. This means that user has made changes to the data of a
 473      * TreeItem, and that the UI should be updated.
 474      *
 475      * @param <S> The type of the TreeItem instances used in this TreeTableView
 476      * @return An EventType that is used to indicate that an edit in a TreeTableView
 477      *      has been committed
 478      */
 479     @SuppressWarnings("unchecked")
 480     public static <S> EventType<TreeTableView.EditEvent<S>> editCommitEvent() {
 481         return (EventType<TreeTableView.EditEvent<S>>) EDIT_COMMIT_EVENT;
 482     }
 483     private static final EventType<?> EDIT_COMMIT_EVENT =
 484             new EventType<>(editAnyEvent(), "EDIT_COMMIT");
 485 
 486     /**
 487      * Returns the number of levels of 'indentation' of the given TreeItem,
 488      * based on how many times {@link javafx.scene.control.TreeItem#getParent()}
 489      * can be recursively called. If the TreeItem does not have any parent set,
 490      * the returned value will be zero. For each time getParent() is recursively
 491      * called, the returned value is incremented by one.
 492      *
 493      * <p><strong>Important note: </strong>This method is deprecated as it does
 494      * not consider the root node. This means that this method will iterate
 495      * past the root node of the TreeTableView control, if the root node has a parent.
 496      * If this is important, call {@link TreeTableView#getTreeItemLevel(TreeItem)}
 497      * instead.
 498      *
 499      * @param node The TreeItem for which the level is needed.
 500      * @return An integer representing the number of parents above the given node,
 501      *         or -1 if the given TreeItem is null.
 502      * @deprecated This method does not correctly calculate the distance from the
 503      *          given TreeItem to the root of the TreeTableView. As of JavaFX 8.0_20,
 504      *          the proper way to do this is via
 505      *          {@link TreeTableView#getTreeItemLevel(TreeItem)}
 506      */
 507     @Deprecated(since="8u20")
 508     public static int getNodeLevel(TreeItem<?> node) {
 509         return TreeView.getNodeLevel(node);
 510     }
 511 
 512     /**
 513      * <p>Very simple resize policy that just resizes the specified column by the
 514      * provided delta and shifts all other columns (to the right of the given column)
 515      * further to the right (when the delta is positive) or to the left (when the
 516      * delta is negative).
 517      *
 518      * <p>It also handles the case where we have nested columns by sharing the new space,
 519      * or subtracting the removed space, evenly between all immediate children columns.
 520      * Of course, the immediate children may themselves be nested, and they would
 521      * then use this policy on their children.
 522      */
 523     public static final Callback<TreeTableView.ResizeFeatures, Boolean> UNCONSTRAINED_RESIZE_POLICY =
 524             new Callback<TreeTableView.ResizeFeatures, Boolean>() {
 525 
 526         @Override public String toString() {
 527             return "unconstrained-resize";
 528         }
 529 
 530         @Override public Boolean call(TreeTableView.ResizeFeatures prop) {
 531             double result = TableUtil.resize(prop.getColumn(), prop.getDelta());
 532             return Double.compare(result, 0.0) == 0;
 533         }
 534     };
 535 
 536     /**
 537      * <p>Simple policy that ensures the width of all visible leaf columns in
 538      * this table sum up to equal the width of the table itself.
 539      *
 540      * <p>When the user resizes a column width with this policy, the table automatically
 541      * adjusts the width of the right hand side columns. When the user increases a
 542      * column width, the table decreases the width of the rightmost column until it
 543      * reaches its minimum width. Then it decreases the width of the second
 544      * rightmost column until it reaches minimum width and so on. When all right
 545      * hand side columns reach minimum size, the user cannot increase the size of
 546      * resized column any more.
 547      */
 548     public static final Callback<TreeTableView.ResizeFeatures, Boolean> CONSTRAINED_RESIZE_POLICY =
 549             new Callback<TreeTableView.ResizeFeatures, Boolean>() {
 550 
 551         private boolean isFirstRun = true;
 552 
 553         @Override public String toString() {
 554             return "constrained-resize";
 555         }
 556 
 557         @Override public Boolean call(TreeTableView.ResizeFeatures prop) {
 558             TreeTableView<?> table = prop.getTable();
 559             List<? extends TableColumnBase<?,?>> visibleLeafColumns = table.getVisibleLeafColumns();
 560             Boolean result = TableUtil.constrainedResize(prop,
 561                                                isFirstRun,
 562                                                table.contentWidth,
 563                                                visibleLeafColumns);
 564             isFirstRun = ! isFirstRun ? false : ! result;
 565             return result;
 566         }
 567     };
 568 
 569     /**
 570      * The default {@link #sortPolicyProperty() sort policy} that this TreeTableView
 571      * will use if no other policy is specified. The sort policy is a simple
 572      * {@link Callback} that accepts a TreeTableView as the sole argument and expects
 573      * a Boolean response representing whether the sort succeeded or not. A Boolean
 574      * response of true represents success, and a response of false (or null) will
 575      * be considered to represent failure.
 576      */
 577     public static final Callback<TreeTableView, Boolean> DEFAULT_SORT_POLICY = new Callback<TreeTableView, Boolean>() {
 578         @Override public Boolean call(TreeTableView table) {
 579             try {
 580                 TreeItem rootItem = table.getRoot();
 581                 if (rootItem == null) return false;
 582 
 583                 TreeSortMode sortMode = table.getSortMode();
 584                 if (sortMode == null) return false;
 585 
 586                 rootItem.lastSortMode = sortMode;
 587                 rootItem.lastComparator = table.getComparator();
 588                 rootItem.sort();
 589                 return true;
 590             } catch (UnsupportedOperationException e) {
 591                 // TODO might need to support other exception types including:
 592                 // ClassCastException - if the class of the specified element prevents it from being added to this list
 593                 // NullPointerException - if the specified element is null and this list does not permit null elements
 594                 // IllegalArgumentException - if some property of this element prevents it from being added to this list
 595 
 596                 // If we are here the list does not support sorting, so we gracefully
 597                 // fail the sort request and ensure the UI is put back to its previous
 598                 // state. This is handled in the code that calls the sort policy.
 599 
 600                 return false;
 601             }
 602         }
 603     };
 604 
 605 
 606 
 607     /***************************************************************************
 608      *                                                                         *
 609      * Instance Variables                                                      *
 610      *                                                                         *
 611      **************************************************************************/
 612 
 613     // used in the tree item modification event listener. Used by the
 614     // layoutChildren method to determine whether the tree item count should
 615     // be recalculated.
 616     private boolean expandedItemCountDirty = true;
 617 
 618     // Used in the getTreeItem(int row) method to act as a cache.
 619     // See RT-26716 for the justification and performance gains.
 620     private Map<Integer, SoftReference<TreeItem<S>>> treeItemCacheMap = new HashMap<>();
 621 
 622     // this is the only publicly writable list for columns. This represents the
 623     // columns as they are given initially by the developer.
 624     private final ObservableList<TreeTableColumn<S,?>> columns = FXCollections.observableArrayList();
 625 
 626     // Finally, as convenience, we also have an observable list that contains
 627     // only the leaf columns that are currently visible.
 628     private final ObservableList<TreeTableColumn<S,?>> visibleLeafColumns = FXCollections.observableArrayList();
 629     private final ObservableList<TreeTableColumn<S,?>> unmodifiableVisibleLeafColumns = FXCollections.unmodifiableObservableList(visibleLeafColumns);
 630 
 631     // Allows for multiple column sorting based on the order of the TreeTableColumns
 632     // in this observableArrayList. Each TreeTableColumn is responsible for whether it is
 633     // sorted using ascending or descending order.
 634     private ObservableList<TreeTableColumn<S,?>> sortOrder = FXCollections.observableArrayList();
 635 
 636     // width of VirtualFlow minus the vbar width
 637     // package protected for testing only
 638     double contentWidth;
 639 
 640     // Used to minimise the amount of work performed prior to the table being
 641     // completely initialised. In particular it reduces the amount of column
 642     // resize operations that occur, which slightly improves startup time.
 643     private boolean isInited = false;
 644 
 645 
 646 
 647     /***************************************************************************
 648      *                                                                         *
 649      * Callbacks and Events                                                    *
 650      *                                                                         *
 651      **************************************************************************/
 652 
 653     // we use this to forward events that have bubbled up TreeItem instances
 654     // to the TreeTableViewSkin, to force it to recalculate teh item count and redraw
 655     // if necessary
 656     private final EventHandler<TreeItem.TreeModificationEvent<S>> rootEvent = e -> {
 657         // this forces layoutChildren at the next pulse, and therefore
 658         // updates the item count if necessary
 659         EventType<?> eventType = e.getEventType();
 660         boolean match = false;
 661         while (eventType != null) {
 662             if (eventType.equals(TreeItem.<S>expandedItemCountChangeEvent())) {
 663                 match = true;
 664                 break;
 665             }
 666             eventType = eventType.getSuperType();
 667         }
 668 
 669         if (match) {
 670             expandedItemCountDirty = true;
 671             requestLayout();
 672         }
 673     };
 674 
 675     private final ListChangeListener<TreeTableColumn<S,?>> columnsObserver = new ListChangeListener<TreeTableColumn<S,?>>() {
 676         @Override public void onChanged(ListChangeListener.Change<? extends TreeTableColumn<S,?>> c) {
 677             final List<TreeTableColumn<S,?>> columns = getColumns();
 678 
 679             // Fix for RT-39822 - don't allow the same column to be installed twice
 680             while (c.next()) {
 681                 if (c.wasAdded()) {
 682                     List<TreeTableColumn<S,?>> duplicates = new ArrayList<>();
 683                     for (TreeTableColumn<S,?> addedColumn : c.getAddedSubList()) {
 684                         if (addedColumn == null) continue;
 685 
 686                         int count = 0;
 687                         for (TreeTableColumn<S,?> column : columns) {
 688                             if (addedColumn == column) {
 689                                 count++;
 690                             }
 691                         }
 692 
 693                         if (count > 1) {
 694                             duplicates.add(addedColumn);
 695                         }
 696                     }
 697 
 698                     if (!duplicates.isEmpty()) {
 699                         String titleList = "";
 700                         for (TreeTableColumn<S,?> dupe : duplicates) {
 701                             titleList += "'" + dupe.getText() + "', ";
 702                         }
 703                         throw new IllegalStateException("Duplicate TreeTableColumns detected in TreeTableView columns list with titles " + titleList);
 704                     }
 705                 }
 706             }
 707             c.reset();
 708 
 709             // Fix for RT-15194: Need to remove removed columns from the
 710             // sortOrder list.
 711             List<TreeTableColumn<S,?>> toRemove = new ArrayList<TreeTableColumn<S,?>>();
 712             while (c.next()) {
 713                 final List<? extends TreeTableColumn<S, ?>> removed = c.getRemoved();
 714                 final List<? extends TreeTableColumn<S, ?>> added = c.getAddedSubList();
 715 
 716                 if (c.wasRemoved()) {
 717                     toRemove.addAll(removed);
 718                     for (TreeTableColumn<S,?> tc : removed) {
 719                         tc.setTreeTableView(null);
 720                     }
 721                 }
 722 
 723                 if (c.wasAdded()) {
 724                     toRemove.removeAll(added);
 725                     for (TreeTableColumn<S,?> tc : added) {
 726                         tc.setTreeTableView(TreeTableView.this);
 727                     }
 728                 }
 729 
 730                 // set up listeners
 731                 TableUtil.removeColumnsListener(removed, weakColumnsObserver);
 732                 TableUtil.addColumnsListener(added, weakColumnsObserver);
 733 
 734                 TableUtil.removeTableColumnListener(c.getRemoved(),
 735                         weakColumnVisibleObserver,
 736                         weakColumnSortableObserver,
 737                         weakColumnSortTypeObserver,
 738                         weakColumnComparatorObserver);
 739                 TableUtil.addTableColumnListener(c.getAddedSubList(),
 740                         weakColumnVisibleObserver,
 741                         weakColumnSortableObserver,
 742                         weakColumnSortTypeObserver,
 743                         weakColumnComparatorObserver);
 744             }
 745 
 746             // We don't maintain a bind for leafColumns, we simply call this update
 747             // function behind the scenes in the appropriate places.
 748             updateVisibleLeafColumns();
 749 
 750             sortOrder.removeAll(toRemove);
 751 
 752             // Fix for RT-38892.
 753             final TreeTableViewFocusModel<S> fm = getFocusModel();
 754             final TreeTableViewSelectionModel<S> sm = getSelectionModel();
 755             c.reset();
 756 
 757             // we need to collect together all removed and all added columns, because
 758             // the code below works on the actually removed columns. If we perform
 759             // the code within this while loop, we'll be deselecting columns that
 760             // should be deselected (because they have just moved place, for example).
 761             List<TreeTableColumn<S,?>> removed = new ArrayList<>();
 762             List<TreeTableColumn<S,?>> added = new ArrayList<>();
 763             while (c.next()) {
 764                 if (c.wasRemoved()) {
 765                     removed.addAll(c.getRemoved());
 766                 }
 767                 if (c.wasAdded()) {
 768                     added.addAll(c.getAddedSubList());
 769                 }
 770             }
 771             removed.removeAll(added);
 772 
 773 
 774             // Fix for focus - we simply move focus to a cell to the left
 775             // of the focused cell if the focused cell was located within
 776             // a column that has been removed.
 777             if (fm != null) {
 778                 TreeTablePosition<S, ?> focusedCell = fm.getFocusedCell();
 779                 boolean match = false;
 780                 for (TreeTableColumn<S, ?> tc : removed) {
 781                     match = focusedCell != null && focusedCell.getTableColumn() == tc;
 782                     if (match) {
 783                         break;
 784                     }
 785                 }
 786 
 787                 if (match) {
 788                     int matchingColumnIndex = lastKnownColumnIndex.getOrDefault(focusedCell.getTableColumn(), 0);
 789                     int newFocusColumnIndex =
 790                             matchingColumnIndex == 0 ? 0 :
 791                                     Math.min(getVisibleLeafColumns().size() - 1, matchingColumnIndex - 1);
 792                     fm.focus(focusedCell.getRow(), getVisibleLeafColumn(newFocusColumnIndex));
 793                 }
 794             }
 795 
 796             // Fix for selection - we remove selection from all cells that
 797             // were within the removed column.
 798             if (sm != null) {
 799                 List<TreeTablePosition> selectedCells = new ArrayList<>(sm.getSelectedCells());
 800                 for (TreeTablePosition selectedCell : selectedCells) {
 801                     boolean match = false;
 802                     for (TreeTableColumn<S, ?> tc : removed) {
 803                         match = selectedCell != null && selectedCell.getTableColumn() == tc;
 804                         if (match) break;
 805                     }
 806 
 807                     if (match) {
 808                         // we can't just use the selectedCell.getTableColumn(), as that
 809                         // column no longer exists and therefore its index is not correct.
 810                         int matchingColumnIndex = lastKnownColumnIndex.getOrDefault(selectedCell.getTableColumn(), -1);
 811                         if (matchingColumnIndex == -1) continue;
 812 
 813                         if (sm instanceof TreeTableViewArrayListSelectionModel) {
 814                             // Also, because the table column no longer exists in the columns
 815                             // list at this point, we can't just call:
 816                             // sm.clearSelection(selectedCell.getRow(), selectedCell.getTableColumn());
 817                             // as the tableColumn would map to an index of -1, which means that
 818                             // selection will not be cleared. Instead, we have to create
 819                             // a new TablePosition with a fixed column index and use that.
 820                             TreeTablePosition<S,?> fixedTablePosition =
 821                                     new TreeTablePosition<S,Object>(TreeTableView.this,
 822                                             selectedCell.getRow(),
 823                                             selectedCell.getTableColumn());
 824                             fixedTablePosition.fixedColumnIndex = matchingColumnIndex;
 825 
 826                             ((TreeTableViewArrayListSelectionModel)sm).clearSelection(fixedTablePosition);
 827                         } else {
 828                             sm.clearSelection(selectedCell.getRow(), selectedCell.getTableColumn());
 829                         }
 830                     }
 831                 }
 832             }
 833 
 834 
 835             // update the lastKnownColumnIndex map
 836             lastKnownColumnIndex.clear();
 837             for (TreeTableColumn<S,?> tc : getColumns()) {
 838                 int index = getVisibleLeafIndex(tc);
 839                 if (index > -1) {
 840                     lastKnownColumnIndex.put(tc, index);
 841                 }
 842             }
 843         }
 844     };
 845 
 846     private final WeakHashMap<TreeTableColumn<S,?>, Integer> lastKnownColumnIndex = new WeakHashMap<>();
 847 
 848     private final InvalidationListener columnVisibleObserver = valueModel -> {
 849         updateVisibleLeafColumns();
 850     };
 851 
 852     private final InvalidationListener columnSortableObserver = valueModel -> {
 853         TreeTableColumn col = (TreeTableColumn) ((BooleanProperty)valueModel).getBean();
 854         if (! getSortOrder().contains(col)) return;
 855         doSort(TableUtil.SortEventType.COLUMN_SORTABLE_CHANGE, col);
 856     };
 857 
 858     private final InvalidationListener columnSortTypeObserver = valueModel -> {
 859         TreeTableColumn col = (TreeTableColumn) ((ObjectProperty)valueModel).getBean();
 860         if (! getSortOrder().contains(col)) return;
 861         doSort(TableUtil.SortEventType.COLUMN_SORT_TYPE_CHANGE, col);
 862     };
 863 
 864     private final InvalidationListener columnComparatorObserver = valueModel -> {
 865         TreeTableColumn col = (TreeTableColumn) ((SimpleObjectProperty)valueModel).getBean();
 866         if (! getSortOrder().contains(col)) return;
 867         doSort(TableUtil.SortEventType.COLUMN_COMPARATOR_CHANGE, col);
 868     };
 869 
 870     /* proxy pseudo-class state change from selectionModel's cellSelectionEnabledProperty */
 871     private final InvalidationListener cellSelectionModelInvalidationListener = o -> {
 872         boolean isCellSelection = ((BooleanProperty)o).get();
 873         pseudoClassStateChanged(PSEUDO_CLASS_CELL_SELECTION,  isCellSelection);
 874         pseudoClassStateChanged(PSEUDO_CLASS_ROW_SELECTION,  !isCellSelection);
 875     };
 876 
 877     private InvalidationListener focusOwnerListener = o -> {
 878         if (!ControlUtils.isFocusOnNodeOrAnyChild(this)) {
 879             edit(-1, null);
 880         }
 881     };
 882     private WeakInvalidationListener weakFocusOwnerListener = new WeakInvalidationListener(focusOwnerListener);
 883 
 884     private WeakEventHandler<TreeItem.TreeModificationEvent<S>> weakRootEventListener;
 885 
 886     private final WeakInvalidationListener weakColumnVisibleObserver =
 887             new WeakInvalidationListener(columnVisibleObserver);
 888 
 889     private final WeakInvalidationListener weakColumnSortableObserver =
 890             new WeakInvalidationListener(columnSortableObserver);
 891 
 892     private final WeakInvalidationListener weakColumnSortTypeObserver =
 893             new WeakInvalidationListener(columnSortTypeObserver);
 894 
 895     private final WeakInvalidationListener weakColumnComparatorObserver =
 896             new WeakInvalidationListener(columnComparatorObserver);
 897 
 898     private final WeakListChangeListener<TreeTableColumn<S,?>> weakColumnsObserver =
 899             new WeakListChangeListener<TreeTableColumn<S,?>>(columnsObserver);
 900 
 901     private final WeakInvalidationListener weakCellSelectionModelInvalidationListener =
 902             new WeakInvalidationListener(cellSelectionModelInvalidationListener);
 903 
 904 
 905 
 906     /***************************************************************************
 907      *                                                                         *
 908      * Properties                                                              *
 909      *                                                                         *
 910      **************************************************************************/
 911 
 912     // --- Root
 913     private ObjectProperty<TreeItem<S>> root = new SimpleObjectProperty<TreeItem<S>>(this, "root") {
 914         private WeakReference<TreeItem<S>> weakOldItem;
 915 
 916         @Override protected void invalidated() {
 917             TreeItem<S> oldTreeItem = weakOldItem == null ? null : weakOldItem.get();
 918             if (oldTreeItem != null && weakRootEventListener != null) {
 919                 oldTreeItem.removeEventHandler(TreeItem.<S>treeNotificationEvent(), weakRootEventListener);
 920             }
 921 
 922             TreeItem<S> root = getRoot();
 923             if (root != null) {
 924                 weakRootEventListener = new WeakEventHandler<>(rootEvent);
 925                 getRoot().addEventHandler(TreeItem.<S>treeNotificationEvent(), weakRootEventListener);
 926                 weakOldItem = new WeakReference<>(root);
 927             }
 928 
 929             // Fix for RT-35763
 930             getSortOrder().clear();
 931 
 932             expandedItemCountDirty = true;
 933             updateRootExpanded();
 934         }
 935     };
 936 
 937     /**
 938      * Sets the root node in this TreeTableView. See the {@link TreeItem} class level
 939      * documentation for more details.
 940      *
 941      * @param value The {@link TreeItem} that will be placed at the root of the
 942      *      TreeTableView.
 943      */
 944     public final void setRoot(TreeItem<S> value) {
 945         rootProperty().set(value);
 946     }
 947 
 948     /**
 949      * Returns the current root node of this TreeTableView, or null if no root node
 950      * is specified.
 951      * @return The current root node, or null if no root node exists.
 952      */
 953     public final TreeItem<S> getRoot() {
 954         return root == null ? null : root.get();
 955     }
 956 
 957     /**
 958      * Property representing the root node of the TreeTableView.
 959      * @return the root property
 960      */
 961     public final ObjectProperty<TreeItem<S>> rootProperty() {
 962         return root;
 963     }
 964 
 965 
 966 
 967     // --- Show Root
 968     private BooleanProperty showRoot;
 969 
 970     /**
 971      * Specifies whether the root {@code TreeItem} should be shown within this
 972      * TreeTableView.
 973      *
 974      * @param value If true, the root TreeItem will be shown, and if false it
 975      *      will be hidden.
 976      */
 977     public final void setShowRoot(boolean value) {
 978         showRootProperty().set(value);
 979     }
 980 
 981     /**
 982      * Returns true if the root of the TreeTableView should be shown, and false if
 983      * it should not. By default, the root TreeItem is visible in the TreeTableView.
 984      * @return true if the root of the TreeTableView should be shown
 985      */
 986     public final boolean isShowRoot() {
 987         return showRoot == null ? true : showRoot.get();
 988     }
 989 
 990     /**
 991      * Property that represents whether or not the TreeTableView root node is visible.
 992      * @return the show root property
 993      */
 994     public final BooleanProperty showRootProperty() {
 995         if (showRoot == null) {
 996             showRoot = new SimpleBooleanProperty(this, "showRoot", true) {
 997                 @Override protected void invalidated() {
 998                     updateRootExpanded();
 999                     updateExpandedItemCount(getRoot());
1000                 }
1001             };
1002         }
1003         return showRoot;
1004     }
1005 
1006 
1007 
1008     // --- Tree Column
1009     private ObjectProperty<TreeTableColumn<S,?>> treeColumn;
1010     /**
1011      * Property that represents which column should have the disclosure node
1012      * shown in it (that is, the column with the arrow). By default this will be
1013      * the left-most column if this property is null, otherwise it will be the
1014      * specified column assuming it is non-null and contained within the
1015      * {@link #getVisibleLeafColumns() visible leaf columns} list.
1016      * @return the tree column property
1017      */
1018     public final ObjectProperty<TreeTableColumn<S,?>> treeColumnProperty() {
1019         if (treeColumn == null) {
1020             treeColumn = new SimpleObjectProperty<>(this, "treeColumn", null);
1021         }
1022         return treeColumn;
1023     }
1024     public final void setTreeColumn(TreeTableColumn<S,?> value) {
1025         treeColumnProperty().set(value);
1026     }
1027     public final TreeTableColumn<S,?> getTreeColumn() {
1028         return treeColumn == null ? null : treeColumn.get();
1029     }
1030 
1031 
1032 
1033     // --- Selection Model
1034     private ObjectProperty<TreeTableViewSelectionModel<S>> selectionModel;
1035 
1036     /**
1037      * Sets the {@link MultipleSelectionModel} to be used in the TreeTableView.
1038      * Despite a TreeTableView requiring a <code><b>Multiple</b>SelectionModel</code>,
1039      * it is possible to configure it to only allow single selection (see
1040      * {@link MultipleSelectionModel#setSelectionMode(javafx.scene.control.SelectionMode)}
1041      * for more information).
1042      * @param value the {@link MultipleSelectionModel} to be used
1043      */
1044     public final void setSelectionModel(TreeTableViewSelectionModel<S> value) {
1045         selectionModelProperty().set(value);
1046     }
1047 
1048     /**
1049      * Returns the currently installed selection model.
1050      * @return the currently installed selection model
1051      */
1052     public final TreeTableViewSelectionModel<S> getSelectionModel() {
1053         return selectionModel == null ? null : selectionModel.get();
1054     }
1055 
1056     /**
1057      * The SelectionModel provides the API through which it is possible
1058      * to select single or multiple items within a TreeTableView, as  well as inspect
1059      * which rows have been selected by the user. Note that it has a generic
1060      * type that must match the type of the TreeTableView itself.
1061      * @return the selection model property
1062      */
1063     public final ObjectProperty<TreeTableViewSelectionModel<S>> selectionModelProperty() {
1064         if (selectionModel == null) {
1065             selectionModel = new SimpleObjectProperty<TreeTableViewSelectionModel<S>>(this, "selectionModel") {
1066 
1067                 TreeTableViewSelectionModel<S> oldValue = null;
1068 
1069                 @Override protected void invalidated() {
1070                     // need to listen to the cellSelectionEnabledProperty
1071                     // in order to set pseudo-class state
1072                     if (oldValue != null) {
1073                         oldValue.cellSelectionEnabledProperty().removeListener(weakCellSelectionModelInvalidationListener);
1074 
1075                         if (oldValue instanceof TreeTableViewArrayListSelectionModel) {
1076                             ((TreeTableViewArrayListSelectionModel)oldValue).dispose();
1077                         }
1078                     }
1079 
1080                     oldValue = get();
1081 
1082                     if (oldValue != null) {
1083                         oldValue.cellSelectionEnabledProperty().addListener(weakCellSelectionModelInvalidationListener);
1084                         // fake invalidation to ensure updated pseudo-class states
1085                         weakCellSelectionModelInvalidationListener.invalidated(oldValue.cellSelectionEnabledProperty());
1086                     }
1087                 }
1088             };
1089         }
1090         return selectionModel;
1091     }
1092 
1093 
1094     // --- Focus Model
1095     private ObjectProperty<TreeTableViewFocusModel<S>> focusModel;
1096 
1097     /**
1098      * Sets the {@link FocusModel} to be used in the TreeTableView.
1099      * @param value the {@link FocusModel} to be used
1100      */
1101     public final void setFocusModel(TreeTableViewFocusModel<S> value) {
1102         focusModelProperty().set(value);
1103     }
1104 
1105     /**
1106      * Returns the currently installed {@link FocusModel}.
1107      * @return the currently installed {@link FocusModel}
1108      */
1109     public final TreeTableViewFocusModel<S> getFocusModel() {
1110         return focusModel == null ? null : focusModel.get();
1111     }
1112 
1113     /**
1114      * The FocusModel provides the API through which it is possible
1115      * to control focus on zero or one rows of the TreeTableView. Generally the
1116      * default implementation should be more than sufficient.
1117      * @return the focus model property
1118      */
1119     public final ObjectProperty<TreeTableViewFocusModel<S>> focusModelProperty() {
1120         if (focusModel == null) {
1121             focusModel = new SimpleObjectProperty<TreeTableViewFocusModel<S>>(this, "focusModel");
1122         }
1123         return focusModel;
1124     }
1125 
1126 
1127     // --- Tree node count
1128     /**
1129      * <p>Represents the number of tree nodes presently able to be visible in the
1130      * TreeTableView. This is essentially the count of all expanded tree items, and
1131      * their children.
1132      *
1133      * <p>For example, if just the root node is visible, the expandedItemCount will
1134      * be one. If the root had three children and the root was expanded, the value
1135      * will be four.
1136      */
1137     private ReadOnlyIntegerWrapper expandedItemCount = new ReadOnlyIntegerWrapper(this, "expandedItemCount", 0);
1138     public final ReadOnlyIntegerProperty expandedItemCountProperty() {
1139         return expandedItemCount.getReadOnlyProperty();
1140     }
1141     private void setExpandedItemCount(int value) {
1142         expandedItemCount.set(value);
1143     }
1144     public final int getExpandedItemCount() {
1145         if (expandedItemCountDirty) {
1146             updateExpandedItemCount(getRoot());
1147         }
1148         return expandedItemCount.get();
1149     }
1150 
1151 
1152     // --- Editable
1153     private BooleanProperty editable;
1154     public final void setEditable(boolean value) {
1155         editableProperty().set(value);
1156     }
1157     public final boolean isEditable() {
1158         return editable == null ? false : editable.get();
1159     }
1160     /**
1161      * Specifies whether this TreeTableView is editable - only if the TreeTableView and
1162      * the TreeCells within it are both editable will a TreeCell be able to go
1163      * into their editing state.
1164      * @return the editable property
1165      */
1166     public final BooleanProperty editableProperty() {
1167         if (editable == null) {
1168             editable = new SimpleBooleanProperty(this, "editable", false);
1169         }
1170         return editable;
1171     }
1172 
1173 
1174     // --- Editing Cell
1175     private ReadOnlyObjectWrapper<TreeTablePosition<S,?>> editingCell;
1176     private void setEditingCell(TreeTablePosition<S,?> value) {
1177         editingCellPropertyImpl().set(value);
1178     }
1179     public final TreeTablePosition<S,?> getEditingCell() {
1180         return editingCell == null ? null : editingCell.get();
1181     }
1182 
1183     /**
1184      * Represents the current cell being edited, or null if
1185      * there is no cell being edited.
1186      * @return the editing cell property
1187      */
1188     public final ReadOnlyObjectProperty<TreeTablePosition<S,?>> editingCellProperty() {
1189         return editingCellPropertyImpl().getReadOnlyProperty();
1190     }
1191 
1192     private ReadOnlyObjectWrapper<TreeTablePosition<S,?>> editingCellPropertyImpl() {
1193         if (editingCell == null) {
1194             editingCell = new ReadOnlyObjectWrapper<TreeTablePosition<S,?>>(this, "editingCell");
1195         }
1196         return editingCell;
1197     }
1198 
1199 
1200     // --- Table menu button visible
1201     private BooleanProperty tableMenuButtonVisible;
1202     /**
1203      * This controls whether a menu button is available when the user clicks
1204      * in a designated space within the TableView, within which is a radio menu
1205      * item for each TreeTableColumn in this table. This menu allows for the user to
1206      * show and hide all TreeTableColumns easily.
1207      * @return the table menu button visible property
1208      */
1209     public final BooleanProperty tableMenuButtonVisibleProperty() {
1210         if (tableMenuButtonVisible == null) {
1211             tableMenuButtonVisible = new SimpleBooleanProperty(this, "tableMenuButtonVisible");
1212         }
1213         return tableMenuButtonVisible;
1214     }
1215     public final void setTableMenuButtonVisible (boolean value) {
1216         tableMenuButtonVisibleProperty().set(value);
1217     }
1218     public final boolean isTableMenuButtonVisible() {
1219         return tableMenuButtonVisible == null ? false : tableMenuButtonVisible.get();
1220     }
1221 
1222 
1223     // --- Column Resize Policy
1224     private ObjectProperty<Callback<TreeTableView.ResizeFeatures, Boolean>> columnResizePolicy;
1225     public final void setColumnResizePolicy(Callback<TreeTableView.ResizeFeatures, Boolean> callback) {
1226         columnResizePolicyProperty().set(callback);
1227     }
1228     public final Callback<TreeTableView.ResizeFeatures, Boolean> getColumnResizePolicy() {
1229         return columnResizePolicy == null ? UNCONSTRAINED_RESIZE_POLICY : columnResizePolicy.get();
1230     }
1231 
1232     /**
1233      * This is the function called when the user completes a column-resize
1234      * operation. The two most common policies are available as static functions
1235      * in the TableView class: {@link #UNCONSTRAINED_RESIZE_POLICY} and
1236      * {@link #CONSTRAINED_RESIZE_POLICY}.
1237      * @return the column resize policy property
1238      */
1239     public final ObjectProperty<Callback<TreeTableView.ResizeFeatures, Boolean>> columnResizePolicyProperty() {
1240         if (columnResizePolicy == null) {
1241             columnResizePolicy = new SimpleObjectProperty<Callback<TreeTableView.ResizeFeatures, Boolean>>(this, "columnResizePolicy", UNCONSTRAINED_RESIZE_POLICY) {
1242                 private Callback<TreeTableView.ResizeFeatures, Boolean> oldPolicy;
1243 
1244                 @Override protected void invalidated() {
1245                     if (isInited) {
1246                         get().call(new TreeTableView.ResizeFeatures(TreeTableView.this, null, 0.0));
1247 
1248                         if (oldPolicy != null) {
1249                             PseudoClass state = PseudoClass.getPseudoClass(oldPolicy.toString());
1250                             pseudoClassStateChanged(state, false);
1251                         }
1252                         if (get() != null) {
1253                             PseudoClass state = PseudoClass.getPseudoClass(get().toString());
1254                             pseudoClassStateChanged(state, true);
1255                         }
1256                         oldPolicy = get();
1257                     }
1258                 }
1259             };
1260         }
1261         return columnResizePolicy;
1262     }
1263 
1264 
1265     // --- Row Factory
1266     private ObjectProperty<Callback<TreeTableView<S>, TreeTableRow<S>>> rowFactory;
1267 
1268     /**
1269      * A function which produces a TreeTableRow. The system is responsible for
1270      * reusing TreeTableRows. Return from this function a TreeTableRow which
1271      * might be usable for representing a single row in a TableView.
1272      * <p>
1273      * Note that a TreeTableRow is <b>not</b> a TableCell. A TreeTableRow is
1274      * simply a container for a TableCell, and in most circumstances it is more
1275      * likely that you'll want to create custom TableCells, rather than
1276      * TreeTableRows. The primary use case for creating custom TreeTableRow
1277      * instances would most probably be to introduce some form of column
1278      * spanning support.
1279      * <p>
1280      * You can create custom TableCell instances per column by assigning the
1281      * appropriate function to the cellFactory property in the TreeTableColumn class.
1282      * @return the row factory property
1283      */
1284     public final ObjectProperty<Callback<TreeTableView<S>, TreeTableRow<S>>> rowFactoryProperty() {
1285         if (rowFactory == null) {
1286             rowFactory = new SimpleObjectProperty<Callback<TreeTableView<S>, TreeTableRow<S>>>(this, "rowFactory");
1287         }
1288         return rowFactory;
1289     }
1290     public final void setRowFactory(Callback<TreeTableView<S>, TreeTableRow<S>> value) {
1291         rowFactoryProperty().set(value);
1292     }
1293     public final Callback<TreeTableView<S>, TreeTableRow<S>> getRowFactory() {
1294         return rowFactory == null ? null : rowFactory.get();
1295     }
1296 
1297 
1298     // --- Placeholder Node
1299     private ObjectProperty<Node> placeholder;
1300     /**
1301      * This Node is shown to the user when the table has no content to show.
1302      * This may be the case because the table model has no data in the first
1303      * place, that a filter has been applied to the table model, resulting
1304      * in there being nothing to show the user, or that there are no currently
1305      * visible columns.
1306      * @return the placeholder property
1307      */
1308     public final ObjectProperty<Node> placeholderProperty() {
1309         if (placeholder == null) {
1310             placeholder = new SimpleObjectProperty<Node>(this, "placeholder");
1311         }
1312         return placeholder;
1313     }
1314     public final void setPlaceholder(Node value) {
1315         placeholderProperty().set(value);
1316     }
1317     public final Node getPlaceholder() {
1318         return placeholder == null ? null : placeholder.get();
1319     }
1320 
1321 
1322     // --- Fixed cell size
1323     private DoubleProperty fixedCellSize;
1324 
1325     /**
1326      * Sets the new fixed cell size for this control. Any value greater than
1327      * zero will enable fixed cell size mode, whereas a zero or negative value
1328      * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size
1329      * mode.
1330      *
1331      * @param value The new fixed cell size value, or a value less than or equal
1332      *              to zero (or Region.USE_COMPUTED_SIZE) to disable.
1333      * @since JavaFX 8.0
1334      */
1335     public final void setFixedCellSize(double value) {
1336         fixedCellSizeProperty().set(value);
1337     }
1338 
1339     /**
1340      * Returns the fixed cell size value. A value less than or equal to zero is
1341      * used to represent that fixed cell size mode is disabled, and a value
1342      * greater than zero represents the size of all cells in this control.
1343      *
1344      * @return A double representing the fixed cell size of this control, or a
1345      *      value less than or equal to zero if fixed cell size mode is disabled.
1346      * @since JavaFX 8.0
1347      */
1348     public final double getFixedCellSize() {
1349         return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get();
1350     }
1351     /**
1352      * Specifies whether this control has cells that are a fixed height (of the
1353      * specified value). If this value is less than or equal to zero,
1354      * then all cells are individually sized and positioned. This is a slow
1355      * operation. Therefore, when performance matters and developers are not
1356      * dependent on variable cell sizes it is a good idea to set the fixed cell
1357      * size value. Generally cells are around 24px, so setting a fixed cell size
1358      * of 24 is likely to result in very little difference in visuals, but a
1359      * improvement to performance.
1360      *
1361      * <p>To set this property via CSS, use the -fx-fixed-cell-size property.
1362      * This should not be confused with the -fx-cell-size property. The difference
1363      * between these two CSS properties is that -fx-cell-size will size all
1364      * cells to the specified size, but it will not enforce that this is the
1365      * only size (thus allowing for variable cell sizes, and preventing the
1366      * performance gains from being possible). Therefore, when performance matters
1367      * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are
1368      * specified in CSS, -fx-fixed-cell-size takes precedence.</p>
1369      *
1370      * @return the fixed cell size property
1371      * @since JavaFX 8.0
1372      */
1373     public final DoubleProperty fixedCellSizeProperty() {
1374         if (fixedCellSize == null) {
1375             fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) {
1376                 @Override public CssMetaData<TreeTableView<?>,Number> getCssMetaData() {
1377                     return StyleableProperties.FIXED_CELL_SIZE;
1378                 }
1379 
1380                 @Override public Object getBean() {
1381                     return TreeTableView.this;
1382                 }
1383 
1384                 @Override public String getName() {
1385                     return "fixedCellSize";
1386                 }
1387             };
1388         }
1389         return fixedCellSize;
1390     }
1391 
1392 
1393     // --- SortMode
1394     /**
1395      * Specifies the sort mode to use when sorting the contents of this TreeTableView,
1396      * should any columns be specified in the {@link #getSortOrder() sort order}
1397      * list.
1398      */
1399     private ObjectProperty<TreeSortMode> sortMode;
1400     public final ObjectProperty<TreeSortMode> sortModeProperty() {
1401         if (sortMode == null) {
1402             sortMode = new SimpleObjectProperty<>(this, "sortMode", TreeSortMode.ALL_DESCENDANTS);
1403         }
1404         return sortMode;
1405     }
1406     public final void setSortMode(TreeSortMode value) {
1407         sortModeProperty().set(value);
1408     }
1409     public final TreeSortMode getSortMode() {
1410         return sortMode == null ? TreeSortMode.ALL_DESCENDANTS : sortMode.get();
1411     }
1412 
1413 
1414     // --- Comparator (built via sortOrder list, so read-only)
1415     /**
1416      * The comparator property is a read-only property that is representative of the
1417      * current state of the {@link #getSortOrder() sort order} list. The sort
1418      * order list contains the columns that have been added to it either programmatically
1419      * or via a user clicking on the headers themselves.
1420      */
1421     private ReadOnlyObjectWrapper<Comparator<TreeItem<S>>> comparator;
1422     private void setComparator(Comparator<TreeItem<S>> value) {
1423         comparatorPropertyImpl().set(value);
1424     }
1425     public final Comparator<TreeItem<S>> getComparator() {
1426         return comparator == null ? null : comparator.get();
1427     }
1428     public final ReadOnlyObjectProperty<Comparator<TreeItem<S>>> comparatorProperty() {
1429         return comparatorPropertyImpl().getReadOnlyProperty();
1430     }
1431     private ReadOnlyObjectWrapper<Comparator<TreeItem<S>>> comparatorPropertyImpl() {
1432         if (comparator == null) {
1433             comparator = new ReadOnlyObjectWrapper<>(this, "comparator");
1434         }
1435         return comparator;
1436     }
1437 
1438 
1439     // --- sortPolicy
1440     /**
1441      * The sort policy specifies how sorting in this TreeTableView should be performed.
1442      * For example, a basic sort policy may just recursively sort the children of
1443      * the root tree item, whereas a more advanced sort policy may call to a
1444      * database to perform the necessary sorting on the server-side.
1445      *
1446      * <p>TreeTableView ships with a {@link TableView#DEFAULT_SORT_POLICY default
1447      * sort policy} that does precisely as mentioned above: it simply attempts
1448      * to sort the tree hierarchy in-place.
1449      *
1450      * <p>It is recommended that rather than override the {@link TreeTableView#sort() sort}
1451      * method that a different sort policy be provided instead.
1452      */
1453     private ObjectProperty<Callback<TreeTableView<S>, Boolean>> sortPolicy;
1454     public final void setSortPolicy(Callback<TreeTableView<S>, Boolean> callback) {
1455         sortPolicyProperty().set(callback);
1456     }
1457     @SuppressWarnings("unchecked")
1458     public final Callback<TreeTableView<S>, Boolean> getSortPolicy() {
1459         return sortPolicy == null ?
1460                 (Callback<TreeTableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY :
1461                 sortPolicy.get();
1462     }
1463     @SuppressWarnings("unchecked")
1464     public final ObjectProperty<Callback<TreeTableView<S>, Boolean>> sortPolicyProperty() {
1465         if (sortPolicy == null) {
1466             sortPolicy = new SimpleObjectProperty<Callback<TreeTableView<S>, Boolean>>(
1467                     this, "sortPolicy", (Callback<TreeTableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY) {
1468                 @Override protected void invalidated() {
1469                     sort();
1470                 }
1471             };
1472         }
1473         return sortPolicy;
1474     }
1475 
1476 
1477     // onSort
1478     /**
1479      * Called when there's a request to sort the control.
1480      */
1481     private ObjectProperty<EventHandler<SortEvent<TreeTableView<S>>>> onSort;
1482 
1483     public void setOnSort(EventHandler<SortEvent<TreeTableView<S>>> value) {
1484         onSortProperty().set(value);
1485     }
1486 
1487     public EventHandler<SortEvent<TreeTableView<S>>> getOnSort() {
1488         if( onSort != null ) {
1489             return onSort.get();
1490         }
1491         return null;
1492     }
1493 
1494     public ObjectProperty<EventHandler<SortEvent<TreeTableView<S>>>> onSortProperty() {
1495         if( onSort == null ) {
1496             onSort = new ObjectPropertyBase<EventHandler<SortEvent<TreeTableView<S>>>>() {
1497                 @Override protected void invalidated() {
1498                     EventType<SortEvent<TreeTableView<S>>> eventType = SortEvent.sortEvent();
1499                     EventHandler<SortEvent<TreeTableView<S>>> eventHandler = get();
1500                     setEventHandler(eventType, eventHandler);
1501                 }
1502 
1503                 @Override public Object getBean() {
1504                     return TreeTableView.this;
1505                 }
1506 
1507                 @Override public String getName() {
1508                     return "onSort";
1509                 }
1510             };
1511         }
1512         return onSort;
1513     }
1514 
1515 
1516 
1517     /***************************************************************************
1518      *                                                                         *
1519      * Public API                                                              *
1520      *                                                                         *
1521      **************************************************************************/
1522 
1523     /** {@inheritDoc} */
1524     @Override protected void layoutChildren() {
1525         if (expandedItemCountDirty) {
1526             updateExpandedItemCount(getRoot());
1527         }
1528 
1529         super.layoutChildren();
1530     }
1531 
1532     /**
1533      * Scrolls the TreeTableView such that the item in the given index is visible to
1534      * the end user.
1535      *
1536      * @param index The index that should be made visible to the user, assuming
1537      *      of course that it is greater than, or equal to 0, and less than the
1538      *      number of the visible items in the TreeTableView.
1539      */
1540     public void scrollTo(int index) {
1541         ControlUtils.scrollToIndex(this, index);
1542     }
1543 
1544     /**
1545      * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
1546      */
1547     private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
1548 
1549     public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
1550         onScrollToProperty().set(value);
1551     }
1552 
1553     public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
1554         if( onScrollTo != null ) {
1555             return onScrollTo.get();
1556         }
1557         return null;
1558     }
1559 
1560     public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
1561         if( onScrollTo == null ) {
1562             onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
1563                 @Override protected void invalidated() {
1564                     setEventHandler(ScrollToEvent.scrollToTopIndex(), get());
1565                 }
1566 
1567                 @Override public Object getBean() {
1568                     return TreeTableView.this;
1569                 }
1570 
1571                 @Override public String getName() {
1572                     return "onScrollTo";
1573                 }
1574             };
1575         }
1576         return onScrollTo;
1577     }
1578 
1579     /**
1580      * Scrolls the TreeTableView so that the given column is visible within the viewport.
1581      * @param column The column that should be visible to the user.
1582      */
1583     public void scrollToColumn(TreeTableColumn<S, ?> column) {
1584         ControlUtils.scrollToColumn(this, column);
1585     }
1586 
1587     /**
1588      * Scrolls the TreeTableView so that the given index is visible within the viewport.
1589      * @param columnIndex The index of a column that should be visible to the user.
1590      */
1591     public void scrollToColumnIndex(int columnIndex) {
1592         if( getColumns() != null ) {
1593             ControlUtils.scrollToColumn(this, getColumns().get(columnIndex));
1594         }
1595     }
1596 
1597     /**
1598      * Called when there's a request to scroll a column into view using {@link #scrollToColumn(TreeTableColumn)}
1599      * or {@link #scrollToColumnIndex(int)}
1600      */
1601     private ObjectProperty<EventHandler<ScrollToEvent<TreeTableColumn<S, ?>>>> onScrollToColumn;
1602 
1603     public void setOnScrollToColumn(EventHandler<ScrollToEvent<TreeTableColumn<S, ?>>> value) {
1604         onScrollToColumnProperty().set(value);
1605     }
1606 
1607     public EventHandler<ScrollToEvent<TreeTableColumn<S, ?>>> getOnScrollToColumn() {
1608         if( onScrollToColumn != null ) {
1609             return onScrollToColumn.get();
1610         }
1611         return null;
1612     }
1613 
1614     public ObjectProperty<EventHandler<ScrollToEvent<TreeTableColumn<S, ?>>>> onScrollToColumnProperty() {
1615         if( onScrollToColumn == null ) {
1616             onScrollToColumn = new ObjectPropertyBase<EventHandler<ScrollToEvent<TreeTableColumn<S, ?>>>>() {
1617                 @Override
1618                 protected void invalidated() {
1619                     EventType<ScrollToEvent<TreeTableColumn<S, ?>>> type = ScrollToEvent.scrollToColumn();
1620                     setEventHandler(type, get());
1621                 }
1622                 @Override
1623                 public Object getBean() {
1624                     return TreeTableView.this;
1625                 }
1626 
1627                 @Override
1628                 public String getName() {
1629                     return "onScrollToColumn";
1630                 }
1631             };
1632         }
1633         return onScrollToColumn;
1634     }
1635 
1636     /**
1637      * Returns the index position of the given TreeItem, assuming that it is
1638      * currently accessible through the tree hierarchy (most notably, that all
1639      * parent tree items are expanded). If a parent tree item is collapsed,
1640      * the result is that this method will return -1 to indicate that the
1641      * given tree item is not accessible in the tree.
1642      *
1643      * @param item The TreeItem for which the index is sought.
1644      * @return An integer representing the location in the current TreeTableView of the
1645      *      first instance of the given TreeItem, or -1 if it is null or can not
1646      *      be found (for example, if a parent (all the way up to the root) is
1647      *      collapsed).
1648      */
1649     public int getRow(TreeItem<S> item) {
1650         return TreeUtil.getRow(item, getRoot(), expandedItemCountDirty, isShowRoot());
1651     }
1652 
1653     /**
1654      * Returns the TreeItem in the given index, or null if it is out of bounds.
1655      *
1656      * @param row The index of the TreeItem being sought.
1657      * @return The TreeItem in the given index, or null if it is out of bounds.
1658      */
1659     public TreeItem<S> getTreeItem(int row) {
1660         if (row < 0) return null;
1661 
1662         // normalize the requested row based on whether showRoot is set
1663         final int _row = isShowRoot() ? row : (row + 1);
1664 
1665         if (expandedItemCountDirty) {
1666             updateExpandedItemCount(getRoot());
1667         } else {
1668             if (treeItemCacheMap.containsKey(_row)) {
1669                 SoftReference<TreeItem<S>> treeItemRef = treeItemCacheMap.get(_row);
1670                 TreeItem<S> treeItem = treeItemRef.get();
1671                 if (treeItem != null) {
1672                     return treeItem;
1673                 }
1674             }
1675         }
1676 
1677         TreeItem<S> treeItem = TreeUtil.getItem(getRoot(), _row, expandedItemCountDirty);
1678         treeItemCacheMap.put(_row, new SoftReference<>(treeItem));
1679         return treeItem;
1680     }
1681 
1682     /**
1683      * Returns the number of levels of 'indentation' of the given TreeItem,
1684      * based on how many times getParent() can be recursively called. If the
1685      * given TreeItem is the root node of this TreeTableView, or if the TreeItem
1686      * does not have any parent set, the returned value will be zero. For each
1687      * time getParent() is recursively called, the returned value is incremented
1688      * by one.
1689      *
1690      * @param node The TreeItem for which the level is needed.
1691      * @return An integer representing the number of parents above the given node,
1692      *         or -1 if the given TreeItem is null.
1693      */
1694     public int getTreeItemLevel(TreeItem<?> node) {
1695         final TreeItem<?> root = getRoot();
1696 
1697         if (node == null) return -1;
1698         if (node == root) return 0;
1699 
1700         int level = 0;
1701         TreeItem<?> parent = node.getParent();
1702         while (parent != null) {
1703             level++;
1704 
1705             if (parent == root) {
1706                 break;
1707             }
1708 
1709             parent = parent.getParent();
1710         }
1711 
1712         return level;
1713     }
1714 
1715     /**
1716      * The TreeTableColumns that are part of this TableView. As the user reorders
1717      * the TableView columns, this list will be updated to reflect the current
1718      * visual ordering.
1719      *
1720      * <p>Note: to display any data in a TableView, there must be at least one
1721      * TreeTableColumn in this ObservableList.</p>
1722      * @return the table table column
1723      */
1724     public final ObservableList<TreeTableColumn<S,?>> getColumns() {
1725         return columns;
1726     }
1727 
1728     /**
1729      * The sortOrder list defines the order in which {@link TreeTableColumn} instances
1730      * are sorted. An empty sortOrder list means that no sorting is being applied
1731      * on the TableView. If the sortOrder list has one TreeTableColumn within it,
1732      * the TableView will be sorted using the
1733      * {@link TreeTableColumn#sortTypeProperty() sortType} and
1734      * {@link TreeTableColumn#comparatorProperty() comparator} properties of this
1735      * TreeTableColumn (assuming
1736      * {@link TreeTableColumn#sortableProperty() TreeTableColumn.sortable} is true).
1737      * If the sortOrder list contains multiple TreeTableColumn instances, then
1738      * the TableView is firstly sorted based on the properties of the first
1739      * TreeTableColumn. If two elements are considered equal, then the second
1740      * TreeTableColumn in the list is used to determine ordering. This repeats until
1741      * the results from all TreeTableColumn comparators are considered, if necessary.
1742      *
1743      * @return An ObservableList containing zero or more TreeTableColumn instances.
1744      */
1745     public final ObservableList<TreeTableColumn<S,?>> getSortOrder() {
1746         return sortOrder;
1747     }
1748 
1749     /**
1750      * Applies the currently installed resize policy against the given column,
1751      * resizing it based on the delta value provided.
1752      * @param column the column
1753      * @param delta the delta
1754      * @return true if column resizing is applied
1755      */
1756     public boolean resizeColumn(TreeTableColumn<S,?> column, double delta) {
1757         if (column == null || Double.compare(delta, 0.0) == 0) return false;
1758 
1759         boolean allowed = getColumnResizePolicy().call(new TreeTableView.ResizeFeatures<S>(TreeTableView.this, column, delta));
1760         if (!allowed) return false;
1761         return true;
1762     }
1763 
1764     /**
1765      * Causes the cell at the given row/column view indexes to switch into
1766      * its editing state, if it is not already in it, and assuming that the
1767      * TableView and column are also editable.
1768      * @param row the row
1769      * @param column the column
1770      */
1771     public void edit(int row, TreeTableColumn<S,?> column) {
1772         if (!isEditable() || (column != null && ! column.isEditable())) {
1773             return;
1774         }
1775 
1776         if (row < 0 && column == null) {
1777             setEditingCell(null);
1778         } else {
1779             setEditingCell(new TreeTablePosition<>(this, row, column));
1780         }
1781     }
1782 
1783     /**
1784      * Returns an unmodifiable list containing the currently visible leaf columns.
1785      * @return an unmodifiable list containing the currently visible leaf columns
1786      */
1787     public ObservableList<TreeTableColumn<S,?>> getVisibleLeafColumns() {
1788         return unmodifiableVisibleLeafColumns;
1789     }
1790 
1791     /**
1792      * Returns the position of the given column, relative to all other
1793      * visible leaf columns.
1794      * @param column the column
1795      * @return the position of the given column, relative to all other
1796      * visible leaf columns
1797      */
1798     public int getVisibleLeafIndex(TreeTableColumn<S,?> column) {
1799         return getVisibleLeafColumns().indexOf(column);
1800     }
1801 
1802     /**
1803      * Returns the TreeTableColumn in the given column index, relative to all other
1804      * visible leaf columns.
1805      * @param column the column
1806      * @return the TreeTableColumn in the given column index, relative to all other
1807      * visible leaf columns
1808      */
1809     public TreeTableColumn<S,?> getVisibleLeafColumn(int column) {
1810         if (column < 0 || column >= visibleLeafColumns.size()) return null;
1811         return visibleLeafColumns.get(column);
1812     }
1813 
1814     /**
1815      * The sort method forces the TreeTableView to re-run its sorting algorithm. More
1816      * often than not it is not necessary to call this method directly, as it is
1817      * automatically called when the {@link #getSortOrder() sort order},
1818      * {@link #sortPolicyProperty() sort policy}, or the state of the
1819      * TreeTableColumn {@link TreeTableColumn#sortTypeProperty() sort type} properties
1820      * change. In other words, this method should only be called directly when
1821      * something external changes and a sort is required.
1822      */
1823     public void sort() {
1824         final ObservableList<TreeTableColumn<S,?>> sortOrder = getSortOrder();
1825 
1826         // update the Comparator property
1827         final Comparator<TreeItem<S>> oldComparator = getComparator();
1828         setComparator(sortOrder.isEmpty() ? null : new TableColumnComparatorBase.TreeTableColumnComparator(sortOrder));
1829 
1830         // fire the onSort event and check if it is consumed, if
1831         // so, don't run the sort
1832         SortEvent<TreeTableView<S>> sortEvent = new SortEvent<>(TreeTableView.this, TreeTableView.this);
1833         fireEvent(sortEvent);
1834         if (sortEvent.isConsumed()) {
1835             // if the sort is consumed we could back out the last action (the code
1836             // is commented out right below), but we don't as we take it as a
1837             // sign that the developer has decided to handle the event themselves.
1838 
1839             // sortLock = true;
1840             // TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
1841             // sortLock = false;
1842             return;
1843         }
1844 
1845         final List<TreeTablePosition<S,?>> prevState = new ArrayList<>(getSelectionModel().getSelectedCells());
1846         final int itemCount = prevState.size();
1847 
1848         // we set makeAtomic to true here, so that we don't fire intermediate
1849         // sort events - instead we send a single permutation event at the end
1850         // of this method.
1851         getSelectionModel().startAtomic();
1852 
1853         // get the sort policy and run it
1854         Callback<TreeTableView<S>, Boolean> sortPolicy = getSortPolicy();
1855         if (sortPolicy == null) return;
1856         Boolean success = sortPolicy.call(this);
1857 
1858         getSelectionModel().stopAtomic();
1859 
1860         if (success == null || ! success) {
1861             // the sort was a failure. Need to backout if possible
1862             sortLock = true;
1863             TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
1864             setComparator(oldComparator);
1865             sortLock = false;
1866         } else {
1867             // sorting was a success, now we possibly fire an event on the
1868             // selection model that the items list has 'permutated' to a new ordering
1869 
1870             // FIXME we should support alternative selection model implementations!
1871             if (getSelectionModel() instanceof TreeTableViewArrayListSelectionModel) {
1872                 final TreeTableViewArrayListSelectionModel<S> sm = (TreeTableViewArrayListSelectionModel<S>) getSelectionModel();
1873                 final ObservableList<TreeTablePosition<S, ?>> newState = sm.getSelectedCells();
1874 
1875                 List<TreeTablePosition<S, ?>> removed = new ArrayList<>();
1876                 for (int i = 0; i < itemCount; i++) {
1877                     TreeTablePosition<S, ?> prevItem = prevState.get(i);
1878                     if (!newState.contains(prevItem)) {
1879                         removed.add(prevItem);
1880                     }
1881                 }
1882 
1883                 if (!removed.isEmpty()) {
1884                     // the sort operation effectively permutates the selectedCells list,
1885                     // but we cannot fire a permutation event as we are talking about
1886                     // TreeTablePosition's changing (which may reside in the same list
1887                     // position before and after the sort). Therefore, we need to fire
1888                     // a single add/remove event to cover the added and removed positions.
1889                     ListChangeListener.Change<TreeTablePosition<S, ?>> c = new NonIterableChange.GenericAddRemoveChange<>(0, itemCount, removed, newState);
1890                     sm.fireCustomSelectedCellsListChangeEvent(c);
1891                 }
1892             }
1893         }
1894     }
1895 
1896     /**
1897      * Calling {@code refresh()} forces the TreeTableView control to recreate and
1898      * repopulate the cells necessary to populate the visual bounds of the control.
1899      * In other words, this forces the TreeTableView to update what it is showing to
1900      * the user. This is useful in cases where the underlying data source has
1901      * changed in a way that is not observed by the TreeTableView itself.
1902      *
1903      * @since JavaFX 8u60
1904      */
1905     public void refresh() {
1906         getProperties().put(Properties.RECREATE, Boolean.TRUE);
1907     }
1908 
1909 
1910 
1911     /***************************************************************************
1912      *                                                                         *
1913      * Private Implementation                                                  *
1914      *                                                                         *
1915      **************************************************************************/
1916 
1917     private boolean sortLock = false;
1918     private TableUtil.SortEventType lastSortEventType = null;
1919     private Object[] lastSortEventSupportInfo = null;
1920 
1921     private void doSort(final TableUtil.SortEventType sortEventType, final Object... supportInfo) {
1922         if (sortLock) {
1923             return;
1924         }
1925 
1926         this.lastSortEventType = sortEventType;
1927         this.lastSortEventSupportInfo = supportInfo;
1928         sort();
1929         this.lastSortEventType = null;
1930         this.lastSortEventSupportInfo = null;
1931     }
1932 
1933     private void updateExpandedItemCount(TreeItem<S> treeItem) {
1934         setExpandedItemCount(TreeUtil.updateExpandedItemCount(treeItem, expandedItemCountDirty, isShowRoot()));
1935 
1936         if (expandedItemCountDirty) {
1937             // this is a very inefficient thing to do, but for now having a cache
1938             // is better than nothing at all...
1939             treeItemCacheMap.clear();
1940         }
1941 
1942         expandedItemCountDirty = false;
1943     }
1944 
1945     private void updateRootExpanded() {
1946         // if we aren't showing the root, and the root isn't expanded, we expand
1947         // it now so that something is shown.
1948         if (!isShowRoot() && getRoot() != null && ! getRoot().isExpanded()) {
1949             getRoot().setExpanded(true);
1950         }
1951     }
1952 
1953 
1954     // --- Content width
1955     private void setContentWidth(double contentWidth) {
1956         this.contentWidth = contentWidth;
1957         if (isInited) {
1958             // sometimes the current column resize policy will have to modify the
1959             // column width of all columns in the table if the table width changes,
1960             // so we short-circuit the resize function and just go straight there
1961             // with a null TreeTableColumn, which indicates to the resize policy function
1962             // that it shouldn't actually do anything specific to one column.
1963             getColumnResizePolicy().call(new TreeTableView.ResizeFeatures<S>(TreeTableView.this, null, 0.0));
1964         }
1965     }
1966 
1967     /**
1968      * Recomputes the currently visible leaf columns in this TableView.
1969      */
1970     private void updateVisibleLeafColumns() {
1971         // update visible leaf columns list
1972         List<TreeTableColumn<S,?>> cols = new ArrayList<TreeTableColumn<S,?>>();
1973         buildVisibleLeafColumns(getColumns(), cols);
1974         visibleLeafColumns.setAll(cols);
1975 
1976         // sometimes the current column resize policy will have to modify the
1977         // column width of all columns in the table if the table width changes,
1978         // so we short-circuit the resize function and just go straight there
1979         // with a null TreeTableColumn, which indicates to the resize policy function
1980         // that it shouldn't actually do anything specific to one column.
1981         getColumnResizePolicy().call(new TreeTableView.ResizeFeatures<S>(TreeTableView.this, null, 0.0));
1982     }
1983 
1984     private void buildVisibleLeafColumns(List<TreeTableColumn<S,?>> cols, List<TreeTableColumn<S,?>> vlc) {
1985         for (TreeTableColumn<S,?> c : cols) {
1986             if (c == null) continue;
1987 
1988             boolean hasChildren = ! c.getColumns().isEmpty();
1989 
1990             if (hasChildren) {
1991                 buildVisibleLeafColumns(c.getColumns(), vlc);
1992             } else if (c.isVisible()) {
1993                 vlc.add(c);
1994             }
1995         }
1996     }
1997 
1998 
1999 
2000     /***************************************************************************
2001      *                                                                         *
2002      * Stylesheet Handling                                                     *
2003      *                                                                         *
2004      **************************************************************************/
2005 
2006     private static final String DEFAULT_STYLE_CLASS = "tree-table-view";
2007 
2008     private static final PseudoClass PSEUDO_CLASS_CELL_SELECTION =
2009             PseudoClass.getPseudoClass("cell-selection");
2010     private static final PseudoClass PSEUDO_CLASS_ROW_SELECTION =
2011             PseudoClass.getPseudoClass("row-selection");
2012 
2013     private static class StyleableProperties {
2014         private static final CssMetaData<TreeTableView<?>,Number> FIXED_CELL_SIZE =
2015                 new CssMetaData<TreeTableView<?>,Number>("-fx-fixed-cell-size",
2016                                                      SizeConverter.getInstance(),
2017                                                      Region.USE_COMPUTED_SIZE) {
2018 
2019                     @Override public Double getInitialValue(TreeTableView<?> node) {
2020                         return node.getFixedCellSize();
2021                     }
2022 
2023                     @Override public boolean isSettable(TreeTableView<?> n) {
2024                         return n.fixedCellSize == null || !n.fixedCellSize.isBound();
2025                     }
2026 
2027                     @Override public StyleableProperty<Number> getStyleableProperty(TreeTableView<?> n) {
2028                         return (StyleableProperty<Number>)(WritableValue<Number>) n.fixedCellSizeProperty();
2029                     }
2030                 };
2031 
2032         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
2033         static {
2034             final List<CssMetaData<? extends Styleable, ?>> styleables =
2035                     new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
2036             styleables.add(FIXED_CELL_SIZE);
2037             STYLEABLES = Collections.unmodifiableList(styleables);
2038         }
2039     }
2040 
2041     /**
2042      * @return The CssMetaData associated with this class, which may include the
2043      * CssMetaData of its superclasses.
2044      */
2045     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
2046         return StyleableProperties.STYLEABLES;
2047     }
2048 
2049     /**
2050      * {@inheritDoc}
2051      * @since JavaFX 8.0
2052      */
2053     @Override
2054     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
2055         return getClassCssMetaData();
2056     }
2057 
2058     /** {@inheritDoc} */
2059     @Override protected Skin<?> createDefaultSkin() {
2060         return new TreeTableViewSkin<S>(this);
2061     }
2062 
2063 
2064 
2065     /***************************************************************************
2066      *                                                                         *
2067      * Accessibility handling                                                  *
2068      *                                                                         *
2069      **************************************************************************/
2070 
2071     @Override
2072     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
2073         switch (attribute) {
2074             case ROW_COUNT: return getExpandedItemCount();
2075             case COLUMN_COUNT: return getVisibleLeafColumns().size();
2076 
2077             /*
2078              * TreeTableViewSkin returns TreeTableRows back to TreeTableView.
2079              * TreeTableRowSkin returns TreeTableCells back to TreeTableRow.
2080              */
2081             case SELECTED_ITEMS: {
2082                 @SuppressWarnings("unchecked")
2083                 ObservableList<TreeTableRow<S>> rows = (ObservableList<TreeTableRow<S>>)super.queryAccessibleAttribute(attribute, parameters);
2084                 List<Node> selection = new ArrayList<>();
2085                 for (TreeTableRow<S> row : rows) {
2086                     @SuppressWarnings("unchecked")
2087                     ObservableList<Node> cells = (ObservableList<Node>)row.queryAccessibleAttribute(attribute, parameters);
2088                     if (cells != null) selection.addAll(cells);
2089                 }
2090                 return FXCollections.observableArrayList(selection);
2091             }
2092             case FOCUS_ITEM: {
2093                 Node row = (Node)super.queryAccessibleAttribute(attribute, parameters);
2094                 if (row == null) return null;
2095                 Node cell = (Node)row.queryAccessibleAttribute(attribute, parameters);
2096                 /* cell equals to null means the row is a placeholder node */
2097                 return cell != null ?  cell : row;
2098             }
2099             case CELL_AT_ROW_COLUMN: {
2100                 @SuppressWarnings("unchecked")
2101                 TreeTableRow<S> row = (TreeTableRow<S>)super.queryAccessibleAttribute(attribute, parameters);
2102                 return row != null ? row.queryAccessibleAttribute(attribute, parameters) : null;
2103             }
2104             case MULTIPLE_SELECTION: {
2105                 TreeTableViewSelectionModel<S> sm = getSelectionModel();
2106                 return sm != null && sm.getSelectionMode() == SelectionMode.MULTIPLE;
2107             }
2108             default: return super.queryAccessibleAttribute(attribute, parameters);
2109         }
2110     }
2111 
2112     /***************************************************************************
2113      *                                                                         *
2114      * Support Classes                                                         *
2115      *                                                                         *
2116      **************************************************************************/
2117 
2118      /**
2119       * An immutable wrapper class for use in the TableView
2120      * {@link TreeTableView#columnResizePolicyProperty() column resize} functionality.
2121       * @since JavaFX 8.0
2122       */
2123      public static class ResizeFeatures<S> extends ResizeFeaturesBase<TreeItem<S>> {
2124         private TreeTableView<S> treeTable;
2125 
2126         /**
2127          * Creates an instance of this class, with the provided TreeTableView,
2128          * TreeTableColumn and delta values being set and stored in this immutable
2129          * instance.
2130          *
2131          * @param treeTable The TreeTableView upon which the resize operation is occurring.
2132          * @param column The column upon which the resize is occurring, or null
2133          *      if this ResizeFeatures instance is being created as a result of a
2134          *      TreeTableView resize operation.
2135          * @param delta The amount of horizontal space added or removed in the
2136          *      resize operation.
2137          */
2138         public ResizeFeatures(TreeTableView<S> treeTable, TreeTableColumn<S,?> column, Double delta) {
2139             super(column, delta);
2140             this.treeTable = treeTable;
2141         }
2142 
2143         /**
2144          * Returns the column upon which the resize is occurring, or null
2145          * if this ResizeFeatures instance was created as a result of a
2146          * TreeTableView resize operation.
2147          */
2148         @Override public TreeTableColumn<S,?> getColumn() {
2149             return (TreeTableColumn<S,?>) super.getColumn();
2150         }
2151 
2152         /**
2153          * Returns the TreeTableView upon which the resize operation is occurring.
2154          * @return the TreeTableView upon which the resize operation is occurring
2155          */
2156         public TreeTableView<S> getTable() { return treeTable; }
2157     }
2158 
2159 
2160 
2161     /**
2162      * An {@link Event} subclass used specifically in TreeTableView for representing
2163      * edit-related events. It provides additional API to easily access the
2164      * TreeItem that the edit event took place on, as well as the input provided
2165      * by the end user.
2166      *
2167      * @param <S> The type of the input, which is the same type as the TreeTableView
2168      *      itself.
2169      * @since JavaFX 8.0
2170      */
2171     public static class EditEvent<S> extends Event {
2172         private static final long serialVersionUID = -4437033058917528976L;
2173 
2174         /**
2175          * Common supertype for all edit event types.
2176          */
2177         public static final EventType<?> ANY = EDIT_ANY_EVENT;
2178 
2179         private final TreeTableView<S> source;
2180         private final S oldValue;
2181         private final S newValue;
2182         private transient final TreeItem<S> treeItem;
2183 
2184         /**
2185          * Creates a new EditEvent instance to represent an edit event. This
2186          * event is used for {@link #EDIT_START_EVENT},
2187          * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT} types.
2188          * @param source the source
2189          * @param eventType the eventType
2190          * @param treeItem the treeItem
2191          * @param oldValue the oldValue
2192          * @param newValue the newValue
2193          */
2194         public EditEvent(TreeTableView<S> source,
2195                          EventType<? extends TreeTableView.EditEvent> eventType,
2196                          TreeItem<S> treeItem, S oldValue, S newValue) {
2197             super(source, Event.NULL_SOURCE_TARGET, eventType);
2198             this.source = source;
2199             this.oldValue = oldValue;
2200             this.newValue = newValue;
2201             this.treeItem = treeItem;
2202         }
2203 
2204         /**
2205          * Returns the TreeTableView upon which the edit took place.
2206          * @return the TreeTableView upon which the edit took place
2207          */
2208         @Override public TreeTableView<S> getSource() {
2209             return source;
2210         }
2211 
2212         /**
2213          * Returns the {@link TreeItem} upon which the edit took place.
2214          * @return the {@link TreeItem} upon which the edit took place
2215          */
2216         public TreeItem<S> getTreeItem() {
2217             return treeItem;
2218         }
2219 
2220         /**
2221          * Returns the new value input into the TreeItem by the end user.
2222          * @return the new value input into the TreeItem by the end user
2223          */
2224         public S getNewValue() {
2225             return newValue;
2226         }
2227 
2228         /**
2229          * Returns the old value that existed in the TreeItem prior to the current
2230          * edit event.
2231          * @return the old value that existed in the TreeItem prior to the current
2232          * edit event
2233          */
2234         public S getOldValue() {
2235             return oldValue;
2236         }
2237     }
2238 
2239 
2240 
2241      /**
2242      * A simple extension of the {@link SelectionModel} abstract class to
2243      * allow for special support for TreeTableView controls.
2244       *
2245      * @since JavaFX 8.0
2246      */
2247     public static abstract class TreeTableViewSelectionModel<S> extends
2248             TableSelectionModel<TreeItem<S>> {
2249 
2250         /***********************************************************************
2251          *                                                                     *
2252          * Private fields                                                      *
2253          *                                                                     *
2254          **********************************************************************/
2255 
2256         private final TreeTableView<S> treeTableView;
2257 
2258 
2259         /***********************************************************************
2260          *                                                                     *
2261          * Constructors                                                        *
2262          *                                                                     *
2263          **********************************************************************/
2264 
2265         /**
2266          * Builds a default TreeTableViewSelectionModel instance with the provided
2267          * TreeTableView.
2268          * @param treeTableView The TreeTableView upon which this selection model should
2269          *      operate.
2270          * @throws NullPointerException TreeTableView can not be null.
2271          */
2272         public TreeTableViewSelectionModel(final TreeTableView<S> treeTableView) {
2273             if (treeTableView == null) {
2274                 throw new NullPointerException("TreeTableView can not be null");
2275             }
2276 
2277             this.treeTableView = treeTableView;
2278         }
2279 
2280 
2281 
2282         /***********************************************************************
2283          *                                                                     *
2284          * Abstract API                                                        *
2285          *                                                                     *
2286          **********************************************************************/
2287 
2288          /**
2289          * A read-only ObservableList representing the currently selected cells
2290          * in this TreeTableView. Rather than directly modify this list, please
2291          * use the other methods provided in the TreeTableViewSelectionModel.
2292          * @return a list of selected cells
2293          */
2294         public abstract ObservableList<TreeTablePosition<S,?>> getSelectedCells();
2295 
2296 
2297 
2298         /***********************************************************************
2299          *                                                                     *
2300          * Public API                                                          *
2301          *                                                                     *
2302          **********************************************************************/
2303 
2304          /**
2305           * Returns the TreeTableView instance that this selection model is installed in.
2306          * @return the TreeTableView instance that this selection model is installed in
2307           */
2308          public TreeTableView<S> getTreeTableView() {
2309              return treeTableView;
2310          }
2311 
2312          /** {@inheritDoc} */
2313          @Override public TreeItem<S> getModelItem(int index) {
2314              return treeTableView.getTreeItem(index);
2315          }
2316 
2317          /** {@inheritDoc} */
2318          @Override protected int getItemCount() {
2319              return treeTableView.getExpandedItemCount();
2320          }
2321 
2322          /** {@inheritDoc} */
2323          @Override public void focus(int row) {
2324              focus(row, null);
2325          }
2326 
2327          /** {@inheritDoc} */
2328          @Override public int getFocusedIndex() {
2329              return getFocusedCell().getRow();
2330          }
2331 
2332          /** {@inheritDoc} */
2333          @Override public void selectRange(int minRow, TableColumnBase<TreeItem<S>,?> minColumn,
2334                                            int maxRow, TableColumnBase<TreeItem<S>,?> maxColumn) {
2335              final int minColumnIndex = treeTableView.getVisibleLeafIndex((TreeTableColumn<S,?>)minColumn);
2336              final int maxColumnIndex = treeTableView.getVisibleLeafIndex((TreeTableColumn<S,?>)maxColumn);
2337              for (int _row = minRow; _row <= maxRow; _row++) {
2338                  for (int _col = minColumnIndex; _col <= maxColumnIndex; _col++) {
2339                      select(_row, treeTableView.getVisibleLeafColumn(_col));
2340                  }
2341              }
2342          }
2343 
2344 
2345 
2346         /***********************************************************************
2347          *                                                                     *
2348          * Private implementation                                              *
2349          *                                                                     *
2350          **********************************************************************/
2351 
2352          private void focus(int row, TreeTableColumn<S,?> column) {
2353              focus(new TreeTablePosition<>(getTreeTableView(), row, column));
2354          }
2355 
2356          private void focus(TreeTablePosition<S,?> pos) {
2357              if (getTreeTableView().getFocusModel() == null) return;
2358 
2359              getTreeTableView().getFocusModel().focus(pos.getRow(), pos.getTableColumn());
2360          }
2361 
2362          private TreeTablePosition<S,?> getFocusedCell() {
2363              if (treeTableView.getFocusModel() == null) {
2364                  return new TreeTablePosition<>(treeTableView, -1, null);
2365              }
2366              return treeTableView.getFocusModel().getFocusedCell();
2367          }
2368      }
2369 
2370 
2371 
2372     /**
2373      * A primitive selection model implementation, using a List<Integer> to store all
2374      * selected indices.
2375      */
2376     // package for testing
2377     static class TreeTableViewArrayListSelectionModel<S> extends TreeTableViewSelectionModel<S> {
2378 
2379         private final MappingChange.Map<TreeTablePosition<S,?>,Integer> cellToIndicesMap = f -> f.getRow();
2380 
2381         private TreeTableView<S> treeTableView = null;
2382 
2383         /***********************************************************************
2384          *                                                                     *
2385          * Constructors                                                        *
2386          *                                                                     *
2387          **********************************************************************/
2388 
2389         public TreeTableViewArrayListSelectionModel(final TreeTableView<S> treeTableView) {
2390             super(treeTableView);
2391             this.treeTableView = treeTableView;
2392 
2393             this.treeTableView.rootProperty().addListener(weakRootPropertyListener);
2394             this.treeTableView.showRootProperty().addListener(showRootPropertyListener);
2395             updateTreeEventListener(null, treeTableView.getRoot());
2396 
2397             selectedCellsMap = new SelectedCellsMap<TreeTablePosition<S,?>>(this::fireCustomSelectedCellsListChangeEvent) {
2398                 @Override public boolean isCellSelectionEnabled() {
2399                     return TreeTableViewArrayListSelectionModel.this.isCellSelectionEnabled();
2400                 }
2401             };
2402 
2403             selectedCellsSeq = new ReadOnlyUnbackedObservableList<TreeTablePosition<S,?>>() {
2404                 @Override public TreeTablePosition<S,?> get(int i) {
2405                     return selectedCellsMap.get(i);
2406                 }
2407 
2408                 @Override public int size() {
2409                     return selectedCellsMap.size();
2410                 }
2411             };
2412 //            selectedCellsSeq.addListener((ListChangeListener<? super TreeTablePosition<S,?>>) c -> {
2413 //                ControlUtils.updateSelectedIndices(this, c);
2414 //            });
2415 
2416             updateDefaultSelection();
2417 
2418             cellSelectionEnabledProperty().addListener(o -> {
2419                 updateDefaultSelection();
2420                 TableCellBehaviorBase.setAnchor(treeTableView, getFocusedCell(), true);
2421             });
2422         }
2423 
2424         private void dispose() {
2425             this.treeTableView.rootProperty().removeListener(weakRootPropertyListener);
2426             this.treeTableView.showRootProperty().removeListener(showRootPropertyListener);
2427 
2428             TreeItem<S> root = this.treeTableView.getRoot();
2429             if (root != null) {
2430                 root.removeEventHandler(TreeItem.<S>expandedItemCountChangeEvent(), weakTreeItemListener);
2431             }
2432         }
2433 
2434         private void updateTreeEventListener(TreeItem<S> oldRoot, TreeItem<S> newRoot) {
2435             if (oldRoot != null && weakTreeItemListener != null) {
2436                 oldRoot.removeEventHandler(TreeItem.<S>expandedItemCountChangeEvent(), weakTreeItemListener);
2437             }
2438 
2439             if (newRoot != null) {
2440                 weakTreeItemListener = new WeakEventHandler<>(treeItemListener);
2441                 newRoot.addEventHandler(TreeItem.<S>expandedItemCountChangeEvent(), weakTreeItemListener);
2442             }
2443         }
2444 
2445         private ChangeListener<TreeItem<S>> rootPropertyListener = (observable, oldValue, newValue) -> {
2446             updateDefaultSelection();
2447 
2448             updateTreeEventListener(oldValue, newValue);
2449         };
2450 
2451         private InvalidationListener showRootPropertyListener = o -> {
2452             shiftSelection(0, treeTableView.isShowRoot() ? 1 : -1, null);
2453         };
2454 
2455         private EventHandler<TreeItem.TreeModificationEvent<S>> treeItemListener = new EventHandler<>() {
2456             @Override public void handle(TreeItem.TreeModificationEvent<S> e) {
2457 
2458                 if (getSelectedIndex() == -1 && getSelectedItem() == null) return;
2459 
2460                 final TreeItem<S> treeItem = e.getTreeItem();
2461                 if (treeItem == null) return;
2462 
2463                 final int oldSelectedIndex = getSelectedIndex();
2464 
2465                 treeTableView.expandedItemCountDirty = true;
2466 
2467                 // we only shift selection from this row - everything before it
2468                 // is safe. We might change this below based on certain criteria
2469                 int startRow = treeTableView.getRow(treeItem);
2470 
2471                 int shift = 0;
2472                 ListChangeListener.Change<? extends TreeItem<?>> change = e.getChange();
2473                 if (change != null) {
2474                     change.next();
2475                 }
2476 
2477                 do {
2478                     final int addedSize = change == null ? 0 : change.getAddedSize();
2479                     final int removedSize = change == null ? 0 : change.getRemovedSize();
2480 
2481                     if (e.wasExpanded()) {
2482                         // need to shuffle selection by the number of visible children
2483                         shift += treeItem.getExpandedDescendentCount(false) - 1;
2484                         startRow++;
2485                     } else if (e.wasCollapsed()) {
2486                         // remove selection from any child treeItem, and also determine
2487                         // if any child item was selected (in which case the parent
2488                         // takes the selection on collapse)
2489                         treeItem.getExpandedDescendentCount(false);
2490                         final int count = treeItem.previousExpandedDescendentCount;
2491 
2492                         final int selectedIndex = getSelectedIndex();
2493                         final boolean wasPrimarySelectionInChild =
2494                                 selectedIndex >= (startRow + 1) &&
2495                                         selectedIndex < (startRow + count);
2496 
2497                         boolean wasAnyChildSelected = false;
2498                         final boolean isCellSelectionMode = isCellSelectionEnabled();
2499                         ObservableList<TreeTableColumn<S, ?>> columns = getTreeTableView().getVisibleLeafColumns();
2500 
2501                         selectedIndices._beginChange();
2502                         final int from = startRow + 1;
2503                         final int to = startRow + count;
2504                         final List<Integer> removed = new ArrayList<>();
2505                         TreeTableColumn<S, ?> selectedColumn = null;
2506                         for (int i = from; i < to; i++) {
2507                             // we have to handle cell selection mode differently than
2508                             // row selection mode. Refer to RT-34103 for the bug report
2509                             // that drove this change, but in short the issue was that
2510                             // when collapsing a branch that had selection, we were
2511                             // always calling isSelected(row), but that always returns
2512                             // false in cell selection mode.
2513                             if (isCellSelectionMode) {
2514                                 for (int column = 0; column < columns.size(); column++) {
2515                                     final TreeTableColumn<S, ?> col = columns.get(column);
2516                                     if (isSelected(i, col)) {
2517                                         wasAnyChildSelected = true;
2518                                         clearSelection(i, col);
2519                                         selectedColumn = col;
2520                                     }
2521                                 }
2522                             } else {
2523                                 if (isSelected(i)) {
2524                                     wasAnyChildSelected = true;
2525                                     removed.add(i);
2526                                 }
2527                             }
2528                         }
2529 
2530                         ControlUtils.reducingChange(selectedIndices, removed);
2531 
2532                         for (int index : removed) {
2533                             startAtomic();
2534                             // we pass in false here to prevent a lookup into the TreeItem, as it is unnecessary
2535                             // and results in JDK-8152396
2536                             clearSelection(new TreeTablePosition<>(treeTableView, index, null, false));
2537                             stopAtomic();
2538                         }
2539                         selectedIndices._endChange();
2540 
2541                         // put selection onto the newly-collapsed tree item
2542                         if (wasPrimarySelectionInChild && wasAnyChildSelected) {
2543                             select(startRow, selectedColumn);
2544                         }
2545 
2546                         shift += -count + 1;
2547                         startRow++;
2548                     } else if (e.wasPermutated()) {
2549                         // General approach:
2550                         //   -- detected a sort has happened
2551                         //   -- Create a permutation lookup map (1)
2552                         //   -- dump all the selected indices into a list (2)
2553                         //   -- create a list containing the new indices (3)
2554                         //   -- for each previously-selected index (4)
2555                         //     -- if index is in the permutation lookup map
2556                         //       -- add the new index to the new indices list
2557                         //   -- Perform batch selection (5)
2558 
2559                         startAtomic();
2560 
2561                         final int offset = startRow + 1;
2562 
2563                         // (1)
2564                         int length = e.getTo() - e.getFrom();
2565                         HashMap<Integer, Integer> pMap = new HashMap<> (length);
2566                         for (int i = e.getFrom(); i < e.getTo(); i++) {
2567                             pMap.put(i, e.getChange().getPermutation(i));
2568                         }
2569 
2570                         // (2)
2571                         List<TreeTablePosition<S,?>> selectedIndices = new ArrayList<>(getSelectedCells());
2572 
2573                         // (3)
2574                         List<TreeTablePosition<S,?>> newIndices = new ArrayList<>(selectedIndices.size());
2575 
2576                         // (4)
2577                         boolean selectionIndicesChanged = false;
2578                         for (int i = 0; i < selectedIndices.size(); i++) {
2579                             final TreeTablePosition<S,?> oldIndex = selectedIndices.get(i);
2580                             final int oldRow = oldIndex.getRow() - offset;
2581 
2582                             if (pMap.containsKey(oldRow)) {
2583                                 int newIndex = pMap.get(oldRow) + offset;
2584 
2585                                 selectionIndicesChanged = selectionIndicesChanged || newIndex != oldRow;
2586 
2587                                 newIndices.add(new TreeTablePosition<>(oldIndex.getTreeTableView(), newIndex, oldIndex.getTableColumn()));
2588                             }
2589 
2590                             // check if the root element of this event was selected, so that we can retain it
2591                             if (oldIndex.getRow() == startRow) {
2592                                 newIndices.add(new TreeTablePosition<>(oldIndex.getTreeTableView(), oldIndex.getRow(), oldIndex.getTableColumn()));
2593                             }
2594                         }
2595 
2596                         if (selectionIndicesChanged) {
2597                             // (5)
2598                             quietClearSelection();
2599                             stopAtomic();
2600 
2601                             selectedCellsMap.setAll(newIndices);
2602 
2603                             final int offsetOldIndex = oldSelectedIndex - offset;
2604                             if (offsetOldIndex >= 0 && offsetOldIndex < getItemCount()) {
2605                                 int newIndex = e.getChange().getPermutation(offsetOldIndex);
2606                                 setSelectedIndex(newIndex + offset);
2607                                 focus(newIndex + offset);
2608                             }
2609                         } else {
2610                             stopAtomic();
2611                         }
2612                     } else if (e.wasAdded()) {
2613                         // shuffle selection by the number of added items
2614                         shift += treeItem.isExpanded() ? addedSize : 0;
2615 
2616                         // RT-32963: We were taking the startRow from the TreeItem
2617                         // in which the children were added, rather than from the
2618                         // actual position of the new child. This led to selection
2619                         // being moved off the parent TreeItem by mistake.
2620                         // The 'if (e.getAddedSize() == 1)' condition here was
2621                         // subsequently commented out due to RT-33894.
2622                         startRow = treeTableView.getRow(e.getChange().getAddedSubList().get(0));
2623 
2624                         TreeTablePosition<S, ?> anchor = TreeTableCellBehavior.getAnchor(treeTableView, null);
2625                         if (anchor != null) {
2626                             boolean isAnchorSelected = isSelected(anchor.getRow(), anchor.getTableColumn());
2627                             if (isAnchorSelected) {
2628                                 TreeTablePosition<S, ?> newAnchor = new TreeTablePosition<>(treeTableView, anchor.getRow() + shift, anchor.getTableColumn());
2629                                 TreeTableCellBehavior.setAnchor(treeTableView, newAnchor, false);
2630                             }
2631                         }
2632                     } else if (e.wasRemoved()) {
2633                         // shuffle selection by the number of removed items
2634                         shift += treeItem.isExpanded() ? -removedSize : 0;
2635 
2636                         // the start row is incorrect - it is _not_ the index of the
2637                         // TreeItem in which the children were removed from (which is
2638                         // what it currently represents). We need to take the 'from'
2639                         // value out of the event and make use of that to understand
2640                         // what actually changed inside the children list.
2641                         startRow += e.getFrom() + 1;
2642 
2643                         // whilst we are here, we should check if the removed items
2644                         // are part of the selectedItems list - and remove them
2645                         // from selection if they are (as per RT-15446)
2646                         final List<Integer> selectedIndices = getSelectedIndices();
2647                         final List<TreeItem<S>> selectedItems = getSelectedItems();
2648                         final TreeItem<S> selectedItem = getSelectedItem();
2649                         final List<? extends TreeItem<S>> removedChildren = e.getChange().getRemoved();
2650 
2651                         for (int i = 0; i < selectedIndices.size() && !selectedItems.isEmpty(); i++) {
2652                             int index = selectedIndices.get(i);
2653                             if (index > selectedItems.size()) break;
2654 
2655                             if (removedChildren.size() == 1 &&
2656                                     selectedItems.size() == 1 &&
2657                                     selectedItem != null &&
2658                                     selectedItem.equals(removedChildren.get(0))) {
2659                                 // Bug fix for RT-28637
2660                                 if (oldSelectedIndex < getItemCount()) {
2661                                     final int previousRow = oldSelectedIndex == 0 ? 0 : oldSelectedIndex - 1;
2662                                     TreeItem<S> newSelectedItem = getModelItem(previousRow);
2663                                     if (!selectedItem.equals(newSelectedItem)) {
2664                                         clearAndSelect(previousRow);
2665                                     }
2666                                 }
2667                             }
2668                         }
2669                     }
2670                 } while (e.getChange() != null && e.getChange().next());
2671 
2672                 shiftSelection(startRow, shift, new Callback<ShiftParams, Void>() {
2673                     @Override public Void call(ShiftParams param) {
2674 
2675                         // we make the shifts atomic, as otherwise listeners to
2676                         // the items / indices lists get a lot of intermediate
2677                         // noise. They eventually get the summary event fired
2678                         // from within shiftSelection, so this is ok.
2679                         startAtomic();
2680 
2681                         final int clearIndex = param.getClearIndex();
2682                         final int setIndex = param.getSetIndex();
2683                         TreeTablePosition<S,?> oldTP = null;
2684                         if (clearIndex > -1) {
2685                             for (int i = 0; i < selectedCellsMap.size(); i++) {
2686                                 TreeTablePosition<S,?> tp = selectedCellsMap.get(i);
2687                                 if (tp.getRow() == clearIndex) {
2688                                     oldTP = tp;
2689                                     selectedCellsMap.remove(tp);
2690                                 } else if (tp.getRow() == setIndex && !param.isSelected()) {
2691                                     selectedCellsMap.remove(tp);
2692                                 }
2693                             }
2694                         }
2695 
2696                         if (oldTP != null && param.isSelected()) {
2697                             TreeTablePosition<S,?> newTP = new TreeTablePosition<>(
2698                                     treeTableView, param.getSetIndex(), oldTP.getTableColumn());
2699 
2700                             selectedCellsMap.add(newTP);
2701                         }
2702 
2703                         stopAtomic();
2704 
2705                         return null;
2706                     }
2707                 });
2708             }
2709         };
2710 
2711         private WeakChangeListener<TreeItem<S>> weakRootPropertyListener =
2712                 new WeakChangeListener<>(rootPropertyListener);
2713 
2714         private WeakEventHandler<TreeItem.TreeModificationEvent<S>> weakTreeItemListener;
2715 
2716 
2717 
2718         /***********************************************************************
2719          *                                                                     *
2720          * Observable properties (and getters/setters)                         *
2721          *                                                                     *
2722          **********************************************************************/
2723 
2724         // the only 'proper' internal data structure, selectedItems and selectedIndices
2725         // are both 'read-only and unbacked'.
2726         private final SelectedCellsMap<TreeTablePosition<S,?>> selectedCellsMap;
2727 
2728         private final ReadOnlyUnbackedObservableList<TreeTablePosition<S,?>> selectedCellsSeq;
2729         @Override public ObservableList<TreeTablePosition<S,?>> getSelectedCells() {
2730             return selectedCellsSeq;
2731         }
2732 
2733 
2734         /***********************************************************************
2735          *                                                                     *
2736          * Internal properties                                                 *
2737          *                                                                     *
2738          **********************************************************************/
2739 
2740 
2741 
2742         /***********************************************************************
2743          *                                                                     *
2744          * Public selection API                                                *
2745          *                                                                     *
2746          **********************************************************************/
2747 
2748         @Override public void clearAndSelect(int row) {
2749             clearAndSelect(row, null);
2750         }
2751 
2752         @Override public void clearAndSelect(int row, TableColumnBase<TreeItem<S>,?> column) {
2753             if (row < 0 || row >= getItemCount()) return;
2754 
2755             final TreeTablePosition<S,?> newTablePosition = new TreeTablePosition<>(getTreeTableView(), row, (TreeTableColumn<S,?>)column);
2756             final boolean isCellSelectionEnabled = isCellSelectionEnabled();
2757 
2758             // replace the anchor
2759             TreeTableCellBehavior.setAnchor(treeTableView, newTablePosition, false);
2760 
2761             // firstly we make a copy of the selection, so that we can send out
2762             // the correct details in the selection change event.
2763             List<TreeTablePosition<S,?>> previousSelection = new ArrayList<>(selectedCellsMap.getSelectedCells());
2764 
2765             // secondly we check if we can short-circuit out of here because the new selection
2766             // equals the current selection
2767             final boolean wasSelected = isSelected(row, column);
2768             if (wasSelected && previousSelection.size() == 1) {
2769                 // before we return, we double-check that the selected item
2770                 // is equal to the item in the given index
2771                 TreeTablePosition<S,?> selectedCell = getSelectedCells().get(0);
2772                 if (getSelectedItem() == getModelItem(row)) {
2773                     if (selectedCell.getRow() == row && selectedCell.getTableColumn() == column) {
2774                         return;
2775                     }
2776                 }
2777             }
2778 
2779             // RT-32411: We used to call quietClearSelection() here, but this
2780             // resulted in the selectedItems and selectedIndices lists never
2781             // reporting that they were empty.
2782             // makeAtomic toggle added to resolve RT-32618
2783             startAtomic();
2784 
2785             // then clear the current selection
2786             clearSelection();
2787 
2788             // and select the new cell
2789             select(row, column);
2790 
2791             stopAtomic();
2792 
2793             // We remove the new selection from the list seeing as it is not removed.
2794             if (isCellSelectionEnabled) {
2795                 previousSelection.remove(newTablePosition);
2796             } else {
2797                 for (TreeTablePosition<S,?> tp : previousSelection) {
2798                     if (tp.getRow() == row) {
2799                         previousSelection.remove(tp);
2800                         break;
2801                     }
2802                 }
2803             }
2804 
2805             // fire off a single add/remove/replace notification (rather than
2806             // individual remove and add notifications) - see RT-33324
2807             ListChangeListener.Change<TreeTablePosition<S, ?>> change;
2808 
2809             /*
2810              * getFrom() documentation:
2811              *   If wasAdded is true, the interval contains all the values that were added.
2812              *   If wasPermutated is true, the interval marks the values that were permutated.
2813              *   If wasRemoved is true and wasAdded is false, getFrom() and getTo() should
2814              *   return the same number - the place where the removed elements were positioned in the list.
2815              */
2816             if (wasSelected) {
2817                 change = ControlUtils.buildClearAndSelectChange(selectedCellsSeq, previousSelection, row);
2818             } else {
2819                 final int changeIndex = isCellSelectionEnabled ? 0 : Math.max(0, selectedCellsSeq.indexOf(newTablePosition));
2820                 final int changeSize = isCellSelectionEnabled ? getSelectedCells().size() : 1;
2821                 change = new NonIterableChange.GenericAddRemoveChange<>(
2822                         changeIndex, changeIndex + changeSize, previousSelection, selectedCellsSeq);
2823 //                selectedCellsSeq._beginChange();
2824 //                selectedCellsSeq._nextAdd(changeIndex, changeIndex + changeSize);
2825 //                selectedCellsSeq._nextRemove(changeIndex, previousSelection);
2826 //                selectedCellsSeq._endChange();
2827             }
2828 
2829             fireCustomSelectedCellsListChangeEvent(change);
2830         }
2831 
2832         @Override public void select(int row) {
2833             select(row, null);
2834         }
2835 
2836         @Override public void select(int row, TableColumnBase<TreeItem<S>,?> column) {
2837             // TODO we need to bring in the TreeView selection stuff here...
2838             if (row < 0 || row >= getRowCount()) return;
2839 
2840             // if I'm in cell selection mode but the column is null, select each
2841             // of the contained cells individually
2842             if (isCellSelectionEnabled() && column == null) {
2843                 List<TreeTableColumn<S,?>> columns = getTreeTableView().getVisibleLeafColumns();
2844                 for (int i = 0; i < columns.size(); i++) {
2845                     select(row, columns.get(i));
2846                 }
2847                 return;
2848             }
2849 
2850             if (TableCellBehavior.hasDefaultAnchor(treeTableView)) {
2851                 TableCellBehavior.removeAnchor(treeTableView);
2852             }
2853 
2854             if (getSelectionMode() == SelectionMode.SINGLE) {
2855                 quietClearSelection();
2856             }
2857             selectedCellsMap.add(new TreeTablePosition<>(getTreeTableView(), row, (TreeTableColumn<S,?>)column));
2858 
2859             updateSelectedIndex(row);
2860             focus(row, (TreeTableColumn<S, ?>) column);
2861         }
2862 
2863         @Override public void select(TreeItem<S> obj) {
2864             if (obj == null && getSelectionMode() == SelectionMode.SINGLE) {
2865                 clearSelection();
2866                 return;
2867             }
2868 
2869             int firstIndex = treeTableView.getRow(obj);
2870             if (firstIndex > -1) {
2871                 if (isSelected(firstIndex)) {
2872                     return;
2873                 }
2874 
2875                 if (getSelectionMode() == SelectionMode.SINGLE) {
2876                     quietClearSelection();
2877                 }
2878 
2879                 select(firstIndex);
2880             } else {
2881                 // if we are here, we did not find the item in the entire data model.
2882                 // Even still, we allow for this item to be set to the give object.
2883                 // We expect that in concrete subclasses of this class we observe the
2884                 // data model such that we check to see if the given item exists in it,
2885                 // whilst SelectedIndex == -1 && SelectedItem != null.
2886                 setSelectedIndex(-1);
2887                 setSelectedItem(obj);
2888             }
2889         }
2890 
2891         @Override public void selectIndices(int row, int... rows) {
2892             if (rows == null) {
2893                 select(row);
2894                 return;
2895             }
2896 
2897             /*
2898              * Performance optimisation - if multiple selection is disabled, only
2899              * process the end-most row index.
2900              */
2901             int rowCount = getRowCount();
2902 
2903             if (getSelectionMode() == SelectionMode.SINGLE) {
2904                 quietClearSelection();
2905 
2906                 for (int i = rows.length - 1; i >= 0; i--) {
2907                     int index = rows[i];
2908                     if (index >= 0 && index < rowCount) {
2909                         select(index);
2910                         break;
2911                     }
2912                 }
2913 
2914                 if (selectedCellsMap.isEmpty()) {
2915                     if (row > 0 && row < rowCount) {
2916                         select(row);
2917                     }
2918                 }
2919             } else {
2920                 int lastIndex = -1;
2921                 Set<TreeTablePosition<S,?>> positions = new LinkedHashSet<>();
2922 
2923                 // --- firstly, we special-case the non-varargs 'row' argument
2924                 if (row >= 0 && row < rowCount) {
2925                     // if I'm in cell selection mode, we want to select each
2926                     // of the contained cells individually
2927                     if (isCellSelectionEnabled()) {
2928                         List<TreeTableColumn<S,?>> columns = getTreeTableView().getVisibleLeafColumns();
2929                         for (int column = 0; column < columns.size(); column++) {
2930                             if (! selectedCellsMap.isSelected(row, column)) {
2931                                 positions.add(new TreeTablePosition<>(getTreeTableView(), row, columns.get(column)));
2932                             }
2933                         }
2934                     } else {
2935                         boolean match = selectedCellsMap.isSelected(row, -1);
2936                         if (!match) {
2937                             positions.add(new TreeTablePosition<>(getTreeTableView(), row, null));
2938                         }
2939                     }
2940 
2941                     lastIndex = row;
2942                 }
2943 
2944                 // --- now we iterate through all varargs values
2945                 for (int i = 0; i < rows.length; i++) {
2946                     int index = rows[i];
2947                     if (index < 0 || index >= rowCount) continue;
2948                     lastIndex = index;
2949 
2950                     if (isCellSelectionEnabled()) {
2951                         List<TreeTableColumn<S,?>> columns = getTreeTableView().getVisibleLeafColumns();
2952                         for (int column = 0; column < columns.size(); column++) {
2953                             if (! selectedCellsMap.isSelected(index, column)) {
2954                                 positions.add(new TreeTablePosition<>(getTreeTableView(), index, columns.get(column)));
2955                                 lastIndex = index;
2956                             }
2957                         }
2958                     } else {
2959                         if (! selectedCellsMap.isSelected(index, -1)) {
2960                             // if we are here then we have successfully gotten through the for-loop above
2961                             positions.add(new TreeTablePosition<>(getTreeTableView(), index, null));
2962                         }
2963                     }
2964                 }
2965 
2966                 selectedCellsMap.addAll(positions);
2967 
2968                 if (lastIndex != -1) {
2969                     select(lastIndex);
2970                 }
2971             }
2972         }
2973 
2974         @Override public void selectAll() {
2975             if (getSelectionMode() == SelectionMode.SINGLE) return;
2976 
2977             if (isCellSelectionEnabled()) {
2978                 List<TreeTablePosition<S,?>> indices = new ArrayList<>();
2979                 TreeTableColumn<S,?> column;
2980                 TreeTablePosition<S,?> tp = null;
2981                 for (int col = 0; col < getTreeTableView().getVisibleLeafColumns().size(); col++) {
2982                     column = getTreeTableView().getVisibleLeafColumns().get(col);
2983                     for (int row = 0; row < getRowCount(); row++) {
2984                         tp = new TreeTablePosition<>(getTreeTableView(), row, column);
2985                         indices.add(tp);
2986                     }
2987                 }
2988                 selectedCellsMap.setAll(indices);
2989 
2990                 if (tp != null) {
2991                     select(tp.getRow(), tp.getTableColumn());
2992                     focus(tp.getRow(), tp.getTableColumn());
2993                 }
2994             } else {
2995                 List<TreeTablePosition<S,?>> indices = new ArrayList<>();
2996                 for (int i = 0; i < getRowCount(); i++) {
2997                     indices.add(new TreeTablePosition<>(getTreeTableView(), i, null));
2998                 }
2999                 selectedCellsMap.setAll(indices);
3000 
3001                 int focusedIndex = getFocusedIndex();
3002                 if (focusedIndex == -1) {
3003                     final int itemCount = getItemCount();
3004                     if (itemCount > 0) {
3005                         select(itemCount - 1);
3006                         focus(indices.get(indices.size() - 1));
3007                     }
3008                 } else {
3009                     select(focusedIndex);
3010                     focus(focusedIndex);
3011                 }
3012             }
3013         }
3014 
3015         @Override public void selectRange(int minRow, TableColumnBase<TreeItem<S>,?> minColumn,
3016                                           int maxRow, TableColumnBase<TreeItem<S>,?> maxColumn) {
3017             if (getSelectionMode() == SelectionMode.SINGLE) {
3018                 quietClearSelection();
3019                 select(maxRow, maxColumn);
3020                 return;
3021             }
3022 
3023             startAtomic();
3024 
3025             final int itemCount = getItemCount();
3026             final boolean isCellSelectionEnabled = isCellSelectionEnabled();
3027 
3028             final int minColumnIndex = treeTableView.getVisibleLeafIndex((TreeTableColumn<S,?>)minColumn);
3029             final int maxColumnIndex = treeTableView.getVisibleLeafIndex((TreeTableColumn<S,?>)maxColumn);
3030             final int _minColumnIndex = Math.min(minColumnIndex, maxColumnIndex);
3031             final int _maxColumnIndex = Math.max(minColumnIndex, maxColumnIndex);
3032 
3033             final int _minRow = Math.min(minRow, maxRow);
3034             final int _maxRow = Math.max(minRow, maxRow);
3035 
3036             List<TreeTablePosition<S,?>> cellsToSelect = new ArrayList<>();
3037 
3038             for (int _row = _minRow; _row <= _maxRow; _row++) {
3039                 // begin copy/paste of select(int, column) method (with some
3040                 // slight modifications)
3041                 if (_row < 0 || _row >= itemCount) continue;
3042 
3043                 if (! isCellSelectionEnabled) {
3044                     cellsToSelect.add(new TreeTablePosition<>(treeTableView, _row, (TreeTableColumn<S,?>)minColumn));
3045                 } else {
3046                     for (int _col = _minColumnIndex; _col <= _maxColumnIndex; _col++) {
3047                         final TreeTableColumn<S, ?> column = treeTableView.getVisibleLeafColumn(_col);
3048 
3049                         // if I'm in cell selection mode but the column is null, I don't want
3050                         // to select the whole row instead...
3051                         if (column == null && isCellSelectionEnabled) continue;
3052 
3053                         cellsToSelect.add(new TreeTablePosition<>(treeTableView, _row, column));
3054                         // end copy/paste
3055                     }
3056                 }
3057             }
3058 
3059             // to prevent duplication we remove all currently selected cells from
3060             // our list of cells to select.
3061             cellsToSelect.removeAll(getSelectedCells());
3062 
3063             selectedCellsMap.addAll(cellsToSelect);
3064             stopAtomic();
3065 
3066             // fire off events
3067             // Note that focus and selection always goes to maxRow, not _maxRow.
3068             updateSelectedIndex(maxRow);
3069             focus(maxRow, (TreeTableColumn<S,?>)maxColumn);
3070 
3071             final TreeTableColumn<S,?> startColumn = (TreeTableColumn<S,?>)minColumn;
3072             final TreeTableColumn<S,?> endColumn = isCellSelectionEnabled ? (TreeTableColumn<S,?>)maxColumn : startColumn;
3073             final int startChangeIndex = selectedCellsMap.indexOf(new TreeTablePosition<>(treeTableView, minRow, startColumn));
3074             final int endChangeIndex = selectedCellsMap.indexOf(new TreeTablePosition<>(treeTableView, maxRow, endColumn));
3075 
3076             if (startChangeIndex > -1 && endChangeIndex > -1) {
3077                 final int startIndex = Math.min(startChangeIndex, endChangeIndex);
3078                 final int endIndex = Math.max(startChangeIndex, endChangeIndex);
3079 
3080                 ListChangeListener.Change c = new NonIterableChange.SimpleAddChange<>(startIndex, endIndex + 1, selectedCellsSeq);
3081                 fireCustomSelectedCellsListChangeEvent(c);
3082 //                selectedCellsSeq.fireChange(() -> selectedCellsSeq._nextAdd(startIndex, endIndex + 1));
3083             }
3084         }
3085 
3086         @Override public void clearSelection(int index) {
3087             clearSelection(index, null);
3088         }
3089 
3090         @Override
3091         public void clearSelection(int row, TableColumnBase<TreeItem<S>,?> column) {
3092             clearSelection(new TreeTablePosition<S,Object>(getTreeTableView(), row, (TreeTableColumn)column));
3093         }
3094 
3095         private void clearSelection(TreeTablePosition<S,?> tp) {
3096             final boolean csMode = isCellSelectionEnabled();
3097             final int row = tp.getRow();
3098             final boolean columnIsNull = tp.getTableColumn() == null;
3099 
3100             List<TreeTablePosition> toRemove = new ArrayList<>();
3101             for (TreeTablePosition pos : getSelectedCells()) {
3102                 if (!csMode) {
3103                     if (pos.getRow() == row) {
3104                         toRemove.add(pos);
3105                         break;
3106                     }
3107                 } else {
3108                     if (columnIsNull && pos.getRow() == row) {
3109                         // if we are in cell selection mode and the column is null,
3110                         // we remove all items in the row
3111                         toRemove.add(pos);
3112                     } else if (pos.equals(tp)) {
3113                         toRemove.add(tp);
3114                         break;
3115                     }
3116                 }
3117             }
3118             toRemove.stream().forEach(selectedCellsMap::remove);
3119 
3120             if (isEmpty() && ! isAtomic()) {
3121                 updateSelectedIndex(-1);
3122                 selectedCellsMap.clear();
3123             }
3124         }
3125 
3126         @Override public void clearSelection() {
3127             final List<TreeTablePosition<S,?>> removed = new ArrayList<>((Collection)getSelectedCells());
3128 
3129             quietClearSelection();
3130 
3131             if (! isAtomic()) {
3132                 updateSelectedIndex(-1);
3133                 focus(-1);
3134 
3135                 if (!removed.isEmpty()) {
3136 //                    selectedCellsSeq.fireChange(() -> selectedCellsSeq._nextRemove(0, removed));
3137                     ListChangeListener.Change<TreeTablePosition<S, ?>> c = new NonIterableChange<TreeTablePosition<S, ?>>(0, 0, selectedCellsSeq) {
3138                         @Override public List<TreeTablePosition<S, ?>> getRemoved() {
3139                             return removed;
3140                         }
3141                     };
3142                     fireCustomSelectedCellsListChangeEvent(c);
3143                 }
3144             }
3145         }
3146 
3147         private void quietClearSelection() {
3148             startAtomic();
3149             selectedCellsMap.clear();
3150             stopAtomic();
3151         }
3152 
3153         @Override public boolean isSelected(int index) {
3154             return isSelected(index, null);
3155         }
3156 
3157         @Override public boolean isSelected(int row, TableColumnBase<TreeItem<S>,?> column) {
3158             // When in cell selection mode, if the column is null, then we interpret
3159             // the users query to be asking if _all_ of the cells in the row are selected,
3160             // rather than if _any_ of the cells in the row are selected.
3161             final boolean isCellSelectionEnabled = isCellSelectionEnabled();
3162             if (isCellSelectionEnabled && column == null) {
3163                 int columnCount = treeTableView.getVisibleLeafColumns().size();
3164                 for (int col = 0; col < columnCount; col++) {
3165                     if (!selectedCellsMap.isSelected(row, col)) {
3166                         return false;
3167                     }
3168                 }
3169                 return true;
3170             } else {
3171                 int columnIndex = !isCellSelectionEnabled || column == null ? -1 : treeTableView.getVisibleLeafIndex((TreeTableColumn<S, ?>) column);
3172                 return selectedCellsMap.isSelected(row, columnIndex);
3173             }
3174         }
3175 
3176         @Override public boolean isEmpty() {
3177             return selectedCellsMap.isEmpty();
3178         }
3179 
3180         @Override public void selectPrevious() {
3181             if (isCellSelectionEnabled()) {
3182                 // in cell selection mode, we have to wrap around, going from
3183                 // right-to-left, and then wrapping to the end of the previous line
3184                 TreeTablePosition<S,?> pos = getFocusedCell();
3185                 if (pos.getColumn() - 1 >= 0) {
3186                     // go to previous row
3187                     select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1));
3188                 } else if (pos.getRow() < getRowCount() - 1) {
3189                     // wrap to end of previous row
3190                     select(pos.getRow() - 1, getTableColumn(getTreeTableView().getVisibleLeafColumns().size() - 1));
3191                 }
3192             } else {
3193                 int focusIndex = getFocusedIndex();
3194                 if (focusIndex == -1) {
3195                     select(getRowCount() - 1);
3196                 } else if (focusIndex > 0) {
3197                     select(focusIndex - 1);
3198                 }
3199             }
3200         }
3201 
3202         @Override public void selectNext() {
3203             if (isCellSelectionEnabled()) {
3204                 // in cell selection mode, we have to wrap around, going from
3205                 // left-to-right, and then wrapping to the start of the next line
3206                 TreeTablePosition<S,?> pos = getFocusedCell();
3207                 if (pos.getColumn() + 1 < getTreeTableView().getVisibleLeafColumns().size()) {
3208                     // go to next column
3209                     select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1));
3210                 } else if (pos.getRow() < getRowCount() - 1) {
3211                     // wrap to start of next row
3212                     select(pos.getRow() + 1, getTableColumn(0));
3213                 }
3214             } else {
3215                 int focusIndex = getFocusedIndex();
3216                 if (focusIndex == -1) {
3217                     select(0);
3218                 } else if (focusIndex < getRowCount() -1) {
3219                     select(focusIndex + 1);
3220                 }
3221             }
3222         }
3223 
3224         @Override public void selectAboveCell() {
3225             TreeTablePosition<S,?> pos = getFocusedCell();
3226             if (pos.getRow() == -1) {
3227                 select(getRowCount() - 1);
3228             } else if (pos.getRow() > 0) {
3229                 select(pos.getRow() - 1, pos.getTableColumn());
3230             }
3231         }
3232 
3233         @Override public void selectBelowCell() {
3234             TreeTablePosition<S,?> pos = getFocusedCell();
3235 
3236             if (pos.getRow() == -1) {
3237                 select(0);
3238             } else if (pos.getRow() < getRowCount() -1) {
3239                 select(pos.getRow() + 1, pos.getTableColumn());
3240             }
3241         }
3242 
3243         @Override public void selectFirst() {
3244             TreeTablePosition<S,?> focusedCell = getFocusedCell();
3245 
3246             if (getSelectionMode() == SelectionMode.SINGLE) {
3247                 quietClearSelection();
3248             }
3249 
3250             if (getRowCount() > 0) {
3251                 if (isCellSelectionEnabled()) {
3252                     select(0, focusedCell.getTableColumn());
3253                 } else {
3254                     select(0);
3255                 }
3256             }
3257         }
3258 
3259         @Override public void selectLast() {
3260             TreeTablePosition<S,?> focusedCell = getFocusedCell();
3261 
3262             if (getSelectionMode() == SelectionMode.SINGLE) {
3263                 quietClearSelection();
3264             }
3265 
3266             int numItems = getRowCount();
3267             if (numItems > 0 && getSelectedIndex() < numItems - 1) {
3268                 if (isCellSelectionEnabled()) {
3269                     select(numItems - 1, focusedCell.getTableColumn());
3270                 } else {
3271                     select(numItems - 1);
3272                 }
3273             }
3274         }
3275 
3276         @Override public void selectLeftCell() {
3277             if (! isCellSelectionEnabled()) return;
3278 
3279             TreeTablePosition<S,?> pos = getFocusedCell();
3280             if (pos.getColumn() - 1 >= 0) {
3281                 select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1));
3282             }
3283         }
3284 
3285         @Override public void selectRightCell() {
3286             if (! isCellSelectionEnabled()) return;
3287 
3288             TreeTablePosition<S,?> pos = getFocusedCell();
3289             if (pos.getColumn() + 1 < getTreeTableView().getVisibleLeafColumns().size()) {
3290                 select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1));
3291             }
3292         }
3293 
3294 
3295 
3296         /***********************************************************************
3297          *                                                                     *
3298          * Support code                                                        *
3299          *                                                                     *
3300          **********************************************************************/
3301 
3302         private void updateDefaultSelection() {
3303             // when the items list totally changes, we should clear out
3304             // the selection
3305             int newSelectionIndex = -1;
3306             TreeItem<S> selectedItem = getSelectedItem();
3307             if (selectedItem != null) {
3308                 newSelectionIndex = treeTableView.getRow(selectedItem);
3309             }
3310 
3311             // we put focus onto the first item, if there is at least
3312             // one item in the list
3313             int newFocusIndex = newSelectionIndex != -1 ? newSelectionIndex : treeTableView.getExpandedItemCount() > 0 ? 0 : -1;
3314 
3315             clearSelection();
3316             select(newSelectionIndex, isCellSelectionEnabled() ? getTableColumn(0) : null);
3317             focus(newFocusIndex, isCellSelectionEnabled() ? getTableColumn(0) : null);
3318         }
3319 
3320         private TreeTableColumn<S,?> getTableColumn(int pos) {
3321             return getTreeTableView().getVisibleLeafColumn(pos);
3322         }
3323 
3324         // Gets a table column to the left or right of the current one, given an offset
3325         private TreeTableColumn<S,?> getTableColumn(TreeTableColumn<S,?> column, int offset) {
3326             int columnIndex = getTreeTableView().getVisibleLeafIndex(column);
3327             int newColumnIndex = columnIndex + offset;
3328             return getTreeTableView().getVisibleLeafColumn(newColumnIndex);
3329         }
3330 
3331         private void updateSelectedIndex(int row) {
3332             setSelectedIndex(row);
3333             setSelectedItem(getModelItem(row));
3334         }
3335 
3336         @Override public void focus(int row) {
3337             focus(row, null);
3338         }
3339 
3340         private void focus(int row, TreeTableColumn<S,?> column) {
3341             focus(new TreeTablePosition<>(getTreeTableView(), row, column));
3342         }
3343 
3344         private void focus(TreeTablePosition<S,?> pos) {
3345             if (getTreeTableView().getFocusModel() == null) return;
3346 
3347             getTreeTableView().getFocusModel().focus(pos.getRow(), pos.getTableColumn());
3348             getTreeTableView().notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
3349         }
3350 
3351         @Override public int getFocusedIndex() {
3352             return getFocusedCell().getRow();
3353         }
3354 
3355         private TreeTablePosition<S,?> getFocusedCell() {
3356             if (treeTableView.getFocusModel() == null) {
3357                 return new TreeTablePosition<>(treeTableView, -1, null);
3358             }
3359             return treeTableView.getFocusModel().getFocusedCell();
3360         }
3361 
3362         private int getRowCount() {
3363             return treeTableView.getExpandedItemCount();
3364         }
3365 
3366         private void fireCustomSelectedCellsListChangeEvent(ListChangeListener.Change<? extends TreeTablePosition<S,?>> c) {
3367             ControlUtils.updateSelectedIndices(this, c);
3368 
3369             if (isAtomic()) {
3370                 return;
3371             }
3372 
3373             selectedCellsSeq.callObservers(new MappingChange<>(c, MappingChange.NOOP_MAP, selectedCellsSeq));
3374         }
3375     }
3376 
3377 
3378 
3379 
3380     /**
3381      * A {@link FocusModel} with additional functionality to support the requirements
3382      * of a TableView control.
3383      *
3384      * @see TableView
3385      * @since JavaFX 8.0
3386      */
3387     public static class TreeTableViewFocusModel<S> extends TableFocusModel<TreeItem<S>, TreeTableColumn<S,?>> {
3388 
3389         private final TreeTableView<S> treeTableView;
3390 
3391         private final TreeTablePosition EMPTY_CELL;
3392 
3393         /**
3394          * Creates a default TableViewFocusModel instance that will be used to
3395          * manage focus of the provided TableView control.
3396          *
3397          * @param treeTableView The tableView upon which this focus model operates.
3398          * @throws NullPointerException The TableView argument can not be null.
3399          */
3400         public TreeTableViewFocusModel(final TreeTableView<S> treeTableView) {
3401             if (treeTableView == null) {
3402                 throw new NullPointerException("TableView can not be null");
3403             }
3404 
3405             this.treeTableView = treeTableView;
3406             this.EMPTY_CELL = new TreeTablePosition<>(treeTableView, -1, null);
3407 
3408             this.treeTableView.rootProperty().addListener(weakRootPropertyListener);
3409             updateTreeEventListener(null, treeTableView.getRoot());
3410 
3411             int focusRow = getItemCount() > 0 ? 0 : -1;
3412             TreeTablePosition<S,?> pos = new TreeTablePosition<>(treeTableView, focusRow, null);
3413             setFocusedCell(pos);
3414 
3415             treeTableView.showRootProperty().addListener(o -> {
3416                 if (isFocused(0)) {
3417                     focus(-1);
3418                     focus(0);
3419                 }
3420             });
3421 
3422             focusedCellProperty().addListener(o -> {
3423                 treeTableView.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
3424             });
3425         }
3426 
3427         private final ChangeListener<TreeItem<S>> rootPropertyListener = (observable, oldValue, newValue) -> {
3428             updateTreeEventListener(oldValue, newValue);
3429         };
3430 
3431         private final WeakChangeListener<TreeItem<S>> weakRootPropertyListener =
3432                 new WeakChangeListener<>(rootPropertyListener);
3433 
3434         private void updateTreeEventListener(TreeItem<S> oldRoot, TreeItem<S> newRoot) {
3435             if (oldRoot != null && weakTreeItemListener != null) {
3436                 oldRoot.removeEventHandler(TreeItem.<S>expandedItemCountChangeEvent(), weakTreeItemListener);
3437             }
3438 
3439             if (newRoot != null) {
3440                 weakTreeItemListener = new WeakEventHandler<>(treeItemListener);
3441                 newRoot.addEventHandler(TreeItem.<S>expandedItemCountChangeEvent(), weakTreeItemListener);
3442             }
3443         }
3444 
3445         private EventHandler<TreeItem.TreeModificationEvent<S>> treeItemListener = new EventHandler<TreeItem.TreeModificationEvent<S>>() {
3446             @Override public void handle(TreeItem.TreeModificationEvent<S> e) {
3447                 // don't shift focus if the event occurred on a tree item after
3448                 // the focused row, or if there is no focus index at present
3449                 if (getFocusedIndex() == -1) return;
3450 
3451                 int shift = 0;
3452                 if (e.getChange() != null) {
3453                     e.getChange().next();
3454                 }
3455 
3456                 do {
3457                     int row = treeTableView.getRow(e.getTreeItem());
3458 
3459                     if (e.wasExpanded()) {
3460                         if (row < getFocusedIndex()) {
3461                             // need to shuffle selection by the number of visible children
3462                             shift += e.getTreeItem().getExpandedDescendentCount(false) - 1;
3463                         }
3464                     } else if (e.wasCollapsed()) {
3465                         if (row < getFocusedIndex()) {
3466                             // need to shuffle selection by the number of visible children
3467                             // that were just hidden
3468                             shift += -e.getTreeItem().previousExpandedDescendentCount + 1;
3469                         }
3470                     } else if (e.wasAdded()) {
3471                         // get the TreeItem the event occurred on - we only need to
3472                         // shift if the tree item is expanded
3473                         TreeItem<S> eventTreeItem = e.getTreeItem();
3474                         if (eventTreeItem.isExpanded()) {
3475                             for (int i = 0; i < e.getAddedChildren().size(); i++) {
3476                                 // get the added item and determine the row it is in
3477                                 TreeItem<S> item = e.getAddedChildren().get(i);
3478                                 row = treeTableView.getRow(item);
3479 
3480                                 if (item != null && row <= (shift+getFocusedIndex())) {
3481                                     shift += item.getExpandedDescendentCount(false);
3482                                 }
3483                             }
3484                         }
3485                     } else if (e.wasRemoved()) {
3486                         row += e.getFrom() + 1;
3487 
3488                         for (int i = 0; i < e.getRemovedChildren().size(); i++) {
3489                             TreeItem<S> item = e.getRemovedChildren().get(i);
3490                             if (item != null && item.equals(getFocusedItem())) {
3491                                 focus(Math.max(0, getFocusedIndex() - 1));
3492                                 return;
3493                             }
3494                         }
3495 
3496                         if (row <= getFocusedIndex()) {
3497                             // shuffle selection by the number of removed items
3498                             shift += e.getTreeItem().isExpanded() ? -e.getRemovedSize() : 0;
3499                         }
3500                     }
3501                 } while (e.getChange() != null && e.getChange().next());
3502 
3503                 if (shift != 0) {
3504                     TreeTablePosition<S, ?> focusedCell = getFocusedCell();
3505                     final int newFocus = focusedCell.getRow() + shift;
3506                     if (newFocus >= 0) {
3507                         Platform.runLater(() -> focus(newFocus, focusedCell.getTableColumn()));
3508                     }
3509                 }
3510             }
3511         };
3512 
3513         private WeakEventHandler<TreeItem.TreeModificationEvent<S>> weakTreeItemListener;
3514 
3515         /** {@inheritDoc} */
3516         @Override protected int getItemCount() {
3517 //            if (tableView.getItems() == null) return -1;
3518 //            return tableView.getItems().size();
3519             return treeTableView.getExpandedItemCount();
3520         }
3521 
3522         /** {@inheritDoc} */
3523         @Override protected TreeItem<S> getModelItem(int index) {
3524             if (index < 0 || index >= getItemCount()) return null;
3525             return treeTableView.getTreeItem(index);
3526         }
3527 
3528         /**
3529          * The position of the current item in the TableView which has the focus.
3530          */
3531         private ReadOnlyObjectWrapper<TreeTablePosition<S,?>> focusedCell;
3532         public final ReadOnlyObjectProperty<TreeTablePosition<S,?>> focusedCellProperty() {
3533             return focusedCellPropertyImpl().getReadOnlyProperty();
3534         }
3535         private void setFocusedCell(TreeTablePosition<S,?> value) { focusedCellPropertyImpl().set(value);  }
3536         public final TreeTablePosition<S,?> getFocusedCell() { return focusedCell == null ? EMPTY_CELL : focusedCell.get(); }
3537 
3538         private ReadOnlyObjectWrapper<TreeTablePosition<S,?>> focusedCellPropertyImpl() {
3539             if (focusedCell == null) {
3540                 focusedCell = new ReadOnlyObjectWrapper<TreeTablePosition<S,?>>(EMPTY_CELL) {
3541                     private TreeTablePosition<S,?> old;
3542                     @Override protected void invalidated() {
3543                         if (get() == null) return;
3544 
3545                         if (old == null || !old.equals(get())) {
3546                             setFocusedIndex(get().getRow());
3547                             setFocusedItem(getModelItem(getValue().getRow()));
3548 
3549                             old = get();
3550                         }
3551                     }
3552 
3553                     @Override
3554                     public Object getBean() {
3555                         return TreeTableView.TreeTableViewFocusModel.this;
3556                     }
3557 
3558                     @Override
3559                     public String getName() {
3560                         return "focusedCell";
3561                     }
3562                 };
3563             }
3564             return focusedCell;
3565         }
3566 
3567 
3568         /**
3569          * Causes the item at the given index to receive the focus.
3570          *
3571          * @param row The row index of the item to give focus to.
3572          * @param column The column of the item to give focus to. Can be null.
3573          */
3574         @Override public void focus(int row, TreeTableColumn<S,?> column) {
3575             if (row < 0 || row >= getItemCount()) {
3576                 setFocusedCell(EMPTY_CELL);
3577             } else {
3578                 TreeTablePosition<S,?> oldFocusCell = getFocusedCell();
3579                 TreeTablePosition<S,?> newFocusCell = new TreeTablePosition<>(treeTableView, row, column);
3580                 setFocusedCell(newFocusCell);
3581 
3582                 if (newFocusCell.equals(oldFocusCell)) {
3583                     // manually update the focus properties to ensure consistency
3584                     setFocusedIndex(row);
3585                     setFocusedItem(getModelItem(row));
3586                 }
3587             }
3588         }
3589 
3590         /**
3591          * Convenience method for setting focus on a particular row or cell
3592          * using a {@link TablePosition}.
3593          *
3594          * @param pos The table position where focus should be set.
3595          */
3596         public void focus(TreeTablePosition<S,?> pos) {
3597             if (pos == null) return;
3598             focus(pos.getRow(), pos.getTableColumn());
3599         }
3600 
3601 
3602         /***********************************************************************
3603          *                                                                     *
3604          * Public API                                                          *
3605          *                                                                     *
3606          **********************************************************************/
3607 
3608         /**
3609          * Tests whether the row / cell at the given location currently has the
3610          * focus within the TableView.
3611          */
3612         @Override public boolean isFocused(int row, TreeTableColumn<S,?> column) {
3613             if (row < 0 || row >= getItemCount()) return false;
3614 
3615             TreeTablePosition<S,?> cell = getFocusedCell();
3616             boolean columnMatch = column == null || column.equals(cell.getTableColumn());
3617 
3618             return cell.getRow() == row && columnMatch;
3619         }
3620 
3621         /**
3622          * Causes the item at the given index to receive the focus. This does not
3623          * cause the current selection to change. Updates the focusedItem and
3624          * focusedIndex properties such that <code>focusedIndex = -1</code> unless
3625          * <pre><code>0 &lt;= index &lt; model size</code></pre>.
3626          *
3627          * @param index The index of the item to get focus.
3628          */
3629         @Override public void focus(int index) {
3630             if (treeTableView.expandedItemCountDirty) {
3631                 treeTableView.updateExpandedItemCount(treeTableView.getRoot());
3632             }
3633 
3634             if (index < 0 || index >= getItemCount()) {
3635                 setFocusedCell(EMPTY_CELL);
3636             } else {
3637                 setFocusedCell(new TreeTablePosition<>(treeTableView, index, null));
3638             }
3639         }
3640 
3641         /**
3642          * Attempts to move focus to the cell above the currently focused cell.
3643          */
3644         @Override public void focusAboveCell() {
3645             TreeTablePosition<S,?> cell = getFocusedCell();
3646 
3647             if (getFocusedIndex() == -1) {
3648                 focus(getItemCount() - 1, cell.getTableColumn());
3649             } else if (getFocusedIndex() > 0) {
3650                 focus(getFocusedIndex() - 1, cell.getTableColumn());
3651             }
3652         }
3653 
3654         /**
3655          * Attempts to move focus to the cell below the currently focused cell.
3656          */
3657         @Override public void focusBelowCell() {
3658             TreeTablePosition<S,?> cell = getFocusedCell();
3659             if (getFocusedIndex() == -1) {
3660                 focus(0, cell.getTableColumn());
3661             } else if (getFocusedIndex() != getItemCount() -1) {
3662                 focus(getFocusedIndex() + 1, cell.getTableColumn());
3663             }
3664         }
3665 
3666         /**
3667          * Attempts to move focus to the cell to the left of the currently focused cell.
3668          */
3669         @Override public void focusLeftCell() {
3670             TreeTablePosition<S,?> cell = getFocusedCell();
3671             if (cell.getColumn() <= 0) return;
3672             focus(cell.getRow(), getTableColumn(cell.getTableColumn(), -1));
3673         }
3674 
3675         /**
3676          * Attempts to move focus to the cell to the right of the the currently focused cell.
3677          */
3678         @Override public void focusRightCell() {
3679             TreeTablePosition<S,?> cell = getFocusedCell();
3680             if (cell.getColumn() == getColumnCount() - 1) return;
3681             focus(cell.getRow(), getTableColumn(cell.getTableColumn(), 1));
3682         }
3683 
3684         /** {@inheritDoc} */
3685         @Override public void focusPrevious() {
3686             if (getFocusedIndex() == -1) {
3687                 focus(0);
3688             } else if (getFocusedIndex() > 0) {
3689                 focusAboveCell();
3690             }
3691         }
3692 
3693         /** {@inheritDoc} */
3694         @Override public void focusNext() {
3695             if (getFocusedIndex() == -1) {
3696                 focus(0);
3697             } else if (getFocusedIndex() != getItemCount() -1) {
3698                 focusBelowCell();
3699             }
3700         }
3701 
3702 
3703 
3704          /***********************************************************************
3705          *                                                                     *
3706          * Private Implementation                                              *
3707          *                                                                     *
3708          **********************************************************************/
3709 
3710         private int getColumnCount() {
3711             return treeTableView.getVisibleLeafColumns().size();
3712         }
3713 
3714         // Gets a table column to the left or right of the current one, given an offset
3715         private TreeTableColumn<S,?> getTableColumn(TreeTableColumn<S,?> column, int offset) {
3716             int columnIndex = treeTableView.getVisibleLeafIndex(column);
3717             int newColumnIndex = columnIndex + offset;
3718             return treeTableView.getVisibleLeafColumn(newColumnIndex);
3719         }
3720     }
3721 }