/*
* Copyright (c) 2010, 2017, 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.skin;
import com.sun.javafx.scene.control.behavior.BehaviorBase;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.scene.control.Control;
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTablePosition;
import javafx.scene.control.TreeTableRow;
import javafx.scene.control.TreeTableView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.beans.property.DoubleProperty;
import javafx.scene.AccessibleAttribute;
import javafx.scene.Node;
import javafx.css.StyleableDoubleProperty;
import javafx.css.CssMetaData;
import javafx.css.converter.SizeConverter;
import com.sun.javafx.scene.control.behavior.TreeTableRowBehavior;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.WritableValue;
import javafx.collections.ObservableList;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.scene.control.TreeView;
Default skin implementation for the TreeTableRow
control. See Also: Since: 9
/**
* Default skin implementation for the {@link TreeTableRow} control.
*
* @see TreeTableRow
* @since 9
*/
public class TreeTableRowSkin<T> extends TableRowSkinBase<TreeItem<T>, TreeTableRow<T>, TreeTableCell<T,?>> {
*
Private Fields *
*
/***************************************************************************
* *
* Private Fields *
* *
**************************************************************************/
// maps into the TreeTableViewSkin items property via
// TreeTableViewSkin.treeItemToListMap
private TreeItem<?> treeItem;
private boolean disclosureNodeDirty = true;
private Node graphic;
private final BehaviorBase<TreeTableRow<T>> behavior;
private TreeTableViewSkin treeTableViewSkin;
private boolean childrenDirty = false;
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
Creates a new TreeTableRowSkin instance, installing the necessary child nodes into the Control children
list, as well as the necessary input mappings for handling key, mouse, etc events. Params: - control – The control that this skin should be installed onto.
/**
* Creates a new TreeTableRowSkin instance, installing the necessary child
* nodes into the Control {@link Control#getChildren() children} list, as
* well as the necessary input mappings for handling key, mouse, etc events.
*
* @param control The control that this skin should be installed onto.
*/
public TreeTableRowSkin(TreeTableRow<T> control) {
super(control);
// install default input map for the TreeTableRow control
behavior = new TreeTableRowBehavior<>(control);
// control.setInputMap(behavior.getInputMap());
updateTreeItem();
updateTableViewSkin();
registerChangeListener(control.treeTableViewProperty(), e -> updateTableViewSkin());
registerChangeListener(control.indexProperty(), e -> updateCells = true);
registerChangeListener(control.treeItemProperty(), e -> {
updateTreeItem();
// There used to be an isDirty = true statement here, but this was
// determined to be unnecessary and led to performance issues such as
// those detailed in JDK-8143266
});
setupTreeTableViewListeners();
}
private void setupTreeTableViewListeners() {
TreeTableView<T> treeTableView = getSkinnable().getTreeTableView();
if (treeTableView == null) {
getSkinnable().treeTableViewProperty().addListener(new InvalidationListener() {
@Override public void invalidated(Observable observable) {
getSkinnable().treeTableViewProperty().removeListener(this);
setupTreeTableViewListeners();
}
});
} else {
registerChangeListener(treeTableView.treeColumnProperty(), e -> {
// Fix for RT-27782: Need to set isDirty to true, rather than the
// cheaper updateCells, as otherwise the text indentation will not
// be recalculated in TreeTableCellSkin.leftLabelPadding()
isDirty = true;
getSkinnable().requestLayout();
});
DoubleProperty fixedCellSizeProperty = getTreeTableView().fixedCellSizeProperty();
if (fixedCellSizeProperty != null) {
registerChangeListener(fixedCellSizeProperty, e -> {
fixedCellSize = fixedCellSizeProperty.get();
fixedCellSizeEnabled = fixedCellSize > 0;
});
fixedCellSize = fixedCellSizeProperty.get();
fixedCellSizeEnabled = fixedCellSize > 0;
// JDK-8144500:
// When in fixed cell size mode, we must listen to the width of the virtual flow, so
// that when it changes, we can appropriately add / remove cells that may or may not
// be required (because we remove all cells that are not visible).
registerChangeListener(getVirtualFlow().widthProperty(), e -> treeTableView.requestLayout());
}
}
}
*
Listeners *
*
/***************************************************************************
* *
* Listeners *
* *
**************************************************************************/
private final InvalidationListener graphicListener = o -> {
disclosureNodeDirty = true;
getSkinnable().requestLayout();
};
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
The amount of space to multiply by the treeItem.level to get the left
margin for this tree cell. This is settable from CSS
/**
* The amount of space to multiply by the treeItem.level to get the left
* margin for this tree cell. This is settable from CSS
*/
private DoubleProperty indent = null;
public final void setIndent(double value) { indentProperty().set(value); }
public final double getIndent() { return indent == null ? 10.0 : indent.get(); }
public final DoubleProperty indentProperty() {
if (indent == null) {
indent = new StyleableDoubleProperty(10.0) {
@Override public Object getBean() {
return TreeTableRowSkin.this;
}
@Override public String getName() {
return "indent";
}
@Override public CssMetaData<TreeTableRow<?>,Number> getCssMetaData() {
return TreeTableRowSkin.StyleableProperties.INDENT;
}
};
}
return indent;
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
{@inheritDoc} /** {@inheritDoc} */
@Override public void dispose() {
super.dispose();
if (behavior != null) {
behavior.dispose();
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected void updateChildren() {
super.updateChildren();
updateDisclosureNodeAndGraphic();
if (childrenDirty) {
childrenDirty = false;
if (cells.isEmpty()) {
getChildren().clear();
} else {
// TODO we can optimise this by only showing cells that are
// visible based on the table width and the amount of horizontal
// scrolling.
getChildren().addAll(cells);
}
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected void layoutChildren(double x, double y, double w, double h) {
if (disclosureNodeDirty) {
updateDisclosureNodeAndGraphic();
disclosureNodeDirty = false;
}
Node disclosureNode = getDisclosureNode();
if (disclosureNode != null && disclosureNode.getScene() == null) {
updateDisclosureNodeAndGraphic();
}
super.layoutChildren(x, y, w, h);
}
/***************************************************************************
* *
* Private Implementation *
* *
**************************************************************************/
{@inheritDoc} /** {@inheritDoc} */
@Override protected TreeTableCell<T, ?> createCell(TableColumnBase tcb) {
TreeTableColumn tableColumn = (TreeTableColumn<T,?>) tcb;
TreeTableCell cell = (TreeTableCell) tableColumn.getCellFactory().call(tableColumn);
cell.updateTreeTableColumn(tableColumn);
cell.updateTreeTableView(tableColumn.getTreeTableView());
return cell;
}
{@inheritDoc} /** {@inheritDoc} */
@Override void updateCells(boolean resetChildren) {
super.updateCells(resetChildren);
if (resetChildren) {
childrenDirty = true;
updateChildren();
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override boolean isIndentationRequired() {
return true;
}
{@inheritDoc} /** {@inheritDoc} */
@Override TableColumnBase getTreeColumn() {
return getTreeTableView().getTreeColumn();
}
{@inheritDoc} /** {@inheritDoc} */
@Override int getIndentationLevel(TreeTableRow<T> control) {
return getTreeTableView().getTreeItemLevel(control.getTreeItem());
}
{@inheritDoc} /** {@inheritDoc} */
@Override double getIndentationPerLevel() {
return getIndent();
}
{@inheritDoc} /** {@inheritDoc} */
@Override Node getDisclosureNode() {
return getSkinnable().getDisclosureNode();
}
@Override boolean isDisclosureNodeVisible() {
return getDisclosureNode() != null && treeItem != null && ! treeItem.isLeaf();
}
@Override boolean isShowRoot() {
return getTreeTableView().isShowRoot();
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected ObservableList<TreeTableColumn<T, ?>> getVisibleLeafColumns() {
return getTreeTableView() == null ? FXCollections.emptyObservableList() : getTreeTableView().getVisibleLeafColumns();
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected void updateCell(TreeTableCell<T, ?> cell, TreeTableRow<T> row) {
cell.updateTreeTableRow(row);
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected TreeTableColumn<T, ?> getTableColumn(TreeTableCell cell) {
return cell.getTableColumn();
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected ObjectProperty<Node> graphicProperty() {
TreeTableRow<T> treeTableRow = getSkinnable();
if (treeTableRow == null) return null;
if (treeItem == null) return null;
return treeItem.graphicProperty();
}
private void updateTreeItem() {
if (treeItem != null) {
treeItem.graphicProperty().removeListener(graphicListener);
}
treeItem = getSkinnable().getTreeItem();
if (treeItem != null) {
treeItem.graphicProperty().addListener(graphicListener);
}
}
private TreeTableView<T> getTreeTableView() {
return getSkinnable().getTreeTableView();
}
private void updateDisclosureNodeAndGraphic() {
if (getSkinnable().isEmpty()) {
getChildren().remove(graphic);
return;
}
// check for graphic missing
ObjectProperty<Node> graphicProperty = graphicProperty();
Node newGraphic = graphicProperty == null ? null : graphicProperty.get();
if (newGraphic != null) {
// RT-30466: remove the old graphic
if (newGraphic != graphic) {
getChildren().remove(graphic);
}
if (! getChildren().contains(newGraphic)) {
getChildren().add(newGraphic);
graphic = newGraphic;
}
}
// check disclosure node
Node disclosureNode = getSkinnable().getDisclosureNode();
if (disclosureNode != null) {
boolean disclosureVisible = treeItem != null && ! treeItem.isLeaf();
disclosureNode.setVisible(disclosureVisible);
if (! disclosureVisible) {
getChildren().remove(disclosureNode);
} else if (disclosureNode.getParent() == null) {
getChildren().add(disclosureNode);
disclosureNode.toFront();
} else {
disclosureNode.toBack();
}
// RT-26625: [TreeView, TreeTableView] can lose arrows while scrolling
// RT-28668: Ensemble tree arrow disappears
if (disclosureNode.getScene() != null) {
disclosureNode.applyCss();
}
}
}
private void updateTableViewSkin() {
TreeTableView<T> tableView = getSkinnable().getTreeTableView();
if (tableView != null && tableView.getSkin() instanceof TreeTableViewSkin) {
treeTableViewSkin = (TreeTableViewSkin)tableView.getSkin();
}
}
*
Stylesheet Handling *
*
/***************************************************************************
* *
* Stylesheet Handling *
* *
**************************************************************************/
private static class StyleableProperties {
private static final CssMetaData<TreeTableRow<?>,Number> INDENT =
new CssMetaData<TreeTableRow<?>,Number>("-fx-indent",
SizeConverter.getInstance(), 10.0) {
@Override public boolean isSettable(TreeTableRow<?> n) {
DoubleProperty p = ((TreeTableRowSkin<?>) n.getSkin()).indentProperty();
return p == null || !p.isBound();
}
@Override public StyleableProperty<Number> getStyleableProperty(TreeTableRow<?> n) {
final TreeTableRowSkin<?> skin = (TreeTableRowSkin<?>) n.getSkin();
return (StyleableProperty<Number>)(WritableValue<Number>)skin.indentProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> styleables =
new ArrayList<CssMetaData<? extends Styleable, ?>>(CellSkinBase.getClassCssMetaData());
styleables.add(INDENT);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
Returns the CssMetaData associated with this class, which may include the
CssMetaData of its superclasses.
Returns: the CssMetaData associated with this class, which may include the
CssMetaData of its superclasses
/**
* Returns the CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses.
* @return the CssMetaData associated with this class, which may include the
* CssMetaData of its superclasses
*/
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
@Override public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return getClassCssMetaData();
}
{@inheritDoc} /** {@inheritDoc} */
@Override protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
final TreeTableView<T> treeTableView = getSkinnable().getTreeTableView();
switch (attribute) {
case SELECTED_ITEMS: {
// FIXME this could be optimised to iterate over cellsMap only
// (selectedCells could be big, cellsMap is much smaller)
List<Node> selection = new ArrayList<>();
int index = getSkinnable().getIndex();
for (TreeTablePosition<T,?> pos : treeTableView.getSelectionModel().getSelectedCells()) {
if (pos.getRow() == index) {
TreeTableColumn<T,?> column = pos.getTableColumn();
if (column == null) {
/* This is the row-based case */
column = treeTableView.getVisibleLeafColumn(0);
}
TreeTableCell<T,?> cell = cellsMap.get(column).get();
if (cell != null) selection.add(cell);
}
return FXCollections.observableArrayList(selection);
}
}
case CELL_AT_ROW_COLUMN: {
int colIndex = (Integer)parameters[1];
TreeTableColumn<T,?> column = treeTableView.getVisibleLeafColumn(colIndex);
if (cellsMap.containsKey(column)) {
return cellsMap.get(column).get();
}
return null;
}
case FOCUS_ITEM: {
TreeTableView.TreeTableViewFocusModel<T> fm = treeTableView.getFocusModel();
TreeTablePosition<T,?> focusedCell = fm.getFocusedCell();
TreeTableColumn<T,?> column = focusedCell.getTableColumn();
if (column == null) {
/* This is the row-based case */
column = treeTableView.getVisibleLeafColumn(0);
}
if (cellsMap.containsKey(column)) {
return cellsMap.get(column).get();
}
return null;
}
default: return super.queryAccessibleAttribute(attribute, parameters);
}
}
}