javafx.scene.control.ListCell Maven / Gradle / Ivy
Show all versions of openjfx-78-backport Show documentation
/*
* 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 com.sun.javafx.scene.control.skin.ListCellSkin;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* The {@link Cell} type used within {@link ListView} instances. In addition
* to the API defined on Cell and {@link IndexedCell}, the ListCell is more
* tightly bound to a ListView, allowing for better support of editing events,
* etc.
*
*
A ListView maintains selection, indicating which cell(s) have been selected,
* and focus, indicating the current focus owner for any given ListView. For each
* property, each ListCell has a boolean reflecting whether this specific cell is
* selected or focused. To achieve this, each ListCell has a reference back to
* the ListView that it is being used within. Each ListCell belongs to one and
* only one ListView.
*
*
Note that in the case of virtualized controls like ListView, when a cell
* has focus this is not in the same sense as application focus. When a ListCell
* has focus it simply represents the fact that the cell will receive keyboard
* events in the situation that the owning ListView actually contains focus. Of
* course, in the case where a cell has a Node set in the
* {@link #graphicProperty() graphic} property, it is completely legal for this
* Node to request, and acquire focus as would normally be expected.
*
* @param The type of the item contained within the ListCell.
* @since JavaFX 2.0
*/
// TODO add code examples
public class ListCell extends IndexedCell {
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates a default ListCell with the default style class of 'list-cell'.
*/
public ListCell() {
getStyleClass().addAll(DEFAULT_STYLE_CLASS);
}
/***************************************************************************
* *
* Listeners *
* We have to listen to a number of properties on the ListView itself *
* as well as attach listeners to a couple different ObservableLists. *
* We have to be sure to unhook these listeners whenever the reference *
* to the ListView changes, or whenever one of the ObservableList *
* references changes (such as setting the selectionModel, focusModel, *
* or items). *
* *
**************************************************************************/
/**
* Listens to the editing index on the ListView. It is possible for the developer
* to call the ListView#edit(int) method and cause a specific cell to start
* editing. In such a case, we need to be notified so we can call startEdit
* on our side.
*/
private final InvalidationListener editingListener = new InvalidationListener() {
@Override public void invalidated(Observable value) {
updateEditing();
}
};
private boolean updateEditingIndex = true;
/**
* Listens to the selection model on the ListView. Whenever the selection model
* is changed (updated), the selected property on the ListCell is updated accordingly.
*/
private final ListChangeListener selectedListener = new ListChangeListener() {
@Override public void onChanged(ListChangeListener.Change extends Integer> c) {
updateSelection();
}
};
/**
* Listens to the selectionModel property on the ListView. Whenever the entire model is changed,
* we have to unhook the weakSelectedListener and update the selection.
*/
private final ChangeListener> selectionModelPropertyListener = new ChangeListener>() {
@Override
public void changed(
ObservableValue extends MultipleSelectionModel> observable,
MultipleSelectionModel oldValue,
MultipleSelectionModel newValue) {
if (oldValue != null) {
oldValue.getSelectedIndices().removeListener(weakSelectedListener);
}
if (newValue != null) {
newValue.getSelectedIndices().addListener(weakSelectedListener);
}
updateSelection();
}
};
/**
* Listens to the items on the ListView. Whenever the items are changed in such a way that
* it impacts the index of this ListCell, then we must update the item.
*/
private final ListChangeListener itemsListener = new ListChangeListener() {
@Override public void onChanged(ListChangeListener.Change extends T> c) {
updateItem();
}
};
/**
* Listens to the items property on the ListView. Whenever the entire list is changed,
* we have to unhook the weakItemsListener and update the item.
*/
private final ChangeListener> itemsPropertyListener = new ChangeListener>() {
@Override public void changed(ObservableValue extends ObservableList> observable,
ObservableList oldValue,
ObservableList newValue) {
if (oldValue != null) {
oldValue.removeListener(weakItemsListener);
}
if (newValue != null) {
newValue.addListener(weakItemsListener);
}
updateItem();
}
};
/**
* Listens to the focus model on the ListView. Whenever the focus model changes,
* the focused property on the ListCell is updated
*/
private final InvalidationListener focusedListener = new InvalidationListener() {
@Override public void invalidated(Observable value) {
updateFocus();
}
};
/**
* Listens to the focusModel property on the ListView. Whenever the entire model is changed,
* we have to unhook the weakFocusedListener and update the focus.
*/
private final ChangeListener> focusModelPropertyListener = new ChangeListener>() {
@Override public void changed(ObservableValue extends FocusModel> observable,
FocusModel oldValue,
FocusModel newValue) {
if (oldValue != null) {
oldValue.focusedIndexProperty().removeListener(weakFocusedListener);
}
if (newValue != null) {
newValue.focusedIndexProperty().addListener(weakFocusedListener);
}
updateFocus();
}
};
private final WeakInvalidationListener weakEditingListener = new WeakInvalidationListener(editingListener);
private final WeakListChangeListener weakSelectedListener = new WeakListChangeListener(selectedListener);
private final WeakChangeListener> weakSelectionModelPropertyListener = new WeakChangeListener>(selectionModelPropertyListener);
private final WeakListChangeListener weakItemsListener = new WeakListChangeListener(itemsListener);
private final WeakChangeListener> weakItemsPropertyListener = new WeakChangeListener>(itemsPropertyListener);
private final WeakInvalidationListener weakFocusedListener = new WeakInvalidationListener(focusedListener);
private final WeakChangeListener> weakFocusModelPropertyListener = new WeakChangeListener>(focusModelPropertyListener);
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
/**
* The ListView associated with this Cell.
*/
private ReadOnlyObjectWrapper> listView = new ReadOnlyObjectWrapper>(this, "listView") {
/**
* A weak reference to the ListView itself, such that whenever the ...
*/
private WeakReference> weakListViewRef = new WeakReference>(null);
@Override protected void invalidated() {
// Get the current and old list view references
final ListView currentListView = get();
final ListView oldListView = weakListViewRef.get();
// If the currentListView is the same as the oldListView, then
// there is nothing to be done.
if (currentListView == oldListView) return;
// If the old list view is not null, then we must unhook all its listeners
if (oldListView != null) {
// If the old selection model isn't null, unhook it
final MultipleSelectionModel sm = oldListView.getSelectionModel();
if (sm != null) {
sm.getSelectedIndices().removeListener(weakSelectedListener);
}
// If the old focus model isn't null, unhook it
final FocusModel fm = oldListView.getFocusModel();
if (fm != null) {
fm.focusedIndexProperty().removeListener(weakFocusedListener);
}
// If the old items isn't null, unhook the listener
final ObservableList items = oldListView.getItems();
if (items != null) {
items.removeListener(weakItemsListener);
}
// Remove the listeners of the properties on ListView
oldListView.editingIndexProperty().removeListener(weakEditingListener);
oldListView.itemsProperty().removeListener(weakItemsPropertyListener);
oldListView.focusModelProperty().removeListener(weakFocusModelPropertyListener);
oldListView.selectionModelProperty().removeListener(weakSelectionModelPropertyListener);
}
if (currentListView != null) {
final MultipleSelectionModel sm = currentListView.getSelectionModel();
if (sm != null) {
sm.getSelectedIndices().addListener(weakSelectedListener);
}
final FocusModel fm = currentListView.getFocusModel();
if (fm != null) {
fm.focusedIndexProperty().addListener(weakFocusedListener);
}
final ObservableList items = currentListView.getItems();
if (items != null) {
items.addListener(weakItemsListener);
}
currentListView.editingIndexProperty().addListener(weakEditingListener);
currentListView.itemsProperty().addListener(weakItemsPropertyListener);
currentListView.focusModelProperty().addListener(weakFocusModelPropertyListener);
currentListView.selectionModelProperty().addListener(weakSelectionModelPropertyListener);
weakListViewRef = new WeakReference>(currentListView);
}
updateItem();
updateSelection();
updateFocus();
requestLayout();
}
};
private void setListView(ListView value) { listView.set(value); }
public final ListView getListView() { return listView.get(); }
public final ReadOnlyObjectProperty> listViewProperty() { return listView.getReadOnlyProperty(); }
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
private int index = -1;
/** {@inheritDoc} */
@Override void indexChanged() {
final int oldIndex = index;
super.indexChanged();
index = getIndex();
if (isEditing() && index == oldIndex) {
// no-op
// Fix for RT-31165 - if we (needlessly) update the index whilst the
// cell is being edited it will no longer be in an editing state.
// This means that in certain (common) circumstances that it will
// appear that a cell is uneditable as, despite being clicked, it
// will not change to the editing state as a layout of VirtualFlow
// is immediately invoked, which forces all cells to be updated.
} else {
updateItem();
updateSelection();
updateFocus();
}
}
/** {@inheritDoc} */
@Override protected Skin> createDefaultSkin() {
return new ListCellSkin(this);
}
/***************************************************************************
* *
* Editing API *
* *
**************************************************************************/
/** {@inheritDoc} */
@Override public void startEdit() {
final ListView list = getListView();
if (!isEditable() || (list != null && ! list.isEditable())) {
return;
}
// it makes sense to get the cell into its editing state before firing
// the event to the ListView below, so that's what we're doing here
// by calling super.startEdit().
super.startEdit();
// Inform the ListView of the edit starting.
if (list != null) {
list.fireEvent(new ListView.EditEvent(list,
ListView.editStartEvent(),
null,
list.getEditingIndex()));
list.edit(getIndex());
list.requestFocus();
}
}
/** {@inheritDoc} */
@Override public void commitEdit(T newValue) {
if (! isEditing()) return;
ListView list = getListView();
if (list != null) {
// Inform the ListView of the edit being ready to be committed.
list.fireEvent(new ListView.EditEvent(list,
ListView.editCommitEvent(),
newValue,
list.getEditingIndex()));
}
// update the item within this cell, so that it represents the new value
updateItem(newValue, false);
// inform parent classes of the commit, so that they can switch us
// out of the editing state
super.commitEdit(newValue);
if (list != null) {
// reset the editing index on the ListView. This must come after the
// event is fired so that the developer on the other side can consult
// the ListView editingIndex property (if they choose to do that
// rather than just grab the int from the event).
list.edit(-1);
list.requestFocus();
}
}
/** {@inheritDoc} */
@Override public void cancelEdit() {
if (! isEditing()) return;
// Inform the ListView of the edit being cancelled.
ListView list = getListView();
super.cancelEdit();
if (list != null) {
int editingIndex = list.getEditingIndex();
// reset the editing index on the ListView
if (updateEditingIndex) list.edit(-1);
list.requestFocus();
list.fireEvent(new ListView.EditEvent(list,
ListView.editCancelEvent(),
null,
editingIndex));
}
}
/* *************************************************************************
* *
* Private implementation *
* *
**************************************************************************/
private void updateItem() {
ListView lv = getListView();
List items = lv == null ? null : lv.getItems();
int index = getIndex();
// Compute whether the index for this cell is for a real item
boolean valid = items != null && index >=0 && index < items.size();
// Cause the cell to update itself
if (valid) {
T oldValue = getItem();
T newValue = items.get(index);
updateItem(newValue, false);
} else {
updateItem(null, true);
}
}
/**
* Updates the ListView associated with this Cell.
*
* @expert This function is intended to be used by experts, primarily
* by those implementing new Skins. It is not common
* for developers or designers to access this function directly.
*/
public final void updateListView(ListView listView) {
setListView(listView);
}
private void updateSelection() {
if (isEmpty()) return;
int index = getIndex();
ListView listView = getListView();
if (index == -1 || listView == null) return;
SelectionModel sm = listView.getSelectionModel();
if (sm == null) return;
boolean isSelected = sm.isSelected(index);
if (isSelected() == isSelected) return;
updateSelected(isSelected);
}
private void updateFocus() {
int index = getIndex();
ListView listView = getListView();
if (index == -1 || listView == null) return;
FocusModel fm = listView.getFocusModel();
if (fm == null) return;
setFocused(fm.isFocused(index));
}
private void updateEditing() {
final int index = getIndex();
final ListView list = getListView();
final int editIndex = list == null ? -1 : list.getEditingIndex();
final boolean editing = isEditing();
// Check that the list is specified, and my index is not -1
if (index != -1 && list != null) {
// If my index is the index being edited and I'm not currently in
// the edit mode, then I need to enter the edit mode
if (index == editIndex && !editing) {
startEdit();
} else if (index != editIndex && editing) {
// If my index is not the one being edited then I need to cancel
// the edit. The tricky thing here is that as part of this call
// I cannot end up calling list.edit(-1) the way that the standard
// cancelEdit method would do. Yet, I need to call cancelEdit
// so that subclasses which override cancelEdit can execute. So,
// I have to use a kind of hacky flag workaround.
updateEditingIndex = false;
cancelEdit();
updateEditingIndex = true;
}
}
}
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
private static final String DEFAULT_STYLE_CLASS = "list-cell";
}