]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/flag/Splitter.java
57c1a64e34151a40a728c619cb044551acde3068
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / flag / Splitter.java
1 package org.simantics.diagram.flag;
2
3 import java.awt.geom.AffineTransform;
4 import java.awt.geom.Line2D;
5 import java.awt.geom.Point2D;
6 import java.util.ArrayDeque;
7 import java.util.Deque;
8 import java.util.HashSet;
9 import java.util.Set;
10
11 import javax.vecmath.Tuple2d;
12 import javax.vecmath.Vector2d;
13
14 import org.simantics.databoard.Bindings;
15 import org.simantics.db.ReadGraph;
16 import org.simantics.db.Resource;
17 import org.simantics.db.Statement;
18 import org.simantics.db.WriteGraph;
19 import org.simantics.db.common.utils.OrderedSetUtils;
20 import org.simantics.db.exception.DatabaseException;
21 import org.simantics.db.exception.ServiceException;
22 import org.simantics.diagram.content.ConnectionUtil;
23 import org.simantics.diagram.content.EdgeResource;
24 import org.simantics.diagram.stubs.DiagramResource;
25 import org.simantics.diagram.synchronization.graph.AddElement;
26 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
27 import org.simantics.g2d.element.IElement;
28 import org.simantics.g2d.elementclass.FlagClass;
29 import org.simantics.g2d.elementclass.FlagClass.Type;
30 import org.simantics.layer0.Layer0;
31 import org.simantics.modeling.ModelingResources;
32 import org.simantics.structural.stubs.StructuralResource2;
33
34 /**
35  * A class that handles splitting a connection in two with diagram local flags.
36  * 
37  * The connection splitting process consists of the following steps:
38  * <ol>
39  * <li>Disconnect the end points of the selected connection edge segment (SEG).
40  * An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This
41  * operation will always split a valid connection into two separate parts.</li>
42  * <li>Calculate the contents of the two separated connection parts (branch
43  * points and connectors) and decide which part to leave with the existing
44  * connection (=P1) and which part to move into a new connection (=P2). The
45  * current strategy is to move the parts that
46  * <em>do not contain output terminal connectors</em> into a new connection.
47  * This works well with diagram to composite mappings where output terminals
48  * generate modules behind connections.</li>
49  * <li>Create new connection C' with the same type and STR.HasConnectionType as
50  * the original connection C.</li>
51  * <li>Copy connection routing settings from C to C'.</li>
52  * <li>Move P2 into C'.</li>
53  * <li>Create two new flag elements into the same diagram, set their type and
54  * interpolate a proper transformation for both along the existing connection
55  * line.</li>
56  * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>
57  * <li>Join the flags together.</li>
58  * </ol>
59  * 
60  * @author Tuukka Lehtonen
61  */
62 public class Splitter {
63
64     Layer0              L0;
65     DiagramResource     DIA;
66     StructuralResource2 STR;
67     ModelingResources   MOD;
68
69     public Splitter(ReadGraph graph) {
70         this.L0 = Layer0.getInstance(graph);
71         this.DIA = DiagramResource.getInstance(graph);
72         this.STR = StructuralResource2.getInstance(graph);
73         this.MOD = ModelingResources.getInstance(graph);
74     }
75
76     public void split(WriteGraph graph, IElement edgeElement, EdgeResource edge, Point2D splitCanvasPos) throws DatabaseException {
77         ConnectionUtil cu = new ConnectionUtil(graph);
78
79         Resource connection = ConnectionUtil.getConnection(graph, edge);
80         Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);
81
82         // Disconnect the edge to calculate the two parts that remain.
83         cu.disconnect(edge);
84
85         Splitter.Parts parts1 = Parts.calculate(graph, edge.first());
86         Splitter.Parts parts2 = Parts.calculate(graph, edge.second());
87
88         // Resolve which part contains the "output" and which contains
89         // "input" to properly position the created flags.
90         Splitter.Parts moveToNewConnection = parts2;
91         Resource keepConnector = edge.first();
92         Resource moveToNewConnectionConnector = edge.second();
93
94         boolean inputsOnly1 = parts1.hasInputsOnly(graph);
95         boolean inputsOnly2 = parts2.hasInputsOnly(graph);
96
97         if (inputsOnly1 && inputsOnly2) {
98             // Let it be, can't really do better with this information.
99         } else if (inputsOnly1) {
100             // Parts1 has input connectors only, therefore we should
101             // move those to the new connection instead of parts2.
102             moveToNewConnection = parts1;
103             keepConnector = edge.second();
104             moveToNewConnectionConnector = edge.first();
105         }
106
107         Resource connectionType = graph.getSingleType(connection, DIA.Connection);
108         Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
109         Resource newConnection = cu.newConnection(diagram, connectionType);
110         if (hasConnectionType != null)
111             graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);
112
113         // Copy connection routing
114         for (Statement routing : graph.getStatements(connection, DIA.Routing))
115             graph.claim(newConnection, routing.getPredicate(), newConnection);
116
117         for (Statement stm : moveToNewConnection.parts) {
118             graph.deny(stm);
119             graph.claim(stm.getSubject(), stm.getPredicate(), newConnection);
120         }
121
122         // 1 = output, 2 = input
123         Type type1 = FlagClass.Type.Out;
124         Type type2 = FlagClass.Type.In;
125
126         // Resolve the "positive" direction of the clicked edge to be split.
127         Line2D nearestEdge= ConnectionUtil.resolveNearestEdgeLineSegment(splitCanvasPos, edgeElement);
128
129         // Calculate split position and edge line nearest intersection point
130         // ab = normalize( vec(a -> b) )
131         // ap = vec(a -> split pos)
132         Vector2d ab = new Vector2d(nearestEdge.getX2() - nearestEdge.getX1(), nearestEdge.getY2() - nearestEdge.getY1());
133         Vector2d ap = new Vector2d(splitCanvasPos.getX() - nearestEdge.getX1(), splitCanvasPos.getY() - nearestEdge.getY1());
134         double theta = Math.atan2(ab.y, ab.x);
135         ab.normalize();
136
137         // intersection = a + ab*(ap.ab)
138         Vector2d intersection = new Vector2d(ab);
139         intersection.scale(ap.dot(ab));
140         intersection.add(new Vector2d(nearestEdge.getX1(), nearestEdge.getY1()));
141
142         // Offset flag positions from the intersection point.
143         Vector2d pos1 = new Vector2d(intersection);
144         Vector2d pos2 = new Vector2d(intersection);
145
146         // TODO: improve logic for flag positioning, flags just move on the nearest line without boundaries
147         ab.normalize();
148         ab.scale(5);
149         pos2.add(ab);
150         ab.negate();
151         pos1.add(ab);
152
153         FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
154         String commonLabel = scheme.generateLabel(graph, diagram);
155
156         // Create flags and connect both disconnected ends to them.
157         Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);
158         Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);
159
160         Resource flagConnector1 = cu.newConnector(connection, DIA.HasArrowConnector);
161         Resource flagConnector2 = cu.newConnector(newConnection, DIA.HasPlainConnector);
162         graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);
163         graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);
164
165         graph.claim(flagConnector1, DIA.AreConnected, keepConnector);
166         graph.claim(flagConnector2, DIA.AreConnected, moveToNewConnectionConnector);
167
168         FlagUtil.join(graph, flag1, flag2);
169     }
170
171     private AffineTransform getFlagTransform(Tuple2d pos, double theta) {
172         AffineTransform at = AffineTransform.getTranslateInstance(pos.x, pos.y);
173         at.rotate(theta);
174         return at;
175     }
176
177     private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {
178         DiagramResource DIA = DiagramResource.getInstance(graph);
179
180         Resource flag = graph.newResource();
181         graph.claim(flag, L0.InstanceOf, null, DIA.Flag);
182         
183         AddElement.claimFreshElementName(graph, diagram, flag);
184         graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);
185         
186         DiagramGraphUtil.setTransform(graph, flag, tr);
187         if (type != null)
188             FlagUtil.setFlagType(graph, flag, type);
189
190         if (label != null)
191             graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
192
193         OrderedSetUtils.add(graph, diagram, flag);
194         return flag;
195     }
196
197     static class Parts {
198         DiagramResource DIA;
199         Set<Statement> parts = new HashSet<Statement>();
200
201         private Parts(ReadGraph graph) {
202             this.DIA = DiagramResource.getInstance(graph);
203         }
204
205         public int size() {
206             return parts.size();
207         }
208
209         public static Splitter.Parts calculate(ReadGraph graph, Resource routeNode) throws DatabaseException {
210             DiagramResource DIA = DiagramResource.getInstance(graph);
211
212             Splitter.Parts p = new Parts(graph);
213             Deque<Resource> todo = new ArrayDeque<Resource>();
214             Set<Resource> visited = new HashSet<Resource>();
215
216             todo.add(routeNode);
217             while (!todo.isEmpty()) {
218                 Resource part = todo.poll();
219                 if (!visited.add(part))
220                     continue;
221
222                 todo.addAll(graph.getObjects(part, DIA.AreConnected));
223
224                 Statement toConnection = graph.getPossibleStatement(part, DIA.IsConnectorOf);
225                 if (toConnection == null)
226                     toConnection = graph.getPossibleStatement(part, DIA.HasInteriorRouteNode_Inverse);
227                 if (toConnection == null)
228                     continue;
229
230                 p.parts.add(toConnection);
231             }
232
233             return p;
234         }
235
236         public boolean hasInputsOnly(ReadGraph graph) throws ServiceException {
237             return hasInputs(graph) && !hasOutputs(graph);
238         }
239
240         public boolean hasInputs(ReadGraph graph) throws ServiceException {
241             return hasRelations(graph, DIA.IsArrowConnectorOf);
242         }
243
244         public boolean hasOutputs(ReadGraph graph) throws ServiceException {
245             return hasRelations(graph, DIA.IsPlainConnectorOf);
246         }
247
248         public boolean hasRelations(ReadGraph graph, Resource relation) throws ServiceException {
249             for (Statement stm : parts)
250                 if (graph.isSubrelationOf(stm.getPredicate(), relation))
251                     return true;
252             return false;
253         }
254
255     }
256
257 }