--- /dev/null
+/*******************************************************************************\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
+}