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