1 /*******************************************************************************
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph.g2d.events;
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;
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;
40 * Delivers events (mouse, key, focus, command, time) to scene graph nodes that
41 * have registered to receive them.
43 * @author Tuukka Lehtonen
45 public class NodeEventHandler implements IEventHandler {
47 private static final boolean DEBUG_EVENTS = false;
48 private static final boolean DEBUG_HANDLER_SORT = false;
50 private static final IEventHandler[] NONE = {};
52 public static class TreePreOrderComparator implements Comparator<IEventHandler> {
60 ArrayList<INode> path1 = new ArrayList<INode>();
61 ArrayList<INode> path2 = new ArrayList<INode>();
64 private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {
65 protected Temp initialValue() {
72 public TreePreOrderComparator(Order order) {
76 void getTreePath(INode node, ArrayList<INode> result) {
78 for (; node != null; node = node.getParent())
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: "
89 public int compare(IEventHandler e1, IEventHandler e2) {
93 Temp tmp = temp.get();
94 ArrayList<INode> path1 = tmp.path1;
95 ArrayList<INode> path2 = tmp.path2;
98 // Get path to root node for both nodes
99 getTreePath((INode) e1, path1);
100 getTreePath((INode) e2, path2);
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);
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);
117 // Pre-order: a node that is on the tree path of another node is first
119 return Order.ASCENDING == order ? -1 : 1;
121 return Order.ASCENDING == order ? 1 : -1;
123 return compare(path1.get(i1), path2.get(i2));
125 // Don't hold on to objects unnecessarily
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;
140 return -1; // sort IG2DNodes before non-IG2DNodes
143 if(n2 instanceof IG2DNode)
146 return 0; // all non-IG2DNodes are equal in comparison
151 TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);
154 * {@link FocusEvent} are propagated first to the scene graph focus node,
155 * then to event handler nodes in scene graph tree pre-order.
157 protected List<IEventHandler> focusListeners = new ArrayList<IEventHandler>();
158 protected IEventHandler[] sortedFocusListeners = null;
161 * {@link TimeEvent} are propagated first to the scene graph focus node,
162 * then to event handler nodes in scene graph tree pre-order.
164 protected List<IEventHandler> timeListeners = new ArrayList<IEventHandler>();
165 protected IEventHandler[] sortedTimeListeners = null;
168 * {@link CommandEvent} are propagated first to the scene graph focus node,
169 * then to event handler nodes in scene graph tree pre-order.
171 protected List<IEventHandler> commandListeners = new ArrayList<IEventHandler>();
172 protected IEventHandler[] sortedCommandListeners = null;
175 * {@link KeyEvent} are propagated first to the scene graph focus node, then
176 * to event handler nodes in scene graph tree pre-order.
178 protected List<IEventHandler> keyListeners = new ArrayList<IEventHandler>();
179 protected IEventHandler[] sortedKeyListeners = null;
182 * {@link MouseEvent} are propagated first to the scene graph focus node,
183 * then to event handler nodes in scene graph tree pre-order.
185 protected List<IEventHandler> mouseListeners = new ArrayList<IEventHandler>();
186 protected IEventHandler[] sortedMouseListeners = null;
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.
192 protected List<IEventHandler> mouseDragBeginListeners = new ArrayList<IEventHandler>();
193 protected IEventHandler[] sortedMouseDragBeginListeners = null;
196 * The scene graph this instance handles event propagation for.
198 protected G2DSceneGraph sg;
201 * For proper initiation of native DnD operations within this AWT-based
204 protected DragSource ds = new DragSource();
206 public NodeEventHandler(G2DSceneGraph sg) {
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));
217 private IEventHandler[] sortInplace(IEventHandler[] sort) {
218 if (DEBUG_HANDLER_SORT)
219 debug("in-place sort " + sort.length + " handlers");
220 Arrays.sort(sort, COMPARATOR);
224 public void setRootPane(Component rootPane) {
225 final DragSourceListener dsl = new DragSourceListener() {
227 public void dropActionChanged(DragSourceDragEvent dsde) {
230 public void dragOver(DragSourceDragEvent dsde) {
233 public void dragExit(DragSourceEvent dse) {
236 public void dragEnter(DragSourceDragEvent dsde) {
239 public void dragDropEnd(DragSourceDropEvent dsde) {
242 DragGestureListener dgl = new DragGestureListener() {
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,
251 AWTMouseEventAdapter.getButtonStatus(e),
252 AWTMouseEventAdapter.getStateMask(e),
253 AWTMouseEventAdapter.getMouseButton(e),
254 // TODO: fix canvas position if necessary
255 new Point2D.Double(),
258 AWTMouseEventAdapter.getScreenPosition(e));
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);
266 debug("dragGestureRecognized: startDrag " + event.transferable);
271 ds.createDefaultDragGestureRecognizer(
273 DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK,
275 ds.addDragSourceListener(dsl);
278 public boolean mousePressed(MouseButtonPressedEvent event) {
279 G2DFocusManager.INSTANCE.clearFocus();
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())
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.
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))
309 for (IEventHandler l : handlers) {
310 if (eats(l.getEventMask(), typeMask)) {
311 if (l.handleEvent(e))
318 private boolean handleMouseEvent(MouseEvent e, int eventType) {
319 IEventHandler[] sorted = sortedMouseListeners;
321 sortedMouseListeners = sorted = sortInplace(mouseListeners.toArray(NONE));
322 return handleEvent(e, sg.getFocusNode(), sorted);
325 private boolean handleMouseDragBeginEvent(MouseEvent e, int eventType) {
326 IEventHandler[] sorted = sortedMouseDragBeginListeners;
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);
334 private boolean handleFocusEvent(FocusEvent e) {
335 IEventHandler[] sorted = sortedFocusListeners;
337 sortedFocusListeners = sorted = sortInplace(focusListeners.toArray(NONE));
338 return handleEvent(e, null, sorted);
341 private boolean handleTimeEvent(TimeEvent e) {
342 IEventHandler[] sorted = sortedTimeListeners;
344 sortedTimeListeners = sorted = sortInplace(timeListeners.toArray(NONE));
345 return handleEvent(e, null, sorted);
348 private boolean handleCommandEvent(CommandEvent e) {
349 IEventHandler[] sorted = sortedCommandListeners;
351 sortedCommandListeners = sorted = sortInplace(commandListeners.toArray(NONE));
352 return handleEvent(e, sg.getFocusNode(), sorted);
355 private boolean handleKeyEvent(KeyEvent e) {
356 IEventHandler[] sorted = sortedKeyListeners;
358 sortedKeyListeners = sorted = sortInplace(keyListeners.toArray(NONE));
359 return handleEvent(e, sg.getFocusNode(), sorted);
363 public int getEventMask() {
364 return EventTypes.AnyMask;
368 public boolean handleEvent(Event e) {
370 debug("handle event: " + e);
372 int eventType = EventTypes.toType(e);
374 case EventTypes.Command:
375 return handleCommandEvent((CommandEvent) e);
377 case EventTypes.FocusGained:
378 case EventTypes.FocusLost:
379 return handleFocusEvent((FocusEvent) e);
381 case EventTypes.KeyPressed:
382 case EventTypes.KeyReleased:
383 return handleKeyEvent((KeyEvent) e);
385 case EventTypes.MouseDragBegin:
386 return handleMouseDragBeginEvent((MouseEvent) e, eventType);
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);
398 case EventTypes.Time:
399 return handleTimeEvent((TimeEvent) e);
404 public void add(IEventHandler item) {
405 if (!(item instanceof IG2DNode))
406 throw new IllegalArgumentException("event handler must be an IG2DNode");
408 int mask = item.getEventMask();
409 if (eats(mask, EventTypes.CommandMask)) {
410 commandListeners.add(item);
411 sortedCommandListeners = null;
413 if (eats(mask, EventTypes.FocusMask)) {
414 focusListeners.add(item);
415 sortedFocusListeners = null;
417 if (eats(mask, EventTypes.KeyMask)) {
418 keyListeners.add(item);
419 sortedKeyListeners = null;
421 if (eats(mask, EventTypes.MouseDragBeginMask)) {
422 mouseDragBeginListeners.add(item);
423 sortedMouseDragBeginListeners = null;
425 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
426 mouseListeners.add(item);
427 sortedMouseListeners = null;
429 if (eats(mask, EventTypes.TimeMask)) {
430 timeListeners.add(item);
431 sortedTimeListeners = null;
435 public boolean remove(IEventHandler item) {
436 if (!(item instanceof IG2DNode))
437 throw new IllegalArgumentException("event handler must be an IG2DNode");
439 int mask = item.getEventMask();
440 boolean removed = false;
441 if (eats(mask, EventTypes.CommandMask)) {
442 removed |= commandListeners.remove(item);
443 sortedCommandListeners = null;
445 if (eats(mask, EventTypes.FocusMask)) {
446 removed |= focusListeners.remove(item);
447 sortedFocusListeners = null;
449 if (eats(mask, EventTypes.KeyMask)) {
450 removed |= keyListeners.remove(item);
451 sortedKeyListeners = null;
453 if (eats(mask, EventTypes.MouseDragBeginMask)) {
454 removed |= mouseDragBeginListeners.remove(item);
455 sortedMouseDragBeginListeners = null;
457 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
458 removed |= mouseListeners.remove(item);
459 sortedMouseListeners = null;
461 if (eats(mask, EventTypes.TimeMask)) {
462 removed |= timeListeners.remove(item);
463 sortedTimeListeners = null;
468 private static boolean eats(int handlerMask, int eventTypeMask) {
469 return (handlerMask & eventTypeMask) != 0;
472 private void debug(String msg) {
473 System.out.println(getClass().getSimpleName() + ": " + msg);
476 public void dispose() {
477 commandListeners.clear();
478 commandListeners = null;
479 focusListeners.clear();
480 focusListeners = null;
481 keyListeners.clear();
483 mouseListeners.clear();
484 mouseListeners = null;
486 sortedCommandListeners = null;
487 sortedKeyListeners = null;
488 sortedMouseListeners = null;
490 timeListeners.clear();
491 timeListeners = null;