changeset 715:2e013bdbf6b9

Automated merge with ssh://jfxsrc.us.oracle.com//javafx/2.1/MASTER/jfx/rt
author kcr
date Thu, 29 Mar 2012 15:10:33 -0700
parents 4d1d81b2c178 dc9b910d1e55
children 412f8e73634c
files .hgtags
diffstat 73 files changed, 9607 insertions(+), 658 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Wed Mar 28 12:38:20 2012 -0700
+++ b/.hgtags	Thu Mar 29 15:10:33 2012 -0700
@@ -15,6 +15,8 @@
 fc8a4ecd18dbe182aac3e0f3caaac110e3e4ca4f 2.1-b15
 159ae36c5cbe2c42e47331a46a976c9d65738b22 2.1-b16
 fbd9064f3f17727254745b6d6ae5a8ef1f2c066c 2.1-b17
+3e46780eb7b965af2664eb16bc8e5b4e3abf8ccb 2.2-b01
 226d6fc60d699af3a37483b7fd52c44211181259 2.1-b18
+d4b1859ad6292248b25e0b2e89e7944cce35620f 2.2-b02
 37d21fdd92853fa5a75bf2e5c836baedb6300996 2.1-b19
 b07470e9d56e9b35d0c75d481c3a997f9c29d5e5 2.1-b20
--- a/javafx-ui-charts/src/javafx/scene/chart/PieChart.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-charts/src/javafx/scene/chart/PieChart.java	Thu Mar 29 15:10:33 2012 -0700
@@ -31,6 +31,7 @@
 import javafx.animation.Interpolator;
 import javafx.animation.KeyFrame;
 import javafx.animation.KeyValue;
+import javafx.beans.DefaultProperty;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.DoubleProperty;
 import javafx.beans.property.DoublePropertyBase;
@@ -80,6 +81,7 @@
  * pie slice labels or not.
  *
  */
+@DefaultProperty("data")
 public class PieChart extends Chart {
 
     // -------------- PRIVATE FIELDS -----------------------------------------------------------------------------------
--- a/javafx-ui-charts/src/javafx/scene/chart/XYChart.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-charts/src/javafx/scene/chart/XYChart.java	Thu Mar 29 15:10:33 2012 -0700
@@ -33,6 +33,7 @@
 import javafx.animation.Interpolator;
 import javafx.animation.KeyFrame;
 import javafx.animation.KeyValue;
+import javafx.beans.DefaultProperty;
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ObjectPropertyBase;
@@ -1406,6 +1407,7 @@
     /**
      * A named series of data items
      */
+    @DefaultProperty("data")
     public static final class Series<X,Y> {
 
         // -------------- PRIVATE PROPERTIES ----------------------------------------
--- a/javafx-ui-common/src/com/sun/javafx/scene/EventHandlerProperties.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/scene/EventHandlerProperties.java	Thu Mar 29 15:10:33 2012 -0700
@@ -38,7 +38,11 @@
 
 import com.sun.javafx.event.EventHandlerManager;
 import javafx.scene.input.MouseDragEvent;
+import javafx.scene.input.RotateEvent;
 import javafx.scene.input.ScrollEvent;
+import javafx.scene.input.SwipeEvent;
+import javafx.scene.input.TouchEvent;
+import javafx.scene.input.ZoomEvent;
 
 public final class EventHandlerProperties {
     private final EventHandlerManager eventDispatcher;
@@ -221,6 +225,210 @@
         return onScroll;
     }
 
+    private EventHandlerProperty<ScrollEvent> onScrollStarted;
+
+    public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
+        return (onScrollStarted == null) ? null : onScrollStarted.get();
+    }
+
+    public ObjectProperty<EventHandler<? super ScrollEvent>>
+            onScrollStartedProperty() {
+        if (onScrollStarted == null) {
+            onScrollStarted = new EventHandlerProperty<ScrollEvent>(
+                                     bean,
+                                     "onScrollStarted",
+                                     ScrollEvent.SCROLL_STARTED);
+        }
+        return onScrollStarted;
+    }
+
+    private EventHandlerProperty<ScrollEvent> onScrollFinished;
+
+    public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
+        return (onScrollFinished == null) ? null : onScrollFinished.get();
+    }
+
+    public ObjectProperty<EventHandler<? super ScrollEvent>>
+            onScrollFinishedProperty() {
+        if (onScrollFinished == null) {
+            onScrollFinished = new EventHandlerProperty<ScrollEvent>(
+                                     bean,
+                                     "onScrollFinished",
+                                     ScrollEvent.SCROLL_FINISHED);
+        }
+        return onScrollFinished;
+    }
+
+    private EventHandlerProperty<RotateEvent> onRotationStarted;
+
+    public final EventHandler<? super RotateEvent> getOnRotationStarted() {
+        return (onRotationStarted == null) ? null : onRotationStarted.get();
+    }
+
+    public ObjectProperty<EventHandler<? super RotateEvent>>
+            onRotationStartedProperty() {
+        if (onRotationStarted == null) {
+            onRotationStarted = new EventHandlerProperty<RotateEvent>(
+                                     bean,
+                                     "onRotationStarted",
+                                     RotateEvent.ROTATION_STARTED);
+        }
+        return onRotationStarted;
+    }
+
+    private EventHandlerProperty<RotateEvent> onRotate;
+
+    public final EventHandler<? super RotateEvent> getOnRotate() {
+        return (onRotate == null) ? null : onRotate.get();
+    }
+
+    public ObjectProperty<EventHandler<? super RotateEvent>>
+            onRotateProperty() {
+        if (onRotate == null) {
+            onRotate = new EventHandlerProperty<RotateEvent>(
+                                     bean,
+                                     "onRotate",
+                                     RotateEvent.ROTATE);
+        }
+        return onRotate;
+    }
+
+    private EventHandlerProperty<RotateEvent> onRotationFinished;
+
+    public final EventHandler<? super RotateEvent> getOnRotationFinished() {
+        return (onRotationFinished == null) ? null : onRotationFinished.get();
+    }
+
+    public ObjectProperty<EventHandler<? super RotateEvent>>
+            onRotationFinishedProperty() {
+        if (onRotationFinished == null) {
+            onRotationFinished = new EventHandlerProperty<RotateEvent>(
+                                     bean,
+                                     "onRotationFinished",
+                                     RotateEvent.ROTATION_FINISHED);
+        }
+        return onRotationFinished;
+    }
+
+    private EventHandlerProperty<ZoomEvent> onZoomStarted;
+
+    public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
+        return (onZoomStarted == null) ? null : onZoomStarted.get();
+    }
+
+    public ObjectProperty<EventHandler<? super ZoomEvent>>
+            onZoomStartedProperty() {
+        if (onZoomStarted == null) {
+            onZoomStarted = new EventHandlerProperty<ZoomEvent>(
+                                     bean,
+                                     "onZoomStarted",
+                                     ZoomEvent.ZOOM_STARTED);
+        }
+        return onZoomStarted;
+    }
+
+    private EventHandlerProperty<ZoomEvent> onZoom;
+
+    public final EventHandler<? super ZoomEvent> getOnZoom() {
+        return (onZoom == null) ? null : onZoom.get();
+    }
+
+    public ObjectProperty<EventHandler<? super ZoomEvent>>
+            onZoomProperty() {
+        if (onZoom == null) {
+            onZoom = new EventHandlerProperty<ZoomEvent>(
+                                     bean,
+                                     "onZoom",
+                                     ZoomEvent.ZOOM);
+        }
+        return onZoom;
+    }
+
+    private EventHandlerProperty<ZoomEvent> onZoomFinished;
+
+    public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
+        return (onZoomFinished == null) ? null : onZoomFinished.get();
+    }
+
+    public ObjectProperty<EventHandler<? super ZoomEvent>>
+            onZoomFinishedProperty() {
+        if (onZoomFinished == null) {
+            onZoomFinished = new EventHandlerProperty<ZoomEvent>(
+                                     bean,
+                                     "onZoomFinished",
+                                     ZoomEvent.ZOOM_FINISHED);
+        }
+        return onZoomFinished;
+    }
+
+    private EventHandlerProperty<SwipeEvent> onSwipeUp;
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
+        return (onSwipeUp == null) ? null : onSwipeUp.get();
+    }
+
+    public ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeUpProperty() {
+        if (onSwipeUp == null) {
+            onSwipeUp = new EventHandlerProperty<SwipeEvent>(
+                                     bean,
+                                     "onSwipeUp",
+                                     SwipeEvent.SWIPE_UP);
+        }
+        return onSwipeUp;
+    }
+
+    private EventHandlerProperty<SwipeEvent> onSwipeDown;
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
+        return (onSwipeDown == null) ? null : onSwipeDown.get();
+    }
+
+    public ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeDownProperty() {
+        if (onSwipeDown == null) {
+            onSwipeDown = new EventHandlerProperty<SwipeEvent>(
+                                     bean,
+                                     "onSwipeDown",
+                                     SwipeEvent.SWIPE_DOWN);
+        }
+        return onSwipeDown;
+    }
+
+    private EventHandlerProperty<SwipeEvent> onSwipeLeft;
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
+        return (onSwipeLeft == null) ? null : onSwipeLeft.get();
+    }
+
+    public ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeLeftProperty() {
+        if (onSwipeLeft == null) {
+            onSwipeLeft = new EventHandlerProperty<SwipeEvent>(
+                                     bean,
+                                     "onSwipeLeft",
+                                     SwipeEvent.SWIPE_LEFT);
+        }
+        return onSwipeLeft;
+    }
+
+    private EventHandlerProperty<SwipeEvent> onSwipeRight;
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
+        return (onSwipeRight == null) ? null : onSwipeRight.get();
+    }
+
+    public ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeRightProperty() {
+        if (onSwipeRight == null) {
+            onSwipeRight = new EventHandlerProperty<SwipeEvent>(
+                                     bean,
+                                     "onSwipeRight",
+                                     SwipeEvent.SWIPE_RIGHT);
+        }
+        return onSwipeRight;
+    }
+
     private EventHandlerProperty<MouseDragEvent> onMouseDragOver;
 
     public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
@@ -478,4 +686,72 @@
             eventDispatcher.setEventHandler(eventType, get());
         }
     }
+
+    private EventHandlerProperty<TouchEvent> onTouchPressed;
+
+    public final EventHandler<? super TouchEvent> getOnTouchPressed() {
+        return (onTouchPressed == null) ? null : onTouchPressed.get();
+    }
+
+    public ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchPressedProperty() {
+        if (onTouchPressed == null) {
+            onTouchPressed = new EventHandlerProperty<TouchEvent>(
+                                     bean,
+                                     "onTouchPressed",
+                                     TouchEvent.TOUCH_PRESSED);
+        }
+        return onTouchPressed;
+    }
+
+    private EventHandlerProperty<TouchEvent> onTouchMoved;
+
+    public final EventHandler<? super TouchEvent> getOnTouchMoved() {
+        return (onTouchMoved == null) ? null : onTouchMoved.get();
+    }
+
+    public ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchMovedProperty() {
+        if (onTouchMoved == null) {
+            onTouchMoved = new EventHandlerProperty<TouchEvent>(
+                                     bean,
+                                     "onTouchMoved",
+                                     TouchEvent.TOUCH_MOVED);
+        }
+        return onTouchMoved;
+    }
+
+    private EventHandlerProperty<TouchEvent> onTouchReleased;
+
+    public final EventHandler<? super TouchEvent> getOnTouchReleased() {
+        return (onTouchReleased == null) ? null : onTouchReleased.get();
+    }
+
+    public ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchReleasedProperty() {
+        if (onTouchReleased == null) {
+            onTouchReleased = new EventHandlerProperty<TouchEvent>(
+                                     bean,
+                                     "onTouchReleased",
+                                     TouchEvent.TOUCH_RELEASED);
+        }
+        return onTouchReleased;
+    }
+
+    private EventHandlerProperty<TouchEvent> onTouchStationary;
+
+    public final EventHandler<? super TouchEvent> getOnTouchStationary() {
+        return (onTouchStationary == null) ? null : onTouchStationary.get();
+    }
+
+    public ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchStationaryProperty() {
+        if (onTouchStationary == null) {
+            onTouchStationary = new EventHandlerProperty<TouchEvent>(
+                                     bean,
+                                     "onTouchStationary",
+                                     TouchEvent.TOUCH_STATIONARY);
+        }
+        return onTouchStationary;
+    }
 }
--- a/javafx-ui-common/src/com/sun/javafx/stage/StagePeerListener.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/stage/StagePeerListener.java	Thu Mar 29 15:10:33 2012 -0700
@@ -29,26 +29,34 @@
 
 
 public class StagePeerListener extends WindowPeerListener {
+    private final Stage stage;
+    private final StageAccessor stageAccessor;
 
-    private Stage stage;
+    public static interface StageAccessor {
+        public void setIconified(Stage stage, boolean iconified);
+        public void setResizable(Stage stage, boolean resizable);
+        public void setFullScreen(Stage stage, boolean fs);
+    }
 
-    public StagePeerListener(Stage stage) {
+    public StagePeerListener(Stage stage, StageAccessor stageAccessor) {
         super(stage);
         this.stage = stage;
+        this.stageAccessor = stageAccessor;
     }
 
+
     @Override
     public void changedIconified(boolean iconified) {
-        stage.setIconified(iconified);
+        stageAccessor.setIconified(stage, iconified);
     }
 
     @Override
     public void changedResizable(boolean resizable) {
-        stage.setResizable(resizable);
+        stageAccessor.setResizable(stage, resizable);
     }
 
     @Override
     public void changedFullscreen(boolean fs) {
-        stage.setFullScreen(fs);
+        stageAccessor.setFullScreen(stage, fs);
     }
 }
--- a/javafx-ui-common/src/com/sun/javafx/tk/TKSceneListener.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/tk/TKSceneListener.java	Thu Mar 29 15:10:33 2012 -0700
@@ -43,6 +43,13 @@
  */
 package com.sun.javafx.tk;
 
+import javafx.scene.input.TouchPoint;
+import javafx.event.EventType;
+import javafx.scene.input.RotateEvent;
+import javafx.scene.input.ScrollEvent;
+import javafx.scene.input.SwipeEvent;
+import javafx.scene.input.ZoomEvent;
+
 /**
  * TKSceneListener - Listener for the Scene Peer TKScene to pass updates and events back to the scene
  *
@@ -88,15 +95,49 @@
     public void inputMethodEvent(Object event);
 
     public void scrollEvent(
-            double scrollX, double scrollY,
-            double xMultiplier, double yMultiplier,
+            EventType<ScrollEvent> eventType, double scrollX, double scrollY,
+            double totalScrollX, double totalScrollY,
+            double xMultiplier, double yMultiplier, int touchCount,
             int scrollTextX, int scrollTextY,
             int defaultTextX, int defaultTextY,
             double x, double y, double screenX, double screenY,
             boolean _shiftDown, boolean _controlDown,
-            boolean _altDown, boolean _metaDown);
+            boolean _altDown, boolean _metaDown, 
+            boolean _direct, boolean _inertia);
 
     public void menuEvent(double x, double y, double xAbs, double yAbs,
             boolean isKeyboardTrigger);
+    
+    public void zoomEvent(
+            EventType<ZoomEvent> eventType,
+            double zoomFactor, double totalZoomFactor,
+            double x, double y, double screenX, double screenY,
+            boolean _shiftDown, boolean _controlDown,
+            boolean _altDown, boolean _metaDown, 
+            boolean _direct, boolean _inertia);
+
+    public void rotateEvent(
+            EventType<RotateEvent> eventType, double angle, double totalAngle,
+            double x, double y, double screenX, double screenY,
+            boolean _shiftDown, boolean _controlDown,
+            boolean _altDown, boolean _metaDown, 
+            boolean _direct, boolean _inertia);
+
+    public void swipeEvent(
+            EventType<SwipeEvent> eventType, int touchCount,
+            double x, double y, double screenX, double screenY,
+            boolean _shiftDown, boolean _controlDown,
+            boolean _altDown, boolean _metaDown, boolean _direct);
+
+    public void touchEventBegin(
+            long time, int touchCount, boolean isDirect,
+            boolean _shiftDown, boolean _controlDown,
+            boolean _altDown, boolean _metaDown);
+
+    public void touchEventNext(
+            TouchPoint.State state, long touchId,
+            int x, int y, int xAbs, int yAbs);
+
+    public void touchEventEnd();
 
 }
--- a/javafx-ui-common/src/javafx/application/ConditionalFeature.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/application/ConditionalFeature.java	Thu Mar 29 15:10:33 2012 -0700
@@ -68,5 +68,16 @@
      * If an application specifies an input method on a platform that does
      * not support it, the input method will be ignored.
      */
-    INPUT_METHOD
+    INPUT_METHOD,
+
+    /**
+     * Indicates that the system supports full window transparency.
+     * Transparent windows will have only limited or no functionality on a platform that
+     * doesn't support it.
+     * <p>
+     * NOTE: Currently, this support is available on all platforms
+     * except Linux systems without the XComposite extension. The
+     * XShape extension is used in that case, so the window edges are aliased.
+     */
+    TRANSPARENT_WINDOW
 }
--- a/javafx-ui-common/src/javafx/scene/Node.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Node.java	Thu Mar 29 15:10:33 2012 -0700
@@ -58,6 +58,9 @@
 import javafx.scene.input.MouseEvent;
 import javafx.scene.input.MouseDragEvent;
 import javafx.scene.input.TransferMode;
+import javafx.scene.input.RotateEvent;
+import javafx.scene.input.ZoomEvent;
+import javafx.scene.input.ScrollEvent;
 import javafx.scene.transform.Rotate;
 import javafx.scene.transform.Transform;
 
@@ -94,7 +97,8 @@
 import java.util.*;
 import javafx.beans.property.*;
 import javafx.beans.value.WritableValue;
-import javafx.scene.input.ScrollEvent;
+import javafx.scene.input.SwipeEvent;
+import javafx.scene.input.TouchEvent;
 import javafx.scene.text.Font;
 
 /**
@@ -354,7 +358,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    protected boolean impl_isDirty(DirtyBits dirtyBit) {
+    protected final boolean impl_isDirty(DirtyBits dirtyBit) {
         return (dirtyBits & dirtyBit.getMask()) != 0;
     }
 
@@ -376,7 +380,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    protected void impl_clearDirty(DirtyBits dirtyBit) {
+    protected final void impl_clearDirty(DirtyBits dirtyBit) {
         dirtyBits &= ~dirtyBit.getMask();
     }
 
@@ -401,7 +405,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    protected boolean impl_isDirtyEmpty() {
+    protected final boolean impl_isDirtyEmpty() {
         return dirtyBits == 0;
     }
 
@@ -1754,7 +1758,7 @@
     private Node clipParent;
     // Use a getter function instead of giving clipParent package access,
     // so that clipParent doesn't get turned into a Location.
-    Node impl_getClipParent() {
+    final Node impl_getClipParent() {
         return clipParent;
     }
 
@@ -2697,7 +2701,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    protected void impl_layoutBoundsChanged() {
+    protected final void impl_layoutBoundsChanged() {
         layoutBounds.invalidate();
         if (getScaleX() != 1 || getScaleY() != 1 || getScaleZ() != 1 || getRotate() != 0) {
             // if either the scale or rotate convenience variables are used,
@@ -3468,7 +3472,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public BaseTransform impl_getLeafTransform() {
+    public final BaseTransform impl_getLeafTransform() {
         return getLocalToParentTransform(TempState.getInstance().leafTx);
     }
 
@@ -3479,7 +3483,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public void impl_transformsChanged() {
+    public final void impl_transformsChanged() {
         impl_markDirty(DirtyBits.NODE_TRANSFORM);
         transformDirty = true;
         transformedBoundsChanged();
@@ -3490,7 +3494,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public double impl_getPivotX() {
+    public final double impl_getPivotX() {
         final Bounds bounds = getLayoutBounds();
         return bounds.getMinX() + bounds.getWidth()/2;
     }
@@ -3500,7 +3504,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public double impl_getPivotY() {
+    public final double impl_getPivotY() {
         final Bounds bounds = getLayoutBounds();
         return bounds.getMinY() + bounds.getHeight()/2;
     }
@@ -3510,7 +3514,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public double impl_getPivotZ() {
+    public final double impl_getPivotZ() {
         final Bounds bounds = getLayoutBounds();
         return bounds.getMinZ() + bounds.getDepth()/2;
     }
@@ -3535,7 +3539,7 @@
                 localToParentTx.translate(getTranslateX() + getLayoutX(), getTranslateY() + getLayoutY(), getTranslateZ());
             }
 
-            if (hasTransforms()) {
+            if (impl_hasTransforms()) {
                 for (Transform t : getTransforms()) {
                     t.impl_apply(localToParentTx);
                 }
@@ -3615,7 +3619,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public Node impl_pickNode(double parentX, double parentY) {
+    public final Node impl_pickNode(double parentX, double parentY) {
 
         // TODO this check for whether there is no scene is dubious and complicates testing
         // In some conditions we can omit picking this node or subgraph
@@ -3678,7 +3682,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public Node impl_pickNode(PickRay pickRay) {
+    public final Node impl_pickNode(PickRay pickRay) {
 
         // TODO this check for whether there is no scene is dubious and complicates testing
         // In some conditions we can omit picking this node or subgraph
@@ -3717,7 +3721,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    protected boolean impl_intersects(PickRay pickRay) {
+    protected final boolean impl_intersects(PickRay pickRay) {
         // TODO: Need to handle clip and effect
         return contentIntersects(pickRay);
     }
@@ -4016,7 +4020,12 @@
         return nodeTransformation;
     }
 
-    private boolean hasTransforms() {
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    protected boolean impl_hasTransforms() {
         return (nodeTransformation != null)
                 && nodeTransformation.hasTransforms();
     }
@@ -5127,7 +5136,108 @@
         return getEventHandlerProperties().onDragDetectedProperty();
     }
 
-   public final void setOnScroll(
+    public final void setOnMouseDragOver(
+            EventHandler<? super MouseDragEvent> value) {
+        onMouseDragOverProperty().set(value);
+    }
+
+    public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnMouseDragOver();
+    }
+
+    /**
+     * Defines a function to be called when a full press-drag-release gesture
+     * progresses within this {@code Node}.
+     */
+    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
+            onMouseDragOverProperty() {
+        return getEventHandlerProperties().onMouseDragOverProperty();
+    }
+
+    public final void setOnMouseDragReleased(
+            EventHandler<? super MouseDragEvent> value) {
+        onMouseDragReleasedProperty().set(value);
+    }
+
+    public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnMouseDragReleased();
+    }
+
+    /**
+     * Defines a function to be called when a full press-drag-release gesture
+     * ends (by releasing mouse button) within this {@code Node}.
+     */
+    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
+            onMouseDragReleasedProperty() {
+        return getEventHandlerProperties().onMouseDragReleasedProperty();
+    }
+
+    public final void setOnMouseDragEntered(
+            EventHandler<? super MouseDragEvent> value) {
+        onMouseDragEnteredProperty().set(value);
+    }
+
+    public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnMouseDragEntered();
+    }
+
+    /**
+     * Defines a function to be called when a full press-drag-release gesture
+     * enters this {@code Node}.
+     */
+    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
+            onMouseDragEnteredProperty() {
+        return getEventHandlerProperties().onMouseDragEnteredProperty();
+    }
+
+    public final void setOnMouseDragExited(
+            EventHandler<? super MouseDragEvent> value) {
+        onMouseDragExitedProperty().set(value);
+    }
+
+    public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnMouseDragExited();
+    }
+
+    /**
+     * Defines a function to be called when a full press-drag-release gesture
+     * leaves this {@code Node}.
+     */
+    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
+            onMouseDragExitedProperty() {
+        return getEventHandlerProperties().onMouseDragExitedProperty();
+    }
+
+
+    /* *************************************************************************
+     *                                                                         *
+     *                           Gestures Handling                             *
+     *                                                                         *
+     **************************************************************************/
+
+    public final void setOnScrollStarted(
+            EventHandler<? super ScrollEvent> value) {
+        onScrollStartedProperty().set(value);
+    }
+
+    public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnScrollStarted();
+    }
+
+    /**
+     * Defines a function to be called when a scrolling gesture is detected.
+     */
+    public final ObjectProperty<EventHandler<? super ScrollEvent>>
+            onScrollStartedProperty() {
+        return getEventHandlerProperties().onScrollStartedProperty();
+    }
+
+    public final void setOnScroll(
             EventHandler<? super ScrollEvent> value) {
         onScrollProperty().set(value);
     }
@@ -5138,89 +5248,294 @@
     }
 
     /**
-     * Defines a function to be called when user performs a scrolling action. 
+     * Defines a function to be called when user performs a scrolling action.
      */
     public final ObjectProperty<EventHandler<? super ScrollEvent>>
             onScrollProperty() {
         return getEventHandlerProperties().onScrollProperty();
     }
 
-    public final void setOnMouseDragOver(
-            EventHandler<? super MouseDragEvent> value) {
-        onMouseDragOverProperty().set(value);
-    }
-
-    public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
+    public final void setOnScrollFinished(
+            EventHandler<? super ScrollEvent> value) {
+        onScrollFinishedProperty().set(value);
+    }
+
+    public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
         return (eventHandlerProperties == null)
-                ? null : eventHandlerProperties.getOnMouseDragOver();
-    }
-
-    /**
-     * Defines a function to be called when a full press-drag-release gesture
-     * progresses within this {@code Node}.
-     */
-    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
-            onMouseDragOverProperty() {
-        return getEventHandlerProperties().onMouseDragOverProperty();
-    }
-
-    public final void setOnMouseDragReleased(
-            EventHandler<? super MouseDragEvent> value) {
-        onMouseDragReleasedProperty().set(value);
-    }
-
-    public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() {
+                ? null : eventHandlerProperties.getOnScrollFinished();
+    }
+
+    /**
+     * Defines a function to be called when a scrolling gesture ends.
+     */
+    public final ObjectProperty<EventHandler<? super ScrollEvent>>
+            onScrollFinishedProperty() {
+        return getEventHandlerProperties().onScrollFinishedProperty();
+    }
+
+    public final void setOnRotationStarted(
+            EventHandler<? super RotateEvent> value) {
+        onRotationStartedProperty().set(value);
+    }
+
+    public final EventHandler<? super RotateEvent> getOnRotationStarted() {
         return (eventHandlerProperties == null)
-                ? null : eventHandlerProperties.getOnMouseDragReleased();
-    }
-
-    /**
-     * Defines a function to be called when a full press-drag-release gesture
-     * ends (by releasing mouse button) within this {@code Node}.
-     */
-    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
-            onMouseDragReleasedProperty() {
-        return getEventHandlerProperties().onMouseDragReleasedProperty();
-    }
-
-    public final void setOnMouseDragEntered(
-            EventHandler<? super MouseDragEvent> value) {
-        onMouseDragEnteredProperty().set(value);
-    }
-
-    public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() {
+                ? null : eventHandlerProperties.getOnRotationStarted();
+    }
+
+    /**
+     * Defines a function to be called when a rotation gesture is detected.
+     */
+    public final ObjectProperty<EventHandler<? super RotateEvent>>
+            onRotationStartedProperty() {
+        return getEventHandlerProperties().onRotationStartedProperty();
+    }
+
+    public final void setOnRotate(
+            EventHandler<? super RotateEvent> value) {
+        onRotateProperty().set(value);
+    }
+
+    public final EventHandler<? super RotateEvent> getOnRotate() {
         return (eventHandlerProperties == null)
-                ? null : eventHandlerProperties.getOnMouseDragEntered();
-    }
-
-    /**
-     * Defines a function to be called when a full press-drag-release gesture
-     * enters this {@code Node}.
-     */
-    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
-            onMouseDragEnteredProperty() {
-        return getEventHandlerProperties().onMouseDragEnteredProperty();
-    }
-
-    public final void setOnMouseDragExited(
-            EventHandler<? super MouseDragEvent> value) {
-        onMouseDragExitedProperty().set(value);
-    }
-
-    public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() {
+                ? null : eventHandlerProperties.getOnRotate();
+    }
+
+    /**
+     * Defines a function to be called when user performs a rotation action.
+     */
+    public final ObjectProperty<EventHandler<? super RotateEvent>>
+            onRotateProperty() {
+        return getEventHandlerProperties().onRotateProperty();
+    }
+
+    public final void setOnRotationFinished(
+            EventHandler<? super RotateEvent> value) {
+        onRotationFinishedProperty().set(value);
+    }
+
+    public final EventHandler<? super RotateEvent> getOnRotationFinished() {
         return (eventHandlerProperties == null)
-                ? null : eventHandlerProperties.getOnMouseDragExited();
-    }
-
-    /**
-     * Defines a function to be called when a full press-drag-release gesture
-     * leaves this {@code Node}.
-     */
-    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
-            onMouseDragExitedProperty() {
-        return getEventHandlerProperties().onMouseDragExitedProperty();
-    }
-
+                ? null : eventHandlerProperties.getOnRotationFinished();
+    }
+
+    /**
+     * Defines a function to be called when a rotation gesture ends.
+     */
+    public final ObjectProperty<EventHandler<? super RotateEvent>>
+            onRotationFinishedProperty() {
+        return getEventHandlerProperties().onRotationFinishedProperty();
+    }
+
+    public final void setOnZoomStarted(
+            EventHandler<? super ZoomEvent> value) {
+        onZoomStartedProperty().set(value);
+    }
+
+    public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnZoomStarted();
+    }
+
+    /**
+     * Defines a function to be called when a zooming gesture is detected.
+     */
+    public final ObjectProperty<EventHandler<? super ZoomEvent>>
+            onZoomStartedProperty() {
+        return getEventHandlerProperties().onZoomStartedProperty();
+    }
+
+    public final void setOnZoom(
+            EventHandler<? super ZoomEvent> value) {
+        onZoomProperty().set(value);
+    }
+
+    public final EventHandler<? super ZoomEvent> getOnZoom() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnZoom();
+    }
+
+    /**
+     * Defines a function to be called when user performs a zooming action.
+     */
+    public final ObjectProperty<EventHandler<? super ZoomEvent>>
+            onZoomProperty() {
+        return getEventHandlerProperties().onZoomProperty();
+    }
+
+    public final void setOnZoomFinished(
+            EventHandler<? super ZoomEvent> value) {
+        onZoomFinishedProperty().set(value);
+    }
+
+    public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnZoomFinished();
+    }
+
+    /**
+     * Defines a function to be called when a zooming gesture ends.
+     */
+    public final ObjectProperty<EventHandler<? super ZoomEvent>>
+            onZoomFinishedProperty() {
+        return getEventHandlerProperties().onZoomFinishedProperty();
+    }
+
+    public final void setOnSwipeUp(
+            EventHandler<? super SwipeEvent> value) {
+        onSwipeUpProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnSwipeUp();
+    }
+
+    /**
+     * Defines a function to be called when an upward swipe gesture
+     * centered over this node happens.
+     */
+    public final ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeUpProperty() {
+        return getEventHandlerProperties().onSwipeUpProperty();
+    }
+
+    public final void setOnSwipeDown(
+            EventHandler<? super SwipeEvent> value) {
+        onSwipeDownProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnSwipeDown();
+    }
+
+    /**
+     * Defines a function to be called when a downward swipe gesture
+     * centered over this node happens.
+     */
+    public final ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeDownProperty() {
+        return getEventHandlerProperties().onSwipeDownProperty();
+    }
+
+    public final void setOnSwipeLeft(
+            EventHandler<? super SwipeEvent> value) {
+        onSwipeLeftProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnSwipeLeft();
+    }
+
+    /**
+     * Defines a function to be called when a leftward swipe gesture
+     * centered over this node happens.
+     */
+    public final ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeLeftProperty() {
+        return getEventHandlerProperties().onSwipeLeftProperty();
+    }
+
+    public final void setOnSwipeRight(
+            EventHandler<? super SwipeEvent> value) {
+        onSwipeRightProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnSwipeRight();
+    }
+
+    /**
+     * Defines a function to be called when an rightward swipe gesture
+     * centered over this node happens.
+     */
+    public final ObjectProperty<EventHandler<? super SwipeEvent>>
+            onSwipeRightProperty() {
+        return getEventHandlerProperties().onSwipeRightProperty();
+    }
+
+
+    /* *************************************************************************
+     *                                                                         *
+     *                             Touch Handling                              *
+     *                                                                         *
+     **************************************************************************/
+
+    public final void setOnTouchPressed(
+            EventHandler<? super TouchEvent> value) {
+        onTouchPressedProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchPressed() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnTouchPressed();
+    }
+
+    /**
+     * Defines a function to be called when a new touch point is pressed.
+     */
+    public final ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchPressedProperty() {
+        return getEventHandlerProperties().onTouchPressedProperty();
+    }
+
+    public final void setOnTouchMoved(
+            EventHandler<? super TouchEvent> value) {
+        onTouchMovedProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchMoved() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnTouchMoved();
+    }
+
+    /**
+     * Defines a function to be called when a touch point is moved.
+     */
+    public final ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchMovedProperty() {
+        return getEventHandlerProperties().onTouchMovedProperty();
+    }
+
+    public final void setOnTouchReleased(
+            EventHandler<? super TouchEvent> value) {
+        onTouchReleasedProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchReleased() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnTouchReleased();
+    }
+
+    /**
+     * Defines a function to be called when a touch point is released.
+     */
+    public final ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchReleasedProperty() {
+        return getEventHandlerProperties().onTouchReleasedProperty();
+    }
+
+    public final void setOnTouchStationary(
+            EventHandler<? super TouchEvent> value) {
+        onTouchStationaryProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchStationary() {
+        return (eventHandlerProperties == null)
+                ? null : eventHandlerProperties.getOnTouchStationary();
+    }
+
+    /**
+     * Defines a function to be called when a touch point stays pressed and
+     * still.
+     */
+    public final ObjectProperty<EventHandler<? super TouchEvent>>
+            onTouchStationaryProperty() {
+        return getEventHandlerProperties().onTouchStationaryProperty();
+    }
 
     /* *************************************************************************
      *                                                                         *
@@ -5347,6 +5662,69 @@
      *                             Focus Traversal                             *
      *                                                                         *
      **************************************************************************/
+
+    /**
+     * Special boolean property which allows for atomic focus change.
+     * Focus change means defocusing the old focus owner and focusing a new
+     * one. With a usual property, defocusing the old node fires the value
+     * changed event and user code can react with something that breaks
+     * focusability of the new node, or even remove the new node from the scene.
+     * This leads to various error states. This property allows for setting
+     * the state without firing the event. The focus change first sets both
+     * properties and then fires both events. This makes the focus change look
+     * like an atomic operation - when the old node is notified to loose focus,
+     * the new node is already focused.
+     */
+    final class FocusedProperty extends ReadOnlyBooleanPropertyBase {
+        private boolean value;
+        private boolean valid;
+        private boolean needsChangeEvent = false;
+
+        public void store(final boolean value) {
+            if (value != this.value) {
+                this.value = value;
+                markInvalid();
+            }
+        }
+
+        public void notifyListeners() {
+            if (needsChangeEvent) {
+                fireValueChangedEvent();
+                needsChangeEvent = false;
+            }
+        }
+
+        private void markInvalid() {
+            if (valid) {
+                valid = false;
+
+                impl_pseudoClassStateChanged("focused");
+                PlatformLogger logger = Logging.getFocusLogger();
+                if (logger.isLoggable(PlatformLogger.FINE)) {
+                    logger.fine(this + " focused=" + get());
+                }
+
+                needsChangeEvent = true;
+            }
+        }
+
+        @Override
+        public boolean get() {
+            valid = true;
+            return value;
+        }
+
+        @Override
+        public Object getBean() {
+            return Node.this;
+        }
+
+        @Override
+        public String getName() {
+            return "focused";
+        }
+    }
+
     /**
      * Indicates whether this {@code Node} currently has the input focus.
      * To have the input focus, a node must be the {@code Scene}'s focus
@@ -5356,10 +5734,14 @@
      * @see #requestFocus()
      * @defaultValue false
      */
-    private ReadOnlyBooleanWrapper focused;
+    private FocusedProperty focused;
 
     protected final void setFocused(boolean value) {
-        focusedPropertyImpl().set(value);
+        FocusedProperty fp = focusedPropertyImpl();
+        if (fp.value != value) {
+            fp.store(value);
+            fp.notifyListeners();
+        }
     }
 
     public final boolean isFocused() {
@@ -5367,32 +5749,12 @@
     }
 
     public final ReadOnlyBooleanProperty focusedProperty() {
-        return focusedPropertyImpl().getReadOnlyProperty();
-    }
-
-    private ReadOnlyBooleanWrapper focusedPropertyImpl() {
+        return focusedPropertyImpl();
+    }
+
+    private FocusedProperty focusedPropertyImpl() {
         if (focused == null) {
-            focused = new ReadOnlyBooleanWrapper() {
-
-                @Override
-                protected void invalidated() {
-                    impl_pseudoClassStateChanged("focused");
-                    PlatformLogger logger = Logging.getFocusLogger();
-                    if (logger.isLoggable(PlatformLogger.FINE)) {
-                        logger.fine(this + " focused=" + get());
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Node.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "focused";
-                }
-            };
+            focused = new FocusedProperty();
         }
         return focused;
     }
@@ -5498,7 +5860,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public void impl_requestFocusImpl(Runnable r) {
+    public final void impl_requestFocusImpl(Runnable r) {
         r.run();
     }
 
@@ -5512,7 +5874,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public void impl_traverse(Direction dir) {
+    public final void impl_traverse(Direction dir) {
         if (getScene() == null) {
             return;
         }
@@ -5608,7 +5970,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public boolean impl_isTreeVisible() {
+    public final boolean impl_isTreeVisible() {
         return treeVisibleProperty().get();
     }
 
@@ -5721,7 +6083,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public BooleanProperty impl_showMnemonicsProperty() {
+    public final BooleanProperty impl_showMnemonicsProperty() {
         if (impl_showMnemonics == null) {
             impl_showMnemonics = new BooleanPropertyBase(false) {
 
@@ -6305,7 +6667,7 @@
       * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
       */
      @Deprecated
-     public ObservableMap<WritableValue, List<Style>> impl_getStyleMap() {
+     public final ObservableMap<WritableValue, List<Style>> impl_getStyleMap() {
          return impl_getStyleable().getStyleMap();
      }
 
@@ -6315,7 +6677,7 @@
       * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
       */
      @Deprecated
-     public void impl_setStyleMap(ObservableMap<WritableValue, List<Style>> styleMap) {
+     public final void impl_setStyleMap(ObservableMap<WritableValue, List<Style>> styleMap) {
          impl_getStyleable().setStyleMap(styleMap);
      }
           
@@ -6339,7 +6701,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    CSSFlags impl_getCSSFlags() { return cssFlag; }
+    final CSSFlags impl_getCSSFlags() { return cssFlag; }
 
     /**
      * Used to specify that the list of which pseudoclasses apply to this
@@ -6401,7 +6763,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public void impl_reapplyCSS() {
+    public final void impl_reapplyCSS() {
         // If there is no scene, then we cannot make it dirty, so we'll leave
         // the flag alone
         if (getScene() == null) return;
--- a/javafx-ui-common/src/javafx/scene/Parent.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Parent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -543,7 +543,7 @@
     }
 
     // implementation of Node.toFront function
-    void impl_toFront(Node node) {
+    final void impl_toFront(Node node) {
         if (Utils.assertionEnabled()) {
             if (!childSet.contains(node)) {
                 throw new java.lang.AssertionError(
@@ -563,7 +563,7 @@
     }
 
     // implementation of Node.toBack function
-    void impl_toBack(Node node) {
+    final void impl_toBack(Node node) {
         if (Utils.assertionEnabled()) {
             if (!childSet.contains(node)) {
                 throw new java.lang.AssertionError(
@@ -1030,7 +1030,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    protected void impl_resizeChildren(boolean snapToPixel) {
+    protected final void impl_resizeChildren(boolean snapToPixel) {
         for (Node node : getChildren()) {
             if (node.isResizable() && node.isManaged()) {
                 node.autosize();
@@ -1090,7 +1090,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public List<String> impl_getAllParentStylesheets() {
+    public /* Do not make this final! */ List<String> impl_getAllParentStylesheets() {
         
         List<String> list = null;
         final Parent myParent = getParent();
@@ -1735,7 +1735,7 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public void impl_printBranch() {
+    public final void impl_printBranch() {
         final int nodecount = printBranch("");
         System.out.println("total node count="+nodecount);
     }
--- a/javafx-ui-common/src/javafx/scene/Scene.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Scene.java	Thu Mar 29 15:10:33 2012 -0700
@@ -99,14 +99,22 @@
 import com.sun.javafx.tk.TKScenePaintListener;
 import com.sun.javafx.tk.TKStage;
 import com.sun.javafx.tk.Toolkit;
+import java.util.Arrays;
+import java.util.LinkedList;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ObjectPropertyBase;
 import javafx.beans.property.ReadOnlyDoubleProperty;
 import javafx.beans.property.ReadOnlyDoubleWrapper;
 import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
+import javafx.scene.input.GestureEvent;
 import javafx.scene.input.MouseDragEvent;
+import javafx.scene.input.RotateEvent;
 import javafx.scene.input.ScrollEvent;
+import javafx.scene.input.SwipeEvent;
+import javafx.scene.input.TouchEvent;
+import javafx.scene.input.TouchPoint;
+import javafx.scene.input.ZoomEvent;
 
 
 /**
@@ -289,7 +297,7 @@
                     scene.impl_processMouseEvent(mouseEvent);
                 }
                 public void processScrollEvent(Scene scene, ScrollEvent scrollEvent) {
-                    scene.processScrollEvent(scrollEvent);
+                    scene.processGestureEvent(scrollEvent, scene.scrollGesture);
                 }
                 public ObservableList<Node> getChildren(Parent parent) {
                     return parent.getChildren(); //was impl_getChildren
@@ -1267,6 +1275,30 @@
     private MouseHandler mouseHandler;
     private ClickGenerator clickGenerator;
 
+    // gesture events handling
+    private Point2D cursorScreenPos;
+    private Point2D cursorScenePos;
+
+    private class TouchGesture {
+        EventTarget target;
+        Point2D sceneCoords;
+        Point2D screenCoords;
+    }
+
+    private final TouchGesture scrollGesture = new TouchGesture();
+    private final TouchGesture zoomGesture = new TouchGesture();
+    private final TouchGesture rotateGesture = new TouchGesture();
+    private final TouchGesture swipeGesture = new TouchGesture();
+
+    // touch events handling
+    private TouchMap touchMap = new TouchMap();
+    private TouchEvent nextTouchEvent = null;
+    private TouchPoint[] touchPoints = null;
+    private int touchEventSetId = 0;
+    private int touchPointIndex = 0;
+    private Map<Integer, EventTarget> touchTargets =
+            new HashMap<Integer, EventTarget>();
+
     /**
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
@@ -1308,27 +1340,124 @@
         if (!isKeyboardTrigger) Scene.inMousePick = false;
     }
 
-    private void processScrollEvent(ScrollEvent e) {
+    private void processGestureEvent(GestureEvent e, TouchGesture gesture) {
         EventTarget pickedTarget = null;
 
-        if (e.getEventType() != MouseEvent.MOUSE_EXITED) {
-            if (getCamera() instanceof PerspectiveCamera) {
-                final PickRay pickRay = new PickRay();
-                Scene.this.impl_peer.computePickRay((float)e.getX(), (float)e.getY(), pickRay);
-                pickedTarget = mouseHandler.pickNode(pickRay);
-            }
-            else {
-                pickedTarget = mouseHandler.pickNode(e.getX(), e.getY());
-            }
+        inMousePick = true;
+
+        if (e.getEventType() == ZoomEvent.ZOOM_STARTED ||
+                e.getEventType() == RotateEvent.ROTATION_STARTED ||
+                e.getEventType() == ScrollEvent.SCROLL_STARTED) {
+            gesture.target = null;
+        }
+
+        if (gesture.target != null) {
+            pickedTarget = gesture.target;
+        } else if (getCamera() instanceof PerspectiveCamera) {
+            final PickRay pickRay = new PickRay();
+            Scene.this.impl_peer.computePickRay((float)e.getX(), (float)e.getY(), pickRay);
+            pickedTarget = mouseHandler.pickNode(pickRay);
+        } else {
+            pickedTarget = mouseHandler.pickNode(e.getX(), e.getY());
         }
 
         if (pickedTarget == null) {
             pickedTarget = Scene.this;
         }
 
+        if (e.getEventType() == ZoomEvent.ZOOM_STARTED ||
+                e.getEventType() == RotateEvent.ROTATION_STARTED ||
+                e.getEventType() == ScrollEvent.SCROLL_STARTED) {
+            gesture.target = pickedTarget;
+        }
+        if (e.getEventType() != ZoomEvent.ZOOM_FINISHED &&
+                e.getEventType() != RotateEvent.ROTATION_FINISHED &&
+                e.getEventType() != ScrollEvent.SCROLL_FINISHED &&
+                !e.isInertia()) {
+            gesture.sceneCoords = new Point2D(e.getSceneX(), e.getSceneY());
+            gesture.screenCoords = new Point2D(e.getScreenX(), e.getScreenY());
+        }
+
         Event.fireEvent(pickedTarget, e);
-    }
-    
+
+        inMousePick = false;
+    }
+
+    private void processTouchEvent(TouchEvent e, TouchPoint[] touchPoints) {
+        inMousePick = true;
+        // pick targets for all touch points
+        for (TouchPoint tp : touchPoints) {
+            EventTarget pickedTarget = touchTargets.get(tp.getId());
+            if (pickedTarget == null) {
+                if (getCamera() instanceof PerspectiveCamera) {
+                    final PickRay pickRay = new PickRay();
+                    Scene.this.impl_peer.computePickRay((float)tp.getX(), (float)tp.getY(), pickRay);
+                    pickedTarget = mouseHandler.pickNode(pickRay);
+                } else {
+                    pickedTarget = mouseHandler.pickNode(tp.getX(), tp.getY());
+                }
+
+                if (pickedTarget == null) {
+                    pickedTarget = Scene.this;
+                }
+            } else {
+                tp.grab(pickedTarget);
+            }
+
+            if (tp.getState() == TouchPoint.State.PRESSED) {
+                tp.grab(pickedTarget);
+                touchTargets.put(tp.getId(), pickedTarget);
+            } else if (tp.getState() == TouchPoint.State.RELEASED) {
+                touchTargets.remove(tp.getId());
+            }
+
+            tp.impl_setTarget(pickedTarget);
+        }
+
+        touchEventSetId++;
+
+        List<TouchPoint> touchList = Arrays.asList(touchPoints);
+
+        // fire all the events
+        for (TouchPoint tp : touchPoints) {
+            EventType<TouchEvent> type = null;
+            switch (tp.getState()) {
+                case MOVED:
+                    type = TouchEvent.TOUCH_MOVED;
+                    break;
+                case PRESSED:
+                    type = TouchEvent.TOUCH_PRESSED;
+                    break;
+                case RELEASED:
+                    type = TouchEvent.TOUCH_RELEASED;
+                    break;
+                case STATIONARY:
+                    type = TouchEvent.TOUCH_STATIONARY;
+                    break;
+            }
+
+            TouchEvent te = TouchEvent.impl_touchEvent(type, tp, touchList,
+                    touchEventSetId, e.isShiftDown(), e.isControlDown(),
+                    e.isAltDown(), e.isMetaDown());
+
+            Event.fireEvent(tp.getTarget(), te);
+        }
+
+        // process grabbing
+        for (TouchPoint tp : touchPoints) {
+            EventTarget grabbed = tp.getGrabbed();
+            if (grabbed != null) {
+                touchTargets.put(tp.getId(), grabbed);
+            };
+
+            if (grabbed == null || tp.getState() == TouchPoint.State.RELEASED) {
+                touchTargets.remove(tp.getId());
+            }
+        }
+
+        inMousePick = false;
+    }
+
     /**
      * Note: The only user of this method is in unit test: PickAndContainTest.
      */
@@ -1925,15 +2054,24 @@
             Scene.this.impl_processInputMethodEvent(Toolkit.getToolkit().convertInputMethodEventToFX(event));
         }
 
+        public void menuEvent(double x, double y, double xAbs, double yAbs,
+                boolean isKeyboardTrigger) {
+            Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger);
+        }
+
         @Override
         public void scrollEvent(
-                double scrollX, double scrollY, 
+                EventType<ScrollEvent> eventType,
+                double scrollX, double scrollY,
+                double totalScrollX, double totalScrollY,
                 double xMultiplier, double yMultiplier,
+                int touchCount,
                 int scrollTextX, int scrollTextY,
                 int defaultTextX, int defaultTextY,
                 double x, double y, double screenX, double screenY,
                 boolean _shiftDown, boolean _controlDown, 
-                boolean _altDown, boolean _metaDown) {
+                boolean _altDown, boolean _metaDown,
+                boolean _direct, boolean _inertia) {
 
             ScrollEvent.HorizontalTextScrollUnits xUnits = scrollTextX > 0 ?
                     ScrollEvent.HorizontalTextScrollUnits.CHARACTERS :
@@ -1956,20 +2094,170 @@
             yMultiplier = defaultTextY > 0 && scrollTextY >= 0
                     ? Math.round(yMultiplier * scrollTextY / defaultTextY)
                     : yMultiplier;
-            
-            Scene.this.processScrollEvent(ScrollEvent.impl_scrollEvent(
+
+            if (eventType == ScrollEvent.SCROLL_FINISHED) {
+                x = scrollGesture.sceneCoords.getX();
+                y = scrollGesture.sceneCoords.getY();
+                screenX = scrollGesture.screenCoords.getX();
+                screenY = scrollGesture.screenCoords.getY();
+            } else if (Double.isNaN(x) || Double.isNaN(y) ||
+                    Double.isNaN(screenX) || Double.isNaN(screenY)) {
+                if (cursorScenePos == null || cursorScreenPos == null) {
+                    return;
+                }
+                x = cursorScenePos.getX();
+                y = cursorScenePos.getY();
+                screenX = cursorScreenPos.getX();
+                screenY = cursorScreenPos.getY();
+            } 
+
+            Scene.this.processGestureEvent(ScrollEvent.impl_scrollEvent(
+                    eventType,
                     scrollX * xMultiplier, scrollY * yMultiplier,
-                    xUnits, xText, yUnits, yText, 
+                    totalScrollX * xMultiplier, totalScrollY * yMultiplier,
+                    xUnits, xText, yUnits, yText,
+                    touchCount,
                     x, y, screenX, screenY, 
-                    _shiftDown, _controlDown, _altDown, _metaDown));
+                    _shiftDown, _controlDown, _altDown, _metaDown, 
+                    _direct, _inertia),
+                    scrollGesture);
         }
 
         @Override
-        public void menuEvent(double x, double y, double xAbs, double yAbs,
-                boolean isKeyboardTrigger) {
-            Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger);
-        }
-        
+        public void zoomEvent(
+                EventType<ZoomEvent> eventType,
+                double zoomFactor, double totalZoomFactor,
+                double x, double y, double screenX, double screenY,
+                boolean _shiftDown, boolean _controlDown,
+                boolean _altDown, boolean _metaDown,
+                boolean _direct, boolean _inertia) {
+
+            if (eventType == ZoomEvent.ZOOM_FINISHED) {
+                x = zoomGesture.sceneCoords.getX();
+                y = zoomGesture.sceneCoords.getY();
+                screenX = zoomGesture.screenCoords.getX();
+                screenY = zoomGesture.screenCoords.getY();
+            } else if (Double.isNaN(x) || Double.isNaN(y) ||
+                    Double.isNaN(screenX) || Double.isNaN(screenY)) {
+                if (cursorScenePos == null || cursorScreenPos == null) {
+                    return;
+                }
+                x = cursorScenePos.getX();
+                y = cursorScenePos.getY();
+                screenX = cursorScreenPos.getX();
+                screenY = cursorScreenPos.getY();
+            }
+
+            Scene.this.processGestureEvent(ZoomEvent.impl_zoomEvent(
+                    eventType, zoomFactor, totalZoomFactor,
+                    x, y, screenX, screenY,
+                    _shiftDown, _controlDown, _altDown, _metaDown, 
+                    _direct, _inertia),
+                    zoomGesture);
+        }
+
+        @Override
+        public void rotateEvent(
+                EventType<RotateEvent> eventType, double angle, double totalAngle,
+                double x, double y, double screenX, double screenY,
+                boolean _shiftDown, boolean _controlDown,
+                boolean _altDown, boolean _metaDown,
+                boolean _direct, boolean _inertia) {
+
+            if (eventType == RotateEvent.ROTATION_FINISHED) {
+                x = rotateGesture.sceneCoords.getX();
+                y = rotateGesture.sceneCoords.getY();
+                screenX = rotateGesture.screenCoords.getX();
+                screenY = rotateGesture.screenCoords.getY();
+            } else if (Double.isNaN(x) || Double.isNaN(y) ||
+                    Double.isNaN(screenX) || Double.isNaN(screenY)) {
+                if (cursorScenePos == null || cursorScreenPos == null) {
+                    return;
+                }
+                x = cursorScenePos.getX();
+                y = cursorScenePos.getY();
+                screenX = cursorScreenPos.getX();
+                screenY = cursorScreenPos.getY();
+            }
+
+            Scene.this.processGestureEvent(RotateEvent.impl_rotateEvent(
+                    eventType, angle, totalAngle, x, y, screenX, screenY,
+                    _shiftDown, _controlDown, _altDown, _metaDown, 
+                    _direct, _inertia),
+                    rotateGesture);
+
+        }
+
+        @Override
+        public void swipeEvent(
+                EventType<SwipeEvent> eventType, int touchCount,
+                double x, double y, double screenX, double screenY,
+                boolean _shiftDown, boolean _controlDown,
+                boolean _altDown, boolean _metaDown, boolean _direct) {
+
+            Scene.this.processGestureEvent(SwipeEvent.impl_swipeEvent(
+                    eventType, touchCount, x, y, screenX, screenY,
+                    _shiftDown, _controlDown, _altDown, _metaDown, _direct), 
+                    swipeGesture);
+        }
+
+        @Override
+        public void touchEventBegin(
+                long time, int touchCount, boolean isDirect,
+                boolean _shiftDown, boolean _controlDown,
+                boolean _altDown, boolean _metaDown) {
+
+            nextTouchEvent = TouchEvent.impl_touchEvent(
+                    TouchEvent.ANY, null, null, 0,
+                    _shiftDown, _controlDown, _altDown, _metaDown);
+            nextTouchEvent.impl_setDirect(isDirect);
+            if (touchPoints == null || touchPoints.length != touchCount) {
+                touchPoints = new TouchPoint[touchCount];
+            }
+            touchPointIndex = 0;
+        }
+
+        @Override
+        public void touchEventNext(
+                TouchPoint.State state, long touchId,
+                int x, int y, int xAbs, int yAbs) {
+
+            touchPointIndex++;
+            int id = (state == TouchPoint.State.PRESSED
+                    ? touchMap.add(touchId) :  touchMap.get(touchId));
+            if (state == TouchPoint.State.RELEASED) {
+                touchMap.remove(touchId);
+            }
+            int order = touchMap.getOrder(id);
+
+            //TODO: this is workaround for RT-20139, remove when fixed
+            if (!nextTouchEvent.impl_isDirect()) {
+                order = touchPointIndex - 1;
+            }
+
+            if (order >= touchPoints.length) {
+                throw new RuntimeException("Too many touch points reported");
+            }
+
+            touchPoints[order] = new TouchPoint(id, state, x, y, xAbs, yAbs);
+        }
+
+        @Override
+        public void touchEventEnd() {
+            if (touchPointIndex != touchPoints.length) {
+                throw new RuntimeException("Wrong number of touch points reported");
+            }
+
+            // for now we don't want to process indirect touch events
+            if (nextTouchEvent.impl_isDirect()) {
+                Scene.this.processTouchEvent(nextTouchEvent, touchPoints);
+            }
+
+            if (touchMap.cleanup()) {
+                // gesture finished
+                touchEventSetId = 0;
+            }
+        }
     }
 
     private class ScenePeerPaintListener implements TKScenePaintListener {
@@ -2212,7 +2500,7 @@
             //      this code is not used right now anyway
             MouseEvent me = MouseEvent.impl_mouseEvent(de.getX(), de.getY(),
                     de.getSceneX(), de.getScreenY(), MouseButton.PRIMARY, 1,
-                    false, false, false, false, false, true, false, false,
+                    false, false, false, false, false, true, false, false, false,
                     MouseEvent.DRAG_DETECTED);
 
             processingDragDetected();
@@ -2785,6 +3073,9 @@
             Toolkit.getToolkit().checkFxUserThread();
             Scene.inMousePick = true;
 
+            cursorScreenPos = new Point2D(e.getScreenX(), e.getScreenY());
+            cursorScenePos = new Point2D(e.getSceneX(), e.getSceneY());
+
             boolean gestureStarted = false;
             if (!onPulse) {
                 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) {
@@ -3073,14 +3364,14 @@
         private void setFocusOwner(Node value) {
             Node oldFocusOwner = focusOwner;
             if (oldFocusOwner != null) {
-                oldFocusOwner.setFocused(false);
+                ((Node.FocusedProperty) oldFocusOwner.focusedProperty()).store(false);
             }
             focusOwner = value;
 
             Scene.this.setImpl_focusOwner(focusOwner);// = Scene{ impl_focusOwner = bind keyHandler.focusOwner };
 
             if (focusOwner != null) {
-                focusOwner.setFocused(windowFocused);
+                ((Node.FocusedProperty) focusOwner.focusedProperty()).store(windowFocused);
                 if (focusOwner != oldFocusOwner) {
                     focusOwner.getScene().impl_enableInputMethodEvents(
                         focusOwner.getInputMethodRequests() != null &&
@@ -3088,6 +3379,13 @@
                 }
             }
 
+            if (oldFocusOwner != null) {
+                ((Node.FocusedProperty) oldFocusOwner.focusedProperty()).notifyListeners();
+            }
+            if (focusOwner != null) {
+                ((Node.FocusedProperty) focusOwner.focusedProperty()).notifyListeners();
+            }
+
             PlatformLogger logger = Logging.getFocusLogger();
             if (logger.isLoggable(PlatformLogger.FINE)) {
                 logger.fine("Changed focus from "
@@ -3762,42 +4060,6 @@
     }
 
     /**
-     * Defines a function to be called when user performs a scrolling action. 
-     */
-    private ObjectProperty<EventHandler<? super ScrollEvent>> onScroll;
-
-    public final void setOnScroll(EventHandler<? super ScrollEvent> value) {
-        onScrollProperty().set(value);
-    }
-
-    public final EventHandler<? super ScrollEvent> getOnScroll() {
-        return onScroll == null ? null : onScroll.get();
-    }
-
-    public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollProperty() {
-        if (onScroll == null) {
-            onScroll = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() {
-
-                @Override
-                protected void invalidated() {
-                    setEventHandler(ScrollEvent.SCROLL, get());
-                }
-
-                @Override
-                public Object getBean() {
-                    return Scene.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "onScroll";
-                }
-            };
-        }
-        return onScroll;
-    }
-
-    /**
      * Defines a function to be called when a full press-drag-release gesture
      * progresses within this {@code Scene}.
      */
@@ -3948,6 +4210,718 @@
 
     /***************************************************************************
      *                                                                         *
+     *                           Gestures Handling                             *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * Defines a function to be called when a scrolling gesture is detected.
+     */
+    private ObjectProperty<EventHandler<? super ScrollEvent>> onScrollStarted;
+
+    public final void setOnScrollStarted(EventHandler<? super ScrollEvent> value) {
+        onScrollStartedProperty().set(value);
+    }
+
+    public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
+        return onScrollStarted == null ? null : onScrollStarted.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollStartedProperty() {
+        if (onScrollStarted == null) {
+            onScrollStarted = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollEvent.SCROLL_STARTED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScrollStarted";
+                }
+            };
+        }
+        return onScrollStarted;
+    }
+
+    /**
+     * Defines a function to be called when user performs a scrolling action.
+     */
+    private ObjectProperty<EventHandler<? super ScrollEvent>> onScroll;
+
+    public final void setOnScroll(EventHandler<? super ScrollEvent> value) {
+        onScrollProperty().set(value);
+    }
+
+    public final EventHandler<? super ScrollEvent> getOnScroll() {
+        return onScroll == null ? null : onScroll.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollProperty() {
+        if (onScroll == null) {
+            onScroll = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollEvent.SCROLL, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScroll";
+                }
+            };
+        }
+        return onScroll;
+    }
+
+    /**
+     * Defines a function to be called when a scrolling gesture ends.
+     */
+    private ObjectProperty<EventHandler<? super ScrollEvent>> onScrollFinished;
+
+    public final void setOnScrollFinished(EventHandler<? super ScrollEvent> value) {
+        onScrollFinishedProperty().set(value);
+    }
+
+    public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
+        return onScrollFinished == null ? null : onScrollFinished.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollFinishedProperty() {
+        if (onScrollFinished == null) {
+            onScrollFinished = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ScrollEvent.SCROLL_FINISHED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onScrollFinished";
+                }
+            };
+        }
+        return onScrollFinished;
+    }
+
+    /**
+     * Defines a function to be called when a rotating gesture is detected.
+     */
+    private ObjectProperty<EventHandler<? super RotateEvent>> onRotationStarted;
+
+    public final void setOnRotationStarted(EventHandler<? super RotateEvent> value) {
+        onRotationStartedProperty().set(value);
+    }
+
+    public final EventHandler<? super RotateEvent> getOnRotationStarted() {
+        return onRotationStarted == null ? null : onRotationStarted.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super RotateEvent>> onRotationStartedProperty() {
+        if (onRotationStarted == null) {
+            onRotationStarted = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(RotateEvent.ROTATION_STARTED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onRotationStarted";
+                }
+            };
+        }
+        return onRotationStarted;
+    }
+
+    /**
+     * Defines a function to be called when user performs a rotating action.
+     */
+    private ObjectProperty<EventHandler<? super RotateEvent>> onRotate;
+
+    public final void setOnRotate(EventHandler<? super RotateEvent> value) {
+        onRotateProperty().set(value);
+    }
+
+    public final EventHandler<? super RotateEvent> getOnRotate() {
+        return onRotate == null ? null : onRotate.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super RotateEvent>> onRotateProperty() {
+        if (onRotate == null) {
+            onRotate = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(RotateEvent.ROTATE, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onRotate";
+                }
+            };
+        }
+        return onRotate;
+    }
+
+    /**
+     * Defines a function to be called when a rotating gesture ends.
+     */
+    private ObjectProperty<EventHandler<? super RotateEvent>> onRotationFinished;
+
+    public final void setOnRotationFinished(EventHandler<? super RotateEvent> value) {
+        onRotationFinishedProperty().set(value);
+    }
+
+    public final EventHandler<? super RotateEvent> getOnRotationFinished() {
+        return onRotationFinished == null ? null : onRotationFinished.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super RotateEvent>> onRotationFinishedProperty() {
+        if (onRotationFinished == null) {
+            onRotationFinished = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(RotateEvent.ROTATION_FINISHED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onRotationFinished";
+                }
+            };
+        }
+        return onRotationFinished;
+    }
+
+    /**
+     * Defines a function to be called when a zooming gesture is detected.
+     */
+    private ObjectProperty<EventHandler<? super ZoomEvent>> onZoomStarted;
+
+    public final void setOnZoomStarted(EventHandler<? super ZoomEvent> value) {
+        onZoomStartedProperty().set(value);
+    }
+
+    public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
+        return onZoomStarted == null ? null : onZoomStarted.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomStartedProperty() {
+        if (onZoomStarted == null) {
+            onZoomStarted = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ZoomEvent.ZOOM_STARTED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onZoomStarted";
+                }
+            };
+        }
+        return onZoomStarted;
+    }
+
+    /**
+     * Defines a function to be called when user performs a zooming action.
+     */
+    private ObjectProperty<EventHandler<? super ZoomEvent>> onZoom;
+
+    public final void setOnZoom(EventHandler<? super ZoomEvent> value) {
+        onZoomProperty().set(value);
+    }
+
+    public final EventHandler<? super ZoomEvent> getOnZoom() {
+        return onZoom == null ? null : onZoom.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomProperty() {
+        if (onZoom == null) {
+            onZoom = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ZoomEvent.ZOOM, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onZoom";
+                }
+            };
+        }
+        return onZoom;
+    }
+
+    /**
+     * Defines a function to be called when a zooming gesture ends.
+     */
+    private ObjectProperty<EventHandler<? super ZoomEvent>> onZoomFinished;
+
+    public final void setOnZoomFinished(EventHandler<? super ZoomEvent> value) {
+        onZoomFinishedProperty().set(value);
+    }
+
+    public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
+        return onZoomFinished == null ? null : onZoomFinished.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomFinishedProperty() {
+        if (onZoomFinished == null) {
+            onZoomFinished = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(ZoomEvent.ZOOM_FINISHED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onZoomFinished";
+                }
+            };
+        }
+        return onZoomFinished;
+    }
+
+    /**
+     * Defines a function to be called when an upward swipe gesture
+     * happens in this scene.
+     */
+    private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeUp;
+
+    public final void setOnSwipeUp(EventHandler<? super SwipeEvent> value) {
+        onSwipeUpProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
+        return onSwipeUp == null ? null : onSwipeUp.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeUpProperty() {
+        if (onSwipeUp == null) {
+            onSwipeUp = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(SwipeEvent.SWIPE_UP, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onSwipeUp";
+                }
+            };
+        }
+        return onSwipeUp;
+    }
+
+    /**
+     * Defines a function to be called when an downward swipe gesture
+     * happens in this scene.
+     */
+    private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeDown;
+
+    public final void setOnSwipeDown(EventHandler<? super SwipeEvent> value) {
+        onSwipeDownProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
+        return onSwipeDown == null ? null : onSwipeDown.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeDownProperty() {
+        if (onSwipeDown == null) {
+            onSwipeDown = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(SwipeEvent.SWIPE_DOWN, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onSwipeDown";
+                }
+            };
+        }
+        return onSwipeDown;
+    }
+
+    /**
+     * Defines a function to be called when an leftward swipe gesture
+     * happens in this scene.
+     */
+    private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeLeft;
+
+    public final void setOnSwipeLeft(EventHandler<? super SwipeEvent> value) {
+        onSwipeLeftProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
+        return onSwipeLeft == null ? null : onSwipeLeft.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeLeftProperty() {
+        if (onSwipeLeft == null) {
+            onSwipeLeft = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(SwipeEvent.SWIPE_LEFT, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onSwipeLeft";
+                }
+            };
+        }
+        return onSwipeLeft;
+    }
+
+    /**
+     * Defines a function to be called when an rightward swipe gesture
+     * happens in this scene.
+     */
+    private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeRight;
+
+    public final void setOnSwipeRight(EventHandler<? super SwipeEvent> value) {
+        onSwipeRightProperty().set(value);
+    }
+
+    public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
+        return onSwipeRight == null ? null : onSwipeRight.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeRightProperty() {
+        if (onSwipeRight == null) {
+            onSwipeRight = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(SwipeEvent.SWIPE_RIGHT, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onSwipeRight";
+                }
+            };
+        }
+        return onSwipeRight;
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     *                            Touch Handling                               *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * Defines a function to be called when a new touch point is pressed.
+     */
+    private ObjectProperty<EventHandler<? super TouchEvent>> onTouchPressed;
+
+    public final void setOnTouchPressed(EventHandler<? super TouchEvent> value) {
+        onTouchPressedProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchPressed() {
+        return onTouchPressed == null ? null : onTouchPressed.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchPressedProperty() {
+        if (onTouchPressed == null) {
+            onTouchPressed = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(TouchEvent.TOUCH_PRESSED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onTouchPressed";
+                }
+            };
+        }
+        return onTouchPressed;
+    }
+
+    /**
+     * Defines a function to be called when a touch point is moved.
+     */
+    private ObjectProperty<EventHandler<? super TouchEvent>> onTouchMoved;
+
+    public final void setOnTouchMoved(EventHandler<? super TouchEvent> value) {
+        onTouchMovedProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchMoved() {
+        return onTouchMoved == null ? null : onTouchMoved.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchMovedProperty() {
+        if (onTouchMoved == null) {
+            onTouchMoved = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(TouchEvent.TOUCH_MOVED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onTouchMoved";
+                }
+            };
+        }
+        return onTouchMoved;
+    }
+
+    /**
+     * Defines a function to be called when a new touch point is pressed.
+     */
+    private ObjectProperty<EventHandler<? super TouchEvent>> onTouchReleased;
+
+    public final void setOnTouchReleased(EventHandler<? super TouchEvent> value) {
+        onTouchReleasedProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchReleased() {
+        return onTouchReleased == null ? null : onTouchReleased.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchReleasedProperty() {
+        if (onTouchReleased == null) {
+            onTouchReleased = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(TouchEvent.TOUCH_RELEASED, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onTouchReleased";
+                }
+            };
+        }
+        return onTouchReleased;
+    }
+
+    /**
+     * Defines a function to be called when a touch point stays pressed and
+     * still.
+     */
+    private ObjectProperty<EventHandler<? super TouchEvent>> onTouchStationary;
+
+    public final void setOnTouchStationary(EventHandler<? super TouchEvent> value) {
+        onTouchStationaryProperty().set(value);
+    }
+
+    public final EventHandler<? super TouchEvent> getOnTouchStationary() {
+        return onTouchStationary == null ? null : onTouchStationary.get();
+    }
+
+    public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchStationaryProperty() {
+        if (onTouchStationary == null) {
+            onTouchStationary = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
+
+                @Override
+                protected void invalidated() {
+                    setEventHandler(TouchEvent.TOUCH_STATIONARY, get());
+                }
+
+                @Override
+                public Object getBean() {
+                    return Scene.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "onTouchStationary";
+                }
+            };
+        }
+        return onTouchStationary;
+    }
+
+    /*
+     * This class provides reordering and ID mapping of particular touch points.
+     * Platform may report arbitrary touch point IDs and they may be reused
+     * during one gesture. This class keeps track of it and provides
+     * sequentially sorted IDs, unique in scope of a gesture.
+     *
+     * Some platforms report always small numbers, these take fast paths through
+     * the algorithm, directly indexing an array. Bigger numbers take a slow
+     * path using a hash map.
+     *
+     * The algorithm performance was measured and it doesn't impose
+     * any significant slowdown on the event delivery.
+     */
+    private class TouchMap {
+        private static final int FAST_THRESHOLD = 10;
+        int[] fastMap = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        Map<Long, Integer> slowMap = new HashMap<Long, Integer>();
+        List<Integer> order = new LinkedList<Integer>();
+        List<Long> removed = new ArrayList<Long>(10);
+        int counter = 0;
+        int active = 0;
+
+        public int add(long id) {
+            counter++;
+            active++;
+            if (id < FAST_THRESHOLD) {
+                fastMap[(int) id] = counter;
+            } else {
+                slowMap.put(id, counter);
+            }
+            order.add(counter);
+            return counter;
+        }
+
+        public void remove(long id) {
+            // book the removal - it needs to be done after all touch points
+            // of an event are processed - see cleanup()
+            removed.add(id);
+        }
+
+        public int get(long id) {
+            if (id < FAST_THRESHOLD) {
+                int result = fastMap[(int) id];
+                if (result == 0) {
+                    throw new RuntimeException("Platform reported wrong "
+                            + "touch point ID");
+                }
+                return result;
+            } else {
+                try {
+                    return slowMap.get(id);
+                } catch (NullPointerException e) {
+                    throw new RuntimeException("Platform reported wrong "
+                            + "touch point ID");
+                }
+            }
+        }
+
+        public int getOrder(int id) {
+            return order.indexOf(id);
+        }
+
+        // returns true if gesture finished (no finger is touched)
+        public boolean cleanup() {
+            for (long id : removed) {
+                active--;
+                order.remove(Integer.valueOf(get(id)));
+                if (id < FAST_THRESHOLD) {
+                    fastMap[(int) id] = 0;
+                } else {
+                    slowMap.remove(id);
+                }
+                if (active == 0) {
+                    // gesture finished
+                    counter = 0;
+                }
+            }
+            removed.clear();
+            return active == 0;
+        }
+    }
+
+
+    /***************************************************************************
+     *                                                                         *
      *                         Drag and Drop Handling                          *
      *                                                                         *
      **************************************************************************/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/input/GestureEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.input;
+
+import com.sun.javafx.scene.input.InputEventUtils;
+import com.sun.javafx.tk.Toolkit;
+import javafx.event.Event;
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+import javafx.geometry.Point2D;
+
+/**
+ * An event indicating gesture input. Gestures are typically caused by
+ * direct (touch screen) or indirect (track pad) touch events.
+ */
+public class GestureEvent extends InputEvent {
+
+    /**
+     * Common supertype for all gestures.
+     */
+    public static final EventType<GestureEvent> ANY =
+            new EventType<GestureEvent>(InputEvent.ANY, "GESTURE");
+
+    /**
+     * Creates a new instance of {@code GestureEvent}.
+     * @param eventType Type of the event
+     */
+    protected GestureEvent(final EventType<? extends GestureEvent> eventType) {
+        super(eventType);
+    }
+
+    /**
+     * Creates a new instance of {@code GestureEvent}.
+     * @param source Event source
+     * @param target Event target
+     * @param eventType Type of the event
+     */
+    protected GestureEvent(Object source, EventTarget target,
+            final EventType<? extends GestureEvent> eventType) {
+        super(source, target, eventType);
+    }
+
+    GestureEvent(final EventType<? extends GestureEvent> eventType,
+            double x, double y, double screenX, double screenY,
+            boolean shiftDown, boolean controlDown, boolean altDown,
+            boolean metaDown, boolean direct, boolean inertia) {
+        super(eventType);
+        this.x = x;
+        this.y = y;
+        this.screenX = screenX;
+        this.screenY = screenY;
+        this.sceneX = x;
+        this.sceneY = y;
+        this.shiftDown = shiftDown;
+        this.controlDown = controlDown;
+        this.altDown = altDown;
+        this.metaDown = metaDown;
+        this.direct = direct;
+        this.inertia = inertia;
+    }
+
+    /**
+     * Fills the given event by this event's coordinates recomputed to the given
+     * source object.
+     * @param newEvent Event whose coordinates are to be filled
+     * @param newSource Source object to compute coordinates for
+     */
+    private void recomputeCoordinatesToSource(GestureEvent newEvent, Object newSource) {
+
+        final Point2D newCoordinates = InputEventUtils.recomputeCoordinates(
+                new Point2D(x, y), source, newSource);
+
+        newEvent.x = newCoordinates.getX();
+        newEvent.y = newCoordinates.getY();
+        newEvent.sceneX = getSceneX();
+        newEvent.sceneY = getSceneY();
+    }
+
+    /**
+     * @InheritDoc
+     */
+    @Override
+    public Event copyFor(Object newSource, EventTarget newTarget) {
+        GestureEvent e = (GestureEvent) super.copyFor(newSource, newTarget);
+        recomputeCoordinatesToSource(e, newSource);
+        return e;
+    }
+
+    /**
+     * Copies all private fields (except of event type) from one event to
+     * another event. This is for implementing impl_copy in subclasses.
+     */
+    static void copyFields(GestureEvent from, GestureEvent to,
+            Object source, EventTarget target) {
+        to.x = from.x;
+        to.y = from.y;
+        to.screenX = from.screenX;
+        to.screenY = from.screenY;
+        to.sceneX = from.sceneX;
+        to.sceneY = from.sceneY;
+        to.shiftDown = from.shiftDown;
+        to.controlDown = from.controlDown;
+        to.altDown = from.altDown;
+        to.metaDown = from.metaDown;
+        to.source = source;
+        to.target = target;
+
+        from.recomputeCoordinatesToSource(to, source);
+    }
+
+    private double x;
+
+    /**
+     * Gets the horizontal position of the event relative to the
+     * origin of the event's source.
+     *
+     * @return the horizontal position of the event relative to the
+     * origin of the event's source.
+     *
+     * @see #isDirect() 
+     */
+    public final double getX() {
+        return x;
+    }
+
+    private double y;
+
+    /**
+     * Gets the vertical position of the event relative to the
+     * origin of the event's source.
+     *
+     * @return the vertical position of the event relative to the
+     * origin of the event's source.
+     *
+     * @see #isDirect()
+     */
+    public final double getY() {
+        return y;
+    }
+
+    private double screenX;
+
+    /**
+     * Gets the absolute horizontal position of the event.
+     * @return the absolute horizontal position of the event
+     *
+     * @see #isDirect()
+     */
+    public final double getScreenX() {
+        return screenX;
+    }
+
+    private double screenY;
+
+    /**
+     * Gets the absolute vertical position of the event.
+     * @return the absolute vertical position of the event
+     *
+     * @see #isDirect()
+     */
+    public final double getScreenY() {
+        return screenY;
+    }
+
+    private double sceneX;
+
+    /**
+     * Gets the horizontal position of the event relative to the
+     * origin of the {@code Scene} that contains the event's source.
+     * If the node is not in a {@code Scene}, then the value is relative to
+     * the boundsInParent of the root-most parent of the event's node.
+     *
+     * @return the horizontal position of the event relative to the
+     * origin of the {@code Scene} that contains the event's source
+     *
+     * @see #isDirect()
+     */
+    public final double getSceneX() {
+        return sceneX;
+    }
+
+    private double sceneY;
+
+    /**
+     * Gets the vertical position of the event relative to the
+     * origin of the {@code Scene} that contains the event's source.
+     * If the node is not in a {@code Scene}, then the value is relative to
+     * the boundsInParent of the root-most parent of the event's node.
+     *
+     * @return the vertical position of the event relative to the
+     * origin of the {@code Scene} that contains the event's source
+     *
+     * @see #isDirect()
+     */
+    public final double getSceneY() {
+        return sceneY;
+    }
+
+    private boolean shiftDown;
+
+    /**
+     * Indicates whether or not the Shift modifier is down on this event.
+     * @return true if the Shift modifier is down on this event
+     */
+    public final boolean isShiftDown() {
+        return shiftDown;
+    }
+
+    private boolean controlDown;
+
+    /**
+     * Indicates whether or not the Control modifier is down on this event.
+     * @return true if the Control modifier is down on this event
+     */
+    public final boolean isControlDown() {
+        return controlDown;
+    }
+
+    private boolean altDown;
+
+    /**
+     * Indicates whether or not the Alt modifier is down on this event.
+     * @return true if the Alt modifier is down on this event
+     */
+    public final boolean isAltDown() {
+        return altDown;
+    }
+
+    private boolean metaDown;
+
+    /**
+     * Indicates whether or not the Meta modifier is down on this event.
+     * @return true if the Meta modifier is down on this event
+     */
+    public final boolean isMetaDown() {
+        return metaDown;
+    }
+
+    private boolean direct;
+
+    /**
+     * Indicates whether this gesture is caused by a direct or indirect input
+     * device. With direct input device the gestures are performed directly at
+     * the concrete coordinates, a typical example would be a touch screen.
+     * With indirect device the gestures are performed indirectly and usually
+     * mouse cursor position is used as the gesture coordinates, a typical
+     * example would be a track pad.
+     * @return true if this event is caused by a direct input device
+     */
+    public final boolean isDirect() {
+        return direct;
+    }
+
+    private boolean inertia;
+
+    /**
+     * Indicates if this event represents an inertia of an already finished
+     * gesture.
+     * @return true if this is an inertia event
+     */
+    public boolean isInertia() {
+        return inertia;
+    }
+
+    /**
+     * Indicates whether or not the host platform common shortcut modifier is
+     * down on this event. This common shortcut modifier is a modifier key which
+     * is used commonly in shortcuts on the host platform. It is for example
+     * {@code control} on Windows and {@code meta} (command key) on Mac.
+     *
+     * @return {@code true} if the shortcut modifier is down, {@code false}
+     *      otherwise
+     */
+    public final boolean isShortcutDown() {
+        switch (Toolkit.getToolkit().getPlatformShortcutKey()) {
+            case SHIFT:
+                return shiftDown;
+
+            case CONTROL:
+                return controlDown;
+
+            case ALT:
+                return altDown;
+
+            case META:
+                return metaDown;
+
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Returns a string representation of this {@code GestureEvent} object.
+     * @return a string representation of this {@code GestureEvent} object.
+     */
+    @Override public String toString() {
+        final StringBuilder sb = new StringBuilder("GestureEvent [");
+
+        sb.append("source = ").append(getSource());
+        sb.append(", target = ").append(getTarget());
+        sb.append(", eventType = ").append(getEventType());
+        sb.append(", consumed = ").append(isConsumed());
+
+        sb.append(", x = ").append(getX()).append(", y = ").append(getY());
+        sb.append(isDirect() ? ", direct" : ", indirect");
+
+        if (isShiftDown()) {
+            sb.append(", shiftDown");
+        }
+        if (isControlDown()) {
+            sb.append(", controlDown");
+        }
+        if (isAltDown()) {
+            sb.append(", altDown");
+        }
+        if (isMetaDown()) {
+            sb.append(", metaDown");
+        }
+        if (isShortcutDown()) {
+            sb.append(", shortcutDown");
+        }
+
+        return sb.append("]").toString();
+    }
+}
--- a/javafx-ui-common/src/javafx/scene/input/KeyEvent.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/KeyEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -157,6 +157,7 @@
                                            primaryButtonDown,
                                            middleButtonDown,
                                            secondaryButtonDown,
+                                           false,
                                            eventType);
             }
 
@@ -169,10 +170,12 @@
                     int x, int y, int screenX, int screenY, 
                     boolean shiftDown, boolean controlDown, 
                     boolean altDown, boolean metaDown) {
-                return ScrollEvent.impl_scrollEvent(scrollX, scrollY, 
-                        xTextUnits, xText, yTextUnits, yText, 
+                return ScrollEvent.impl_scrollEvent(ScrollEvent.SCROLL, 
+                        scrollX, scrollY, 0, 0,
+                        xTextUnits, xText, yTextUnits, yText,
+                        0,
                         x, y, screenX, screenY, 
-                        shiftDown, controlDown, altDown, metaDown);
+                        shiftDown, controlDown, altDown, metaDown, false, false);
             }
         };
         FXRobotHelper.setInputAccessor(a);
--- a/javafx-ui-common/src/javafx/scene/input/MouseEvent.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/MouseEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -34,6 +34,7 @@
 import javafx.scene.Node;
 
 import com.sun.javafx.scene.input.InputEventUtils;
+import javax.sound.midi.Synthesizer;
 
 // PENDING_DOC_REVIEW
 /**
@@ -86,6 +87,12 @@
  * the system switches into the drag and drop mode and {@code DragEvent}s start
  * to be delivered instead of {@code MouseEvent}s. If you don't call any of
  * those methods, the simple press-drag-release gesture continues.
+ * <p>
+ * Note that dragging a finger over touch screen produces mouse dragging events,
+ * but also scroll gesture events. If it means a conflict in an application
+ * (the physical dragging action is handled by two different handlers), the
+ * {@code isSynthesized()} method may be used to detect the problem and make the
+ * dragging handlers behave accordingly.
  *
  * <h4>Mouse enter/exit handling</h4>
  * <p>
@@ -317,6 +324,7 @@
         to.primaryButtonDown = from.primaryButtonDown;
         to.secondaryButtonDown = from.secondaryButtonDown;
         to.middleButtonDown = from.middleButtonDown;
+        to.synthesized = from.synthesized;
         to.source = source;
         to.target = target;
 
@@ -334,7 +342,7 @@
                 evt.screenY, evt.button, evt.clickCount, evt.stillSincePress,
                 evt.shiftDown, evt.controlDown, evt.altDown, evt.metaDown,
                 evt.popupTrigger, evt.primaryButtonDown, evt.middleButtonDown,
-                evt.secondaryButtonDown,
+                evt.secondaryButtonDown, evt.synthesized,
                 (impl_EventType != null
                         ? impl_EventType
                         : (EventType<? extends MouseEvent>)
@@ -361,6 +369,7 @@
           boolean _primaryButtonDown,
           boolean _middleButtonDown,
           boolean _secondaryButtonDown,
+          boolean _synthesized,
           EventType<? extends MouseEvent> _eventType
           )
     {
@@ -382,6 +391,7 @@
         e.primaryButtonDown = _primaryButtonDown;
         e.middleButtonDown = _middleButtonDown;
         e.secondaryButtonDown = _secondaryButtonDown;
+        e.synthesized = _synthesized;
         return e;
     }
 
@@ -404,6 +414,7 @@
           boolean _primaryButtonDown,
           boolean _middleButtonDown,
           boolean _secondaryButtonDown,
+          boolean _synthesized,
           EventType<? extends MouseEvent> _eventType
           )
     {
@@ -425,6 +436,7 @@
         e.primaryButtonDown = _primaryButtonDown;
         e.middleButtonDown = _middleButtonDown;
         e.secondaryButtonDown = _secondaryButtonDown;
+        e.synthesized = _synthesized;
         return e;
     }
 
@@ -685,6 +697,21 @@
         return metaDown;
     }
 
+    private boolean synthesized;
+
+    /**
+     * Indicates whether this event is synthesized from using a touch screen
+     * instead of usual mouse event source devices like mouse or track pad.
+     * When a finger is dragged over a touch screen, both scrolling gesture
+     * and mouse dragging are produced. If it causes a conflict in an
+     * application, this flag can be used to tell apart the usual mouse dragging
+     * from the touch screen dragging already handled as scroll events.
+     * @return true if this event is synthesized from using a touch screen
+     */
+    public boolean isSynthesized() {
+        return synthesized;
+    }
+
     /**
      * Returns whether or not the host platform common shortcut modifier is
      * down on this event. This common shortcut modifier is a modifier key which
@@ -835,6 +862,9 @@
         if (isShortcutDown()) {
             sb.append(", shortcutDown");
         }
+        if (isSynthesized()) {
+            sb.append(", synthesized");
+        }
 
         return sb.append("]").toString();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/input/RotateEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.input;
+
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+
+/**
+ * Rotate event indicates that user performed rotating gesture such as
+ * dragging two fingers around each other on track pad,
+ * touch screen or other similar device.
+ * <p>
+ * The event is delivered to the top-most
+ * node picked on the gesture coordinates in time of the gesture start - the 
+ * whole gesture is delivered to the same node even if the coordinates change
+ * during the gesture.
+ * <p>
+ * The event provides two values: {@code angle} is the rotation angle of this
+ * event, {@code totalAngle} is the rotation angle of the whole gesture. Both
+ * values are in degrees and work well when added to the node's {@code rotate}
+ * property value (positive values for clockwise rotation).
+ * <p>
+ * As all gestures, rotation can be direct (performed directly at
+ * the concrete coordinates as on touch screen) or indirect (performed
+ * indirectly as on track pad - the mouse cursor location is usually used
+ * as the gesture coordinates).
+ * <p>
+ * The gesture's {@code ROTATE} events are surounded by {@code ROTATION_STARTED}
+ * and {@code ROTATION_FINISHED} events. If rotation inertia is active on the
+ * given platform, some {@code ROTATE} events with {@code isInertia()} returning
+ * {@code true} can come after {@code ROTATION_FINISHED}.
+ */
+public class RotateEvent extends GestureEvent {
+
+    /**
+     * Common supertype for all rotate event types.
+     */
+    public static final EventType<RotateEvent> ANY =
+            new EventType<RotateEvent>(GestureEvent.ANY, "ANY_ROTATE");
+
+    /**
+     * This event occurs when user performs a rotating gesture such as
+     * dragging two fingers around each other.
+     */
+    public static final EventType<RotateEvent> ROTATE =
+            new EventType<RotateEvent>(RotateEvent.ANY, "ROTATE");
+
+    /**
+     * This event occurs when a rotating gesture is detected.
+     */
+    public static final EventType<RotateEvent> ROTATION_STARTED =
+            new EventType<RotateEvent>(RotateEvent.ANY, "ROTATION_STARTED");
+
+    /**
+     * This event occurs when a rotating gesture ends.
+     */
+    public static final EventType<RotateEvent> ROTATION_FINISHED =
+            new EventType<RotateEvent>(RotateEvent.ANY, "ROTATION_FINISHED");
+
+    private RotateEvent(final EventType<? extends RotateEvent> eventType) {
+        super(eventType);
+    }
+
+    private RotateEvent(Object source, EventTarget target,
+            final EventType<? extends RotateEvent> eventType) {
+        super(source, target, eventType);
+    }
+
+    private RotateEvent(final EventType<? extends RotateEvent> eventType,
+            double angle, double totalAngle,
+            double x, double y,
+            double screenX, double screenY,
+            boolean shiftDown,
+            boolean controlDown,
+            boolean altDown,
+            boolean metaDown,
+            boolean direct,
+            boolean inertia) {
+
+        super(eventType, x, y, screenX, screenY,
+                shiftDown, controlDown, altDown, metaDown, direct, inertia);
+        this.angle = angle;
+        this.totalAngle = totalAngle;
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    public static RotateEvent impl_rotateEvent(final EventType<? extends RotateEvent> eventType,
+            double angle, double totalAngle,
+            double x, double y,
+            double screenX, double screenY,
+            boolean shiftDown,
+            boolean controlDown,
+            boolean altDown,
+            boolean metaDown,
+            boolean direct,
+            boolean inertia) {
+        return new RotateEvent(eventType, angle, totalAngle,
+                x, y, screenX, screenY,
+                shiftDown, controlDown, altDown, metaDown, direct, inertia);
+    }
+
+    private double angle;
+
+    /**
+     * Gets the rotation angle of this event.
+     * The angle is in degrees and work well when added to the node's
+     * {@code rotate} property value (positive values for clockwise rotation).
+     * @return The rotation angle of this event
+     */
+    public double getAngle() {
+        return angle;
+    }
+
+    private double totalAngle;
+
+    /**
+     * Gets the cumulative rotation angle of this gesture.
+     * The angle is in degrees and work well when added to the node's
+     * {@code rotate} property value (positive values for clockwise rotation).
+     * @return The cumulative rotation angle of this gesture
+     */
+    public double getTotalAngle() {
+        return totalAngle;
+    }
+
+    /**
+     * Returns a string representation of this {@code RotateEvent} object.
+     * @return a string representation of this {@code RotateEvent} object.
+     */
+    @Override public String toString() {
+        final StringBuilder sb = new StringBuilder("RotateEvent [");
+
+        sb.append("source = ").append(getSource());
+        sb.append(", target = ").append(getTarget());
+        sb.append(", eventType = ").append(getEventType());
+        sb.append(", consumed = ").append(isConsumed());
+
+        sb.append(", angle = ").append(getAngle());
+        sb.append(", totalAngle = ").append(getTotalAngle());
+        sb.append(", x = ").append(getX()).append(", y = ").append(getY());
+        sb.append(isDirect() ? ", direct" : ", indirect");
+
+        if (isShiftDown()) {
+            sb.append(", shiftDown");
+        }
+        if (isControlDown()) {
+            sb.append(", controlDown");
+        }
+        if (isAltDown()) {
+            sb.append(", altDown");
+        }
+        if (isMetaDown()) {
+            sb.append(", metaDown");
+        }
+        if (isShortcutDown()) {
+            sb.append(", shortcutDown");
+        }
+
+        return sb.append("]").toString();
+    }
+}
--- a/javafx-ui-common/src/javafx/scene/input/ScrollEvent.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/ScrollEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -24,22 +24,34 @@
  */
 package javafx.scene.input;
 
-import com.sun.javafx.scene.input.InputEventUtils;
-import com.sun.javafx.tk.Toolkit;
-import javafx.event.Event;
 import javafx.event.EventTarget;
 import javafx.event.EventType;
-import javafx.geometry.Point2D;
 
 /**
- * Scroll event indicates that user performed scrolling by mouse wheel, touchpad
- * or other similar device. It is always delivered to the node under cursor
- * regardless of current focus owner (similarly to mouse events).
+ * Scroll event indicates that user performed scrolling by mouse wheel,
+ * track pad, touch screen or other similar device.
+ * <p>
+ * When the scrolling is produced by a touch gesture (such as dragging a finger
+ * over a touch screen), it is surrounded by the {@code SCROLL_STARTED} and 
+ * {@code SCROLL_FINISHED} events. When the scrolling is caused by a mouse
+ * wheel rotation, only a one-time {@code SCROLL} event is delivered, without
+ * the started/finished surroundings. If scrolling inertia is active on the
+ * given platform, some {@code SCROLL} events with {@code isInertia()} returning
+ * {@code true} can come after {@code SCROLL_FINISHED}.
+ * <p>
+ * The event is delivered to the top-most
+ * node picked on the gesture coordinates in time of the gesture start - the
+ * whole gesture is delivered to the same node even if the coordinates change
+ * during the gesture. For mouse wheel rotation the event is delivered to the
+ * top-most node picked on mouse cursor location. The delivery is independent
+ * of current focus owner.
  * <p>
  * The event provides two different types of scrolling values: pixel-based and 
  * character/line-based. The basic {@code deltaX} and {@code deltaY} values 
  * give reasonable results when used as number of pixels
- * to scroll. For scrolling text (or other line-based content as tables) the 
+ * to scroll (The {@code totalDeltaX} and {@code totalDeltaY} contain the 
+ * cumulative values for the whole gesture, zeros for mouse wheel).
+ * For scrolling text (or other line-based content as tables) the
  * {@code textDelta} values should be used if they are available. The 
  * {@code textDeltaXUnits} and {@code textDeltaYUnits} determine how to 
  * interpret the {@code textDeltaX} and {@code textDeltaY} values. If the 
@@ -47,6 +59,11 @@
  * (not provided by the underlying platform) and the pixel-based values
  * need to be used.
  * <p>
+ * As all gestures, scrolling can be direct (performed directly at
+ * the concrete coordinates as on touch screen) or indirect (performed
+ * indirectly as on track pad or with mouse - the mouse cursor location
+ * is usually used as the gesture coordinates).
+ * <p>
  * For example, scrolling a graphical node can be achieved by following code:
  * <code><pre>
     node.setOnScroll(new EventHandler<ScrollEvent>() {
@@ -73,19 +90,35 @@
     }
  </pre></code>
  */
-public class ScrollEvent extends InputEvent {
-
-    /**
-     * This event occurs when user performs a scrolling action such as
-     * rotating mouse wheel.
-     */
-    public static final EventType<ScrollEvent> SCROLL =
-            new EventType<ScrollEvent>(InputEvent.ANY, "SCROLL");
+public class ScrollEvent extends GestureEvent {
 
     /**
      * Common supertype for all scroll event types.
      */
-    public static final EventType<ScrollEvent> ANY = SCROLL;
+    public static final EventType<ScrollEvent> ANY =
+            new EventType<ScrollEvent>(GestureEvent.ANY, "ANY_SCROLL");
+
+    /**
+     * This event occurs when user performs a scrolling action such as
+     * rotating mouse wheel or dragging a finger over touch screen.
+     */
+    public static final EventType<ScrollEvent> SCROLL =
+            new EventType<ScrollEvent>(ScrollEvent.ANY, "SCROLL");
+
+    /**
+     * This event occurs when a scrolling gesture is detected. It doesn't
+     * occur for mouse wheel scrolling.
+     */
+    public static final EventType<ScrollEvent> SCROLL_STARTED =
+            new EventType<ScrollEvent>(ScrollEvent.ANY, "SCROLL_STARTED");
+
+    /**
+     * This event occurs when a scrolling gesture ends. It doesn't
+     * occur for mouse wheel scrolling.
+     */
+    public static final EventType<ScrollEvent> SCROLL_FINISHED =
+            new EventType<ScrollEvent>(ScrollEvent.ANY, "SCROLL_FINISHED");
+
     
     private ScrollEvent(final EventType<? extends ScrollEvent> eventType) {
         super(eventType);
@@ -95,34 +128,35 @@
             final EventType<? extends ScrollEvent> eventType) {
         super(source, target, eventType);
     }
-    
-    /**
-     * Fills the given event by this event's coordinates recomputed to the given
-     * source object.
-     * @param newEvent Event whose coordinates are to be filled
-     * @param newSource Source object to compute coordinates for
-     */
-    private void recomputeCoordinatesToSource(ScrollEvent newEvent, Object newSource) {
 
-        final Point2D newCoordinates = InputEventUtils.recomputeCoordinates(
-                new Point2D(x, y), source, newSource);
+    private ScrollEvent(final EventType<? extends ScrollEvent> eventType,
+            double deltaX, double deltaY,
+            double gestureDeltaX, double gestureDeltaY,
+            HorizontalTextScrollUnits textDeltaXUnits, double textDeltaX,
+            VerticalTextScrollUnits textDeltaYUnits, double textDeltaY,
+            int touchCount,
+            double x, double y,
+            double screenX, double screenY,
+            boolean shiftDown,
+            boolean controlDown,
+            boolean altDown,
+            boolean metaDown,
+            boolean direct,
+            boolean inertia) {
 
-        newEvent.x = newCoordinates.getX();
-        newEvent.y = newCoordinates.getY();
-        newEvent.sceneX = getSceneX();
-        newEvent.sceneY = getSceneY();
+        super(eventType, x, y, screenX, screenY,
+                shiftDown, controlDown, altDown, metaDown, direct, inertia);
+        this.deltaX = deltaX;
+        this.deltaY = deltaY;
+        this.totalDeltaX = gestureDeltaX;
+        this.totalDeltaY = gestureDeltaY;
+        this.textDeltaXUnits = textDeltaXUnits;
+        this.textDeltaX = textDeltaX;
+        this.textDeltaYUnits = textDeltaYUnits;
+        this.textDeltaY = textDeltaY;
+        this.touchCount = touchCount;
     }
     
-    /**
-     * @InheritDoc
-     */
-    @Override
-    public Event copyFor(Object newSource, EventTarget newTarget) {
-        ScrollEvent e = (ScrollEvent) super.copyFor(newSource, newTarget);
-        recomputeCoordinatesToSource(e, newSource);
-        return e;
-    }
-
     private double deltaX;
 
     /**
@@ -161,6 +195,44 @@
         return deltaY;
     }
     
+    private double totalDeltaX;
+
+    /**
+     * Gets the cumulative horizontal scroll amount for the whole gesture.
+     * This value should be interpreted as a number of pixels to scroll
+     * relatively to the state at the beginning of the gesture.
+     * Contains zeros for mouse wheel scrolling.
+     * <p>
+     * The sign of the value is reversed compared to the coordinate system
+     * (when you scroll right, the content actually needs to go left). So the
+     * returned value can be simply added to the content's {@code X}
+     * coordinate.
+     *
+     * @return Number of pixels scrolled horizontally during the gesture
+     */
+    public double getTotalDeltaX() {
+        return totalDeltaX;
+    }
+
+    private double totalDeltaY;
+
+    /**
+     * Gets the cumulative vertical scroll amount for the whole gesture.
+     * This value should be interpreted as a number of pixels to scroll
+     * relatively to the state at the beginning of the gesture.
+     * Contains zeros for mouse wheel scrolling.
+     * <p>
+     * The sign of the value is reversed compared to the coordinate system
+     * (when you scroll down, the content actually needs to go up). So the
+     * returned value can be simply added to the content's {@code Y}
+     * coordinate.
+     *
+     * @return Number of pixels to scrolled vertically during the gesture
+     */
+    public double getTotalDeltaY() {
+        return totalDeltaY;
+    }
+
     private HorizontalTextScrollUnits textDeltaXUnits;
 
     /**
@@ -218,149 +290,17 @@
     public double getTextDeltaY() {
         return textDeltaY;
     }
-    
-    private double x;
+
+    private int touchCount;
 
     /**
-     * Gets the horizontal position of the event relative to the
-     * origin of the ScrollEvent's source.
-     *
-     * @return the horizontal position of the event relative to the
-     * origin of the ScrollEvent's source.
+     * Gets number of touch points that caused this event. For non-touch source
+     * devices as mouse wheel and for inertia events after gesture finish
+     * it returns zero.
+     * @return Number of touch points that caused this event
      */
-    public final double getX() {
-        return x;
-    }
-
-    private double y;
-
-    /**
-     * Gets the vertical position of the event relative to the
-     * origin of the ScrollEvent's source.
-     * 
-     * @return the vertical position of the event relative to the
-     * origin of the ScrollEvent's source.
-     */
-    public final double getY() {
-        return y;
-    }
-
-    private double screenX;
-
-    /**
-     * Gets the absolute horizontal position of the event.
-     * @return the absolute horizontal position of the event
-     */
-    public final double getScreenX() {
-        return screenX;
-    }
-
-    private double screenY;
-
-    /**
-     * Gets the absolute vertical position of the event.
-     * @return the absolute vertical position of the event
-     */
-    public final double getScreenY() {
-        return screenY;
-    }
-
-    private double sceneX;
-
-    /**
-     * Gets the horizontal position of the event relative to the
-     * origin of the {@code Scene} that contains the ScrollEvent's source.
-     * If the node is not in a {@code Scene}, then the value is relative to
-     * the boundsInParent of the root-most parent of the ScrollEvent's node.
-     * 
-     * @return the horizontal position of the event relative to the
-     * origin of the {@code Scene} that contains the ScrollEvent's source
-     */
-    public final double getSceneX() {
-        return sceneX;
-    }
-
-    private double sceneY;
-
-    /**
-     * Gets the vertical position of the event relative to the
-     * origin of the {@code Scene} that contains the ScrollEvent's source.
-     * If the node is not in a {@code Scene}, then the value is relative to
-     * the boundsInParent of the root-most parent of the ScrollEvent's node.
-     * 
-     * @return the vertical position of the event relative to the
-     * origin of the {@code Scene} that contains the ScrollEvent's source
-     */
-    public final double getSceneY() {
-        return sceneY;
-    }
-    
-    private boolean shiftDown;
-
-    /**
-     * Indicates whether or not the Shift modifier is down on this event.
-     * @return true if the Shift modifier is down on this event
-     */
-    public final boolean isShiftDown() {
-        return shiftDown;
-    }
-
-    private boolean controlDown;
-
-    /**
-     * Indicates whether or not the Control modifier is down on this event.
-     * @return true if the Control modifier is down on this event
-     */
-    public final boolean isControlDown() {
-        return controlDown;
-    }
-
-    private boolean altDown;
-
-    /**
-     * Indicates whether or not the Alt modifier is down on this event.
-     * @return true if the Alt modifier is down on this event
-     */
-    public final boolean isAltDown() {
-        return altDown;
-    }
-
-    private boolean metaDown;
-
-    /**
-     * Indicates whether or not the Meta modifier is down on this event.
-     * @return true if the Meta modifier is down on this event
-     */
-    public final boolean isMetaDown() {
-        return metaDown;
-    }
-
-    /**
-     * Indicates whether or not the host platform common shortcut modifier is
-     * down on this event. This common shortcut modifier is a modifier key which
-     * is used commonly in shortcuts on the host platform. It is for example
-     * {@code control} on Windows and {@code meta} (command key) on Mac.
-     *
-     * @return {@code true} if the shortcut modifier is down, {@code false}
-     *      otherwise
-     */
-    public final boolean isShortcutDown() {
-        switch (Toolkit.getToolkit().getPlatformShortcutKey()) {
-            case SHIFT:
-                return shiftDown;
-
-            case CONTROL:
-                return controlDown;
-
-            case ALT:
-                return altDown;
-
-            case META:
-                return metaDown;
-
-            default:
-                return false;
-        }
+    public int getTouchCount() {
+        return touchCount;
     }
 
     /**
@@ -368,36 +308,30 @@
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
     @Deprecated
-    public static ScrollEvent impl_scrollEvent(
+    public static ScrollEvent impl_scrollEvent(EventType<ScrollEvent> eventType,
             double _scrollX, double _scrollY,
+            double _totalScrollX, double _totalScrollY,
             HorizontalTextScrollUnits _scrollTextXUnits, double _scrollTextX,
             VerticalTextScrollUnits _scrollTextYUnits, double _scrollTextY,
+            int _touchPoints,
             double _x, double _y,
             double _screenX, double _screenY,
             boolean _shiftDown,
             boolean _controlDown,
             boolean _altDown,
-            boolean _metaDown
+            boolean _metaDown,
+            boolean _direct,
+            boolean _inertia
           )
     {
-        ScrollEvent e = new ScrollEvent(SCROLL);
-        e.deltaX = _scrollX;
-        e.deltaY = _scrollY;
-        e.textDeltaXUnits = _scrollTextXUnits;
-        e.textDeltaX = _scrollTextX;
-        e.textDeltaYUnits = _scrollTextYUnits;
-        e.textDeltaY = _scrollTextY;
-        e.x = _x;
-        e.y = _y;
-        e.screenX = _screenX;
-        e.screenY = _screenY;
-        e.sceneX = _x;
-        e.sceneY = _y;
-        e.shiftDown = _shiftDown;
-        e.controlDown = _controlDown;
-        e.altDown = _altDown;
-        e.metaDown = _metaDown;
-        return e;
+        return new ScrollEvent(eventType, _scrollX, _scrollY,
+                _totalScrollX, _totalScrollY,
+                _scrollTextXUnits, _scrollTextX,
+                _scrollTextYUnits, _scrollTextY,
+                _touchPoints,
+                _x, _y, _screenX, _screenY,
+                _shiftDown, _controlDown, _altDown, _metaDown, 
+                _direct, _inertia);
     }
     
     /**
@@ -414,11 +348,15 @@
 
         sb.append(", deltaX = ").append(getDeltaX())
                 .append(", deltaY = ").append(getDeltaY());
+        sb.append(", totalDeltaX = ").append(getTotalDeltaX())
+                .append(", totalDeltaY = ").append(getTotalDeltaY());
         sb.append(", textDeltaXUnits = ").append(getTextDeltaXUnits())
                 .append(", textDeltaX = ").append(getTextDeltaX());
         sb.append(", textDeltaYUnits = ").append(getTextDeltaYUnits())
                 .append(", textDeltaY = ").append(getTextDeltaY());
+        sb.append(", touchCount = ").append(getTouchCount());
         sb.append(", x = ").append(getX()).append(", y = ").append(getY());
+        sb.append(isDirect() ? ", direct" : ", indirect");
 
         if (isShiftDown()) {
             sb.append(", shiftDown");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/input/SwipeEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package javafx.scene.input;
+
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+
+/**
+ * Swipe event indicates that user performed a swipe gesture such as
+ * dragging a finger in one direction on touch screen.
+ * <p>
+ * Unlike some other gestures, the swipe gesture is not continual - the whole
+ * gesture produces only one event. The event is delivered to the top-most
+ * node picked on the gesture coordinates.
+ * <p>
+ * The swipe gesture has four types according to the movement direction.
+ * The gesture can be performed by any number of touch points, the number
+ * is provided by {@code getTouchCount()} method.
+ * <p>
+ * Note that swipe and scroll gestures are not exclusive. A single touch screen
+ * action can result in both gestures being delivered.
+ * <p>
+ * As all gestures, swipe can be direct (performed directly at
+ * the concrete coordinates as on touch screen - the center of the gesture
+ * is used as gesture coordinates) or indirect (performed
+ * indirectly as on track pad - the mouse cursor location is usually used
+ * as the gesture coordinates in this case).
+ */
+public class SwipeEvent extends GestureEvent {
+
+    /**
+     * Common supertype for all swipe event types.
+     */
+    public static final EventType<SwipeEvent> ANY =
+            new EventType<SwipeEvent>(GestureEvent.ANY, "ANY_SWIPE");
+
+    /**
+     * This event occurs when user performs leftward swipe gesture.
+     */
+    public static final EventType<SwipeEvent> SWIPE_LEFT =
+            new EventType<SwipeEvent>(SwipeEvent.ANY, "SWIPE_LEFT");
+
+    /**
+     * This event occurs when user performs rightward swipe gesture.
+     */
+    public static final EventType<SwipeEvent> SWIPE_RIGHT =
+            new EventType<SwipeEvent>(SwipeEvent.ANY, "SWIPE_RIGHT");
+
+    /**
+     * This event occurs when user performs upward swipe gesture.
+     */
+    public static final EventType<SwipeEvent> SWIPE_UP =
+            new EventType<SwipeEvent>(SwipeEvent.ANY, "SWIPE_UP");
+
+    /**
+     * This event occurs when user performs downward swipe gesture.
+     */
+    public static final EventType<SwipeEvent> SWIPE_DOWN =
+            new EventType<SwipeEvent>(SwipeEvent.ANY, "SWIPE_DOWN");
+
+    private SwipeEvent(final EventType<? extends SwipeEvent> eventType) {
+        super(eventType);
+    }
+
+    private SwipeEvent(Object source, EventTarget target,
+            final EventType<? extends SwipeEvent> eventType) {
+        super(source, target, eventType);
+    }
+
+    private SwipeEvent(final EventType<? extends SwipeEvent> eventType,
+            int touchCount,
+            double x, double y,
+            double screenX, double screenY,
+            boolean shiftDown,
+            boolean controlDown,
+            boolean altDown,
+            boolean metaDown,
+            boolean direct) {
+
+        super(eventType, x, y, screenX, screenY,
+                shiftDown, controlDown, altDown, metaDown, direct, false);
+        this.touchCount = touchCount;
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    public static SwipeEvent impl_swipeEvent(final EventType<? extends SwipeEvent> eventType,
+            int touchCount,
+            double x, double y,
+            double screenX, double screenY,
+            boolean shiftDown,
+            boolean controlDown,
+            boolean altDown,
+            boolean metaDown,
+            boolean direct) {
+        return new SwipeEvent(eventType, touchCount,
+                x, y, screenX, screenY,
+                shiftDown, controlDown, altDown, metaDown, direct);
+    }
+
+
+    private int touchCount;
+
+    /**
+     * Gets number of touch points that caused this event.
+     * @return Number of touch points that caused this event
+     */
+    public int getTouchCount() {
+        return touchCount;
+    }
+
+    /**
+     * Returns a string representation of this {@code SwipeEvent} object.
+     * @return a string representation of this {@code SwipeEvent} object.
+     */
+    @Override public String toString() {
+        final StringBuilder sb = new StringBuilder("SwipeEvent [");
+
+        sb.append("source = ").append(getSource());
+        sb.append(", target = ").append(getTarget());
+        sb.append(", eventType = ").append(getEventType());
+        sb.append(", consumed = ").append(isConsumed());
+        sb.append(", touchCount = ").append(getTouchCount());
+
+        sb.append(", x = ").append(getX()).append(", y = ").append(getY());
+        sb.append(isDirect() ? ", direct" : ", indirect");
+
+        if (isShiftDown()) {
+            sb.append(", shiftDown");
+        }
+        if (isControlDown()) {
+            sb.append(", controlDown");
+        }
+        if (isAltDown()) {
+            sb.append(", altDown");
+        }
+        if (isMetaDown()) {
+            sb.append(", metaDown");
+        }
+        if (isShortcutDown()) {
+            sb.append(", shortcutDown");
+        }
+
+        return sb.append("]").toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/input/TouchEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.input;
+
+import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
+import java.util.Collections;
+import java.util.List;
+import javafx.event.Event;
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+
+/**
+ * Touch event indicates a touch screen action. It contains detailed information
+ * about each particular touch point.
+ * <p>
+ * Touch point represents a single touched finger and has its location,
+ * state (pressed/moved/released/stationary) and an ID unique in scope of a
+ * single gesture. For detailed reference see {@link TouchPoint}.
+ * <p>
+ * For each multi-touch action a set of touch events is generated - for each
+ * touch point one. The event has type corresponds to its touch point's state.
+ * Each of the events also contain
+ * list of all the touch points. This design allows for handling complicated
+ * multi-touch actions from one place while keeping it possible to
+ * filter/consume each touch point separately. To recognize
+ * which events belong into a single set there is {@code getEventSetId()}
+ * method.
+ * <p>
+ * Each touch point is - similarly to mouse dragging - delivered to a single
+ * node on which it was pressed, regardless of where it moves then. It is
+ * possible to change this behavior by using a grabbing mechanism described
+ * in {@link TouchPoint} documentation.
+ */
+public final class TouchEvent extends InputEvent {
+
+    /**
+     * Common supertype for all touch event types.
+     */
+    public static final EventType<TouchEvent> ANY =
+            new EventType<TouchEvent>(InputEvent.ANY);
+
+    /**
+     * This event occurs when the touch point is pressed (touched for the
+     * first time).
+     */
+    public static final EventType<TouchEvent> TOUCH_PRESSED =
+            new EventType<TouchEvent>(ANY, "TOUCH_PRESSED");
+
+    /**
+     * This event occurs when the touch point is moved.
+     */
+    public static final EventType<TouchEvent> TOUCH_MOVED =
+            new EventType<TouchEvent>(ANY, "TOUCH_MOVED");
+
+    /**
+     * This event occurs when the touch point is released.
+     */
+    public static final EventType<TouchEvent> TOUCH_RELEASED =
+            new EventType<TouchEvent>(ANY, "TOUCH_RELEASED");
+
+    /**
+     * This event occurs when the touch point is pressed and still (doesn't
+     * move).
+     */
+    public static final EventType<TouchEvent> TOUCH_STATIONARY =
+            new EventType<TouchEvent>(ANY, "TOUCH_STATIONARY");
+
+    private TouchEvent(EventType<? extends TouchEvent> eventType) {
+        super(eventType);
+    }
+
+    private TouchEvent(EventType<? extends TouchEvent> eventType,
+            TouchPoint touchPoint, List<TouchPoint> touchPoints, int eventSetId,
+            boolean shiftDown, boolean controlDown, boolean altDown,
+            boolean metaDown) {
+        super(eventType);
+        if (touchPoints != null) {
+            this.touchPoints = Collections.unmodifiableList(touchPoints);
+        }
+        this.eventSetId = eventSetId;
+        this.shiftDown = shiftDown;
+        this.controlDown = controlDown;
+        this.altDown = altDown;
+        this.metaDown = metaDown;
+        this.touchPoint = touchPoint;
+    }
+
+    /**
+     * Returns number of touch points represented by this touch event set.
+     * The returned number matches the size of the {@code touchPoints} list.
+     * @return
+     */
+    public int getTouchCount() {
+        return touchPoints.size();
+    }
+
+    /**
+     * Recomputes touch event for the given event source object.
+     * @param event Event to modify
+     * @param oldSource Source object of the current values
+     * @param newSource Source object to compute values for
+     */
+    private static void recomputeToSource(TouchEvent event, Object oldSource,
+            Object newSource) {
+
+        for (TouchPoint tp : event.touchPoints) {
+            tp.recomputeToSource(oldSource, newSource);
+        }
+    }
+
+
+    /**
+     * @InheritDoc
+     */
+    @Override
+    public Event copyFor(Object newSource, EventTarget newTarget) {
+        TouchEvent e = (TouchEvent) super.copyFor(newSource, newTarget);
+        recomputeToSource(e, getSource(), newSource);
+
+        return e;
+    }
+
+    // isDirect doesn't currently have public getter because we are currently
+    // ignoring indirect touch events and claim that touch events always
+    // represent a direct touch-screen action
+    private boolean isDirect;
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    public boolean impl_isDirect() {
+        return isDirect;
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    public void impl_setDirect(boolean direct) {
+        isDirect = direct;
+    }
+
+    private int eventSetId;
+
+    /**
+     * Gets sequential number of the set of touch events representing the same
+     * multi-touch action. For a multi-touch user action, number of touch points
+     * may exist; each of them produces a touch event, each of those touch
+     * events carry the same list of touch points - and all of them return the
+     * same number from this method. Then state of some of the touch points
+     * changes and the new set of events has new id. The id is guaranteed
+     * to be sequential and unique in scope of one gesture (is reset when
+     * all touch points are released).
+     *
+     * @return Sequential id of event set unique in scope of a gesture
+     */
+    public final int getEventSetId() {
+        return eventSetId;
+    }
+
+
+    /**
+     * Whether or not the Shift modifier is down on this event.
+     */
+    private boolean shiftDown;
+
+    /**
+     * Whether or not the Shift modifier is down on this event.
+     * @return true if the Shift modifier is down on this event
+     */
+    public final boolean isShiftDown() {
+        return shiftDown;
+    }
+
+    /**
+     * Whether or not the Control modifier is down on this event.
+     */
+    private boolean controlDown;
+
+    /**
+     * Whether or not the Control modifier is down on this event.
+     * @return true if the Control modifier is down on this event
+     */
+    public final boolean isControlDown() {
+        return controlDown;
+    }
+
+    /**
+     * Whether or not the Alt modifier is down on this event.
+     */
+    private boolean altDown;
+
+    /**
+     * Whether or not the Alt modifier is down on this event.
+     * @return true if the Alt modifier is down on this event
+     */
+    public final boolean isAltDown() {
+        return altDown;
+    }
+
+    /**
+     * Whether or not the Meta modifier is down on this event.
+     */
+    private boolean metaDown;
+
+    /**
+     * Whether or not the Meta modifier is down on this event.
+     * @return true if the Meta modifier is down on this event
+     */
+    public final boolean isMetaDown() {
+        return metaDown;
+    }
+
+    private TouchPoint touchPoint;
+
+    /**
+     * Gets the touch point of this event.
+     * @return Touch point of this event
+     */
+    public TouchPoint getTouchPoint() {
+        return touchPoint;
+    }
+
+    private List<TouchPoint> touchPoints;
+
+    /**
+     * Gets all the touch points represented by this set of touch events,
+     * including the touch point of this event. The list is unmodifiable and 
+     * is sorted by their IDs, which means it is also sorted by the time
+     * they were pressed. To distinguish between touch points belonging to
+     * a node and unrelated touch points, TouchPoint's {@code belongsTo}
+     * method can be used.
+     * @return All current touch points in an unmodifiable list
+     */
+    @ReturnsUnmodifiableCollection 
+    public List<TouchPoint> getTouchPoints() {
+        return touchPoints;
+    }
+
+    /**
+     * Returns a string representation of this {@code TouchEvent} object.
+     * @return a string representation of this {@code TouchEvent} object.
+     */
+    @Override public String toString() {
+        final StringBuilder sb = new StringBuilder("TouchEvent [");
+
+        sb.append("source = ").append(getSource());
+        sb.append(", target = ").append(getTarget());
+        sb.append(", eventType = ").append(getEventType());
+        sb.append(", consumed = ").append(isConsumed());
+        sb.append(", touchCount = ").append(getTouchCount());
+        sb.append(", eventSetId = ").append(getEventSetId());
+
+        sb.append(", touchPoint = ").append(getTouchPoint().toString());
+
+        return sb.append("]").toString();
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    public static TouchEvent impl_touchEvent(EventType<? extends TouchEvent> eventType,
+            TouchPoint touchPoint, List<TouchPoint> touchPoints, int eventSetId,
+            boolean shiftDown, boolean controlDown, boolean altDown,
+            boolean metaDown) {
+        return new TouchEvent(eventType, touchPoint, touchPoints, eventSetId,
+                shiftDown, controlDown, altDown, metaDown);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/input/TouchPoint.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.input;
+
+import com.sun.javafx.scene.input.InputEventUtils;
+import javafx.event.EventTarget;
+import javafx.geometry.Point2D;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+
+/**
+ * Touch point represents a single point of a multi-touch action, typically
+ * one finger touching a screen. It is contained in {@link TouchEvent}.
+ * <p>
+ * The touch point has its coordinates, state (see {@link State}) and ID. The
+ * ID is sequential number of this touch point unique in scope of a single
+ * multi-touch gesture.
+ * <p>
+ * Each touch point is by default delivered to a single node during its whole
+ * trajectory - to the node on which it was pressed. There is a grabbing API
+ * to modify this behavior. The above means that when touch point is pressed,
+ * it is automatically grabbed by the top-most node on the press coordinates.
+ * Any time during the gesture {@code grab()} and {@code ungrab()} methods
+ * can be used to alter the event delivery target. When grabbed by a different
+ * node, it will next time be targeted to it; when ungrabbed, it will be
+ * always targeted to the top-most node on the current location.
+ */
+public final class TouchPoint {
+
+    private EventTarget target;
+    private Object source;
+
+    public TouchPoint(int id, State state, double x, double y, double screenX,
+            double screenY) {
+        this.target = null;
+        this.id = id;
+        this.state = state;
+        this.x = x;
+        this.y = y;
+        this.sceneX = x;
+        this.sceneY = y;
+        this.screenX = screenX;
+        this.screenY = screenY;
+    }
+
+    /**
+     * Recomputes this touch point (coordinates, relevancy) for the given event
+     * source object.
+     * @param oldSource Source object of the current values
+     * @param newSource Source object to compute values for
+     */
+    void recomputeToSource(Object oldSource, Object newSource) {
+
+        final Point2D newCoordinates = InputEventUtils.recomputeCoordinates(
+                new Point2D(x, y), oldSource, newSource);
+
+        x = newCoordinates.getX();
+        y = newCoordinates.getY();
+
+        source = newSource;
+    }
+
+    /**
+     * Distinguishes between touch points targeted to the given node or some
+     * of its children from touch points targeted somewhere else. This allows
+     * for testing all touch points carried by one touch event on their
+     * relevance for a given node.
+     * @param target Node or other event target to be tested
+     * @return true if this touch point is targeted to the given target or
+     * some of its children
+     */
+    public boolean belongsTo(EventTarget target) {
+
+        if (this.target instanceof Node) {
+            Node n = (Node) this.target;
+
+            if (target instanceof Scene) {
+                return n.getScene() == target;
+            }
+            while (n != null) {
+                if (n == target) {
+                    return true;
+                }
+                n = n.getParent();
+            }
+        }
+        
+        return target == this.target;
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    public void impl_setTarget(EventTarget target) {
+        this.target = target;
+    }
+
+    private EventTarget grabbed = null;
+
+    /**
+     * Gets event target which has grabbed this touch point.
+     * @return The current grabbed target, null if the touch point is ungrabbed
+     */
+    public EventTarget getGrabbed() {
+        return grabbed;
+    }
+
+    /**
+     * Grabs this touch point by current event source. Next event containing
+     * this touch point will be targeted to the same node whose event handler
+     * called this method.
+     */
+    public void grab() {
+        if (source instanceof EventTarget) {
+            grabbed = (EventTarget) source;
+        } else {
+            throw new IllegalStateException("Cannot grab touch point, "
+                    + "source is not an instance of EventTarget: " + source);
+        }
+    }
+
+    /**
+     * Grabs this touch point by the given target. Next event containing this
+     * touch point will be targeted to it.
+     * @param target Target by which to grab the touch point
+     */
+    public void grab(EventTarget target) {
+        grabbed = target;
+    }
+
+    public void ungrab() {
+        grabbed = null;
+    }
+
+    private int id;
+
+    /**
+     * Gets identifier of this touch point. The number is sequential and unique
+     * in scope of one multi touch gesture. The first pressed touch point has id
+     * {@code 1}, each subsequently pressed touch points gets the next ordinal
+     * number until all touch points are released and the counter is reset.
+     *
+     * @return the identifier of this touch point.
+     */
+    public final int getId() {
+        return id;
+    }
+
+    private State state;
+
+    /**
+     * Gets state of this touch point
+     * @return state of this touch point
+     */
+    public final State getState() {
+        return state;
+    }
+
+
+    private double x;
+
+    /**
+     * Gets the horizontal position of the touch point relative to the
+     * origin of the TouchEvent's source.
+     *
+     * @return the horizontal position of the touch point relative to the
+     * origin of the TouchEvent's source.
+     */
+    public final double getX() {
+        return x;
+    }
+
+    private double y;
+
+    /**
+     * Gets the vertical position of the touch point relative to the
+     * origin of the TouchEvent's source.
+     *
+     * @return the vertical position of the touch point relative to the
+     * origin of the TouchEvent's source.
+     */
+    public final double getY() {
+        return y;
+    }
+
+    private double screenX;
+
+    /**
+     * Gets the absolute horizontal position of the touch point.
+     * @return the absolute horizontal position of the touch point
+     */
+    public final double getScreenX() {
+        return screenX;
+    }
+
+    private double screenY;
+
+    /**
+     * Gets the absolute vertical position of the touch point.
+     * @return the absolute vertical position of the touch point
+     */
+    public final double getScreenY() {
+        return screenY;
+    }
+
+    private double sceneX;
+
+    /**
+     * Gets the horizontal position of the touch point relative to the
+     * origin of the {@code Scene} that contains the TouchEvent's source.
+     * If the node is not in a {@code Scene}, then the value is relative to
+     * the boundsInParent of the root-most parent of the TouchEvent's node.
+     *
+     * @return the horizontal position of the touch point relative to the
+     * origin of the {@code Scene} that contains the TouchEvent's source
+     */
+    public final double getSceneX() {
+        return sceneX;
+    }
+
+    private double sceneY;
+
+    /**
+     * Gets the vertical position of the touch point relative to the
+     * origin of the {@code Scene} that contains the TouchEvent's source.
+     * If the node is not in a {@code Scene}, then the value is relative to
+     * the boundsInParent of the root-most parent of the TouchEvent's node.
+     *
+     * @return the vertical position of the touch point relative to the
+     * origin of the {@code Scene} that contains the TouchEvent's source
+     */
+    public final double getSceneY() {
+        return sceneY;
+    }
+
+    /**
+     * Gets event target on which the touch event carrying this touch point
+     * is fired.
+     * @return Event target for this touch point
+     */
+    public EventTarget getTarget() {
+        return target;
+    }
+
+    /**
+     * Returns a string representation of this {@code TouchPoint} object.
+     * @return a string representation of this {@code TouchPoint} object.
+     */
+    @Override public String toString() {
+        final StringBuilder sb = new StringBuilder("TouchPoint [");
+
+        sb.append("state = ").append(getState());
+        sb.append(", id = ").append(getId());
+        sb.append(", target = ").append(getTarget());
+        sb.append(", x = ").append(getX()).append(", y = ").append(getY());
+
+        return sb.append("]").toString();
+    }
+
+    /**
+     * Represents current state of the touch point
+     */
+    public enum State {
+        /**
+         * The touch point has just been pressed (touched for the first time)
+         */
+        PRESSED,
+        /**
+         * The touch point has been moved
+         */
+        MOVED,
+        /**
+         * The touch point remains pressed and still (without moving)
+         */
+        STATIONARY,
+        /**
+         * The touch point has been released
+         */
+        RELEASED
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/input/ZoomEvent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.input;
+
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+
+/**
+ * Zoom event indicates that user performed zooming gesture such as
+ * dragging two fingers apart on track pad, touch screen or other
+ * similar device.
+ * <p>
+ * The event is delivered to the top-most
+ * node picked on the gesture coordinates in time of the gesture start - the
+ * whole gesture is delivered to the same node even if the coordinates change
+ * during the gesture.
+ * <p>
+ * The event provides two values: {@code zoomFactor} is the zooming amount
+ * of this event, {@code totalZoomFactor} is the zooming amount of the whole
+ * gesture. The values work well when multiplied with the node's {@code scale}
+ * properties (values greater than {@code 1} for zooming in).
+ * <p>
+ * As all gestures, zooming can be direct (performed directly at
+ * the concrete coordinates as on touch screen) or indirect (performed
+ * indirectly as on track pad - the mouse cursor location is usually used
+ * as the gesture coordinates).
+ * <p>
+ * The gesture's {@code ZOOM} events are surounded by {@code ZOOM_STARTED}
+ * and {@code ZOOM_FINISHED} events. If zooming inertia is active on the
+ * given platform, some {@code ZOOM} events with {@code isInertia()} returning
+ * {@code true} can come after {@code ZOOM_FINISHED}.
+ */
+public class ZoomEvent extends GestureEvent {
+
+    /**
+     * Common supertype for all zoom event types.
+     */
+    public static final EventType<ZoomEvent> ANY =
+            new EventType<ZoomEvent>(GestureEvent.ANY, "ANY_ZOOM");
+
+    /**
+     * This event occurs when user performs a zooming gesture such as
+     * dragging two fingers apart.
+     */
+    public static final EventType<ZoomEvent> ZOOM =
+            new EventType<ZoomEvent>(ZoomEvent.ANY, "ZOOM");
+
+    /**
+     * This event occurs when a zooming gesture is detected.
+     */
+    public static final EventType<ZoomEvent> ZOOM_STARTED =
+            new EventType<ZoomEvent>(ZoomEvent.ANY, "ZOOM_STARTED");
+
+    /**
+     * This event occurs when a zooming gesture ends.
+     */
+    public static final EventType<ZoomEvent> ZOOM_FINISHED =
+            new EventType<ZoomEvent>(ZoomEvent.ANY, "ZOOM_FINISHED");
+
+    private ZoomEvent(final EventType<? extends ZoomEvent> eventType) {
+        super(eventType);
+    }
+
+    private ZoomEvent(Object source, EventTarget target,
+            final EventType<? extends ZoomEvent> eventType) {
+        super(source, target, eventType);
+    }
+
+    private ZoomEvent(final EventType<? extends ZoomEvent> eventType,
+            double zoomFactor,
+            double totalZoomFactor,
+            double x, double y,
+            double screenX, double screenY,
+            boolean shiftDown,
+            boolean controlDown,
+            boolean altDown,
+            boolean metaDown,
+            boolean direct,
+            boolean inertia) {
+
+        super(eventType, x, y, screenX, screenY,
+                shiftDown, controlDown, altDown, metaDown, direct, inertia);
+        this.zoomFactor = zoomFactor;
+        this.totalZoomFactor = totalZoomFactor;
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    public static ZoomEvent impl_zoomEvent(final EventType<? extends ZoomEvent> eventType,
+            double zoomFactor,
+            double totalZoomFactor,
+            double x, double y,
+            double screenX, double screenY,
+            boolean shiftDown,
+            boolean controlDown,
+            boolean altDown,
+            boolean metaDown,
+            boolean direct,
+            boolean inertia) {
+        return new ZoomEvent(eventType, zoomFactor, totalZoomFactor,
+                x, y, screenX, screenY,
+                shiftDown, controlDown, altDown, metaDown, direct, inertia);
+    }
+
+
+    private double zoomFactor;
+
+    /**
+     * Gets the zooming amount of this event. The factor value works well when
+     * multiplied with the node's {@code scale} properties (values greater
+     * than {@code 1} for zooming in, values between {@code 0} and {@code 1}
+     * for zooming out).
+     * @return The zooming amount of this event
+     */
+    public double getZoomFactor() {
+        return zoomFactor;
+    }
+
+    private double totalZoomFactor;
+
+    /**
+     * Gets the zooming amount of this gesture. The factor value works well when
+     * multiplied with the node's {@code scale} properties (values greater
+     * than {@code 1} for zooming in, values between {@code 0} and {@code 1}
+     * for zooming out).
+     * @return The cumulative zooming amount of this gesture
+     */
+    public double getTotalZoomFactor() {
+        return totalZoomFactor;
+    }
+
+    /**
+     * Returns a string representation of this {@code ZoomEvent} object.
+     * @return a string representation of this {@code ZoomEvent} object.
+     */
+    @Override public String toString() {
+        final StringBuilder sb = new StringBuilder("ZoomEvent [");
+
+        sb.append("source = ").append(getSource());
+        sb.append(", target = ").append(getTarget());
+        sb.append(", eventType = ").append(getEventType());
+        sb.append(", consumed = ").append(isConsumed());
+
+        sb.append(", zoomFactor = ").append(getZoomFactor());
+        sb.append(", totalZoomFactor = ").append(getTotalZoomFactor());
+        sb.append(", x = ").append(getX()).append(", y = ").append(getY());
+        sb.append(isDirect() ? ", direct" : ", indirect");
+
+        if (isShiftDown()) {
+            sb.append(", shiftDown");
+        }
+        if (isControlDown()) {
+            sb.append(", controlDown");
+        }
+        if (isAltDown()) {
+            sb.append(", altDown");
+        }
+        if (isMetaDown()) {
+            sb.append(", metaDown");
+        }
+        if (isShortcutDown()) {
+            sb.append(", shortcutDown");
+        }
+
+        return sb.append("]").toString();
+    }
+}
--- a/javafx-ui-common/src/javafx/scene/shape/Shape.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/shape/Shape.java	Thu Mar 29 15:10:33 2012 -0700
@@ -842,7 +842,8 @@
     private static final double MIN_STROKE_MITER_LIMIT = 1.0f;
 
     private void updatePGShape() {
-        if (impl_isDirty(DirtyBits.SHAPE_STROKEATTRS)) {
+        if (strokeAttributesDirty && (getStroke() != null)) {
+            // set attributes of stroke only when stroke paint is not null
             final float[] pgDashArray =
                     (hasStrokeDashArray())
                             ? toPGDashArray(getStrokeDashArray())
@@ -857,6 +858,8 @@
                         (float)Utils.clampMin(getStrokeMiterLimit(),
                                               MIN_STROKE_MITER_LIMIT),
                         pgDashArray, (float)getStrokeDashOffset());
+
+           strokeAttributesDirty = false;
         }
 
         if (impl_isDirty(DirtyBits.SHAPE_MODE)) {
@@ -1103,6 +1106,8 @@
         return false;
     }
 
+    private boolean strokeAttributesDirty = true;
+
     private StrokeAttributes strokeAttributes;
 
     private StrokeAttributes getStrokeAttributes() {
@@ -1442,6 +1447,7 @@
 
         private void invalidated(final StyleableProperty propertyCssKey) {
             impl_markDirty(DirtyBits.SHAPE_STROKEATTRS);
+            strokeAttributesDirty = true;
             if (propertyCssKey != StyleableProperties.STROKE_DASH_OFFSET) {
                 // all stroke attributes change geometry except for the
                 // stroke dash offset
--- a/javafx-ui-common/src/javafx/stage/Stage.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/stage/Stage.java	Thu Mar 29 15:10:33 2012 -0700
@@ -30,6 +30,7 @@
 
 import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.BooleanPropertyBase;
+import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.StringProperty;
 import javafx.beans.property.StringPropertyBase;
 import javafx.collections.FXCollections;
@@ -43,6 +44,7 @@
 import com.sun.javafx.robot.impl.FXRobotHelper;
 import com.sun.javafx.stage.StageHelper;
 import com.sun.javafx.stage.StagePeerListener;
+import com.sun.javafx.tk.TKPulseListener;
 import com.sun.javafx.tk.TKStage;
 import com.sun.javafx.tk.Toolkit;
 import javafx.beans.property.DoubleProperty;
@@ -165,6 +167,24 @@
             }
         });
     }
+    
+    private static final StagePeerListener.StageAccessor STAGE_ACCESSOR = new StagePeerListener.StageAccessor() {
+
+        @Override
+        public void setIconified(Stage stage, boolean iconified) {
+            stage.iconifiedPropertyImpl().set(iconified);
+        }
+
+        @Override
+        public void setResizable(Stage stage, boolean resizable) {
+            stage.resizableProperty().set(resizable);
+        }
+
+        @Override
+        public void setFullScreen(Stage stage, boolean fs) {
+            stage.fullScreenPropertyImpl().set(fs);
+        }
+    };
 
     /**
      * Creates a new instance of decorated {@code Stage}.
@@ -480,6 +500,8 @@
     public final void setFullScreen(boolean value) {
         Toolkit.getToolkit().checkFxUserThread();
         fullScreenPropertyImpl().set(value);
+        if (impl_peer != null)
+            impl_peer.setFullScreen(value);
     }
 
     public final boolean isFullScreen() {
@@ -492,25 +514,7 @@
 
     private ReadOnlyBooleanWrapper fullScreenPropertyImpl () {
         if (fullScreen == null) {
-            fullScreen = new ReadOnlyBooleanWrapper() {
-
-                @Override
-                protected void invalidated() {
-                    if (impl_peer != null) {
-                        impl_peer.setFullScreen(get());
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Stage.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "fullScreen";
-                }
-            };
+            fullScreen = new ReadOnlyBooleanWrapper(Stage.this, "fullScreen");
         }
         return fullScreen;
     }
@@ -597,6 +601,8 @@
 
     public final void setIconified(boolean value) {
         iconifiedPropertyImpl().set(value);
+        if (impl_peer != null)
+            impl_peer.setIconified(value);
     }
 
     public final boolean isIconified() {
@@ -609,25 +615,7 @@
 
     private final ReadOnlyBooleanWrapper iconifiedPropertyImpl() {
         if (iconified == null) {
-            iconified = new ReadOnlyBooleanWrapper() {
-
-                @Override
-                protected void invalidated() {
-                    if (impl_peer != null) {
-                        impl_peer.setIconified(get());
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Stage.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "iconified";
-                }
-            };
+            iconified = new ReadOnlyBooleanWrapper(Stage.this, "iconified");
         }
         return iconified;
     }
@@ -644,6 +632,10 @@
 
     public final void setResizable(boolean value) {
         resizableProperty().set(value);
+        if (impl_peer != null) {
+            applyBounds();
+            impl_peer.setResizable(value);
+        }
     }
 
     public final boolean isResizable() {
@@ -652,25 +644,7 @@
 
     public final BooleanProperty resizableProperty() {
         if (resizable == null) {
-            resizable = new BooleanPropertyBase(true) {
-
-                @Override
-                protected void invalidated() {
-                    if (impl_peer != null) {
-                        impl_peer.setResizable(get());
-                    }
-                }
-
-                @Override
-                public Object getBean() {
-                    return Stage.this;
-                }
-
-                @Override
-                public String getName() {
-                    return "resizable";
-                }
-            };
+            resizable = new SimpleBooleanProperty(Stage.this, "resizable", true);
         }
         return resizable;
     }
@@ -869,8 +843,8 @@
             impl_peer = toolkit.createTKStage(getStyle(), isPrimary(),
                     getModality(), tkStage);
             impl_peer.setImportant(isImportant());
-            peerListener = new StagePeerListener(this);
-
+            peerListener = new StagePeerListener(this, STAGE_ACCESSOR);
+            
             // Finish initialization
             impl_peer.setResizable(isResizable());
             impl_peer.setFullScreen(isFullScreen());
--- a/javafx-ui-common/src/javafx/stage/Window.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/src/javafx/stage/Window.java	Thu Mar 29 15:10:33 2012 -0700
@@ -205,6 +205,7 @@
             }
 
             peerBoundsConfigurator.setSize(w, h, cw, ch);
+            applyBounds();
         }
     }
 
@@ -219,6 +220,8 @@
             double centerX = bounds.getMinX() + (bounds.getWidth() - getWidth()) / 2.0f;
             double centerY = bounds.getMinY() + (bounds.getHeight() - getHeight()) / 3.0f;
 
+            x.set(centerX);
+            y.set(centerY);
             peerBoundsConfigurator.setLocation(centerX, centerY);
         }
     }
@@ -711,17 +714,13 @@
                     }
                     
                     if (!xExplicit && !yExplicit) {
-                        // need to call apply before centering in the case the
-                        // window with and height needs to be calculated from
-                        // the scene (client) width / height (won't work on X11)
-                        peerBoundsConfigurator.apply();
                         centerOnScreen();
                     } else {
                         peerBoundsConfigurator.setLocation(getX(), getY());
                     }
 
-                    // set bounds before the window is shown
-                    peerBoundsConfigurator.apply();
+                    // set peer bounds before the window is shown
+                    applyBounds();
 
                     impl_peer.setOpacity((float)getOpacity());
 
@@ -1006,6 +1005,10 @@
         }
     }
 
+    final void applyBounds() {
+        peerBoundsConfigurator.apply();
+    }
+    
     /**
      * Caches all requested bounds settings and applies them at once during
      * the next pulse.
--- a/javafx-ui-common/test/unit/com/sun/javafx/test/MouseEventGenerator.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/com/sun/javafx/test/MouseEventGenerator.java	Thu Mar 29 15:10:33 2012 -0700
@@ -52,7 +52,7 @@
 
         MouseEvent event = MouseEvent.impl_mouseEvent(x, y, x, y, button,
                 1, false, false, false, false, false, primaryButtonDown,
-                false, false, type);
+                false, false, false, type);
 
         return event;
     }
--- a/javafx-ui-common/test/unit/javafx/scene/SceneTest.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/SceneTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -27,10 +27,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 import javafx.scene.layout.StackPane;
 import javafx.scene.shape.Rectangle;
 import javafx.scene.transform.Scale;
 import javafx.stage.Stage;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
 
 import org.junit.After;
 import org.junit.Before;
@@ -43,6 +46,9 @@
 public class SceneTest {
 
     private Stage stage;
+    private boolean handler1Called = false;
+    private boolean handler2Called = false;
+
 
     @Before
     public void setUp() {
@@ -510,6 +516,52 @@
         assertEquals(600, (int) scene.getHeight());
     }
 
+    @Test
+    public void focusChangeShouldBeAtomic() {
+        final Group root = new Group();
+
+        final Rectangle r1 = new Rectangle();
+        final Rectangle r2 = new Rectangle();
+
+        root.getChildren().addAll(r1, r2);
+        final Scene scene = new Scene(root, 600, 600);
+        stage.setScene(scene);
+
+        r1.requestFocus();
+
+        assertTrue(r1.isFocused());
+        assertFalse(r2.isFocused());
+
+        handler1Called = false;
+        handler2Called = true;
+
+        r1.focusedProperty().addListener(new ChangeListener<Boolean>() {
+            @Override
+            public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean focused) {
+                assertFalse(focused); // r1 is being defocused
+                assertTrue(r2.isFocused()); // r2 is already focused
+                handler1Called = true;
+
+                root.getChildren().remove(r2); // be evil: remove r2
+            }
+        });
+
+        r2.focusedProperty().addListener(new ChangeListener<Boolean>() {
+            @Override
+            public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean focused) {
+                assertTrue(focused); // r2 is being focused
+                assertFalse(r1.isFocused()); // r1 is already defocused
+                assertTrue(handler1Called); // r1 listener was called first
+                handler2Called = true;
+                // remove the listener otherwise thi final defocus calls it again
+                r2.focusedProperty().removeListener(this);
+            }
+        });
+
+        r2.requestFocus();
+        assertTrue(handler2Called); // both listeners were called
+    }
+
 }
 
 
--- a/javafx-ui-common/test/unit/javafx/scene/Scenegraph_eventHandlers_Test.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/Scenegraph_eventHandlers_Test.java	Thu Mar 29 15:10:33 2012 -0700
@@ -333,7 +333,7 @@
                                           button, clickCount,
                                           false, false, false, false,
                                           false, primaryButtonDown,
-                                          false, false, mouseEventType);
+                                          false, false, false, mouseEventType);
     }
 
     private static void setEventHandler(
--- a/javafx-ui-common/test/unit/javafx/scene/input/DragAndDropTest.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/DragAndDropTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -1272,7 +1272,7 @@
 
             MouseEvent event = MouseEvent.impl_mouseEvent(x, y, x, y, button,
                     1, false, false, false, false, false, primaryButtonDown,
-                    false, false, type);
+                    false, false, false, type);
 
             if (type == MouseEvent.MOUSE_RELEASED) {
                 primaryButtonDown = false;
--- a/javafx-ui-common/test/unit/javafx/scene/input/MouseDragEventTest.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/MouseDragEventTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -555,7 +555,7 @@
 
             MouseEvent event = MouseEvent.impl_mouseEvent(x, y, x, y, button,
                     1, false, false, false, false, false, primaryButtonDown,
-                    false, false, type);
+                    false, false, false, type);
 
             return event;
         }
--- a/javafx-ui-common/test/unit/javafx/scene/input/MouseEventTest.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/MouseEventTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -43,7 +43,7 @@
     private final Node node2 = new TestNode(10);
     private final MouseEvent doubleclick = MouseEvent.impl_mouseEvent(
             11, 12, 13, 14, MouseButton.PRIMARY, 2,
-            true, false, true, false, true, false, true, false,
+            true, false, true, false, true, false, true, false, false,
             MouseEvent.MOUSE_CLICKED);
 
     @Test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/test/unit/javafx/scene/input/RotateEventTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+package javafx.scene.input;
+
+import com.sun.javafx.pgstub.StubScene;
+import com.sun.javafx.test.MouseEventGenerator;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class RotateEventTest {
+
+    private boolean rotated;
+    private boolean rotated2;
+    
+    @Test
+    public void shouldDeliverRotateEventToPickedNode() {
+        Scene scene = createScene();
+        Rectangle rect = 
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                rotated = true;
+            }
+        });
+        
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 1, 1,
+                50, 50, 50, 50, false, false, false, false, false, false);
+        
+        assertFalse(rotated);
+
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 1, 1,
+                150, 150, 150, 150, false, false, false, false, false, false);
+        
+        assertTrue(rotated);
+    }
+    
+    @Test
+    public void shouldPassAngles() {
+        Scene scene = createScene();
+        Rectangle rect = 
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertEquals(90, event.getAngle(), 0.0001);
+                assertEquals(-180, event.getTotalAngle(), 0.0001);
+                rotated = true;
+            }
+        });
+        
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 90, -180,
+                150, 150, 150, 150, false, false, false, false, false, false);
+        
+        assertTrue(rotated);
+    }
+
+    @Test
+    public void shouldPassModifiers() {
+        Scene scene = createScene();
+        Rectangle rect = 
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertTrue(event.isShiftDown());
+                assertFalse(event.isControlDown());
+                assertTrue(event.isAltDown());
+                assertFalse(event.isMetaDown());
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                150, 150, 150, 150, true, false, true, false, false, false);
+        assertTrue(rotated);
+
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertFalse(event.isShiftDown());
+                assertTrue(event.isControlDown());
+                assertFalse(event.isAltDown());
+                assertTrue(event.isMetaDown());
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(rotated);
+    }
+
+    @Test
+    public void shouldPassDirect() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertTrue(event.isDirect());
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(rotated);
+
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertFalse(event.isDirect());
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(rotated);
+    }
+
+    @Test
+    public void shouldPassInertia() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertTrue(event.isInertia());
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, true);
+        assertTrue(rotated);
+
+        rotated = false;
+        rect.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertFalse(event.isInertia());
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                150, 150, 150, 150, false, true, false, true, true, false);
+        assertTrue(rotated);
+    }
+
+    @Test
+    public void shouldPassEventType() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        rotated = false;
+        rect.setOnRotationStarted(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(rotated);
+
+        rotated = false;
+        rect.setOnRotationFinished(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                rotated = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_FINISHED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(rotated);
+    }
+
+    @Test
+    public void handlingAnyShouldGetAllTypes() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        rect.addEventHandler(RotateEvent.ANY, new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                rotated = true;
+            }
+        });
+
+        rotated = false;
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(rotated);
+
+        rotated = false;
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(rotated);
+
+        rotated = false;
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_FINISHED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(rotated);
+    }
+
+    @Test
+    public void shouldDeliverWholeGestureToOneNode() {
+        Scene scene = createScene();
+        Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        rect1.addEventHandler(RotateEvent.ANY, new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                rotated = true;
+            }
+        });
+        rect2.addEventHandler(RotateEvent.ANY, new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                rotated2 = true;
+            }
+        });
+
+        rotated = false;
+        rotated2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(rotated);
+        assertFalse(rotated2);
+
+        rotated = false;
+        rotated2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+        assertTrue(rotated);
+        assertFalse(rotated2);
+
+        rotated = false;
+        rotated2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_FINISHED, 2, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+        assertTrue(rotated);
+        assertFalse(rotated2);
+    }
+
+    @Test
+    public void unknownLocationShouldBeReplacedByMouseLocation() {
+        Scene scene = createScene();
+        Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+        rect1.addEventHandler(RotateEvent.ANY, new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                rotated = true;
+            }
+        });
+
+        MouseEventGenerator generator = new MouseEventGenerator();
+
+        rotated = false;
+        rotated2 = false;
+        rect2.setOnRotationStarted(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertEquals(250.0, event.getSceneX(), 0.0001);
+                assertEquals(250.0, event.getSceneY(), 0.0001);
+                rotated2 = true;
+            }
+        });
+        scene.impl_processMouseEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_MOVED, 250, 250));
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_STARTED, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(rotated);
+        assertTrue(rotated2);
+
+        rotated = false;
+        rotated2 = false;
+        rect2.setOnRotate(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertEquals(150.0, event.getSceneX(), 0.0001);
+                assertEquals(150.0, event.getSceneY(), 0.0001);
+                rotated2 = true;
+            }
+        });
+        scene.impl_processMouseEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_MOVED, 150, 150));
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(rotated);
+        assertTrue(rotated2);
+
+        rotated = false;
+        rotated2 = false;
+        rect2.setOnRotationFinished(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertEquals(150.0, event.getSceneX(), 0.0001);
+                assertEquals(150.0, event.getSceneY(), 0.0001);
+                rotated2 = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_FINISHED, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(rotated);
+        assertTrue(rotated2);
+    }
+
+    @Test
+    public void finishedLocationShouldBeFixed() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        rect.setOnRotationFinished(new EventHandler<RotateEvent>() {
+            @Override public void handle(RotateEvent event) {
+                assertEquals(250.0, event.getSceneX(), 0.0001);
+                assertEquals(250.0, event.getSceneY(), 0.0001);
+                rotated = true;
+            }
+        });
+
+        rotated = false;
+
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATE, 2, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+
+        assertFalse(rotated);
+
+        ((StubScene) scene.impl_getPeer()).getListener().rotateEvent(
+                RotateEvent.ROTATION_FINISHED, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+
+        assertTrue(rotated);
+    }
+
+    private Scene createScene() {
+        final Group root = new Group();
+        
+        final Scene scene = new Scene(root, 400, 400);
+
+        Rectangle rect = new Rectangle(100, 100, 100, 100);
+        Rectangle rect2 = new Rectangle(200, 200, 100, 100);
+
+        root.getChildren().addAll(rect, rect2);
+
+        Stage stage = new Stage();
+        stage.setScene(scene);
+        stage.show();
+        
+        return scene;
+    }
+}
--- a/javafx-ui-common/test/unit/javafx/scene/input/ScrollEventTest.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/ScrollEventTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -25,6 +25,7 @@
 package javafx.scene.input;
 
 import com.sun.javafx.pgstub.StubScene;
+import com.sun.javafx.test.MouseEventGenerator;
 import javafx.event.EventHandler;
 import javafx.scene.Group;
 import javafx.scene.Scene;
@@ -36,6 +37,7 @@
 public class ScrollEventTest {
 
     private boolean scrolled;
+    private boolean scrolled2;
     
     @Test
     public void shouldDeliverScrollEventToPickedNode() {
@@ -51,12 +53,14 @@
         });
         
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                1, 1, 1, 1, 1, 1, 1, 1, 50, 50, 50, 50, false, false, false, false);
+                ScrollEvent.SCROLL, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                50, 50, 50, 50, false, false, false, false, false, false);
         
         assertFalse(scrolled);
 
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                1, 1, 1, 1, 1, 1, 1, 1, 150, 150, 150, 150, false, false, false, false);
+                ScrollEvent.SCROLL, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                150, 150, 150, 150, false, false, false, false, false, false);
         
         assertTrue(scrolled);
     }
@@ -72,12 +76,15 @@
             @Override public void handle(ScrollEvent event) {
                 assertEquals(66.0, event.getDeltaX(), 0.0001);
                 assertEquals(99.0, event.getDeltaY(), 0.0001);
+                assertEquals(132.0, event.getTotalDeltaX(), 0.0001);
+                assertEquals(198.0, event.getTotalDeltaY(), 0.0001);
                 scrolled = true;
             }
         });
         
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                2, 3, 33, 33, 1, 1, 1, 1, 150, 150, 150, 150, false, false, false, false);
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 1, 1, 1, 1,
+                150, 150, 150, 150, false, false, false, false, false, false);
         
         assertTrue(scrolled);
     }
@@ -99,7 +106,8 @@
             }
         });
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                2, 3, 33, 33, 0, 0, 0, 0, 150, 150, 150, 150, false, false, false, false);
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 0, 0, 0, 0, 0,
+                150, 150, 150, 150, false, false, false, false, false, false);
         assertTrue(scrolled);
 
         scrolled = false;
@@ -113,7 +121,8 @@
             }
         });
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                2, 3, 33, 33, 4, 5, 3, 3, 150, 150, 150, 150, false, false, false, false);
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 4, 5, 3, 3,
+                150, 150, 150, 150, false, false, false, false, false, false);
         assertTrue(scrolled);
         
         scrolled = false;
@@ -127,7 +136,8 @@
             }
         });
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                2, 3, 33, 33, -1, -1, 3, 3, 150, 150, 150, 150, false, false, false, false);
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 5, -1, -1, 3, 3,
+                150, 150, 150, 150, false, false, false, false, false, false);
         assertTrue(scrolled);
         
     }
@@ -149,7 +159,8 @@
             }
         });
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                2, 3, 33, 33, 1, 1, 3, 3, 150, 150, 150, 150, true, false, true, false);
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, false, false);
         assertTrue(scrolled);
 
         scrolled = false;
@@ -163,18 +174,318 @@
             }
         });
         ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
-                2, 3, 33, 33, 1, 1, 3, 3, 150, 150, 150, 150, false, true, false, true);
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 1, 1, 3, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(scrolled);
+    }
+
+    @Test
+    public void shouldPassDirect() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        scrolled = false;
+        rect.setOnScroll(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertTrue(event.isDirect());
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+
+        scrolled = false;
+        rect.setOnScroll(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertFalse(event.isDirect());
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 1, 1, 3, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(scrolled);
+    }
+
+    @Test
+    public void shouldPassInertia() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        scrolled = false;
+        rect.setOnScroll(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertTrue(event.isInertia());
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, false, true);
+        assertTrue(scrolled);
+
+        scrolled = false;
+        rect.setOnScroll(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertFalse(event.isInertia());
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 1, 1, 1, 3, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(scrolled);
+    }
+
+    @Test
+    public void shouldPassTouchCount() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        scrolled = false;
+        rect.setOnScroll(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertEquals(0, event.getTouchCount());
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+
+        scrolled = false;
+        rect.setOnScroll(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertEquals(5, event.getTouchCount());
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 5, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
         assertTrue(scrolled);
     }
     
+    @Test
+    public void shouldPassEventType() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        scrolled = false;
+        rect.setOnScrollStarted(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_STARTED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+
+        scrolled = false;
+        rect.setOnScrollFinished(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                scrolled = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_FINISHED, 2, 3, 4, 6, 33, 33, 5, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+    }
+
+    @Test
+    public void handlingAnyShouldGetAllTypes() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        rect.addEventHandler(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                scrolled = true;
+            }
+        });
+
+        scrolled = false;
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_STARTED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+
+        scrolled = false;
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 5, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+
+        scrolled = false;
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_FINISHED, 2, 3, 4, 6, 33, 33, 5, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+    }
+
+    @Test
+    public void shouldDeliverWholeGestureToOneNode() {
+        Scene scene = createScene();
+        Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        rect1.addEventHandler(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                scrolled = true;
+            }
+        });
+        rect2.addEventHandler(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                scrolled2 = true;
+            }
+        });
+
+        scrolled = false;
+        scrolled2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_STARTED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(scrolled);
+        assertFalse(scrolled2);
+
+        scrolled = false;
+        scrolled2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+        assertTrue(scrolled);
+        assertFalse(scrolled2);
+
+        scrolled = false;
+        scrolled2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_FINISHED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+        assertTrue(scrolled);
+        assertFalse(scrolled2);
+    }
+
+    @Test
+    public void unknownLocationShouldBeReplacedByMouseLocation() {
+        Scene scene = createScene();
+        Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+        rect1.addEventHandler(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                scrolled = true;
+            }
+        });
+
+        MouseEventGenerator generator = new MouseEventGenerator();
+
+        scrolled = false;
+        scrolled2 = false;
+        rect2.setOnScrollStarted(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertEquals(250.0, event.getSceneX(), 0.0001);
+                assertEquals(250.0, event.getSceneY(), 0.0001);
+                scrolled2 = true;
+            }
+        });
+        scene.impl_processMouseEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_MOVED, 250, 250));
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_STARTED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(scrolled);
+        assertTrue(scrolled2);
+
+        scrolled = false;
+        scrolled2 = false;
+        rect2.setOnScroll(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertEquals(150.0, event.getSceneX(), 0.0001);
+                assertEquals(150.0, event.getSceneY(), 0.0001);
+                scrolled2 = true;
+            }
+        });
+        scene.impl_processMouseEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_MOVED, 150, 150));
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(scrolled);
+        assertTrue(scrolled2);
+
+        scrolled = false;
+        scrolled2 = false;
+        rect2.setOnScrollFinished(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertEquals(150.0, event.getSceneX(), 0.0001);
+                assertEquals(150.0, event.getSceneY(), 0.0001);
+                scrolled2 = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_FINISHED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(scrolled);
+        assertTrue(scrolled2);
+    }
+
+    @Test
+    public void finishedLocationShouldBeFixed() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        rect.setOnScrollFinished(new EventHandler<ScrollEvent>() {
+            @Override public void handle(ScrollEvent event) {
+                assertEquals(250.0, event.getSceneX(), 0.0001);
+                assertEquals(250.0, event.getSceneY(), 0.0001);
+                scrolled = true;
+            }
+        });
+
+        scrolled = false;
+
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_STARTED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+
+        assertFalse(scrolled);
+
+        ((StubScene) scene.impl_getPeer()).getListener().scrollEvent(
+                ScrollEvent.SCROLL_FINISHED, 2, 3, 4, 6, 33, 33, 0, 1, 1, 3, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+
+        assertTrue(scrolled);
+    }
+
     private Scene createScene() {
         final Group root = new Group();
         
         final Scene scene = new Scene(root, 400, 400);
 
         Rectangle rect = new Rectangle(100, 100, 100, 100);
+        Rectangle rect2 = new Rectangle(200, 200, 100, 100);
 
-        root.getChildren().add(rect);
+        root.getChildren().addAll(rect, rect2);
 
         Stage stage = new Stage();
         stage.setScene(scene);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/test/unit/javafx/scene/input/TouchEventTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,1347 @@
+/*
+ * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+package javafx.scene.input;
+
+import com.sun.javafx.pgstub.StubScene;
+import java.util.Random;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import javafx.event.EventHandler;
+import org.junit.Ignore;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class TouchEventTest {
+    private static final int SANE_BENCHMARK_CYCLES = 1000000;
+    private static final int CRAZY_BENCHMARK_CYCLES = 500000;
+
+    private int touched;
+
+    @Test
+    public void shouldPassModifiers() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                        assertEquals(true, event.isShiftDown());
+                        assertEquals(false, event.isControlDown());
+                        assertEquals(true, event.isAltDown());
+                        assertEquals(false, event.isMetaDown());
+                        break;
+                    case 2:
+                        assertEquals(false, event.isShiftDown());
+                        assertEquals(true, event.isControlDown());
+                        assertEquals(false, event.isAltDown());
+                        assertEquals(true, event.isMetaDown());
+                        break;
+                    case 3:
+                        assertEquals(false, event.isShiftDown());
+                        assertEquals(true, event.isControlDown());
+                        assertEquals(true, event.isAltDown());
+                        assertEquals(false, event.isMetaDown());
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, true, false, true);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, true, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(3, touched);
+    }
+
+    @Test
+    public void shouldCountTouchesCorrectly() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                        assertEquals(1, event.getTouchCount());
+                        assertEquals(1, event.getTouchPoints().size());
+                        break;
+                    case 2:
+                    case 3:
+                        assertEquals(2, event.getTouchCount());
+                        assertEquals(2, event.getTouchPoints().size());
+                        break;
+                    case 4:
+                    case 5:
+                        assertEquals(2, event.getTouchCount());
+                        assertEquals(2, event.getTouchPoints().size());
+                        break;
+                    case 6:
+                        assertEquals(1, event.getTouchCount());
+                        assertEquals(1, event.getTouchPoints().size());
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, true, false, true);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, true, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, true, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(6, touched);
+    }
+
+    @Test
+    public void shouldGenerateCorrectEventSetIDs() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                        assertEquals(1, event.getEventSetId());
+                        break;
+                    case 2:
+                    case 3:
+                        assertEquals(2, event.getEventSetId());
+                        break;
+                    case 4:
+                    case 5:
+                        assertEquals(3, event.getEventSetId());
+                        break;
+                    case 6:
+                        assertEquals(4, event.getEventSetId());
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, true, false, true);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, true, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, true, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(6, touched);
+    }
+
+    @Test
+    public void shouldReIDTouchPoints() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.setOnTouchPressed(new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(event.getTouchPoint().getId()) {
+                    case 1:
+                        assertEquals(110.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(110.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, touched);
+                        break;
+                    case 2:
+                        assertEquals(120.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(120.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(2, touched);
+                        break;
+                    case 3:
+                        assertEquals(130.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(130.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(3, touched);
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 3, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 120, 120, 120, 120);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 152, 130, 130, 130, 130);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(3, touched);
+    }
+
+    @Test
+    public void shouldNotReuseTouchPointID() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.setOnTouchPressed(new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(event.getTouchPoint().getId()) {
+                    case 1:
+                        assertEquals(110.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(110.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, touched);
+                        break;
+                    case 2:
+                        assertEquals(120.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(120.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(2, touched);
+                        break;
+                    case 3:
+                        assertEquals(130.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(130.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(3, touched);
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 120, 120, 120, 120);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 120, 120, 120, 120);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 130, 130, 130, 130);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(3, touched);
+    }
+
+    @Test
+    public void shouldMaintainPressOrder() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.setOnTouchPressed(new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(event.getTouchPoint().getId()) {
+                    case 1:
+                        assertEquals(110.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(110.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, event.getTouchPoints().get(0).getId());
+                        assertEquals(2, event.getTouchPoints().get(1).getId());
+                        assertEquals(1, touched);
+                        break;
+                    case 2:
+                        assertEquals(120.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(120.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, event.getTouchPoints().get(0).getId());
+                        assertEquals(2, event.getTouchPoints().get(1).getId());
+                        assertEquals(2, touched);
+                        break;
+                    case 3:
+                        assertEquals(130.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(130.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, event.getTouchPoints().get(0).getId());
+                        assertEquals(3, event.getTouchPoints().get(1).getId());
+                        assertEquals(3, touched);
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 120, 120, 120, 120);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 120, 120, 120, 120);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 130, 130, 130, 130);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.STATIONARY, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(3, touched);
+    }
+
+    @Test
+    public void shouldMaintainIDMapping() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.setOnTouchPressed(new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(event.getTouchPoint().getId()) {
+                    case 1:
+                        assertEquals(110.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(110.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, event.getTouchPoints().get(0).getId());
+                        assertEquals(2, event.getTouchPoints().get(1).getId());
+                        assertEquals(1, touched);
+                        break;
+                    case 2:
+                        assertEquals(120.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(120.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, event.getTouchPoints().get(0).getId());
+                        assertEquals(2, event.getTouchPoints().get(1).getId());
+                        assertEquals(2, touched);
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        rect.setOnTouchMoved(new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(event.getTouchPoint().getId()) {
+                    case 1:
+                        assertEquals(120.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(120.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, event.getTouchPoints().get(0).getId());
+                        assertEquals(2, event.getTouchPoints().get(1).getId());
+                        assertEquals(3, touched);
+                        break;
+                    case 2:
+                        assertEquals(110.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(110.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(1, event.getTouchPoints().get(0).getId());
+                        assertEquals(2, event.getTouchPoints().get(1).getId());
+                        assertEquals(4, touched);
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 127, 120, 120, 120, 120);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 127, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 1368, 120, 120, 120, 120);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void shouldResetIDsAfterGesture() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                    case 2:
+                        assertEquals(1, event.getEventSetId());
+                        assertEquals(touched, event.getTouchPoint().getId());
+                        break;
+                    case 3:
+                    case 4:
+                        assertEquals(2, event.getEventSetId());
+                        assertEquals(touched - 2, event.getTouchPoint().getId());
+                        break;
+                    case 5:
+                    case 6:
+                        assertEquals(1, event.getEventSetId());
+                        assertEquals(touched - 4, event.getTouchPoint().getId());
+                        break;
+                    case 7:
+                    case 8:
+                        assertEquals(2, event.getEventSetId());
+                        assertEquals(touched - 6, event.getTouchPoint().getId());
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, true, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, true, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+
+        assertEquals(8, touched);
+    }
+
+    @Test
+    public void touchPointsShouldContainTouchPoint() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        touched = 0;
+        rect.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                    case 3:
+                        assertSame(event.getTouchPoint(), event.getTouchPoints().get(0));
+                        break;
+                    case 2:
+                    case 4:
+                        assertSame(event.getTouchPoint(), event.getTouchPoints().get(1));
+                        break;
+                    default:
+                        fail("Wrong touch point id " + event.getTouchPoint().getId());
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 2, 120, 120, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void touchPointsShouldHaveCorrectTarget() {
+        Scene scene = createScene();
+
+        final Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        final Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        touched = 0;
+        rect1.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                assertSame(rect1, event.getTouchPoint().getTarget());
+                assertSame(rect2, event.getTouchPoints().get(1).getTarget());
+            }
+        });
+        rect2.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                assertSame(rect2, event.getTouchPoint().getTarget());
+                assertSame(rect1, event.getTouchPoints().get(0).getTarget());
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 2, 220, 220, 220, 220);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 2, 220, 220, 220, 220);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void testTouchPointsBelongsTo() {
+        final Scene scene = createScene();
+
+        final Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        final Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        touched = 0;
+        rect1.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                assertTrue(event.getTouchPoint().belongsTo(rect1));
+                assertTrue(event.getTouchPoint().belongsTo(scene.getRoot()));
+                assertTrue(event.getTouchPoint().belongsTo(scene));
+                assertFalse(event.getTouchPoint().belongsTo(rect2));
+
+                assertFalse(event.getTouchPoints().get(1).belongsTo(rect1));
+                assertTrue(event.getTouchPoints().get(1).belongsTo(scene.getRoot()));
+                assertTrue(event.getTouchPoints().get(1).belongsTo(scene));
+                assertTrue(event.getTouchPoints().get(1).belongsTo(rect2));
+            }
+        });
+        rect2.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                assertTrue(event.getTouchPoint().belongsTo(rect2));
+                assertTrue(event.getTouchPoint().belongsTo(scene.getRoot()));
+                assertTrue(event.getTouchPoint().belongsTo(scene));
+                assertFalse(event.getTouchPoint().belongsTo(rect1));
+
+                assertFalse(event.getTouchPoints().get(0).belongsTo(rect2));
+                assertTrue(event.getTouchPoints().get(0).belongsTo(scene.getRoot()));
+                assertTrue(event.getTouchPoints().get(0).belongsTo(scene));
+                assertTrue(event.getTouchPoints().get(0).belongsTo(rect1));
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 2, 220, 220, 220, 220);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 2, 220, 220, 220, 220);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void shouldPickAndGrabTouchPoints() {
+        Scene scene = createScene();
+        final Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        final Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        touched = 0;
+        rect1.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(150.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(150.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1150.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1155.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    case 3:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(250.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(250.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1250.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1255.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        rect2.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 2:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(260.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(260.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1260.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1265.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    case 4:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(160.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(160.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1160.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1165.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 3, 150, 155, 1150, 1155);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 4, 260, 265, 1260, 1265);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 3, 250, 255, 1250, 1255);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 4, 160, 165, 1160, 1165);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void ungrabShouldEnablePickingForTouchPoints() {
+        Scene scene = createScene();
+        Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        touched = 0;
+        rect1.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(150.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(150.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1150.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1155.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        event.getTouchPoint().ungrab();
+                        break;
+                    case 4:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(160.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(160.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1160.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1165.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        rect2.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 2:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(260.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(260.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1260.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1265.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        event.getTouchPoint().ungrab();
+                        break;
+                    case 3:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(250.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(250.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1250.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1255.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 3, 150, 155, 1150, 1155);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 4, 260, 265, 1260, 1265);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 3, 250, 255, 1250, 1255);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 4, 160, 165, 1160, 1165);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void grabWithArgShouldAffectDelivery() {
+        Scene scene = createScene();
+        final Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        final Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        touched = 0;
+        rect1.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(150.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(150.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1150.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1155.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        event.getTouchPoints().get(1).grab(rect1);
+                        break;
+                    case 3:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(250.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(250.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1250.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1255.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    case 4:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(160.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(160.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1160.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1165.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        rect2.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 2:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(260.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(260.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1260.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1265.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 3, 150, 155, 1150, 1155);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 4, 260, 265, 1260, 1265);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 3, 250, 255, 1250, 1255);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 4, 160, 165, 1160, 1165);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void grabWithoutArgShouldAffectDelivery() {
+        Scene scene = createScene();
+        final Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        final Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        touched = 0;
+        rect1.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 1:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(150.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(150.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(155.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1150.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1155.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        event.getTouchPoints().get(1).grab();
+                        break;
+                    case 3:
+                        assertEquals(1, event.getTouchPoint().getId());
+                        assertEquals(250.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(250.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(255.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1250.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1255.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    case 4:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(160.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(160.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(165.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1160.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1165.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        rect2.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                touched++;
+                switch(touched) {
+                    case 2:
+                        assertEquals(2, event.getTouchPoint().getId());
+                        assertEquals(260.0, event.getTouchPoint().getX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getY(), 0.0001);
+                        assertEquals(260.0, event.getTouchPoint().getSceneX(), 0.0001);
+                        assertEquals(265.0, event.getTouchPoint().getSceneY(), 0.0001);
+                        assertEquals(1260.0, event.getTouchPoint().getScreenX(), 0.0001);
+                        assertEquals(1265.0, event.getTouchPoint().getScreenY(), 0.0001);
+                        break;
+                    default:
+                        fail("Wrong touch point delivery");
+                }
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 3, 150, 155, 1150, 1155);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 4, 260, 265, 1260, 1265);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 3, 250, 255, 1250, 1255);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 4, 160, 165, 1160, 1165);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        assertEquals(4, touched);
+    }
+
+    @Test
+    public void shouldIgnoreIndirectTouchEvents() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        rect.addEventHandler(TouchEvent.ANY, new EventHandler<TouchEvent>() {
+            @Override public void handle(TouchEvent event) {
+                fail("Delivered indirect touch event");
+            }
+        });
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, false, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, false, true, false, true, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.RELEASED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void shouldThrowREOnWrongSmallId() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 2, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void shouldThrowREOnWrongLargeId() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.MOVED, 127, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void shouldThrowREOnBigTPNumber() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void shouldThrowREOnSmallTPNumber() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 2, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void shouldThrowREOnLostRelease() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, true, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+    }
+
+    @Ignore("In Scene is workaround for RT-20139 which makes this test fail")
+    @Test(expected=RuntimeException.class)
+    public void shouldThrowREOnLostIndirectRelease() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, false, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1368, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                System.currentTimeMillis(), 1, false, false, false, false, false);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                TouchPoint.State.PRESSED, 1, 110, 110, 110, 110);
+        ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+    }
+
+    private Scene createScene() {
+        final Group root = new Group();
+
+        final Scene scene = new Scene(root, 400, 400);
+
+        Rectangle rect = new Rectangle(100, 100, 100, 100);
+        Rectangle rect2 = new Rectangle(200, 200, 100, 100);
+
+        root.getChildren().addAll(rect, rect2);
+
+        Stage stage = new Stage();
+        stage.setScene(scene);
+        stage.show();
+
+        return scene;
+    }
+
+    @Test
+    @Ignore("This is a benchmark, not any functional test. Run it individually if you wish.")
+    public void saneOrderingBenchmark() {
+        long[] ids = new long[] { 2, 3, 4, 5, 6 };
+        boolean[] active = new boolean[] { false, false, false, false, false };
+        int count = 0;
+        int tick = 5;
+        int available = 5;
+        Random rand = new Random();
+
+        Scene scene = createScene();
+
+
+        int ticker = 0;
+        int added = -1;
+        int removed = -1;
+        long timer = System.currentTimeMillis();
+        for (int i = 0; i < SANE_BENCHMARK_CYCLES; i++) {
+            ticker++;
+            if (ticker == tick) {
+                ticker = 0;
+
+                boolean up;
+                if (count == available) {
+                    up = false;
+                } else if (count == 0) {
+                    up = true;
+                } else {
+                    up = Math.random() > 0.4;
+                }
+
+                if (up) {
+                    for (int j = 0; j < available; j++) {
+                        if (!active[j]) {
+                            active[j] = true;
+                            added = j;
+                            count++;
+                            break;
+                        }
+                    }
+                } else {
+                    int which = rand.nextInt(count);
+                    int k = 0;
+                    for (int j = 0; j < available; j++) {
+                        if (active[j]) {
+                            k++;
+                            if (k == which) {
+                                active[j] = false;
+                                removed = j;
+                                count--;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            int reporting = count + (removed >= 0 ? 1 : 0);
+
+            ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                    System.currentTimeMillis(), reporting, true, false, false, false, false);
+
+            for (int j = 0; j < available; j++) {
+                if (active[j] || removed == j) {
+                    TouchPoint.State state = TouchPoint.State.MOVED;
+                    if (added == j) {
+                        state = TouchPoint.State.PRESSED;
+                    } else if (removed == j) {
+                        state = TouchPoint.State.RELEASED;
+                    } else {
+                    }
+
+                    ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                            state, ids[j], 150, 150, 150, 150);
+                }
+            }
+
+            ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+            removed = -1;
+            added = -1;
+        }
+        long timer2 = System.currentTimeMillis();
+        System.out.println("*************************************************");
+        System.out.println("Benchmark1 time: " + (timer2 - timer));
+        System.out.println("*************************************************");
+        System.out.println("");
+    }
+
+    @Test
+    @Ignore("This is a benchmark, not any functional test. Run it individually if you wish.")
+    public void crazyOrderingBenchmark() {
+        long[] ids = new long[] { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };
+        boolean[] active = new boolean[] { false, false, false, false, false,
+                false, false, false, false, false };
+        int count = 0;
+        int tick = 5;
+        int available = 10;
+        Random rand = new Random();
+
+        Scene scene = createScene();
+
+
+        int ticker = 0;
+        int added = -1;
+        int removed = -1;
+        long timer = System.currentTimeMillis();
+        for (int i = 0; i < CRAZY_BENCHMARK_CYCLES; i++) {
+            ticker++;
+            if (ticker == tick) {
+                ticker = 0;
+
+                boolean up;
+                if (count == available) {
+                    up = false;
+                } else if (count == 0) {
+                    up = true;
+                } else {
+                    up = Math.random() > 0.4;
+                }
+
+                if (up) {
+                    int which = rand.nextInt(available - count);
+                    int k = 0;
+                    for (int j = 0; j < available; j++) {
+                        if (!active[j]) {
+                            k++;
+                            if (k == which) {
+                                active[j] = true;
+                                added = j;
+                                count++;
+                                ids[j] = Math.abs(rand.nextLong());
+                                if (ids[j] == 0) {
+                                    ids[j] = 1;
+                                }
+                                break;
+                            }
+                        }
+                    }
+                } else {
+                    int which = rand.nextInt(count);
+                    int k = 0;
+                    for (int j = 0; j < available; j++) {
+                        if (active[j]) {
+                            k++;
+                            if (k == which) {
+                                active[j] = false;
+                                removed = j;
+                                count--;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            int reporting = count + (removed >= 0 ? 1 : 0);
+
+            ((StubScene) scene.impl_getPeer()).getListener().touchEventBegin(
+                    System.currentTimeMillis(), reporting, true, false, false, false, false);
+
+            for (int j = 0; j < available; j++) {
+                if (active[j] || removed == j) {
+                    TouchPoint.State state = TouchPoint.State.MOVED;
+                    if (added == j) {
+                        state = TouchPoint.State.PRESSED;
+                    } else if (removed == j) {
+                        state = TouchPoint.State.RELEASED;
+                    }
+
+                    ((StubScene) scene.impl_getPeer()).getListener().touchEventNext(
+                            state, ids[j], 150, 150, 150, 150);
+                }
+            }
+
+            ((StubScene) scene.impl_getPeer()).getListener().touchEventEnd();
+            removed = -1;
+            added = -1;
+        }
+        long timer2 = System.currentTimeMillis();
+        System.out.println("*************************************************");
+        System.out.println("Benchmark2 time: " + (timer2 - timer));
+        System.out.println("*************************************************");
+        System.out.println("");
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/test/unit/javafx/scene/input/ZoomEventTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+package javafx.scene.input;
+
+import com.sun.javafx.pgstub.StubScene;
+import com.sun.javafx.test.MouseEventGenerator;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ZoomEventTest {
+
+    private boolean zoomed;
+    private boolean zoomed2;
+    
+    @Test
+    public void shouldDeliverZoomEventToPickedNode() {
+        Scene scene = createScene();
+        Rectangle rect = 
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                zoomed = true;
+            }
+        });
+        
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 1, 1,
+                50, 50, 50, 50, false, false, false, false, false, false);
+        
+        assertFalse(zoomed);
+
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 1, 1,
+                150, 150, 150, 150, false, false, false, false, false, false);
+        
+        assertTrue(zoomed);
+    }
+    
+    @Test
+    public void shouldPassFactors() {
+        Scene scene = createScene();
+        Rectangle rect = 
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertEquals(1.2, event.getZoomFactor(), 0.0001);
+                assertEquals(2.4, event.getTotalZoomFactor(), 0.0001);
+                zoomed = true;
+            }
+        });
+        
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 1.2, 2.4,
+                150, 150, 150, 150, false, false, false, false, false, false);
+        
+        assertTrue(zoomed);
+    }
+
+    @Test
+    public void shouldPassModifiers() {
+        Scene scene = createScene();
+        Rectangle rect = 
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertTrue(event.isShiftDown());
+                assertFalse(event.isControlDown());
+                assertTrue(event.isAltDown());
+                assertFalse(event.isMetaDown());
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                150, 150, 150, 150, true, false, true, false, false, false);
+        assertTrue(zoomed);
+
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertFalse(event.isShiftDown());
+                assertTrue(event.isControlDown());
+                assertFalse(event.isAltDown());
+                assertTrue(event.isMetaDown());
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(zoomed);
+    }
+
+    @Test
+    public void shouldPassDirect() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertTrue(event.isDirect());
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(zoomed);
+
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertFalse(event.isDirect());
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(zoomed);
+    }
+
+    @Test
+    public void shouldPassInertia() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertTrue(event.isInertia());
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                150, 150, 150, 150, true, false, true, false, false, true);
+        assertTrue(zoomed);
+
+        zoomed = false;
+        rect.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertFalse(event.isInertia());
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                150, 150, 150, 150, false, true, false, true, false, false);
+        assertTrue(zoomed);
+    }
+
+    @Test
+    public void shouldPassEventType() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        zoomed = false;
+        rect.setOnZoomStarted(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(zoomed);
+
+        zoomed = false;
+        rect.setOnZoomFinished(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                zoomed = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_FINISHED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(zoomed);
+    }
+
+    @Test
+    public void handlingAnyShouldGetAllTypes() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+
+        rect.addEventHandler(ZoomEvent.ANY, new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                zoomed = true;
+            }
+        });
+
+        zoomed = false;
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(zoomed);
+
+        zoomed = false;
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(zoomed);
+
+        zoomed = false;
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_FINISHED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(zoomed);
+    }
+
+    @Test
+    public void shouldDeliverWholeGestureToOneNode() {
+        Scene scene = createScene();
+        Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+
+        rect1.addEventHandler(ZoomEvent.ANY, new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                zoomed = true;
+            }
+        });
+        rect2.addEventHandler(ZoomEvent.ANY, new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                zoomed2 = true;
+            }
+        });
+
+        zoomed = false;
+        zoomed2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+        assertTrue(zoomed);
+        assertFalse(zoomed2);
+
+        zoomed = false;
+        zoomed2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+        assertTrue(zoomed);
+        assertFalse(zoomed2);
+
+        zoomed = false;
+        zoomed2 = false;
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_FINISHED, 2, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+        assertTrue(zoomed);
+        assertFalse(zoomed2);
+    }
+
+    @Test
+    public void unknownLocationShouldBeReplacedByMouseLocation() {
+        Scene scene = createScene();
+        Rectangle rect1 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        Rectangle rect2 =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(1);
+        rect1.addEventHandler(ZoomEvent.ANY, new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                zoomed = true;
+            }
+        });
+
+        MouseEventGenerator generator = new MouseEventGenerator();
+
+        zoomed = false;
+        zoomed2 = false;
+        rect2.setOnZoomStarted(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertEquals(250.0, event.getSceneX(), 0.0001);
+                assertEquals(250.0, event.getSceneY(), 0.0001);
+                zoomed2 = true;
+            }
+        });
+        scene.impl_processMouseEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_MOVED, 250, 250));
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_STARTED, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(zoomed);
+        assertTrue(zoomed2);
+
+        zoomed = false;
+        zoomed2 = false;
+        rect2.setOnZoom(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertEquals(150.0, event.getSceneX(), 0.0001);
+                assertEquals(150.0, event.getSceneY(), 0.0001);
+                zoomed2 = true;
+            }
+        });
+        scene.impl_processMouseEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_MOVED, 150, 150));
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(zoomed);
+        assertTrue(zoomed2);
+
+        zoomed = false;
+        zoomed2 = false;
+        rect2.setOnZoomFinished(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertEquals(150.0, event.getSceneX(), 0.0001);
+                assertEquals(150.0, event.getSceneY(), 0.0001);
+                zoomed2 = true;
+            }
+        });
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_FINISHED, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+        assertFalse(zoomed);
+        assertTrue(zoomed2);
+    }
+
+    @Test
+    public void finishedLocationShouldBeFixed() {
+        Scene scene = createScene();
+        Rectangle rect =
+                (Rectangle) scene.getRoot().getChildrenUnmodifiable().get(0);
+        rect.setOnZoomFinished(new EventHandler<ZoomEvent>() {
+            @Override public void handle(ZoomEvent event) {
+                assertEquals(250.0, event.getSceneX(), 0.0001);
+                assertEquals(250.0, event.getSceneY(), 0.0001);
+                zoomed = true;
+            }
+        });
+
+        zoomed = false;
+
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_STARTED, 2, 3,
+                150, 150, 150, 150, true, false, true, false, true, false);
+
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM, 2, 3,
+                250, 250, 250, 250, true, false, true, false, true, false);
+
+        assertFalse(zoomed);
+
+        ((StubScene) scene.impl_getPeer()).getListener().zoomEvent(
+                ZoomEvent.ZOOM_FINISHED, 2, 3,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN,
+                true, false, true, false, true, false);
+
+        assertTrue(zoomed);
+    }
+
+    private Scene createScene() {
+        final Group root = new Group();
+        
+        final Scene scene = new Scene(root, 400, 400);
+
+        Rectangle rect = new Rectangle(100, 100, 100, 100);
+        Rectangle rect2 = new Rectangle(200, 200, 100, 100);
+
+        root.getChildren().addAll(rect, rect2);
+
+        Stage stage = new Stage();
+        stage.setScene(scene);
+        stage.show();
+        
+        return scene;
+    }
+}
--- a/javafx-ui-common/test/unit/javafx/stage/StageTest.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/stage/StageTest.java	Thu Mar 29 15:10:33 2012 -0700
@@ -159,7 +159,7 @@
         pulse();
         assertEquals(200f, peer.y);
         assertEquals(2, peer.numTimesSetSizeAndLocation - initialNumTimesSetSizeAndLocation);
-        // .. same for setting y
+        // .. same for setting x
         s.setX(100);
         pulse();
         assertEquals(100f, peer.x);
@@ -261,6 +261,84 @@
     }
 
     @Test
+    public void testFullscreenNotLostForAsyncNotifications() {
+        peer.holdNotifications();
+
+        s.setFullScreen(true);
+        assertTrue(s.isFullScreen());
+
+        s.setFullScreen(false);
+        assertFalse(s.isFullScreen());
+        
+        peer.releaseSingleNotification();
+        assertTrue(s.isFullScreen());
+
+        peer.releaseNotifications();
+        
+        assertFalse(s.isFullScreen());
+    }
+
+    @Test
+    public void testFullScreenNotification() {
+        peer.setFullScreen(true);
+        assertTrue(s.isFullScreen());
+        peer.setFullScreen(false);
+        assertFalse(s.isFullScreen());
+    }
+
+    @Test
+    public void testResizableNotLostForAsyncNotifications() {
+        peer.holdNotifications();
+
+        s.setResizable(true);
+        assertTrue(s.isResizable());
+
+        s.setResizable(false);
+        assertFalse(s.isResizable());
+
+        peer.releaseSingleNotification();
+        assertTrue(s.isResizable());
+
+        peer.releaseNotifications();
+
+        assertFalse(s.isResizable());
+    }
+
+    @Test
+    public void testResizableNotification() {
+        peer.setResizable(false);
+        assertFalse(s.isResizable());
+        peer.setResizable(true);
+        assertTrue(s.isResizable());
+    }
+    
+    @Test
+    public void testIconifiedNotLostForAsyncNotifications() {
+        peer.holdNotifications();
+
+        s.setIconified(true);
+        assertTrue(s.isIconified());
+
+        s.setIconified(false);
+        assertFalse(s.isIconified());
+
+        peer.releaseSingleNotification();
+        assertTrue(s.isIconified());
+
+        peer.releaseNotifications();
+
+        assertFalse(s.isIconified());
+    }
+
+    @Test
+    public void testIconifiedNotification() {
+        peer.setIconified(true);
+        assertTrue(s.isIconified());
+        peer.setIconified(false);
+        assertFalse(s.isIconified());
+    }
+
+    @Test
     public void testBoundsSetAfterPeerIsRecreated() {
         s.setX(20);
         s.setY(50);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/ColorPicker.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,31 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.sun.javafx.scene.control;
+
+import javafx.scene.control.ComboBoxBase;
+
+/**
+ *
+ * @author paru
+ */
+public class ColorPicker<Color> extends ComboBoxBase<Color> {
+
+    public static final String STYLE_CLASS_BUTTON = "button";
+    public static final String STYLE_CLASS_SPLIT_BUTTON = "split-button";
+    
+    // Need API to turn off Color Label text.
+    
+    public ColorPicker() {
+        getStyleClass().add(DEFAULT_STYLE_CLASS);
+    }
+    
+    /***************************************************************************
+     *                                                                         *
+     * Stylesheet Handling                                                     *
+     *                                                                         *
+     **************************************************************************/
+
+    private static final String DEFAULT_STYLE_CLASS = "color-picker";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/ColorPickerPanel.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,285 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.sun.javafx.scene.control;
+
+import java.util.List;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.collections.FXCollections;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.control.Button;
+import javafx.scene.effect.DropShadow;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.CycleMethod;
+import javafx.scene.paint.LinearGradient;
+import javafx.scene.paint.Stop;
+import javafx.scene.shape.ArcTo;
+import javafx.scene.shape.ClosePath;
+import javafx.scene.shape.LineTo;
+import javafx.scene.shape.MoveTo;
+import javafx.scene.shape.Path;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeType;
+
+
+public class ColorPickerPanel extends Region {
+    
+    private static final int ARROW_SIZE = 10;
+    private static final int RADIUS = 8;
+    
+    ColorPickerGrid cpg;
+    Path path;
+    
+    private ObjectProperty<Color> color = new SimpleObjectProperty<Color>(Color.WHITE);
+    public ObjectProperty<Color> colorProperty() { return color; }
+    public Color getColor() { return color.get(); }
+    public void setColor(Color newColor) { color.set(newColor);}
+    
+    public ColorPickerPanel(Color initPaint) {
+        getStyleClass().add("color-panel");
+        cpg = new ColorPickerGrid(initPaint);
+        colorProperty().bindBidirectional(cpg.colorProperty());
+        // create popup path for main shape
+        path = new Path();
+        path.setFill(new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, new Stop(0, Color.web("#313131")), new Stop(0.5, Color.web("#5f5f5f")), new Stop(1, Color.web("#313131"))));
+        path.setStroke(null);
+        path.setEffect(new DropShadow(15, 0, 1, Color.gray(0, 0.6)));
+        path.setCache(true);
+        getChildren().addAll(path, cpg);
+    }
+    
+    @Override protected void layoutChildren() {
+        double paddingX = getInsets().getLeft();
+        double paddingY = getInsets().getTop();
+        double popupWidth = cpg.prefWidth(-1) + paddingX+getInsets().getRight();
+        double popupHeight = cpg.prefHeight(-1) + getInsets().getTop() + getInsets().getBottom();
+        System.out.println("cpg width = "+cpg.prefWidth(-1)+" paddingX = "+paddingX+" paddingY = "+
+                paddingY);
+        double arrowX = paddingX+RADIUS;
+        path.getElements().addAll(
+                new MoveTo(paddingX, getInsets().getTop() + ARROW_SIZE + RADIUS), 
+                new ArcTo(RADIUS, RADIUS, 90, paddingX + RADIUS, paddingX + ARROW_SIZE, false, true), 
+                new LineTo(paddingX + arrowX - (ARROW_SIZE * 0.8), paddingX + ARROW_SIZE), 
+                new LineTo(paddingX + arrowX, paddingX), 
+                new LineTo(paddingX + arrowX + (ARROW_SIZE * 0.8), paddingX + ARROW_SIZE), 
+                new LineTo(paddingX + popupWidth - RADIUS, paddingX + ARROW_SIZE), 
+                new ArcTo(RADIUS, RADIUS, 90, paddingX + popupWidth, paddingX + ARROW_SIZE + RADIUS, false, true), 
+                new LineTo(paddingX + popupWidth, paddingX + ARROW_SIZE + popupHeight - RADIUS), 
+                new ArcTo(RADIUS, RADIUS, 90, paddingX + popupWidth - RADIUS, paddingX + ARROW_SIZE + popupHeight, false, true), 
+                new LineTo(paddingX + RADIUS, paddingX + ARROW_SIZE + popupHeight), 
+                new ArcTo(RADIUS, RADIUS, 90, paddingX, paddingX + ARROW_SIZE + popupHeight - RADIUS, false, true), 
+                new ClosePath());
+        cpg.relocate(paddingX*2, 2*getInsets().getTop()+ARROW_SIZE);
+    }
+}
+
+class ColorPickerGrid extends GridPane {
+    private static final int SQUARE_SIZE = 15;
+    private static final int NUM_OF_COLUMNS = 12;
+    
+    private Color currentColor = null;
+    private final List<ColorSquare> squares;
+    
+    public ColorPickerGrid(Color initPaint) {
+        setId("ColorCustomizerColorGrid");
+        setGridLinesVisible(true);
+        int columnIndex = 0, rowIndex = 0;
+        
+        squares = FXCollections.observableArrayList();
+        int numColors = rawValues.length / 3;
+        Color[] colors = new Color[numColors];
+        for (int i = 0; i < numColors; i++) {
+            colors[i] = new Color(rawValues[(i * 3)] / 255,
+                    rawValues[(i * 3) + 1] / 255, rawValues[(i * 3) + 2] / 255,
+                    1.0);
+            ColorSquare cs = new ColorSquare(colors[i]);
+            squares.add(cs);
+        }
+
+        for (ColorSquare square : squares) {
+            add(square, columnIndex, rowIndex);
+            columnIndex++;
+            if (columnIndex == NUM_OF_COLUMNS) {
+                columnIndex = 0;
+                rowIndex++;
+            }
+        }
+        
+        setColor(initPaint);
+    }
+    
+    private ObjectProperty<Color> color = new SimpleObjectProperty<Color>(Color.RED) {
+        @Override protected void invalidated() {
+             for (ColorSquare cs : squares) {
+            if (cs.getFill().equals(get())) {
+                // Check css rule has not been already added
+                if (!cs.getStyleClass().contains("selected")) {
+                    cs.getStyleClass().add("selected");
+                }
+            } else {
+                cs.getStyleClass().remove("selected");
+            }
+        }
+        currentColor = get(); 
+        }
+    };
+    public ObjectProperty<Color> colorProperty() { return color; }
+    public Color getColor() { return color.get(); }
+    public void setColor(Color newColor) { color.set(newColor);}
+    
+    private class ColorSquare extends Rectangle {
+        public ColorSquare(Color color) {
+            setFill(color);
+            setSmooth(false);
+//            Utils.setBlocksMouse(this, true);
+            setWidth(SQUARE_SIZE);
+            setHeight(SQUARE_SIZE);
+            setStrokeType(StrokeType.INSIDE);
+            // Add style class to handle selected color square
+            getStyleClass().add("color-square");
+            addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
+                @Override
+                public void handle(MouseEvent event) {
+                    if (event.getClickCount() == 1) {
+                        if (getFill() != null) {
+                            if (getFill() instanceof Color) {
+                                setColor((Color) getFill());
+                            }
+                            event.consume();
+                        }
+                    }
+                }
+            });
+        }
+    }
+    
+    double[] rawValues = {
+        255, 255, 255, // first row
+        242, 242, 242,
+        230, 230, 230,
+        204, 204, 204,
+        179, 179, 179,
+        153, 153, 153,
+        128, 128, 128,
+        102, 102, 102,
+        77, 77, 77,
+        51, 51, 51,
+        26, 26, 26,
+        0, 0, 0,
+        0, 51, 51, // second row
+        0, 26, 128,
+        26, 0, 104,
+        51, 0, 51,
+        77, 0, 26,
+        153, 0, 0,
+        153, 51, 0,
+        153, 77, 0,
+        153, 102, 0,
+        153, 153, 0,
+        102, 102, 0,
+        0, 51, 0,
+        26, 77, 77, // third row
+        26, 51, 153,
+        51, 26, 128,
+        77, 26, 77,
+        102, 26, 51,
+        179, 26, 26,
+        179, 77, 26,
+        179, 102, 26,
+        179, 128, 26,
+        179, 179, 26,
+        128, 128, 26,
+        26, 77, 26,
+        51, 102, 102, // fourth row
+        51, 77, 179,
+        77, 51, 153,
+        102, 51, 102,
+        128, 51, 77,
+        204, 51, 51,
+        204, 102, 51,
+        204, 128, 51,
+        204, 153, 51,
+        204, 204, 51,
+        153, 153, 51,
+        51, 102, 51,
+        77, 128, 128, // fifth row
+        77, 102, 204,
+        102, 77, 179,
+        128, 77, 128,
+        153, 77, 102,
+        230, 77, 77,
+        230, 128, 77,
+        230, 153, 77,
+        230, 179, 77,
+        230, 230, 77,
+        179, 179, 77,
+        77, 128, 77,
+        102, 153, 153, // sixth row
+        102, 128, 230,
+        128, 102, 204,
+        153, 102, 153,
+        179, 102, 128,
+        255, 102, 102,
+        255, 153, 102,
+        255, 179, 102,
+        255, 204, 102,
+        255, 255, 77,
+        204, 204, 102,
+        102, 153, 102,
+        128, 179, 179, // seventh row
+        128, 153, 255,
+        153, 128, 230,
+        179, 128, 179,
+        204, 128, 153,
+        255, 128, 128,
+        255, 153, 128,
+        255, 204, 128,
+        255, 230, 102,
+        255, 255, 102,
+        230, 230, 128,
+        128, 179, 128,
+        153, 204, 204, // eigth row
+        153, 179, 255,
+        179, 153, 255,
+        204, 153, 204,
+        230, 153, 179,
+        255, 153, 153,
+        255, 179, 128,
+        255, 204, 153,
+        255, 230, 128,
+        255, 255, 128,
+        230, 230, 153,
+        153, 204, 153,
+        179, 230, 230, // ninth row
+        179, 204, 255,
+        204, 179, 255,
+        230, 179, 230,
+        230, 179, 204,
+        255, 179, 179,
+        255, 179, 153,
+        255, 230, 179,
+        255, 230, 153,
+        255, 255, 153,
+        230, 230, 179,
+        179, 230, 179,
+        204, 255, 255, // tenth row
+        204, 230, 255,
+        230, 204, 255,
+        255, 204, 255,
+        255, 204, 230,
+        255, 204, 204,
+        255, 204, 179,
+        255, 230, 204,
+        255, 255, 179,
+        255, 255, 204,
+        230, 230, 204,
+        204, 255, 204
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/Pagination.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ * 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.javafx.scene.control;
+
+import com.sun.javafx.css.StyleableIntegerProperty;
+import com.sun.javafx.css.StyleableProperty;
+import com.sun.javafx.css.Stylesheet.Origin;
+import com.sun.javafx.css.converters.SizeConverter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javafx.beans.DefaultProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.WritableValue;
+import javafx.scene.Node;
+import javafx.scene.control.Control;
+import javafx.util.Callback;
+
+@DefaultProperty("pages")
+public class Pagination<T> extends Control {
+
+    private static final int DEFAULT_NUMBER_OF_VISIBLE_PAGES = 10;
+
+    public static final String STYLE_CLASS_BULLET = "bullet";
+
+    /**
+     * Constructs a new Pagination.
+     */
+    public Pagination(int numberOfItems) {
+        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
+        setNumberOfItems(numberOfItems);
+    }
+
+    private Pagination() {
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Properties                                                              *
+     *                                                                         *
+     **************************************************************************/
+
+    /**
+     * The number of visible page indicators
+     */
+    public final IntegerProperty numberOfVisiblePagesProperty() {
+        if (numberOfVisiblePages == null) {
+            numberOfVisiblePages = new StyleableIntegerProperty(DEFAULT_NUMBER_OF_VISIBLE_PAGES) {
+                @Override
+                public StyleableProperty getStyleableProperty() {
+                    return StyleableProperties.NUMBER_OF_VISIBLE_PAGES;
+                }
+
+                @Override
+                public Object getBean() {
+                    return Pagination.this;
+                }
+
+                @Override
+                public String getName() {
+                    return "numberOfVisiblePages";
+                }
+            };
+        }
+        return numberOfVisiblePages;
+    }
+
+    private IntegerProperty numberOfVisiblePages;
+    public final void setNumberOfVisiblePages(int value) {
+        numberOfVisiblePagesProperty().set(value);
+    }
+
+    public final int getNumberOfVisiblePages() {
+        return numberOfVisiblePages == null ? DEFAULT_NUMBER_OF_VISIBLE_PAGES : numberOfVisiblePages.get();
+    }
+
+    /**
+     * The max number of items per page
+     */
+    public final IntegerProperty itemsPerPage = new SimpleIntegerProperty(this, "itemsPerPage", 10);
+    public final void setItemsPerPage(int value) { itemsPerPage.set(value); }
+    public final int getItemsPerPage() { return itemsPerPage.get(); }
+    public final IntegerProperty itemsPerPageProperty() { return itemsPerPage; }
+
+    /**
+     * The total number of items
+     */
+    public final IntegerProperty numberOfItems = new SimpleIntegerProperty(this, "numberOfItems", 1);
+    private final void setNumberOfItems(int value) { numberOfItems.set(value); }
+    public final int getNumberOfItems() { return numberOfItems.get(); }
+    public final IntegerProperty numberOfItemsProperty() { return numberOfItems; }
+
+    /**
+     * The current page index
+     */
+    public final IntegerProperty pageIndex = new SimpleIntegerProperty(this, "pageIndex", 0);
+    public final void setPageIndex(int value) { pageIndex.set(value); }
+    public final int getPageIndex() { return pageIndex.get(); }
+    public final IntegerProperty pageIndexProperty() { return pageIndex; }
+
+    /**
+     * The page callback
+     */
+    private ObjectProperty<Callback<Integer, Node>> pageFactory =
+            new SimpleObjectProperty<Callback<Integer, Node>>(this, "pageFactory");
+    public final void setPageFactory(Callback<Integer, Node> value) { pageFactoryProperty().set(value); }
+    public final Callback<Integer, Node> getPageFactory() {return pageFactoryProperty().get(); }
+    public ObjectProperty<Callback<Integer, Node>> pageFactoryProperty() { return pageFactory; }
+
+    /***************************************************************************
+     *                                                                         *
+     *                         Stylesheet Handling                             *
+     *                                                                         *
+     **************************************************************************/
+
+    private static final String DEFAULT_STYLE_CLASS = "pagination";
+
+    private static class StyleableProperties {
+        private static final StyleableProperty<Pagination,Number> NUMBER_OF_VISIBLE_PAGES =
+            new StyleableProperty<Pagination,Number>("-fx-number-of-visible-pages",
+                SizeConverter.getInstance(), DEFAULT_NUMBER_OF_VISIBLE_PAGES) {
+
+            @Override
+            public void set(Pagination node, Number value, Origin origin) {
+                super.set(node, value.intValue(), origin);
+            }
+
+            @Override
+            public boolean isSettable(Pagination n) {
+                return n.numberOfVisiblePages == null || !n.numberOfVisiblePages.isBound();
+            }
+
+            @Override
+            public WritableValue<Number> getWritableValue(Pagination n) {
+                return n.numberOfVisiblePagesProperty();
+            }
+        };
+        private static final List<StyleableProperty> STYLEABLES;
+        static {
+            final List<StyleableProperty> styleables =
+                new ArrayList<StyleableProperty>(Control.impl_CSS_STYLEABLES());
+            Collections.addAll(styleables,
+                NUMBER_OF_VISIBLE_PAGES
+            );
+            STYLEABLES = Collections.unmodifiableList(styleables);
+        }
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     */
+    @Deprecated
+    public static List<StyleableProperty> impl_CSS_STYLEABLES() {
+        return Pagination.StyleableProperties.STYLEABLES;
+    }
+
+    /**
+     * @treatAsPrivate implementation detail
+     * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
+     */
+    @Deprecated
+    public List<StyleableProperty> impl_getStyleableProperties() {
+        return impl_CSS_STYLEABLES();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/PaginationCell.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ * 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.javafx.scene.control;
+
+import javafx.scene.control.ListCell;
+
+public class PaginationCell<T> extends ListCell<T> {
+
+    public PaginationCell() {
+        getStyleClass().addAll("pagination-cell");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/ColorPickerBehavior.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2010, 2011, 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.behavior;
+
+import javafx.scene.control.ComboBox;
+import static javafx.scene.input.KeyCode.*;
+import static javafx.scene.input.KeyEvent.*;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import com.sun.javafx.scene.control.ColorPicker;
+
+import javafx.scene.control.SelectionModel;
+import javafx.scene.input.MouseEvent;
+
+public class ColorPickerBehavior<T> extends ComboBoxBaseBehavior<T> {
+
+    /***************************************************************************
+     *                                                                         *
+     * Constructors                                                            *
+     *                                                                         *
+     **************************************************************************/
+    
+    /**
+     * 
+     */
+    public ColorPickerBehavior(final ColorPicker<T> colorPicker) {
+        super(colorPicker);
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Key event handling                                                      *
+     *                                                                         *
+     **************************************************************************/
+
+    protected static final List<KeyBinding> COMBO_BOX_BINDINGS = new ArrayList<KeyBinding>();
+    static {
+        COMBO_BOX_BINDINGS.addAll(COMBO_BOX_BASE_BINDINGS);
+    }
+
+    @Override protected List<KeyBinding> createKeyBindings() {
+        return COMBO_BOX_BINDINGS;
+    }
+
+    @Override protected void callAction(String name) {
+        super.callAction(name);
+    }
+    
+    private ColorPicker<T> getColorPicker() {
+        return (ColorPicker<T>) getControl();
+    }
+    
+     /**************************************************************************
+     *                                                                        *
+     * Mouse Events                                                           *
+     *                                                                        *
+     *************************************************************************/
+     /**
+     * When a mouse button is pressed, we either want to behave like a button or
+     * show the popup.  This will be called by the skin.
+     *
+     * @param e the mouse press event
+     * @param behaveLikeButton if true, this should act just like a button
+     */
+//    public void mousePressed(MouseEvent e) {
+//        super.mousePressed(e);
+//    }
+    
+    @Override public void mouseReleased(MouseEvent e) {
+        // Overriding to not do the usual on mouseReleased.
+        // The event is handled by the skin instead, which calls
+        // the method below.
+    }
+//    
+    /**
+     * Handles mouse release events.  This will be called by the skin.
+     *
+     * @param e the mouse press event
+     * @param behaveLikeButton if true, this should act just like a button
+     */
+    public void mouseReleased(MouseEvent e, boolean behaveLikeSplitButton) {
+        if (behaveLikeSplitButton) {
+            super.mouseReleased(e);
+        } else {
+            disarm();
+        }
+    }
+}
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/ComboBoxBaseBehavior.java	Thu Mar 29 15:10:33 2012 -0700
@@ -227,7 +227,7 @@
         }
     }
     
-    private boolean wasComboBoxButtonClickedForAutoHide = false;
+    boolean wasComboBoxButtonClickedForAutoHide = false;
     private boolean mouseInsideButton = false;
     public void onAutoHide() {
         // if the ComboBox button was clicked, and it was this that forced the
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/PaginationBehavior.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ * 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.
+ *
+ * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.javafx.scene.control.behavior;
+
+import com.sun.javafx.scene.control.Pagination;
+import java.util.ArrayList;
+import java.util.List;
+import javafx.scene.input.MouseEvent;
+
+public class PaginationBehavior<T> extends BehaviorBase<Pagination<T>> {
+
+    /**************************************************************************
+     *                          Setup KeyBindings                             *
+     *************************************************************************/
+
+    protected static final List<KeyBinding> PAGINATION_BINDINGS = new ArrayList<KeyBinding>();
+    static {
+        PAGINATION_BINDINGS.addAll(TRAVERSAL_BINDINGS);
+    }
+
+    @Override protected List<KeyBinding> createKeyBindings() {
+        return PAGINATION_BINDINGS;
+    }
+
+    /***************************************************************************
+     *                                                                         *
+     * Mouse event handling                                                    *
+     *                                                                         *
+     **************************************************************************/
+
+    @Override public void mousePressed(MouseEvent e) {
+        super.mousePressed(e);
+        Pagination p = getControl();
+        p.requestFocus();
+    }
+
+    /**************************************************************************
+     *                         State and Functions                            *
+     *************************************************************************/
+
+    public PaginationBehavior(Pagination pagination) {
+        super(pagination);
+    }
+}
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java	Thu Mar 29 15:10:33 2012 -0700
@@ -136,6 +136,7 @@
             else if ("SelectEnd".equals(name)) selectEnd();
             else if ("SelectHomeExtend".equals(name)) selectHomeExtend();
             else if ("SelectEndExtend".equals(name)) selectEndExtend();
+            /*DEBUG*/else if ("UseVK".equals(name)) ((com.sun.javafx.scene.control.skin.TextInputControlSkin)textInputControl.getSkin()).toggleUseVK();
             else super.callAction(name);
             setCaretAnimating(true);
         } else if ("Copy".equals(name)) {
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextInputControlBindings.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/behavior/TextInputControlBindings.java	Thu Mar 29 15:10:33 2012 -0700
@@ -139,5 +139,6 @@
         BINDINGS.add(new KeyBinding(TAB, "TraversePrevious").shift());
         // TODO XXX DEBUGGING ONLY
 //        BINDINGS.add(new KeyBinding(F4, "TraverseDebug").alt().ctrl().shift());
+        BINDINGS.add(new KeyBinding(DIGIT9, "UseVK").ctrl().shift());
     }
 }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ChoiceBoxSkin.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ChoiceBoxSkin.java	Thu Mar 29 15:10:33 2012 -0700
@@ -67,6 +67,7 @@
         registerChangeListener(control.showingProperty(), "SHOWING");
         registerChangeListener(control.itemsProperty(), "ITEMS");
         registerChangeListener(control.getSelectionModel().selectedItemProperty(), "SELECTION_CHANGED");
+        registerChangeListener(control.converterProperty(), "CONVERTER");
     }
 
     private ObservableList<?> choiceBoxItems;
@@ -238,6 +239,9 @@
             } else {
                 popup.hide();
             }
+        } else if ("CONVERTER".equals(p)) {
+            updateChoiceBoxItems();
+            updatePopupItems();
         }
     }
 
@@ -300,7 +304,7 @@
             label.setText("");
         } else {
             int selectedIndex = selectionModel.getSelectedIndex();
-            if (selectedIndex == -1) {
+            if (selectedIndex == -1 || selectedIndex > popup.getItems().size()) {
                 label.setText(""); // clear label text
                 return;
             }
@@ -310,12 +314,9 @@
                     ((RadioMenuItem) selectedItem).setSelected(true);
                     toggleGroup.selectToggle(null);
                 }
+                // update the label
+                label.setText(popup.getItems().get(selectedIndex).getText());
             }
-
-            // update the label
-            Object selected = choiceBoxItems.get(selectedIndex);
-            String text = selected == null ? "" : selected.toString();
-            label.setText(text);
         }
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ColorPickerSkin.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,171 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.sun.javafx.scene.control.skin;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import javafx.scene.Node;
+import javafx.beans.binding.ObjectBinding;
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.EventHandler;
+import javafx.scene.control.*;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.StackPane;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.RectangleBuilder;
+import com.sun.javafx.scene.control.behavior.ColorPickerBehavior;
+import com.sun.javafx.scene.control.ColorPicker;
+import com.sun.javafx.scene.control.ColorPickerPanel;
+
+/**
+ *
+ * @author paru
+ */
+public class ColorPickerSkin<T> extends ComboBoxPopupControl<T> {
+
+    private Label displayNode; 
+    private StackPane icon; 
+    private Rectangle colorRect; 
+    private ColorPickerPanel popup = new ColorPickerPanel(Color.WHITE);
+    
+    private boolean behaveLikeSplitButton = false;
+    
+    private ObjectProperty<Color> color = new SimpleObjectProperty<Color>(Color.WHITE);
+    public ObjectProperty<Color> colorProperty() { return color; }
+    public Color getColor() { return color.get(); }
+    public void setColor(Color newColor) { color.set(newColor); }
+    
+    public ColorPickerSkin(final ColorPicker<T> colorPicker) {
+        super(colorPicker, new ColorPickerBehavior<T>(colorPicker));
+        initialize();
+        if (arrowButton.getOnMouseReleased() == null) {
+            colorPicker.setOnMouseReleased(new EventHandler<MouseEvent>() {
+                @Override public void handle(MouseEvent e) {
+                    ((ColorPickerBehavior)getBehavior()).mouseReleased(e, behaveLikeSplitButton);
+                    e.consume();
+                }
+            });
+        }
+    }
+    
+    private void initialize() {
+        updateComboBoxMode();
+        popup.addEventFilter(MouseEvent.MOUSE_RELEASED, new EventHandler<MouseEvent>() {
+            @Override public void handle(MouseEvent t) {
+                ((ColorPicker)getSkinnable()).hide();
+            }
+        });
+        popup.colorProperty().addListener(new ChangeListener<Color>() {
+            @Override public void changed(ObservableValue<? extends Color> ov, Color t, Color t1) {
+                setColor(t1);
+            }
+        });
+    }
+     
+    private void updateComboBoxMode() {
+        if (getSkinnable().getStyleClass().contains(ColorPicker.STYLE_CLASS_BUTTON)) {
+            setMode(ComboBoxMode.BUTTON);
+            behaveLikeSplitButton = false;
+        }
+        else if (getSkinnable().getStyleClass().contains(ColorPicker.STYLE_CLASS_SPLIT_BUTTON)) {
+            setMode(ComboBoxMode.SPLITBUTTON);
+            behaveLikeSplitButton = true;
+        }
+    }
+    
+      private static final List<String> colorNames = new ArrayList<String>();
+    static {
+        // Initializes the namedColors map
+        Color.web("white", 1.0);
+        for (Field f : Color.class.getDeclaredFields()) {
+            int modifier = f.getModifiers();
+            if (Modifier.isStatic(modifier) && Modifier.isPublic(modifier) && f.getType().equals(Color.class)) {
+                colorNames.add(f.getName());
+            }
+        }
+        Collections.sort(colorNames);
+    }
+    
+    private static String colorValueToWeb(Color c) {
+        String web = null;
+        if (colorNames != null) {
+            // Find a name for the color. Note that there can
+            // be more than one name for a color, e.g. #ff0ff
+            // is named both "fuchsia" and "magenta".
+            // We return the first name encountered (alphabetically).
+
+            // TODO: Use a hash map for performance
+            for (String name : colorNames) {
+                if (Color.web(name).equals(c)) {
+                    web = name;
+                    break;
+                }
+            }
+        }
+        if (web == null) {
+            web = String.format((Locale) null, "%02x%02x%02x", Math.round(c.getRed() * 255), Math.round(c.getGreen() * 255), Math.round(c.getBlue() * 255));
+        }
+        return web;
+    }
+    
+    @Override protected Node getPopupContent() {
+       return popup;
+    }
+    
+    @Override public Node getDisplayNode() {
+        if (displayNode == null) {
+            displayNode = new Label();
+            displayNode.getStyleClass().add("color-picker-label");
+            // label text
+            displayNode.textProperty().bind(new StringBinding() {
+                { bind(color); }
+                @Override protected String computeValue() {
+                    return colorValueToWeb(getColor());
+                }
+            });
+            // label graphic
+            icon = new StackPane();
+            icon.getStyleClass().add("picker-color");
+            colorRect = RectangleBuilder.create()
+                    .width(16).height(16)
+                    .build();
+            colorRect.fillProperty().bind(new ObjectBinding<Paint>() {
+                { bind(color); }
+                @Override protected Paint computeValue() {
+                    return getColor();
+                }
+            });         
+            
+            icon.getChildren().add(colorRect);
+            displayNode.setGraphic(icon);
+            if (displayNode.getOnMouseReleased() == null) {
+                displayNode.setOnMouseReleased(new EventHandler<MouseEvent>() {
+                    @Override
+                    public void handle(MouseEvent e) {
+                        ((ColorPickerBehavior)getBehavior()).mouseReleased(e, false);
+                        e.consume();
+                    }
+                });
+            }
+        }
+        return displayNode;
+    }
+    
+    @Override protected void layoutChildren() {
+        updateComboBoxMode();
+        super.layoutChildren();
+    }
+    
+}
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ComboBoxBaseSkin.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ComboBoxBaseSkin.java	Thu Mar 29 15:10:33 2012 -0700
@@ -42,6 +42,11 @@
     protected StackPane arrowButton;
     protected StackPane arrow;
     
+    /** The mode in which this control will be represented. */
+    private ComboBoxMode mode = ComboBoxMode.COMBOBOX;
+    protected final ComboBoxMode getMode() { return mode; }
+    protected final void setMode(ComboBoxMode value) { mode = value; }
+    
     public ComboBoxBaseSkin(final ComboBoxBase<T> comboBox, final ComboBoxBaseBehavior behavior) {
         // Call the super method with the button we were just given in the 
         // constructor, as well as an instance of the behavior class.
@@ -110,7 +115,7 @@
             updateDisplayArea();
         } else if (p == "VALUE") {
             updateDisplayArea();
-        }
+        } 
     }
     
     private void updateDisplayArea() {
@@ -122,6 +127,10 @@
         getChildren().add(0, displayNode);
     }
     
+    private boolean isButton() {
+        return getMode() == ComboBoxMode.BUTTON;
+    }
+    
     @Override protected void layoutChildren() {
         if (displayNode == null) {
             updateDisplayArea();
@@ -137,21 +146,33 @@
         final double h = getSkinnable().getHeight() - (padding.getTop() + padding.getBottom());
 
         final double arrowWidth = snapSize(arrow.prefWidth(-1));
-        final double arrowButtonWidth = snapSpace(arrowButtonPadding.getLeft()) + 
-                                        arrowWidth + 
-                                        snapSpace(arrowButtonPadding.getRight());
+        final double arrowButtonWidth = (isButton()) ? 0 :
+                (snapSpace(arrowButtonPadding.getLeft()) + arrowWidth + 
+                snapSpace(arrowButtonPadding.getRight()));
         
         if (displayNode != null) {
-            displayNode.resizeRelocate(x, y, w, h);
+            displayNode.resizeRelocate(x, y, w-arrowButtonWidth, h);
         }
         
+        if (isButton()) return;
+        
         arrowButton.resize(arrowButtonWidth, getHeight());
         positionInArea(arrowButton, getWidth() - padding.getRight() - arrowButtonWidth, 0, 
                 arrowButtonWidth, getHeight(), 0, HPos.CENTER, VPos.CENTER);
     }
     
     @Override protected double computePrefWidth(double height) {
-        return displayNode == null ? 100 : displayNode.prefWidth(height);
+        final Insets arrowButtonPadding = arrowButton.getInsets();
+        final double arrowWidth = snapSize(arrow.prefWidth(-1));
+        final double arrowButtonWidth = (isButton()) ? 0 : 
+                                        (snapSpace(arrowButtonPadding.getLeft()) + 
+                                        arrowWidth + 
+                                        snapSpace(arrowButtonPadding.getRight()));
+        
+        final double totalWidth = (displayNode == null) ? 0 : (displayNode.prefWidth(height) 
+                + arrowButtonWidth);
+        return getInsets().getLeft() + totalWidth +
+                + getInsets().getRight();
     }
     
     @Override protected double computePrefHeight(double width) {
@@ -159,15 +180,19 @@
             updateDisplayArea();
         }
 
+        double ph;
         if (displayNode == null) {
             final int DEFAULT_HEIGHT = 21;
-            final Insets padding = getInsets();
             final Insets arrowButtonPadding = arrowButton.getInsets();
-            double arrowHeight = arrowButtonPadding.getTop() + arrow.prefHeight(-1) + arrowButtonPadding.getBottom();
-            return padding.getTop() + Math.max(DEFAULT_HEIGHT, arrowHeight) + padding.getBottom();
+            double arrowHeight = (isButton()) ? 0 : 
+                    (arrowButtonPadding.getTop() + arrow.prefHeight(-1) + arrowButtonPadding.getBottom());
+            ph = Math.max(DEFAULT_HEIGHT, arrowHeight);
         } else {
-            return displayNode.prefHeight(width);
+            ph = displayNode.prefHeight(width);
         }
+
+        final Insets padding = getInsets();
+        return padding.getTop()+ ph + padding.getBottom();
     }
 
     @Override protected double computeMaxWidth(double height) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ComboBoxMode.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,28 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.sun.javafx.scene.control.skin;
+
+/**
+ *
+ * @author paru
+ */
+public enum ComboBoxMode {
+    
+     /**
+     * The default look with display and arrow button.
+     */
+    COMBOBOX,
+
+    /**
+     * Split Menu Button look with display and arrow button separated by a line.
+     */
+    SPLITBUTTON,
+    
+    /**
+     * Simple Button look with display area only.
+     */       
+    BUTTON 
+}
+
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ContextMenuContent.java	Wed Mar 28 12:38:20 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/ContextMenuContent.java	Thu Mar 29 15:10:33 2012 -0700
@@ -95,7 +95,7 @@
     private double maxLeftWidth = 0;
 
     private Rectangle clipRect;
-    private MenuBox itemsContainer;
+    MenuBox itemsContainer;
     private ArrowMenuItem upArrow;
     private ArrowMenuItem downArrow;
 
@@ -129,6 +129,21 @@
         initialize();
         setUpBinds();
     }
+    
+       //For testing purpose only
+    VBox getItemsContainer() {
+        return itemsContainer;
+    }
+    //For testing purpose only
+    int getCurrentFocusIndex() {
+        return currentFocusedIndex;
+    }
+    //For testing purpose only
+    void setCurrentFocusedIndex(int index) {
+        if (index < itemsContainer.getChildren().size()) {
+            currentFocusedIndex = index;
+        }
+    }
 
     private void computeVisualMetrics() {
         maxRightWidth = 0;
@@ -545,7 +560,7 @@
                     // if submenu for this menu is already showing then do nothing
                     // Menubar will process the right key and move to the next menu
                     if (openSubmenu == menu && submenu.isShowing()) return;
-                    showSubmenu(menu);
+                    menu.show();
                     // request focus on the first item of the submenu after it is shown
                     ContextMenuContent cmContent = (ContextMenuContent)submenu.getSkin().getNode();
                     if (cmContent != null) {
@@ -573,7 +588,7 @@
                     }
                     if (menu.isDisable()) return;
                     selectedBackground = ((MenuItemContainer)n);
-                    showSubmenu(menu);
+                    menu.show();
                 } else {
                     ((MenuItemContainer)n).doSelect();
                 }
@@ -732,6 +747,11 @@
         }
     }
 
+    // For test purpose only
+    ContextMenu getSubMenu() {
+        return submenu;
+    }
+
     private void showSubmenu(Menu menu) {
         openSubmenu = menu;
 
@@ -973,6 +993,10 @@
         protected Label getLabel(){
             return (Label) label;
         }
+        
+        protected MenuItem getItem() {
+            return item;
+        }
 
         public MenuItemContainer(MenuItem item){
             if (item == null) {
@@ -1115,6 +1139,18 @@
                             doSelect();
                         }
                     });
+                    // RT-19546 update currentFocusedIndex when MenuItemContainer gets focused.
+                    // e.g this happens when you press the Right key to open a submenu; the first
+                    // menuitem is focused.
+                    focusedProperty().addListener(new ChangeListener<Boolean>() {
+                        @Override public void changed(ObservableValue<? extends Boolean> ov,
+                                                                    Boolean t, Boolean t1) {
+                            if (t1 && !t) {
+                                currentFocusedIndex =
+                                    itemsContainer.getChildren().indexOf(MenuItemContainer.this);
+                            }
+                        }
+                    });
                 }
             }
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/FXVK.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2010, 2011, 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.BooleanPropertyBase;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ObjectPropertyBase;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.WritableValue;
+import javafx.geometry.HPos;
+import javafx.geometry.Point2D;
+import javafx.geometry.VPos;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+
+import com.sun.javafx.css.StyleManager;
+import com.sun.javafx.css.StyleableBooleanProperty;
+import com.sun.javafx.css.StyleableProperty;
+import com.sun.javafx.css.converters.BooleanConverter;
+import javafx.beans.DefaultProperty;
+
+public class FXVK extends Control {
+
+    public String[] chars;
+
+    public FXVK() {
+        getStyleClass().setAll(DEFAULT_STYLE_CLASS);
+    }
+
+
+    public final ObjectProperty<Node> attachedNodeProperty() {
+        if (attachedNode == null) {
+            attachedNode = new ObjectPropertyBase<Node>() {
+                @Override public Object getBean() {
+                    return FXVK.this;
+                }
+
+                @Override public String getName() {
+                    return "attachedNode";
+                }
+            };
+        }
+        return attachedNode;
+    }
+    private ObjectProperty<Node> attachedNode;
+    final void setAttachedNode(Node value) { attachedNodeProperty().setValue(value); }
+    public final Node getAttachedNode() { return attachedNode == null ? null : attachedNode.getValue(); }
+
+
+    private static FXVK vk;
+
+    public static void attach(final TextInputControl textInput) {
+        if (vk == null) {
+            vk = new FXVK();
+//             vk.impl_processCSS(true);
+            vk.setSkin(new FXVKSkin(vk));
+        }
+
+        vk.setAttachedNode(textInput);
+    }
+
+    public static void detach() {
+        if (vk != null) {
+            vk.setAttachedNode(null);
+        }
+    }
+
+
+
+
+
+
+
+
+
+
+    /***************************************************************************
+     *                                                                         *
+     * Stylesheet Handling                                                     *
+     *                                                                         *
+     **************************************************************************/
+
+    private static final String DEFAULT_STYLE_CLASS = "fxvk";
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/FXVKSkin.java	Thu Mar 29 15:10:33 2012 -0700
@@ -0,0 +1,774 @@
+/*
+ * Copyright (c) 2010, 2011, 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 java.lang.reflect.Field;
+import java.util.*;
+
+import javafx.animation.Interpolator;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.application.Platform;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.event.ActionEvent;
+import javafx.event.Event;
+import javafx.event.EventHandler;
+import javafx.event.EventTarget;
+import javafx.event.EventType;
+import javafx.geometry.HPos;
+import javafx.geometry.Point2D;
+import javafx.geometry.VPos;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.*;
+import javafx.stage.Popup;
+import javafx.util.Duration;
+
+import com.sun.javafx.robot.impl.FXRobotHelper;
+import com.sun.javafx.robot.impl.FXRobotHelper.FXRobotInputAccessor;
+import com.sun.javafx.scene.control.behavior.BehaviorBase;
+
+import javafx.animation.Animation.Status;
+import static javafx.scene.input.KeyCode.*;
+import static javafx.scene.input.MouseEvent.*;
+
+public class FXVKSkin extends SkinBase<FXVK, BehaviorBase<FXVK>> {
+
+    private static Region oldRoot;
+    private static Pane newRoot;
+    private static Popup secondaryPopup;
+    private static FXVK primaryVK;
+    private static FXVK secondaryVK;
+    private static Popup vkPopup;
+    private static Timeline slideInTimeline;
+    private static Timeline slideOutTimeline;
+    private static Timeline slideRootTimeline;
+    private static Timeline secondaryVKDelay;
+    private static CharKey secondaryVKKey;
+
+    private Node attachedNode;
+
+    FXVK fxvk;
+    Control[][] keyRows;
+
+    enum State { NORMAL, SHIFTED, SHIFT_LOCK, NUMERIC; };
+
+    static State state = State.NORMAL;
+
+    static final boolean USE_POPUP = false;
+    static final double VK_WIDTH = 800;
+    static final double VK_HEIGHT = 230;
+    static final double VK_SLIDE_MILLIS = 250;
+    static final double PREF_KEY_WIDTH = 40;
+    static final double PREF_KEY_HEIGHT = 30;
+    static final double hGap = 2;
+    static final double vGap = 3;
+
+    double keyWidth = PREF_KEY_WIDTH;
+    double keyHeight = PREF_KEY_HEIGHT;
+
+    private ShiftKey shiftKey;
+    private SymbolKey symbolKey;
+
+    public FXVKSkin(final FXVK fxvk) {
+        super(fxvk, new BehaviorBase<FXVK>(fxvk));
+        this.fxvk = fxvk;
+
+        fxvk.setFocusTraversable(false);
+
+        createKeys();
+
+        fxvk.attachedNodeProperty().addListener(new InvalidationListener() {
+            @Override public void invalidated(Observable valueModel) {
+                Node oldNode = attachedNode;
+                attachedNode = fxvk.getAttachedNode();
+
+if (USE_POPUP) {
+                if (fxvk != secondaryVK && oldNode != null) {
+                    translatePane(oldNode.getScene().getRoot(), 0);
+                }
+} else {
+                if (fxvk != secondaryVK && oldRoot != null) {
+                    translatePane(oldRoot, 0);
+                }
+}
+
+                if (attachedNode != null) {
+                    final Scene scene = attachedNode.getScene();
+
+                    if (secondaryVKDelay == null) {
+                        secondaryVKDelay = new Timeline();
+                        KeyFrame kf = new KeyFrame(Duration.millis(500), new EventHandler<ActionEvent>() {
+                            @Override public void handle(ActionEvent event) {
+                                if (secondaryVKKey != null) {
+                                    showSecondaryVK(secondaryVKKey, fxvk, state);
+                                }
+                            }
+                        });
+                        secondaryVKDelay.getKeyFrames().add(kf);
+                    }
+
+if (USE_POPUP) {
+                    if (vkPopup == null) {
+                        vkPopup = new Popup();
+                        vkPopup.getContent().add(fxvk);
+                    }
+
+                    if (vkPopup.isShowing()) {
+                        Point2D nodePoint =
+                            com.sun.javafx.Utils.pointRelativeTo(attachedNode,
+                                                                 vkPopup.getWidth(), vkPopup.getHeight(),
+                                                                 HPos.CENTER, VPos.BOTTOM, 0, 2, true);
+                        Point2D point =
+                            com.sun.javafx.Utils.pointRelativeTo(scene.getRoot(),
+                                                                 vkPopup.getWidth(), vkPopup.getHeight(),
+                                                                 HPos.CENTER, VPos.BOTTOM, 0, 0, true);
+                        double y = point.getY() - fxvk.prefHeight(-1);
+                        double nodeBottom = nodePoint.getY();
+                        if (y < nodeBottom) {
+                            translatePane(scene.getRoot(), y - nodeBottom);
+                        }
+                    } else {
+                        Platform.runLater(new Runnable() {
+                            public void run() {
+                                Point2D nodePoint =
+                                    com.sun.javafx.Utils.pointRelativeTo(attachedNode,
+                                                                         vkPopup.getWidth(), vkPopup.getHeight(),
+                                                                         HPos.CENTER, VPos.BOTTOM, 0, 2, true);
+                                Point2D point =
+                                    com.sun.javafx.Utils.pointRelativeTo(scene.getRoot(),
+                                                                         vkPopup.getWidth(), vkPopup.getHeight(),
+                                                                         HPos.CENTER, VPos.BOTTOM, 0, 0, true);
+                                double y = point.getY() - fxvk.prefHeight(-1);
+                                vkPopup.show(attachedNode, point.getX(), y);
+
+
+                                double nodeBottom = nodePoint.getY();
+                                if (y < nodeBottom) {
+                                    translatePane(scene.getRoot(), y - nodeBottom);
+                                }
+                            }
+                        });
+                    }
+
+                    if (oldNode == null || oldNode.getScene() != attachedNode.getScene()) {
+                        fxvk.setPrefWidth(scene.getWidth());
+                        fxvk.setMaxWidth(USE_PREF_SIZE);
+                        fxvk.setPrefHeight(200);
+                    }
+} else {
+                    if (newRoot == null) {
+                        oldRoot = (Region)scene.getRoot();
+                        newRoot = new NewRootPane(oldRoot);
+                        scene.setRoot(newRoot);
+                        newRoot.getChildren().add(fxvk);
+                        slideInTimeline = new Timeline();
+                    }
+
+                    fxvk.setVisible(true);
+                    if (fxvk != secondaryVK && fxvk.getHeight() > 0 &&
+                        (fxvk.getLayoutY() == 0 || fxvk.getLayoutY() > scene.getHeight() - fxvk.getHeight())) {
+
+                        slideOutTimeline.stop();
+                        slideInTimeline.playFromStart();
+                    }
+
+                    if (fxvk != secondaryVK) {
+                        Platform.runLater(new Runnable() {
+                            public void run() {
+                                double nodeBottom =
+                                    attachedNode.localToScene(attachedNode.getBoundsInLocal()).getMaxY() + 2;
+                                if (nodeBottom > fxvk.getLayoutY()) {
+                                    translatePane(oldRoot, fxvk.getLayoutY() - nodeBottom);
+                                }
+                            }
+                        });
+
+                        if (oldNode == null || oldNode.getScene() != attachedNode.getScene()) {
+                            fxvk.setPrefWidth(VK_WIDTH);
+                            fxvk.setMaxWidth(USE_PREF_SIZE);
+                            fxvk.setPrefHeight(VK_HEIGHT);
+                            fxvk.setMinHeight(USE_PREF_SIZE);
+                        }
+                    }
+}
+                } else {
+if (USE_POPUP) {
+                    if (vkPopup != null) {
+                        vkPopup.hide();
+                    }
+} else {
+                    if (fxvk != secondaryVK) {
+                        slideInTimeline.stop();
+                        slideOutTimeline.playFromStart();
+                    }
+}
+                    if (secondaryVK != null) {
+                        secondaryVK.setAttachedNode(null);
+                        secondaryPopup.hide();
+                    }
+                    return;
+                }
+            }
+        });
+    }
+
+    private void translatePane(Parent pane, double y) {
+        if (slideRootTimeline == null) {
+            slideRootTimeline = new Timeline();
+        } else {
+            slideRootTimeline.stop();
+        }
+
+        slideRootTimeline.getKeyFrames().setAll(
+            new KeyFrame(Duration.millis(VK_SLIDE_MILLIS),
+                         new KeyValue(pane.translateYProperty(), y, Interpolator.EASE_BOTH)));
+        slideRootTimeline.playFromStart();
+    }
+
+    private void createKeys() {
+        getChildren().clear();
+
+        if (fxvk.chars != null) {
+            // Secondary popup
+            int nKeys = fxvk.chars.length;
+            int nRows = (int)Math.floor(Math.sqrt(Math.max(1, nKeys - 2)));
+            int nKeysPerRow = (int)Math.ceil(nKeys / (double)nRows);
+            keyRows = new Control[nRows][];
+            for (int i = 0; i < nRows; i++) {
+                keyRows[i] =
+                    makeKeyRow((Object[])Arrays.copyOfRange(fxvk.chars, i * nKeysPerRow,
+                                                            Math.min((i + 1) * nKeysPerRow, fxvk.chars.length)));
+            }
+        } else {
+            // TODO: Move this to a resource bundle.
+            keyRows = new Control[][] {
+                makeKeyRow("q 1 [",
+                           "w 2 ]",
+                           "e 3 { \u00e8 \u00e9 \u00ea \u00eb", // e 3 { egrave eacute ecircumflex ediaeresis
+                           "r 4 } \u00ae", // r 4 } registered
+                           "t 5 \\ \u2122", // t 5 \ TM
+                           "y 6 | \u1ef3 \u00fd \u0177 \u0233 \u00ff \u1ef7", // y 6 | ygrave yacute ycircumflex ymacron ydiaeresis yhook
+                           "u 7 \" \u00f9 \u00fa \u00fb \u00fc", // u 7 \" ugrave uacute ucircumflex udiaeresis
+                           "i 8 < \u00ec \u00ed \u00ee \u00ef", // i 8 < igrave iacute icircumflex idiaeresis
+                           "o 9 > \u00f2 \u00f3 \u00f4 \u00f5 \u00f6 \u00f8 \u00b0", // o 9 > ograve oacute ocircumflex otilde odiaeresis oslash degree
+                           "p 0 _ \u00a7 \u00b6 \u03c0"),       // p 0 _ paragraph pilcrow pi
+                makeKeyRow("a @ ~ \u00e0 \u00e1 \u00e2 \u00e3 \u00e4 \u00e5", // a @ ~ agrave aacute acircumflex atilde adiaeresis aring
+                           "s # ` \u015f \u0161 \u00df \u03c3", // s # ` scedilla scaron sharps sigma
+                           "d $ \u20ac \u00f0",                 // d $ euro eth
+                           "f % \u00a3",                        // f % sterling
+                           "g ^ \u00a5",                        // g ^ yen
+                           "h & \u00a7",                        // h & paragraph (TODO: use only once)
+                           "j * \u00b7",                        // j * middledot
+                           "k ( \u00b0",                        // k ( degree (TODO: use only once)
+                           "l ) \u2260"),                       // l ) notequalto
+                makeKeyRow(shiftKey = new ShiftKey(keyWidth * 1.5),
+                           "z - \u00a1",                        // z - invertedexclamationmark
+                           "x = \u00bf",                        // x = invertedquestionmark
+                           "c + \u2030 \u00e7 \u00a9 \u00a2",   // c + permille ccedilla copyright cent
+                           "v ; \u00ae",                        // v ; registered (TODO: use only once)
+                           "b : \u2122",                        // b : TM  (TODO: use only once)
+                           "n / \u00ab \u00f1",                 // n / doubleleftangle ntilde
+                           "m ' \u00bb",                        // m ' doublerightangle (add micro)
+                           new CommandKey("\u232b", BACK_SPACE, keyWidth * 1.5)),
+                makeKeyRow(symbolKey = new SymbolKey("!#123 ABC", keyWidth * 2.5 + (9-4) * hGap / 2),
+                           ", !",                               // , !
+                           " ",                                 // space
+                           ". ?",                               // . ?
+                           new CommandKey("\u21b5", ENTER, keyWidth * 2.5 + (9-4) * hGap / 2))
+            };
+        }
+
+        VBox vbox = new VBox(vGap);
+        vbox.setFillWidth(true);
+        getChildren().add(vbox);
+
+        double primaryFontSize = 16 * keyWidth / PREF_KEY_WIDTH;
+        double secondaryFontSize = 8 * keyWidth / PREF_KEY_WIDTH;
+
+        for (Control[] row : keyRows) {
+            HBox hbox = new HBox(hGap);
+            // Primary keyboard has centered keys, secondary has left aligned keys.
+            hbox.setAlignment((fxvk.chars != null) ? Pos.CENTER_LEFT : Pos.CENTER);
+            vbox.getChildren().add(hbox);
+            for (Control c : row) {
+                hbox.getChildren().add(c);
+                HBox.setHgrow(c, Priority.ALWAYS);
+                if (c instanceof Key) {
+                    Key key = (Key)c;
+                    if (fxvk.chars != null) {
+                        key.getStyleClass().add("secondary-key");
+                    }
+                    key.setStyle("-fx-font-size: "+primaryFontSize+"px;");
+                    if (key.getGraphic() instanceof Label) {
+                        ((Label)key.getGraphic()).setStyle("-fx-font-size: "+secondaryFontSize+"px;");
+                    }
+                }
+            }
+        }
+    }
+
+
+    private Control[] makeKeyRow(Object... obj) {
+        List<Object> keyList = Arrays.asList((Object[])obj);
+        return makeKeyRow(keyList);
+    }
+
+    private Control[] makeKeyRow(List<Object> keyList) {
+        Control[] keyRow = new Control[keyList.size()];
+        for (int i = 0; i < keyRow.length; i++) {
+            if (keyList.get(i) instanceof String) {
+                keyRow[i] = new CharKey((String)keyList.get(i));
+            } else {
+                keyRow[i] = (Control)keyList.get(i);
+            }
+        }
+        return keyRow;
+    }
+
+    private void toggleShift() {
+        State newState;
+        switch (state) {
+          case NORMAL:
+            newState = State.SHIFTED;
+            break;
+
+          case SHIFTED:
+            newState = State.SHIFT_LOCK;
+            break;
+
+          case SHIFT_LOCK:
+          default:
+            newState = State.NORMAL;
+        }
+        state = newState;
+
+        if (fxvk == secondaryVK) {
+            ((FXVKSkin)primaryVK.getSkin()).updateLabels();
+        } else {
+            updateLabels();
+        }
+    }
+
+    private void updateLabels() {
+        for (Control[] row : keyRows) {
+            for (Control button : row) {
+                if (button instanceof CharKey) {
+                    CharKey key = (CharKey)button;
+                    String txt = key.chars[0];
+                    String alt = (key.chars.length > 1) ? key.chars[1] : "";
+                    if (key.chars.length > 1 && state == State.NUMERIC) {
+                        txt = key.chars[1];
+                        if (key.chars.length > 2) {
+                            alt = key.chars[2];
+                        } else {
+                            alt = "";
+                        }
+                    } else if (state == State.SHIFTED || state == State.SHIFT_LOCK) {
+                        txt = txt.toUpperCase();
+                    }
+                    key.setText(txt);
+                    if (key.graphic != null) {
+                        key.graphic.setText(alt);
+                    }
+                }
+            }
+        }
+        symbolKey.setText(symbolKey.chars[(state == State.NUMERIC) ? 1 : 0]);
+    }
+
+    private void fireKeyEvent(Node target, EventType<? extends KeyEvent> eventType,
+                           KeyCode keyCode, String keyChar, String keyText,
+                           boolean shiftDown, boolean controlDown,
+                           boolean altDown, boolean metaDown) {
+        try {
+            Field fld = FXRobotHelper.class.getDeclaredField("inputAccessor");
+            fld.setAccessible(true);
+            FXRobotInputAccessor inputAccessor = (FXRobotInputAccessor)fld.get(null);
+            target.fireEvent(inputAccessor.createKeyEvent(eventType,
+                                                          keyCode, keyChar, keyText,
+                                                          shiftDown, controlDown,
+                                                          altDown, metaDown));
+        } catch (Exception e) {
+            System.err.println(e);
+        }
+    }
+
+
+
+    private class Key extends Button {
+        private Key() {
+            this(null);
+        }
+
+        private Key(String text) {
+            super(text);
+
+            getStyleClass().add("key");
+            setFocusTraversable(false);
+
+            setMinHeight(USE_PREF_SIZE);
+            setPrefHeight(keyHeight);
+        }
+
+    }
+
+    private class CharKey extends Key {
+        String str;
+        String[] chars;
+        Label graphic;
+
+        EventHandler<ActionEvent> actionHandler = new EventHandler<ActionEvent>() {
+            @Override public void handle(ActionEvent e) {
+                if (fxvk != secondaryVK && secondaryPopup != null && secondaryPopup.isShowing()) {
+                    return;
+                }
+
+                Node target = fxvk.getAttachedNode();
+                if (target instanceof EventTarget) {
+                    String txt = getText();
+                    if (txt.length() > 1 && txt.contains(" ")) {
+                        //txt = txt.split(" ")[shift ? 1 : 0];
+                        txt = txt.split(" ")[0];
+                    }
+                    for (int i = 0; i < txt.length(); i++) {
+                        String str = txt.substring(i, i+1);
+                        fireKeyEvent(target, KeyEvent.KEY_TYPED, null, str, str,
+                                  state == State.SHIFTED, false, false, false);
+                    }
+
+                    if (state == State.SHIFTED) {
+                        toggleShift();
+                        toggleShift();
+                    }
+                }
+
+                if (fxvk == secondaryVK) {
+                    showSecondaryVK(null, fxvk, state);
+                }
+            }
+        };
+
+        CharKey(String str) {
+            this.str = str;
+            setOnAction(actionHandler);
+
+            if (fxvk != secondaryVK) {
+                setOnMousePressed(new EventHandler<MouseEvent>() {
+                    @Override public void handle(MouseEvent event) {
+                        showSecondaryVK(null, fxvk, state);
+                        if (state != State.NUMERIC || chars.length > 2) {
+                            secondaryVKKey = CharKey.this;
+                            secondaryVKDelay.playFromStart();
+                        } else {
+                            secondaryVKKey = null;
+                        }
+                    }
+                });
+
+                setOnMouseReleased(new EventHandler<MouseEvent>() {
+                    @Override public void handle(MouseEvent event) {
+                        secondaryVKDelay.stop();
+                    }
+                });
+            }
+
+            if (str.length() == 1) {
+                chars = new String[] { str };
+            } else {
+                chars = str.split(" ");
+            }
+            setContentDisplay(ContentDisplay.RIGHT);
+            setText(chars[0]);
+            if (chars.length > 1) {
+                graphic = new Label((chars.length > 1) ? chars[1] : " ");
+                graphic.setPrefWidth(keyWidth / 2 - 10);
+                graphic.setMinWidth(USE_PREF_SIZE);
+                graphic.setPrefHeight(keyHeight - 6);
+                setGraphic(graphic);
+            }
+
+            setPrefWidth((str == " ") ? keyWidth * 3 : keyWidth);
+        }
+    }
+
+    private class CommandKey extends Key {
+        KeyCode code;
+
+        EventHandler<ActionEvent> actionHandler = new EventHandler<ActionEvent>() {
+            @Override public void handle(ActionEvent e) {
+                showSecondaryVK(null, null, null);
+                Node target = fxvk.getAttachedNode();
+                if (target instanceof EventTarget) {
+                    String txt = getText();
+                    fireKeyEvent(target, KeyEvent.KEY_PRESSED, code, null, null,
+                              false, false, false, false);
+                    if (state == State.SHIFTED) {
+                        toggleShift();
+                        toggleShift();
+                    }
+                }
+            }
+        };
+
+        CommandKey(String label, KeyCode code, double width) {
+            super(label);
+            this.code = code;
+            getStyleClass().add("special-key");
+            setOnAction(actionHandler);
+            setPrefWidth(width);
+        }
+    }
+
+    private class ShiftKey extends Key {
+        EventHandler<ActionEvent> actionHandler = new EventHandler<ActionEvent>() {
+            @Override public void handle(ActionEvent e) {
+                showSecondaryVK(null, null, null);
+                toggleShift();
+            }
+        };
+
+        ShiftKey(double width) {
+            super("\u21d1");
+            getStyleClass().add("special-key");
+            setFocusTraversable(false);
+            setOnAction(actionHandler);
+            setPrefWidth(width);
+        }
+    }
+
+    private class SymbolKey extends Key {
+        String str;
+        String[] chars;
+
+        EventHandler<ActionEvent> actionHandler = new EventHandler<ActionEvent>() {
+            @Override public void handle(ActionEvent e) {
+                state = (state == State.NUMERIC) ? State.NORMAL : State.NUMERIC;
+                shiftKey.setDisable(state == State.NUMERIC);
+                showSecondaryVK(null, null, null);
+                updateLabels();
+            }
+        };
+
+        SymbolKey(String str, double width) {
+            this.str = str;
+            getStyleClass().add("special-key");
+
+            if (str.length() == 1) {
+                chars = new String[] { str };
+            } else {
+                chars = str.split(" ");
+            }
+            setText(chars[0]);
+
+            setOnAction(actionHandler);
+            setPrefWidth(width);
+        }
+    }
+
+    @Override public void layoutChildren() {
+        double kw, kh;
+        Insets insets = getInsets();
+
+        if (fxvk == secondaryVK) {
+            kw = ((FXVKSkin)primaryVK.getSkin()).keyWidth;
+            kh = ((FXVKSkin)primaryVK.getSkin()).keyHeight;
+        } else {
+            kw = (getWidth() / 10) - 2 * hGap;
+            kh = (getHeight() - insets.getTop() - insets.getBottom() - (keyRows.length - 1) * vGap) / keyRows.length;
+        }
+
+        if (keyWidth != kw || keyHeight != kh) {
+            keyWidth = kw;
+            keyHeight = kh;
+            createKeys();
+        }
+
+        super.layoutChildren();
+    }
+
+    private static void showSecondaryVK(final CharKey key, FXVK primVK, State state) {
+        if (key != null) {
+            primaryVK = primVK;
+            final Node textInput = primaryVK.getAttachedNode();
+
+            if (secondaryPopup == null) {
+                secondaryVK = new FXVK();
+                secondaryVK.getStyleClass().add("fxvk-secondary");
+                secondaryPopup = new Popup();
+                secondaryPopup.getContent().add(secondaryVK);
+            }
+
+            if (state == State.NUMERIC) {
+                ArrayList<String> symbols = new ArrayList<String>();
+                for (String ch : key.chars) {
+                    if (!Character.isLetter(ch.charAt(0))) {
+                        symbols.add(ch);
+                    }
+                }
+                secondaryVK.chars = symbols.toArray(new String[symbols.size()]);
+            } else if (state == State.SHIFTED || state == State.SHIFT_LOCK) {
+                secondaryVK.chars = new String[key.chars.length];
+                System.arraycopy(key.chars, 0, secondaryVK.chars, 0, secondaryVK.chars.length);
+                for (int i = 0; i < secondaryVK.chars.length; i++) {
+                    secondaryVK.chars[i] = key.chars[i].toUpperCase();
+                }
+            } else {
+                secondaryVK.chars = key.chars;
+            }
+
+            if (secondaryVK.getSkin() != null) {
+                ((FXVKSkin)secondaryVK.getSkin()).createKeys();
+            }
+
+            secondaryVK.setAttachedNode(textInput);
+            FXVKSkin primarySkin = (FXVKSkin)primaryVK.getSkin();
+            Insets insets = primarySkin.getInsets();
+            int nKeys = secondaryVK.chars.length;
+            int nRows = (int)Math.floor(Math.sqrt(Math.max(1, nKeys - 2)));
+            int nKeysPerRow = (int)Math.ceil(nKeys / (double)nRows);
+            final double w = insets.getLeft() + insets.getRight() +
+                             nKeysPerRow * primarySkin.keyWidth + (nKeys - 1) * hGap;
+            final double h = nRows * primarySkin.keyHeight + (nRows-1) * vGap + 5;
+            secondaryVK.setPrefWidth(w);
+            secondaryVK.setMinWidth(USE_PREF_SIZE);
+            secondaryVK.setPrefHeight(h);
+            secondaryVK.setMinHeight(USE_PREF_SIZE);
+            Platform.runLater(new Runnable() {
+                public void run() {
+                    // Position popup on screen
+                    Point2D nodePoint =
+                        com.sun.javafx.Utils.pointRelativeTo(key, w, h, HPos.CENTER, VPos.TOP,
+                                                             5, -3, true);
+                    double x = nodePoint.getX();
+                    double y = nodePoint.getY();
+                    Scene scene = key.getScene();
+                    x = Math.min(x, scene.getWindow().getX() + scene.getWidth() - w);
+                    secondaryPopup.show(key.getScene().getWindow(), x, y);
+                }
+            });
+        } else {
+            if (secondaryVK != null) {
+                secondaryVK.setAttachedNode(null);
+                secondaryPopup.hide();
+            }
+        }
+    }
+
+    class NewRootPane extends Pane {
+        double dragStartY;
+
+        NewRootPane(final Region oldRoot) {
+            getChildren().add(oldRoot);
+            prefWidthProperty().bind(oldRoot.prefWidthProperty());
+            prefHeightProperty().bind(oldRoot.prefHeightProperty());
+
+
+            addEventHandler(MOUSE_PRESSED, new EventHandler<MouseEvent>() {
+                @Override public void handle(MouseEvent e) {
+                    dragStartY = e.getY() - oldRoot.getTranslateY();
+                    e.consume();
+                }
+            });
+
+            addEventHandler(MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
+                @Override public void handle(MouseEvent e) {
+                    if (fxvk.isVisible()) {
+                        double y =
<