]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / pointertool / PointerInteractor.java
index 2cbbf55d24cf37a1cd26d6b599db647b6e7ee397..33b4402db56b46dfc738a5aea43f17445133318d 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.diagram.participant.pointertool;\r
-\r
-import java.awt.Shape;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Path2D;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Set;\r
-\r
-import org.simantics.g2d.canvas.Hints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.ICanvasParticipant;\r
-import org.simantics.g2d.canvas.IToolMode;\r
-import org.simantics.g2d.canvas.impl.CanvasContext;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;\r
-import org.simantics.g2d.connection.IConnectionAdvisor;\r
-import org.simantics.g2d.connection.handler.ConnectionHandler;\r
-import org.simantics.g2d.diagram.DiagramHints;\r
-import org.simantics.g2d.diagram.handler.PickContext;\r
-import org.simantics.g2d.diagram.handler.PickRequest;\r
-import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;\r
-import org.simantics.g2d.diagram.handler.PickRequest.PickSorter;\r
-import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;\r
-import org.simantics.g2d.diagram.participant.Selection;\r
-import org.simantics.g2d.diagram.participant.TerminalPainter;\r
-import org.simantics.g2d.diagram.participant.TerminalPainter.ChainedHoverStrategy;\r
-import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;\r
-import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;\r
-import org.simantics.g2d.element.ElementClassProviders;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.IElementClassProvider;\r
-import org.simantics.g2d.participant.KeyUtil;\r
-import org.simantics.g2d.participant.MouseUtil;\r
-import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.g2d.scenegraph.SceneGraphConstants;\r
-import org.simantics.g2d.utils.CanvasUtils;\r
-import org.simantics.g2d.utils.GeometryUtils;\r
-import org.simantics.scenegraph.g2d.events.Event;\r
-import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
-import org.simantics.scenegraph.g2d.events.KeyEvent;\r
-import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;\r
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
-import org.simantics.scenegraph.g2d.events.command.Commands;\r
-import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
-import org.simantics.utils.ObjectUtils;\r
-import org.simantics.utils.datastructures.context.IContext;\r
-import org.simantics.utils.datastructures.context.IContextListener;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-\r
-/**\r
- * Pointer tool does the following operations with mouse:\r
- * <ul>\r
- * <li>Selections</li>\r
- * <li>Scale</li>\r
- * <li>Rotate</li>\r
- * <li>Translate</li>\r
- * <li>Draws connections (requires re-implementing\r
- * {@link #createConnectTool(TerminalInfo, int, Point2D)})</li>\r
- * </ul>\r
- * \r
- * Pointer tool is active only when {@link Hints#KEY_TOOL} is\r
- * {@value Hints#POINTERTOOL}.\r
- * \r
- * @author Toni Kalajainen\r
- */\r
-public class PointerInteractor extends AbstractDiagramParticipant {\r
-\r
-    /**\r
-     * A hint key for terminal pick distance in control pixels.\r
-     * @see #PICK_DIST\r
-     */\r
-    public static final Key KEY_PICK_DISTANCE = new KeyOf(Double.class, "PICK_DISTANCE");\r
-\r
-    /**\r
-     * Default terminal pick distance in control pixels.\r
-     * @see #DEFAULT_PICK_DISTANCE\r
-     */\r
-    public static final double PICK_DIST = 10;\r
-\r
-    /**\r
-     * @see #altToggled(KeyEvent)\r
-     */\r
-    protected int              lastStateMask;\r
-\r
-    /**\r
-     * @see #altToggled(KeyEvent)\r
-     */\r
-    protected boolean          temporarilyEnabledConnectTool = false;\r
-\r
-    public class DefaultHoverStrategy extends ChainedHoverStrategy {\r
-        public DefaultHoverStrategy(TerminalHoverStrategy orig) {\r
-            super(orig);\r
-        }\r
-        @Override\r
-        public boolean highlightEnabled() {\r
-            if (Hints.CONNECTTOOL.equals(getToolMode()))\r
-                return true;\r
-\r
-            boolean ct = connectToolModifiersPressed(lastStateMask);\r
-            //System.out.println("highlightEnabled: " + String.format("%x", lastStateMask) + " : " + ct);\r
-            return ct;\r
-        }\r
-        @Override\r
-        public boolean canHighlight(TerminalInfo ti) {\r
-            //boolean alt = (lastStateMask & MouseEvent.ALT_MASK) != 0;\r
-            //System.out.println("canHighlight: " + String.format("%x", lastStateMask) + " : " + alt);\r
-            //if (!alt)\r
-            //   return false;\r
-            IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
-            return advisor == null || advisor.canBeginConnection(null, ti.e, ti.t);\r
-        }\r
-    }\r
-\r
-    @Dependency Selection selection;\r
-    @Dependency KeyUtil keys;\r
-    @Dependency TransformUtil util;\r
-    @Dependency PickContext pickContext;\r
-    @Dependency MouseUtil mice;\r
-    @Reference TerminalPainter terminalPainter;\r
-\r
-    /**\r
-     * This must be higher than\r
-     * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}\r
-     * ({@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to allow for\r
-     * better selection handling than is possible by using the plain scene graph\r
-     * event handling facilities which are installed in {@link CanvasContext}.\r
-     */\r
-    public static final int TOOL_PRIORITY = 1 << 21;\r
-\r
-    /**\r
-     * This must be lower than\r
-     * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY} (\r
-     * {@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to not start box\r
-     * handling before scene graph nodes have been given a chance to react\r
-     * events.\r
-     */\r
-    public static final int BOX_SELECT_PRIORITY = 1 << 19;\r
-\r
-    private static final Path2D LINE10;\r
-    private static final Path2D LINE15;\r
-    private static final Path2D LINE20;\r
-\r
-    boolean clickSelect;\r
-    boolean boxSelect;\r
-    boolean dragElement, dndDragElement;\r
-    boolean connect;\r
-    boolean doubleClickEdit;\r
-    protected IElementClassProvider elementClassProvider;\r
-\r
-    PickPolicy boxSelectMode = PickPolicy.PICK_CONTAINED_OBJECTS;\r
-\r
-    DefaultHoverStrategy hoverStrategy;\r
-\r
-    private PickSorter pickSorter;\r
-    \r
-    public PointerInteractor() {\r
-        this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), null);\r
-    }\r
-\r
-    public PointerInteractor(PickSorter pickSorter) {\r
-        this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), pickSorter);\r
-    }\r
-\r
-    public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, IElementClassProvider ecp) {\r
-        this(clickSelect, boxSelect, dragElement, dndDragElement, connect, false, ecp, null);\r
-    }\r
-\r
-    public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider ecp, PickSorter pickSorter) {\r
-        super();\r
-        this.clickSelect = clickSelect;\r
-        this.boxSelect = boxSelect;\r
-        this.dragElement = dragElement;\r
-        this.dndDragElement = dndDragElement;\r
-        this.connect = connect;\r
-        this.doubleClickEdit = doubleClickEdit;\r
-        this.elementClassProvider = ecp;\r
-        this.pickSorter = pickSorter;\r
-    }\r
-\r
-    @Override\r
-    public void addedToContext(ICanvasContext ctx) {\r
-        super.addedToContext(ctx);\r
-        hoverStrategy = new DefaultHoverStrategy((TerminalHoverStrategy) getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY));\r
-        setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, hoverStrategy);\r
-    }\r
-\r
-    @EventHandler(priority = 0)\r
-    public boolean handleStateMask(MouseEvent me) {\r
-        lastStateMask = me.stateMask;\r
-        if (temporarilyEnabledConnectTool) {\r
-            if (!connectToolModifiersPressed(me.stateMask)) {\r
-                temporarilyEnabledConnectTool = false;\r
-                setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);\r
-            }\r
-        } else {\r
-            // It may be that the mouse has come into this control\r
-            // from outside and no key state changes have occurred yet.\r
-            // In this case this code should take care of moving the canvas\r
-            // context into CONNECTTOOL mode.\r
-            if (getToolMode() == Hints.POINTERTOOL\r
-                    && connectToolModifiersPressed(me.stateMask))\r
-            {\r
-                temporarilyEnabledConnectTool = true;\r
-                setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    private static int mask(int mask, int mask2, boolean set) {\r
-        return set ? mask | mask2 : mask & ~mask2;\r
-    }\r
-\r
-    @EventHandler(priority = -1)\r
-    public boolean altToggled(KeyEvent ke) {\r
-        int mods = ke.stateMask;\r
-        boolean press = ke instanceof KeyPressedEvent;\r
-        boolean modifierPressed = false;\r
-        if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {\r
-            mods = mask(mods, MouseEvent.ALT_MASK, press);\r
-            modifierPressed = true;\r
-        }\r
-        if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT_GRAPH) {\r
-            mods = mask(mods, MouseEvent.ALT_GRAPH_MASK, press);\r
-            modifierPressed = true;\r
-        }\r
-        if (ke.keyCode == java.awt.event.KeyEvent.VK_SHIFT) {\r
-            mods = mask(mods, MouseEvent.SHIFT_MASK, press);\r
-            modifierPressed = true;\r
-        }\r
-        if (ke.keyCode == java.awt.event.KeyEvent.VK_CONTROL) {\r
-            mods = mask(mods, MouseEvent.CTRL_MASK, press);\r
-            modifierPressed = true;\r
-        }\r
-        if (ke.keyCode == java.awt.event.KeyEvent.VK_META) {\r
-            // TODO: NO MASK FOR META!\r
-            modifierPressed = true;\r
-        }\r
-        // Don't deny connecting when CTRL is marked pressed because ALT_GRAPH\r
-        // is actually ALT+CTRL in SWT. There's no way in SWT to tell apart\r
-        // CTRL+ALT and ALT GRAPH.\r
-        boolean otherModifiers = (mods & (MouseEvent.SHIFT_MASK)) != 0;\r
-        boolean altModifier = (mods & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;\r
-        if (modifierPressed) {\r
-            boolean altPressed = !otherModifiers && altModifier;\r
-            lastStateMask = mods;\r
-            if (altPressed) {\r
-                IToolMode mode = getToolMode();\r
-                if (mode == Hints.POINTERTOOL) {\r
-                    //System.out.println("TEMP++");\r
-                    temporarilyEnabledConnectTool = true;\r
-                    setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);\r
-                }\r
-            } else {\r
-                if (temporarilyEnabledConnectTool) {\r
-                    //System.out.println("TEMP--");\r
-                    temporarilyEnabledConnectTool = false;\r
-                    setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);\r
-                }\r
-            }\r
-            // Make sure that TerminalPainter updates its scene graph.\r
-            if (terminalPainter != null) {\r
-                terminalPainter.update(terminalPainter.highlightEnabled());\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * @param controlPos\r
-     * @return <code>null</code> if current canvas transform is not invertible\r
-     */\r
-    public Shape getCanvasPickShape(Point2D controlPos) {\r
-        AffineTransform inverse = util.getInverseTransform();\r
-        if (inverse == null)\r
-            return null;\r
-\r
-        double      pd              = getPickDistance();\r
-        Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2+1, pd*2+1);\r
-        Shape       canvasShape     = GeometryUtils.transformShape(controlPickRect, inverse);\r
-        return canvasShape;\r
-    }\r
-\r
-    public List<TerminalInfo> pickTerminals(Point2D controlPos)\r
-    {\r
-        Shape canvasPickRect = getCanvasPickShape(controlPos);\r
-        if (canvasPickRect == null)\r
-            return Collections.emptyList();\r
-        return TerminalUtil.pickTerminals(diagram, canvasPickRect, true, true);\r
-    }\r
-\r
-    public TerminalInfo pickTerminal(Point2D controlPos)\r
-    {\r
-        Shape canvasPickRect = getCanvasPickShape(controlPos);\r
-        if (canvasPickRect == null)\r
-            return null;\r
-        TerminalInfo ti = TerminalUtil.pickTerminal(diagram, canvasPickRect);\r
-        return ti;\r
-    }\r
-\r
-    @EventHandler(priority = TOOL_PRIORITY)\r
-    public boolean handlePress(MouseButtonPressedEvent me) {\r
-        if (!connects())\r
-            return false;\r
-        if (me.button != MouseEvent.LEFT_BUTTON)\r
-            return false;\r
-\r
-        IToolMode mode = getToolMode();\r
-\r
-        // It may be that the mouse has come into this control\r
-        // from outside without focusing it and without any mouse\r
-        // buttons being pressed. If the user is pressing the\r
-        // connection modifier we need to temporarily enable connect\r
-        // mode here and now.\r
-        if (mode == Hints.POINTERTOOL && connectToolModifiersPressed(me.stateMask)) {\r
-            temporarilyEnabledConnectTool = true;\r
-            mode = Hints.CONNECTTOOL;\r
-            setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);\r
-        }\r
-\r
-        if (mode == Hints.CONNECTTOOL) {\r
-            Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, null);\r
-            return checkInitiateConnectTool(me, curCanvasPos);\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    protected boolean checkInitiateConnectTool(MouseEvent me, Point2D mouseCanvasPos) {\r
-        // Pick Terminal\r
-        IToolMode mode = getToolMode();\r
-        if (mode == Hints.CONNECTTOOL || connectToolModifiersPressed(me.stateMask)) {\r
-            IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
-            TerminalInfo ti = pickTerminal(me.controlPosition);\r
-\r
-            ICanvasParticipant bsi = null;\r
-            if (ti != null) {\r
-                if (advisor == null || advisor.canBeginConnection(null, ti.e, ti.t)) {\r
-                    bsi = createConnectTool(ti, me.mouseId, mouseCanvasPos);\r
-                }\r
-            } else {\r
-                ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
-                if (snapAdvisor != null)\r
-                    snapAdvisor.snap(mouseCanvasPos);\r
-\r
-                // Start connection out of thin air, without a terminal.\r
-                bsi = createConnectTool(null, me.mouseId, mouseCanvasPos);\r
-            }\r
-\r
-            // Did we catch anything?\r
-            if (bsi != null) {\r
-                startConnectTool(bsi);\r
-                return true;\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    protected void startConnectTool(ICanvasParticipant tool) {\r
-        getContext().add(tool);\r
-        //System.out.println("TEMP: " + temporarilyEnabledConnectTool);\r
-        if (temporarilyEnabledConnectTool) {\r
-            // Resets pointer tool back into use if necessary after\r
-            // connection has been finished or canceled.\r
-            getContext().addContextListener(new ToolModeResetter(tool));\r
-        }\r
-    }\r
-\r
-    @EventHandler(priority = TOOL_PRIORITY)\r
-    public boolean handleClick(MouseClickEvent me) {\r
-        //System.out.println(getClass().getSimpleName() + ": mouse clicked: @ " + me.time);\r
-\r
-        if (hasDoubleClickEdit() && me.clickCount == 2) {\r
-            if (handleDoubleClick(me))\r
-                return true;\r
-        }\r
-\r
-        if (!hasClickSelect()) return false;\r
-        if (!hasToolMode(Hints.POINTERTOOL)) return false;\r
-\r
-        // Don't handle any events where click count is more than 1 to prevent\r
-        // current diagram selection from bouncing around unnecessarily.\r
-        if (me.clickCount > 1)\r
-            return false;\r
-\r
-        boolean isLeft = me.button == MouseEvent.LEFT_BUTTON;\r
-        boolean isRight = me.button == MouseEvent.RIGHT_BUTTON;\r
-        if (!isLeft && !isRight) return false;\r
-\r
-        boolean popupWasVisible = wasPopupJustClosed(me);\r
-        boolean noModifiers = !anyModifierPressed(me);\r
-        boolean isShiftPressed = me.hasAllModifiers(MouseEvent.SHIFT_MASK);\r
-\r
-        assertDependencies();\r
-\r
-        // Pick Terminal\r
-        double pickDist = getPickDistance();\r
-        Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);\r
-        Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());\r
-        int selectionId = me.mouseId;\r
-\r
-        PickRequest req = new PickRequest(canvasPickRect);\r
-        req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;\r
-        req.pickSorter = pickSorter;\r
-        //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;\r
-        List<IElement> pickables = new ArrayList<IElement>();\r
-        pickContext.pick(diagram, req, pickables);\r
-\r
-        Set<IElement> currentSelection = selection.getSelection(selectionId);\r
-\r
-        // Clear selection\r
-        if (pickables.isEmpty()) {\r
-            if (!popupWasVisible) {\r
-                // Only clear selection on left button clicks.\r
-                if (isLeft) {\r
-                    selection.clear(selectionId);\r
-                }\r
-            }\r
-            if (isRight) {\r
-                if (/*!currentSelection.isEmpty() &&*/ noModifiers)\r
-                    setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);\r
-            }\r
-            return false;\r
-        }\r
-\r
-        // Toggle select\r
-        if ((me.stateMask & MouseEvent.CTRL_MASK) != 0) {\r
-            if (isLeft) {\r
-                /*\r
-                 * - If the mouse points to an object not in the selection, add it to selection.\r
-                 * - If the mouse points to multiple objects, add the first.\r
-                 * - If all objects the mouse points are already in selection, remove one of them from selection.\r
-                 */\r
-                IElement removable = null;\r
-                for (int i = pickables.size() - 1; i >= 0; --i) {\r
-                    IElement pickable = pickables.get(i);\r
-                    if (selection.add(selectionId, pickable)) {\r
-                        removable = null;\r
-                        break;\r
-                    } else\r
-                        removable = pickable;\r
-\r
-                    // Do not perform rotating pick in toggle selection\r
-                    // when only CTRL is pressed. Requires SHIFT+CTRL.\r
-                    if (!isShiftPressed)\r
-                        break;\r
-                }\r
-                if (removable != null)\r
-                    selection.remove(selectionId, removable);\r
-            }\r
-            return false;\r
-        }\r
-\r
-        // Click Select\r
-        {\r
-            if (isLeft && popupWasVisible)\r
-                // Popup menu is visible, just let it close\r
-                return false;\r
-\r
-            // Don't change selection on right clicks if there's more to pick\r
-            // than a single element.\r
-            if (isRight && pickables.size() > 1) {\r
-                IElement selectElement = singleElementAboveNonselectedConnections(currentSelection, pickables);\r
-                if (selectElement != null) {\r
-                    selection.setSelection(selectionId, selectElement);\r
-                }\r
-                if (!currentSelection.isEmpty() && noModifiers)\r
-                    setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);\r
-                return false;\r
-            }\r
-\r
-            /*\r
-             * Select the one object the mouse points to. If multiple object\r
-             * are picked, select the one that is after the earliest by\r
-             * index of the current selection when shift is pressed. Otherwise\r
-             * always pick the topmost element.\r
-             */\r
-            IElement selectedPick = isShiftPressed\r
-                    ? rotatingPick(currentSelection, pickables)\r
-                            : pickables.get(pickables.size() - 1);\r
-\r
-            // Only select when\r
-            // 1. the selection would actually change\r
-            // AND\r
-            //   2.1. left button was pressed\r
-            //   OR\r
-            //   2.2. right button was pressed and the element to-be-selected\r
-            //        is NOT a part of the current selection\r
-            if (!Collections.singleton(selectedPick).equals(currentSelection)\r
-                    && (isLeft || (isRight && !currentSelection.contains(selectedPick)))) {\r
-                selection.setSelection(selectionId, selectedPick);\r
-            }\r
-\r
-            if (isRight && pickables.size() == 1 && noModifiers) {\r
-                setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * A heuristic needed for implementing right-click diagram selection in a\r
-     * sensible manner.\r
-     * \r
-     * @param currentSelection\r
-     * @param pickables\r
-     * @return\r
-     */\r
-    private IElement singleElementAboveNonselectedConnections(Set<IElement> currentSelection, List<IElement> pickables) {\r
-        if (pickables.isEmpty())\r
-            return null;\r
-\r
-        // Check that the pickable-list doesn't contain anything that is in the current selection.\r
-        if (!Collections.disjoint(currentSelection, pickables))\r
-            return null;\r
-\r
-        IElement top = pickables.get(pickables.size() - 1);\r
-        boolean elementOnTop = !PickRequest.PickFilter.FILTER_CONNECTIONS.accept(top);\r
-        if (!elementOnTop)\r
-            return null;\r
-        for (int i = pickables.size() - 2; i >= 0; --i) {\r
-            IElement e = pickables.get(i);\r
-            if (!PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e))\r
-                return null;\r
-        }\r
-        return top;\r
-    }\r
-\r
-    /**\r
-     * Since there's seems to be no better method available for finding out if\r
-     * the SWT popup menu was just recently closed or not, we use the following\r
-     * heuristic:\r
-     * \r
-     * SWT popup was just closed if it was closed < 300ms ago.\r
-     * \r
-     * Note that this is a very bad heuristic and may fail on slower machines or\r
-     * under heavy system load.\r
-     * \r
-     * @return\r
-     */\r
-    private boolean wasPopupJustClosed(Event event) {\r
-        Long popupCloseTime = getHint(DiagramHints.POPUP_MENU_HIDDEN);\r
-        if (popupCloseTime != null) {\r
-            long timeDiff = event.time - popupCloseTime;\r
-            //System.out.println("time diff: " + timeDiff);\r
-            if (timeDiff < 300) {\r
-                //System.out.println("POPUP WAS JUST CLOSED!");\r
-                return true;\r
-            }\r
-        }\r
-        //System.out.println("Popup has been closed for a while.");\r
-        return false;\r
-    }\r
-\r
-    boolean handleDoubleClick(MouseClickEvent me) {\r
-        //System.out.println("mouse double clicked: " + me);\r
-        if (!hasDoubleClickEdit()) return false;\r
-        if (me.button != MouseEvent.LEFT_BUTTON) return false;\r
-        if (getToolMode() != Hints.POINTERTOOL) return false;\r
-        if (me.clickCount < 2) return false;\r
-\r
-        // Pick Terminal\r
-        double pickDist = getPickDistance();\r
-        Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);\r
-        Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());\r
-        int         selectionId     = me.mouseId;\r
-\r
-        PickRequest req             = new PickRequest(canvasPickRect);\r
-        req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;\r
-        List<IElement> pick         = new ArrayList<IElement>();\r
-        pickContext.pick(diagram, req, pick);\r
-\r
-        // Clear selection\r
-        if (pick.isEmpty()) {\r
-            selection.clear(selectionId);\r
-            return false;\r
-        }\r
-\r
-        IElement selectedPick = rotatingPick(selectionId, pick);\r
-\r
-        if (!selection.contains(selectionId, selectedPick)) {\r
-            selection.setSelection(selectionId, selectedPick);\r
-        }\r
-\r
-        CanvasUtils.sendCommand(getContext(), Commands.RENAME);\r
-\r
-        return false;\r
-    }\r
-\r
-    // Values shared by #handleDrag and #handleBoxSelect\r
-    private transient Point2D curCanvasDragPos = new Point2D.Double();\r
-    private transient Set<IElement> elementsToDrag = Collections.emptySet();\r
-\r
-    /**\r
-     * Invoked before scene graph event handling and {@link #handleBoxSelect(MouseDragBegin)}.\r
-     * @param me\r
-     * @return\r
-     * @see #handleBoxSelect(MouseDragBegin)\r
-     */\r
-    @EventHandler(priority = TOOL_PRIORITY)\r
-    public boolean handleDrag(MouseDragBegin me) {\r
-        if (!hasElementDrag() && !hasBoxSelect()) return false;\r
-        if (me.button != MouseEvent.LEFT_BUTTON) return false;\r
-        if (getToolMode() != Hints.POINTERTOOL) return false;\r
-        if (hasToolMode(me.mouseId)) return false;\r
-\r
-        boolean anyModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);\r
-        boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK\r
-                ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));\r
-\r
-        if (nonSelectionModifierPressed)\r
-            return false;\r
-\r
-        assertDependencies();\r
-\r
-        Point2D         curCanvasPos    = util.controlToCanvas(me.controlPosition, curCanvasDragPos);\r
-        PickRequest     req             = new PickRequest(me.startCanvasPos);\r
-        req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;\r
-        List<IElement>  picks           = new ArrayList<IElement>();\r
-        pickContext.pick(diagram, req, picks);\r
-\r
-        //System.out.println(picks);\r
-        if (picks.isEmpty()) {\r
-            // Widen the area of searching if nothing is found with point picking\r
-               double pickDist = getPickDistance();\r
-            Rectangle2D     controlPickRect     = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);\r
-            Shape           canvasPickRect      = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());\r
-            req = new PickRequest(canvasPickRect);\r
-            req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;\r
-            pickContext.pick(diagram, req, picks);\r
-            //System.out.println("2nd try: " + picks);\r
-        }\r
-\r
-        Set<IElement> sel            = selection.getSelection(me.mouseId);\r
-        IElement      topMostPick    = picks.isEmpty() ? null : picks.get(picks.size() - 1);\r
-        Set<IElement> elementsToDrag = new HashSet<IElement>();\r
-        this.elementsToDrag = elementsToDrag;\r
-\r
-        if (!Collections.disjoint(sel, picks)) {\r
-            elementsToDrag.addAll(sel);\r
-        } else {\r
-            if (topMostPick != null && (sel.isEmpty() || !sel.contains(topMostPick))) {\r
-                selection.setSelection(me.mouseId, topMostPick);\r
-                sel = selection.getSelection(me.mouseId);\r
-                elementsToDrag.addAll(sel);\r
-            }\r
-        }\r
-\r
-        // Drag Elements\r
-        if (!elementsToDrag.isEmpty() && hasElementDnDDrag()) {\r
-            // To Be Implemented in the next Diagram data model.\r
-        } else {\r
-            if (!anyModifierPressed && !elementsToDrag.isEmpty() && hasElementDrag()) {\r
-                // Connections are not translatable, re-routing is in RouteGraphNode.\r
-                boolean onlyConnections = onlyConnections(elementsToDrag);\r
-                if (!onlyConnections) {\r
-                    ICanvasParticipant tm = createTranslateTool(me.mouseId, me.startCanvasPos, curCanvasPos, elementsToDrag);\r
-                    if (tm != null) {\r
-                        getContext().add(tm);\r
-                        return !onlyConnections;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * Always invoked after after {@link #handleDrag(MouseDragBegin)} and scene\r
-     * graph event handling to prevent the box selection mode from being\r
-     * initiated before scene graph nodes have a chance to react.\r
-     * \r
-     * <p>\r
-     * Note that this method assumes that <code>elementsToDrag</code> and\r
-     * <code>curCanvasPos</code> are already set by\r
-     * {@link #handleDrag(MouseDragBegin)}.\r
-     * \r
-     * @param me\r
-     * @return\r
-     */\r
-    @EventHandler(priority = BOX_SELECT_PRIORITY)\r
-    public boolean handleBoxSelect(MouseDragBegin me) {\r
-        if (!hasBoxSelect()) return false;\r
-        if (me.button != MouseEvent.LEFT_BUTTON) return false;\r
-        if (getToolMode() != Hints.POINTERTOOL) return false;\r
-        if (hasToolMode(me.mouseId)) return false;\r
-\r
-        boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK\r
-                ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));\r
-        if (nonSelectionModifierPressed)\r
-            return false;\r
-\r
-        if (!nonSelectionModifierPressed && elementsToDrag.isEmpty()) {\r
-            // Box Select\r
-            ICanvasParticipant bsm = createBoxSelectTool(me.mouseId, me.startCanvasPos, curCanvasDragPos, me.button, boxSelectMode);\r
-            if (bsm != null)\r
-                getContext().add(bsm);\r
-        }\r
-\r
-        return false;\r
-    }\r
-\r
-    private static boolean onlyConnections(Set<IElement> elements) {\r
-        for (IElement e : elements)\r
-            if (!e.getElementClass().containsClass(ConnectionHandler.class))\r
-                return false;\r
-        return true;\r
-    }\r
-\r
-    private IElement rotatingPick(int selectionId, List<IElement> pickables) {\r
-        Set<IElement> sel = selection.getSelection(selectionId);\r
-        return rotatingPick(sel, pickables);\r
-    }\r
-\r
-    private IElement rotatingPick(Set<IElement> sel, List<IElement> pickables) {\r
-        int earliestIndex = pickables.size();\r
-        for (int i = pickables.size() - 1; i >= 0; --i) {\r
-            if (sel.contains(pickables.get(i))) {\r
-                earliestIndex = i;\r
-                break;\r
-            }\r
-        }\r
-        if (earliestIndex == 0)\r
-            earliestIndex = pickables.size();\r
-        IElement selectedPick = pickables.get(earliestIndex - 1);\r
-        return selectedPick;\r
-    }\r
-\r
-    /**\r
-     * Is mouse in some kind of mode?\r
-     * @param mouseId\r
-     * @return\r
-     */\r
-    boolean hasToolMode(int mouseId) {\r
-        for (AbstractMode am : getContext().getItemsByClass(AbstractMode.class))\r
-            if (am.mouseId==mouseId) return true;\r
-        return false;\r
-    }\r
-\r
-    boolean hasToolMode(IToolMode mode) {\r
-        return ObjectUtils.objectEquals(mode, getToolMode());\r
-    }\r
-\r
-    boolean hasToolMode(IToolMode... modes) {\r
-        IToolMode current = getToolMode();\r
-        if (current == null)\r
-            return false;\r
-        for (IToolMode mode : modes)\r
-            if (current.equals(mode))\r
-                return true;\r
-        return false;\r
-    }\r
-\r
-    protected IToolMode getToolMode() {\r
-        return getHint(Hints.KEY_TOOL);\r
-    }\r
-\r
-    /// is box select enabled\r
-    protected boolean hasBoxSelect() {\r
-        return boxSelect;\r
-    }\r
-\r
-    /// is click select enabled\r
-    protected boolean hasClickSelect() {\r
-        return clickSelect;\r
-    }\r
-\r
-    /// is double click edit enabled\r
-    protected boolean hasDoubleClickEdit() {\r
-        return doubleClickEdit;\r
-    }\r
-\r
-    // is element drag enabled\r
-    protected boolean hasElementDrag() {\r
-        return dragElement;\r
-    }\r
-\r
-    // is element drag enabled\r
-    protected boolean hasElementDnDDrag() {\r
-        return dndDragElement;\r
-    }\r
-\r
-    // is connect enabled\r
-    protected boolean connects() {\r
-        return connect;\r
-    }\r
-\r
-    public double getPickDistance() {\r
-        Double pickDistance = getHint(KEY_PICK_DISTANCE);\r
-        return pickDistance == null ? PICK_DIST : Math.max(pickDistance, 0);\r
-    }\r
-\r
-    public PickPolicy getBoxSelectMode() {\r
-        return boxSelectMode;\r
-    }\r
-\r
-    public void setBoxSelectMode(PickPolicy boxSelectMode) {\r
-        this.boxSelectMode = boxSelectMode;\r
-    }\r
-\r
-    public void setSelectionEnabled(boolean select) {\r
-        this.clickSelect = select;\r
-        this.boxSelect = select;\r
-        if(select == false) { // Clear all selections if select is disabled\r
-            final int[] ids = selection.getSelectionIds();\r
-            if(ids.length > 0) {\r
-                ThreadUtils.asyncExec(getContext().getThreadAccess(), new Runnable() {\r
-                    @Override\r
-                    public void run() {\r
-                        for(int id : ids)\r
-                            selection.clear(id);\r
-                        getContext().getContentContext().setDirty();\r
-                    }\r
-                });\r
-            }\r
-        }\r
-    }\r
-\r
-    boolean anyModifierPressed(MouseEvent e) {\r
-        return e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);\r
-    }\r
-\r
-    boolean connectToolModifiersPressed(int stateMask) {\r
-        return (stateMask & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;\r
-    }\r
-\r
-    static {\r
-        LINE10 = new Path2D.Double();\r
-        LINE10.moveTo(0, 0);\r
-        LINE10.lineTo(10, 0);\r
-        LINE10.lineTo(7, -3);\r
-        LINE10.moveTo(10, 0);\r
-        LINE10.lineTo(7, 3);\r
-\r
-        LINE15 = new Path2D.Double();\r
-        LINE15.moveTo(0, 0);\r
-        LINE15.lineTo(15, 0);\r
-        LINE15.lineTo(12, -3);\r
-        LINE15.moveTo(15, 0);\r
-        LINE15.lineTo(12, 3);\r
-\r
-        LINE20 = new Path2D.Double();\r
-        LINE20.moveTo(0, 0);\r
-        LINE20.lineTo(20, 0);\r
-        LINE20.lineTo(17, -3);\r
-        LINE20.moveTo(20, 0);\r
-        LINE20.lineTo(17, 3);\r
-\r
-    }\r
-\r
-    // CUSTOMIZE\r
-\r
-    protected ICanvasParticipant createConnectTool(TerminalInfo ti, int mouseId, Point2D startCanvasPos) {\r
-        return null;\r
-    }\r
-\r
-    protected ICanvasParticipant createConnectToolWithTerminals(List<TerminalInfo> tis, int mouseId, Point2D startCanvasPos) {\r
-        return null;\r
-    }\r
-\r
-    protected ICanvasParticipant createTranslateTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, Set<IElement> elementsToDrag) {\r
-        return new TranslateMode(startCanvasPos, curCanvasPos, mouseId, elementsToDrag);\r
-    }\r
-\r
-    protected ICanvasParticipant createBoxSelectTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, int button, PickPolicy boxSelectMode) {\r
-        return new BoxSelectionMode(startCanvasPos, curCanvasPos, mouseId, button, boxSelectMode);\r
-    }\r
-\r
-    /**\r
-     * A context listener for resetting tool mode back to pointer mode after the\r
-     * tracked participant has been removed.\r
-     */\r
-    protected class ToolModeResetter implements IContextListener<ICanvasParticipant> {\r
-        private ICanvasParticipant tracked;\r
-        public ToolModeResetter(ICanvasParticipant trackedParticipant) {\r
-            this.tracked = trackedParticipant;\r
-        }\r
-        @Override\r
-        public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {\r
-        }\r
-        @Override\r
-        public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {\r
-            if (item == tracked) {\r
-                sender.removeContextListener(this);\r
-                if (!isRemoved() && !connectToolModifiersPressed(lastStateMask)) {\r
-                    temporarilyEnabledConnectTool = false;\r
-                    setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);\r
-                }\r
-            }\r
-        }\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 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.g2d.diagram.participant.pointertool;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.ICanvasParticipant;
+import org.simantics.g2d.canvas.IToolMode;
+import org.simantics.g2d.canvas.impl.CanvasContext;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
+import org.simantics.g2d.connection.IConnectionAdvisor;
+import org.simantics.g2d.connection.handler.ConnectionHandler;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.handler.PickContext;
+import org.simantics.g2d.diagram.handler.PickRequest;
+import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
+import org.simantics.g2d.diagram.handler.PickRequest.PickSorter;
+import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
+import org.simantics.g2d.diagram.participant.Selection;
+import org.simantics.g2d.diagram.participant.TerminalPainter;
+import org.simantics.g2d.diagram.participant.TerminalPainter.ChainedHoverStrategy;
+import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
+import org.simantics.g2d.element.ElementClassProviders;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.IElementClassProvider;
+import org.simantics.g2d.participant.KeyUtil;
+import org.simantics.g2d.participant.MouseUtil;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.g2d.scenegraph.SceneGraphConstants;
+import org.simantics.g2d.utils.CanvasUtils;
+import org.simantics.g2d.utils.GeometryUtils;
+import org.simantics.scenegraph.g2d.events.Event;
+import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
+import org.simantics.scenegraph.g2d.events.KeyEvent;
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
+import org.simantics.utils.ObjectUtils;
+import org.simantics.utils.datastructures.context.IContext;
+import org.simantics.utils.datastructures.context.IContextListener;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.threads.ThreadUtils;
+
+/**
+ * Pointer tool does the following operations with mouse:
+ * <ul>
+ * <li>Selections</li>
+ * <li>Scale</li>
+ * <li>Rotate</li>
+ * <li>Translate</li>
+ * <li>Draws connections (requires re-implementing
+ * {@link #createConnectTool(TerminalInfo, int, Point2D)})</li>
+ * </ul>
+ * 
+ * Pointer tool is active only when {@link Hints#KEY_TOOL} is
+ * {@value Hints#POINTERTOOL}.
+ * 
+ * @author Toni Kalajainen
+ */
+public class PointerInteractor extends AbstractDiagramParticipant {
+
+    /**
+     * A hint key for terminal pick distance in control pixels.
+     * @see #PICK_DIST
+     */
+    public static final Key KEY_PICK_DISTANCE = new KeyOf(Double.class, "PICK_DISTANCE");
+
+    /**
+     * Default terminal pick distance in control pixels.
+     * @see #DEFAULT_PICK_DISTANCE
+     */
+    public static final double PICK_DIST = 10;
+
+    /**
+     * @see #altToggled(KeyEvent)
+     */
+    protected int              lastStateMask;
+
+    /**
+     * @see #altToggled(KeyEvent)
+     */
+    protected boolean          temporarilyEnabledConnectTool = false;
+
+    public class DefaultHoverStrategy extends ChainedHoverStrategy {
+        public DefaultHoverStrategy(TerminalHoverStrategy orig) {
+            super(orig);
+        }
+        @Override
+        public boolean highlightEnabled() {
+            if (Hints.CONNECTTOOL.equals(getToolMode()))
+                return true;
+
+            boolean ct = connectToolModifiersPressed(lastStateMask);
+            //System.out.println("highlightEnabled: " + String.format("%x", lastStateMask) + " : " + ct);
+            return ct;
+        }
+        @Override
+        public boolean canHighlight(TerminalInfo ti) {
+            //boolean alt = (lastStateMask & MouseEvent.ALT_MASK) != 0;
+            //System.out.println("canHighlight: " + String.format("%x", lastStateMask) + " : " + alt);
+            //if (!alt)
+            //   return false;
+            IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
+            return advisor == null || advisor.canBeginConnection(null, ti.e, ti.t);
+        }
+    }
+
+    @Dependency Selection selection;
+    @Dependency KeyUtil keys;
+    @Dependency TransformUtil util;
+    @Dependency PickContext pickContext;
+    @Dependency MouseUtil mice;
+    @Reference TerminalPainter terminalPainter;
+
+    /**
+     * This must be higher than
+     * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}
+     * ({@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to allow for
+     * better selection handling than is possible by using the plain scene graph
+     * event handling facilities which are installed in {@link CanvasContext}.
+     */
+    public static final int TOOL_PRIORITY = 1 << 21;
+
+    /**
+     * This must be lower than
+     * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY} (
+     * {@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to not start box
+     * handling before scene graph nodes have been given a chance to react
+     * events.
+     */
+    public static final int BOX_SELECT_PRIORITY = 1 << 19;
+
+    private static final Path2D LINE10;
+    private static final Path2D LINE15;
+    private static final Path2D LINE20;
+
+    boolean clickSelect;
+    boolean boxSelect;
+    boolean dragElement, dndDragElement;
+    boolean connect;
+    boolean doubleClickEdit;
+    protected IElementClassProvider elementClassProvider;
+
+    PickPolicy boxSelectMode = PickPolicy.PICK_CONTAINED_OBJECTS;
+
+    DefaultHoverStrategy hoverStrategy;
+
+    private PickSorter pickSorter;
+    
+    public PointerInteractor() {
+        this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), null);
+    }
+
+    public PointerInteractor(PickSorter pickSorter) {
+        this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), pickSorter);
+    }
+
+    public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, IElementClassProvider ecp) {
+        this(clickSelect, boxSelect, dragElement, dndDragElement, connect, false, ecp, null);
+    }
+
+    public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider ecp, PickSorter pickSorter) {
+        super();
+        this.clickSelect = clickSelect;
+        this.boxSelect = boxSelect;
+        this.dragElement = dragElement;
+        this.dndDragElement = dndDragElement;
+        this.connect = connect;
+        this.doubleClickEdit = doubleClickEdit;
+        this.elementClassProvider = ecp;
+        this.pickSorter = pickSorter;
+    }
+
+    @Override
+    public void addedToContext(ICanvasContext ctx) {
+        super.addedToContext(ctx);
+        hoverStrategy = new DefaultHoverStrategy((TerminalHoverStrategy) getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY));
+        setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, hoverStrategy);
+    }
+
+    @EventHandler(priority = 0)
+    public boolean handleStateMask(MouseEvent me) {
+        lastStateMask = me.stateMask;
+        if (temporarilyEnabledConnectTool) {
+            if (!connectToolModifiersPressed(me.stateMask)) {
+                temporarilyEnabledConnectTool = false;
+                setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
+            }
+        } else {
+            // It may be that the mouse has come into this control
+            // from outside and no key state changes have occurred yet.
+            // In this case this code should take care of moving the canvas
+            // context into CONNECTTOOL mode.
+            if (getToolMode() == Hints.POINTERTOOL
+                    && connectToolModifiersPressed(me.stateMask))
+            {
+                temporarilyEnabledConnectTool = true;
+                setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
+            }
+        }
+        return false;
+    }
+
+    private static int mask(int mask, int mask2, boolean set) {
+        return set ? mask | mask2 : mask & ~mask2;
+    }
+
+    @EventHandler(priority = -1)
+    public boolean altToggled(KeyEvent ke) {
+        int mods = ke.stateMask;
+        boolean press = ke instanceof KeyPressedEvent;
+        boolean modifierPressed = false;
+        if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
+            mods = mask(mods, MouseEvent.ALT_MASK, press);
+            modifierPressed = true;
+        }
+        if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT_GRAPH) {
+            mods = mask(mods, MouseEvent.ALT_GRAPH_MASK, press);
+            modifierPressed = true;
+        }
+        if (ke.keyCode == java.awt.event.KeyEvent.VK_SHIFT) {
+            mods = mask(mods, MouseEvent.SHIFT_MASK, press);
+            modifierPressed = true;
+        }
+        if (ke.keyCode == java.awt.event.KeyEvent.VK_CONTROL) {
+            mods = mask(mods, MouseEvent.CTRL_MASK, press);
+            modifierPressed = true;
+        }
+        if (ke.keyCode == java.awt.event.KeyEvent.VK_META) {
+            // TODO: NO MASK FOR META!
+            modifierPressed = true;
+        }
+        // Don't deny connecting when CTRL is marked pressed because ALT_GRAPH
+        // is actually ALT+CTRL in SWT. There's no way in SWT to tell apart
+        // CTRL+ALT and ALT GRAPH.
+        boolean otherModifiers = (mods & (MouseEvent.SHIFT_MASK)) != 0;
+        boolean altModifier = (mods & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;
+        if (modifierPressed) {
+            boolean altPressed = !otherModifiers && altModifier;
+            lastStateMask = mods;
+            if (altPressed) {
+                IToolMode mode = getToolMode();
+                if (mode == Hints.POINTERTOOL) {
+                    //System.out.println("TEMP++");
+                    temporarilyEnabledConnectTool = true;
+                    setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
+                }
+            } else {
+                if (temporarilyEnabledConnectTool) {
+                    //System.out.println("TEMP--");
+                    temporarilyEnabledConnectTool = false;
+                    setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
+                }
+            }
+            // Make sure that TerminalPainter updates its scene graph.
+            if (terminalPainter != null) {
+                terminalPainter.update(terminalPainter.highlightEnabled());
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @param controlPos
+     * @return <code>null</code> if current canvas transform is not invertible
+     */
+    public Shape getCanvasPickShape(Point2D controlPos) {
+        AffineTransform inverse = util.getInverseTransform();
+        if (inverse == null)
+            return null;
+
+        double      pd              = getPickDistance();
+        Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2+1, pd*2+1);
+        Shape       canvasShape     = GeometryUtils.transformShape(controlPickRect, inverse);
+        return canvasShape;
+    }
+
+    public List<TerminalInfo> pickTerminals(Point2D controlPos)
+    {
+        Shape canvasPickRect = getCanvasPickShape(controlPos);
+        if (canvasPickRect == null)
+            return Collections.emptyList();
+        return TerminalUtil.pickTerminals(diagram, canvasPickRect, true, true);
+    }
+
+    public TerminalInfo pickTerminal(Point2D controlPos)
+    {
+        Shape canvasPickRect = getCanvasPickShape(controlPos);
+        if (canvasPickRect == null)
+            return null;
+        TerminalInfo ti = TerminalUtil.pickTerminal(diagram, canvasPickRect);
+        return ti;
+    }
+
+    @EventHandler(priority = TOOL_PRIORITY)
+    public boolean handlePress(MouseButtonPressedEvent me) {
+        if (!connects())
+            return false;
+        if (me.button != MouseEvent.LEFT_BUTTON)
+            return false;
+
+        IToolMode mode = getToolMode();
+
+        // It may be that the mouse has come into this control
+        // from outside without focusing it and without any mouse
+        // buttons being pressed. If the user is pressing the
+        // connection modifier we need to temporarily enable connect
+        // mode here and now.
+        if (mode == Hints.POINTERTOOL && connectToolModifiersPressed(me.stateMask)) {
+            temporarilyEnabledConnectTool = true;
+            mode = Hints.CONNECTTOOL;
+            setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
+        }
+
+        if (mode == Hints.CONNECTTOOL) {
+            Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, null);
+            return checkInitiateConnectTool(me, curCanvasPos);
+        }
+
+        return false;
+    }
+
+    protected boolean checkInitiateConnectTool(MouseEvent me, Point2D mouseCanvasPos) {
+        // Pick Terminal
+        IToolMode mode = getToolMode();
+        if (mode == Hints.CONNECTTOOL || connectToolModifiersPressed(me.stateMask)) {
+            IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
+            TerminalInfo ti = pickTerminal(me.controlPosition);
+
+            ICanvasParticipant bsi = null;
+            if (ti != null) {
+                if (advisor == null || advisor.canBeginConnection(null, ti.e, ti.t)) {
+                    bsi = createConnectTool(ti, me.mouseId, mouseCanvasPos);
+                }
+            } else {
+                ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+                if (snapAdvisor != null)
+                    snapAdvisor.snap(mouseCanvasPos);
+
+                // Start connection out of thin air, without a terminal.
+                bsi = createConnectTool(null, me.mouseId, mouseCanvasPos);
+            }
+
+            // Did we catch anything?
+            if (bsi != null) {
+                startConnectTool(bsi);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected void startConnectTool(ICanvasParticipant tool) {
+        getContext().add(tool);
+        //System.out.println("TEMP: " + temporarilyEnabledConnectTool);
+        if (temporarilyEnabledConnectTool) {
+            // Resets pointer tool back into use if necessary after
+            // connection has been finished or canceled.
+            getContext().addContextListener(new ToolModeResetter(tool));
+        }
+    }
+
+    @EventHandler(priority = TOOL_PRIORITY)
+    public boolean handleClick(MouseClickEvent me) {
+        //System.out.println(getClass().getSimpleName() + ": mouse clicked: @ " + me.time);
+
+        if (hasDoubleClickEdit() && me.clickCount == 2) {
+            if (handleDoubleClick(me))
+                return true;
+        }
+
+        if (!hasClickSelect()) return false;
+        if (!hasToolMode(Hints.POINTERTOOL)) return false;
+
+        // Don't handle any events where click count is more than 1 to prevent
+        // current diagram selection from bouncing around unnecessarily.
+        if (me.clickCount > 1)
+            return false;
+
+        boolean isLeft = me.button == MouseEvent.LEFT_BUTTON;
+        boolean isRight = me.button == MouseEvent.RIGHT_BUTTON;
+        if (!isLeft && !isRight) return false;
+
+        boolean popupWasVisible = wasPopupJustClosed(me);
+        boolean noModifiers = !anyModifierPressed(me);
+        boolean isShiftPressed = me.hasAllModifiers(MouseEvent.SHIFT_MASK);
+
+        assertDependencies();
+
+        // Pick Terminal
+        double pickDist = getPickDistance();
+        Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);
+        Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
+        int selectionId = me.mouseId;
+
+        PickRequest req = new PickRequest(canvasPickRect);
+        req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
+        req.pickSorter = pickSorter;
+        //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;
+        List<IElement> pickables = new ArrayList<IElement>();
+        pickContext.pick(diagram, req, pickables);
+
+        Set<IElement> currentSelection = selection.getSelection(selectionId);
+
+        // Clear selection
+        if (pickables.isEmpty()) {
+            if (!popupWasVisible) {
+                // Only clear selection on left button clicks.
+                if (isLeft) {
+                    selection.clear(selectionId);
+                }
+            }
+            if (isRight) {
+                if (/*!currentSelection.isEmpty() &&*/ noModifiers)
+                    setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
+            }
+            return false;
+        }
+
+        // Toggle select
+        if ((me.stateMask & MouseEvent.CTRL_MASK) != 0) {
+            if (isLeft) {
+                /*
+                 * - If the mouse points to an object not in the selection, add it to selection.
+                 * - If the mouse points to multiple objects, add the first.
+                 * - If all objects the mouse points are already in selection, remove one of them from selection.
+                 */
+                IElement removable = null;
+                for (int i = pickables.size() - 1; i >= 0; --i) {
+                    IElement pickable = pickables.get(i);
+                    if (selection.add(selectionId, pickable)) {
+                        removable = null;
+                        break;
+                    } else
+                        removable = pickable;
+
+                    // Do not perform rotating pick in toggle selection
+                    // when only CTRL is pressed. Requires SHIFT+CTRL.
+                    if (!isShiftPressed)
+                        break;
+                }
+                if (removable != null)
+                    selection.remove(selectionId, removable);
+            }
+            return false;
+        }
+
+        // Click Select
+        {
+            if (isLeft && popupWasVisible)
+                // Popup menu is visible, just let it close
+                return false;
+
+            // Don't change selection on right clicks if there's more to pick
+            // than a single element.
+            if (isRight && pickables.size() > 1) {
+                IElement selectElement = singleElementAboveNonselectedConnections(currentSelection, pickables);
+                if (selectElement != null) {
+                    selection.setSelection(selectionId, selectElement);
+                }
+                if (!currentSelection.isEmpty() && noModifiers)
+                    setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
+                return false;
+            }
+
+            /*
+             * Select the one object the mouse points to. If multiple object
+             * are picked, select the one that is after the earliest by
+             * index of the current selection when shift is pressed. Otherwise
+             * always pick the topmost element.
+             */
+            IElement selectedPick = isShiftPressed
+                    ? rotatingPick(currentSelection, pickables)
+                            : pickables.get(pickables.size() - 1);
+
+            // Only select when
+            // 1. the selection would actually change
+            // AND
+            //   2.1. left button was pressed
+            //   OR
+            //   2.2. right button was pressed and the element to-be-selected
+            //        is NOT a part of the current selection
+            if (!Collections.singleton(selectedPick).equals(currentSelection)
+                    && (isLeft || (isRight && !currentSelection.contains(selectedPick)))) {
+                selection.setSelection(selectionId, selectedPick);
+            }
+
+            if (isRight && pickables.size() == 1 && noModifiers) {
+                setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * A heuristic needed for implementing right-click diagram selection in a
+     * sensible manner.
+     * 
+     * @param currentSelection
+     * @param pickables
+     * @return
+     */
+    private IElement singleElementAboveNonselectedConnections(Set<IElement> currentSelection, List<IElement> pickables) {
+        if (pickables.isEmpty())
+            return null;
+
+        // Check that the pickable-list doesn't contain anything that is in the current selection.
+        if (!Collections.disjoint(currentSelection, pickables))
+            return null;
+
+        IElement top = pickables.get(pickables.size() - 1);
+        boolean elementOnTop = !PickRequest.PickFilter.FILTER_CONNECTIONS.accept(top);
+        if (!elementOnTop)
+            return null;
+        for (int i = pickables.size() - 2; i >= 0; --i) {
+            IElement e = pickables.get(i);
+            if (!PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e))
+                return null;
+        }
+        return top;
+    }
+
+    /**
+     * Since there's seems to be no better method available for finding out if
+     * the SWT popup menu was just recently closed or not, we use the following
+     * heuristic:
+     * 
+     * SWT popup was just closed if it was closed < 300ms ago.
+     * 
+     * Note that this is a very bad heuristic and may fail on slower machines or
+     * under heavy system load.
+     * 
+     * @return
+     */
+    private boolean wasPopupJustClosed(Event event) {
+        Long popupCloseTime = getHint(DiagramHints.POPUP_MENU_HIDDEN);
+        if (popupCloseTime != null) {
+            long timeDiff = event.time - popupCloseTime;
+            //System.out.println("time diff: " + timeDiff);
+            if (timeDiff < 300) {
+                //System.out.println("POPUP WAS JUST CLOSED!");
+                return true;
+            }
+        }
+        //System.out.println("Popup has been closed for a while.");
+        return false;
+    }
+
+    boolean handleDoubleClick(MouseClickEvent me) {
+        //System.out.println("mouse double clicked: " + me);
+        if (!hasDoubleClickEdit()) return false;
+        if (me.button != MouseEvent.LEFT_BUTTON) return false;
+        if (getToolMode() != Hints.POINTERTOOL) return false;
+        if (me.clickCount < 2) return false;
+
+        // Pick Terminal
+        double pickDist = getPickDistance();
+        Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);
+        Shape       canvasPickRect  = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
+        int         selectionId     = me.mouseId;
+
+        PickRequest req             = new PickRequest(canvasPickRect);
+        req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
+        List<IElement> pick         = new ArrayList<IElement>();
+        pickContext.pick(diagram, req, pick);
+
+        // Clear selection
+        if (pick.isEmpty()) {
+            selection.clear(selectionId);
+            return false;
+        }
+
+        IElement selectedPick = rotatingPick(selectionId, pick);
+
+        if (!selection.contains(selectionId, selectedPick)) {
+            selection.setSelection(selectionId, selectedPick);
+        }
+
+        CanvasUtils.sendCommand(getContext(), Commands.RENAME);
+
+        return false;
+    }
+
+    // Values shared by #handleDrag and #handleBoxSelect
+    private transient Point2D curCanvasDragPos = new Point2D.Double();
+    private transient Set<IElement> elementsToDrag = Collections.emptySet();
+
+    /**
+     * Invoked before scene graph event handling and {@link #handleBoxSelect(MouseDragBegin)}.
+     * @param me
+     * @return
+     * @see #handleBoxSelect(MouseDragBegin)
+     */
+    @EventHandler(priority = TOOL_PRIORITY)
+    public boolean handleDrag(MouseDragBegin me) {
+        if (!hasElementDrag() && !hasBoxSelect()) return false;
+        if (me.button != MouseEvent.LEFT_BUTTON) return false;
+        if (getToolMode() != Hints.POINTERTOOL) return false;
+        if (hasToolMode(me.mouseId)) return false;
+
+        boolean anyModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);
+        boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK
+                ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));
+
+        if (nonSelectionModifierPressed)
+            return false;
+
+        assertDependencies();
+
+        Point2D         curCanvasPos    = util.controlToCanvas(me.controlPosition, curCanvasDragPos);
+        PickRequest     req             = new PickRequest(me.startCanvasPos);
+        req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
+        List<IElement>  picks           = new ArrayList<IElement>();
+        pickContext.pick(diagram, req, picks);
+
+        //System.out.println(picks);
+        if (picks.isEmpty()) {
+            // Widen the area of searching if nothing is found with point picking
+               double pickDist = getPickDistance();
+            Rectangle2D     controlPickRect     = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);
+            Shape           canvasPickRect      = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
+            req = new PickRequest(canvasPickRect);
+            req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
+            pickContext.pick(diagram, req, picks);
+            //System.out.println("2nd try: " + picks);
+        }
+
+        Set<IElement> sel            = selection.getSelection(me.mouseId);
+        IElement      topMostPick    = picks.isEmpty() ? null : picks.get(picks.size() - 1);
+        Set<IElement> elementsToDrag = new HashSet<IElement>();
+        this.elementsToDrag = elementsToDrag;
+
+        if (!Collections.disjoint(sel, picks)) {
+            elementsToDrag.addAll(sel);
+        } else {
+            if (topMostPick != null && (sel.isEmpty() || !sel.contains(topMostPick))) {
+                selection.setSelection(me.mouseId, topMostPick);
+                sel = selection.getSelection(me.mouseId);
+                elementsToDrag.addAll(sel);
+            }
+        }
+
+        // Drag Elements
+        if (!elementsToDrag.isEmpty() && hasElementDnDDrag()) {
+            // To Be Implemented in the next Diagram data model.
+        } else {
+            if (!anyModifierPressed && !elementsToDrag.isEmpty() && hasElementDrag()) {
+                // Connections are not translatable, re-routing is in RouteGraphNode.
+                boolean onlyConnections = onlyConnections(elementsToDrag);
+                if (!onlyConnections) {
+                    ICanvasParticipant tm = createTranslateTool(me.mouseId, me.startCanvasPos, curCanvasPos, elementsToDrag);
+                    if (tm != null) {
+                        getContext().add(tm);
+                        return !onlyConnections;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Always invoked after after {@link #handleDrag(MouseDragBegin)} and scene
+     * graph event handling to prevent the box selection mode from being
+     * initiated before scene graph nodes have a chance to react.
+     * 
+     * <p>
+     * Note that this method assumes that <code>elementsToDrag</code> and
+     * <code>curCanvasPos</code> are already set by
+     * {@link #handleDrag(MouseDragBegin)}.
+     * 
+     * @param me
+     * @return
+     */
+    @EventHandler(priority = BOX_SELECT_PRIORITY)
+    public boolean handleBoxSelect(MouseDragBegin me) {
+        if (!hasBoxSelect()) return false;
+        if (me.button != MouseEvent.LEFT_BUTTON) return false;
+        if (getToolMode() != Hints.POINTERTOOL) return false;
+        if (hasToolMode(me.mouseId)) return false;
+
+        boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK
+                ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));
+        if (nonSelectionModifierPressed)
+            return false;
+
+        if (!nonSelectionModifierPressed && elementsToDrag.isEmpty()) {
+            // Box Select
+            ICanvasParticipant bsm = createBoxSelectTool(me.mouseId, me.startCanvasPos, curCanvasDragPos, me.button, boxSelectMode);
+            if (bsm != null)
+                getContext().add(bsm);
+        }
+
+        return false;
+    }
+
+    private static boolean onlyConnections(Set<IElement> elements) {
+        for (IElement e : elements)
+            if (!e.getElementClass().containsClass(ConnectionHandler.class))
+                return false;
+        return true;
+    }
+
+    private IElement rotatingPick(int selectionId, List<IElement> pickables) {
+        Set<IElement> sel = selection.getSelection(selectionId);
+        return rotatingPick(sel, pickables);
+    }
+
+    private IElement rotatingPick(Set<IElement> sel, List<IElement> pickables) {
+        int earliestIndex = pickables.size();
+        for (int i = pickables.size() - 1; i >= 0; --i) {
+            if (sel.contains(pickables.get(i))) {
+                earliestIndex = i;
+                break;
+            }
+        }
+        if (earliestIndex == 0)
+            earliestIndex = pickables.size();
+        IElement selectedPick = pickables.get(earliestIndex - 1);
+        return selectedPick;
+    }
+
+    /**
+     * Is mouse in some kind of mode?
+     * @param mouseId
+     * @return
+     */
+    boolean hasToolMode(int mouseId) {
+        for (AbstractMode am : getContext().getItemsByClass(AbstractMode.class))
+            if (am.mouseId==mouseId) return true;
+        return false;
+    }
+
+    boolean hasToolMode(IToolMode mode) {
+        return ObjectUtils.objectEquals(mode, getToolMode());
+    }
+
+    boolean hasToolMode(IToolMode... modes) {
+        IToolMode current = getToolMode();
+        if (current == null)
+            return false;
+        for (IToolMode mode : modes)
+            if (current.equals(mode))
+                return true;
+        return false;
+    }
+
+    protected IToolMode getToolMode() {
+        return getHint(Hints.KEY_TOOL);
+    }
+
+    /// is box select enabled
+    protected boolean hasBoxSelect() {
+        return boxSelect;
+    }
+
+    /// is click select enabled
+    protected boolean hasClickSelect() {
+        return clickSelect;
+    }
+
+    /// is double click edit enabled
+    protected boolean hasDoubleClickEdit() {
+        return doubleClickEdit;
+    }
+
+    // is element drag enabled
+    protected boolean hasElementDrag() {
+        return dragElement;
+    }
+
+    // is element drag enabled
+    protected boolean hasElementDnDDrag() {
+        return dndDragElement;
+    }
+
+    // is connect enabled
+    protected boolean connects() {
+        return connect;
+    }
+
+    public double getPickDistance() {
+        Double pickDistance = getHint(KEY_PICK_DISTANCE);
+        return pickDistance == null ? PICK_DIST : Math.max(pickDistance, 0);
+    }
+
+    public PickPolicy getBoxSelectMode() {
+        return boxSelectMode;
+    }
+
+    public void setBoxSelectMode(PickPolicy boxSelectMode) {
+        this.boxSelectMode = boxSelectMode;
+    }
+
+    public void setSelectionEnabled(boolean select) {
+        this.clickSelect = select;
+        this.boxSelect = select;
+        if(select == false) { // Clear all selections if select is disabled
+            final int[] ids = selection.getSelectionIds();
+            if(ids.length > 0) {
+                ThreadUtils.asyncExec(getContext().getThreadAccess(), new Runnable() {
+                    @Override
+                    public void run() {
+                        for(int id : ids)
+                            selection.clear(id);
+                        getContext().getContentContext().setDirty();
+                    }
+                });
+            }
+        }
+    }
+
+    boolean anyModifierPressed(MouseEvent e) {
+        return e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);
+    }
+
+    boolean connectToolModifiersPressed(int stateMask) {
+        return (stateMask & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;
+    }
+
+    static {
+        LINE10 = new Path2D.Double();
+        LINE10.moveTo(0, 0);
+        LINE10.lineTo(10, 0);
+        LINE10.lineTo(7, -3);
+        LINE10.moveTo(10, 0);
+        LINE10.lineTo(7, 3);
+
+        LINE15 = new Path2D.Double();
+        LINE15.moveTo(0, 0);
+        LINE15.lineTo(15, 0);
+        LINE15.lineTo(12, -3);
+        LINE15.moveTo(15, 0);
+        LINE15.lineTo(12, 3);
+
+        LINE20 = new Path2D.Double();
+        LINE20.moveTo(0, 0);
+        LINE20.lineTo(20, 0);
+        LINE20.lineTo(17, -3);
+        LINE20.moveTo(20, 0);
+        LINE20.lineTo(17, 3);
+
+    }
+
+    // CUSTOMIZE
+
+    protected ICanvasParticipant createConnectTool(TerminalInfo ti, int mouseId, Point2D startCanvasPos) {
+        return null;
+    }
+
+    protected ICanvasParticipant createConnectToolWithTerminals(List<TerminalInfo> tis, int mouseId, Point2D startCanvasPos) {
+        return null;
+    }
+
+    protected ICanvasParticipant createTranslateTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, Set<IElement> elementsToDrag) {
+        return new TranslateMode(startCanvasPos, curCanvasPos, mouseId, elementsToDrag);
+    }
+
+    protected ICanvasParticipant createBoxSelectTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, int button, PickPolicy boxSelectMode) {
+        return new BoxSelectionMode(startCanvasPos, curCanvasPos, mouseId, button, boxSelectMode);
+    }
+
+    /**
+     * A context listener for resetting tool mode back to pointer mode after the
+     * tracked participant has been removed.
+     */
+    protected class ToolModeResetter implements IContextListener<ICanvasParticipant> {
+        private ICanvasParticipant tracked;
+        public ToolModeResetter(ICanvasParticipant trackedParticipant) {
+            this.tracked = trackedParticipant;
+        }
+        @Override
+        public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
+        }
+        @Override
+        public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
+            if (item == tracked) {
+                sender.removeContextListener(this);
+                if (!isRemoved() && !connectToolModifiersPressed(lastStateMask)) {
+                    temporarilyEnabledConnectTool = false;
+                    setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
+                }
+            }
+        }
+    }
+
+}