changeset 798:e9d45acca75f 2.2-b04

Automated merge with ssh://jpgodine@jfxsrc.us.oracle.com//javafx/2.2/MASTER/jfx/rt
author jpgodine@JPGODINE-LAP.st-users.us.oracle.com
date Tue, 10 Apr 2012 09:16:44 -0700
parents fdc80b4d14e9 b98d11b3a594
children be3c4671a7e7 ff654a59c586
files javafx-ui-common/src/javafx/scene/Node.java javafx-ui-common/src/javafx/scene/Scene.java javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualFlow.java
diffstat 11 files changed, 532 insertions(+), 372 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ueber-jar/pom.xml	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ueber-jar/pom.xml	Tue Apr 10 09:16:44 2012 -0700
@@ -49,6 +49,7 @@
                 <artifactId>maven-shade-plugin</artifactId>
                 <version>1.5</version>
                 <configuration>
+                    <createSourcesJar>true</createSourcesJar>
                     <artifactSet>
                         <includes>
                             <include>*</include>
--- a/javafx-ui-common/src/javafx/scene/Node.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Node.java	Tue Apr 10 09:16:44 2012 -0700
@@ -683,9 +683,8 @@
                 protected void invalidated() {
                     Scene _scene = get();
                     if (oldScene != _scene) {
-                        if (oldScene != null) {
-                            oldScene.removeFromDirtyList(Node.this);
-                        }
+                        //Note: no need to remove from scene's dirty list
+                        //Scene's is checking if the node's scene is correct
                         impl_reapplyCSS();
                         if (_scene != null && !impl_isDirtyEmpty()) {
                             _scene.addToDirtyList(Node.this);
--- a/javafx-ui-common/src/javafx/scene/Scene.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Scene.java	Tue Apr 10 09:16:44 2012 -0700
@@ -32,8 +32,6 @@
 import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -115,6 +113,8 @@
 import javafx.scene.input.TouchEvent;
 import javafx.scene.input.TouchPoint;
 import javafx.scene.input.ZoomEvent;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
 
 
 /**
@@ -308,10 +308,8 @@
             });
         }
 
-        // Reserve space for 30 nodes in the dirtyNodes and dirtyCSSNodes sets.
-        // We need to account for the default HashSet load factor.
-        private static final double HASH_LOAD = 0.75f; // default load factor for HashSet
-        private static final int MIN_DIRTY_CAPACITY = (int) (30.0f / HASH_LOAD);
+        // Reserve space for 30 nodes in the dirtyNodes set.
+        private static final int MIN_DIRTY_CAPACITY = 30;
 
         // For debugging
         private static boolean inSynchronizer = false;
@@ -367,10 +365,6 @@
      * is added to this list. Note that if state on the Node changes, but it
      * was already dirty, then the Node doesn't add itself again.
      * <p>
-     * We need this to be a set so that adding and removing nodes from the list
-     * will be inexpensive (constant time); we use a LinkedHashSet so that
-     * iteration performance won't suffer.
-     * <p>
      * Because at initialization time every node in the scene graph is dirty,
      * we have a special state and special code path during initialization
      * that does not involve adding each node to the dirtyNodes list. When
@@ -381,27 +375,28 @@
      * set while processing the existing set. This avoids our having to
      * take a snapshot of the set (e.g., with toArray()) and reduces garbage.
      */
-    private LinkedHashSet dirtyNodesA;
-    private LinkedHashSet dirtyNodesB;
-    private LinkedHashSet dirtyNodes; // refers to dirtyNodesA or dirtyNodesB
-
+    private Node[] dirtyNodes;
+    private int dirtyNodesSize;
+    
     /**
      * Add the specified node to this scene's dirty list. Called by the
      * markDirty method in Node or when the Node's scene changes.
      */
     void addToDirtyList(Node n) {
-        if (dirtyNodes == null || dirtyNodes.isEmpty()) {
+        if (dirtyNodes == null || dirtyNodesSize == 0) {
             if (impl_peer != null) {
                 Toolkit.getToolkit().requestNextPulse();
             }
         }
 
-        if (dirtyNodes != null) dirtyNodes.add(n);
-    }
-
-    void removeFromDirtyList(Node n) {
-        if (dirtyNodes != null)
-            dirtyNodes.remove(n);
+        if (dirtyNodes != null) {
+            if (dirtyNodesSize == dirtyNodes.length) {
+                Node[] tmp = new Node[dirtyNodesSize + (dirtyNodesSize >> 1)];
+                System.arraycopy(dirtyNodes, 0, tmp, 0, dirtyNodesSize);
+                dirtyNodes = tmp;
+            }
+            dirtyNodes[dirtyNodesSize++] = n;
+        }
     }
 
     private void doCSSPass() {
@@ -637,6 +632,7 @@
         }
         impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill()));
         impl_peer.setCamera(getCamera() == null ? null : getCamera().getPlatformCamera());
+        pickingCamera = getCamera();
 
         impl_setAllowPGAccess(false);
 
@@ -805,6 +801,17 @@
         return height;
     }
 
+    /*
+     * 3D pickRays are computed on the toolkit layer. The camera settings are
+     * pushed to the toolkit layer on pulse. So we need to keep track of the
+     * camera currently used by toolkit not to ask it to compute 3D pick ray
+     * when it thinks we are using 2D camera.
+     */
+    private Camera pickingCamera;
+
+    // Reusable target wrapper (to avoid creating new one for each picking)
+    private TargetWrapper tmpTargetWrapper = new TargetWrapper();
+
     /**
      * Specifies the type of camera use for rendering this {@code Scene}.
      * If {@code camera} is null, a parallel camera is used for rendering.
@@ -1316,23 +1323,13 @@
             Node sceneFocusOwner = keyHandler.getFocusOwner();
             eventTarget = sceneFocusOwner != null ? sceneFocusOwner : Scene.this;
         } else {
-            EventTarget pickedTarget = null;
-            if (getCamera() instanceof PerspectiveCamera) {
-                final PickRay pickRay = new PickRay();
-                Scene.this.impl_peer.computePickRay((float)x2, (float)y2, pickRay);
-                pickedTarget = mouseHandler.pickNode(pickRay);
-            }
-            else {
-                pickedTarget = mouseHandler.pickNode(x2, y2);
-            }
-            if (pickedTarget == null) {
-                pickedTarget = Scene.this;
-            }
-            eventTarget = pickedTarget;
-        }
-        ContextMenuEvent context = ContextMenuEvent.impl_contextEvent(
-                x2, y2, xAbs, yAbs, isKeyboardTrigger, ContextMenuEvent.CONTEXT_MENU_REQUESTED);
-        Event.fireEvent(eventTarget, context);
+            eventTarget = pick(x2, y2);
+        }
+        if (eventTarget != null) {
+            ContextMenuEvent context = ContextMenuEvent.impl_contextEvent(
+                    x2, y2, xAbs, yAbs, isKeyboardTrigger, ContextMenuEvent.CONTEXT_MENU_REQUESTED);
+            Event.fireEvent(eventTarget, context);
+        }
         if (!isKeyboardTrigger) Scene.inMousePick = false;
     }
 
@@ -1349,16 +1346,8 @@
 
         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;
+            pickedTarget = pick(e.getX(), e.getY());
         }
 
         if (e.getEventType() == ZoomEvent.ZOOM_STARTED ||
@@ -1374,7 +1363,9 @@
             gesture.screenCoords = new Point2D(e.getScreenX(), e.getScreenY());
         }
 
-        Event.fireEvent(pickedTarget, e);
+        if (pickedTarget != null) {
+            Event.fireEvent(pickedTarget, e);
+        }
 
         inMousePick = false;
     }
@@ -1385,17 +1376,7 @@
         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;
-                }
+                pickedTarget = pick(tp.getX(), tp.getY());
             } else {
                 tp.grab(pickedTarget);
             }
@@ -1416,27 +1397,29 @@
 
         // 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;
+            if (tp.getTarget() != null) {
+                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);
             }
-
-            TouchEvent te = TouchEvent.impl_touchEvent(type, tp, touchList,
-                    touchEventSetId, e.isShiftDown(), e.isControlDown(),
-                    e.isAltDown(), e.isMetaDown());
-
-            Event.fireEvent(tp.getTarget(), te);
         }
 
         // process grabbing
@@ -1464,6 +1447,43 @@
         return pickedNode;
     }
 
+    private EventTarget pick(final double x, final double y) {
+        pick(tmpTargetWrapper, x, y);
+        return tmpTargetWrapper.getEventTarget();
+    }
+
+    private void pick(TargetWrapper target, final double x, final double y) {
+        Node n = null;
+
+        if (pickingCamera instanceof PerspectiveCamera) {
+            final PickRay pickRay = new PickRay();
+            Scene.this.impl_peer.computePickRay(
+                    (float)x, (float)y, pickRay);
+            n = mouseHandler.pickNode(pickRay);
+        } else {
+            n = mouseHandler.pickNode(x, y);
+        }
+
+        if (n != null) {
+            target.setNode(n);
+        } else if (
+                x >= 0 && y >= 0 &&
+                x <= getWidth() &&
+                y <= getHeight())  {
+
+            Window w = getWindow();
+            if (w instanceof Stage
+                    && ((Stage) w).getStyle() == StageStyle.TRANSPARENT
+                    && getFill() == null) {
+                target.clear();
+            }
+
+            target.setScene(this);
+        } else {
+            target.clear();
+        }
+    }
+
     /***************************************************************************
      *                                                                         *
      * Key Events and Focus Traversal                                          *
@@ -1841,36 +1861,22 @@
             // scene and then create the dirty nodes array list
             if (Scene.this.dirtyNodes == null) {
                 // must do this recursively
-                final int size = syncAll(getRoot());
-                // Default capacity is hard-coded to minimum capacity
-                // This heuristic can be changed over time if we like
-                Scene.this.dirtyNodesA = new LinkedHashSet(MIN_DIRTY_CAPACITY);
-                Scene.this.dirtyNodesB = new LinkedHashSet(MIN_DIRTY_CAPACITY);
-                Scene.this.dirtyNodes = Scene.this.dirtyNodesA;
+                syncAll(getRoot());
+                dirtyNodes = new Node[MIN_DIRTY_CAPACITY];
+                
             } else {
                 // This is not the first time this scene has been synchronized,
                 // so we will only synchronize those nodes that need it
-                LinkedHashSet currDirtyNodes = Scene.this.dirtyNodes;
-                // Swap the double buffer
-                if (Scene.this.dirtyNodes == Scene.this.dirtyNodesA) {
-                    Scene.this.dirtyNodes = Scene.this.dirtyNodesB;
-                } else {
-                    Scene.this.dirtyNodes = Scene.this.dirtyNodesA;
-                }
-
-
-                final Iterator<Node> it = currDirtyNodes.iterator();
-
-                while(it.hasNext()) {
-                    Node node = it.next();
+                for (int i = 0 ; i < dirtyNodesSize; ++i) {
+                    Node node = dirtyNodes[i];
+                    dirtyNodes[i] = null;
                     if (node.getScene() == Scene.this) {
                         node.impl_syncPGNode();
                     }
                 }
-
-                currDirtyNodes.clear();
+                dirtyNodesSize = 0;
             }
-
+            
             Scene.inSynchronizer = false;
         }
 
@@ -1915,8 +1921,10 @@
                 if (getCamera() != null) {
                     getCamera().impl_update();
                     impl_peer.setCamera(getCamera().getPlatformCamera());
+                    pickingCamera = getCamera();
                 } else {
                     impl_peer.setCamera(null);
+                    pickingCamera = null;
                 }
             }
 
@@ -1959,7 +1967,7 @@
             Scene.this.doCSSPass();
             Scene.this.doLayoutPass();
 
-            boolean dirty = dirtyNodes == null || !dirtyNodes.isEmpty() || !isDirtyEmpty();
+            boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty();
             if (dirty) {
                 // synchronize scene properties
                 synchronizeSceneProperties();
@@ -2332,7 +2340,7 @@
 
            Scene.this.dndGesture = new DnDGesture();
            final DragEvent de = Toolkit.getToolkit().convertDragRecognizedEventToFX(e, null);
-           final Node pickedNode = Scene.this.mouseHandler.pickNode(de.getX(), de.getY());
+           final EventTarget pickedNode = pick(de.getX(), de.getY());
            Scene.this.dndGesture.dragboard = de.impl_getPlatformDragboard();
 
             if (Scene.this.dndGesture.processRecognized(pickedNode, de)) {
@@ -2367,17 +2375,12 @@
         private EventTarget fullPDRSource = null;
 
         /**
-         * Returns the given target or scene if it's null
-         */
-        private EventTarget targetOrScene(EventTarget target) {
-            return target != null ? target : Scene.this;
-        }
-
-        /**
          * Fires event on a given target or on scene if the node is null
          */
         private void fireEvent(EventTarget target, Event e) {
-            Event.fireEvent(targetOrScene(target), e);
+            if (target != null) {
+                Event.fireEvent(target, e);
+            }
         }
 
         /**
@@ -2412,8 +2415,7 @@
         /**
          * Sets the default dragDetect value
          */
-        private void processDragDetection(MouseEvent mouseEvent,
-                EventTarget target) {
+        private void processDragDetection(MouseEvent mouseEvent) {
 
             if (dragDetected != DragDetectedState.NOT_YET) {
                 mouseEvent.setDragDetect(false);
@@ -2451,11 +2453,13 @@
 
                     processingDragDetected();
 
-                    final MouseEvent detectedEvent = MouseEvent.impl_copy(
-                            mouseEvent.getSource(), target, mouseEvent,
-                            MouseEvent.DRAG_DETECTED);
-
-                    fireEvent(target, detectedEvent);
+                    if (target != null) {
+                        final MouseEvent detectedEvent = MouseEvent.impl_copy(
+                                mouseEvent.getSource(), target, mouseEvent,
+                                MouseEvent.DRAG_DETECTED);
+
+                        fireEvent(target, detectedEvent);
+                    }
 
                     dragDetectedProcessed();
                 }
@@ -2472,7 +2476,7 @@
          * the publicly visible drag and drop API, as it is responsible for calling
          * the Node.onDragSourceRecognized function.
          */
-        private boolean processRecognized(Node n, DragEvent de) {
+        private boolean processRecognized(EventTarget target, DragEvent de) {
             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,
@@ -2480,7 +2484,7 @@
 
             processingDragDetected();
 
-            fireEvent(n, me);
+            fireEvent(target, me);
 
             dragDetectedProcessed();
 
@@ -2507,7 +2511,8 @@
 
             Event.fireEvent(source, de);
 
-            handleExitEnter(de, null);
+            tmpTargetWrapper.clear();
+            handleExitEnter(de, tmpTargetWrapper);
 
             // at this point the drag and drop operation is completely over, so we
             // can tell the toolkit that it can clean up if needs be.
@@ -2515,34 +2520,28 @@
         }
 
         private TransferMode processTargetEnterOver(DragEvent de) {
-            final Node pickedNode = Scene.this.mouseHandler.pickNode(de.getX(), de.getY());
-            if (pickedNode == null || pickedNode.impl_isTreeVisible()) {
-
-                if (dragboard == null) {
-                    dragboard = createDragboard();
-                }
-
-                de = DragEvent.impl_copy(de.getSource(), pickedNode, source,
-                        potentialTarget, dragboard, de);
-
-                handleExitEnter(de, targetOrScene(pickedNode));
-
-                de = DragEvent.impl_copy(de.getSource(), pickedNode, source,
-                        potentialTarget, de, DragEvent.DRAG_OVER);
-
-                fireEvent(pickedNode, de);
-
-                Object acceptingObject = de.impl_getAcceptingObject();
-                potentialTarget = acceptingObject instanceof EventTarget
-                        ? (EventTarget) acceptingObject : null;
-                acceptedTransferMode = de.getAcceptedTransferMode();
-                return acceptedTransferMode;
-            } else {
-                processTargetExit(de);
+            pick(tmpTargetWrapper, de.getX(), de.getY());
+            final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget();
+
+            if (dragboard == null) {
+                dragboard = createDragboard();
             }
 
-            acceptedTransferMode = null;
-            return null;
+            de = DragEvent.impl_copy(de.getSource(), pickedTarget, source,
+                    potentialTarget, dragboard, de);
+
+            handleExitEnter(de, tmpTargetWrapper);
+
+            de = DragEvent.impl_copy(de.getSource(), pickedTarget, source,
+                    potentialTarget, de, DragEvent.DRAG_OVER);
+
+            fireEvent(pickedTarget, de);
+
+            Object acceptingObject = de.impl_getAcceptingObject();
+            potentialTarget = acceptingObject instanceof EventTarget
+                    ? (EventTarget) acceptingObject : null;
+            acceptedTransferMode = de.getAcceptedTransferMode();
+            return acceptedTransferMode;
         }
 
         private void processTargetActionChanged(DragEvent de) {
@@ -2564,60 +2563,48 @@
         private void processTargetExit(DragEvent de) {
             if (currentTargets.size() > 0) {
                 potentialTarget = null;
-                handleExitEnter(de, null);
+                tmpTargetWrapper.clear();
+                handleExitEnter(de, tmpTargetWrapper);
             }
         }
 
         private TransferMode processTargetDrop(DragEvent de) {
-            final Node pickedNode = Scene.this.mouseHandler.pickNode(de.getX(), de.getY());
-            if (pickedNode == null || pickedNode.impl_isTreeVisible()) {
-
-                de = DragEvent.impl_copy(de.getSource(), pickedNode, source,
-                        potentialTarget, acceptedTransferMode, de,
-                        DragEvent.DRAG_DROPPED);
-
-                if (dragboard == null) {
-                    dragboard = createDragboard();
-                }
-
-                handleExitEnter(de, targetOrScene(pickedNode));
-
-                fireEvent(pickedNode, de);
-
-                Object acceptingObject = de.impl_getAcceptingObject();
-                potentialTarget = acceptingObject instanceof EventTarget
-                        ? (EventTarget) acceptingObject : null;
-                target = potentialTarget;
-
-                TransferMode result = de.isDropCompleted() ?
-                    de.getAcceptedTransferMode() : null;
-
-                handleExitEnter(de, null);
-
-                return result;
-            } else {
-                processTargetExit(de);
+            pick(tmpTargetWrapper, de.getX(), de.getY());
+            final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget();
+
+            de = DragEvent.impl_copy(de.getSource(), pickedTarget, source,
+                    potentialTarget, acceptedTransferMode, de,
+                    DragEvent.DRAG_DROPPED);
+
+            if (dragboard == null) {
+                dragboard = createDragboard();
             }
-            return null;
-        }
-
-        private void handleExitEnter(DragEvent e, EventTarget target) {
+
+            handleExitEnter(de, tmpTargetWrapper);
+
+            fireEvent(pickedTarget, de);
+
+            Object acceptingObject = de.impl_getAcceptingObject();
+            potentialTarget = acceptingObject instanceof EventTarget
+                    ? (EventTarget) acceptingObject : null;
+            target = potentialTarget;
+
+            TransferMode result = de.isDropCompleted() ?
+                de.getAcceptedTransferMode() : null;
+
+            tmpTargetWrapper.clear();
+            handleExitEnter(de, tmpTargetWrapper);
+
+            return result;
+        }
+
+        private void handleExitEnter(DragEvent e, TargetWrapper target) {
             EventTarget currentTarget =
                     currentTargets.size() > 0 ? currentTargets.get(0) : null;
 
-            if (target != currentTarget) {
-
-                newTargets.clear();
-                if (target instanceof Node) {
-                    Node newNode = (Node) target;
-                    while(newNode != null) {
-                        newTargets.add(newNode);
-                        newNode = newNode.getParent();
-                    }
-                }
-                if (target != null) {
-                    newTargets.add(Scene.this);
-                }
+            if (target.getEventTarget() != currentTarget) {
+
+                target.fillHierarchy(newTargets);
 
                 int i = currentTargets.size() - 1;
                 int j = newTargets.size() - 1;
@@ -2676,7 +2663,8 @@
                     Event.fireEvent(source, de);
                 }
 
-                handleExitEnter(de, null);
+                tmpTargetWrapper.clear();
+                handleExitEnter(de, tmpTargetWrapper);
 
                 return false;
             }
@@ -2756,7 +2744,6 @@
         private boolean still;
         private Timeline timeout;
         private double pressedX, pressedY;
-        private EventTarget clickTarget;
 
         private void inc() { count++; }
         private int get() { return count; }
@@ -2764,7 +2751,6 @@
 
         private void clear() {
             count = 0;
-            clickTarget = null;
             stopTimeout();
         }
 
@@ -2809,34 +2795,6 @@
             still = true;
         }
 
-        private void setTarget(EventTarget target) {
-            this.clickTarget = target;
-        }
-
-        private void checkTarget(EventTarget target) {
-            if (clickTarget == null) {
-                return;
-            }
-
-            boolean isThere = false;
-            if (target instanceof Node) {
-                Node n = (Node) target;
-                while(n != null) {
-                    if (n == clickTarget) {
-                        isThere = true;
-                        break;
-                    }
-                    n = n.getParent();
-                }
-            }
-            if (clickTarget == Scene.this) {
-                isThere = true;
-            }
-            if (!isThere) {
-                out();
-            }
-        }
-
         private void stopTimeout() {
             if (timeout != null) {
                 timeout.stop();
@@ -2850,6 +2808,8 @@
 
         private Map<MouseButton, ClickCounter> counters =
                 new EnumMap<MouseButton, ClickCounter>(MouseButton.class);
+        private List<EventTarget> pressedTargets = new ArrayList<EventTarget>();
+        private List<EventTarget> releasedTargets = new ArrayList<EventTarget>();
 
         public ClickGenerator() {
             for (MouseButton mb : MouseButton.values()) {
@@ -2859,7 +2819,7 @@
             }
         }
 
-        private void preProcess(MouseEvent e, EventTarget target) {
+        private void preProcess(MouseEvent e) {
             for (ClickCounter cc : counters.values()) {
                 cc.moved(e.getSceneX(), e.getSceneY());
             }
@@ -2873,7 +2833,6 @@
                 if (! e.isSecondaryButtonDown()) { counters.get(MouseButton.SECONDARY).clear(); }
                 if (! e.isMiddleButtonDown()) { counters.get(MouseButton.MIDDLE).clear(); }
 
-                cc.checkTarget(target);
                 cc.applyOut();
                 cc.inc();
                 cc.start(e.getSceneX(), e.getSceneY());
@@ -2885,32 +2844,27 @@
                     still);
         }
 
-        private void postProcess(MouseEvent e, EventTarget target) {
+        private void postProcess(MouseEvent e, TargetWrapper target, TargetWrapper pickedTarget) {
+
             if (e.getEventType() == MouseEvent.MOUSE_RELEASED) {
                 ClickCounter cc = counters.get(e.getButton());
+
+                target.fillHierarchy(pressedTargets);
+                pickedTarget.fillHierarchy(releasedTargets);
+                int i = pressedTargets.size() - 1;
+                int j = releasedTargets.size() - 1;
+
                 EventTarget clickedTarget = null;
-                if (target instanceof Node) {
-                    Node n = (Node) target;
-                    while(n != null) {
-                        if (n.contains(n.sceneToLocal(e.getSceneX(), e.getSceneY()))) {
-                            clickedTarget = n;
-                            break;
-                        }
-                        n = n.getParent();
-                    }
-                }
-                if (clickedTarget == null &&
-                        e.getSceneX() >= 0 && e.getSceneY() >= 0 &&
-                        e.getSceneX() <= Scene.this.getWidth() &&
-                        e.getSceneY() <= Scene.this.getHeight())  {
-                    clickedTarget = Scene.this;
+                while (i >= 0 && j >= 0 && pressedTargets.get(i) == releasedTargets.get(j)) {
+                    clickedTarget = pressedTargets.get(i);
+                    i--;
+                    j--;
                 }
 
                 if (clickedTarget != null) {
                     MouseEvent click = MouseEvent.impl_copy(null, clickedTarget, e,
                             MouseEvent.MOUSE_CLICKED);
                     click.impl_setClickParams(cc.get(), lastPress.isStill());
-                    cc.setTarget(clickedTarget);
                     Event.fireEvent(clickedTarget, click);
                 }
             }
@@ -2918,7 +2872,7 @@
     }
 
     class MouseHandler {
-        private EventTarget pdrEventTarget = null; // pdr - press-drag-release
+        private TargetWrapper pdrEventTarget = new TargetWrapper(); // pdr - press-drag-release
         private boolean pdrInProgress = false;
         private boolean fullPDREntered = false;
 
@@ -2931,6 +2885,7 @@
         private boolean middleButtonDown = false;
 
         private EventTarget fullPDRSource = null;
+        private TargetWrapper fullPDRTmpTargetWrapper = new TargetWrapper();
 
         /* lists needed for enter/exit events generation */
         private final List<EventTarget> pdrEventTargets = new ArrayList<EventTarget>();
@@ -2958,7 +2913,7 @@
             pdrInProgress = false;
             currentEventTarget = currentEventTargets.size() > 0
                     ? currentEventTargets.get(0) : null;
-            pdrEventTarget = null;
+            pdrEventTarget.clear();
         }
 
         public void enterFullPDR(EventTarget gestureSource) {
@@ -2983,22 +2938,15 @@
             fullPDRCurrentEventTargets.clear();
             fullPDRCurrentTarget = null;
         }
-        
-        private void handleEnterExit(MouseEvent e, EventTarget pickedTarget) {
-            if (pickedTarget != currentEventTarget ||
+
+        private void handleEnterExit(MouseEvent e, TargetWrapper pickedTarget) {
+            if (pickedTarget.getEventTarget() != currentEventTarget ||
                     e.getEventType() == MouseEvent.MOUSE_EXITED) {
 
-                newEventTargets.clear();
-
-                if (e.getEventType() != MouseEvent.MOUSE_EXITED) {
-                    if (pickedTarget instanceof Node) {
-                        Node newNode = (Node) pickedTarget;
-                        while(newNode != null) {
-                            newEventTargets.add(newNode);
-                            newNode = newNode.getParent();
-                        }
-                    }
-                    newEventTargets.add(Scene.this);
+                if (e.getEventType() == MouseEvent.MOUSE_EXITED) {
+                    newEventTargets.clear();
+                } else {
+                    pickedTarget.fillHierarchy(newEventTargets);
                 }
 
                 int newTargetsSize = newEventTargets.size();
@@ -3036,7 +2984,7 @@
                             MouseEvent.MOUSE_ENTERED_TARGET));
                 }
 
-                currentEventTarget = pickedTarget;
+                currentEventTarget = pickedTarget.getEventTarget();
                 currentEventTargets.clear();
                 for (j++; j < newTargetsSize; j++) {
                     currentEventTargets.add(newEventTargets.get(j));
@@ -3076,81 +3024,48 @@
                 middleButtonDown = e.isMiddleButtonDown();
             }
 
-            //maps parent to most visible child
-            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 = pickNode(pickRay);
-                }
-                else {
-                    pickedTarget = pickNode(e.getX(), e.getY());
-                }
+                pick(tmpTargetWrapper, e.getX(), e.getY());
+            } else {
+                tmpTargetWrapper.clear();
             }
 
-            if (pickedTarget == null) {
-                pickedTarget = Scene.this;
-            }
-
-            EventTarget target;
+            TargetWrapper target;
             if (pdrInProgress) {
                 target = pdrEventTarget;
             } else {
-                target = pickedTarget;
+                target = tmpTargetWrapper;
             }
 
             if (gestureStarted) {
-                pdrEventTarget = target;
-
-                pdrEventTargets.clear();
-                if (pdrEventTarget instanceof Node) {
-                    Node n = (Node) pdrEventTarget;
-                    while(n != null) {
-                        pdrEventTargets.add(n);
-                        n = n.getParent();
-                    }
-                }
-
-                pdrEventTargets.add(Scene.this);
+                pdrEventTarget.copy(target);
+                pdrEventTarget.fillHierarchy(pdrEventTargets);
             }
 
             if (!onPulse) {
-                clickGenerator.preProcess(e, target);
+                clickGenerator.preProcess(e);
             }
 
             // enter/exit handling
-            handleEnterExit(e, pickedTarget);
-
-            Cursor cursor = null;
+            handleEnterExit(e, tmpTargetWrapper);
+
+            Cursor cursor = target.getCursor();
 
             //deliver event to the target node
-            if (target instanceof Node) {
-
-                if (cursor == null) {
-                    cursor = ((Node) target).getCursor();
-                    Parent p = ((Node) target).getParent();
-                    while (cursor == null && p != null) {
-                        cursor = p.getCursor();
-                        p = p.getParent();
-                    }
-                }
-            }
 
             if (Scene.this.dndGesture != null) {
-                Scene.this.dndGesture.processDragDetection(e, target);
+                Scene.this.dndGesture.processDragDetection(e);
             }
 
             if (fullPDREntered && e.getEventType() == MouseEvent.MOUSE_RELEASED) {
                 processFullPDR(e, onPulse);
             }
 
-            if (target != null) {
+            if (target.getEventTarget() != null) {
                 if (e.getEventType() != MouseEvent.MOUSE_ENTERED
                         && e.getEventType() != MouseEvent.MOUSE_EXITED
                         && !onPulse) {
-                    Event.fireEvent(target, e);
+                    Event.fireEvent(target.getEventTarget(), e);
                 }
             }
 
@@ -3159,14 +3074,14 @@
             }
 
             if (!onPulse) {
-                clickGenerator.postProcess(e, target);
+                clickGenerator.postProcess(e, target, tmpTargetWrapper);
             }
 
             // handle drag and drop
 
             if (!PLATFORM_DRAG_GESTURE_INITIATION && !onPulse) {
                 if (Scene.this.dndGesture != null) {
-                    if (!Scene.this.dndGesture.process(e, target)) {
+                    if (!Scene.this.dndGesture.process(e, target.getEventTarget())) {
                         dndGesture = null;
                     }
                 }
@@ -3188,7 +3103,7 @@
                     !(primaryButtonDown || secondaryButtonDown || middleButtonDown)) {
                 clearPDREventTargets();
                 exitFullPDR(e);
-                handleEnterExit(e, pickedTarget);
+                handleEnterExit(e, tmpTargetWrapper);
             }
 
             lastEvent = e;
@@ -3197,42 +3112,14 @@
 
         private void processFullPDR(MouseEvent e, boolean onPulse) {
 
-            // picking
-            EventTarget target = 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);
-                    target = pickNode(pickRay);
-                } else {
-                    target = pickNode(e.getX(), e.getY());
-                }
-            }
-
-            if (target == null &&
-                    e.getSceneX() >= 0 && e.getSceneY() >= 0 &&
-                    e.getSceneX() <= Scene.this.getWidth() &&
-                    e.getSceneY() <= Scene.this.getHeight())  {
-                target = Scene.this;
-            }
+            pick(fullPDRTmpTargetWrapper, e.getX(), e.getY());
+
+            final EventTarget eventTarget = fullPDRTmpTargetWrapper.getEventTarget();
 
             // enter/exit handling
-            if (target != fullPDRCurrentTarget) {
-
-                fullPDRNewEventTargets.clear();
-
-                if (target != null) {
-                    if (target instanceof Node) {
-                        Node newNode = (Node) target;
-                        while(newNode != null) {
-                            fullPDRNewEventTargets.add(newNode);
-                            newNode = newNode.getParent();
-                        }
-                    }
-                    fullPDRNewEventTargets.add(Scene.this);
-                }
+            if (eventTarget != fullPDRCurrentTarget) {
+
+                fullPDRTmpTargetWrapper.fillHierarchy(fullPDRNewEventTargets);
 
                 int newTargetsSize = fullPDRNewEventTargets.size();
                 int i = fullPDRCurrentEventTargets.size() - 1;
@@ -3258,22 +3145,22 @@
                             MouseDragEvent.MOUSE_DRAG_ENTERED_TARGET));
                 }
 
-                fullPDRCurrentTarget = target;
+                fullPDRCurrentTarget = eventTarget;
                 fullPDRCurrentEventTargets.clear();
                 fullPDRCurrentEventTargets.addAll(fullPDRNewEventTargets);
             }
             // done enter/exit handling
 
             // event delivery
-            if (target != null && !onPulse) {
+            if (eventTarget != null && !onPulse) {
                 if (e.getEventType() == MouseEvent.MOUSE_DRAGGED) {
-                    Event.fireEvent(target, MouseDragEvent.impl_copy(
-                            target, target, fullPDRSource, e,
+                    Event.fireEvent(eventTarget, MouseDragEvent.impl_copy(
+                            eventTarget, eventTarget, fullPDRSource, e,
                             MouseDragEvent.MOUSE_DRAG_OVER));
                 }
                 if (e.getEventType() == MouseEvent.MOUSE_RELEASED) {
-                    Event.fireEvent(target, MouseDragEvent.impl_copy(
-                            target, target, fullPDRSource, e,
+                    Event.fireEvent(eventTarget, MouseDragEvent.impl_copy(
+                            eventTarget, eventTarget, fullPDRSource, e,
                             MouseDragEvent.MOUSE_DRAG_RELEASED));
                 }
             }
@@ -3673,7 +3560,7 @@
 
         return tail;
     }
-    
+
     /***************************************************************************
      *                                                                         *
      *                             Context Menus                               *
@@ -5370,4 +5257,69 @@
         }
         return onInputMethodTextChanged;
     }
+
+    /*
+     * This class represents a picked target - either node, or scne, or null.
+     * It provides functionality needed for the targets and covers the fact
+     * that they are different kinds of animals.
+     */
+    private class TargetWrapper {
+        private Scene scene;
+        private Node node;
+
+        /**
+         * Fills the list with the target and all its parents (including scene)
+         */
+        public void fillHierarchy(final List<EventTarget> list) {
+            list.clear();
+            Node n = node;
+            while(n != null) {
+                list.add(n);
+                n = n.getParent();
+            }
+
+            if (scene != null) {
+                list.add(scene);
+            }
+        }
+
+        public EventTarget getEventTarget() {
+            return node != null ? node : scene;
+        }
+
+        public Cursor getCursor() {
+            Cursor cursor = null;
+            if (node != null) {
+                cursor = node.getCursor();
+                Parent p = node.getParent();
+                while (cursor == null && p != null) {
+                    cursor = p.getCursor();
+                    p = p.getParent();
+                }
+            }
+            return cursor;
+        }
+
+        public void clear() {
+            set(null, null);
+        }
+
+        public void setScene(Scene scene) {
+            set(null, scene);
+        }
+
+        public void setNode(Node node) {
+            set(node, node.getScene());
+        }
+
+        public void copy(TargetWrapper tw) {
+            node = tw.node;
+            scene = tw.scene;
+        }
+
+        private void set(Node n, Scene s) {
+            node = n;
+            scene = s;
+        }
+    }
 }
--- a/javafx-ui-common/src/javafx/scene/image/Image.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/image/Image.java	Tue Apr 10 09:16:44 2012 -0700
@@ -25,7 +25,10 @@
 
 package javafx.scene.image;
 
+import java.io.File;
 import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
 import java.util.LinkedList;
 import java.util.Queue;
 
@@ -502,9 +505,11 @@
      *
      * @param url the string representing the URL to use in fetching the pixel
      *      data
+     * @throws NullPointerException if URL is null
+     * @throws IllegalArgumentException if URL is invalid or unsupported
      */
     public Image(String url) {
-        this(url, null, 0, 0, false, false, false);
+        this(validateUrl(url), null, 0, 0, false, false, false);
         initialize(null);
     }
 
@@ -515,9 +520,11 @@
      *      data
      * @param backgroundLoading indicates whether the image
      *      is being loaded in the background
+     * @throws NullPointerException if URL is null
+     * @throws IllegalArgumentException if URL is invalid or unsupported
      */
     public Image(String url, boolean backgroundLoading) {
-        this(url, null, 0, 0, false, false, backgroundLoading);
+        this(validateUrl(url), null, 0, 0, false, false, backgroundLoading);
         initialize(null);
     }
 
@@ -534,11 +541,13 @@
      * @param smooth indicates whether to use a better quality filtering
      *      algorithm or a faster one when scaling this image to fit within
      *      the specified bounding box
+     * @throws NullPointerException if URL is null
+     * @throws IllegalArgumentException if URL is invalid or unsupported
      */
     public Image(String url, double requestedWidth, double requestedHeight,
                  boolean preserveRatio, boolean smooth) {
-        this(url, null, requestedWidth, requestedHeight, preserveRatio, smooth,
-             false);
+        this(validateUrl(url), null, requestedWidth, requestedHeight,
+             preserveRatio, smooth, false);
         initialize(null);
     }
 
@@ -557,6 +566,8 @@
      *      the specified bounding box
      * @param backgroundLoading indicates whether the image
      *      is being loaded in the background
+     * @throws NullPointerException if URL is null
+     * @throws IllegalArgumentException if URL is invalid or unsupported
      */
     public Image(
             @Default("\"\"") String url,
@@ -565,8 +576,8 @@
             boolean preserveRatio,
             @Default("true") boolean smooth,
             boolean backgroundLoading) {
-        this(url, null, requestedWidth, requestedHeight, preserveRatio, smooth,
-             backgroundLoading);
+        this(validateUrl(url), null, requestedWidth, requestedHeight,
+             preserveRatio, smooth, backgroundLoading);
         initialize(null);
     }
 
@@ -575,9 +586,10 @@
      * input stream.
      *
      * @param is the stream from which to load the image
+     * @throws NullPointerException if input stream is null
      */
     public Image(InputStream is) {
-        this(null, is, 0, 0, false, false, false);
+        this(null, validateInputStream(is), 0, 0, false, false, false);
         initialize(null);
     }
 
@@ -593,11 +605,12 @@
      * @param smooth indicates whether to use a better quality filtering
      *      algorithm or a faster one when scaling this image to fit within
      *      the specified bounding box
+     * @throws NullPointerException if input stream is null
      */
     public Image(InputStream is, double requestedWidth, double requestedHeight,
                  boolean preserveRatio, boolean smooth) {
-        this(null, is, requestedWidth, requestedHeight, preserveRatio, smooth,
-             false);
+        this(null, validateInputStream(is), requestedWidth, requestedHeight,
+             preserveRatio, smooth, false);
         initialize(null);
     }
 
@@ -889,6 +902,64 @@
         return Toolkit.getToolkit().loadPlatformImage(platformImage);
     }
 
+    private static String validateUrl(final String url) {
+        if (url == null) {
+            throw new NullPointerException("URL must not be null");
+        }
+
+        if (url.trim().isEmpty()) {
+            throw new IllegalArgumentException("URL must not be empty");
+        }
+
+        final URI baseUri = getBaseUri();
+        final URI resolvedUri;
+        try {
+            resolvedUri = (baseUri != null) ? baseUri.resolve(url)
+                                            : URI.create(url);
+
+            return resolvedUri.toURL().toString();
+        } catch (final IllegalArgumentException e) {
+            throw new IllegalArgumentException(
+                    constructDetailedExceptionMessage("Invalid URL", e), e);
+        } catch (final MalformedURLException e) {
+            throw new IllegalArgumentException(
+                    constructDetailedExceptionMessage("Invalid URL", e), e);
+        }
+    }
+
+    private static InputStream validateInputStream(
+            final InputStream inputStream) {
+        if (inputStream == null) {
+            throw new NullPointerException("Input stream must not be null");
+        }
+
+        return inputStream;
+    }
+
+    private static String constructDetailedExceptionMessage(
+            final String mainMessage,
+            final Throwable cause) {
+        if (cause == null) {
+            return mainMessage;
+        }
+
+        final String causeMessage = cause.getMessage();
+        return constructDetailedExceptionMessage(
+                       (causeMessage != null)
+                               ? mainMessage + ": " + causeMessage
+                               : mainMessage,
+                       cause.getCause());
+    }
+
+    private static URI getBaseUri() {
+        try {
+            // we might want to use getDocumentBase() from HostServices here,
+            // but that would be an incompatible change
+            return new File("").toURI();
+        } catch (final Exception e) {
+            return null;
+        }
+    }
 
     /**
      * This method converts a JavaFX Image to the specified image class or
--- a/javafx-ui-common/src/javafx/scene/image/ImageView.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/image/ImageView.java	Tue Apr 10 09:16:44 2012 -0700
@@ -153,6 +153,8 @@
      * </p>
      *
      * @param url the string representing the URL from which to load the image
+     * @throws NullPointerException if URL is null
+     * @throws IllegalArgumentException if URL is invalid or unsupported
      * @since JavaFX 2.1
      */
     public ImageView(String url) {
--- a/javafx-ui-common/src/javafx/scene/input/MouseEvent.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/input/MouseEvent.java	Tue Apr 10 09:16:44 2012 -0700
@@ -34,7 +34,6 @@
 import javafx.scene.Node;
 
 import com.sun.javafx.scene.input.InputEventUtils;
-import javax.sound.midi.Synthesizer;
 
 // PENDING_DOC_REVIEW
 /**
--- a/javafx-ui-common/src/javafx/scene/text/Text.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/text/Text.java	Tue Apr 10 09:16:44 2012 -0700
@@ -58,7 +58,6 @@
 import com.sun.javafx.css.StyleableProperty;
 import com.sun.javafx.css.converters.BooleanConverter;
 import com.sun.javafx.css.converters.EnumConverter;
-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;
@@ -1578,12 +1577,6 @@
              * 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(),
--- a/javafx-ui-common/test/unit/javafx/scene/MouseTest.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/MouseTest.java	Tue Apr 10 09:16:44 2012 -0700
@@ -172,6 +172,9 @@
         assertFalse(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertFalse(scene.groupTracker.wasExitedMe());
+        assertTrue(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
+        scene.clear();
 
         scene.processEvent(generator.generateMouseEvent(
                 MouseEvent.MOUSE_MOVED, 250, 250));
@@ -182,6 +185,8 @@
         assertFalse(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertFalse(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
         scene.clear();
 
         scene.processEvent(generator.generateMouseEvent(
@@ -193,6 +198,8 @@
         assertTrue(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertFalse(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
         scene.clear();
 
         scene.processEvent(generator.generateMouseEvent(
@@ -204,6 +211,21 @@
         assertFalse(scene.smallSquareTracker.wasExitedMe());
         assertTrue(scene.bigSquareTracker.wasExitedMe());
         assertTrue(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
+        scene.clear();
+
+        scene.processEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_MOVED, -1, 50));
+        assertFalse(scene.smallSquareTracker.wasEnteredMe());
+        assertFalse(scene.bigSquareTracker.wasEnteredMe());
+        assertFalse(scene.groupTracker.wasEnteredMe());
+        assertFalse(scene.smallSquareTracker.wasExitedMe());
+        assertFalse(scene.bigSquareTracker.wasExitedMe());
+        assertFalse(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertTrue(scene.sceneTracker.wasExitedMe());
+        scene.clear();
     }
 
     @Test
@@ -223,6 +245,8 @@
         assertFalse(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertFalse(scene.groupTracker.wasExitedMe());
+        assertTrue(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
         scene.clear();
 
         scene.processEvent(generator.generateMouseEvent(
@@ -234,6 +258,8 @@
         assertTrue(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertFalse(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
         scene.clear();
 
         scene.processEvent(generator.generateMouseEvent(
@@ -245,6 +271,21 @@
         assertFalse(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertTrue(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
+        scene.clear();
+
+        scene.processEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_DRAGGED, -1, 50));
+
+        assertFalse(scene.smallSquareTracker.wasEnteredMe());
+        assertFalse(scene.bigSquareTracker.wasEnteredMe());
+        assertFalse(scene.groupTracker.wasEnteredMe());
+        assertFalse(scene.smallSquareTracker.wasExitedMe());
+        assertFalse(scene.bigSquareTracker.wasExitedMe());
+        assertFalse(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertTrue(scene.sceneTracker.wasExitedMe());
         scene.clear();
 
         scene.processEvent(generator.generateMouseEvent(
@@ -256,6 +297,8 @@
         assertFalse(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertFalse(scene.groupTracker.wasExitedMe());
+        assertTrue(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
         scene.clear();
 
         scene.processEvent(generator.generateMouseEvent(
@@ -267,6 +310,8 @@
         assertFalse(scene.smallSquareTracker.wasExitedMe());
         assertFalse(scene.bigSquareTracker.wasExitedMe());
         assertTrue(scene.groupTracker.wasExitedMe());
+        assertFalse(scene.sceneTracker.wasEnteredMe());
+        assertFalse(scene.sceneTracker.wasExitedMe());
     }
 
     @Test
@@ -560,6 +605,25 @@
     }
 
     @Test
+    public void doubleClickShouldBeGeneratedEvenIfNodeChangesInBetween() {
+        SimpleTestScene scene = new SimpleTestScene();
+        MouseEventGenerator generator = new MouseEventGenerator();
+
+        scene.processEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_PRESSED, 199, 250));
+        scene.processEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_RELEASED, 199, 250));
+        scene.processEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_PRESSED, 201, 250));
+        scene.processEvent(generator.generateMouseEvent(
+                MouseEvent.MOUSE_RELEASED, 201, 250));
+
+        assertTrue(scene.smallSquareTracker.wasDoubleClicked());
+        assertFalse(scene.bigSquareTracker.wasDoubleClicked());
+        assertTrue(scene.bigSquareTracker.wasClicked());
+    }
+
+    @Test
     public void doubleClickShouldBeGeneratedFromPressedReleasedTwoTimesWithHysteresis() {
         SimpleTestScene scene = new SimpleTestScene();
         MouseEventGenerator generator = new MouseEventGenerator();
@@ -848,6 +912,7 @@
     
     private static class SimpleTestScene {
 
+        MouseEventTracker sceneTracker;
         MouseEventTracker groupTracker;
         MouseEventTracker bigSquareTracker;
         MouseEventTracker smallSquareTracker;
@@ -870,6 +935,7 @@
 
             root.getChildren().add(group);
 
+            sceneTracker = new MouseEventTracker(scene);
             groupTracker = new MouseEventTracker(group);
             bigSquareTracker = new MouseEventTracker(bigSquare);
             smallSquareTracker = new MouseEventTracker(smallSquare);
@@ -900,6 +966,7 @@
 
         public void clear() {
             moused = false;
+            sceneTracker.clear();
             groupTracker.clear();
             bigSquareTracker.clear();
             smallSquareTracker.clear();
@@ -996,6 +1063,22 @@
             });
         }
 
+        public MouseEventTracker(Scene scene) {
+            scene.setOnMouseEntered(new EventHandler<MouseEvent>() {
+                @Override
+                public void handle(MouseEvent event) {
+                    enteredMe = true;
+                }
+            });
+
+            scene.setOnMouseExited(new EventHandler<MouseEvent>() {
+                @Override
+                public void handle(MouseEvent event) {
+                    exitedMe = true;
+                }
+            });
+        }
+
         public boolean wasClicked() {
             return clicked;
         }
--- a/javafx-ui-common/test/unit/javafx/scene/image/ImageTest.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-common/test/unit/javafx/scene/image/ImageTest.java	Tue Apr 10 09:16:44 2012 -0700
@@ -506,4 +506,49 @@
         final Image image = Image.impl_fromExternalImage(fakeExternalImage);
         verifyLoadedImage(image, 0, 0, false, false, 123, 456);
     }
+
+    @Test(expected=NullPointerException.class)
+    public void createImageFromNullUrlTest() {
+        new Image((String) null);
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void createImageAsyncFromNullUrlTest() {
+        new Image((String) null, true);
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void createImageFromNullInputStreamTest() {
+        new Image((InputStream) null);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void createImageFromEmptyUrlTest() {
+        new Image((String) "");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void createImageAsyncFromEmptyUrlTest() {
+        new Image((String) "", true);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void createImageFromInvalidUrlTest() {
+        new Image((String) ":");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void createImageAsyncFromInvalidUrlTest() {
+        new Image((String) ":", true);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void createImageFromUnsupportedUrlTest() {
+        new Image((String) "unsupported:image.png");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void createImageAsyncFromUnsupportedUrlTest() {
+        new Image((String) "unsupported:image.png", true);
+    }
 }
--- a/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualFlow.java	Mon Apr 09 13:05:50 2012 -0700
+++ b/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/VirtualFlow.java	Tue Apr 10 09:16:44 2012 -0700
@@ -1596,7 +1596,7 @@
 
         if (cell == null) {
             if (pile.size() > 0) {
-                cell = pile.removeFirst();
+                cell = (prefIndex < pile.getFirst().getIndex())? pile.removeLast() : pile.removeFirst();
             } else {
                 cell = createCell.call(this);
             }
--- a/pom.xml	Mon Apr 09 13:05:50 2012 -0700
+++ b/pom.xml	Tue Apr 10 09:16:44 2012 -0700
@@ -22,6 +22,21 @@
         </dependency>
     </dependencies>
     <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.1.2</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>            
+        </plugins>
         <sourceDirectory>src</sourceDirectory>
         <testSourceDirectory>test</testSourceDirectory>
     </build>