X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;ds=sidebyside;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fparticipant%2FConnectTool2.java;h=7d3a5a4005078f314f96d49b931978eabb5ad4c5;hb=02000c4f6ecbfb41993d3b9424d0bb144dac7fa3;hp=4aaa717d843109cb5d92e97e925fc3dd6dfab020;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java
index 4aaa717d8..7d3a5a400 100644
--- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java
+++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectTool2.java
@@ -1,1177 +1,1189 @@
-/*******************************************************************************
- * 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.diagram.participant;
-
-import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Composite;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Path2D;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-
-import org.simantics.Simantics;
-import org.simantics.db.ReadGraph;
-import org.simantics.db.Resource;
-import org.simantics.db.WriteGraph;
-import org.simantics.db.common.request.UniqueRead;
-import org.simantics.db.common.request.WriteRequest;
-import org.simantics.db.common.utils.NameUtils;
-import org.simantics.db.exception.DatabaseException;
-import org.simantics.diagram.connection.RouteGraph;
-import org.simantics.diagram.connection.RouteGraphConnectionClass;
-import org.simantics.diagram.connection.RouteTerminal;
-import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
-import org.simantics.diagram.content.ResourceTerminal;
-import org.simantics.diagram.stubs.DiagramResource;
-import org.simantics.diagram.synchronization.ISynchronizationContext;
-import org.simantics.diagram.synchronization.SynchronizationHints;
-import org.simantics.g2d.canvas.ICanvasContext;
-import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
-import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
-import org.simantics.g2d.connection.IConnectionAdvisor;
-import org.simantics.g2d.diagram.DiagramHints;
-import org.simantics.g2d.diagram.DiagramUtils;
-import org.simantics.g2d.diagram.IDiagram;
-import org.simantics.g2d.diagram.handler.Topology.Terminal;
-import org.simantics.g2d.diagram.participant.ElementPainter;
-import org.simantics.g2d.diagram.participant.TerminalPainter;
-import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
-import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
-import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
-import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
-import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
-import org.simantics.g2d.element.ElementClass;
-import org.simantics.g2d.element.ElementClasses;
-import org.simantics.g2d.element.ElementUtils;
-import org.simantics.g2d.element.IElement;
-import org.simantics.g2d.element.IElementClassProvider;
-import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
-import org.simantics.g2d.element.handler.SceneGraph;
-import org.simantics.g2d.element.handler.TerminalTopology;
-import org.simantics.g2d.element.impl.Element;
-import org.simantics.g2d.elementclass.BranchPoint;
-import org.simantics.g2d.elementclass.BranchPoint.Direction;
-import org.simantics.g2d.elementclass.FlagClass;
-import org.simantics.g2d.elementclass.FlagHandler;
-import org.simantics.g2d.participant.RenderingQualityInteractor;
-import org.simantics.g2d.participant.TransformUtil;
-import org.simantics.modeling.ModelingResources;
-import org.simantics.scenegraph.INode;
-import org.simantics.scenegraph.g2d.G2DParentNode;
-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.MouseButtonEvent;
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
-import org.simantics.scenegraph.g2d.events.command.CommandEvent;
-import org.simantics.scenegraph.g2d.events.command.Commands;
-import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
-import org.simantics.scenegraph.g2d.nodes.ShapeNode;
-import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
-import org.simantics.scenegraph.utils.GeometryUtils;
-import org.simantics.scenegraph.utils.Quality;
-import org.simantics.structural2.modelingRules.ConnectionJudgement;
-import org.simantics.utils.datastructures.Callback;
-import org.simantics.utils.datastructures.Pair;
-import org.simantics.utils.logging.TimeLogger;
-import org.simantics.utils.ui.ErrorLogger;
-import org.simantics.utils.ui.ExceptionUtils;
-
-/**
- * A basic tool for making connection on diagrams.
- *
- * This version defines the starting, ending and route points of a connection.
- * The routing itself is left up to the diagram router employed by
- * {@link DiagramUtils#validateAndFix(IDiagram, ICanvasContext)}.
- *
- * Manual:
- *
- * This tool is added to the diagram when a connection sequence is initiated by
- * another participant. PointerInteractor is one such participant which adds the
- * tool when a terminal or non-terminal-occupied canvas space is ALT+clicked
- * (see {@link PointerInteractor#checkInitiateConnectTool(MouseEvent, Point2D)}
- * ). The connection will be finished when another allowed terminal is clicked
- * upon or empty canvas space is ALT+clicked. Route points for the connection
- * can be created by clicking around on non-terminal-occupied canvas space while
- * connecting.
- *
- *
- * Connections can be started from and ended in flags by pressing ALT while
- * left-clicking.
- *
- * @author Tuukka Lehtonen
- */
-public class ConnectTool2 extends AbstractMode {
-
- public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
-
- @Reference
- protected RenderingQualityInteractor quality;
-
- @Dependency
- protected TransformUtil util;
-
- @Dependency
- protected ElementPainter diagramPainter;
-
- @Dependency
- protected PointerInteractor pi;
-
- /**
- * Start element terminal of the connection. null if connection
- * was started from a flag or a branch point.
- *
- * The value is received by the constructor.
- */
- protected List startTerminals;
-
- /**
- * Refers to any of the possible overlapping start terminals. The value is
- * taken from the first index of {@link #startTerminals} assuming that the
- * first one is the nearest. It is null if
- * {@link #startTerminals} is empty.
- */
- protected TerminalInfo startTerminal;
-
- protected TerminalInfo startFlag;
-
- /**
- * Starting position of the connection, received as an external argument.
- */
- protected final Point2D startPos;
-
- /**
- * true if this tool should create connection continuation
- * flags, false otherwise.
- */
- protected boolean createFlags;
-
- /**
- *
- */
- protected IElementClassProvider elementClassProvider;
-
- /**
- *
- */
- protected Deque controlPoints = new ArrayDeque();
-
- /**
- * Contains null when a connection is started from a new flag
- * or one of the terminals in {@link #startTerminals} when a connection is
- * being created starting from a terminal or possibly a set of terminals.
- *
- *
- * Note that this is different from {@link #startTerminal} which simply
- * represents the first element of {@link #startTerminals}.
- *
- *
- * Only when this value and {@link #endTerminal} is properly set will a
- * connection be created between two element terminals.
- */
- protected TerminalInfo selectedStartTerminal;
-
- /**
- * Element terminal of connection end element. null if
- * connection cannot be ended where it is currently being attempted to end.
- */
- protected TerminalInfo endTerminal;
-
- /**
- * The latest connectability judgment from the active
- * {@link IConnectionAdvisor} should the connection happen between
- * {@link #selectedStartTerminal} and {@link #endTerminal}.
- */
- protected ConnectionJudgement connectionJudgment;
-
- /**
- * If non-null during connection drawing this field tells the direction
- * forced for the current branch point by the user through the UI commands
- * {@link Commands#ROTATE_ELEMENT_CCW} and
- * {@link Commands#ROTATE_ELEMENT_CW}.
- */
- private Direction forcedBranchPointDirection;
-
- /**
- * A temporary variable for use with
- * {@link TerminalTopology#getTerminals(IElement, Collection)}.
- */
- protected Collection terminals = new ArrayList();
-
- /**
- * Previous mouse canvas position recorded by
- * {@link #processMouseMove(MouseMovedEvent)}.
- */
- protected Point2D lastMouseCanvasPos = new Point2D.Double();
-
- /**
- * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been
- * invoked at least once. This is used to tell whether to allow creation of
- * branch points or finising the connection in thin air. It will not be
- * allowed if the mouse has not moved at all since starting the connection.
- */
- protected boolean mouseHasMoved = false;
-
- protected TerminalHoverStrategy originalStrategy = null;
-
- protected TerminalHoverStrategy terminalHoverStrategy = new TerminalHoverStrategy() {
- @Override
- public boolean highlightEnabled() {
- return !isEndingInFlag();
- }
-
- @Override
- public boolean highlight(TerminalInfo ti) {
- boolean reflexive = isStartTerminal(ti.e, ti.t);
- if (reflexive && !allowReflexiveConnections())
- return false;
-
- return canConnect(ti.e, ti.t) != null;
- }
- };
-
- protected final static Composite ALPHA_COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);
-
- /**
- * Root scene graph node for all visualization performed by this tool.
- */
- protected G2DParentNode ghostNode;
-
- /**
- * Indicates whether the connection is about to be ended into a new
- * flag/branchpoint or not.
- */
- protected TerminalInfo endFlag;
-
- protected G2DParentNode endFlagNode;
-
- /**
- * @param startTerminal
- * @param mouseId
- * @param startCanvasPos
- */
- public ConnectTool2(TerminalInfo startTerminal, int mouseId, Point2D startCanvasPos) {
- this(startTerminal == null ? Collections. emptyList()
- : Collections.singletonList(startTerminal),
- mouseId,
- startCanvasPos);
- }
-
- /**
- * @param startTerminals
- * @param mouseId
- * @param startCanvasPos
- */
- public ConnectTool2(List startTerminals, int mouseId, Point2D startCanvasPos) {
- super(mouseId);
-
- if (startCanvasPos == null)
- throw new NullPointerException("null start position");
- if (startTerminals == null)
- throw new NullPointerException("null start terminals");
-
- this.startPos = startCanvasPos;
- this.lastMouseCanvasPos.setLocation(startPos);
-
- this.startTerminals = startTerminals;
- this.startTerminal = startTerminals.isEmpty() ? null : startTerminals.get(0);
- }
-
- @Override
- public void addedToContext(ICanvasContext ctx) {
- super.addedToContext(ctx);
-
- if (quality != null)
- quality.setStaticQuality(Quality.LOW);
-
- // Force terminals to always be highlighted without pressing certain
- // keys or key combinations.
- originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
- setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
- }
-
- @Override
- protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
- if (newDiagram != null) {
- // Get IElementClassProvider
- ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);
- if (ctx != null) {
- this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);
- }
-
- // See if flags should be created or not.
- this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));
- startConnection();
- }
- }
-
- @Override
- public void removedFromContext(ICanvasContext ctx) {
- if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {
- if (originalStrategy != null)
- setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);
- else
- removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
- }
-
- if (quality != null)
- quality.setStaticQuality(null);
-
- super.removedFromContext(ctx);
- }
-
- protected void startConnection() {
- Point2D startPos = (Point2D) this.startPos.clone();
- ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
- if (snapAdvisor != null)
- snapAdvisor.snap(startPos);
-
- // Resolve the first element and terminal of the connection.
- ControlPoint start = new ControlPoint(startPos);
-
- if (startTerminal != null) {
- assert ElementUtils.peekDiagram(startTerminal.e) == diagram;
- Point2D terminalPos = new Point2D.Double(startTerminal.posDia.getTranslateX(),
- startTerminal.posDia.getTranslateY());
- start.setPosition(terminalPos).setAttachedToTerminal(startTerminal);
- } else {
- // Create TerminalInfo describing the flag to be created.
- if (createFlags) {
- // This prevents connection creation from creating a branch
- // point in place of this flag.
- startFlag = createFlag(EdgeEnd.Begin);
- start.setAttachedToTerminal(startFlag);
- showElement(ghostNode, "startFlag", startFlag.e, startPos);
- }
- }
- controlPoints.add(start);
- controlPoints.add(new ControlPoint(startPos));
-
- // Make sure that we are ending with a flag if ALT is pressed.
- // This makes the tool always start with a flag which can be quite
- // cumbersome and is therefore disabled. The current version will not
- // end the connection if the mouse has not moved at all.
- //if (keyUtil.isKeyPressed(java.awt.event.KeyEvent.VK_ALT)) {
- // endWithoutTerminal(lastMouseCanvasPos, true);
- //}
- }
-
- @SGInit
- public void initSG(G2DParentNode parent) {
- ghostNode = parent.addNode(G2DParentNode.class);
- ghostNode.setZIndex(PAINT_PRIORITY);
-
- ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
- pathNode.setColor(new Color(160, 0, 0));
- pathNode.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,
- new float[] { 5f, 2f }, 0));
- pathNode.setScaleStroke(true);
- pathNode.setZIndex(0);
-
- G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);
- points.setZIndex(1);
-
- updateSG();
- }
-
- static class Segment {
- public final ControlPoint begin;
- public final ControlPoint end;
- public Path2D path;
-
- public Segment(ControlPoint begin, ControlPoint end) {
- this.begin = begin;
- this.end = end;
- }
-
- @Override
- public String toString() {
- return "Segment[begin=" + begin + ", end=" + end + ", path=" + path + "]";
- }
- }
-
- private RouteTerminal addControlPoint(RouteGraph routeGraph, ControlPoint cp) {
- TerminalInfo ti = cp.getAttachedTerminal();
- if(ti != null && ti != startFlag && ti != endFlag) {
- Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
- GeometryUtils.expandRectangle(bounds, 2);
- int allowedDirections = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
- ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
- return routeGraph.addTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(),
- bounds, allowedDirections, PlainLineEndStyle.INSTANCE);
- }
- else {
- double x = cp.getPosition().getX();
- double y = cp.getPosition().getY();
- int allowedDirections = 0xf;
- switch(cp.getDirection()) {
- case Horizontal: allowedDirections = 5; break;
- case Vertical: allowedDirections = 10; break;
- case Any: allowedDirections = 15; break;
- }
- return routeGraph.addTerminal(x, y, x, y, x, y, allowedDirections);
- }
- }
-
- protected void updateSG() {
- if (controlPoints.size() != 2)
- return;
-
- ControlPoint begin = controlPoints.getFirst();
- ControlPoint end = controlPoints.getLast();
-
- RouteGraph routeGraph = new RouteGraph();
- RouteTerminal a = addControlPoint(routeGraph, begin);
- RouteTerminal b = addControlPoint(routeGraph, end);
- routeGraph.link(a, b);
-
- // Route connection segments separately
- /*Router2 router = ElementUtils.getHintOrDefault(diagram, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);
- final List segments = toSegments(controlPoints);
- //System.out.println("controlpoints: " + controlPoints);
- //System.out.println("segments: " + segments);
- router.route(new IConnection() {
- @Override
- public Collection extends Object> getSegments() {
- return segments;
- }
-
- @Override
- public Connector getBegin(Object seg) {
- return getConnector(((Segment) seg).begin);
- }
-
- @Override
- public Connector getEnd(Object seg) {
- return getConnector(((Segment) seg).end);
- }
-
- private Connector getConnector(ControlPoint cp) {
- Connector c = new Connector();
- c.x = cp.getPosition().getX();
- c.y = cp.getPosition().getY();
-
- TerminalInfo ti = cp.getAttachedTerminal();
- if (ti != null && (ti == startFlag || ti != endFlag)) {
- //System.out.println("CP1: " + cp);
- c.parentObstacle = DiagramUtils.getObstacleShape(ti.e);
- ConnectionDirectionUtil.determineAllowedDirections(c);
- } else {
- //System.out.println("CP2: " + cp);
- c.parentObstacle = GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),
- BranchPointClass.DEFAULT_IMAGE2.getBounds());
- c.allowedDirections = toAllowedDirections(cp.getDirection());
- }
-
- return c;
- }
-
- @Override
- public void setPath(Object seg, Path2D path) {
- ((Segment) seg).path = (Path2D) path.clone();
- }
-
- private int toAllowedDirections(BranchPoint.Direction direction) {
- switch (direction) {
- case Any:
- return 0xf;
- case Horizontal:
- return Constants.EAST_FLAG | Constants.WEST_FLAG;
- case Vertical:
- return Constants.NORTH_FLAG | Constants.SOUTH_FLAG;
- default:
- throw new IllegalArgumentException("unrecognized direction: " + direction);
- }
- }
- });
-
- // Combine the routed paths
- Path2D path = new Path2D.Double();
- for (Segment seg : segments) {
- //System.out.println("SEG: " + seg);
- if (seg.path != null)
- path.append(seg.path.getPathIterator(null), true);
- }*/
-
- Path2D path = routeGraph.getPath2D();
-
- // Create scene graph to visualize the connection.
- ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
- pathNode.setShape(path);
-
- G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);
- HashSet unusedChildren = new HashSet(points.getNodes());
- int i = 0;
- for (ControlPoint cp : controlPoints) {
- if (cp.isAttachedToTerminal())
- continue;
-
- String id = String.valueOf(i);
- BranchPointNode bpn = points.getOrCreateNode(id, BranchPointNode.class);
- bpn.setDegree(2);
- bpn.setDirection((byte) cp.getDirection().ordinal());
- bpn.setTransform(AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY()));
-
- ++i;
- unusedChildren.remove(bpn);
- }
- for (INode unusedChild : unusedChildren)
- points.removeNode(unusedChild);
-
- setDirty();
- }
-
- private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {
- return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));
- }
-
- private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {
- G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);
- elementParent.setTransform(tr);
- elementParent.removeNodes();
- for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))
- sg.init(element, elementParent);
- return elementParent;
- }
-
- private List toSegments(Deque points) {
- if (points.isEmpty())
- return Collections.emptyList();
-
- List segments = new ArrayList();
-
- Iterator it = points.iterator();
- ControlPoint prev = it.next();
- while (it.hasNext()) {
- ControlPoint next = it.next();
- segments.add(new Segment(prev, next));
- prev = next;
- }
-
- return segments;
- }
-
- @SGCleanup
- public void cleanupSG() {
- ghostNode.remove();
- ghostNode = null;
- }
-
- @EventHandler(priority = 200)
- public boolean handleCommandEvents(CommandEvent ce) {
- if (ce.command.equals(Commands.CANCEL)) {
- setDirty();
- remove();
- return true;
- } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {
- return rotateLastBranchPoint(ce.command.equals(Commands.ROTATE_ELEMENT_CW));
- }
- return false;
- }
-
- @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
- public boolean handleKeyEvents(KeyEvent ke) {
- if (ke instanceof KeyPressedEvent) {
- // Back-space, cancel prev bend
- if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)
- return cancelPreviousBend();
- }
-
- if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
- if (createFlags) {
- endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));
- return true;
- }
- }
-
- return false;
- }
-
- @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
- public boolean handleEvent(MouseEvent me) {
- // Only handle events for the connection-initiating mouse
- if (me.mouseId != mouseId)
- return false;
-
- if (me instanceof MouseMovedEvent)
- return processMouseMove((MouseMovedEvent) me);
-
- if (me instanceof MouseButtonPressedEvent)
- return processMouseButtonPress((MouseButtonPressedEvent) me);
-
- return false;
- }
-
- protected boolean processMouseMove(MouseMovedEvent me) {
- mouseHasMoved = true;
-
- Point2D mouseControlPos = me.controlPosition;
- Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());
-
- ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
- if (snapAdvisor != null)
- snapAdvisor.snap(mouseCanvasPos);
-
- // Record last snapped canvas position of mouse.
- this.lastMouseCanvasPos.setLocation(mouseCanvasPos);
-
- if (isEndingInFlag()) {
- endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));
- }
-
- List tis = pi.pickTerminals(me.controlPosition);
- tis = TerminalUtil.findNearestOverlappingTerminals(tis);
- if (!tis.isEmpty() && !containsStartTerminal(tis)) {
- //System.out.println("end terminals (" + tis.size() + "):\n" + EString.implode(tis));
- for (TerminalInfo ti : tis) {
- Pair canConnect = canConnect(ti.e, ti.t);
- if (canConnect != null) {
- connectionJudgment = canConnect.first;
-
- if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
- if (canConnect.second != null) {
- controlPoints.getFirst()
- .setPosition(canConnect.second.posDia)
- .setAttachedToTerminal(canConnect.second);
- }
- controlPoints.getLast()
- .setPosition(ti.posDia)
- .setAttachedToTerminal(ti);
-
- selectedStartTerminal = canConnect.second;
- endTerminal = ti;
- }
-
- // Make sure that we are ending with a flag if ALT is pressed
- // and no end terminal is defined.
- endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
-
- updateSG();
- return false;
- }
- }
- }
-
- connectionJudgment = null;
- if (isEndTerminalDefined()) {
- // CASE: Mouse was previously on top of a valid terminal to end
- // the connection. Now the mouse has been moved where there is
- // no longer a terminal to connect to.
- //
- // => Disconnect the last edge segment from the previous
- // terminal, mark endElement/endTerminal non-existent
- // and connect the disconnected edge to a new branch point.
-
- controlPoints.getLast()
- .setPosition(mouseCanvasPos)
- .setDirection(calculateCurrentBranchPointDirection())
- .setAttachedToTerminal(null);
-
- endTerminal = null;
- } else {
- // CASE: Mouse was not previously on top of a valid ending
- // element terminal.
- //
- // => Move and re-orient last branch point.
-
- controlPoints.getLast()
- .setPosition(mouseCanvasPos)
- .setDirection(calculateCurrentBranchPointDirection());
- }
-
- // Make sure that we are ending with a flag if ALT is pressed and no end
- // terminal is defined.
- endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
-
- updateSG();
-
- return false;
- }
-
- protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {
- MouseButtonEvent me = e;
-
- // Do nothing before the mouse has moved at least a little.
- // This prevents the user from ending the connection right where
- // it started.
- if (!mouseHasMoved)
- return true;
-
- if (me.button == MouseEvent.LEFT_BUTTON) {
- Point2D mouseControlPos = me.controlPosition;
- Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());
-
- ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
- if (snapAdvisor != null)
- snapAdvisor.snap(mouseCanvasPos);
-
- // Clicked on an allowed end terminal. End connection & end mode.
- if (isEndTerminalDefined()) {
- createConnection();
- remove();
- return true;
- } else {
- // Finish connection in thin air only if the
- // connection was started from a valid terminal.
- if (me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) && !startTerminals.isEmpty()) {
- Pair pair = canConnect(null, null);
- if (pair != null) {
- connectionJudgment = (ConnectionJudgement) pair.first;
- selectedStartTerminal = pair.second;
-// endFlag = createFlag(EdgeEnd.End);
-// controlPoints.getLast().setAttachedToTerminal(endFlag);
- createConnection();
- setDirty();
- remove();
- } else {
- // Inform the user why connection couldn't be created.
- String tmsg = terminalsToString(startTerminals);
- ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection when starting from one of the following terminals:\n" + tmsg, null);
- }
- return true;
- } else if (routePointsAllowed()
- && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
- // Add new connection control point.
- controlPoints.add(newControlPointWithCalculatedDirection(mouseCanvasPos));
- resetForcedBranchPointDirection();
- updateSG();
- }
- }
-
- // Eat the event to prevent other participants from doing
- // incompatible things while in this connection mode.
- return true;
- } else if (me.button == MouseEvent.RIGHT_BUTTON) {
- return cancelPreviousBend();
- }
-
- return false;
- }
-
- protected boolean cancelPreviousBend() {
- if (!routePointsAllowed())
- return false;
-
- // Just to make this code more comprehensible, prevent an editing
- // case that requires ugly code to work.
- if (isEndingInFlag())
- return true;
-
- // If there are no real route points, cancel whole connection.
- if (controlPoints.size() <= 2) {
- setDirty();
- remove();
- return true;
- }
-
- // Cancel last bend
- controlPoints.removeLast();
- controlPoints.getLast().setPosition(lastMouseCanvasPos);
- resetForcedBranchPointDirection();
-
- updateSG();
- return true;
- }
-
- /**
- * Rotates the last branch point in the created connection in either
- * clockwise or counter-clockwise direction as a response to a user
- * interaction.
- *
- *
- * At the same time it use {@link #forcedBranchPointDirection} to mark the
- * current last branch point to be forcefully oriented according to the
- * users wishes instead of calculating a default value for the orientation
- * from the routed connection path. See
- * {@link #calculateCurrentBranchPointDirection()} for more information on
- * this.
- *
- *
- * The logic of this method goes as follows:
- *
- *
Calculate the current branch point direction
- *
If the branch point direction is currently user selected (
- * {@link #forcedBranchPointDirection}
- *
- *
- *
- *
- * @param clockwise
- * @return true if the rotation was successful
- */
- protected boolean rotateLastBranchPoint(boolean clockwise) {
- Direction oldDir = calculateCurrentBranchPointDirection();
-
- if (forcedBranchPointDirection == null) {
- forcedBranchPointDirection = oldDir.toggleDetermined();
- } else {
- forcedBranchPointDirection = clockwise ? oldDir.cycleNext() : oldDir.cyclePrevious();
- }
-
- controlPoints.getLast().setDirection(forcedBranchPointDirection);
-
- updateSG();
-
- return true;
- }
-
- /**
- * Set preferred direction for a branch/route point element.
- *
- * @param branchPoint the element to set the direction for
- * @param direction the direction to set
- * @return
- */
- protected void setDirection(IElement branchPoint, Direction direction) {
- branchPoint.getElementClass().getSingleItem(BranchPoint.class).setDirectionPreference(branchPoint, direction);
- }
-
- protected Direction forcedBranchPointDirection() {
- return forcedBranchPointDirection;
- }
-
- protected void resetForcedBranchPointDirection() {
- forcedBranchPointDirection = null;
- }
-
- protected void forceBranchPointDirection(Direction direction) {
- forcedBranchPointDirection = direction;
- }
-
- /**
- * @return
- */
- protected Direction calculateCurrentBranchPointDirection() {
- // If this is not the first branch point, toggle direction compared to
- // last.
- if (forcedBranchPointDirection != null)
- return forcedBranchPointDirection;
-
- if (controlPoints.size() > 2) {
- // This is not the first edge segment, toggle route point
- // directions.
- Iterator it = controlPoints.descendingIterator();
- it.next();
- ControlPoint secondLastCp = it.next();
-
- Direction dir = secondLastCp.getDirection();
- switch (dir) {
- case Horizontal:
- return Direction.Vertical;
- case Vertical:
- return Direction.Horizontal;
- case Any:
- }
- }
-
- // If this is the first branch point, calculate based on edge segment
- // angle.
- if (controlPoints.size() > 1) {
- Iterator it = controlPoints.descendingIterator();
- ControlPoint last = it.next();
- ControlPoint secondLast = it.next();
-
- double angle = Math.atan2(Math.abs(last.getPosition().getY() - secondLast.getPosition().getY()),
- Math.abs(last.getPosition().getX() - secondLast.getPosition().getX()));
-
- if (angle >= 0 && angle < Math.PI / 4) {
- return Direction.Horizontal;
- } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {
- return Direction.Vertical;
- }
- }
-
- return Direction.Any;
- }
-
- protected boolean isEndingInFlag() {
- return endFlag != null;
- }
-
- protected void endWithoutTerminal(Point2D mousePos, boolean altDown) {
- // Just go with branch points if flags are not allowed.
- if (!createFlags)
- return;
-
- boolean endTerminalDefined = isEndTerminalDefined();
-
- if (altDown) {
- if (!isEndingInFlag()) {
- endFlag = createFlag(EdgeEnd.End);
- endFlagNode = showElement(ghostNode, "endFlag", endFlag.e, mousePos);
- controlPoints.getLast()
- .setDirection(calculateCurrentBranchPointDirection())
- .setAttachedToTerminal(endFlag);
-
- // TerminalPainter must refresh
- setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
-
- updateSG();
- }
- } else {
- if (isEndingInFlag()) {
- // Currently ending with flag but ALT is no longer down
- // so that flag must be removed.
- endFlag = null;
- endFlagNode.remove();
- endFlagNode = null;
-
- ControlPoint cp = controlPoints.getLast();
- cp.setDirection(calculateCurrentBranchPointDirection())
- .setAttachedToTerminal(endTerminal);
-
- if (endTerminalDefined) {
- cp.setPosition(endTerminal.posDia);
- } else {
- cp.setPosition(mousePos);
- }
-
- // Force TerminalPainter refresh
- setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
-
- updateSG();
- }
- }
- }
-
- protected void createConnection() {
- createConnection(
- this.selectedStartTerminal,
- this.endTerminal,
- this.connectionJudgment,
- this.controlPoints);
- }
-
- protected void createConnection(
- final TerminalInfo startTerminal,
- final TerminalInfo endTerminal,
- final ConnectionJudgement judgement,
- final Deque controlPoints)
- {
- TimeLogger.resetTimeAndLog(getClass(), "createConnection");
- if (judgement == null) {
- // Inform the user why connection couldn't be created.
- String tmsg = terminalsToString(Arrays.asList(startTerminal, endTerminal));
- ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity when connecting the terminals:\n" + tmsg, null);
- return;
- }
-
- final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
-
- Simantics.getSession().asyncRequest(new WriteRequest() {
- @Override
- public void perform(WriteGraph graph) throws DatabaseException {
- builder.create(graph, judgement, controlPoints, startTerminal, endTerminal);
- }
- }, new Callback() {
- @Override
- public void run(DatabaseException parameter) {
- if (parameter != null)
- ExceptionUtils.logAndShowError(parameter);
- }
- });
- }
-
- /**
- * @param canvasPos
- * @return
- */
- protected ControlPoint newControlPointWithCalculatedDirection(Point2D canvasPos) {
- return new ControlPoint(canvasPos, calculateCurrentBranchPointDirection());
- }
-
- /**
- * @param e
- * @param t
- * @return true if the specified element terminal matches any
- * TerminalInfo in {@link #startTerminals}
- */
- protected boolean isStartTerminal(IElement e, Terminal t) {
- if (startTerminal == null)
- return false;
- for (TerminalInfo st : startTerminals) {
- if (st.e == e && st.t == t) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @param e
- * @param t
- * @return true if the specified element terminal matches any
- * TerminalInfo in {@link #startTerminals}
- */
- protected boolean containsStartTerminal(List tis) {
- if (startTerminal == null)
- return false;
- for (TerminalInfo st : startTerminals) {
- for (TerminalInfo et : tis) {
- if (st.e == et.e && st.t == et.t) {
- return true;
- }
- }
- }
- return false;
- }
-
- protected static FlagClass.Type endToFlagType(EdgeEnd end) {
- switch (end) {
- case Begin:
- return FlagClass.Type.In;
- case End:
- return FlagClass.Type.Out;
- default:
- throw new IllegalArgumentException("unrecognized edge end: " + end);
- }
- }
-
- protected TerminalInfo createFlag(EdgeEnd connectionEnd) {
- ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);
- IElement e = Element.spawnNew(flagClass);
-
- e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));
- e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);
-
- TerminalInfo ti = new TerminalInfo();
- ti.e = e;
- ti.t = ElementUtils.getSingleTerminal(e);
- ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);
- ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);
-
- return ti;
- }
-
- protected boolean shouldEndWithFlag(MouseEvent me) {
- return shouldEndWithFlag( me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) );
- }
-
- protected boolean shouldEndWithFlag(boolean altPressed) {
- return altPressed && !isEndTerminalDefined() && createFlags && startFlag == null;
- }
-
- protected boolean isEndTerminalDefined() {
- return endTerminal != null;
- }
-
- protected boolean isFlagTerminal(TerminalInfo ti) {
- return ti.e.getElementClass().containsClass(FlagHandler.class);
- }
-
- protected boolean allowReflexiveConnections() {
- return false;
- }
-
- protected boolean routePointsAllowed() {
- return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
- }
-
- /**
- * @param endElement
- * @param endTerminal
- * @return
- */
- @SuppressWarnings("unchecked")
- protected final Pair canConnect(IElement endElement, Terminal endTerminal) {
- IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
- Object judgement = canConnect(advisor, endElement, endTerminal);
- if (judgement == null)
- return null;
- if (judgement instanceof Pair, ?>)
- return (Pair) judgement;
- return Pair.make((ConnectionJudgement) judgement, startTerminal);
- }
-
- protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {
- if (advisor == null)
- return Pair.make(ConnectionJudgement.CANBEMADELEGAL, startTerminal);
- if (startTerminals.isEmpty()) {
- ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, null, null, endElement, endTerminal);
- return obj != null ? Pair.make(obj, null) : null;
- }
- for (TerminalInfo st : startTerminals) {
- ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, st.e, st.t, endElement, endTerminal);
- if (obj != null) {
- return Pair.make(obj, st);
- }
- }
- return null;
- }
-
- /**
- * For generating debugging information of what was attempted by the user
- * when a connection couldn't be created.
- *
- * @param ts
- * @return
- */
- private String terminalsToString(final Iterable ts) {
- try {
- return Simantics.sync(new UniqueRead() {
- @Override
- public String perform(ReadGraph graph) throws DatabaseException {
- DiagramResource DIA = DiagramResource.getInstance(graph);
- ModelingResources MOD = ModelingResources.getInstance(graph);
- StringBuilder sb = new StringBuilder();
- boolean first = true;
- for (TerminalInfo ti : ts) {
- if (!first)
- sb.append("\n");
- first = false;
- sb.append("element ");
- Object o = ElementUtils.getObject(ti.e);
- if (o instanceof Resource) {
- Resource er = (Resource) o;
- Resource cer = graph.getPossibleObject(er, MOD.ElementToComponent);
- Resource r = cer != null ? cer : er;
- sb.append(NameUtils.getSafeName(graph, r)).append(" : ");
- for (Resource type : graph.getPrincipalTypes(r)) {
- sb.append(NameUtils.getSafeName(graph, type, true));
- }
- } else {
- sb.append(ti.e.toString());
- }
- sb.append(", terminal ");
- if (ti.t instanceof ResourceTerminal) {
- Resource tr = ((ResourceTerminal) ti.t).getResource();
- Resource cp = graph.getPossibleObject(tr, DIA.HasConnectionPoint);
- Resource r = cp != null ? cp : tr;
- sb.append(NameUtils.getSafeName(graph, r, true));
- } else {
- sb.append(ti.t.toString());
- }
- }
- return sb.toString();
- }
- });
- } catch (DatabaseException e) {
- return e.getMessage();
- }
- }
-
+/*******************************************************************************
+ * 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.diagram.participant;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.UniqueRead;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.diagram.connection.RouteGraph;
+import org.simantics.diagram.connection.RouteGraphConnectionClass;
+import org.simantics.diagram.connection.RouteLine;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.delta.RouteGraphDelta;
+import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
+import org.simantics.diagram.content.ResourceTerminal;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.synchronization.ISynchronizationContext;
+import org.simantics.diagram.synchronization.SynchronizationHints;
+import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.connection.IConnectionAdvisor;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.PickContext;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.diagram.participant.ElementPainter;
+import org.simantics.g2d.diagram.participant.TerminalPainter;
+import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
+import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
+import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementClasses;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.IElementClassProvider;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.SceneGraph;
+import org.simantics.g2d.element.handler.TerminalTopology;
+import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
+import org.simantics.g2d.element.impl.Element;
+import org.simantics.g2d.elementclass.BranchPoint;
+import org.simantics.g2d.elementclass.BranchPoint.Direction;
+import org.simantics.g2d.elementclass.FlagClass;
+import org.simantics.g2d.elementclass.FlagHandler;
+import org.simantics.g2d.participant.RenderingQualityInteractor;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.g2d.utils.geom.DirectionSet;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+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.MouseButtonEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
+import org.simantics.scenegraph.g2d.events.command.CommandEvent;
+import org.simantics.scenegraph.g2d.events.command.Commands;
+import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
+import org.simantics.scenegraph.g2d.nodes.ShapeNode;
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.scenegraph.utils.Quality;
+import org.simantics.structural2.modelingRules.ConnectionJudgement;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.logging.TimeLogger;
+import org.simantics.utils.ui.ErrorLogger;
+import org.simantics.utils.ui.ExceptionUtils;
+
+import gnu.trove.map.hash.THashMap;
+
+/**
+ * A basic tool for making connection on diagrams.
+ *
+ * This version defines the starting, ending and route points of a connection.
+ * The routing itself is left up to the diagram router employed by
+ * {@link DiagramUtils#validateAndFix(IDiagram, ICanvasContext)}.
+ *
+ * Manual:
+ *
+ * This tool is added to the diagram when a connection sequence is initiated by
+ * another participant. PointerInteractor is one such participant which adds the
+ * tool when a terminal or non-terminal-occupied canvas space is ALT+clicked
+ * (see {@link PointerInteractor#checkInitiateConnectTool(MouseEvent, Point2D)}
+ * ). The connection will be finished when another allowed terminal is clicked
+ * upon or empty canvas space is ALT+clicked. Route points for the connection
+ * can be created by clicking around on non-terminal-occupied canvas space while
+ * connecting.
+ *
+ *
+ * Connections can be started from and ended in flags by pressing ALT while
+ * left-clicking.
+ *
+ * @author Tuukka Lehtonen
+ */
+public class ConnectTool2 extends AbstractMode {
+
+ public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
+
+ @Reference
+ protected RenderingQualityInteractor quality;
+
+ @Dependency
+ protected TransformUtil util;
+
+ @Dependency
+ protected ElementPainter diagramPainter;
+
+ @Dependency
+ protected PointerInteractor pi;
+
+ @Dependency
+ protected PickContext pickContext;
+
+ /**
+ * Start element terminal of the connection. null if connection
+ * was started from a flag or a branch point.
+ *
+ * The value is received by the constructor.
+ */
+ protected List startTerminals;
+
+ /**
+ * Refers to any of the possible overlapping start terminals. The value is
+ * taken from the first index of {@link #startTerminals} assuming that the
+ * first one is the nearest. It is null if
+ * {@link #startTerminals} is empty.
+ */
+ protected TerminalInfo startTerminal;
+
+ protected TerminalInfo startFlag;
+
+ /**
+ * Starting position of the connection, received as an external argument.
+ */
+ protected final Point2D startPos;
+
+ /**
+ * true if this tool should create connection continuation
+ * flags, false otherwise.
+ */
+ protected boolean createFlags;
+
+ /**
+ *
+ */
+ protected IElementClassProvider elementClassProvider;
+
+ /**
+ *
+ */
+ protected Deque controlPoints = new ArrayDeque();
+
+ /**
+ * Contains null when a connection is started from a new flag
+ * or one of the terminals in {@link #startTerminals} when a connection is
+ * being created starting from a terminal or possibly a set of terminals.
+ *
+ *
+ * Note that this is different from {@link #startTerminal} which simply
+ * represents the first element of {@link #startTerminals}.
+ *
+ *
+ * Only when this value and {@link #endTerminal} is properly set will a
+ * connection be created between two element terminals.
+ */
+ protected TerminalInfo selectedStartTerminal;
+
+ /**
+ * Element terminal of connection end element. null if
+ * connection cannot be ended where it is currently being attempted to end.
+ */
+ protected TerminalInfo endTerminal;
+
+ /**
+ * The latest connectability judgment from the active
+ * {@link IConnectionAdvisor} should the connection happen between
+ * {@link #selectedStartTerminal} and {@link #endTerminal}.
+ */
+ protected ConnectionJudgement connectionJudgment;
+
+ /**
+ * The latest connectability judgment from the active
+ * {@link IConnectionAdvisor} should the connection happen between
+ * {@link #selectedStartTerminal} and {@link #lastRouteGraphTarget}.
+ */
+ protected ConnectionJudgement attachToConnectionJudgement;
+
+ /**
+ * If non-null during connection drawing this field tells the direction
+ * forced for the current branch point by the user through the UI commands
+ * {@link Commands#ROTATE_ELEMENT_CCW} and
+ * {@link Commands#ROTATE_ELEMENT_CW}.
+ */
+ private Direction forcedBranchPointDirection;
+
+ /**
+ * A temporary variable for use with
+ * {@link TerminalTopology#getTerminals(IElement, Collection)}.
+ */
+ protected Collection terminals = new ArrayList();
+
+ /**
+ * Previous mouse canvas position recorded by
+ * {@link #processMouseMove(MouseMovedEvent)}.
+ */
+ protected Point2D lastMouseCanvasPos = new Point2D.Double();
+
+ /**
+ * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been
+ * invoked at least once. This is used to tell whether to allow creation of
+ * branch points or finising the connection in thin air. It will not be
+ * allowed if the mouse has not moved at all since starting the connection.
+ */
+ protected boolean mouseHasMoved = false;
+
+ protected TerminalHoverStrategy originalStrategy = null;
+
+ protected TerminalHoverStrategy terminalHoverStrategy = new TerminalHoverStrategy() {
+ @Override
+ public boolean highlightEnabled() {
+ return !isEndingInFlag();
+ }
+
+ @Override
+ public boolean highlight(TerminalInfo ti) {
+ boolean reflexive = isStartTerminal(ti.e, ti.t);
+ if (reflexive && !allowReflexiveConnections())
+ return false;
+
+ return canConnect(ti.e, ti.t) != null;
+ }
+ };
+
+ protected final static Composite ALPHA_COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);
+
+ /**
+ * Root scene graph node for all visualization performed by this tool.
+ */
+ protected G2DParentNode ghostNode;
+
+ /**
+ * Indicates whether the connection is about to be ended into a new
+ * flag/branchpoint or not.
+ */
+ protected TerminalInfo endFlag;
+
+ protected G2DParentNode endFlagNode;
+
+ private RouteGraphTarget lastRouteGraphTarget;
+
+ /**
+ * @param startTerminal
+ * @param mouseId
+ * @param startCanvasPos
+ */
+ public ConnectTool2(TerminalInfo startTerminal, int mouseId, Point2D startCanvasPos) {
+ this(startTerminal == null ? Collections. emptyList()
+ : Collections.singletonList(startTerminal),
+ mouseId,
+ startCanvasPos);
+ }
+
+ /**
+ * @param startTerminals
+ * @param mouseId
+ * @param startCanvasPos
+ */
+ public ConnectTool2(List startTerminals, int mouseId, Point2D startCanvasPos) {
+ super(mouseId);
+
+ if (startCanvasPos == null)
+ throw new NullPointerException("null start position");
+ if (startTerminals == null)
+ throw new NullPointerException("null start terminals");
+
+ this.startPos = startCanvasPos;
+ this.lastMouseCanvasPos.setLocation(startPos);
+
+ this.startTerminals = startTerminals;
+ this.startTerminal = startTerminals.isEmpty() ? null : startTerminals.get(0);
+ }
+
+ @Override
+ public void addedToContext(ICanvasContext ctx) {
+ super.addedToContext(ctx);
+
+ if (quality != null)
+ quality.setStaticQuality(Quality.LOW);
+
+ // Force terminals to always be highlighted without pressing certain
+ // keys or key combinations.
+ originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
+ setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
+ }
+
+ @Override
+ protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
+ if (newDiagram != null) {
+ // Get IElementClassProvider
+ ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);
+ if (ctx != null) {
+ this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);
+ }
+
+ // See if flags should be created or not.
+ this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));
+ startConnection();
+ }
+ }
+
+ @Override
+ public void removedFromContext(ICanvasContext ctx) {
+ if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {
+ if (originalStrategy != null)
+ setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);
+ else
+ removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
+ }
+
+ if (quality != null)
+ quality.setStaticQuality(null);
+
+ super.removedFromContext(ctx);
+ }
+
+ protected void startConnection() {
+ Point2D startPos = (Point2D) this.startPos.clone();
+ ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+ if (snapAdvisor != null)
+ snapAdvisor.snap(startPos);
+
+ // Resolve the first element and terminal of the connection.
+ ControlPoint start = new ControlPoint(startPos);
+
+ if (startTerminal != null) {
+ assert ElementUtils.peekDiagram(startTerminal.e) == diagram;
+ Point2D terminalPos = new Point2D.Double(startTerminal.posDia.getTranslateX(),
+ startTerminal.posDia.getTranslateY());
+ start.setPosition(terminalPos).setAttachedToTerminal(startTerminal);
+ } else {
+ // Create TerminalInfo describing the flag to be created.
+ if (createFlags) {
+ // This prevents connection creation from creating a branch
+ // point in place of this flag.
+ startFlag = createFlag(EdgeEnd.Begin);
+ start.setAttachedToTerminal(startFlag);
+ showElement(ghostNode, "startFlag", startFlag.e, startPos);
+ }
+ }
+ controlPoints.add(start);
+ controlPoints.add(new ControlPoint(startPos));
+
+ // Make sure that we are ending with a flag if ALT is pressed.
+ // This makes the tool always start with a flag which can be quite
+ // cumbersome and is therefore disabled. The current version will not
+ // end the connection if the mouse has not moved at all.
+ //if (keyUtil.isKeyPressed(java.awt.event.KeyEvent.VK_ALT)) {
+ // endWithoutTerminal(lastMouseCanvasPos, true);
+ //}
+ }
+
+ @SGInit
+ public void initSG(G2DParentNode parent) {
+ ghostNode = parent.addNode(G2DParentNode.class);
+ ghostNode.setZIndex(PAINT_PRIORITY);
+
+ ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
+ pathNode.setColor(new Color(160, 0, 0));
+ pathNode.setStroke(new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,
+ new float[] { 0.5f, 0.2f }, 0));
+ pathNode.setScaleStroke(false);
+ pathNode.setZIndex(0);
+
+ G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);
+ points.setZIndex(1);
+
+ updateSG();
+ }
+
+ private RouteTerminal addControlPoint(RouteGraph routeGraph, ControlPoint cp) {
+ TerminalInfo ti = cp.getAttachedTerminal();
+ if(ti != null && ti != startFlag && ti != endFlag) {
+ Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
+ GeometryUtils.expandRectangle(bounds, 2);
+ int allowedDirections = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
+ ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
+ return routeGraph.addTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(),
+ bounds, allowedDirections, PlainLineEndStyle.INSTANCE);
+ }
+ else {
+ double x = cp.getPosition().getX();
+ double y = cp.getPosition().getY();
+ int allowedDirections = 0xf;
+ switch(cp.getDirection()) {
+ case Horizontal: allowedDirections = 5; break;
+ case Vertical: allowedDirections = 10; break;
+ case Any: allowedDirections = 15; break;
+ }
+ return routeGraph.addTerminal(x, y, x, y, x, y, allowedDirections);
+ }
+ }
+
+ protected void updateSG() {
+ if (controlPoints.size() != 2)
+ return;
+
+ ControlPoint begin = controlPoints.getFirst();
+ ControlPoint end = controlPoints.getLast();
+
+ RouteGraph routeGraph = new RouteGraph();
+ RouteTerminal a = addControlPoint(routeGraph, begin);
+ RouteTerminal b = addControlPoint(routeGraph, end);
+ routeGraph.link(a, b);
+
+ Path2D path = routeGraph.getPath2D();
+
+ // Create scene graph to visualize the connection.
+ ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
+ pathNode.setShape(path);
+
+ setDirty();
+ }
+
+ private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {
+ return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));
+ }
+
+ private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {
+ G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);
+ elementParent.setTransform(tr);
+ elementParent.removeNodes();
+ for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))
+ sg.init(element, elementParent);
+ return elementParent;
+ }
+
+ @SGCleanup
+ public void cleanupSG() {
+ ghostNode.remove();
+ ghostNode = null;
+ }
+
+ @EventHandler(priority = 200)
+ public boolean handleCommandEvents(CommandEvent ce) {
+ if (ce.command.equals(Commands.CANCEL)) {
+ setDirty();
+ remove();
+ return true;
+ } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {
+ return rotateLastBranchPoint(ce.command.equals(Commands.ROTATE_ELEMENT_CW));
+ }
+ return false;
+ }
+
+ @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
+ public boolean handleKeyEvents(KeyEvent ke) {
+ if (ke instanceof KeyPressedEvent) {
+ // Back-space, cancel prev bend
+ if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)
+ return cancelPreviousBend();
+ }
+
+ if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
+ if (createFlags) {
+ endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
+ public boolean handleEvent(MouseEvent me) {
+ // Only handle events for the connection-initiating mouse
+ if (me.mouseId != mouseId)
+ return false;
+
+ if (me instanceof MouseMovedEvent)
+ return processMouseMove((MouseMovedEvent) me);
+
+ if (me instanceof MouseButtonPressedEvent)
+ return processMouseButtonPress((MouseButtonPressedEvent) me);
+
+ // #7653: Support creating connections between terminals without lifting mouse button in between.
+ if (me instanceof MouseButtonReleasedEvent)
+ return processMouseButtonRelease((MouseButtonReleasedEvent) me);
+
+ return false;
+ }
+
+ protected boolean processMouseMove(MouseMovedEvent me) {
+ mouseHasMoved = true;
+
+ Point2D mouseControlPos = me.controlPosition;
+ Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());
+
+ ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+ if (snapAdvisor != null)
+ snapAdvisor.snap(mouseCanvasPos);
+
+ // Record last snapped canvas position of mouse.
+ this.lastMouseCanvasPos.setLocation(mouseCanvasPos);
+
+ if (isEndingInFlag()) {
+ endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));
+ }
+
+ List tis = pi.pickTerminals(me.controlPosition);
+ tis = TerminalUtil.findNearestOverlappingTerminals(tis);
+ if (!tis.isEmpty() && !containsStartTerminal(tis)) {
+ //System.out.println("end terminals (" + tis.size() + "):\n" + EString.implode(tis));
+ for (TerminalInfo ti : tis) {
+ Pair canConnect = canConnect(ti.e, ti.t);
+ if (canConnect != null) {
+ connectionJudgment = canConnect.first;
+
+ if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
+ if (canConnect.second != null) {
+ controlPoints.getFirst()
+ .setPosition(canConnect.second.posDia)
+ .setAttachedToTerminal(canConnect.second);
+ }
+ controlPoints.getLast()
+ .setPosition(ti.posDia)
+ .setAttachedToTerminal(ti);
+
+ selectedStartTerminal = canConnect.second;
+ endTerminal = ti;
+ }
+
+ // Make sure that we are ending with a flag if ALT is pressed
+ // and no end terminal is defined.
+ if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
+ updateSG();
+ return false;
+ }
+ }
+ } else {
+ RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(
+ diagram,
+ pi.getCanvasPickShape(me.controlPosition),
+ pi.getPickDistance());
+ if (cp != null) {
+ // Remove branch point highlight from previously picked route graph.
+ if (lastRouteGraphTarget != null && cp.getNode() != lastRouteGraphTarget.getNode())
+ cp.getNode().showBranchPoint(null);
+ lastRouteGraphTarget = cp;
+
+ // Validate connection before visualizing connectability
+ Point2D isectPos = cp.getIntersectionPosition();
+ TerminalInfo ti = TerminalInfo.create(
+ isectPos,
+ cp.getElement(),
+ BranchPointTerminal.existingTerminal(
+ isectPos,
+ DirectionSet.ANY,
+ BranchPointNode.SHAPE),
+ BranchPointNode.SHAPE);
+ Pair canConnect = canConnect(ti.e, ti.t);
+ if (canConnect != null) {
+ attachToConnectionJudgement = canConnect.first;
+ controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);
+ endTerminal = ti;
+ cp.getNode().showBranchPoint(isectPos);
+ if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
+ updateSG();
+ return false;
+ }
+ } else {
+ if (lastRouteGraphTarget != null) {
+ lastRouteGraphTarget.getNode().showBranchPoint(null);
+ lastRouteGraphTarget = null;
+ }
+ }
+ }
+
+ connectionJudgment = null;
+ attachToConnectionJudgement = null;
+ if (isEndTerminalDefined()) {
+ // CASE: Mouse was previously on top of a valid terminal to end
+ // the connection. Now the mouse has been moved where there is
+ // no longer a terminal to connect to.
+ //
+ // => Disconnect the last edge segment from the previous
+ // terminal, mark endElement/endTerminal non-existent
+ // and connect the disconnected edge to a new branch point.
+
+ controlPoints.getLast()
+ .setPosition(mouseCanvasPos)
+ .setDirection(calculateCurrentBranchPointDirection())
+ .setAttachedToTerminal(null);
+
+ endTerminal = null;
+ } else {
+ // CASE: Mouse was not previously on top of a valid ending
+ // element terminal.
+ //
+ // => Move and re-orient last branch point.
+
+ controlPoints.getLast()
+ .setPosition(mouseCanvasPos)
+ .setDirection(calculateCurrentBranchPointDirection());
+ }
+
+ // Make sure that we are ending with a flag if ALT is pressed and no end
+ // terminal is defined.
+ if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))
+ updateSG();
+
+ return false;
+ }
+
+ protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {
+ MouseButtonEvent me = e;
+
+ // Do nothing before the mouse has moved at least a little.
+ // This prevents the user from ending the connection right where
+ // it started.
+ if (!mouseHasMoved)
+ return true;
+
+ if (me.button == MouseEvent.LEFT_BUTTON) {
+ Point2D mouseControlPos = me.controlPosition;
+ Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());
+
+ ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
+ if (snapAdvisor != null)
+ snapAdvisor.snap(mouseCanvasPos);
+
+ if (tryEndConnection()) {
+ return true;
+ } else {
+ // Finish connection in thin air only if the
+ // connection was started from a valid terminal.
+ if (me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) && !startTerminals.isEmpty()) {
+ Pair pair = canConnect(null, null);
+ if (pair != null) {
+ connectionJudgment = (ConnectionJudgement) pair.first;
+ selectedStartTerminal = pair.second;
+// endFlag = createFlag(EdgeEnd.End);
+// controlPoints.getLast().setAttachedToTerminal(endFlag);
+ createConnection();
+ setDirty();
+ remove();
+ } else {
+ // Inform the user why connection couldn't be created.
+ String tmsg = terminalsToString(startTerminals);
+ ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection when starting from one of the following terminals:\n" + tmsg, null);
+ }
+ return true;
+ } else if (routePointsAllowed()
+ && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
+ // Add new connection control point.
+ controlPoints.add(newControlPointWithCalculatedDirection(mouseCanvasPos));
+ resetForcedBranchPointDirection();
+ updateSG();
+ }
+ }
+
+ // Eat the event to prevent other participants from doing
+ // incompatible things while in this connection mode.
+ return true;
+ } else if (me.button == MouseEvent.RIGHT_BUTTON) {
+ return cancelPreviousBend();
+ }
+
+ return false;
+ }
+
+ private int mouseLeftReleaseCount = 0;
+
+ protected boolean processMouseButtonRelease(MouseButtonReleasedEvent me) {
+ if (me.button == MouseEvent.LEFT_BUTTON
+ && ++mouseLeftReleaseCount == 1) {
+ return tryEndConnection();
+ }
+ return false;
+ }
+
+ /**
+ * @return true if connection was successfully ended
+ */
+ private boolean tryEndConnection() {
+ if (isEndTerminalDefined() && connectionJudgment != null) {
+ createConnection();
+ remove();
+ return true;
+ } else if (lastRouteGraphTarget != null && attachToConnectionJudgement != null) {
+ lastRouteGraphTarget.getNode().showBranchPoint(null);
+ attachToConnection();
+ remove();
+ return true;
+ }
+ return false;
+ }
+
+ private void attachToConnection() {
+ ConnectionJudgement judgment = this.attachToConnectionJudgement;
+ if (judgment == null) {
+ ErrorLogger.defaultLogError("Cannot attach to connection, no judgment available on connection validity", null);
+ return;
+ }
+
+ ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
+ RouteGraph before = lastRouteGraphTarget.getNode().getRouteGraph();
+ THashMap