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