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