--- /dev/null
+package org.simantics.diagram.flag;\r
+\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+import javax.vecmath.Tuple2d;\r
+import javax.vecmath.Vector2d;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Statement;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.request.PossibleTypedParent;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.common.utils.OrderedSetUtils;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.diagram.adapter.RouteGraphUtils;\r
+import org.simantics.diagram.connection.RouteGraph;\r
+import org.simantics.diagram.connection.RouteLine;\r
+import org.simantics.diagram.connection.RouteNode;\r
+import org.simantics.diagram.connection.RoutePoint;\r
+import org.simantics.diagram.connection.RouteTerminal;\r
+import org.simantics.diagram.connection.splitting.SplittedRouteGraph;\r
+import org.simantics.diagram.content.ConnectionUtil;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.diagram.synchronization.graph.AddElement;\r
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
+import org.simantics.diagram.synchronization.graph.RouteGraphModification;\r
+import org.simantics.g2d.elementclass.FlagClass;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.modeling.ModelingResources;\r
+import org.simantics.structural.stubs.StructuralResource2;\r
+\r
+/**\r
+ * A class that handles splitting a route graph connection in two with diagram\r
+ * local flags.\r
+ * \r
+ * The connection splitting process consists of the following steps:\r
+ * <ol>\r
+ * <li>Disconnect the end points of the selected connection edge segment (SEG).\r
+ * An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This\r
+ * operation will always split a valid connection into two separate parts.</li>\r
+ * <li>Calculate the contents of the two separated connection parts (branch\r
+ * points and connectors) and decide which part to leave with the existing\r
+ * connection (=P1) and which part to move into a new connection (=P2). The\r
+ * current strategy is to move the parts that\r
+ * <em>do not contain output terminal connectors</em> into a new connection.\r
+ * This works well with diagram to composite mappings where output terminals\r
+ * generate modules behind connections.</li>\r
+ * <li>Create new connection C' with the same type and STR.HasConnectionType as\r
+ * the original connection C.</li>\r
+ * <li>Copy connection routing settings from C to C'.</li>\r
+ * <li>Move P2 into C'.</li>\r
+ * <li>Create two new flag elements into the same diagram, set their type and\r
+ * interpolate a proper transformation for both along the existing connection\r
+ * line.</li>\r
+ * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>\r
+ * <li>Join the flags together.</li>\r
+ * </ol>\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ * @author Hannu Niemistö\r
+ */\r
+public class RouteGraphConnectionSplitter {\r
+\r
+ private final static boolean DEBUG = false;\r
+\r
+ Layer0 L0;\r
+ DiagramResource DIA;\r
+ StructuralResource2 STR;\r
+ ModelingResources MOD;\r
+ SerialisationSupport ss;\r
+\r
+ public RouteGraphConnectionSplitter(ReadGraph graph) {\r
+ this.L0 = Layer0.getInstance(graph);\r
+ this.DIA = DiagramResource.getInstance(graph);\r
+ this.STR = StructuralResource2.getInstance(graph);\r
+ this.MOD = ModelingResources.getInstance(graph);\r
+ this.ss = graph.getService(SerialisationSupport.class);\r
+ }\r
+ \r
+ public void split(WriteGraph graph, Resource connection, RouteGraph rg, Point2D splitCanvasPos) throws DatabaseException {\r
+ // Create modification writer\r
+ RouteGraphModification modis = new RouteGraphModification(ss, rg);\r
+ TObjectIntHashMap<RouteNode> idMap = modis.getIdMap();\r
+\r
+ // Find the edge to disconnect in the graph.\r
+ // Bisect the nearest route line.\r
+ RouteLine line = SplittedRouteGraph.findNearestLine(rg, splitCanvasPos);\r
+ if (DEBUG)\r
+ rg.print();\r
+ if (line == null)\r
+ return;\r
+ if (DEBUG) {\r
+ line.print(System.out);\r
+ for (RoutePoint rp : line.getPoints())\r
+ System.out.println("RP: " + rp.getX() + ", " + rp.getY());\r
+ }\r
+\r
+ // Get exact intersection point on the line\r
+ double isectX = splitCanvasPos.getX();\r
+ double isectY = splitCanvasPos.getY();\r
+ SplittedRouteGraph srg;\r
+ if (line.isHorizontal()) {\r
+ isectY = line.getPosition();\r
+ srg = rg.splitGraph(line, isectX);\r
+ }\r
+ else {\r
+ isectX = line.getPosition();\r
+ srg = rg.splitGraph(line, isectY);\r
+ }\r
+ if (DEBUG)\r
+ System.out.println(srg);\r
+ \r
+ // Disconnect\r
+ if(rg.isSimpleConnection()) {\r
+ RouteNode na = srg.terminals1.iterator().next();\r
+ RouteNode nb = srg.terminals2.iterator().next();\r
+ Resource a = ss.getResource((Long)na.getData());\r
+ Resource b = ss.getResource((Long)nb.getData());\r
+ graph.deny(a, DIA.AreConnected, b);\r
+ modis.addModi(new RouteGraphModification.RemoveLink(\r
+ idMap.get(na), idMap.get(nb)\r
+ ));\r
+ }\r
+ else if(srg.splitLine.isTransient()) {\r
+ RouteTerminal terminal = srg.splitLine.getTerminal();\r
+ Resource connector = ss.getResource((Long)terminal.getData());\r
+ graph.deny(connector, DIA.AreConnected);\r
+ modis.addModi(new RouteGraphModification.RemoveLink(\r
+ idMap.get(terminal), idMap.get(terminal.getLine())\r
+ ));\r
+ }\r
+ else {\r
+ graph.deny(ss.getResource((Long)srg.splitLine.getData()));\r
+ // TODO remove links\r
+ modis.addModi(new RouteGraphModification.RemoveLine(\r
+ idMap.get(srg.splitLine)\r
+ ));\r
+ }\r
+ \r
+ ArrayList<Resource> interfaceNodes1Resources = new ArrayList<Resource>(srg.interfaceNodes1.size());\r
+ for(RouteNode n : srg.interfaceNodes1)\r
+ interfaceNodes1Resources.add(ss.getResource((Long)n.getData()));\r
+ ArrayList<Resource> interfaceNodes2Resources = new ArrayList<Resource>(srg.interfaceNodes2.size());\r
+ for(RouteNode n : srg.interfaceNodes2)\r
+ interfaceNodes2Resources.add(ss.getResource((Long)n.getData()));\r
+ \r
+ ArrayList<Resource> lines2Resources = new ArrayList<Resource>(srg.lines2.size());\r
+ for(RouteLine n : srg.lines2)\r
+ lines2Resources.add(ss.getResource((Long)n.getData()));\r
+ \r
+ ArrayList<Resource> terminals1Resources = new ArrayList<Resource>(srg.terminals1.size());\r
+ for(RouteTerminal n : srg.terminals1)\r
+ terminals1Resources.add(ss.getResource((Long)n.getData()));\r
+ ArrayList<Resource> terminals2Resources = new ArrayList<Resource>(srg.terminals2.size());\r
+ for(RouteTerminal n : srg.terminals2)\r
+ terminals2Resources.add(ss.getResource((Long)n.getData()));\r
+ doSplit(graph, connection,\r
+ interfaceNodes1Resources,\r
+ interfaceNodes2Resources,\r
+ lines2Resources,\r
+ terminals1Resources,\r
+ terminals2Resources,\r
+ line.isHorizontal(),\r
+ isectX, isectY);\r
+ modis.addModi(new RouteGraphModification.Split(\r
+ modis.toIds(interfaceNodes1Resources),\r
+ modis.toIds(interfaceNodes2Resources),\r
+ modis.toIds(lines2Resources),\r
+ modis.toIds(terminals1Resources),\r
+ modis.toIds(terminals2Resources),\r
+ line.isHorizontal(),\r
+ isectX, isectY\r
+ ));\r
+ \r
+ }\r
+ \r
+ public void doSplit(WriteGraph graph, \r
+ Resource connection,\r
+ ArrayList<Resource> interfaceNodes1Resources,\r
+ ArrayList<Resource> interfaceNodes2Resources,\r
+ ArrayList<Resource> lines2Resources,\r
+ ArrayList<Resource> terminals1Resources,\r
+ ArrayList<Resource> terminals2Resources,\r
+ boolean isHorizontal, \r
+ double isectX, double isectY) throws DatabaseException {\r
+\r
+ if (DEBUG) {\r
+ System.out.println("doSplit:");\r
+ System.out.println(NameUtils.getSafeName(graph, connection, true));\r
+ for (Resource i : interfaceNodes1Resources)\r
+ System.out.println("i1: " + NameUtils.getSafeName(graph, i, true));\r
+ for (Resource i : interfaceNodes2Resources)\r
+ System.out.println("i2: " + NameUtils.getSafeName(graph, i, true));\r
+ for (Resource l : lines2Resources)\r
+ System.out.println("l2r: " + NameUtils.getSafeName(graph, l, true));\r
+ for (Resource t : terminals1Resources)\r
+ System.out.println("t1: " + NameUtils.getSafeName(graph, t, true));\r
+ for (Resource t : terminals2Resources)\r
+ System.out.println("t2: " + NameUtils.getSafeName(graph, t, true));\r
+ System.out.println("is horizontal: " + isHorizontal);\r
+ System.out.println("@(x,y): " + isectX + ", " + isectY);\r
+ }\r
+\r
+ ConnectionUtil cu = new ConnectionUtil(graph);\r
+ Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);\r
+\r
+ Resource connectionType = graph.getSingleType(connection, DIA.Connection);\r
+ Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);\r
+ Resource newConnection = cu.newConnection(diagram, connectionType);\r
+ if (hasConnectionType != null)\r
+ graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);\r
+\r
+ // Give running name to connection increment the counter attached to the diagram.\r
+ AddElement.claimFreshElementName(graph, diagram, newConnection);\r
+\r
+ // WORKAROUND for mapping problems:\r
+ // If any terminal of the split connection contains a flag, make sure their STR.Joins relations are all removed\r
+ // to give mapping a chance to fix them properly.\r
+ removeFlagJoins(graph, cu, connection, terminals1Resources);\r
+ removeFlagJoins(graph, cu, connection, terminals2Resources);\r
+\r
+ // Move route nodes to correct connections\r
+ for(Resource rn : lines2Resources) {\r
+ // TODO: use same predicate that was removed\r
+ graph.denyStatement(connection, DIA.HasInteriorRouteNode, rn);\r
+ graph.claim(newConnection, DIA.HasInteriorRouteNode, rn);\r
+ }\r
+ for(Resource rn : terminals2Resources) {\r
+ Statement stat = graph.getSingleStatement(rn, DIA.IsConnectorOf);\r
+ Resource predicate = stat.getPredicate();\r
+ graph.deny(rn, predicate);\r
+ graph.claim(rn, predicate, newConnection);\r
+ }\r
+\r
+ // 1 = output, 2 = input\r
+ FlagClass.Type type1, type2;\r
+\r
+ FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);\r
+ String commonLabel = scheme.generateLabel(graph, diagram);\r
+\r
+ // Create flags and connect both disconnected ends to them.\r
+ Vector2d pos1, pos2;\r
+ double theta;\r
+ double flagDist = 3.0;\r
+ if(isHorizontal) {\r
+ theta = 0.0;\r
+ pos1 = new Vector2d(isectX-flagDist, isectY);\r
+ pos2 = new Vector2d(isectX+flagDist, isectY);\r
+ }\r
+ else {\r
+ theta = Math.PI*0.5;\r
+ pos1 = new Vector2d(isectX, isectY-flagDist);\r
+ pos2 = new Vector2d(isectX, isectY+flagDist);\r
+ }\r
+\r
+ // Chooses flag directions\r
+ {\r
+ @SuppressWarnings("unused")\r
+ int inputs1 = 0, outputs1 = 0;\r
+ for(Resource connector : terminals1Resources) {\r
+ if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))\r
+ ++inputs1;\r
+ else\r
+ ++outputs1;\r
+ }\r
+ @SuppressWarnings("unused")\r
+ int inputs2 = 0, outputs2 = 0;\r
+ for(Resource connector : terminals2Resources) {\r
+ if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))\r
+ ++inputs2;\r
+ else\r
+ ++outputs2;\r
+ }\r
+ \r
+ if(outputs1 == 0) {\r
+ type1 = FlagClass.Type.In;\r
+ type2 = FlagClass.Type.Out;\r
+ theta += Math.PI;\r
+ }\r
+ else {\r
+ type1 = FlagClass.Type.Out;\r
+ type2 = FlagClass.Type.In;\r
+ }\r
+ if (DEBUG) {\r
+ System.out.println("inputs1: " + inputs1);\r
+ System.out.println("outputs1: " + outputs1);\r
+ System.out.println("=> type1: " + type1);\r
+ System.out.println("inputs2: " + inputs2);\r
+ System.out.println("outputs2: " + outputs2);\r
+ System.out.println("=> type2: " + type2);\r
+ }\r
+ }\r
+ Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);\r
+ Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);\r
+ if (DEBUG) {\r
+ System.out.println("FLAG1: " + NameUtils.getSafeName(graph, flag1, true));\r
+ System.out.println("FLAG2: " + NameUtils.getSafeName(graph, flag2, true));\r
+ }\r
+\r
+// System.out.println("conn1: " + NameUtils.getSafeLabel(graph, type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));\r
+// System.out.println("conn2: " + NameUtils.getSafeLabel(graph, type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));\r
+ Resource flagConnector1 = cu.newConnector(connection, \r
+ type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);\r
+ Resource flagConnector2 = cu.newConnector(newConnection, \r
+ type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);\r
+ graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);\r
+ graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);\r
+\r
+ double position = isHorizontal ? isectY : isectX;\r
+ connectFlag(graph, isHorizontal, position, connection, flagConnector1, \r
+ interfaceNodes1Resources);\r
+ connectFlag(graph, isHorizontal, position, newConnection, flagConnector2, \r
+ interfaceNodes2Resources);\r
+\r
+ FlagUtil.join(graph, flag1, flag2);\r
+\r
+ // Move mapping relations to new connection if necessary\r
+ if(type1 == FlagClass.Type.In) {\r
+ moveStatements(graph, connection, newConnection, MOD.ElementToComponent);\r
+ moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnection);\r
+ moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnectionSpecial);\r
+ FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(newConnection, MOD.DiagramConnectionToConnection));\r
+ }\r
+ else\r
+ FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));\r
+ }\r
+\r
+ /**\r
+ * A workaround for problems with mapping not removing the necessary\r
+ * STR.Joins relations from flags after a split.\r
+ * \r
+ * @param graph\r
+ * @param cu\r
+ * @param connection\r
+ * @param connectors\r
+ * @throws DatabaseException\r
+ */\r
+ private void removeFlagJoins(WriteGraph graph, ConnectionUtil cu, Resource connection, ArrayList<Resource> connectors) throws DatabaseException {\r
+ for (Resource connector : connectors) {\r
+ Resource e = cu.getConnectedComponent(connection, connector);\r
+ if (graph.isInstanceOf(e, DIA.Flag)) {\r
+ Resource diagram = graph.syncRequest(new PossibleTypedParent(e, DIA.Diagram));\r
+ if (diagram == null)\r
+ continue;\r
+ for (Resource join : graph.getObjects(e, DIA.FlagIsJoinedBy)) {\r
+ Collection<Resource> joinsComposites = graph.getObjects(join, STR.JoinsComposite);\r
+ if (joinsComposites.size() == 1) {\r
+ // Only remove joins that are internal to a diagram.\r
+ graph.deny(join, STR.Joins);\r
+ } else if (joinsComposites.size() == 2) {\r
+ // Only remove the joins relations that refer to\r
+ // connections that are part of the same diagram.\r
+ for (Resource joins : graph.getObjects(join, STR.Joins)) {\r
+ Resource diagramConnection = graph.getPossibleObject(joins, MOD.ConnectionToDiagramConnection);\r
+ if (diagramConnection == null)\r
+ continue;\r
+ Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));\r
+ if (diagram.equals(partOfDiagram)) {\r
+ graph.deny(join, STR.Joins, joins);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private static void moveStatements(WriteGraph graph, Resource from, Resource to, Resource relation) throws DatabaseException {\r
+ if(from.equals(to))\r
+ return;\r
+ for(Statement stat : graph.getStatements(from, relation))\r
+ if(stat.getSubject().equals(from))\r
+ graph.claim(to, stat.getPredicate(), stat.getObject());\r
+ graph.deny(from, relation);\r
+ }\r
+ \r
+ private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection<Resource> interfaceNodes) \r
+ throws DatabaseException {\r
+ if(interfaceNodes.size() > 1) {\r
+ Resource routeLine = graph.newResource();\r
+ graph.claim(routeLine, L0.InstanceOf, DIA.RouteLine);\r
+ graph.claim(connection, DIA.HasInteriorRouteNode, routeLine);\r
+ graph.claimLiteral(routeLine, DIA.IsHorizontal, isHorizontal);\r
+ graph.claimLiteral(routeLine, DIA.HasPosition, position);\r
+ graph.claim(routeLine, DIA.AreConnected, flagConnector);\r
+ flagConnector = routeLine;\r
+ }\r
+ for(Resource rn : interfaceNodes) {\r
+ graph.claim(flagConnector, DIA.AreConnected, rn);\r
+ }\r
+ }\r
+\r
+ private AffineTransform getFlagTransform(Tuple2d pos, double theta) {\r
+ AffineTransform at = AffineTransform.getTranslateInstance(pos.x, pos.y);\r
+ at.rotate(theta);\r
+ return at;\r
+ }\r
+\r
+ private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {\r
+ DiagramResource DIA = DiagramResource.getInstance(graph);\r
+\r
+ Resource flag = graph.newResource();\r
+ graph.claim(flag, L0.InstanceOf, null, DIA.Flag);\r
+ AddElement.claimFreshElementName(graph, diagram, flag);\r
+ graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);\r
+ \r
+ DiagramGraphUtil.setTransform(graph, flag, tr);\r
+ if (type != null)\r
+ FlagUtil.setFlagType(graph, flag, type);\r
+\r
+ if (label != null)\r
+ graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);\r
+\r
+ OrderedSetUtils.add(graph, diagram, flag);\r
+ return flag;\r
+ }\r
+\r
+ public static void splitConnection(WriteGraph graph, Resource connection, double x, double y) throws DatabaseException {\r
+ RouteGraph rg = RouteGraphUtils.load(graph, null, connection);\r
+ new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y));\r
+ }\r
+}
\ No newline at end of file