1 /*
   2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.HashMap;
  31 import java.util.List;
  32 
  33 import com.sun.javafx.scene.control.Properties;
  34 import com.sun.javafx.scene.control.SelectedItemsReadOnlyObservableList;
  35 import com.sun.javafx.scene.control.behavior.ListCellBehavior;
  36 import javafx.beans.InvalidationListener;
  37 import javafx.beans.Observable;
  38 import javafx.beans.WeakInvalidationListener;
  39 import javafx.beans.property.BooleanProperty;
  40 import javafx.beans.property.DoubleProperty;
  41 import javafx.beans.property.ObjectProperty;
  42 import javafx.beans.property.ObjectPropertyBase;
  43 import javafx.beans.property.ReadOnlyIntegerProperty;
  44 import javafx.beans.property.ReadOnlyIntegerWrapper;
  45 import javafx.beans.property.SimpleBooleanProperty;
  46 import javafx.beans.property.SimpleObjectProperty;
  47 import javafx.beans.value.WritableValue;
  48 import javafx.collections.FXCollections;
  49 import javafx.collections.ListChangeListener;
  50 import javafx.collections.ListChangeListener.Change;
  51 import javafx.collections.MapChangeListener;
  52 import javafx.collections.ObservableList;
  53 import javafx.css.StyleableDoubleProperty;
  54 import javafx.event.Event;
  55 import javafx.event.EventHandler;
  56 import javafx.event.EventType;
  57 import javafx.geometry.Orientation;
  58 import javafx.scene.layout.Region;
  59 import javafx.util.Callback;
  60 import javafx.css.StyleableObjectProperty;
  61 import javafx.css.CssMetaData;
  62 
  63 import javafx.css.converter.EnumConverter;
  64 
  65 import javafx.collections.WeakListChangeListener;
  66 
  67 import javafx.css.converter.SizeConverter;
  68 import javafx.scene.control.skin.ListViewSkin;
  69 
  70 import java.lang.ref.WeakReference;
  71 
  72 import javafx.css.PseudoClass;
  73 import javafx.beans.DefaultProperty;
  74 import javafx.css.Styleable;
  75 import javafx.css.StyleableProperty;
  76 import javafx.scene.AccessibleAttribute;
  77 import javafx.scene.AccessibleRole;
  78 import javafx.scene.Node;
  79 import javafx.util.Pair;
  80 
  81 /**
  82  * A ListView displays a horizontal or vertical list of items from which the
  83  * user may select, or with which the user may interact. A ListView is able to
  84  * have its generic type set to represent the type of data in the backing model.
  85  * Doing this has the benefit of making various methods in the ListView, as well
  86  * as the supporting classes (mentioned below), type-safe. In addition, making
  87  * use of the generic type supports substantially simplified development of applications
  88  * making use of ListView, as all modern IDEs are able to auto-complete far
  89  * more successfully with the additional type information.
  90  *
  91  * <h3>Populating a ListView</h3>
  92  * <p>A simple example of how to create and populate a ListView of names (Strings)
  93  * is shown here:
  94  *
  95  * <pre>
  96  * {@code
  97  * ObservableList<String> names = FXCollections.observableArrayList(
  98  *          "Julia", "Ian", "Sue", "Matthew", "Hannah", "Stephan", "Denise");
  99  * ListView<String> listView = new ListView<String>(names);}</pre>
 100  *
 101  * <p>The elements of the ListView are contained within the
 102  * {@link #itemsProperty() items} {@link ObservableList}. This
 103  * ObservableList is automatically observed by the ListView, such that any
 104  * changes that occur inside the ObservableList will be automatically shown in
 105  * the ListView itself. If passing the <code>ObservableList</code> in to the
 106  * ListView constructor is not feasible, the recommended approach for setting
 107  * the items is to simply call:
 108  *
 109  * <pre>
 110  * {@code
 111  * ObservableList<T> content = ...
 112  * listView.setItems(content);}</pre>
 113  *
 114  * The end result of this is, as noted above, that the ListView will automatically
 115  * refresh the view to represent the items in the list.
 116  *
 117  * <p>Another approach, whilst accepted by the ListView, <b>is not the
 118  * recommended approach</b>:
 119  *
 120  * <pre>
 121  * {@code
 122  * List<T> content = ...
 123  * getItems().setAll(content);}</pre>
 124  *
 125  * The issue with the approach shown above is that the content list is being
 126  * copied into the items list - meaning that subsequent changes to the content
 127  * list are not observed, and will not be reflected visually within the ListView.
 128  *
 129  * <h3>ListView Selection / Focus APIs</h3>
 130  * <p>To track selection and focus, it is necessary to become familiar with the
 131  * {@link SelectionModel} and {@link FocusModel} classes. A ListView has at most
 132  * one instance of each of these classes, available from
 133  * {@link #selectionModelProperty() selectionModel} and
 134  * {@link #focusModelProperty() focusModel} properties respectively.
 135  * Whilst it is possible to use this API to set a new selection model, in
 136  * most circumstances this is not necessary - the default selection and focus
 137  * models should work in most circumstances.
 138  *
 139  * <p>The default {@link SelectionModel} used when instantiating a ListView is
 140  * an implementation of the {@link MultipleSelectionModel} abstract class.
 141  * However, as noted in the API documentation for
 142  * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode}
 143  * property, the default value is {@link SelectionMode#SINGLE}. To enable
 144  * multiple selection in a default ListView instance, it is therefore necessary
 145  * to do the following:
 146  *
 147  * <pre>
 148  * {@code
 149  * listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}</pre>
 150  *
 151  * <h3>Customizing ListView Visuals</h3>
 152  * <p>The visuals of the ListView can be entirely customized by replacing the
 153  * default {@link #cellFactoryProperty() cell factory}. A cell factory is used to
 154  * generate {@link ListCell} instances, which are used to represent an item in the
 155  * ListView. See the {@link Cell} class documentation for a more complete
 156  * description of how to write custom Cells.
 157  *
 158  * <h3>Editing</h3>
 159  * <p>This control supports inline editing of values, and this section attempts to
 160  * give an overview of the available APIs and how you should use them.</p>
 161  *
 162  * <p>Firstly, cell editing most commonly requires a different user interface
 163  * than when a cell is not being edited. This is the responsibility of the
 164  * {@link Cell} implementation being used. For ListView, this is the responsibility
 165  * of the {@link #cellFactoryProperty() cell factory}. It is your choice whether the cell is
 166  * permanently in an editing state (e.g. this is common for {@link CheckBox} cells),
 167  * or to switch to a different UI when editing begins (e.g. when a double-click
 168  * is received on a cell).</p>
 169  *
 170  * <p>To know when editing has been requested on a cell,
 171  * simply override the {@link javafx.scene.control.Cell#startEdit()} method, and
 172  * update the cell {@link javafx.scene.control.Cell#textProperty() text} and
 173  * {@link javafx.scene.control.Cell#graphicProperty() graphic} properties as
 174  * appropriate (e.g. set the text to null and set the graphic to be a
 175  * {@link TextField}). Additionally, you should also override
 176  * {@link Cell#cancelEdit()} to reset the UI back to its original visual state
 177  * when the editing concludes. In both cases it is important that you also
 178  * ensure that you call the super method to have the cell perform all duties it
 179  * must do to enter or exit its editing mode.</p>
 180  *
 181  * <p>Once your cell is in an editing state, the next thing you are most probably
 182  * interested in is how to commit or cancel the editing that is taking place. This is your
 183  * responsibility as the cell factory provider. Your cell implementation will know
 184  * when the editing is over, based on the user input (e.g. when the user presses
 185  * the Enter or ESC keys on their keyboard). When this happens, it is your
 186  * responsibility to call {@link Cell#commitEdit(Object)} or
 187  * {@link Cell#cancelEdit()}, as appropriate.</p>
 188  *
 189  * <p>When you call {@link Cell#commitEdit(Object)} an event is fired to the
 190  * ListView, which you can observe by adding an {@link EventHandler} via
 191  * {@link ListView#setOnEditCommit(javafx.event.EventHandler)}. Similarly,
 192  * you can also observe edit events for
 193  * {@link ListView#setOnEditStart(javafx.event.EventHandler) edit start}
 194  * and {@link ListView#setOnEditCancel(javafx.event.EventHandler) edit cancel}.</p>
 195  *
 196  * <p>By default the ListView edit commit handler is non-null, with a default
 197  * handler that attempts to overwrite the property value for the
 198  * item in the currently-being-edited row. It is able to do this as the
 199  * {@link Cell#commitEdit(Object)} method is passed in the new value, and this
 200  * is passed along to the edit commit handler via the
 201  * {@link EditEvent} that is fired. It is simply a matter of calling
 202  * {@link EditEvent#getNewValue()} to retrieve this value.
 203  *
 204  * <p>It is very important to note that if you call
 205  * {@link ListView#setOnEditCommit(javafx.event.EventHandler)} with your own
 206  * {@link EventHandler}, then you will be removing the default handler. Unless
 207  * you then handle the writeback to the property (or the relevant data source),
 208  * nothing will happen. You can work around this by using the
 209  * {@link ListView#addEventHandler(javafx.event.EventType, javafx.event.EventHandler)}
 210  * method to add a {@link ListView#EDIT_COMMIT_EVENT} {@link EventType} with
 211  * your desired {@link EventHandler} as the second argument. Using this method,
 212  * you will not replace the default implementation, but you will be notified when
 213  * an edit commit has occurred.</p>
 214  *
 215  * <p>Hopefully this summary answers some of the commonly asked questions.
 216  * Fortunately, JavaFX ships with a number of pre-built cell factories that
 217  * handle all the editing requirements on your behalf. You can find these
 218  * pre-built cell factories in the javafx.scene.control.cell package.</p>
 219  *
 220  * @see ListCell
 221  * @see MultipleSelectionModel
 222  * @see FocusModel
 223  * @param <T> This type is used to represent the type of the objects stored in
 224  *          the ListViews {@link #itemsProperty() items} ObservableList. It is
 225  *          also used in the {@link #selectionModelProperty() selection model}
 226  *          and {@link #focusModelProperty() focus model}.
 227  * @since JavaFX 2.0
 228  */
 229 // TODO add code examples
 230 @DefaultProperty("items")
 231 public class ListView<T> extends Control {
 232 
 233     /***************************************************************************
 234      *                                                                         *
 235      * Static properties and methods                                           *
 236      *                                                                         *
 237      **************************************************************************/
 238 
 239     /**
 240      * An EventType that indicates some edit event has occurred. It is the parent
 241      * type of all other edit events: {@link #EDIT_START_EVENT},
 242      *  {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT}.
 243      */
 244     @SuppressWarnings("unchecked")
 245     public static <T> EventType<ListView.EditEvent<T>> editAnyEvent() {
 246         return (EventType<ListView.EditEvent<T>>) EDIT_ANY_EVENT;
 247     }
 248     private static final EventType<?> EDIT_ANY_EVENT =
 249             new EventType<>(Event.ANY, "LIST_VIEW_EDIT");
 250 
 251     /**
 252      * An EventType used to indicate that an edit event has started within the
 253      * ListView upon which the event was fired.
 254      */
 255     @SuppressWarnings("unchecked")
 256     public static <T> EventType<ListView.EditEvent<T>> editStartEvent() {
 257         return (EventType<ListView.EditEvent<T>>) EDIT_START_EVENT;
 258     }
 259     private static final EventType<?> EDIT_START_EVENT =
 260             new EventType<>(editAnyEvent(), "EDIT_START");
 261 
 262     /**
 263      * An EventType used to indicate that an edit event has just been canceled
 264      * within the ListView upon which the event was fired.
 265      */
 266     @SuppressWarnings("unchecked")
 267     public static <T> EventType<ListView.EditEvent<T>> editCancelEvent() {
 268         return (EventType<ListView.EditEvent<T>>) EDIT_CANCEL_EVENT;
 269     }
 270     private static final EventType<?> EDIT_CANCEL_EVENT =
 271             new EventType<>(editAnyEvent(), "EDIT_CANCEL");
 272 
 273     /**
 274      * An EventType used to indicate that an edit event has been committed
 275      * within the ListView upon which the event was fired.
 276      */
 277     @SuppressWarnings("unchecked")
 278     public static <T> EventType<ListView.EditEvent<T>> editCommitEvent() {
 279         return (EventType<ListView.EditEvent<T>>) EDIT_COMMIT_EVENT;
 280     }
 281     private static final EventType<?> EDIT_COMMIT_EVENT =
 282             new EventType<>(editAnyEvent(), "EDIT_COMMIT");
 283 
 284 
 285 
 286     /***************************************************************************
 287      *                                                                         *
 288      * Fields                                                                  *
 289      *                                                                         *
 290      **************************************************************************/
 291 
 292     // by default we always select the first row in the ListView, and when the
 293     // items list changes, we also reselect the first row. In some cases, such as
 294     // for the ComboBox, this is not desirable, so it can be disabled here.
 295     private boolean selectFirstRowByDefault = true;
 296 
 297 
 298 
 299     /***************************************************************************
 300      *                                                                         *
 301      * Constructors                                                            *
 302      *                                                                         *
 303      **************************************************************************/
 304 
 305     /**
 306      * Creates a default ListView which will display contents stacked vertically.
 307      * As no {@link ObservableList} is provided in this constructor, an empty
 308      * ObservableList is created, meaning that it is legal to directly call
 309      * {@link #getItems()} if so desired. However, as noted elsewhere, this
 310      * is not the recommended approach
 311      * (instead call {@link #setItems(javafx.collections.ObservableList)}).
 312      *
 313      * <p>Refer to the {@link ListView} class documentation for details on the
 314      * default state of other properties.
 315      */
 316     public ListView() {
 317         this(FXCollections.<T>observableArrayList());
 318     }
 319 
 320     /**
 321      * Creates a default ListView which will stack the contents retrieved from the
 322      * provided {@link ObservableList} vertically.
 323      *
 324      * <p>Attempts to add a listener to the {@link ObservableList}, such that all
 325      * subsequent changes inside the list will be shown to the user.
 326      *
 327      * <p>Refer to the {@link ListView} class documentation for details on the
 328      * default state of other properties.
 329      */
 330     public ListView(ObservableList<T> items) {
 331         getStyleClass().setAll(DEFAULT_STYLE_CLASS);
 332         setAccessibleRole(AccessibleRole.LIST_VIEW);
 333 
 334         setItems(items);
 335 
 336         // Install default....
 337         // ...selection model
 338         setSelectionModel(new ListView.ListViewBitSetSelectionModel<T>(this));
 339 
 340         // ...focus model
 341         setFocusModel(new ListView.ListViewFocusModel<T>(this));
 342 
 343         // ...edit commit handler
 344         setOnEditCommit(DEFAULT_EDIT_COMMIT_HANDLER);
 345 
 346         // Fix for RT-36651, which was introduced by RT-35679 (above) and resolved
 347         // by having special-case code to remove the listener when requested.
 348         // This is done by ComboBoxListViewSkin, so that selection is not done
 349         // when a ComboBox is shown.
 350         getProperties().addListener((MapChangeListener<Object, Object>) change -> {
 351             if (change.wasAdded() && "selectFirstRowByDefault".equals(change.getKey())) {
 352                 Boolean _selectFirstRowByDefault = (Boolean) change.getValueAdded();
 353                 if (_selectFirstRowByDefault == null) return;
 354                 selectFirstRowByDefault = _selectFirstRowByDefault;
 355             }
 356         });
 357     }
 358 
 359 
 360 
 361     /***************************************************************************
 362      *                                                                         *
 363      * Callbacks and Events                                                    *
 364      *                                                                         *
 365      **************************************************************************/
 366 
 367     private EventHandler<ListView.EditEvent<T>> DEFAULT_EDIT_COMMIT_HANDLER = t -> {
 368         int index = t.getIndex();
 369         List<T> list = getItems();
 370         if (index < 0 || index >= list.size()) return;
 371         list.set(index, t.getNewValue());
 372     };
 373 
 374 
 375 
 376     /***************************************************************************
 377      *                                                                         *
 378      * Properties                                                              *
 379      *                                                                         *
 380      **************************************************************************/
 381 
 382     // --- Items
 383     private ObjectProperty<ObservableList<T>> items;
 384 
 385     /**
 386      * Sets the underlying data model for the ListView. Note that it has a generic
 387      * type that must match the type of the ListView itself.
 388      */
 389     public final void setItems(ObservableList<T> value) {
 390         itemsProperty().set(value);
 391     }
 392 
 393     /**
 394      * Returns an {@link ObservableList} that contains the items currently being
 395      * shown to the user. This may be null if
 396      * {@link #setItems(javafx.collections.ObservableList)} has previously been
 397      * called, however, by default it is an empty ObservableList.
 398      *
 399      * @return An ObservableList containing the items to be shown to the user, or
 400      *      null if the items have previously been set to null.
 401      */
 402     public final ObservableList<T> getItems() {
 403         return items == null ? null : items.get();
 404     }
 405 
 406     /**
 407      * The underlying data model for the ListView. Note that it has a generic
 408      * type that must match the type of the ListView itself.
 409      */
 410     public final ObjectProperty<ObservableList<T>> itemsProperty() {
 411         if (items == null) {
 412             items = new SimpleObjectProperty<>(this, "items");
 413         }
 414         return items;
 415     }
 416 
 417 
 418     // --- Placeholder Node
 419     private ObjectProperty<Node> placeholder;
 420     /**
 421      * This Node is shown to the user when the listview has no content to show.
 422      * This may be the case because the table model has no data in the first
 423      * place or that a filter has been applied to the list model, resulting
 424      * in there being nothing to show the user..
 425      * @since JavaFX 8.0
 426      */
 427     public final ObjectProperty<Node> placeholderProperty() {
 428         if (placeholder == null) {
 429             placeholder = new SimpleObjectProperty<Node>(this, "placeholder");
 430         }
 431         return placeholder;
 432     }
 433     public final void setPlaceholder(Node value) {
 434         placeholderProperty().set(value);
 435     }
 436     public final Node getPlaceholder() {
 437         return placeholder == null ? null : placeholder.get();
 438     }
 439 
 440 
 441     // --- Selection Model
 442     private ObjectProperty<MultipleSelectionModel<T>> selectionModel = new SimpleObjectProperty<MultipleSelectionModel<T>>(this, "selectionModel");
 443 
 444     /**
 445      * Sets the {@link MultipleSelectionModel} to be used in the ListView.
 446      * Despite a ListView requiring a <b>Multiple</b>SelectionModel, it is possible
 447      * to configure it to only allow single selection (see
 448      * {@link MultipleSelectionModel#setSelectionMode(javafx.scene.control.SelectionMode)}
 449      * for more information).
 450      */
 451     public final void setSelectionModel(MultipleSelectionModel<T> value) {
 452         selectionModelProperty().set(value);
 453     }
 454 
 455     /**
 456      * Returns the currently installed selection model.
 457      */
 458     public final MultipleSelectionModel<T> getSelectionModel() {
 459         return selectionModel == null ? null : selectionModel.get();
 460     }
 461 
 462     /**
 463      * The SelectionModel provides the API through which it is possible
 464      * to select single or multiple items within a ListView, as  well as inspect
 465      * which items have been selected by the user. Note that it has a generic
 466      * type that must match the type of the ListView itself.
 467      */
 468     public final ObjectProperty<MultipleSelectionModel<T>> selectionModelProperty() {
 469         return selectionModel;
 470     }
 471 
 472 
 473     // --- Focus Model
 474     private ObjectProperty<FocusModel<T>> focusModel;
 475 
 476     /**
 477      * Sets the {@link FocusModel} to be used in the ListView.
 478      */
 479     public final void setFocusModel(FocusModel<T> value) {
 480         focusModelProperty().set(value);
 481     }
 482 
 483     /**
 484      * Returns the currently installed {@link FocusModel}.
 485      */
 486     public final FocusModel<T> getFocusModel() {
 487         return focusModel == null ? null : focusModel.get();
 488     }
 489 
 490     /**
 491      * The FocusModel provides the API through which it is possible
 492      * to both get and set the focus on a single item within a ListView. Note
 493      * that it has a generic type that must match the type of the ListView itself.
 494      */
 495     public final ObjectProperty<FocusModel<T>> focusModelProperty() {
 496         if (focusModel == null) {
 497             focusModel = new SimpleObjectProperty<FocusModel<T>>(this, "focusModel");
 498         }
 499         return focusModel;
 500     }
 501 
 502 
 503     // --- Orientation
 504     private ObjectProperty<Orientation> orientation;
 505 
 506     /**
 507      * Sets the orientation of the ListView, which dictates whether
 508      * it scrolls vertically or horizontally.
 509      */
 510     public final void setOrientation(Orientation value) {
 511         orientationProperty().set(value);
 512     };
 513 
 514     /**
 515      * Returns the current orientation of the ListView, which dictates whether
 516      * it scrolls vertically or horizontally.
 517      */
 518     public final Orientation getOrientation() {
 519         return orientation == null ? Orientation.VERTICAL : orientation.get();
 520     }
 521 
 522     /**
 523      * The orientation of the {@code ListView} - this can either be horizontal
 524      * or vertical.
 525      */
 526     public final ObjectProperty<Orientation> orientationProperty() {
 527         if (orientation == null) {
 528             orientation = new StyleableObjectProperty<Orientation>(Orientation.VERTICAL) {
 529                 @Override public void invalidated() {
 530                     final boolean active = (get() == Orientation.VERTICAL);
 531                     pseudoClassStateChanged(PSEUDO_CLASS_VERTICAL,    active);
 532                     pseudoClassStateChanged(PSEUDO_CLASS_HORIZONTAL, !active);
 533                 }
 534 
 535                 @Override
 536                 public CssMetaData<ListView<?>,Orientation> getCssMetaData() {
 537                     return ListView.StyleableProperties.ORIENTATION;
 538                 }
 539 
 540                 @Override
 541                 public Object getBean() {
 542                     return ListView.this;
 543                 }
 544 
 545                 @Override
 546                 public String getName() {
 547                     return "orientation";
 548                 }
 549             };
 550         }
 551         return orientation;
 552     }
 553 
 554 
 555 
 556 
 557     // --- Cell Factory
 558     private ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactory;
 559 
 560     /**
 561      * Sets a new cell factory to use in the ListView. This forces all old
 562      * {@link ListCell}'s to be thrown away, and new ListCell's created with
 563      * the new cell factory.
 564      */
 565     public final void setCellFactory(Callback<ListView<T>, ListCell<T>> value) {
 566         cellFactoryProperty().set(value);
 567     }
 568 
 569     /**
 570      * Returns the current cell factory.
 571      */
 572     public final Callback<ListView<T>, ListCell<T>> getCellFactory() {
 573         return cellFactory == null ? null : cellFactory.get();
 574     }
 575 
 576     /**
 577      * <p>Setting a custom cell factory has the effect of deferring all cell
 578      * creation, allowing for total customization of the cell. Internally, the
 579      * ListView is responsible for reusing ListCells - all that is necessary
 580      * is for the custom cell factory to return from this function a ListCell
 581      * which might be usable for representing any item in the ListView.
 582      *
 583      * <p>Refer to the {@link Cell} class documentation for more detail.
 584      */
 585     public final ObjectProperty<Callback<ListView<T>, ListCell<T>>> cellFactoryProperty() {
 586         if (cellFactory == null) {
 587             cellFactory = new SimpleObjectProperty<Callback<ListView<T>, ListCell<T>>>(this, "cellFactory");
 588         }
 589         return cellFactory;
 590     }
 591 
 592 
 593     // --- Fixed cell size
 594     private DoubleProperty fixedCellSize;
 595 
 596     /**
 597      * Sets the new fixed cell size for this control. Any value greater than
 598      * zero will enable fixed cell size mode, whereas a zero or negative value
 599      * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size
 600      * mode.
 601      *
 602      * @param value The new fixed cell size value, or a value less than or equal
 603      *              to zero (or Region.USE_COMPUTED_SIZE) to disable.
 604      * @since JavaFX 8.0
 605      */
 606     public final void setFixedCellSize(double value) {
 607         fixedCellSizeProperty().set(value);
 608     }
 609 
 610     /**
 611      * Returns the fixed cell size value. A value less than or equal to zero is
 612      * used to represent that fixed cell size mode is disabled, and a value
 613      * greater than zero represents the size of all cells in this control.
 614      *
 615      * @return A double representing the fixed cell size of this control, or a
 616      *      value less than or equal to zero if fixed cell size mode is disabled.
 617      * @since JavaFX 8.0
 618      */
 619     public final double getFixedCellSize() {
 620         return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get();
 621     }
 622     /**
 623      * Specifies whether this control has cells that are a fixed height (of the
 624      * specified value). If this value is less than or equal to zero,
 625      * then all cells are individually sized and positioned. This is a slow
 626      * operation. Therefore, when performance matters and developers are not
 627      * dependent on variable cell sizes it is a good idea to set the fixed cell
 628      * size value. Generally cells are around 24px, so setting a fixed cell size
 629      * of 24 is likely to result in very little difference in visuals, but a
 630      * improvement to performance.
 631      *
 632      * <p>To set this property via CSS, use the -fx-fixed-cell-size property.
 633      * This should not be confused with the -fx-cell-size property. The difference
 634      * between these two CSS properties is that -fx-cell-size will size all
 635      * cells to the specified size, but it will not enforce that this is the
 636      * only size (thus allowing for variable cell sizes, and preventing the
 637      * performance gains from being possible). Therefore, when performance matters
 638      * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are
 639      * specified in CSS, -fx-fixed-cell-size takes precedence.</p>
 640      *
 641      * @since JavaFX 8.0
 642      */
 643     public final DoubleProperty fixedCellSizeProperty() {
 644         if (fixedCellSize == null) {
 645             fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) {
 646                 @Override public CssMetaData<ListView<?>,Number> getCssMetaData() {
 647                     return StyleableProperties.FIXED_CELL_SIZE;
 648                 }
 649 
 650                 @Override public Object getBean() {
 651                     return ListView.this;
 652                 }
 653 
 654                 @Override public String getName() {
 655                     return "fixedCellSize";
 656                 }
 657             };
 658         }
 659         return fixedCellSize;
 660     }
 661 
 662 
 663     // --- Editable
 664     private BooleanProperty editable;
 665     public final void setEditable(boolean value) {
 666         editableProperty().set(value);
 667     }
 668     public final boolean isEditable() {
 669         return editable == null ? false : editable.get();
 670     }
 671     /**
 672      * Specifies whether this ListView is editable - only if the ListView and
 673      * the ListCells within it are both editable will a ListCell be able to go
 674      * into their editing state.
 675      */
 676     public final BooleanProperty editableProperty() {
 677         if (editable == null) {
 678             editable = new SimpleBooleanProperty(this, "editable", false);
 679         }
 680         return editable;
 681     }
 682 
 683 
 684     // --- Editing Index
 685     private ReadOnlyIntegerWrapper editingIndex;
 686 
 687     private void setEditingIndex(int value) {
 688         editingIndexPropertyImpl().set(value);
 689     }
 690 
 691     /**
 692      * Returns the index of the item currently being edited in the ListView,
 693      * or -1 if no item is being edited.
 694      */
 695     public final int getEditingIndex() {
 696         return editingIndex == null ? -1 : editingIndex.get();
 697     }
 698 
 699     /**
 700      * <p>A property used to represent the index of the item currently being edited
 701      * in the ListView, if editing is taking place, or -1 if no item is being edited.
 702      *
 703      * <p>It is not possible to set the editing index, instead it is required that
 704      * you call {@link #edit(int)}.
 705      */
 706     public final ReadOnlyIntegerProperty editingIndexProperty() {
 707         return editingIndexPropertyImpl().getReadOnlyProperty();
 708     }
 709 
 710     private ReadOnlyIntegerWrapper editingIndexPropertyImpl() {
 711         if (editingIndex == null) {
 712             editingIndex = new ReadOnlyIntegerWrapper(this, "editingIndex", -1);
 713         }
 714         return editingIndex;
 715     }
 716 
 717 
 718     // --- On Edit Start
 719     private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditStart;
 720 
 721     /**
 722      * Sets the {@link EventHandler} that will be called when the user begins
 723      * an edit.
 724      *
 725      * <p>This is a convenience method - the same result can be
 726      * achieved by calling
 727      * <code>addEventHandler(ListView.EDIT_START_EVENT, eventHandler)</code>.
 728      */
 729     public final void setOnEditStart(EventHandler<ListView.EditEvent<T>> value) {
 730         onEditStartProperty().set(value);
 731     }
 732 
 733     /**
 734      * Returns the {@link EventHandler} that will be called when the user begins
 735      * an edit.
 736      */
 737     public final EventHandler<ListView.EditEvent<T>> getOnEditStart() {
 738         return onEditStart == null ? null : onEditStart.get();
 739     }
 740 
 741     /**
 742      * This event handler will be fired when the user successfully initiates
 743      * editing.
 744      */
 745     public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditStartProperty() {
 746         if (onEditStart == null) {
 747             onEditStart = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
 748                 @Override protected void invalidated() {
 749                     setEventHandler(ListView.<T>editStartEvent(), get());
 750                 }
 751 
 752                 @Override
 753                 public Object getBean() {
 754                     return ListView.this;
 755                 }
 756 
 757                 @Override
 758                 public String getName() {
 759                     return "onEditStart";
 760                 }
 761             };
 762         }
 763         return onEditStart;
 764     }
 765 
 766 
 767     // --- On Edit Commit
 768     private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCommit;
 769 
 770     /**
 771      * Sets the {@link EventHandler} that will be called when the user has
 772      * completed their editing. This is called as part of the
 773      * {@link ListCell#commitEdit(java.lang.Object)} method.
 774      *
 775      * <p>This is a convenience method - the same result can be
 776      * achieved by calling
 777      * <code>addEventHandler(ListView.EDIT_START_EVENT, eventHandler)</code>.
 778      */
 779     public final void setOnEditCommit(EventHandler<ListView.EditEvent<T>> value) {
 780         onEditCommitProperty().set(value);
 781     }
 782 
 783     /**
 784      * Returns the {@link EventHandler} that will be called when the user commits
 785      * an edit.
 786      */
 787     public final EventHandler<ListView.EditEvent<T>> getOnEditCommit() {
 788         return onEditCommit == null ? null : onEditCommit.get();
 789     }
 790 
 791     /**
 792      * <p>This property is used when the user performs an action that should
 793      * result in their editing input being persisted.</p>
 794      *
 795      * <p>The EventHandler in this property should not be called directly -
 796      * instead call {@link ListCell#commitEdit(java.lang.Object)} from within
 797      * your custom ListCell. This will handle firing this event, updating the
 798      * view, and switching out of the editing state.</p>
 799      */
 800     public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCommitProperty() {
 801         if (onEditCommit == null) {
 802             onEditCommit = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
 803                 @Override protected void invalidated() {
 804                     setEventHandler(ListView.<T>editCommitEvent(), get());
 805                 }
 806 
 807                 @Override
 808                 public Object getBean() {
 809                     return ListView.this;
 810                 }
 811 
 812                 @Override
 813                 public String getName() {
 814                     return "onEditCommit";
 815                 }
 816             };
 817         }
 818         return onEditCommit;
 819     }
 820 
 821 
 822     // --- On Edit Cancel
 823     private ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCancel;
 824 
 825     /**
 826      * Sets the {@link EventHandler} that will be called when the user cancels
 827      * an edit.
 828      */
 829     public final void setOnEditCancel(EventHandler<ListView.EditEvent<T>> value) {
 830         onEditCancelProperty().set(value);
 831     }
 832 
 833     /**
 834      * Returns the {@link EventHandler} that will be called when the user cancels
 835      * an edit.
 836      */
 837     public final EventHandler<ListView.EditEvent<T>> getOnEditCancel() {
 838         return onEditCancel == null ? null : onEditCancel.get();
 839     }
 840 
 841     /**
 842      * This event handler will be fired when the user cancels editing a cell.
 843      */
 844     public final ObjectProperty<EventHandler<ListView.EditEvent<T>>> onEditCancelProperty() {
 845         if (onEditCancel == null) {
 846             onEditCancel = new ObjectPropertyBase<EventHandler<ListView.EditEvent<T>>>() {
 847                 @Override protected void invalidated() {
 848                     setEventHandler(ListView.<T>editCancelEvent(), get());
 849                 }
 850 
 851                 @Override
 852                 public Object getBean() {
 853                     return ListView.this;
 854                 }
 855 
 856                 @Override
 857                 public String getName() {
 858                     return "onEditCancel";
 859                 }
 860             };
 861         }
 862         return onEditCancel;
 863     }
 864 
 865 
 866 
 867 
 868     /***************************************************************************
 869      *                                                                         *
 870      * Public API                                                              *
 871      *                                                                         *
 872      **************************************************************************/
 873 
 874     /**
 875      * Instructs the ListView to begin editing the item in the given index, if
 876      * the ListView is {@link #editableProperty() editable}. Once
 877      * this method is called, if the current {@link #cellFactoryProperty()} is
 878      * set up to support editing, the Cell will switch its visual state to enable
 879      * for user input to take place.
 880      *
 881      * @param itemIndex The index of the item in the ListView that should be
 882      *     edited.
 883      */
 884     public void edit(int itemIndex) {
 885         if (!isEditable()) return;
 886         setEditingIndex(itemIndex);
 887     }
 888 
 889     /**
 890      * Scrolls the ListView such that the item in the given index is visible to
 891      * the end user.
 892      *
 893      * @param index The index that should be made visible to the user, assuming
 894      *      of course that it is greater than, or equal to 0, and less than the
 895      *      size of the items list contained within the given ListView.
 896      */
 897     public void scrollTo(int index) {
 898         ControlUtils.scrollToIndex(this, index);
 899     }
 900 
 901     /**
 902      * Scrolls the TableView so that the given object is visible within the viewport.
 903      * @param object The object that should be visible to the user.
 904      * @since JavaFX 8.0
 905      */
 906     public void scrollTo(T object) {
 907         if( getItems() != null ) {
 908             int idx = getItems().indexOf(object);
 909             if( idx >= 0 ) {
 910                 ControlUtils.scrollToIndex(this, idx);
 911             }
 912         }
 913     }
 914 
 915     /**
 916      * Called when there's a request to scroll an index into view using {@link #scrollTo(int)}
 917      * or {@link #scrollTo(Object)}
 918      * @since JavaFX 8.0
 919      */
 920     private ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollTo;
 921 
 922     public void setOnScrollTo(EventHandler<ScrollToEvent<Integer>> value) {
 923         onScrollToProperty().set(value);
 924     }
 925 
 926     public EventHandler<ScrollToEvent<Integer>> getOnScrollTo() {
 927         if( onScrollTo != null ) {
 928             return onScrollTo.get();
 929         }
 930         return null;
 931     }
 932 
 933     public ObjectProperty<EventHandler<ScrollToEvent<Integer>>> onScrollToProperty() {
 934         if( onScrollTo == null ) {
 935             onScrollTo = new ObjectPropertyBase<EventHandler<ScrollToEvent<Integer>>>() {
 936                 @Override protected void invalidated() {
 937                     setEventHandler(ScrollToEvent.scrollToTopIndex(), get());
 938                 }
 939 
 940                 @Override public Object getBean() {
 941                     return ListView.this;
 942                 }
 943 
 944                 @Override public String getName() {
 945                     return "onScrollTo";
 946                 }
 947             };
 948         }
 949         return onScrollTo;
 950     }
 951 
 952     /** {@inheritDoc} */
 953     @Override protected Skin<?> createDefaultSkin() {
 954         return new ListViewSkin<T>(this);
 955     }
 956 
 957     /**
 958      * Calling {@code refresh()} forces the ListView control to recreate and
 959      * repopulate the cells necessary to populate the visual bounds of the control.
 960      * In other words, this forces the ListView to update what it is showing to
 961      * the user. This is useful in cases where the underlying data source has
 962      * changed in a way that is not observed by the ListView itself.
 963      *
 964      * @since JavaFX 8u60
 965      */
 966     public void refresh() {
 967         getProperties().put(Properties.RECREATE, Boolean.TRUE);
 968     }
 969 
 970 
 971 
 972     /***************************************************************************
 973      *                                                                         *
 974      * Private Implementation                                                  *
 975      *                                                                         *
 976      **************************************************************************/
 977 
 978 
 979 
 980     /***************************************************************************
 981      *                                                                         *
 982      * Stylesheet Handling                                                     *
 983      *                                                                         *
 984      **************************************************************************/
 985 
 986     private static final String DEFAULT_STYLE_CLASS = "list-view";
 987 
 988     private static class StyleableProperties {
 989         private static final CssMetaData<ListView<?>,Orientation> ORIENTATION =
 990             new CssMetaData<ListView<?>,Orientation>("-fx-orientation",
 991                 new EnumConverter<Orientation>(Orientation.class),
 992                 Orientation.VERTICAL) {
 993 
 994             @Override
 995             public Orientation getInitialValue(ListView<?> node) {
 996                 // A vertical ListView should remain vertical
 997                 return node.getOrientation();
 998             }
 999 
1000             @Override
1001             public boolean isSettable(ListView<?> n) {
1002                 return n.orientation == null || !n.orientation.isBound();
1003             }
1004 
1005             @SuppressWarnings("unchecked") // orientationProperty() is a StyleableProperty<Orientation>
1006             @Override
1007             public StyleableProperty<Orientation> getStyleableProperty(ListView<?> n) {
1008                 return (StyleableProperty<Orientation>)n.orientationProperty();
1009             }
1010         };
1011 
1012         private static final CssMetaData<ListView<?>,Number> FIXED_CELL_SIZE =
1013             new CssMetaData<ListView<?>,Number>("-fx-fixed-cell-size",
1014                                                 SizeConverter.getInstance(),
1015                                                 Region.USE_COMPUTED_SIZE) {
1016 
1017                 @Override public Double getInitialValue(ListView<?> node) {
1018                     return node.getFixedCellSize();
1019                 }
1020 
1021                 @Override public boolean isSettable(ListView<?> n) {
1022                     return n.fixedCellSize == null || !n.fixedCellSize.isBound();
1023                 }
1024 
1025                 @Override public StyleableProperty<Number> getStyleableProperty(ListView<?> n) {
1026                     return (StyleableProperty<Number>)(WritableValue<Number>)n.fixedCellSizeProperty();
1027                 }
1028             };
1029 
1030         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1031         static {
1032             final List<CssMetaData<? extends Styleable, ?>> styleables =
1033                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
1034             styleables.add(ORIENTATION);
1035             styleables.add(FIXED_CELL_SIZE);
1036             STYLEABLES = Collections.unmodifiableList(styleables);
1037         }
1038     }
1039 
1040     /**
1041      * @return The CssMetaData associated with this class, which may include the
1042      * CssMetaData of its superclasses.
1043      * @since JavaFX 8.0
1044      */
1045     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1046         return StyleableProperties.STYLEABLES;
1047     }
1048 
1049     /**
1050      * {@inheritDoc}
1051      * @since JavaFX 8.0
1052      */
1053     @Override
1054     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
1055         return getClassCssMetaData();
1056     }
1057 
1058     private static final PseudoClass PSEUDO_CLASS_VERTICAL =
1059             PseudoClass.getPseudoClass("vertical");
1060     private static final PseudoClass PSEUDO_CLASS_HORIZONTAL =
1061             PseudoClass.getPseudoClass("horizontal");
1062 
1063 
1064 
1065     /***************************************************************************
1066      *                                                                         *
1067      * Accessibility handling                                                  *
1068      *                                                                         *
1069      **************************************************************************/
1070 
1071     @Override
1072     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1073         switch (attribute) {
1074             case MULTIPLE_SELECTION: {
1075                 MultipleSelectionModel<T> sm = getSelectionModel();
1076                 return sm != null && sm.getSelectionMode() == SelectionMode.MULTIPLE;
1077             }
1078             default: return super.queryAccessibleAttribute(attribute, parameters);
1079         }
1080     }
1081 
1082 
1083     /***************************************************************************
1084      *                                                                         *
1085      * Support Interfaces                                                      *
1086      *                                                                         *
1087      **************************************************************************/
1088 
1089 
1090 
1091     /***************************************************************************
1092      *                                                                         *
1093      * Support Classes                                                         *
1094      *                                                                         *
1095      **************************************************************************/
1096 
1097     /**
1098      * An {@link Event} subclass used specifically in ListView for representing
1099      * edit-related events. It provides additional API to easily access the
1100      * index that the edit event took place on, as well as the input provided
1101      * by the end user.
1102      *
1103      * @param <T> The type of the input, which is the same type as the ListView
1104      *      itself.
1105      * @since JavaFX 2.0
1106      */
1107     public static class EditEvent<T> extends Event {
1108         private final T newValue;
1109         private final int editIndex;
1110         private final ListView<T> source;
1111 
1112         private static final long serialVersionUID = 20130724L;
1113 
1114         /**
1115          * Common supertype for all edit event types.
1116          * @since JavaFX 8.0
1117          */
1118         public static final EventType<?> ANY = EDIT_ANY_EVENT;
1119 
1120         /**
1121          * Creates a new EditEvent instance to represent an edit event. This
1122          * event is used for {@link #EDIT_START_EVENT},
1123          * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT} types.
1124          */
1125         public EditEvent(ListView<T> source,
1126                          EventType<? extends ListView.EditEvent<T>> eventType,
1127                          T newValue,
1128                          int editIndex) {
1129             super(source, Event.NULL_SOURCE_TARGET, eventType);
1130             this.source = source;
1131             this.editIndex = editIndex;
1132             this.newValue = newValue;
1133         }
1134 
1135         /**
1136          * Returns the ListView upon which the edit took place.
1137          */
1138         @Override public ListView<T> getSource() {
1139             return source;
1140         }
1141 
1142         /**
1143          * Returns the index in which the edit took place.
1144          */
1145         public int getIndex() {
1146             return editIndex;
1147         }
1148 
1149         /**
1150          * Returns the value of the new input provided by the end user.
1151          */
1152         public T getNewValue() {
1153             return newValue;
1154         }
1155 
1156         /**
1157          * Returns a string representation of this {@code EditEvent} object.
1158          * @return a string representation of this {@code EditEvent} object.
1159          */
1160         @Override public String toString() {
1161             return "ListViewEditEvent [ newValue: " + getNewValue() + ", ListView: " + getSource() + " ]";
1162         }
1163     }
1164 
1165 
1166 
1167     // package for testing
1168     static class ListViewBitSetSelectionModel<T> extends MultipleSelectionModelBase<T> {
1169 
1170         /***********************************************************************
1171          *                                                                     *
1172          * Constructors                                                        *
1173          *                                                                     *
1174          **********************************************************************/
1175 
1176         public ListViewBitSetSelectionModel(final ListView<T> listView) {
1177             if (listView == null) {
1178                 throw new IllegalArgumentException("ListView can not be null");
1179             }
1180 
1181             this.listView = listView;
1182 
1183             ((SelectedItemsReadOnlyObservableList)getSelectedItems()).setItemsList(listView.getItems());
1184 
1185 
1186             /*
1187              * The following two listeners are used in conjunction with
1188              * SelectionModel.select(T obj) to allow for a developer to select
1189              * an item that is not actually in the data model. When this occurs,
1190              * we actively try to find an index that matches this object, going
1191              * so far as to actually watch for all changes to the items list,
1192              * rechecking each time.
1193              */
1194             itemsObserver = new InvalidationListener() {
1195                 private WeakReference<ObservableList<T>> weakItemsRef = new WeakReference<>(listView.getItems());
1196 
1197                 @Override public void invalidated(Observable observable) {
1198                     ObservableList<T> oldItems = weakItemsRef.get();
1199                     weakItemsRef = new WeakReference<>(listView.getItems());
1200                     ((SelectedItemsReadOnlyObservableList)getSelectedItems()).setItemsList(listView.getItems());
1201                     updateItemsObserver(oldItems, listView.getItems());
1202                 }
1203             };
1204 
1205             this.listView.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
1206             if (listView.getItems() != null) {
1207                 this.listView.getItems().addListener(weakItemsContentObserver);
1208             }
1209 
1210             updateItemCount();
1211 
1212             updateDefaultSelection();
1213         }
1214 
1215         // watching for changes to the items list content
1216         private final ListChangeListener<T> itemsContentObserver = new ListChangeListener<T>() {
1217             @Override public void onChanged(Change<? extends T> c) {
1218                 updateItemCount();
1219 
1220                 boolean doSelectionUpdate = true;
1221 
1222                 while (c.next()) {
1223                     final T selectedItem = getSelectedItem();
1224                     final int selectedIndex = getSelectedIndex();
1225 
1226                     if (listView.getItems() == null || listView.getItems().isEmpty()) {
1227                         selectedItemChange = c;
1228                         clearSelection();
1229                         selectedItemChange = null;
1230                     } else if (selectedIndex == -1 && selectedItem != null) {
1231                         int newIndex = listView.getItems().indexOf(selectedItem);
1232                         if (newIndex != -1) {
1233                             setSelectedIndex(newIndex);
1234                             doSelectionUpdate = false;
1235                         }
1236                     } else if (c.wasRemoved() &&
1237                             c.getRemovedSize() == 1 &&
1238                             ! c.wasAdded() &&
1239                             selectedItem != null &&
1240                             selectedItem.equals(c.getRemoved().get(0))) {
1241                         // Bug fix for RT-28637
1242                         if (getSelectedIndex() < getItemCount()) {
1243                             final int previousRow = selectedIndex == 0 ? 0 : selectedIndex - 1;
1244                             T newSelectedItem = getModelItem(previousRow);
1245                             if (! selectedItem.equals(newSelectedItem)) {
1246                                 startAtomic();
1247                                 clearSelection(selectedIndex);
1248                                 stopAtomic();
1249                                 select(newSelectedItem);
1250                             }
1251                         }
1252                     }
1253                 }
1254 
1255                 if (doSelectionUpdate) {
1256                     updateSelection(c);
1257                 }
1258             }
1259         };
1260 
1261         // watching for changes to the items list
1262         private final InvalidationListener itemsObserver;
1263 
1264         private WeakListChangeListener<T> weakItemsContentObserver =
1265                 new WeakListChangeListener<>(itemsContentObserver);
1266 
1267 
1268 
1269 
1270         /***********************************************************************
1271          *                                                                     *
1272          * Internal properties                                                 *
1273          *                                                                     *
1274          **********************************************************************/
1275 
1276         private final ListView<T> listView;
1277 
1278         private int itemCount = 0;
1279 
1280         private int previousModelSize = 0;
1281 
1282         // Listen to changes in the listview items list, such that when it
1283         // changes we can update the selected indices bitset to refer to the
1284         // new indices.
1285         // At present this is basically a left/right shift operation, which
1286         // seems to work ok.
1287         private void updateSelection(Change<? extends T> c) {
1288 //            // debugging output
1289 //            System.out.println(listView.getId());
1290 //            if (c.wasAdded()) {
1291 //                System.out.println("\tAdded size: " + c.getAddedSize() + ", Added sublist: " + c.getAddedSubList());
1292 //            }
1293 //            if (c.wasRemoved()) {
1294 //                System.out.println("\tRemoved size: " + c.getRemovedSize() + ", Removed sublist: " + c.getRemoved());
1295 //            }
1296 //            if (c.wasReplaced()) {
1297 //                System.out.println("\tWas replaced");
1298 //            }
1299 //            if (c.wasPermutated()) {
1300 //                System.out.println("\tWas permutated");
1301 //            }
1302             c.reset();
1303 
1304             List<Pair<Integer, Integer>> shifts = new ArrayList<>();
1305             while (c.next()) {
1306                 if (c.wasReplaced()) {
1307                     if (c.getList().isEmpty()) {
1308                         // the entire items list was emptied - clear selection
1309                         clearSelection();
1310                     } else {
1311                         int index = getSelectedIndex();
1312 
1313                         if (previousModelSize == c.getRemovedSize()) {
1314                             // all items were removed from the model
1315                             clearSelection();
1316                         } else if (index < getItemCount() && index >= 0) {
1317                             // Fix for RT-18969: the list had setAll called on it
1318                             // Use of makeAtomic is a fix for RT-20945
1319                             startAtomic();
1320                             clearSelection(index);
1321                             stopAtomic();
1322                             select(index);
1323                         } else {
1324                             // Fix for RT-22079
1325                             clearSelection();
1326                         }
1327                     }
1328                 } else if (c.wasAdded() || c.wasRemoved()) {
1329                     int shift = c.wasAdded() ? c.getAddedSize() : -c.getRemovedSize();
1330                     shifts.add(new Pair<>(c.getFrom(), shift));
1331                 } else if (c.wasPermutated()) {
1332 
1333                     // General approach:
1334                     //   -- detected a sort has happened
1335                     //   -- Create a permutation lookup map (1)
1336                     //   -- dump all the selected indices into a list (2)
1337                     //   -- clear the selected items / indexes (3)
1338                     //   -- create a list containing the new indices (4)
1339                     //   -- for each previously-selected index (5)
1340                     //     -- if index is in the permutation lookup map
1341                     //       -- add the new index to the new indices list
1342                     //   -- Perform batch selection (6)
1343 
1344                     // (1)
1345                     int length = c.getTo() - c.getFrom();
1346                     HashMap<Integer, Integer> pMap = new HashMap<Integer, Integer>(length);
1347                     for (int i = c.getFrom(); i < c.getTo(); i++) {
1348                         pMap.put(i, c.getPermutation(i));
1349                     }
1350 
1351                     // (2)
1352                     List<Integer> selectedIndices = new ArrayList<Integer>(getSelectedIndices());
1353 
1354 
1355                     // (3)
1356                     clearSelection();
1357 
1358                     // (4)
1359                     List<Integer> newIndices = new ArrayList<Integer>(getSelectedIndices().size());
1360 
1361                     // (5)
1362                     for (int i = 0; i < selectedIndices.size(); i++) {
1363                         int oldIndex = selectedIndices.get(i);
1364 
1365                         if (pMap.containsKey(oldIndex)) {
1366                             Integer newIndex = pMap.get(oldIndex);
1367                             newIndices.add(newIndex);
1368                         }
1369                     }
1370 
1371                     // (6)
1372                     if (!newIndices.isEmpty()) {
1373                         if (newIndices.size() == 1) {
1374                             select(newIndices.get(0));
1375                         } else {
1376                             int[] ints = new int[newIndices.size() - 1];
1377                             for (int i = 0; i < newIndices.size() - 1; i++) {
1378                                 ints[i] = newIndices.get(i + 1);
1379                             }
1380                             selectIndices(newIndices.get(0), ints);
1381                         }
1382                     }
1383                 }
1384             }
1385 
1386             if (!shifts.isEmpty()) {
1387                 shiftSelection(shifts, null);
1388             }
1389 
1390             previousModelSize = getItemCount();
1391         }
1392 
1393 
1394 
1395         /***********************************************************************
1396          *                                                                     *
1397          * Public selection API                                                *
1398          *                                                                     *
1399          **********************************************************************/
1400 
1401         /** {@inheritDoc} */
1402         @Override public void selectAll() {
1403             // when a selectAll happens, the anchor should not change, so we store it
1404             // before, and restore it afterwards
1405             final int anchor = ListCellBehavior.getAnchor(listView, -1);
1406             super.selectAll();
1407             ListCellBehavior.setAnchor(listView, anchor, false);
1408         }
1409 
1410         /** {@inheritDoc} */
1411         @Override public void clearAndSelect(int row) {
1412             ListCellBehavior.setAnchor(listView, row, false);
1413             super.clearAndSelect(row);
1414         }
1415 
1416         /** {@inheritDoc} */
1417         @Override protected void focus(int row) {
1418             if (listView.getFocusModel() == null) return;
1419             listView.getFocusModel().focus(row);
1420 
1421             listView.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
1422         }
1423 
1424         /** {@inheritDoc} */
1425         @Override protected int getFocusedIndex() {
1426             if (listView.getFocusModel() == null) return -1;
1427             return listView.getFocusModel().getFocusedIndex();
1428         }
1429 
1430         @Override protected int getItemCount() {
1431             return itemCount;
1432         }
1433 
1434         @Override protected T getModelItem(int index) {
1435             List<T> items = listView.getItems();
1436             if (items == null) return null;
1437             if (index < 0 || index >= itemCount) return null;
1438 
1439             return items.get(index);
1440         }
1441 
1442 
1443 
1444         /***********************************************************************
1445          *                                                                     *
1446          * Private implementation                                              *
1447          *                                                                     *
1448          **********************************************************************/
1449 
1450         private void updateItemCount() {
1451             if (listView == null) {
1452                 itemCount = -1;
1453             } else {
1454                 List<T> items = listView.getItems();
1455                 itemCount = items == null ? -1 : items.size();
1456             }
1457         }
1458 
1459         private void updateItemsObserver(ObservableList<T> oldList, ObservableList<T> newList) {
1460             // update listeners
1461             if (oldList != null) {
1462                 oldList.removeListener(weakItemsContentObserver);
1463             }
1464             if (newList != null) {
1465                 newList.addListener(weakItemsContentObserver);
1466             }
1467 
1468             updateItemCount();
1469             updateDefaultSelection();
1470         }
1471 
1472         private void updateDefaultSelection() {
1473             // when the items list totally changes, we should clear out
1474             // the selection and focus
1475             int newSelectionIndex = -1;
1476             int newFocusIndex = -1;
1477             if (listView.getItems() != null) {
1478                 T selectedItem = getSelectedItem();
1479                 if (selectedItem != null) {
1480                     newSelectionIndex = listView.getItems().indexOf(selectedItem);
1481                     newFocusIndex = newSelectionIndex;
1482                 }
1483 
1484                 // we put focus onto the first item, if there is at least
1485                 // one item in the list
1486                 if (listView.selectFirstRowByDefault && newFocusIndex == -1) {
1487                     newFocusIndex = listView.getItems().size() > 0 ? 0 : -1;
1488                 }
1489             }
1490 
1491             clearSelection();
1492             select(newSelectionIndex);
1493 //            focus(newFocusIndex);
1494         }
1495     }
1496 
1497 
1498 
1499     // package for testing
1500     static class ListViewFocusModel<T> extends FocusModel<T> {
1501 
1502         private final ListView<T> listView;
1503         private int itemCount = 0;
1504 
1505         public ListViewFocusModel(final ListView<T> listView) {
1506             if (listView == null) {
1507                 throw new IllegalArgumentException("ListView can not be null");
1508             }
1509 
1510             this.listView = listView;
1511 
1512             itemsObserver = new InvalidationListener() {
1513                 private WeakReference<ObservableList<T>> weakItemsRef = new WeakReference<>(listView.getItems());
1514 
1515                 @Override public void invalidated(Observable observable) {
1516                     ObservableList<T> oldItems = weakItemsRef.get();
1517                     weakItemsRef = new WeakReference<>(listView.getItems());
1518                     updateItemsObserver(oldItems, listView.getItems());
1519                 }
1520             };
1521             this.listView.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
1522             if (listView.getItems() != null) {
1523                 this.listView.getItems().addListener(weakItemsContentListener);
1524             }
1525 
1526             updateItemCount();
1527             updateDefaultFocus();
1528 
1529             focusedIndexProperty().addListener(o -> {
1530                 listView.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_ITEM);
1531             });
1532         }
1533 
1534 
1535         private void updateItemsObserver(ObservableList<T> oldList, ObservableList<T> newList) {
1536             // the listview items list has changed, we need to observe
1537             // the new list, and remove any observer we had from the old list
1538             if (oldList != null) oldList.removeListener(weakItemsContentListener);
1539             if (newList != null) newList.addListener(weakItemsContentListener);
1540 
1541             updateItemCount();
1542             updateDefaultFocus();
1543         }
1544 
1545         private final InvalidationListener itemsObserver;
1546 
1547         // Listen to changes in the listview items list, such that when it
1548         // changes we can update the focused index to refer to the new indices.
1549         private final ListChangeListener<T> itemsContentListener = c -> {
1550             updateItemCount();
1551 
1552             while (c.next()) {
1553                 // looking at the first change
1554                 int from = c.getFrom();
1555 
1556                 if (c.wasReplaced() || c.getAddedSize() == getItemCount()) {
1557                     updateDefaultFocus();
1558                     return;
1559                 }
1560 
1561                 if (getFocusedIndex() == -1 || from > getFocusedIndex()) {
1562                     return;
1563                 }
1564 
1565                 c.reset();
1566                 boolean added = false;
1567                 boolean removed = false;
1568                 int addedSize = 0;
1569                 int removedSize = 0;
1570                 while (c.next()) {
1571                     added |= c.wasAdded();
1572                     removed |= c.wasRemoved();
1573                     addedSize += c.getAddedSize();
1574                     removedSize += c.getRemovedSize();
1575                 }
1576 
1577                 if (added && !removed) {
1578                     focus(Math.min(getItemCount() - 1, getFocusedIndex() + addedSize));
1579                 } else if (!added && removed) {
1580                     focus(Math.max(0, getFocusedIndex() - removedSize));
1581                 }
1582             }
1583         };
1584 
1585         private WeakListChangeListener<T> weakItemsContentListener
1586                 = new WeakListChangeListener<T>(itemsContentListener);
1587 
1588         @Override protected int getItemCount() {
1589             return itemCount;
1590         }
1591 
1592         @Override protected T getModelItem(int index) {
1593             if (isEmpty()) return null;
1594             if (index < 0 || index >= itemCount) return null;
1595 
1596             return listView.getItems().get(index);
1597         }
1598 
1599         private boolean isEmpty() {
1600             return itemCount == -1;
1601         }
1602 
1603         private void updateItemCount() {
1604             if (listView == null) {
1605                 itemCount = -1;
1606             } else {
1607                 List<T> items = listView.getItems();
1608                 itemCount = items == null ? -1 : items.size();
1609             }
1610         }
1611 
1612         private void updateDefaultFocus() {
1613             // when the items list totally changes, we should clear out
1614             // the focus
1615             int newValueIndex = -1;
1616             if (listView.getItems() != null) {
1617                 T focusedItem = getFocusedItem();
1618                 if (focusedItem != null) {
1619                     newValueIndex = listView.getItems().indexOf(focusedItem);
1620                 }
1621 
1622                 // we put focus onto the first item, if there is at least
1623                 // one item in the list
1624                 if (newValueIndex == -1) {
1625                     newValueIndex = listView.getItems().size() > 0 ? 0 : -1;
1626                 }
1627             }
1628 
1629             focus(newValueIndex);
1630         }
1631     }
1632 }