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.geom.Point2D;
\r
24 import java.util.ArrayList;
\r
25 import java.util.Arrays;
\r
26 import java.util.Comparator;
\r
28 import org.simantics.scenegraph.INode;
\r
29 import org.simantics.scenegraph.g2d.G2DFocusManager;
\r
30 import org.simantics.scenegraph.g2d.G2DSceneGraph;
\r
31 import org.simantics.scenegraph.g2d.IG2DNode;
\r
32 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
33 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
34 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
\r
37 * Delivers events (mouse, key, focus, command, time) to scene graph nodes that
\r
38 * have registered to receive them.
\r
40 * @author Tuukka Lehtonen
\r
42 public class NodeEventHandler implements IEventHandler {
44 private static final boolean DEBUG_EVENTS = false;
\r
45 private static final boolean DEBUG_HANDLER_SORT = false;
\r
47 public static class TreePreOrderComparator implements Comparator<IEventHandler> {
\r
55 ArrayList<INode> path1 = new ArrayList<INode>();
\r
56 ArrayList<INode> path2 = new ArrayList<INode>();
\r
59 private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {
\r
60 protected Temp initialValue() {
\r
67 public TreePreOrderComparator(Order order) {
\r
71 void getTreePath(INode node, ArrayList<INode> result) {
\r
73 for (INode parent = node.getParent(); parent != null; parent = parent.getParent())
\r
77 void notSameGraph(INode o1, INode o2) {
\r
78 throw new IllegalStateException("nodes " + o1 + " and " + o2
\r
79 + " not part of same scene graph.\n\t root 1: " + o1.getRootNode() + "\n\troot 2: "
\r
80 + o2.getRootNode());
\r
84 public int compare(IEventHandler e1, IEventHandler e2) {
\r
88 Temp tmp = temp.get();
\r
89 ArrayList<INode> path1 = tmp.path1;
\r
90 ArrayList<INode> path2 = tmp.path2;
\r
92 // Get path to root node for both nodes
\r
93 INode o1 = (INode) e1;
\r
94 INode o2 = (INode) e2;
\r
95 getTreePath(o1, path1);
\r
96 getTreePath(o2, path2);
\r
98 // Sanity checks: nodes part of same scene graph
\r
99 INode root1 = path1.isEmpty() ? o1 : path1.get(path1.size() - 1);
\r
100 INode root2 = path2.isEmpty() ? o2 : path2.get(path2.size() - 1);
\r
101 if (root1 != root2)
\r
102 notSameGraph(o1, o2);
\r
105 // Find first non-matching nodes in the paths starting from the root node
\r
106 int i1 = path1.size() - 1;
\r
107 int i2 = path2.size() - 1;
\r
108 for (; i1 >= 0 && i2 >= 0; --i1, --i2) {
\r
109 INode p1 = path1.get(i1);
\r
110 INode p2 = path2.get(i2);
\r
116 // Pre-order: a node that is on the tree path of another node is first
\r
118 return Order.ASCENDING == order ? -1 : 1;
\r
120 return Order.ASCENDING == order ? 1 : -1;
\r
122 INode n1 = path1.get(i1);
\r
123 INode n2 = path2.get(i2);
\r
124 IG2DNode g1 = n1 instanceof IG2DNode ? (IG2DNode) n1 : null;
\r
125 IG2DNode g2 = n2 instanceof IG2DNode ? (IG2DNode) n2 : null;
\r
126 if (g1 != null && g2 != null) {
\r
127 int z1 = g1.getZIndex();
\r
128 int z2 = g2.getZIndex();
\r
129 int c = compare(z1, z2);
\r
130 return order == Order.ASCENDING ? c : -c;
\r
132 // Can't sort non-IG2DNodes.
\r
135 // Don't hold on to objects unnecessarily
\r
141 private int compare(int v1, int v2) {
\r
142 return v1 < v2 ? -1 : (v1 > v2 ? 1 : 0);
\r
146 TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);
\r
149 * FocusEvents are propagated to event handlers in undefined order.
\r
151 protected ListenerList<IEventHandler> focusListeners = new ListenerList<IEventHandler>(IEventHandler.class);
\r
154 * TimeEvents are propagated to events handlers in an undefined order.
\r
156 protected ListenerList<IEventHandler> timeListeners = new ListenerList<IEventHandler>(IEventHandler.class);
\r
159 * CommandEvents are propagated first to the scene graph focus node, then to
\r
160 * event handler nodes in scene graph tree pre-order.
\r
162 protected ListenerList<IEventHandler> commandListeners = new ListenerList<IEventHandler>(IEventHandler.class);
\r
163 protected IEventHandler[] sortedCommandListeners = null;
\r
166 * KeyEvents are propagated first to the scene graph focus node, then to
\r
167 * event handler nodes in scene graph tree pre-order.
\r
169 protected ListenerList<IEventHandler> keyListeners = new ListenerList<IEventHandler>(IEventHandler.class);
\r
170 protected IEventHandler[] sortedKeyListeners = null;
\r
173 * MouseEvents are propagated first to the scene graph focus node, then to
\r
174 * event handler nodes in scene graph tree pre-order.
\r
176 protected ListenerList<IEventHandler> mouseListeners = new ListenerList<IEventHandler>(IEventHandler.class);
\r
177 protected IEventHandler[] sortedMouseListeners = null;
\r
180 * The scene graph this instance handles event propagation for.
\r
182 protected G2DSceneGraph sg;
\r
184 protected DragSource ds = new DragSource();
\r
186 public NodeEventHandler(G2DSceneGraph sg) {
\r
190 private IEventHandler[] sort(IEventHandler[] sort) {
\r
191 if (DEBUG_HANDLER_SORT)
\r
192 debug("sort " + sort.length + " handlers");
\r
193 IEventHandler[] copy = Arrays.copyOf(sort, sort.length);
\r
194 Arrays.sort(copy, COMPARATOR);
\r
198 public void setRootPane(Component rootPane) {
\r
200 final DragSourceListener dsl = new DragSourceListener() {
\r
203 public void dropActionChanged(DragSourceDragEvent dsde) {
\r
207 public void dragOver(DragSourceDragEvent dsde) {
\r
211 public void dragExit(DragSourceEvent dse) {
\r
215 public void dragEnter(DragSourceDragEvent dsde) {
\r
219 public void dragDropEnd(DragSourceDropEvent dsde) {
\r
222 ds.createDefaultDragGestureRecognizer(rootPane, DnDConstants.ACTION_COPY_OR_MOVE | DnDConstants.ACTION_LINK, new DragGestureListener() {
\r
225 public void dragGestureRecognized(DragGestureEvent dge) {
\r
226 MouseDragBegin event = new MouseDragBegin(NodeEventHandler.this,
\r
228 new Point2D.Double(),new Point2D.Double(),
\r
229 new Point2D.Double(),new Point2D.Double());
\r
230 handleMouseEvent(event, EventTypes.MouseDragBegin);
\r
231 if(event.transferable != null) {
\r
232 ds.startDrag(dge, null, event.transferable, dsl);
\r
234 debug("dragGestureRecognized: startDrag " + event.transferable);
\r
238 ds.addDragSourceListener(dsl);
\r
243 // public void mouseReleased(MouseEvent event) {
244 // Point op = event.getPoint();
245 // for (MouseListener l : mouseListeners.getListeners()) {
246 // MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
247 // l.mouseReleased(e);
248 // event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));
249 // if (e.isConsumed())
255 // public void mouseMoved(MouseEvent event) {
256 // for (MouseMotionListener l : mouseMotionListeners.getListeners()) {
257 // MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
259 // if (e.isConsumed())
264 public boolean mousePressed(MouseButtonPressedEvent event) {
\r
265 G2DFocusManager.INSTANCE.clearFocus();
\r
267 // Point op = event.getPoint();
\r
268 // for (MouseListener l : mouseListeners.getListeners()) {
\r
269 // MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
\r
270 // l.mousePressed(e);
\r
271 // event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));
\r
272 // if (e.isConsumed())
\r
277 if (sg.getRootPane() != null) {
\r
278 if (G2DFocusManager.INSTANCE.getFocusOwner() == null) {
\r
279 sg.getRootPane().requestFocusInWindow();
\r
280 //sg.getRootPane().repaint(); //TODO : why repaint here? FocusOwner seems to be always null, so this causes unnecessary delays when interacting the canvas.
\r
286 private boolean handleMouseEvent(MouseEvent e, int eventType) {
\r
287 IEventHandler[] sorted = sortedMouseListeners;
\r
288 if (sorted == null)
\r
289 sortedMouseListeners = sorted = sort(mouseListeners.getListeners());
\r
290 return handleEvent(e, sg.getFocusNode(), sorted);
\r
293 private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) {
\r
294 if (focusNode instanceof IEventHandler) {
\r
295 IEventHandler h = (IEventHandler) focusNode;
\r
296 if (eats(h.getEventMask(), EventTypes.toTypeMask(e))) {
\r
297 if (h.handleEvent(e))
\r
301 for (IEventHandler l : handlers) {
\r
302 if (l.handleEvent(e))
\r
308 private boolean handleFocusEvent(FocusEvent e) {
\r
309 return handleEvent(e, null, focusListeners.getListeners());
\r
312 private boolean handleTimeEvent(TimeEvent e) {
\r
313 return handleEvent(e, null, timeListeners.getListeners());
\r
316 private boolean handleCommandEvent(CommandEvent e) {
\r
317 IEventHandler[] sorted = sortedCommandListeners;
\r
318 if (sorted == null)
\r
319 sortedCommandListeners = sorted = sort(commandListeners.getListeners());
\r
320 return handleEvent(e, sg.getFocusNode(), sorted);
\r
323 private boolean handleKeyEvent(KeyEvent e) {
\r
324 IEventHandler[] sorted = sortedKeyListeners;
\r
325 if (sorted == null)
\r
326 sortedKeyListeners = sorted = sort(keyListeners.getListeners());
\r
327 return handleEvent(e, sg.getFocusNode(), sorted);
\r
331 public int getEventMask() {
332 return EventTypes.AnyMask;
336 public boolean handleEvent(Event e) {
\r
338 debug("handle event: " + e);
\r
340 int eventType = EventTypes.toType(e);
342 case EventTypes.Command:
\r
343 return handleCommandEvent((CommandEvent) e);
\r
345 case EventTypes.FocusGained:
\r
346 case EventTypes.FocusLost:
\r
347 return handleFocusEvent((FocusEvent) e);
\r
349 case EventTypes.KeyPressed:
\r
350 case EventTypes.KeyReleased:
\r
351 return handleKeyEvent((KeyEvent) e);
\r
353 case EventTypes.MouseButtonPressed:
\r
354 case EventTypes.MouseButtonReleased:
\r
355 case EventTypes.MouseClick:
\r
356 case EventTypes.MouseDoubleClick:
\r
357 case EventTypes.MouseDragBegin:
\r
358 case EventTypes.MouseEnter:
\r
359 case EventTypes.MouseExit:
\r
360 case EventTypes.MouseMoved:
\r
361 case EventTypes.MouseWheel:
\r
362 return handleMouseEvent((MouseEvent) e, eventType);
\r
364 case EventTypes.Time:
365 return handleTimeEvent((TimeEvent) e);
\r
370 public void add(IEventHandler item) {
\r
371 if (!(item instanceof IG2DNode))
\r
372 throw new IllegalArgumentException("event handler must be an IG2DNode");
\r
374 int mask = item.getEventMask();
\r
375 if (eats(mask, EventTypes.CommandMask)) {
\r
376 commandListeners.add(item);
\r
377 sortedCommandListeners = null;
\r
379 if (eats(mask, EventTypes.FocusMask)) {
\r
380 focusListeners.add(item);
\r
382 if (eats(mask, EventTypes.KeyMask)) {
\r
383 keyListeners.add(item);
\r
384 sortedKeyListeners = null;
\r
386 if (eats(mask, EventTypes.MouseMask)) {
\r
387 mouseListeners.add(item);
\r
388 sortedMouseListeners = null;
\r
390 if (eats(mask, EventTypes.TimeMask)) {
\r
391 timeListeners.add(item);
\r
395 public boolean remove(IEventHandler item) {
\r
396 if (!(item instanceof IG2DNode))
\r
397 throw new IllegalArgumentException("event handler must be an IG2DNode");
\r
399 int mask = item.getEventMask();
\r
400 boolean removed = false;
\r
401 if (eats(mask, EventTypes.CommandMask)) {
\r
402 removed |= commandListeners.remove(item);
\r
403 sortedCommandListeners = null;
\r
405 if (eats(mask, EventTypes.FocusMask)) {
\r
406 removed |= focusListeners.remove(item);
\r
408 if (eats(mask, EventTypes.KeyMask)) {
\r
409 removed |= keyListeners.remove(item);
\r
410 sortedKeyListeners = null;
\r
412 if (eats(mask, EventTypes.MouseMask)) {
\r
413 removed |= mouseListeners.remove(item);
\r
414 sortedMouseListeners = null;
\r
416 if (eats(mask, EventTypes.TimeMask)) {
\r
417 removed |= timeListeners.remove(item);
\r
422 private static boolean eats(int handlerMask, int eventTypeMask) {
\r
423 return (handlerMask & eventTypeMask) != 0;
\r
426 private void debug(String msg) {
\r
427 System.out.println(getClass().getSimpleName() + ": " + msg);
\r
430 public void dispose() {
\r
431 commandListeners.clear();
\r
432 commandListeners = null;
\r
433 focusListeners.clear();
\r
434 focusListeners = null;
\r
435 keyListeners.clear();
\r
436 keyListeners = null;
\r
437 mouseListeners.clear();
\r
438 mouseListeners = null;
\r
440 sortedCommandListeners = null;
\r
441 sortedKeyListeners = null;
\r
442 sortedMouseListeners = null;
\r
444 timeListeners.clear();
\r
445 timeListeners = null;
\r