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