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.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Comparator;
17 import java.util.List;
19 import org.simantics.scenegraph.INode;
20 import org.simantics.scenegraph.g2d.G2DFocusManager;
21 import org.simantics.scenegraph.g2d.G2DSceneGraph;
22 import org.simantics.scenegraph.g2d.IG2DNode;
23 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
24 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
25 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
30 * Delivers events (mouse, key, focus, command, time) to scene graph nodes that
31 * have registered to receive them.
33 * @author Tuukka Lehtonen
35 public class NodeEventHandler implements IEventHandler {
37 private static final Logger LOGGER = LoggerFactory.getLogger(NodeEventHandler.class);
39 private static final boolean DEBUG_EVENTS = false;
40 private static final boolean DEBUG_HANDLER_SORT = false;
42 private static final IEventHandler[] NONE = {};
44 public static class TreePreOrderComparator implements Comparator<IEventHandler> {
52 ArrayList<INode> path1 = new ArrayList<INode>();
53 ArrayList<INode> path2 = new ArrayList<INode>();
56 private transient ThreadLocal<Temp> temp = new ThreadLocal<Temp>() {
57 protected Temp initialValue() {
64 public TreePreOrderComparator(Order order) {
68 void getTreePath(INode node, ArrayList<INode> result) {
70 for (; node != null; node = node.getParent())
74 void notSameGraph(INode o1, INode o2) {
75 throw new IllegalStateException("nodes " + o1 + " and " + o2
76 + " not part of same scene graph.\n\t root 1: " + o1.getRootNode() + "\n\troot 2: "
81 public int compare(IEventHandler e1, IEventHandler e2) {
85 Temp tmp = temp.get();
86 ArrayList<INode> path1 = tmp.path1;
87 ArrayList<INode> path2 = tmp.path2;
90 // Get path to root node for both nodes
91 getTreePath((INode) e1, path1);
92 getTreePath((INode) e2, path2);
94 // Sanity checks: nodes part of same scene graph
95 if (path1.get(path1.size() - 1) != path2.get(path2.size() - 1))
96 notSameGraph((INode)e1, (INode)e2);
98 // Find first non-matching nodes in the paths starting from the root node
99 int i1 = path1.size() - 1;
100 int i2 = path2.size() - 1;
101 for (; i1 >= 0 && i2 >= 0; --i1, --i2) {
102 INode p1 = path1.get(i1);
103 INode p2 = path2.get(i2);
109 // Pre-order: a node that is on the tree path of another node is first
111 return Order.ASCENDING == order ? -1 : 1;
113 return Order.ASCENDING == order ? 1 : -1;
115 return compare(path1.get(i1), path2.get(i2));
117 // Don't hold on to objects unnecessarily
123 private int compare(INode n1, INode n2) {
124 if(n1 instanceof IG2DNode) {
125 if(n2 instanceof IG2DNode) {
126 int z1 = ((IG2DNode)n1).getZIndex();
127 int z2 = ((IG2DNode)n2).getZIndex();
128 int c = Integer.compare(z1, z2);
129 return order == Order.ASCENDING ? c : -c;
132 return -1; // sort IG2DNodes before non-IG2DNodes
135 if(n2 instanceof IG2DNode)
138 return 0; // all non-IG2DNodes are equal in comparison
143 TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING);
146 * {@link FocusEvent} are propagated first to the scene graph focus node,
147 * then to event handler nodes in scene graph tree pre-order.
149 protected List<IEventHandler> focusListeners = new ArrayList<IEventHandler>();
150 protected IEventHandler[] sortedFocusListeners = null;
153 * {@link TimeEvent} are propagated first to the scene graph focus node,
154 * then to event handler nodes in scene graph tree pre-order.
156 protected List<IEventHandler> timeListeners = new ArrayList<IEventHandler>();
157 protected IEventHandler[] sortedTimeListeners = null;
160 * {@link CommandEvent} are propagated first to the scene graph focus node,
161 * then to event handler nodes in scene graph tree pre-order.
163 protected List<IEventHandler> commandListeners = new ArrayList<IEventHandler>();
164 protected IEventHandler[] sortedCommandListeners = null;
167 * {@link KeyEvent} are propagated first to the scene graph focus node, then
168 * to event handler nodes in scene graph tree pre-order.
170 protected List<IEventHandler> keyListeners = new ArrayList<IEventHandler>();
171 protected IEventHandler[] sortedKeyListeners = null;
174 * {@link MouseEvent} are propagated first to the scene graph focus node,
175 * then to event handler nodes in scene graph tree pre-order.
177 protected List<IEventHandler> mouseListeners = new ArrayList<IEventHandler>();
178 protected IEventHandler[] sortedMouseListeners = null;
181 * {@link MouseDragBegin} events are propagated first to the scene graph focus node, then
182 * to event handler nodes in scene graph tree pre-order.
184 protected List<IEventHandler> mouseDragBeginListeners = new ArrayList<IEventHandler>();
185 protected IEventHandler[] sortedMouseDragBeginListeners = null;
188 * The scene graph this instance handles event propagation for.
190 protected G2DSceneGraph sg;
192 public NodeEventHandler(G2DSceneGraph sg) {
196 @SuppressWarnings("unused")
197 private IEventHandler[] sort(IEventHandler[] sort) {
198 if (DEBUG_HANDLER_SORT)
199 debug("copy sort " + sort.length + " handlers");
200 return sortInplace(Arrays.copyOf(sort, sort.length));
203 private IEventHandler[] sortInplace(IEventHandler[] sort) {
204 if (DEBUG_HANDLER_SORT)
205 debug("in-place sort " + sort.length + " handlers");
206 Arrays.sort(sort, COMPARATOR);
210 public boolean mousePressed(MouseButtonPressedEvent event) {
211 G2DFocusManager.INSTANCE.clearFocus();
213 // Point op = event.getPoint();
214 // for (MouseListener l : mouseListeners.getListeners()) {
215 // MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l);
216 // l.mousePressed(e);
217 // event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY()));
218 // if (e.isConsumed())
223 if (sg.getRootPane() != null) {
224 if (G2DFocusManager.INSTANCE.getFocusOwner() == null) {
225 sg.getRootPane().requestFocusInWindow();
226 //sg.getRootPane().repaint(); //TODO : why repaint here? FocusOwner seems to be always null, so this causes unnecessary delays when interacting the canvas.
232 private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) {
233 int typeMask = EventTypes.toTypeMask(e);
234 if (focusNode instanceof IEventHandler) {
235 IEventHandler h = (IEventHandler) focusNode;
236 if (eats(h.getEventMask(), typeMask)) {
237 if (h.handleEvent(e))
241 for (IEventHandler l : handlers) {
242 if (eats(l.getEventMask(), typeMask)) {
243 if (l.handleEvent(e))
250 private boolean handleMouseEvent(MouseEvent e, int eventType) {
251 IEventHandler[] sorted = sortedMouseListeners;
253 sortedMouseListeners = sorted = sortInplace(mouseListeners.toArray(NONE));
254 return handleEvent(e, sg.getFocusNode(), sorted);
257 private boolean handleMouseDragBeginEvent(MouseEvent e, int eventType) {
258 IEventHandler[] sorted = sortedMouseDragBeginListeners;
260 sortedMouseDragBeginListeners = sorted = sortInplace(mouseDragBeginListeners.toArray(NONE));
261 // Give null for focusNode because we want to propagate
262 // this event in scene tree pre-order only.
263 return handleEvent(e, null, sorted);
266 private boolean handleFocusEvent(FocusEvent e) {
267 IEventHandler[] sorted = sortedFocusListeners;
269 sortedFocusListeners = sorted = sortInplace(focusListeners.toArray(NONE));
270 return handleEvent(e, null, sorted);
273 private boolean handleTimeEvent(TimeEvent e) {
274 IEventHandler[] sorted = sortedTimeListeners;
276 sortedTimeListeners = sorted = sortInplace(timeListeners.toArray(NONE));
277 return handleEvent(e, null, sorted);
280 private boolean handleCommandEvent(CommandEvent e) {
281 IEventHandler[] sorted = sortedCommandListeners;
283 sortedCommandListeners = sorted = sortInplace(commandListeners.toArray(NONE));
284 return handleEvent(e, sg.getFocusNode(), sorted);
287 private boolean handleKeyEvent(KeyEvent e) {
288 IEventHandler[] sorted = sortedKeyListeners;
290 sortedKeyListeners = sorted = sortInplace(keyListeners.toArray(NONE));
291 return handleEvent(e, sg.getFocusNode(), sorted);
295 public int getEventMask() {
296 return EventTypes.AnyMask;
300 public boolean handleEvent(Event e) {
302 debug("handle event: " + e);
304 int eventType = EventTypes.toType(e);
306 case EventTypes.Command:
307 return handleCommandEvent((CommandEvent) e);
309 case EventTypes.FocusGained:
310 case EventTypes.FocusLost:
311 return handleFocusEvent((FocusEvent) e);
313 case EventTypes.KeyPressed:
314 case EventTypes.KeyReleased:
315 return handleKeyEvent((KeyEvent) e);
317 case EventTypes.MouseDragBegin:
318 return handleMouseDragBeginEvent((MouseEvent) e, eventType);
320 case EventTypes.MouseButtonPressed:
321 case EventTypes.MouseButtonReleased:
322 case EventTypes.MouseClick:
323 case EventTypes.MouseDoubleClick:
324 case EventTypes.MouseEnter:
325 case EventTypes.MouseExit:
326 case EventTypes.MouseMoved:
327 case EventTypes.MouseWheel:
328 return handleMouseEvent((MouseEvent) e, eventType);
330 case EventTypes.Time:
331 return handleTimeEvent((TimeEvent) e);
336 public void add(IEventHandler item) {
337 if (!(item instanceof IG2DNode))
338 throw new IllegalArgumentException("event handler must be an IG2DNode");
340 int mask = item.getEventMask();
341 if (eats(mask, EventTypes.CommandMask)) {
342 commandListeners.add(item);
343 sortedCommandListeners = null;
345 if (eats(mask, EventTypes.FocusMask)) {
346 focusListeners.add(item);
347 sortedFocusListeners = null;
349 if (eats(mask, EventTypes.KeyMask)) {
350 keyListeners.add(item);
351 sortedKeyListeners = null;
353 if (eats(mask, EventTypes.MouseDragBeginMask)) {
354 mouseDragBeginListeners.add(item);
355 sortedMouseDragBeginListeners = null;
357 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
358 mouseListeners.add(item);
359 sortedMouseListeners = null;
361 if (eats(mask, EventTypes.TimeMask)) {
362 timeListeners.add(item);
363 sortedTimeListeners = null;
367 public boolean remove(IEventHandler item) {
368 if (!(item instanceof IG2DNode))
369 throw new IllegalArgumentException("event handler must be an IG2DNode");
371 int mask = item.getEventMask();
372 boolean removed = false;
373 if (eats(mask, EventTypes.CommandMask)) {
374 removed |= commandListeners.remove(item);
375 sortedCommandListeners = null;
377 if (eats(mask, EventTypes.FocusMask)) {
378 removed |= focusListeners.remove(item);
379 sortedFocusListeners = null;
381 if (eats(mask, EventTypes.KeyMask)) {
382 removed |= keyListeners.remove(item);
383 sortedKeyListeners = null;
385 if (eats(mask, EventTypes.MouseDragBeginMask)) {
386 removed |= mouseDragBeginListeners.remove(item);
387 sortedMouseDragBeginListeners = null;
389 if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) {
390 removed |= mouseListeners.remove(item);
391 sortedMouseListeners = null;
393 if (eats(mask, EventTypes.TimeMask)) {
394 removed |= timeListeners.remove(item);
395 sortedTimeListeners = null;
400 private static boolean eats(int handlerMask, int eventTypeMask) {
401 return (handlerMask & eventTypeMask) != 0;
404 private void debug(String msg) {
405 System.out.println(getClass().getSimpleName() + ": " + msg);
408 public void dispose() {
409 commandListeners.clear();
410 commandListeners = null;
411 focusListeners.clear();
412 focusListeners = null;
413 keyListeners.clear();
415 mouseListeners.clear();
416 mouseListeners = null;
418 sortedCommandListeners = null;
419 sortedKeyListeners = null;
420 sortedMouseListeners = null;
422 timeListeners.clear();
423 timeListeners = null;