]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / pointertool / PointerInteractor.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/PointerInteractor.java
new file mode 100644 (file)
index 0000000..2cbbf55
--- /dev/null
@@ -0,0 +1,917 @@
+/*******************************************************************************\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