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