changeset 787:b66c767bcbcb

Automated merge with ssh://jfxsrc.us.oracle.com//javafx/2.1/MASTER/jfx/rt
author kcr
date Thu, 05 Apr 2012 14:43:38 -0700
parents 5c3b3d524f07 cebb10de0795
children fdc80b4d14e9
files .hgtags javafx-ui-common/src/com/sun/javafx/tk/TextHelper.java
diffstat 102 files changed, 10487 insertions(+), 1249 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Apr 05 12:55:37 2012 -0700
+++ b/.hgignore	Thu Apr 05 14:43:38 2012 -0700
@@ -12,4 +12,5 @@
 ^webrev/
 ^webrev\.zip$
 nbproject/private/
+target/
 ^.*~$
--- a/.hgtags	Thu Apr 05 12:55:37 2012 -0700
+++ b/.hgtags	Thu Apr 05 14:43:38 2012 -0700
@@ -15,7 +15,10 @@
 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
+474801a028b90335ea93c5a5440dfce20274228f 2.2-b03
 4d1d81b2c178891869533885a76410fcc4535979 2.1-b21
--- a/.idea/misc.xml	Thu Apr 05 12:55:37 2012 -0700
+++ b/.idea/misc.xml	Thu Apr 05 14:43:38 2012 -0700
@@ -3,11 +3,13 @@
   <component name="EntryPointsManager">
     <entry_points version="2.0" />
   </component>
+  <component name="IdProvider" IDEtalkID="5A22F63458C80CE167DF63107CDD247D" />
   <component name="ProjectResources">
     <default-html-doctype>http://www.w3.org/1999/xhtml</default-html-doctype>
   </component>
   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build" />
   </component>
+  <component name="WebServicesPlugin" addRequiredLibraries="true" />
 </project>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans-dt/pom.xml	Thu Apr 05 14:43:38 2012 -0700
@@ -0,0 +1,15 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>javafx</artifactId>
+        <groupId>javafx</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>javafx</groupId>
+  <artifactId>javafx-beans-dt</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+  <name>javafx-beans-dt</name>
+  <url>http://www.javafx.com</url>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-concurrent/pom.xml	Thu Apr 05 14:43:38 2012 -0700
@@ -0,0 +1,16 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>javafx</artifactId>
+        <groupId>javafx</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>javafx</groupId>
+    <artifactId>javafx-concurrent</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+    <name>javafx-concurrent</name>
+    <url>http://www.javafx.com</url>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-designtime/pom.xml	Thu Apr 05 14:43:38 2012 -0700
@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>javafx</artifactId>
+        <groupId>javafx</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>javafx</groupId>
+    <artifactId>javafx-designtime</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+    <name>javafx-designtime</name>
+    <url>http://www.javafx.com</url>
+    <dependencies>
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>javafx-beans-dt</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ueber-jar/pom.xml	Thu Apr 05 14:43:38 2012 -0700
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>javafx</groupId>
+    <artifactId>javafx-rt</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>javafx-rt</name>
+    <url>http://maven.apache.org</url>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>javafx-beans-dt</artifactId>
+            <scope>compile</scope>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>    
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>javafx-concurrent</artifactId>
+            <scope>compile</scope>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>    
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>javafx-ui-controls</artifactId>
+            <scope>compile</scope>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>    
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>javafx-designtime</artifactId>
+            <scope>compile</scope>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>    
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>javafx-ui-common</artifactId>
+            <scope>compile</scope>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>    
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>1.5</version>
+                <configuration>
+                    <artifactSet>
+                        <includes>
+                            <include>*</include>
+                        </includes>
+                        <excludes>
+                            <exclude>junit:junit</exclude>
+                        </excludes>
+                    </artifactSet>
+                    <finalName>jfxrt</finalName>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
--- a/javafx-ui-charts/src/javafx/scene/chart/PieChart.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-charts/src/javafx/scene/chart/PieChart.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-charts/src/javafx/scene/chart/XYChart.java	Thu Apr 05 14:43:38 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 ----------------------------------------
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/pom.xml	Thu Apr 05 14:43:38 2012 -0700
@@ -0,0 +1,22 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+      <artifactId>javafx</artifactId>
+      <groupId>javafx</groupId>
+      <version>1.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>javafx</groupId>
+  <artifactId>javafx-ui-common</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+  <name>javafx-ui-common</name>
+  <url>http://www.javafx.com</url>
+    <dependencies>
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>test-stub-toolkit</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
--- a/javafx-ui-common/src/com/sun/javafx/scene/EventHandlerProperties.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/scene/EventHandlerProperties.java	Thu Apr 05 14:43:38 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/PopupWindowPeerListener.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/stage/PopupWindowPeerListener.java	Thu Apr 05 14:43:38 2012 -0700
@@ -39,15 +39,6 @@
         this.popupWindow = popupWindow;
     }
 
-    public void changedSize(float w, float h) {
-        if (w != popupWindow.getWidth()) {
-            popupWindow.setWidth(w);
-        }
-        if (h != popupWindow.getHeight()) {
-            popupWindow.setHeight(h);
-        }
-    }
-
     public void changedFocused(boolean cf, FocusCause cause) {
         // TODO: at the native level popup windows are unfocusable, so we
         // don't get any focus notifications from the platform. Temporary
--- a/javafx-ui-common/src/com/sun/javafx/stage/StagePeerListener.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/stage/StagePeerListener.java	Thu Apr 05 14:43:38 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/DummyToolkit.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/tk/DummyToolkit.java	Thu Apr 05 14:43:38 2012 -0700
@@ -415,11 +415,6 @@
     }
 
     @Override
-    public TextHelper createTextHelper(Text text) {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-
-    @Override
     public boolean imageContains(Object image, float x, float y) {
         throw new UnsupportedOperationException("Not supported yet.");
     }
--- a/javafx-ui-common/src/com/sun/javafx/tk/TKSceneListener.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/tk/TKSceneListener.java	Thu Apr 05 14:43:38 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/com/sun/javafx/tk/TKStage.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/tk/TKStage.java	Thu Apr 05 14:43:38 2012 -0700
@@ -70,8 +70,35 @@
      */
     public void setScene(TKScene scene);
 
+    /**
+     * Sets the window bounds to the specified values.
+     *
+     * Gravity values specify how to correct window location if only its size
+     * changes (for example when stage decorations are added). User initiated
+     * resizing should be ignored and must not influence window location through
+     * this mechanism.
+     *
+     * The corresponding correction formulas are:
+     *
+     * {@code x -= xGravity * deltaW}
+     * {@code y -= yGravity * deltaH}
+     *
+     * @param x the new window horizontal position, ignored if xSet is set to
+     *          false
+     * @param y the new window vertical position, ignored if ySet is set to
+     *          false
+     * @param xSet indicates whether the x parameter is valid
+     * @param ySet indicates whether the y parameter is valid
+     * @param w the new window width, ignored if set to -1
+     * @param h the new window height, ignored if set to -1
+     * @param cw the new window content width, ignored if set to -1
+     * @param ch the new window content height, ignored if set to -1
+     * @param xGravity the xGravity coefficient
+     * @param yGravity the yGravity coefficient
+     */
     public void setBounds(float x, float y, boolean xSet, boolean ySet,
-                          float w, float h, float cw, float ch);
+                          float w, float h, float cw, float ch,
+                          float xGravity, float yGravity);
 
     public void setIcons(java.util.List icons);
 
--- a/javafx-ui-common/src/com/sun/javafx/tk/TextHelper.java	Thu Apr 05 12:55:37 2012 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2011, 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 com.sun.javafx.tk;
-
-import com.sun.javafx.geom.BaseBounds;
-import com.sun.javafx.geom.transform.BaseTransform;
-
-/**
- * Utility class used by Text nodes for measuring their bounds. instances of
- * this class are produced by Toolkit implementations. Generally a different
- * TextBoundsHelper class is created for each Text node, so as to allow the
- * toolkit implementation to cache information on the TextBoundsHelper.
- */
-public abstract class TextHelper {
-    public abstract BaseBounds computeBounds(BaseBounds bounds, BaseTransform tx);
-    public abstract BaseBounds computeLayoutBounds(BaseBounds bounds);
-    public abstract Object getCaretShape(int charIndex, boolean isLeading);
-    public abstract Object getSelectionShape();
-    public abstract Object getRangeShape(int start, int end);
-    public abstract Object getUnderlineShape(int start, int end);
-    public abstract Object getShape();
-    public abstract Object getHitInfo(float localX, float localY);
-    public abstract boolean contains(float localX, float localY);
-}
--- a/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java	Thu Apr 05 14:43:38 2012 -0700
@@ -636,8 +636,6 @@
     public abstract Object createSVGPathObject(SVGPath svgpath);
     public abstract Path2D createSVGPath2D(SVGPath svgpath);
 
-    public abstract TextHelper createTextHelper(Text text);
-
     /**
      * Tests whether the pixel on the given coordinates in the given image
      * is non-empty (not fully transparent). Return value is not defined
--- a/javafx-ui-common/src/javafx/application/ConditionalFeature.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/application/ConditionalFeature.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Node.java	Thu Apr 05 14:43:38 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;
     }
 
@@ -415,31 +419,6 @@
      *************************************************************************/
 
     /**
-     * Called only by Text to update the state and clear dirtybits in the PG graph
-     * TODO: This must be removed as soon as RT-13735 is complete, since
-     * TextHelper should work with a PG node directly or some other means rather
-     * than using this method.
-     *
-     * @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_syncPGNodeDirect() {
-        if (!impl_isDirtyEmpty()) {
-            // Workaround for Don't RT-7446 update the state controlled by
-            // NODE_TRANSFORM.
-            boolean nodeTxDirty = impl_isDirty(DirtyBits.NODE_TRANSFORM);
-            impl_clearDirty(DirtyBits.NODE_TRANSFORM);
-            boolean boundsDirty = impl_isDirty(DirtyBits.NODE_BOUNDS);
-            impl_clearDirty(DirtyBits.NODE_BOUNDS);
-            impl_updatePG();
-            clearDirty();
-            if (nodeTxDirty) impl_setDirty(DirtyBits.NODE_TRANSFORM);
-            if (boundsDirty) impl_setDirty(DirtyBits.NODE_BOUNDS);
-        }
-    }
-
-    /**
      * Called by the synchronizer to update the state and
      * clear dirtybits of this node in the PG graph
      *
@@ -1754,7 +1733,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 +2676,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 +3447,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 +3458,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 +3469,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 +3479,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 +3489,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 +3514,7 @@
                 localToParentTx.translate(getTranslateX() + getLayoutX(), getTranslateY() + getLayoutY(), getTranslateZ());
             }
 
-            if (hasTransforms()) {
+            if (impl_hasTransforms()) {
                 for (Transform t : getTransforms()) {
                     t.impl_apply(localToParentTx);
                 }
@@ -3615,7 +3594,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 +3657,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 +3696,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 +3995,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
+    public boolean impl_hasTransforms() {
         return (nodeTransformation != null)
                 && nodeTransformation.hasTransforms();
     }
@@ -5127,7 +5111,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 +5223,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 +5637,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 +5709,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 +5724,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 +5835,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 +5849,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 +5945,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 +6058,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) {
 
@@ -5891,10 +6228,6 @@
 
     /**
      * Event dispatcher for invoking preprocessing of mouse events
-     *
-     * TODO: The dispatcher is inserted into the event dispatch chain, this
-     * needs to be solved differently, this way it's impossible for user
-     * to override this behavior.
      */
     private EventDispatcher preprocessMouseEventDispatcher;
 
@@ -6305,7 +6638,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 +6648,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 +6672,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 +6734,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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Parent.java	Thu Apr 05 14:43:38 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(
@@ -676,9 +676,6 @@
      */
     @Deprecated
     @Override protected Node impl_pickNodeLocal(PickRay pickRay) {
-//      TODO: FIX RT-5258: Optimize 3D picking
-       // if (intersects(pickRay)) {
-
             for (int i = children.size()-1; i >= 0; i--) {
                 Node picked = children.get(i).impl_pickNode(pickRay);
 
@@ -686,9 +683,6 @@
                     return picked;
                 }
             }
-
-      //}
-
         return null;
     }
 
@@ -1030,7 +1024,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 +1084,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();
@@ -1361,8 +1355,6 @@
         return extended;
     }
 
-
-    // TODO!!!
     // This is called when either the child is actually removed, OR IF IT IS
     // TOGGLED TO BE INVISIBLE. This is because in both cases it needs to be
     // cleared from the state which manages bounds.
@@ -1414,9 +1406,6 @@
             if (dirtyChildren != null) dirtyChildren.clear();
             cachedBoundsInvalid = false;
             cachedBounds.makeEmpty();
-            //TODO: isn't the above already done by childRemoved? If not in some
-            //      cases, shouldn't we do the following?
-            // top = left = bottom = right = near = far = null;
             return;
         }
 
@@ -1429,12 +1418,8 @@
 
             if (node.isVisible()) {
                 cachedBounds = node.getTransformedBounds(cachedBounds, BaseTransform.IDENTITY_TRANSFORM);
-                //TODO: shouldn't we do this?
-                //top = left = bottom = right = near = far = node;
             } else {
                 cachedBounds.makeEmpty();
-                //TODO: shouldn't we do this?
-                //top = left = bottom = right = near = far = null;
             }
             node.boundsChanged = false;
             return;
@@ -1735,7 +1720,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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Scene.java	Thu Apr 05 14:43:38 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
@@ -625,8 +633,6 @@
             // content may have been added after scene was constructed, so
             // try again to set size based on content if the scene or window
             // weren't explicitly sized;
-            // (TODO): ideally we'd do this just once, at the latest point
-            // we could, before the window was assigned a size and made visible
             preferredSize();
         }
         impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill()));
@@ -1133,10 +1139,6 @@
         protected void onChanged(Change<String> c) {
             StyleManager.getInstance().updateStylesheets(Scene.this);
             getRoot().impl_reapplyCSS();
-            // we'll immediately reapply the style for this scene. It might be
-            // better to defer eventually
-            // TODO needs to be wired up
-            // StyleManager.getInstance().getStyleHelper(this);
         }
     };
 
@@ -1186,14 +1188,9 @@
         if (PerformanceTracker.isLoggingEnabled()) {
             PerformanceTracker.logEvent("Scene.init for [" + this + "]");
         }
-        // TODO JASPER sure there is more init needed here
-
         mouseHandler = new MouseHandler();
         clickGenerator = new ClickGenerator();
 
-        // TODO need to reimplement
-        //StyleManager.getInstance().getStyleHelper(this);
-
         initialized = true;
 
         if (PerformanceTracker.isLoggingEnabled()) {
@@ -1267,6 +1264,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 +1329,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.
      */
@@ -1362,7 +1480,6 @@
      * Set to true if something has happened to the focused node that makes
      * it no longer eligible to have the focus.
      *
-     * TODO: need to schedule a pulse if this turns true?
      */
     private boolean focusDirty = true;
 
@@ -1536,7 +1653,6 @@
     /**
      * Gets the scene's current focus owner node.
      *
-     * TODO: probably should be removed in favor of impl_focusOwner below.
      *
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
@@ -1550,10 +1666,6 @@
      * variable might be false if this scene has no window, or if the
      * window is inactive (window.focused == false).
      *
-     * TODO this was added because of RT-3930. This needs to be reconciled
-     * with impl_getFocusOwner(). We don't need both. Exposing a variable
-     * is more powerful because it allows code to bind to it.
-     *
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
@@ -1622,15 +1734,6 @@
      * Returns true if this scene is quiescent, i.e. it has no activity
      * pending on it such as CSS processing or layout requests.
      *
-     * TODO this is for testing purposes only. It's not clear that
-     * the set of things this checks is exactly right. It doesn't check
-     * for events pending in the event queue for instance. However, it
-     * seems to work reasonably well at present for UI testing.
-     *
-     * TODO this should be replaced with a better interface, say a
-     * package-private interface that can be called from test support
-     * code that has been loaded into javafx.scene.
-     *
      * @return boolean indicating whether the scene is quiescent
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
@@ -1646,9 +1749,6 @@
      * A listener for pulses, used for testing. If non-null, this is called at
      * the very end of ScenePulseListener.pulse().
      *
-     * TODO this is public so it can be written to from test code. Ugly,
-     * but effective. This should be replaced with a cleaner interface.
-     *
      * @treatAsPrivate implementation detail
      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      */
@@ -1925,15 +2025,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 +2065,169 @@
             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);
+
+            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 {
@@ -2208,11 +2466,9 @@
          * the Node.onDragSourceRecognized function.
          */
         private boolean processRecognized(Node n, DragEvent de) {
-            //TODO: Should get Mouse Event, for now we have to make up one
-            //      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 +3041,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) {
@@ -2817,8 +3076,6 @@
                 if (getCamera() instanceof PerspectiveCamera) {
                     final PickRay pickRay = new PickRay();
                     Scene.this.impl_peer.computePickRay((float)e.getX(), (float)e.getY(), pickRay);
-    //                System.out.println("** 3D: origin = " + pickRay.getOriginNoClone()
-    //                        + ", direction = " + pickRay.getDirectionNoClone());
                     pickedTarget = pickNode(pickRay);
                 }
                 else {
@@ -2832,14 +3089,6 @@
 
             EventTarget target;
             if (pdrInProgress) {
-                // TODO I believe this is bogus. We should still deliver mouse
-                // enter / exit / move events to nodes that are not part of the
-                // press-drag-release event, but only pdr nodes get the mouse
-                // dragged events, and other nodes DO NOT (?) get pressed events...
-                // The use case here is that I press a button and a popup is visible
-                // and while the button is still depressed I "drag" over the item
-                // in the popup I want and release. So in this case I need to get
-                // some events on the items in the popup, just not the drag events.
                 target = pdrEventTarget;
             } else {
                 target = pickedTarget;
@@ -3066,21 +3315,21 @@
      *                                                                             *
      ******************************************************************************/
 
-    class KeyHandler implements InvalidationListener {
+    class KeyHandler {
         private Node focusOwner = null;
         private Node getFocusOwner() { return focusOwner; }
 
         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 +3337,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 "
@@ -3156,12 +3412,6 @@
                 });
             }
         }
-
-        // TODO: What is the point of extending a listener, if handle() is not overridden?
-        @Override
-        public void invalidated(Observable valueModel) {
-            //nothing to do, implemented because of extending ChangeListener
-        }
     }
     /***************************************************************************
      *                                                                         *
@@ -3762,42 +4012,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 +4162,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                          *
      *                                                                         *
      **************************************************************************/
--- a/javafx-ui-common/src/javafx/scene/input/DragEvent.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/DragEvent.java	Thu Apr 05 14:43:38 2012 -0700
@@ -730,10 +730,6 @@
             double _screenX, double _screenY, TransferMode _transferMode,
             Dragboard _dragboard, TKDropEvent _tkDropEvent) {
         
-        //TODO: this is not nice. Toolkit always creates DragEvent of type ANY
-        //      and scenegraph then copies it and fixes the type.
-        //      Serious scenegraph/toolkit contract rework is needed
-        //      to get rid of this.
         DragEvent de = new DragEvent(DragEvent.ANY);
 
         de.x = _x;
--- a/javafx-ui-common/src/javafx/scene/input/Dragboard.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/Dragboard.java	Thu Apr 05 14:43:38 2012 -0700
@@ -63,18 +63,5 @@
     public static Dragboard impl_create(TKClipboard peer) {
         return new Dragboard(peer);
     }
-
-    // TODO: DragView support
-//    /**
-//     * Visual representation of data being transfered in a drag and drop gesture.
-//     * This will be shown to the side of the mouse cursor as it is moved around
-//     * the screen.
-//     */
-//    public Node dragView;
-//
-//    /**
-//     * Specifies the opacity of the dragView node as the drag occurs. If this
-//     * is not specified, a default opacity of 0.65 will be applied.
-//     */
-//    public float dragViewOpacity = 0.65f;
+    
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-common/src/javafx/scene/input/GestureEvent.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/KeyEvent.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/MouseEvent.java	Thu Apr 05 14:43:38 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 Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/ScrollEvent.java	Thu Apr 05 14:43:38 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 Apr 05 14:43:38 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 Apr 05 14:43:38 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 Apr 05 14:43:38 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 Apr 05 14:43:38 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/package.html	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/package.html	Thu Apr 05 14:43:38 2012 -0700
@@ -87,7 +87,7 @@
         root.getChildren().add(circle);
         root.getChildren().add(text);
         stage.setScene(scene);
-        stage.setVisible(true);
+        stage.show();
     }
 
     public static void main(String[] args) {
--- a/javafx-ui-common/src/javafx/scene/shape/Path.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/shape/Path.java	Thu Apr 05 14:43:38 2012 -0700
@@ -189,10 +189,6 @@
     private final ObservableList<PathElement> elements = new TrackableObservableList<PathElement>() {
         @Override
         protected void onChanged(Change<PathElement> c) {
-            //
-            // TODO: Need to keep a list of Path objects in PathElement if we want
-            // to support sharability. See how transforms is handled in Node.
-            //
             List<PathElement> list = c.getList();
             boolean firstElementChanged = false;
             while (c.next()) {
--- a/javafx-ui-common/src/javafx/scene/shape/Rectangle.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/shape/Rectangle.java	Thu Apr 05 14:43:38 2012 -0700
@@ -504,7 +504,6 @@
         }
         if ((getArcWidth() > 0) && (getArcHeight() > 0)
                 && ((tx.getType() & NON_RECTILINEAR_TYPE_MASK) != 0)) {
-            // TODO: Optimize rotated bounds...
             return computeShapeBounds(bounds, tx, impl_configShape());
         }
         double upad;
--- a/javafx-ui-common/src/javafx/scene/shape/Shape.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/shape/Shape.java	Thu Apr 05 14:43:38 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)) {
@@ -942,10 +945,8 @@
                 x1 += dx;
                 y1 += dy;
             }
-            // TODO - only pad by upad or dpad, depending on transform
             _dpad += upad;
         } else {
-            // TODO - only pad by upad or dpad, depending on transform
             x0 -= upad;
             y0 -= upad;
             x1 += upad*2;
@@ -1103,6 +1104,8 @@
         return false;
     }
 
+    private boolean strokeAttributesDirty = true;
+
     private StrokeAttributes strokeAttributes;
 
     private StrokeAttributes getStrokeAttributes() {
@@ -1442,6 +1445,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/scene/text/Text.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/text/Text.java	Thu Apr 05 14:43:38 2012 -0700
@@ -45,11 +45,13 @@
 import javafx.geometry.Bounds;
 import javafx.geometry.Point2D;
 import javafx.geometry.VPos;
+import javafx.scene.Node;
 import javafx.scene.Scene;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.Paint;
 import javafx.scene.shape.PathElement;
 import javafx.scene.shape.Shape;
+import javafx.scene.transform.Transform;
 
 import com.sun.javafx.css.StyleableBooleanProperty;
 import com.sun.javafx.css.StyleableObjectProperty;
@@ -59,6 +61,7 @@
 import com.sun.javafx.css.converters.FontConverter;
 import com.sun.javafx.geom.BaseBounds;
 import com.sun.javafx.geom.RectBounds;
+import com.sun.javafx.geom.transform.Affine3D;
 import com.sun.javafx.geom.transform.BaseTransform;
 import com.sun.javafx.scene.DirtyBits;
 import com.sun.javafx.scene.shape.PathUtils;
@@ -66,7 +69,8 @@
 import com.sun.javafx.sg.PGNode;
 import com.sun.javafx.sg.PGShape;
 import com.sun.javafx.sg.PGText;
-import com.sun.javafx.tk.TextHelper;
+import com.sun.javafx.sg.PGTextHelper;
+import com.sun.javafx.tk.FontLoader;
 import com.sun.javafx.tk.Toolkit;
 import javafx.beans.DefaultProperty;
 import javafx.beans.property.*;
@@ -108,7 +112,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
@@ -116,17 +121,38 @@
         return Toolkit.getToolkit().createPGText();
     }
 
-    PGText getPGText() {
+    private PGText getPGText() {
         return (PGText) impl_getPGNode();
     }
 
+    private PGTextHelper textHelper;
+    /* 
+     * The Text node state is synced down to *its* helper on return.
+     * This doesn't mean its synced to the peer! That happens only
+     * during the pulse.
+     */
+    private PGTextHelper getTextHelper() {
+        if (textHelper == null) {
+            Scene.impl_setAllowPGAccess(true);
+            textHelper = getPGText().getTextHelper();
+            Scene.impl_setAllowPGAccess(false);
+        }
+        updatePGTextHelper(textHelper);
+        return textHelper;
+    }
+
+    private static FontLoader fontLoader = null;
     /**
      * Creates an empty instance of Text.
      */
     public Text() {
+        if (fontLoader == null) {
+            fontLoader = Toolkit.getToolkit().getFontLoader();
+        }
         setPickOnBounds(true);
         getDecorationShapes();
-        setBaselineOffset(Toolkit.getToolkit().getFontLoader().getFontMetrics(getFontInternal()).getAscent());
+        setBaselineOffset(
+             fontLoader.getFontMetrics(getFontInternal()).getAscent());
     }
 
     /**
@@ -187,8 +213,10 @@
                     setImpl_selectionStart(-1);
                     setImpl_selectionEnd(-1);
                     impl_geomChanged();
-                    // MH: Functionality copied from store() method, which was removed
-                    // Wonder what should happen if text is bound and becomes null?
+                    // MH: Functionality copied from store() method,
+                    // which was removed.
+                    // Wonder what should happen if text is bound
+                    //  and becomes null?
                     final String value = get();
                     if ((value == null) && !isBound()) {
                         set("");
@@ -322,7 +350,8 @@
                 public void invalidated() {
                     impl_markDirty(DirtyBits.TEXT_FONT);
                     impl_geomChanged();
-                    setBaselineOffset(Toolkit.getToolkit().getFontLoader().getFontMetrics(getFontInternal()).getAscent());
+                    setBaselineOffset(fontLoader.getFontMetrics(
+                                          getFontInternal()).getAscent());
                 }
 
                 @Override 
@@ -412,7 +441,8 @@
 
     public final ObjectProperty<TextBoundsType> boundsTypeProperty() {
         if (boundsType == null) {
-            boundsType = new ObjectPropertyBase<TextBoundsType>(TextBoundsType.LOGICAL) {
+            boundsType =
+               new ObjectPropertyBase<TextBoundsType>(TextBoundsType.LOGICAL) {
 
                 @Override
                 public void invalidated() {
@@ -588,7 +618,8 @@
 
     public final ObjectProperty<TextAlignment> textAlignmentProperty() {
         if (textAlignment == null) {
-            textAlignment = new StyleableObjectProperty<TextAlignment>(TextAlignment.LEFT) {
+            textAlignment =
+                new StyleableObjectProperty<TextAlignment>(TextAlignment.LEFT) {
 
                 @Override
                 public void invalidated() {
@@ -616,7 +647,8 @@
     }
 
     /**
-     * The 'alphabetic' (or roman) baseline offset from the Text node's layoutBounds.minY location.
+     * The 'alphabetic' (or roman) baseline offset from the Text node's
+     * layoutBounds.minY location.
      * The value typically corresponds to the max ascent of the font.
      *
      * @since JavaFX 1.3
@@ -717,7 +749,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
@@ -729,7 +762,8 @@
     /**
      * Shape of selection in local coordinates.
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     private ObjectProperty<PathElement[]> impl_selectionShape;
@@ -737,7 +771,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_selectionShape(PathElement[] value) {
@@ -746,7 +781,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final PathElement[] getImpl_selectionShape() {
@@ -755,7 +791,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final ObjectProperty<PathElement[]> impl_selectionShapeProperty() {
@@ -772,14 +809,16 @@
      * set to {@code -1} to unset selection.
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     private IntegerProperty impl_selectionStart;
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_selectionStart(int value) {
@@ -788,7 +827,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final int getImpl_selectionStart() {
@@ -797,7 +837,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final IntegerProperty impl_selectionStartProperty() {
@@ -829,14 +870,16 @@
      * set to {@code -1} to unset selection.
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     private IntegerProperty impl_selectionEnd;
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_selectionEnd(int value) {
@@ -845,7 +888,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final int getImpl_selectionEnd() {
@@ -854,7 +898,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final IntegerProperty impl_selectionEndProperty() {
@@ -884,7 +929,8 @@
     /**
      * stroke paint to be used for selected content.
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override protected final void impl_strokeOrFillChanged() {
@@ -894,7 +940,8 @@
     /**
      * Shape of caret in local coordinates.
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     /*public-read*/
@@ -902,7 +949,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_caretShape(PathElement[] value) {
@@ -911,7 +959,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final PathElement[] getImpl_caretShape() {
@@ -920,7 +969,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final ObjectProperty<PathElement[]> impl_caretShapeProperty() {
@@ -937,14 +987,16 @@
      * set to {@code -1} to unset caret.
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     private IntegerProperty impl_caretPosition;
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_caretPosition(int value) {
@@ -953,7 +1005,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final int getImpl_caretPosition() {
@@ -962,7 +1015,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final IntegerProperty impl_caretPositionProperty() {
@@ -992,14 +1046,16 @@
      * caret bias in the content. true means a bias towards forward charcter
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     private BooleanProperty impl_caretBias;
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final void setImpl_caretBias(boolean value) {
@@ -1008,7 +1064,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final boolean isImpl_caretBias() {
@@ -1017,7 +1074,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final BooleanProperty impl_caretBiasProperty() {
@@ -1047,18 +1105,22 @@
      * Maps local point to index in the content.
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final HitInfo impl_hitTestChar(Point2D point) {
-        return Toolkit.getToolkit().convertHitInfoToFX(getTextHelper().getHitInfo((float)point.getX(), (float)point.getY()));
+        return Toolkit.getToolkit().convertHitInfoToFX(
+            getTextHelper().getHitInfo((float)point.getX(),
+                                      (float)point.getY()));
     }
 
     /**
      * Returns shape for the range of the text in local coordinates.
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final PathElement[] impl_getRangeShape(int start, int end) {
@@ -1070,7 +1132,8 @@
      * Returns shape for the underline in local coordinates.
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final PathElement[] impl_getUnderlineShape(int start, int end) {
@@ -1085,19 +1148,18 @@
         if (getImpl_caretPosition() >= 0) {
             //convert insertion postiion into character index
             int charIndex = getImpl_caretPosition() - ((isImpl_caretBias()) ? 0 : 1);
-            Scene.impl_setAllowPGAccess(true);
-            Object nativeShape = getTextHelper().getCaretShape(charIndex, isImpl_caretBias());
-            Scene.impl_setAllowPGAccess(false);
-            setImpl_caretShape(Toolkit.getToolkit().convertShapeToFXPath(nativeShape));
+            Object nativeShape =
+                getTextHelper().getCaretShape(charIndex, isImpl_caretBias());
+            setImpl_caretShape(
+                Toolkit.getToolkit().convertShapeToFXPath(nativeShape));
         } else {
             setImpl_caretShape(null);
         }
 
         if (getImpl_selectionStart() >= 0 && getImpl_selectionEnd() >= 0) {
-            Scene.impl_setAllowPGAccess(true);
             Object nativeShape = getTextHelper().getSelectionShape();
-            Scene.impl_setAllowPGAccess(false);
-            setImpl_selectionShape(Toolkit.getToolkit().convertShapeToFXPath(nativeShape));
+            setImpl_selectionShape(
+                Toolkit.getToolkit().convertShapeToFXPath(nativeShape));
         } else {
             setImpl_selectionShape(null);
         }
@@ -1107,20 +1169,13 @@
      * Shows/Hides on-screen keyboard if available (mobile platform)
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final void impl_displaySoftwareKeyboard(boolean display) {
     }
 
-    private TextHelper textHelper;
-    private TextHelper getTextHelper() {
-        if (textHelper == null) {
-            textHelper = Toolkit.getToolkit().createTextHelper(this);
-        }
-        return textHelper;
-    }
-
     /**
      * The cached layout bounds.
      * This is never null, but is frequently set to be
@@ -1131,7 +1186,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
@@ -1143,7 +1199,8 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final BaseBounds impl_computeLayoutBoundsInt(RectBounds bounds) {
@@ -1163,7 +1220,8 @@
      * reporting mode for this node, this may be logical or visual bounds.
      *
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
@@ -1183,11 +1241,13 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
-    public final BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
+    public final BaseBounds impl_computeGeomBounds(BaseBounds bounds,
+                                                   BaseTransform tx) {
         // fast path for case where neither fill nor stroke is set or where
         // there simply isn't any text. Applies only to VISUAL bounds.
         if ((impl_mode == PGShape.Mode.EMPTY || getTextInternal().equals("") &&
@@ -1195,26 +1255,27 @@
         {
             return bounds.makeEmpty();
         }
-        return getTextHelper().computeBounds(bounds, tx);
+        return getTextHelper().computeContentBounds(bounds, tx);
     }
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
     protected final boolean impl_computeContains(double localX, double localY) {
         // Need to call the TextHelper to do glyph (geometry) based picking.
-
-        // Perform the expensive glyph (geometry) based picking
-        // See the computeContains function in SGText.java for detail.
-        return getTextHelper().contains((float)localX, (float)localY);
+        // Performs expensive glyph (geometry) based picking
+        // This is currently unimplemented in the peer (just returns true).
+        return getTextHelper().computeContains((float)localX, (float)localY);
     }
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
@@ -1230,21 +1291,24 @@
 
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     public final ObjectProperty<Paint> impl_selectionFillProperty() {
         if (selectionFill == null) {
-            selectionFill = new SimpleObjectProperty<Paint>(this, "selectionFill", Color.WHITE);
+            selectionFill = new SimpleObjectProperty<Paint>(this,
+                                                            "selectionFill",
+                                                            Color.WHITE);
         }
         return selectionFill;
     }
 
-    /***************************************************************************
-     *                                                                         *
-     *                         Stylesheet Handling                             *
-     *                                                                         *
-     **************************************************************************/
+   /***************************************************************************
+    *                                                                         *
+    *                            Stylesheet Handling                          *
+    *                                                                         *
+    **************************************************************************/
 
      /**
       * Super-lazy instantiation pattern from Bill Pugh.
@@ -1288,7 +1352,8 @@
 
             @Override
             public boolean isSettable(Text node) {
-                return node.strikethrough == null || !node.strikethrough.isBound();
+                return node.strikethrough == null ||
+                      !node.strikethrough.isBound();
             }
                      
             @Override
@@ -1298,14 +1363,16 @@
 
          };
          
-         private static final StyleableProperty<Text,TextAlignment> TEXT_ALIGNMENT = 
+         private static final
+             StyleableProperty<Text,TextAlignment> TEXT_ALIGNMENT =
                  new StyleableProperty<Text,TextAlignment>("-fx-text-alignment",
                  new EnumConverter<TextAlignment>(TextAlignment.class),
                  TextAlignment.LEFT) {
 
             @Override
             public boolean isSettable(Text node) {
-                return node.textAlignment == null || !node.textAlignment.isBound();
+                return node.textAlignment == null ||
+                      !node.textAlignment.isBound();
             }
 
             @Override
@@ -1354,7 +1421,7 @@
          private static final List<StyleableProperty> STYLEABLES;
          static {
             final List<StyleableProperty> styleables =
-                    new ArrayList<StyleableProperty>(Shape.impl_CSS_STYLEABLES());
+                new ArrayList<StyleableProperty>(Shape.impl_CSS_STYLEABLES());
             Collections.addAll(styleables,
                 FONT,
                 UNDERLINE,
@@ -1369,11 +1436,13 @@
     }
 
     /**
-     * Super-lazy instantiation pattern from Bill Pugh. StyleableProperties is referenced
-     * no earlier (and therefore loaded no earlier by the class loader) than
+     * Super-lazy instantiation pattern from Bill Pugh.
+     * StyleableProperties is referenced  no earlier
+     * (and therefore loaded no earlier by the class loader) than
      * the moment that  impl_CSS_STYLEABLES() is called.
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @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() {
@@ -1383,7 +1452,8 @@
     /**
      * RT-19263
      * @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 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() {
@@ -1391,48 +1461,179 @@
     }
 
     private void updatePGText() {
+        getTextHelper(); // implicitly syncs Text to Helper.
+        getPGText().updateText();
+    }
+
+    /*
+     * This skips the pivot x/y as that requires knowing the bounds
+     * but we need to sync the transform down to the helper when
+     * getting bounds. And we only need the portion of the transform
+     * that affects text size anyway.
+     */
+    private Affine3D getConcatenatedNodeTransform(Node node, Affine3D dst) {
+        if (node.getScaleX() != 1 || node.getScaleY() != 1 ||
+            node.getRotate() != 0 || node.impl_hasTransforms())
+        {
+            if (node.getRotate() != 0) {
+                dst.rotate(Math.toRadians(node.getRotate()),
+                           node.getRotationAxis().getX(),
+                           node.getRotationAxis().getY(),
+                           node.getRotationAxis().getZ());
+            }
+            if (node.getScaleX() != 1 || node.getScaleY() != 1) {
+                dst.scale(node.getScaleX(), node.getScaleY());
+            }
+            if (node.impl_hasTransforms()) {
+                for (Transform t : node.getTransforms()) {
+                    t.impl_apply(dst);
+                }
+            }
+        }
+        return dst;
+    }
+
+    private BaseTransform getCumulativeTransform() {
+        Affine3D dst = new Affine3D();
+        Node node = this;
+        do {
+            dst = getConcatenatedNodeTransform(node, dst);
+            node = node.getParent();
+        } while (node != null);
+        return dst;
+    }
+
+    private PGShape.Mode getMode() {
+        if (getFill() != null && getStroke() != null) {
+            return PGShape.Mode.STROKE_FILL;
+        } else if (getFill() != null) {
+            return PGShape.Mode.FILL;
+        } else if (getStroke() != null) {
+            return PGShape.Mode.STROKE;
+        } else {
+            return PGShape.Mode.EMPTY;
+        }
+    }
+
+    /* This method can be called outside of the pulse */
+    private void updatePGTextHelper(PGTextHelper helper) {
+
         if (impl_isDirty(DirtyBits.NODE_GEOMETRY)) {
-            getPGText().setLocation((float)getX(), (float)getY());
+            helper.setLocation((float)getX(), (float)getY());
+            impl_clearDirty(DirtyBits.NODE_GEOMETRY);
         }
         if (impl_isDirty(DirtyBits.TEXT_ATTRS)) {
-            PGText peer = getPGText();
-            peer.setTextBoundsType(getBoundsType().ordinal());
-            peer.setTextOrigin(getTextOrigin().ordinal());
-            peer.setWrappingWidth((float)getWrappingWidth());
-            peer.setUnderline(isUnderline());
-            peer.setStrikethrough(isStrikethrough());
-            peer.setTextAlignment(getTextAlignment().ordinal());
-            peer.setFontSmoothingType(getFontSmoothingType().ordinal());
+            helper.setTextBoundsType(getBoundsType().ordinal());
+            helper.setTextOrigin(getTextOrigin().ordinal());
+            helper.setWrappingWidth((float)getWrappingWidth());
+            helper.setUnderline(isUnderline());
+            helper.setStrikethrough(isStrikethrough());
+            helper.setTextAlignment(getTextAlignment().ordinal());
+            helper.setFontSmoothingType(getFontSmoothingType().ordinal());
+            impl_clearDirty(DirtyBits.TEXT_ATTRS);
         }
         if (impl_isDirty(DirtyBits.TEXT_FONT)) {
-            getPGText().setFont(getFontInternal().impl_getNativeFont());
+            helper.setFont(getFontInternal().impl_getNativeFont());
+            impl_clearDirty(DirtyBits.TEXT_FONT);
         }
         if (impl_isDirty(DirtyBits.NODE_CONTENTS)) {
-            getPGText().setText(getTextInternal());
+            helper.setText(getTextInternal());
+            impl_clearDirty(DirtyBits.NODE_CONTENTS);
         }
         if (impl_isDirty(DirtyBits.TEXT_SELECTION)) {
             if (getImpl_selectionStart() >= 0 && getImpl_selectionEnd() >= 0) {
-                getPGText().setLogicalSelection(getImpl_selectionStart(),
-                                                getImpl_selectionEnd());
+                helper.setLogicalSelection(getImpl_selectionStart(),
+                                           getImpl_selectionEnd());
                 // getStroke and getFill can be null
-                Paint strokePaint   = getStroke();
-                Paint fillPaint     = selectionFill == null ? null : selectionFill.get();
+                Paint strokePaint = getStroke();
+                Paint fillPaint =
+                    selectionFill == null ? null : selectionFill.get();
                 Object strokeObj = (strokePaint == null) ? null :
-                                    strokePaint.impl_getPlatformPaint();
+                    strokePaint.impl_getPlatformPaint();
                 Object fillObj = (fillPaint == null) ? null :
-                                    fillPaint.impl_getPlatformPaint();
+                    fillPaint.impl_getPlatformPaint();
 
-                getPGText().setSelectionPaint(strokeObj, fillObj);
+                helper.setSelectionPaint(strokeObj, fillObj);
             } else {
                 // Deselect any PGText, in order to update selected text color
-                getPGText().setLogicalSelection(0, 0);
+                helper.setLogicalSelection(0, 0);
+            }
+            impl_clearDirty(DirtyBits.TEXT_SELECTION);
+        }
+        /* Rendering state like transform, Mode, and stroke also matter
+         * for bounds calculations. Need to pass down this information too.
+         */
+        helper.setCumulativeTransform(getCumulativeTransform());
+        helper.setMode(getMode());
+        if (impl_isDirty(DirtyBits.SHAPE_STROKE) ||
+            impl_isDirty(DirtyBits.SHAPE_STROKEATTRS))
+        {
+            boolean hasStroke = getStroke() != null;
+            helper.setStroke(hasStroke);
+            /* We don't want to do this work unless there's currently
+             * a stroke set. And we also don't want to repeat it unless
+             * something changed. We know something has changed if we
+             * are here because of the dirty bits, so we then have to
+             * check if there's a stroke been set. The case this does
+             * extra work is if the stroke's Paint changes, but nothing else.
+             */
+            if (hasStroke) {
+                List<Double> daList = getStrokeDashArray();
+                int len = daList.size();
+                float[] strokeDashArray = new float[len];
+                for (int i=0; i<len; i++) {
+                    strokeDashArray[i] = daList.get(i).floatValue();
+                }
+                helper.setStrokeParameters(
+                       getPGStrokeType(),
+                       getPGStrokeDashArray(),
+                       (float)getStrokeDashOffset(),
+                       getPGStrokeLineCap(),
+                       getPGStrokeLineJoin(),
+                       Math.max((float)getStrokeMiterLimit(), 1f),
+                       Math.max((float)getStrokeWidth(), 0f));
             }
         }
     }
 
+    private com.sun.javafx.sg.PGShape.StrokeType getPGStrokeType() {
+        switch (getStrokeType()) {
+            case INSIDE: return PGShape.StrokeType.INSIDE;
+            case OUTSIDE: return PGShape.StrokeType.OUTSIDE;
+            default: return PGShape.StrokeType.CENTERED;
+        }
+    }
+
+    private com.sun.javafx.sg.PGShape.StrokeLineCap getPGStrokeLineCap() {
+        switch (getStrokeLineCap()) {
+            case SQUARE: return PGShape.StrokeLineCap.SQUARE;
+            case BUTT:   return PGShape.StrokeLineCap.BUTT;
+            default: return PGShape.StrokeLineCap.ROUND;
+        }
+    }
+
+    private com.sun.javafx.sg.PGShape.StrokeLineJoin getPGStrokeLineJoin() {
+         switch (getStrokeLineJoin()) {
+             case MITER: return PGShape.StrokeLineJoin.MITER;
+             case BEVEL: return PGShape.StrokeLineJoin.BEVEL;
+             default: return PGShape.StrokeLineJoin.ROUND;
+         }
+    }
+
+    private float[] getPGStrokeDashArray() {
+        List<Double> daList = getStrokeDashArray();
+        int len = daList.size();
+        float[] strokeDashArray = new float[len];
+        for (int i=0; i<len; i++) {
+            strokeDashArray[i] = daList.get(i).floatValue();
+        }
+        return strokeDashArray;
+    }
+
     /**
      * @treatAsPrivate implementation detail
-     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
+     * @deprecated This is an internal API that is not intended
+     * for use and will be removed in the next version
      */
     @Deprecated
     @Override
--- a/javafx-ui-common/src/javafx/stage/PopupWindow.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/stage/PopupWindow.java	Thu Apr 05 14:43:38 2012 -0700
@@ -217,9 +217,6 @@
     /**
      * Specifies whether Popups should auto hide. If a popup loses focus and
      * autoHide is true, then the popup will be hidden automatically.
-     * TODO How does this function if you want to animate the popup becoming invisible??
-     * For example, if you wanted to scroll up the popup (such as if the popup
-     * is used for a drop down list).
      * @defaultValue false
      */
     private BooleanProperty autoHide =
--- a/javafx-ui-common/src/javafx/stage/Stage.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/stage/Stage.java	Thu Apr 05 14:43:38 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}.
@@ -207,8 +227,6 @@
         super.show();
     }
     
-    // TODO do I also want to expose the model as being a writable model?
-    
     private boolean primary = false;
 
     /**
@@ -222,7 +240,6 @@
         this.primary = primary;
     }
 
-    // TODO: consider making this public
     /**
      * Returns whether this stage is the primary stage.
      * When run as an applet, the primary stage will appear in the broswer
@@ -480,6 +497,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 +511,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 +598,8 @@
 
     public final void setIconified(boolean value) {
         iconifiedPropertyImpl().set(value);
+        if (impl_peer != null)
+            impl_peer.setIconified(value);
     }
 
     public final boolean isIconified() {
@@ -609,25 +612,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 +629,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 +641,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 +840,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());
@@ -937,7 +908,6 @@
         }
     }
 
-    // TODO: remove
     /**
      * Closes this {@code Stage}.
      * This call is equivalent to {@code hide()}.
--- a/javafx-ui-common/src/javafx/stage/Window.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/stage/Window.java	Thu Apr 05 14:43:38 2012 -0700
@@ -205,9 +205,13 @@
             }
 
             peerBoundsConfigurator.setSize(w, h, cw, ch);
+            applyBounds();
         }
     }
 
+    private static final float CENTER_ON_SCREEN_X_FRACTION = 1.0f / 2;
+    private static final float CENTER_ON_SCREEN_Y_FRACTION = 1.0f / 3;
+    
     /**
      * Sets x and y properties on this Window so that it is centered on the screen.
      */
@@ -216,10 +220,19 @@
         yExplicit = false;
         if (impl_peer != null) {
             Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
-            double centerX = bounds.getMinX() + (bounds.getWidth() - getWidth()) / 2.0f;
-            double centerY = bounds.getMinY() + (bounds.getHeight() - getHeight()) / 3.0f;
+            double centerX = 
+                    bounds.getMinX() + (bounds.getWidth() - getWidth())
+                                           * CENTER_ON_SCREEN_X_FRACTION;
+            double centerY =
+                    bounds.getMinY() + (bounds.getHeight() - getHeight())
+                                           * CENTER_ON_SCREEN_Y_FRACTION;
 
-            peerBoundsConfigurator.setLocation(centerX, centerY);
+            x.set(centerX);
+            y.set(centerY);
+            peerBoundsConfigurator.setLocation(centerX, centerY,
+                                               CENTER_ON_SCREEN_X_FRACTION,
+                                               CENTER_ON_SCREEN_Y_FRACTION);
+            applyBounds();
         }
     }
 
@@ -236,7 +249,7 @@
 
     public final void setX(double value) {
         x.set(value);
-        peerBoundsConfigurator.setX(value);
+        peerBoundsConfigurator.setX(value, 0);
         xExplicit = true;
     }
     public final double getX() { return x.get(); }
@@ -255,7 +268,7 @@
 
     public final void setY(double value) {
         y.set(value);
-        peerBoundsConfigurator.setY(value);
+        peerBoundsConfigurator.setY(value, 0);
         yExplicit = true;
     }
     public final double getY() { return y.get(); }
@@ -711,17 +724,14 @@
                     }
                     
                     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());
+                        peerBoundsConfigurator.setLocation(getX(), getY(),
+                                                           0, 0);
                     }
 
-                    // 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 +1016,10 @@
         }
     }
 
+    final void applyBounds() {
+        peerBoundsConfigurator.apply();
+    }
+    
     /**
      * Caches all requested bounds settings and applies them at once during
      * the next pulse.
@@ -1013,6 +1027,8 @@
     private final class TKBoundsConfigurator implements TKPulseListener {
         private double x;
         private double y;
+        private float xGravity;
+        private float yGravity;
         private double windowWidth;
         private double windowHeight;
         private double clientWidth;
@@ -1024,13 +1040,15 @@
             reset();
         }
 
-        public void setX(final double x) {
+        public void setX(final double x, final float xGravity) {
             this.x = x;
+            this.xGravity = xGravity;
             setDirty();
         }
 
-        public void setY(final double y) {
+        public void setY(final double y, final float yGravity) {
             this.y = y;
+            this.yGravity = yGravity;
             setDirty();
         }
 
@@ -1054,9 +1072,14 @@
             setDirty();
         }
 
-        public void setLocation(final double x, final double y) {
+        public void setLocation(final double x,
+                                final double y,
+                                final float xGravity,
+                                final float yGravity) {
             this.x = x;
             this.y = y;
+            this.xGravity = xGravity;
+            this.yGravity = yGravity;
             setDirty();
         }
 
@@ -1080,7 +1103,8 @@
                                     (float) windowWidth,
                                     (float) windowHeight,
                                     (float) clientWidth,
-                                    (float) clientHeight);
+                                    (float) clientHeight,
+                                    xGravity, yGravity);
 
                 reset();
             }
@@ -1094,6 +1118,8 @@
         private void reset() {
             x = Double.NaN;
             y = Double.NaN;
+            xGravity = 0;
+            yGravity = 0;
             windowWidth = -1;
             windowHeight = -1;
             clientWidth = -1;
--- a/javafx-ui-common/test/unit/com/sun/javafx/pgstub/StubToolkit.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/com/sun/javafx/pgstub/StubToolkit.java	Thu Apr 05 14:43:38 2012 -0700
@@ -102,7 +102,6 @@
 import com.sun.javafx.tk.TKScreenConfigurationListener;
 import com.sun.javafx.tk.TKStage;
 import com.sun.javafx.tk.TKSystemMenu;
-import com.sun.javafx.tk.TextHelper;
 import com.sun.javafx.tk.Toolkit;
 import com.sun.prism.BasicStroke;
 import com.sun.scenario.DelayedRunnable;
@@ -419,11 +418,6 @@
         return new StubText();
     }
 
-    @Override
-    public TextHelper createTextHelper(Text text) {
-        return new StubTextHelper(text);
-    }
-
     /*
      * additional testing functions
      */
--- a/javafx-ui-common/test/unit/com/sun/javafx/test/MouseEventGenerator.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/com/sun/javafx/test/MouseEventGenerator.java	Thu Apr 05 14:43:38 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/Parent_structure_sync_Test.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/Parent_structure_sync_Test.java	Thu Apr 05 14:43:38 2012 -0700
@@ -24,6 +24,7 @@
  */
 package javafx.scene;
 
+import com.sun.javafx.pgstub.StubToolkit;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -33,6 +34,8 @@
 import org.junit.Test;
 
 import com.sun.javafx.sg.PGGroup;
+import com.sun.javafx.tk.Toolkit;
+import javafx.stage.Stage;
 
 /**
  * Tests to make sure the synchronization of children between a Parent and PGGroup
@@ -51,11 +54,17 @@
         r4 = new Rectangle(0, 0, 10, 10);
         r5 = new Rectangle(0, 0, 10, 10);
         pg = (PGGroup) parent.impl_getPGNode();
+
+        Scene scene = new Scene(parent);
+        Stage stage = new Stage();
+        stage.setScene(scene);
+        stage.show();
+
         sync();
     }
     
     private void sync() {
-        parent.impl_syncPGNodeDirect();
+        ((StubToolkit) Toolkit.getToolkit()).firePulse();
     }
     
     @Test public void emptyParentShouldHaveEmptyPGGroup() {
--- a/javafx-ui-common/test/unit/javafx/scene/SceneTest.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/SceneTest.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/Scenegraph_eventHandlers_Test.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/DragAndDropTest.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/MouseDragEventTest.java	Thu Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/MouseEventTest.java	Thu Apr 05 14:43:38 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 Apr 05 14:43:38 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	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/input/ScrollEventTest.java	Thu Apr 05 14:43:38 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 Apr 05 14:43:38 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 Apr 05 14:43:38 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/scene/text/TextTest.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/text/TextTest.java	Thu Apr 05 14:43:38 2012 -0700
@@ -82,137 +82,137 @@
     }
 */
 
-    @Test public void testPropertyPropagation_textOrigin() throws Exception {
-        final Text node = new Text();
-        NodeTest.testObjectPropertyPropagation(node, "textOrigin", "textOrigin",
-                VPos.BASELINE, VPos.TOP, new NodeTest.ObjectValueConvertor() {
-                    @Override
-                    public Object toSg(Object pgValue) {
-                        return VPos.values()[((Number)pgValue).intValue()];
-                    }
-                });
-    }
+//     @Test public void testPropertyPropagation_textOrigin() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testObjectPropertyPropagation(node, "textOrigin", "textOrigin",
+//                 VPos.BASELINE, VPos.TOP, new NodeTest.ObjectValueConvertor() {
+//                     @Override
+//                     public Object toSg(Object pgValue) {
+//                         return VPos.values()[((Number)pgValue).intValue()];
+//                     }
+//                 });
+//     }
     
-    @Test public void testPropertyPropagation_boundsType() throws Exception {
-        final Text node = new Text();
-        NodeTest.testObjectPropertyPropagation(node, "boundsType", "textBoundsType",
-                TextBoundsType.LOGICAL, TextBoundsType.VISUAL, new NodeTest.ObjectValueConvertor() {
-                    @Override
-                    public Object toSg(Object pgValue) {
-                        return TextBoundsType.values()[((Number)pgValue).intValue()];
-                    }
-                });
-    }
+//     @Test public void testPropertyPropagation_boundsType() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testObjectPropertyPropagation(node, "boundsType", "textBoundsType",
+//                 TextBoundsType.LOGICAL, TextBoundsType.VISUAL, new NodeTest.ObjectValueConvertor() {
+//                     @Override
+//                     public Object toSg(Object pgValue) {
+//                         return TextBoundsType.values()[((Number)pgValue).intValue()];
+//                     }
+//                 });
+//     }
     
-    @Test public void testPropertyPropagation_textAlignment() throws Exception {
-        final Text node = new Text();
-        NodeTest.testObjectPropertyPropagation(node, "textAlignment", "textAlignment", 
-                TextAlignment.LEFT, TextAlignment.CENTER, new NodeTest.ObjectValueConvertor() {
-                    @Override
-                    public Object toSg(Object pgValue) {
-                        return TextAlignment.values()[(((Number)pgValue).intValue())];
-                    }
-                });
-    }
+//     @Test public void testPropertyPropagation_textAlignment() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testObjectPropertyPropagation(node, "textAlignment", "textAlignment", 
+//                 TextAlignment.LEFT, TextAlignment.CENTER, new NodeTest.ObjectValueConvertor() {
+//                     @Override
+//                     public Object toSg(Object pgValue) {
+//                         return TextAlignment.values()[(((Number)pgValue).intValue())];
+//                     }
+//                 });
+//     }
     
-    @Test public void testPropertyPropagation_visible() throws Exception {
-        final Text node = new Text();
-        NodeTest.testBooleanPropertyPropagation(node, "visible", false, true);
-    }
+//     @Test public void testPropertyPropagation_visible() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testBooleanPropertyPropagation(node, "visible", false, true);
+//     }
 
-    @Test public void testPropertyPropagation_text() throws Exception {
-        final Text node = new Text();
-        NodeTest.testObjectPropertyPropagation(node, "text", "text", "Hello", "World");
-    }
+//     @Test public void testPropertyPropagation_text() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testObjectPropertyPropagation(node, "text", "text", "Hello", "World");
+//     }
 
-    @Test public void testPropertyPropagation_strikethrough() throws Exception {
-        final Text node = new Text();
-        NodeTest.testBooleanPropertyPropagation(node, "strikethrough", false, true);
-    }
+//     @Test public void testPropertyPropagation_strikethrough() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testBooleanPropertyPropagation(node, "strikethrough", false, true);
+//     }
 
-    @Test public void testPropertyPropagation_underline() throws Exception {
-        final Text node = new Text();
-        NodeTest.testBooleanPropertyPropagation(node, "underline", false, true);
-    }
+//     @Test public void testPropertyPropagation_underline() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testBooleanPropertyPropagation(node, "underline", false, true);
+//     }
 
-    @Test public void testPropertyPropagation_x() throws Exception {
-        final Text node = new Text();
-        NodeTest.testDoublePropertyPropagation(node, "x", 100, 200);
-    }
+//     @Test public void testPropertyPropagation_x() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testDoublePropertyPropagation(node, "x", 100, 200);
+//     }
 
-    @Test public void testPropertyPropagation_y() throws Exception {
-        final Text node = new Text();
-        NodeTest.testDoublePropertyPropagation(node, "y", 100, 200);
-    }
+//     @Test public void testPropertyPropagation_y() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testDoublePropertyPropagation(node, "y", 100, 200);
+//     }
 
-    @Test public void testPropertyPropagation_wrappingWidth() throws Exception {
-        final Text node = new Text();
-        NodeTest.testDoublePropertyPropagation(node, "wrappingWidth", 100, 200);
-    }
+//     @Test public void testPropertyPropagation_wrappingWidth() throws Exception {
+//         final Text node = new Text();
+//         NodeTest.testDoublePropertyPropagation(node, "wrappingWidth", 100, 200);
+//     }
 
-    @Test public void testBoundPropertySync_X() throws Exception {
-        NodeTest.assertDoublePropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "x", "x", 10.0);
-    }
+//     @Test public void testBoundPropertySync_X() throws Exception {
+//         NodeTest.assertDoublePropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "x", "x", 10.0);
+//     }
 
-    @Test public void testBoundPropertySync_Y() throws Exception {
-        NodeTest.assertDoublePropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "y", "y", 20.0);
-    }
+//     @Test public void testBoundPropertySync_Y() throws Exception {
+//         NodeTest.assertDoublePropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "y", "y", 20.0);
+//     }
 
-    @Test public void testBoundPropertySync_Text() throws Exception {
-        NodeTest.assertStringPropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "text", "text", "The Changed Text");
-    }
+//     @Test public void testBoundPropertySync_Text() throws Exception {
+//         NodeTest.assertStringPropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "text", "text", "The Changed Text");
+//     }
 
-    // The StubFontLoader is not adequate. SansSerif is the default font
-    // family. But StubFontLoader is hard coded with some knowledge of
-    // Amble so we end up with a null reference for its the PGFont
-    // and it sets null on the PGText node. StubFontLoader needs to be
-    // replaced with the real font loader.
-/*
-    @Test public void testBoundPropertySync_Font() throws Exception {
-        List<String> fontNames = Font.getFontNames();
-        String fontName = fontNames.get(fontNames.size() - 1);
-        NodeTest.assertObjectPropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "font", "font", new Font(fontName, 22));
-    }
-*/
+//     // The StubFontLoader is not adequate. SansSerif is the default font
+//     // family. But StubFontLoader is hard coded with some knowledge of
+//     // Amble so we end up with a null reference for its the PGFont
+//     // and it sets null on the PGText node. StubFontLoader needs to be
+//     // replaced with the real font loader.
+// /*
+//     @Test public void testBoundPropertySync_Font() throws Exception {
+//         List<String> fontNames = Font.getFontNames();
+//         String fontName = fontNames.get(fontNames.size() - 1);
+//         NodeTest.assertObjectPropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "font", "font", new Font(fontName, 22));
+//     }
+// */
 
-    @Test public void testBoundPropertySync_BoundsType() throws Exception {
-        NodeTest.assertObjectPropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "boundsType", "textBoundsType", TextBoundsType.VISUAL);
-    }
+//     @Test public void testBoundPropertySync_BoundsType() throws Exception {
+//         NodeTest.assertObjectPropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "boundsType", "textBoundsType", TextBoundsType.VISUAL);
+//     }
 
     
-    @Test public void testBoundPropertySync_WrappingWidth() throws Exception {
-        NodeTest.assertDoublePropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "wrappingWidth", "wrappingWidth", 50);
-    }
+//     @Test public void testBoundPropertySync_WrappingWidth() throws Exception {
+//         NodeTest.assertDoublePropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "wrappingWidth", "wrappingWidth", 50);
+//     }
     
 
-    @Test public void testBoundPropertySync_Underline() throws Exception {
-        NodeTest.assertBooleanPropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "underline", "underline", true);
-    }
+//     @Test public void testBoundPropertySync_Underline() throws Exception {
+//         NodeTest.assertBooleanPropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "underline", "underline", true);
+//     }
 
-    @Test public void testBoundPropertySync_Strikethrough() throws Exception {
-        NodeTest.assertBooleanPropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "strikethrough", "strikethrough", true);
-    }
+//     @Test public void testBoundPropertySync_Strikethrough() throws Exception {
+//         NodeTest.assertBooleanPropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "strikethrough", "strikethrough", true);
+//     }
 
-    @Test public void testBoundPropertySync_TextAlignment() throws Exception {
-        NodeTest.assertObjectPropertySynced(
-                new Text(1.0, 2.0, "The Text"),
-                "textAlignment", "textAlignment", TextAlignment.RIGHT);
-    }
+//     @Test public void testBoundPropertySync_TextAlignment() throws Exception {
+//         NodeTest.assertObjectPropertySynced(
+//                 new Text(1.0, 2.0, "The Text"),
+//                 "textAlignment", "textAlignment", TextAlignment.RIGHT);
+//     }
 
 }
--- a/javafx-ui-common/test/unit/javafx/stage/StageTest.java	Thu Apr 05 12:55:37 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/stage/StageTest.java	Thu Apr 05 14:43:38 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/pom.xml	Thu Apr 05 14:43:38 2012 -0700
@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>javafx</artifactId>
+        <groupId>javafx</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>javafx</groupId>
+    <artifactId>javafx-ui-controls</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+    <name>javafx-ui-controls</name>
+    <url>www.javafx.com</url>
+    <dependencies>
+        <dependency>
+            <groupId>javafx</groupId>
+            <artifactId>test-stub-toolkit</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/ColorPicker.java	Thu Apr 05 14:43:38 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                                                     *
+     *                                                                         *
+     **********************************************************