changeset 1495:987198f6a34a

Automated merge with ssh://jfxsrc.us.oracle.com//javafx/3.0/scrum/controls/jfx/rt
author jgiles
date Tue, 17 Jul 2012 13:28:48 +1200
parents f28a395961d2 b4b4efb44663
children fd3003d95715
files
diffstat 3 files changed, 588 insertions(+), 521 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/SliderSkin.java	Wed Jul 11 12:24:20 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/SliderSkin.java	Tue Jul 17 13:28:48 2012 +1200
@@ -38,7 +38,7 @@
 /**
  * Region/css based skin for Slider
 */
-public class SliderSkin extends SkinBase<Slider, SliderBehavior> {
+public class SliderSkin extends javafx.scene.control.SkinBase<Slider, SliderBehavior> {
 
     /** Track if slider is vertical/horizontal and cause re layout */
 //    private boolean horizontal;
--- a/javafx-ui-controls/src/javafx/scene/control/Control.java	Wed Jul 11 12:24:20 2012 -0700
+++ b/javafx-ui-controls/src/javafx/scene/control/Control.java	Tue Jul 17 13:28:48 2012 +1200
@@ -34,12 +34,8 @@
 import java.util.Collections;
 import java.util.List;
 
-import javafx.beans.property.DoubleProperty;
-import javafx.beans.property.DoublePropertyBase;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ObjectPropertyBase;
-import javafx.beans.property.ReadOnlyDoubleProperty;
-import javafx.beans.property.ReadOnlyDoubleWrapper;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.StringProperty;
 import javafx.beans.value.WritableValue;
@@ -47,9 +43,16 @@
 import javafx.geometry.Bounds;
 import javafx.scene.Node;
 import javafx.scene.Parent;
+import javafx.scene.layout.Region;
 
 import com.sun.javafx.logging.PlatformLogger;
 import com.sun.javafx.scene.control.Logging;
+import javafx.collections.ObservableList;
+import javafx.event.EventHandler;
+import javafx.event.EventType;
+import javafx.scene.input.ContextMenuEvent;
+import javafx.scene.input.MouseEvent;
+
 
 /**
  * Base class for all user interface controls. A "Control" is a node in the
@@ -68,23 +71,7 @@
  * controls that are containers {@link ScrollPane} and {@link ToolBar} do not.
  * Consult individual control documentation for details.
  */
-public abstract class Control extends Parent implements Skinnable {
-
-    /**
-     * Sentinel value which can be passed to a control's setMinWidth(), setMinHeight(),
-     * setMaxWidth() or setMaxHeight() methods to indicate that the preferred dimension
-     * should be used for that max and/or min constraint.
-     */
-    public static final double USE_PREF_SIZE = Double.NEGATIVE_INFINITY;
-
-    /**
-     * Sentinel value which can be passed to a control's setMinWidth(), setMinHeight(),
-     * setPrefWidth(), setPrefHeight(), setMaxWidth(), setMaxHeight() methods
-     * to reset the control's size constraint back to it's intrinsic size returned
-     * by computeMinWidth(), computeMinHeight(), computePrefWidth(), computePrefHeight(),
-     * computeMaxWidth(), or computeMaxHeight().
-     */
-    public static final double USE_COMPUTED_SIZE = -1;
+public abstract class Control extends Region implements Skinnable {
 
     static {
         // Ensures that the caspian.css file is set as the user agent style sheet
@@ -93,12 +80,80 @@
     }
     
     
+    
+    /***************************************************************************
+     *                                                                         *
+     * Event Handlers / Listeners                                              *
+     *                                                                         *
+     **************************************************************************/     
+    
+    /**
+     * Forwards mouse events received by a MouseListener to the behavior.
+     * Note that we use this pattern to remove some of the anonymous inner
+     * classes which we'd otherwise have to create. When lambda expressions
+     * are supported, we could do it that way instead (or use MethodHandles).
+     */
+    private final EventHandler<MouseEvent> mouseHandler =
+            new EventHandler<MouseEvent>() {
+        @Override public void handle(MouseEvent e) {
+            if (! ((getSkin() instanceof SkinBase))) return;
+            
+            SkinBase skin = (SkinBase) getSkin();
+            
+            final EventType<?> type = e.getEventType();
+
+            if (type == MouseEvent.MOUSE_ENTERED) skin.getBehavior().mouseEntered(e);
+            else if (type == MouseEvent.MOUSE_EXITED) skin.getBehavior().mouseExited(e);
+            else if (type == MouseEvent.MOUSE_PRESSED) skin.getBehavior().mousePressed(e);
+            else if (type == MouseEvent.MOUSE_RELEASED) skin.getBehavior().mouseReleased(e);
+            else if (type == MouseEvent.MOUSE_DRAGGED) skin.getBehavior().mouseDragged(e);
+            else { // no op
+                throw new AssertionError("Unsupported event type received");
+            }
+        }
+    };
+    
+    /**
+     * Handles context menu requests by popping up the menu.
+     * Note that we use this pattern to remove some of the anonymous inner
+     * classes which we'd otherwise have to create. When lambda expressions
+     * are supported, we could do it that way instead (or use MethodHandles).
+     */
+    private final EventHandler<ContextMenuEvent> contextMenuHandler = new EventHandler<ContextMenuEvent>() {
+        @Override public void handle(ContextMenuEvent event) {
+            // If a context menu was shown, consume the event to prevent multiple context menus
+            if (getContextMenu() != null) {
+                getContextMenu().show(Control.this, event.getScreenX(), event.getScreenY());
+                event.consume();
+            }
+        }
+    };
+    
+    /**
+     * Mouse handler used for consuming all mouse events (preventing them
+     * from bubbling up to parent)
+     */
+    private static final EventHandler<MouseEvent> mouseEventConsumer = new EventHandler<MouseEvent>() {
+        @Override public void handle(MouseEvent event) {
+            /*
+            ** we used to consume mouse wheel rotations here, 
+            ** be we've switched to ScrollEvents, and only consume those which we use.
+            ** See RT-13995 & RT-14480
+            */
+            event.consume();
+        }
+    };
+
+
+    
     /***************************************************************************
      *                                                                         *
      * Properties                                                              *
      *                                                                         *
      **************************************************************************/    
     
+    
+    
     // --- skin
     /**
      * Skin is responsible for rendering this {@code Control}. From the
@@ -149,10 +204,22 @@
 
             // Dispose of the old skin
             if (oldValue != null) oldValue.dispose();
+            
             // Get the new value, and save it off as the new oldValue
             final Skin<?> skin = oldValue = getValue();
+            
+            // check if the skin is a new-style skin that defers to the 
+            // Control for the children. If this is the case, we don't add
+            // the skin node into the children list of the Control
+            boolean isNewStyleSkin = skin instanceof SkinBase;
+            
             // Update the children list with the new skin node
-            updateChildren();
+            if (! isNewStyleSkin) {
+                final Node n = getSkinNode();
+                if (n != null) getChildren().setAll(n);
+                else getChildren().clear();
+            }
+            
             // DEBUG: Log that we've changed the skin
             final PlatformLogger logger = Logging.getControlsLogger();
             if (logger.isLoggable(PlatformLogger.FINEST)) {
@@ -241,331 +308,7 @@
     public final ContextMenu getContextMenu() { return contextMenu == null ? null : contextMenu.getValue(); }
 
     
-    // --- width
-    /**
-     * The width of this control.
-     */
-    public final ReadOnlyDoubleProperty widthProperty() { return width.getReadOnlyProperty(); }
-    protected final void setWidth(double value) { width.set(value); }
-    public final double getWidth() { return width.get(); }
-    private ReadOnlyDoubleWrapper width = new ReadOnlyDoubleWrapper() {
-        @Override protected void invalidated() {
-            impl_layoutBoundsChanged();
-            requestLayout();
-        }
 
-        @Override
-        public Object getBean() {
-            return Control.this;
-        }
-
-        @Override
-        public String getName() {
-            return "width";
-        }
-    };
-
-    
-    // --- height
-    /**
-     * The height of this control.
-     */
-    public final ReadOnlyDoubleProperty heightProperty() { return height.getReadOnlyProperty(); }
-    protected final void setHeight(double value) { height.set(value); }
-    public final double getHeight() { return height.get(); }
-    private ReadOnlyDoubleWrapper height = new ReadOnlyDoubleWrapper() {
-        @Override protected void invalidated() {
-            impl_layoutBoundsChanged();
-            requestLayout();
-        }
-
-        @Override
-        public Object getBean() {
-            return Control.this;
-        }
-
-        @Override
-        public String getName() {
-            return "height";
-        }
-    };
-
-    
-    // --- min width
-    /**
-     * Property for overriding the control's computed minimum width.
-     * This should only be set if the control's internally computed minimum width
-     * doesn't meet the application's layout needs.
-     * <p>
-     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
-     * <code>getMinWidth(forHeight)</code> will return the control's internally
-     * computed minimum width.
-     * <p>
-     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
-     * <code>getMinWidth(forHeight)</code> to return the control's preferred width,
-     * enabling applications to easily restrict the resizability of the control.
-     *
-     */
-    public final DoubleProperty minWidthProperty() {
-        if (minWidth == null) {
-            minWidth = new DoublePropertyBase(USE_COMPUTED_SIZE) {
-                @Override
-                public void invalidated() {
-                    if (getParent() != null) {
-                        getParent().requestLayout();
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Control.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "minWidth";
-                }
-            };
-        }
-        return minWidth;
-    }
-    private DoubleProperty minWidth;
-    public final void setMinWidth(double value) { minWidthProperty().set(value); }
-    public final double getMinWidth() { return minWidth == null ? USE_COMPUTED_SIZE : minWidth.get(); }
-
-
-    // --- min height
-    /**
-     * Property for overriding the control's computed minimum height.
-     * This should only be set if the control's internally computed minimum height
-     * doesn't meet the application's layout needs.
-     * <p>
-     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
-     * <code>getMinHeight(forWidth)</code> will return the control's internally
-     * computed minimum height.
-     * <p>
-     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
-     * <code>getMinHeight(forWidth)</code> to return the control's preferred height,
-     * enabling applications to easily restrict the resizability of the control.
-     *
-     */
-    public final DoubleProperty minHeightProperty() {
-        if (minHeight == null) {
-            minHeight = new DoublePropertyBase(USE_COMPUTED_SIZE) {
-                @Override
-                public void invalidated() {
-                    if (getParent() != null) {
-                        getParent().requestLayout();
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Control.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "minHeight";
-                }
-            };
-        }
-        return minHeight;
-    }
-    private DoubleProperty minHeight;
-    public final void setMinHeight(double value) { minHeightProperty().set(value); }
-    public final double getMinHeight() { return minHeight == null ? USE_COMPUTED_SIZE : minHeight.get(); }
-
-    /**
-     * Convenience method for overriding the control's computed minimum width and height.
-     * This should only be called if the control's internally computed minimum size
-     * doesn't meet the application's layout needs.
-     *
-     * @see #setMinWidth(double)
-     * @see #setMinHeight(double)
-     * @param minWidth  the override value for minimum width
-     * @param minHeight the override value for minimum height
-     */
-    public void setMinSize(double minWidth, double minHeight) {
-        setMinWidth(minWidth);
-        setMinHeight(minHeight);
-    }
-
-    /**
-     * Property for overriding the control's computed preferred width.
-     * This should only be set if the control's internally computed preferred width
-     * doesn't meet the application's layout needs.
-     * <p>
-     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
-     * <code>getPrefWidth(forHeight)</code> will return the control's internally
-     * computed preferred width.
-     *
-     */
-    public final DoubleProperty prefWidthProperty() {
-        if (prefWidth == null) {
-            prefWidth = new DoublePropertyBase(USE_COMPUTED_SIZE) {
-                @Override
-                public void invalidated() {
-                    if (getParent() != null) {
-                        getParent().requestLayout();
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Control.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "prefWidth";
-                }
-            };
-        }
-        return prefWidth;
-    }
-    private DoubleProperty prefWidth;
-    public final void setPrefWidth(double value) { prefWidthProperty().set(value); }
-    public final double getPrefWidth() { return prefWidth == null ? USE_COMPUTED_SIZE : prefWidth.get(); }
-
-
-    /**
-     * Property for overriding the control's computed preferred height.
-     * This should only be set if the control's internally computed preferred height
-     * doesn't meet the application's layout needs.
-     * <p>
-     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
-     * <code>getPrefHeight(forWidth)</code> will return the control's internally
-     * computed preferred width.
-     *
-     */
-    public final DoubleProperty prefHeightProperty() {
-        if (prefHeight == null) {
-            prefHeight = new DoublePropertyBase(USE_COMPUTED_SIZE) {
-                @Override
-                public void invalidated() {
-                    if (getParent() != null) {
-                        getParent().requestLayout();
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Control.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "prefHeight";
-                }
-            };
-        }
-        return prefHeight;
-    }
-    private DoubleProperty prefHeight;
-    public final void setPrefHeight(double value) { prefHeightProperty().set(value); }
-    public final double getPrefHeight() { return prefHeight == null ? USE_COMPUTED_SIZE : prefHeight.get(); }
-
-
-    /**
-     * Convenience method for overriding the control's computed preferred width and height.
-     * This should only be called if the control's internally computed preferred size
-     * doesn't meet the application's layout needs.
-     *
-     * @see #setPrefWidth(double)
-     * @see #setPrefHeight(double)
-     * @param prefWidth the override value for preferred width
-     * @param prefHeight the override value for preferred height
-     */
-    public void setPrefSize(double prefWidth, double prefHeight) {
-        setPrefWidth(prefWidth);
-        setPrefHeight(prefHeight);
-    }
-
-    /**
-     * Property for overriding the control's computed maximum width.
-     * This should only be set if the control's internally computed maximum width
-     * doesn't meet the application's layout needs.
-     * <p>
-     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
-     * <code>getMaxWidth(forHeight)</code> will return the control's internally
-     * computed maximum width.
-     * <p>
-     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
-     * <code>getMaxWidth(forHeight)</code> to return the control's preferred width,
-     * enabling applications to easily restrict the resizability of the control.
-     *
-     */
-    public final DoubleProperty maxWidthProperty() {
-        if (maxWidth == null) {
-            maxWidth = new DoublePropertyBase(USE_COMPUTED_SIZE) {
-                @Override
-                public void invalidated() {
-                    if (getParent() != null) {
-                        getParent().requestLayout();
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Control.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "maxWidth";
-                }
-            };
-        }
-        return maxWidth;
-    }
-    private DoubleProperty maxWidth;
-    public final void setMaxWidth(double value) { maxWidthProperty().set(value); }
-    public final double getMaxWidth() { return maxWidth == null ? USE_COMPUTED_SIZE : maxWidth.get(); }
-
-    /**
-     * Property for overriding the control's computed maximum height.
-     * This should only be set if the control's internally computed maximum height
-     * doesn't meet the application's layout needs.
-     * <p>
-     * Defaults to the <code>USE_COMPUTED_SIZE</code> flag, which means that
-     * <code>getMaxHeight(forWidth)</code> will return the control's internally
-     * computed maximum height.
-     * <p>
-     * Setting this value to the <code>USE_PREF_SIZE</code> flag will cause
-     * <code>getMaxHeight(forWidth)</code> to return the control's preferred height,
-     * enabling applications to easily restrict the resizability of the control.
-     *
-     */
-    public final DoubleProperty maxHeightProperty() {
-        if (maxHeight == null) {
-            maxHeight = new DoublePropertyBase(USE_COMPUTED_SIZE) {
-                @Override
-                public void invalidated() {
-                    if (getParent() != null) {
-                        getParent().requestLayout();
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Control.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "maxHeight";
-                }
-            };
-        }
-        return maxHeight;
-    }
-    private DoubleProperty maxHeight;
-    public final void setMaxHeight(double value) { maxHeightProperty().set(value); }
-    public final double getMaxHeight() { return maxHeight == null ? USE_COMPUTED_SIZE : maxHeight.get(); }
-
-    
-    
     /***************************************************************************
      *                                                                         *
      * Constructors                                                            *
@@ -581,7 +324,22 @@
         // override. Initializing focusTraversable by calling set on the 
         // StyleableProperty ensures that css will be able to override the value.        
         final StyleableProperty prop = StyleableProperty.getStyleableProperty(focusTraversableProperty());
-        prop.set(this, Boolean.TRUE);            
+        prop.set(this, Boolean.TRUE);  
+        
+        // We will auto-add listeners for wiring up Region mouse events to
+        // be sent to the behavior
+        this.addEventHandler(MouseEvent.MOUSE_ENTERED, mouseHandler);
+        this.addEventHandler(MouseEvent.MOUSE_EXITED, mouseHandler);
+        this.addEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
+        this.addEventHandler(MouseEvent.MOUSE_RELEASED, mouseHandler);
+        this.addEventHandler(MouseEvent.MOUSE_DRAGGED, mouseHandler);
+        
+        // we add a listener for menu request events to show the context menu
+        // that may be set on the Control
+        this.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler);
+
+        // Default behavior for controls is to consume all mouse events
+        consumeMouseEvents(true);
     }
     
     
@@ -590,8 +348,19 @@
      *                                                                         *
      * Public API                                                              *
      *                                                                         *
-     **************************************************************************/    
-
+     **************************************************************************/
+    
+    /**
+     * Determines whether all mouse events should be automatically consumed.
+     */
+    protected final void consumeMouseEvents(boolean value) {
+        if (value) {
+            addEventHandler(MouseEvent.ANY, mouseEventConsumer);
+        } else {
+            removeEventHandler(MouseEvent.ANY, mouseEventConsumer);
+        }
+    }
+    
     /**
      * Returns <code>true</code> since all Controls are resizable.
      * @return whether this node can be resized by its parent during layout
@@ -600,144 +369,6 @@
         return true;
     }
 
-    /**
-     * Invoked by the control's parent during layout to set the control's
-     * width and height.  <b>Applications should not invoke this method directly</b>.
-     * If an application needs to directly set the size of the control, it should
-     * override its size constraints by calling <code>setMinSize()</code>,
-     *  <code>setPrefSize()</code>, or <code>setMaxSize()</code> and it's parent
-     * will honor those overrides during layout.
-     *
-     * @param width the target layout bounds width
-     * @param height the target layout bounds height
-     */
-    @Override public void resize(double width, double height) {
-        setWidth(width);
-        setHeight(height);
-        final PlatformLogger logger = com.sun.javafx.Logging.getLayoutLogger();
-        if (logger.isLoggable(PlatformLogger.FINER)) {
-            logger.finer(this.toString()+" resized to "+width+" x "+height);
-        }
-    }
-
-    /**
-     * Called during layout to determine the minimum width for this node.
-     * Returns the value from <code>computeMinWidth(forHeight)</code> unless
-     * the application overrode the minimum width by setting the minWidth property.
-     *
-     * @see #setMinWidth(double)
-     * @return the minimum width that this node should be resized to during layout
-     */
-    @Override public final double minWidth(double height) {
-        double override = getMinWidth();
-        if (override == USE_COMPUTED_SIZE) {
-            return super.minWidth(height);
-        } else if (override == USE_PREF_SIZE) {
-            return prefWidth(height);
-        }
-        return override;
-    }
-
-    /**
-     * Called during layout to determine the minimum height for this node.
-     * Returns the value from <code>computeMinHeight(forWidth)</code> unless
-     * the application overrode the minimum height by setting the minHeight property.
-     *
-     * @see #setMinHeight(double)
-     * @return the minimum height that this node should be resized to during layout
-     */
-    @Override public final double minHeight(double width) {
-        double override = getMinHeight();
-        if (override == USE_COMPUTED_SIZE) {
-            return super.minHeight(width);
-        } else if (override == USE_PREF_SIZE) {
-            return prefHeight(width);
-        }
-        return override;
-    }
-
-    /**
-     * Called during layout to determine the preferred width for this node.
-     * Returns the value from <code>computePrefWidth(forHeight)</code> unless
-     * the application overrode the preferred width by setting the prefWidth property.
-     *
-     * @see #setPrefWidth(double)
-     * @return the preferred width that this node should be resized to during layout
-     */
-    @Override public final double prefWidth(double height) {
-        double override = getPrefWidth();
-        if (override == USE_COMPUTED_SIZE) {
-            return super.prefWidth(height);
-        } 
-        return override;
-    }
-
-    /**
-     * Called during layout to determine the preferred height for this node.
-     * Returns the value from <code>computePrefHeight(forWidth)</code> unless
-     * the application overrode the preferred height by setting the prefHeight property.
-     *
-     * @see #setPrefHeight(double)
-     * @return the preferred height that this node should be resized to during layout
-     */
-    @Override public final double prefHeight(double width) {
-        double override = getPrefHeight();
-        if (override == USE_COMPUTED_SIZE) {
-            return super.prefHeight(width);
-        } 
-        return override;
-    }
-    /**
-     * Called during layout to determine the maximum width for this node.
-     * Returns the value from <code>computeMaxWidth(forHeight)</code> unless
-     * the application overrode the maximum width by setting the maxWidth property.
-     *
-     * @see #setMaxWidth(double)
-     * @return the maximum width that this node should be resized to during layout
-     */
-    @Override public final double maxWidth(double height) {
-        double override = getMaxWidth();
-        if (override == USE_COMPUTED_SIZE) {
-            return computeMaxWidth(height);
-        } else if (override == USE_PREF_SIZE) {
-            return prefWidth(height);
-        }
-        return override;
-    }
-
-    /**
-     * Called during layout to determine the maximum height for this node.
-     * Returns the value from <code>computeMaxHeight(forWidth)</code> unless
-     * the application overrode the maximum height by setting the maxHeight property.
-     *
-     * @see #setMaxHeight(double)
-     * @return the maximum height that this node should be resized to during layout
-     */
-    @Override public final double maxHeight(double width) {
-        double override = getMaxHeight();
-        if (override == USE_COMPUTED_SIZE) {
-            return computeMaxHeight(width);
-        } else if (override == USE_PREF_SIZE) {
-            return prefHeight(width);
-        }
-        return override;
-    }
-    
-    /**
-     * Convenience method for overriding the control's computed maximum width and height.
-     * This should only be called if the control's internally computed maximum size
-     * doesn't meet the application's layout needs.
-     *
-     * @see #setMaxWidth(double)
-     * @see #setMaxHeight(double)
-     * @param maxWidth  the override value for maximum width
-     * @param maxHeight the override value for maximum height
-     */
-    public void setMaxSize(double maxWidth, double maxHeight) {
-        setMaxWidth(maxWidth);
-        setMaxHeight(maxHeight);
-    }
-
     // Implementation of the Resizable interface.
     // Because only the skin can know the min, pref, and max sizes, these
     // functions are implemented to delegate to skin. If there is no skin then
@@ -754,7 +385,12 @@
      * @return A double representing the minimum width of this control.
      */
     @Override protected double computeMinWidth(double height) {
-        return getSkinNode() == null ? 0 : getSkinNode().minWidth(height);
+        if (getSkin() instanceof SkinBase) {    
+            SkinBase skin = (SkinBase) getSkin();
+            return skin == null ? 0 : skin.computeMinWidth(height);
+        } else {
+            return getSkinNode() == null ? 0 : getSkinNode().minWidth(height);
+        }
     }
     /**
      * Computes the minimum allowable height of the Control, based on the provided
@@ -767,7 +403,12 @@
      * @return A double representing the minimum height of this control.
      */
     @Override protected double computeMinHeight(double width) {
-        return getSkinNode() == null ? 0 : getSkinNode().minHeight(width);
+        if (getSkin() instanceof SkinBase) {    
+            SkinBase skin = (SkinBase) getSkin();
+            return skin == null ? 0 : skin.computeMinHeight(width);
+        } else {
+            return getSkinNode() == null ? 0 : getSkinNode().minHeight(width);
+        }
     }
     /**
      * Computes the maximum allowable width of the Control, based on the provided
@@ -779,8 +420,13 @@
      *      the maximum width.
      * @return A double representing the maximum width of this control.
      */
-    protected double computeMaxWidth(double height) {
-        return getSkinNode() == null ? 0 : getSkinNode().maxWidth(height);
+    @Override protected double computeMaxWidth(double height) {
+        if (getSkin() instanceof SkinBase) {    
+            SkinBase skin = (SkinBase) getSkin();
+            return skin == null ? 0 : skin.computeMaxWidth(height);
+        } else {
+            return getSkinNode() == null ? 0 : getSkinNode().maxWidth(height);
+        }
     }
     /**
      * Computes the maximum allowable height of the Control, based on the provided
@@ -792,19 +438,42 @@
      *      the maximum height.
      * @return A double representing the maximum height of this control.
      */
-    protected double computeMaxHeight(double width) {
-        return getSkinNode() == null ? 0 : getSkinNode().maxHeight(width);
+    @Override protected double computeMaxHeight(double width) {
+        if (getSkin() instanceof SkinBase) {    
+            SkinBase skin = (SkinBase) getSkin();
+            return skin == null ? 0 : skin.computeMaxHeight(width);
+        } else {
+            return getSkinNode() == null ? 0 : getSkinNode().maxHeight(width);
+        }
     }
     /** {@inheritDoc} */
     @Override protected double computePrefWidth(double height) {
-        return getSkinNode() == null? 0 : getSkinNode().prefWidth(height);
+        if (getSkin() instanceof SkinBase) {    
+            SkinBase skin = (SkinBase) getSkin();
+            return skin == null ? 0 : skin.computePrefWidth(height);
+        } else {
+            return getSkinNode() == null? 0 : getSkinNode().prefWidth(height);
+        }
     }
     /** {@inheritDoc} */
     @Override protected double computePrefHeight(double width) {
-        return getSkinNode() == null? 0 : getSkinNode().prefHeight(width);
+        if (getSkin() instanceof SkinBase) {    
+            SkinBase skin = (SkinBase) getSkin();
+            return skin == null ? 0 : skin.computePrefHeight(width);
+        } else {
+            return getSkinNode() == null? 0 : getSkinNode().prefHeight(width);
+        }
     }
+    
     /** {@inheritDoc} */
-    @Override public double getBaselineOffset() { return getSkinNode() == null? 0 : getSkinNode().getBaselineOffset(); }
+    @Override public double getBaselineOffset() { 
+        if (getSkin() instanceof SkinBase) {    
+            SkinBase skin = (SkinBase) getSkin();
+            return skin == null ? 0 : skin.getBaselineOffset();
+        } else {
+            return getSkinNode() == null? 0 : getSkinNode().getBaselineOffset(); 
+        }
+    }
 
     /***************************************************************************
      * Implementation of layout bounds for the Control. We want to preserve    *
@@ -813,16 +482,16 @@
      * recompute it on demand.                                                 *
      **************************************************************************/
 
-    /**
-     * @treatAsPrivate
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
-     */
-    @Deprecated
-    @Override protected void impl_notifyLayoutBoundsChanged() {
-        // override Node's default behavior of having a geometric bounds change
-        // trigger a change in layoutBounds. For Resizable nodes, layoutBounds
-        // is unrelated to geometric bounds.
-    }
+//    /**
+//     * @treatAsPrivate
+//     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+//     */
+//    @Deprecated
+//    @Override protected void impl_notifyLayoutBoundsChanged() {
+//        // override Node's default behavior of having a geometric bounds change
+//        // trigger a change in layoutBounds. For Resizable nodes, layoutBounds
+//        // is unrelated to geometric bounds.
+//    }
 
     /**
      * @treatAsPrivate implementation detail
@@ -835,9 +504,14 @@
 
     /** {@inheritDoc} */
     @Override protected void layoutChildren() {
-        Node n = getSkinNode();
-        if (n != null) {
-            n.resizeRelocate(0, 0, getWidth(), getHeight());
+        Skin skin = getSkin();
+        if (skin instanceof SkinBase) {
+            ((SkinBase)skin).layoutChildren();
+        } else {
+            Node n = getSkinNode();
+            if (n != null) {
+                n.resizeRelocate(0, 0, getWidth(), getHeight());
+            }
         }
     }
 
@@ -866,6 +540,10 @@
     }
     
     
+    // package private for SkinBase
+    ObservableList<Node> getControlChildren() {
+        return getChildren();
+    }
     
     /***************************************************************************
      *                                                                         *
@@ -886,17 +564,6 @@
     }
 
     /**
-     * Called from several places whenever the children of the Control
-     * may need to be updated (principally, when the tool tip changes,
-     * the skin changes, or the skin.node changes).
-     */
-    private void updateChildren() {
-        final Node n = getSkinNode();
-        if (n != null) getChildren().setAll(n);
-        else getChildren().clear();
-    }
-
-    /**
      * Keeps a reference to the name of the class currently acting as the skin.
      */
     private String currentSkinClassName = null;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/javafx/scene/control/SkinBase.java	Tue Jul 17 13:28:48 2012 +1200
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2012, 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;
+
+import com.sun.javafx.scene.control.behavior.BehaviorBase;
+import java.util.HashMap;
+import java.util.Map;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.beans.value.WeakChangeListener;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ListChangeListener.Change;
+import javafx.collections.ObservableList;
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.layout.Region;
+
+/**
+ *
+ */
+public abstract class SkinBase<C extends Control, B extends BehaviorBase<C>> implements Skin<C> {
+    
+    /***************************************************************************
+     *                                                                         *
+     * Private fields                                                          *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * The {@code Control} that is referencing this Skin. There is a
+     * one-to-one relationship between a {@code Skin} and a {@code Control}.
+     * When a {@code Skin} is set on a {@code Control}, this variable is
+     * automatically updated.
+     */
+    private C control;
+    
+    /**
+     * The {@link BehaviorBase} that encapsulates the interaction with the
+     * {@link Control} from this {@code Skin}. The {@code Skin} does not modify
+     * the {@code Control} directly, but rather redirects events into the
+     * {@code BehaviorBase} which then handles the events by modifying internal state
+     * and public state in the {@code Control}. Generally, specific
+     * {@code Skin} implementations will require specific {@code BehaviorBase}
+     * implementations. For example, a ButtonSkin might require a ButtonBehavior.
+     */
+    private B behavior;
+    
+    /**
+     * An ObservableList of the Nodes that make up the skin. This list differs
+     * from the Controls children list, in that it will be a subset (not including
+     * children such as the tooltip). When this children list changes, we 
+     * manually update the children of the Control itself, so that the Nodes
+     * are part of the scenegraph (as the Skin is not a member of the scenegraph).
+     */
+    private ObservableList<Node> children;
+    
+    /**
+     * This is part of the workaround introduced during delomboking. We probably will
+     * want to adjust the way listeners are added rather than continuing to use this
+     * map (although it doesn't really do much harm).
+     */
+    private Map<ObservableValue,String> propertyReferenceMap =
+            new HashMap<ObservableValue,String>();
+    
+    /***************************************************************************
+     *                                                                         *
+     * Event Handlers / Listeners                                              *
+     *                                                                         *
+     **************************************************************************/     
+    
+    private final ChangeListener controlPropertyChangedListener = new ChangeListener() {
+        @Override public void changed(ObservableValue property, Object oldValue, Object newValue) {
+            handleControlPropertyChanged(propertyReferenceMap.get(property));
+        }
+    };
+    
+    
+    
+    /***************************************************************************
+     *                                                                         *
+     * Constructor                                                             *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * Constructor for all SkinBase instances.
+     * 
+     * @param control The control for which this Skin should attach to.
+     * @param behavior The behavior for which this Skin should defer to.
+     */
+    protected SkinBase(final C control, final B behavior) {
+        if (control == null || behavior == null) {
+            throw new IllegalArgumentException("Cannot pass null for control or behavior");
+        }
+
+        // Update the control and behavior
+        this.control = control;
+        this.behavior = behavior;
+    }
+    
+    
+
+    /***************************************************************************
+     *                                                                         *
+     * Public API (from Skin)                                                  *
+     *                                                                         *
+     **************************************************************************/    
+
+    /** {@inheritDoc} */
+    @Override public C getSkinnable() {
+        return control;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Node getNode() {
+        return control; 
+    }
+    
+    /** {@inheritDoc} */
+    public B getBehavior() {
+        return behavior;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void dispose() { 
+        // unhook listeners
+        for (ObservableValue value : propertyReferenceMap.keySet()) {
+            value.removeListener(controlPropertyChangedListener);
+        }
+
+//        this.removeEventHandler(MouseEvent.MOUSE_ENTERED, mouseHandler);
+//        this.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseHandler);
+//        this.removeEventHandler(MouseEvent.MOUSE_PRESSED, mouseHandler);
+//        this.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseHandler);
+//        this.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseHandler);
+//        
+//        control.removeEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, contextMenuHandler);
+
+        this.control = null;
+        this.behavior = null;
+    }
+    
+    
+    
+    /***************************************************************************
+     *                                                                         *
+     * Public API                                                              *
+     *                                                                         *
+     **************************************************************************/     
+    
+    /**
+     * Returns the children of the skin.
+     */
+    protected ObservableList<Node> getChildren() {
+        if (children == null) {
+            instantiateChildren();
+        }
+        return children;
+    }
+    
+    /**
+     * Called during the layout pass of the scenegraph. 
+     */
+    protected abstract void layoutChildren();
+    
+    /**
+     * Subclasses can invoke this method to register that we want to listen to
+     * property change events for the given property.
+     *
+     * @param property
+     * @param reference
+     */
+    protected final void registerChangeListener(ObservableValue property, String reference) {
+        if (!propertyReferenceMap.containsKey(property)) {
+            propertyReferenceMap.put(property, reference);
+            property.addListener(new WeakChangeListener(controlPropertyChangedListener));
+        }
+    }
+    
+    /**
+     * Skin subclasses will override this method to handle changes in corresponding
+     * control's properties.
+     */
+    protected void handleControlPropertyChanged(String propertyReference) {
+        // no-op
+    }
+    
+    
+    
+    /***************************************************************************
+     *                                                                         *
+     * Public Layout-related API                                               *
+     *                                                                         *
+     **************************************************************************/
+    
+    /**
+     * Computes the minimum allowable width of the Skin, based on the provided
+     * height.
+     * 
+     * @param height The height of the Skin, in case this value might dictate
+     *      the minimum width.
+     * @return A double representing the minimum width of this Skin.
+     */
+    protected double computeMinWidth(double height) {
+        return computePrefWidth(height);
+    }
+    /**
+     * Computes the minimum allowable height of the Skin, based on the provided
+     * width.
+     * 
+     * @param width The width of the Skin, in case this value might dictate
+     *      the minimum height.
+     * @return A double representing the minimum height of this Skin.
+     */
+    protected double computeMinHeight(double width) {
+        return computePrefHeight(width);
+    }
+    /**
+     * Computes the maximum allowable width of the Skin, based on the provided
+     * height.
+     * 
+     * @param height The height of the Skin, in case this value might dictate
+     *      the maximum width.
+     * @return A double representing the maximum width of this Skin.
+     */
+    protected double computeMaxWidth(double height) {
+        return computePrefWidth(height);
+    }
+    
+    /**
+     * Computes the maximum allowable height of the Skin, based on the provided
+     * width.
+     * 
+     * @param width The width of the Skin, in case this value might dictate
+     *      the maximum height.
+     * @return A double representing the maximum height of this Skin.
+     */
+    protected double computeMaxHeight(double width) {
+        return computePrefHeight(width);
+    }
+    
+    // PENDING_DOC_REVIEW
+    /**
+     * Calculates the preferred width of this {@code SkinBase}. The default
+     * implementation calculates this width as the width of the area occupied
+     * by its managed children when they are positioned at their
+     * current positions at their preferred widths.
+     *
+     * @param height the height that should be used if preferred width depends
+     *      on it
+     * @return the calculated preferred width
+     */
+    protected double computePrefWidth(double height) {
+        double minX = 0;
+        double maxX = 0;
+        for (int i = 0; i < getChildren().size(); i++) {
+            Node node = getChildren().get(i);
+            if (node.isManaged()) {
+                final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
+                minX = Math.min(minX, x);
+                maxX = Math.max(maxX, x + node.prefWidth(-1));
+            }
+        }
+        return maxX - minX;
+    }
+    
+    // PENDING_DOC_REVIEW
+    /**
+     * Calculates the preferred height of this {@code SkinBase}. The default
+     * implementation calculates this height as the height of the area occupied
+     * by its managed children when they are positioned at their current
+     * positions at their preferred heights.
+     *
+     * @param width the width that should be used if preferred height depends
+     *      on it
+     * @return the calculated preferred height
+     */
+    protected double computePrefHeight(double width) {
+        double minY = 0;
+        double maxY = 0;
+        for (int i = 0; i < getChildren().size(); i++) {
+            Node node = getChildren().get(i);
+            if (node.isManaged()) {
+                final double y = node.getLayoutBounds().getMinY() + node.getLayoutY();
+                minY = Math.min(minY, y);
+                maxY = Math.max(maxY, y + node.prefHeight(-1));
+            }
+        }
+        return maxY - minY;
+    }
+    
+    /**
+     * Calculates the baseline offset based on the first managed child. If there
+     * is no such child, returns {@link Node#getBaselineOffset()}.
+     *
+     * @return baseline offset
+     */
+    public double getBaselineOffset() {
+        int size = children.size();
+        for (int i = 0; i < size; ++i) {
+            Node child = children.get(i);
+            if (child.isManaged()) {
+                return child.getLayoutBounds().getMinY() + child.getLayoutY() + child.getBaselineOffset();
+            }
+        }
+        return getSkinnable().getLayoutBounds().getHeight();
+    }
+    
+    
+    /***************************************************************************
+     *                                                                         *
+     * Convenience API                                                         *
+     *                                                                         *
+     **************************************************************************/      
+    
+    /**
+     * Calls requestLayout() on the Skinnable
+     */
+    protected void requestLayout() {
+        getSkinnable().requestLayout();
+    }
+    
+    /**
+     * Calls getInsets() on the Skinnable
+     */
+    protected Insets getInsets() {
+        return getSkinnable().getInsets();
+    }
+    
+    /**
+     * Calls getWidth() on the Skinnable
+     */
+    protected double getWidth() {
+        return getSkinnable().getWidth();
+    }
+    
+    /**
+     * Calls getHeight() on the Skinnable
+     */
+    protected double getHeight() {
+        return getSkinnable().getHeight();
+    }    
+    
+    
+    /***************************************************************************
+     *                                                                         *
+     * Private Implementation                                                  *
+     *                                                                         *
+     **************************************************************************/     
+    
+    private void instantiateChildren() {
+        this.children = FXCollections.observableArrayList();
+        
+        // listen to the children in this skin so that we may add it to the 
+        // scenegraph (which is the children of Control)
+        this.children.addListener(new ListChangeListener<Node>() {
+            @Override public void onChanged(Change<? extends Node> change) {
+                handleSkinChildrenChanges(change);
+            }
+        });
+    }
+
+    // TODO this should be improved to retain the children z-order
+    private void handleSkinChildrenChanges(Change<? extends Node> change) {
+        while (change.next()) {
+            if (change.wasRemoved()) {
+                // remove nodes from Control children
+                getSkinnable().getControlChildren().removeAll(change.getRemoved());
+            }
+
+            if (change.wasAdded()) {
+                // add new nodes to Control children
+                getSkinnable().getControlChildren().addAll(change.getAddedSubList());
+            }
+        }
+    }
+}