X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;ds=sidebyside;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fparticipant%2FRouteGraphConnectTool.java;h=7454a2f3986cfff025242c5d9557876eaa53f809;hb=0861b325fcbbfa8c5985f1e11cfc39154a3808d1;hp=57f5cd4900310263031a81d3227c99f355a25917;hpb=bd5bc6e45f700e755b61bd112631796631330ecb;p=simantics%2Fplatform.git
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
index 57f5cd490..7454a2f39 100644
--- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
+++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
@@ -1,905 +1,926 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2016 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
- * Semantum Oy - Fixed bug #6364
- *******************************************************************************/
-package org.simantics.diagram.participant;
-
-import java.awt.AlphaComposite;
-import java.awt.Composite;
-import java.awt.Shape;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import org.simantics.Simantics;
-import org.simantics.db.Resource;
-import org.simantics.db.WriteGraph;
-import org.simantics.db.common.request.WriteRequest;
-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.RoutePoint;
-import org.simantics.diagram.connection.RouteTerminal;
-import org.simantics.diagram.connection.delta.RouteGraphDelta;
-import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
-import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;
-import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
-import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
-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.SGNodeReflection.SGCleanup;
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
-import org.simantics.g2d.connection.ConnectionEntity;
-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.PickRequest;
-import org.simantics.g2d.diagram.handler.Topology.Connection;
-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.ElementHints;
-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.FlagClass;
-import org.simantics.g2d.elementclass.FlagHandler;
-import org.simantics.g2d.participant.TransformUtil;
-import org.simantics.g2d.utils.geom.DirectionSet;
-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.ConnectionNode;
-import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
-import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
-import org.simantics.scenegraph.utils.GeometryUtils;
-import org.simantics.structural2.modelingRules.ConnectionJudgement;
-import org.simantics.utils.datastructures.Callback;
-import org.simantics.utils.datastructures.Pair;
-import org.simantics.utils.datastructures.Triple;
-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 RouteGraphConnectTool extends AbstractMode {
-
- private static final String END_TERMINAL_DATA = "END";
-
- public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
-
- @Dependency
- protected TransformUtil util;
-
- @Dependency
- protected ElementPainter diagramPainter;
-
- @Dependency
- protected PointerInteractor pi;
-
- /**
- * Starting point designation.
- *
- * The value is received by the constructor.
- */
- protected RouteGraphTarget startingPoint;
-
- protected TerminalInfo startTerminal = new TerminalInfo();
-
- /**
- * true
if this tool should create connection continuation
- * flags, false
otherwise.
- */
- protected boolean createFlags;
-
- /**
- *
- */
- protected IElementClassProvider elementClassProvider;
-
- /**
- *
- */
- protected Deque controlPoints = new ArrayDeque();
-
- /**
- * 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 #startTerminal} and {@link #endTerminal}.
- */
- protected ConnectionJudgement connectionJudgment;
-
- /**
- * 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) {
- 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 ConnectionNode ghostNode;
-
- protected RouteGraphNode rgNode;
-
- protected RouteGraph routeGraph;
- protected RouteTerminal endRouteTerminal;
- private ILineEndStyle endTerminalStyle;
-
- /**
- * Indicates whether the connection is about to be ended into a new
- * flag/branchpoint or not.
- */
- protected TerminalInfo endFlag;
-
- protected G2DParentNode endFlagNode;
-
- private RouteLine attachedToRouteLine;
-
- protected RouteGraph beforeRouteGraph;
-
- private IRouteGraphRenderer beforeRenderer;
-
- private boolean beforeEditable;
-
- protected RouteGraphDelta routeGraphDelta;
-
- private Set> alreadyConnected;
-
- /**
- * @param startElement
- * @param routeGraphConnection
- * @param mouseId
- * @param startCanvasPos
- */
- public RouteGraphConnectTool(RouteGraphTarget startingPoint, int mouseId) {
- super(mouseId);
-
- Point2D intersection = startingPoint.getIntersectionPosition();
-
- this.startingPoint = startingPoint;
- this.lastMouseCanvasPos.setLocation(intersection);
-
- BranchPointTerminal t = BranchPointTerminal.existingTerminal(
- AffineTransform.getTranslateInstance(intersection.getX(), intersection.getY()),
- DirectionSet.ANY,
- BranchPointNode.SHAPE);
-
- Point2D p = startingPoint.getIntersectionPosition();
- AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());
-
- startTerminal.e = startingPoint.getElement();
- startTerminal.t = t;
- startTerminal.posElem = at;
- startTerminal.posDia = at;
- startTerminal.shape = t.getShape();
-
- controlPoints.add( newControlPoint( startingPoint.getIntersectionPosition() ) );
- controlPoints.add( newControlPoint( startingPoint.getCanvasPosition() ) );
-
- alreadyConnected = new HashSet>();
- IElement connection = startingPoint.getElement();
- ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
- Collection tcs = ce.getTerminalConnections(null);
- for (Connection tc : tcs)
- alreadyConnected.add(Pair.make(tc.node, tc.terminal));
- }
-
- @Override
- public void addedToContext(ICanvasContext ctx) {
- super.addedToContext(ctx);
-
- // 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));
- }
- }
-
- @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);
- }
-
- super.removedFromContext(ctx);
- }
-
- int straightDirections(RouteLine line) {
- return line.isHorizontal()
- ? (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP)
- : (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);
- }
-
- private static RouteTerminal addTerminal(Object data, RouteGraph rg, double x, double y, Rectangle2D bounds, int allowedDirections, ILineEndStyle style) {
- RouteTerminal rt;
- if (bounds != null)
- rt = rg.addTerminal(x, y, bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), allowedDirections, style);
- else
- rt = rg.addTerminal(x, y, x, y, x, y, allowedDirections, style);
- rt.setData( data );
- return rt;
- }
-
- private RouteTerminal setEndTerminal(double x, double y, Rectangle2D bounds, int allowedDirections) {
-
- // First add then remove to prevent deletion of route lines in case there are 2 only terminals
-
- RouteTerminal toRemove = endRouteTerminal;
-
- endRouteTerminal = addTerminal( END_TERMINAL_DATA, routeGraph, x, y, bounds, allowedDirections, endTerminalStyle );
- routeGraph.link( attachedToRouteLine, endRouteTerminal );
-
- if (toRemove != null)
- routeGraph.remove(toRemove);
-
- return endRouteTerminal;
-
- }
-
- protected void setEndTerminal(TerminalInfo ti) {
- Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
- GeometryUtils.expandRectangle(bounds, 2);
- int dir = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
- ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
- setEndTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds, dir);
- }
-
- @SGInit
- public void initSG(G2DParentNode parent) {
- ghostNode = parent.addNode("branched connection", ConnectionNode.class);
- //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.5f));
- ghostNode.setZIndex(PAINT_PRIORITY);
-
- rgNode = ghostNode.addNode("branch", RouteGraphNode.class);
-
- double ex = startingPoint.getCanvasPosition().getX();
- double ey = startingPoint.getCanvasPosition().getY();
-
- beforeRouteGraph = startingPoint.getNode().getRouteGraph();
- beforeEditable = startingPoint.getNode().isEditable();
-
- RouteGraphNode beforeRgNode = startingPoint.getNode();
- beforeRenderer = beforeRgNode.getRenderer();
-
- rgNode.setRenderer(beforeRenderer);
- rgNode.setEditable(false);
-
- beforeRgNode.setEditable(beforeEditable);
- beforeRgNode.setRenderer(null);
-
- initRG(ex, ey);
-
- }
-
- public void initRG(double ex, double ey) {
-
- THashMap map = new THashMap();
- routeGraph = beforeRouteGraph.copy(map);
-
- endTerminalStyle = PlainLineEndStyle.INSTANCE;
- for (RouteTerminal t : routeGraph.getTerminals()) {
- if (t.getRenderStyle() instanceof ArrowLineEndStyle) {
- endTerminalStyle = t.getRenderStyle();
- break;
- }
- }
-
- attachedToRouteLine = (RouteLine) map.get(startingPoint.getLine());
- routeGraph.makePersistent(attachedToRouteLine);
- for (RouteLine line : routeGraph.getAllLines()) {
- if (!line.isTransient() && line.isHorizontal() == attachedToRouteLine.isHorizontal()
- && line.getPosition() == attachedToRouteLine.getPosition()) {
- attachedToRouteLine = line;
- break;
- }
- }
- routeGraphDelta = new RouteGraphDelta(beforeRouteGraph, routeGraph);
-
-// beforeRouteGraph.print();
-// routeGraph.print();
-// routeGraphDelta.print();
-
- setEndTerminal(ex, ey, null, 0xf);
-
- rgNode.setRouteGraph(routeGraph);
-
- }
-
- @SGCleanup
- public void cleanupSG() {
- RouteGraphNode beforeRgNode = startingPoint.getNode();
- beforeRgNode.setRouteGraph(beforeRouteGraph);
- beforeRgNode.setRenderer(beforeRenderer);
- beforeRgNode.setEditable(beforeEditable);
-
- ghostNode.remove();
- ghostNode = null;
-
- setDirty();
- }
-
- @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)) {
- // TODO: rotate flag?
- }
- 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()));
- }
-
- TerminalInfo ti = pi.pickTerminal(me.controlPosition);
- if (ti != null) {
- Object canConnect = canConnect(ti.e, ti.t);
- if (canConnect != null) {
- connectionJudgment = (ConnectionJudgement) canConnect;
-
- if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
- controlPoints.getLast()
- .setPosition(ti.posDia)
- .setAttachedToTerminal(ti);
-
- endTerminal = ti;
-
- connect(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(new Point2D.Double(ti.posDia.getTranslateX(), ti.posDia.getTranslateY()));
- 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.
-
- disconnect(mouseCanvasPos);
-
- controlPoints.getLast()
- .setPosition(mouseCanvasPos)
- .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);
- }
-
- // Make sure that we are ending with a flag if ALT is pressed and no end
- // terminal is defined.
- endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
-
- updateSG(lastMouseCanvasPos);
-
- return false;
- }
-
- protected void connect(TerminalInfo ti) {
- setEndTerminal(ti);
- }
-
- protected void disconnect(Point2D mouseCanvasPos) {
- setEndTerminal(mouseCanvasPos.getX(), mouseCanvasPos.getY(), null, 0xf);
- }
-
- 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.stateMask & MouseEvent.ALT_MASK) != 0 && startTerminal != null) {
- connectionJudgment = (ConnectionJudgement) canConnect(null, null);
- if (connectionJudgment == null)
- return true;
- createConnection();
- remove();
- return true;
- } else if (routePointsAllowed()
- && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
- // Add new connection control point.
- controlPoints.add(newControlPoint(mouseCanvasPos));
- updateSG(mouseCanvasPos);
- }
- }
-
- // 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);
-
- updateSG(lastMouseCanvasPos);
- return true;
- }
-
- protected void updateSG(Point2D mousePos) {
- routeGraph.setLocation(endRouteTerminal, mousePos.getX(), mousePos.getY());
- //routeGraph.print(System.err);
- setDirty();
- }
-
- protected boolean shouldEndWithFlag(MouseEvent me) {
- return shouldEndWithFlag((me.stateMask & MouseEvent.ALT_MASK) != 0);
- }
-
- protected boolean shouldEndWithFlag(boolean altPressed) {
- return altPressed && !isEndTerminalDefined() && createFlags;
- }
-
- protected boolean isEndTerminalDefined() {
- return endTerminal != null;
- }
-
- protected boolean isFlagTerminal(TerminalInfo ti) {
- return ti.e.getElementClass().containsClass(FlagHandler.class);
- }
-
-
- 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()
- .setAttachedToTerminal(endFlag);
-
- // TerminalPainter must refresh
- setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
-
- updateSG(mousePos);
- }
- } 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.setAttachedToTerminal(endTerminal);
-
- if (endTerminalDefined) {
- cp.setPosition(endTerminal.posDia);
- } else {
- cp.setPosition(mousePos);
- }
-
- // Force Terminalpainter refresh
- setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
-
- updateSG(mousePos);
- }
- }
- }
-
- 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;
- }
-
- /**
- * @param canvasPos
- * @return
- */
- protected ControlPoint newControlPoint(Point2D canvasPos) {
- return new ControlPoint(canvasPos);
- }
-
- protected Triple prepareRouteGraphDelta() {
- // Prevent route graph connection synchronization from crashing on the
- // transient route terminal that doesn't exist yet. It is created after
- // persisting the route line, which is the only purpose of this route
- // graph synchronization.
- routeGraph.remove(endRouteTerminal);
- return Triple.make(beforeRouteGraph, routeGraph, routeGraphDelta);
- }
-
- protected void createConnection() {
- TimeLogger.resetTimeAndLog(getClass(), "createConnection");
- final ConnectionJudgement judgment = this.connectionJudgment;
- if (judgment == null) {
- ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity", null);
- return;
- }
-
- final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
- final Triple rgs = prepareRouteGraphDelta();
-
- Simantics.getSession().asyncRequest(new WriteRequest() {
- @Override
- public void perform(WriteGraph graph) throws DatabaseException {
- graph.markUndoPoint();
- Resource connection = ElementUtils.getObject(startTerminal.e);
- if (!rgs.third.isEmpty()) {
- new RouteGraphConnection(graph, connection).synchronize(graph, rgs.first, rgs.second, rgs.third);
- }
- Resource attachToLine = RouteGraphConnection.deserialize(graph, attachedToRouteLine.getData());
- builder.attachToRouteGraph(graph, judgment, connection, attachToLine, controlPoints, endTerminal, FlagClass.Type.Out);
- }
- }, new Callback() {
- @Override
- public void run(DatabaseException parameter) {
- if (parameter != null)
- ExceptionUtils.logAndShowError(parameter);
- }
- });
- }
-
- protected boolean routePointsAllowed() {
- return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
- }
-
- protected Object canConnect(IElement endElement, Terminal endTerminal) {
- IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
- return canConnect(advisor, endElement, endTerminal);
- }
-
- protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {
- if (advisor == null)
- return ConnectionJudgement.CANBEMADELEGAL;
- if (alreadyConnected.contains(Pair.make(endElement, endTerminal)))
- return null;
- return advisor.canBeConnected(null, startTerminal.e, startTerminal.t, endElement, endTerminal);
- }
-
- 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;
- }
-
- // ------------------------------------------------------------------------
-
- static RouteGraphTarget pickRouteGraphConnection(IDiagram diagram, Shape pickShape, double pickDistance) {
- ArrayList elements = new ArrayList();
- PickRequest req = new PickRequest(pickShape);
- DiagramUtils.pick(diagram, req, elements);
- for (Iterator it = elements.iterator(); it.hasNext();) {
- IElement e = it.next();
- RouteGraphNode rgn = e.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
- if (rgn == null || rgn.getRouteGraph() == null)
- it.remove();
- }
- if (elements.isEmpty())
- return null;
-
- Rectangle2D pickRect = pickShape.getBounds2D();
- final double x = pickRect.getCenterX();
- final double y = pickRect.getCenterY();
-
- return pickNearestRouteGraphConnection(elements, x, y, pickDistance);
- }
-
- private static RouteGraphTarget pickNearestRouteGraphConnection(ArrayList elements, double x, double y, double pd) {
- // Find the nearest distance at which we get hits.
- double hi = pd + 1;
- double lo = hi * .01;
- double limit = 0.5;
- while (true) {
- double delta = (hi - lo);
- if (delta <= limit)
- break;
-
- pd = (lo + hi) * .5;
-
- boolean hit = false;
- for (IElement connection : elements) {
- RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
- RouteLine line = rgn.getRouteGraph().pickLine(x, y, pd);
- if (line != null) {
- hit = true;
- break;
- }
- }
-
- if (hit)
- hi = pd;
- else
- lo = pd;
- }
-
- // Now that the nearest hitting distance is found, find the nearest intersection.
- RouteGraphTarget nearestTarget = null;
- double nearest = Double.MAX_VALUE;
- for (IElement connection : elements) {
- RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
- RouteLine line = rgn.getRouteGraph().pickLine(x, y, pd);
- if (line == null)
- continue;
-
- Point2D intersection = intersectionPoint(x, y, line);
- if (intersection == null)
- continue;
-
- double dx = intersection.getX() - x;
- double dy = intersection.getY() - y;
- double dist = Math.sqrt(dx*dx + dy*dy);
- if (dist < nearest) {
- nearest = dist;
- nearestTarget = new RouteGraphTarget(connection, rgn, line, new Point2D.Double(x, y), intersection);
- }
- }
-
- return nearestTarget;
- }
-
- static Point2D intersectionPoint(double x, double y, RouteLine line) {
- Collection points = line.getPoints();
- if (points.size() < 2)
- return null;
- RoutePoint s = line.getBegin();
- RoutePoint e = line.getEnd();
- return GeometryUtils.intersectionToLine(s.getX(), s.getY(), e.getX(), e.getY(), x, y);
- }
-
+/*******************************************************************************
+ * Copyright (c) 2007, 2016 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
+ * Semantum Oy - Fixed bug #6364
+ *******************************************************************************/
+package org.simantics.diagram.participant;
+
+import java.awt.AlphaComposite;
+import java.awt.Composite;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.simantics.Simantics;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.diagram.connection.RouteGraph;
+import org.simantics.diagram.connection.RouteLine;
+import org.simantics.diagram.connection.RoutePoint;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.delta.RouteGraphDelta;
+import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
+import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;
+import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
+import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
+import org.simantics.diagram.connection.segments.Segment;
+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.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.connection.ConnectionEntity;
+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.PickRequest;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+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.ElementHints;
+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.FlagClass;
+import org.simantics.g2d.elementclass.FlagHandler;
+import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.g2d.utils.geom.DirectionSet;
+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.ConnectionNode;
+import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
+import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.structural2.modelingRules.ConnectionJudgement;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.datastructures.Triple;
+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 RouteGraphConnectTool extends AbstractMode {
+
+ private static final String END_TERMINAL_DATA = "END";
+
+ public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
+
+ @Dependency
+ protected TransformUtil util;
+
+ @Dependency
+ protected ElementPainter diagramPainter;
+
+ @Dependency
+ protected PointerInteractor pi;
+
+ /**
+ * Starting point designation.
+ *
+ * The value is received by the constructor.
+ */
+ protected RouteGraphTarget startingPoint;
+
+ protected TerminalInfo startTerminal = new TerminalInfo();
+
+ /**
+ * true
if this tool should create connection continuation
+ * flags, false
otherwise.
+ */
+ protected boolean createFlags;
+
+ /**
+ *
+ */
+ protected IElementClassProvider elementClassProvider;
+
+ /**
+ *
+ */
+ protected Deque controlPoints = new ArrayDeque();
+
+ /**
+ * 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 #startTerminal} and {@link #endTerminal}.
+ */
+ protected ConnectionJudgement connectionJudgment;
+
+ /**
+ * 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) {
+ 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 ConnectionNode ghostNode;
+
+ protected RouteGraphNode rgNode;
+
+ protected RouteGraph routeGraph;
+ protected RouteTerminal endRouteTerminal;
+ private ILineEndStyle endTerminalStyle;
+
+ /**
+ * Indicates whether the connection is about to be ended into a new
+ * flag/branchpoint or not.
+ */
+ protected TerminalInfo endFlag;
+
+ protected G2DParentNode endFlagNode;
+
+ private RouteLine attachedToRouteLine;
+
+ protected RouteGraph beforeRouteGraph;
+
+ private IRouteGraphRenderer beforeRenderer;
+
+ private boolean beforeEditable;
+
+ protected RouteGraphDelta routeGraphDelta;
+
+ private Set> alreadyConnected;
+
+ /**
+ * @param startElement
+ * @param routeGraphConnection
+ * @param mouseId
+ * @param startCanvasPos
+ */
+ public RouteGraphConnectTool(RouteGraphTarget startingPoint, int mouseId) {
+ super(mouseId);
+
+ Point2D intersection = startingPoint.getIntersectionPosition();
+
+ this.startingPoint = startingPoint;
+ this.lastMouseCanvasPos.setLocation(intersection);
+
+ BranchPointTerminal t = BranchPointTerminal.existingTerminal(
+ AffineTransform.getTranslateInstance(intersection.getX(), intersection.getY()),
+ DirectionSet.ANY,
+ BranchPointNode.SHAPE);
+
+ Point2D p = startingPoint.getIntersectionPosition();
+ AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());
+
+ startTerminal.e = startingPoint.getElement();
+ startTerminal.t = t;
+ startTerminal.posElem = at;
+ startTerminal.posDia = at;
+ startTerminal.shape = t.getShape();
+
+ controlPoints.add( newControlPoint( startingPoint.getIntersectionPosition() ) );
+ controlPoints.add( newControlPoint( startingPoint.getCanvasPosition() ) );
+
+ alreadyConnected = new HashSet>();
+ IElement connection = startingPoint.getElement();
+ ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+ Collection tcs = ce.getTerminalConnections(null);
+ for (Connection tc : tcs)
+ alreadyConnected.add(Pair.make(tc.node, tc.terminal));
+ }
+
+ @Override
+ public void addedToContext(ICanvasContext ctx) {
+ super.addedToContext(ctx);
+
+ // 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));
+ }
+ }
+
+ @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);
+ }
+
+ super.removedFromContext(ctx);
+ }
+
+ int straightDirections(RouteLine line) {
+ return line.isHorizontal()
+ ? (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP)
+ : (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);
+ }
+
+ private static RouteTerminal addTerminal(Object data, RouteGraph rg, double x, double y, Rectangle2D bounds, int allowedDirections, ILineEndStyle style) {
+ RouteTerminal rt;
+ if (bounds != null)
+ rt = rg.addTerminal(x, y, bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), allowedDirections, style);
+ else
+ rt = rg.addTerminal(x, y, x, y, x, y, allowedDirections, style);
+ rt.setData( data );
+ return rt;
+ }
+
+ private RouteTerminal setEndTerminal(double x, double y, Rectangle2D bounds, int allowedDirections) {
+
+ // First add then remove to prevent deletion of route lines in case there are 2 only terminals
+
+ RouteTerminal toRemove = endRouteTerminal;
+
+ endRouteTerminal = addTerminal( END_TERMINAL_DATA, routeGraph, x, y, bounds, allowedDirections, endTerminalStyle );
+ routeGraph.link( attachedToRouteLine, endRouteTerminal );
+
+ if (toRemove != null)
+ routeGraph.remove(toRemove);
+
+ return endRouteTerminal;
+
+ }
+
+ protected void setEndTerminal(TerminalInfo ti) {
+ Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
+ GeometryUtils.expandRectangle(bounds, 2);
+ int dir = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
+ ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
+ setEndTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds, dir);
+ }
+
+ @SGInit
+ public void initSG(G2DParentNode parent) {
+ ghostNode = parent.addNode("branched connection", ConnectionNode.class);
+ //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.5f));
+ ghostNode.setZIndex(PAINT_PRIORITY);
+
+ rgNode = ghostNode.addNode("branch", RouteGraphNode.class);
+
+ double ex = startingPoint.getCanvasPosition().getX();
+ double ey = startingPoint.getCanvasPosition().getY();
+
+ beforeRouteGraph = startingPoint.getNode().getRouteGraph();
+ beforeEditable = startingPoint.getNode().isEditable();
+
+ RouteGraphNode beforeRgNode = startingPoint.getNode();
+ beforeRenderer = beforeRgNode.getRenderer();
+
+ rgNode.setRenderer(beforeRenderer);
+ rgNode.setEditable(false);
+
+ beforeRgNode.setEditable(beforeEditable);
+ beforeRgNode.setRenderer(null);
+
+ initRG(ex, ey);
+
+ }
+
+ public void initRG(double ex, double ey) {
+
+ THashMap map = new THashMap();
+ routeGraph = beforeRouteGraph.copy(map);
+
+ endTerminalStyle = PlainLineEndStyle.INSTANCE;
+ for (RouteTerminal t : routeGraph.getTerminals()) {
+ if (t.getRenderStyle() instanceof ArrowLineEndStyle) {
+ endTerminalStyle = t.getRenderStyle();
+ break;
+ }
+ }
+
+ attachedToRouteLine = (RouteLine) map.get(startingPoint.getLine());
+ routeGraph.makePersistent(attachedToRouteLine);
+ for (RouteLine line : routeGraph.getAllLines()) {
+ if (!line.isTransient() && line.isHorizontal() == attachedToRouteLine.isHorizontal()
+ && line.getPosition() == attachedToRouteLine.getPosition()) {
+ attachedToRouteLine = line;
+ break;
+ }
+ }
+ routeGraphDelta = new RouteGraphDelta(beforeRouteGraph, routeGraph);
+
+// beforeRouteGraph.print();
+// routeGraph.print();
+// routeGraphDelta.print();
+
+ setEndTerminal(ex, ey, null, 0xf);
+
+ rgNode.setRouteGraph(routeGraph);
+
+ }
+
+ @SGCleanup
+ public void cleanupSG() {
+ RouteGraphNode beforeRgNode = startingPoint.getNode();
+ beforeRgNode.setRouteGraph(beforeRouteGraph);
+ beforeRgNode.setRenderer(beforeRenderer);
+ beforeRgNode.setEditable(beforeEditable);
+
+ ghostNode.remove();
+ ghostNode = null;
+
+ setDirty();
+ }
+
+ @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)) {
+ // TODO: rotate flag?
+ }
+ 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()) {
+ for (TerminalInfo ti : tis) {
+ Object canConnect = canConnect(ti.e, ti.t);
+
+ if (canConnect != null) {
+ connectionJudgment = (ConnectionJudgement) canConnect;
+
+ if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
+ controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);
+
+ endTerminal = ti;
+
+ connect(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(new Point2D.Double(ti.posDia.getTranslateX(), ti.posDia.getTranslateY()));
+ 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.
+
+ disconnect(mouseCanvasPos);
+
+ controlPoints.getLast()
+ .setPosition(mouseCanvasPos)
+ .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);
+ }
+
+ // Make sure that we are ending with a flag if ALT is pressed and no end
+ // terminal is defined.
+ endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
+
+ updateSG(lastMouseCanvasPos);
+
+ return false;
+ }
+
+ protected void connect(TerminalInfo ti) {
+ setEndTerminal(ti);
+ }
+
+ protected void disconnect(Point2D mouseCanvasPos) {
+ setEndTerminal(mouseCanvasPos.getX(), mouseCanvasPos.getY(), null, 0xf);
+ }
+
+ protected boolean processMouseButtonPress(MouseButtonEvent me) {
+ // 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 (tryEndConnection()) {
+ return true;
+ } else {
+ // Finish connection in thin air only if the
+ // connection was started from a valid terminal.
+ if ((me.stateMask & MouseEvent.ALT_MASK) != 0 && startTerminal != null) {
+ connectionJudgment = (ConnectionJudgement) canConnect(null, null);
+ if (connectionJudgment == null)
+ return true;
+ createConnection();
+ remove();
+ return true;
+ } else if (routePointsAllowed()
+ && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
+ // Add new connection control point.
+ controlPoints.add(newControlPoint(mouseCanvasPos));
+ updateSG(mouseCanvasPos);
+ }
+ }
+
+ // 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()) {
+ createConnection();
+ remove();
+ return true;
+ }
+ 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);
+
+ updateSG(lastMouseCanvasPos);
+ return true;
+ }
+
+ protected void updateSG(Point2D mousePos) {
+ routeGraph.setLocation(endRouteTerminal, mousePos.getX(), mousePos.getY());
+ //routeGraph.print(System.err);
+ setDirty();
+ }
+
+ protected boolean shouldEndWithFlag(MouseEvent me) {
+ return shouldEndWithFlag((me.stateMask & MouseEvent.ALT_MASK) != 0);
+ }
+
+ protected boolean shouldEndWithFlag(boolean altPressed) {
+ return altPressed && !isEndTerminalDefined() && createFlags;
+ }
+
+ protected boolean isEndTerminalDefined() {
+ return endTerminal != null;
+ }
+
+ protected boolean isFlagTerminal(TerminalInfo ti) {
+ return ti.e.getElementClass().containsClass(FlagHandler.class);
+ }
+
+
+ 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()
+ .setAttachedToTerminal(endFlag);
+
+ // TerminalPainter must refresh
+ setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
+
+ updateSG(mousePos);
+ }
+ } 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.setAttachedToTerminal(endTerminal);
+
+ if (endTerminalDefined) {
+ cp.setPosition(endTerminal.posDia);
+ } else {
+ cp.setPosition(mousePos);
+ }
+
+ // Force Terminalpainter refresh
+ setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
+
+ updateSG(mousePos);
+ }
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * @param canvasPos
+ * @return
+ */
+ protected ControlPoint newControlPoint(Point2D canvasPos) {
+ return new ControlPoint(canvasPos);
+ }
+
+ protected Triple prepareRouteGraphDelta() {
+ // Prevent route graph connection synchronization from crashing on the
+ // transient route terminal that doesn't exist yet. It is created after
+ // persisting the route line, which is the only purpose of this route
+ // graph synchronization.
+ routeGraph.remove(endRouteTerminal);
+ return Triple.make(beforeRouteGraph, routeGraph, routeGraphDelta);
+ }
+
+ protected void createConnection() {
+ TimeLogger.resetTimeAndLog(getClass(), "createConnection");
+ final ConnectionJudgement judgment = this.connectionJudgment;
+ if (judgment == null) {
+ ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity", null);
+ return;
+ }
+
+ final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
+ final Triple rgs = prepareRouteGraphDelta();
+
+ Simantics.getSession().asyncRequest(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ Resource connection = ElementUtils.getObject(startTerminal.e);
+ if (!rgs.third.isEmpty()) {
+ new RouteGraphConnection(graph, connection).synchronize(graph, rgs.first, rgs.second, rgs.third);
+ }
+ Resource attachToLine = RouteGraphConnection.deserialize(graph, attachedToRouteLine.getData());
+ builder.attachToRouteGraph(graph, judgment, connection, attachToLine, controlPoints, endTerminal, FlagClass.Type.Out);
+ }
+ }, e -> {
+ if (e != null)
+ ExceptionUtils.logAndShowError(e);
+ });
+ }
+
+ protected boolean routePointsAllowed() {
+ return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
+ }
+
+ protected Object canConnect(IElement endElement, Terminal endTerminal) {
+ IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
+ return canConnect(advisor, endElement, endTerminal);
+ }
+
+ protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {
+ if (advisor == null)
+ return ConnectionJudgement.CANBEMADELEGAL;
+ if (alreadyConnected.contains(Pair.make(endElement, endTerminal)))
+ return null;
+ return advisor.canBeConnected(null, startTerminal.e, startTerminal.t, endElement, endTerminal);
+ }
+
+ 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;
+ }
+
+ // ------------------------------------------------------------------------
+
+ static RouteGraphTarget pickRouteGraphConnection(ICanvasContext ctx, IDiagram diagram, Shape pickShape, double pickDistance) {
+ ArrayList elements = new ArrayList();
+ PickRequest req = new PickRequest(pickShape).context(ctx);
+ DiagramUtils.pick(diagram, req, elements);
+ for (Iterator it = elements.iterator(); it.hasNext();) {
+ IElement e = it.next();
+ RouteGraphNode rgn = e.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
+ if (rgn == null || rgn.getRouteGraph() == null)
+ it.remove();
+ }
+ if (elements.isEmpty())
+ return null;
+
+ Rectangle2D pickRect = pickShape.getBounds2D();
+ final double x = pickRect.getCenterX();
+ final double y = pickRect.getCenterY();
+
+ return pickNearestRouteGraphConnection(elements, x, y, pickDistance);
+ }
+
+ private static RouteGraphTarget pickNearestRouteGraphConnection(ArrayList elements, double x, double y, double pd) {
+ Segment nearestSegment = null;
+ RouteLine nearestLine = null;
+ IElement nearestConnection = null;
+
+ double minDistanceSq = Double.MAX_VALUE;
+
+ for (IElement connection : elements) {
+ RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
+ for (RouteLine line : rgn.getRouteGraph().getAllLines()) {
+ ArrayList segments = new ArrayList();
+ line.collectSegments(segments);
+ for (Segment segment : segments) {
+ RoutePoint p1 = segment.p1;
+ RoutePoint p2 = segment.p2;
+
+ double distanceSq = Line2D.ptSegDistSq(p1.getX(), p1.getY(), p2.getX(), p2.getY(), x, y);
+ if (distanceSq < minDistanceSq && distanceSq < Math.pow(pd + rgn.getSelectionStrokeWidth() / 2, 2)) {
+ minDistanceSq = distanceSq;
+ nearestSegment = segment;
+ nearestLine = line;
+ nearestConnection = connection;
+ }
+ }
+ }
+ }
+
+ if (nearestSegment != null) {
+ RoutePoint p1 = nearestSegment.p1;
+ RoutePoint p2 = nearestSegment.p2;
+ double d = Math.pow(p2.getX() - p1.getX(), 2.0) + Math.pow(p2.getY() - p1.getY(), 2.0);
+ Point2D p;
+ if (d == 0) {
+ p = new Point2D.Double(p1.getX(), p1.getY());
+ } else {
+ double u = ((x - p1.getX()) * (p2.getX() - p1.getX()) + (y - p1.getY()) * (p2.getY() - p1.getY())) / d;
+ if (u > 1.0) {
+ p = new Point2D.Double(p2.getX(), p2.getY());
+ } else if (u <= 0.0) {
+ p = new Point2D.Double(p1.getX(), p1.getY());
+ } else {
+ p = new Point2D.Double(p2.getX() * u + p1.getX() * (1.0-u), (p2.getY() * u + p1.getY() * (1.0- u)));
+ }
+ }
+ return new RouteGraphTarget(nearestConnection, nearestConnection.getHint(RouteGraphConnectionClass.KEY_RG_NODE), nearestLine, new Point2D.Double(x, y), p);
+ } else {
+ return null;
+ }
+ }
+
+ static Point2D intersectionPoint(double x, double y, RouteLine line) {
+ Collection points = line.getPoints();
+ if (points.size() < 2)
+ return null;
+ RoutePoint s = line.getBegin();
+ RoutePoint e = line.getEnd();
+ return GeometryUtils.intersectionToLine(s.getX(), s.getY(), e.getX(), e.getY(), x, y);
+ }
+
}
\ No newline at end of file