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