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.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;
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;
43 * Delivers events (mouse, key, focus, command, time) to scene graph nodes that
44 * have registered to receive them.
46 * @author Tuukka Lehtonen
48 public class NodeEventHandler implements IEventHandler {
50 private static final Logger LOGGER = LoggerFactory.getLogger(NodeEventHandler.class);
52 private static final boolean DEBUG_EVENTS = false;
53 private static final boolean DEBUG_HANDLER_SORT = false;
55 private static final IEventHandler[] NONE = {};
57 public static class TreePreOrderComparator implements Comparator<IEventHandler> {
65 ArrayList<INode> path1 = new ArrayList<INode>();
66 ArrayList<INode> path2 = new ArrayList<INode>();
69 private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {
70 protected Temp initialValue() {
77 public TreePreOrderComparator(Order order) {
81 void getTreePath(INode node, ArrayList<INode> result) {
83 for (; node != null; node = node.getParent())
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: "
94 public int compare(IEventHandler e1, IEventHandler e2) {
98 Temp tmp = temp.get();
99 ArrayList<INode> path1 = tmp.path1;
100 ArrayList<INode> path2 = tmp.path2;
103 // Get path to root node for both nodes
104 getTreePath((INode) e1, path1);
105 getTreePath((INode) e2, path2);
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);
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);
122 // Pre-order: a node that is on the tree path of another node is first
124 return Order.ASCENDING == order ? -1 : 1;
126 return Order.ASCENDING == order ? 1 : -1;
128 return compare(path1.get(i1), path2.get(i2));
130 // Don't hold on to objects unnecessarily
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;
145 return -1; // sort IG2DNodes before non-IG2DNodes
148 if(n2 instanceof IG2DNode)
151 return 0; // all non-IG2DNodes are equal in comparison
156 TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);
159 * {@link FocusEvent} are propagated first to the scene graph focus node,
160 * then to event handler nodes in scene graph tree pre-order.
162 protected List<IEventHandler> focusListeners = new ArrayList<IEventHandler>();
163 protected IEventHandler[] sortedFocusListeners = null;
166 * {@link TimeEvent} are propagated first to the scene graph focus node,
167 * then to event handler nodes in scene graph tree pre-order.
169 protected List<IEventHandler> timeListeners = new ArrayList<IEventHandler>();
170 protected IEventHandler[] sortedTimeListeners = null;
173 * {@link CommandEvent} are propagated first to the scene graph focus node,
174 * then to event handler nodes in scene graph tree pre-order.
176 protected List<IEventHandler> commandListeners = new ArrayList<IEventHandler>();
177 protected IEventHandler[] sortedCommandListeners = null;
180 * {@link KeyEvent} are propagated first to the scene graph focus node, then
181 * to event handler nodes in scene graph tree pre-order.
183 protected List<IEventHandler> keyListeners = new ArrayList<IEventHandler>();
184 protected IEventHandler[] sortedKeyListeners = null;
187 * {@link MouseEvent} are propagated first to the scene graph focus node,
188 * then to event handler nodes in scene graph tree pre-order.
190 protected List<IEventHandler> mouseListeners = new ArrayList<IEventHandler>();
191 protected IEventHandler[] sortedMouseListeners = null;
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.
197 protected List<IEventHandler> mouseDragBeginListeners = new ArrayList<IEventHandler>();
198 protected IEventHandler[] sortedMouseDragBeginListeners = null;
201 * The scene graph this instance handles event propagation for.
203 protected G2DSceneGraph sg;
205 public NodeEventHandler(G2DSceneGraph sg) {
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));
216 private IEventHandler[] sortInplace(IEventHandler[] sort) {
217 if (DEBUG_HANDLER_SORT)
218 debug("in-place sort " + sort.length + " handlers");
219 Arrays.sort(sort, COMPARATOR);
223 public void setRootPane(Component rootPane) {
224 if (GraphicsEnvironment.isHeadless()) {
225 LOGGER.info("Disabling DragSource in headless environments");
228 final DragSource ds = new DragSource();
229 final DragSourceListener dsl = new DragSourceListener() {
231 public void dropActionChanged(DragSourceDragEvent dsde) {
234 public void dragOver(DragSourceDragEvent dsde) {
237 public void dragExit(DragSourceEvent dse) {
240 public void dragEnter(DragSourceDragEvent dsde) {
243 public void dragDropEnd(DragSourceDropEvent dsde) {
246 DragGestureListener dgl = new DragGestureListener() {
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,
255 AWTMouseEventAdapter.getButtonStatus(e),
256 AWTMouseEventAdapter.getStateMask(e),
257 AWTMouseEventAdapter.getMouseButton(e),
258 // TODO: fix canvas position if necessary
259 new Point2D.Double(),
262 AWTMouseEventAdapter.getScreenPosition(e));
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);
270 debug("dragGestureRecognized: startDrag " + event.transferable);
275 ds.createDefaultDragGestureRecognizer(
277 DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK,
279 ds.addDragSourceListener(dsl);
282 public boolean mousePressed(MouseButtonPressedEvent event) {
283 G2DFocusManager.INSTANCE.clearFocus();
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())
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.
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))
313 for (IEventHandler l : handlers) {
314 if (eats(l.getEventMask(), typeMask)) {
315 if (l.handleEvent(e))
322 private boolean handleMouseEvent(MouseEvent e, int eventType) {
323 IEventHandler[] sorted = sortedMouseListeners;
325 sortedMouseListeners = sorted = sortInplace(mouseListeners.toArray(NONE));
326 return handleEvent(e, sg.getFocusNode(), sorted);
329 private boolean handleMouseDragBeginEvent(MouseEvent e, int eventType) {
330 IEventHandler[] sorted = sortedMouseDragBeginListeners;
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);
338 private boolean handleFocusEvent(FocusEvent e) {
339 IEventHandler[] sorted = sortedFocusListeners;
341 sortedFocusListeners = sorted = sortInplace(focusListeners.toArray(NONE));
342 return handleEvent(e, null, sorted);
345 private boolean handleTimeEvent(TimeEvent e) {
346 IEventHandler[] sorted = sortedTimeListeners;
348 sortedTimeListeners = sorted = sortInplace(timeListeners.toArray(NONE));
349 return handleEvent(e, null, sorted);
352 private boolean handleCommandEvent(CommandEvent e) {
353 IEventHandler[] sorted = sortedCommandListeners;
355 sortedCommandListeners = sorted = sortInplace(commandListeners.toArray(NONE));
356 return handleEvent(e, sg.getFocusNode(), sorted);
359 private boolean handleKeyEvent(KeyEvent e) {
360 IEventHandler[] sorted = sortedKeyListeners;
362 sortedKeyListeners = sorted = sortInplace(keyListeners.toArray(NONE));
363 return handleEvent(e, sg.getFocusNode(), sorted);
367 public int getEventMask() {
368 return EventTypes.AnyMask;
372 public boolean handleEvent(Event e) {
374 debug("handle event: " + e);
376 int eventType = EventTypes.toType(e);
378 case EventTypes.Command:
379 return handleCommandEvent((CommandEvent) e);
381 case EventTypes.FocusGained:
382 case EventTypes.FocusLost:
383 return handleFocusEvent((FocusEvent) e);
385 case EventTypes.KeyPressed:
386 case EventTypes.KeyReleased:
387 return handleKeyEvent((KeyEvent) e);
389 case EventTypes.MouseDragBegin:
390 return handleMouseDragBeginEvent((MouseEvent) e, eventType);
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);
402 case EventTypes.Time:
403 return handleTimeEvent((TimeEvent) e);
408 public void add(IEventHandler item) {
409 if (!(item instanceof IG2DNode))
410 throw new IllegalArgumentException("event handler must be an IG2DNode");
412 int mask = item.getEventMask();
413 if (eats(mask, EventTypes.CommandMask)) {
414 commandListeners.add(item);
415 sortedCommandListeners = null;
417 if (eats(mask, EventTypes.FocusMask)) {
418 focusListeners.add(item);
419 sortedFocusListeners = null;
421 if (eats(mask, EventTypes.KeyMask)) {
422 keyListeners.add(item);
423 sortedKeyListeners = null;
425 if (eats(mask, EventTypes.MouseDragBeginMask)) {
426 mouseDragBeginListeners.add(item);
427 sortedMouseDragBeginListeners = null;
429 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
430 mouseListeners.add(item);
431 sortedMouseListeners = null;
433 if (eats(mask, EventTypes.TimeMask)) {
434 timeListeners.add(item);
435 sortedTimeListeners = null;
439 public boolean remove(IEventHandler item) {
440 if (!(item instanceof IG2DNode))
441 throw new IllegalArgumentException("event handler must be an IG2DNode");
443 int mask = item.getEventMask();
444 boolean removed = false;
445 if (eats(mask, EventTypes.CommandMask)) {
446 removed |= commandListeners.remove(item);
447 sortedCommandListeners = null;
449 if (eats(mask, EventTypes.FocusMask)) {
450 removed |= focusListeners.remove(item);
451 sortedFocusListeners = null;
453 if (eats(mask, EventTypes.KeyMask)) {
454 removed |= keyListeners.remove(item);
455 sortedKeyListeners = null;
457 if (eats(mask, EventTypes.MouseDragBeginMask)) {
458 removed |= mouseDragBeginListeners.remove(item);
459 sortedMouseDragBeginListeners = null;
461 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
462 removed |= mouseListeners.remove(item);
463 sortedMouseListeners = null;
465 if (eats(mask, EventTypes.TimeMask)) {
466 removed |= timeListeners.remove(item);
467 sortedTimeListeners = null;
472 private static boolean eats(int handlerMask, int eventTypeMask) {
473 return (handlerMask & eventTypeMask) != 0;
476 private void debug(String msg) {
477 System.out.println(getClass().getSimpleName() + ": " + msg);
480 public void dispose() {
481 commandListeners.clear();
482 commandListeners = null;
483 focusListeners.clear();
484 focusListeners = null;
485 keyListeners.clear();
487 mouseListeners.clear();
488 mouseListeners = null;
490 sortedCommandListeners = null;
491 sortedKeyListeners = null;
492 sortedMouseListeners = null;
494 timeListeners.clear();
495 timeListeners = null;