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;
}