1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2011 Association for Decentralized Information Management in
\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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.g2d.events;
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
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
40 * Delivers events (mouse, key, focus, command, time) to scene graph nodes that
\r
41 * have registered to receive them.
\r
43 * @author Tuukka Lehtonen
\r
45 public class NodeEventHandler implements IEventHandler {
47 private static final boolean DEBUG_EVENTS = false;
\r
48 private static final boolean DEBUG_HANDLER_SORT = false;
\r
50 private static final IEventHandler[] NONE = {};
\r
52 public static class TreePreOrderComparator implements Comparator<IEventHandler> {
\r
60 ArrayList<INode> path1 = new ArrayList<INode>();
\r
61 ArrayList<INode> path2 = new ArrayList<INode>();
\r
64 private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {
\r
65 protected Temp initialValue() {
\r
72 public TreePreOrderComparator(Order order) {
\r
76 void getTreePath(INode node, ArrayList<INode> result) {
\r
78 for (INode parent = node.getParent(); parent != null; parent = parent.getParent())
\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
89 public int compare(IEventHandler e1, IEventHandler e2) {
\r
93 Temp tmp = temp.get();
\r
94 ArrayList<INode> path1 = tmp.path1;
\r
95 ArrayList<INode> path2 = tmp.path2;
\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
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
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
121 // Pre-order: a node that is on the tree path of another node is first
\r
123 return Order.ASCENDING == order ? -1 : 1;
\r
125 return Order.ASCENDING == order ? 1 : -1;
\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
137 // Can't sort non-IG2DNodes.
\r
140 // Don't hold on to objects unnecessarily
\r
146 private int compare(int v1, int v2) {
\r
147 return v1 < v2 ? -1 : (v1 > v2 ? 1 : 0);
\r
151 TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);
\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
157 protected List<IEventHandler> focusListeners = new ArrayList<IEventHandler>();
\r
158 protected IEventHandler[] sortedFocusListeners = null;
\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
164 protected List<IEventHandler> timeListeners = new ArrayList<IEventHandler>();
\r
165 protected IEventHandler[] sortedTimeListeners = null;
\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
171 protected List<IEventHandler> commandListeners = new ArrayList<IEventHandler>();
\r
172 protected IEventHandler[] sortedCommandListeners = null;
\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
178 protected List<IEventHandler> keyListeners = new ArrayList<IEventHandler>();
\r
179 protected IEventHandler[] sortedKeyListeners = null;
\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
185 protected List<IEventHandler> mouseListeners = new ArrayList<IEventHandler>();
\r
186 protected IEventHandler[] sortedMouseListeners = null;
\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
192 protected List<IEventHandler> mouseDragBeginListeners = new ArrayList<IEventHandler>();
\r
193 protected IEventHandler[] sortedMouseDragBeginListeners = null;
\r
196 * The scene graph this instance handles event propagation for.
\r
198 protected G2DSceneGraph sg;
\r
201 * For proper initiation of native DnD operations within this AWT-based
\r
202 * scenegraph system.
\r
204 protected DragSource ds = new DragSource();
\r
206 public NodeEventHandler(G2DSceneGraph sg) {
\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
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
224 public void setRootPane(Component rootPane) {
\r
225 final DragSourceListener dsl = new DragSourceListener() {
\r
227 public void dropActionChanged(DragSourceDragEvent dsde) {
\r
230 public void dragOver(DragSourceDragEvent dsde) {
\r
233 public void dragExit(DragSourceEvent dse) {
\r
236 public void dragEnter(DragSourceDragEvent dsde) {
\r
239 public void dragDropEnd(DragSourceDropEvent dsde) {
\r
242 DragGestureListener dgl = new DragGestureListener() {
\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
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
258 AWTMouseEventAdapter.getScreenPosition(e));
\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
266 debug("dragGestureRecognized: startDrag " + event.transferable);
\r
271 ds.createDefaultDragGestureRecognizer(
\r
273 DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK,
\r
275 ds.addDragSourceListener(dsl);
\r
278 public boolean mousePressed(MouseButtonPressedEvent event) {
\r
279 G2DFocusManager.INSTANCE.clearFocus();
\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
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
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
309 for (IEventHandler l : handlers) {
\r
310 if (eats(l.getEventMask(), typeMask)) {
\r
311 if (l.handleEvent(e))
\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
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
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
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
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
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
363 public int getEventMask() {
364 return EventTypes.AnyMask;
368 public boolean handleEvent(Event e) {
\r
370 debug("handle event: " + e);
\r
372 int eventType = EventTypes.toType(e);
374 case EventTypes.Command:
\r
375 return handleCommandEvent((CommandEvent) e);
\r
377 case EventTypes.FocusGained:
\r
378 case EventTypes.FocusLost:
\r
379 return handleFocusEvent((FocusEvent) e);
\r
381 case EventTypes.KeyPressed:
\r
382 case EventTypes.KeyReleased:
\r
383 return handleKeyEvent((KeyEvent) e);
\r
385 case EventTypes.MouseDragBegin:
\r
386 return handleMouseDragBeginEvent((MouseEvent) e, eventType);
\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
398 case EventTypes.Time:
399 return handleTimeEvent((TimeEvent) e);
\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
408 int mask = item.getEventMask();
\r
409 if (eats(mask, EventTypes.CommandMask)) {
\r
410 commandListeners.add(item);
\r
411 sortedCommandListeners = null;
\r
413 if (eats(mask, EventTypes.FocusMask)) {
\r
414 focusListeners.add(item);
\r
415 sortedFocusListeners = null;
\r
417 if (eats(mask, EventTypes.KeyMask)) {
\r
418 keyListeners.add(item);
\r
419 sortedKeyListeners = null;
\r
421 if (eats(mask, EventTypes.MouseDragBeginMask)) {
\r
422 mouseDragBeginListeners.add(item);
\r
423 sortedMouseDragBeginListeners = null;
\r
425 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
\r
426 mouseListeners.add(item);
\r
427 sortedMouseListeners = null;
\r
429 if (eats(mask, EventTypes.TimeMask)) {
\r
430 timeListeners.add(item);
\r
431 sortedTimeListeners = null;
\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
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
445 if (eats(mask, EventTypes.FocusMask)) {
\r
446 removed |= focusListeners.remove(item);
\r
447 sortedFocusListeners = null;
\r
449 if (eats(mask, EventTypes.KeyMask)) {
\r
450 removed |= keyListeners.remove(item);
\r
451 sortedKeyListeners = null;
\r
453 if (eats(mask, EventTypes.MouseDragBeginMask)) {
\r
454 removed |= mouseDragBeginListeners.remove(item);
\r
455 sortedMouseDragBeginListeners = null;
\r
457 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
\r
458 removed |= mouseListeners.remove(item);
\r
459 sortedMouseListeners = null;
\r
461 if (eats(mask, EventTypes.TimeMask)) {
\r
462 removed |= timeListeners.remove(item);
\r
463 sortedTimeListeners = null;
\r
468 private static boolean eats(int handlerMask, int eventTypeMask) {
\r
469 return (handlerMask & eventTypeMask) != 0;
\r
472 private void debug(String msg) {
\r
473 System.out.println(getClass().getSimpleName() + ": " + msg);
\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
486 sortedCommandListeners = null;
\r
487 sortedKeyListeners = null;
\r
488 sortedMouseListeners = null;
\r
490 timeListeners.clear();
\r
491 timeListeners = null;
\r