X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fparticipant%2FRouteGraphConnectTool.java;fp=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fparticipant%2FRouteGraphConnectTool.java;h=af1f48f5d89d42dc253257995bd09cc8cddacc25;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;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
new file mode 100644
index 000000000..af1f48f5d
--- /dev/null
+++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/RouteGraphConnectTool.java
@@ -0,0 +1,905 @@
+/*******************************************************************************
+ * 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