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