Improved the positioning of issue decoration for connection elements 02/3202/2
authorJussi Koskela <jussi.koskela@semantum.fi>
Thu, 5 Sep 2019 10:06:16 +0000 (13:06 +0300)
committerJussi Koskela <jussi.koskela@semantum.fi>
Thu, 5 Sep 2019 10:09:25 +0000 (13:09 +0300)
Shows the issue decorator at a point that belongs to the connection and
is closest to the center of its bounding box. Also fixed the updating of
position.

gitlab #372

Change-Id: Icf948997a9a2ce11a6420d83286144328af4e877

bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteGraph.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/IssueDecorationStyle.java

index 62d3de68fae5f110a6b08eb80dcc7558ca34df80..249399d0a8b5b7b6c875c68d8464fda198961160 100644 (file)
  *******************************************************************************/
 package org.simantics.diagram.connection;
 
-import gnu.trove.list.array.TDoubleArrayList;
-import gnu.trove.map.hash.THashMap;
-import gnu.trove.map.hash.TObjectIntHashMap;
-import gnu.trove.set.hash.THashSet;
-
 import java.awt.geom.Line2D;
 import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.io.PrintStream;
 import java.io.Serializable;
@@ -34,6 +30,11 @@ import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
 import org.simantics.diagram.connection.segments.Segment;
 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
 
+import gnu.trove.list.array.TDoubleArrayList;
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TObjectIntHashMap;
+import gnu.trove.set.hash.THashSet;
+
 public class RouteGraph implements Serializable {
 
     private static final long serialVersionUID = 2004022454972623908L;
@@ -1474,6 +1475,46 @@ public class RouteGraph implements Serializable {
             routeLine.collectSegments(segments);
         return segments;
     }
+    
+    public Segment findNearestSegment(double x, double y) {
+        Segment nearest = null;
+        double minDistanceSq = Double.MAX_VALUE;
+
+        for (Segment segment : getSegments()) {
+            RoutePoint p1 = segment.p1;
+            RoutePoint p2 = segment.p2;
+
+            double distanceSq = Line2D.ptSegDistSq(p1.x, p1.y, p2.x, p2.y, x, y);
+            if (distanceSq < minDistanceSq) {
+                minDistanceSq = distanceSq;
+                nearest = segment;
+            }
+        }
+        return nearest;
+    }
+
+    public Point2D findNearestPoint(double x, double y) {
+        Segment nearest = findNearestSegment(x, y);
+        if (nearest == null) return null;
+
+        RoutePoint p1 = nearest.p1;
+        RoutePoint p2 = nearest.p2;
+
+        double d = Math.pow(p2.x - p1.x, 2.0) + Math.pow(p2.y - p1.y, 2.0);
+
+        if (d == 0) {
+            return new Point2D.Double(p1.x, p1.y);
+        } else {
+            double u = ((x - p1.x) * (p2.x - p1.x) + (y - p1.y) * (p2.y - p1.y)) / d;
+            if (u > 1.0) {
+                return new Point2D.Double(p2.x, p2.y);
+            } else if (u <= 0.0) {
+                return new Point2D.Double(p1.x, p1.y);
+            } else {
+                return new Point2D.Double(p2.x * u + p1.x * (1.0-u), (p2.y * u + p1.y * (1.0- u)));
+            }
+        }
+    }
 
     public Path2D getPath2D() {
         Path2D result = new Path2D.Double();
index d473ff46df044a4db03b1bb694fb23e5bbf3ca3c..68210f9e263f5c48e51eb10bbbc30f0324b85244 100644 (file)
 package org.simantics.modeling.ui.diagram.style;
 
 import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -25,9 +28,13 @@ import org.simantics.db.common.procedure.adapter.TransientCacheListener;
 import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.layer0.variable.Variable;
 import org.simantics.db.layer0.variable.Variables;
+import org.simantics.diagram.connection.RouteGraph;
 import org.simantics.diagram.elements.DecorationSVGNode;
 import org.simantics.diagram.elements.SVGNode;
+import org.simantics.diagram.handler.Paster;
+import org.simantics.diagram.handler.Paster.RouteLine;
 import org.simantics.diagram.profile.StyleBase;
+import org.simantics.diagram.stubs.DiagramResource;
 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
 import org.simantics.issues.Severity;
 import org.simantics.issues.common.IssueResourcesContexts;
@@ -36,10 +43,13 @@ import org.simantics.modeling.ModelingResources;
 import org.simantics.modeling.ui.Activator;
 import org.simantics.modeling.ui.diagram.style.IssueDecorationStyle.IssueResult;
 import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
 import org.simantics.scenegraph.g2d.nodes.Decoration;
+import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
 import org.simantics.scenegraph.profile.EvaluationContext;
 import org.simantics.scenegraph.profile.common.ProfileVariables;
 import org.simantics.scenegraph.utils.NodeUtil;
+import org.simantics.structural.stubs.StructuralResource2;
 import org.simantics.utils.datastructures.map.Tuple;
 
 
@@ -74,7 +84,6 @@ public class IssueDecorationStyle extends StyleBase<IssueResult> {
             return null;
 
         List<Resource> contexts = getContexts(graph, element);
-        AffineTransform transform = DiagramGraphUtil.getAffineTransform(graph, element);
         Map<Severity, List<Resource>> issuesBySeverity = graph.syncRequest(new ListModelIssuesBySeverity(model, true, true, Severity.NOTE),
                 TransientCacheListener.<Map<Severity, List<Resource>>>instance());
 
@@ -82,14 +91,43 @@ public class IssueDecorationStyle extends StyleBase<IssueResult> {
             List<Resource> issues = issuesBySeverity.get(severity);
             if (issues != null) {
                 Set<Resource> issueContexts = graph.syncRequest(new IssueResourcesContexts(issues));
-                if (!Collections.disjoint(issueContexts, contexts))
-                    return new IssueResult(severity, transform);
+                if (!Collections.disjoint(issueContexts, contexts)) {
+                    return new IssueResult(severity, getIdentifier(graph, runtimeDiagram, element));
+                }
             }
         }
 
         return null;
     }
 
+    private static Object getIdentifier(ReadGraph graph, Resource runtimeDiagram, Resource element) throws DatabaseException {
+        DiagramResource DIA = DiagramResource.getInstance(graph);
+        StructuralResource2 STR = StructuralResource2.getInstance(graph);
+        if (graph.isInstanceOf(element, DIA.RouteGraphConnection)) {
+            Collection<Resource> connectors = graph.getObjects(element, DIA.HasConnector);
+            Collection<Resource> routeNodes = graph.getObjects(element, DIA.HasInteriorRouteNode);
+
+            // This is needed to make this query result change every time the underlying element changes visually.
+            Set<Object> identifier = new HashSet<Object>(connectors.size() + routeNodes.size());
+
+            for (Resource connector : connectors) {
+                for (Resource connectedTo : graph.getObjects(connector, STR.Connects)) {
+                    if (!connectedTo.equals(element)) {
+                        AffineTransform at = DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, connectedTo, DIA.HasDynamicTransform, false);
+                        identifier.add(at);
+                    }
+                }
+            }
+            for (Resource routeLine : routeNodes) {
+                RouteLine rl = Paster.readRouteLine(graph, routeLine);
+                identifier.add(rl);
+            }
+            return identifier;
+        } else {
+            return DiagramGraphUtil.getAffineTransform(graph, element);
+        }
+    }
+
     @Override
     public void applyStyleForNode(EvaluationContext observer, INode node, IssueResult result) {
         if (result == null) {
@@ -117,6 +155,18 @@ public class IssueDecorationStyle extends StyleBase<IssueResult> {
     protected AffineTransform getDecorationPosition(INode node) {
        Rectangle2D bounds = NodeUtil.getLocalBounds(node, Decoration.class);
 
+        if (node instanceof ConnectionNode) {
+            for (INode child : ((ConnectionNode)node).getSortedNodes()) {
+                if (child instanceof RouteGraphNode) {
+                    RouteGraphNode rgn = (RouteGraphNode) child;
+                    RouteGraph rg = rgn.getRouteGraph();
+                    Point2D nearest = rg.findNearestPoint(bounds.getCenterX(), bounds.getCenterY());
+                    if (nearest != null) {
+                        return AffineTransform.getTranslateInstance(nearest.getX(), nearest.getY());
+                    }
+                }
+            }
+        }
         double tx = bounds.getX();
         double ty = bounds.getY();
         return AffineTransform.getTranslateInstance(tx, ty);
@@ -148,8 +198,8 @@ public class IssueDecorationStyle extends StyleBase<IssueResult> {
      * element moves.
      */
     public static class IssueResult extends Tuple {
-        public IssueResult(Severity severity, AffineTransform transform) {
-            super(severity, transform);
+        public IssueResult(Severity severity, Object identifier) {
+            super(severity, identifier);
         }
         public Severity getSeverity() {
             return (Severity) getField(0);