Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.control;
027
028import java.lang.ref.WeakReference;
029import java.util.ArrayList;
030import java.util.BitSet;
031import java.util.Collections;
032import java.util.Comparator;
033import java.util.HashMap;
034import java.util.LinkedHashSet;
035import java.util.List;
036import java.util.Set;
037
038import javafx.beans.DefaultProperty;
039import javafx.beans.InvalidationListener;
040import javafx.beans.Observable;
041import javafx.beans.WeakInvalidationListener;
042import javafx.beans.property.BooleanProperty;
043import javafx.beans.property.DoubleProperty;
044import javafx.beans.property.ObjectProperty;
045import javafx.beans.property.ObjectPropertyBase;
046import javafx.beans.property.ReadOnlyObjectProperty;
047import javafx.beans.property.ReadOnlyObjectWrapper;
048import javafx.beans.property.SimpleBooleanProperty;
049import javafx.beans.property.SimpleDoubleProperty;
050import javafx.beans.property.SimpleObjectProperty;
051import javafx.beans.value.ChangeListener;
052import javafx.beans.value.ObservableValue;
053import javafx.beans.value.WeakChangeListener;
054import javafx.collections.FXCollections;
055import javafx.collections.ListChangeListener;
056import javafx.collections.MapChangeListener;
057import javafx.collections.ObservableList;
058import javafx.collections.WeakListChangeListener;
059import javafx.css.CssMetaData;
060import javafx.css.PseudoClass;
061import javafx.css.Styleable;
062import javafx.css.StyleableDoubleProperty;
063import javafx.css.StyleableProperty;
064import javafx.event.EventHandler;
065import javafx.event.EventType;
066import javafx.geometry.Orientation;
067import javafx.scene.Node;
068import javafx.scene.layout.Region;
069import javafx.util.Callback;
070
071import com.sun.javafx.collections.MappingChange;
072import com.sun.javafx.collections.NonIterableChange;
073import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
074import com.sun.javafx.css.converters.EnumConverter;
075import com.sun.javafx.css.converters.SizeConverter;
076import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
077import com.sun.javafx.scene.control.TableColumnComparatorBase.TableColumnComparator;
078import com.sun.javafx.scene.control.skin.TableViewSkin;
079import com.sun.javafx.scene.control.skin.TableViewSkinBase;
080
081/**
082 * The TableView control is designed to visualize an unlimited number of rows
083 * of data, broken out into columns. A TableView is therefore very similar to the
084 * {@link ListView} control, with the addition of support for columns. For an
085 * example on how to create a TableView, refer to the 'Creating a TableView'
086 * control section below.
087 *
088 * <p>The TableView control has a number of features, including:
089 * <ul>
090 * <li>Powerful {@link TableColumn} API:
091 *   <ul>
092 *   <li>Support for {@link TableColumn#cellFactoryProperty() cell factories} to
093 *      easily customize {@link Cell cell} contents in both rendering and editing
094 *      states.
095 *   <li>Specification of {@link #minWidthProperty() minWidth}/
096 *      {@link #prefWidthProperty() prefWidth}/{@link #maxWidthProperty() maxWidth},
097 *      and also {@link TableColumn#resizableProperty() fixed width columns}.
098 *   <li>Width resizing by the user at runtime.
099 *   <li>Column reordering by the user at runtime.
100 *   <li>Built-in support for {@link TableColumn#getColumns() column nesting}
101 *   </ul>
102 * <li>Different {@link #columnResizePolicyProperty() resizing policies} to 
103 *      dictate what happens when the user resizes columns.
104 * <li>Support for {@link #getSortOrder() multiple column sorting} by clicking 
105 *      the column header (hold down Shift keyboard key whilst clicking on a 
106 *      header to sort by multiple columns).
107 * </ul>
108 * </p>
109 *
110 * <p>Note that TableView is intended to be used to visualize data - it is not
111 * intended to be used for laying out your user interface. If you want to lay
112 * your user interface out in a grid-like fashion, consider the 
113 * {@link GridPane} layout.</p>
114 *
115 * <h2>Creating a TableView</h2>
116 *
117 * <p>Creating a TableView is a multi-step process, and also depends on the
118 * underlying data model needing to be represented. For this example we'll use
119 * an ObservableList<Person>, as it is the simplest way of showing data in a 
120 * TableView. The <code>Person</code> class will consist of a first
121 * name and last name properties. That is:
122 * 
123 * <pre>
124 * {@code
125 * public class Person {
126 *     private StringProperty firstName;
127 *     public void setFirstName(String value) { firstNameProperty().set(value); }
128 *     public String getFirstName() { return firstNameProperty().get(); }
129 *     public StringProperty firstNameProperty() { 
130 *         if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
131 *         return firstName; 
132 *     }
133 * 
134 *     private StringProperty lastName;
135 *     public void setLastName(String value) { lastNameProperty().set(value); }
136 *     public String getLastName() { return lastNameProperty().get(); }
137 *     public StringProperty lastNameProperty() { 
138 *         if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
139 *         return lastName; 
140 *     } 
141 * }}</pre>
142 * 
143 * <p>Firstly, a TableView instance needs to be defined, as such:
144 * 
145 * <pre>
146 * {@code
147 * TableView<Person> table = new TableView<Person>();}</pre>
148 *
149 * <p>With the basic table defined, we next focus on the data model. As mentioned,
150 * for this example, we'll be using a ObservableList<Person>. We can immediately
151 * set such a list directly in to the TableView, as such:
152 *
153 * <pre>
154 * {@code
155 * ObservableList<Person> teamMembers = getTeamMembers();
156 * table.setItems(teamMembers);}</pre>
157 * 
158 * <p>With the items set as such, TableView will automatically update whenever
159 * the <code>teamMembers</code> list changes. If the items list is available
160 * before the TableView is instantiated, it is possible to pass it directly into
161 * the constructor. 
162 * 
163 * <p>At this point we now have a TableView hooked up to observe the 
164 * <code>teamMembers</code> observableList. The missing ingredient 
165 * now is the means of splitting out the data contained within the model and 
166 * representing it in one or more {@link TableColumn TableColumn} instances. To 
167 * create a two-column TableView to show the firstName and lastName properties,
168 * we extend the last code sample as follows:
169 * 
170 * <pre>
171 * {@code
172 * ObservableList<Person> teamMembers = ...;
173 * table.setItems(teamMembers);
174 * 
175 * TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
176 * firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
177 * TableColumn<Person,String> lastNameCol = new TableColumn<Person,String>("Last Name");
178 * lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
179 * 
180 * table.getColumns().setAll(firstNameCol, lastNameCol);}</pre>
181 * 
182 * <p>With the code shown above we have fully defined the minimum properties
183 * required to create a TableView instance. Running this code (assuming the
184 * people ObservableList is appropriately created) will result in a TableView being
185 * shown with two columns for firstName and lastName. Any other properties of the
186 * Person class will not be shown, as no TableColumns are defined.
187 * 
188 * <h3>TableView support for classes that don't contain properties</h3>
189 *
190 * <p>The code shown above is the shortest possible code for creating a TableView
191 * when the domain objects are designed with JavaFX properties in mind 
192 * (additionally, {@link javafx.scene.control.cell.PropertyValueFactory} supports
193 * normal JavaBean properties too, although there is a caveat to this, so refer 
194 * to the class documentation for more information). When this is not the case, 
195 * it is necessary to provide a custom cell value factory. More information
196 * about cell value factories can be found in the {@link TableColumn} API 
197 * documentation, but briefly, here is how a TableColumn could be specified:
198 * 
199 * <pre>
200 * {@code
201 * firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
202 *     public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
203 *         // p.getValue() returns the Person instance for a particular TableView row
204 *         return p.getValue().firstNameProperty();
205 *     }
206 *  });
207 * }}</pre>
208 * 
209 * <h3>TableView Selection / Focus APIs</h3>
210 * <p>To track selection and focus, it is necessary to become familiar with the
211 * {@link SelectionModel} and {@link FocusModel} classes. A TableView has at most
212 * one instance of each of these classes, available from 
213 * {@link #selectionModelProperty() selectionModel} and 
214 * {@link #focusModelProperty() focusModel} properties respectively.
215 * Whilst it is possible to use this API to set a new selection model, in
216 * most circumstances this is not necessary - the default selection and focus
217 * models should work in most circumstances.
218 * 
219 * <p>The default {@link SelectionModel} used when instantiating a TableView is
220 * an implementation of the {@link MultipleSelectionModel} abstract class. 
221 * However, as noted in the API documentation for
222 * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode}
223 * property, the default value is {@link SelectionMode#SINGLE}. To enable 
224 * multiple selection in a default TableView instance, it is therefore necessary
225 * to do the following:
226 * 
227 * <pre>
228 * {@code 
229 * tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre>
230 *
231 * <h3>Customizing TableView Visuals</h3>
232 * <p>The visuals of the TableView can be entirely customized by replacing the 
233 * default {@link #rowFactoryProperty() row factory}. A row factory is used to
234 * generate {@link TableRow} instances, which are used to represent an entire
235 * row in the TableView. 
236 * 
237 * <p>In many cases, this is not what is desired however, as it is more commonly
238 * the case that cells be customized on a per-column basis, not a per-row basis.
239 * It is therefore important to note that a {@link TableRow} is not a 
240 * {@link TableCell}. A  {@link TableRow} is simply a container for zero or more
241 * {@link TableCell}, and in most circumstances it is more likely that you'll 
242 * want to create custom TableCells, rather than TableRows. The primary use case
243 * for creating custom TableRow instances would most probably be to introduce
244 * some form of column spanning support.
245 * 
246 * <p>You can create custom {@link TableCell} instances per column by assigning 
247 * the appropriate function to the TableColumn
248 * {@link TableColumn#cellFactoryProperty() cell factory} property.
249 * 
250 * <p>See the {@link Cell} class documentation for a more complete
251 * description of how to write custom Cells.
252 *
253 * @see TableColumn
254 * @see TablePosition
255 * @param <S> The type of the objects contained within the TableView items list.
256 */
257@DefaultProperty("items")
258public class TableView<S> extends Control {
259    
260    /***************************************************************************
261     *                                                                         *
262     * Static properties and methods                                           *
263     *                                                                         *
264     **************************************************************************/
265
266    // strings used to communicate via the TableView properties map between
267    // the control and the skin. Because they are private here, the strings
268    // are also duplicated in the TableViewSkin class - so any changes to these
269    // strings must also be duplicated there
270    static final String SET_CONTENT_WIDTH = "TableView.contentWidth";
271    
272    /**
273     * <p>Very simple resize policy that just resizes the specified column by the
274     * provided delta and shifts all other columns (to the right of the given column)
275     * further to the right (when the delta is positive) or to the left (when the
276     * delta is negative).
277     *
278     * <p>It also handles the case where we have nested columns by sharing the new space,
279     * or subtracting the removed space, evenly between all immediate children columns.
280     * Of course, the immediate children may themselves be nested, and they would
281     * then use this policy on their children.
282     */
283    public static final Callback<ResizeFeatures, Boolean> UNCONSTRAINED_RESIZE_POLICY = new Callback<ResizeFeatures, Boolean>() {
284        @Override public String toString() {
285            return "unconstrained-resize";
286        }
287        
288        @Override public Boolean call(ResizeFeatures prop) {
289            double result = TableUtil.resize(prop.getColumn(), prop.getDelta());
290            return Double.compare(result, 0.0) == 0;
291        }
292    };
293
294    /**
295     * <p>Simple policy that ensures the width of all visible leaf columns in 
296     * this table sum up to equal the width of the table itself.
297     * 
298     * <p>When the user resizes a column width with this policy, the table automatically
299     * adjusts the width of the right hand side columns. When the user increases a
300     * column width, the table decreases the width of the rightmost column until it
301     * reaches its minimum width. Then it decreases the width of the second
302     * rightmost column until it reaches minimum width and so on. When all right
303     * hand side columns reach minimum size, the user cannot increase the size of
304     * resized column any more.
305     */
306    public static final Callback<ResizeFeatures, Boolean> CONSTRAINED_RESIZE_POLICY = new Callback<ResizeFeatures, Boolean>() {
307
308        private boolean isFirstRun = true;
309        
310        @Override public String toString() {
311            return "constrained-resize";
312        }
313        
314        @Override public Boolean call(ResizeFeatures prop) {
315            TableView<?> table = prop.getTable();
316            List<? extends TableColumnBase<?,?>> visibleLeafColumns = table.getVisibleLeafColumns();
317            Boolean result = TableUtil.constrainedResize(prop, 
318                                               isFirstRun, 
319                                               table.contentWidth,
320                                               visibleLeafColumns);
321            isFirstRun = false;
322            return result;
323        }
324    };
325    
326    /**
327     * The default {@link #sortPolicyProperty() sort policy} that this TableView
328     * will use if no other policy is specified. The sort policy is a simple 
329     * {@link Callback} that accepts a TableView as the sole argument and expects
330     * a Boolean response representing whether the sort succeeded or not. A Boolean
331     * response of true represents success, and a response of false (or null) will
332     * be considered to represent failure.
333     */
334    public static final Callback<TableView, Boolean> DEFAULT_SORT_POLICY = new Callback<TableView, Boolean>() {
335        @Override public Boolean call(TableView table) {
336            try {
337                FXCollections.sort(table.getItems(), table.getComparator());
338                return true;
339            } catch (UnsupportedOperationException e) {
340                // TODO might need to support other exception types including:
341                // ClassCastException - if the class of the specified element prevents it from being added to this list
342                // NullPointerException - if the specified element is null and this list does not permit null elements
343                // IllegalArgumentException - if some property of this element prevents it from being added to this list
344
345                // If we are here the list does not support sorting, so we gracefully 
346                // fail the sort request and ensure the UI is put back to its previous
347                // state. This is handled in the code that calls the sort policy.
348                
349                return false;
350            }
351        }
352    };
353    
354    
355    
356    /***************************************************************************
357     *                                                                         *
358     * Constructors                                                            *
359     *                                                                         *
360     **************************************************************************/
361
362    /**
363     * Creates a default TableView control with no content.
364     * 
365     * <p>Refer to the {@link TableView} class documentation for details on the
366     * default state of other properties.
367     */
368    public TableView() {
369        this(FXCollections.<S>observableArrayList());
370    }
371
372    /**
373     * Creates a TableView with the content provided in the items ObservableList.
374     * This also sets up an observer such that any changes to the items list
375     * will be immediately reflected in the TableView itself.
376     * 
377     * <p>Refer to the {@link TableView} class documentation for details on the
378     * default state of other properties.
379     * 
380     * @param items The items to insert into the TableView, and the list to watch
381     *          for changes (to automatically show in the TableView).
382     */
383    public TableView(ObservableList<S> items) {
384        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
385
386        // we quite happily accept items to be null here
387        setItems(items);
388
389        // install default selection and focus models
390        // it's unlikely this will be changed by many users.
391        setSelectionModel(new TableViewArrayListSelectionModel<S>(this));
392        setFocusModel(new TableViewFocusModel<S>(this));
393
394        // we watch the columns list, such that when it changes we can update
395        // the leaf columns and visible leaf columns lists (which are read-only).
396        getColumns().addListener(weakColumnsObserver);
397
398        // watch for changes to the sort order list - and when it changes run
399        // the sort method.
400        getSortOrder().addListener(new ListChangeListener<TableColumn<S,?>>() {
401            @Override public void onChanged(Change<? extends TableColumn<S,?>> c) {
402                doSort(TableUtil.SortEventType.SORT_ORDER_CHANGE, c);
403            }
404        });
405
406        // We're watching for changes to the content width such
407        // that the resize policy can be run if necessary. This comes from
408        // TreeViewSkin.
409        getProperties().addListener(new MapChangeListener<Object, Object>() {
410            @Override
411            public void onChanged(Change<? extends Object, ? extends Object> c) {
412                if (c.wasAdded() && SET_CONTENT_WIDTH.equals(c.getKey())) {
413                    if (c.getValueAdded() instanceof Number) {
414                        setContentWidth((Double) c.getValueAdded());
415                    }
416                    getProperties().remove(SET_CONTENT_WIDTH);
417                }
418            }
419        });
420
421        isInited = true;
422    }
423
424    
425    
426    /***************************************************************************
427     *                                                                         *
428     * Instance Variables                                                      *
429     *                                                                         *
430     **************************************************************************/    
431
432    // this is the only publicly writable list for columns. This represents the
433    // columns as they are given initially by the developer.
434    private final ObservableList<TableColumn<S,?>> columns = FXCollections.observableArrayList();
435
436    // Finally, as convenience, we also have an observable list that contains
437    // only the leaf columns that are currently visible.
438    private final ObservableList<TableColumn<S,?>> visibleLeafColumns = FXCollections.observableArrayList();
439    private final ObservableList<TableColumn<S,?>> unmodifiableVisibleLeafColumns = FXCollections.unmodifiableObservableList(visibleLeafColumns);
440    
441    
442    // Allows for multiple column sorting based on the order of the TableColumns
443    // in this observableArrayList. Each TableColumn is responsible for whether it is
444    // sorted using ascending or descending order.
445    private ObservableList<TableColumn<S,?>> sortOrder = FXCollections.observableArrayList();
446
447    // width of VirtualFlow minus the vbar width
448    private double contentWidth;
449    
450    // Used to minimise the amount of work performed prior to the table being
451    // completely initialised. In particular it reduces the amount of column
452    // resize operations that occur, which slightly improves startup time.
453    private boolean isInited = false;
454    
455    
456    
457    /***************************************************************************
458     *                                                                         *
459     * Callbacks and Events                                                    *
460     *                                                                         *
461     **************************************************************************/
462    
463    private final ListChangeListener<TableColumn<S,?>> columnsObserver = new ListChangeListener<TableColumn<S,?>>() {
464        @Override public void onChanged(Change<? extends TableColumn<S,?>> c) {
465            // We don't maintain a bind for leafColumns, we simply call this update
466            // function behind the scenes in the appropriate places.
467            updateVisibleLeafColumns();
468            
469            // Fix for RT-15194: Need to remove removed columns from the 
470            // sortOrder list.
471            List<TableColumn<S,?>> toRemove = new ArrayList<TableColumn<S,?>>();
472            while (c.next()) {
473                final List<? extends TableColumn<S, ?>> removed = c.getRemoved();
474                final List<? extends TableColumn<S, ?>> added = c.getAddedSubList();
475                
476                if (c.wasRemoved()) {
477                    toRemove.addAll(removed);
478                    for (TableColumn<S,?> tc : removed) {
479                        tc.setTableView(null);
480                    }
481                }
482                
483                if (c.wasAdded()) {
484                    toRemove.removeAll(added);
485                    for (TableColumn<S,?> tc : added) {
486                        tc.setTableView(TableView.this);
487                    }
488                }
489                
490                // set up listeners
491                TableUtil.removeColumnsListener(removed, weakColumnsObserver);
492                TableUtil.addColumnsListener(added, weakColumnsObserver);
493                
494                TableUtil.removeTableColumnListener(c.getRemoved(),
495                        weakColumnVisibleObserver,
496                        weakColumnSortableObserver,
497                        weakColumnSortTypeObserver,
498                        weakColumnComparatorObserver);
499                TableUtil.addTableColumnListener(c.getAddedSubList(),
500                        weakColumnVisibleObserver,
501                        weakColumnSortableObserver,
502                        weakColumnSortTypeObserver,
503                        weakColumnComparatorObserver);
504            }
505            
506            sortOrder.removeAll(toRemove);
507        }
508    };
509    
510    private final InvalidationListener columnVisibleObserver = new InvalidationListener() {
511        @Override public void invalidated(Observable valueModel) {
512            updateVisibleLeafColumns();
513        }
514    };
515    
516    private final InvalidationListener columnSortableObserver = new InvalidationListener() {
517        @Override public void invalidated(Observable valueModel) {
518            TableColumn col = (TableColumn) ((BooleanProperty)valueModel).getBean();
519            if (! getSortOrder().contains(col)) return;
520            doSort(TableUtil.SortEventType.COLUMN_SORTABLE_CHANGE, col);
521        }
522    };
523
524    private final InvalidationListener columnSortTypeObserver = new InvalidationListener() {
525        @Override public void invalidated(Observable valueModel) {
526            TableColumn col = (TableColumn) ((ObjectProperty)valueModel).getBean();
527            if (! getSortOrder().contains(col)) return;
528            doSort(TableUtil.SortEventType.COLUMN_SORT_TYPE_CHANGE, col);
529        }
530    };
531    
532    private final InvalidationListener columnComparatorObserver = new InvalidationListener() {
533        @Override public void invalidated(Observable valueModel) {
534            TableColumn col = (TableColumn) ((SimpleObjectProperty)valueModel).getBean();
535            if (! getSortOrder().contains(col)) return;
536            doSort(TableUtil.SortEventType.COLUMN_COMPARATOR_CHANGE, col);
537        }
538    };
539    
540    /* proxy pseudo-class state change from selectionModel's cellSelectionEnabledProperty */
541    private final InvalidationListener cellSelectionModelInvalidationListener = new InvalidationListener() {
542        @Override public void invalidated(Observable o) {
543            final boolean isCellSelection = ((BooleanProperty)o).get();
544            pseudoClassStateChanged(PSEUDO_CLASS_CELL_SELECTION,  isCellSelection);
545            pseudoClassStateChanged(PSEUDO_CLASS_ROW_SELECTION,  !isCellSelection);
546        }
547    };
548    
549    
550    private final WeakInvalidationListener weakColumnVisibleObserver = 
551            new WeakInvalidationListener(columnVisibleObserver);
552    
553    private final WeakInvalidationListener weakColumnSortableObserver = 
554            new WeakInvalidationListener(columnSortableObserver);
555    
556    private final WeakInvalidationListener weakColumnSortTypeObserver = 
557            new WeakInvalidationListener(columnSortTypeObserver);
558    
559    private final WeakInvalidationListener weakColumnComparatorObserver = 
560            new WeakInvalidationListener(columnComparatorObserver);
561    
562    private final WeakListChangeListener<TableColumn<S,?>> weakColumnsObserver = 
563            new WeakListChangeListener<TableColumn<S,?>>(columnsObserver);
564    
565    private final WeakInvalidationListener weakCellSelectionModelInvalidationListener = 
566            new WeakInvalidationListener(cellSelectionModelInvalidationListener);
567    
568    /***************************************************************************
569     *                                                                         *
570     * Properties                                                              *
571     *                                                                         *
572     **************************************************************************/
573
574
575    // --- Items
576    /**
577     * The underlying data model for the TableView. Note that it has a generic
578     * type that must match the type of the TableView itself.
579     */
580    public final ObjectProperty<ObservableList<S>> itemsProperty() { return items; }
581    private ObjectProperty<ObservableList<S>> items = 
582        new SimpleObjectProperty<ObservableList<S>>(this, "items") {
583            WeakReference<ObservableList<S>> oldItemsRef;
584            
585            @Override protected void invalidated() {
586                ObservableList<S> oldItems = oldItemsRef == null ? null : oldItemsRef.get();
587                
588                // FIXME temporary fix for RT-15793. This will need to be
589                // properly fixed when time permits
590                if (getSelectionModel() instanceof TableViewArrayListSelectionModel) {
591                    ((TableViewArrayListSelectionModel)getSelectionModel()).updateItemsObserver(oldItems, getItems());
592                }
593                if (getFocusModel() != null) {
594                    ((TableViewFocusModel)getFocusModel()).updateItemsObserver(oldItems, getItems());
595                }
596                if (getSkin() instanceof TableViewSkin) {
597                    TableViewSkin skin = (TableViewSkin) getSkin();
598                    skin.updateTableItems(oldItems, getItems());
599                }
600                
601                oldItemsRef = new WeakReference<ObservableList<S>>(getItems());
602            }
603        };
604    public final void setItems(ObservableList<S> value) { itemsProperty().set(value); }
605    public final ObservableList<S> getItems() {return items.get(); }
606    
607    
608    // --- Table menu button visible
609    private BooleanProperty tableMenuButtonVisible;
610    /**
611     * This controls whether a menu button is available when the user clicks
612     * in a designated space within the TableView, within which is a radio menu
613     * item for each TableColumn in this table. This menu allows for the user to
614     * show and hide all TableColumns easily.
615     */
616    public final BooleanProperty tableMenuButtonVisibleProperty() {
617        if (tableMenuButtonVisible == null) {
618            tableMenuButtonVisible = new SimpleBooleanProperty(this, "tableMenuButtonVisible");
619        }
620        return tableMenuButtonVisible;
621    }
622    public final void setTableMenuButtonVisible (boolean value) {
623        tableMenuButtonVisibleProperty().set(value);
624    }
625    public final boolean isTableMenuButtonVisible() {
626        return tableMenuButtonVisible == null ? false : tableMenuButtonVisible.get();
627    }
628    
629    
630    // --- Column Resize Policy
631    private ObjectProperty<Callback<ResizeFeatures, Boolean>> columnResizePolicy;
632    public final void setColumnResizePolicy(Callback<ResizeFeatures, Boolean> callback) {
633        columnResizePolicyProperty().set(callback);
634    }
635    public final Callback<ResizeFeatures, Boolean> getColumnResizePolicy() {
636        return columnResizePolicy == null ? UNCONSTRAINED_RESIZE_POLICY : columnResizePolicy.get();
637    }
638
639    /**
640     * This is the function called when the user completes a column-resize
641     * operation. The two most common policies are available as static functions
642     * in the TableView class: {@link #UNCONSTRAINED_RESIZE_POLICY} and
643     * {@link #CONSTRAINED_RESIZE_POLICY}.
644     */
645    public final ObjectProperty<Callback<ResizeFeatures, Boolean>> columnResizePolicyProperty() {
646        if (columnResizePolicy == null) {
647            columnResizePolicy = new SimpleObjectProperty<Callback<ResizeFeatures, Boolean>>(this, "columnResizePolicy", UNCONSTRAINED_RESIZE_POLICY) {
648                private Callback<ResizeFeatures, Boolean> oldPolicy;
649                
650                @Override protected void invalidated() {
651                    if (isInited) {
652                        get().call(new ResizeFeatures(TableView.this, null, 0.0));
653                        refresh();
654                
655                        if (oldPolicy != null) {
656                            PseudoClass state = PseudoClass.getPseudoClass(oldPolicy.toString());
657                            pseudoClassStateChanged(state, false);
658                        }
659                        if (get() != null) {
660                            PseudoClass state = PseudoClass.getPseudoClass(get().toString());
661                            pseudoClassStateChanged(state, true);
662                        }
663                        oldPolicy = get();
664                    }
665                }
666            };
667        }
668        return columnResizePolicy;
669    }
670    
671    
672    // --- Row Factory
673    private ObjectProperty<Callback<TableView<S>, TableRow<S>>> rowFactory;
674
675    /**
676     * A function which produces a TableRow. The system is responsible for
677     * reusing TableRows. Return from this function a TableRow which
678     * might be usable for representing a single row in a TableView.
679     * <p>
680     * Note that a TableRow is <b>not</b> a TableCell. A TableRow is
681     * simply a container for a TableCell, and in most circumstances it is more
682     * likely that you'll want to create custom TableCells, rather than
683     * TableRows. The primary use case for creating custom TableRow
684     * instances would most probably be to introduce some form of column
685     * spanning support.
686     * <p>
687     * You can create custom TableCell instances per column by assigning the
688     * appropriate function to the cellFactory property in the TableColumn class.
689     */
690    public final ObjectProperty<Callback<TableView<S>, TableRow<S>>> rowFactoryProperty() {
691        if (rowFactory == null) {
692            rowFactory = new SimpleObjectProperty<Callback<TableView<S>, TableRow<S>>>(this, "rowFactory");
693        }
694        return rowFactory;
695    }
696    public final void setRowFactory(Callback<TableView<S>, TableRow<S>> value) {
697        rowFactoryProperty().set(value);
698    }
699    public final Callback<TableView<S>, TableRow<S>> getRowFactory() {
700        return rowFactory == null ? null : rowFactory.get();
701    }
702    
703    
704    // --- Placeholder Node
705    private ObjectProperty<Node> placeholder;
706    /**
707     * This Node is shown to the user when the table has no content to show.
708     * This may be the case because the table model has no data in the first
709     * place, that a filter has been applied to the table model, resulting
710     * in there being nothing to show the user, or that there are no currently
711     * visible columns.
712     */
713    public final ObjectProperty<Node> placeholderProperty() {
714        if (placeholder == null) {
715            placeholder = new SimpleObjectProperty<Node>(this, "placeholder");
716        }
717        return placeholder;
718    }
719    public final void setPlaceholder(Node value) {
720        placeholderProperty().set(value);
721    }
722    public final Node getPlaceholder() {
723        return placeholder == null ? null : placeholder.get();
724    }
725
726
727    // --- Selection Model
728    private ObjectProperty<TableViewSelectionModel<S>> selectionModel 
729            = new SimpleObjectProperty<TableViewSelectionModel<S>>(this, "selectionModel") {
730
731        TableViewSelectionModel<S> oldValue = null;
732        
733        @Override protected void invalidated() {
734            
735            if (oldValue != null) {
736                oldValue.cellSelectionEnabledProperty().removeListener(weakCellSelectionModelInvalidationListener);
737            }
738            
739            oldValue = get();
740            
741            if (oldValue != null) {
742                oldValue.cellSelectionEnabledProperty().addListener(weakCellSelectionModelInvalidationListener);
743                // fake an invalidation to ensure updated pseudo-class state
744                weakCellSelectionModelInvalidationListener.invalidated(oldValue.cellSelectionEnabledProperty());
745            } 
746        }
747    };
748    
749    /**
750     * The SelectionModel provides the API through which it is possible
751     * to select single or multiple items within a TableView, as  well as inspect
752     * which items have been selected by the user. Note that it has a generic
753     * type that must match the type of the TableView itself.
754     */
755    public final ObjectProperty<TableViewSelectionModel<S>> selectionModelProperty() {
756        return selectionModel;
757    }
758    public final void setSelectionModel(TableViewSelectionModel<S> value) {
759        selectionModelProperty().set(value);
760    }
761
762    public final TableViewSelectionModel<S> getSelectionModel() {
763        return selectionModel.get();
764    }
765
766    
767    // --- Focus Model
768    private ObjectProperty<TableViewFocusModel<S>> focusModel;
769    public final void setFocusModel(TableViewFocusModel<S> value) {
770        focusModelProperty().set(value);
771    }
772    public final TableViewFocusModel<S> getFocusModel() {
773        return focusModel == null ? null : focusModel.get();
774    }
775    /**
776     * Represents the currently-installed {@link TableViewFocusModel} for this
777     * TableView. Under almost all circumstances leaving this as the default
778     * focus model will suffice.
779     */
780    public final ObjectProperty<TableViewFocusModel<S>> focusModelProperty() {
781        if (focusModel == null) {
782            focusModel = new SimpleObjectProperty<TableViewFocusModel<S>>(this, "focusModel");
783        }
784        return focusModel;
785    }
786    
787    
788//    // --- Span Model
789//    private ObjectProperty<SpanModel<S>> spanModel 
790//            = new SimpleObjectProperty<SpanModel<S>>(this, "spanModel") {
791//
792//        @Override protected void invalidated() {
793//            ObservableList<String> styleClass = getStyleClass();
794//            if (getSpanModel() == null) {
795//                styleClass.remove(CELL_SPAN_TABLE_VIEW_STYLE_CLASS);
796//            } else if (! styleClass.contains(CELL_SPAN_TABLE_VIEW_STYLE_CLASS)) {
797//                styleClass.add(CELL_SPAN_TABLE_VIEW_STYLE_CLASS);
798//            }
799//        }
800//    };
801//
802//    public final ObjectProperty<SpanModel<S>> spanModelProperty() {
803//        return spanModel;
804//    }
805//    public final void setSpanModel(SpanModel<S> value) {
806//        spanModelProperty().set(value);
807//    }
808//
809//    public final SpanModel<S> getSpanModel() {
810//        return spanModel.get();
811//    }
812    
813    // --- Editable
814    private BooleanProperty editable;
815    public final void setEditable(boolean value) {
816        editableProperty().set(value);
817    }
818    public final boolean isEditable() {
819        return editable == null ? false : editable.get();
820    }
821    /**
822     * Specifies whether this TableView is editable - only if the TableView, the
823     * TableColumn (if applicable) and the TableCells within it are both 
824     * editable will a TableCell be able to go into their editing state.
825     */
826    public final BooleanProperty editableProperty() {
827        if (editable == null) {
828            editable = new SimpleBooleanProperty(this, "editable", false);
829        }
830        return editable;
831    }
832
833
834    // --- Fixed cell size
835    private DoubleProperty fixedCellSize;
836
837    /**
838     * Sets the new fixed cell size for this control. Any value greater than
839     * zero will enable fixed cell size mode, whereas a zero or negative value
840     * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size
841     * mode.
842     *
843     * @param value The new fixed cell size value, or -1 (or Region.USE_COMPUTED_SIZE)
844     *                  to disable.
845     */
846    public final void setFixedCellSize(double value) {
847        fixedCellSizeProperty().set(value);
848    }
849
850    /**
851     * Returns the fixed cell size value, which may be -1 to represent fixed cell
852     * size mode is disabled, or a value greater than zero to represent the size
853     * of all cells in this control.
854     *
855     * @return A double representing the fixed cell size of this control, or -1
856     *      if fixed cell size mode is disabled.
857     */
858    public final double getFixedCellSize() {
859        return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get();
860    }
861    /**
862     * Specifies whether this control has cells that are a fixed height (of the
863     * specified value). If this value is -1 (i.e. {@link Region#USE_COMPUTED_SIZE}),
864     * then all cells are individually sized and positioned. This is a slow
865     * operation. Therefore, when performance matters and developers are not
866     * dependent on variable cell sizes it is a good idea to set the fixed cell
867     * size value. Generally cells are around 24px, so setting a fixed cell size
868     * of 24 is likely to result in very little difference in visuals, but a
869     * improvement to performance.
870     *
871     * <p>To set this property via CSS, use the -fx-fixed-cell-size property.
872     * This should not be confused with the -fx-cell-size property. The difference
873     * between these two CSS properties is that -fx-cell-size will size all
874     * cells to the specified size, but it will not enforce that this is the
875     * only size (thus allowing for variable cell sizes, and preventing the
876     * performance gains from being possible). Therefore, when performance matters
877     * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are
878     * specified in CSS, -fx-fixed-cell-size takes precedence.</p>
879     */
880    public final DoubleProperty fixedCellSizeProperty() {
881        if (fixedCellSize == null) {
882            fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) {
883                @Override public CssMetaData<TableView<?>,Number> getCssMetaData() {
884                    return StyleableProperties.FIXED_CELL_SIZE;
885                }
886
887                @Override public Object getBean() {
888                    return TableView.this;
889                }
890
891                @Override public String getName() {
892                    return "fixedCellSize";
893                }
894            };
895        }
896        return fixedCellSize;
897    }
898
899
900    // --- Editing Cell
901    private ReadOnlyObjectWrapper<TablePosition<S,?>> editingCell;
902    private void setEditingCell(TablePosition<S,?> value) {
903        editingCellPropertyImpl().set(value);
904    }
905    public final TablePosition<S,?> getEditingCell() {
906        return editingCell == null ? null : editingCell.get();
907    }
908
909    /**
910     * Represents the current cell being edited, or null if
911     * there is no cell being edited.
912     */
913    public final ReadOnlyObjectProperty<TablePosition<S,?>> editingCellProperty() {
914        return editingCellPropertyImpl().getReadOnlyProperty();
915    }
916
917    private ReadOnlyObjectWrapper<TablePosition<S,?>> editingCellPropertyImpl() {
918        if (editingCell == null) {
919            editingCell = new ReadOnlyObjectWrapper<TablePosition<S,?>>(this, "editingCell");
920        }
921        return editingCell;
922    }
923
924    
925    // --- Comparator (built via sortOrder list, so read-only)
926    /**
927     * The comparator property is a read-only property that is representative of the
928     * current state of the {@link #getSortOrder() sort order} list. The sort
929     * order list contains the columns that have been added to it either programmatically
930     * or via a user clicking on the headers themselves.
931     */
932    private ReadOnlyObjectWrapper<Comparator<S>> comparator;
933    private void setComparator(Comparator<S> value) {
934        comparatorPropertyImpl().set(value);
935    }
936    public final Comparator<S> getComparator() {
937        return comparator == null ? null : comparator.get();
938    }
939    public final ReadOnlyObjectProperty<Comparator<S>> comparatorProperty() {
940        return comparatorPropertyImpl().getReadOnlyProperty();
941    }
942    private ReadOnlyObjectWrapper<Comparator<S>> comparatorPropertyImpl() {
943        if (comparator == null) {
944            comparator = new ReadOnlyObjectWrapper<Comparator<S>>(this, "comparator");
945        }
946        return comparator;
947    }
948    
949    
950    // --- sortPolicy
951    /**
952     * The sort policy specifies how sorting in this TableView should be performed.
953     * For example, a basic sort policy may just call 
954     * {@code FXCollections.sort(tableView.getItems())}, whereas a more advanced
955     * sort policy may call to a database to perform the necessary sorting on the
956     * server-side.
957     * 
958     * <p>TableView ships with a {@link TableView#DEFAULT_SORT_POLICY default
959     * sort policy} that does precisely as mentioned above: it simply attempts
960     * to sort the items list in-place.
961     * 
962     * <p>It is recommended that rather than override the {@link TableView#sort() sort}
963     * method that a different sort policy be provided instead.
964     */
965    private ObjectProperty<Callback<TableView<S>, Boolean>> sortPolicy;
966    public final void setSortPolicy(Callback<TableView<S>, Boolean> callback) {
967        sortPolicyProperty().set(callback);
968    }
969    @SuppressWarnings("unchecked") 
970    public final Callback<TableView<S>, Boolean> getSortPolicy() {
971        return sortPolicy == null ? 
972                (Callback<TableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY : 
973                sortPolicy.get();
974    }
975    @SuppressWarnings("unchecked")
976    public final ObjectProperty<Callback<TableView<S>, Boolean>> sortPolicyProperty() {
977        if (sortPolicy == null) {
978            sortPolicy = new SimpleObjectProperty<Callback<TableView<S>, Boolean>>(
979                    this, "sortPolicy", (Callback<TableView<S>, Boolean>)(Object) DEFAULT_SORT_POLICY) {
980                @Override protected void invalidated() {
981                    sort();
982                }
983            };
984        }
985        return sortPolicy;
986    }
987    
988    
989    // onSort
990    /**
991     * Called when there's a request to sort the control.
992     */
993    private ObjectProperty<EventHandler<SortEvent<TableView<S>>>> onSort;
994    
995    public void setOnSort(EventHandler<SortEvent<TableView<S>>> value) {
996        onSortProperty().set(value);
997    }
998    
999    public EventHandler<SortEvent<TableView<S>>> getOnSort() {
1000        if( onSort != null ) {
1001            return onSort.get();
1002        }
1003        return null;
1004    }
1005    
1006    public ObjectProperty<EventHandler<SortEvent<TableView<S>>>> onSortProperty() {
1007        if( onSort == null ) {
1008            onSort = new ObjectPropertyBase<EventHandler<SortEvent<TableView<S>>>>() {
1009                @Override protected void invalidated() {
1010                    EventType<SortEvent<TableView<S>>> eventType = SortEvent.sortEvent();
1011                    EventHandler<SortEvent<TableView<S>>> eventHandler = get();
1012                    setEventHandler(eventType, eventHandler);
1013                }
1014                
1015                @Override public Object getBean() {
1016                    return TableView.this;
1017                }
1018
1019                @Override public String getName() {
1020                    return "onSort";
1021                }
1022            };
1023        }
1024        return onSort;
1025    }
1026
1027    
1028    /***************************************************************************
1029     *                                                                         *
1030     * Public API                                                              *
1031     *                                                                         *
1032     **************************************************************************/
1033    /**
1034     * The TableColumns that are part of this TableView. As the user reorders
1035     * the TableView columns, this list will be updated to reflect the current
1036     * visual ordering.
1037     *
1038     * <p>Note: to display any data in a TableView, there must be at least one
1039     * TableColumn in this ObservableList.</p>
1040     */
1041    public final ObservableList<TableColumn<S,?>> getColumns() {
1042        return columns;
1043    }
1044    
1045    /**
1046     * The sortOrder list defines the order in which {@link TableColumn} instances
1047     * are sorted. An empty sortOrder list means that no sorting is being applied
1048     * on the TableView. If the sortOrder list has one TableColumn within it, 
1049     * the TableView will be sorted using the 
1050     * {@link TableColumn#sortTypeProperty() sortType} and
1051     * {@link TableColumn#comparatorProperty() comparator} properties of this
1052     * TableColumn (assuming 
1053     * {@link TableColumn#sortableProperty() TableColumn.sortable} is true).
1054     * If the sortOrder list contains multiple TableColumn instances, then
1055     * the TableView is firstly sorted based on the properties of the first 
1056     * TableColumn. If two elements are considered equal, then the second
1057     * TableColumn in the list is used to determine ordering. This repeats until
1058     * the results from all TableColumn comparators are considered, if necessary.
1059     * 
1060     * @return An ObservableList containing zero or more TableColumn instances.
1061     */
1062    public final ObservableList<TableColumn<S,?>> getSortOrder() {
1063        return sortOrder;
1064    }
1065    
1066    /**
1067     * Scrolls the TableView so that the given index is visible within the viewport.
1068     * @param index The index of an item that should be visible to the user.
1069     */
1070    public void scrollTo(int index) {
1071       ControlUtils.scrollToIndex(this, index);
1072    }
1073    
1074    /**
1075     * Scrolls the TableView so that the given object is visible within the viewport.
1076     * @param object The object that should be visible to the user.
1077     */
1078    public void scrollTo(S object) {
1079        if( getItems() != null ) {
1080            int idx = getItems().indexOf(object);
1081            if( idx >= 0 ) {
1082                ControlUtils.scrollToIndex(this, idx);        
1083            }
1084        }
1085    }
1086    
1087    /**
1088     * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
1089     * or {@link #scrollTo(Object)}
1090     */
1091    private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
1092    
1093    public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
1094        onScrollToProperty().set(value);
1095    }
1096    
1097    public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
1098        if( onScrollTo != null ) {
1099            return onScrollTo.get();
1100        }
1101        return null;
1102    }
1103    
1104    public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
1105        if( onScrollTo == null ) {
1106            onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
1107                @Override
1108                protected void invalidated() {
1109                    setEventHandler(ScrollToEvent.scrollToTopIndex(), get());
1110                }
1111                @Override
1112                public Object getBean() {
1113                    return TableView.this;
1114                }
1115
1116                @Override
1117                public String getName() {
1118                    return "onScrollTo";
1119                }
1120            };
1121        }
1122        return onScrollTo;
1123    }
1124    
1125    /**
1126     * Scrolls the TableView so that the given column is visible within the viewport.
1127     * @param column The column that should be visible to the user.
1128     */
1129    public void scrollToColumn(TableColumn<S, ?> column) {
1130        ControlUtils.scrollToColumn(this, column);
1131    }
1132    
1133    /**
1134     * Scrolls the TableView so that the given index is visible within the viewport.
1135     * @param columnIndex The index of a column that should be visible to the user.
1136     */
1137    public void scrollToColumnIndex(int columnIndex) {
1138        if( getColumns() != null ) {
1139            ControlUtils.scrollToColumn(this, getColumns().get(columnIndex));
1140        }
1141    }
1142    
1143    /**
1144     * Called when there's a request to scroll a column into view using {@link #scrollToColumn(TableColumn)} 
1145     * or {@link #scrollToColumnIndex(int)}
1146     */
1147    private ObjectProperty<EventHandler<ScrollToEvent<TableColumn<S, ?>>>> onScrollToColumn;
1148    
1149    public void setOnScrollToColumn(EventHandler<ScrollToEvent<TableColumn<S, ?>>> value) {
1150        onScrollToColumnProperty().set(value);
1151    }
1152    
1153    public EventHandler<ScrollToEvent<TableColumn<S, ?>>> getOnScrollToColumn() {
1154        if( onScrollToColumn != null ) {
1155            return onScrollToColumn.get();
1156        }
1157        return null;
1158    }
1159    
1160    public ObjectProperty<EventHandler<ScrollToEvent<TableColumn<S, ?>>>> onScrollToColumnProperty() {
1161        if( onScrollToColumn == null ) {
1162            onScrollToColumn = new ObjectPropertyBase<EventHandler<ScrollToEvent<TableColumn<S, ?>>>>() {
1163                @Override protected void invalidated() {
1164                    EventType<ScrollToEvent<TableColumn<S, ?>>> type = ScrollToEvent.scrollToColumn();
1165                    setEventHandler(type, get());
1166                }
1167                
1168                @Override public Object getBean() {
1169                    return TableView.this;
1170                }
1171
1172                @Override public String getName() {
1173                    return "onScrollToColumn";
1174                }
1175            };
1176        }
1177        return onScrollToColumn;
1178    }
1179    
1180    /**
1181     * Applies the currently installed resize policy against the given column,
1182     * resizing it based on the delta value provided.
1183     */
1184    public boolean resizeColumn(TableColumn<S,?> column, double delta) {
1185        if (column == null || Double.compare(delta, 0.0) == 0) return false;
1186
1187        boolean allowed = getColumnResizePolicy().call(new ResizeFeatures<S>(TableView.this, column, delta));
1188        if (!allowed) return false;
1189
1190        // This fixes the issue where if the column width is reduced and the
1191        // table width is also reduced, horizontal scrollbars will begin to
1192        // appear at the old width. This forces the VirtualFlow.maxPrefBreadth
1193        // value to be reset to -1 and subsequently recalculated. Of course
1194        // ideally we'd just refreshView, but for the time-being no such function
1195        // exists.
1196        refresh();
1197        return true;
1198    }
1199
1200    /**
1201     * Causes the cell at the given row/column view indexes to switch into
1202     * its editing state, if it is not already in it, and assuming that the 
1203     * TableView and column are also editable.
1204     */
1205    public void edit(int row, TableColumn<S,?> column) {
1206        if (!isEditable() || (column != null && ! column.isEditable())) return;
1207        setEditingCell(new TablePosition(this, row, column));
1208    }
1209    
1210    /**
1211     * Returns an unmodifiable list containing the currently visible leaf columns.
1212     */
1213    @ReturnsUnmodifiableCollection
1214    public ObservableList<TableColumn<S,?>> getVisibleLeafColumns() {
1215        return unmodifiableVisibleLeafColumns;
1216    }
1217    
1218    /**
1219     * Returns the position of the given column, relative to all other 
1220     * visible leaf columns.
1221     */
1222    public int getVisibleLeafIndex(TableColumn<S,?> column) {
1223        return visibleLeafColumns.indexOf(column);
1224    }
1225
1226    /**
1227     * Returns the TableColumn in the given column index, relative to all other
1228     * visible leaf columns.
1229     */
1230    public TableColumn<S,?> getVisibleLeafColumn(int column) {
1231        if (column < 0 || column >= visibleLeafColumns.size()) return null;
1232        return visibleLeafColumns.get(column);
1233    }
1234
1235    /** {@inheritDoc} */
1236    @Override protected Skin<?> createDefaultSkin() {
1237        return new TableViewSkin(this);
1238    }
1239    
1240    /**
1241     * The sort method forces the TableView to re-run its sorting algorithm. More 
1242     * often than not it is not necessary to call this method directly, as it is
1243     * automatically called when the {@link #getSortOrder() sort order}, 
1244     * {@link #sortPolicyProperty() sort policy}, or the state of the 
1245     * TableColumn {@link TableColumn#sortTypeProperty() sort type} properties 
1246     * change. In other words, this method should only be called directly when
1247     * something external changes and a sort is required.
1248     */
1249    public void sort() {
1250        final ObservableList<? extends TableColumnBase> sortOrder = getSortOrder();
1251        
1252        // update the Comparator property
1253        final Comparator<S> oldComparator = getComparator();
1254        Comparator<S> newComparator = new TableColumnComparator(sortOrder);
1255        setComparator(newComparator);
1256        
1257//        if (sortOrder.isEmpty()) {
1258//            // TODO this should eventually handle returning a SortedList back
1259//            // to its unsorted state
1260//            setComparator(null);
1261//        }
1262        
1263        // fire the onSort event and check if it is consumed, if
1264        // so, don't run the sort
1265        SortEvent<TableView<S>> sortEvent = new SortEvent<TableView<S>>(TableView.this, TableView.this);
1266        fireEvent(sortEvent);
1267        if (sortEvent.isConsumed()) {
1268            // if the sort is consumed we could back out the last action (the code
1269            // is commented out right below), but we don't as we take it as a 
1270            // sign that the developer has decided to handle the event themselves.
1271            
1272            // sortLock = true;
1273            // TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
1274            // sortLock = false;
1275            return;
1276        }
1277
1278        // get the sort policy and run it
1279        Callback<TableView<S>, Boolean> sortPolicy = getSortPolicy();
1280        if (sortPolicy == null) return;
1281        Boolean success = sortPolicy.call(this);
1282        
1283        if (success == null || ! success) {
1284            // the sort was a failure. Need to backout if possible
1285            sortLock = true;
1286            TableUtil.handleSortFailure(sortOrder, lastSortEventType, lastSortEventSupportInfo);
1287            setComparator(oldComparator);
1288            sortLock = false;
1289        }
1290    }
1291    
1292    
1293
1294    /***************************************************************************
1295     *                                                                         *
1296     * Private Implementation                                                  *
1297     *                                                                         *
1298     **************************************************************************/
1299    
1300    private boolean sortLock = false;
1301    private TableUtil.SortEventType lastSortEventType = null;
1302    private Object[] lastSortEventSupportInfo = null;
1303    
1304    private void doSort(final TableUtil.SortEventType sortEventType, final Object... supportInfo) {
1305        if (sortLock) {
1306            return;
1307        }
1308        
1309        this.lastSortEventType = sortEventType;
1310        this.lastSortEventSupportInfo = supportInfo;
1311        sort();
1312        this.lastSortEventType = null;
1313        this.lastSortEventSupportInfo = null;
1314    }
1315
1316    /**
1317     * Call this function to force the TableView to re-evaluate itself. This is
1318     * useful when the underlying data model is provided by a TableModel, and
1319     * you know that the data model has changed. This will force the TableView
1320     * to go back to the dataProvider and get the row count, as well as update
1321     * the view to ensure all sorting is still correct based on any changes to
1322     * the data model.
1323     */
1324    private void refresh() {
1325        getProperties().put(TableViewSkinBase.REFRESH, Boolean.TRUE);
1326    }
1327
1328
1329    // --- Content width
1330    private void setContentWidth(double contentWidth) {
1331        this.contentWidth = contentWidth;
1332        if (isInited) {
1333            // sometimes the current column resize policy will have to modify the
1334            // column width of all columns in the table if the table width changes,
1335            // so we short-circuit the resize function and just go straight there
1336            // with a null TableColumn, which indicates to the resize policy function
1337            // that it shouldn't actually do anything specific to one column.
1338            getColumnResizePolicy().call(new ResizeFeatures<S>(TableView.this, null, 0.0));
1339            refresh();
1340        }
1341    }
1342    
1343    /**
1344     * Recomputes the currently visible leaf columns in this TableView.
1345     */
1346    private void updateVisibleLeafColumns() {
1347        // update visible leaf columns list
1348        List<TableColumn<S,?>> cols = new ArrayList<TableColumn<S,?>>();
1349        buildVisibleLeafColumns(getColumns(), cols);
1350        visibleLeafColumns.setAll(cols);
1351
1352        // sometimes the current column resize policy will have to modify the
1353        // column width of all columns in the table if the table width changes,
1354        // so we short-circuit the resize function and just go straight there
1355        // with a null TableColumn, which indicates to the resize policy function
1356        // that it shouldn't actually do anything specific to one column.
1357        getColumnResizePolicy().call(new ResizeFeatures<S>(TableView.this, null, 0.0));
1358        refresh();
1359    }
1360
1361    private void buildVisibleLeafColumns(List<TableColumn<S,?>> cols, List<TableColumn<S,?>> vlc) {
1362        for (TableColumn<S,?> c : cols) {
1363            if (c == null) continue;
1364
1365            boolean hasChildren = ! c.getColumns().isEmpty();
1366
1367            if (hasChildren) {
1368                buildVisibleLeafColumns(c.getColumns(), vlc);
1369            } else if (c.isVisible()) {
1370                vlc.add(c);
1371            }
1372        }
1373    }
1374    
1375
1376    
1377    /***************************************************************************
1378     *                                                                         *
1379     * Stylesheet Handling                                                     *
1380     *                                                                         *
1381     **************************************************************************/
1382
1383    private static final String DEFAULT_STYLE_CLASS = "table-view";
1384
1385    private static final PseudoClass PSEUDO_CLASS_CELL_SELECTION =
1386            PseudoClass.getPseudoClass("cell-selection");
1387    private static final PseudoClass PSEUDO_CLASS_ROW_SELECTION =
1388            PseudoClass.getPseudoClass("row-selection");
1389
1390    /** @treatAsPrivate */
1391    private static class StyleableProperties {
1392        private static final CssMetaData<TableView<?>,Number> FIXED_CELL_SIZE =
1393                new CssMetaData<TableView<?>,Number>("-fx-fixed-cell-size",
1394                                                    SizeConverter.getInstance(),
1395                                                    Region.USE_COMPUTED_SIZE) {
1396
1397                    @Override public Double getInitialValue(TableView node) {
1398                        return node.getFixedCellSize();
1399                    }
1400
1401                    @Override public boolean isSettable(TableView n) {
1402                        return n.fixedCellSize == null || !n.fixedCellSize.isBound();
1403                    }
1404
1405                    @Override public StyleableProperty<Number> getStyleableProperty(TableView n) {
1406                        return (StyleableProperty<Number>) n.fixedCellSizeProperty();
1407                    }
1408                };
1409
1410        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1411        static {
1412            final List<CssMetaData<? extends Styleable, ?>> styleables =
1413                    new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
1414            styleables.add(FIXED_CELL_SIZE);
1415            STYLEABLES = Collections.unmodifiableList(styleables);
1416        }
1417    }
1418
1419    /**
1420     * @return The CssMetaData associated with this class, which may include the
1421     * CssMetaData of its super classes.
1422     */
1423    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1424        return StyleableProperties.STYLEABLES;
1425    }
1426
1427    /**
1428     * {@inheritDoc}
1429     */
1430    @Override
1431    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
1432        return getClassCssMetaData();
1433    }
1434    
1435
1436
1437    /***************************************************************************
1438     *                                                                         *
1439     * Support Interfaces                                                      *
1440     *                                                                         *
1441     **************************************************************************/
1442
1443     /**
1444      * An immutable wrapper class for use in the TableView 
1445     * {@link TableView#columnResizePolicyProperty() column resize} functionality.
1446      */
1447     public static class ResizeFeatures<S> extends ResizeFeaturesBase<S> {
1448        private TableView<S> table;
1449
1450        /**
1451         * Creates an instance of this class, with the provided TableView, 
1452         * TableColumn and delta values being set and stored in this immutable
1453         * instance.
1454         * 
1455         * @param table The TableView upon which the resize operation is occurring.
1456         * @param column The column upon which the resize is occurring, or null
1457         *      if this ResizeFeatures instance is being created as a result of a
1458         *      TableView resize operation.
1459         * @param delta The amount of horizontal space added or removed in the 
1460         *      resize operation.
1461         */
1462        public ResizeFeatures(TableView<S> table, TableColumn<S,?> column, Double delta) {
1463            super(column, delta);
1464            this.table = table;
1465        }
1466        
1467        /**
1468         * Returns the column upon which the resize is occurring, or null
1469         * if this ResizeFeatures instance was created as a result of a
1470         * TableView resize operation.
1471         */
1472        @Override public TableColumn<S,?> getColumn() { 
1473            return (TableColumn) super.getColumn(); 
1474        }
1475        
1476        /**
1477         * Returns the TableView upon which the resize operation is occurring.
1478         */
1479        public TableView<S> getTable() { 
1480            return table; 
1481        }
1482    }
1483
1484
1485
1486    /***************************************************************************
1487     *                                                                         *
1488     * Support Classes                                                         *
1489     *                                                                         *
1490     **************************************************************************/
1491
1492     
1493    /**
1494     * A simple extension of the {@link SelectionModel} abstract class to
1495     * allow for special support for TableView controls.
1496     */
1497    public static abstract class TableViewSelectionModel<S> extends TableSelectionModel<S, TableColumn<S,?>> {
1498
1499        /***********************************************************************
1500         *                                                                     *
1501         * Private fields                                                      *
1502         *                                                                     *
1503         **********************************************************************/
1504
1505        private final TableView<S> tableView;
1506
1507
1508
1509        /***********************************************************************
1510         *                                                                     *
1511         * Constructors                                                        *
1512         *                                                                     *
1513         **********************************************************************/
1514
1515        /**
1516         * Builds a default TableViewSelectionModel instance with the provided
1517         * TableView.
1518         * @param tableView The TableView upon which this selection model should
1519         *      operate.
1520         * @throws NullPointerException TableView can not be null.
1521         */
1522        public TableViewSelectionModel(final TableView<S> tableView) {
1523            if (tableView == null) {
1524                throw new NullPointerException("TableView can not be null");
1525            }
1526
1527            this.tableView = tableView;
1528        }
1529
1530
1531
1532        /***********************************************************************
1533         *                                                                     *
1534         * Abstract API                                                        *
1535         *                                                                     *
1536         **********************************************************************/
1537
1538        /**
1539         * A read-only ObservableList representing the currently selected cells 
1540         * in this TableView. Rather than directly modify this list, please
1541         * use the other methods provided in the TableViewSelectionModel.
1542         */
1543        public abstract ObservableList<TablePosition> getSelectedCells();
1544
1545
1546
1547        /***********************************************************************
1548         *                                                                     *
1549         * Public API                                                          *
1550         *                                                                     *
1551         **********************************************************************/
1552
1553        /**
1554         * Returns the TableView instance that this selection model is installed in.
1555         */
1556        public TableView<S> getTableView() {
1557            return tableView;
1558        }
1559
1560        /**
1561         * Convenience method that returns getTableView().getItems().
1562         * @return The items list of the current TableView.
1563         */
1564        protected ObservableList<S> getTableModel()  {
1565            return tableView.getItems();
1566        }
1567
1568        /** {@inheritDoc} */
1569        @Override protected S getModelItem(int index) {
1570            if (index < 0 || index > getItemCount()) return null;
1571            return tableView.getItems().get(index);
1572        }
1573
1574        /** {@inheritDoc} */
1575        @Override protected int getItemCount() {
1576            return getTableModel().size();
1577        }
1578
1579        /** {@inheritDoc} */
1580        @Override public void focus(int row) {
1581            focus(row, null);
1582        }
1583
1584        /** {@inheritDoc} */
1585        @Override public int getFocusedIndex() {
1586            return getFocusedCell().getRow();
1587        }
1588
1589
1590
1591        /***********************************************************************
1592         *                                                                     *
1593         * Private implementation                                              *
1594         *                                                                     *
1595         **********************************************************************/
1596
1597        void focus(int row, TableColumn<S,?> column) {
1598            focus(new TablePosition(getTableView(), row, column));
1599        }
1600
1601        void focus(TablePosition pos) {
1602            if (getTableView().getFocusModel() == null) return;
1603
1604            getTableView().getFocusModel().focus(pos.getRow(), pos.getTableColumn());
1605        }
1606
1607        TablePosition getFocusedCell() {
1608            if (getTableView().getFocusModel() == null) {
1609                return new TablePosition(getTableView(), -1, null);
1610            }
1611            return getTableView().getFocusModel().getFocusedCell();
1612        }
1613    }
1614    
1615    
1616
1617    /**
1618     * A primitive selection model implementation, using a List<Integer> to store all
1619     * selected indices.
1620     */
1621    // package for testing
1622    static class TableViewArrayListSelectionModel<S> extends TableViewSelectionModel<S> {
1623        
1624        private int itemCount = 0;
1625
1626        /***********************************************************************
1627         *                                                                     *
1628         * Constructors                                                        *
1629         *                                                                     *
1630         **********************************************************************/
1631
1632        public TableViewArrayListSelectionModel(final TableView<S> tableView) {
1633            super(tableView);
1634            this.tableView = tableView;
1635//            this.selectedIndicesBitSet = new BitSet();
1636            
1637            updateItemCount();
1638            
1639            cellSelectionEnabledProperty().addListener(new InvalidationListener() {
1640                @Override public void invalidated(Observable o) {
1641                    isCellSelectionEnabled();
1642                    clearSelection();
1643                }
1644            });
1645            
1646            final MappingChange.Map<TablePosition<S,?>,S> cellToItemsMap = new MappingChange.Map<TablePosition<S,?>, S>() {
1647                @Override public S map(TablePosition<S,?> f) {
1648                    return getModelItem(f.getRow());
1649                }
1650            };
1651            
1652            final MappingChange.Map<TablePosition<S,?>,Integer> cellToIndicesMap = new MappingChange.Map<TablePosition<S,?>, Integer>() {
1653                @Override public Integer map(TablePosition f) {
1654                    return f.getRow();
1655                }
1656            };
1657            
1658            selectedCells = FXCollections.<TablePosition<S,?>>observableArrayList();
1659            selectedCells.addListener(new ListChangeListener<TablePosition<S,?>>() {
1660                @Override public void onChanged(final Change<? extends TablePosition<S,?>> c) {
1661                    // RT-29313: because selectedIndices and selectedItems represent
1662                    // row-based selection, we need to update the
1663                    // selectedIndicesBitSet when the selectedCells changes to 
1664                    // ensure that selectedIndices and selectedItems return only
1665                    // the correct values (and only once). The issue identified
1666                    // by RT-29313 is that the size and contents of selectedIndices
1667                    // and selectedItems can not simply defer to the
1668                    // selectedCells as selectedCells may be representing 
1669                    // multiple cells from one row (e.g. selectedCells of 
1670                    // [(0,1), (1,1), (1,2), (1,3)] should result in 
1671                    // selectedIndices of [0,1], not [0,1,1,1]).
1672                    // An inefficient solution would rebuild the selectedIndicesBitSet
1673                    // every time the change happens, but we can do better than
1674                    // that. Inefficient solution:
1675                    //
1676                    // selectedIndicesBitSet.clear();
1677                    // for (int i = 0; i < selectedCells.size(); i++) {
1678                    //     final TablePosition<S,?> tp = selectedCells.get(i);
1679                    //     final int row = tp.getRow();
1680                    //     selectedIndicesBitSet.set(row);
1681                    // }
1682                    // 
1683                    // A more efficient solution:
1684                    final List<Integer> newlySelectedRows = new ArrayList<Integer>();
1685                    final List<Integer> newlyUnselectedRows = new ArrayList<Integer>();
1686                    
1687                    while (c.next()) {
1688                        if (c.wasRemoved()) {
1689                            List<? extends TablePosition<S,?>> removed = c.getRemoved();
1690                            for (int i = 0; i < removed.size(); i++) {
1691                                final TablePosition<S,?> tp = removed.get(i);
1692                                final int row = tp.getRow();
1693                                
1694                                if (selectedIndices.get(row)) {
1695                                    selectedIndices.clear(row);
1696                                    newlySelectedRows.add(row);
1697                                }
1698                            }
1699                        }
1700                        if (c.wasAdded()) {
1701                            List<? extends TablePosition<S,?>> added = c.getAddedSubList();
1702                            for (int i = 0; i < added.size(); i++) {
1703                                final TablePosition<S,?> tp = added.get(i);
1704                                final int row = tp.getRow();
1705                                
1706                                if (! selectedIndices.get(row)) {
1707                                    selectedIndices.set(row);
1708                                    newlySelectedRows.add(row);
1709                                }
1710                            }
1711                        }
1712                    }
1713                    c.reset();
1714                    
1715                    // when the selectedCells observableArrayList changes, we manually call
1716                    // the observers of the selectedItems, selectedIndices and
1717                    // selectedCells lists.
1718                    
1719                    // create an on-demand list of the removed objects contained in the
1720                    // given rows
1721                    selectedItems.callObservers(new MappingChange<TablePosition<S,?>, S>(c, cellToItemsMap, selectedItems));
1722                    c.reset();
1723                    
1724                    final ReadOnlyUnbackedObservableList<Integer> selectedIndicesSeq = 
1725                            (ReadOnlyUnbackedObservableList<Integer>)getSelectedIndices();
1726
1727                    if (! newlySelectedRows.isEmpty() && newlyUnselectedRows.isEmpty()) {
1728                        // need to come up with ranges based on the actualSelectedRows, and
1729                        // then fire the appropriate number of changes. We also need to
1730                        // translate from a desired row to select to where that row is 
1731                        // represented in the selectedIndices list. For example,
1732                        // we may have requested to select row 5, and the selectedIndices
1733                        // list may therefore have the following: [1,4,5], meaning row 5
1734                        // is in position 2 of the selectedIndices list
1735                        Change<Integer> change = createRangeChange(selectedIndicesSeq, newlySelectedRows);
1736                        selectedIndicesSeq.callObservers(change);
1737                    } else {
1738                        selectedIndicesSeq.callObservers(new MappingChange<TablePosition<S,?>, Integer>(c, cellToIndicesMap, selectedIndicesSeq));
1739                        c.reset();
1740                    }
1741
1742                    selectedCellsSeq.callObservers(new MappingChange<TablePosition<S,?>, TablePosition<S,?>>(c, MappingChange.NOOP_MAP, selectedCellsSeq));
1743                    c.reset();
1744                }
1745            });
1746
1747            selectedItems = new ReadOnlyUnbackedObservableList<S>() {
1748                @Override public S get(int i) {
1749                    return getModelItem(getSelectedIndices().get(i));
1750                }
1751
1752                @Override public int size() {
1753                    return getSelectedIndices().size();
1754                }
1755            };
1756            
1757            selectedCellsSeq = new ReadOnlyUnbackedObservableList<TablePosition<S,?>>() {
1758                @Override public TablePosition<S,?> get(int i) {
1759                    return selectedCells.get(i);
1760                }
1761
1762                @Override public int size() {
1763                    return selectedCells.size();
1764                }
1765            };
1766
1767
1768            /*
1769             * The following two listeners are used in conjunction with
1770             * SelectionModel.select(T obj) to allow for a developer to select
1771             * an item that is not actually in the data model. When this occurs,
1772             * we actively try to find an index that matches this object, going
1773             * so far as to actually watch for all changes to the items list,
1774             * rechecking each time.
1775             */
1776
1777            // watching for changes to the items list
1778            tableView.itemsProperty().addListener(weakItemsPropertyListener);
1779            
1780            // watching for changes to the items list content
1781            ObservableList<S> items = getTableModel();
1782            if (items != null) {
1783                items.addListener(weakItemsContentListener);
1784            }
1785        }
1786        
1787        private final TableView<S> tableView;
1788        
1789        private ChangeListener<ObservableList<S>> itemsPropertyListener = new ChangeListener<ObservableList<S>>() {
1790            @Override
1791            public void changed(ObservableValue<? extends ObservableList<S>> observable, 
1792                ObservableList<S> oldList, ObservableList<S> newList) {
1793                    updateItemsObserver(oldList, newList);
1794            }
1795        };
1796        
1797        private WeakChangeListener<ObservableList<S>> weakItemsPropertyListener = 
1798                new WeakChangeListener<ObservableList<S>>(itemsPropertyListener);
1799
1800        final ListChangeListener<S> itemsContentListener = new ListChangeListener<S>() {
1801            @Override public void onChanged(Change<? extends S> c) {
1802                updateItemCount();
1803
1804                List<S> items = getTableModel();
1805
1806                while (c.next()) {
1807                    final S selectedItem = getSelectedItem();
1808                    final int selectedIndex = getSelectedIndex();
1809                    
1810                    if (items == null || items.isEmpty()) {
1811                        clearSelection();
1812                    } else if (getSelectedIndex() == -1 && getSelectedItem() != null) {
1813                        int newIndex = items.indexOf(getSelectedItem());
1814                        if (newIndex != -1) {
1815                            setSelectedIndex(newIndex);
1816                        }
1817                    } else if (c.wasRemoved() && 
1818                            c.getRemovedSize() == 1 && 
1819                            ! c.wasAdded() && 
1820                            selectedItem != null && 
1821                            selectedItem.equals(c.getRemoved().get(0))) {
1822                        // Bug fix for RT-28637
1823                        if (getSelectedIndex() < getItemCount()) {
1824                            S newSelectedItem = getModelItem(selectedIndex);
1825                            if (! selectedItem.equals(newSelectedItem)) {
1826                                setSelectedItem(newSelectedItem);
1827                            }
1828                        }
1829                    }
1830                }
1831                
1832                updateSelection(c);
1833            }
1834        };
1835        
1836        final WeakListChangeListener weakItemsContentListener 
1837                = new WeakListChangeListener(itemsContentListener);
1838        
1839        private void updateItemsObserver(ObservableList<S> oldList, ObservableList<S> newList) {
1840            // the listview items list has changed, we need to observe
1841            // the new list, and remove any observer we had from the old list
1842            if (oldList != null) {
1843                oldList.removeListener(weakItemsContentListener);
1844            }
1845            if (newList != null) {
1846                newList.addListener(weakItemsContentListener);
1847            }
1848            
1849            updateItemCount();
1850
1851            // when the items list totally changes, we should clear out
1852            // the selection
1853            setSelectedIndex(-1);
1854        }
1855        
1856
1857        /***********************************************************************
1858         *                                                                     *
1859         * Observable properties (and getters/setters)                         *
1860         *                                                                     *
1861         **********************************************************************/
1862        
1863        // the only 'proper' internal observableArrayList, selectedItems and selectedIndices
1864        // are both 'read-only and unbacked'.
1865        private final ObservableList<TablePosition<S,?>> selectedCells;
1866
1867        // used to represent the _row_ backing data for the selectedCells
1868        private final ReadOnlyUnbackedObservableList<S> selectedItems;
1869        @Override public ObservableList<S> getSelectedItems() {
1870            return selectedItems;
1871        }
1872
1873        private final ReadOnlyUnbackedObservableList<TablePosition<S,?>> selectedCellsSeq;
1874        @Override public ObservableList<TablePosition> getSelectedCells() {
1875            return (ObservableList<TablePosition>)(Object)selectedCellsSeq;
1876        }
1877
1878
1879        /***********************************************************************
1880         *                                                                     *
1881         * Internal properties                                                 *
1882         *                                                                     *
1883         **********************************************************************/
1884
1885        private int previousModelSize = 0;
1886        
1887        // Listen to changes in the tableview items list, such that when it 
1888        // changes we can update the selected indices list to refer to the 
1889        // new indices.
1890        private void updateSelection(ListChangeListener.Change<? extends S> c) {
1891            c.reset();
1892            while (c.next()) {
1893                if (c.wasReplaced()) {
1894                    if (c.getList().isEmpty()) {
1895                        // the entire items list was emptied - clear selection
1896                        clearSelection();
1897                    } else {
1898                        int index = getSelectedIndex();
1899                        
1900                        if (previousModelSize == c.getRemovedSize()) {
1901                            // all items were removed from the model
1902                            clearSelection();
1903                        } else if (index < getItemCount() && index >= 0) {
1904                            // Fix for RT-18969: the list had setAll called on it
1905                            // Use of makeAtomic is a fix for RT-20945
1906                            makeAtomic = true;
1907                            clearSelection(index);
1908                            makeAtomic = false;
1909                            select(index);
1910                        } else {
1911                            // Fix for RT-22079
1912                            clearSelection();
1913                        }
1914                    }
1915                } else if (c.wasAdded() || c.wasRemoved()) {
1916                    int position = c.getFrom();
1917                    int shift = c.wasAdded() ? c.getAddedSize() : -c.getRemovedSize();
1918                    
1919                    if (position < 0) return;
1920                    if (shift == 0) return;
1921                    
1922                    List<TablePosition<S,?>> newIndices = new ArrayList<TablePosition<S,?>>(selectedCells.size());
1923        
1924                    for (int i = 0; i < selectedCells.size(); i++) {
1925                        final TablePosition<S,?> old = selectedCells.get(i);
1926                        final int oldRow = old.getRow();
1927                        final int newRow = oldRow < position ? oldRow : oldRow + shift;
1928                        
1929                        // Special case for RT-28637 (See unit test in TableViewTest).
1930                        // Essentially the selectedItem was correct, but selectedItems
1931                        // was empty.
1932                        if (oldRow == 0 && shift == -1) {
1933                            newIndices.add(new TablePosition(getTableView(), 0, old.getTableColumn()));
1934                            continue;
1935                        }
1936                        
1937                        if (newRow < 0) continue;
1938                        newIndices.add(new TablePosition(getTableView(), newRow, old.getTableColumn()));
1939                    }
1940                    
1941                    quietClearSelection();
1942                    
1943                    // Fix for RT-22079
1944                    for (int i = 0; i < newIndices.size(); i++) {
1945                        TablePosition<S,?> tp = newIndices.get(i);
1946                        select(tp.getRow(), tp.getTableColumn());
1947                    }
1948                } else if (c.wasPermutated()) {
1949                    // General approach:
1950                    //   -- detected a sort has happened
1951                    //   -- Create a permutation lookup map (1)
1952                    //   -- dump all the selected indices into a list (2)
1953                    //   -- clear the selected items / indexes (3)
1954                    //   -- create a list containing the new indices (4)
1955                    //   -- for each previously-selected index (5)
1956                    //     -- if index is in the permutation lookup map
1957                    //       -- add the new index to the new indices list
1958                    //   -- Perform batch selection (6)
1959
1960                    // (1)
1961                    int length = c.getTo() - c.getFrom();
1962                    HashMap<Integer, Integer> pMap = new HashMap<Integer, Integer> (length);
1963                    for (int i = c.getFrom(); i < c.getTo(); i++) {
1964                        pMap.put(i, c.getPermutation(i));
1965                    }
1966
1967                    // (2)
1968                    List<TablePosition<S,?>> selectedIndices =
1969                            new ArrayList<TablePosition<S,?>>((ObservableList<TablePosition<S,?>>)(Object)getSelectedCells());
1970
1971
1972                    // (3)
1973                    clearSelection();
1974
1975                    // (4)
1976                    List<TablePosition<S,?>> newIndices = new ArrayList<TablePosition<S,?>>(getSelectedIndices().size());
1977
1978                    // (5)
1979                    for (int i = 0; i < selectedIndices.size(); i++) {
1980                        TablePosition<S,?> oldIndex = selectedIndices.get(i);
1981
1982                        if (pMap.containsKey(oldIndex.getRow())) {
1983                            Integer newIndex = pMap.get(oldIndex.getRow());
1984                            newIndices.add(new TablePosition(oldIndex.getTableView(), newIndex, oldIndex.getTableColumn()));
1985                        }
1986                    }
1987
1988                    // (6)
1989                    quietClearSelection();
1990                    selectedCells.setAll(newIndices);
1991                    selectedCellsSeq.callObservers(new NonIterableChange.SimpleAddChange<TablePosition<S,?>>(0, newIndices.size(), selectedCellsSeq));
1992                }
1993            }
1994            
1995            previousModelSize = getItemCount();
1996        }
1997
1998        /***********************************************************************
1999         *                                                                     *
2000         * Public selection API                                                *
2001         *                                                                     *
2002         **********************************************************************/
2003
2004        @Override public void clearAndSelect(int row) {
2005            clearAndSelect(row, null);
2006        }
2007
2008        @Override public void clearAndSelect(int row, TableColumn<S,?> column) {
2009            quietClearSelection();
2010            select(row, column);
2011        }
2012
2013        @Override public void select(int row) {
2014            select(row, null);
2015        }
2016
2017        @Override
2018        public void select(int row, TableColumn<S,?> column) {
2019            if (row < 0 || row >= getItemCount()) return;
2020
2021            // if I'm in cell selection mode but the column is null, I don't want
2022            // to select the whole row instead...
2023            if (isCellSelectionEnabled() && column == null) return;
2024//            
2025//            // If I am not in cell selection mode (so I want to select rows only),
2026//            // if a column is given, I return
2027//            if (! isCellSelectionEnabled() && column != null) return;
2028
2029            TablePosition pos = new TablePosition(getTableView(), row, column);
2030            
2031            if (getSelectionMode() == SelectionMode.SINGLE) {
2032                quietClearSelection();
2033            }
2034
2035            if (! selectedCells.contains(pos)) {
2036                selectedCells.add(pos);
2037            }
2038
2039            updateSelectedIndex(row);
2040            focus(row, column);
2041        }
2042
2043        @Override public void select(S obj) {
2044            if (obj == null && getSelectionMode() == SelectionMode.SINGLE) {
2045                clearSelection();
2046                return;
2047            }
2048            
2049            // We have no option but to iterate through the model and select the
2050            // first occurrence of the given object. Once we find the first one, we
2051            // don't proceed to select any others.
2052            S rowObj = null;
2053            for (int i = 0; i < getItemCount(); i++) {
2054                rowObj = getModelItem(i);
2055                if (rowObj == null) continue;
2056
2057                if (rowObj.equals(obj)) {
2058                    if (isSelected(i)) {
2059                        return;
2060                    }
2061
2062                    if (getSelectionMode() == SelectionMode.SINGLE) {
2063                        quietClearSelection();
2064                    }
2065
2066                    select(i);
2067                    return;
2068                }
2069            }
2070
2071            // if we are here, we did not find the item in the entire data model.
2072            // Even still, we allow for this item to be set to the give object.
2073            // We expect that in concrete subclasses of this class we observe the
2074            // data model such that we check to see if the given item exists in it,
2075            // whilst SelectedIndex == -1 && SelectedItem != null.
2076            setSelectedItem(obj);
2077        }
2078
2079        @Override public void selectIndices(int row, int... rows) {
2080            if (rows == null) {
2081                select(row);
2082                return;
2083            }
2084
2085            /*
2086             * Performance optimisation - if multiple selection is disabled, only
2087             * process the end-most row index.
2088             */
2089            int rowCount = getItemCount();
2090
2091            if (getSelectionMode() == SelectionMode.SINGLE) {
2092                quietClearSelection();
2093
2094                for (int i = rows.length - 1; i >= 0; i--) {
2095                    int index = rows[i];
2096                    if (index >= 0 && index < rowCount) {
2097                        select(index);
2098                        break;
2099                    }
2100                }
2101
2102                if (selectedCells.isEmpty()) {
2103                    if (row > 0 && row < rowCount) {
2104                        select(row);
2105                    }
2106                }
2107            } else {
2108                int lastIndex = -1;
2109                Set<TablePosition<S,?>> positions = new LinkedHashSet<TablePosition<S,?>>();
2110
2111                if (row >= 0 && row < rowCount) {
2112                    TablePosition<S,Object> tp = new TablePosition<S,Object>(getTableView(), row, null);
2113                    
2114                    // refer to the multi-line comment below for the justification for the following
2115                    // code.
2116                    boolean match = false;
2117                    for (int j = 0; j < selectedCells.size(); j++) {
2118                        TablePosition<S,?> selectedCell = selectedCells.get(j);
2119                        if (selectedCell.getRow() == row) {
2120                            match = true;
2121                            break;
2122                        }
2123                    }
2124                    if (! match) {
2125                        positions.add(tp);
2126                        lastIndex = row;
2127                    }
2128                }
2129
2130                outer: for (int i = 0; i < rows.length; i++) {
2131                    int index = rows[i];
2132                    if (index < 0 || index >= rowCount) continue;
2133                    lastIndex = index;
2134                    
2135                    // we need to manually check all selected cells to see whether this index is already
2136                    // selected. This is because selectIndices is inherently row-based, but there may
2137                    // be a selected cell where the column is non-null. If we were to simply do a
2138                    // selectedCells.contains(pos), then we would not find the match and duplicate the
2139                    // row selection. This leads to bugs such as RT-29930.
2140                    for (int j = 0; j < selectedCells.size(); j++) {
2141                        TablePosition<S,?> selectedCell = selectedCells.get(j);
2142                        if (selectedCell.getRow() == index) continue outer;
2143                    }
2144                    
2145                    // if we are here then we have successfully gotten through the for-loop above
2146                    TablePosition<S,Object> pos = new TablePosition<S,Object>(getTableView(), index, null);
2147                    positions.add(pos);
2148                }
2149                
2150                selectedCells.addAll(positions);
2151                
2152                if (lastIndex != -1) {
2153                    select(lastIndex);
2154                }
2155            }
2156        }
2157
2158        @Override public void selectAll() {
2159            if (getSelectionMode() == SelectionMode.SINGLE) return;
2160
2161            quietClearSelection();
2162
2163            if (isCellSelectionEnabled()) {
2164                List<TablePosition<S,?>> indices = new ArrayList<TablePosition<S,?>>();
2165                TableColumn column;
2166                TablePosition<S,?> tp = null;
2167                for (int col = 0; col < getTableView().getVisibleLeafColumns().size(); col++) {
2168                    column = getTableView().getVisibleLeafColumns().get(col);
2169                    for (int row = 0; row < getItemCount(); row++) {
2170                        tp = new TablePosition(getTableView(), row, column);
2171                        indices.add(tp);
2172                    }
2173                }
2174                selectedCells.setAll(indices);
2175                
2176                if (tp != null) {
2177                    select(tp.getRow(), tp.getTableColumn());
2178                    focus(tp.getRow(), tp.getTableColumn());
2179                }
2180            } else {
2181                List<TablePosition<S,?>> indices = new ArrayList<TablePosition<S,?>>();
2182                for (int i = 0; i < getItemCount(); i++) {
2183                    indices.add(new TablePosition(getTableView(), i, null));
2184                }
2185                selectedCells.setAll(indices);
2186                
2187                int focusedIndex = getFocusedIndex();
2188                if (focusedIndex == -1) {
2189                    select(getItemCount() - 1);
2190                    focus(indices.get(indices.size() - 1));
2191                } else {
2192                    select(focusedIndex);
2193                    focus(focusedIndex);
2194                }
2195            }
2196        }
2197
2198        @Override public void clearSelection(int index) {
2199            clearSelection(index, null);
2200        }
2201
2202        @Override
2203        public void clearSelection(int row, TableColumn<S,?> column) {
2204            TablePosition tp = new TablePosition(getTableView(), row, column);
2205
2206            boolean csMode = isCellSelectionEnabled();
2207            
2208            for (TablePosition pos : getSelectedCells()) {
2209                if ((! csMode && pos.getRow() == row) || (csMode && pos.equals(tp))) {
2210                    selectedCells.remove(pos);
2211
2212                    // give focus to this cell index
2213                    focus(row);
2214
2215                    return;
2216                }
2217            }
2218        }
2219
2220        @Override public void clearSelection() {
2221            updateSelectedIndex(-1);
2222            focus(-1);
2223            quietClearSelection();
2224        }
2225
2226        private void quietClearSelection() {
2227            selectedCells.clear();
2228        }
2229
2230        @Override public boolean isSelected(int index) {
2231            return isSelected(index, null);
2232        }
2233
2234        @Override
2235        public boolean isSelected(int row, TableColumn<S,?> column) {
2236            // When in cell selection mode, we currently do NOT support selecting
2237            // entire rows, so a isSelected(row, null) 
2238            // should always return false.
2239            if (isCellSelectionEnabled() && (column == null)) return false;
2240            
2241            for (TablePosition tp : getSelectedCells()) {
2242                boolean columnMatch = ! isCellSelectionEnabled() || 
2243                        (column == null && tp.getTableColumn() == null) || 
2244                        (column != null && column.equals(tp.getTableColumn()));
2245                
2246                if (tp.getRow() == row && columnMatch) {
2247                    return true;
2248                }
2249            }
2250            return false;
2251        }
2252
2253        @Override public boolean isEmpty() {
2254            return selectedCells.isEmpty();
2255        }
2256
2257        @Override public void selectPrevious() {
2258            if (isCellSelectionEnabled()) {
2259                // in cell selection mode, we have to wrap around, going from
2260                // right-to-left, and then wrapping to the end of the previous line
2261                TablePosition<S,?> pos = getFocusedCell();
2262                if (pos.getColumn() - 1 >= 0) {
2263                    // go to previous row
2264                    select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1));
2265                } else if (pos.getRow() < getItemCount() - 1) {
2266                    // wrap to end of previous row
2267                    select(pos.getRow() - 1, getTableColumn(getTableView().getVisibleLeafColumns().size() - 1));
2268                }
2269            } else {
2270                int focusIndex = getFocusedIndex();
2271                if (focusIndex == -1) {
2272                    select(getItemCount() - 1);
2273                } else if (focusIndex > 0) {
2274                    select(focusIndex - 1);
2275                }
2276            }
2277        }
2278
2279        @Override public void selectNext() {
2280            if (isCellSelectionEnabled()) {
2281                // in cell selection mode, we have to wrap around, going from
2282                // left-to-right, and then wrapping to the start of the next line
2283                TablePosition<S,?> pos = getFocusedCell();
2284                if (pos.getColumn() + 1 < getTableView().getVisibleLeafColumns().size()) {
2285                    // go to next column
2286                    select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1));
2287                } else if (pos.getRow() < getItemCount() - 1) {
2288                    // wrap to start of next row
2289                    select(pos.getRow() + 1, getTableColumn(0));
2290                }
2291            } else {
2292                int focusIndex = getFocusedIndex();
2293                if (focusIndex == -1) {
2294                    select(0);
2295                } else if (focusIndex < getItemCount() -1) {
2296                    select(focusIndex + 1);
2297                }
2298            }
2299        }
2300
2301        @Override public void selectAboveCell() {
2302            TablePosition pos = getFocusedCell();
2303            if (pos.getRow() == -1) {
2304                select(getItemCount() - 1);
2305            } else if (pos.getRow() > 0) {
2306                select(pos.getRow() - 1, pos.getTableColumn());
2307            }
2308        }
2309
2310        @Override public void selectBelowCell() {
2311            TablePosition pos = getFocusedCell();
2312
2313            if (pos.getRow() == -1) {
2314                select(0);
2315            } else if (pos.getRow() < getItemCount() -1) {
2316                select(pos.getRow() + 1, pos.getTableColumn());
2317            }
2318        }
2319
2320        @Override public void selectFirst() {
2321            TablePosition focusedCell = getFocusedCell();
2322
2323            if (getSelectionMode() == SelectionMode.SINGLE) {
2324                quietClearSelection();
2325            }
2326
2327            if (getItemCount() > 0) {
2328                if (isCellSelectionEnabled()) {
2329                    select(0, focusedCell.getTableColumn());
2330                } else {
2331                    select(0);
2332                }
2333            }
2334        }
2335
2336        @Override public void selectLast() {
2337            TablePosition focusedCell = getFocusedCell();
2338
2339            if (getSelectionMode() == SelectionMode.SINGLE) {
2340                quietClearSelection();
2341            }
2342
2343            int numItems = getItemCount();
2344            if (numItems > 0 && getSelectedIndex() < numItems - 1) {
2345                if (isCellSelectionEnabled()) {
2346                    select(numItems - 1, focusedCell.getTableColumn());
2347                } else {
2348                    select(numItems - 1);
2349                }
2350            }
2351        }
2352
2353        @Override
2354        public void selectLeftCell() {
2355            if (! isCellSelectionEnabled()) return;
2356
2357            TablePosition pos = getFocusedCell();
2358            if (pos.getColumn() - 1 >= 0) {
2359                select(pos.getRow(), getTableColumn(pos.getTableColumn(), -1));
2360            }
2361        }
2362
2363        @Override
2364        public void selectRightCell() {
2365            if (! isCellSelectionEnabled()) return;
2366
2367            TablePosition pos = getFocusedCell();
2368            if (pos.getColumn() + 1 < getTableView().getVisibleLeafColumns().size()) {
2369                select(pos.getRow(), getTableColumn(pos.getTableColumn(), 1));
2370            }
2371        }
2372
2373
2374
2375        /***********************************************************************
2376         *                                                                     *
2377         * Support code                                                        *
2378         *                                                                     *
2379         **********************************************************************/
2380        
2381        private TableColumn<S,?> getTableColumn(int pos) {
2382            return getTableView().getVisibleLeafColumn(pos);
2383        }
2384        
2385//        private TableColumn<S,?> getTableColumn(TableColumn<S,?> column) {
2386//            return getTableColumn(column, 0);
2387//        }
2388
2389        // Gets a table column to the left or right of the current one, given an offset
2390        private TableColumn<S,?> getTableColumn(TableColumn<S,?> column, int offset) {
2391            int columnIndex = getTableView().getVisibleLeafIndex(column);
2392            int newColumnIndex = columnIndex + offset;
2393            return getTableView().getVisibleLeafColumn(newColumnIndex);
2394        }
2395
2396        private void updateSelectedIndex(int row) {
2397            setSelectedIndex(row);
2398            setSelectedItem(getModelItem(row));
2399        }
2400        
2401        /** {@inheritDoc} */
2402        @Override protected int getItemCount() {
2403            return itemCount;
2404        }
2405
2406        private void updateItemCount() {
2407            if (tableView == null) {
2408                itemCount = -1;
2409            } else {
2410                List<S> items = getTableModel();
2411                itemCount = items == null ? -1 : items.size();
2412            }
2413        }
2414    }
2415    
2416    
2417    
2418    
2419    /**
2420     * A {@link FocusModel} with additional functionality to support the requirements
2421     * of a TableView control.
2422     * 
2423     * @see TableView
2424     */
2425    public static class TableViewFocusModel<S> extends TableFocusModel<S, TableColumn<S, ?>> {
2426
2427        private final TableView<S> tableView;
2428
2429        private final TablePosition EMPTY_CELL;
2430
2431        /**
2432         * Creates a default TableViewFocusModel instance that will be used to
2433         * manage focus of the provided TableView control.
2434         * 
2435         * @param tableView The tableView upon which this focus model operates.
2436         * @throws NullPointerException The TableView argument can not be null.
2437         */
2438        public TableViewFocusModel(final TableView<S> tableView) {
2439            if (tableView == null) {
2440                throw new NullPointerException("TableView can not be null");
2441            }
2442
2443            this.tableView = tableView;
2444            
2445            this.tableView.itemsProperty().addListener(weakItemsPropertyListener);
2446            if (tableView.getItems() != null) {
2447                this.tableView.getItems().addListener(weakItemsContentListener);
2448            }
2449
2450            TablePosition pos = new TablePosition(tableView, -1, null);
2451            setFocusedCell(pos);
2452            EMPTY_CELL = pos;
2453        }
2454        
2455        private ChangeListener<ObservableList<S>> itemsPropertyListener = new ChangeListener<ObservableList<S>>() {
2456            @Override
2457            public void changed(ObservableValue<? extends ObservableList<S>> observable, 
2458                ObservableList<S> oldList, ObservableList<S> newList) {
2459                    updateItemsObserver(oldList, newList);
2460            }
2461        };
2462        
2463        private WeakChangeListener<ObservableList<S>> weakItemsPropertyListener = 
2464                new WeakChangeListener<ObservableList<S>>(itemsPropertyListener);
2465        
2466        // Listen to changes in the tableview items list, such that when it
2467        // changes we can update the focused index to refer to the new indices.
2468        private final ListChangeListener<S> itemsContentListener = new ListChangeListener<S>() {
2469            @Override public void onChanged(Change<? extends S> c) {
2470                c.next();
2471                if (c.getFrom() > getFocusedIndex()) return;
2472                c.reset();
2473                boolean added = false;
2474                boolean removed = false;
2475                int addedSize = 0;
2476                int removedSize = 0;
2477                while (c.next()) {
2478                    added |= c.wasAdded();
2479                    removed |= c.wasRemoved();
2480                    addedSize += c.getAddedSize();
2481                    removedSize += c.getRemovedSize();
2482                }
2483                if (added && ! removed) {
2484                    focus(getFocusedIndex() + addedSize);
2485                } else if (!added && removed) {
2486                    focus(getFocusedIndex() - removedSize);
2487                }
2488            }
2489        };
2490        
2491        private WeakListChangeListener<S> weakItemsContentListener 
2492                = new WeakListChangeListener<S>(itemsContentListener);
2493        
2494        private void updateItemsObserver(ObservableList<S> oldList, ObservableList<S> newList) {
2495            // the tableview items list has changed, we need to observe
2496            // the new list, and remove any observer we had from the old list
2497            if (oldList != null) oldList.removeListener(weakItemsContentListener);
2498            if (newList != null) newList.addListener(weakItemsContentListener);
2499        }
2500
2501        /** {@inheritDoc} */
2502        @Override protected int getItemCount() {
2503            if (tableView.getItems() == null) return -1;
2504            return tableView.getItems().size();
2505        }
2506
2507        /** {@inheritDoc} */
2508        @Override protected S getModelItem(int index) {
2509            if (tableView.getItems() == null) return null;
2510
2511            if (index < 0 || index >= getItemCount()) return null;
2512
2513            return tableView.getItems().get(index);
2514        }
2515
2516        /**
2517         * The position of the current item in the TableView which has the focus.
2518         */
2519        private ReadOnlyObjectWrapper<TablePosition> focusedCell;
2520        public final ReadOnlyObjectProperty<TablePosition> focusedCellProperty() {
2521            return focusedCellPropertyImpl().getReadOnlyProperty();
2522        }
2523        private void setFocusedCell(TablePosition value) { focusedCellPropertyImpl().set(value);  }
2524        public final TablePosition getFocusedCell() { return focusedCell == null ? EMPTY_CELL : focusedCell.get(); }
2525
2526        private ReadOnlyObjectWrapper<TablePosition> focusedCellPropertyImpl() {
2527            if (focusedCell == null) {
2528                focusedCell = new ReadOnlyObjectWrapper<TablePosition>(EMPTY_CELL) {
2529                    private TablePosition old;
2530                    @Override protected void invalidated() {
2531                        if (get() == null) return;
2532
2533                        if (old == null || !old.equals(get())) {
2534                            setFocusedIndex(get().getRow());
2535                            setFocusedItem(getModelItem(getValue().getRow()));
2536                            
2537                            old = get();
2538                        }
2539                    }
2540
2541                    @Override
2542                    public Object getBean() {
2543                        return TableViewFocusModel.this;
2544                    }
2545
2546                    @Override
2547                    public String getName() {
2548                        return "focusedCell";
2549                    }
2550                };
2551            }
2552            return focusedCell;
2553        }
2554
2555
2556        /**
2557         * Causes the item at the given index to receive the focus.
2558         *
2559         * @param row The row index of the item to give focus to.
2560         * @param column The column of the item to give focus to. Can be null.
2561         */
2562        @Override public void focus(int row, TableColumn<S,?> column) {
2563            if (row < 0 || row >= getItemCount()) {
2564                setFocusedCell(EMPTY_CELL);
2565            } else {
2566                setFocusedCell(new TablePosition(tableView, row, column));
2567            }
2568        }
2569
2570        /**
2571         * Convenience method for setting focus on a particular row or cell
2572         * using a {@link TablePosition}.
2573         * 
2574         * @param pos The table position where focus should be set.
2575         */
2576        public void focus(TablePosition pos) {
2577            if (pos == null) return;
2578            focus(pos.getRow(), pos.getTableColumn());
2579        }
2580
2581
2582        /***********************************************************************
2583         *                                                                     *
2584         * Public API                                                          *
2585         *                                                                     *
2586         **********************************************************************/
2587
2588        /**
2589         * Tests whether the row / cell at the given location currently has the
2590         * focus within the TableView.
2591         */
2592        @Override public boolean isFocused(int row, TableColumn<S,?> column) {
2593            if (row < 0 || row >= getItemCount()) return false;
2594
2595            TablePosition cell = getFocusedCell();
2596            boolean columnMatch = column == null || column.equals(cell.getTableColumn());
2597
2598            return cell.getRow() == row && columnMatch;
2599        }
2600
2601        /**
2602         * Causes the item at the given index to receive the focus. This does not
2603         * cause the current selection to change. Updates the focusedItem and
2604         * focusedIndex properties such that <code>focusedIndex = -1</code> unless
2605         * <pre><code>0 <= index < model size</code></pre>.
2606         *
2607         * @param index The index of the item to get focus.
2608         */
2609        @Override public void focus(int index) {
2610            if (index < 0 || index >= getItemCount()) {
2611                setFocusedCell(EMPTY_CELL);
2612            } else {
2613                setFocusedCell(new TablePosition(tableView, index, null));
2614            }
2615        }
2616
2617        /**
2618         * Attempts to move focus to the cell above the currently focused cell.
2619         */
2620        @Override public void focusAboveCell() {
2621            TablePosition cell = getFocusedCell();
2622
2623            if (getFocusedIndex() == -1) {
2624                focus(getItemCount() - 1, cell.getTableColumn());
2625            } else if (getFocusedIndex() > 0) {
2626                focus(getFocusedIndex() - 1, cell.getTableColumn());
2627            }
2628        }
2629
2630        /**
2631         * Attempts to move focus to the cell below the currently focused cell.
2632         */
2633        @Override public void focusBelowCell() {
2634            TablePosition cell = getFocusedCell();
2635            if (getFocusedIndex() == -1) {
2636                focus(0, cell.getTableColumn());
2637            } else if (getFocusedIndex() != getItemCount() -1) {
2638                focus(getFocusedIndex() + 1, cell.getTableColumn());
2639            }
2640        }
2641
2642        /**
2643         * Attempts to move focus to the cell to the left of the currently focused cell.
2644         */
2645        @Override public void focusLeftCell() {
2646            TablePosition cell = getFocusedCell();
2647            if (cell.getColumn() <= 0) return;
2648            focus(cell.getRow(), getTableColumn(cell.getTableColumn(), -1));
2649        }
2650
2651        /**
2652         * Attempts to move focus to the cell to the right of the the currently focused cell.
2653         */
2654        @Override public void focusRightCell() {
2655            TablePosition cell = getFocusedCell();
2656            if (cell.getColumn() == getColumnCount() - 1) return;
2657            focus(cell.getRow(), getTableColumn(cell.getTableColumn(), 1));
2658        }
2659
2660
2661
2662         /***********************************************************************
2663         *                                                                     *
2664         * Private Implementation                                              *
2665         *                                                                     *
2666         **********************************************************************/
2667
2668        private int getColumnCount() {
2669            return tableView.getVisibleLeafColumns().size();
2670        }
2671
2672        // Gets a table column to the left or right of the current one, given an offset
2673        private TableColumn<S,?> getTableColumn(TableColumn<S,?> column, int offset) {
2674            int columnIndex = tableView.getVisibleLeafIndex(column);
2675            int newColumnIndex = columnIndex + offset;
2676            return tableView.getVisibleLeafColumn(newColumnIndex);
2677        }
2678    }
2679
2680}