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