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,37 +21,51 @@
  * 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;
+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 com.sun.javafx.css.converters.SizeConverter;
-import com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler;
+import javafx.css.converter.SizeConverter;
 import com.sun.javafx.scene.control.behavior.TreeCellBehavior;
 
 import javafx.css.Styleable;
 
-public class TreeCellSkin<T> extends CellSkinBase<TreeCell<T>, TreeCellBehavior<T>> {
+/**
+ * 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,140 +80,125 @@
      * 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 fields                                                          *
+     *                                                                         *
+     **************************************************************************/
     
     private boolean disclosureNodeDirty = true;
     private TreeItem<?> treeItem;
+    private final BehaviorBase<TreeCell<T>> behavior;
 
     private double fixedCellSize;
     private boolean fixedCellSizeEnabled;
     
-    private MultiplePropertyChangeListenerHandler treeItemListener = new MultiplePropertyChangeListenerHandler(p -> {
-        if ("EXPANDED".equals(p)) {
-            updateDisclosureNodeRotation(true);
-        }
-        return null;
-    });
+    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, new TreeCellBehavior<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(), "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)) {
+        registerChangeListener(control.treeItemProperty(), e -> {
             updateTreeItem();
             disclosureNodeDirty = true;
             getSkinnable().requestLayout();
-        } else if ("TEXT".equals(p)) {
-            getSkinnable().requestLayout();
-        } else if ("FIXED_CELL_SIZE".equals(p)) {
+        });
+        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;
     }
     
-    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);
-//        }
+                @Override public String getName() {
+                    return "indent";
     }
     
-    private void updateTreeItem() {
-        if (treeItem != null) {
-            treeItemListener.unregisterChangeListener(treeItem.expandedProperty());
+                @Override public CssMetaData<TreeCell<?>,Number> getCssMetaData() {
+                    return StyleableProperties.INDENT;
         }
-        treeItem = getSkinnable().getTreeItem();
-        if (treeItem != null) {
-            treeItemListener.registerChangeListener(treeItem.expandedProperty(), "EXPANDED");
+            };
         }
-        
-        updateDisclosureNodeRotation(false);
+        return indent;
     }
     
-    private void updateDisclosureNode() {
-        if (getSkinnable().isEmpty()) return;
 
-        Node disclosureNode = getSkinnable().getDisclosureNode();
-        if (disclosureNode == null) return;
         
-        boolean disclosureVisible = treeItem != null && ! treeItem.isLeaf();
-        disclosureNode.setVisible(disclosureVisible);
+    /***************************************************************************
+     *                                                                         *
+     * Public API                                                              *
+     *                                                                         *
+     **************************************************************************/
             
-        if (! disclosureVisible) {
-            getChildren().remove(disclosureNode);
-        } else if (disclosureNode.getParent() == null) {
-            getChildren().add(disclosureNode);
-            disclosureNode.toFront();
-        } else {
-            disclosureNode.toBack();
-        }
+    /** {@inheritDoc} */
+    @Override public void dispose() {
+        super.dispose();
         
-        // RT-26625: [TreeView, TreeTableView] can lose arrows while scrolling
-        // RT-28668: Ensemble tree arrow disappears
-        if (disclosureNode.getScene() != null) {
-            disclosureNode.applyCss();
+        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,20 +260,22 @@
         }
 
         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,18 +288,20 @@
         // 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,10 +325,78 @@
         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,11 +427,11 @@
             STYLEABLES = Collections.unmodifiableList(styleables);
         }
     }
     
     /**
-     * @return The CssMetaData associated with this class, which may include the
+     * 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;
     }