d1f6e6ef2da50f32399b0b914d4bc02692738e21
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / events / NodeEventHandler.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.scenegraph.g2d.events;
13
14 import java.awt.Component;\r
15 import java.awt.dnd.DnDConstants;\r
16 import java.awt.dnd.DragGestureEvent;\r
17 import java.awt.dnd.DragGestureListener;\r
18 import java.awt.dnd.DragSource;\r
19 import java.awt.dnd.DragSourceDragEvent;\r
20 import java.awt.dnd.DragSourceDropEvent;\r
21 import java.awt.dnd.DragSourceEvent;\r
22 import java.awt.dnd.DragSourceListener;\r
23 import java.awt.geom.Point2D;\r
24 import java.util.ArrayList;\r
25 import java.util.Arrays;\r
26 import java.util.Comparator;\r
27 \r
28 import org.simantics.scenegraph.INode;\r
29 import org.simantics.scenegraph.g2d.G2DFocusManager;\r
30 import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
31 import org.simantics.scenegraph.g2d.IG2DNode;\r
32 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
33 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
34 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
35
36 /**\r
37  * Delivers events (mouse, key, focus, command, time) to scene graph nodes that\r
38  * have registered to receive them.\r
39  * \r
40  * @author Tuukka Lehtonen\r
41  */
42 public class NodeEventHandler implements IEventHandler {
43 \r
44     private static final boolean DEBUG_EVENTS       = false;\r
45     private static final boolean DEBUG_HANDLER_SORT = false;\r
46 \r
47     public static class TreePreOrderComparator implements Comparator<IEventHandler> {\r
48 \r
49         static enum Order {\r
50             ASCENDING,\r
51             DESCENDING\r
52         }\r
53 \r
54         static class Temp {\r
55             ArrayList<INode> path1 = new ArrayList<INode>();\r
56             ArrayList<INode> path2 = new ArrayList<INode>();\r
57         }\r
58 \r
59         private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {\r
60                 protected Temp initialValue() {\r
61                         return new Temp();\r
62                 }\r
63         };\r
64 \r
65         Order order;\r
66 \r
67         public TreePreOrderComparator(Order order) {\r
68             this.order = order;\r
69         }\r
70 \r
71         void getTreePath(INode node, ArrayList<INode> result) {\r
72              result.clear();\r
73              for (INode parent = node.getParent(); parent != null; parent = parent.getParent())\r
74                  result.add(parent);\r
75         }\r
76 \r
77         void notSameGraph(INode o1, INode o2) {\r
78             throw new IllegalStateException("nodes " + o1 + " and " + o2\r
79                     + " not part of same scene graph.\n\t root 1: " + o1.getRootNode() + "\n\troot 2: "\r
80                     + o2.getRootNode());\r
81         }\r
82 \r
83         @Override\r
84         public int compare(IEventHandler e1, IEventHandler e2) {\r
85             if (e1 == e2)\r
86                 return 0;\r
87 \r
88             Temp tmp = temp.get();\r
89             ArrayList<INode> path1 = tmp.path1;\r
90             ArrayList<INode> path2 = tmp.path2;\r
91 \r
92             // Get path to root node for both nodes\r
93             INode o1 = (INode) e1;\r
94             INode o2 = (INode) e2;\r
95             getTreePath(o1, path1);\r
96             getTreePath(o2, path2);\r
97 \r
98             // Sanity checks: nodes part of same scene graph\r
99             INode root1 = path1.isEmpty() ? o1 : path1.get(path1.size() - 1);\r
100             INode root2 = path2.isEmpty() ? o2 : path2.get(path2.size() - 1);\r
101             if (root1 != root2)\r
102                 notSameGraph(o1, o2);\r
103 \r
104             try {\r
105                 // Find first non-matching nodes in the paths starting from the root node\r
106                 int i1 = path1.size() - 1;\r
107                 int i2 = path2.size() - 1;\r
108                 for (; i1 >= 0 && i2 >= 0; --i1, --i2) {\r
109                     INode p1 = path1.get(i1);\r
110                     INode p2 = path2.get(i2);\r
111                     if (p1 != p2) {\r
112                         break;\r
113                     }\r
114                 }\r
115 \r
116                 // Pre-order: a node that is on the tree path of another node is first\r
117                 if (i1 < 0)\r
118                     return Order.ASCENDING == order ? -1 : 1;\r
119                 if (i2 < 0)\r
120                     return Order.ASCENDING == order ? 1 : -1;\r
121 \r
122                 INode n1 = path1.get(i1);\r
123                 INode n2 = path2.get(i2);\r
124                 IG2DNode g1 = n1 instanceof IG2DNode ? (IG2DNode) n1 : null;\r
125                 IG2DNode g2 = n2 instanceof IG2DNode ? (IG2DNode) n2 : null;\r
126                 if (g1 != null && g2 != null) {\r
127                     int z1 = g1.getZIndex();\r
128                     int z2 = g2.getZIndex();\r
129                     int c = compare(z1, z2);\r
130                     return order == Order.ASCENDING ? c : -c;\r
131                 }\r
132                 // Can't sort non-IG2DNodes.\r
133                 return 0;\r
134             } finally {\r
135                 // Don't hold on to objects unnecessarily\r
136                 path1.clear();\r
137                 path2.clear();\r
138             }\r
139         }\r
140 \r
141         private int compare(int v1, int v2) {\r
142             return v1 < v2 ? -1 : (v1 > v2 ? 1 : 0);\r
143         }\r
144     };\r
145 \r
146     TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);\r
147 \r
148     /**\r
149      * FocusEvents are propagated to event handlers in undefined order.\r
150      */\r
151     protected ListenerList<IEventHandler> focusListeners         = new ListenerList<IEventHandler>(IEventHandler.class);\r
152 \r
153     /**\r
154      * TimeEvents are propagated to events handlers in an undefined order.\r
155      */\r
156     protected ListenerList<IEventHandler> timeListeners          = new ListenerList<IEventHandler>(IEventHandler.class);\r
157 \r
158     /**\r
159      * CommandEvents are propagated first to the scene graph focus node, then to\r
160      * event handler nodes in scene graph tree pre-order.\r
161      */\r
162     protected ListenerList<IEventHandler> commandListeners       = new ListenerList<IEventHandler>(IEventHandler.class);\r
163     protected IEventHandler[]             sortedCommandListeners = null;\r
164 \r
165     /**\r
166      * KeyEvents are propagated first to the scene graph focus node, then to\r
167      * event handler nodes in scene graph tree pre-order.\r
168      */\r
169     protected ListenerList<IEventHandler> keyListeners           = new ListenerList<IEventHandler>(IEventHandler.class);\r
170     protected IEventHandler[]             sortedKeyListeners     = null;\r
171 \r
172     /**\r
173      * MouseEvents are propagated first to the scene graph focus node, then to\r
174      * event handler nodes in scene graph tree pre-order.\r
175      */\r
176     protected ListenerList<IEventHandler> mouseListeners         = new ListenerList<IEventHandler>(IEventHandler.class);\r
177     protected IEventHandler[]             sortedMouseListeners   = null;\r
178
179     /**\r
180      * The scene graph this instance handles event propagation for.\r
181      */\r
182     protected G2DSceneGraph               sg;\r
183     \r
184     protected DragSource ds = new DragSource();\r
185
186     public NodeEventHandler(G2DSceneGraph sg) {\r
187         this.sg = sg;
188     }
189 \r
190     private IEventHandler[] sort(IEventHandler[] sort) {\r
191         if (DEBUG_HANDLER_SORT)\r
192             debug("sort " + sort.length + " handlers");\r
193         IEventHandler[] copy = Arrays.copyOf(sort, sort.length);\r
194         Arrays.sort(copy, COMPARATOR);\r
195         return copy;\r
196     }\r
197     \r
198     public void setRootPane(Component rootPane) {\r
199         \r
200         final DragSourceListener dsl = new DragSourceListener() {\r
201                         \r
202                         @Override\r
203                         public void dropActionChanged(DragSourceDragEvent dsde) {\r
204                         }\r
205                         \r
206                         @Override\r
207                         public void dragOver(DragSourceDragEvent dsde) {\r
208                         }\r
209                         \r
210                         @Override\r
211                         public void dragExit(DragSourceEvent dse) {\r
212                         }\r
213                         \r
214                         @Override\r
215                         public void dragEnter(DragSourceDragEvent dsde) {\r
216                         }\r
217                         \r
218                         @Override\r
219                         public void dragDropEnd(DragSourceDropEvent dsde) {\r
220                         }\r
221                 };\r
222                 ds.createDefaultDragGestureRecognizer(rootPane, DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK, new DragGestureListener() {\r
223                         \r
224                         @Override\r
225                         public void dragGestureRecognized(DragGestureEvent dge) {\r
226                                 MouseDragBegin event = new MouseDragBegin(NodeEventHandler.this,\r
227                                                 0, 0, 0, 0, 0,\r
228                                                 new Point2D.Double(),new Point2D.Double(),\r
229                                                 new Point2D.Double(),new Point2D.Double());\r
230                                 handleMouseEvent(event, EventTypes.MouseDragBegin);\r
231                                 if(event.transferable != null) {\r
232                                         ds.startDrag(dge, null, event.transferable, dsl);\r
233                                         if (DEBUG_EVENTS)\r
234                                                 debug("dragGestureRecognized: startDrag " + event.transferable);\r
235                                 }\r
236                         }\r
237                 });\r
238                 ds.addDragSourceListener(dsl);\r
239         \r
240     }\r
241 \r
242 //    @Override
243 //    public void mouseReleased(MouseEvent event) {
244 //        Point op = event.getPoint();
245 //        for (MouseListener l : mouseListeners.getListeners()) {
246 //            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
247 //            l.mouseReleased(e);
248 //            event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));
249 //            if (e.isConsumed())
250 //                break;
251 //        }
252 //    }
253 //
254 //    @Override
255 //    public void mouseMoved(MouseEvent event) {
256 //        for (MouseMotionListener l : mouseMotionListeners.getListeners()) {
257 //            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
258 //            l.mouseMoved(e);
259 //            if (e.isConsumed())
260 //                break;
261 //        }
262 //    }
263
264     public boolean mousePressed(MouseButtonPressedEvent event) {\r
265         G2DFocusManager.INSTANCE.clearFocus();\r
266         try {\r
267 //        Point op = event.getPoint();\r
268 //        for (MouseListener l : mouseListeners.getListeners()) {\r
269 //            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);\r
270 //            l.mousePressed(e);\r
271 //            event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));\r
272 //            if (e.isConsumed())\r
273 //                break;\r
274 //        }\r
275             return false;\r
276         } finally {\r
277             if (sg.getRootPane() != null) {\r
278                 if (G2DFocusManager.INSTANCE.getFocusOwner() == null) {\r
279                     sg.getRootPane().requestFocusInWindow();\r
280                     //sg.getRootPane().repaint(); //TODO : why repaint here? FocusOwner seems to be always null, so this causes unnecessary delays when interacting the canvas.\r
281                 }\r
282             }\r
283         }\r
284     }\r
285 \r
286     private boolean handleMouseEvent(MouseEvent e, int eventType) {\r
287         IEventHandler[] sorted = sortedMouseListeners;\r
288         if (sorted == null)\r
289             sortedMouseListeners = sorted = sort(mouseListeners.getListeners());\r
290         return handleEvent(e, sg.getFocusNode(), sorted);\r
291     }\r
292 \r
293     private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) {\r
294         if (focusNode instanceof IEventHandler) {\r
295             IEventHandler h = (IEventHandler) focusNode;\r
296             if (eats(h.getEventMask(), EventTypes.toTypeMask(e))) {\r
297                 if (h.handleEvent(e))\r
298                     return true;\r
299             }\r
300         }\r
301         for (IEventHandler l : handlers) {\r
302             if (l.handleEvent(e))\r
303                 return true;\r
304         }\r
305         return false;\r
306     }\r
307 \r
308     private boolean handleFocusEvent(FocusEvent e) {\r
309         return handleEvent(e, null, focusListeners.getListeners());\r
310     }\r
311 \r
312     private boolean handleTimeEvent(TimeEvent e) {\r
313         return handleEvent(e, null, timeListeners.getListeners());\r
314     }\r
315 \r
316     private boolean handleCommandEvent(CommandEvent e) {\r
317         IEventHandler[] sorted = sortedCommandListeners;\r
318         if (sorted == null)\r
319             sortedCommandListeners = sorted = sort(commandListeners.getListeners());\r
320         return handleEvent(e, sg.getFocusNode(), sorted);\r
321     }\r
322 \r
323     private boolean handleKeyEvent(KeyEvent e) {\r
324         IEventHandler[] sorted = sortedKeyListeners;\r
325         if (sorted == null)\r
326             sortedKeyListeners = sorted = sort(keyListeners.getListeners());\r
327         return handleEvent(e, sg.getFocusNode(), sorted);\r
328     }\r
329
330     @Override
331     public int getEventMask() {
332         return EventTypes.AnyMask;
333     }
334
335     @Override
336     public boolean handleEvent(Event e) {\r
337         if (DEBUG_EVENTS)\r
338             debug("handle event: " + e);\r
339
340         int eventType = EventTypes.toType(e);
341         switch (eventType) {
342             case EventTypes.Command:\r
343                 return handleCommandEvent((CommandEvent) e);\r
344 \r
345             case EventTypes.FocusGained:\r
346             case EventTypes.FocusLost:\r
347                 return handleFocusEvent((FocusEvent) e);\r
348 \r
349             case EventTypes.KeyPressed:\r
350             case EventTypes.KeyReleased:\r
351                 return handleKeyEvent((KeyEvent) e);\r
352 \r
353             case EventTypes.MouseButtonPressed:\r
354             case EventTypes.MouseButtonReleased:\r
355             case EventTypes.MouseClick:\r
356             case EventTypes.MouseDoubleClick:\r
357             case EventTypes.MouseDragBegin:\r
358             case EventTypes.MouseEnter:\r
359             case EventTypes.MouseExit:\r
360             case EventTypes.MouseMoved:\r
361             case EventTypes.MouseWheel:\r
362                 return handleMouseEvent((MouseEvent) e, eventType);\r
363 \r
364             case EventTypes.Time:
365                 return handleTimeEvent((TimeEvent) e);\r
366         }\r
367         return false;
368     }\r
369 \r
370     public void add(IEventHandler item) {\r
371         if (!(item instanceof IG2DNode))\r
372             throw new IllegalArgumentException("event handler must be an IG2DNode");\r
373 \r
374         int mask = item.getEventMask();\r
375         if (eats(mask, EventTypes.CommandMask)) {\r
376             commandListeners.add(item);\r
377             sortedCommandListeners = null;\r
378         }\r
379         if (eats(mask, EventTypes.FocusMask)) {\r
380             focusListeners.add(item);\r
381         }\r
382         if (eats(mask, EventTypes.KeyMask)) {\r
383             keyListeners.add(item);\r
384             sortedKeyListeners = null;\r
385         }\r
386         if (eats(mask, EventTypes.MouseMask)) {\r
387             mouseListeners.add(item);\r
388             sortedMouseListeners = null;\r
389         }\r
390         if (eats(mask, EventTypes.TimeMask)) {\r
391             timeListeners.add(item);\r
392         }\r
393     }\r
394 \r
395     public boolean remove(IEventHandler item) {\r
396         if (!(item instanceof IG2DNode))\r
397             throw new IllegalArgumentException("event handler must be an IG2DNode");\r
398 \r
399         int mask = item.getEventMask();\r
400         boolean removed = false;\r
401         if (eats(mask, EventTypes.CommandMask)) {\r
402             removed |= commandListeners.remove(item);\r
403             sortedCommandListeners = null;\r
404         }\r
405         if (eats(mask, EventTypes.FocusMask)) {\r
406             removed |= focusListeners.remove(item);\r
407         }\r
408         if (eats(mask, EventTypes.KeyMask)) {\r
409             removed |= keyListeners.remove(item);\r
410             sortedKeyListeners = null;\r
411         }\r
412         if (eats(mask, EventTypes.MouseMask)) {\r
413             removed |= mouseListeners.remove(item);\r
414             sortedMouseListeners = null;\r
415         }\r
416         if (eats(mask, EventTypes.TimeMask)) {\r
417             removed |= timeListeners.remove(item);\r
418         }\r
419         return removed;\r
420     }\r
421 \r
422     private static boolean eats(int handlerMask, int eventTypeMask) {\r
423         return (handlerMask & eventTypeMask) != 0;\r
424     }\r
425 \r
426     private void debug(String msg) {\r
427         System.out.println(getClass().getSimpleName() + ": " + msg);\r
428     }\r
429 \r
430     public void dispose() {\r
431         commandListeners.clear();\r
432         commandListeners = null;\r
433         focusListeners.clear();\r
434         focusListeners = null;\r
435         keyListeners.clear();\r
436         keyListeners = null;\r
437         mouseListeners.clear();\r
438         mouseListeners = null;\r
439         sg = null;\r
440         sortedCommandListeners = null;\r
441         sortedKeyListeners = null;\r
442         sortedMouseListeners = null;\r
443 \r
444         timeListeners.clear();\r
445         timeListeners = null;\r
446     }\r
447 \r
448 }