1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.diagram.participant.pointertool;
14 import java.awt.Shape;
15 import java.awt.geom.AffineTransform;
16 import java.awt.geom.Path2D;
17 import java.awt.geom.Point2D;
18 import java.awt.geom.Rectangle2D;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.HashSet;
22 import java.util.List;
25 import org.simantics.g2d.canvas.Hints;
26 import org.simantics.g2d.canvas.ICanvasContext;
27 import org.simantics.g2d.canvas.ICanvasParticipant;
28 import org.simantics.g2d.canvas.IToolMode;
29 import org.simantics.g2d.canvas.impl.CanvasContext;
30 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
31 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
32 import org.simantics.g2d.connection.IConnectionAdvisor;
33 import org.simantics.g2d.connection.handler.ConnectionHandler;
34 import org.simantics.g2d.diagram.DiagramHints;
35 import org.simantics.g2d.diagram.handler.PickContext;
36 import org.simantics.g2d.diagram.handler.PickRequest;
37 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
38 import org.simantics.g2d.diagram.handler.PickRequest.PickSorter;
39 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
40 import org.simantics.g2d.diagram.participant.Selection;
41 import org.simantics.g2d.diagram.participant.TerminalPainter;
42 import org.simantics.g2d.diagram.participant.TerminalPainter.ChainedHoverStrategy;
43 import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
44 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
45 import org.simantics.g2d.element.ElementClassProviders;
46 import org.simantics.g2d.element.IElement;
47 import org.simantics.g2d.element.IElementClassProvider;
48 import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
49 import org.simantics.g2d.participant.KeyUtil;
50 import org.simantics.g2d.participant.MouseUtil;
51 import org.simantics.g2d.participant.TransformUtil;
52 import org.simantics.g2d.scenegraph.SceneGraphConstants;
53 import org.simantics.g2d.utils.CanvasUtils;
54 import org.simantics.g2d.utils.GeometryUtils;
55 import org.simantics.scenegraph.g2d.G2DSceneGraph;
56 import org.simantics.scenegraph.g2d.events.Event;
57 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
58 import org.simantics.scenegraph.g2d.events.KeyEvent;
59 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
60 import org.simantics.scenegraph.g2d.events.MouseEvent;
61 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
62 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
63 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
64 import org.simantics.scenegraph.g2d.events.command.Commands;
65 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
66 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
67 import org.simantics.utils.ObjectUtils;
68 import org.simantics.utils.datastructures.context.IContext;
69 import org.simantics.utils.datastructures.context.IContextListener;
70 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
71 import org.simantics.utils.datastructures.hints.IHintContext.Key;
72 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
73 import org.simantics.utils.datastructures.hints.IHintObservable;
74 import org.simantics.utils.threads.ThreadUtils;
77 * Pointer tool does the following operations with mouse:
83 * <li>Draws connections (requires re-implementing
84 * {@link #createConnectTool(TerminalInfo, int, Point2D)})</li>
87 * Pointer tool is active only when {@link Hints#KEY_TOOL} is
88 * {@value Hints#POINTERTOOL}.
90 * @author Toni Kalajainen
92 public class PointerInteractor extends AbstractDiagramParticipant {
95 * Hint keys for pick distances in control pixels.
98 public static final Key KEY_PICK_DISTANCE = new KeyOf(Double.class, "PICK_DISTANCE");
99 public static final Key KEY_TERMINAL_PICK_DISTANCE = new KeyOf(Double.class, "TERMINAL_PICK_DISTANCE");
102 * Default pick distances in control pixels.
103 * @see #DEFAULT_PICK_DISTANCE
105 public static final double PICK_DIST = 5;
106 public static final double TERMINAL_PICK_DIST = 15;
109 * @see #altToggled(KeyEvent)
111 protected int lastStateMask;
114 * @see #altToggled(KeyEvent)
116 protected boolean temporarilyEnabledConnectTool = false;
118 public class DefaultHoverStrategy extends ChainedHoverStrategy {
119 public DefaultHoverStrategy(TerminalHoverStrategy orig) {
123 public boolean highlightEnabled() {
124 if (Hints.CONNECTTOOL.equals(getToolMode()))
127 boolean ct = connectToolModifiersPressed(lastStateMask);
128 //System.out.println("highlightEnabled: " + String.format("%x", lastStateMask) + " : " + ct);
132 public boolean canHighlight(TerminalInfo ti) {
133 //boolean alt = (lastStateMask & MouseEvent.ALT_MASK) != 0;
134 //System.out.println("canHighlight: " + String.format("%x", lastStateMask) + " : " + alt);
137 IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
138 return advisor == null || advisor.canBeginConnection(null, ti.e, ti.t);
142 @Dependency Selection selection;
143 @Dependency KeyUtil keys;
144 @Dependency TransformUtil util;
145 @Dependency PickContext pickContext;
146 @Dependency MouseUtil mice;
147 @Reference TerminalPainter terminalPainter;
150 * This must be higher than
151 * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}
152 * ({@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to allow for
153 * better selection handling than is possible by using the plain scene graph
154 * event handling facilities which are installed in {@link CanvasContext}.
156 public static final int TOOL_PRIORITY = 1 << 21;
159 * This must be lower than
160 * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY} (
161 * {@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to not start box
162 * handling before scene graph nodes have been given a chance to react
165 public static final int BOX_SELECT_PRIORITY = 1 << 19;
167 private static final Path2D LINE10;
168 private static final Path2D LINE15;
169 private static final Path2D LINE20;
173 boolean dragElement, dndDragElement;
175 boolean doubleClickEdit;
176 protected IElementClassProvider elementClassProvider;
178 PickPolicy boxSelectMode = PickPolicy.PICK_CONTAINED_OBJECTS;
180 DefaultHoverStrategy hoverStrategy;
182 private PickSorter pickSorter;
184 public PointerInteractor() {
185 this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), null);
188 public PointerInteractor(PickSorter pickSorter) {
189 this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), pickSorter);
192 public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, IElementClassProvider ecp) {
193 this(clickSelect, boxSelect, dragElement, dndDragElement, connect, false, ecp, null);
196 public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider ecp, PickSorter pickSorter) {
198 this.clickSelect = clickSelect;
199 this.boxSelect = boxSelect;
200 this.dragElement = dragElement;
201 this.dndDragElement = dndDragElement;
202 this.connect = connect;
203 this.doubleClickEdit = doubleClickEdit;
204 this.elementClassProvider = ecp;
205 this.pickSorter = pickSorter;
209 public void addedToContext(ICanvasContext ctx) {
210 super.addedToContext(ctx);
211 hoverStrategy = new DefaultHoverStrategy((TerminalHoverStrategy) getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY));
212 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, hoverStrategy);
214 getContext().getSceneGraph().setGlobalProperty(G2DSceneGraph.PICK_DISTANCE, getPickDistance());
215 getHintStack().addKeyHintListener(KEY_PICK_DISTANCE, new HintListenerAdapter() {
217 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
218 getContext().getSceneGraph().setGlobalProperty(G2DSceneGraph.PICK_DISTANCE, getPickDistance());
223 @EventHandler(priority = 0)
224 public boolean handleStateMask(MouseEvent me) {
225 lastStateMask = me.stateMask;
226 if (temporarilyEnabledConnectTool) {
227 if (!connectToolModifiersPressed(me.stateMask)) {
228 temporarilyEnabledConnectTool = false;
229 setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
232 // It may be that the mouse has come into this control
233 // from outside and no key state changes have occurred yet.
234 // In this case this code should take care of moving the canvas
235 // context into CONNECTTOOL mode.
236 if (getToolMode() == Hints.POINTERTOOL
237 && connectToolModifiersPressed(me.stateMask))
239 temporarilyEnabledConnectTool = true;
240 setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
246 private static int mask(int mask, int mask2, boolean set) {
247 return set ? mask | mask2 : mask & ~mask2;
250 @EventHandler(priority = -1)
251 public boolean altToggled(KeyEvent ke) {
252 int mods = ke.stateMask;
253 boolean press = ke instanceof KeyPressedEvent;
254 boolean modifierPressed = false;
255 if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
256 mods = mask(mods, MouseEvent.ALT_MASK, press);
257 modifierPressed = true;
259 if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT_GRAPH) {
260 mods = mask(mods, MouseEvent.ALT_GRAPH_MASK, press);
261 modifierPressed = true;
263 if (ke.keyCode == java.awt.event.KeyEvent.VK_SHIFT) {
264 mods = mask(mods, MouseEvent.SHIFT_MASK, press);
265 modifierPressed = true;
267 if (ke.keyCode == java.awt.event.KeyEvent.VK_CONTROL) {
268 mods = mask(mods, MouseEvent.CTRL_MASK, press);
269 modifierPressed = true;
271 if (ke.keyCode == java.awt.event.KeyEvent.VK_META) {
272 // TODO: NO MASK FOR META!
273 modifierPressed = true;
275 // Don't deny connecting when CTRL is marked pressed because ALT_GRAPH
276 // is actually ALT+CTRL in SWT. There's no way in SWT to tell apart
277 // CTRL+ALT and ALT GRAPH.
278 boolean otherModifiers = (mods & (MouseEvent.SHIFT_MASK)) != 0;
279 boolean altModifier = (mods & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;
280 if (modifierPressed) {
281 boolean altPressed = !otherModifiers && altModifier;
282 lastStateMask = mods;
284 IToolMode mode = getToolMode();
285 if (mode == Hints.POINTERTOOL) {
286 //System.out.println("TEMP++");
287 temporarilyEnabledConnectTool = true;
288 setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
291 if (temporarilyEnabledConnectTool) {
292 //System.out.println("TEMP--");
293 temporarilyEnabledConnectTool = false;
294 setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
297 // Make sure that TerminalPainter updates its scene graph.
298 if (terminalPainter != null) {
299 terminalPainter.update(terminalPainter.highlightEnabled());
307 * @return <code>null</code> if current canvas transform is not invertible
309 public Shape getCanvasPickShape(Point2D controlPos) {
310 AffineTransform inverse = util.getInverseTransform();
314 double pd = getPickDistance();
315 Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2, pd*2);
316 Shape canvasShape = GeometryUtils.transformShape(controlPickRect, inverse);
322 * @return <code>null</code> if current canvas transform is not invertible
324 public Shape getTerminalCanvasPickShape(Point2D controlPos) {
325 AffineTransform inverse = util.getInverseTransform();
329 double pd = getTerminalPickDistance();
330 Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2, pd*2);
331 Shape canvasShape = GeometryUtils.transformShape(controlPickRect, inverse);
335 public List<TerminalInfo> pickTerminals(Point2D controlPos)
337 Shape canvasPickRect = getTerminalCanvasPickShape(controlPos);
338 if (canvasPickRect == null)
339 return Collections.emptyList();
340 return TerminalUtil.pickTerminals(getContext(), diagram, canvasPickRect, true, true);
343 public TerminalInfo pickTerminal(Point2D controlPos)
345 Shape canvasPickRect = getTerminalCanvasPickShape(controlPos);
346 if (canvasPickRect == null)
348 TerminalInfo ti = TerminalUtil.pickTerminal(getContext(), diagram, canvasPickRect);
352 @EventHandler(priority = TOOL_PRIORITY)
353 public boolean handlePress(MouseButtonPressedEvent me) {
356 if (me.button != MouseEvent.LEFT_BUTTON)
359 IToolMode mode = getToolMode();
361 // It may be that the mouse has come into this control
362 // from outside without focusing it and without any mouse
363 // buttons being pressed. If the user is pressing the
364 // connection modifier we need to temporarily enable connect
365 // mode here and now.
366 if (mode == Hints.POINTERTOOL && connectToolModifiersPressed(me.stateMask)) {
367 temporarilyEnabledConnectTool = true;
368 mode = Hints.CONNECTTOOL;
369 setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
372 if (mode == Hints.CONNECTTOOL) {
373 Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, null);
374 return checkInitiateConnectTool(me, curCanvasPos);
380 protected boolean checkInitiateConnectTool(MouseEvent me, Point2D mouseCanvasPos) {
382 IToolMode mode = getToolMode();
383 if (mode == Hints.CONNECTTOOL || connectToolModifiersPressed(me.stateMask)) {
384 IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
385 TerminalInfo ti = pickTerminal(me.controlPosition);
387 ICanvasParticipant bsi = null;
389 if (advisor == null || advisor.canBeginConnection(null, ti.e, ti.t)) {
390 bsi = createConnectTool(ti, me.mouseId, mouseCanvasPos);
393 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
394 if (snapAdvisor != null)
395 snapAdvisor.snap(mouseCanvasPos);
397 // Start connection out of thin air, without a terminal.
398 bsi = createConnectTool(null, me.mouseId, mouseCanvasPos);
401 // Did we catch anything?
403 startConnectTool(bsi);
411 protected void startConnectTool(ICanvasParticipant tool) {
412 getContext().add(tool);
413 //System.out.println("TEMP: " + temporarilyEnabledConnectTool);
414 if (temporarilyEnabledConnectTool) {
415 // Resets pointer tool back into use if necessary after
416 // connection has been finished or canceled.
417 getContext().addContextListener(new ToolModeResetter(tool));
421 @EventHandler(priority = TOOL_PRIORITY)
422 public boolean handleClick(MouseClickEvent me) {
423 //System.out.println(getClass().getSimpleName() + ": mouse clicked: @ " + me.time);
425 if (hasDoubleClickEdit() && me.clickCount == 2) {
426 if (handleDoubleClick(me))
430 if (!hasClickSelect()) return false;
431 if (!hasToolMode(Hints.POINTERTOOL)) return false;
433 // Don't handle any events where click count is more than 1 to prevent
434 // current diagram selection from bouncing around unnecessarily.
435 if (me.clickCount > 1)
438 boolean isLeft = me.button == MouseEvent.LEFT_BUTTON;
439 boolean isRight = me.button == MouseEvent.RIGHT_BUTTON;
440 if (!isLeft && !isRight) return false;
442 boolean popupWasVisible = wasPopupJustClosed(me);
443 boolean noModifiers = !anyModifierPressed(me);
444 boolean isShiftPressed = me.hasAllModifiers(MouseEvent.SHIFT_MASK);
446 assertDependencies();
448 Shape canvasPickRect = getCanvasPickShape(me.controlPosition);
449 int selectionId = me.mouseId;
451 PickRequest req = new PickRequest(canvasPickRect).context(getContext());
452 req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
453 req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY());
455 //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;
456 List<IElement> pickables = new ArrayList<IElement>();
457 pickContext.pick(diagram, req, pickables);
459 Set<IElement> currentSelection = selection.getSelection(selectionId);
462 if (pickables.isEmpty()) {
463 if (!popupWasVisible) {
464 // Only clear selection on left button clicks.
466 selection.clear(selectionId);
470 if (/*!currentSelection.isEmpty() &&*/ noModifiers)
471 setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
477 if ((me.stateMask & MouseEvent.CTRL_MASK) != 0) {
480 * - If the mouse points to an object not in the selection, add it to selection.
481 * - If the mouse points to multiple objects, add the first.
482 * - If all objects the mouse points are already in selection, remove one of them from selection.
484 IElement removable = null;
485 for (int i = pickables.size() - 1; i >= 0; --i) {
486 IElement pickable = pickables.get(i);
487 if (selection.add(selectionId, pickable)) {
491 removable = pickable;
493 // Do not perform rotating pick in toggle selection
494 // when only CTRL is pressed. Requires SHIFT+CTRL.
498 if (removable != null)
499 selection.remove(selectionId, removable);
504 boolean result = false;
508 if (isLeft && popupWasVisible)
509 // Popup menu is visible, just let it close
512 // Don't change selection on right clicks if there's more to pick
513 // than a single element.
514 if (isRight && pickables.size() > 1) {
515 IElement selectElement = singleElementAboveNonselectedConnections(currentSelection, pickables);
516 if (selectElement != null) {
517 selection.setSelection(selectionId, selectElement);
519 if (!currentSelection.isEmpty() && noModifiers)
520 setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
525 * Select the one object the mouse points to. If multiple object
526 * are picked, select the one that is after the earliest by
527 * index of the current selection when shift is pressed. Otherwise
528 * always pick the topmost element.
530 IElement selectedPick = isShiftPressed
531 ? rotatingPick(currentSelection, pickables)
532 : pickables.get(pickables.size() - 1);
535 // 1. the selection would actually change
537 // 2.1. left button was pressed
539 // 2.2. right button was pressed and the element to-be-selected
540 // is NOT a part of the current selection
541 if (!Collections.singleton(selectedPick).equals(currentSelection)
542 && (isLeft || (isRight && !currentSelection.contains(selectedPick)))) {
543 selection.setSelection(selectionId, selectedPick);
548 if (isRight && pickables.size() == 1 && noModifiers) {
549 setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
557 * A heuristic needed for implementing right-click diagram selection in a
560 * @param currentSelection
564 private IElement singleElementAboveNonselectedConnections(Set<IElement> currentSelection, List<IElement> pickables) {
565 if (pickables.isEmpty())
568 // Check that the pickable-list doesn't contain anything that is in the current selection.
569 if (!Collections.disjoint(currentSelection, pickables))
572 IElement top = pickables.get(pickables.size() - 1);
573 boolean elementOnTop = !PickRequest.PickFilter.FILTER_CONNECTIONS.accept(top);
576 for (int i = pickables.size() - 2; i >= 0; --i) {
577 IElement e = pickables.get(i);
578 if (!PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e))
585 * Since there's seems to be no better method available for finding out if
586 * the SWT popup menu was just recently closed or not, we use the following
589 * SWT popup was just closed if it was closed < 300ms ago.
591 * Note that this is a very bad heuristic and may fail on slower machines or
592 * under heavy system load.
596 private boolean wasPopupJustClosed(Event event) {
597 Long popupCloseTime = getHint(DiagramHints.POPUP_MENU_HIDDEN);
598 if (popupCloseTime != null) {
599 long timeDiff = event.time - popupCloseTime;
600 //System.out.println("time diff: " + timeDiff);
601 if (timeDiff < 300) {
602 //System.out.println("POPUP WAS JUST CLOSED!");
606 //System.out.println("Popup has been closed for a while.");
610 boolean handleDoubleClick(MouseClickEvent me) {
611 //System.out.println("mouse double clicked: " + me);
612 if (!hasDoubleClickEdit()) return false;
613 if (me.button != MouseEvent.LEFT_BUTTON) return false;
614 if (getToolMode() != Hints.POINTERTOOL) return false;
615 if (me.clickCount < 2) return false;
617 Shape canvasPickRect = getCanvasPickShape(me.controlPosition);
618 int selectionId = me.mouseId;
620 PickRequest req = new PickRequest(canvasPickRect).context(getContext());
621 req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
623 req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY());
624 List<IElement> pick = new ArrayList<IElement>();
625 pickContext.pick(diagram, req, pick);
628 if (pick.isEmpty()) {
629 selection.clear(selectionId);
633 IElement selectedPick = rotatingPick(selectionId, pick);
635 if (!selection.contains(selectionId, selectedPick)) {
636 selection.setSelection(selectionId, selectedPick);
639 CanvasUtils.sendCommand(getContext(), Commands.RENAME);
644 // Values shared by #handleDrag and #handleBoxSelect
645 private transient Point2D curCanvasDragPos = new Point2D.Double();
646 private transient Set<IElement> elementsToDrag = Collections.emptySet();
649 * Invoked before scene graph event handling and {@link #handleBoxSelect(MouseDragBegin)}.
652 * @see #handleBoxSelect(MouseDragBegin)
654 @EventHandler(priority = TOOL_PRIORITY)
655 public boolean handleDrag(MouseDragBegin me) {
656 if (!hasElementDrag() && !hasBoxSelect()) return false;
657 if (me.button != MouseEvent.LEFT_BUTTON) return false;
658 if (getToolMode() != Hints.POINTERTOOL) return false;
659 if (hasToolMode(me.mouseId)) return false;
661 boolean anyModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);
662 boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK
663 ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));
665 if (nonSelectionModifierPressed)
668 assertDependencies();
670 Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, curCanvasDragPos);
671 Shape canvasPickRect = getCanvasPickShape(me.controlPosition);
672 PickRequest req = new PickRequest(canvasPickRect).context(getContext());
673 req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
674 req.pickSorter = PickRequest.PickSorter.connectionSorter(pickSorter, req.pickArea.getBounds2D().getCenterX(), req.pickArea.getBounds2D().getCenterY());
675 List<IElement> picks = new ArrayList<IElement>();
676 pickContext.pick(diagram, req, picks);
678 Set<IElement> sel = selection.getSelection(me.mouseId);
679 IElement topMostPick = picks.isEmpty() ? null : picks.get(picks.size() - 1);
680 Set<IElement> elementsToDrag = new HashSet<IElement>();
681 this.elementsToDrag = elementsToDrag;
683 if (!Collections.disjoint(sel, picks)) {
684 elementsToDrag.addAll(sel);
686 if (topMostPick != null && (sel.isEmpty() || !sel.contains(topMostPick))) {
687 selection.setSelection(me.mouseId, topMostPick);
688 sel = selection.getSelection(me.mouseId);
689 elementsToDrag.addAll(sel);
694 if (!elementsToDrag.isEmpty() && hasElementDnDDrag()) {
695 // To Be Implemented in the next Diagram data model.
697 if (!anyModifierPressed && !elementsToDrag.isEmpty() && hasElementDrag()) {
698 // Connections are not translatable, re-routing is in RouteGraphNode.
699 boolean onlyConnections = onlyConnections(elementsToDrag);
700 if (!onlyConnections) {
701 ICanvasParticipant tm = createTranslateTool(me.mouseId, me.startCanvasPos, curCanvasPos, elementsToDrag);
703 getContext().add(tm);
704 return !onlyConnections;
707 // forward MouseDragBegin to closest RouteGraphNode
708 for (int i = picks.size() - 1; i >= 0; i--) {
709 RouteGraphNode rgn = picks.get(i).getHint(RouteGraphConnectionClass.KEY_RG_NODE);
723 * Always invoked after after {@link #handleDrag(MouseDragBegin)} and scene
724 * graph event handling to prevent the box selection mode from being
725 * initiated before scene graph nodes have a chance to react.
728 * Note that this method assumes that <code>elementsToDrag</code> and
729 * <code>curCanvasPos</code> are already set by
730 * {@link #handleDrag(MouseDragBegin)}.
735 @EventHandler(priority = BOX_SELECT_PRIORITY)
736 public boolean handleBoxSelect(MouseDragBegin me) {
737 if (!hasBoxSelect()) return false;
738 if (me.button != MouseEvent.LEFT_BUTTON) return false;
739 if (getToolMode() != Hints.POINTERTOOL) return false;
740 if (hasToolMode(me.mouseId)) return false;
742 boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK
743 ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));
744 if (nonSelectionModifierPressed)
747 if (!nonSelectionModifierPressed && elementsToDrag.isEmpty()) {
749 ICanvasParticipant bsm = createBoxSelectTool(me.mouseId, me.startCanvasPos, curCanvasDragPos, me.button, boxSelectMode);
751 getContext().add(bsm);
757 private static boolean onlyConnections(Set<IElement> elements) {
758 for (IElement e : elements)
759 if (!e.getElementClass().containsClass(ConnectionHandler.class))
764 private IElement rotatingPick(int selectionId, List<IElement> pickables) {
765 Set<IElement> sel = selection.getSelection(selectionId);
766 return rotatingPick(sel, pickables);
769 private IElement rotatingPick(Set<IElement> sel, List<IElement> pickables) {
770 int earliestIndex = pickables.size();
771 for (int i = pickables.size() - 1; i >= 0; --i) {
772 if (sel.contains(pickables.get(i))) {
777 if (earliestIndex == 0)
778 earliestIndex = pickables.size();
779 IElement selectedPick = pickables.get(earliestIndex - 1);
784 * Is mouse in some kind of mode?
788 boolean hasToolMode(int mouseId) {
789 for (AbstractMode am : getContext().getItemsByClass(AbstractMode.class))
790 if (am.mouseId==mouseId) return true;
794 boolean hasToolMode(IToolMode mode) {
795 return ObjectUtils.objectEquals(mode, getToolMode());
798 boolean hasToolMode(IToolMode... modes) {
799 IToolMode current = getToolMode();
802 for (IToolMode mode : modes)
803 if (current.equals(mode))
808 protected IToolMode getToolMode() {
809 return getHint(Hints.KEY_TOOL);
812 /// is box select enabled
813 protected boolean hasBoxSelect() {
817 /// is click select enabled
818 protected boolean hasClickSelect() {
822 /// is double click edit enabled
823 protected boolean hasDoubleClickEdit() {
824 return doubleClickEdit;
827 // is element drag enabled
828 protected boolean hasElementDrag() {
832 // is element drag enabled
833 protected boolean hasElementDnDDrag() {
834 return dndDragElement;
837 // is connect enabled
838 protected boolean connects() {
842 public double getPickDistance() {
843 Double pickDistance = getHint(KEY_PICK_DISTANCE);
844 return pickDistance == null ? PICK_DIST : Math.max(pickDistance, 0);
847 public double getTerminalPickDistance() {
848 Double pickDistance = getHint(KEY_TERMINAL_PICK_DISTANCE);
849 return pickDistance == null ? TERMINAL_PICK_DIST : Math.max(pickDistance, 0);
852 public PickPolicy getBoxSelectMode() {
853 return boxSelectMode;
856 public void setBoxSelectMode(PickPolicy boxSelectMode) {
857 this.boxSelectMode = boxSelectMode;
860 public void setSelectionEnabled(boolean select) {
861 this.clickSelect = select;
862 this.boxSelect = select;
863 if(select == false) { // Clear all selections if select is disabled
864 final int[] ids = selection.getSelectionIds();
866 ThreadUtils.asyncExec(getContext().getThreadAccess(), new Runnable() {
871 getContext().getContentContext().setDirty();
878 boolean anyModifierPressed(MouseEvent e) {
879 return e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);
882 boolean connectToolModifiersPressed(int stateMask) {
883 return (stateMask & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;
887 LINE10 = new Path2D.Double();
889 LINE10.lineTo(10, 0);
890 LINE10.lineTo(7, -3);
891 LINE10.moveTo(10, 0);
894 LINE15 = new Path2D.Double();
896 LINE15.lineTo(15, 0);
897 LINE15.lineTo(12, -3);
898 LINE15.moveTo(15, 0);
899 LINE15.lineTo(12, 3);
901 LINE20 = new Path2D.Double();
903 LINE20.lineTo(20, 0);
904 LINE20.lineTo(17, -3);
905 LINE20.moveTo(20, 0);
906 LINE20.lineTo(17, 3);
912 protected ICanvasParticipant createConnectTool(TerminalInfo ti, int mouseId, Point2D startCanvasPos) {
916 protected ICanvasParticipant createConnectToolWithTerminals(List<TerminalInfo> tis, int mouseId, Point2D startCanvasPos) {
920 protected ICanvasParticipant createTranslateTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, Set<IElement> elementsToDrag) {
921 return new TranslateMode(startCanvasPos, curCanvasPos, mouseId, elementsToDrag);
924 protected ICanvasParticipant createBoxSelectTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, int button, PickPolicy boxSelectMode) {
925 return new BoxSelectionMode(startCanvasPos, curCanvasPos, mouseId, button, boxSelectMode);
929 * A context listener for resetting tool mode back to pointer mode after the
930 * tracked participant has been removed.
932 protected class ToolModeResetter implements IContextListener<ICanvasParticipant> {
933 private ICanvasParticipant tracked;
934 public ToolModeResetter(ICanvasParticipant trackedParticipant) {
935 this.tracked = trackedParticipant;
938 public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
941 public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
942 if (item == tracked) {
943 sender.removeContextListener(this);
944 if (!isRemoved() && !connectToolModifiersPressed(lastStateMask)) {
945 temporarilyEnabledConnectTool = false;
946 setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);