-/*******************************************************************************\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.elementclass.RouteGraphConnectionClass;
+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.G2DSceneGraph;
+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.nodes.connection.RouteGraphNode;
+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.HintListenerAdapter;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+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);
+
+ getContext().getSceneGraph().setGlobalProperty(G2DSceneGraph.PICK_DISTANCE, getPickDistance());
+ getHintStack().addKeyHintListener(KEY_PICK_DISTANCE, new HintListenerAdapter() {
+ @Override
+ public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+ getContext().getSceneGraph().setGlobalProperty(G2DSceneGraph.PICK_DISTANCE, getPickDistance());
+ }
+ });
+ }
+
+ @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, pd*2);
+ 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(getContext(), diagram, canvasPickRect, true, true);
+ }
+
+ public TerminalInfo pickTerminal(Point2D controlPos)
+ {
+ Shape canvasPickRect = getCanvasPickShape(controlPos);
+ if (canvasPickRect == null)
+ return null;
+ TerminalInfo ti = TerminalUtil.pickTerminal(getContext(), 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();
+
+ Shape canvasPickRect = getCanvasPickShape(me.controlPosition);
+ int selectionId = me.mouseId;
+
+ PickRequest req = new PickRequest(canvasPickRect).context(getContext());
+ req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
+ req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY());
+
+ //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;
+ }
+
+ boolean result = 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);
+ // Stop propagation
+ result = true;
+ }
+
+ if (isRight && pickables.size() == 1 && noModifiers) {
+ setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * 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;
+
+ Shape canvasPickRect = getCanvasPickShape(me.controlPosition);
+ int selectionId = me.mouseId;
+
+ PickRequest req = new PickRequest(canvasPickRect).context(getContext());
+ req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
+
+ req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY());
+ 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);
+ Shape canvasPickRect = getCanvasPickShape(me.controlPosition);
+ PickRequest req = new PickRequest(canvasPickRect).context(getContext());
+ req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
+ req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY());
+ List<IElement> picks = new ArrayList<IElement>();
+ pickContext.pick(diagram, req, 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;
+ }
+ } else {
+ // forward MouseDragBegin to closest RouteGraphNode
+ for (int i = picks.size() - 1; i >= 0; i--) {
+ RouteGraphNode rgn = picks.get(i).getHint(RouteGraphConnectionClass.KEY_RG_NODE);
+ if (rgn != null) {
+ rgn.handleDrag(me);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+}