]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/NodeEventHandler.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / events / NodeEventHandler.java
diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/NodeEventHandler.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/NodeEventHandler.java
new file mode 100644 (file)
index 0000000..d1f6e6e
--- /dev/null
@@ -0,0 +1,448 @@
+/*******************************************************************************\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
+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.geom.Point2D;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Comparator;\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.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
+ */
+public class NodeEventHandler implements IEventHandler {
+\r
+    private static final boolean DEBUG_EVENTS       = false;\r
+    private static final boolean DEBUG_HANDLER_SORT = false;\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
+     * FocusEvents are propagated to event handlers in undefined order.\r
+     */\r
+    protected ListenerList<IEventHandler> focusListeners         = new ListenerList<IEventHandler>(IEventHandler.class);\r
+\r
+    /**\r
+     * TimeEvents are propagated to events handlers in an undefined order.\r
+     */\r
+    protected ListenerList<IEventHandler> timeListeners          = new ListenerList<IEventHandler>(IEventHandler.class);\r
+\r
+    /**\r
+     * CommandEvents are propagated first to the scene graph focus node, then to\r
+     * event handler nodes in scene graph tree pre-order.\r
+     */\r
+    protected ListenerList<IEventHandler> commandListeners       = new ListenerList<IEventHandler>(IEventHandler.class);\r
+    protected IEventHandler[]             sortedCommandListeners = null;\r
+\r
+    /**\r
+     * KeyEvents are propagated first to the scene graph focus node, then to\r
+     * event handler nodes in scene graph tree pre-order.\r
+     */\r
+    protected ListenerList<IEventHandler> keyListeners           = new ListenerList<IEventHandler>(IEventHandler.class);\r
+    protected IEventHandler[]             sortedKeyListeners     = null;\r
+\r
+    /**\r
+     * MouseEvents are propagated first to the scene graph focus node, then to\r
+     * event handler nodes in scene graph tree pre-order.\r
+     */\r
+    protected ListenerList<IEventHandler> mouseListeners         = new ListenerList<IEventHandler>(IEventHandler.class);\r
+    protected IEventHandler[]             sortedMouseListeners   = null;\r
+
+    /**\r
+     * The scene graph this instance handles event propagation for.\r
+     */\r
+    protected G2DSceneGraph               sg;\r
+    \r
+    protected DragSource ds = new DragSource();\r
+
+    public NodeEventHandler(G2DSceneGraph sg) {\r
+        this.sg = sg;
+    }
+\r
+    private IEventHandler[] sort(IEventHandler[] sort) {\r
+        if (DEBUG_HANDLER_SORT)\r
+            debug("sort " + sort.length + " handlers");\r
+        IEventHandler[] copy = Arrays.copyOf(sort, sort.length);\r
+        Arrays.sort(copy, COMPARATOR);\r
+        return copy;\r
+    }\r
+    \r
+    public void setRootPane(Component rootPane) {\r
+       \r
+       final DragSourceListener dsl = new DragSourceListener() {\r
+                       \r
+                       @Override\r
+                       public void dropActionChanged(DragSourceDragEvent dsde) {\r
+                       }\r
+                       \r
+                       @Override\r
+                       public void dragOver(DragSourceDragEvent dsde) {\r
+                       }\r
+                       \r
+                       @Override\r
+                       public void dragExit(DragSourceEvent dse) {\r
+                       }\r
+                       \r
+                       @Override\r
+                       public void dragEnter(DragSourceDragEvent dsde) {\r
+                       }\r
+                       \r
+                       @Override\r
+                       public void dragDropEnd(DragSourceDropEvent dsde) {\r
+                       }\r
+               };\r
+               ds.createDefaultDragGestureRecognizer(rootPane, DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK, new DragGestureListener() {\r
+                       \r
+                       @Override\r
+                       public void dragGestureRecognized(DragGestureEvent dge) {\r
+                               MouseDragBegin event = new MouseDragBegin(NodeEventHandler.this,\r
+                                               0, 0, 0, 0, 0,\r
+                                               new Point2D.Double(),new Point2D.Double(),\r
+                                               new Point2D.Double(),new Point2D.Double());\r
+                               handleMouseEvent(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
+               ds.addDragSourceListener(dsl);\r
+       \r
+    }\r
+\r
+//    @Override
+//    public void mouseReleased(MouseEvent event) {
+//        Point op = event.getPoint();
+//        for (MouseListener l : mouseListeners.getListeners()) {
+//            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
+//            l.mouseReleased(e);
+//            event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));
+//            if (e.isConsumed())
+//                break;
+//        }
+//    }
+//
+//    @Override
+//    public void mouseMoved(MouseEvent event) {
+//        for (MouseMotionListener l : mouseMotionListeners.getListeners()) {
+//            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
+//            l.mouseMoved(e);
+//            if (e.isConsumed())
+//                break;
+//        }
+//    }
+
+    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 handleMouseEvent(MouseEvent e, int eventType) {\r
+        IEventHandler[] sorted = sortedMouseListeners;\r
+        if (sorted == null)\r
+            sortedMouseListeners = sorted = sort(mouseListeners.getListeners());\r
+        return handleEvent(e, sg.getFocusNode(), sorted);\r
+    }\r
+\r
+    private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) {\r
+        if (focusNode instanceof IEventHandler) {\r
+            IEventHandler h = (IEventHandler) focusNode;\r
+            if (eats(h.getEventMask(), EventTypes.toTypeMask(e))) {\r
+                if (h.handleEvent(e))\r
+                    return true;\r
+            }\r
+        }\r
+        for (IEventHandler l : handlers) {\r
+            if (l.handleEvent(e))\r
+                return true;\r
+        }\r
+        return false;\r
+    }\r
+\r
+    private boolean handleFocusEvent(FocusEvent e) {\r
+        return handleEvent(e, null, focusListeners.getListeners());\r
+    }\r
+\r
+    private boolean handleTimeEvent(TimeEvent e) {\r
+        return handleEvent(e, null, timeListeners.getListeners());\r
+    }\r
+\r
+    private boolean handleCommandEvent(CommandEvent e) {\r
+        IEventHandler[] sorted = sortedCommandListeners;\r
+        if (sorted == null)\r
+            sortedCommandListeners = sorted = sort(commandListeners.getListeners());\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 = sort(keyListeners.getListeners());\r
+        return handleEvent(e, sg.getFocusNode(), sorted);\r
+    }\r
+
+    @Override
+    public int getEventMask() {
+        return EventTypes.AnyMask;
+    }
+
+    @Override
+    public boolean handleEvent(Event e) {\r
+        if (DEBUG_EVENTS)\r
+            debug("handle event: " + e);\r
+
+        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.MouseButtonPressed:\r
+            case EventTypes.MouseButtonReleased:\r
+            case EventTypes.MouseClick:\r
+            case EventTypes.MouseDoubleClick:\r
+            case EventTypes.MouseDragBegin:\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.Time:
+                return handleTimeEvent((TimeEvent) e);\r
+        }\r
+        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
+        }\r
+        if (eats(mask, EventTypes.KeyMask)) {\r
+            keyListeners.add(item);\r
+            sortedKeyListeners = null;\r
+        }\r
+        if (eats(mask, EventTypes.MouseMask)) {\r
+            mouseListeners.add(item);\r
+            sortedMouseListeners = null;\r
+        }\r
+        if (eats(mask, EventTypes.TimeMask)) {\r
+            timeListeners.add(item);\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
+        }\r
+        if (eats(mask, EventTypes.KeyMask)) {\r
+            removed |= keyListeners.remove(item);\r
+            sortedKeyListeners = null;\r
+        }\r
+        if (eats(mask, EventTypes.MouseMask)) {\r
+            removed |= mouseListeners.remove(item);\r
+            sortedMouseListeners = null;\r
+        }\r
+        if (eats(mask, EventTypes.TimeMask)) {\r
+            removed |= timeListeners.remove(item);\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
+}