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