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