1 package org.simantics.diagram.flag;
3 import gnu.trove.map.hash.TObjectIntHashMap;
5 import java.awt.geom.AffineTransform;
6 import java.awt.geom.Point2D;
7 import java.util.ArrayList;
8 import java.util.Collection;
10 import javax.vecmath.Tuple2d;
11 import javax.vecmath.Vector2d;
13 import org.simantics.databoard.Bindings;
14 import org.simantics.db.ReadGraph;
15 import org.simantics.db.Resource;
16 import org.simantics.db.Statement;
17 import org.simantics.db.WriteGraph;
18 import org.simantics.db.common.request.PossibleTypedParent;
19 import org.simantics.db.common.utils.NameUtils;
20 import org.simantics.db.common.utils.OrderedSetUtils;
21 import org.simantics.db.exception.DatabaseException;
22 import org.simantics.db.service.SerialisationSupport;
23 import org.simantics.diagram.adapter.RouteGraphUtils;
24 import org.simantics.diagram.connection.RouteGraph;
25 import org.simantics.diagram.connection.RouteLine;
26 import org.simantics.diagram.connection.RouteNode;
27 import org.simantics.diagram.connection.RoutePoint;
28 import org.simantics.diagram.connection.RouteTerminal;
29 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
30 import org.simantics.diagram.content.ConnectionUtil;
31 import org.simantics.diagram.stubs.DiagramResource;
32 import org.simantics.diagram.synchronization.graph.AddElement;
33 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
34 import org.simantics.diagram.synchronization.graph.RouteGraphModification;
35 import org.simantics.g2d.elementclass.FlagClass;
36 import org.simantics.layer0.Layer0;
37 import org.simantics.modeling.ModelingResources;
38 import org.simantics.structural.stubs.StructuralResource2;
41 * A class that handles splitting a route graph connection in two with diagram
44 * The connection splitting process consists of the following steps:
46 * <li>Disconnect the end points of the selected connection edge segment (SEG).
47 * An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This
48 * operation will always split a valid connection into two separate parts.</li>
49 * <li>Calculate the contents of the two separated connection parts (branch
50 * points and connectors) and decide which part to leave with the existing
51 * connection (=P1) and which part to move into a new connection (=P2). The
52 * current strategy is to move the parts that
53 * <em>do not contain output terminal connectors</em> into a new connection.
54 * This works well with diagram to composite mappings where output terminals
55 * generate modules behind connections.</li>
56 * <li>Create new connection C' with the same type and STR.HasConnectionType as
57 * the original connection C.</li>
58 * <li>Copy connection routing settings from C to C'.</li>
59 * <li>Move P2 into C'.</li>
60 * <li>Create two new flag elements into the same diagram, set their type and
61 * interpolate a proper transformation for both along the existing connection
63 * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>
64 * <li>Join the flags together.</li>
67 * @author Tuukka Lehtonen
68 * @author Hannu Niemistö
70 public class RouteGraphConnectionSplitter {
72 private final static boolean DEBUG = false;
76 StructuralResource2 STR;
77 ModelingResources MOD;
78 SerialisationSupport ss;
80 public RouteGraphConnectionSplitter(ReadGraph graph) {
81 this.L0 = Layer0.getInstance(graph);
82 this.DIA = DiagramResource.getInstance(graph);
83 this.STR = StructuralResource2.getInstance(graph);
84 this.MOD = ModelingResources.getInstance(graph);
85 this.ss = graph.getService(SerialisationSupport.class);
88 public void split(WriteGraph graph, Resource connection, RouteGraph rg, Point2D splitCanvasPos) throws DatabaseException {
89 // Create modification writer
90 RouteGraphModification modis = new RouteGraphModification(ss, rg);
91 TObjectIntHashMap<RouteNode> idMap = modis.getIdMap();
93 // Find the edge to disconnect in the graph.
94 // Bisect the nearest route line.
95 RouteLine line = SplittedRouteGraph.findNearestLine(rg, splitCanvasPos);
101 line.print(System.out);
102 for (RoutePoint rp : line.getPoints())
103 System.out.println("RP: " + rp.getX() + ", " + rp.getY());
106 // Get exact intersection point on the line
107 double isectX = splitCanvasPos.getX();
108 double isectY = splitCanvasPos.getY();
109 SplittedRouteGraph srg;
110 if (line.isHorizontal()) {
111 isectY = line.getPosition();
112 srg = rg.splitGraph(line, isectX);
115 isectX = line.getPosition();
116 srg = rg.splitGraph(line, isectY);
119 System.out.println(srg);
122 if(rg.isSimpleConnection()) {
123 RouteNode na = srg.terminals1.iterator().next();
124 RouteNode nb = srg.terminals2.iterator().next();
125 Resource a = ss.getResource((Long)na.getData());
126 Resource b = ss.getResource((Long)nb.getData());
127 graph.deny(a, DIA.AreConnected, b);
128 modis.addModi(new RouteGraphModification.RemoveLink(
129 idMap.get(na), idMap.get(nb)
132 else if(srg.splitLine.isTransient()) {
133 RouteTerminal terminal = srg.splitLine.getTerminal();
134 Resource connector = ss.getResource((Long)terminal.getData());
135 graph.deny(connector, DIA.AreConnected);
136 modis.addModi(new RouteGraphModification.RemoveLink(
137 idMap.get(terminal), idMap.get(terminal.getLine())
141 graph.deny(ss.getResource((Long)srg.splitLine.getData()));
143 modis.addModi(new RouteGraphModification.RemoveLine(
144 idMap.get(srg.splitLine)
148 ArrayList<Resource> interfaceNodes1Resources = new ArrayList<Resource>(srg.interfaceNodes1.size());
149 for(RouteNode n : srg.interfaceNodes1)
150 interfaceNodes1Resources.add(ss.getResource((Long)n.getData()));
151 ArrayList<Resource> interfaceNodes2Resources = new ArrayList<Resource>(srg.interfaceNodes2.size());
152 for(RouteNode n : srg.interfaceNodes2)
153 interfaceNodes2Resources.add(ss.getResource((Long)n.getData()));
155 ArrayList<Resource> lines2Resources = new ArrayList<Resource>(srg.lines2.size());
156 for(RouteLine n : srg.lines2)
157 lines2Resources.add(ss.getResource((Long)n.getData()));
159 ArrayList<Resource> terminals1Resources = new ArrayList<Resource>(srg.terminals1.size());
160 for(RouteTerminal n : srg.terminals1)
161 terminals1Resources.add(ss.getResource((Long)n.getData()));
162 ArrayList<Resource> terminals2Resources = new ArrayList<Resource>(srg.terminals2.size());
163 for(RouteTerminal n : srg.terminals2)
164 terminals2Resources.add(ss.getResource((Long)n.getData()));
165 doSplit(graph, connection,
166 interfaceNodes1Resources,
167 interfaceNodes2Resources,
173 modis.addModi(new RouteGraphModification.Split(
174 modis.toIds(interfaceNodes1Resources),
175 modis.toIds(interfaceNodes2Resources),
176 modis.toIds(lines2Resources),
177 modis.toIds(terminals1Resources),
178 modis.toIds(terminals2Resources),
185 public void doSplit(WriteGraph graph,
187 ArrayList<Resource> interfaceNodes1Resources,
188 ArrayList<Resource> interfaceNodes2Resources,
189 ArrayList<Resource> lines2Resources,
190 ArrayList<Resource> terminals1Resources,
191 ArrayList<Resource> terminals2Resources,
192 boolean isHorizontal,
193 double isectX, double isectY) throws DatabaseException {
196 System.out.println("doSplit:");
197 System.out.println(NameUtils.getSafeName(graph, connection, true));
198 for (Resource i : interfaceNodes1Resources)
199 System.out.println("i1: " + NameUtils.getSafeName(graph, i, true));
200 for (Resource i : interfaceNodes2Resources)
201 System.out.println("i2: " + NameUtils.getSafeName(graph, i, true));
202 for (Resource l : lines2Resources)
203 System.out.println("l2r: " + NameUtils.getSafeName(graph, l, true));
204 for (Resource t : terminals1Resources)
205 System.out.println("t1: " + NameUtils.getSafeName(graph, t, true));
206 for (Resource t : terminals2Resources)
207 System.out.println("t2: " + NameUtils.getSafeName(graph, t, true));
208 System.out.println("is horizontal: " + isHorizontal);
209 System.out.println("@(x,y): " + isectX + ", " + isectY);
212 ConnectionUtil cu = new ConnectionUtil(graph);
213 Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);
215 Resource connectionType = graph.getSingleType(connection, DIA.Connection);
216 Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
217 Resource newConnection = cu.newConnection(diagram, connectionType);
218 if (hasConnectionType != null)
219 graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);
221 // Give running name to connection increment the counter attached to the diagram.
222 AddElement.claimFreshElementName(graph, diagram, newConnection);
224 // WORKAROUND for mapping problems:
225 // If any terminal of the split connection contains a flag, make sure their STR.Joins relations are all removed
226 // to give mapping a chance to fix them properly.
227 removeFlagJoins(graph, cu, connection, terminals1Resources);
228 removeFlagJoins(graph, cu, connection, terminals2Resources);
230 // Move route nodes to correct connections
231 for(Resource rn : lines2Resources) {
232 // TODO: use same predicate that was removed
233 graph.denyStatement(connection, DIA.HasInteriorRouteNode, rn);
234 graph.claim(newConnection, DIA.HasInteriorRouteNode, rn);
236 for(Resource rn : terminals2Resources) {
237 Statement stat = graph.getSingleStatement(rn, DIA.IsConnectorOf);
238 Resource predicate = stat.getPredicate();
239 graph.deny(rn, predicate);
240 graph.claim(rn, predicate, newConnection);
243 // 1 = output, 2 = input
244 FlagClass.Type type1, type2;
246 FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
247 String commonLabel = scheme.generateLabel(graph, diagram);
249 // Create flags and connect both disconnected ends to them.
252 double flagDist = 3.0;
255 pos1 = new Vector2d(isectX-flagDist, isectY);
256 pos2 = new Vector2d(isectX+flagDist, isectY);
260 pos1 = new Vector2d(isectX, isectY-flagDist);
261 pos2 = new Vector2d(isectX, isectY+flagDist);
264 // Chooses flag directions
266 @SuppressWarnings("unused")
267 int inputs1 = 0, outputs1 = 0;
268 for(Resource connector : terminals1Resources) {
269 if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
274 @SuppressWarnings("unused")
275 int inputs2 = 0, outputs2 = 0;
276 for(Resource connector : terminals2Resources) {
277 if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
284 type1 = FlagClass.Type.In;
285 type2 = FlagClass.Type.Out;
289 type1 = FlagClass.Type.Out;
290 type2 = FlagClass.Type.In;
293 System.out.println("inputs1: " + inputs1);
294 System.out.println("outputs1: " + outputs1);
295 System.out.println("=> type1: " + type1);
296 System.out.println("inputs2: " + inputs2);
297 System.out.println("outputs2: " + outputs2);
298 System.out.println("=> type2: " + type2);
301 Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);
302 Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);
304 System.out.println("FLAG1: " + NameUtils.getSafeName(graph, flag1, true));
305 System.out.println("FLAG2: " + NameUtils.getSafeName(graph, flag2, true));
308 // System.out.println("conn1: " + NameUtils.getSafeLabel(graph, type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));
309 // System.out.println("conn2: " + NameUtils.getSafeLabel(graph, type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));
310 Resource flagConnector1 = cu.newConnector(connection,
311 type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);
312 Resource flagConnector2 = cu.newConnector(newConnection,
313 type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);
314 graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);
315 graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);
317 double position = isHorizontal ? isectY : isectX;
318 connectFlag(graph, isHorizontal, position, connection, flagConnector1,
319 interfaceNodes1Resources);
320 connectFlag(graph, isHorizontal, position, newConnection, flagConnector2,
321 interfaceNodes2Resources);
323 FlagUtil.join(graph, flag1, flag2);
325 // Move mapping relations to new connection if necessary
326 if(type1 == FlagClass.Type.In) {
327 moveStatements(graph, connection, newConnection, MOD.ElementToComponent);
328 moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnection);
329 moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnectionSpecial);
330 FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(newConnection, MOD.DiagramConnectionToConnection));
333 FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));
337 * A workaround for problems with mapping not removing the necessary
338 * STR.Joins relations from flags after a split.
344 * @throws DatabaseException
346 private void removeFlagJoins(WriteGraph graph, ConnectionUtil cu, Resource connection, ArrayList<Resource> connectors) throws DatabaseException {
347 for (Resource connector : connectors) {
348 Resource e = cu.getConnectedComponent(connection, connector);
349 if (graph.isInstanceOf(e, DIA.Flag)) {
350 Resource diagram = graph.syncRequest(new PossibleTypedParent(e, DIA.Diagram));
353 for (Resource join : graph.getObjects(e, DIA.FlagIsJoinedBy)) {
354 Collection<Resource> joinsComposites = graph.getObjects(join, STR.JoinsComposite);
355 if (joinsComposites.size() == 1) {
356 // Only remove joins that are internal to a diagram.
357 graph.deny(join, STR.Joins);
358 } else if (joinsComposites.size() == 2) {
359 // Only remove the joins relations that refer to
360 // connections that are part of the same diagram.
361 for (Resource joins : graph.getObjects(join, STR.Joins)) {
362 Resource diagramConnection = graph.getPossibleObject(joins, MOD.ConnectionToDiagramConnection);
363 if (diagramConnection == null)
365 Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));
366 if (diagram.equals(partOfDiagram)) {
367 graph.deny(join, STR.Joins, joins);
376 private static void moveStatements(WriteGraph graph, Resource from, Resource to, Resource relation) throws DatabaseException {
379 for(Statement stat : graph.getStatements(from, relation))
380 if(stat.getSubject().equals(from))
381 graph.claim(to, stat.getPredicate(), stat.getObject());
382 graph.deny(from, relation);
385 private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection<Resource> interfaceNodes)
386 throws DatabaseException {
387 if(interfaceNodes.size() > 1) {
388 Resource routeLine = graph.newResource();
389 graph.claim(routeLine, L0.InstanceOf, DIA.RouteLine);
390 graph.claim(connection, DIA.HasInteriorRouteNode, routeLine);
391 graph.claimLiteral(routeLine, DIA.IsHorizontal, isHorizontal);
392 graph.claimLiteral(routeLine, DIA.HasPosition, position);
393 graph.claim(routeLine, DIA.AreConnected, flagConnector);
394 flagConnector = routeLine;
396 for(Resource rn : interfaceNodes) {
397 graph.claim(flagConnector, DIA.AreConnected, rn);
401 private AffineTransform getFlagTransform(Tuple2d pos, double theta) {
402 AffineTransform at = AffineTransform.getTranslateInstance(pos.x, pos.y);
407 private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {
408 DiagramResource DIA = DiagramResource.getInstance(graph);
410 Resource flag = graph.newResource();
411 graph.claim(flag, L0.InstanceOf, null, DIA.Flag);
412 AddElement.claimFreshElementName(graph, diagram, flag);
413 graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);
415 DiagramGraphUtil.setTransform(graph, flag, tr);
417 FlagUtil.setFlagType(graph, flag, type);
420 graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
422 OrderedSetUtils.add(graph, diagram, flag);
426 public static void splitConnection(WriteGraph graph, Resource connection, double x, double y) throws DatabaseException {
427 RouteGraph rg = RouteGraphUtils.load(graph, null, connection);
428 new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y));