/*
 * 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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import com.sun.javafx.scene.control.behavior.BehaviorBase;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.WritableValue;
import javafx.geometry.HPos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.ListView;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.css.StyleableDoubleProperty;
import javafx.css.StyleableProperty;
import javafx.css.CssMetaData;

import javafx.css.converter.SizeConverter;
import com.sun.javafx.scene.control.behavior.TreeCellBehavior;

import javafx.css.Styleable;

Default skin implementation for the TreeCell control.
See Also:
Since:9
/** * Default skin implementation for the {@link TreeCell} control. * * @see TreeCell * @since 9 */
public class TreeCellSkin<T> extends CellSkinBase<TreeCell<T>> {
* Static fields * *
/*************************************************************************** * * * Static fields * * * **************************************************************************/
/* * This is rather hacky - but it is a quick workaround to resolve the * issue that we don't know maximum width of a disclosure node for a given * TreeView. If we don't know the maximum width, we have no way to ensure * consistent indentation for a given TreeView. * * To work around this, we create a single WeakHashMap to store a max * disclosureNode width per TreeView. We use WeakHashMap to help prevent * any memory leaks. * * RT-19656 identifies a related issue, which is that we may not provide * indentation to any TreeItems because we have not yet encountered a cell * which has a disclosureNode. Once we scroll and encounter one, indentation * happens in a displeasing way. */ private static final Map<TreeView<?>, Double> maxDisclosureWidthMap = new WeakHashMap<TreeView<?>, Double>();
* Private fields * *
/*************************************************************************** * * * Private fields * * * **************************************************************************/
private boolean disclosureNodeDirty = true; private TreeItem<?> treeItem; private final BehaviorBase<TreeCell<T>> behavior; private double fixedCellSize; private boolean fixedCellSizeEnabled; /*************************************************************************** * * * Constructors * * * **************************************************************************/
Creates a new TreeCellSkin 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 TreeCellSkin 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 TreeCellSkin(TreeCell<T> control) { super(control); // install default input map for the TreeCell control behavior = new TreeCellBehavior<>(control); // control.setInputMap(behavior.getInputMap()); updateTreeItem(); registerChangeListener(control.treeItemProperty(), e -> { updateTreeItem(); disclosureNodeDirty = true; getSkinnable().requestLayout(); }); registerChangeListener(control.textProperty(), e -> getSkinnable().requestLayout()); setupTreeViewListeners(); } private void setupTreeViewListeners() { TreeView<T> treeView = getSkinnable().getTreeView(); if (treeView == null) { getSkinnable().treeViewProperty().addListener(new InvalidationListener() { @Override public void invalidated(Observable observable) { getSkinnable().treeViewProperty().removeListener(this); setupTreeViewListeners(); } }); } else { this.fixedCellSize = treeView.getFixedCellSize(); this.fixedCellSizeEnabled = fixedCellSize > 0; registerChangeListener(treeView.fixedCellSizeProperty(), e -> { this.fixedCellSize = getSkinnable().getTreeView().getFixedCellSize(); this.fixedCellSizeEnabled = fixedCellSize > 0; }); } } /*************************************************************************** * * * 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 TreeCellSkin.this; } @Override public String getName() { return "indent"; } @Override public CssMetaData<TreeCell<?>,Number> getCssMetaData() { return 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(); updateDisclosureNode(); }
{@inheritDoc}
/** {@inheritDoc} */
@Override protected void layoutChildren(double x, final double y, double w, final double h) { // RT-25876: can not null-check here as this prevents empty rows from // being cleaned out. // if (treeItem == null) return; TreeView<T> tree = getSkinnable().getTreeView(); if (tree == null) return; if (disclosureNodeDirty) { updateDisclosureNode(); disclosureNodeDirty = false; } Node disclosureNode = getSkinnable().getDisclosureNode(); int level = tree.getTreeItemLevel(treeItem); if (! tree.isShowRoot()) level--; double leftMargin = getIndent() * level; x += leftMargin; // position the disclosure node so that it is at the proper indent boolean disclosureVisible = disclosureNode != null && treeItem != null && ! treeItem.isLeaf(); final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(tree) ? maxDisclosureWidthMap.get(tree) : 18; // RT-19656: default width of default disclosure node double disclosureWidth = defaultDisclosureWidth; if (disclosureVisible) { if (disclosureNode == null || disclosureNode.getScene() == null) { updateChildren(); } if (disclosureNode != null) { disclosureWidth = disclosureNode.prefWidth(h); if (disclosureWidth > defaultDisclosureWidth) { maxDisclosureWidthMap.put(tree, disclosureWidth); } double ph = disclosureNode.prefHeight(disclosureWidth); disclosureNode.resize(disclosureWidth, ph); positionInArea(disclosureNode, x, y, disclosureWidth, ph, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER); } } // determine starting point of the graphic or cell node, and the // remaining width available to them final int padding = treeItem != null && treeItem.getGraphic() == null ? 0 : 3; x += disclosureWidth + padding; w -= (leftMargin + disclosureWidth + padding); // Rather ugly fix for RT-38519, where graphics are disappearing in // certain circumstances Node graphic = getSkinnable().getGraphic(); if (graphic != null && !getChildren().contains(graphic)) { getChildren().add(graphic); } layoutLabelInArea(x, y, w, h); }
{@inheritDoc}
/** {@inheritDoc} */
@Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { if (fixedCellSizeEnabled) { return fixedCellSize; } double pref = super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset); Node d = getSkinnable().getDisclosureNode(); return (d == null) ? pref : Math.max(d.minHeight(-1), pref); }
{@inheritDoc}
/** {@inheritDoc} */
@Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { if (fixedCellSizeEnabled) { return fixedCellSize; } final TreeCell<T> cell = getSkinnable(); final double pref = super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset); final Node d = cell.getDisclosureNode(); final double prefHeight = (d == null) ? pref : Math.max(d.prefHeight(-1), pref); // RT-30212: TreeCell does not honor minSize of cells. // snapSize for RT-36460 return snapSizeY(Math.max(cell.getMinHeight(), prefHeight)); }
{@inheritDoc}
/** {@inheritDoc} */
@Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { if (fixedCellSizeEnabled) { return fixedCellSize; } return super.computeMaxHeight(width, topInset, rightInset, bottomInset, leftInset); }
{@inheritDoc}
/** {@inheritDoc} */
@Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { double labelWidth = super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset); double pw = snappedLeftInset() + snappedRightInset(); TreeView<T> tree = getSkinnable().getTreeView(); if (tree == null) return pw; if (treeItem == null) return pw; pw = labelWidth; // determine the amount of indentation int level = tree.getTreeItemLevel(treeItem); if (! tree.isShowRoot()) level--; pw += getIndent() * level; // include the disclosure node width Node disclosureNode = getSkinnable().getDisclosureNode(); double disclosureNodePrefWidth = disclosureNode == null ? 0 : disclosureNode.prefWidth(-1); final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(tree) ? maxDisclosureWidthMap.get(tree) : 0; pw += Math.max(defaultDisclosureWidth, disclosureNodePrefWidth); return pw; }
* Private implementation * *
/*************************************************************************** * * * Private implementation * * * **************************************************************************/
private void updateTreeItem() { treeItem = getSkinnable().getTreeItem(); } private void updateDisclosureNode() { if (getSkinnable().isEmpty()) return; Node disclosureNode = getSkinnable().getDisclosureNode(); if (disclosureNode == null) return; 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(); } }
* Stylesheet Handling * *
/*************************************************************************** * * * Stylesheet Handling * * * **************************************************************************/
private static class StyleableProperties { private static final CssMetaData<TreeCell<?>,Number> INDENT = new CssMetaData<TreeCell<?>,Number>("-fx-indent", SizeConverter.getInstance(), 10.0) { @Override public boolean isSettable(TreeCell<?> n) { DoubleProperty p = ((TreeCellSkin<?>) n.getSkin()).indentProperty(); return p == null || !p.isBound(); } @Override public StyleableProperty<Number> getStyleableProperty(TreeCell<?> n) { final TreeCellSkin<?> skin = (TreeCellSkin<?>) 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(); } }