]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java
Improvements to styling of connection lines
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / RouteGraphUtils.java
index de99bd25fb8c626d7ca080161f77e0581681730b..3ff98cd0b1cc115d33687764e650282c707623bb 100644 (file)
@@ -20,6 +20,7 @@ import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -31,21 +32,25 @@ import org.simantics.db.Statement;
 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
 import org.simantics.db.common.request.ResourceRead2;
 import org.simantics.db.common.request.UnaryRead;
+import org.simantics.db.common.utils.ListUtils;
 import org.simantics.db.common.utils.NameUtils;
 import org.simantics.db.exception.DatabaseException;
 import org.simantics.diagram.connection.ConnectionVisuals;
 import org.simantics.diagram.connection.RouteGraph;
-import org.simantics.diagram.connection.RouteGraphConnectionClass;
 import org.simantics.diagram.connection.RouteLine;
 import org.simantics.diagram.connection.RouteNode;
 import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.RouteTerminalPosition;
+import org.simantics.diagram.connection.rendering.AggregateConnectionStyle;
 import org.simantics.diagram.connection.rendering.BasicConnectionStyle;
 import org.simantics.diagram.connection.rendering.ConnectionStyle;
+import org.simantics.diagram.connection.rendering.ExampleConnectionStyle;
 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;
 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.content.EdgeResource;
+import org.simantics.diagram.content.ResourceTerminal;
 import org.simantics.diagram.content.TerminalMap;
 import org.simantics.diagram.query.DiagramRequests;
 import org.simantics.diagram.stubs.DiagramResource;
@@ -55,12 +60,16 @@ import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
 import org.simantics.g2d.canvas.ICanvasContext;
 import org.simantics.g2d.canvas.impl.CanvasContext;
 import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.DataElementMap;
 import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
 import org.simantics.g2d.diagram.impl.ElementDiagram;
 import org.simantics.g2d.element.ElementUtils;
 import org.simantics.g2d.element.IElement;
 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.TerminalLayout;
 import org.simantics.g2d.elementclass.FlagClass.Type;
+import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
 import org.simantics.layer0.Layer0;
 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;
 import org.simantics.scenegraph.utils.GeometryUtils;
@@ -69,12 +78,15 @@ import org.simantics.structural2.modelingRules.CPTerminal;
 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;
 import org.simantics.structural2.modelingRules.IModelingRules;
 import org.simantics.utils.threads.CurrentThread;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import gnu.trove.map.hash.THashMap;
 import gnu.trove.set.hash.THashSet;
 
 public class RouteGraphUtils {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(RouteGraph.class);
     public static boolean DEBUG = false;
 
     public static final ILineEndStyle HEAD  = new ArrowLineEndStyle("fill 2 1 0");
@@ -90,10 +102,10 @@ public class RouteGraphUtils {
         Layer0 L0 = Layer0.getInstance(graph);
         Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf);
         IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.<IModelingRules>instance());
-        return load(graph, diagramRuntime, connection, canvas, diagram, modelingRules, null);
+        return load(graph, diagramRuntime, connection, canvas, diagram, null, modelingRules, null);
     }
 
-    public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, IModelingRules modelingRules, Set<BackendConnection> backendConnections) throws DatabaseException {
+    public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, IElement element, IModelingRules modelingRules, Set<BackendConnection> backendConnections) throws DatabaseException {
 
         DiagramResource DIA = DiagramResource.getInstance(graph);
         StructuralResource2 STR = StructuralResource2.getInstance(graph);
@@ -101,12 +113,21 @@ public class RouteGraphUtils {
         RouteGraph rg = new RouteGraph();
 
         // Default capacity should be enough for common cases.
-        Set<EdgeResource> links = new THashSet<EdgeResource>();
-        Map<Object, RouteNode> nodeByData = new THashMap<Object, RouteNode>();
+        Set<EdgeResource> links = new THashSet<>();
+        Map<Object, RouteNode> nodeByData = new THashMap<>();
 
         // Load all route graph interior RouteNodes: route lines and points
         for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {
             if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {
+                Collection<Resource> areConnected = graph.getObjects(interiorNode, DIA.AreConnected);
+                if (areConnected.size() < 2) {
+                    // Degenerated route line encountered, most likely due to a bug somewhere else.
+                    // Ignoring them because adding them to the RouteGraph structure would cause
+                    // problems during rendering.
+                    LOGGER.warn("Stray RouteLine found: " + NameUtils.getSafeName(graph, interiorNode));
+                    continue;
+                }
+
                 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);
                 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);
                 RouteLine line = rg.addLine(isHorizontal, position);
@@ -114,7 +135,7 @@ public class RouteGraphUtils {
 
                 nodeByData.put( interiorNode, line );
 
-                for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {
+                for (Resource connectedTo : areConnected) {
                     links.add( new EdgeResource(interiorNode, connectedTo) );
                 }
             } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {
@@ -168,7 +189,7 @@ public class RouteGraphUtils {
                         connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
                     connectorToModeledAttachment.put(connector, attachment);
                     if (DEBUG)
-                        System.out.println("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
+                        LOGGER.debug("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
                 } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) {
                     // Secondary: believe flag type
                     attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules, DIA);
@@ -177,7 +198,7 @@ public class RouteGraphUtils {
                             connectorToModeledAttachment = new THashMap<Resource, Resource>(toConnectorStatements.size());
                         connectorToModeledAttachment.put(connector, attachment);
                         if (DEBUG)
-                            System.out.println("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
+                            LOGGER.debug("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")");
                     }
                 }
             }
@@ -190,17 +211,18 @@ public class RouteGraphUtils {
         if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) {
             forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next(), DIA);
             if (DEBUG)
-                System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));
+                LOGGER.debug("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation));
         }
 
         Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); 
+        DataElementMap diagramDataElementMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
 
         // Load all node terminal connections as RouteTerminals
         for (Statement toConnector : toConnectorStatements) {
             Resource connector = toConnector.getObject();
             Resource attachmentRelation = toConnector.getPredicate();
             if (DEBUG)
-                System.out.println("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+                LOGGER.debug("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation));
 
             Statement terminalStm = findTerminalStatement(graph, connection, connector, STR);
             if (terminalStm == null)
@@ -234,19 +256,18 @@ public class RouteGraphUtils {
             if (position.length != 2)
                 position = new double[] { 0, 0 };
 
+            AffineTransform terminalTr = DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement); 
+            final AffineTransform terminalElementTransform = new AffineTransform(terminalTr);
+
             if (DEBUG) {
-                System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));
-                System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate()));
+                LOGGER.debug("terminalStm: " + NameUtils.toString(graph, terminalStm));
+                LOGGER.debug("terminal: " + NameUtils.getURIOrSafeNameInternal(graph, terminalStm.getPredicate()));
+                LOGGER.debug("terminalElement: " + NameUtils.getURIOrSafeNameInternal(graph, terminalElement) + " : " + NameUtils.getURIOrSafeNameInternal(graph, terminalElementType));
+                LOGGER.debug("terminalElementTr: " + terminalTr);
             }
-            AffineTransform terminalElementTr = diagramRuntime != null ?
-                    DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement) : 
-                    DiagramGraphUtil.getWorldTransform(graph, terminalElement);
 
-            if (DEBUG)
-                System.out.println("terminalElementTr: " + terminalElementTr);
-
-            double x = terminalElementTr.getTranslateX();
-            double y = terminalElementTr.getTranslateY();
+            double x = terminalTr.getTranslateX();
+            double y = terminalTr.getTranslateY();
             double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;
             int direction = 0x0;
 
@@ -256,20 +277,20 @@ public class RouteGraphUtils {
             if (att != null) {
                 attachmentRelation = att;
                 if (DEBUG)
-                    System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+                    LOGGER.debug("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
             } else if (forcedAttachmentRelation != null) {
                 attachmentRelation = forcedAttachmentRelation;
                 if (DEBUG)
-                    System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+                    LOGGER.debug("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
             }
             if (DEBUG)
-                System.out.println("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+                LOGGER.debug("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));
 
             // Get element bounds to decide allowed terminal direction(s)
             IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));
             ElementUtils.getElementBounds(te, bounds);
             {
-                Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);
+                Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalTr);
                 bounds.setFrame(shp.getBounds2D());
             }
 
@@ -282,28 +303,26 @@ public class RouteGraphUtils {
             maxx = bounds.getMaxX();
             maxy = bounds.getMaxY();
 
-            AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal);
-            //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal);
+            final ResourceTerminal rt = new ResourceTerminal(terminal);
+            final TerminalLayout tl = te.getElementClass().getSingleItem(TerminalLayout.class);
+            AffineTransform terminalPos = tl.getTerminalPosition(te, rt);
+
             if (terminalPos != null) {
-                if (DEBUG) {
-                    System.out.println("terminalPos: " + terminalPos);
-                    //System.out.println("terminalPos2: " + terminalPos2);
-                }
-                terminalElementTr.concatenate(terminalPos);
+                terminalTr.concatenate(terminalPos);
+                x = terminalTr.getTranslateX();
+                y = terminalTr.getTranslateY();
                 if (DEBUG)
-                    System.out.println("terminalElementTr: " + terminalElementTr);
-                x = terminalElementTr.getTranslateX();
-                y = terminalElementTr.getTranslateY();
+                    LOGGER.debug("terminalPos/Tr: " + terminalPos + ", " + terminalTr);
             }
 
             Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);
             if (allowedDirections != null) {
                 direction |= allowedDirections;
-                direction = rotateDirection(direction, terminalElementTr);
+                direction = rotateDirection(direction, terminalTr);
             } else {
                 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);
             }
-            //System.out.println("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));
+            //LOGGER.debug("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction));
 
             if (backendConnections != null) {
                 backendConnections.add(
@@ -323,10 +342,11 @@ public class RouteGraphUtils {
             // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals.
 
             if (DEBUG)
-                System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));
+                LOGGER.debug("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));
             ILineEndStyle endStyle = loadLineEndStyle(graph, attachmentRelation, connectionType, TAIL);
 
-            RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle);
+            RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle,
+                    new RouteTerminalPositionImpl(diagram, diagramDataElementMap, terminalElement, terminalElementTransform, tl, rt));
             routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );
 
             nodeByData.put( connector, routeTerminal );
@@ -341,7 +361,7 @@ public class RouteGraphUtils {
             RouteNode n1 = nodeByData.get(link.first());
             RouteNode n2 = nodeByData.get(link.second());
             if (n1 == null || n2 == null) {
-                System.err.println("Stray connection link found: " + link.toString(graph));
+                LOGGER.warn("Stray connection link found: " + link.toString(graph));
                 continue;
             }
             rg.link(n1, n2);
@@ -516,6 +536,8 @@ public class RouteGraphUtils {
         }
     }
 
+    private static final ConnectionStyle DEFAULT_CONNECTION_STYLE = new BasicConnectionStyle(Color.BLACK, Color.BLACK, 3, ExampleConnectionStyle.SOLID, ExampleConnectionStyle.SOLID, 8);
+
     /**
      * @param graph
      * @param canvas
@@ -524,13 +546,30 @@ public class RouteGraphUtils {
      * @return
      * @throws DatabaseException
      */
-    protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR) throws DatabaseException {
+    protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR, DiagramResource DIA) throws DatabaseException {
+        Resource connectionStyle = graph.getPossibleObject(connection, DIA.HasConnectionStyle);
         Resource connectionType = null;
-        if (modelingRules != null)
-            connectionType = modelingRules.getConnectionType(graph, connection);
-        if (connectionType == null)
-            connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
-        return readConnectionStyleFromConnectionType(graph, connectionType);
+        if (connectionStyle == null) {
+            if (modelingRules != null)
+                connectionType = modelingRules.getConnectionType(graph, connection);
+            if (connectionType == null)
+                connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
+            connectionStyle = graph.getPossibleObject(connectionType, DIA.HasConnectionStyle);
+        }
+        if (connectionStyle != null) {
+            List<Resource> lineStyles = ListUtils.toList(graph, connectionStyle);
+            if (lineStyles.size() != 1) {
+                AggregateConnectionStyle aggregate = new AggregateConnectionStyle();
+                for (Resource connectionLine : ListUtils.toList(graph, connectionStyle)) {
+                    aggregate.addStyle(readConnectionStyleFromConnectionType(graph, connectionLine));
+                }
+                return aggregate;
+            } else {
+                return readConnectionStyleFromConnectionType(graph, lineStyles.get(0));
+            }
+        } else {
+            return connectionType != null ? readConnectionStyleFromConnectionType(graph, connectionType) : DEFAULT_CONNECTION_STYLE;
+        }
     }
 
     protected static ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException {
@@ -559,9 +598,9 @@ public class RouteGraphUtils {
 
         // Fixed style settings
         Color branchPointColor = Color.BLACK;
-        double branchPointRadius = 0.5;
+        double branchPointRadius = cv != null && cv.branchPointRadius != null ? cv.branchPointRadius : 0.5;
         double degenerateLineLength = 0.8;
-
+        
         Color lineColor = cv != null ? cv.toColor() : null;
         if (lineColor == null)
             lineColor = Color.DARK_GRAY;
@@ -569,6 +608,8 @@ public class RouteGraphUtils {
         if (lineStroke == null)
             lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);
         Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2);
+        double rounding = cv.rounding == null ? 0.0 : cv.rounding;
+        double offset = cv.offset == null ? 0.0 : cv.offset;
 
         return new BasicConnectionStyle(
                 lineColor,
@@ -576,7 +617,9 @@ public class RouteGraphUtils {
                 branchPointRadius,
                 lineStroke,
                 routeLineStroke,
-                degenerateLineLength);
+                degenerateLineLength,
+                rounding,
+                offset);
     }
 
     public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {
@@ -700,4 +743,50 @@ public class RouteGraphUtils {
         }
     }
 
+    private static class RouteTerminalPositionImpl implements RouteTerminalPosition {
+
+        private IDiagram diagram;
+        private DataElementMap dataElementMap;
+        private Resource element;
+        private AffineTransform elementTransform;
+        private TerminalLayout terminalLayout;
+        private Terminal elementTerminal;
+
+        private transient AffineTransform lastTerminalTr;
+        private transient AffineTransform transform;
+
+        public RouteTerminalPositionImpl(IDiagram diagram, DataElementMap dem, Resource element, AffineTransform elementTransform, TerminalLayout terminalLayout, Terminal terminal) {
+            this.diagram = diagram;
+            this.dataElementMap = dem;
+            this.element = element;
+            this.elementTransform = elementTransform;
+            this.terminalLayout = terminalLayout;
+            this.elementTerminal = terminal;
+        }
+
+        @Override
+        public AffineTransform getTransform() {
+            IElement actualElement = dataElementMap.getElement(diagram, element);
+            AffineTransform terminalTr = actualElement != null ? terminalLayout.getTerminalPosition(actualElement, elementTerminal) : null;
+            if (terminalTr == null)
+                return elementTransform;
+
+            // Return cached transform if terminal transform has not changed.
+            AffineTransform result = this.transform;
+            AffineTransform lastTerminalTr = this.lastTerminalTr;
+            if (lastTerminalTr != null) {
+                if (terminalTr.equals(lastTerminalTr))
+                    return result;
+                lastTerminalTr.setTransform(terminalTr);
+            } else {
+                lastTerminalTr = this.lastTerminalTr = new AffineTransform(terminalTr);
+                result = this.transform = new AffineTransform();
+            }
+
+            result.setTransform(elementTransform);
+            result.concatenate(terminalTr);
+            return result;
+        }
+
+    }
 }