/******************************************************************************* * 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: * * * 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 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, pd*2); 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(); 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 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; 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 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); 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 picks = new ArrayList(); pickContext.pick(diagram, req, 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; } } 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. * *

* 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); } } } } }