]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/NodeEventHandler.java
SceneGraph NodeEventHandler should work in headless environments
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / events / NodeEventHandler.java
index ac0e36f73804b8c171db1282dabe05707c6097b9..1c1ac8a37a7b523ace66705188f148c1972ed763 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
 package org.simantics.scenegraph.g2d.events;
 
-import java.awt.Component;\r
-import java.awt.dnd.DnDConstants;\r
-import java.awt.dnd.DragGestureEvent;\r
-import java.awt.dnd.DragGestureListener;\r
-import java.awt.dnd.DragSource;\r
-import java.awt.dnd.DragSourceDragEvent;\r
-import java.awt.dnd.DragSourceDropEvent;\r
-import java.awt.dnd.DragSourceEvent;\r
-import java.awt.dnd.DragSourceListener;\r
-import java.awt.event.InputEvent;\r
-import java.awt.geom.Point2D;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Comparator;\r
-import java.util.List;\r
-\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.g2d.G2DFocusManager;\r
-import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
-import org.simantics.scenegraph.g2d.IG2DNode;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
-import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter;\r
-import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
-
-/**\r
- * Delivers events (mouse, key, focus, command, time) to scene graph nodes that\r
- * have registered to receive them.\r
- * \r
- * @author Tuukka Lehtonen\r
+import java.awt.Component;
+import java.awt.GraphicsEnvironment;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DragGestureEvent;
+import java.awt.dnd.DragGestureListener;
+import java.awt.dnd.DragSource;
+import java.awt.dnd.DragSourceDragEvent;
+import java.awt.dnd.DragSourceDropEvent;
+import java.awt.dnd.DragSourceEvent;
+import java.awt.dnd.DragSourceListener;
+import java.awt.event.InputEvent;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.G2DFocusManager;
+import org.simantics.scenegraph.g2d.G2DSceneGraph;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
+import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter;
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Delivers events (mouse, key, focus, command, time) to scene graph nodes that
+ * have registered to receive them.
+ * 
+ * @author Tuukka Lehtonen
  */
 public class NodeEventHandler implements IEventHandler {
-\r
-    private static final boolean DEBUG_EVENTS       = false;\r
-    private static final boolean DEBUG_HANDLER_SORT = false;\r
-\r
-    private static final IEventHandler[] NONE = {};\r
-\r
-    public static class TreePreOrderComparator implements Comparator<IEventHandler> {\r
-\r
-        static enum Order {\r
-            ASCENDING,\r
-            DESCENDING\r
-        }\r
-\r
-        static class Temp {\r
-            ArrayList<INode> path1 = new ArrayList<INode>();\r
-            ArrayList<INode> path2 = new ArrayList<INode>();\r
-        }\r
-\r
-        private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {\r
-               protected Temp initialValue() {\r
-                       return new Temp();\r
-               }\r
-        };\r
-\r
-        Order order;\r
-\r
-        public TreePreOrderComparator(Order order) {\r
-            this.order = order;\r
-        }\r
-\r
-        void getTreePath(INode node, ArrayList<INode> result) {\r
-             result.clear();\r
-             for (INode parent = node.getParent(); parent != null; parent = parent.getParent())\r
-                 result.add(parent);\r
-        }\r
-\r
-        void notSameGraph(INode o1, INode o2) {\r
-            throw new IllegalStateException("nodes " + o1 + " and " + o2\r
-                    + " not part of same scene graph.\n\t root 1: " + o1.getRootNode() + "\n\troot 2: "\r
-                    + o2.getRootNode());\r
-        }\r
-\r
-        @Override\r
-        public int compare(IEventHandler e1, IEventHandler e2) {\r
-            if (e1 == e2)\r
-                return 0;\r
-\r
-            Temp tmp = temp.get();\r
-            ArrayList<INode> path1 = tmp.path1;\r
-            ArrayList<INode> path2 = tmp.path2;\r
-\r
-            // Get path to root node for both nodes\r
-            INode o1 = (INode) e1;\r
-            INode o2 = (INode) e2;\r
-            getTreePath(o1, path1);\r
-            getTreePath(o2, path2);\r
-\r
-            // Sanity checks: nodes part of same scene graph\r
-            INode root1 = path1.isEmpty() ? o1 : path1.get(path1.size() - 1);\r
-            INode root2 = path2.isEmpty() ? o2 : path2.get(path2.size() - 1);\r
-            if (root1 != root2)\r
-                notSameGraph(o1, o2);\r
-\r
-            try {\r
-                // Find first non-matching nodes in the paths starting from the root node\r
-                int i1 = path1.size() - 1;\r
-                int i2 = path2.size() - 1;\r
-                for (; i1 >= 0 && i2 >= 0; --i1, --i2) {\r
-                    INode p1 = path1.get(i1);\r
-                    INode p2 = path2.get(i2);\r
-                    if (p1 != p2) {\r
-                        break;\r
-                    }\r
-                }\r
-\r
-                // Pre-order: a node that is on the tree path of another node is first\r
-                if (i1 < 0)\r
-                    return Order.ASCENDING == order ? -1 : 1;\r
-                if (i2 < 0)\r
-                    return Order.ASCENDING == order ? 1 : -1;\r
-\r
-                INode n1 = path1.get(i1);\r
-                INode n2 = path2.get(i2);\r
-                IG2DNode g1 = n1 instanceof IG2DNode ? (IG2DNode) n1 : null;\r
-                IG2DNode g2 = n2 instanceof IG2DNode ? (IG2DNode) n2 : null;\r
-                if (g1 != null && g2 != null) {\r
-                    int z1 = g1.getZIndex();\r
-                    int z2 = g2.getZIndex();\r
-                    int c = compare(z1, z2);\r
-                    return order == Order.ASCENDING ? c : -c;\r
-                }\r
-                // Can't sort non-IG2DNodes.\r
-                return 0;\r
-            } finally {\r
-                // Don't hold on to objects unnecessarily\r
-                path1.clear();\r
-                path2.clear();\r
-            }\r
-        }\r
-\r
-        private int compare(int v1, int v2) {\r
-            return v1 < v2 ? -1 : (v1 > v2 ? 1 : 0);\r
-        }\r
-    };\r
-\r
-    TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);\r
-\r
-    /**\r
-     * {@link FocusEvent} are propagated first to the scene graph focus node,\r
-     * then to event handler nodes in scene graph tree pre-order.\r
-     */\r
-    protected List<IEventHandler>         focusListeners         = new ArrayList<IEventHandler>();\r
-    protected IEventHandler[]             sortedFocusListeners   = null;\r
-\r
-    /**\r
-     * {@link TimeEvent} are propagated first to the scene graph focus node,\r
-     * then to event handler nodes in scene graph tree pre-order.\r
-     */\r
-    protected List<IEventHandler>         timeListeners          = new ArrayList<IEventHandler>();\r
-    protected IEventHandler[]             sortedTimeListeners    = null;\r
-\r
-    /**\r
-     * {@link CommandEvent} are propagated first to the scene graph focus node,\r
-     * then to event handler nodes in scene graph tree pre-order.\r
-     */\r
-    protected List<IEventHandler>         commandListeners       = new ArrayList<IEventHandler>();\r
-    protected IEventHandler[]             sortedCommandListeners = null;\r
-\r
-    /**\r
-     * {@link KeyEvent} are propagated first to the scene graph focus node, then\r
-     * to event handler nodes in scene graph tree pre-order.\r
-     */\r
-    protected List<IEventHandler>         keyListeners           = new ArrayList<IEventHandler>();\r
-    protected IEventHandler[]             sortedKeyListeners     = null;\r
-\r
-    /**\r
-     * {@link MouseEvent} are propagated first to the scene graph focus node,\r
-     * then to event handler nodes in scene graph tree pre-order.\r
-     */\r
-    protected List<IEventHandler>         mouseListeners         = new ArrayList<IEventHandler>();\r
-    protected IEventHandler[]             sortedMouseListeners   = null;\r
-\r
-    /**\r
-     * {@link MouseDragBegin} events are propagated first to the scene graph focus node, then\r
-     * to event handler nodes in scene graph tree pre-order.\r
-     */\r
-    protected List<IEventHandler>         mouseDragBeginListeners = new ArrayList<IEventHandler>();\r
-    protected IEventHandler[]             sortedMouseDragBeginListeners = null;\r
-
-    /**\r
-     * The scene graph this instance handles event propagation for.\r
-     */\r
-    protected G2DSceneGraph               sg;\r
-\r
-    /**\r
-     * For proper initiation of native DnD operations within this AWT-based\r
-     * scenegraph system.\r
-     */\r
-    protected DragSource                  ds = new DragSource();\r
-
-    public NodeEventHandler(G2DSceneGraph sg) {\r
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(NodeEventHandler.class);
+
+    private static final boolean DEBUG_EVENTS       = false;
+    private static final boolean DEBUG_HANDLER_SORT = false;
+
+    private static final IEventHandler[] NONE = {};
+
+    public static class TreePreOrderComparator implements Comparator<IEventHandler> {
+
+        static enum Order {
+            ASCENDING,
+            DESCENDING
+        }
+
+        static class Temp {
+            ArrayList<INode> path1 = new ArrayList<INode>();
+            ArrayList<INode> path2 = new ArrayList<INode>();
+        }
+
+        private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {
+               protected Temp initialValue() {
+                       return new Temp();
+               }
+        };
+
+        Order order;
+
+        public TreePreOrderComparator(Order order) {
+            this.order = order;
+        }
+
+        void getTreePath(INode node, ArrayList<INode> result) {
+             result.clear();
+             for (; node != null; node = node.getParent())
+                 result.add(node);
+        }
+
+        void notSameGraph(INode o1, INode o2) {
+            throw new IllegalStateException("nodes " + o1 + " and " + o2
+                    + " not part of same scene graph.\n\t root 1: " + o1.getRootNode() + "\n\troot 2: "
+                    + o2.getRootNode());
+        }
+
+        @Override
+        public int compare(IEventHandler e1, IEventHandler e2) {
+            if (e1 == e2)
+                return 0;
+
+            Temp tmp = temp.get();
+            ArrayList<INode> path1 = tmp.path1;
+            ArrayList<INode> path2 = tmp.path2;
+
+            try {
+               // Get path to root node for both nodes
+               getTreePath((INode) e1, path1);
+               getTreePath((INode) e2, path2);
+
+               // Sanity checks: nodes part of same scene graph
+               if (path1.get(path1.size() - 1) != path2.get(path2.size() - 1))
+                       notSameGraph((INode)e1, (INode)e2);
+
+                // Find first non-matching nodes in the paths starting from the root node
+                int i1 = path1.size() - 1;
+                int i2 = path2.size() - 1;
+                for (; i1 >= 0 && i2 >= 0; --i1, --i2) {
+                    INode p1 = path1.get(i1);
+                    INode p2 = path2.get(i2);
+                    if (p1 != p2) {
+                        break;
+                    }
+                }
+
+                // Pre-order: a node that is on the tree path of another node is first
+                if (i1 < 0)
+                    return Order.ASCENDING == order ? -1 : 1;
+                if (i2 < 0)
+                    return Order.ASCENDING == order ? 1 : -1;
+
+                return compare(path1.get(i1), path2.get(i2));
+            } finally {
+                // Don't hold on to objects unnecessarily
+                path1.clear();
+                path2.clear();
+            }
+        }
+        
+        private int compare(INode n1, INode n2) {
+               if(n1 instanceof IG2DNode) {
+                       if(n2 instanceof IG2DNode) {
+                       int z1 = ((IG2DNode)n1).getZIndex();
+                       int z2 = ((IG2DNode)n2).getZIndex();
+                       int c = Integer.compare(z1, z2);
+                       return order == Order.ASCENDING ? c : -c;
+                       }
+                       else
+                               return -1; // sort IG2DNodes before non-IG2DNodes
+            }
+               else {
+                       if(n2 instanceof IG2DNode)
+                               return 1;
+                       else
+                               return 0; // all non-IG2DNodes are equal in comparison
+               }
+        }
+    };
+
+    TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);
+
+    /**
+     * {@link FocusEvent} are propagated first to the scene graph focus node,
+     * then to event handler nodes in scene graph tree pre-order.
+     */
+    protected List<IEventHandler>         focusListeners         = new ArrayList<IEventHandler>();
+    protected IEventHandler[]             sortedFocusListeners   = null;
+
+    /**
+     * {@link TimeEvent} are propagated first to the scene graph focus node,
+     * then to event handler nodes in scene graph tree pre-order.
+     */
+    protected List<IEventHandler>         timeListeners          = new ArrayList<IEventHandler>();
+    protected IEventHandler[]             sortedTimeListeners    = null;
+
+    /**
+     * {@link CommandEvent} are propagated first to the scene graph focus node,
+     * then to event handler nodes in scene graph tree pre-order.
+     */
+    protected List<IEventHandler>         commandListeners       = new ArrayList<IEventHandler>();
+    protected IEventHandler[]             sortedCommandListeners = null;
+
+    /**
+     * {@link KeyEvent} are propagated first to the scene graph focus node, then
+     * to event handler nodes in scene graph tree pre-order.
+     */
+    protected List<IEventHandler>         keyListeners           = new ArrayList<IEventHandler>();
+    protected IEventHandler[]             sortedKeyListeners     = null;
+
+    /**
+     * {@link MouseEvent} are propagated first to the scene graph focus node,
+     * then to event handler nodes in scene graph tree pre-order.
+     */
+    protected List<IEventHandler>         mouseListeners         = new ArrayList<IEventHandler>();
+    protected IEventHandler[]             sortedMouseListeners   = null;
+
+    /**
+     * {@link MouseDragBegin} events are propagated first to the scene graph focus node, then
+     * to event handler nodes in scene graph tree pre-order.
+     */
+    protected List<IEventHandler>         mouseDragBeginListeners = new ArrayList<IEventHandler>();
+    protected IEventHandler[]             sortedMouseDragBeginListeners = null;
+
+    /**
+     * The scene graph this instance handles event propagation for.
+     */
+    protected G2DSceneGraph               sg;
+
+    public NodeEventHandler(G2DSceneGraph sg) {
         this.sg = sg;
     }
-\r
-    @SuppressWarnings("unused")\r
-    private IEventHandler[] sort(IEventHandler[] sort) {\r
-        if (DEBUG_HANDLER_SORT)\r
-            debug("copy sort " + sort.length + " handlers");\r
-        return sortInplace(Arrays.copyOf(sort, sort.length));\r
-    }\r
-\r
-    private IEventHandler[] sortInplace(IEventHandler[] sort) {\r
-        if (DEBUG_HANDLER_SORT)\r
-            debug("in-place sort " + sort.length + " handlers");\r
-        Arrays.sort(sort, COMPARATOR);\r
-        return sort;\r
-    }\r
-\r
-    public void setRootPane(Component rootPane) {\r
-        final DragSourceListener dsl = new DragSourceListener() {\r
-            @Override\r
-            public void dropActionChanged(DragSourceDragEvent dsde) {\r
-            }\r
-            @Override\r
-            public void dragOver(DragSourceDragEvent dsde) {\r
-            }\r
-            @Override\r
-            public void dragExit(DragSourceEvent dse) {\r
-            }\r
-            @Override\r
-            public void dragEnter(DragSourceDragEvent dsde) {\r
-            }\r
-            @Override\r
-            public void dragDropEnd(DragSourceDropEvent dsde) {\r
-            }\r
-        };\r
-        DragGestureListener dgl = new DragGestureListener() {\r
-            @Override\r
-            public void dragGestureRecognized(DragGestureEvent dge) {\r
-                InputEvent ie = dge.getTriggerEvent();\r
-                if (ie instanceof java.awt.event.MouseEvent) {\r
-                    java.awt.event.MouseEvent e = (java.awt.event.MouseEvent) ie;\r
-                    Point2D controlPos = AWTMouseEventAdapter.getControlPosition(e);\r
-                    MouseDragBegin event = new MouseDragBegin(NodeEventHandler.this,\r
-                            e.getWhen(), 0,\r
-                            AWTMouseEventAdapter.getButtonStatus(e),\r
-                            AWTMouseEventAdapter.getStateMask(e),\r
-                            AWTMouseEventAdapter.getMouseButton(e),\r
-                            // TODO: fix canvas position if necessary\r
-                            new Point2D.Double(),\r
-                            controlPos,\r
-                            controlPos,\r
-                            AWTMouseEventAdapter.getScreenPosition(e));\r
-\r
-                    // Send MouseDragBegin to the scenegraph and see\r
-                    // if anyone sets event.transferable to start DnD.\r
-                    handleMouseDragBeginEvent(event, EventTypes.MouseDragBegin);\r
-                    if (event.transferable != null) {\r
-                        ds.startDrag(dge, null, event.transferable, dsl);\r
-                        if (DEBUG_EVENTS)\r
-                            debug("dragGestureRecognized: startDrag " + event.transferable);\r
-                    }\r
-                }\r
-            }\r
-        };\r
-        ds.createDefaultDragGestureRecognizer(\r
-                rootPane,\r
-                DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK,\r
-                dgl);\r
-        ds.addDragSourceListener(dsl);\r
-    }\r
-
-    public boolean mousePressed(MouseButtonPressedEvent event) {\r
-        G2DFocusManager.INSTANCE.clearFocus();\r
-        try {\r
-//        Point op = event.getPoint();\r
-//        for (MouseListener l : mouseListeners.getListeners()) {\r
-//            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);\r
-//            l.mousePressed(e);\r
-//            event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));\r
-//            if (e.isConsumed())\r
-//                break;\r
-//        }\r
-            return false;\r
-        } finally {\r
-            if (sg.getRootPane() != null) {\r
-                if (G2DFocusManager.INSTANCE.getFocusOwner() == null) {\r
-                    sg.getRootPane().requestFocusInWindow();\r
-                    //sg.getRootPane().repaint(); //TODO : why repaint here? FocusOwner seems to be always null, so this causes unnecessary delays when interacting the canvas.\r
-                }\r
-            }\r
-        }\r
-    }\r
-\r
-    private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) {\r
-        int typeMask = EventTypes.toTypeMask(e);\r
-        if (focusNode instanceof IEventHandler) {\r
-            IEventHandler h = (IEventHandler) focusNode;\r
-            if (eats(h.getEventMask(), typeMask)) {\r
-                if (h.handleEvent(e))\r
-                    return true;\r
-            }\r
-        }\r
-        for (IEventHandler l : handlers) {\r
-            if (eats(l.getEventMask(), typeMask)) {\r
-                if (l.handleEvent(e))\r
-                    return true;\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    private boolean handleMouseEvent(MouseEvent e, int eventType) {\r
-        IEventHandler[] sorted = sortedMouseListeners;\r
-        if (sorted == null)\r
-            sortedMouseListeners = sorted = sortInplace(mouseListeners.toArray(NONE));\r
-        return handleEvent(e, sg.getFocusNode(), sorted);\r
-    }\r
-\r
-    private boolean handleMouseDragBeginEvent(MouseEvent e, int eventType) {\r
-        IEventHandler[] sorted = sortedMouseDragBeginListeners;\r
-        if (sorted == null)\r
-            sortedMouseDragBeginListeners = sorted = sortInplace(mouseDragBeginListeners.toArray(NONE));\r
-        // Give null for focusNode because we want to propagate\r
-        // this event in scene tree pre-order only.\r
-        return handleEvent(e, null, sorted);\r
-    }\r
-\r
-    private boolean handleFocusEvent(FocusEvent e) {\r
-        IEventHandler[] sorted = sortedFocusListeners;\r
-        if (sorted == null)\r
-            sortedFocusListeners = sorted = sortInplace(focusListeners.toArray(NONE));\r
-        return handleEvent(e, null, sorted);\r
-    }\r
-\r
-    private boolean handleTimeEvent(TimeEvent e) {\r
-        IEventHandler[] sorted = sortedTimeListeners;\r
-        if (sorted == null)\r
-            sortedTimeListeners = sorted = sortInplace(timeListeners.toArray(NONE));\r
-        return handleEvent(e, null, sorted);\r
-    }\r
-\r
-    private boolean handleCommandEvent(CommandEvent e) {\r
-        IEventHandler[] sorted = sortedCommandListeners;\r
-        if (sorted == null)\r
-            sortedCommandListeners = sorted = sortInplace(commandListeners.toArray(NONE));\r
-        return handleEvent(e, sg.getFocusNode(), sorted);\r
-    }\r
-\r
-    private boolean handleKeyEvent(KeyEvent e) {\r
-        IEventHandler[] sorted = sortedKeyListeners;\r
-        if (sorted == null)\r
-            sortedKeyListeners = sorted = sortInplace(keyListeners.toArray(NONE));\r
-        return handleEvent(e, sg.getFocusNode(), sorted);\r
-    }\r
+
+    @SuppressWarnings("unused")
+    private IEventHandler[] sort(IEventHandler[] sort) {
+        if (DEBUG_HANDLER_SORT)
+            debug("copy sort " + sort.length + " handlers");
+        return sortInplace(Arrays.copyOf(sort, sort.length));
+    }
+
+    private IEventHandler[] sortInplace(IEventHandler[] sort) {
+        if (DEBUG_HANDLER_SORT)
+            debug("in-place sort " + sort.length + " handlers");
+        Arrays.sort(sort, COMPARATOR);
+        return sort;
+    }
+
+    public void setRootPane(Component rootPane) {
+        if (GraphicsEnvironment.isHeadless()) {
+            LOGGER.info("Disabling DragSource in headless environments");
+            return;
+        }
+        final DragSource ds = new DragSource();
+        final DragSourceListener dsl = new DragSourceListener() {
+            @Override
+            public void dropActionChanged(DragSourceDragEvent dsde) {
+            }
+            @Override
+            public void dragOver(DragSourceDragEvent dsde) {
+            }
+            @Override
+            public void dragExit(DragSourceEvent dse) {
+            }
+            @Override
+            public void dragEnter(DragSourceDragEvent dsde) {
+            }
+            @Override
+            public void dragDropEnd(DragSourceDropEvent dsde) {
+            }
+        };
+        DragGestureListener dgl = new DragGestureListener() {
+            @Override
+            public void dragGestureRecognized(DragGestureEvent dge) {
+                InputEvent ie = dge.getTriggerEvent();
+                if (ie instanceof java.awt.event.MouseEvent) {
+                    java.awt.event.MouseEvent e = (java.awt.event.MouseEvent) ie;
+                    Point2D controlPos = AWTMouseEventAdapter.getControlPosition(e);
+                    MouseDragBegin event = new MouseDragBegin(NodeEventHandler.this,
+                            e.getWhen(), 0,
+                            AWTMouseEventAdapter.getButtonStatus(e),
+                            AWTMouseEventAdapter.getStateMask(e),
+                            AWTMouseEventAdapter.getMouseButton(e),
+                            // TODO: fix canvas position if necessary
+                            new Point2D.Double(),
+                            controlPos,
+                            controlPos,
+                            AWTMouseEventAdapter.getScreenPosition(e));
+
+                    // Send MouseDragBegin to the scenegraph and see
+                    // if anyone sets event.transferable to start DnD.
+                    handleMouseDragBeginEvent(event, EventTypes.MouseDragBegin);
+                    if (event.transferable != null) {
+                        ds.startDrag(dge, null, event.transferable, dsl);
+                        if (DEBUG_EVENTS)
+                            debug("dragGestureRecognized: startDrag " + event.transferable);
+                    }
+                }
+            }
+        };
+        ds.createDefaultDragGestureRecognizer(
+                rootPane,
+                DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK,
+                dgl);
+        ds.addDragSourceListener(dsl);
+    }
+
+    public boolean mousePressed(MouseButtonPressedEvent event) {
+        G2DFocusManager.INSTANCE.clearFocus();
+        try {
+//        Point op = event.getPoint();
+//        for (MouseListener l : mouseListeners.getListeners()) {
+//            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
+//            l.mousePressed(e);
+//            event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));
+//            if (e.isConsumed())
+//                break;
+//        }
+            return false;
+        } finally {
+            if (sg.getRootPane() != null) {
+                if (G2DFocusManager.INSTANCE.getFocusOwner() == null) {
+                    sg.getRootPane().requestFocusInWindow();
+                    //sg.getRootPane().repaint(); //TODO : why repaint here? FocusOwner seems to be always null, so this causes unnecessary delays when interacting the canvas.
+                }
+            }
+        }
+    }
+
+    private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) {
+        int typeMask = EventTypes.toTypeMask(e);
+        if (focusNode instanceof IEventHandler) {
+            IEventHandler h = (IEventHandler) focusNode;
+            if (eats(h.getEventMask(), typeMask)) {
+                if (h.handleEvent(e))
+                    return true;
+            }
+        }
+        for (IEventHandler l : handlers) {
+            if (eats(l.getEventMask(), typeMask)) {
+                if (l.handleEvent(e))
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean handleMouseEvent(MouseEvent e, int eventType) {
+        IEventHandler[] sorted = sortedMouseListeners;
+        if (sorted == null)
+            sortedMouseListeners = sorted = sortInplace(mouseListeners.toArray(NONE));
+        return handleEvent(e, sg.getFocusNode(), sorted);
+    }
+
+    private boolean handleMouseDragBeginEvent(MouseEvent e, int eventType) {
+        IEventHandler[] sorted = sortedMouseDragBeginListeners;
+        if (sorted == null)
+            sortedMouseDragBeginListeners = sorted = sortInplace(mouseDragBeginListeners.toArray(NONE));
+        // Give null for focusNode because we want to propagate
+        // this event in scene tree pre-order only.
+        return handleEvent(e, null, sorted);
+    }
+
+    private boolean handleFocusEvent(FocusEvent e) {
+        IEventHandler[] sorted = sortedFocusListeners;
+        if (sorted == null)
+            sortedFocusListeners = sorted = sortInplace(focusListeners.toArray(NONE));
+        return handleEvent(e, null, sorted);
+    }
+
+    private boolean handleTimeEvent(TimeEvent e) {
+        IEventHandler[] sorted = sortedTimeListeners;
+        if (sorted == null)
+            sortedTimeListeners = sorted = sortInplace(timeListeners.toArray(NONE));
+        return handleEvent(e, null, sorted);
+    }
+
+    private boolean handleCommandEvent(CommandEvent e) {
+        IEventHandler[] sorted = sortedCommandListeners;
+        if (sorted == null)
+            sortedCommandListeners = sorted = sortInplace(commandListeners.toArray(NONE));
+        return handleEvent(e, sg.getFocusNode(), sorted);
+    }
+
+    private boolean handleKeyEvent(KeyEvent e) {
+        IEventHandler[] sorted = sortedKeyListeners;
+        if (sorted == null)
+            sortedKeyListeners = sorted = sortInplace(keyListeners.toArray(NONE));
+        return handleEvent(e, sg.getFocusNode(), sorted);
+    }
 
     @Override
     public int getEventMask() {
@@ -365,130 +369,130 @@ public class NodeEventHandler implements IEventHandler {
     }
 
     @Override
-    public boolean handleEvent(Event e) {\r
-        if (DEBUG_EVENTS)\r
-            debug("handle event: " + e);\r
+    public boolean handleEvent(Event e) {
+        if (DEBUG_EVENTS)
+            debug("handle event: " + e);
 
         int eventType = EventTypes.toType(e);
         switch (eventType) {
-            case EventTypes.Command:\r
-                return handleCommandEvent((CommandEvent) e);\r
-\r
-            case EventTypes.FocusGained:\r
-            case EventTypes.FocusLost:\r
-                return handleFocusEvent((FocusEvent) e);\r
-\r
-            case EventTypes.KeyPressed:\r
-            case EventTypes.KeyReleased:\r
-                return handleKeyEvent((KeyEvent) e);\r
-\r
-            case EventTypes.MouseDragBegin:\r
-                return handleMouseDragBeginEvent((MouseEvent) e, eventType);\r
-\r
-            case EventTypes.MouseButtonPressed:\r
-            case EventTypes.MouseButtonReleased:\r
-            case EventTypes.MouseClick:\r
-            case EventTypes.MouseDoubleClick:\r
-            case EventTypes.MouseEnter:\r
-            case EventTypes.MouseExit:\r
-            case EventTypes.MouseMoved:\r
-            case EventTypes.MouseWheel:\r
-                return handleMouseEvent((MouseEvent) e, eventType);\r
-\r
+            case EventTypes.Command:
+                return handleCommandEvent((CommandEvent) e);
+
+            case EventTypes.FocusGained:
+            case EventTypes.FocusLost:
+                return handleFocusEvent((FocusEvent) e);
+
+            case EventTypes.KeyPressed:
+            case EventTypes.KeyReleased:
+                return handleKeyEvent((KeyEvent) e);
+
+            case EventTypes.MouseDragBegin:
+                return handleMouseDragBeginEvent((MouseEvent) e, eventType);
+
+            case EventTypes.MouseButtonPressed:
+            case EventTypes.MouseButtonReleased:
+            case EventTypes.MouseClick:
+            case EventTypes.MouseDoubleClick:
+            case EventTypes.MouseEnter:
+            case EventTypes.MouseExit:
+            case EventTypes.MouseMoved:
+            case EventTypes.MouseWheel:
+                return handleMouseEvent((MouseEvent) e, eventType);
+
             case EventTypes.Time:
-                return handleTimeEvent((TimeEvent) e);\r
-        }\r
+                return handleTimeEvent((TimeEvent) e);
+        }
         return false;
-    }\r
-\r
-    public void add(IEventHandler item) {\r
-        if (!(item instanceof IG2DNode))\r
-            throw new IllegalArgumentException("event handler must be an IG2DNode");\r
-\r
-        int mask = item.getEventMask();\r
-        if (eats(mask, EventTypes.CommandMask)) {\r
-            commandListeners.add(item);\r
-            sortedCommandListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.FocusMask)) {\r
-            focusListeners.add(item);\r
-            sortedFocusListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.KeyMask)) {\r
-            keyListeners.add(item);\r
-            sortedKeyListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.MouseDragBeginMask)) {\r
-            mouseDragBeginListeners.add(item);\r
-            sortedMouseDragBeginListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {\r
-            mouseListeners.add(item);\r
-            sortedMouseListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.TimeMask)) {\r
-            timeListeners.add(item);\r
-            sortedTimeListeners = null;\r
-        }\r
-    }\r
-\r
-    public boolean remove(IEventHandler item) {\r
-        if (!(item instanceof IG2DNode))\r
-            throw new IllegalArgumentException("event handler must be an IG2DNode");\r
-\r
-        int mask = item.getEventMask();\r
-        boolean removed = false;\r
-        if (eats(mask, EventTypes.CommandMask)) {\r
-            removed |= commandListeners.remove(item);\r
-            sortedCommandListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.FocusMask)) {\r
-            removed |= focusListeners.remove(item);\r
-            sortedFocusListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.KeyMask)) {\r
-            removed |= keyListeners.remove(item);\r
-            sortedKeyListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.MouseDragBeginMask)) {\r
-            removed |= mouseDragBeginListeners.remove(item);\r
-            sortedMouseDragBeginListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {\r
-            removed |= mouseListeners.remove(item);\r
-            sortedMouseListeners = null;\r
-        }\r
-        if (eats(mask, EventTypes.TimeMask)) {\r
-            removed |= timeListeners.remove(item);\r
-            sortedTimeListeners = null;\r
-        }\r
-        return removed;\r
-    }\r
-\r
-    private static boolean eats(int handlerMask, int eventTypeMask) {\r
-        return (handlerMask & eventTypeMask) != 0;\r
-    }\r
-\r
-    private void debug(String msg) {\r
-        System.out.println(getClass().getSimpleName() + ": " + msg);\r
-    }\r
-\r
-    public void dispose() {\r
-        commandListeners.clear();\r
-        commandListeners = null;\r
-        focusListeners.clear();\r
-        focusListeners = null;\r
-        keyListeners.clear();\r
-        keyListeners = null;\r
-        mouseListeners.clear();\r
-        mouseListeners = null;\r
-        sg = null;\r
-        sortedCommandListeners = null;\r
-        sortedKeyListeners = null;\r
-        sortedMouseListeners = null;\r
-\r
-        timeListeners.clear();\r
-        timeListeners = null;\r
-    }\r
-\r
+    }
+
+    public void add(IEventHandler item) {
+        if (!(item instanceof IG2DNode))
+            throw new IllegalArgumentException("event handler must be an IG2DNode");
+
+        int mask = item.getEventMask();
+        if (eats(mask, EventTypes.CommandMask)) {
+            commandListeners.add(item);
+            sortedCommandListeners = null;
+        }
+        if (eats(mask, EventTypes.FocusMask)) {
+            focusListeners.add(item);
+            sortedFocusListeners = null;
+        }
+        if (eats(mask, EventTypes.KeyMask)) {
+            keyListeners.add(item);
+            sortedKeyListeners = null;
+        }
+        if (eats(mask, EventTypes.MouseDragBeginMask)) {
+            mouseDragBeginListeners.add(item);
+            sortedMouseDragBeginListeners = null;
+        }
+        if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
+            mouseListeners.add(item);
+            sortedMouseListeners = null;
+        }
+        if (eats(mask, EventTypes.TimeMask)) {
+            timeListeners.add(item);
+            sortedTimeListeners = null;
+        }
+    }
+
+    public boolean remove(IEventHandler item) {
+        if (!(item instanceof IG2DNode))
+            throw new IllegalArgumentException("event handler must be an IG2DNode");
+
+        int mask = item.getEventMask();
+        boolean removed = false;
+        if (eats(mask, EventTypes.CommandMask)) {
+            removed |= commandListeners.remove(item);
+            sortedCommandListeners = null;
+        }
+        if (eats(mask, EventTypes.FocusMask)) {
+            removed |= focusListeners.remove(item);
+            sortedFocusListeners = null;
+        }
+        if (eats(mask, EventTypes.KeyMask)) {
+            removed |= keyListeners.remove(item);
+            sortedKeyListeners = null;
+        }
+        if (eats(mask, EventTypes.MouseDragBeginMask)) {
+            removed |= mouseDragBeginListeners.remove(item);
+            sortedMouseDragBeginListeners = null;
+        }
+        if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
+            removed |= mouseListeners.remove(item);
+            sortedMouseListeners = null;
+        }
+        if (eats(mask, EventTypes.TimeMask)) {
+            removed |= timeListeners.remove(item);
+            sortedTimeListeners = null;
+        }
+        return removed;
+    }
+
+    private static boolean eats(int handlerMask, int eventTypeMask) {
+        return (handlerMask & eventTypeMask) != 0;
+    }
+
+    private void debug(String msg) {
+        System.out.println(getClass().getSimpleName() + ": " + msg);
+    }
+
+    public void dispose() {
+        commandListeners.clear();
+        commandListeners = null;
+        focusListeners.clear();
+        focusListeners = null;
+        keyListeners.clear();
+        keyListeners = null;
+        mouseListeners.clear();
+        mouseListeners = null;
+        sg = null;
+        sortedCommandListeners = null;
+        sortedKeyListeners = null;
+        sortedMouseListeners = null;
+
+        timeListeners.clear();
+        timeListeners = null;
+    }
+
 }