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