/* * Copyright (c) 2011, 2014, 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 javafx.beans.InvalidationListener; import javafx.beans.WeakInvalidationListener; import javafx.collections.ListChangeListener; import javafx.scene.AccessibleAttribute; import javafx.scene.AccessibleRole; import javafx.scene.control.TableView.TableViewFocusModel; import javafx.collections.WeakListChangeListener; import java.lang.ref.WeakReference; import java.util.List; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.scene.control.skin.TableRowSkin; /** *

TableRow is an {@link javafx.scene.control.IndexedCell IndexedCell}, but * rarely needs to be used by developers creating TableView instances. The only * time TableRow is likely to be encountered at all by a developer is if they * wish to create a custom {@link TableView#rowFactoryProperty() rowFactory} * that replaces an entire row of a TableView.

* *

More often than not, it is actually easier for a developer to customize * individual cells in a row, rather than the whole row itself. To do this, * you can specify a custom {@link TableColumn#cellFactoryProperty() cellFactory} * on each TableColumn instance.

* * @see TableView * @see TableColumn * @see TableCell * @see IndexedCell * @see Cell * @param The type of the item contained within the Cell. * @since JavaFX 2.0 */ public class TableRow extends IndexedCell { /*************************************************************************** * * * Constructors * * * **************************************************************************/ /** * Constructs a default TableRow instance with a style class of 'table-row-cell' */ public TableRow() { getStyleClass().addAll(DEFAULT_STYLE_CLASS); setAccessibleRole(AccessibleRole.TABLE_ROW); } /*************************************************************************** * * * Instance Variables * * * **************************************************************************/ /*************************************************************************** * * * Callbacks and Events * * * **************************************************************************/ /* * This is the list observer we use to keep an eye on the SelectedCells * list in the table view. Because it is possible that the table can * be mutated, we create this observer here, and add/remove it from the * storeTableView method. */ private ListChangeListener selectedListener = c -> { updateSelection(); }; // Same as selectedListener, but this time for focus events private final InvalidationListener focusedListener = valueModel -> { updateFocus(); }; // same as above, but for editing events private final InvalidationListener editingListener = valueModel -> { updateEditing(); }; private final WeakListChangeListener weakSelectedListener = new WeakListChangeListener<>(selectedListener); private final WeakInvalidationListener weakFocusedListener = new WeakInvalidationListener(focusedListener); private final WeakInvalidationListener weakEditingListener = new WeakInvalidationListener(editingListener); /*************************************************************************** * * * Properties * * * **************************************************************************/ // --- TableView private ReadOnlyObjectWrapper> tableView; private void setTableView(TableView value) { tableViewPropertyImpl().set(value); } public final TableView getTableView() { return tableView == null ? null : tableView.get(); } /** * The TableView associated with this Cell. */ public final ReadOnlyObjectProperty> tableViewProperty() { return tableViewPropertyImpl().getReadOnlyProperty(); } private ReadOnlyObjectWrapper> tableViewPropertyImpl() { if (tableView == null) { tableView = new ReadOnlyObjectWrapper>() { private WeakReference> weakTableViewRef; @Override protected void invalidated() { TableView.TableViewSelectionModel sm; TableViewFocusModel fm; if (weakTableViewRef != null) { TableView oldTableView = weakTableViewRef.get(); if (oldTableView != null) { sm = oldTableView.getSelectionModel(); if (sm != null) { sm.getSelectedCells().removeListener(weakSelectedListener); } fm = oldTableView.getFocusModel(); if (fm != null) { fm.focusedCellProperty().removeListener(weakFocusedListener); } oldTableView.editingCellProperty().removeListener(weakEditingListener); } weakTableViewRef = null; } TableView tableView = getTableView(); if (tableView != null) { sm = tableView.getSelectionModel(); if (sm != null) { sm.getSelectedCells().addListener(weakSelectedListener); } fm = tableView.getFocusModel(); if (fm != null) { fm.focusedCellProperty().addListener(weakFocusedListener); } tableView.editingCellProperty().addListener(weakEditingListener); weakTableViewRef = new WeakReference>(get()); } } @Override public Object getBean() { return TableRow.this; } @Override public String getName() { return "tableView"; } }; } return tableView; } /*************************************************************************** * * * Public API * * * **************************************************************************/ /** {@inheritDoc} */ @Override protected Skin createDefaultSkin() { return new TableRowSkin<>(this); } /*************************************************************************** * * * Private implementation * * * **************************************************************************/ /** {@inheritDoc} */ @Override void indexChanged(int oldIndex, int newIndex) { super.indexChanged(oldIndex, newIndex); updateItem(oldIndex); updateSelection(); updateFocus(); } private boolean isFirstRun = true; private void updateItem(int oldIndex) { TableView tv = getTableView(); if (tv == null || tv.getItems() == null) return; final List items = tv.getItems(); final int itemCount = items == null ? -1 : items.size(); // Compute whether the index for this cell is for a real item final int newIndex = getIndex(); boolean valid = newIndex >= 0 && newIndex < itemCount; final T oldValue = getItem(); final boolean isEmpty = isEmpty(); // Cause the cell to update itself outer: if (valid) { final T newValue = items.get(newIndex); // RT-35864 - if the index didn't change, then avoid calling updateItem // unless the item has changed. if (oldIndex == newIndex) { if (!isItemChanged(oldValue, newValue)) { // RT-37054: we break out of the if/else code here and // proceed with the code following this, so that we may // still update references, listeners, etc as required. break outer; } } updateItem(newValue, false); } else { // RT-30484 We need to allow a first run to be special-cased to allow // for the updateItem method to be called at least once to allow for // the correct visual state to be set up. In particular, in RT-30484 // refer to Ensemble8PopUpTree.png - in this case the arrows are being // shown as the new cells are instantiated with the arrows in the // children list, and are only hidden in updateItem. if ((!isEmpty && oldValue != null) || isFirstRun) { updateItem(null, true); isFirstRun = false; } } } private void updateSelection() { /* * This cell should be selected if the selection mode of the table * is row-based, and if the row that this cell represents is selected. * * If the selection mode is not row-based, then the listener in the * TableCell class might pick up the need to set a single cell to be * selected. */ if (getIndex() == -1) return; TableView table = getTableView(); boolean isSelected = table != null && table.getSelectionModel() != null && ! table.getSelectionModel().isCellSelectionEnabled() && table.getSelectionModel().isSelected(getIndex()); updateSelected(isSelected); } private void updateFocus() { if (getIndex() == -1) return; TableView table = getTableView(); if (table == null) return; TableView.TableViewSelectionModel sm = table.getSelectionModel(); TableView.TableViewFocusModel fm = table.getFocusModel(); if (sm == null || fm == null) return; boolean isFocused = ! sm.isCellSelectionEnabled() && fm.isFocused(getIndex()); setFocused(isFocused); } private void updateEditing() { if (getIndex() == -1) return; TableView table = getTableView(); if (table == null) return; TableView.TableViewSelectionModel sm = table.getSelectionModel(); if (sm == null || sm.isCellSelectionEnabled()) return; TablePosition editCell = table.getEditingCell(); if (editCell != null && editCell.getTableColumn() != null) { return; } boolean rowMatch = editCell == null ? false : editCell.getRow() == getIndex(); if (! isEditing() && rowMatch) { startEdit(); } else if (isEditing() && ! rowMatch) { cancelEdit(); } } /*************************************************************************** * * * Expert API * * * **************************************************************************/ /** * Updates the TableView associated with this TableCell. This is typically * only done once when the TableCell is first added to the TableView. * * @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 updateTableView(TableView tv) { setTableView(tv); } /*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/ private static final String DEFAULT_STYLE_CLASS = "table-row-cell"; /*************************************************************************** * * * Accessibility handling * * * **************************************************************************/ @Override public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) { switch (attribute) { case INDEX: return getIndex(); default: return super.queryAccessibleAttribute(attribute, parameters); } } }