Sync git svn branch with SVN repository r33392.
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / participant / ConnectTool2.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in 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.diagram.participant;\r
13 \r
14 import java.awt.AlphaComposite;\r
15 import java.awt.BasicStroke;\r
16 import java.awt.Color;\r
17 import java.awt.Composite;\r
18 import java.awt.geom.AffineTransform;\r
19 import java.awt.geom.Path2D;\r
20 import java.awt.geom.Point2D;\r
21 import java.awt.geom.Rectangle2D;\r
22 import java.util.ArrayDeque;\r
23 import java.util.ArrayList;\r
24 import java.util.Arrays;\r
25 import java.util.Collection;\r
26 import java.util.Collections;\r
27 import java.util.Deque;\r
28 import java.util.Iterator;\r
29 import java.util.List;\r
30 \r
31 import org.simantics.Simantics;\r
32 import org.simantics.db.ReadGraph;\r
33 import org.simantics.db.Resource;\r
34 import org.simantics.db.WriteGraph;\r
35 import org.simantics.db.common.request.UniqueRead;\r
36 import org.simantics.db.common.request.WriteRequest;\r
37 import org.simantics.db.common.utils.NameUtils;\r
38 import org.simantics.db.exception.DatabaseException;\r
39 import org.simantics.diagram.connection.RouteGraph;\r
40 import org.simantics.diagram.connection.RouteGraphConnectionClass;\r
41 import org.simantics.diagram.connection.RouteLine;\r
42 import org.simantics.diagram.connection.RouteTerminal;\r
43 import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
44 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;\r
45 import org.simantics.diagram.content.ResourceTerminal;\r
46 import org.simantics.diagram.stubs.DiagramResource;\r
47 import org.simantics.diagram.synchronization.ISynchronizationContext;\r
48 import org.simantics.diagram.synchronization.SynchronizationHints;\r
49 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;\r
50 import org.simantics.g2d.canvas.ICanvasContext;\r
51 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
52 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;\r
53 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
54 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
55 import org.simantics.g2d.connection.IConnectionAdvisor;\r
56 import org.simantics.g2d.diagram.DiagramHints;\r
57 import org.simantics.g2d.diagram.DiagramUtils;\r
58 import org.simantics.g2d.diagram.IDiagram;\r
59 import org.simantics.g2d.diagram.handler.PickContext;\r
60 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
61 import org.simantics.g2d.diagram.participant.ElementPainter;\r
62 import org.simantics.g2d.diagram.participant.TerminalPainter;\r
63 import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;\r
64 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;\r
65 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;\r
66 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\r
67 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;\r
68 import org.simantics.g2d.element.ElementClass;\r
69 import org.simantics.g2d.element.ElementClasses;\r
70 import org.simantics.g2d.element.ElementUtils;\r
71 import org.simantics.g2d.element.IElement;\r
72 import org.simantics.g2d.element.IElementClassProvider;\r
73 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
74 import org.simantics.g2d.element.handler.SceneGraph;\r
75 import org.simantics.g2d.element.handler.TerminalTopology;\r
76 import org.simantics.g2d.element.handler.impl.BranchPointTerminal;\r
77 import org.simantics.g2d.element.impl.Element;\r
78 import org.simantics.g2d.elementclass.BranchPoint;\r
79 import org.simantics.g2d.elementclass.BranchPoint.Direction;\r
80 import org.simantics.g2d.elementclass.FlagClass;\r
81 import org.simantics.g2d.elementclass.FlagHandler;\r
82 import org.simantics.g2d.participant.RenderingQualityInteractor;\r
83 import org.simantics.g2d.participant.TransformUtil;\r
84 import org.simantics.g2d.utils.geom.DirectionSet;\r
85 import org.simantics.modeling.ModelingResources;\r
86 import org.simantics.scenegraph.g2d.G2DParentNode;\r
87 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
88 import org.simantics.scenegraph.g2d.events.KeyEvent;\r
89 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
90 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
91 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;\r
92 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;\r
93 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
94 import org.simantics.scenegraph.g2d.events.command.CommandEvent;\r
95 import org.simantics.scenegraph.g2d.events.command.Commands;\r
96 import org.simantics.scenegraph.g2d.nodes.BranchPointNode;\r
97 import org.simantics.scenegraph.g2d.nodes.ShapeNode;\r
98 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;\r
99 import org.simantics.scenegraph.utils.GeometryUtils;\r
100 import org.simantics.scenegraph.utils.Quality;\r
101 import org.simantics.structural2.modelingRules.ConnectionJudgement;\r
102 import org.simantics.utils.datastructures.Pair;\r
103 import org.simantics.utils.logging.TimeLogger;\r
104 import org.simantics.utils.ui.ErrorLogger;\r
105 import org.simantics.utils.ui.ExceptionUtils;\r
106 \r
107 import gnu.trove.map.hash.THashMap;\r
108 \r
109 /**\r
110  * A basic tool for making connection on diagrams.\r
111  * \r
112  * This version defines the starting, ending and route points of a connection.\r
113  * The routing itself is left up to the diagram router employed by\r
114  * {@link DiagramUtils#validateAndFix(IDiagram, ICanvasContext)}.\r
115  * \r
116  * Manual:\r
117  * \r
118  * This tool is added to the diagram when a connection sequence is initiated by\r
119  * another participant. PointerInteractor is one such participant which adds the\r
120  * tool when a terminal or non-terminal-occupied canvas space is ALT+clicked\r
121  * (see {@link PointerInteractor#checkInitiateConnectTool(MouseEvent, Point2D)}\r
122  * ). The connection will be finished when another allowed terminal is clicked\r
123  * upon or empty canvas space is ALT+clicked. Route points for the connection\r
124  * can be created by clicking around on non-terminal-occupied canvas space while\r
125  * connecting.\r
126  * \r
127  * <p>\r
128  * Connections can be started from and ended in flags by pressing ALT while\r
129  * left-clicking.\r
130  * \r
131  * @author Tuukka Lehtonen\r
132  */\r
133 public class ConnectTool2 extends AbstractMode {\r
134 \r
135     public static final int          PAINT_PRIORITY        = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;\r
136 \r
137     @Reference\r
138     protected RenderingQualityInteractor quality;\r
139 \r
140     @Dependency\r
141     protected TransformUtil          util;\r
142 \r
143     @Dependency\r
144     protected ElementPainter         diagramPainter;\r
145 \r
146     @Dependency\r
147     protected PointerInteractor      pi;\r
148 \r
149     @Dependency\r
150     protected PickContext            pickContext;\r
151 \r
152     /**\r
153      * Start element terminal of the connection. <code>null</code> if connection\r
154      * was started from a flag or a branch point.\r
155      * \r
156      * The value is received by the constructor.\r
157      */\r
158     protected List<TerminalInfo>     startTerminals;\r
159 \r
160     /**\r
161      * Refers to any of the possible overlapping start terminals. The value is\r
162      * taken from the first index of {@link #startTerminals} assuming that the\r
163      * first one is the nearest. It is <code>null</code> if\r
164      * {@link #startTerminals} is empty.\r
165      */\r
166     protected TerminalInfo           startTerminal;\r
167 \r
168     protected TerminalInfo           startFlag;\r
169 \r
170     /**\r
171      * Starting position of the connection, received as an external argument.\r
172      */\r
173     protected final Point2D          startPos;\r
174 \r
175     /**\r
176      * <code>true</code> if this tool should create connection continuation\r
177      * flags, <code>false</code> otherwise.\r
178      */\r
179     protected boolean                createFlags;\r
180 \r
181     /**\r
182      * \r
183      */\r
184     protected IElementClassProvider  elementClassProvider;\r
185 \r
186     /**\r
187      * \r
188      */\r
189     protected Deque<ControlPoint>    controlPoints         = new ArrayDeque<ControlPoint>();\r
190 \r
191     /**\r
192      * Contains <code>null</code> when a connection is started from a new flag\r
193      * or one of the terminals in {@link #startTerminals} when a connection is\r
194      * being created starting from a terminal or possibly a set of terminals.\r
195      * \r
196      * <p>\r
197      * Note that this is different from {@link #startTerminal} which simply\r
198      * represents the first element of {@link #startTerminals}.\r
199      * \r
200      * <p>\r
201      * Only when this value and {@link #endTerminal} is properly set will a\r
202      * connection be created between two element terminals.\r
203      */\r
204     protected TerminalInfo           selectedStartTerminal;\r
205 \r
206     /**\r
207      * Element terminal of connection end element. <code>null</code> if\r
208      * connection cannot be ended where it is currently being attempted to end.\r
209      */\r
210     protected TerminalInfo           endTerminal;\r
211 \r
212     /**\r
213      * The latest connectability judgment from the active\r
214      * {@link IConnectionAdvisor} should the connection happen between\r
215      * {@link #selectedStartTerminal} and {@link #endTerminal}.\r
216      */\r
217     protected ConnectionJudgement    connectionJudgment;\r
218 \r
219     /**\r
220      * The latest connectability judgment from the active\r
221      * {@link IConnectionAdvisor} should the connection happen between\r
222      * {@link #selectedStartTerminal} and {@link #lastRouteGraphTarget}.\r
223      */\r
224     protected ConnectionJudgement    attachToConnectionJudgement;\r
225 \r
226     /**\r
227      * If non-null during connection drawing this field tells the direction\r
228      * forced for the current branch point by the user through the UI commands\r
229      * {@link Commands#ROTATE_ELEMENT_CCW} and\r
230      * {@link Commands#ROTATE_ELEMENT_CW}.\r
231      */\r
232     private Direction                forcedBranchPointDirection;\r
233 \r
234     /**\r
235      * A temporary variable for use with\r
236      * {@link TerminalTopology#getTerminals(IElement, Collection)}.\r
237      */\r
238     protected Collection<Terminal>   terminals             = new ArrayList<Terminal>();\r
239 \r
240     /**\r
241      * Previous mouse canvas position recorded by\r
242      * {@link #processMouseMove(MouseMovedEvent)}.\r
243      */\r
244     protected Point2D                lastMouseCanvasPos    = new Point2D.Double();\r
245 \r
246     /**\r
247      * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been\r
248      * invoked at least once. This is used to tell whether to allow creation of\r
249      * branch points or finising the connection in thin air. It will not be\r
250      * allowed if the mouse has not moved at all since starting the connection.\r
251      */\r
252     protected boolean                mouseHasMoved         = false;\r
253 \r
254     protected TerminalHoverStrategy  originalStrategy      = null;\r
255 \r
256     protected TerminalHoverStrategy  terminalHoverStrategy = new TerminalHoverStrategy() {\r
257         @Override\r
258         public boolean highlightEnabled() {\r
259             return !isEndingInFlag();\r
260         }\r
261 \r
262         @Override\r
263         public boolean highlight(TerminalInfo ti) {\r
264             boolean reflexive = isStartTerminal(ti.e, ti.t);\r
265             if (reflexive && !allowReflexiveConnections())\r
266                 return false;\r
267 \r
268             return canConnect(ti.e, ti.t) != null;\r
269         }\r
270     };\r
271 \r
272     protected final static Composite ALPHA_COMPOSITE       = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);\r
273 \r
274     /**\r
275      * Root scene graph node for all visualization performed by this tool.\r
276      */\r
277     protected G2DParentNode          ghostNode;\r
278 \r
279     /**\r
280      * Indicates whether the connection is about to be ended into a new\r
281      * flag/branchpoint or not.\r
282      */\r
283     protected TerminalInfo           endFlag;\r
284 \r
285     protected G2DParentNode          endFlagNode;\r
286 \r
287     private RouteGraphTarget         lastRouteGraphTarget;\r
288 \r
289     /**\r
290      * @param startTerminal\r
291      * @param mouseId\r
292      * @param startCanvasPos\r
293      */\r
294     public ConnectTool2(TerminalInfo startTerminal, int mouseId, Point2D startCanvasPos) {\r
295         this(startTerminal == null ? Collections.<TerminalInfo> emptyList()\r
296                 : Collections.singletonList(startTerminal),\r
297                 mouseId,\r
298                 startCanvasPos);\r
299     }\r
300 \r
301     /**\r
302      * @param startTerminals\r
303      * @param mouseId\r
304      * @param startCanvasPos\r
305      */\r
306     public ConnectTool2(List<TerminalInfo> startTerminals, int mouseId, Point2D startCanvasPos) {\r
307         super(mouseId);\r
308 \r
309         if (startCanvasPos == null)\r
310             throw new NullPointerException("null start position");\r
311         if (startTerminals == null)\r
312             throw new NullPointerException("null start terminals");\r
313 \r
314         this.startPos = startCanvasPos;\r
315         this.lastMouseCanvasPos.setLocation(startPos);\r
316 \r
317         this.startTerminals = startTerminals;\r
318         this.startTerminal = startTerminals.isEmpty() ? null : startTerminals.get(0);\r
319     }\r
320 \r
321     @Override\r
322     public void addedToContext(ICanvasContext ctx) {\r
323         super.addedToContext(ctx);\r
324 \r
325         if (quality != null)\r
326             quality.setStaticQuality(Quality.LOW);\r
327 \r
328         // Force terminals to always be highlighted without pressing certain\r
329         // keys or key combinations.\r
330         originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);\r
331         setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);\r
332     }\r
333 \r
334     @Override\r
335     protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {\r
336         if (newDiagram != null) {\r
337             // Get IElementClassProvider\r
338             ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);\r
339             if (ctx != null) {\r
340                 this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);\r
341             }\r
342 \r
343             // See if flags should be created or not.\r
344             this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));\r
345             startConnection();\r
346         }\r
347     }\r
348 \r
349     @Override\r
350     public void removedFromContext(ICanvasContext ctx) {\r
351         if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {\r
352             if (originalStrategy != null)\r
353                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);\r
354             else\r
355                 removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);\r
356         }\r
357 \r
358         if (quality != null)\r
359             quality.setStaticQuality(null);\r
360 \r
361         super.removedFromContext(ctx);\r
362     }\r
363 \r
364     protected void startConnection() {\r
365         Point2D startPos = (Point2D) this.startPos.clone();\r
366         ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
367         if (snapAdvisor != null)\r
368             snapAdvisor.snap(startPos);\r
369 \r
370         // Resolve the first element and terminal of the connection.\r
371         ControlPoint start = new ControlPoint(startPos);\r
372 \r
373         if (startTerminal != null) {\r
374             assert ElementUtils.peekDiagram(startTerminal.e) == diagram;\r
375             Point2D terminalPos = new Point2D.Double(startTerminal.posDia.getTranslateX(),\r
376                     startTerminal.posDia.getTranslateY());\r
377             start.setPosition(terminalPos).setAttachedToTerminal(startTerminal);\r
378         } else {\r
379             // Create TerminalInfo describing the flag to be created.\r
380             if (createFlags) {\r
381                 // This prevents connection creation from creating a branch\r
382                 // point in place of this flag.\r
383                 startFlag = createFlag(EdgeEnd.Begin);\r
384                 start.setAttachedToTerminal(startFlag);\r
385                 showElement(ghostNode, "startFlag", startFlag.e, startPos);\r
386             }\r
387         }\r
388         controlPoints.add(start);\r
389         controlPoints.add(new ControlPoint(startPos));\r
390 \r
391         // Make sure that we are ending with a flag if ALT is pressed.\r
392         // This makes the tool always start with a flag which can be quite\r
393         // cumbersome and is therefore disabled. The current version will not\r
394         // end the connection if the mouse has not moved at all.\r
395         //if (keyUtil.isKeyPressed(java.awt.event.KeyEvent.VK_ALT)) {\r
396         //    endWithoutTerminal(lastMouseCanvasPos, true);\r
397         //}\r
398     }\r
399 \r
400     @SGInit\r
401     public void initSG(G2DParentNode parent) {\r
402         ghostNode = parent.addNode(G2DParentNode.class);\r
403         ghostNode.setZIndex(PAINT_PRIORITY);\r
404 \r
405         ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);\r
406         pathNode.setColor(new Color(160, 0, 0));\r
407         pathNode.setStroke(new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10,\r
408                 new float[] { 0.5f, 0.2f }, 0));\r
409         pathNode.setScaleStroke(false);\r
410         pathNode.setZIndex(0);\r
411 \r
412         G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);\r
413         points.setZIndex(1);\r
414 \r
415         updateSG();\r
416     }\r
417 \r
418     private RouteTerminal addControlPoint(RouteGraph routeGraph, ControlPoint cp) {\r
419         TerminalInfo ti = cp.getAttachedTerminal();\r
420         if(ti != null && ti != startFlag && ti != endFlag) {\r
421             Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());\r
422             GeometryUtils.expandRectangle(bounds, 2);\r
423             int allowedDirections = RouteGraphConnectionClass.shortestDirectionOutOfBounds(\r
424                     ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);\r
425             return routeGraph.addTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(),\r
426                     bounds, allowedDirections, PlainLineEndStyle.INSTANCE);\r
427         }\r
428         else {\r
429             double x = cp.getPosition().getX();\r
430             double y = cp.getPosition().getY();\r
431             int allowedDirections = 0xf;\r
432             switch(cp.getDirection()) {\r
433             case Horizontal: allowedDirections = 5; break;\r
434             case Vertical: allowedDirections = 10; break;\r
435             case Any: allowedDirections = 15; break;\r
436             }\r
437             return routeGraph.addTerminal(x, y, x, y, x, y, allowedDirections);\r
438         }\r
439     }\r
440     \r
441     protected void updateSG() {\r
442         if (controlPoints.size() != 2)\r
443             return;\r
444 \r
445         ControlPoint begin = controlPoints.getFirst();\r
446         ControlPoint end = controlPoints.getLast();\r
447 \r
448         RouteGraph routeGraph = new RouteGraph();\r
449         RouteTerminal a = addControlPoint(routeGraph, begin);\r
450         RouteTerminal b = addControlPoint(routeGraph, end);\r
451         routeGraph.link(a, b);\r
452 \r
453         Path2D path = routeGraph.getPath2D();\r
454 \r
455         // Create scene graph to visualize the connection.\r
456         ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);\r
457         pathNode.setShape(path);\r
458 \r
459         setDirty();\r
460     }\r
461 \r
462     private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {\r
463         return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));\r
464     }\r
465 \r
466     private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {\r
467         G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);\r
468         elementParent.setTransform(tr);\r
469         elementParent.removeNodes();\r
470         for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))\r
471             sg.init(element, elementParent);\r
472         return elementParent;\r
473     }\r
474 \r
475     @SGCleanup\r
476     public void cleanupSG() {\r
477         ghostNode.remove();\r
478         ghostNode = null;\r
479     }\r
480 \r
481     @EventHandler(priority = 200)\r
482     public boolean handleCommandEvents(CommandEvent ce) {\r
483         if (ce.command.equals(Commands.CANCEL)) {\r
484             setDirty();\r
485             remove();\r
486             return true;\r
487         } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {\r
488             return rotateLastBranchPoint(ce.command.equals(Commands.ROTATE_ELEMENT_CW));\r
489         }\r
490         return false;\r
491     }\r
492 \r
493     @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)\r
494     public boolean handleKeyEvents(KeyEvent ke) {\r
495         if (ke instanceof KeyPressedEvent) {\r
496             // Back-space, cancel prev bend\r
497             if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)\r
498                 return cancelPreviousBend();\r
499         }\r
500 \r
501         if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {\r
502             if (createFlags) {\r
503                 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));\r
504                 return true;\r
505             }\r
506         }\r
507 \r
508         return false;\r
509     }\r
510 \r
511     @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)\r
512     public boolean handleEvent(MouseEvent me) {\r
513         // Only handle events for the connection-initiating mouse\r
514         if (me.mouseId != mouseId)\r
515             return false;\r
516 \r
517         if (me instanceof MouseMovedEvent)\r
518             return processMouseMove((MouseMovedEvent) me);\r
519 \r
520         if (me instanceof MouseButtonPressedEvent)\r
521             return processMouseButtonPress((MouseButtonPressedEvent) me);\r
522 \r
523         return false;\r
524     }\r
525 \r
526     protected boolean processMouseMove(MouseMovedEvent me) {\r
527         mouseHasMoved = true;\r
528 \r
529         Point2D mouseControlPos = me.controlPosition;\r
530         Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());\r
531 \r
532         ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
533         if (snapAdvisor != null)\r
534             snapAdvisor.snap(mouseCanvasPos);\r
535 \r
536         // Record last snapped canvas position of mouse.\r
537         this.lastMouseCanvasPos.setLocation(mouseCanvasPos);\r
538 \r
539         if (isEndingInFlag()) {\r
540             endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));\r
541         }\r
542 \r
543         List<TerminalInfo> tis = pi.pickTerminals(me.controlPosition);\r
544         tis = TerminalUtil.findNearestOverlappingTerminals(tis);\r
545         if (!tis.isEmpty() && !containsStartTerminal(tis)) {\r
546             //System.out.println("end terminals (" + tis.size() + "):\n" + EString.implode(tis));\r
547             for (TerminalInfo ti : tis) {\r
548                 Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);\r
549                 if (canConnect != null) {\r
550                     connectionJudgment = canConnect.first;\r
551 \r
552                     if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {\r
553                         if (canConnect.second != null) {\r
554                             controlPoints.getFirst()\r
555                             .setPosition(canConnect.second.posDia)\r
556                             .setAttachedToTerminal(canConnect.second);\r
557                         }\r
558                         controlPoints.getLast()\r
559                         .setPosition(ti.posDia)\r
560                         .setAttachedToTerminal(ti);\r
561 \r
562                         selectedStartTerminal = canConnect.second;\r
563                         endTerminal = ti;\r
564                     }\r
565 \r
566                     // Make sure that we are ending with a flag if ALT is pressed\r
567                     // and no end terminal is defined.\r
568                     if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))\r
569                         updateSG();\r
570                     return false;\r
571                 }\r
572             }\r
573         } else {\r
574             RouteGraphTarget cp = RouteGraphConnectTool.pickRouteGraphConnection(\r
575                     diagram,\r
576                     pi.getCanvasPickShape(me.controlPosition),\r
577                     pi.getPickDistance());\r
578             if (cp != null) {\r
579                 // Remove branch point highlight from previously picked route graph.\r
580                 if (lastRouteGraphTarget != null && cp.getNode() != lastRouteGraphTarget.getNode())\r
581                     cp.getNode().showBranchPoint(null);\r
582                 lastRouteGraphTarget = cp;\r
583 \r
584                 // Validate connection before visualizing connectability\r
585                 Point2D isectPos = cp.getIntersectionPosition();\r
586                 TerminalInfo ti = TerminalInfo.create(\r
587                         isectPos,\r
588                         cp.getElement(),\r
589                         BranchPointTerminal.existingTerminal(\r
590                                 isectPos,\r
591                                 DirectionSet.ANY,\r
592                                 BranchPointNode.SHAPE),\r
593                         BranchPointNode.SHAPE);\r
594                 Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);\r
595                 if (canConnect != null) {\r
596                     attachToConnectionJudgement = canConnect.first;\r
597                     controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);\r
598                     endTerminal = ti;\r
599                     cp.getNode().showBranchPoint(isectPos);\r
600                     if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))\r
601                         updateSG();\r
602                     return false;\r
603                 }\r
604             } else {\r
605                 if (lastRouteGraphTarget != null) {\r
606                     lastRouteGraphTarget.getNode().showBranchPoint(null);\r
607                     lastRouteGraphTarget = null;\r
608                 }\r
609             }\r
610         }\r
611 \r
612         connectionJudgment = null;\r
613         attachToConnectionJudgement = null;\r
614         if (isEndTerminalDefined()) {\r
615             // CASE: Mouse was previously on top of a valid terminal to end\r
616             // the connection. Now the mouse has been moved where there is\r
617             // no longer a terminal to connect to.\r
618             //\r
619             // => Disconnect the last edge segment from the previous\r
620             // terminal, mark endElement/endTerminal non-existent\r
621             // and connect the disconnected edge to a new branch point.\r
622 \r
623             controlPoints.getLast()\r
624             .setPosition(mouseCanvasPos)\r
625             .setDirection(calculateCurrentBranchPointDirection())\r
626             .setAttachedToTerminal(null);\r
627 \r
628             endTerminal = null;\r
629         } else {\r
630             // CASE: Mouse was not previously on top of a valid ending\r
631             // element terminal.\r
632             //\r
633             // => Move and re-orient last branch point.\r
634 \r
635             controlPoints.getLast()\r
636             .setPosition(mouseCanvasPos)\r
637             .setDirection(calculateCurrentBranchPointDirection());\r
638         }\r
639 \r
640         // Make sure that we are ending with a flag if ALT is pressed and no end\r
641         // terminal is defined.\r
642         if (!endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me)))\r
643             updateSG();\r
644 \r
645         return false;\r
646     }\r
647 \r
648     protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {\r
649         MouseButtonEvent me = e;\r
650 \r
651         // Do nothing before the mouse has moved at least a little.\r
652         // This prevents the user from ending the connection right where\r
653         // it started.\r
654         if (!mouseHasMoved)\r
655             return true;\r
656 \r
657         if (me.button == MouseEvent.LEFT_BUTTON) {\r
658             Point2D mouseControlPos = me.controlPosition;\r
659             Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());\r
660 \r
661             ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);\r
662             if (snapAdvisor != null)\r
663                 snapAdvisor.snap(mouseCanvasPos);\r
664 \r
665             if (isEndTerminalDefined() && connectionJudgment != null) {\r
666                 // Clicked on an allowed end terminal. End connection & end mode.\r
667                 createConnection();\r
668                 remove();\r
669                 return true;\r
670             } else if (lastRouteGraphTarget != null && attachToConnectionJudgement != null) {\r
671                 lastRouteGraphTarget.getNode().showBranchPoint(null);\r
672                 attachToConnection();\r
673                 remove();\r
674                 return true;\r
675             } else {\r
676                 // Finish connection in thin air only if the\r
677                 // connection was started from a valid terminal.\r
678                 if (me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) && !startTerminals.isEmpty()) {\r
679                     Pair<ConnectionJudgement, TerminalInfo> pair = canConnect(null, null);\r
680                     if (pair != null) {\r
681                         connectionJudgment = (ConnectionJudgement) pair.first;\r
682                         selectedStartTerminal = pair.second;\r
683 //                        endFlag = createFlag(EdgeEnd.End);\r
684 //                        controlPoints.getLast().setAttachedToTerminal(endFlag);\r
685                         createConnection();\r
686                         setDirty();\r
687                         remove();\r
688                     } else {\r
689                         // Inform the user why connection couldn't be created.\r
690                         String tmsg = terminalsToString(startTerminals);\r
691                         ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection when starting from one of the following terminals:\n" + tmsg, null);\r
692                     }\r
693                     return true;\r
694                 } else if (routePointsAllowed()\r
695                         && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {\r
696                     // Add new connection control point.\r
697                     controlPoints.add(newControlPointWithCalculatedDirection(mouseCanvasPos));\r
698                     resetForcedBranchPointDirection();\r
699                     updateSG();\r
700                 }\r
701             }\r
702 \r
703             // Eat the event to prevent other participants from doing\r
704             // incompatible things while in this connection mode.\r
705             return true;\r
706         } else if (me.button == MouseEvent.RIGHT_BUTTON) {\r
707             return cancelPreviousBend();\r
708         }\r
709 \r
710         return false;\r
711     }\r
712 \r
713     private void attachToConnection() {\r
714         ConnectionJudgement judgment = this.attachToConnectionJudgement;\r
715         if (judgment == null) {\r
716             ErrorLogger.defaultLogError("Cannot attach to connection, no judgment available on connection validity", null);\r
717             return;\r
718         }\r
719 \r
720         ConnectionBuilder builder = new ConnectionBuilder(this.diagram);\r
721         RouteGraph before = lastRouteGraphTarget.getNode().getRouteGraph();\r
722         THashMap<Object, Object> copyMap = new THashMap<>();\r
723         RouteGraph after = before.copy(copyMap);\r
724 \r
725         RouteLine attachTo = (RouteLine) copyMap.get(lastRouteGraphTarget.getLine());\r
726         after.makePersistent(attachTo);\r
727         for (RouteLine line : after.getAllLines()) {\r
728             if (!line.isTransient() && line.isHorizontal() == attachTo.isHorizontal()\r
729                     && line.getPosition() == attachTo.getPosition()) {\r
730                 attachTo = line;\r
731                 break;\r
732             }\r
733         }\r
734         RouteLine attachToLine = attachTo;\r
735         RouteGraphDelta delta = new RouteGraphDelta(before, after);\r
736 \r
737         Simantics.getSession().asyncRequest(new WriteRequest() {\r
738             @Override\r
739             public void perform(WriteGraph graph) throws DatabaseException {\r
740                 graph.markUndoPoint();\r
741                 Resource connection = ElementUtils.getObject(endTerminal.e);\r
742                 if (!delta.isEmpty()) {\r
743                     new RouteGraphConnection(graph, connection).synchronize(graph, before, after, delta);\r
744                 }\r
745                 Resource line = RouteGraphConnection.deserialize(graph, attachToLine.getData());\r
746                 Deque<ControlPoint> cps = new ArrayDeque<>();\r
747                 for (Iterator<ControlPoint> iterator = controlPoints.descendingIterator(); iterator.hasNext();)\r
748                     cps.add(iterator.next());\r
749                 builder.attachToRouteGraph(graph, judgment, connection, line, cps, startTerminal, FlagClass.Type.In);\r
750             }\r
751         }, parameter -> {\r
752             if (parameter != null)\r
753                 ExceptionUtils.logAndShowError(parameter);\r
754         });\r
755     }\r
756 \r
757     protected boolean cancelPreviousBend() {\r
758         if (!routePointsAllowed())\r
759             return false;\r
760 \r
761         // Just to make this code more comprehensible, prevent an editing\r
762         // case that requires ugly code to work.\r
763         if (isEndingInFlag())\r
764             return true;\r
765 \r
766         // If there are no real route points, cancel whole connection.\r
767         if (controlPoints.size() <= 2) {\r
768             setDirty();\r
769             remove();\r
770             return true;\r
771         }\r
772 \r
773         // Cancel last bend\r
774         controlPoints.removeLast();\r
775         controlPoints.getLast().setPosition(lastMouseCanvasPos);\r
776         resetForcedBranchPointDirection();\r
777 \r
778         updateSG();\r
779         return true;\r
780     }\r
781 \r
782     /**\r
783      * Rotates the last branch point in the created connection in either\r
784      * clockwise or counter-clockwise direction as a response to a user\r
785      * interaction.\r
786      * \r
787      * <p>\r
788      * At the same time it use {@link #forcedBranchPointDirection} to mark the\r
789      * current last branch point to be forcefully oriented according to the\r
790      * users wishes instead of calculating a default value for the orientation\r
791      * from the routed connection path. See\r
792      * {@link #calculateCurrentBranchPointDirection()} for more information on\r
793      * this.\r
794      * \r
795      * <p>\r
796      * The logic of this method goes as follows:\r
797      * <ul>\r
798      * <li>Calculate the current branch point direction</li>\r
799      * <li>If the branch point direction is currently user selected (\r
800      * {@link #forcedBranchPointDirection}</li>\r
801      * <li></li>\r
802      * <li></li>\r
803      * </ul>\r
804      * \r
805      * @param clockwise\r
806      * @return <code>true</code> if the rotation was successful\r
807      */\r
808     protected boolean rotateLastBranchPoint(boolean clockwise) {\r
809         Direction oldDir = calculateCurrentBranchPointDirection();\r
810 \r
811         if (forcedBranchPointDirection == null) {\r
812             forcedBranchPointDirection = oldDir.toggleDetermined();\r
813         } else {\r
814             forcedBranchPointDirection = clockwise ? oldDir.cycleNext() : oldDir.cyclePrevious();\r
815         }\r
816 \r
817         controlPoints.getLast().setDirection(forcedBranchPointDirection);\r
818 \r
819         updateSG();\r
820 \r
821         return true;\r
822     }\r
823 \r
824     /**\r
825      * Set preferred direction for a branch/route point element.\r
826      * \r
827      * @param branchPoint the element to set the direction for\r
828      * @param direction the direction to set\r
829      * @return\r
830      */\r
831     protected void setDirection(IElement branchPoint, Direction direction) {\r
832         branchPoint.getElementClass().getSingleItem(BranchPoint.class).setDirectionPreference(branchPoint, direction);\r
833     }\r
834 \r
835     protected Direction forcedBranchPointDirection() {\r
836         return forcedBranchPointDirection;\r
837     }\r
838 \r
839     protected void resetForcedBranchPointDirection() {\r
840         forcedBranchPointDirection = null;\r
841     }\r
842 \r
843     protected void forceBranchPointDirection(Direction direction) {\r
844         forcedBranchPointDirection = direction;\r
845     }\r
846 \r
847     /**\r
848      * @return\r
849      */\r
850     protected Direction calculateCurrentBranchPointDirection() {\r
851         // If this is not the first branch point, toggle direction compared to\r
852         // last.\r
853         if (forcedBranchPointDirection != null)\r
854             return forcedBranchPointDirection;\r
855 \r
856         if (controlPoints.size() > 2) {\r
857             // This is not the first edge segment, toggle route point\r
858             // directions.\r
859             Iterator<ControlPoint> it = controlPoints.descendingIterator();\r
860             it.next();\r
861             ControlPoint secondLastCp = it.next();\r
862 \r
863             Direction dir = secondLastCp.getDirection();\r
864             switch (dir) {\r
865                 case Horizontal:\r
866                     return Direction.Vertical;\r
867                 case Vertical:\r
868                     return Direction.Horizontal;\r
869                 case Any:\r
870             }\r
871         }\r
872 \r
873         // If this is the first branch point, calculate based on edge segment\r
874         // angle.\r
875         if (controlPoints.size() > 1) {\r
876             Iterator<ControlPoint> it = controlPoints.descendingIterator();\r
877             ControlPoint last = it.next();\r
878             ControlPoint secondLast = it.next();\r
879 \r
880             double angle = Math.atan2(Math.abs(last.getPosition().getY() - secondLast.getPosition().getY()),\r
881                     Math.abs(last.getPosition().getX() - secondLast.getPosition().getX()));\r
882 \r
883             if (angle >= 0 && angle < Math.PI / 4) {\r
884                 return Direction.Horizontal;\r
885             } else if (angle > Math.PI / 4 && angle <= Math.PI / 2) {\r
886                 return Direction.Vertical;\r
887             }\r
888         }\r
889 \r
890         return Direction.Any;\r
891     }\r
892 \r
893     protected boolean isEndingInFlag() {\r
894         return endFlag != null;\r
895     }\r
896 \r
897     /**\r
898      * @param mousePos\r
899      * @param altDown\r
900      * @return <code>true</code> if updateSG was executed, <code>false</code>\r
901      *         otherwise\r
902      */\r
903     protected boolean endWithoutTerminal(Point2D mousePos, boolean altDown) {\r
904         // Just go with branch points if flags are not allowed.\r
905         if (!createFlags)\r
906             return false;\r
907 \r
908         boolean endTerminalDefined = isEndTerminalDefined();\r
909 \r
910         if (altDown) {\r
911             if (!isEndingInFlag()) {\r
912                 endFlag = createFlag(EdgeEnd.End);\r
913                 endFlagNode = showElement(ghostNode, "endFlag", endFlag.e, mousePos);\r
914                 controlPoints.getLast()\r
915                 .setDirection(calculateCurrentBranchPointDirection())\r
916                 .setAttachedToTerminal(endFlag);\r
917 \r
918                 // TerminalPainter must refresh\r
919                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);\r
920 \r
921                 updateSG();\r
922                 return true;\r
923             }\r
924         } else {\r
925             if (isEndingInFlag()) {\r
926                 // Currently ending with flag but ALT is no longer down\r
927                 // so that flag must be removed.\r
928                 endFlag = null;\r
929                 endFlagNode.remove();\r
930                 endFlagNode = null;\r
931 \r
932                 ControlPoint cp = controlPoints.getLast();\r
933                 cp.setDirection(calculateCurrentBranchPointDirection())\r
934                 .setAttachedToTerminal(endTerminal);\r
935 \r
936                 if (endTerminalDefined) {\r
937                     cp.setPosition(endTerminal.posDia);\r
938                 } else {\r
939                     cp.setPosition(mousePos);\r
940                 }\r
941 \r
942                 // Force TerminalPainter refresh\r
943                 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);\r
944 \r
945                 updateSG();\r
946                 return true;\r
947             }\r
948         }\r
949         return false;\r
950     }\r
951 \r
952     protected void createConnection() {\r
953         createConnection(\r
954                 this.selectedStartTerminal,\r
955                 this.endTerminal,\r
956                 this.connectionJudgment,\r
957                 this.controlPoints);\r
958     }\r
959 \r
960     protected void createConnection(\r
961             final TerminalInfo startTerminal,\r
962             final TerminalInfo endTerminal,\r
963             final ConnectionJudgement judgement,\r
964             final Deque<ControlPoint> controlPoints)\r
965     {\r
966         TimeLogger.resetTimeAndLog(getClass(), "createConnection");\r
967         if (judgement == null) {\r
968             // Inform the user why connection couldn't be created.\r
969             String tmsg = terminalsToString(Arrays.asList(startTerminal, endTerminal));\r
970             ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity when connecting the terminals:\n" + tmsg, null);\r
971             return;\r
972         }\r
973 \r
974         final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);\r
975 \r
976         Simantics.getSession().asyncRequest(new WriteRequest() {\r
977             @Override\r
978             public void perform(WriteGraph graph) throws DatabaseException {\r
979                 builder.create(graph, judgement, controlPoints, startTerminal, endTerminal);\r
980             }\r
981         }, parameter -> {\r
982             if (parameter != null)\r
983                 ExceptionUtils.logAndShowError(parameter);\r
984         });\r
985     }\r
986 \r
987     /**\r
988      * @param canvasPos\r
989      * @return\r
990      */\r
991     protected ControlPoint newControlPointWithCalculatedDirection(Point2D canvasPos) {\r
992         return new ControlPoint(canvasPos, calculateCurrentBranchPointDirection());\r
993     }\r
994 \r
995     /**\r
996      * @param e\r
997      * @param t\r
998      * @return <code>true</code> if the specified element terminal matches any\r
999      *         TerminalInfo in {@link #startTerminals}\r
1000      */\r
1001     protected boolean isStartTerminal(IElement e, Terminal t) {\r
1002         if (startTerminal == null)\r
1003             return false;\r
1004         for (TerminalInfo st : startTerminals) {\r
1005             if (st.e == e && st.t == t) {\r
1006                 return true;\r
1007             }\r
1008         }\r
1009         return false;\r
1010     }\r
1011 \r
1012     /**\r
1013      * @param e\r
1014      * @param t\r
1015      * @return <code>true</code> if the specified element terminal matches any\r
1016      *         TerminalInfo in {@link #startTerminals}\r
1017      */\r
1018     protected boolean containsStartTerminal(List<TerminalInfo> tis) {\r
1019         if (startTerminal == null)\r
1020             return false;\r
1021         for (TerminalInfo st : startTerminals) {\r
1022             for (TerminalInfo et : tis) {\r
1023                 if (st.e == et.e && st.t == et.t) {\r
1024                     return true;\r
1025                 }\r
1026             }\r
1027         }\r
1028         return false;\r
1029     }\r
1030 \r
1031     protected static FlagClass.Type endToFlagType(EdgeEnd end) {\r
1032         switch (end) {\r
1033             case Begin:\r
1034                 return FlagClass.Type.In;\r
1035             case End:\r
1036                 return FlagClass.Type.Out;\r
1037             default:\r
1038                 throw new IllegalArgumentException("unrecognized edge end: " + end);\r
1039         }\r
1040     }\r
1041 \r
1042     protected TerminalInfo createFlag(EdgeEnd connectionEnd) {\r
1043         ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);\r
1044         IElement e = Element.spawnNew(flagClass);\r
1045 \r
1046         e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));\r
1047         e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);\r
1048 \r
1049         TerminalInfo ti = new TerminalInfo();\r
1050         ti.e = e;\r
1051         ti.t = ElementUtils.getSingleTerminal(e);\r
1052         ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);\r
1053         ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);\r
1054 \r
1055         return ti;\r
1056     }\r
1057 \r
1058     protected boolean shouldEndWithFlag(MouseEvent me) {\r
1059         return shouldEndWithFlag( me.hasAnyModifier(MouseEvent.ALT_MASK | MouseEvent.ALT_GRAPH_MASK) );\r
1060     }\r
1061 \r
1062     protected boolean shouldEndWithFlag(boolean altPressed) {\r
1063         return altPressed && !isEndTerminalDefined() && createFlags && startFlag == null;\r
1064     }\r
1065 \r
1066     protected boolean isEndTerminalDefined() {\r
1067         return endTerminal != null;\r
1068     }\r
1069 \r
1070     protected boolean isFlagTerminal(TerminalInfo ti) {\r
1071         return ti.e.getElementClass().containsClass(FlagHandler.class);\r
1072     }\r
1073 \r
1074     protected boolean allowReflexiveConnections() {\r
1075         return false;\r
1076     }\r
1077 \r
1078     protected boolean routePointsAllowed() {\r
1079         return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));\r
1080     }\r
1081 \r
1082     /**\r
1083      * @param endElement\r
1084      * @param endTerminal\r
1085      * @return\r
1086      */\r
1087     @SuppressWarnings("unchecked")\r
1088     protected final Pair<ConnectionJudgement, TerminalInfo> canConnect(IElement endElement, Terminal endTerminal) {\r
1089         IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);\r
1090         Object judgement = canConnect(advisor, endElement, endTerminal);\r
1091         if (judgement == null)\r
1092             return null;\r
1093         if (judgement instanceof Pair<?, ?>)\r
1094             return (Pair<ConnectionJudgement, TerminalInfo>) judgement;\r
1095         return Pair.<ConnectionJudgement, TerminalInfo>make((ConnectionJudgement) judgement, startTerminal);\r
1096     }\r
1097 \r
1098     protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {\r
1099         if (advisor == null)\r
1100             return Pair.make(ConnectionJudgement.CANBEMADELEGAL, startTerminal);\r
1101         if (startTerminals.isEmpty()) {\r
1102             ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, null, null, endElement, endTerminal);\r
1103             return obj != null ? Pair.<ConnectionJudgement, TerminalInfo>make(obj, null) : null;\r
1104         }\r
1105         for (TerminalInfo st : startTerminals) {\r
1106             ConnectionJudgement obj = (ConnectionJudgement) advisor.canBeConnected(null, st.e, st.t, endElement, endTerminal);\r
1107             if (obj != null) {\r
1108                 return Pair.make(obj, st);\r
1109             }\r
1110         }\r
1111         return null;\r
1112     }\r
1113 \r
1114     /**\r
1115      * For generating debugging information of what was attempted by the user\r
1116      * when a connection couldn't be created.\r
1117      * \r
1118      * @param ts\r
1119      * @return\r
1120      */\r
1121     private String terminalsToString(final Iterable<TerminalInfo> ts) {\r
1122         try {\r
1123             return Simantics.sync(new UniqueRead<String>() {\r
1124                 @Override\r
1125                 public String perform(ReadGraph graph) throws DatabaseException {\r
1126                     DiagramResource DIA = DiagramResource.getInstance(graph);\r
1127                     ModelingResources MOD = ModelingResources.getInstance(graph);\r
1128                     StringBuilder sb = new StringBuilder();\r
1129                     boolean first = true;\r
1130                     for (TerminalInfo ti : ts) {\r
1131                         if (!first)\r
1132                             sb.append("\n");\r
1133                         first = false;\r
1134                         sb.append("element ");\r
1135                         Object o = ElementUtils.getObject(ti.e);\r
1136                         if (o instanceof Resource) {\r
1137                             Resource er = (Resource) o;\r
1138                             Resource cer = graph.getPossibleObject(er, MOD.ElementToComponent);\r
1139                             Resource r = cer != null ? cer : er;\r
1140                             sb.append(NameUtils.getSafeName(graph, r)).append(" : ");\r
1141                             for (Resource type : graph.getPrincipalTypes(r)) {\r
1142                                 sb.append(NameUtils.getSafeName(graph, type, true));\r
1143                             }\r
1144                         } else {\r
1145                             sb.append(ti.e.toString());\r
1146                         }\r
1147                         sb.append(", terminal ");\r
1148                         if (ti.t instanceof ResourceTerminal) {\r
1149                             Resource tr = ((ResourceTerminal) ti.t).getResource();\r
1150                             Resource cp = graph.getPossibleObject(tr, DIA.HasConnectionPoint);\r
1151                             Resource r = cp != null ? cp : tr;\r
1152                             sb.append(NameUtils.getSafeName(graph, r, true));\r
1153                         } else {\r
1154                             sb.append(ti.t.toString());\r
1155                         }\r
1156                     }\r
1157                     return sb.toString();\r
1158                 }\r
1159             });\r
1160         } catch (DatabaseException e) {\r
1161             return e.getMessage();\r
1162         }\r
1163     }\r
1164 \r
1165 }