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