1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.diagram.participant.pointertool;
\r
14 import java.awt.Shape;
\r
15 import java.awt.geom.AffineTransform;
\r
16 import java.awt.geom.Path2D;
\r
17 import java.awt.geom.Point2D;
\r
18 import java.awt.geom.Rectangle2D;
\r
19 import java.util.ArrayList;
\r
20 import java.util.Collections;
\r
21 import java.util.HashSet;
\r
22 import java.util.List;
\r
23 import java.util.Set;
\r
25 import org.simantics.g2d.canvas.Hints;
\r
26 import org.simantics.g2d.canvas.ICanvasContext;
\r
27 import org.simantics.g2d.canvas.ICanvasParticipant;
\r
28 import org.simantics.g2d.canvas.IToolMode;
\r
29 import org.simantics.g2d.canvas.impl.CanvasContext;
\r
30 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
31 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
\r
32 import org.simantics.g2d.connection.IConnectionAdvisor;
\r
33 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
34 import org.simantics.g2d.diagram.DiagramHints;
\r
35 import org.simantics.g2d.diagram.handler.PickContext;
\r
36 import org.simantics.g2d.diagram.handler.PickRequest;
\r
37 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
\r
38 import org.simantics.g2d.diagram.handler.PickRequest.PickSorter;
\r
39 import org.simantics.g2d.diagram.participant.AbstractDiagramParticipant;
\r
40 import org.simantics.g2d.diagram.participant.Selection;
\r
41 import org.simantics.g2d.diagram.participant.TerminalPainter;
\r
42 import org.simantics.g2d.diagram.participant.TerminalPainter.ChainedHoverStrategy;
\r
43 import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
\r
44 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
\r
45 import org.simantics.g2d.element.ElementClassProviders;
\r
46 import org.simantics.g2d.element.IElement;
\r
47 import org.simantics.g2d.element.IElementClassProvider;
\r
48 import org.simantics.g2d.participant.KeyUtil;
\r
49 import org.simantics.g2d.participant.MouseUtil;
\r
50 import org.simantics.g2d.participant.TransformUtil;
\r
51 import org.simantics.g2d.scenegraph.SceneGraphConstants;
\r
52 import org.simantics.g2d.utils.CanvasUtils;
\r
53 import org.simantics.g2d.utils.GeometryUtils;
\r
54 import org.simantics.scenegraph.g2d.events.Event;
\r
55 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
\r
56 import org.simantics.scenegraph.g2d.events.KeyEvent;
\r
57 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
\r
58 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
59 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
60 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
\r
61 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
\r
62 import org.simantics.scenegraph.g2d.events.command.Commands;
\r
63 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
\r
64 import org.simantics.utils.ObjectUtils;
\r
65 import org.simantics.utils.datastructures.context.IContext;
\r
66 import org.simantics.utils.datastructures.context.IContextListener;
\r
67 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
68 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
69 import org.simantics.utils.threads.ThreadUtils;
\r
72 * Pointer tool does the following operations with mouse:
\r
74 * <li>Selections</li>
\r
77 * <li>Translate</li>
\r
78 * <li>Draws connections (requires re-implementing
\r
79 * {@link #createConnectTool(TerminalInfo, int, Point2D)})</li>
\r
82 * Pointer tool is active only when {@link Hints#KEY_TOOL} is
\r
83 * {@value Hints#POINTERTOOL}.
\r
85 * @author Toni Kalajainen
\r
87 public class PointerInteractor extends AbstractDiagramParticipant {
\r
90 * A hint key for terminal pick distance in control pixels.
\r
93 public static final Key KEY_PICK_DISTANCE = new KeyOf(Double.class, "PICK_DISTANCE");
\r
96 * Default terminal pick distance in control pixels.
\r
97 * @see #DEFAULT_PICK_DISTANCE
\r
99 public static final double PICK_DIST = 10;
\r
102 * @see #altToggled(KeyEvent)
\r
104 protected int lastStateMask;
\r
107 * @see #altToggled(KeyEvent)
\r
109 protected boolean temporarilyEnabledConnectTool = false;
\r
111 public class DefaultHoverStrategy extends ChainedHoverStrategy {
\r
112 public DefaultHoverStrategy(TerminalHoverStrategy orig) {
\r
116 public boolean highlightEnabled() {
\r
117 if (Hints.CONNECTTOOL.equals(getToolMode()))
\r
120 boolean ct = connectToolModifiersPressed(lastStateMask);
\r
121 //System.out.println("highlightEnabled: " + String.format("%x", lastStateMask) + " : " + ct);
\r
125 public boolean canHighlight(TerminalInfo ti) {
\r
126 //boolean alt = (lastStateMask & MouseEvent.ALT_MASK) != 0;
\r
127 //System.out.println("canHighlight: " + String.format("%x", lastStateMask) + " : " + alt);
\r
130 IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
\r
131 return advisor == null || advisor.canBeginConnection(null, ti.e, ti.t);
\r
135 @Dependency Selection selection;
\r
136 @Dependency KeyUtil keys;
\r
137 @Dependency TransformUtil util;
\r
138 @Dependency PickContext pickContext;
\r
139 @Dependency MouseUtil mice;
\r
140 @Reference TerminalPainter terminalPainter;
\r
143 * This must be higher than
\r
144 * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}
\r
145 * ({@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to allow for
\r
146 * better selection handling than is possible by using the plain scene graph
\r
147 * event handling facilities which are installed in {@link CanvasContext}.
\r
149 public static final int TOOL_PRIORITY = 1 << 21;
\r
152 * This must be lower than
\r
153 * {@link SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY} (
\r
154 * {@value SceneGraphConstants#SCENEGRAPH_EVENT_PRIORITY}) to not start box
\r
155 * handling before scene graph nodes have been given a chance to react
\r
158 public static final int BOX_SELECT_PRIORITY = 1 << 19;
\r
160 private static final Path2D LINE10;
\r
161 private static final Path2D LINE15;
\r
162 private static final Path2D LINE20;
\r
164 boolean clickSelect;
\r
166 boolean dragElement, dndDragElement;
\r
168 boolean doubleClickEdit;
\r
169 protected IElementClassProvider elementClassProvider;
\r
171 PickPolicy boxSelectMode = PickPolicy.PICK_CONTAINED_OBJECTS;
\r
173 DefaultHoverStrategy hoverStrategy;
\r
175 private PickSorter pickSorter;
\r
177 public PointerInteractor() {
\r
178 this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), null);
\r
181 public PointerInteractor(PickSorter pickSorter) {
\r
182 this(true, true, true, false, true, false, ElementClassProviders.staticProvider(null), pickSorter);
\r
185 public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, IElementClassProvider ecp) {
\r
186 this(clickSelect, boxSelect, dragElement, dndDragElement, connect, false, ecp, null);
\r
189 public PointerInteractor(boolean clickSelect, boolean boxSelect, boolean dragElement, boolean dndDragElement, boolean connect, boolean doubleClickEdit, IElementClassProvider ecp, PickSorter pickSorter) {
\r
191 this.clickSelect = clickSelect;
\r
192 this.boxSelect = boxSelect;
\r
193 this.dragElement = dragElement;
\r
194 this.dndDragElement = dndDragElement;
\r
195 this.connect = connect;
\r
196 this.doubleClickEdit = doubleClickEdit;
\r
197 this.elementClassProvider = ecp;
\r
198 this.pickSorter = pickSorter;
\r
202 public void addedToContext(ICanvasContext ctx) {
\r
203 super.addedToContext(ctx);
\r
204 hoverStrategy = new DefaultHoverStrategy((TerminalHoverStrategy) getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY));
\r
205 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, hoverStrategy);
\r
208 @EventHandler(priority = 0)
\r
209 public boolean handleStateMask(MouseEvent me) {
\r
210 lastStateMask = me.stateMask;
\r
211 if (temporarilyEnabledConnectTool) {
\r
212 if (!connectToolModifiersPressed(me.stateMask)) {
\r
213 temporarilyEnabledConnectTool = false;
\r
214 setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
\r
217 // It may be that the mouse has come into this control
\r
218 // from outside and no key state changes have occurred yet.
\r
219 // In this case this code should take care of moving the canvas
\r
220 // context into CONNECTTOOL mode.
\r
221 if (getToolMode() == Hints.POINTERTOOL
\r
222 && connectToolModifiersPressed(me.stateMask))
\r
224 temporarilyEnabledConnectTool = true;
\r
225 setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
\r
231 private static int mask(int mask, int mask2, boolean set) {
\r
232 return set ? mask | mask2 : mask & ~mask2;
\r
235 @EventHandler(priority = -1)
\r
236 public boolean altToggled(KeyEvent ke) {
\r
237 int mods = ke.stateMask;
\r
238 boolean press = ke instanceof KeyPressedEvent;
\r
239 boolean modifierPressed = false;
\r
240 if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
\r
241 mods = mask(mods, MouseEvent.ALT_MASK, press);
\r
242 modifierPressed = true;
\r
244 if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT_GRAPH) {
\r
245 mods = mask(mods, MouseEvent.ALT_GRAPH_MASK, press);
\r
246 modifierPressed = true;
\r
248 if (ke.keyCode == java.awt.event.KeyEvent.VK_SHIFT) {
\r
249 mods = mask(mods, MouseEvent.SHIFT_MASK, press);
\r
250 modifierPressed = true;
\r
252 if (ke.keyCode == java.awt.event.KeyEvent.VK_CONTROL) {
\r
253 mods = mask(mods, MouseEvent.CTRL_MASK, press);
\r
254 modifierPressed = true;
\r
256 if (ke.keyCode == java.awt.event.KeyEvent.VK_META) {
\r
257 // TODO: NO MASK FOR META!
\r
258 modifierPressed = true;
\r
260 // Don't deny connecting when CTRL is marked pressed because ALT_GRAPH
\r
261 // is actually ALT+CTRL in SWT. There's no way in SWT to tell apart
\r
262 // CTRL+ALT and ALT GRAPH.
\r
263 boolean otherModifiers = (mods & (MouseEvent.SHIFT_MASK)) != 0;
\r
264 boolean altModifier = (mods & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;
\r
265 if (modifierPressed) {
\r
266 boolean altPressed = !otherModifiers && altModifier;
\r
267 lastStateMask = mods;
\r
269 IToolMode mode = getToolMode();
\r
270 if (mode == Hints.POINTERTOOL) {
\r
271 //System.out.println("TEMP++");
\r
272 temporarilyEnabledConnectTool = true;
\r
273 setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
\r
276 if (temporarilyEnabledConnectTool) {
\r
277 //System.out.println("TEMP--");
\r
278 temporarilyEnabledConnectTool = false;
\r
279 setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
\r
282 // Make sure that TerminalPainter updates its scene graph.
\r
283 if (terminalPainter != null) {
\r
284 terminalPainter.update(terminalPainter.highlightEnabled());
\r
291 * @param controlPos
\r
292 * @return <code>null</code> if current canvas transform is not invertible
\r
294 public Shape getCanvasPickShape(Point2D controlPos) {
\r
295 AffineTransform inverse = util.getInverseTransform();
\r
296 if (inverse == null)
\r
299 double pd = getPickDistance();
\r
300 Rectangle2D controlPickRect = new Rectangle2D.Double(controlPos.getX()-pd, controlPos.getY()-pd, pd*2+1, pd*2+1);
\r
301 Shape canvasShape = GeometryUtils.transformShape(controlPickRect, inverse);
\r
302 return canvasShape;
\r
305 public List<TerminalInfo> pickTerminals(Point2D controlPos)
\r
307 Shape canvasPickRect = getCanvasPickShape(controlPos);
\r
308 if (canvasPickRect == null)
\r
309 return Collections.emptyList();
\r
310 return TerminalUtil.pickTerminals(diagram, canvasPickRect, true, true);
\r
313 public TerminalInfo pickTerminal(Point2D controlPos)
\r
315 Shape canvasPickRect = getCanvasPickShape(controlPos);
\r
316 if (canvasPickRect == null)
\r
318 TerminalInfo ti = TerminalUtil.pickTerminal(diagram, canvasPickRect);
\r
322 @EventHandler(priority = TOOL_PRIORITY)
\r
323 public boolean handlePress(MouseButtonPressedEvent me) {
\r
326 if (me.button != MouseEvent.LEFT_BUTTON)
\r
329 IToolMode mode = getToolMode();
\r
331 // It may be that the mouse has come into this control
\r
332 // from outside without focusing it and without any mouse
\r
333 // buttons being pressed. If the user is pressing the
\r
334 // connection modifier we need to temporarily enable connect
\r
335 // mode here and now.
\r
336 if (mode == Hints.POINTERTOOL && connectToolModifiersPressed(me.stateMask)) {
\r
337 temporarilyEnabledConnectTool = true;
\r
338 mode = Hints.CONNECTTOOL;
\r
339 setHint(Hints.KEY_TOOL, Hints.CONNECTTOOL);
\r
342 if (mode == Hints.CONNECTTOOL) {
\r
343 Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, null);
\r
344 return checkInitiateConnectTool(me, curCanvasPos);
\r
350 protected boolean checkInitiateConnectTool(MouseEvent me, Point2D mouseCanvasPos) {
\r
352 IToolMode mode = getToolMode();
\r
353 if (mode == Hints.CONNECTTOOL || connectToolModifiersPressed(me.stateMask)) {
\r
354 IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
\r
355 TerminalInfo ti = pickTerminal(me.controlPosition);
\r
357 ICanvasParticipant bsi = null;
\r
359 if (advisor == null || advisor.canBeginConnection(null, ti.e, ti.t)) {
\r
360 bsi = createConnectTool(ti, me.mouseId, mouseCanvasPos);
\r
363 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
\r
364 if (snapAdvisor != null)
\r
365 snapAdvisor.snap(mouseCanvasPos);
\r
367 // Start connection out of thin air, without a terminal.
\r
368 bsi = createConnectTool(null, me.mouseId, mouseCanvasPos);
\r
371 // Did we catch anything?
\r
373 startConnectTool(bsi);
\r
381 protected void startConnectTool(ICanvasParticipant tool) {
\r
382 getContext().add(tool);
\r
383 //System.out.println("TEMP: " + temporarilyEnabledConnectTool);
\r
384 if (temporarilyEnabledConnectTool) {
\r
385 // Resets pointer tool back into use if necessary after
\r
386 // connection has been finished or canceled.
\r
387 getContext().addContextListener(new ToolModeResetter(tool));
\r
391 @EventHandler(priority = TOOL_PRIORITY)
\r
392 public boolean handleClick(MouseClickEvent me) {
\r
393 //System.out.println(getClass().getSimpleName() + ": mouse clicked: @ " + me.time);
\r
395 if (hasDoubleClickEdit() && me.clickCount == 2) {
\r
396 if (handleDoubleClick(me))
\r
400 if (!hasClickSelect()) return false;
\r
401 if (!hasToolMode(Hints.POINTERTOOL)) return false;
\r
403 // Don't handle any events where click count is more than 1 to prevent
\r
404 // current diagram selection from bouncing around unnecessarily.
\r
405 if (me.clickCount > 1)
\r
408 boolean isLeft = me.button == MouseEvent.LEFT_BUTTON;
\r
409 boolean isRight = me.button == MouseEvent.RIGHT_BUTTON;
\r
410 if (!isLeft && !isRight) return false;
\r
412 boolean popupWasVisible = wasPopupJustClosed(me);
\r
413 boolean noModifiers = !anyModifierPressed(me);
\r
414 boolean isShiftPressed = me.hasAllModifiers(MouseEvent.SHIFT_MASK);
\r
416 assertDependencies();
\r
419 double pickDist = getPickDistance();
\r
420 Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);
\r
421 Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
\r
422 int selectionId = me.mouseId;
\r
424 PickRequest req = new PickRequest(canvasPickRect);
\r
425 req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
\r
426 req.pickSorter = pickSorter;
\r
427 //req.pickSorter = PickRequest.PickSorter.CONNECTIONS_LAST;
\r
428 List<IElement> pickables = new ArrayList<IElement>();
\r
429 pickContext.pick(diagram, req, pickables);
\r
431 Set<IElement> currentSelection = selection.getSelection(selectionId);
\r
434 if (pickables.isEmpty()) {
\r
435 if (!popupWasVisible) {
\r
436 // Only clear selection on left button clicks.
\r
438 selection.clear(selectionId);
\r
442 if (/*!currentSelection.isEmpty() &&*/ noModifiers)
\r
443 setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
\r
449 if ((me.stateMask & MouseEvent.CTRL_MASK) != 0) {
\r
452 * - If the mouse points to an object not in the selection, add it to selection.
\r
453 * - If the mouse points to multiple objects, add the first.
\r
454 * - If all objects the mouse points are already in selection, remove one of them from selection.
\r
456 IElement removable = null;
\r
457 for (int i = pickables.size() - 1; i >= 0; --i) {
\r
458 IElement pickable = pickables.get(i);
\r
459 if (selection.add(selectionId, pickable)) {
\r
463 removable = pickable;
\r
465 // Do not perform rotating pick in toggle selection
\r
466 // when only CTRL is pressed. Requires SHIFT+CTRL.
\r
467 if (!isShiftPressed)
\r
470 if (removable != null)
\r
471 selection.remove(selectionId, removable);
\r
478 if (isLeft && popupWasVisible)
\r
479 // Popup menu is visible, just let it close
\r
482 // Don't change selection on right clicks if there's more to pick
\r
483 // than a single element.
\r
484 if (isRight && pickables.size() > 1) {
\r
485 IElement selectElement = singleElementAboveNonselectedConnections(currentSelection, pickables);
\r
486 if (selectElement != null) {
\r
487 selection.setSelection(selectionId, selectElement);
\r
489 if (!currentSelection.isEmpty() && noModifiers)
\r
490 setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
\r
495 * Select the one object the mouse points to. If multiple object
\r
496 * are picked, select the one that is after the earliest by
\r
497 * index of the current selection when shift is pressed. Otherwise
\r
498 * always pick the topmost element.
\r
500 IElement selectedPick = isShiftPressed
\r
501 ? rotatingPick(currentSelection, pickables)
\r
502 : pickables.get(pickables.size() - 1);
\r
504 // Only select when
\r
505 // 1. the selection would actually change
\r
507 // 2.1. left button was pressed
\r
509 // 2.2. right button was pressed and the element to-be-selected
\r
510 // is NOT a part of the current selection
\r
511 if (!Collections.singleton(selectedPick).equals(currentSelection)
\r
512 && (isLeft || (isRight && !currentSelection.contains(selectedPick)))) {
\r
513 selection.setSelection(selectionId, selectedPick);
\r
516 if (isRight && pickables.size() == 1 && noModifiers) {
\r
517 setHint(DiagramHints.SHOW_POPUP_MENU, me.controlPosition);
\r
525 * A heuristic needed for implementing right-click diagram selection in a
\r
528 * @param currentSelection
\r
532 private IElement singleElementAboveNonselectedConnections(Set<IElement> currentSelection, List<IElement> pickables) {
\r
533 if (pickables.isEmpty())
\r
536 // Check that the pickable-list doesn't contain anything that is in the current selection.
\r
537 if (!Collections.disjoint(currentSelection, pickables))
\r
540 IElement top = pickables.get(pickables.size() - 1);
\r
541 boolean elementOnTop = !PickRequest.PickFilter.FILTER_CONNECTIONS.accept(top);
\r
544 for (int i = pickables.size() - 2; i >= 0; --i) {
\r
545 IElement e = pickables.get(i);
\r
546 if (!PickRequest.PickFilter.FILTER_CONNECTIONS.accept(e))
\r
553 * Since there's seems to be no better method available for finding out if
\r
554 * the SWT popup menu was just recently closed or not, we use the following
\r
557 * SWT popup was just closed if it was closed < 300ms ago.
\r
559 * Note that this is a very bad heuristic and may fail on slower machines or
\r
560 * under heavy system load.
\r
564 private boolean wasPopupJustClosed(Event event) {
\r
565 Long popupCloseTime = getHint(DiagramHints.POPUP_MENU_HIDDEN);
\r
566 if (popupCloseTime != null) {
\r
567 long timeDiff = event.time - popupCloseTime;
\r
568 //System.out.println("time diff: " + timeDiff);
\r
569 if (timeDiff < 300) {
\r
570 //System.out.println("POPUP WAS JUST CLOSED!");
\r
574 //System.out.println("Popup has been closed for a while.");
\r
578 boolean handleDoubleClick(MouseClickEvent me) {
\r
579 //System.out.println("mouse double clicked: " + me);
\r
580 if (!hasDoubleClickEdit()) return false;
\r
581 if (me.button != MouseEvent.LEFT_BUTTON) return false;
\r
582 if (getToolMode() != Hints.POINTERTOOL) return false;
\r
583 if (me.clickCount < 2) return false;
\r
586 double pickDist = getPickDistance();
\r
587 Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);
\r
588 Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
\r
589 int selectionId = me.mouseId;
\r
591 PickRequest req = new PickRequest(canvasPickRect);
\r
592 req.pickPolicy = PickPolicy.PICK_INTERSECTING_OBJECTS;
\r
593 List<IElement> pick = new ArrayList<IElement>();
\r
594 pickContext.pick(diagram, req, pick);
\r
597 if (pick.isEmpty()) {
\r
598 selection.clear(selectionId);
\r
602 IElement selectedPick = rotatingPick(selectionId, pick);
\r
604 if (!selection.contains(selectionId, selectedPick)) {
\r
605 selection.setSelection(selectionId, selectedPick);
\r
608 CanvasUtils.sendCommand(getContext(), Commands.RENAME);
\r
613 // Values shared by #handleDrag and #handleBoxSelect
\r
614 private transient Point2D curCanvasDragPos = new Point2D.Double();
\r
615 private transient Set<IElement> elementsToDrag = Collections.emptySet();
\r
618 * Invoked before scene graph event handling and {@link #handleBoxSelect(MouseDragBegin)}.
\r
621 * @see #handleBoxSelect(MouseDragBegin)
\r
623 @EventHandler(priority = TOOL_PRIORITY)
\r
624 public boolean handleDrag(MouseDragBegin me) {
\r
625 if (!hasElementDrag() && !hasBoxSelect()) return false;
\r
626 if (me.button != MouseEvent.LEFT_BUTTON) return false;
\r
627 if (getToolMode() != Hints.POINTERTOOL) return false;
\r
628 if (hasToolMode(me.mouseId)) return false;
\r
630 boolean anyModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);
\r
631 boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK
\r
632 ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));
\r
634 if (nonSelectionModifierPressed)
\r
637 assertDependencies();
\r
639 Point2D curCanvasPos = util.controlToCanvas(me.controlPosition, curCanvasDragPos);
\r
640 PickRequest req = new PickRequest(me.startCanvasPos);
\r
641 req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
\r
642 List<IElement> picks = new ArrayList<IElement>();
\r
643 pickContext.pick(diagram, req, picks);
\r
645 //System.out.println(picks);
\r
646 if (picks.isEmpty()) {
\r
647 // Widen the area of searching if nothing is found with point picking
\r
648 double pickDist = getPickDistance();
\r
649 Rectangle2D controlPickRect = new Rectangle2D.Double(me.controlPosition.getX()-pickDist, me.controlPosition.getY()-pickDist, pickDist*2+1, pickDist*2+1);
\r
650 Shape canvasPickRect = GeometryUtils.transformShape(controlPickRect, util.getInverseTransform());
\r
651 req = new PickRequest(canvasPickRect);
\r
652 req.pickPolicy = PickRequest.PickPolicy.PICK_INTERSECTING_OBJECTS;
\r
653 pickContext.pick(diagram, req, picks);
\r
654 //System.out.println("2nd try: " + picks);
\r
657 Set<IElement> sel = selection.getSelection(me.mouseId);
\r
658 IElement topMostPick = picks.isEmpty() ? null : picks.get(picks.size() - 1);
\r
659 Set<IElement> elementsToDrag = new HashSet<IElement>();
\r
660 this.elementsToDrag = elementsToDrag;
\r
662 if (!Collections.disjoint(sel, picks)) {
\r
663 elementsToDrag.addAll(sel);
\r
665 if (topMostPick != null && (sel.isEmpty() || !sel.contains(topMostPick))) {
\r
666 selection.setSelection(me.mouseId, topMostPick);
\r
667 sel = selection.getSelection(me.mouseId);
\r
668 elementsToDrag.addAll(sel);
\r
673 if (!elementsToDrag.isEmpty() && hasElementDnDDrag()) {
\r
674 // To Be Implemented in the next Diagram data model.
\r
676 if (!anyModifierPressed && !elementsToDrag.isEmpty() && hasElementDrag()) {
\r
677 // Connections are not translatable, re-routing is in RouteGraphNode.
\r
678 boolean onlyConnections = onlyConnections(elementsToDrag);
\r
679 if (!onlyConnections) {
\r
680 ICanvasParticipant tm = createTranslateTool(me.mouseId, me.startCanvasPos, curCanvasPos, elementsToDrag);
\r
682 getContext().add(tm);
\r
683 return !onlyConnections;
\r
693 * Always invoked after after {@link #handleDrag(MouseDragBegin)} and scene
\r
694 * graph event handling to prevent the box selection mode from being
\r
695 * initiated before scene graph nodes have a chance to react.
\r
698 * Note that this method assumes that <code>elementsToDrag</code> and
\r
699 * <code>curCanvasPos</code> are already set by
\r
700 * {@link #handleDrag(MouseDragBegin)}.
\r
705 @EventHandler(priority = BOX_SELECT_PRIORITY)
\r
706 public boolean handleBoxSelect(MouseDragBegin me) {
\r
707 if (!hasBoxSelect()) return false;
\r
708 if (me.button != MouseEvent.LEFT_BUTTON) return false;
\r
709 if (getToolMode() != Hints.POINTERTOOL) return false;
\r
710 if (hasToolMode(me.mouseId)) return false;
\r
712 boolean nonSelectionModifierPressed = me.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK
\r
713 ^ (MouseEvent.SHIFT_MASK /*| MouseEvent.CTRL_MASK*/));
\r
714 if (nonSelectionModifierPressed)
\r
717 if (!nonSelectionModifierPressed && elementsToDrag.isEmpty()) {
\r
719 ICanvasParticipant bsm = createBoxSelectTool(me.mouseId, me.startCanvasPos, curCanvasDragPos, me.button, boxSelectMode);
\r
721 getContext().add(bsm);
\r
727 private static boolean onlyConnections(Set<IElement> elements) {
\r
728 for (IElement e : elements)
\r
729 if (!e.getElementClass().containsClass(ConnectionHandler.class))
\r
734 private IElement rotatingPick(int selectionId, List<IElement> pickables) {
\r
735 Set<IElement> sel = selection.getSelection(selectionId);
\r
736 return rotatingPick(sel, pickables);
\r
739 private IElement rotatingPick(Set<IElement> sel, List<IElement> pickables) {
\r
740 int earliestIndex = pickables.size();
\r
741 for (int i = pickables.size() - 1; i >= 0; --i) {
\r
742 if (sel.contains(pickables.get(i))) {
\r
747 if (earliestIndex == 0)
\r
748 earliestIndex = pickables.size();
\r
749 IElement selectedPick = pickables.get(earliestIndex - 1);
\r
750 return selectedPick;
\r
754 * Is mouse in some kind of mode?
\r
758 boolean hasToolMode(int mouseId) {
\r
759 for (AbstractMode am : getContext().getItemsByClass(AbstractMode.class))
\r
760 if (am.mouseId==mouseId) return true;
\r
764 boolean hasToolMode(IToolMode mode) {
\r
765 return ObjectUtils.objectEquals(mode, getToolMode());
\r
768 boolean hasToolMode(IToolMode... modes) {
\r
769 IToolMode current = getToolMode();
\r
770 if (current == null)
\r
772 for (IToolMode mode : modes)
\r
773 if (current.equals(mode))
\r
778 protected IToolMode getToolMode() {
\r
779 return getHint(Hints.KEY_TOOL);
\r
782 /// is box select enabled
\r
783 protected boolean hasBoxSelect() {
\r
787 /// is click select enabled
\r
788 protected boolean hasClickSelect() {
\r
789 return clickSelect;
\r
792 /// is double click edit enabled
\r
793 protected boolean hasDoubleClickEdit() {
\r
794 return doubleClickEdit;
\r
797 // is element drag enabled
\r
798 protected boolean hasElementDrag() {
\r
799 return dragElement;
\r
802 // is element drag enabled
\r
803 protected boolean hasElementDnDDrag() {
\r
804 return dndDragElement;
\r
807 // is connect enabled
\r
808 protected boolean connects() {
\r
812 public double getPickDistance() {
\r
813 Double pickDistance = getHint(KEY_PICK_DISTANCE);
\r
814 return pickDistance == null ? PICK_DIST : Math.max(pickDistance, 0);
\r
817 public PickPolicy getBoxSelectMode() {
\r
818 return boxSelectMode;
\r
821 public void setBoxSelectMode(PickPolicy boxSelectMode) {
\r
822 this.boxSelectMode = boxSelectMode;
\r
825 public void setSelectionEnabled(boolean select) {
\r
826 this.clickSelect = select;
\r
827 this.boxSelect = select;
\r
828 if(select == false) { // Clear all selections if select is disabled
\r
829 final int[] ids = selection.getSelectionIds();
\r
830 if(ids.length > 0) {
\r
831 ThreadUtils.asyncExec(getContext().getThreadAccess(), new Runnable() {
\r
833 public void run() {
\r
835 selection.clear(id);
\r
836 getContext().getContentContext().setDirty();
\r
843 boolean anyModifierPressed(MouseEvent e) {
\r
844 return e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK);
\r
847 boolean connectToolModifiersPressed(int stateMask) {
\r
848 return (stateMask & (MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK)) != 0;
\r
852 LINE10 = new Path2D.Double();
\r
853 LINE10.moveTo(0, 0);
\r
854 LINE10.lineTo(10, 0);
\r
855 LINE10.lineTo(7, -3);
\r
856 LINE10.moveTo(10, 0);
\r
857 LINE10.lineTo(7, 3);
\r
859 LINE15 = new Path2D.Double();
\r
860 LINE15.moveTo(0, 0);
\r
861 LINE15.lineTo(15, 0);
\r
862 LINE15.lineTo(12, -3);
\r
863 LINE15.moveTo(15, 0);
\r
864 LINE15.lineTo(12, 3);
\r
866 LINE20 = new Path2D.Double();
\r
867 LINE20.moveTo(0, 0);
\r
868 LINE20.lineTo(20, 0);
\r
869 LINE20.lineTo(17, -3);
\r
870 LINE20.moveTo(20, 0);
\r
871 LINE20.lineTo(17, 3);
\r
877 protected ICanvasParticipant createConnectTool(TerminalInfo ti, int mouseId, Point2D startCanvasPos) {
\r
881 protected ICanvasParticipant createConnectToolWithTerminals(List<TerminalInfo> tis, int mouseId, Point2D startCanvasPos) {
\r
885 protected ICanvasParticipant createTranslateTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, Set<IElement> elementsToDrag) {
\r
886 return new TranslateMode(startCanvasPos, curCanvasPos, mouseId, elementsToDrag);
\r
889 protected ICanvasParticipant createBoxSelectTool(int mouseId, Point2D startCanvasPos, Point2D curCanvasPos, int button, PickPolicy boxSelectMode) {
\r
890 return new BoxSelectionMode(startCanvasPos, curCanvasPos, mouseId, button, boxSelectMode);
\r
894 * A context listener for resetting tool mode back to pointer mode after the
\r
895 * tracked participant has been removed.
\r
897 protected class ToolModeResetter implements IContextListener<ICanvasParticipant> {
\r
898 private ICanvasParticipant tracked;
\r
899 public ToolModeResetter(ICanvasParticipant trackedParticipant) {
\r
900 this.tracked = trackedParticipant;
\r
903 public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
\r
906 public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {
\r
907 if (item == tracked) {
\r
908 sender.removeContextListener(this);
\r
909 if (!isRemoved() && !connectToolModifiersPressed(lastStateMask)) {
\r
910 temporarilyEnabledConnectTool = false;
\r
911 setHint(Hints.KEY_TOOL, Hints.POINTERTOOL);
\r