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