modules/controls/src/main/java/javafx/scene/control/skin/TreeCellSkin.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization

*** 21,57 **** * 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 com.sun.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 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.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; import javafx.css.StyleableDoubleProperty; import javafx.css.StyleableProperty; import javafx.css.CssMetaData; ! import com.sun.javafx.css.converters.SizeConverter; ! import com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler; import com.sun.javafx.scene.control.behavior.TreeCellBehavior; import javafx.css.Styleable; ! public class TreeCellSkin<T> extends CellSkinBase<TreeCell<T>, TreeCellBehavior<T>> { /* * 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 --- 21,71 ---- * 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.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.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 {@link TreeCell} control. ! * ! * @see TreeCell ! * @since 9 ! */ ! public class TreeCellSkin<T> extends CellSkinBase<TreeCell<T>> { ! ! /*************************************************************************** ! * * ! * 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
*** 66,205 **** * 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>(); - /** - * 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; ! } private boolean disclosureNodeDirty = true; private TreeItem<?> treeItem; private double fixedCellSize; private boolean fixedCellSizeEnabled; ! private MultiplePropertyChangeListenerHandler treeItemListener = new MultiplePropertyChangeListenerHandler(p -> { ! if ("EXPANDED".equals(p)) { ! updateDisclosureNodeRotation(true); ! } ! return null; ! }); public TreeCellSkin(TreeCell<T> control) { ! super(control, new TreeCellBehavior<T>(control)); this.fixedCellSize = control.getTreeView().getFixedCellSize(); this.fixedCellSizeEnabled = fixedCellSize > 0; updateTreeItem(); updateDisclosureNodeRotation(false); ! registerChangeListener(control.treeItemProperty(), "TREE_ITEM"); ! registerChangeListener(control.textProperty(), "TEXT"); ! registerChangeListener(control.getTreeView().fixedCellSizeProperty(), "FIXED_CELL_SIZE"); ! } ! ! @Override protected void handleControlPropertyChanged(String p) { ! super.handleControlPropertyChanged(p); ! if ("TREE_ITEM".equals(p)) { updateTreeItem(); disclosureNodeDirty = true; getSkinnable().requestLayout(); ! } else if ("TEXT".equals(p)) { ! getSkinnable().requestLayout(); ! } else if ("FIXED_CELL_SIZE".equals(p)) { this.fixedCellSize = getSkinnable().getTreeView().getFixedCellSize(); this.fixedCellSizeEnabled = fixedCellSize > 0; } } ! private void updateDisclosureNodeRotation(boolean animate) { ! // no-op, this is now handled in CSS (although we no longer animate) ! // if (treeItem == null || treeItem.isLeaf()) return; ! // ! // Node disclosureNode = getSkinnable().getDisclosureNode(); ! // if (disclosureNode == null) return; ! // ! // final boolean isExpanded = treeItem.isExpanded(); ! // int fromAngle = isExpanded ? 0 : 90; ! // int toAngle = isExpanded ? 90 : 0; ! // ! // if (animate) { ! // RotateTransition rt = new RotateTransition(Duration.millis(200), disclosureNode); ! // rt.setFromAngle(fromAngle); ! // rt.setToAngle(toAngle); ! // rt.play(); ! // } else { ! // disclosureNode.setRotate(toAngle); ! // } } ! private void updateTreeItem() { ! if (treeItem != null) { ! treeItemListener.unregisterChangeListener(treeItem.expandedProperty()); } ! treeItem = getSkinnable().getTreeItem(); ! if (treeItem != null) { ! treeItemListener.registerChangeListener(treeItem.expandedProperty(), "EXPANDED"); } ! ! updateDisclosureNodeRotation(false); } - 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(); } } @Override protected void updateChildren() { super.updateChildren(); updateDisclosureNode(); } @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; --- 80,204 ---- * 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 boolean disclosureNodeDirty = true; private TreeItem<?> treeItem; + private final BehaviorBase<TreeCell<T>> behavior; private double fixedCellSize; private boolean fixedCellSizeEnabled; ! private InvalidationListener expandedListener = o -> updateDisclosureNodeRotation(true); + + + /*************************************************************************** + * * + * Constructors * + * * + **************************************************************************/ + + /** + * 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()); this.fixedCellSize = control.getTreeView().getFixedCellSize(); this.fixedCellSizeEnabled = fixedCellSize > 0; updateTreeItem(); updateDisclosureNodeRotation(false); ! registerChangeListener(control.treeItemProperty(), e -> { updateTreeItem(); disclosureNodeDirty = true; getSkinnable().requestLayout(); ! }); ! registerChangeListener(control.textProperty(), e -> getSkinnable().requestLayout()); ! registerChangeListener(control.getTreeView().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 + */ + 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} */ ! @Override public void dispose() { ! super.dispose(); ! if (behavior != null) { ! behavior.dispose(); } } + /** {@inheritDoc} */ @Override protected void updateChildren() { super.updateChildren(); updateDisclosureNode(); } + /** {@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;
*** 261,280 **** --- 260,281 ---- } layoutLabelInArea(x, y, w, h); } + /** {@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} */ @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { if (fixedCellSizeEnabled) { return fixedCellSize; }
*** 287,304 **** --- 288,307 ---- // RT-30212: TreeCell does not honor minSize of cells. // snapSize for RT-36460 return snapSize(Math.max(cell.getMinHeight(), prefHeight)); } + /** {@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} */ @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();
*** 322,331 **** --- 325,402 ---- pw += Math.max(defaultDisclosureWidth, disclosureNodePrefWidth); return pw; } + + + /*************************************************************************** + * * + * Private implementation * + * * + **************************************************************************/ + + private void updateDisclosureNodeRotation(boolean animate) { + // no-op, this is now handled in CSS (although we no longer animate) + // if (treeItem == null || treeItem.isLeaf()) return; + // + // Node disclosureNode = getSkinnable().getDisclosureNode(); + // if (disclosureNode == null) return; + // + // final boolean isExpanded = treeItem.isExpanded(); + // int fromAngle = isExpanded ? 0 : 90; + // int toAngle = isExpanded ? 90 : 0; + // + // if (animate) { + // RotateTransition rt = new RotateTransition(Duration.millis(200), disclosureNode); + // rt.setFromAngle(fromAngle); + // rt.setToAngle(toAngle); + // rt.play(); + // } else { + // disclosureNode.setRotate(toAngle); + // } + } + + private void updateTreeItem() { + if (treeItem != null) { + treeItem.expandedProperty().removeListener(expandedListener); + } + treeItem = getSkinnable().getTreeItem(); + if (treeItem != null) { + treeItem.expandedProperty().addListener(expandedListener); + } + + updateDisclosureNodeRotation(false); + } + + 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 * * * **************************************************************************/
*** 356,366 **** STYLEABLES = Collections.unmodifiableList(styleables); } } /** ! * @return The CssMetaData associated with this class, which may include the * CssMetaData of its super classes. */ public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { return StyleableProperties.STYLEABLES; } --- 427,437 ---- STYLEABLES = Collections.unmodifiableList(styleables); } } /** ! * Returns the CssMetaData associated with this class, which may include the * CssMetaData of its super classes. */ public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { return StyleableProperties.STYLEABLES; }