All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javafx.scene.control.ListView Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene.control;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.css.StyleableDoubleProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Orientation;
import javafx.scene.layout.Region;
import javafx.util.Callback;

import javafx.css.StyleableObjectProperty;
import javafx.css.CssMetaData;
import com.sun.javafx.css.converters.EnumConverter;
import javafx.collections.WeakListChangeListener;
import com.sun.javafx.css.converters.SizeConverter;
import com.sun.javafx.scene.control.accessible.AccessibleList;
import com.sun.javafx.scene.control.skin.ListViewSkin;
import com.sun.javafx.scene.control.skin.VirtualContainerBase;
import java.lang.ref.WeakReference;
import com.sun.javafx.accessible.providers.AccessibleProvider;
import javafx.css.PseudoClass;
import javafx.beans.DefaultProperty;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.scene.Node;

/**
 * A ListView displays a horizontal or vertical list of items from which the
 * user may select, or with which the user may interact. A ListView is able to
 * have its generic type set to represent the type of data in the backing model.
 * Doing this has the benefit of making various methods in the ListView, as well
 * as the supporting classes (mentioned below), type-safe. In addition, making 
 * use of the generic supports substantially simplifies development of applications
 * making use of ListView, as all modern IDEs are able to auto-complete far
 * more successfully with the additional type information.
 * 
 * 

Populating a ListView

*

A simple example of how to create and populate a ListView of names (Strings) * is shown here: * *

 * {@code
 * ObservableList names = FXCollections.observableArrayList(
 *          "Julia", "Ian", "Sue", "Matthew", "Hannah", "Stephan", "Denise");
 * ListView listView = new ListView(names);}
* *

The elements of the ListView are contained within the * {@link #itemsProperty() items} {@link ObservableList}. This * ObservableList is automatically observed by the ListView, such that any * changes that occur inside the ObservableList will be automatically shown in * the ListView itself. If passying the ObservableList in to the * ListView constructor is not feasible, the recommended approach for setting * the items is to simply call: * *

 * {@code
 * ObservableList content = ...
 * listView.setItems(content);}
* * The end result of this is, as noted above, that the ListView will automatically * refresh the view to represent the items in the list. * *

Another approach, whilst accepted by the ListView, is not the * recommended approach: * *

 * {@code
 * List content = ...
 * getItems().setAll(content);}
* * The issue with the approach shown above is that the content list is being * copied into the items list - meaning that subsequent changes to the content * list are not observed, and will not be reflected visually within the ListView. * *

ListView Selection / Focus APIs

*

To track selection and focus, it is necessary to become familiar with the * {@link SelectionModel} and {@link FocusModel} classes. A ListView has at most * one instance of each of these classes, available from * {@link #selectionModelProperty() selectionModel} and * {@link #focusModelProperty() focusModel} properties respectively. * Whilst it is possible to use this API to set a new selection model, in * most circumstances this is not necessary - the default selection and focus * models should work in most circumstances. * *

The default {@link SelectionModel} used when instantiating a ListView is * an implementation of the {@link MultipleSelectionModel} abstract class. * However, as noted in the API documentation for * the {@link MultipleSelectionModel#selectionModeProperty() selectionMode} * property, the default value is {@link SelectionMode#SINGLE}. To enable * multiple selection in a default ListView instance, it is therefore necessary * to do the following: * *

 * {@code 
 * listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);}
* *

Customizing ListView Visuals

*

The visuals of the ListView can be entirely customized by replacing the * default {@link #cellFactoryProperty() cell factory}. A cell factory is used to * generate {@link ListCell} instances, which are used to represent an item in the * ListView. See the {@link Cell} class documentation for a more complete * description of how to write custom Cells. * * @see ListCell * @see MultipleSelectionModel * @see FocusModel * @param This type is used to represent the type of the objects stored in * the ListViews {@link #itemsProperty() items} ObservableList. It is * also used in the {@link #selectionModelProperty() selection model} * and {@link #focusModelProperty() focus model}. * @since JavaFX 2.0 */ // TODO add code examples @DefaultProperty("items") public class ListView extends Control { /*************************************************************************** * * * Static properties and methods * * * **************************************************************************/ /** * An EventType that indicates some edit event has occurred. It is the parent * type of all other edit events: {@link #EDIT_START_EVENT}, * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT}. */ @SuppressWarnings("unchecked") public static EventType> editAnyEvent() { return (EventType>) EDIT_ANY_EVENT; } private static final EventType EDIT_ANY_EVENT = new EventType(Event.ANY, "LIST_VIEW_EDIT"); /** * An EventType used to indicate that an edit event has started within the * ListView upon which the event was fired. */ @SuppressWarnings("unchecked") public static EventType> editStartEvent() { return (EventType>) EDIT_START_EVENT; } private static final EventType EDIT_START_EVENT = new EventType(editAnyEvent(), "EDIT_START"); /** * An EventType used to indicate that an edit event has just been canceled * within the ListView upon which the event was fired. */ @SuppressWarnings("unchecked") public static EventType> editCancelEvent() { return (EventType>) EDIT_CANCEL_EVENT; } private static final EventType EDIT_CANCEL_EVENT = new EventType(editAnyEvent(), "EDIT_CANCEL"); /** * An EventType used to indicate that an edit event has been committed * within the ListView upon which the event was fired. */ @SuppressWarnings("unchecked") public static EventType> editCommitEvent() { return (EventType>) EDIT_COMMIT_EVENT; } private static final EventType EDIT_COMMIT_EVENT = new EventType(editAnyEvent(), "EDIT_COMMIT"); /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Creates a default ListView which will display contents stacked vertically. * As no {@link ObservableList} is provided in this constructor, an empty * ObservableList is created, meaning that it is legal to directly call * {@link #getItems()} if so desired. However, as noted elsewhere, this * is not the recommended approach * (instead call {@link #setItems(javafx.collections.ObservableList)}). * *

Refer to the {@link ListView} class documentation for details on the * default state of other properties. */ public ListView() { this(FXCollections.observableArrayList()); } /** * Creates a default ListView which will stack the contents retrieved from the * provided {@link ObservableList} vertically. * *

Attempts to add a listener to the {@link ObservableList}, such that all * subsequent changes inside the list will be shown to the user. * *

Refer to the {@link ListView} class documentation for details on the * default state of other properties. */ public ListView(ObservableList items) { getStyleClass().setAll(DEFAULT_STYLE_CLASS); setItems(items); // Install default.... // ...selection model setSelectionModel(new ListView.ListViewBitSetSelectionModel(this)); // ...focus model setFocusModel(new ListView.ListViewFocusModel(this)); // ...edit commit handler setOnEditCommit(DEFAULT_EDIT_COMMIT_HANDLER); } /*************************************************************************** * * * Callbacks and Events * * * **************************************************************************/ private EventHandler> DEFAULT_EDIT_COMMIT_HANDLER = new EventHandler>() { @Override public void handle(ListView.EditEvent t) { int index = t.getIndex(); List list = getItems(); if (index < 0 || index >= list.size()) return; list.set(index, t.getNewValue()); } }; /*************************************************************************** * * * Properties * * * **************************************************************************/ // --- Items private ObjectProperty> items; /** * Sets the underlying data model for the ListView. Note that it has a generic * type that must match the type of the ListView itself. */ public final void setItems(ObservableList value) { itemsProperty().set(value); } /** * Returns an {@link ObservableList} that contains the items currently being * shown to the user. This may be null if * {@link #setItems(javafx.collections.ObservableList)} has previously been * called, however, by default it is an empty ObservableList. * * @return An ObservableList containing the items to be shown to the user, or * null if the items have previously been set to null. */ public final ObservableList getItems() { return items == null ? null : items.get(); } /** * The underlying data model for the ListView. Note that it has a generic * type that must match the type of the ListView itself. */ public final ObjectProperty> itemsProperty() { if (items == null) { items = new SimpleObjectProperty>(this, "items") { WeakReference> oldItemsRef; @Override protected void invalidated() { ObservableList oldItems = oldItemsRef == null ? null : oldItemsRef.get(); // FIXME temporary fix for RT-15793. This will need to be // properly fixed when time permits if (getSelectionModel() instanceof ListView.ListViewBitSetSelectionModel) { ((ListView.ListViewBitSetSelectionModel)getSelectionModel()).updateItemsObserver(oldItems, getItems()); } if (getFocusModel() instanceof ListView.ListViewFocusModel) { ((ListView.ListViewFocusModel)getFocusModel()).updateItemsObserver(oldItems, getItems()); } if (getSkin() instanceof ListViewSkin) { ListViewSkin skin = (ListViewSkin) getSkin(); skin.updateListViewItems(); } oldItemsRef = new WeakReference>(getItems()); } }; } return items; } // --- Placeholder Node private ObjectProperty placeholder; /** * This Node is shown to the user when the listview has no content to show. * This may be the case because the table model has no data in the first * place or that a filter has been applied to the list model, resulting * in there being nothing to show the user.. * @since JavaFX 8.0 */ public final ObjectProperty placeholderProperty() { if (placeholder == null) { placeholder = new SimpleObjectProperty(this, "placeholder"); } return placeholder; } public final void setPlaceholder(Node value) { placeholderProperty().set(value); } public final Node getPlaceholder() { return placeholder == null ? null : placeholder.get(); } // --- Selection Model private ObjectProperty> selectionModel = new SimpleObjectProperty>(this, "selectionModel"); /** * Sets the {@link MultipleSelectionModel} to be used in the ListView. * Despite a ListView requiring a MultipleSelectionModel, it is possible * to configure it to only allow single selection (see * {@link MultipleSelectionModel#setSelectionMode(javafx.scene.control.SelectionMode)} * for more information). */ public final void setSelectionModel(MultipleSelectionModel value) { selectionModelProperty().set(value); } /** * Returns the currently installed selection model. */ public final MultipleSelectionModel getSelectionModel() { return selectionModel == null ? null : selectionModel.get(); } /** * The SelectionModel provides the API through which it is possible * to select single or multiple items within a ListView, as well as inspect * which items have been selected by the user. Note that it has a generic * type that must match the type of the ListView itself. */ public final ObjectProperty> selectionModelProperty() { return selectionModel; } // --- Focus Model private ObjectProperty> focusModel; /** * Sets the {@link FocusModel} to be used in the ListView. */ public final void setFocusModel(FocusModel value) { focusModelProperty().set(value); } /** * Returns the currently installed {@link FocusModel}. */ public final FocusModel getFocusModel() { return focusModel == null ? null : focusModel.get(); } /** * The FocusModel provides the API through which it is possible * to both get and set the focus on a single item within a ListView. Note * that it has a generic type that must match the type of the ListView itself. */ public final ObjectProperty> focusModelProperty() { if (focusModel == null) { focusModel = new SimpleObjectProperty>(this, "focusModel"); } return focusModel; } // --- Orientation private ObjectProperty orientation; /** * Sets the orientation of the ListView, which dictates whether * it scrolls vertically or horizontally. */ public final void setOrientation(Orientation value) { orientationProperty().set(value); }; /** * Returns the current orientation of the ListView, which dictates whether * it scrolls vertically or horizontally. */ public final Orientation getOrientation() { return orientation == null ? Orientation.VERTICAL : orientation.get(); } /** * The orientation of the {@code ListView} - this can either be horizontal * or vertical. */ public final ObjectProperty orientationProperty() { if (orientation == null) { orientation = new StyleableObjectProperty(Orientation.VERTICAL) { @Override public void invalidated() { final boolean active = (get() == Orientation.VERTICAL); pseudoClassStateChanged(PSEUDO_CLASS_VERTICAL, active); pseudoClassStateChanged(PSEUDO_CLASS_HORIZONTAL, !active); } @Override public CssMetaData,Orientation> getCssMetaData() { return ListView.StyleableProperties.ORIENTATION; } @Override public Object getBean() { return ListView.this; } @Override public String getName() { return "orientation"; } }; } return orientation; } // --- Cell Factory private ObjectProperty, ListCell>> cellFactory; /** * Sets a new cell factory to use in the ListView. This forces all old * {@link ListCell}'s to be thrown away, and new ListCell's created with * the new cell factory. */ public final void setCellFactory(Callback, ListCell> value) { cellFactoryProperty().set(value); } /** * Returns the current cell factory. */ public final Callback, ListCell> getCellFactory() { return cellFactory == null ? null : cellFactory.get(); } /** *

Setting a custom cell factory has the effect of deferring all cell * creation, allowing for total customization of the cell. Internally, the * ListView is responsible for reusing ListCells - all that is necessary * is for the custom cell factory to return from this function a ListCell * which might be usable for representing any item in the ListView. * *

Refer to the {@link Cell} class documentation for more detail. */ public final ObjectProperty, ListCell>> cellFactoryProperty() { if (cellFactory == null) { cellFactory = new SimpleObjectProperty, ListCell>>(this, "cellFactory"); } return cellFactory; } // --- Fixed cell size private DoubleProperty fixedCellSize; /** * Sets the new fixed cell size for this control. Any value greater than * zero will enable fixed cell size mode, whereas a zero or negative value * (or Region.USE_COMPUTED_SIZE) will be used to disabled fixed cell size * mode. * * @param value The new fixed cell size value, or -1 (or Region.USE_COMPUTED_SIZE) * to disable. * @since JavaFX 8.0 */ public final void setFixedCellSize(double value) { fixedCellSizeProperty().set(value); } /** * Returns the fixed cell size value, which may be -1 to represent fixed cell * size mode is disabled, or a value greater than zero to represent the size * of all cells in this control. * * @return A double representing the fixed cell size of this control, or -1 * if fixed cell size mode is disabled. * @since JavaFX 8.0 */ public final double getFixedCellSize() { return fixedCellSize == null ? Region.USE_COMPUTED_SIZE : fixedCellSize.get(); } /** * Specifies whether this control has cells that are a fixed height (of the * specified value). If this value is -1 (i.e. {@link Region#USE_COMPUTED_SIZE}), * then all cells are individually sized and positioned. This is a slow * operation. Therefore, when performance matters and developers are not * dependent on variable cell sizes it is a good idea to set the fixed cell * size value. Generally cells are around 24px, so setting a fixed cell size * of 24 is likely to result in very little difference in visuals, but a * improvement to performance. * *

To set this property via CSS, use the -fx-fixed-cell-size property. * This should not be confused with the -fx-cell-size property. The difference * between these two CSS properties is that -fx-cell-size will size all * cells to the specified size, but it will not enforce that this is the * only size (thus allowing for variable cell sizes, and preventing the * performance gains from being possible). Therefore, when performance matters * use -fx-fixed-cell-size, instead of -fx-cell-size. If both properties are * specified in CSS, -fx-fixed-cell-size takes precedence.

* @since JavaFX 8.0 */ public final DoubleProperty fixedCellSizeProperty() { if (fixedCellSize == null) { fixedCellSize = new StyleableDoubleProperty(Region.USE_COMPUTED_SIZE) { @Override public CssMetaData,Number> getCssMetaData() { return StyleableProperties.FIXED_CELL_SIZE; } @Override public Object getBean() { return ListView.this; } @Override public String getName() { return "fixedCellSize"; } }; } return fixedCellSize; } // --- Editable private BooleanProperty editable; public final void setEditable(boolean value) { editableProperty().set(value); } public final boolean isEditable() { return editable == null ? false : editable.get(); } /** * Specifies whether this ListView is editable - only if the ListView and * the ListCells within it are both editable will a ListCell be able to go * into their editing state. */ public final BooleanProperty editableProperty() { if (editable == null) { editable = new SimpleBooleanProperty(this, "editable", false); } return editable; } // --- Editing Index private ReadOnlyIntegerWrapper editingIndex; private void setEditingIndex(int value) { editingIndexPropertyImpl().set(value); } /** * Returns the index of the item currently being edited in the ListView, * or -1 if no item is being edited. */ public final int getEditingIndex() { return editingIndex == null ? -1 : editingIndex.get(); } /** *

A property used to represent the index of the item currently being edited * in the ListView, if editing is taking place, or -1 if no item is being edited. * *

It is not possible to set the editing index, instead it is required that * you call {@link #edit(int)}. */ public final ReadOnlyIntegerProperty editingIndexProperty() { return editingIndexPropertyImpl().getReadOnlyProperty(); } private ReadOnlyIntegerWrapper editingIndexPropertyImpl() { if (editingIndex == null) { editingIndex = new ReadOnlyIntegerWrapper(this, "editingIndex", -1); } return editingIndex; } // --- On Edit Start private ObjectProperty>> onEditStart; /** * Sets the {@link EventHandler} that will be called when the user begins * an edit. * *

This is a convenience method - the same result can be * achieved by calling * addEventHandler(ListView.EDIT_START_EVENT, eventHandler). */ public final void setOnEditStart(EventHandler> value) { onEditStartProperty().set(value); } /** * Returns the {@link EventHandler} that will be called when the user begins * an edit. */ public final EventHandler> getOnEditStart() { return onEditStart == null ? null : onEditStart.get(); } /** * This event handler will be fired when the user successfully initiates * editing. */ public final ObjectProperty>> onEditStartProperty() { if (onEditStart == null) { onEditStart = new ObjectPropertyBase>>() { @Override protected void invalidated() { setEventHandler(ListView.editStartEvent(), get()); } @Override public Object getBean() { return ListView.this; } @Override public String getName() { return "onEditStart"; } }; } return onEditStart; } // --- On Edit Commit private ObjectProperty>> onEditCommit; /** * Sets the {@link EventHandler} that will be called when the user has * completed their editing. This is called as part of the * {@link ListCell#commitEdit(java.lang.Object)} method. * *

This is a convenience method - the same result can be * achieved by calling * addEventHandler(ListView.EDIT_START_EVENT, eventHandler). */ public final void setOnEditCommit(EventHandler> value) { onEditCommitProperty().set(value); } /** * Returns the {@link EventHandler} that will be called when the user commits * an edit. */ public final EventHandler> getOnEditCommit() { return onEditCommit == null ? null : onEditCommit.get(); } /** *

This property is used when the user performs an action that should * result in their editing input being persisted.

* *

The EventHandler in this property should not be called directly - * instead call {@link ListCell#commitEdit(java.lang.Object)} from within * your custom ListCell. This will handle firing this event, updating the * view, and switching out of the editing state.

*/ public final ObjectProperty>> onEditCommitProperty() { if (onEditCommit == null) { onEditCommit = new ObjectPropertyBase>>() { @Override protected void invalidated() { setEventHandler(ListView.editCommitEvent(), get()); } @Override public Object getBean() { return ListView.this; } @Override public String getName() { return "onEditCommit"; } }; } return onEditCommit; } // --- On Edit Cancel private ObjectProperty>> onEditCancel; /** * Sets the {@link EventHandler} that will be called when the user cancels * an edit. */ public final void setOnEditCancel(EventHandler> value) { onEditCancelProperty().set(value); } /** * Returns the {@link EventHandler} that will be called when the user cancels * an edit. */ public final EventHandler> getOnEditCancel() { return onEditCancel == null ? null : onEditCancel.get(); } /** * This event handler will be fired when the user cancels editing a cell. */ public final ObjectProperty>> onEditCancelProperty() { if (onEditCancel == null) { onEditCancel = new ObjectPropertyBase>>() { @Override protected void invalidated() { setEventHandler(ListView.editCancelEvent(), get()); } @Override public Object getBean() { return ListView.this; } @Override public String getName() { return "onEditCancel"; } }; } return onEditCancel; } /*************************************************************************** * * * Public API * * * **************************************************************************/ /** * Instructs the ListView to begin editing the item in the given index, if * the ListView is {@link #editableProperty() editable}. Once * this method is called, if the current {@link #cellFactoryProperty()} is * set up to support editing, the Cell will switch its visual state to enable * for user input to take place. * * @param itemIndex The index of the item in the ListView that should be * edited. */ public void edit(int itemIndex) { if (!isEditable()) return; setEditingIndex(itemIndex); } /** * Scrolls the ListView such that the item in the given index is visible to * the end user. * * @param index The index that should be made visible to the user, assuming * of course that it is greater than, or equal to 0, and less than the * size of the items list contained within the given ListView. */ public void scrollTo(int index) { ControlUtils.scrollToIndex(this, index); } /** * Scrolls the TableView so that the given object is visible within the viewport. * @param object The object that should be visible to the user. * @since JavaFX 8.0 */ public void scrollTo(T object) { if( getItems() != null ) { int idx = getItems().indexOf(object); if( idx >= 0 ) { ControlUtils.scrollToIndex(this, idx); } } } /** * Called when there's a request to scroll an index into view using {@link #scrollTo(int)} * or {@link #scrollTo(S)} * @since JavaFX 8.0 */ private ObjectProperty>> onScrollTo; public void setOnScrollTo(EventHandler> value) { onScrollToProperty().set(value); } public EventHandler> getOnScrollTo() { if( onScrollTo != null ) { return onScrollTo.get(); } return null; } public ObjectProperty>> onScrollToProperty() { if( onScrollTo == null ) { onScrollTo = new ObjectPropertyBase>>() { @Override protected void invalidated() { setEventHandler(ScrollToEvent.scrollToTopIndex(), get()); } @Override public Object getBean() { return ListView.this; } @Override public String getName() { return "onScrollTo"; } }; } return onScrollTo; } private AccessibleList accListView ; /** * @treatAsPrivate implementation detail * @deprecated This is an internal API that is not intended for use and will be removed in the next version */ @Deprecated @Override public AccessibleProvider impl_getAccessible() { if( accListView == null) accListView = new AccessibleList(this); return (AccessibleProvider)accListView ; } /** {@inheritDoc} */ @Override protected Skin createDefaultSkin() { return new ListViewSkin(this); } /*************************************************************************** * * * Private Implementation * * * **************************************************************************/ /*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/ private static final String DEFAULT_STYLE_CLASS = "list-view"; /** @treatAsPrivate */ private static class StyleableProperties { private static final CssMetaData,Orientation> ORIENTATION = new CssMetaData,Orientation>("-fx-orientation", new EnumConverter(Orientation.class), Orientation.VERTICAL) { @Override public Orientation getInitialValue(ListView node) { // A vertical ListView should remain vertical return node.getOrientation(); } @Override public boolean isSettable(ListView n) { return n.orientation == null || !n.orientation.isBound(); } @SuppressWarnings("unchecked") // orientationProperty() is a StyleableProperty @Override public StyleableProperty getStyleableProperty(ListView n) { return (StyleableProperty)n.orientationProperty(); } }; private static final CssMetaData,Number> FIXED_CELL_SIZE = new CssMetaData,Number>("-fx-fixed-cell-size", SizeConverter.getInstance(), Region.USE_COMPUTED_SIZE) { @Override public Double getInitialValue(ListView node) { return node.getFixedCellSize(); } @Override public boolean isSettable(ListView n) { return n.fixedCellSize == null || !n.fixedCellSize.isBound(); } @Override public StyleableProperty getStyleableProperty(ListView n) { return (StyleableProperty) n.fixedCellSizeProperty(); } }; private static final List> STYLEABLES; static { final List> styleables = new ArrayList>(Control.getClassCssMetaData()); styleables.add(ORIENTATION); styleables.add(FIXED_CELL_SIZE); STYLEABLES = Collections.unmodifiableList(styleables); } } /** * @return The CssMetaData associated with this class, which may include the * CssMetaData of its super classes. * @since JavaFX 8.0 */ public static List> getClassCssMetaData() { return StyleableProperties.STYLEABLES; } /** * {@inheritDoc} * @since JavaFX 8.0 */ @Override public List> getControlCssMetaData() { return getClassCssMetaData(); } private static final PseudoClass PSEUDO_CLASS_VERTICAL = PseudoClass.getPseudoClass("vertical"); private static final PseudoClass PSEUDO_CLASS_HORIZONTAL = PseudoClass.getPseudoClass("horizontal"); /*************************************************************************** * * * Support Interfaces * * * **************************************************************************/ /*************************************************************************** * * * Support Classes * * * **************************************************************************/ /** * An {@link Event} subclass used specifically in ListView for representing * edit-related events. It provides additional API to easily access the * index that the edit event took place on, as well as the input provided * by the end user. * * @param The type of the input, which is the same type as the ListView * itself. * @since JavaFX 2.0 */ public static class EditEvent extends Event { private final T newValue; private final int editIndex; /** * Common supertype for all edit event types. * @since JavaFX 8.0 */ public static final EventType ANY = EDIT_ANY_EVENT; /** * Creates a new EditEvent instance to represent an edit event. This * event is used for {@link #EDIT_START_EVENT}, * {@link #EDIT_COMMIT_EVENT} and {@link #EDIT_CANCEL_EVENT} types. */ public EditEvent(ListView source, EventType> eventType, T newValue, int editIndex) { super(source, Event.NULL_SOURCE_TARGET, eventType); this.editIndex = editIndex; this.newValue = newValue; } /** * Returns the ListView upon which the edit took place. */ @Override public ListView getSource() { return (ListView) super.getSource(); } /** * Returns the index in which the edit took place. */ public int getIndex() { return editIndex; } /** * Returns the value of the new input provided by the end user. */ public T getNewValue() { return newValue; } /** * Returns a string representation of this {@code EditEvent} object. * @return a string representation of this {@code EditEvent} object. */ @Override public String toString() { return "ListViewEditEvent [ newValue: " + getNewValue() + ", ListView: " + getSource() + " ]"; } } // package for testing static class ListViewBitSetSelectionModel extends MultipleSelectionModelBase { /*********************************************************************** * * * Constructors * * * **********************************************************************/ public ListViewBitSetSelectionModel(final ListView listView) { if (listView == null) { throw new IllegalArgumentException("ListView can not be null"); } this.listView = listView; /* * The following two listeners are used in conjunction with * SelectionModel.select(T obj) to allow for a developer to select * an item that is not actually in the data model. When this occurs, * we actively try to find an index that matches this object, going * so far as to actually watch for all changes to the items list, * rechecking each time. */ this.listView.itemsProperty().addListener(weakItemsObserver); if (listView.getItems() != null) { this.listView.getItems().addListener(weakItemsContentObserver); // updateItemsObserver(null, this.listView.getItems()); } updateItemCount(); } // watching for changes to the items list content private final ListChangeListener itemsContentObserver = new ListChangeListener() { @Override public void onChanged(Change c) { updateItemCount(); while (c.next()) { final T selectedItem = getSelectedItem(); final int selectedIndex = getSelectedIndex(); if (listView.getItems() == null || listView.getItems().isEmpty()) { clearSelection(); } else if (selectedIndex == -1 && selectedItem != null) { int newIndex = listView.getItems().indexOf(selectedItem); if (newIndex != -1) { setSelectedIndex(newIndex); } } else if (c.wasRemoved() && c.getRemovedSize() == 1 && ! c.wasAdded() && selectedItem != null && selectedItem.equals(c.getRemoved().get(0))) { // Bug fix for RT-28637 if (getSelectedIndex() < getItemCount()) { T newSelectedItem = getModelItem(selectedIndex); if (! selectedItem.equals(newSelectedItem)) { setSelectedItem(newSelectedItem); } } } } updateSelection(c); } }; // watching for changes to the items list private final ChangeListener> itemsObserver = new ChangeListener>() { @Override public void changed(ObservableValue> valueModel, ObservableList oldList, ObservableList newList) { updateItemsObserver(oldList, newList); } }; private WeakListChangeListener weakItemsContentObserver = new WeakListChangeListener(itemsContentObserver); private WeakChangeListener> weakItemsObserver = new WeakChangeListener>(itemsObserver); private void updateItemsObserver(ObservableList oldList, ObservableList newList) { // update listeners if (oldList != null) { oldList.removeListener(weakItemsContentObserver); } if (newList != null) { newList.addListener(weakItemsContentObserver); } updateItemCount(); // when the items list totally changes, we should clear out // the selection and focus setSelectedIndex(-1); focus(-1); } /*********************************************************************** * * * Internal properties * * * **********************************************************************/ private final ListView listView; private int itemCount = 0; private int previousModelSize = 0; // Listen to changes in the listview items list, such that when it // changes we can update the selected indices bitset to refer to the // new indices. // At present this is basically a left/right shift operation, which // seems to work ok. private void updateSelection(Change c) { // // debugging output // System.out.println(listView.getId()); // if (c.wasAdded()) { // System.out.println("\tAdded size: " + c.getAddedSize() + ", Added sublist: " + c.getAddedSubList()); // } // if (c.wasRemoved()) { // System.out.println("\tRemoved size: " + c.getRemovedSize() + ", Removed sublist: " + c.getRemoved()); // } // if (c.wasReplaced()) { // System.out.println("\tWas replaced"); // } // if (c.wasPermutated()) { // System.out.println("\tWas permutated"); // } c.reset(); while (c.next()) { if (c.wasReplaced()) { if (c.getList().isEmpty()) { // the entire items list was emptied - clear selection clearSelection(); } else { int index = getSelectedIndex(); if (previousModelSize == c.getRemovedSize()) { // all items were removed from the model clearSelection(); } else if (index < getItemCount() && index >= 0) { // Fix for RT-18969: the list had setAll called on it // Use of makeAtomic is a fix for RT-20945 makeAtomic = true; clearSelection(index); makeAtomic = false; select(index); } else { // Fix for RT-22079 clearSelection(); } } } else if (c.wasAdded() || c.wasRemoved()) { int shift = c.wasAdded() ? c.getAddedSize() : -c.getRemovedSize(); shiftSelection(c.getFrom(), shift, null); } else if (c.wasPermutated()) { // General approach: // -- detected a sort has happened // -- Create a permutation lookup map (1) // -- dump all the selected indices into a list (2) // -- clear the selected items / indexes (3) // -- create a list containing the new indices (4) // -- for each previously-selected index (5) // -- if index is in the permutation lookup map // -- add the new index to the new indices list // -- Perform batch selection (6) // (1) int length = c.getTo() - c.getFrom(); HashMap pMap = new HashMap(length); for (int i = c.getFrom(); i < c.getTo(); i++) { pMap.put(i, c.getPermutation(i)); } // (2) List selectedIndices = new ArrayList(getSelectedIndices()); // (3) clearSelection(); // (4) List newIndices = new ArrayList(getSelectedIndices().size()); // (5) for (int i = 0; i < selectedIndices.size(); i++) { int oldIndex = selectedIndices.get(i); if (pMap.containsKey(oldIndex)) { Integer newIndex = pMap.get(oldIndex); newIndices.add(newIndex); } } // (6) if (!newIndices.isEmpty()) { if (newIndices.size() == 1) { select(newIndices.get(0)); } else { int[] ints = new int[newIndices.size() - 1]; for (int i = 0; i < newIndices.size() - 1; i++) { ints[i] = newIndices.get(i + 1); } selectIndices(newIndices.get(0), ints); } } } } previousModelSize = getItemCount(); } /*********************************************************************** * * * Public selection API * * * **********************************************************************/ /** {@inheritDoc} */ @Override protected void focus(int row) { if (listView.getFocusModel() == null) return; listView.getFocusModel().focus(row); } /** {@inheritDoc} */ @Override protected int getFocusedIndex() { if (listView.getFocusModel() == null) return -1; return listView.getFocusModel().getFocusedIndex(); } @Override protected int getItemCount() { return itemCount; } @Override protected T getModelItem(int index) { List items = listView.getItems(); if (items == null) return null; if (index < 0 || index >= itemCount) return null; return items.get(index); } private void updateItemCount() { if (listView == null) { itemCount = -1; } else { List items = listView.getItems(); itemCount = items == null ? -1 : items.size(); } } } // package for testing static class ListViewFocusModel extends FocusModel { private final ListView listView; private int itemCount = 0; public ListViewFocusModel(final ListView listView) { if (listView == null) { throw new IllegalArgumentException("ListView can not be null"); } this.listView = listView; this.listView.itemsProperty().addListener(weakItemsListener); if (listView.getItems() != null) { this.listView.getItems().addListener(weakItemsContentListener); } updateItemCount(); } private ChangeListener> itemsListener = new ChangeListener>() { @Override public void changed(ObservableValue> observable, ObservableList oldList, ObservableList newList) { updateItemsObserver(oldList, newList); } }; private WeakChangeListener> weakItemsListener = new WeakChangeListener>(itemsListener); private void updateItemsObserver(ObservableList oldList, ObservableList newList) { // the listview items list has changed, we need to observe // the new list, and remove any observer we had from the old list if (oldList != null) oldList.removeListener(weakItemsContentListener); if (newList != null) newList.addListener(weakItemsContentListener); updateItemCount(); } // Listen to changes in the listview items list, such that when it // changes we can update the focused index to refer to the new indices. private final ListChangeListener itemsContentListener = new ListChangeListener() { @Override public void onChanged(Change c) { updateItemCount(); c.next(); // looking at the first change int from = c.getFrom(); if (getFocusedIndex() == -1 || from > getFocusedIndex()) { return; } c.reset(); boolean added = false; boolean removed = false; int addedSize = 0; int removedSize = 0; while (c.next()) { added |= c.wasAdded(); removed |= c.wasRemoved(); addedSize += c.getAddedSize(); removedSize += c.getRemovedSize(); } if (added && !removed) { focus(getFocusedIndex() + addedSize); } else if (!added && removed) { focus(getFocusedIndex() - removedSize); } } }; private WeakListChangeListener weakItemsContentListener = new WeakListChangeListener(itemsContentListener); @Override protected int getItemCount() { return itemCount; } @Override protected T getModelItem(int index) { if (isEmpty()) return null; if (index < 0 || index >= itemCount) return null; return listView.getItems().get(index); } private boolean isEmpty() { return itemCount == -1; } private void updateItemCount() { if (listView == null) { itemCount = -1; } else { List items = listView.getItems(); itemCount = items == null ? -1 : items.size(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy