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