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