Sync git svn branch with SVN repository r33269.
[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.event.InputEvent;\r
24 import java.awt.geom.Point2D;\r
25 import java.util.ArrayList;\r
26 import java.util.Arrays;\r
27 import java.util.Comparator;\r
28 import java.util.List;\r
29 \r
30 import org.simantics.scenegraph.INode;\r
31 import org.simantics.scenegraph.g2d.G2DFocusManager;\r
32 import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
33 import org.simantics.scenegraph.g2d.IG2DNode;\r
34 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
35 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
36 import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter;\r
37 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
38
39 /**\r
40  * Delivers events (mouse, key, focus, command, time) to scene graph nodes that\r
41  * have registered to receive them.\r
42  * \r
43  * @author Tuukka Lehtonen\r
44  */
45 public class NodeEventHandler implements IEventHandler {
46 \r
47     private static final boolean DEBUG_EVENTS       = false;\r
48     private static final boolean DEBUG_HANDLER_SORT = false;\r
49 \r
50     private static final IEventHandler[] NONE = {};\r
51 \r
52     public static class TreePreOrderComparator implements Comparator<IEventHandler> {\r
53 \r
54         static enum Order {\r
55             ASCENDING,\r
56             DESCENDING\r
57         }\r
58 \r
59         static class Temp {\r
60             ArrayList<INode> path1 = new ArrayList<INode>();\r
61             ArrayList<INode> path2 = new ArrayList<INode>();\r
62         }\r
63 \r
64         private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {\r
65                 protected Temp initialValue() {\r
66                         return new Temp();\r
67                 }\r
68         };\r
69 \r
70         Order order;\r
71 \r
72         public TreePreOrderComparator(Order order) {\r
73             this.order = order;\r
74         }\r
75 \r
76         void getTreePath(INode node, ArrayList<INode> result) {\r
77              result.clear();\r
78              for (INode parent = node.getParent(); parent != null; parent = parent.getParent())\r
79                  result.add(parent);\r
80         }\r
81 \r
82         void notSameGraph(INode o1, INode o2) {\r
83             throw new IllegalStateException("nodes " + o1 + " and " + o2\r
84                     + " not part of same scene graph.\n\t root 1: " + o1.getRootNode() + "\n\troot 2: "\r
85                     + o2.getRootNode());\r
86         }\r
87 \r
88         @Override\r
89         public int compare(IEventHandler e1, IEventHandler e2) {\r
90             if (e1 == e2)\r
91                 return 0;\r
92 \r
93             Temp tmp = temp.get();\r
94             ArrayList<INode> path1 = tmp.path1;\r
95             ArrayList<INode> path2 = tmp.path2;\r
96 \r
97             // Get path to root node for both nodes\r
98             INode o1 = (INode) e1;\r
99             INode o2 = (INode) e2;\r
100             getTreePath(o1, path1);\r
101             getTreePath(o2, path2);\r
102 \r
103             // Sanity checks: nodes part of same scene graph\r
104             INode root1 = path1.isEmpty() ? o1 : path1.get(path1.size() - 1);\r
105             INode root2 = path2.isEmpty() ? o2 : path2.get(path2.size() - 1);\r
106             if (root1 != root2)\r
107                 notSameGraph(o1, o2);\r
108 \r
109             try {\r
110                 // Find first non-matching nodes in the paths starting from the root node\r
111                 int i1 = path1.size() - 1;\r
112                 int i2 = path2.size() - 1;\r
113                 for (; i1 >= 0 && i2 >= 0; --i1, --i2) {\r
114                     INode p1 = path1.get(i1);\r
115                     INode p2 = path2.get(i2);\r
116                     if (p1 != p2) {\r
117                         break;\r
118                     }\r
119                 }\r
120 \r
121                 // Pre-order: a node that is on the tree path of another node is first\r
122                 if (i1 < 0)\r
123                     return Order.ASCENDING == order ? -1 : 1;\r
124                 if (i2 < 0)\r
125                     return Order.ASCENDING == order ? 1 : -1;\r
126 \r
127                 INode n1 = path1.get(i1);\r
128                 INode n2 = path2.get(i2);\r
129                 IG2DNode g1 = n1 instanceof IG2DNode ? (IG2DNode) n1 : null;\r
130                 IG2DNode g2 = n2 instanceof IG2DNode ? (IG2DNode) n2 : null;\r
131                 if (g1 != null && g2 != null) {\r
132                     int z1 = g1.getZIndex();\r
133                     int z2 = g2.getZIndex();\r
134                     int c = compare(z1, z2);\r
135                     return order == Order.ASCENDING ? c : -c;\r
136                 }\r
137                 // Can't sort non-IG2DNodes.\r
138                 return 0;\r
139             } finally {\r
140                 // Don't hold on to objects unnecessarily\r
141                 path1.clear();\r
142                 path2.clear();\r
143             }\r
144         }\r
145 \r
146         private int compare(int v1, int v2) {\r
147             return v1 < v2 ? -1 : (v1 > v2 ? 1 : 0);\r
148         }\r
149     };\r
150 \r
151     TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);\r
152 \r
153     /**\r
154      * {@link FocusEvent} are propagated first to the scene graph focus node,\r
155      * then to event handler nodes in scene graph tree pre-order.\r
156      */\r
157     protected List<IEventHandler>         focusListeners         = new ArrayList<IEventHandler>();\r
158     protected IEventHandler[]             sortedFocusListeners   = null;\r
159 \r
160     /**\r
161      * {@link TimeEvent} are propagated first to the scene graph focus node,\r
162      * then to event handler nodes in scene graph tree pre-order.\r
163      */\r
164     protected List<IEventHandler>         timeListeners          = new ArrayList<IEventHandler>();\r
165     protected IEventHandler[]             sortedTimeListeners    = null;\r
166 \r
167     /**\r
168      * {@link CommandEvent} are propagated first to the scene graph focus node,\r
169      * then to event handler nodes in scene graph tree pre-order.\r
170      */\r
171     protected List<IEventHandler>         commandListeners       = new ArrayList<IEventHandler>();\r
172     protected IEventHandler[]             sortedCommandListeners = null;\r
173 \r
174     /**\r
175      * {@link KeyEvent} are propagated first to the scene graph focus node, then\r
176      * to event handler nodes in scene graph tree pre-order.\r
177      */\r
178     protected List<IEventHandler>         keyListeners           = new ArrayList<IEventHandler>();\r
179     protected IEventHandler[]             sortedKeyListeners     = null;\r
180 \r
181     /**\r
182      * {@link MouseEvent} are propagated first to the scene graph focus node,\r
183      * then to event handler nodes in scene graph tree pre-order.\r
184      */\r
185     protected List<IEventHandler>         mouseListeners         = new ArrayList<IEventHandler>();\r
186     protected IEventHandler[]             sortedMouseListeners   = null;\r
187 \r
188     /**\r
189      * {@link MouseDragBegin} events are propagated first to the scene graph focus node, then\r
190      * to event handler nodes in scene graph tree pre-order.\r
191      */\r
192     protected List<IEventHandler>         mouseDragBeginListeners = new ArrayList<IEventHandler>();\r
193     protected IEventHandler[]             sortedMouseDragBeginListeners = null;\r
194
195     /**\r
196      * The scene graph this instance handles event propagation for.\r
197      */\r
198     protected G2DSceneGraph               sg;\r
199 \r
200     /**\r
201      * For proper initiation of native DnD operations within this AWT-based\r
202      * scenegraph system.\r
203      */\r
204     protected DragSource                  ds = new DragSource();\r
205
206     public NodeEventHandler(G2DSceneGraph sg) {\r
207         this.sg = sg;
208     }
209 \r
210     @SuppressWarnings("unused")\r
211     private IEventHandler[] sort(IEventHandler[] sort) {\r
212         if (DEBUG_HANDLER_SORT)\r
213             debug("copy sort " + sort.length + " handlers");\r
214         return sortInplace(Arrays.copyOf(sort, sort.length));\r
215     }\r
216 \r
217     private IEventHandler[] sortInplace(IEventHandler[] sort) {\r
218         if (DEBUG_HANDLER_SORT)\r
219             debug("in-place sort " + sort.length + " handlers");\r
220         Arrays.sort(sort, COMPARATOR);\r
221         return sort;\r
222     }\r
223 \r
224     public void setRootPane(Component rootPane) {\r
225         final DragSourceListener dsl = new DragSourceListener() {\r
226             @Override\r
227             public void dropActionChanged(DragSourceDragEvent dsde) {\r
228             }\r
229             @Override\r
230             public void dragOver(DragSourceDragEvent dsde) {\r
231             }\r
232             @Override\r
233             public void dragExit(DragSourceEvent dse) {\r
234             }\r
235             @Override\r
236             public void dragEnter(DragSourceDragEvent dsde) {\r
237             }\r
238             @Override\r
239             public void dragDropEnd(DragSourceDropEvent dsde) {\r
240             }\r
241         };\r
242         DragGestureListener dgl = new DragGestureListener() {\r
243             @Override\r
244             public void dragGestureRecognized(DragGestureEvent dge) {\r
245                 InputEvent ie = dge.getTriggerEvent();\r
246                 if (ie instanceof java.awt.event.MouseEvent) {\r
247                     java.awt.event.MouseEvent e = (java.awt.event.MouseEvent) ie;\r
248                     Point2D controlPos = AWTMouseEventAdapter.getControlPosition(e);\r
249                     MouseDragBegin event = new MouseDragBegin(NodeEventHandler.this,\r
250                             e.getWhen(), 0,\r
251                             AWTMouseEventAdapter.getButtonStatus(e),\r
252                             AWTMouseEventAdapter.getStateMask(e),\r
253                             AWTMouseEventAdapter.getMouseButton(e),\r
254                             // TODO: fix canvas position if necessary\r
255                             new Point2D.Double(),\r
256                             controlPos,\r
257                             controlPos,\r
258                             AWTMouseEventAdapter.getScreenPosition(e));\r
259 \r
260                     // Send MouseDragBegin to the scenegraph and see\r
261                     // if anyone sets event.transferable to start DnD.\r
262                     handleMouseDragBeginEvent(event, EventTypes.MouseDragBegin);\r
263                     if (event.transferable != null) {\r
264                         ds.startDrag(dge, null, event.transferable, dsl);\r
265                         if (DEBUG_EVENTS)\r
266                             debug("dragGestureRecognized: startDrag " + event.transferable);\r
267                     }\r
268                 }\r
269             }\r
270         };\r
271         ds.createDefaultDragGestureRecognizer(\r
272                 rootPane,\r
273                 DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK,\r
274                 dgl);\r
275         ds.addDragSourceListener(dsl);\r
276     }\r
277
278     public boolean mousePressed(MouseButtonPressedEvent event) {\r
279         G2DFocusManager.INSTANCE.clearFocus();\r
280         try {\r
281 //        Point op = event.getPoint();\r
282 //        for (MouseListener l : mouseListeners.getListeners()) {\r
283 //            MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);\r
284 //            l.mousePressed(e);\r
285 //            event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));\r
286 //            if (e.isConsumed())\r
287 //                break;\r
288 //        }\r
289             return false;\r
290         } finally {\r
291             if (sg.getRootPane() != null) {\r
292                 if (G2DFocusManager.INSTANCE.getFocusOwner() == null) {\r
293                     sg.getRootPane().requestFocusInWindow();\r
294                     //sg.getRootPane().repaint(); //TODO : why repaint here? FocusOwner seems to be always null, so this causes unnecessary delays when interacting the canvas.\r
295                 }\r
296             }\r
297         }\r
298     }\r
299 \r
300     private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) {\r
301         int typeMask = EventTypes.toTypeMask(e);\r
302         if (focusNode instanceof IEventHandler) {\r
303             IEventHandler h = (IEventHandler) focusNode;\r
304             if (eats(h.getEventMask(), typeMask)) {\r
305                 if (h.handleEvent(e))\r
306                     return true;\r
307             }\r
308         }\r
309         for (IEventHandler l : handlers) {\r
310             if (eats(l.getEventMask(), typeMask)) {\r
311                 if (l.handleEvent(e))\r
312                     return true;\r
313             }\r
314         }\r
315         return false;\r
316     }\r
317 \r
318     private boolean handleMouseEvent(MouseEvent e, int eventType) {\r
319         IEventHandler[] sorted = sortedMouseListeners;\r
320         if (sorted == null)\r
321             sortedMouseListeners = sorted = sortInplace(mouseListeners.toArray(NONE));\r
322         return handleEvent(e, sg.getFocusNode(), sorted);\r
323     }\r
324 \r
325     private boolean handleMouseDragBeginEvent(MouseEvent e, int eventType) {\r
326         IEventHandler[] sorted = sortedMouseDragBeginListeners;\r
327         if (sorted == null)\r
328             sortedMouseDragBeginListeners = sorted = sortInplace(mouseDragBeginListeners.toArray(NONE));\r
329         // Give null for focusNode because we want to propagate\r
330         // this event in scene tree pre-order only.\r
331         return handleEvent(e, null, sorted);\r
332     }\r
333 \r
334     private boolean handleFocusEvent(FocusEvent e) {\r
335         IEventHandler[] sorted = sortedFocusListeners;\r
336         if (sorted == null)\r
337             sortedFocusListeners = sorted = sortInplace(focusListeners.toArray(NONE));\r
338         return handleEvent(e, null, sorted);\r
339     }\r
340 \r
341     private boolean handleTimeEvent(TimeEvent e) {\r
342         IEventHandler[] sorted = sortedTimeListeners;\r
343         if (sorted == null)\r
344             sortedTimeListeners = sorted = sortInplace(timeListeners.toArray(NONE));\r
345         return handleEvent(e, null, sorted);\r
346     }\r
347 \r
348     private boolean handleCommandEvent(CommandEvent e) {\r
349         IEventHandler[] sorted = sortedCommandListeners;\r
350         if (sorted == null)\r
351             sortedCommandListeners = sorted = sortInplace(commandListeners.toArray(NONE));\r
352         return handleEvent(e, sg.getFocusNode(), sorted);\r
353     }\r
354 \r
355     private boolean handleKeyEvent(KeyEvent e) {\r
356         IEventHandler[] sorted = sortedKeyListeners;\r
357         if (sorted == null)\r
358             sortedKeyListeners = sorted = sortInplace(keyListeners.toArray(NONE));\r
359         return handleEvent(e, sg.getFocusNode(), sorted);\r
360     }\r
361
362     @Override
363     public int getEventMask() {
364         return EventTypes.AnyMask;
365     }
366
367     @Override
368     public boolean handleEvent(Event e) {\r
369         if (DEBUG_EVENTS)\r
370             debug("handle event: " + e);\r
371
372         int eventType = EventTypes.toType(e);
373         switch (eventType) {
374             case EventTypes.Command:\r
375                 return handleCommandEvent((CommandEvent) e);\r
376 \r
377             case EventTypes.FocusGained:\r
378             case EventTypes.FocusLost:\r
379                 return handleFocusEvent((FocusEvent) e);\r
380 \r
381             case EventTypes.KeyPressed:\r
382             case EventTypes.KeyReleased:\r
383                 return handleKeyEvent((KeyEvent) e);\r
384 \r
385             case EventTypes.MouseDragBegin:\r
386                 return handleMouseDragBeginEvent((MouseEvent) e, eventType);\r
387 \r
388             case EventTypes.MouseButtonPressed:\r
389             case EventTypes.MouseButtonReleased:\r
390             case EventTypes.MouseClick:\r
391             case EventTypes.MouseDoubleClick:\r
392             case EventTypes.MouseEnter:\r
393             case EventTypes.MouseExit:\r
394             case EventTypes.MouseMoved:\r
395             case EventTypes.MouseWheel:\r
396                 return handleMouseEvent((MouseEvent) e, eventType);\r
397 \r
398             case EventTypes.Time:
399                 return handleTimeEvent((TimeEvent) e);\r
400         }\r
401         return false;
402     }\r
403 \r
404     public void add(IEventHandler item) {\r
405         if (!(item instanceof IG2DNode))\r
406             throw new IllegalArgumentException("event handler must be an IG2DNode");\r
407 \r
408         int mask = item.getEventMask();\r
409         if (eats(mask, EventTypes.CommandMask)) {\r
410             commandListeners.add(item);\r
411             sortedCommandListeners = null;\r
412         }\r
413         if (eats(mask, EventTypes.FocusMask)) {\r
414             focusListeners.add(item);\r
415             sortedFocusListeners = null;\r
416         }\r
417         if (eats(mask, EventTypes.KeyMask)) {\r
418             keyListeners.add(item);\r
419             sortedKeyListeners = null;\r
420         }\r
421         if (eats(mask, EventTypes.MouseDragBeginMask)) {\r
422             mouseDragBeginListeners.add(item);\r
423             sortedMouseDragBeginListeners = null;\r
424         }\r
425         if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {\r
426             mouseListeners.add(item);\r
427             sortedMouseListeners = null;\r
428         }\r
429         if (eats(mask, EventTypes.TimeMask)) {\r
430             timeListeners.add(item);\r
431             sortedTimeListeners = null;\r
432         }\r
433     }\r
434 \r
435     public boolean remove(IEventHandler item) {\r
436         if (!(item instanceof IG2DNode))\r
437             throw new IllegalArgumentException("event handler must be an IG2DNode");\r
438 \r
439         int mask = item.getEventMask();\r
440         boolean removed = false;\r
441         if (eats(mask, EventTypes.CommandMask)) {\r
442             removed |= commandListeners.remove(item);\r
443             sortedCommandListeners = null;\r
444         }\r
445         if (eats(mask, EventTypes.FocusMask)) {\r
446             removed |= focusListeners.remove(item);\r
447             sortedFocusListeners = null;\r
448         }\r
449         if (eats(mask, EventTypes.KeyMask)) {\r
450             removed |= keyListeners.remove(item);\r
451             sortedKeyListeners = null;\r
452         }\r
453         if (eats(mask, EventTypes.MouseDragBeginMask)) {\r
454             removed |= mouseDragBeginListeners.remove(item);\r
455             sortedMouseDragBeginListeners = null;\r
456         }\r
457         if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {\r
458             removed |= mouseListeners.remove(item);\r
459             sortedMouseListeners = null;\r
460         }\r
461         if (eats(mask, EventTypes.TimeMask)) {\r
462             removed |= timeListeners.remove(item);\r
463             sortedTimeListeners = null;\r
464         }\r
465         return removed;\r
466     }\r
467 \r
468     private static boolean eats(int handlerMask, int eventTypeMask) {\r
469         return (handlerMask & eventTypeMask) != 0;\r
470     }\r
471 \r
472     private void debug(String msg) {\r
473         System.out.println(getClass().getSimpleName() + ": " + msg);\r
474     }\r
475 \r
476     public void dispose() {\r
477         commandListeners.clear();\r
478         commandListeners = null;\r
479         focusListeners.clear();\r
480         focusListeners = null;\r
481         keyListeners.clear();\r
482         keyListeners = null;\r
483         mouseListeners.clear();\r
484         mouseListeners = null;\r
485         sg = null;\r
486         sortedCommandListeners = null;\r
487         sortedKeyListeners = null;\r
488         sortedMouseListeners = null;\r
489 \r
490         timeListeners.clear();\r
491         timeListeners = null;\r
492     }\r
493 \r
494 }