]> gerrit.simantics Code Review - simantics/sysdyn.git/blob
517a696e57e7f93709d5a20ae622f1725ec872c9
[simantics/sysdyn.git] /
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2011 Association for Decentralized Information Management in\r
3  * Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.sysdyn.ui.elements.connections;\r
13 \r
14 import java.awt.BasicStroke;\r
15 import java.awt.Color;\r
16 import java.awt.Shape;\r
17 import java.awt.Stroke;\r
18 import java.awt.geom.AffineTransform;\r
19 import java.awt.geom.Rectangle2D;\r
20 import java.util.ArrayList;\r
21 import java.util.Collection;\r
22 import java.util.Collections;\r
23 import java.util.HashMap;\r
24 import java.util.HashSet;\r
25 import java.util.Map;\r
26 import java.util.Set;\r
27 import java.util.concurrent.ConcurrentSkipListMap;\r
28 import java.util.concurrent.atomic.AtomicInteger;\r
29 \r
30 import org.eclipse.jface.resource.StringConverter;\r
31 import org.eclipse.swt.graphics.RGB;\r
32 import org.simantics.databoard.Bindings;\r
33 import org.simantics.db.AsyncReadGraph;\r
34 import org.simantics.db.ReadGraph;\r
35 import org.simantics.db.Resource;\r
36 import org.simantics.db.Session;\r
37 import org.simantics.db.Statement;\r
38 import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
39 import org.simantics.db.common.utils.NameUtils;\r
40 import org.simantics.db.exception.DatabaseException;\r
41 import org.simantics.db.procedure.AsyncProcedure;\r
42 import org.simantics.db.procedure.SyncMultiProcedure;\r
43 import org.simantics.diagram.G2DUtils;\r
44 import org.simantics.diagram.adapter.SyncElementFactory;\r
45 import org.simantics.diagram.connection.ConnectionVisuals;\r
46 import org.simantics.diagram.connection.RouteGraph;\r
47 import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
48 import org.simantics.diagram.connection.RouteLine;\r
49 import org.simantics.diagram.connection.RouteNode;\r
50 import org.simantics.diagram.connection.RouteTerminal;\r
51 import org.simantics.diagram.connection.rendering.ConnectionStyle;\r
52 import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer;\r
53 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;\r
54 import org.simantics.diagram.content.EdgeResource;\r
55 import org.simantics.diagram.content.ResourceTerminal;\r
56 import org.simantics.diagram.content.TerminalMap;\r
57 import org.simantics.diagram.query.DiagramRequests;\r
58 import org.simantics.diagram.stubs.DiagramResource;\r
59 import org.simantics.diagram.stubs.G2DResource;\r
60 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
61 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
62 import org.simantics.diagram.ui.DiagramModelHints;\r
63 import org.simantics.g2d.canvas.ICanvasContext;\r
64 import org.simantics.g2d.connection.ConnectionEntity;\r
65 import org.simantics.g2d.diagram.DiagramHints;\r
66 import org.simantics.g2d.diagram.IDiagram;\r
67 import org.simantics.g2d.diagram.handler.DataElementMap;\r
68 import org.simantics.g2d.diagram.handler.Topology.Connection;\r
69 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
70 import org.simantics.g2d.element.ElementClass;\r
71 import org.simantics.g2d.element.ElementHints;\r
72 import org.simantics.g2d.element.ElementUtils;\r
73 import org.simantics.g2d.element.IElement;\r
74 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
75 import org.simantics.g2d.element.handler.TerminalTopology;\r
76 import org.simantics.g2d.element.handler.impl.StaticObjectAdapter;\r
77 import org.simantics.g2d.routing.algorithm2.Router4;\r
78 import org.simantics.g2d.utils.TopologicalSelectionExpander;\r
79 import org.simantics.layer0.Layer0;\r
80 import org.simantics.modeling.ModelingResources;\r
81 import org.simantics.scenegraph.g2d.nodes.connection.IRouteGraphListener;\r
82 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;\r
83 import org.simantics.structural.stubs.StructuralResource2;\r
84 import org.simantics.structural2.modelingRules.CPTerminal;\r
85 import org.simantics.structural2.modelingRules.IAttachmentRelationMap;\r
86 import org.simantics.structural2.modelingRules.IModelingRules;\r
87 import org.simantics.sysdyn.SysdynResource;\r
88 import org.simantics.sysdyn.ui.elements.ValveFactory.ValveSceneGraph;\r
89 import org.simantics.sysdyn.ui.preferences.SysdynDiagramPreferences;\r
90 import org.simantics.sysdyn.ui.preferences.SysdynDiagramPropertyExternalRead;\r
91 import org.simantics.utils.datastructures.Pair;\r
92 /**\r
93  * An element class for Sysdyn Flow elements.\r
94  * Copied from RouteGraphConnectionClassFactory and adapted to Flow needs\r
95  * \r
96  * @author Teemu Lempinen\r
97  * \r
98  */\r
99 public class RouteFlowConnectionFactory extends SyncElementFactory {\r
100 \r
101     public static final ElementClass CLASS = RouteFlowEdgeClass.FLOW_CLASS;\r
102 \r
103     Layer0                             L0;\r
104     DiagramResource                    DIA;\r
105     StructuralResource2                STR;\r
106     ModelingResources                  MOD;\r
107 \r
108     public RouteFlowConnectionFactory(ReadGraph graph) {\r
109         this.L0 = Layer0.getInstance(graph);\r
110         this.DIA = DiagramResource.getInstance(graph);\r
111         this.STR = StructuralResource2.getInstance(graph);\r
112         this.MOD = ModelingResources.getInstance(graph);\r
113     }\r
114 \r
115     @Override\r
116     public void create(AsyncReadGraph graph, ICanvasContext canvas, IDiagram diagram, Resource elementType, final AsyncProcedure<ElementClass> procedure) {\r
117         procedure.execute(graph, CLASS.newClassWith(false, new StaticObjectAdapter(elementType)));\r
118     }\r
119 \r
120     @Override\r
121     protected Resource getElementClassBaseType(AsyncReadGraph graph) {\r
122         return graph.getService(SysdynResource.class).FlowConnection;\r
123     }\r
124 \r
125     @Override\r
126     public void load(ReadGraph graph, final ICanvasContext canvas, final IDiagram diagram, final Resource connection,\r
127             final IElement element) throws DatabaseException {\r
128 \r
129         // Do we need this?\r
130         element.setHint(DiagramHints.ROUTE_ALGORITHM, new Router4(false));\r
131         IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
132 \r
133         Color color = null;\r
134         DiagramResource DR = DiagramResource.getInstance(graph);\r
135         G2DResource G2D = G2DResource.getInstance(graph);\r
136         if (graph.isInstanceOf(connection, DR.ColorProvider)) {\r
137             Statement colorStatement = graph.getPossibleStatement(connection, G2D.HasColor);\r
138             if(colorStatement != null && !colorStatement.isAsserted(connection)) {\r
139                 element.setHint(ElementHints.KEY_TEXT_COLOR, G2DUtils.getColor(graph, colorStatement.getObject()));\r
140             } else {\r
141                 String colorString = graph.syncRequest(new SysdynDiagramPropertyExternalRead(new Pair<Resource, String>(connection, SysdynDiagramPreferences.getColorPreferenceName(graph, connection))));\r
142                 if(colorString != null) {\r
143                     RGB rgb = StringConverter.asRGB(colorString, null);\r
144                     if(rgb != null) {\r
145                         color = new Color(rgb.red, rgb.green, rgb.blue);\r
146                         element.setHint(ElementHints.KEY_TEXT_COLOR, color);\r
147                     }\r
148                 }\r
149 \r
150             }\r
151         }\r
152 \r
153         RouteGraph rg = new RouteGraph();\r
154 \r
155         Set<Resource> nodes = new HashSet<Resource>();\r
156         Set<EdgeResource> links = new HashSet<EdgeResource>();\r
157         Map<Object, RouteNode> nodeByData = new HashMap<Object, RouteNode>();\r
158 \r
159         // Needed to support ConnectionEntity#getTerminalConnections\r
160         Set<BackendConnection> backendonnections = new HashSet<BackendConnection>();\r
161 \r
162         // Load all route graph interior RouteNodes: route lines and points\r
163         for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) {\r
164             if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) {\r
165                 Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN);\r
166                 Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE);\r
167                 RouteLine line = rg.addLine(isHorizontal, position);\r
168                 line.setData( RouteGraphConnection.serialize(graph, interiorNode) );\r
169 \r
170                 nodes.add( interiorNode );\r
171                 nodeByData.put( interiorNode, line );\r
172 \r
173                 for (Resource connectedTo : graph.getObjects(interiorNode, DIA.AreConnected)) {\r
174                     links.add( new EdgeResource(interiorNode, connectedTo) );\r
175                 }\r
176             } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) {\r
177                 // Not supported yet. Ignore.\r
178             }\r
179         }\r
180 \r
181         Rectangle2D bounds = new Rectangle2D.Double();\r
182 \r
183         // Load all node terminal connections as RouteTerminals\r
184         for (Statement toConnector : graph.getStatements(connection, DIA.HasConnector)) {\r
185             Resource connector = toConnector.getObject();\r
186             Resource attachmentRelation = toConnector.getPredicate();\r
187 \r
188             Statement terminalStm = findTerminalStatement(graph, STR, connection, connector);\r
189             if (terminalStm == null)\r
190                 // Ignore broken connector: attached to the connection but not to any terminal.\r
191                 continue;\r
192 \r
193             Resource terminalElement = terminalStm.getObject();\r
194             Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element);\r
195             if (terminalElementType == null)\r
196                 // Ignore non-element terminal elements\r
197                 continue;\r
198 \r
199             Resource connectionRelation = graph.getInverse(terminalStm.getPredicate());\r
200 \r
201             // Discover node and terminal this connector is connected to.\r
202             TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType),\r
203                     TransientCacheListener.<TerminalMap> instance());\r
204             Resource terminal = terminals.getTerminal(connectionRelation);\r
205             if (terminal == null) {\r
206                 System.err.println(getClass().getSimpleName()\r
207                         + ": Could not find terminal for connection point "\r
208                         + NameUtils.getSafeName(graph, connectionRelation, true)\r
209                         + " in element "\r
210                         + NameUtils.getSafeName(graph, terminalElement, true)); \r
211                 continue;\r
212             }\r
213 \r
214             double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY);\r
215             if (position.length != 2)\r
216                 position = new double[] { 0, 0 };\r
217 \r
218             //System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm));\r
219             AffineTransform terminalElementTr = getWorldTransform(graph, terminalElement);\r
220 \r
221             double x = terminalElementTr.getTranslateX();\r
222             double y = terminalElementTr.getTranslateY();\r
223             double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1;\r
224             int direction = 0x0;\r
225 \r
226             // Use modelingRules to ascertain the proper attachmentRelation\r
227             // for this terminal connection, if available.\r
228             if (modelingRules != null) {\r
229                 // Get attachmentRelation from modelingRules if possible.\r
230                 IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection);\r
231                 Resource att = map.get(graph, new CPTerminal(terminalElement, terminal));\r
232                 if (att != null) {\r
233                     //System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, att));\r
234                     attachmentRelation = att;\r
235                 }\r
236             }\r
237             //System.out.println("attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
238 \r
239             // Get element bounds to decide allowed terminal direction(s)\r
240             IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null));\r
241   \r
242             // Fetch the flow width\r
243             float lw = FlowConnectionStyle.DEFAULT_LINE_WIDTH;\r
244             SysdynResource sr = SysdynResource.getInstance(graph);\r
245                 Float width = graph.getPossibleRelatedValue(connection, sr.FlowConnection_width, Bindings.FLOAT);\r
246                 if (width != null)\r
247                         lw = width;\r
248             \r
249                 if(te.getElementClass().containsClass(ValveSceneGraph.class)) {\r
250                 // Valve behaves differently. The flow must start inside the valve bounds\r
251                 ValveSceneGraph vs = te.getElementClass().getSingleItem(ValveSceneGraph.class);\r
252                 Rectangle2D size = new Rectangle2D.Double();\r
253                 vs.getValveBounds(te, size);\r
254                 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(size, terminalElementTr);\r
255                 size = (Rectangle2D) shp;\r
256                 bounds.setFrame(new Rectangle2D.Double(size.getCenterX() - (lw/2), size.getCenterY() - (lw/2), lw, lw));\r
257             } else {\r
258                 // Basic bounds\r
259                 bounds = ElementUtils.getElementShape(te).getBounds2D();\r
260                 Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalElementTr);\r
261                 bounds.setFrame(shp.getBounds2D());\r
262             }\r
263             \r
264             x = bounds.getCenterX();\r
265             y = bounds.getCenterY();\r
266 \r
267             // Expand bounds by 4mm to make the connections enter the terminals\r
268             // at a straight angle and from a distance instead of coming in\r
269             // "horizontally".\r
270             //GeometryUtils.expandRectangle(bounds, 4);\r
271 \r
272             minx = bounds.getMinX();\r
273             miny = bounds.getMinY();\r
274             maxx = bounds.getMaxX();\r
275             maxy = bounds.getMaxY();\r
276 \r
277             Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER);\r
278 \r
279             // Valve behaves differently. Allowed directions depend on the orientation of the valve\r
280             if(te.getElementClass().containsClass(ValveSceneGraph.class)) {\r
281                 if(graph.hasStatement(terminalElement, sr.ValveSymbol_orientation, sr.Vertical)) {\r
282                     allowedDirections = 10; // Directions up and down (1010)\r
283                 } else {\r
284                     allowedDirections = 5; // Directions left and right (0101)\r
285                 }\r
286             }\r
287             if (allowedDirections != null) {\r
288                 direction |= allowedDirections;\r
289             } else {\r
290                 direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds);\r
291             }\r
292 \r
293             backendonnections.add(\r
294                     new BackendConnection(\r
295                             toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin),\r
296                             terminalElement,\r
297                             terminal)\r
298                     );\r
299 \r
300             if (direction == 0)\r
301                 // Accept any horizontal/vertical direction if nothing is defined\r
302                 direction = 0xf;\r
303 \r
304             //System.out.println("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation));\r
305             ILineEndStyle endStyle = loadLineEndStyle(graph, te, attachmentRelation, color, lw);\r
306 \r
307             RouteTerminal routeTerminal = rg.addBigTerminal(/*x, y,*/ minx, miny, maxx, maxy, /*direction,*/ endStyle);\r
308             routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) );\r
309 \r
310             nodes.add( connector );\r
311             nodeByData.put( connector, routeTerminal );\r
312 \r
313             for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) {\r
314                 links.add( new EdgeResource(connectedTo, connector) );\r
315             }\r
316         }\r
317 \r
318         // Finish route graph loading by Linking route nodes together\r
319         for (EdgeResource link : links) {\r
320             RouteNode n1 = nodeByData.get(link.first());\r
321             RouteNode n2 = nodeByData.get(link.second());\r
322             if (n1 == null || n2 == null) {\r
323                 System.err.println("Stray connection link found: " + link.toString(graph));\r
324                 continue;\r
325             }\r
326             rg.link(n1, n2);\r
327         }\r
328 \r
329         // Load connection line style\r
330         ConnectionStyle style = readConnectionStyle(graph, modelingRules, connection, element);\r
331         StyledRouteGraphRenderer renderer = new StyledRouteGraphRenderer(style);\r
332 \r
333         // Finish element load\r
334         element.setHint(RouteGraphConnectionClass.KEY_ROUTEGRAPH, rg);\r
335         element.setHint(RouteGraphConnectionClass.KEY_RENDERER, renderer);\r
336         element.setHint(RouteGraphConnectionClass.KEY_PICK_TOLERANCE, 0.5);\r
337 \r
338         // Initialize ConnectionEntity in element\r
339         // NOTE: MUST use the mapped element with class CE, not the connection (element) were loading into.\r
340         // GDS will synchronize element into mappedElement in a controlled manner.\r
341         element.setHint(ElementHints.KEY_CONNECTION_ENTITY, new CE(diagram, connection, element, backendonnections));\r
342 \r
343         // Setup graph writeback support for route graph modifications\r
344         final Session session = graph.getSession();\r
345         element.setHint(RouteGraphConnectionClass.KEY_RG_LISTENER, new IRouteGraphListener() {\r
346             @Override\r
347             public void routeGraphChanged(RouteGraphChangeEvent event) {\r
348                 scheduleSynchronize(session, connection, event);\r
349             }\r
350         });\r
351         \r
352      // A complicated-looking procedure for obtaining all HasProperties to properties map\r
353         final AtomicInteger ready = new AtomicInteger(1);\r
354         final ConcurrentSkipListMap<String, Pair<Resource, Object>> properties = new ConcurrentSkipListMap<String, Pair<Resource, Object>>();\r
355         graph.forEachPredicate(connection, new SyncMultiProcedure<Resource>() {\r
356 \r
357             @Override\r
358             public void exception(ReadGraph graph, Throwable throwable) {\r
359                 throwable.printStackTrace();\r
360             }\r
361 \r
362             @Override\r
363             public void execute(ReadGraph graph, final Resource property) {\r
364 \r
365                 ready.incrementAndGet();\r
366                 Layer0 l0;\r
367                 try {\r
368                     l0 = Layer0.getInstance(graph.getSession());\r
369                 } catch (DatabaseException e) {\r
370                     e.printStackTrace();\r
371                     return;\r
372                 }\r
373 \r
374                 graph.forIsSubrelationOf(property, l0.HasProperty, new AsyncProcedure<Boolean>() {\r
375 \r
376                     @Override\r
377                     public void exception(AsyncReadGraph graph, Throwable throwable) {\r
378                         throwable.printStackTrace();\r
379                     }\r
380 \r
381                     @Override\r
382                     public void execute(AsyncReadGraph graph, final Boolean isProperty) {\r
383 \r
384                         if(isProperty) {\r
385 \r
386                             graph.forPossibleRelatedValue(connection, property, new AsyncProcedure<Object>() {\r
387 \r
388                                 @Override\r
389                                 public void exception(AsyncReadGraph graph, Throwable throwable) {\r
390                                     throwable.printStackTrace();\r
391                                 }\r
392 \r
393                                 @Override\r
394                                 public void execute(AsyncReadGraph graph, final Object value) {\r
395 \r
396                                     Layer0 l0;\r
397                                     try {\r
398                                         l0 = Layer0.getInstance(graph.getSession());\r
399                                     } catch (DatabaseException e) {\r
400                                         e.printStackTrace();\r
401                                         return;\r
402                                     }\r
403 \r
404                                     graph.forPossibleRelatedValue(property, l0.HasName, Bindings.STRING, new AsyncProcedure<String>() {\r
405 \r
406                                         @Override\r
407                                         public void exception(AsyncReadGraph graph, Throwable throwable) {\r
408                                             throwable.printStackTrace();\r
409                                         }\r
410 \r
411                                         @Override\r
412                                         public void execute(AsyncReadGraph graph, String name) {\r
413 \r
414                                             properties.put(name, Pair.make(property, value));\r
415                                             if(ready.decrementAndGet() == 0) {\r
416                                                 element.setHint(DiagramHints.PROPERTIES, new HashMap<String, Pair<Resource, Object>>(properties));\r
417                                             }\r
418 \r
419                                         }\r
420 \r
421                                     });\r
422 \r
423                                 }\r
424 \r
425                             });\r
426 \r
427 \r
428                         } else {\r
429 \r
430                             if(ready.decrementAndGet() == 0) {\r
431                                 element.setHint(DiagramHints.PROPERTIES, new HashMap<String, Pair<Resource, Object>>(properties));\r
432                             }\r
433 \r
434                         }\r
435 \r
436                     }\r
437 \r
438                 });\r
439             }\r
440 \r
441             @Override\r
442             public void finished(ReadGraph graph) {\r
443 \r
444                 if(ready.decrementAndGet() == 0) {\r
445                     element.setHint(DiagramHints.PROPERTIES, new HashMap<String, Object>(properties));\r
446                 }\r
447 \r
448             }\r
449 \r
450         });\r
451 \r
452     }\r
453 \r
454     private EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue)\r
455             throws DatabaseException {\r
456         if (graph.isSubrelationOf(attachmentRelation, DIA.IsTailConnectorOf))\r
457             return EdgeEnd.Begin;\r
458         if (graph.isSubrelationOf(attachmentRelation, DIA.IsHeadConnectorOf))\r
459             return EdgeEnd.End;\r
460         return defaultValue;\r
461     }\r
462 \r
463     private ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection,\r
464             IElement element) throws DatabaseException {\r
465         Resource connectionType = null;\r
466         if (modelingRules != null)\r
467             connectionType = modelingRules.getConnectionType(graph, connection);\r
468         if (connectionType == null)\r
469             connectionType = graph.getPossibleObject(connection, STR.HasConnectionType);\r
470 \r
471         ConnectionVisuals cv = null;\r
472         if (connectionType != null)\r
473             cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType),\r
474                     TransientCacheListener.<ConnectionVisuals> instance());\r
475 \r
476 \r
477         Color lineColor = element.getHint(ElementHints.KEY_TEXT_COLOR);\r
478         if (lineColor == null)\r
479             lineColor = (cv != null && cv.toColor() != null) ? cv.toColor() : Color.DARK_GRAY;\r
480 \r
481             Stroke lineStroke = cv != null ? cv.stroke : null;\r
482             if (lineStroke == null)\r
483                 lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0);\r
484 \r
485             return new FlowConnectionStyle(\r
486                     lineColor,\r
487                     lineStroke,\r
488                     connection);\r
489     }\r
490 \r
491     /**\r
492      * @param graph\r
493      * @param STR\r
494      * @param connection\r
495      * @param connector\r
496      * @return connection relation statement from diagram connection connector\r
497      *         to a node\r
498      * @throws DatabaseException\r
499      */\r
500     private static Statement findTerminalStatement(ReadGraph graph, StructuralResource2 STR, Resource connection,\r
501             Resource connector) throws DatabaseException {\r
502         for (Statement stm : graph.getStatements(connector, STR.Connects)) {\r
503             if (connection.equals(stm.getObject()))\r
504                 continue;\r
505             return stm;\r
506         }\r
507         return null;\r
508     }\r
509 \r
510     public ILineEndStyle loadLineEndStyle(ReadGraph graph, IElement te, Resource attachmentRelation, Color color, float lineWidth)\r
511             throws DatabaseException {\r
512         ILineEndStyle style;\r
513         // TODO: change bounds according to terminal type: Very small rectangle for Valves, Text box size for Stocks and Clouds\r
514         if(te.getElementClass().containsClass(ValveSceneGraph.class)) {\r
515             style =  new FlowArrowLineStyle("none 0 0 0", color);\r
516         } else {\r
517             if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) {\r
518                 float arrowSize = lineWidth * 1.3f;\r
519                 style = new FlowArrowLineStyle("fill " + arrowSize + " " + arrowSize + " 0", color);\r
520             } else {\r
521                 style =  new FlowArrowLineStyle("none 0 0 0", color);\r
522             }\r
523         }\r
524         return style;\r
525     }\r
526 \r
527     /**\r
528      * @param graph\r
529      * @param element\r
530      * @return\r
531      * @throws DatabaseException\r
532      */\r
533     private static AffineTransform getWorldTransform(ReadGraph graph, Resource element) throws DatabaseException {\r
534         ModelingResources MOD = ModelingResources.getInstance(graph);\r
535         AffineTransform result = DiagramGraphUtil.getAffineTransform(graph, element);\r
536         while (true) {\r
537             Resource parentComponent = graph.getPossibleObject(element, MOD.HasParentComponent);\r
538             if (parentComponent == null)\r
539                 return result;\r
540             element = graph.getPossibleObject(parentComponent, MOD.ComponentToElement);\r
541             if (element == null)\r
542                 return result;\r
543             AffineTransform tr = DiagramGraphUtil.getAffineTransform(graph, element);\r
544             tr.setToTranslation(tr.getTranslateX(), tr.getTranslateY());\r
545             result.preConcatenate(tr);\r
546         }\r
547     }\r
548 \r
549 \r
550     protected void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) {\r
551         session.asyncRequest(RouteGraphConnection.synchronizer(connection, event));\r
552     }\r
553 \r
554     /**\r
555      * Must have this in order for {@link TopologicalSelectionExpander} to work.\r
556      * Otherwise this is pretty useless and should be deprecated altogether.\r
557      * \r
558      * @see ElementHints#KEY_CONNECTION_ENTITY\r
559      */\r
560     static class CE implements ConnectionEntity {\r
561 \r
562         \r
563         private IDiagram diagram;\r
564 \r
565         private transient DataElementMap dataMap;\r
566         \r
567         /**\r
568          * The connection instance resource in the graph backend.\r
569          */\r
570         final Resource               connection;\r
571 \r
572         /**\r
573          * The connection entity element which is a part of the diagram.\r
574          */\r
575         IElement               connectionElement;\r
576 \r
577         /**\r
578          * @see #getTerminalConnections(Collection)\r
579          */\r
580         final Set<BackendConnection> backendConnections;\r
581 \r
582         /**\r
583          * Cache.\r
584          */\r
585         Set<Connection>              terminalConnections;\r
586 \r
587         \r
588         public CE(IDiagram diagram, Resource connection, IElement connectionElement, Set<BackendConnection> backendConnections) {\r
589             if (connectionElement == null)\r
590                 throw new NullPointerException("null connection element");\r
591             this.diagram = diagram;\r
592             this.dataMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
593             this.connection = connection;\r
594             this.connectionElement = connectionElement;\r
595             this.backendConnections = backendConnections;\r
596             IElement ce = getConnection0();\r
597             if (ce != null)\r
598                 this.connectionElement = ce;\r
599         }\r
600 \r
601         public IElement getConnection0() {\r
602             IElement connectionElement = dataMap.getElement(diagram, connection);\r
603             return connectionElement;\r
604         }\r
605 \r
606         @Override\r
607         public IElement getConnection() {\r
608             IElement c = getConnection0();\r
609             if (c == null)\r
610                 c = this.connectionElement;\r
611             return c;\r
612         }\r
613 \r
614         public Object getConnectionObject() {\r
615             return connection;\r
616         }\r
617 \r
618         public IElement getConnectionElement() {\r
619             return connectionElement;\r
620         }\r
621 \r
622         @Override\r
623         public Collection<IElement> getBranchPoints(Collection<IElement> result) {\r
624             return result != null ? result : Collections.<IElement> emptyList();\r
625         }\r
626 \r
627         @Override\r
628         public Collection<IElement> getSegments(Collection<IElement> result) {\r
629             return result != null ? result : Collections.<IElement> emptyList();\r
630         }\r
631 \r
632         @Override\r
633         public Collection<Connection> getTerminalConnections(Collection<Connection> result) {\r
634             if (terminalConnections == null)\r
635                 terminalConnections = calculateTerminalConnections();\r
636             if (result == null)\r
637                 result = new ArrayList<Connection>(terminalConnections);\r
638             else\r
639                 result.addAll(terminalConnections);\r
640             return terminalConnections;\r
641         }\r
642 \r
643         private Set<Connection> calculateTerminalConnections() {\r
644             DataElementMap dem = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
645             Set<Connection> result = new HashSet<Connection>();\r
646             ArrayList<Terminal> ts = new ArrayList<Terminal>();\r
647             for (BackendConnection bc : backendConnections) {\r
648                 IElement e = dem.getElement(diagram, bc.node);\r
649                 if (e == null)\r
650                     continue;\r
651                 TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class);\r
652                 tt.getTerminals(e, ts);\r
653                 for (Terminal t : ts) {\r
654                     if (t instanceof ResourceTerminal) {\r
655                         ResourceTerminal rt = (ResourceTerminal) t;\r
656                         if (bc.terminal.equals(rt.getResource())) {\r
657                             result.add(new Connection(connectionElement, bc.end, e, t));\r
658                             break;\r
659                         }\r
660                     }\r
661                 }\r
662             }\r
663             return result;\r
664         }\r
665 \r
666         @Override\r
667         public void setListener(ConnectionListener listener) {\r
668             throw new UnsupportedOperationException();\r
669         }\r
670 \r
671         @Override\r
672         public String toString() {\r
673             return getClass().getSimpleName() + "[resource=" + connection + ", connectionElement=" + connectionElement\r
674                     + "]";\r
675         }\r
676 \r
677     }\r
678 \r
679     public static class BackendConnection {\r
680         public final Resource node;\r
681         public final Resource terminal;\r
682         public final EdgeEnd  end;\r
683         public BackendConnection(EdgeEnd end, Resource node, Resource terminal) {\r
684             assert end != null;\r
685             assert node != null;\r
686             assert terminal != null;\r
687             this.end = end;\r
688             this.node = node;\r
689             this.terminal = terminal;\r
690         }\r
691         @Override\r
692         public boolean equals(Object obj) {\r
693             if (this == obj)\r
694                 return true;\r
695             if (!(obj instanceof Connection))\r
696                 return false;\r
697             Connection other = (Connection) obj;\r
698             return other.terminal == terminal\r
699                     && other.node == node\r
700                     && other.end == end;\r
701         }\r
702         @Override\r
703         public int hashCode() {\r
704             final int prime = 31;\r
705             int result = 1;\r
706             result = prime * result + end.hashCode();\r
707             result = prime * result + ((node == null) ? 0 : node.hashCode());\r
708             result = prime * result + ((terminal == null) ? 0 : terminal.hashCode());\r
709             return result;\r
710         }\r
711         @Override\r
712         public String toString() {\r
713             return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]";\r
714         }\r
715     }\r
716 \r
717 }\r
718 \r