changeset 4761:6c4ef975d190

Automated merge with ssh://jfxsrc.us.oracle.com//javafx/8.0/MASTER/jfx/rt
author David Grieve<david.grieve@oracle.com>
date Thu, 22 Aug 2013 09:49:13 -0400
parents 61b8d7420fc9 eb24b745d98f
children 9659769d6d54
files
diffstat 22 files changed, 1633 insertions(+), 1260 deletions(-) [+]
line wrap: on
line diff
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/ListViewBehavior.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/ListViewBehavior.java	Thu Aug 22 09:49:13 2013 -0400
@@ -709,7 +709,9 @@
         getControl().getSelectionModel().select(focusedIndex);
 
         // edit this row also
-        getControl().edit(focusedIndex);
+        if (focusedIndex >= 0) {
+            getControl().edit(focusedIndex);
+        }
     }
     
     private void toggleFocusOwnerSelection() {
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/TableViewBehaviorBase.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/behavior/TableViewBehaviorBase.java	Thu Aug 22 09:49:13 2013 -0400
@@ -835,7 +835,9 @@
         sm.select(cell.getRow(), cell.getTableColumn());
 
         // edit this row also
-        editCell(cell.getRow(), cell.getTableColumn());
+        if (cell.getRow() >= 0) {
+            editCell(cell.getRow(), cell.getTableColumn());
+        }
     }
     
     protected void selectAllToFocus() {
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/LabelSkin.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/LabelSkin.java	Thu Aug 22 09:49:13 2013 -0400
@@ -25,6 +25,9 @@
 
 package com.sun.javafx.scene.control.skin;
 
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.Node;
 import javafx.scene.control.Label;
 import java.util.Collections;
 import com.sun.javafx.scene.control.behavior.BehaviorBase;
@@ -40,5 +43,13 @@
 
         // Labels do not block the mouse by default, unlike most other UI Controls.
         consumeMouseEvents(false);
+
+        label.labelForProperty().addListener(labelForListener);
     }
+
+    private final ChangeListener<Node> labelForListener = new ChangeListener<Node>() {
+        @Override public void changed(ObservableValue<? extends Node> observable, Node oldNode, Node newNode) {
+            mnemonicTargetChanged(oldNode, newNode);
+        }
+    };
 }
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/LabeledSkinBase.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/LabeledSkinBase.java	Thu Aug 22 09:49:13 2013 -0400
@@ -252,6 +252,51 @@
     }
 
     /*
+    ** The Label is a mnemonic, and it's target node
+    ** has changed, but it's label hasn't so just
+    ** swap them over, and tidy up.
+    */
+    protected void mnemonicTargetChanged(Node oldNode, Node newNode) {
+        if (containsMnemonic == true) {
+            KeyCodeCombination mnemonicKeyCombo =
+                new KeyCodeCombination(
+                                       mnemonicCode,
+                                       com.sun.javafx.PlatformUtil.isMac()
+                                       ? KeyCombination.META_DOWN
+                                       : KeyCombination.ALT_DOWN);
+
+            /*
+            ** was there previously a labelFor
+            */
+            if (oldNode != null) {
+                Mnemonic myMnemonic = new Mnemonic(oldNode, mnemonicKeyCombo);
+                mnemonicScene.removeMnemonic(myMnemonic);
+                mnemonicScene = null;
+            }
+            else {
+                /*
+                ** maybe we were a target ourselves
+                */
+                Mnemonic myMnemonic = new Mnemonic(getSkinnable(), mnemonicKeyCombo);
+                mnemonicScene.removeMnemonic(myMnemonic);
+                mnemonicScene = null;
+            }
+
+            /*
+            ** is there a new labelFor
+            */
+            if (newNode != null) {
+                Mnemonic myMnemonic = new Mnemonic(newNode, mnemonicKeyCombo);
+                mnemonicScene = newNode.getScene();
+                if (mnemonicScene != null) {
+                    mnemonicScene.addMnemonic(myMnemonic);
+                }
+            }
+            labeledNode = newNode;
+        }
+    }
+
+    /*
     ** parent has changed,
     ** if it's null then remove any mnemonics from the scene,
     ** if it's valid then check to see if mnemonics should
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/ScrollPaneSkin.java	Thu Aug 22 09:49:13 2013 -0400
@@ -1,1225 +1,1190 @@
-/*
- * Copyright (c) 2010, 2013, 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 com.sun.javafx.scene.control.skin;
-
-import javafx.animation.Animation.Status;
-import javafx.animation.KeyFrame;
-import javafx.animation.KeyValue;
-import javafx.animation.Timeline;
-import javafx.beans.InvalidationListener;
-import javafx.beans.Observable;
-import javafx.beans.property.DoubleProperty;
-import javafx.beans.property.DoublePropertyBase;
-import javafx.beans.value.ChangeListener;
-import javafx.beans.value.ObservableValue;
-import javafx.event.ActionEvent;
-import javafx.event.Event;
-import javafx.event.EventDispatchChain;
-import javafx.event.EventDispatcher;
-import javafx.event.EventHandler;
-import javafx.geometry.BoundingBox;
-import javafx.geometry.Bounds;
-import javafx.geometry.Insets;
-import javafx.geometry.Orientation;
-import javafx.scene.Cursor;
-import javafx.scene.Node;
-import javafx.scene.control.ScrollBar;
-import javafx.scene.control.ScrollPane;
-import javafx.scene.control.ScrollPane.ScrollBarPolicy;
-import javafx.scene.input.MouseEvent;
-import javafx.scene.input.ScrollEvent;
-import javafx.scene.input.TouchEvent;
-import javafx.scene.layout.StackPane;
-import javafx.scene.shape.Rectangle;
-import javafx.util.Duration;
-import com.sun.javafx.Utils;
-import com.sun.javafx.scene.control.behavior.ScrollPaneBehavior;
-import com.sun.javafx.scene.traversal.TraversalEngine;
-import com.sun.javafx.scene.traversal.TraverseListener;
-import static com.sun.javafx.Utils.clamp;
-import static com.sun.javafx.scene.control.skin.Utils.boundedSize;
-
-public class ScrollPaneSkin extends BehaviorSkinBase<ScrollPane, ScrollPaneBehavior> implements TraverseListener {
-    /***************************************************************************
-     *                                                                         *
-     * UI Subcomponents                                                        *
-     *                                                                         *
-     **************************************************************************/
-
-    private static final double DEFAULT_PREF_SIZE = 100.0;
-
-    private static final double DEFAULT_MIN_SIZE = 36.0;
-
-    private static final double DEFAULT_SB_BREADTH = 12.0;
-    private static final double DEFAULT_EMBEDDED_SB_BREADTH = 8.0;
-
-    private static final double PAN_THRESHOLD = 0.5;
-
-    // state from the control
-
-    private Node scrollNode;
-
-    private double nodeWidth;
-    private double nodeHeight;
-
-    private double posX;
-    private double posY;
-
-    // working state
-
-    private boolean hsbvis;
-    private boolean vsbvis;
-    private double hsbHeight;
-    private double vsbWidth;
-
-    // substructure
-
-    private StackPane viewRect;
-    private StackPane viewContent;
-    private double contentWidth;
-    private double contentHeight;
-    private StackPane corner;
-    protected ScrollBar hsb;
-    protected ScrollBar vsb;
-
-    double pressX;
-    double pressY;
-    double ohvalue;
-    double ovvalue;
-    private Cursor saveCursor =  null;
-    private boolean dragDetected = false;
-    private boolean touchDetected = false;
-    private boolean mouseDown = false;
-
-    Rectangle clipRect;
-
-    /***************************************************************************
-     *                                                                         *
-     * Constructors                                                            *
-     *                                                                         *
-     **************************************************************************/
-
-    public ScrollPaneSkin(final ScrollPane scrollpane) {
-        super(scrollpane, new ScrollPaneBehavior(scrollpane));
-        initialize();
-        // Register listeners
-        registerChangeListener(scrollpane.contentProperty(), "NODE");
-        registerChangeListener(scrollpane.fitToWidthProperty(), "FIT_TO_WIDTH");
-        registerChangeListener(scrollpane.fitToHeightProperty(), "FIT_TO_HEIGHT");
-        registerChangeListener(scrollpane.hbarPolicyProperty(), "HBAR_POLICY");
-        registerChangeListener(scrollpane.vbarPolicyProperty(), "VBAR_POLICY");
-        registerChangeListener(scrollpane.hvalueProperty(), "HVALUE");
-        registerChangeListener(scrollpane.vvalueProperty(), "VVALUE");
-        registerChangeListener(scrollpane.prefViewportWidthProperty(), "PREF_VIEWPORT_WIDTH");
-        registerChangeListener(scrollpane.prefViewportHeightProperty(), "PREF_VIEWPORT_HEIGHT");
-    }
-
-    private final InvalidationListener nodeListener = new InvalidationListener() {
-        @Override public void invalidated(Observable valueModel) {
-            if (nodeWidth != -1.0 && nodeHeight != -1.0) {
-                /*
-                ** if the new size causes scrollbar visibility to change, then need to relayout
-                ** we also need to correct the thumb size when the scrollnode's size changes 
-                */
-                if (vsbvis != determineVerticalSBVisible() || hsbvis != determineHorizontalSBVisible() ||
-                    (scrollNode.getLayoutBounds().getWidth() != 0.0  && nodeWidth != scrollNode.getLayoutBounds().getWidth()) ||
-                    (scrollNode.getLayoutBounds().getHeight() != 0.0 && nodeHeight != scrollNode.getLayoutBounds().getHeight())) {
-                    getSkinnable().requestLayout();
-                } else {
-                    // otherwise just update scrollbars based on new scrollNode size
-                    updateVerticalSB();
-                    updateHorizontalSB();
-                }
-            }
-        }
-    };
-
-
-    /*
-    ** The content of the ScrollPane has just changed bounds, check scrollBar positions.
-    */ 
-   private final ChangeListener<Bounds> boundsChangeListener = new ChangeListener<Bounds>() {
-        @Override public void changed(ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds newBounds) {
-            
-            /*
-            ** For a height change then we want to reduce
-            ** viewport vertical jumping as much as possible. 
-            ** We set a new vsb value to try to keep the same
-            ** content position at the top of the viewport
-            */
-            double oldHeight = oldBounds.getHeight();
-            double newHeight = newBounds.getHeight();
-            if (oldHeight != newHeight) {
-                double oldPositionY = (snapPosition(snappedTopInset() - posY / (vsb.getMax() - vsb.getMin()) * (oldHeight - contentHeight)));
-                double newPositionY = (snapPosition(snappedTopInset() - posY / (vsb.getMax() - vsb.getMin()) * (newHeight - contentHeight)));
-                
-                double newValueY = (oldPositionY/newPositionY)*vsb.getValue();
-                if (newValueY < 0.0) {
-                    vsb.setValue(0.0);
-                }
-                else if (newValueY < 1.0) {
-                    vsb.setValue(newValueY);
-                }
-                else if (newValueY > 1.0) {
-                    vsb.setValue(1.0);
-                }
-            }
-
-            /*
-            ** For a width change then we want to reduce
-            ** viewport horizontal jumping as much as possible. 
-            ** We set a new hsb value to try to keep the same
-            ** content position to the left of the viewport
-            */
-            double oldWidth = oldBounds.getWidth();
-            double newWidth = newBounds.getWidth();
-            if (oldWidth != newWidth) {
-                double oldPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (oldWidth - contentWidth)));
-                double newPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (newWidth - contentWidth)));
-
-                double newValueX = (oldPositionX/newPositionX)*hsb.getValue();
-                if (newValueX < 0.0) {
-                    hsb.setValue(0.0);
-                }
-                else if (newValueX < 1.0) {
-                    hsb.setValue(newValueX);
-                }
-                else if (newValueX > 1.0) {
-                    hsb.setValue(1.0);
-                }
-            }
-        }
-   };
-
-
-    private void initialize() {
-        // requestLayout calls below should not trigger requestLayout above ScrollPane
-//        setManaged(false);
-
-        ScrollPane control = getSkinnable();
-        scrollNode = control.getContent();
-
-        if (scrollNode != null) {
-            scrollNode.layoutBoundsProperty().addListener(nodeListener);
-            scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
-        }
-
-        viewRect = new StackPane() {
-
-            @Override
-            protected void layoutChildren() {
-                viewContent.resize(getWidth(), getHeight());
-            }
-
-        };
-        // prevent requestLayout requests from within scrollNode from percolating up
-        viewRect.setManaged(false);
-        viewRect.setCache(true);
-
-        clipRect = new Rectangle();
-        viewRect.setClip(clipRect);
-
-        hsb = new ScrollBar();
-
-        vsb = new ScrollBar();
-        vsb.setOrientation(Orientation.VERTICAL);
-
-        corner = new StackPane();
-        corner.getStyleClass().setAll("corner");
-
-        viewContent = new StackPane() {
-            @Override public void requestLayout() {
-                // if scrollNode requested layout, will want to recompute
-                nodeWidth = -1;
-                nodeHeight = -1;
-                if (canChangeHorizontalSBVisibility() || canChangeVerticalSBVisibility()) {
-                    getSkinnable().requestLayout();
-                }
-                super.requestLayout(); // add as layout root for next layout pass
-            }
-            @Override protected void layoutChildren() {
-                if (nodeWidth == -1 || nodeHeight == -1) {
-                    computeScrollNodeSize(getWidth(),getHeight());
-                }
-                if (scrollNode != null && scrollNode.isResizable()) {
-                    scrollNode.resize(snapSize(nodeWidth), snapSize(nodeHeight));
-                }
-                if (scrollNode != null) {
-                    scrollNode.relocate(0,0);
-                }
-            }
-        };
-        viewRect.getChildren().add(viewContent);
-        
-        if (scrollNode != null) {
-            viewContent.getChildren().add(scrollNode);
-            viewRect.nodeOrientationProperty().bind(scrollNode.nodeOrientationProperty());
-        }
-
-        getChildren().clear();
-        getChildren().addAll(viewRect, vsb, hsb, corner);
-
-        /*
-        ** listeners, and assorted housekeeping
-        */
-        InvalidationListener vsbListener = new InvalidationListener() {
-            @Override public void invalidated(Observable valueModel) {
-                if (!IS_TOUCH_SUPPORTED) {
-                    posY = Utils.clamp(getSkinnable().getVmin(), vsb.getValue(), getSkinnable().getVmax());
-                }
-                else {
-                    posY = vsb.getValue();
-                }
-                updatePosY();
-            }
-        };
-        vsb.valueProperty().addListener(vsbListener);
-
-        InvalidationListener hsbListener = new InvalidationListener() {
-            @Override public void invalidated(Observable valueModel) {
-                if (!IS_TOUCH_SUPPORTED) {
-                    posX = Utils.clamp(getSkinnable().getHmin(), hsb.getValue(), getSkinnable().getHmax());
-                }
-                else {
-                    posX = hsb.getValue();
-                }
-                updatePosX();
-            }
-        };
-        hsb.valueProperty().addListener(hsbListener);
-
-        viewRect.setOnMousePressed(new EventHandler<javafx.scene.input.MouseEvent>() {
-           @Override public void handle(javafx.scene.input.MouseEvent e) {
-               if (IS_TOUCH_SUPPORTED) {
-                   startSBReleasedAnimation();
-               }
-               mouseDown = true;
-               pressX = e.getX();
-               pressY = e.getY();
-               ohvalue = hsb.getValue();
-               ovvalue = vsb.getValue();
-           }
-        });
-
-
-        viewRect.setOnDragDetected(new EventHandler<javafx.scene.input.MouseEvent>() {
-           @Override public void handle(javafx.scene.input.MouseEvent e) {
-                if (IS_TOUCH_SUPPORTED) {
-                    startSBReleasedAnimation();
-                }
-               if (getSkinnable().isPannable()) {
-                 dragDetected = true;
-                 if (saveCursor == null) {
-                     saveCursor = getSkinnable().getCursor();
-                     if (saveCursor == null) {
-                         saveCursor = Cursor.DEFAULT;
-                     }
-                     getSkinnable().setCursor(Cursor.MOVE);
-                     getSkinnable().requestLayout();
-                 }
-               }
-           }
-        });
-
-        viewRect.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
-            @Override public void handle(MouseEvent e) {
-                  if (IS_TOUCH_SUPPORTED) {
-                    startSBReleasedAnimation();
-                 }
-                 mouseDown = false;
-
-                 if (dragDetected == true) {
-                     if (saveCursor != null) {
-                         getSkinnable().setCursor(saveCursor);
-                         saveCursor = null;
-                         getSkinnable().requestLayout();
-                     }
-                     dragDetected = false;
-                 }
-
-                 /*
-                 ** if the contents need repositioning, and there's is no
-                 ** touch event in progress, then start the repositioning.
-                 */
-                 if ((posY > getSkinnable().getVmax() || posY < getSkinnable().getVmin() ||
-                     posX > getSkinnable().getHmax() || posX < getSkinnable().getHmin()) && !touchDetected) {
-                     startContentsToViewport();
-                 }
-            }
-        });
-        viewRect.setOnMouseDragged(new EventHandler<javafx.scene.input.MouseEvent>() {
-           @Override public void handle(javafx.scene.input.MouseEvent e) {
-                if (IS_TOUCH_SUPPORTED) {
-                    startSBReleasedAnimation();
-                }
-               /*
-               ** for mobile-touch we allow drag, even if not pannagle
-               */
-               if (getSkinnable().isPannable() || IS_TOUCH_SUPPORTED) {
-                   double deltaX = pressX - e.getX();
-                   double deltaY = pressY - e.getY();
-                   /*
-                   ** we only drag if not all of the content is visible.
-                   */
-                   if (hsb.getVisibleAmount() > 0.0 && hsb.getVisibleAmount() < hsb.getMax()) {
-                       if (Math.abs(deltaX) > PAN_THRESHOLD) {
-                           if (isReverseNodeOrientation()) {
-                               deltaX = -deltaX;
-                           }
-                           double newHVal = (ohvalue + deltaX / (nodeWidth - viewRect.getWidth()) * (hsb.getMax() - hsb.getMin()));
-                           if (!IS_TOUCH_SUPPORTED) {
-                               if (newHVal > hsb.getMax()) {
-                                   newHVal = hsb.getMax();
-                               }
-                               else if (newHVal < hsb.getMin()) {
-                                   newHVal = hsb.getMin();
-                               }
-                               hsb.setValue(newHVal);
-                           }
-                           else {
-                               hsb.setValue(newHVal);
-                           }
-                       }
-                   }
-                   /*
-                   ** we only drag if not all of the content is visible.
-                   */
-                   if (vsb.getVisibleAmount() > 0.0 && vsb.getVisibleAmount() < vsb.getMax()) {
-                       if (Math.abs(deltaY) > PAN_THRESHOLD) {
-                           double newVVal = (ovvalue + deltaY / (nodeHeight - viewRect.getHeight()) * (vsb.getMax() - vsb.getMin()));
-                           if (!IS_TOUCH_SUPPORTED) {
-                               if (newVVal > vsb.getMax()) {
-                                   newVVal = vsb.getMax();
-                               }
-                               else if (newVVal < vsb.getMin()) {
-                                   newVVal = vsb.getMin();
-                               }
-                               vsb.setValue(newVVal);
-                           }
-                           else {
-                               vsb.setValue(newVVal);
-                           }
-                       }
-                   }
-               }
-               /*
-               ** we need to consume drag events, as we don't want
-               ** the scrollpane itself to be dragged on every mouse click
-               */
-               e.consume();
-           }
-        });
-
-
-        /*
-        ** don't allow the ScrollBar to handle the ScrollEvent,
-        ** In a ScrollPane a vertical scroll should scroll on the vertical only,
-        ** whereas in a horizontal ScrollBar it can scroll horizontally.
-        */ 
-        final EventDispatcher blockEventDispatcher = new EventDispatcher() {
-           @Override public Event dispatchEvent(Event event, EventDispatchChain tail) {
-               // block the event from being passed down to children
-               return event;
-           }
-        };
-        // block ScrollEvent from being passed down to scrollbar's skin
-        final EventDispatcher oldHsbEventDispatcher = hsb.getEventDispatcher();
-        hsb.setEventDispatcher(new EventDispatcher() {
-           @Override public Event dispatchEvent(Event event, EventDispatchChain tail) {
-               if (event.getEventType() == ScrollEvent.SCROLL &&
-                       !((ScrollEvent)event).isDirect()) {
-                   tail = tail.prepend(blockEventDispatcher);
-                   tail = tail.prepend(oldHsbEventDispatcher);
-                   return tail.dispatchEvent(event);
-               }
-               return oldHsbEventDispatcher.dispatchEvent(event, tail);
-           }
-        });
-        // block ScrollEvent from being passed down to scrollbar's skin
-        final EventDispatcher oldVsbEventDispatcher = vsb.getEventDispatcher();
-        vsb.setEventDispatcher(new EventDispatcher() {
-           @Override public Event dispatchEvent(Event event, EventDispatchChain tail) {
-               if (event.getEventType() == ScrollEvent.SCROLL &&
-                       !((ScrollEvent)event).isDirect()) {
-                   tail = tail.prepend(blockEventDispatcher);
-                   tail = tail.prepend(oldVsbEventDispatcher);
-                   return tail.dispatchEvent(event);
-               }
-               return oldVsbEventDispatcher.dispatchEvent(event, tail);
-           }
-        });
-
-        /*
-        ** listen for ScrollEvents over the whole of the ScrollPane
-        ** area, the above dispatcher having removed the ScrollBars
-        ** scroll event handling.
-        */
-        getSkinnable().setOnScroll(new EventHandler<javafx.scene.input.ScrollEvent>() {
-            @Override public void handle(ScrollEvent event) {
-                if (IS_TOUCH_SUPPORTED) {
-                    startSBReleasedAnimation();
-                }
-                /*
-                ** if we're completely visible then do nothing....
-                ** we only consume an event that we've used.
-                */
-                if (vsb.getVisibleAmount() < vsb.getMax()) {
-                    double vRange = getSkinnable().getVmax()-getSkinnable().getVmin();
-                    double vPixelValue;
-                    if (nodeHeight > 0.0) {
-                        vPixelValue = vRange / nodeHeight;
-                    }
-                    else {
-                        vPixelValue = 0.0;
-                    }
-                    double newValue = vsb.getValue()+(-event.getDeltaY())*vPixelValue;
-                    if (!IS_TOUCH_SUPPORTED) {
-                        if ((event.getDeltaY() > 0.0 && vsb.getValue() > vsb.getMin()) ||
-                            (event.getDeltaY() < 0.0 && vsb.getValue() < vsb.getMax())) {
-                            vsb.setValue(newValue);
-                            event.consume();
-                        }
-                    }
-                    else {
-                        /*
-                        ** if there is a repositioning in progress then we only
-                        ** set the value for 'real' events
-                        */
-                        if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
-                            vsb.setValue(newValue);
-                            if ((newValue > vsb.getMax() || newValue < vsb.getMin()) && (!mouseDown && !touchDetected)) {
-                                startContentsToViewport();
-                            }
-                            event.consume();
-                        }
-                    }
-                }
-
-                if (hsb.getVisibleAmount() < hsb.getMax()) {
-                    double hRange = getSkinnable().getHmax()-getSkinnable().getHmin();
-                    double hPixelValue;
-                    if (nodeWidth > 0.0) {
-                        hPixelValue = hRange / nodeWidth;
-                    }
-                    else {
-                        hPixelValue = 0.0;
-                    }
-
-                    double newValue = hsb.getValue()+(-event.getDeltaX())*hPixelValue;
-                    if (!IS_TOUCH_SUPPORTED) {
-                        if ((event.getDeltaX() > 0.0 && hsb.getValue() > hsb.getMin()) ||
-                            (event.getDeltaX() < 0.0 && hsb.getValue() < hsb.getMax())) {
-                            hsb.setValue(newValue);
-                            event.consume();
-                        }
-                    }
-                    else {
-                        /*
-                        ** if there is a repositioning in progress then we only
-                        ** set the value for 'real' events
-                        */
-                        if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
-                            hsb.setValue(newValue);
-
-                            if ((newValue > hsb.getMax() || newValue < hsb.getMin()) && (!mouseDown && !touchDetected)) {
-                                startContentsToViewport();
-                            }
-                            event.consume();
-                        }
-                    }
-                }
-            }
-        });
-
-        /*
-        ** there are certain animations that need to know if the touch is
-        ** happening.....
-        */
-        getSkinnable().setOnTouchPressed(new EventHandler<TouchEvent>() {
-            @Override public void handle(TouchEvent e) {
-                touchDetected = true;
-                startSBReleasedAnimation();
-                e.consume();
-            }
-        });
-
-        getSkinnable().setOnTouchReleased(new EventHandler<TouchEvent>() {
-            @Override public void handle(TouchEvent e) {
-                touchDetected = false;
-                startSBReleasedAnimation();
-                e.consume();
-            }
-        });
-
-        TraversalEngine traversalEngine = new TraversalEngine(getSkinnable(), false);
-        traversalEngine.addTraverseListener(this);
-        getSkinnable().setImpl_traversalEngine(traversalEngine);
-
-        // ScrollPanes do not block all MouseEvents by default, unlike most other UI Controls.
-        consumeMouseEvents(false);
-    }
-
-
-    @Override protected void handleControlPropertyChanged(String p) {
-        super.handleControlPropertyChanged(p);
-        if ("NODE".equals(p)) {
-            if (scrollNode != getSkinnable().getContent()) {
-                if (scrollNode != null) {
-                    scrollNode.layoutBoundsProperty().removeListener(nodeListener);
-                    scrollNode.layoutBoundsProperty().removeListener(boundsChangeListener);
-                    viewContent.getChildren().remove(scrollNode);
-                }
-                scrollNode = getSkinnable().getContent();
-                if (scrollNode != null) {
-                    nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
-                    nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
-                    viewContent.getChildren().setAll(scrollNode);
-                    scrollNode.layoutBoundsProperty().addListener(nodeListener);
-                    scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
-                }
-            }
-            getSkinnable().requestLayout();
-        } else if ("FIT_TO_WIDTH".equals(p) || "FIT_TO_HEIGHT".equals(p)) {
-            getSkinnable().requestLayout();
-            viewRect.requestLayout();
-        } else if ("HBAR_POLICY".equals(p) || "VBAR_POLICY".equals(p)) {
-            // change might affect pref size, so requestLayout on control
-            getSkinnable().requestLayout();
-        } else if ("HVALUE".equals(p)) {
-            hsb.setValue(getSkinnable().getHvalue());
-        } else if ("VVALUE".equals(p)) {
-            vsb.setValue(getSkinnable().getVvalue());
-        } else if ("PREF_VIEWPORT_WIDTH".equals(p) || "PREF_VIEWPORT_HEIGHT".equals(p)) {
-            // change affects pref size, so requestLayout on control
-            getSkinnable().requestLayout();
-        }
-    }
-    
-    void scrollBoundsIntoView(Bounds b) {
-        double dx = 0.0;
-        double dy = 0.0;
-        boolean needsLayout = false;
-        if (b.getMaxX() > contentWidth) {
-            dx = contentWidth - b.getMaxX();
-        }
-        if (b.getMinX() < 0) {
-            dx = -b.getMinX();
-        }
-        if (b.getMaxY() > contentHeight) {
-            dy = contentHeight - b.getMaxY();
-        }
-        if (b.getMinY() < 0) {
-            dy = -b.getMinY();
-        }
-        // We want to move contentPanel's layoutX,Y by (dx,dy).
-        // But to do this we have to set the scrollbars' values appropriately.
-
-        double newHvalue = hsb.getValue();
-        double newVvalue = vsb.getValue();
-        if (dx != 0) {
-            double sdx = -dx * (hsb.getMax() - hsb.getMin()) / (nodeWidth - viewRect.getWidth());
-            if (sdx < 0) {
-                sdx -= hsb.getUnitIncrement();
-            } else {
-                sdx += hsb.getUnitIncrement();
-            }
-            newHvalue = clamp(hsb.getMin(), hsb.getValue() + sdx, hsb.getMax());
-            hsb.setValue(newHvalue);
-            needsLayout = true;
-        }
-        if (dy != 0) {
-            double sdy = -dy * (vsb.getMax() - vsb.getMin()) / (nodeHeight - viewRect.getHeight());
-            if (sdy < 0) {
-                sdy -= vsb.getUnitIncrement();
-            } else {
-                sdy += vsb.getUnitIncrement();
-            }
-            newVvalue = clamp(vsb.getMin(), vsb.getValue() + sdy, vsb.getMax());
-            vsb.setValue(newVvalue);
-            needsLayout = true;
-        }
-
-        if (needsLayout == true) {
-            getSkinnable().requestLayout();
-        }
-    }
-
-    /*
-    ** auto-scroll so node is within (0,0),(contentWidth,contentHeight)
-    */
-    @Override public void onTraverse(Node n, Bounds b) {
-        scrollBoundsIntoView(b);
-    }
-
-    public void hsbIncrement() {
-        if (hsb != null) hsb.increment();
-    }
-    public void hsbDecrement() {
-        if (hsb != null) hsb.decrement();
-    }
-
-    // TODO: add page increment and decrement
-    public void hsbPageIncrement() {
-        if (hsb != null) hsb.increment();
-    }
-    // TODO: add page increment and decrement
-    public void hsbPageDecrement() {
-        if (hsb != null) hsb.decrement();
-    }
-
-    public void vsbIncrement() {
-        if (vsb != null) vsb.increment();
-    }
-    public void vsbDecrement() {
-        if (vsb != null) vsb.decrement();
-    }
-
-    // TODO: add page increment and decrement
-    public void vsbPageIncrement() {
-        if (vsb != null) vsb.increment();
-    }
-    // TODO: add page increment and decrement
-    public void vsbPageDecrement() {
-        if (vsb != null) vsb.decrement();
-    }
-
-    /***************************************************************************
-     *                                                                         *
-     * Layout                                                                  *
-     *                                                                         *
-     **************************************************************************/
-
-    @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
-        final ScrollPane sp = getSkinnable();
-        
-        if (sp.getPrefViewportWidth() > 0) {
-            double vsbWidth = sp.getVbarPolicy() == ScrollBarPolicy.ALWAYS? vsb.prefWidth(-1) : 0;
-            return (sp.getPrefViewportWidth() + vsbWidth + snappedLeftInset() + snappedRightInset());
-        }
-        else {
-            return DEFAULT_PREF_SIZE;
-        }
-    }
-
-    @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
-        final ScrollPane sp = getSkinnable();
-        
-        if (sp.getPrefViewportHeight() > 0) {
-            double hsbHeight = sp.getHbarPolicy() == ScrollBarPolicy.ALWAYS? hsb.prefHeight(-1) : 0;
-            return (sp.getPrefViewportHeight() + hsbHeight + snappedTopInset() + snappedBottomInset());
-        }
-        else {
-            return DEFAULT_PREF_SIZE;
-        }
-    }
-
-    @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
-        double w = corner.minWidth(-1);
-        return (w > 0) ? (3 * w) : (DEFAULT_MIN_SIZE);
-    }
-
-    @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
-        double h = corner.minHeight(-1);
-        return (h > 0) ? (3 * h) : (DEFAULT_MIN_SIZE);
-    }
-
-    @Override protected void layoutChildren(final double x, final double y,
-            final double w, final double h) {
-        final ScrollPane control = getSkinnable();
-        final Insets insets = control.getInsets();
-        final Insets padding = control.getPadding();
-
-        vsb.setMin(control.getVmin());
-        vsb.setMax(control.getVmax());
-
-        //should only do this on css setup
-        hsb.setMin(control.getHmin());
-        hsb.setMax(control.getHmax());
-
-        contentWidth = w;
-        contentHeight = h;
-
-        /*
-        ** we want the scrollbars to go right to the border
-        */
-        double hsbWidth = contentWidth + padding.getLeft() + padding.getRight();
-        double vsbHeight = contentHeight + padding.getTop() + padding.getBottom();
-
-        computeScrollNodeSize(contentWidth, contentHeight);
-        computeScrollBarSize();
-        vsbvis = determineVerticalSBVisible();
-        hsbvis = determineHorizontalSBVisible();
-
-        if (vsbvis) {
-            hsbWidth -= vsbWidth;
-            if (!IS_TOUCH_SUPPORTED) {
-                contentWidth -= vsbWidth;
-            }
-        }
-        if (hsbvis) {
-            vsbHeight -= hsbHeight;
-            if (!IS_TOUCH_SUPPORTED) {
-                contentHeight -= hsbHeight;
-            }
-        }
-        if (scrollNode != null && scrollNode.isResizable()) {
-            // maybe adjust size now that scrollbars may take up space
-            if (vsbvis && hsbvis) {
-                // adjust just once to accommodate
-                computeScrollNodeSize(contentWidth, contentHeight);
-
-            } else if (hsbvis && !vsbvis) {
-                computeScrollNodeSize(contentWidth, contentHeight);
-                vsbvis = determineVerticalSBVisible();
-                if (vsbvis) {
-                    // now both are visible
-                    contentWidth -= vsbWidth;
-                    computeScrollNodeSize(contentWidth, contentHeight);
-                }
-            } else if (vsbvis && !hsbvis) {
-                computeScrollNodeSize(contentWidth, contentHeight);
-                hsbvis = determineHorizontalSBVisible();
-                if (hsbvis) {
-                    // now both are visible
-                    contentHeight -= hsbHeight;
-                    computeScrollNodeSize(contentWidth, contentHeight);
-                }
-            }
-        }
-
-        // figure out the content area that is to be filled
-        double cx = insets.getLeft()-padding.getLeft();
-        double cy = insets.getTop()-padding.getTop();
-
-        vsb.setVisible(vsbvis);
-        if (vsbvis) {
-            /*
-            ** round up position of ScrollBar, round down it's size.
-            **
-            ** Positioning the ScrollBar
-            **  The Padding should go between the content and the edge,
-            **  otherwise changes in padding move the ScrollBar, and could
-            **  in extreme cases size the ScrollBar to become unusable.
-            **  The -1, +1 plus one bit : 
-            **   If padding in => 1 then we allow one pixel to appear as the
-            **   outside border of the Scrollbar, and the rest on the inside.
-            **   If padding is < 1 then we just stick to the edge.
-            */
-            if (padding.getRight() < 1) {
-                vsb.resizeRelocate(snapPosition(control.getWidth() - (vsbWidth + (insets.getRight()-padding.getRight()))), 
-                                   snapPosition(cy), snapSize(vsbWidth), snapSize(vsbHeight));
-            }
-            else {
-                vsb.resizeRelocate(snapPosition(control.getWidth() - ((vsbWidth+1) + (insets.getRight()-padding.getRight()))), 
-                                   snapPosition(cy), snapSize(vsbWidth)+1, snapSize(vsbHeight));
-            }
-        }
-        updateVerticalSB();
-
-        hsb.setVisible(hsbvis);
-        if (hsbvis) {
-            /*
-            ** round up position of ScrollBar, round down it's size.
-            **
-            ** Positioning the ScrollBar
-            **  The Padding should go between the content and the edge,
-            **  otherwise changes in padding move the ScrollBar, and could
-            **  in extreme cases size the ScrollBar to become unusable.
-            **  The -1, +1 plus one bit : 
-            **   If padding in => 1 then we allow one pixel to appear as the
-            **   outside border of the Scrollbar, and the rest on the inside.
-            **   If padding is < 1 then we just stick to the edge.
-            */
-            if (padding.getBottom() < 1) {
-                hsb.resizeRelocate(snapPosition(cx), snapPosition(control.getHeight() - (hsbHeight + (insets.getBottom()-padding.getBottom()))), 
-                                                     snapSize(hsbWidth), snapSize(hsbHeight));
-            }
-            else {
-                hsb.resizeRelocate(snapPosition(cx), snapPosition(control.getHeight() - ((hsbHeight+1) + (insets.getBottom()-padding.getBottom()))), 
-                                                     snapSize(hsbWidth), snapSize(hsbHeight)+1);
-            }
-        }
-        updateHorizontalSB();
-
-        viewRect.resize(snapSize(contentWidth), snapSize(contentHeight));
-        resetClip();
-
-        if (vsbvis && hsbvis) {
-            corner.setVisible(true);
-            double cornerWidth = vsbWidth;
-            double cornerHeight = hsbHeight;
-
-            if (padding.getRight() >= 1) {
-                cornerWidth++;
-            }
-            if (padding.getBottom() >= 1) {
-                cornerHeight++;
-            }
-            corner.resizeRelocate(snapPosition(vsb.getLayoutX()), snapPosition(hsb.getLayoutY()), snapSize(cornerWidth), snapSize(cornerHeight));
-        } else {
-            corner.setVisible(false);
-        }
-        control.setViewportBounds(new BoundingBox(snapPosition(viewContent.getLayoutX()), snapPosition(viewContent.getLayoutY()), snapSize(contentWidth), snapSize(contentHeight)));
-    }
-    
-    private void computeScrollNodeSize(double contentWidth, double contentHeight) {
-        if (scrollNode != null) {
-            if (scrollNode.isResizable()) {
-                ScrollPane control = getSkinnable();
-                Orientation bias = scrollNode.getContentBias();
-                if (bias == null) {
-                    nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
-                                                         scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
-                    nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
-                                                          scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));
-
-                } else if (bias == Orientation.HORIZONTAL) {
-                    nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
-                                                         scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
-                    nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(nodeWidth),
-                                                          scrollNode.minHeight(nodeWidth),scrollNode.maxHeight(nodeWidth)));
-
-                } else { // bias == VERTICAL
-                    nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
-                                                          scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));
-                    nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(nodeHeight),
-                                                         scrollNode.minWidth(nodeHeight),scrollNode.maxWidth(nodeHeight)));
-                }
-
-            } else {
-                nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
-                nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
-            }
-        }
-    }
-
-    private boolean isReverseNodeOrientation() {
-        return (scrollNode != null &&
-                getSkinnable().getEffectiveNodeOrientation() !=
-                            scrollNode.getEffectiveNodeOrientation());
-    }
-
-    private boolean determineHorizontalSBVisible() {
-        final ScrollPane sp = getSkinnable();
-        final double contentw = sp.getWidth() - snappedLeftInset() - snappedRightInset();
-        if (IS_TOUCH_SUPPORTED) {
-            return (tempVisibility && (nodeWidth > contentw));
-        }
-        else {
-            return (getSkinnable().getHbarPolicy().equals(ScrollBarPolicy.NEVER)) ? false :
-                ((getSkinnable().getHbarPolicy().equals(ScrollBarPolicy.ALWAYS)) ? true :
-                 ((getSkinnable().isFitToWidth() && scrollNode != null ? scrollNode.isResizable() : false) ?
-                  (nodeWidth > contentw && scrollNode.minWidth(-1) > contentw) : (nodeWidth > contentw)));
-        }
-    }
-
-    private boolean canChangeVerticalSBVisibility() {
-        final ScrollPane c = getSkinnable();
-        if (c.isFitToHeight() || !c.getVbarPolicy().equals(ScrollBarPolicy.AS_NEEDED)) {
-            return false;
-        }
-        // Note, we can safely assume here that the content height won't change.
-        // Otherwise, the layout of the scrollpane will be triggered anyway.
-        // Also, as fitToHeight is false, the computation below is also safe
-        final double contenth = c.getHeight()- snappedBottomInset()- snappedTopInset();
-        if (c.getContentBias() != Orientation.HORIZONTAL) {
-            // Optimization: we can already compute the nodeHeight here
-            nodeHeight = snapSize(boundedSize(scrollNode.prefHeight(-1),
-                    scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));
-            return vsbvis ? nodeHeight <= contenth : nodeHeight > contenth;
-        }
-        return true;
-    }
-    
-    private boolean canChangeHorizontalSBVisibility() {
-        final ScrollPane c = getSkinnable();
-        if (c.isFitToWidth()|| !c.getHbarPolicy().equals(ScrollBarPolicy.AS_NEEDED)) {
-            return false;
-        }
-        // Note, we can safely assume here that the content width won't change.
-        // Otherwise, the layout of the scrollpane will be triggered anyway.
-        // Also, as fitToWidth is false, the computation below is also safe
-        final double contentw = c.getWidth()- snappedLeftInset()- snappedRightInset();
-        if (c.getContentBias() != Orientation.VERTICAL) {
-            // Optimization: we can already compute the nodeHeight here
-            nodeWidth = snapSize(boundedSize(scrollNode.prefWidth(-1),
-                    scrollNode.minWidth(-1), scrollNode.maxWidth(-1)));
-            return hsbvis ? nodeWidth <= contentw : nodeWidth > contentw;
-        }
-        return true;
-    }
-
-    private boolean determineVerticalSBVisible() {
-        final ScrollPane sp = getSkinnable();
-        final double contenth = sp.getHeight() - snappedTopInset() - snappedBottomInset();
-        if (IS_TOUCH_SUPPORTED) {
-            return (tempVisibility && (nodeHeight > contenth));
-        }
-        else {
-            return (getSkinnable().getVbarPolicy().equals(ScrollBarPolicy.NEVER)) ? false :
-                ((getSkinnable().getVbarPolicy().equals(ScrollBarPolicy.ALWAYS)) ? true :
-                 ((getSkinnable().isFitToHeight() && scrollNode != null ? scrollNode.isResizable() : false) ?
-                  (nodeHeight > contenth && scrollNode.minHeight(-1) > contenth) : (nodeHeight > contenth)));
-        }
-    }
-
-    private void computeScrollBarSize() {
-        vsbWidth = snapSize(vsb.prefWidth(-1));
-        if (vsbWidth == 0) {
-            //            println("*** WARNING ScrollPaneSkin: can't get scroll bar width, using {DEFAULT_SB_BREADTH}");
-            if (IS_TOUCH_SUPPORTED) {
-                vsbWidth = DEFAULT_EMBEDDED_SB_BREADTH;
-            }
-            else {
-                vsbWidth = DEFAULT_SB_BREADTH;
-            }
-        }
-        hsbHeight = snapSize(hsb.prefHeight(-1));
-        if (hsbHeight == 0) {
-            //            println("*** WARNING ScrollPaneSkin: can't get scroll bar height, using {DEFAULT_SB_BREADTH}");
-            if (IS_TOUCH_SUPPORTED) {
-                hsbHeight = DEFAULT_EMBEDDED_SB_BREADTH;
-            }
-            else {
-                hsbHeight = DEFAULT_SB_BREADTH;
-            }
-        }
-    }
-
-    private void updateHorizontalSB() {
-        double contentRatio = nodeWidth * (hsb.getMax() - hsb.getMin());
-        if (contentRatio > 0.0) {
-            hsb.setVisibleAmount(contentWidth / contentRatio);
-            hsb.setBlockIncrement(0.9 * hsb.getVisibleAmount());
-            hsb.setUnitIncrement(0.1 * hsb.getVisibleAmount());
-        }
-        else {
-            hsb.setVisibleAmount(0.0);
-            hsb.setBlockIncrement(0.0);
-            hsb.setUnitIncrement(0.0);
-        }
-
-        if (hsb.isVisible()) {
-            updatePosX();
-        } else {
-            if (nodeWidth > contentWidth) {
-                updatePosX();
-            } else {
-                viewContent.setLayoutX(snappedLeftInset());
-            }
-        }
-    }
-    
-    private void updateVerticalSB() {
-        double contentRatio = nodeHeight * (vsb.getMax() - vsb.getMin());
-        if (contentRatio > 0.0) {
-            vsb.setVisibleAmount(contentHeight / contentRatio);
-            vsb.setBlockIncrement(0.9 * vsb.getVisibleAmount());
-            vsb.setUnitIncrement(0.1 * vsb.getVisibleAmount());
-        }
-        else {
-            vsb.setVisibleAmount(0.0);
-            vsb.setBlockIncrement(0.0);
-            vsb.setUnitIncrement(0.0);
-        }
-
-        if (vsb.isVisible()) {
-            updatePosY();
-        } else {
-            if (nodeHeight > contentHeight) {
-                updatePosY();
-            } else {
-                viewContent.setLayoutY(snappedTopInset());
-            }
-        }
-    }
-
-    private double updatePosX() {
-        final ScrollPane sp = getSkinnable();
-        double x = isReverseNodeOrientation() ? (hsb.getMax() - (posX - hsb.getMin())) : posX;
-        viewContent.setLayoutX(snapPosition(snappedLeftInset() - x / (hsb.getMax() - hsb.getMin()) * (nodeWidth - contentWidth)));
-        sp.setHvalue(Utils.clamp(sp.getHmin(), posX, sp.getHmax()));
-        return posX;
-    }
-
-    private double updatePosY() {
-        final ScrollPane sp = getSkinnable();
-        viewContent.setLayoutY(snapPosition(snappedTopInset() - posY / (vsb.getMax() - vsb.getMin()) * (nodeHeight - contentHeight)));
-        sp.setVvalue(Utils.clamp(sp.getVmin(), posY, sp.getVmax()));
-        return posY;
-    }
-
-    private void resetClip() {
-        clipRect.setWidth(snapSize(contentWidth));
-        clipRect.setHeight(snapSize(contentHeight));
-        clipRect.relocate(snappedLeftInset(), snappedTopInset());
-    }
-
-    Timeline sbTouchTimeline;
-    KeyFrame sbTouchKF1;
-    KeyFrame sbTouchKF2;
-    Timeline contentsToViewTimeline;
-    KeyFrame contentsToViewKF1;
-    KeyFrame contentsToViewKF2;
-    KeyFrame contentsToViewKF3;
-
-    private boolean tempVisibility;
-
-
-    protected void startSBReleasedAnimation() {
-        if (sbTouchTimeline == null) {
-            /*
-            ** timeline to leave the scrollbars visible for a short
-            ** while after a scroll/drag
-            */
-            sbTouchTimeline = new Timeline();
-            sbTouchKF1 = new KeyFrame(Duration.millis(0), new EventHandler<ActionEvent>() {
-                @Override public void handle(ActionEvent event) {
-                    tempVisibility = true;
-                }
-            });
-
-            sbTouchKF2 = new KeyFrame(Duration.millis(500), new EventHandler<ActionEvent>() {
-                @Override public void handle(ActionEvent event) {
-                    tempVisibility = false;
-                    getSkinnable().requestLayout();
-                }
-            });
-            sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2);
-        }
-        sbTouchTimeline.playFromStart();
-    }
-
-
-
-    protected void startContentsToViewport() {
-        double newPosX = posX;
-        double newPosY = posY;
-
-        setContentPosX(posX);
-        setContentPosY(posY);
-
-        if (posY > getSkinnable().getVmax()) {
-            newPosY = getSkinnable().getVmax();
-        }
-        else if (posY < getSkinnable().getVmin()) {
-            newPosY = getSkinnable().getVmin();
-        }             
-        
-
-        if (posX > getSkinnable().getHmax()) {
-            newPosX = getSkinnable().getHmax();
-        }
-        else if (posX < getSkinnable().getHmin()) {
-            newPosX = getSkinnable().getHmin();
-        }
-
-        if (!IS_TOUCH_SUPPORTED) {
-            startSBReleasedAnimation();
-        }
-
-        /*
-        ** timeline to return the contents of the scrollpane to the viewport
-        */
-        if (contentsToViewTimeline != null) {
-            contentsToViewTimeline.stop();
-        }
-        contentsToViewTimeline = new Timeline();
-        /*
-        ** short pause before animation starts
-        */
-        contentsToViewKF1 = new KeyFrame(Duration.millis(50));
-        /*
-        ** reposition
-        */
-        contentsToViewKF2 = new KeyFrame(Duration.millis(150), new EventHandler<ActionEvent>() {
-                @Override public void handle(ActionEvent event) {
-                    getSkinnable().requestLayout();
-                }
-            },
-            new KeyValue(contentPosX, newPosX),
-            new KeyValue(contentPosY, newPosY)
-            );
-        /*
-        ** block out 'aftershocks', but real events will
-        ** still reactivate
-        */
-        contentsToViewKF3 = new KeyFrame(Duration.millis(1500));
-        contentsToViewTimeline.getKeyFrames().addAll(contentsToViewKF1, contentsToViewKF2, contentsToViewKF3);
-        contentsToViewTimeline.playFromStart();
-    }
-
-
-    private DoubleProperty contentPosX;
-    private void setContentPosX(double value) { contentPosXProperty().set(value); }
-    private double getContentPosX() { return contentPosX == null ? 0.0 : contentPosX.get(); }
-    private DoubleProperty contentPosXProperty() {
-        if (contentPosX == null) {
-            contentPosX = new DoublePropertyBase() {
-                @Override protected void invalidated() {
-                    hsb.setValue(getContentPosX());
-                    getSkinnable().requestLayout();
-                }
-
-                @Override
-                public Object getBean() {
-                    return ScrollPaneSkin.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "contentPosX";
-                }
-            };
-        }
-        return contentPosX;
-    }
-
-    private DoubleProperty contentPosY;
-    private void setContentPosY(double value) { contentPosYProperty().set(value); }
-    private double getContentPosY() { return contentPosY == null ? 0.0 : contentPosY.get(); }
-    private DoubleProperty contentPosYProperty() {
-        if (contentPosY == null) {
-            contentPosY = new DoublePropertyBase() {
-                @Override protected void invalidated() {
-                    vsb.setValue(getContentPosY());
-                    getSkinnable().requestLayout();
-                }
-
-                @Override
-                public Object getBean() {
-                    return ScrollPaneSkin.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "contentPosY";
-                }
-            };
-        }
-        return contentPosY;
-    }
-}
+/*
+ * Copyright (c) 2010, 2013, 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 com.sun.javafx.scene.control.skin;
+
+import javafx.animation.Animation.Status;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.DoublePropertyBase;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.ActionEvent;
+import javafx.event.Event;
+import javafx.event.EventDispatchChain;
+import javafx.event.EventDispatcher;
+import javafx.event.EventHandler;
+import javafx.geometry.BoundingBox;
+import javafx.geometry.Bounds;
+import javafx.geometry.Orientation;
+import javafx.scene.Cursor;
+import javafx.scene.Node;
+import javafx.scene.control.ScrollBar;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.ScrollPane.ScrollBarPolicy;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.input.ScrollEvent;
+import javafx.scene.input.TouchEvent;
+import javafx.scene.layout.StackPane;
+import javafx.scene.shape.Rectangle;
+import javafx.util.Duration;
+import com.sun.javafx.Utils;
+import com.sun.javafx.application.PlatformImpl;
+import com.sun.javafx.scene.control.behavior.ScrollPaneBehavior;
+import com.sun.javafx.scene.traversal.TraversalEngine;
+import com.sun.javafx.scene.traversal.TraverseListener;
+import static com.sun.javafx.Utils.*;
+import static com.sun.javafx.scene.control.skin.Utils.*;
+import javafx.geometry.Insets;
+
+public class ScrollPaneSkin extends BehaviorSkinBase<ScrollPane, ScrollPaneBehavior> implements TraverseListener {
+    /***************************************************************************
+     *                                                                         *
+     * UI Subcomponents                                                        *
+     *                                                                         *
+     **************************************************************************/
+
+    private static final double DEFAULT_PREF_SIZE = 100.0;
+
+    private static final double DEFAULT_MIN_SIZE = 36.0;
+
+    private static final double DEFAULT_SB_BREADTH = 12.0;
+    private static final double DEFAULT_EMBEDDED_SB_BREADTH = 8.0;
+
+    private static final double PAN_THRESHOLD = 0.5;
+
+    // state from the control
+
+    private Node scrollNode;
+
+    private double nodeWidth;
+    private double nodeHeight;
+
+    private double posX;
+    private double posY;
+
+    // working state
+
+    private boolean hsbvis;
+    private boolean vsbvis;
+    private double hsbHeight;
+    private double vsbWidth;
+
+    // substructure
+
+    private StackPane viewRect;
+    private StackPane viewContent;
+    private double contentWidth;
+    private double contentHeight;
+    private StackPane corner;
+    protected ScrollBar hsb;
+    protected ScrollBar vsb;
+
+    double pressX;
+    double pressY;
+    double ohvalue;
+    double ovvalue;
+    private Cursor saveCursor =  null;
+    private boolean dragDetected = false;
+    private boolean touchDetected = false;
+    private boolean mouseDown = false;
+
+    Rectangle clipRect;
+
+    /***************************************************************************
+     *                                                                         *
+     * Constructors                                                            *
+     *                                                                         *
+     **************************************************************************/
+
+    public ScrollPaneSkin(final ScrollPane scrollpane) {
+        super(scrollpane, new ScrollPaneBehavior(scrollpane));
+        initialize();
+        // Register listeners
+        registerChangeListener(scrollpane.contentProperty(), "NODE");
+        registerChangeListener(scrollpane.fitToWidthProperty(), "FIT_TO_WIDTH");
+        registerChangeListener(scrollpane.fitToHeightProperty(), "FIT_TO_HEIGHT");
+        registerChangeListener(scrollpane.hbarPolicyProperty(), "HBAR_POLICY");
+        registerChangeListener(scrollpane.vbarPolicyProperty(), "VBAR_POLICY");
+        registerChangeListener(scrollpane.hvalueProperty(), "HVALUE");
+        registerChangeListener(scrollpane.vvalueProperty(), "VVALUE");
+        registerChangeListener(scrollpane.prefViewportWidthProperty(), "PREF_VIEWPORT_WIDTH");
+        registerChangeListener(scrollpane.prefViewportHeightProperty(), "PREF_VIEWPORT_HEIGHT");
+    }
+
+    private final InvalidationListener nodeListener = new InvalidationListener() {
+        @Override public void invalidated(Observable valueModel) {
+            if (nodeWidth != -1.0 && nodeHeight != -1.0) {
+                /*
+                ** if the new size causes scrollbar visibility to change, then need to relayout
+                ** we also need to correct the thumb size when the scrollnode's size changes 
+                */
+                if (vsbvis != determineVerticalSBVisible() || hsbvis != determineHorizontalSBVisible() ||
+                    (scrollNode.getLayoutBounds().getWidth() != 0.0  && nodeWidth != scrollNode.getLayoutBounds().getWidth()) ||
+                    (scrollNode.getLayoutBounds().getHeight() != 0.0 && nodeHeight != scrollNode.getLayoutBounds().getHeight())) {
+                    getSkinnable().requestLayout();
+                } else {
+                    // otherwise just update scrollbars based on new scrollNode size
+                    updateVerticalSB();
+                    updateHorizontalSB();
+                }
+            }
+        }
+    };
+
+
+    /*
+    ** The content of the ScrollPane has just changed bounds, check scrollBar positions.
+    */ 
+   private final ChangeListener<Bounds> boundsChangeListener = new ChangeListener<Bounds>() {
+        @Override public void changed(ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds newBounds) {
+            
+            /*
+            ** For a height change then we want to reduce
+            ** viewport vertical jumping as much as possible. 
+            ** We set a new vsb value to try to keep the same
+            ** content position at the top of the viewport
+            */
+            double oldHeight = oldBounds.getHeight();
+            double newHeight = newBounds.getHeight();
+            if (oldHeight != newHeight) {
+                double oldPositionY = (snapPosition(snappedTopInset() - posY / (vsb.getMax() - vsb.getMin()) * (oldHeight - contentHeight)));
+                double newPositionY = (snapPosition(snappedTopInset() - posY / (vsb.getMax() - vsb.getMin()) * (newHeight - contentHeight)));
+                
+                double newValueY = (oldPositionY/newPositionY)*vsb.getValue();
+                if (newValueY < 0.0) {
+                    vsb.setValue(0.0);
+                }
+                else if (newValueY < 1.0) {
+                    vsb.setValue(newValueY);
+                }
+                else if (newValueY > 1.0) {
+                    vsb.setValue(1.0);
+                }
+            }
+
+            /*
+            ** For a width change then we want to reduce
+            ** viewport horizontal jumping as much as possible. 
+            ** We set a new hsb value to try to keep the same
+            ** content position to the left of the viewport
+            */
+            double oldWidth = oldBounds.getWidth();
+            double newWidth = newBounds.getWidth();
+            if (oldWidth != newWidth) {
+                double oldPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (oldWidth - contentWidth)));
+                double newPositionX = (snapPosition(snappedLeftInset() - posX / (hsb.getMax() - hsb.getMin()) * (newWidth - contentWidth)));
+
+                double newValueX = (oldPositionX/newPositionX)*hsb.getValue();
+                if (newValueX < 0.0) {
+                    hsb.setValue(0.0);
+                }
+                else if (newValueX < 1.0) {
+                    hsb.setValue(newValueX);
+                }
+                else if (newValueX > 1.0) {
+                    hsb.setValue(1.0);
+                }
+            }
+        }
+   };
+
+
+    private void initialize() {
+        // requestLayout calls below should not trigger requestLayout above ScrollPane
+//        setManaged(false);
+
+        ScrollPane control = getSkinnable();
+        scrollNode = control.getContent();
+
+        if (scrollNode != null) {
+            scrollNode.layoutBoundsProperty().addListener(nodeListener);
+            scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
+        }
+
+        viewRect = new StackPane() {
+
+            @Override
+            protected void layoutChildren() {
+                viewContent.resize(getWidth(), getHeight());
+            }
+
+        };
+        // prevent requestLayout requests from within scrollNode from percolating up
+        viewRect.setManaged(false);
+        viewRect.setCache(true);
+
+        clipRect = new Rectangle();
+        viewRect.setClip(clipRect);
+
+        hsb = new ScrollBar();
+
+        vsb = new ScrollBar();
+        vsb.setOrientation(Orientation.VERTICAL);
+
+        corner = new StackPane();
+        corner.getStyleClass().setAll("corner");
+
+        viewContent = new StackPane() {
+            @Override public void requestLayout() {
+                // if scrollNode requested layout, will want to recompute
+                nodeWidth = -1;
+                nodeHeight = -1;
+                super.requestLayout(); // add as layout root for next layout pass
+            }
+            @Override protected void layoutChildren() {
+                if (nodeWidth == -1 || nodeHeight == -1) {
+                    computeScrollNodeSize(getWidth(),getHeight());
+                }
+                if (scrollNode != null && scrollNode.isResizable()) {
+                    scrollNode.resize(snapSize(nodeWidth), snapSize(nodeHeight));
+                    if (vsbvis != determineVerticalSBVisible() || hsbvis != determineHorizontalSBVisible()) {
+                        getSkinnable().requestLayout();
+                    }
+                }
+                if (scrollNode != null) {
+                    scrollNode.relocate(0,0);
+                }
+            }
+        };
+        viewRect.getChildren().add(viewContent);
+        
+        if (scrollNode != null) {
+            viewContent.getChildren().add(scrollNode);
+            viewRect.nodeOrientationProperty().bind(scrollNode.nodeOrientationProperty());
+        }
+
+        getChildren().clear();
+        getChildren().addAll(viewRect, vsb, hsb, corner);
+
+        /*
+        ** listeners, and assorted housekeeping
+        */
+        InvalidationListener vsbListener = new InvalidationListener() {
+            @Override public void invalidated(Observable valueModel) {
+                if (!IS_TOUCH_SUPPORTED) {
+                    posY = Utils.clamp(getSkinnable().getVmin(), vsb.getValue(), getSkinnable().getVmax());
+                }
+                else {
+                    posY = vsb.getValue();
+                }
+                updatePosY();
+            }
+        };
+        vsb.valueProperty().addListener(vsbListener);
+
+        InvalidationListener hsbListener = new InvalidationListener() {
+            @Override public void invalidated(Observable valueModel) {
+                if (!IS_TOUCH_SUPPORTED) {
+                    posX = Utils.clamp(getSkinnable().getHmin(), hsb.getValue(), getSkinnable().getHmax());
+                }
+                else {
+                    posX = hsb.getValue();
+                }
+                updatePosX();
+            }
+        };
+        hsb.valueProperty().addListener(hsbListener);
+
+        viewRect.setOnMousePressed(new EventHandler<javafx.scene.input.MouseEvent>() {
+           @Override public void handle(javafx.scene.input.MouseEvent e) {
+               if (IS_TOUCH_SUPPORTED) {
+                   startSBReleasedAnimation();
+               }
+               mouseDown = true;
+               pressX = e.getX();
+               pressY = e.getY();
+               ohvalue = hsb.getValue();
+               ovvalue = vsb.getValue();
+           }
+        });
+
+
+        viewRect.setOnDragDetected(new EventHandler<javafx.scene.input.MouseEvent>() {
+           @Override public void handle(javafx.scene.input.MouseEvent e) {
+                if (IS_TOUCH_SUPPORTED) {
+                    startSBReleasedAnimation();
+                }
+               if (getSkinnable().isPannable()) {
+                 dragDetected = true;
+                 if (saveCursor == null) {
+                     saveCursor = getSkinnable().getCursor();
+                     if (saveCursor == null) {
+                         saveCursor = Cursor.DEFAULT;
+                     }
+                     getSkinnable().setCursor(Cursor.MOVE);
+                     getSkinnable().requestLayout();
+                 }
+               }
+           }
+        });
+
+        viewRect.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
+            @Override public void handle(MouseEvent e) {
+                  if (IS_TOUCH_SUPPORTED) {
+                    startSBReleasedAnimation();
+                 }
+                 mouseDown = false;
+
+                 if (dragDetected == true) {
+                     if (saveCursor != null) {
+                         getSkinnable().setCursor(saveCursor);
+                         saveCursor = null;
+                         getSkinnable().requestLayout();
+                     }
+                     dragDetected = false;
+                 }
+
+                 /*
+                 ** if the contents need repositioning, and there's is no
+                 ** touch event in progress, then start the repositioning.
+                 */
+                 if ((posY > getSkinnable().getVmax() || posY < getSkinnable().getVmin() ||
+                     posX > getSkinnable().getHmax() || posX < getSkinnable().getHmin()) && !touchDetected) {
+                     startContentsToViewport();
+                 }
+            }
+        });
+        viewRect.setOnMouseDragged(new EventHandler<javafx.scene.input.MouseEvent>() {
+           @Override public void handle(javafx.scene.input.MouseEvent e) {
+                if (IS_TOUCH_SUPPORTED) {
+                    startSBReleasedAnimation();
+                }
+               /*
+               ** for mobile-touch we allow drag, even if not pannagle
+               */
+               if (getSkinnable().isPannable() || IS_TOUCH_SUPPORTED) {
+                   double deltaX = pressX - e.getX();
+                   double deltaY = pressY - e.getY();
+                   /*
+                   ** we only drag if not all of the content is visible.
+                   */
+                   if (hsb.getVisibleAmount() > 0.0 && hsb.getVisibleAmount() < hsb.getMax()) {
+                       if (Math.abs(deltaX) > PAN_THRESHOLD) {
+                           if (isReverseNodeOrientation()) {
+                               deltaX = -deltaX;
+                           }
+                           double newHVal = (ohvalue + deltaX / (nodeWidth - viewRect.getWidth()) * (hsb.getMax() - hsb.getMin()));
+                           if (!IS_TOUCH_SUPPORTED) {
+                               if (newHVal > hsb.getMax()) {
+                                   newHVal = hsb.getMax();
+                               }
+                               else if (newHVal < hsb.getMin()) {
+                                   newHVal = hsb.getMin();
+                               }
+                               hsb.setValue(newHVal);
+                           }
+                           else {
+                               hsb.setValue(newHVal);
+                           }
+                       }
+                   }
+                   /*
+                   ** we only drag if not all of the content is visible.
+                   */
+                   if (vsb.getVisibleAmount() > 0.0 && vsb.getVisibleAmount() < vsb.getMax()) {
+                       if (Math.abs(deltaY) > PAN_THRESHOLD) {
+                           double newVVal = (ovvalue + deltaY / (nodeHeight - viewRect.getHeight()) * (vsb.getMax() - vsb.getMin()));
+                           if (!IS_TOUCH_SUPPORTED) {
+                               if (newVVal > vsb.getMax()) {
+                                   newVVal = vsb.getMax();
+                               }
+                               else if (newVVal < vsb.getMin()) {
+                                   newVVal = vsb.getMin();
+                               }
+                               vsb.setValue(newVVal);
+                           }
+                           else {
+                               vsb.setValue(newVVal);
+                           }
+                       }
+                   }
+               }
+               /*
+               ** we need to consume drag events, as we don't want
+               ** the scrollpane itself to be dragged on every mouse click
+               */
+               e.consume();
+           }
+        });
+
+
+        /*
+        ** don't allow the ScrollBar to handle the ScrollEvent,
+        ** In a ScrollPane a vertical scroll should scroll on the vertical only,
+        ** whereas in a horizontal ScrollBar it can scroll horizontally.
+        */ 
+        final EventDispatcher blockEventDispatcher = new EventDispatcher() {
+           @Override public Event dispatchEvent(Event event, EventDispatchChain tail) {
+               // block the event from being passed down to children
+               return event;
+           }
+        };
+        // block ScrollEvent from being passed down to scrollbar's skin
+        final EventDispatcher oldHsbEventDispatcher = hsb.getEventDispatcher();
+        hsb.setEventDispatcher(new EventDispatcher() {
+           @Override public Event dispatchEvent(Event event, EventDispatchChain tail) {
+               if (event.getEventType() == ScrollEvent.SCROLL &&
+                       !((ScrollEvent)event).isDirect()) {
+                   tail = tail.prepend(blockEventDispatcher);
+                   tail = tail.prepend(oldHsbEventDispatcher);
+                   return tail.dispatchEvent(event);
+               }
+               return oldHsbEventDispatcher.dispatchEvent(event, tail);
+           }
+        });
+        // block ScrollEvent from being passed down to scrollbar's skin
+        final EventDispatcher oldVsbEventDispatcher = vsb.getEventDispatcher();
+        vsb.setEventDispatcher(new EventDispatcher() {
+           @Override public Event dispatchEvent(Event event, EventDispatchChain tail) {
+               if (event.getEventType() == ScrollEvent.SCROLL &&
+                       !((ScrollEvent)event).isDirect()) {
+                   tail = tail.prepend(blockEventDispatcher);
+                   tail = tail.prepend(oldVsbEventDispatcher);
+                   return tail.dispatchEvent(event);
+               }
+               return oldVsbEventDispatcher.dispatchEvent(event, tail);
+           }
+        });
+
+        /*
+        ** listen for ScrollEvents over the whole of the ScrollPane
+        ** area, the above dispatcher having removed the ScrollBars
+        ** scroll event handling.
+        */
+        getSkinnable().setOnScroll(new EventHandler<javafx.scene.input.ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                if (IS_TOUCH_SUPPORTED) {
+                    startSBReleasedAnimation();
+                }
+                /*
+                ** if we're completely visible then do nothing....
+                ** we only consume an event that we've used.
+                */
+                if (vsb.getVisibleAmount() < vsb.getMax()) {
+                    double vRange = getSkinnable().getVmax()-getSkinnable().getVmin();
+                    double vPixelValue;
+                    if (nodeHeight > 0.0) {
+                        vPixelValue = vRange / nodeHeight;
+                    }
+                    else {
+                        vPixelValue = 0.0;
+                    }
+                    double newValue = vsb.getValue()+(-event.getDeltaY())*vPixelValue;
+                    if (!IS_TOUCH_SUPPORTED) {
+                        if ((event.getDeltaY() > 0.0 && vsb.getValue() > vsb.getMin()) ||
+                            (event.getDeltaY() < 0.0 && vsb.getValue() < vsb.getMax())) {
+                            vsb.setValue(newValue);
+                            event.consume();
+                        }
+                    }
+                    else {
+                        /*
+                        ** if there is a repositioning in progress then we only
+                        ** set the value for 'real' events
+                        */
+                        if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
+                            vsb.setValue(newValue);
+                            if ((newValue > vsb.getMax() || newValue < vsb.getMin()) && (!mouseDown && !touchDetected)) {
+                                startContentsToViewport();
+                            }
+                            event.consume();
+                        }
+                    }
+                }
+
+                if (hsb.getVisibleAmount() < hsb.getMax()) {
+                    double hRange = getSkinnable().getHmax()-getSkinnable().getHmin();
+                    double hPixelValue;
+                    if (nodeWidth > 0.0) {
+                        hPixelValue = hRange / nodeWidth;
+                    }
+                    else {
+                        hPixelValue = 0.0;
+                    }
+
+                    double newValue = hsb.getValue()+(-event.getDeltaX())*hPixelValue;
+                    if (!IS_TOUCH_SUPPORTED) {
+                        if ((event.getDeltaX() > 0.0 && hsb.getValue() > hsb.getMin()) ||
+                            (event.getDeltaX() < 0.0 && hsb.getValue() < hsb.getMax())) {
+                            hsb.setValue(newValue);
+                            event.consume();
+                        }
+                    }
+                    else {
+                        /*
+                        ** if there is a repositioning in progress then we only
+                        ** set the value for 'real' events
+                        */
+                        if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED)) {
+                            hsb.setValue(newValue);
+
+                            if ((newValue > hsb.getMax() || newValue < hsb.getMin()) && (!mouseDown && !touchDetected)) {
+                                startContentsToViewport();
+                            }
+                            event.consume();
+                        }
+                    }
+                }
+            }
+        });
+
+        /*
+        ** there are certain animations that need to know if the touch is
+        ** happening.....
+        */
+        getSkinnable().setOnTouchPressed(new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent e) {
+                touchDetected = true;
+                startSBReleasedAnimation();
+                e.consume();
+            }
+        });
+
+        getSkinnable().setOnTouchReleased(new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent e) {
+                touchDetected = false;
+                startSBReleasedAnimation();
+                e.consume();
+            }
+        });
+
+        TraversalEngine traversalEngine = new TraversalEngine(getSkinnable(), false);
+        traversalEngine.addTraverseListener(this);
+        getSkinnable().setImpl_traversalEngine(traversalEngine);
+
+        // ScrollPanes do not block all MouseEvents by default, unlike most other UI Controls.
+        consumeMouseEvents(false);
+    }
+
+
+    @Override protected void handleControlPropertyChanged(String p) {
+        super.handleControlPropertyChanged(p);
+        if ("NODE".equals(p)) {
+            if (scrollNode != getSkinnable().getContent()) {
+                if (scrollNode != null) {
+                    scrollNode.layoutBoundsProperty().removeListener(nodeListener);
+                    scrollNode.layoutBoundsProperty().removeListener(boundsChangeListener);
+                    viewContent.getChildren().remove(scrollNode);
+                }
+                scrollNode = getSkinnable().getContent();
+                if (scrollNode != null) {
+                    nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
+                    nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
+                    viewContent.getChildren().setAll(scrollNode);
+                    scrollNode.layoutBoundsProperty().addListener(nodeListener);
+                    scrollNode.layoutBoundsProperty().addListener(boundsChangeListener);
+                }
+            }
+            getSkinnable().requestLayout();
+        } else if ("FIT_TO_WIDTH".equals(p) || "FIT_TO_HEIGHT".equals(p)) {
+            getSkinnable().requestLayout();
+            viewRect.requestLayout();
+        } else if ("HBAR_POLICY".equals(p) || "VBAR_POLICY".equals(p)) {
+            // change might affect pref size, so requestLayout on control
+            getSkinnable().requestLayout();
+        } else if ("HVALUE".equals(p)) {
+            hsb.setValue(getSkinnable().getHvalue());
+        } else if ("VVALUE".equals(p)) {
+            vsb.setValue(getSkinnable().getVvalue());
+        } else if ("PREF_VIEWPORT_WIDTH".equals(p) || "PREF_VIEWPORT_HEIGHT".equals(p)) {
+            // change affects pref size, so requestLayout on control
+            getSkinnable().requestLayout();
+        }
+    }
+    
+    void scrollBoundsIntoView(Bounds b) {
+        double dx = 0.0;
+        double dy = 0.0;
+        boolean needsLayout = false;
+        if (b.getMaxX() > contentWidth) {
+            dx = contentWidth - b.getMaxX();
+        }
+        if (b.getMinX() < 0) {
+            dx = -b.getMinX();
+        }
+        if (b.getMaxY() > contentHeight) {
+            dy = contentHeight - b.getMaxY();
+        }
+        if (b.getMinY() < 0) {
+            dy = -b.getMinY();
+        }
+        // We want to move contentPanel's layoutX,Y by (dx,dy).
+        // But to do this we have to set the scrollbars' values appropriately.
+
+        double newHvalue = hsb.getValue();
+        double newVvalue = vsb.getValue();
+        if (dx != 0) {
+            double sdx = -dx * (hsb.getMax() - hsb.getMin()) / (nodeWidth - viewRect.getWidth());
+            if (sdx < 0) {
+                sdx -= hsb.getUnitIncrement();
+            } else {
+                sdx += hsb.getUnitIncrement();
+            }
+            newHvalue = clamp(hsb.getMin(), hsb.getValue() + sdx, hsb.getMax());
+            hsb.setValue(newHvalue);
+            needsLayout = true;
+        }
+        if (dy != 0) {
+            double sdy = -dy * (vsb.getMax() - vsb.getMin()) / (nodeHeight - viewRect.getHeight());
+            if (sdy < 0) {
+                sdy -= vsb.getUnitIncrement();
+            } else {
+                sdy += vsb.getUnitIncrement();
+            }
+            newVvalue = clamp(vsb.getMin(), vsb.getValue() + sdy, vsb.getMax());
+            vsb.setValue(newVvalue);
+            needsLayout = true;
+        }
+
+        if (needsLayout == true) {
+            getSkinnable().requestLayout();
+        }
+    }
+
+    /*
+    ** auto-scroll so node is within (0,0),(contentWidth,contentHeight)
+    */
+    @Override public void onTraverse(Node n, Bounds b) {
+        scrollBoundsIntoView(b);
+    }
+
+    public void hsbIncrement() {
+        if (hsb != null) hsb.increment();
+    }
+    public void hsbDecrement() {
+        if (hsb != null) hsb.decrement();
+    }
+
+    // TODO: add page increment and decrement
+    public void hsbPageIncrement() {
+        if (hsb != null) hsb.increment();
+    }
+    // TODO: add page increment and decrement
+    public void hsbPageDecrement() {
+        if (hsb != null) hsb.decrement();
+    }
+
+    public void vsbIncrement() {
+        if (vsb != null) vsb.increment();
+    }
+    public void vsbDecrement() {
+        if (vsb != null) vsb.decrement();
+    }
+
+    // TODO: add page increment and decrement
+    public void vsbPageIncrement() {
+        if (vsb != null) vsb.increment();
+    }
+    // TODO: add page increment and decrement
+    public void vsbPageDecrement() {
+        if (vsb != null) vsb.decrement();
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Layout                                                                  *
+     *                                                                         *
+     **************************************************************************/
+
+    @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+        final ScrollPane sp = getSkinnable();
+        
+        if (sp.getPrefViewportWidth() > 0) {
+            double vsbWidth = sp.getVbarPolicy() == ScrollBarPolicy.ALWAYS? vsb.prefWidth(-1) : 0;
+            return (sp.getPrefViewportWidth() + vsbWidth + snappedLeftInset() + snappedRightInset());
+        }
+        else {
+            return DEFAULT_PREF_SIZE;
+        }
+    }
+
+    @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+        final ScrollPane sp = getSkinnable();
+        
+        if (sp.getPrefViewportHeight() > 0) {
+            double hsbHeight = sp.getHbarPolicy() == ScrollBarPolicy.ALWAYS? hsb.prefHeight(-1) : 0;
+            return (sp.getPrefViewportHeight() + hsbHeight + snappedTopInset() + snappedBottomInset());
+        }
+        else {
+            return DEFAULT_PREF_SIZE;
+        }
+    }
+
+    @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
+        double w = corner.minWidth(-1);
+        return (w > 0) ? (3 * w) : (DEFAULT_MIN_SIZE);
+    }
+
+    @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
+        double h = corner.minHeight(-1);
+        return (h > 0) ? (3 * h) : (DEFAULT_MIN_SIZE);
+    }
+
+    @Override protected void layoutChildren(final double x, final double y,
+            final double w, final double h) {
+        final ScrollPane control = getSkinnable();
+        final Insets insets = control.getInsets();
+        final Insets padding = control.getPadding();
+
+        vsb.setMin(control.getVmin());
+        vsb.setMax(control.getVmax());
+
+        //should only do this on css setup
+        hsb.setMin(control.getHmin());
+        hsb.setMax(control.getHmax());
+
+        contentWidth = w;
+        contentHeight = h;
+
+        /*
+        ** we want the scrollbars to go right to the border
+        */
+        double hsbWidth = contentWidth + padding.getLeft() + padding.getRight();
+        double vsbHeight = contentHeight + padding.getTop() + padding.getBottom();
+
+        computeScrollNodeSize(contentWidth, contentHeight);
+        computeScrollBarSize();
+        vsbvis = determineVerticalSBVisible();
+        hsbvis = determineHorizontalSBVisible();
+
+        if (vsbvis) {
+            hsbWidth -= vsbWidth;
+            if (!IS_TOUCH_SUPPORTED) {
+                contentWidth -= vsbWidth;
+            }
+        }
+        if (hsbvis) {
+            vsbHeight -= hsbHeight;
+            if (!IS_TOUCH_SUPPORTED) {
+                contentHeight -= hsbHeight;
+            }
+        }
+        if (scrollNode != null && scrollNode.isResizable()) {
+            // maybe adjust size now that scrollbars may take up space
+            if (vsbvis && hsbvis) {
+                // adjust just once to accommodate
+                computeScrollNodeSize(contentWidth, contentHeight);
+
+            } else if (hsbvis && !vsbvis) {
+                computeScrollNodeSize(contentWidth, contentHeight);
+                vsbvis = determineVerticalSBVisible();
+                if (vsbvis) {
+                    // now both are visible
+                    contentWidth -= vsbWidth;
+                    computeScrollNodeSize(contentWidth, contentHeight);
+                }
+            } else if (vsbvis && !hsbvis) {
+                computeScrollNodeSize(contentWidth, contentHeight);
+                hsbvis = determineHorizontalSBVisible();
+                if (hsbvis) {
+                    // now both are visible
+                    contentHeight -= hsbHeight;
+                    computeScrollNodeSize(contentWidth, contentHeight);
+                }
+            }
+        }
+
+        // figure out the content area that is to be filled
+        double cx = insets.getLeft()-padding.getLeft();
+        double cy = insets.getTop()-padding.getTop();
+
+        vsb.setVisible(vsbvis);
+        if (vsbvis) {
+            /*
+            ** round up position of ScrollBar, round down it's size.
+            **
+            ** Positioning the ScrollBar
+            **  The Padding should go between the content and the edge,
+            **  otherwise changes in padding move the ScrollBar, and could
+            **  in extreme cases size the ScrollBar to become unusable.
+            **  The -1, +1 plus one bit : 
+            **   If padding in => 1 then we allow one pixel to appear as the
+            **   outside border of the Scrollbar, and the rest on the inside.
+            **   If padding is < 1 then we just stick to the edge.
+            */
+            if (padding.getRight() < 1) {
+                vsb.resizeRelocate(snapPosition(control.getWidth() - (vsbWidth + (insets.getRight()-padding.getRight()))), 
+                                   snapPosition(cy), snapSize(vsbWidth), snapSize(vsbHeight));
+            }
+            else {
+                vsb.resizeRelocate(snapPosition(control.getWidth() - ((vsbWidth+1) + (insets.getRight()-padding.getRight()))), 
+                                   snapPosition(cy), snapSize(vsbWidth)+1, snapSize(vsbHeight));
+            }
+        }
+        updateVerticalSB();
+
+        hsb.setVisible(hsbvis);
+        if (hsbvis) {
+            /*
+            ** round up position of ScrollBar, round down it's size.
+            **
+            ** Positioning the ScrollBar
+            **  The Padding should go between the content and the edge,
+            **  otherwise changes in padding move the ScrollBar, and could
+            **  in extreme cases size the ScrollBar to become unusable.
+            **  The -1, +1 plus one bit : 
+            **   If padding in => 1 then we allow one pixel to appear as the
+            **   outside border of the Scrollbar, and the rest on the inside.
+            **   If padding is < 1 then we just stick to the edge.
+            */
+            if (padding.getBottom() < 1) {
+                hsb.resizeRelocate(snapPosition(cx), snapPosition(control.getHeight() - (hsbHeight + (insets.getBottom()-padding.getBottom()))), 
+                                                     snapSize(hsbWidth), snapSize(hsbHeight));
+            }
+            else {
+                hsb.resizeRelocate(snapPosition(cx), snapPosition(control.getHeight() - ((hsbHeight+1) + (insets.getBottom()-padding.getBottom()))), 
+                                                     snapSize(hsbWidth), snapSize(hsbHeight)+1);
+            }
+        }
+        updateHorizontalSB();
+
+        viewRect.resize(snapSize(contentWidth), snapSize(contentHeight));
+        resetClip();
+
+        if (vsbvis && hsbvis) {
+            corner.setVisible(true);
+            double cornerWidth = vsbWidth;
+            double cornerHeight = hsbHeight;
+
+            if (padding.getRight() >= 1) {
+                cornerWidth++;
+            }
+            if (padding.getBottom() >= 1) {
+                cornerHeight++;
+            }
+            corner.resizeRelocate(snapPosition(vsb.getLayoutX()), snapPosition(hsb.getLayoutY()), snapSize(cornerWidth), snapSize(cornerHeight));
+        } else {
+            corner.setVisible(false);
+        }
+        control.setViewportBounds(new BoundingBox(snapPosition(viewContent.getLayoutX()), snapPosition(viewContent.getLayoutY()), snapSize(contentWidth), snapSize(contentHeight)));
+    }
+    
+    private void computeScrollNodeSize(double contentWidth, double contentHeight) {
+        if (scrollNode != null) {
+            if (scrollNode.isResizable()) {
+                ScrollPane control = getSkinnable();
+                Orientation bias = scrollNode.getContentBias();
+                if (bias == null) {
+                    nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
+                                                         scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
+                    nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
+                                                          scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));
+
+                } else if (bias == Orientation.HORIZONTAL) {
+                    nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(-1),
+                                                         scrollNode.minWidth(-1),scrollNode.maxWidth(-1)));
+                    nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(nodeWidth),
+                                                          scrollNode.minHeight(nodeWidth),scrollNode.maxHeight(nodeWidth)));
+
+                } else { // bias == VERTICAL
+                    nodeHeight = snapSize(boundedSize(control.isFitToHeight()? contentHeight : scrollNode.prefHeight(-1),
+                                                          scrollNode.minHeight(-1), scrollNode.maxHeight(-1)));
+                    nodeWidth = snapSize(boundedSize(control.isFitToWidth()? contentWidth : scrollNode.prefWidth(nodeHeight),
+                                                         scrollNode.minWidth(nodeHeight),scrollNode.maxWidth(nodeHeight)));
+                }
+
+            } else {
+                nodeWidth = snapSize(scrollNode.getLayoutBounds().getWidth());
+                nodeHeight = snapSize(scrollNode.getLayoutBounds().getHeight());
+            }
+        }
+    }
+
+    private boolean isReverseNodeOrientation() {
+        return (scrollNode != null &&
+                getSkinnable().getEffectiveNodeOrientation() !=
+                            scrollNode.getEffectiveNodeOrientation());
+    }
+
+    private boolean determineHorizontalSBVisible() {
+        final ScrollPane sp = getSkinnable();
+        final double contentw = sp.getWidth() - snappedLeftInset() - snappedRightInset();
+        if (IS_TOUCH_SUPPORTED) {
+            return (tempVisibility && (nodeWidth > contentw));
+        }
+        else {
+            return (getSkinnable().getHbarPolicy().equals(ScrollBarPolicy.NEVER)) ? false :
+                ((getSkinnable().getHbarPolicy().equals(ScrollBarPolicy.ALWAYS)) ? true :
+                 ((getSkinnable().isFitToWidth() && scrollNode != null ? scrollNode.isResizable() : false) ?
+                  (nodeWidth > contentw && scrollNode.minWidth(-1) > contentw) : (nodeWidth > contentw)));
+        }
+    }
+
+    private boolean determineVerticalSBVisible() {
+        final ScrollPane sp = getSkinnable();
+        final double contenth = sp.getHeight() - snappedTopInset() - snappedBottomInset();
+        if (IS_TOUCH_SUPPORTED) {
+            return (tempVisibility && (nodeHeight > contenth));
+        }
+        else {
+            return (getSkinnable().getVbarPolicy().equals(ScrollBarPolicy.NEVER)) ? false :
+                ((getSkinnable().getVbarPolicy().equals(ScrollBarPolicy.ALWAYS)) ? true :
+                 ((getSkinnable().isFitToHeight() && scrollNode != null ? scrollNode.isResizable() : false) ?
+                  (nodeHeight > contenth && scrollNode.minHeight(-1) > contenth) : (nodeHeight > contenth)));
+        }
+    }
+
+    private void computeScrollBarSize() {
+        vsbWidth = snapSize(vsb.prefWidth(-1));
+        if (vsbWidth == 0) {
+            //            println("*** WARNING ScrollPaneSkin: can't get scroll bar width, using {DEFAULT_SB_BREADTH}");
+            if (IS_TOUCH_SUPPORTED) {
+                vsbWidth = DEFAULT_EMBEDDED_SB_BREADTH;
+            }
+            else {
+                vsbWidth = DEFAULT_SB_BREADTH;
+            }
+        }
+        hsbHeight = snapSize(hsb.prefHeight(-1));
+        if (hsbHeight == 0) {
+            //            println("*** WARNING ScrollPaneSkin: can't get scroll bar height, using {DEFAULT_SB_BREADTH}");
+            if (IS_TOUCH_SUPPORTED) {
+                hsbHeight = DEFAULT_EMBEDDED_SB_BREADTH;
+            }
+            else {
+                hsbHeight = DEFAULT_SB_BREADTH;
+            }
+        }
+    }
+
+    private void updateHorizontalSB() {
+        double contentRatio = nodeWidth * (hsb.getMax() - hsb.getMin());
+        if (contentRatio > 0.0) {
+            hsb.setVisibleAmount(contentWidth / contentRatio);
+            hsb.setBlockIncrement(0.9 * hsb.getVisibleAmount());
+            hsb.setUnitIncrement(0.1 * hsb.getVisibleAmount());
+        }
+        else {
+            hsb.setVisibleAmount(0.0);
+            hsb.setBlockIncrement(0.0);
+            hsb.setUnitIncrement(0.0);
+        }
+
+        if (hsb.isVisible()) {
+            updatePosX();
+        } else {
+            if (nodeWidth > contentWidth) {
+                updatePosX();
+            } else {
+                viewContent.setLayoutX(snappedLeftInset());
+            }
+        }
+    }
+    
+    private void updateVerticalSB() {
+        double contentRatio = nodeHeight * (vsb.getMax() - vsb.getMin());
+        if (contentRatio > 0.0) {
+            vsb.setVisibleAmount(contentHeight / contentRatio);
+            vsb.setBlockIncrement(0.9 * vsb.getVisibleAmount());
+            vsb.setUnitIncrement(0.1 * vsb.getVisibleAmount());
+        }
+        else {
+            vsb.setVisibleAmount(0.0);
+            vsb.setBlockIncrement(0.0);
+            vsb.setUnitIncrement(0.0);
+        }
+
+        if (vsb.isVisible()) {
+            updatePosY();
+        } else {
+            if (nodeHeight > contentHeight) {
+                updatePosY();
+            } else {
+                viewContent.setLayoutY(snappedTopInset());
+            }
+        }
+    }
+
+    private double updatePosX() {
+        final ScrollPane sp = getSkinnable();
+        double x = isReverseNodeOrientation() ? (hsb.getMax() - (posX - hsb.getMin())) : posX;
+        viewContent.setLayoutX(snapPosition(snappedLeftInset() - x / (hsb.getMax() - hsb.getMin()) * (nodeWidth - contentWidth)));
+        sp.setHvalue(Utils.clamp(sp.getHmin(), posX, sp.getHmax()));
+        return posX;
+    }
+
+    private double updatePosY() {
+        final ScrollPane sp = getSkinnable();
+        viewContent.setLayoutY(snapPosition(snappedTopInset() - posY / (vsb.getMax() - vsb.getMin()) * (nodeHeight - contentHeight)));
+        sp.setVvalue(Utils.clamp(sp.getVmin(), posY, sp.getVmax()));
+        return posY;
+    }
+
+    private void resetClip() {
+        clipRect.setWidth(snapSize(contentWidth));
+        clipRect.setHeight(snapSize(contentHeight));
+        clipRect.relocate(snappedLeftInset(), snappedTopInset());
+    }
+
+    Timeline sbTouchTimeline;
+    KeyFrame sbTouchKF1;
+    KeyFrame sbTouchKF2;
+    Timeline contentsToViewTimeline;
+    KeyFrame contentsToViewKF1;
+    KeyFrame contentsToViewKF2;
+    KeyFrame contentsToViewKF3;
+
+    private boolean tempVisibility;
+
+
+    protected void startSBReleasedAnimation() {
+        if (sbTouchTimeline == null) {
+            /*
+            ** timeline to leave the scrollbars visible for a short
+            ** while after a scroll/drag
+            */
+            sbTouchTimeline = new Timeline();
+            sbTouchKF1 = new KeyFrame(Duration.millis(0), new EventHandler<ActionEvent>() {
+                @Override public void handle(ActionEvent event) {
+                    tempVisibility = true;
+                }
+            });
+
+            sbTouchKF2 = new KeyFrame(Duration.millis(500), new EventHandler<ActionEvent>() {
+                @Override public void handle(ActionEvent event) {
+                    tempVisibility = false;
+                    getSkinnable().requestLayout();
+                }
+            });
+            sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2);
+        }
+        sbTouchTimeline.playFromStart();
+    }
+
+
+
+    protected void startContentsToViewport() {
+        double newPosX = posX;
+        double newPosY = posY;
+
+        setContentPosX(posX);
+        setContentPosY(posY);
+
+        if (posY > getSkinnable().getVmax()) {
+            newPosY = getSkinnable().getVmax();
+        }
+        else if (posY < getSkinnable().getVmin()) {
+            newPosY = getSkinnable().getVmin();
+        }             
+        
+
+        if (posX > getSkinnable().getHmax()) {
+            newPosX = getSkinnable().getHmax();
+        }
+        else if (posX < getSkinnable().getHmin()) {
+            newPosX = getSkinnable().getHmin();
+        }
+
+        if (!IS_TOUCH_SUPPORTED) {
+            startSBReleasedAnimation();
+        }
+
+        /*
+        ** timeline to return the contents of the scrollpane to the viewport
+        */
+        if (contentsToViewTimeline != null) {
+            contentsToViewTimeline.stop();
+        }
+        contentsToViewTimeline = new Timeline();
+        /*
+        ** short pause before animation starts
+        */
+        contentsToViewKF1 = new KeyFrame(Duration.millis(50));
+        /*
+        ** reposition
+        */
+        contentsToViewKF2 = new KeyFrame(Duration.millis(150), new EventHandler<ActionEvent>() {
+                @Override public void handle(ActionEvent event) {
+                    getSkinnable().requestLayout();
+                }
+            },
+            new KeyValue(contentPosX, newPosX),
+            new KeyValue(contentPosY, newPosY)
+            );
+        /*
+        ** block out 'aftershocks', but real events will
+        ** still reactivate
+        */
+        contentsToViewKF3 = new KeyFrame(Duration.millis(1500));
+        contentsToViewTimeline.getKeyFrames().addAll(contentsToViewKF1, contentsToViewKF2, contentsToViewKF3);
+        contentsToViewTimeline.playFromStart();
+    }
+
+
+    private DoubleProperty contentPosX;
+    private void setContentPosX(double value) { contentPosXProperty().set(value); }
+    private double getContentPosX() { return contentPosX == null ? 0.0 : contentPosX.get(); }
+    private DoubleProperty contentPosXProperty() {
+        if (contentPosX == null) {
+            contentPosX = new DoublePropertyBase() {
+                @Override protected void invalidated() {
+                    hsb.setValue(getContentPosX());
+                    getSkinnable().requestLayout();
+                }
+
+                @Override
+                public Object getBean() {
+                    return ScrollPaneSkin.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "contentPosX";
+                }
+            };
+        }
+        return contentPosX;
+    }
+
+    private DoubleProperty contentPosY;
+    private void setContentPosY(double value) { contentPosYProperty().set(value); }
+    private double getContentPosY() { return contentPosY == null ? 0.0 : contentPosY.get(); }
+    private DoubleProperty contentPosYProperty() {
+        if (contentPosY == null) {
+            contentPosY = new DoublePropertyBase() {
+                @Override protected void invalidated() {
+                    vsb.setValue(getContentPosY());
+                    getSkinnable().requestLayout();
+                }
+
+                @Override
+                public Object getBean() {
+                    return ScrollPaneSkin.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "contentPosY";
+                }
+            };
+        }
+        return contentPosY;
+    }
+}
--- a/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/TableRowSkinBase.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/TableRowSkinBase.java	Thu Aug 22 09:49:13 2013 -0400
@@ -152,6 +152,7 @@
         // --- end init bindings
 
         registerChangeListener(control.itemProperty(), "ITEM");
+        registerChangeListener(control.indexProperty(), "INDEX");
 
         if (fixedCellSizeProperty() != null) {
             registerChangeListener(fixedCellSizeProperty(), "FIXED_CELL_SIZE");
@@ -224,6 +225,12 @@
         if ("ITEM".equals(p)) {
             updateCells = true;
             getSkinnable().requestLayout();
+        } else if ("INDEX".equals(p)){
+            // update the index of all children cells (RT-29849)
+            final int newIndex = getSkinnable().getIndex();
+            for (int i = 0, max = cells.size(); i < max; i++) {
+                cells.get(i).updateIndex(newIndex);
+            }
         } else if ("FIXED_CELL_SIZE".equals(p)) {
             fixedCellSize = fixedCellSizeProperty().get();
             fixedCellSizeEnabled = fixedCellSize > 0;
--- a/modules/controls/src/main/java/javafx/scene/control/ListCell.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/ListCell.java	Thu Aug 22 09:49:13 2013 -0400
@@ -370,14 +370,17 @@
                     newValue,
                     list.getEditingIndex()));
         }
+
+        // inform parent classes of the commit, so that they can switch us
+        // out of the editing state.
+        // This MUST come before the updateItem call below, otherwise it will
+        // call cancelEdit(), resulting in both commit and cancel events being
+        // fired (as identified in RT-29650)
+        super.commitEdit(newValue);
         
         // update the item within this cell, so that it represents the new value
         updateItem(newValue, false);
 
-        // inform parent classes of the commit, so that they can switch us
-        // out of the editing state
-        super.commitEdit(newValue);
-        
         if (list != null) {
             // reset the editing index on the ListView. This must come after the
             // event is fired so that the developer on the other side can consult
--- a/modules/controls/src/main/java/javafx/scene/control/MultipleSelectionModelBase.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/MultipleSelectionModelBase.java	Thu Aug 22 09:49:13 2013 -0400
@@ -307,8 +307,11 @@
     }
 
     @Override public void clearAndSelect(int row) {
-        // clear out all other selection quietly - so that we don't fire events
-        quietClearSelection();
+        // RT-32411 We used to call quietClearSelection() here, but this
+        // resulted in the selectedItems and selectedIndices lists never
+        // reporting that they were empty.
+        // quietClearSelection();
+        clearSelection();
 
         // and select
         select(row);
--- a/modules/controls/src/main/java/javafx/scene/control/TableCell.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/TableCell.java	Thu Aug 22 09:49:13 2013 -0400
@@ -324,13 +324,16 @@
             Event.fireEvent(getTableColumn(), editEvent);
         }
 
+        // inform parent classes of the commit, so that they can switch us
+        // out of the editing state.
+        // This MUST come before the updateItem call below, otherwise it will
+        // call cancelEdit(), resulting in both commit and cancel events being
+        // fired (as identified in RT-29650)
+        super.commitEdit(newValue);
+
         // update the item within this cell, so that it represents the new value
         updateItem(newValue, false);
 
-        // inform parent classes of the commit, so that they can switch us
-        // out of the editing state
-        super.commitEdit(newValue);
-        
         if (table != null) {
             // reset the editing cell on the TableView
             table.edit(-1, null);
--- a/modules/controls/src/main/java/javafx/scene/control/TableRow.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/TableRow.java	Thu Aug 22 09:49:13 2013 -0400
@@ -323,6 +323,10 @@
         if (sm == null || sm.isCellSelectionEnabled()) return;
 
         TablePosition<T,?> editCell = table.getEditingCell();
+        if (editCell != null && editCell.getTableColumn() != null) {
+            return;
+        }
+
         boolean rowMatch = editCell == null ? false : editCell.getRow() == getIndex();
 
         if (! isEditing() && rowMatch) {
--- a/modules/controls/src/main/java/javafx/scene/control/TableView.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/TableView.java	Thu Aug 22 09:49:13 2013 -0400
@@ -1841,7 +1841,7 @@
                                 
                                 if (selectedIndices.get(row)) {
                                     selectedIndices.clear(row);
-                                    newlySelectedRows.add(row);
+                                    newlyUnselectedRows.add(row);
                                 }
                             }
                         }
@@ -2155,7 +2155,12 @@
         }
 
         @Override public void clearAndSelect(int row, TableColumn<S,?> column) {
-            quietClearSelection();
+            // RT-32411 We used to call quietClearSelection() here, but this
+            // resulted in the selectedItems and selectedIndices lists never
+            // reporting that they were empty.
+            // quietClearSelection();
+            clearSelection();
+
             select(row, column);
         }
 
--- a/modules/controls/src/main/java/javafx/scene/control/TreeCell.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/TreeCell.java	Thu Aug 22 09:49:13 2013 -0400
@@ -389,7 +389,14 @@
                     getItem(),
                     newValue));
         }
-        
+
+        // inform parent classes of the commit, so that they can switch us
+        // out of the editing state.
+        // This MUST come before the updateItem call below, otherwise it will
+        // call cancelEdit(), resulting in both commit and cancel events being
+        // fired (as identified in RT-29650)
+        super.commitEdit(newValue);
+
         // update the item within this cell, so that it represents the new value
         if (treeItem != null) {
             treeItem.setValue(newValue);
@@ -397,10 +404,6 @@
             updateItem(newValue, false);
         }
         
-        // inform parent classes of the commit, so that they can switch us
-        // out of the editing state
-        super.commitEdit(newValue);
-
         if (tree != null) {
             // reset the editing item in the TreetView
             tree.edit(null);
--- a/modules/controls/src/main/java/javafx/scene/control/TreeTableCell.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/TreeTableCell.java	Thu Aug 22 09:49:13 2013 -0400
@@ -322,13 +322,16 @@
             Event.fireEvent(getTableColumn(), editEvent);
         }
 
+        // inform parent classes of the commit, so that they can switch us
+        // out of the editing state.
+        // This MUST come before the updateItem call below, otherwise it will
+        // call cancelEdit(), resulting in both commit and cancel events being
+        // fired (as identified in RT-29650)
+        super.commitEdit(newValue);
+
         // update the item within this cell, so that it represents the new value
         updateItem(newValue, false);
 
-        // inform parent classes of the commit, so that they can switch us
-        // out of the editing state
-        super.commitEdit(newValue);
-        
         if (table != null) {
             // reset the editing cell on the TableView
             table.edit(-1, null);
--- a/modules/controls/src/main/java/javafx/scene/control/TreeTableRow.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/TreeTableRow.java	Thu Aug 22 09:49:13 2013 -0400
@@ -446,6 +446,10 @@
         if (getIndex() == -1 || getTreeTableView() == null || getTreeItem() == null) return;
 
         final TreeTablePosition<T,?> editingCell = getTreeTableView().getEditingCell();
+        if (editingCell != null && editingCell.getTableColumn() != null) {
+            return;
+        }
+
         final TreeItem<T> editItem = editingCell == null ? null : editingCell.getTreeItem();
         if (! isEditing() && getTreeItem().equals(editItem)) {
             startEdit();
--- a/modules/controls/src/main/java/javafx/scene/control/TreeTableView.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/TreeTableView.java	Thu Aug 22 09:49:13 2013 -0400
@@ -2131,7 +2131,7 @@
                                 
                                 if (selectedIndices.get(row)) {
                                     selectedIndices.clear(row);
-                                    newlySelectedRows.add(row);
+                                    newlyUnselectedRows.add(row);
                                 }
                             }
                         }
@@ -2393,7 +2393,12 @@
         }
 
         @Override public void clearAndSelect(int row, TableColumnBase<TreeItem<S>,?> column) {
-            quietClearSelection();
+            // RT-32411 We used to call quietClearSelection() here, but this
+            // resulted in the selectedItems and selectedIndices lists never
+            // reporting that they were empty.
+            // quietClearSelection();
+            clearSelection();
+
             select(row, column);
         }
 
--- a/modules/controls/src/main/java/javafx/scene/control/cell/TextFieldTreeCell.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/main/java/javafx/scene/control/cell/TextFieldTreeCell.java	Thu Aug 22 09:49:13 2013 -0400
@@ -218,7 +218,6 @@
         super.updateItem(item, empty);
         
         TreeItem<T> treeItem = getTreeItem();
-        Node graphic = treeItem == null ? null : treeItem.getGraphic();
         CellUtils.updateItem(this, getConverter(), hbox, getTreeItemGraphic(), textField);
     }
     
--- a/modules/controls/src/test/java/javafx/scene/control/ListViewTest.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/test/java/javafx/scene/control/ListViewTest.java	Thu Aug 22 09:49:13 2013 -0400
@@ -36,6 +36,7 @@
 
 import java.util.Arrays;
 
+import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
 import javafx.beans.property.SimpleObjectProperty;
@@ -43,9 +44,14 @@
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.event.Event;
+import javafx.event.EventHandler;
 import javafx.scene.control.cell.CheckBoxListCell;
 import javafx.scene.control.cell.ComboBoxListCell;
+import javafx.scene.control.cell.TextFieldListCell;
+import javafx.scene.control.cell.TextFieldTreeCell;
 import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyCode;
 import javafx.scene.layout.VBox;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.Rectangle;
@@ -734,4 +740,46 @@
         VirtualFlowTestUtils.assertGraphicIsNotVisible(listView, 5);
     }
 
+    private int rt_29650_start_count = 0;
+    private int rt_29650_commit_count = 0;
+    private int rt_29650_cancel_count = 0;
+    @Test public void test_rt_29650() {
+        listView.setOnEditStart(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_start_count++;
+            }
+        });
+        listView.setOnEditCommit(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_commit_count++;
+            }
+        });
+        listView.setOnEditCancel(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_cancel_count++;
+            }
+        });
+
+        listView.getItems().setAll("one", "two", "three", "four", "five");
+        listView.setEditable(true);
+        listView.setCellFactory(TextFieldListCell.forListView());
+
+        new StageLoader(listView);
+
+        listView.edit(0);
+
+        Toolkit.getToolkit().firePulse();
+
+        ListCell rootCell = (ListCell) VirtualFlowTestUtils.getCell(listView, 0);
+        TextField textField = (TextField) rootCell.getGraphic();
+        textField.setText("Testing!");
+        KeyEventFirer keyboard = new KeyEventFirer(textField);
+        keyboard.doKeyPress(KeyCode.ENTER);
+
+        // TODO should the following assert be enabled?
+//        assertEquals("Testing!", listView.getItems().get(0));
+        assertEquals(1, rt_29650_start_count);
+        assertEquals(1, rt_29650_commit_count);
+        assertEquals(0, rt_29650_cancel_count);
+    }
 }
--- a/modules/controls/src/test/java/javafx/scene/control/MultipleSelectionModelImplTest.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/test/java/javafx/scene/control/MultipleSelectionModelImplTest.java	Thu Aug 22 09:49:13 2013 -0400
@@ -842,4 +842,58 @@
         assertTrue(msModel().isSelected(3));
         assertTrue(cell_3.isSelected());
     }
+
+    private int rt_32411_add_count = 0;
+    private int rt_32411_remove_count = 0;
+    @Test public void test_rt_32411_selectedItems() {
+        model.getSelectedItems().addListener(new ListChangeListener<String>() {
+            @Override public void onChanged(final Change<? extends String> change) {
+                while (change.next()) {
+                    rt_32411_remove_count += change.getRemovedSize();
+                    rt_32411_add_count += change.getAddedSize();
+                }
+            }
+        });
+
+        // reset fields
+        rt_32411_add_count = 0;
+        rt_32411_remove_count = 0;
+
+        // select a row - no problems here
+        model.select(2);
+        assertEquals(1, rt_32411_add_count);
+        assertEquals(0, rt_32411_remove_count);
+
+        // clear and select a new row. We should receive a remove event followed
+        // by an added event - but this bug shows we don't get the remove event.
+        model.clearAndSelect(4);
+        assertEquals(2, rt_32411_add_count);
+        assertEquals(1, rt_32411_remove_count);
+    }
+
+    @Test public void test_rt_32411_selectedIndices() {
+        model.getSelectedIndices().addListener(new ListChangeListener<Number>() {
+            @Override public void onChanged(final Change<? extends Number> change) {
+                while (change.next()) {
+                    rt_32411_remove_count += change.getRemovedSize();
+                    rt_32411_add_count += change.getAddedSize();
+                }
+            }
+        });
+
+        // reset fields
+        rt_32411_add_count = 0;
+        rt_32411_remove_count = 0;
+
+        // select a row - no problems here
+        model.select(2);
+        assertEquals(1, rt_32411_add_count);
+        assertEquals(0, rt_32411_remove_count);
+
+        // clear and select a new row. We should receive a remove event followed
+        // by an added event - but this bug shows we don't get the remove event.
+        model.clearAndSelect(4);
+        assertEquals(2, rt_32411_add_count);
+        assertEquals(1, rt_32411_remove_count);
+    }
 }
--- a/modules/controls/src/test/java/javafx/scene/control/TableViewTest.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/test/java/javafx/scene/control/TableViewTest.java	Thu Aug 22 09:49:13 2013 -0400
@@ -32,6 +32,7 @@
 
 import java.util.*;
 
+import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
 import com.sun.javafx.scene.control.infrastructure.StageLoader;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyBooleanWrapper;
@@ -40,12 +41,11 @@
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.event.Event;
 import javafx.event.EventHandler;
-import javafx.scene.control.cell.CheckBoxTableCell;
-import javafx.scene.control.cell.ChoiceBoxTableCell;
-import javafx.scene.control.cell.ComboBoxListCell;
-import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.scene.control.cell.*;
 import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyCode;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.Rectangle;
 import javafx.util.Callback;
@@ -1792,4 +1792,84 @@
         assertEquals(200, newWidth, 0.0);
         assertTrue(initialWidth != newWidth);
     }
+
+    private int rt_29650_start_count = 0;
+    private int rt_29650_commit_count = 0;
+    private int rt_29650_cancel_count = 0;
+    @Test public void test_rt_29650() {
+        TableView<Person> table = new TableView<>();
+        table.setEditable(true);
+        table.setItems(FXCollections.observableArrayList(
+                new Person("John", "Smith", "jacob.smith@example.com")
+        ));
+
+        TableColumn<Person,String> first = new TableColumn<Person,String>("first");
+        first.setCellValueFactory(new PropertyValueFactory("firstName"));
+        first.setCellFactory(TextFieldTableCell.forTableColumn());
+        table.getColumns().addAll(first);
+        first.setOnEditStart(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_start_count++;
+            }
+        });
+        first.setOnEditCommit(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_commit_count++;
+            }
+        });
+        first.setOnEditCancel(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_cancel_count++;
+            }
+        });
+
+        new StageLoader(table);
+
+        table.edit(0, first);
+
+        Toolkit.getToolkit().firePulse();
+
+        TableCell rootCell = (TableCell) VirtualFlowTestUtils.getCell(table, 0, 0);
+        TextField textField = (TextField) rootCell.getGraphic();
+        textField.setText("Testing!");
+        KeyEventFirer keyboard = new KeyEventFirer(textField);
+        keyboard.doKeyPress(KeyCode.ENTER);
+
+        // TODO should the following assert be enabled?
+//        assertEquals("Testing!", listView.getItems().get(0));
+        assertEquals(1, rt_29650_start_count);
+        assertEquals(1, rt_29650_commit_count);
+        assertEquals(0, rt_29650_cancel_count);
+    }
+
+    private int rt_29849_start_count = 0;
+    @Test public void test_rt_29849() {
+        TableView<Person> table = new TableView<>();
+        table.setEditable(true);
+        table.setItems(FXCollections.observableArrayList(
+            new Person("John", "Smith", "jacob.smith@example.com")
+        ));
+
+        TableColumn<Person,String> first = new TableColumn<Person,String>("first");
+        first.setCellValueFactory(new PropertyValueFactory("firstName"));
+        first.setCellFactory(TextFieldTableCell.forTableColumn());
+        table.getColumns().addAll(first);
+        first.setOnEditStart(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29849_start_count++;
+            }
+        });
+
+        // load the table so the default cells are created
+        new StageLoader(table);
+
+        // now replace the cell factory
+        first.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
+
+        Toolkit.getToolkit().firePulse();
+
+        // now start an edit and count the start edit events - it should be just 1
+        table.edit(0, first);
+        assertEquals(1, rt_29849_start_count);
+    }
 }
--- a/modules/controls/src/test/java/javafx/scene/control/TreeTableViewTest.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/test/java/javafx/scene/control/TreeTableViewTest.java	Thu Aug 22 09:49:13 2013 -0400
@@ -39,6 +39,7 @@
 import java.util.List;
 import java.util.Random;
 
+import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
 import com.sun.javafx.scene.control.test.Data;
 
 import javafx.beans.InvalidationListener;
@@ -52,16 +53,15 @@
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.event.Event;
 import javafx.event.EventHandler;
 import javafx.scene.Group;
 import javafx.scene.Node;
 import javafx.scene.Scene;
 import javafx.scene.control.TreeTableView.TreeTableViewFocusModel;
-import javafx.scene.control.cell.CheckBoxTreeTableCell;
-import javafx.scene.control.cell.PropertyValueFactory;
-import javafx.scene.control.cell.TextFieldTreeTableCell;
-import javafx.scene.control.cell.TreeItemPropertyValueFactory;
+import javafx.scene.control.cell.*;
 import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyCode;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.Circle;
 import javafx.scene.shape.Rectangle;
@@ -2571,4 +2571,88 @@
 
         assertEquals(treeTableView.contentWidth, col.getWidth(), 0.0);
     }
+
+    private int rt_29650_start_count = 0;
+    private int rt_29650_commit_count = 0;
+    private int rt_29650_cancel_count = 0;
+    @Test public void test_rt_29650() {
+        installChildren();
+        treeTableView.setEditable(true);
+
+        TreeTableColumn<String, String> col = new TreeTableColumn<>("column");
+        col.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
+        col.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<String, String>, ObservableValue<String>>() {
+            @Override public ObservableValue<String> call(TreeTableColumn.CellDataFeatures<String, String> param) {
+                return new ReadOnlyObjectWrapper<>(param.getValue().getValue());
+            }
+        });
+        treeTableView.getColumns().add(col);
+
+        col.setOnEditStart(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_start_count++;
+            }
+        });
+        col.setOnEditCommit(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_commit_count++;
+            }
+        });
+        col.setOnEditCancel(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_cancel_count++;
+            }
+        });
+
+        new StageLoader(treeTableView);
+
+        treeTableView.edit(0, col);
+
+        Toolkit.getToolkit().firePulse();
+
+        TreeTableCell rootCell = (TreeTableCell) VirtualFlowTestUtils.getCell(treeTableView, 0, 0);
+        TextField textField = (TextField) rootCell.getGraphic();
+        textField.setText("Testing!");
+        KeyEventFirer keyboard = new KeyEventFirer(textField);
+        keyboard.doKeyPress(KeyCode.ENTER);
+
+        // TODO should the following assert be enabled?
+//        assertEquals("Testing!", listView.getItems().get(0));
+        assertEquals(1, rt_29650_start_count);
+        assertEquals(1, rt_29650_commit_count);
+        assertEquals(0, rt_29650_cancel_count);
+    }
+
+    private int rt_29849_start_count = 0;
+    @Test public void test_rt_29849() {
+        installChildren();
+        treeTableView.setEditable(true);
+
+        TreeTableColumn<String, String> col = new TreeTableColumn<>("column");
+        col.setEditable(true);
+        col.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<String, String>, ObservableValue<String>>() {
+            @Override public ObservableValue<String> call(TreeTableColumn.CellDataFeatures<String, String> param) {
+                return new ReadOnlyObjectWrapper<>(param.getValue().getValue());
+            }
+        });
+        treeTableView.getColumns().add(col);
+
+        col.setOnEditStart(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29849_start_count++;
+            }
+        });
+
+        // load the table so the default cells are created
+        new StageLoader(treeTableView);
+
+        // now replace the cell factory
+        col.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
+
+        Toolkit.getToolkit().firePulse();
+
+        // now start an edit and count the start edit events - it should be just 1
+        treeTableView.edit(0, col);
+        assertEquals(1, rt_29849_start_count);
+    }
 }
--- a/modules/controls/src/test/java/javafx/scene/control/TreeViewTest.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/controls/src/test/java/javafx/scene/control/TreeViewTest.java	Thu Aug 22 09:49:13 2013 -0400
@@ -27,6 +27,7 @@
 
 import com.sun.javafx.application.PlatformImpl;
 import com.sun.javafx.runtime.VersionInfo;
+import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
 import com.sun.javafx.scene.control.infrastructure.StageLoader;
 import com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;
 import com.sun.javafx.scene.control.skin.VirtualScrollBar;
@@ -52,6 +53,8 @@
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
+import javafx.event.Event;
+import javafx.event.EventHandler;
 import javafx.scene.Group;
 import javafx.scene.Node;
 import javafx.scene.Scene;
@@ -60,6 +63,7 @@
 import javafx.scene.control.cell.TextFieldTreeCell;
 import javafx.scene.control.cell.TreeItemPropertyValueFactory;
 import javafx.scene.image.ImageView;
+import javafx.scene.input.KeyCode;
 import javafx.scene.layout.VBox;
 import javafx.scene.paint.Color;
 import javafx.scene.shape.Circle;
@@ -1147,4 +1151,43 @@
         VirtualFlowTestUtils.assertGraphicIsNotVisible(treeView, 4);
         VirtualFlowTestUtils.assertGraphicIsNotVisible(treeView, 5);
     }
+
+    private int rt_29650_start_count = 0;
+    private int rt_29650_commit_count = 0;
+    private int rt_29650_cancel_count = 0;
+    @Test public void test_rt_29650() {
+        installChildren();
+        treeView.setOnEditStart(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_start_count++;
+            }
+        });
+        treeView.setOnEditCommit(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_commit_count++;
+            }
+        });
+        treeView.setOnEditCancel(new EventHandler() {
+            @Override public void handle(Event t) {
+                rt_29650_cancel_count++;
+            }
+        });
+
+        treeView.setEditable(true);
+        treeView.setCellFactory(TextFieldTreeCell.forTreeView());
+
+        new StageLoader(treeView);
+
+        treeView.edit(root);
+        TreeCell rootCell = (TreeCell) VirtualFlowTestUtils.getCell(treeView, 0);
+        TextField textField = (TextField) rootCell.getGraphic();
+        textField.setText("Testing!");
+        KeyEventFirer keyboard = new KeyEventFirer(textField);
+        keyboard.doKeyPress(KeyCode.ENTER);
+
+        assertEquals("Testing!", root.getValue());
+        assertEquals(1, rt_29650_start_count);
+        assertEquals(1, rt_29650_commit_count);
+        assertEquals(0, rt_29650_cancel_count);
+    }
 }
--- a/modules/web/src/main/java/com/sun/javafx/scene/web/skin/HTMLEditorSkin.java	Tue Aug 20 11:37:49 2013 -0700
+++ b/modules/web/src/main/java/com/sun/javafx/scene/web/skin/HTMLEditorSkin.java	Thu Aug 22 09:49:13 2013 -0400
@@ -243,7 +243,7 @@
     private TraversalEngine engine;
 
     private boolean resetToolbarState = false;
-    private String cachedHTMLText = "<html><body></body></html>";
+    private String cachedHTMLText = "<html><head></head><body contenteditable=\"true\"></body></html>";
     private ListChangeListener<Node> itemsListener = new ListChangeListener<Node>() {
         @Override public void onChanged(ListChangeListener.Change<? extends Node> c) {
             while (c.next()) {