X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fflag%2FSplitter.java;h=a5220f99c8a1ce7678d6b7ead492de954127427d;hp=6303f80c1be21d565431fcf3914f2199a0ff4afa;hb=7c02b1deb5b7b3d098db37b1a0a737c839483d61;hpb=969bd23cab98a79ca9101af33334000879fb60c5 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/Splitter.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/Splitter.java index 6303f80c1..a5220f99c 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/Splitter.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/Splitter.java @@ -1,257 +1,248 @@ -package org.simantics.diagram.flag; - -import java.awt.geom.AffineTransform; -import java.awt.geom.Line2D; -import java.awt.geom.Point2D; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.HashSet; -import java.util.Set; - -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.utils.OrderedSetUtils; -import org.simantics.db.exception.DatabaseException; -import org.simantics.db.exception.ServiceException; -import org.simantics.diagram.content.ConnectionUtil; -import org.simantics.diagram.content.EdgeResource; -import org.simantics.diagram.stubs.DiagramResource; -import org.simantics.diagram.synchronization.graph.AddElement; -import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; -import org.simantics.g2d.element.IElement; -import org.simantics.g2d.elementclass.FlagClass; -import org.simantics.g2d.elementclass.FlagClass.Type; -import org.simantics.layer0.Layer0; -import org.simantics.modeling.ModelingResources; -import org.simantics.structural.stubs.StructuralResource2; - -/** - * A class that handles splitting a 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 - */ -public class Splitter { - - Layer0 L0; - DiagramResource DIA; - StructuralResource2 STR; - ModelingResources MOD; - - public Splitter(ReadGraph graph) { - this.L0 = Layer0.getInstance(graph); - this.DIA = DiagramResource.getInstance(graph); - this.STR = StructuralResource2.getInstance(graph); - this.MOD = ModelingResources.getInstance(graph); - } - - public void split(WriteGraph graph, IElement edgeElement, EdgeResource edge, Point2D splitCanvasPos) throws DatabaseException { - ConnectionUtil cu = new ConnectionUtil(graph); - - Resource connection = ConnectionUtil.getConnection(graph, edge); - Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram); - - // Disconnect the edge to calculate the two parts that remain. - cu.disconnect(edge); - - Splitter.Parts parts1 = Parts.calculate(graph, edge.first()); - Splitter.Parts parts2 = Parts.calculate(graph, edge.second()); - - // Resolve which part contains the "output" and which contains - // "input" to properly position the created flags. - Splitter.Parts moveToNewConnection = parts2; - Resource keepConnector = edge.first(); - Resource moveToNewConnectionConnector = edge.second(); - - boolean inputsOnly1 = parts1.hasInputsOnly(graph); - boolean inputsOnly2 = parts2.hasInputsOnly(graph); - - if (inputsOnly1 && inputsOnly2) { - // Let it be, can't really do better with this information. - } else if (inputsOnly1) { - // Parts1 has input connectors only, therefore we should - // move those to the new connection instead of parts2. - moveToNewConnection = parts1; - keepConnector = edge.second(); - moveToNewConnectionConnector = edge.first(); - } - - 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); - - // Copy connection routing - for (Statement routing : graph.getStatements(connection, DIA.Routing)) - graph.claim(newConnection, routing.getPredicate(), newConnection); - - for (Statement stm : moveToNewConnection.parts) { - graph.deny(stm); - graph.claim(stm.getSubject(), stm.getPredicate(), newConnection); - } - - // 1 = output, 2 = input - Type type1 = FlagClass.Type.Out; - Type type2 = FlagClass.Type.In; - - // Resolve the "positive" direction of the clicked edge to be split. - Line2D nearestEdge= ConnectionUtil.resolveNearestEdgeLineSegment(splitCanvasPos, edgeElement); - - // Calculate split position and edge line nearest intersection point - // ab = normalize( vec(a -> b) ) - // ap = vec(a -> split pos) - Vector2d ab = new Vector2d(nearestEdge.getX2() - nearestEdge.getX1(), nearestEdge.getY2() - nearestEdge.getY1()); - Vector2d ap = new Vector2d(splitCanvasPos.getX() - nearestEdge.getX1(), splitCanvasPos.getY() - nearestEdge.getY1()); - double theta = Math.atan2(ab.y, ab.x); - ab.normalize(); - - // intersection = a + ab*(ap.ab) - Vector2d intersection = new Vector2d(ab); - intersection.scale(ap.dot(ab)); - intersection.add(new Vector2d(nearestEdge.getX1(), nearestEdge.getY1())); - - // Offset flag positions from the intersection point. - Vector2d pos1 = new Vector2d(intersection); - Vector2d pos2 = new Vector2d(intersection); - - // TODO: improve logic for flag positioning, flags just move on the nearest line without boundaries - ab.normalize(); - ab.scale(5); - pos2.add(ab); - ab.negate(); - pos1.add(ab); - - FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph); - String commonLabel = scheme.generateLabel(graph, diagram); - - // Create flags and connect both disconnected ends to them. - Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel); - Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel); - - Resource flagConnector1 = cu.newConnector(connection, DIA.HasArrowConnector); - Resource flagConnector2 = cu.newConnector(newConnection, DIA.HasPlainConnector); - graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1); - graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2); - - graph.claim(flagConnector1, DIA.AreConnected, keepConnector); - graph.claim(flagConnector2, DIA.AreConnected, moveToNewConnectionConnector); - - FlagUtil.join(graph, flag1, flag2); - } - - 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; - } - - static class Parts { - DiagramResource DIA; - Set parts = new HashSet(); - - private Parts(ReadGraph graph) { - this.DIA = DiagramResource.getInstance(graph); - } - - public int size() { - return parts.size(); - } - - public static Splitter.Parts calculate(ReadGraph graph, Resource routeNode) throws DatabaseException { - DiagramResource DIA = DiagramResource.getInstance(graph); - - Splitter.Parts p = new Parts(graph); - Deque todo = new ArrayDeque(); - Set visited = new HashSet(); - - todo.add(routeNode); - while (!todo.isEmpty()) { - Resource part = todo.poll(); - if (!visited.add(part)) - continue; - - todo.addAll(graph.getObjects(part, DIA.AreConnected)); - - Statement toConnection = graph.getPossibleStatement(part, DIA.IsConnectorOf); - if (toConnection == null) - toConnection = graph.getPossibleStatement(part, DIA.HasInteriorRouteNode_Inverse); - if (toConnection == null) - continue; - - p.parts.add(toConnection); - } - - return p; - } - - public boolean hasInputsOnly(ReadGraph graph) throws ServiceException { - return hasInputs(graph) && !hasOutputs(graph); - } - - public boolean hasInputs(ReadGraph graph) throws ServiceException { - return hasRelations(graph, DIA.IsArrowConnectorOf); - } - - public boolean hasOutputs(ReadGraph graph) throws ServiceException { - return hasRelations(graph, DIA.IsPlainConnectorOf); - } - - public boolean hasRelations(ReadGraph graph, Resource relation) throws ServiceException { - for (Statement stm : parts) - if (graph.isSubrelationOf(stm.getPredicate(), relation)) - return true; - return false; - } - - } - +package org.simantics.diagram.flag; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.math3.geometry.euclidean.twod.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.utils.OrderedSetUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.exception.ServiceException; +import org.simantics.diagram.content.ConnectionUtil; +import org.simantics.diagram.content.EdgeResource; +import org.simantics.diagram.stubs.DiagramResource; +import org.simantics.diagram.synchronization.graph.AddElement; +import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.elementclass.FlagClass; +import org.simantics.g2d.elementclass.FlagClass.Type; +import org.simantics.layer0.Layer0; +import org.simantics.modeling.ModelingResources; +import org.simantics.structural.stubs.StructuralResource2; + +/** + * A class that handles splitting a 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 + */ +public class Splitter { + + Layer0 L0; + DiagramResource DIA; + StructuralResource2 STR; + ModelingResources MOD; + + public Splitter(ReadGraph graph) { + this.L0 = Layer0.getInstance(graph); + this.DIA = DiagramResource.getInstance(graph); + this.STR = StructuralResource2.getInstance(graph); + this.MOD = ModelingResources.getInstance(graph); + } + + public void split(WriteGraph graph, IElement edgeElement, EdgeResource edge, Point2D splitCanvasPos) throws DatabaseException { + ConnectionUtil cu = new ConnectionUtil(graph); + + Resource connection = ConnectionUtil.getConnection(graph, edge); + Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram); + + // Disconnect the edge to calculate the two parts that remain. + cu.disconnect(edge); + + Splitter.Parts parts1 = Parts.calculate(graph, edge.first()); + Splitter.Parts parts2 = Parts.calculate(graph, edge.second()); + + // Resolve which part contains the "output" and which contains + // "input" to properly position the created flags. + Splitter.Parts moveToNewConnection = parts2; + Resource keepConnector = edge.first(); + Resource moveToNewConnectionConnector = edge.second(); + + boolean inputsOnly1 = parts1.hasInputsOnly(graph); + boolean inputsOnly2 = parts2.hasInputsOnly(graph); + + if (inputsOnly1 && inputsOnly2) { + // Let it be, can't really do better with this information. + } else if (inputsOnly1) { + // Parts1 has input connectors only, therefore we should + // move those to the new connection instead of parts2. + moveToNewConnection = parts1; + keepConnector = edge.second(); + moveToNewConnectionConnector = edge.first(); + } + + 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); + + // Copy connection routing + for (Statement routing : graph.getStatements(connection, DIA.Routing)) + graph.claim(newConnection, routing.getPredicate(), newConnection); + + for (Statement stm : moveToNewConnection.parts) { + graph.deny(stm); + graph.claim(stm.getSubject(), stm.getPredicate(), newConnection); + } + + // 1 = output, 2 = input + Type type1 = FlagClass.Type.Out; + Type type2 = FlagClass.Type.In; + + // Resolve the "positive" direction of the clicked edge to be split. + Line2D nearestEdge= ConnectionUtil.resolveNearestEdgeLineSegment(splitCanvasPos, edgeElement); + + // Calculate split position and edge line nearest intersection point + // ab = normalize( vec(a -> b) ) + // ap = vec(a -> split pos) + Vector2D a = new Vector2D(nearestEdge.getX1(), nearestEdge.getY1()); + Vector2D ab = new Vector2D(nearestEdge.getX2() - nearestEdge.getX1(), nearestEdge.getY2() - nearestEdge.getY1()); + Vector2D ap = new Vector2D(splitCanvasPos.getX() - nearestEdge.getX1(), splitCanvasPos.getY() - nearestEdge.getY1()); + double theta = Math.atan2(ab.getY(), ab.getX()); + ab = ab.normalize(); + + // intersection = a + ab*(ap.ab) + Vector2D intersection = a.add( ab.scalarMultiply(ap.dotProduct(ab)) ); + + // Offset flag positions from the intersection point. + // TODO: improve logic for flag positioning, flags just move on the nearest line without boundaries + Vector2D pos1 = intersection.subtract(5, ab); + Vector2D pos2 = intersection.add(5, ab); + + FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph); + String commonLabel = scheme.generateLabel(graph, diagram); + + // Create flags and connect both disconnected ends to them. + Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel); + Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel); + + Resource flagConnector1 = cu.newConnector(connection, DIA.HasArrowConnector); + Resource flagConnector2 = cu.newConnector(newConnection, DIA.HasPlainConnector); + graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1); + graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2); + + graph.claim(flagConnector1, DIA.AreConnected, keepConnector); + graph.claim(flagConnector2, DIA.AreConnected, moveToNewConnectionConnector); + + FlagUtil.join(graph, flag1, flag2); + } + + private AffineTransform getFlagTransform(Vector2D pos, double theta) { + AffineTransform at = AffineTransform.getTranslateInstance(pos.getX(), pos.getY()); + 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; + } + + static class Parts { + DiagramResource DIA; + Set parts = new HashSet(); + + private Parts(ReadGraph graph) { + this.DIA = DiagramResource.getInstance(graph); + } + + public int size() { + return parts.size(); + } + + public static Splitter.Parts calculate(ReadGraph graph, Resource routeNode) throws DatabaseException { + DiagramResource DIA = DiagramResource.getInstance(graph); + + Splitter.Parts p = new Parts(graph); + Deque todo = new ArrayDeque(); + Set visited = new HashSet(); + + todo.add(routeNode); + while (!todo.isEmpty()) { + Resource part = todo.poll(); + if (!visited.add(part)) + continue; + + todo.addAll(graph.getObjects(part, DIA.AreConnected)); + + Statement toConnection = graph.getPossibleStatement(part, DIA.IsConnectorOf); + if (toConnection == null) + toConnection = graph.getPossibleStatement(part, DIA.HasInteriorRouteNode_Inverse); + if (toConnection == null) + continue; + + p.parts.add(toConnection); + } + + return p; + } + + public boolean hasInputsOnly(ReadGraph graph) throws ServiceException { + return hasInputs(graph) && !hasOutputs(graph); + } + + public boolean hasInputs(ReadGraph graph) throws ServiceException { + return hasRelations(graph, DIA.IsArrowConnectorOf); + } + + public boolean hasOutputs(ReadGraph graph) throws ServiceException { + return hasRelations(graph, DIA.IsPlainConnectorOf); + } + + public boolean hasRelations(ReadGraph graph, Resource relation) throws ServiceException { + for (Statement stm : parts) + if (graph.isSubrelationOf(stm.getPredicate(), relation)) + return true; + return false; + } + + } + } \ No newline at end of file