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

- * Note that this method assumes that elementsToDrag and - * curCanvasPos 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 elements) { - for (IElement e : elements) - if (!e.getElementClass().containsClass(ConnectionHandler.class)) - return false; - return true; - } - - private IElement rotatingPick(int selectionId, List pickables) { - Set sel = selection.getSelection(selectionId); - return rotatingPick(sel, pickables); - } - - private IElement rotatingPick(Set sel, List 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 tis, int mouseId, Point2D startCanvasPos) { - return null; - } - - protected ICanvasParticipant createTranslateTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, Set 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 { - private ICanvasParticipant tracked; - public ToolModeResetter(ICanvasParticipant trackedParticipant) { - this.tracked = trackedParticipant; - } - @Override - public void itemAdded(IContext sender, ICanvasParticipant item) { - } - @Override - public void itemRemoved(IContext sender, ICanvasParticipant item) { - if (item == tracked) { - sender.removeContextListener(this); - if (!isRemoved() && !connectToolModifiersPressed(lastStateMask)) { - temporarilyEnabledConnectTool = false; - setHint(Hints.KEY_TOOL, Hints.POINTERTOOL); - } - } - } - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.diagram.participant.pointertool; + +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.simantics.g2d.canvas.Hints; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.canvas.ICanvasParticipant; +import org.simantics.g2d.canvas.IToolMode; +import org.simantics.g2d.canvas.impl.CanvasContext; +import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency; +import org.simantics.g2d.canvas.impl.DependencyReflection.Reference; +import org.simantics.g2d.connection.IConnectionAdvisor; +import org.simantics.g2d.connection.handler.ConnectionHandler; +import org.simantics.g2d.diagram.DiagramHints; +import org.simantics.g2d.diagram.handler.PickContext; +import org.simantics.g2d.diagram.handler.PickRequest; +import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy; +import org.simantics.g2d.diagram.handler.PickRequest.PickSorter; +import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant; +import org.simantics.g2d.diagram.participant.Selection; +import org.simantics.g2d.diagram.participant.TerminalPainter; +import org.simantics.g2d.diagram.participant.TerminalPainter.ChainedHoverStrategy; +import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy; +import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo; +import org.simantics.g2d.element.ElementClassProviders; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.IElementClassProvider; +import org.simantics.g2d.participant.KeyUtil; +import org.simantics.g2d.participant.MouseUtil; +import org.simantics.g2d.participant.TransformUtil; +import org.simantics.g2d.scenegraph.SceneGraphConstants; +import org.simantics.g2d.utils.CanvasUtils; +import org.simantics.g2d.utils.GeometryUtils; +import org.simantics.scenegraph.g2d.events.Event; +import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; +import org.simantics.scenegraph.g2d.events.KeyEvent; +import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin; +import org.simantics.scenegraph.g2d.events.command.Commands; +import org.simantics.scenegraph.g2d.snap.ISnapAdvisor; +import org.simantics.utils.ObjectUtils; +import org.simantics.utils.datastructures.context.IContext; +import org.simantics.utils.datastructures.context.IContextListener; +import org.simantics.utils.datastructures.hints.IHintContext.Key; +import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; +import org.simantics.utils.threads.ThreadUtils; + +/** + * Pointer tool does the following operations with mouse: + *

    + *
  • Selections
  • + *
  • Scale
  • + *
  • Rotate
  • + *
  • Translate
  • + *
  • Draws connections (requires re-implementing + * {@link #createConnectTool(TerminalInfo, int, Point2D)})
  • + *
+ * + * Pointer tool is active only when {@link Hints#KEY_TOOL} is + * {@value Hints#POINTERTOOL}. + * + * @author Toni Kalajainen + */ +public class PointerInteractor extends AbstractDiagramParticipant { + + /** + * A hint key for terminal pick distance in control pixels. + * @see #PICK_DIST + */ + public static final Key KEY_PICK_DISTANCE = new KeyOf(Double.class, "PICK_DISTANCE"); + + /** + * Default terminal pick distance in control pixels. + * @see #DEFAULT_PICK_DISTANCE + */ + public static final double PICK_DIST = 10; + + /** + * @see #altToggled(KeyEvent) + */ + protected int lastStateMask; + + /** + * @see #altToggled(KeyEvent) + */ + protected boolean temporarilyEnabledConnectTool = false; + + public class DefaultHoverStrategy extends ChainedHoverStrategy { + public DefaultHoverStrategy(TerminalHoverStrategy orig) { + super(orig); + } + @Override + public boolean highlightEnabled() { + if (Hints.CONNECTTOOL.equals(getToolMode())) + return true; + + boolean ct = connectToolModifiersPressed(lastStateMask); + //System.out.println("highlightEnabled: " + String.format("%x", lastStateMask) + " : " + ct); + return ct; + } + @Override + public boolean canHighlight(TerminalInfo ti) { + //boolean alt = (lastStateMask & MouseEvent.ALT_MASK) != 0; + //System.out.println("canHighlight: " + String.format("%x", lastStateMask) + " : " + alt); + //if (!alt) + // return false; + IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR); + return advisor == null || advisor.canBeginConnection(null, ti.e, ti.t); + } + } + + @Dependency Selection selection; + @Dependency KeyUtil keys; + @Dependency TransformUtil util; + @Dependency PickContext pickContext; + @Dependency MouseUtil mice; + @Reference TerminalPainter terminalPainter; + + /** + * This must be higher than + * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY} + * ({@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to allow for + * better selection handling than is possible by using the plain scene graph + * event handling facilities which are installed in {@link CanvasContext}. + */ + public static final int TOOL_PRIORITY = 1 << 21; + + /** + * This must be lower than + * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY} ( + * {@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to not start box + * handling before scene graph nodes have been given a chance to react + * events. + */ + public static final int BOX_SELECT_PRIORITY = 1 << 19; + + private static final Path2D LINE10; + private static final Path2D LINE15; + private static final Path2D LINE20; + + boolean clickSelect; + boolean boxSelect; + boolean dragElement, dndDragElement; + boolean connect; + boolean doubleClickEdit; + protected IElementClassProvider elementClassProvider; + + PickPolicy boxSelectMode = PickPolicy.PICK_CONTAINED_OBJECTS; + + DefaultHoverStrategy hoverStrategy; + + private PickSorter pickSorter; + + public PointerInteractor() { + this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), null); + } + + public PointerInteractor(PickSorter pickSorter) { + this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), pickSorter); + } + + public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, IElementClassProvider ecp) { + this(clickSelect, boxSelect, dragElement, dndDragElement, connect, false, ecp, null); + } + + public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider ecp, PickSorter pickSorter) { + super(); + this.clickSelect = clickSelect; + this.boxSelect = boxSelect; + this.dragElement = dragElement; + this.dndDragElement = dndDragElement; + this.connect = connect; + this.doubleClickEdit = doubleClickEdit; + this.elementClassProvider = ecp; + this.pickSorter = pickSorter; + } + + @Override + public void addedToContext(ICanvasContext ctx) { + super.addedToContext(ctx); + hoverStrategy = new DefaultHoverStrategy((TerminalHoverStrategy) getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY)); + setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, hoverStrategy); + } + + @EventHandler(priority = 0) + public boolean handleStateMask(MouseEvent me) { + lastStateMask = me.stateMask; + if (temporarilyEnabledConnectTool) { + if (!connectToolModifiersPressed(me.stateMask)) { + temporarilyEnabledConnectTool = false; + setHint(Hints.KEY_TOOL, Hints.POINTERTOOL); + } + } else { + // It may be that the mouse has come into this control + // from outside and no key state changes have occurred yet. + // In this case this code should take care of moving the canvas + // context into CONNECTTOOL mode. + if (getToolMode() == Hints.POINTERTOOL + && connectToolModifiersPressed(me.stateMask)) + { + temporarilyEnabledConnectTool = true; + setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL); + } + } + return false; + } + + private static int mask(int mask, int mask2, boolean set) { + return set ? mask | mask2 : mask & ~mask2; + } + + @EventHandler(priority = -1) + public boolean altToggled(KeyEvent ke) { + int mods = ke.stateMask; + boolean press = ke instanceof KeyPressedEvent; + boolean modifierPressed = false; + if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) { + mods = mask(mods, MouseEvent.ALT_MASK, press); + modifierPressed = true; + } + if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT_GRAPH) { + mods = mask(mods, MouseEvent.ALT_GRAPH_MASK, press); + modifierPressed = true; + } + if (ke.keyCode == java.awt.event.KeyEvent.VK_SHIFT) { + mods = mask(mods, MouseEvent.SHIFT_MASK, press); + modifierPressed = true; + } + if (ke.keyCode == java.awt.event.KeyEvent.VK_CONTROL) { + mods = mask(mods, MouseEvent.CTRL_MASK, press); + modifierPressed = true; + } + if (ke.keyCode == java.awt.event.KeyEvent.VK_META) { + // TODO: NO MASK FOR META! + modifierPressed = true; + } + // Don't deny connecting when CTRL is marked pressed because ALT_GRAPH + // is actually ALT+CTRL in SWT. There's no way in SWT to tell apart + // CTRL+ALT and ALT GRAPH. + boolean otherModifiers = (mods & (MouseEvent.SHIFT_MASK)) != 0; + boolean altModifier = (mods & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0; + if (modifierPressed) { + boolean altPressed = !otherModifiers && altModifier; + lastStateMask = mods; + if (altPressed) { + IToolMode mode = getToolMode(); + if (mode == Hints.POINTERTOOL) { + //System.out.println("TEMP++"); + temporarilyEnabledConnectTool = true; + setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL); + } + } else { + if (temporarilyEnabledConnectTool) { + //System.out.println("TEMP--"); + temporarilyEnabledConnectTool = false; + setHint(Hints.KEY_TOOL, Hints.POINTERTOOL); + } + } + // Make sure that TerminalPainter updates its scene graph. + if (terminalPainter != null) { + terminalPainter.update(terminalPainter.highlightEnabled()); + } + } + return false; + } + + /** + * @param controlPos + * @return null if current canvas transform is not invertible + */ + public Shape getCanvasPickShape(Point2D controlPos) { + AffineTransform inverse = util.getInverseTransform(); + if (inverse == null) + return null; + + double pd = getPickDistance(); + Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2+1, pd*2+1); + Shape canvasShape = GeometryUtils.transformShape(controlPickRect, inverse); + return canvasShape; + } + + public List 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(); + + // Pick Terminal + double pickDist = getPickDistance(); + Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1); + Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); + int selectionId = me.mouseId; + + PickRequest req = new PickRequest(canvasPickRect).context(getContext()); + req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS; + req.pickSorter = pickSorter; + //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST; + List pickables = new ArrayList(); + pickContext.pick(diagram, req, pickables); + + Set 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 currentSelection, List pickables) { + if (pickables.isEmpty()) + return null; + + // Check that the pickable-list doesn't contain anything that is in the current selection. + if (!Collections.disjoint(currentSelection, pickables)) + return null; + + IElement top = pickables.get(pickables.size() - 1); + boolean elementOnTop = !PickRequest.PickFilter.FILTER_CONNECTIONS.accept(top); + if (!elementOnTop) + return null; + for (int i = pickables.size() - 2; i >= 0; --i) { + IElement e = pickables.get(i); + if (!PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e)) + return null; + } + return top; + } + + /** + * Since there's seems to be no better method available for finding out if + * the SWT popup menu was just recently closed or not, we use the following + * heuristic: + * + * SWT popup was just closed if it was closed < 300ms ago. + * + * Note that this is a very bad heuristic and may fail on slower machines or + * under heavy system load. + * + * @return + */ + private boolean wasPopupJustClosed(Event event) { + Long popupCloseTime = getHint(DiagramHints.POPUP_MENU_HIDDEN); + if (popupCloseTime != null) { + long timeDiff = event.time - popupCloseTime; + //System.out.println("time diff: " + timeDiff); + if (timeDiff < 300) { + //System.out.println("POPUP WAS JUST CLOSED!"); + return true; + } + } + //System.out.println("Popup has been closed for a while."); + return false; + } + + boolean handleDoubleClick(MouseClickEvent me) { + //System.out.println("mouse double clicked: " + me); + if (!hasDoubleClickEdit()) return false; + if (me.button != MouseEvent.LEFT_BUTTON) return false; + if (getToolMode() != Hints.POINTERTOOL) return false; + if (me.clickCount < 2) return false; + + // Pick Terminal + double pickDist = getPickDistance(); + Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1); + Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); + int selectionId = me.mouseId; + + PickRequest req = new PickRequest(canvasPickRect).context(getContext()); + req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS; + List pick = new ArrayList(); + 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 elementsToDrag = Collections.emptySet(); + + /** + * Invoked before scene graph event handling and {@link #handleBoxSelect(MouseDragBegin)}. + * @param me + * @return + * @see #handleBoxSelect(MouseDragBegin) + */ + @EventHandler(priority = TOOL_PRIORITY) + public boolean handleDrag(MouseDragBegin me) { + if (!hasElementDrag() && !hasBoxSelect()) return false; + if (me.button != MouseEvent.LEFT_BUTTON) return false; + if (getToolMode() != Hints.POINTERTOOL) return false; + if (hasToolMode(me.mouseId)) return false; + + boolean anyModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK); + boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK + ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/)); + + if (nonSelectionModifierPressed) + return false; + + assertDependencies(); + + Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, curCanvasDragPos); + PickRequest req = new PickRequest(me.startCanvasPos).context(getContext()); + req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS; + List picks = new ArrayList(); + pickContext.pick(diagram, req, picks); + + //System.out.println(picks); + if (picks.isEmpty()) { + // Widen the area of searching if nothing is found with point picking + double pickDist = getPickDistance(); + Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1); + Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform()); + req = new PickRequest(canvasPickRect).context(getContext()); + req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS; + pickContext.pick(diagram, req, picks); + //System.out.println("2nd try: " + picks); + } + + Set sel = selection.getSelection(me.mouseId); + IElement topMostPick = picks.isEmpty() ? null : picks.get(picks.size() - 1); + Set elementsToDrag = new HashSet(); + this.elementsToDrag = elementsToDrag; + + if (!Collections.disjoint(sel, picks)) { + elementsToDrag.addAll(sel); + } else { + if (topMostPick != null && (sel.isEmpty() || !sel.contains(topMostPick))) { + selection.setSelection(me.mouseId, topMostPick); + sel = selection.getSelection(me.mouseId); + elementsToDrag.addAll(sel); + } + } + + // Drag Elements + if (!elementsToDrag.isEmpty() && hasElementDnDDrag()) { + // To Be Implemented in the next Diagram data model. + } else { + if (!anyModifierPressed && !elementsToDrag.isEmpty() && hasElementDrag()) { + // Connections are not translatable, re-routing is in RouteGraphNode. + boolean onlyConnections = onlyConnections(elementsToDrag); + if (!onlyConnections) { + ICanvasParticipant tm = createTranslateTool(me.mouseId, me.startCanvasPos, curCanvasPos, elementsToDrag); + if (tm != null) { + getContext().add(tm); + return !onlyConnections; + } + } + } + } + + return false; + } + + /** + * Always invoked after after {@link #handleDrag(MouseDragBegin)} and scene + * graph event handling to prevent the box selection mode from being + * initiated before scene graph nodes have a chance to react. + * + *

+ * Note that this method assumes that elementsToDrag and + * curCanvasPos 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 elements) { + for (IElement e : elements) + if (!e.getElementClass().containsClass(ConnectionHandler.class)) + return false; + return true; + } + + private IElement rotatingPick(int selectionId, List pickables) { + Set sel = selection.getSelection(selectionId); + return rotatingPick(sel, pickables); + } + + private IElement rotatingPick(Set sel, List 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 tis, int mouseId, Point2D startCanvasPos) { + return null; + } + + protected ICanvasParticipant createTranslateTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, Set 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 { + private ICanvasParticipant tracked; + public ToolModeResetter(ICanvasParticipant trackedParticipant) { + this.tracked = trackedParticipant; + } + @Override + public void itemAdded(IContext sender, ICanvasParticipant item) { + } + @Override + public void itemRemoved(IContext sender, ICanvasParticipant item) { + if (item == tracked) { + sender.removeContextListener(this); + if (!isRemoved() && !connectToolModifiersPressed(lastStateMask)) { + temporarilyEnabledConnectTool = false; + setHint(Hints.KEY_TOOL, Hints.POINTERTOOL); + } + } + } + } + +}