/******************************************************************************* * Copyright (c) 2007, 2011 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.scenegraph.g2d.events; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DFocusManager; import org.simantics.scenegraph.g2d.G2DSceneGraph; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin; import org.simantics.scenegraph.g2d.events.command.CommandEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Delivers events (mouse, key, focus, command, time) to scene graph nodes that * have registered to receive them. * * @author Tuukka Lehtonen */ public class NodeEventHandler implements IEventHandler { private static final Logger LOGGER = LoggerFactory.getLogger(NodeEventHandler.class); private static final boolean DEBUG_EVENTS = false; private static final boolean DEBUG_HANDLER_SORT = false; private static final IEventHandler[] NONE = {}; public static class TreePreOrderComparator implements Comparator { static enum Order { ASCENDING, DESCENDING } static class Temp { ArrayList path1 = new ArrayList(); ArrayList path2 = new ArrayList(); } private transient ThreadLocal temp = new ThreadLocal() { protected Temp initialValue() { return new Temp(); } }; Order order; public TreePreOrderComparator(Order order) { this.order = order; } void getTreePath(INode node, ArrayList result) { result.clear(); for (; node != null; node = node.getParent()) result.add(node); } void notSameGraph(INode o1, INode o2) { throw new IllegalStateException("nodes " + o1 + " and " + o2 + " not part of same scene graph.\n\t root 1: " + o1.getRootNode() + "\n\troot 2: " + o2.getRootNode()); } @Override public int compare(IEventHandler e1, IEventHandler e2) { if (e1 == e2) return 0; Temp tmp = temp.get(); ArrayList path1 = tmp.path1; ArrayList path2 = tmp.path2; try { // Get path to root node for both nodes getTreePath((INode) e1, path1); getTreePath((INode) e2, path2); // Sanity checks: nodes part of same scene graph if (path1.get(path1.size() - 1) != path2.get(path2.size() - 1)) notSameGraph((INode)e1, (INode)e2); // Find first non-matching nodes in the paths starting from the root node int i1 = path1.size() - 1; int i2 = path2.size() - 1; for (; i1 >= 0 && i2 >= 0; --i1, --i2) { INode p1 = path1.get(i1); INode p2 = path2.get(i2); if (p1 != p2) { break; } } // Pre-order: a node that is on the tree path of another node is first if (i1 < 0) return Order.ASCENDING == order ? -1 : 1; if (i2 < 0) return Order.ASCENDING == order ? 1 : -1; return compare(path1.get(i1), path2.get(i2)); } finally { // Don't hold on to objects unnecessarily path1.clear(); path2.clear(); } } private int compare(INode n1, INode n2) { if(n1 instanceof IG2DNode) { if(n2 instanceof IG2DNode) { int z1 = ((IG2DNode)n1).getZIndex(); int z2 = ((IG2DNode)n2).getZIndex(); int c = Integer.compare(z1, z2); return order == Order.ASCENDING ? c : -c; } else return -1; // sort IG2DNodes before non-IG2DNodes } else { if(n2 instanceof IG2DNode) return 1; else return 0; // all non-IG2DNodes are equal in comparison } } }; TreePreOrderComparator COMPARATOR = new TreePreOrderComparator(TreePreOrderComparator.Order.DESCENDING); /** * {@link FocusEvent} are propagated first to the scene graph focus node, * then to event handler nodes in scene graph tree pre-order. */ protected List focusListeners = new ArrayList(); protected IEventHandler[] sortedFocusListeners = null; /** * {@link TimeEvent} are propagated first to the scene graph focus node, * then to event handler nodes in scene graph tree pre-order. */ protected List timeListeners = new ArrayList(); protected IEventHandler[] sortedTimeListeners = null; /** * {@link CommandEvent} are propagated first to the scene graph focus node, * then to event handler nodes in scene graph tree pre-order. */ protected List commandListeners = new ArrayList(); protected IEventHandler[] sortedCommandListeners = null; /** * {@link KeyEvent} are propagated first to the scene graph focus node, then * to event handler nodes in scene graph tree pre-order. */ protected List keyListeners = new ArrayList(); protected IEventHandler[] sortedKeyListeners = null; /** * {@link MouseEvent} are propagated first to the scene graph focus node, * then to event handler nodes in scene graph tree pre-order. */ protected List mouseListeners = new ArrayList(); protected IEventHandler[] sortedMouseListeners = null; /** * {@link MouseDragBegin} events are propagated first to the scene graph focus node, then * to event handler nodes in scene graph tree pre-order. */ protected List mouseDragBeginListeners = new ArrayList(); protected IEventHandler[] sortedMouseDragBeginListeners = null; /** * The scene graph this instance handles event propagation for. */ protected G2DSceneGraph sg; public NodeEventHandler(G2DSceneGraph sg) { this.sg = sg; } @SuppressWarnings("unused") private IEventHandler[] sort(IEventHandler[] sort) { if (DEBUG_HANDLER_SORT) debug("copy sort " + sort.length + " handlers"); return sortInplace(Arrays.copyOf(sort, sort.length)); } private IEventHandler[] sortInplace(IEventHandler[] sort) { if (DEBUG_HANDLER_SORT) debug("in-place sort " + sort.length + " handlers"); Arrays.sort(sort, COMPARATOR); return sort; } public boolean mousePressed(MouseButtonPressedEvent event) { G2DFocusManager.INSTANCE.clearFocus(); try { // Point op = event.getPoint(); // for (MouseListener l : mouseListeners.getListeners()) { // MouseEvent e = (MouseEvent) NodeUtil.transformEvent(event,(IG2DNode) l); // l.mousePressed(e); // event.translatePoint((int)(op.getX()-event.getX()), (int)(op.getY()-event.getY())); // if (e.isConsumed()) // break; // } return false; } finally { if (sg.getRootPane() != null) { if (G2DFocusManager.INSTANCE.getFocusOwner() == null) { sg.getRootPane().requestFocusInWindow(); //sg.getRootPane().repaint(); //TODO : why repaint here? FocusOwner seems to be always null, so this causes unnecessary delays when interacting the canvas. } } } } private boolean handleEvent(Event e, IG2DNode focusNode, IEventHandler[] handlers) { int typeMask = EventTypes.toTypeMask(e); if (focusNode instanceof IEventHandler) { IEventHandler h = (IEventHandler) focusNode; if (eats(h.getEventMask(), typeMask)) { if (h.handleEvent(e)) return true; } } for (IEventHandler l : handlers) { if (eats(l.getEventMask(), typeMask)) { if (l.handleEvent(e)) return true; } } return false; } private boolean handleMouseEvent(MouseEvent e, int eventType) { IEventHandler[] sorted = sortedMouseListeners; if (sorted == null) sortedMouseListeners = sorted = sortInplace(mouseListeners.toArray(NONE)); return handleEvent(e, sg.getFocusNode(), sorted); } private boolean handleMouseDragBeginEvent(MouseEvent e, int eventType) { IEventHandler[] sorted = sortedMouseDragBeginListeners; if (sorted == null) sortedMouseDragBeginListeners = sorted = sortInplace(mouseDragBeginListeners.toArray(NONE)); // Give null for focusNode because we want to propagate // this event in scene tree pre-order only. return handleEvent(e, null, sorted); } private boolean handleFocusEvent(FocusEvent e) { IEventHandler[] sorted = sortedFocusListeners; if (sorted == null) sortedFocusListeners = sorted = sortInplace(focusListeners.toArray(NONE)); return handleEvent(e, null, sorted); } private boolean handleTimeEvent(TimeEvent e) { IEventHandler[] sorted = sortedTimeListeners; if (sorted == null) sortedTimeListeners = sorted = sortInplace(timeListeners.toArray(NONE)); return handleEvent(e, null, sorted); } private boolean handleCommandEvent(CommandEvent e) { IEventHandler[] sorted = sortedCommandListeners; if (sorted == null) sortedCommandListeners = sorted = sortInplace(commandListeners.toArray(NONE)); return handleEvent(e, sg.getFocusNode(), sorted); } private boolean handleKeyEvent(KeyEvent e) { IEventHandler[] sorted = sortedKeyListeners; if (sorted == null) sortedKeyListeners = sorted = sortInplace(keyListeners.toArray(NONE)); return handleEvent(e, sg.getFocusNode(), sorted); } @Override public int getEventMask() { return EventTypes.AnyMask; } @Override public boolean handleEvent(Event e) { if (DEBUG_EVENTS) debug("handle event: " + e); int eventType = EventTypes.toType(e); switch (eventType) { case EventTypes.Command: return handleCommandEvent((CommandEvent) e); case EventTypes.FocusGained: case EventTypes.FocusLost: return handleFocusEvent((FocusEvent) e); case EventTypes.KeyPressed: case EventTypes.KeyReleased: return handleKeyEvent((KeyEvent) e); case EventTypes.MouseDragBegin: return handleMouseDragBeginEvent((MouseEvent) e, eventType); case EventTypes.MouseButtonPressed: case EventTypes.MouseButtonReleased: case EventTypes.MouseClick: case EventTypes.MouseDoubleClick: case EventTypes.MouseEnter: case EventTypes.MouseExit: case EventTypes.MouseMoved: case EventTypes.MouseWheel: return handleMouseEvent((MouseEvent) e, eventType); case EventTypes.Time: return handleTimeEvent((TimeEvent) e); } return false; } public void add(IEventHandler item) { if (!(item instanceof IG2DNode)) throw new IllegalArgumentException("event handler must be an IG2DNode"); int mask = item.getEventMask(); if (eats(mask, EventTypes.CommandMask)) { commandListeners.add(item); sortedCommandListeners = null; } if (eats(mask, EventTypes.FocusMask)) { focusListeners.add(item); sortedFocusListeners = null; } if (eats(mask, EventTypes.KeyMask)) { keyListeners.add(item); sortedKeyListeners = null; } if (eats(mask, EventTypes.MouseDragBeginMask)) { mouseDragBeginListeners.add(item); sortedMouseDragBeginListeners = null; } if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) { mouseListeners.add(item); sortedMouseListeners = null; } if (eats(mask, EventTypes.TimeMask)) { timeListeners.add(item); sortedTimeListeners = null; } } public boolean remove(IEventHandler item) { if (!(item instanceof IG2DNode)) throw new IllegalArgumentException("event handler must be an IG2DNode"); int mask = item.getEventMask(); boolean removed = false; if (eats(mask, EventTypes.CommandMask)) { removed |= commandListeners.remove(item); sortedCommandListeners = null; } if (eats(mask, EventTypes.FocusMask)) { removed |= focusListeners.remove(item); sortedFocusListeners = null; } if (eats(mask, EventTypes.KeyMask)) { removed |= keyListeners.remove(item); sortedKeyListeners = null; } if (eats(mask, EventTypes.MouseDragBeginMask)) { removed |= mouseDragBeginListeners.remove(item); sortedMouseDragBeginListeners = null; } if (eats(mask, EventTypes.MouseMask & ~EventTypes.MouseDragBeginMask)) { removed |= mouseListeners.remove(item); sortedMouseListeners = null; } if (eats(mask, EventTypes.TimeMask)) { removed |= timeListeners.remove(item); sortedTimeListeners = null; } return removed; } private static boolean eats(int handlerMask, int eventTypeMask) { return (handlerMask & eventTypeMask) != 0; } private void debug(String msg) { System.out.println(getClass().getSimpleName() + ": " + msg); } public void dispose() { commandListeners.clear(); commandListeners = null; focusListeners.clear(); focusListeners = null; keyListeners.clear(); keyListeners = null; mouseListeners.clear(); mouseListeners = null; sg = null; sortedCommandListeners = null; sortedKeyListeners = null; sortedMouseListeners = null; timeListeners.clear(); timeListeners = null; } }