]> gerrit.simantics Code Review - simantics/sysdyn.git/blob
e08193eac62d848046d95e6d5deea115d8c55b29
[simantics/sysdyn.git] /
1 package org.simantics.sysdyn.ui.editor.participant;\r
2 \r
3 import java.awt.BasicStroke;\r
4 import java.awt.Color;\r
5 import java.awt.geom.AffineTransform;\r
6 import java.awt.geom.Path2D;\r
7 import java.awt.geom.Point2D;\r
8 import java.awt.geom.Rectangle2D;\r
9 import java.util.ArrayList;\r
10 import java.util.Collection;\r
11 import java.util.Collections;\r
12 import java.util.Deque;\r
13 import java.util.Iterator;\r
14 import java.util.List;\r
15 \r
16 import org.simantics.db.ReadGraph;\r
17 import org.simantics.db.Resource;\r
18 import org.simantics.db.WriteGraph;\r
19 import org.simantics.db.common.request.WriteRequest;\r
20 import org.simantics.db.exception.DatabaseException;\r
21 import org.simantics.db.request.Read;\r
22 import org.simantics.diagram.participant.ConnectTool2;\r
23 import org.simantics.diagram.participant.ConnectionBuilder;\r
24 import org.simantics.diagram.participant.ControlPoint;\r
25 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
26 import org.simantics.g2d.connection.IConnectionAdvisor;\r
27 import org.simantics.g2d.diagram.DiagramHints;\r
28 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
29 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\r
30 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;\r
31 import org.simantics.g2d.element.ElementClass;\r
32 import org.simantics.g2d.element.ElementClasses;\r
33 import org.simantics.g2d.element.ElementUtils;\r
34 import org.simantics.g2d.element.IElement;\r
35 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
36 import org.simantics.g2d.element.handler.impl.StaticObjectAdapter;\r
37 import org.simantics.g2d.element.impl.Element;\r
38 import org.simantics.g2d.elementclass.BranchPointClass;\r
39 import org.simantics.g2d.elementclass.FlagClass;\r
40 import org.simantics.g2d.routing.Constants;\r
41 import org.simantics.g2d.routing.IConnection;\r
42 import org.simantics.g2d.routing.IRouter2;\r
43 import org.simantics.g2d.routing.TrivialRouter2;\r
44 import org.simantics.scenegraph.g2d.G2DParentNode;\r
45 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
46 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;\r
47 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
48 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
49 import org.simantics.scenegraph.g2d.nodes.ShapeNode;\r
50 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
51 import org.simantics.structural2.modelingRules.ConnectionJudgement;\r
52 import org.simantics.sysdyn.SysdynResource;\r
53 import org.simantics.sysdyn.ui.editor.routing.FlowRouter;\r
54 import org.simantics.sysdyn.ui.elements.CloudFactory;\r
55 import org.simantics.sysdyn.ui.elements.SysdynElementHints;\r
56 import org.simantics.sysdyn.ui.elements.ValveFactory.ValveSceneGraph;\r
57 import org.simantics.sysdyn.ui.elements.connections.ConnectionClasses;\r
58 import org.simantics.ui.SimanticsUI;\r
59 import org.simantics.utils.datastructures.Callback;\r
60 import org.simantics.utils.datastructures.Pair;\r
61 import org.simantics.utils.ui.ErrorLogger;\r
62 import org.simantics.utils.ui.ExceptionUtils;\r
63 \r
64 public class SysdynConnectTool extends ConnectTool2 {\r
65 \r
66         public SysdynConnectTool(TerminalInfo startTerminal, int mouseId,\r
67                         Point2D startCanvasPos) {\r
68                 super(startTerminal, mouseId, startCanvasPos);\r
69         }\r
70 \r
71         @Override\r
72         @SGInit\r
73         public void initSG(G2DParentNode parent) {\r
74                 ghostNode = parent.addNode(G2DParentNode.class);\r
75                 ghostNode.setZIndex(PAINT_PRIORITY);\r
76 \r
77                 ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);\r
78                 pathNode.setColor(Color.BLACK);\r
79                 pathNode.setStroke(new BasicStroke(1f));\r
80                 pathNode.setScaleStroke(true);\r
81                 pathNode.setZIndex(0);\r
82 \r
83                 G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);\r
84                 points.setZIndex(1);\r
85 \r
86                 updateSG();\r
87         }\r
88 \r
89         @Override\r
90         protected TerminalInfo createFlag(EdgeEnd connectionEnd) {\r
91                 ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);\r
92                 IElement e = Element.spawnNew(flagClass);\r
93 \r
94                 e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));\r
95                 e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);\r
96 \r
97                 TerminalInfo ti = new TerminalInfo();\r
98                 ti.e = e;\r
99 \r
100                 // start: this part changed to support overlapping terminals\r
101                 ArrayList<Terminal> terminals = new ArrayList<Terminal>();\r
102                 ElementUtils.getTerminals(e, terminals, false);\r
103                 ti.t = terminals.get(0);\r
104                 // end\r
105 \r
106                 ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);\r
107                 ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);\r
108 \r
109                 return ti;\r
110         }\r
111 \r
112         static class Segment {\r
113                 public final ControlPoint begin;\r
114                 public final ControlPoint end;\r
115                 public Path2D             path;\r
116 \r
117                 public Segment(ControlPoint begin, ControlPoint end) {\r
118                         this.begin = begin;\r
119                         this.end = end;\r
120                 }\r
121 \r
122                 @Override\r
123                 public String toString() {\r
124                         return "Segment[begin=" + begin + ", end=" + end + ", path=" + path + "]";\r
125                 }\r
126         }\r
127 \r
128         private List<Segment> toSegments(Deque<ControlPoint> points) {\r
129                 if (points.isEmpty())\r
130                         return Collections.emptyList();\r
131 \r
132                 List<Segment> segments = new ArrayList<Segment>();\r
133 \r
134                 Iterator<ControlPoint> it = points.iterator();\r
135                 ControlPoint prev = it.next();\r
136                 while (it.hasNext()) {\r
137                         ControlPoint next = it.next();\r
138                         segments.add(new Segment(prev, next));\r
139                         prev = next;\r
140                 }\r
141 \r
142                 return segments;\r
143         }\r
144 \r
145         public interface SysdynConnection extends IConnection { }\r
146 \r
147         @Override\r
148         protected void updateSG() {\r
149                 if (controlPoints.isEmpty())\r
150                         return;\r
151 \r
152                 // Route connection segments separately\r
153                 IRouter2 router = ElementUtils.getHintOrDefault(diagram, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);\r
154                 final List<Segment> segments = toSegments(controlPoints);\r
155                 //System.out.println("controlpoints: " + controlPoints);\r
156                 //System.out.println("segments: " + segments);\r
157                 router.route(new SysdynConnection() {\r
158                         @Override\r
159                         public Collection<? extends Object> getSegments() {\r
160                                 return segments;\r
161                         }\r
162 \r
163                         @Override\r
164                         public Connector getBegin(Object seg) {\r
165                                 return getConnector(((Segment) seg).begin);\r
166                         }\r
167 \r
168                         @Override\r
169                         public Connector getEnd(Object seg) {\r
170                                 return getConnector(((Segment) seg).end);\r
171                         }\r
172 \r
173                         private Connector getConnector(ControlPoint cp) {\r
174                                 Connector c = new Connector();\r
175                                 c.x = cp.getPosition().getX();\r
176                                 c.y = cp.getPosition().getY();\r
177 \r
178                                 c.allowedDirections = Constants.EAST_FLAG | Constants.WEST_FLAG\r
179                                                 | Constants.NORTH_FLAG | Constants.SOUTH_FLAG;\r
180 \r
181                                 TerminalInfo ti = cp.getAttachedTerminal();\r
182                                 if (ti != null && (ti != startFlag && ti != endFlag)) {\r
183                                         if(ti.e.getElementClass().containsClass(ValveSceneGraph.class)) {\r
184                                                 Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D();\r
185                                                 c.parentObstacle = new Rectangle2D.Double(\r
186                                                                 bounds.getCenterX() - FlowRouter.OFFSET,\r
187                                                                 bounds.getCenterY() - FlowRouter.OFFSET, \r
188                                                                 FlowRouter.OFFSET * 2,\r
189                                                                 FlowRouter.OFFSET * 2);\r
190                                         } else {\r
191                                                 c.parentObstacle =  ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D();\r
192                                         }\r
193                                 } else if (ti != null && ti == startFlag) {\r
194                                         c.parentObstacle = org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),\r
195                                                         ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D());\r
196                                 } else if (ti != null && isEndingInFlag()) {\r
197                                         c.parentObstacle = org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),\r
198                                                         CloudFactory.CLOUD_IMAGE.getBounds());\r
199                                 } else {\r
200                                         c.parentObstacle = org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),\r
201                                                         BranchPointClass.DEFAULT_IMAGE2.getBounds());\r
202                                 }\r
203 \r
204                                 return c;\r
205                         }\r
206 \r
207                         @Override\r
208                         public void setPath(Object seg, Path2D path) {\r
209                                 ((Segment) seg).path = (Path2D) path.clone();\r
210                         }\r
211                 });\r
212 \r
213                 // Combine the routed paths\r
214                 Path2D path = new Path2D.Double();\r
215                 for (Segment seg : segments) {\r
216                         //System.out.println("SEG: " + seg);\r
217                         if (seg.path != null)\r
218                                 path.append(seg.path.getPathIterator(null), true);\r
219                 }\r
220 \r
221                 // Create scene graph to visualize the connection.\r
222                 ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);\r
223                 pathNode.setShape(path);\r
224 \r
225                 /*\r
226                  * Removed Points\r
227                  */\r
228 \r
229                 setDirty();\r
230         }\r
231 \r
232         @Override\r
233         protected Object canConnect(final IConnectionAdvisor advisor, final IElement endElement, final Terminal endTerminal) {\r
234                 final IElement se = startTerminal != null ? startTerminal.e : startFlag.e;\r
235                 final Terminal st = startTerminal != null ? startTerminal.t : null;\r
236 \r
237                 if(se.equals(endElement)) return null;\r
238                 if(Boolean.FALSE.equals(diagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS)) && endElement == null) {\r
239                         return null;\r
240                 }\r
241 \r
242                 if(endElement == null && endTerminal == null)\r
243                         return advisor.canBeConnected(null, se, st, endElement, endTerminal);\r
244 \r
245                 try {\r
246                         return SimanticsUI.getSession().syncRequest(new Read<Object>() {\r
247 \r
248                                 @Override\r
249                                 public Object perform(ReadGraph g) throws DatabaseException {\r
250 \r
251                                         // Checking if connection type can be connected to the intended endElement\r
252                                         SysdynResource sr = SysdynResource.getInstance(g);\r
253                                         StaticObjectAdapter soa = endElement.getElementClass().getSingleItem(StaticObjectAdapter.class);\r
254                                         Resource end = soa.adapt(Resource.class);\r
255                                         ElementClass dependency = elementClassProvider.get(ConnectionClasses.DEPENDENCY);\r
256                                         ElementClass flow = elementClassProvider.get(ConnectionClasses.FLOW);\r
257                                         ElementClass currentConnection = elementClassProvider.get(ElementClasses.CONNECTION);\r
258                                         if(currentConnection.equals(dependency)) {\r
259                                                 if(end.equals(sr.CloudSymbol)) return null;\r
260                                                 soa = se.getElementClass().getSingleItem(StaticObjectAdapter.class);\r
261                                                 Resource start = soa.adapt(Resource.class);\r
262                                                 if(g.isInheritedFrom(start, sr.ModuleSymbol) && !end.equals(sr.InputSymbol)) return null;\r
263                                                 if(end.equals(sr.ShadowSymbol)) return null;\r
264                                         } else if (currentConnection.equals(flow)) {\r
265                                                 if(!(end.equals(sr.StockSymbol) || end.equals(sr.ValveSymbol) || end.equals(sr.CloudSymbol))) return null;\r
266                                         } else {\r
267                                                 return null;\r
268                                         }\r
269 \r
270 \r
271                                         if (advisor == null)\r
272                                                 return Boolean.TRUE;     \r
273                                         return advisor.canBeConnected(g, se, st, endElement, endTerminal);\r
274                                 }\r
275 \r
276                         });\r
277                 } catch(DatabaseException e) {\r
278                         e.printStackTrace();\r
279                         return null;\r
280                 }\r
281 \r
282         }\r
283 \r
284         @Override\r
285         protected boolean processMouseMove(MouseMovedEvent me) {\r
286                 mouseHasMoved = true;\r
287 \r
288                 Point2D mouseControlPos = me.controlPosition;\r
289                 Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());\r
290 \r
291                 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
292                 if (snapAdvisor != null)\r
293                         snapAdvisor.snap(mouseCanvasPos);\r
294 \r
295                 // Record last snapped canvas position of mouse.\r
296                 this.lastMouseCanvasPos.setLocation(mouseCanvasPos);\r
297 \r
298                 if (isEndingInFlag()) {\r
299                         endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));\r
300                 }\r
301 \r
302                 List<TerminalInfo> tiList = ((SysdynPointerInteractor)pi).pickTerminals(me.controlPosition);\r
303                 TerminalInfo ti = null;\r
304 \r
305                 IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
306                 for(TerminalInfo info : tiList) {\r
307                         if(advisor == null || info.e == null || info.t == null)\r
308                                 continue;\r
309                         Object canConnect = canConnect(advisor, info.e, info.t);\r
310                         if (canConnect != null) {\r
311                                 ti = info;\r
312                                 break;\r
313                         }\r
314                 }\r
315 \r
316                 if (ti != null && !isStartTerminal(ti.e, ti.t)) {\r
317                         Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);\r
318                         if (canConnect != null) {\r
319                                 connectionJudgment = canConnect.first;\r
320 \r
321                                 if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {\r
322                                         controlPoints.getLast()\r
323                                         .setPosition(ti.posDia)\r
324                                         .setAttachedToTerminal(ti);\r
325 \r
326                                         endTerminal = ti;\r
327                                 }\r
328 \r
329                                 // Make sure that we are ending with a flag if ALT is pressed\r
330                                 // and no end terminal is defined. If we are in flow creation\r
331                                 // mode, we want to show the terminal cloud (or flag) even when\r
332                                 // alt is not pressed.\r
333                                 if (inFlowMode() && flowInProgress() && !startTerminals.isEmpty())\r
334                                         endWithoutTerminal(lastMouseCanvasPos, true);\r
335                                 else\r
336                                         endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));\r
337                                 updateSG();\r
338                                 return false;\r
339                         }\r
340                 }\r
341 \r
342                 connectionJudgment = null;\r
343                 if (isEndTerminalDefined()) {\r
344                         // CASE: Mouse was previously on top of a valid terminal to end\r
345                         // the connection. Now the mouse has been moved where there is\r
346                         // no longer a terminal to connect to.\r
347                         //\r
348                         // => Disconnect the last edge segment from the previous\r
349                         // terminal, mark endElement/endTerminal non-existent\r
350                         // and connect the disconnected edge to a new branch point.\r
351 \r
352                         controlPoints.getLast()\r
353                         .setPosition(mouseCanvasPos)\r
354                         .setDirection(calculateCurrentBranchPointDirection())\r
355                         .setAttachedToTerminal(null);\r
356 \r
357                         endTerminal = null;\r
358                 } else {\r
359                         // CASE: Mouse was not previously on top of a valid ending\r
360                         // element terminal.\r
361                         //\r
362                         // => Move and re-orient last branch point.\r
363 \r
364                         controlPoints.getLast()\r
365                         .setPosition(mouseCanvasPos)\r
366                         .setDirection(calculateCurrentBranchPointDirection());\r
367                 }\r
368 \r
369                 // Make sure that we are ending with a flag if ALT is pressed and no end\r
370                 // terminal is defined. If we are in flow creation mode, we want to show \r
371                 // the terminal cloud (or flag) even when alt is not pressed.\r
372                 if (inFlowMode() && flowInProgress() && !startTerminals.isEmpty())\r
373                         endWithoutTerminal(lastMouseCanvasPos, true);\r
374                 else\r
375                         endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));\r
376                 updateSG();\r
377                 return false;\r
378         }\r
379 \r
380         @Override\r
381         protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {\r
382                 MouseButtonEvent me = e;\r
383 \r
384                 // Do nothing before the mouse has moved at least a little.\r
385         // This prevents the user from ending the connection right where\r
386         // it started.\r
387                 if (!mouseHasMoved)\r
388                         return true;\r
389 \r
390                 if (me.button == MouseEvent.LEFT_BUTTON || \r
391                                 (me.button == MouseEvent.RIGHT_BUTTON && flowInProgress() && !inFlowMode())) {\r
392                         Point2D mouseControlPos = me.controlPosition;\r
393                         Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());\r
394 \r
395                         ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
396                         if (snapAdvisor != null)\r
397                                 snapAdvisor.snap(mouseCanvasPos);\r
398 \r
399                         // Clicked on an allowed end terminal. End connection & end mode.\r
400                         if (isEndTerminalDefined()) {\r
401                                 createConnection();\r
402                                 remove();\r
403                                 return true;\r
404                         } else {\r
405                                 // Finish connection in thin air only if the\r
406                                 // connection was started from a valid terminal.\r
407                                 \r
408                                 // If we are in flow creation mode, we want to be able to\r
409                                 // create the terminal cloud (or flag) without having to \r
410                                 // press alt.\r
411                                 \r
412                                 if (!startTerminals.isEmpty() && ((me.stateMask & MouseEvent.ALT_MASK) != 0 || \r
413                                                 (inFlowMode() && flowInProgress()))) {\r
414                                         Pair<ConnectionJudgement, TerminalInfo> pair = canConnect(null, null);\r
415                                         if (pair != null) {\r
416                                                 connectionJudgment = (ConnectionJudgement) pair.first;\r
417                                                 selectedStartTerminal = pair.second;\r
418                                                 createConnection();\r
419                                                 setDirty();\r
420                                                 remove();\r
421                                         } else {\r
422                                                 // Inform the user why connection couldn't be created.\r
423                                                 ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection.", null);\r
424                                         }\r
425                                         return true;\r
426                                 } else if (routePointsAllowed()\r
427                                                 && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {\r
428                                         // Add new connection control point.\r
429                                         controlPoints.add(newControlPointWithCalculatedDirection(mouseCanvasPos));\r
430                                         resetForcedBranchPointDirection();\r
431                                         updateSG();\r
432                                 }\r
433                         }\r
434                 }\r
435 \r
436                 return true;\r
437         }\r
438         \r
439         private boolean inFlowMode() {\r
440                 return SysdynElementHints.FLOW_TOOL.equals(getHint(SysdynElementHints.SYSDYN_KEY_TOOL));\r
441         }\r
442         \r
443         private boolean flowInProgress() {\r
444                 return elementClassProvider.get(ElementClasses.CONNECTION).equals(elementClassProvider.get(ConnectionClasses.FLOW));\r
445         }\r
446 \r
447         @Override\r
448         protected void createConnection() {\r
449 \r
450                 if(this.connectionJudgment == null) return;\r
451 \r
452                 final ConnectionJudgement judgment = this.connectionJudgment;\r
453                 // ConnectionBuilder changed to SysdynconnectionBuilder to support overlapping terminals and valve creation\r
454                 final ConnectionBuilder builder = new SysdynConnectionBuilder(this.diagram);\r
455                 final Deque<ControlPoint> controlPoints = this.controlPoints;\r
456                 final TerminalInfo startTerminal = this.startTerminal;\r
457                 final TerminalInfo endTerminal = this.endTerminal;\r
458 \r
459                 SimanticsUI.getSession().asyncRequest(new WriteRequest() {\r
460                         @Override\r
461                         public void perform(WriteGraph graph) throws DatabaseException {\r
462                                 builder.create(graph, judgment, controlPoints, startTerminal, endTerminal);\r
463                         }\r
464                 }, new Callback<DatabaseException>() {\r
465                         @Override\r
466                         public void run(DatabaseException parameter) {\r
467                                 if (parameter != null)\r
468                                         ExceptionUtils.logAndShowError(parameter);\r
469                         }\r
470                 });\r
471         }    \r
472 }\r