1 /*******************************************************************************
2 * Copyright (c) 2007, 2016 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 * Semantum Oy - Fixed bug #6364
12 *******************************************************************************/
13 package org.simantics.diagram.participant;
15 import java.awt.AlphaComposite;
16 import java.awt.Composite;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Line2D;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.util.ArrayDeque;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Deque;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
31 import org.simantics.Simantics;
32 import org.simantics.db.Resource;
33 import org.simantics.db.WriteGraph;
34 import org.simantics.db.common.request.WriteRequest;
35 import org.simantics.db.exception.DatabaseException;
36 import org.simantics.diagram.connection.RouteGraph;
37 import org.simantics.diagram.connection.RouteLine;
38 import org.simantics.diagram.connection.RoutePoint;
39 import org.simantics.diagram.connection.RouteTerminal;
40 import org.simantics.diagram.connection.delta.RouteGraphDelta;
41 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
42 import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle;
43 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
44 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
45 import org.simantics.diagram.connection.segments.Segment;
46 import org.simantics.diagram.synchronization.ISynchronizationContext;
47 import org.simantics.diagram.synchronization.SynchronizationHints;
48 import org.simantics.diagram.synchronization.graph.RouteGraphConnection;
49 import org.simantics.g2d.canvas.ICanvasContext;
50 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
51 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
52 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
53 import org.simantics.g2d.connection.ConnectionEntity;
54 import org.simantics.g2d.connection.IConnectionAdvisor;
55 import org.simantics.g2d.diagram.DiagramHints;
56 import org.simantics.g2d.diagram.DiagramUtils;
57 import org.simantics.g2d.diagram.IDiagram;
58 import org.simantics.g2d.diagram.handler.PickRequest;
59 import org.simantics.g2d.diagram.handler.Topology.Connection;
60 import org.simantics.g2d.diagram.handler.Topology.Terminal;
61 import org.simantics.g2d.diagram.participant.ElementPainter;
62 import org.simantics.g2d.diagram.participant.TerminalPainter;
63 import org.simantics.g2d.diagram.participant.TerminalPainter.TerminalHoverStrategy;
64 import org.simantics.g2d.diagram.participant.pointertool.AbstractMode;
65 import org.simantics.g2d.diagram.participant.pointertool.PointerInteractor;
66 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
67 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
68 import org.simantics.g2d.element.ElementClass;
69 import org.simantics.g2d.element.ElementClasses;
70 import org.simantics.g2d.element.ElementHints;
71 import org.simantics.g2d.element.ElementUtils;
72 import org.simantics.g2d.element.IElement;
73 import org.simantics.g2d.element.IElementClassProvider;
74 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
75 import org.simantics.g2d.element.handler.SceneGraph;
76 import org.simantics.g2d.element.handler.TerminalTopology;
77 import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
78 import org.simantics.g2d.element.impl.Element;
79 import org.simantics.g2d.elementclass.FlagClass;
80 import org.simantics.g2d.elementclass.FlagHandler;
81 import org.simantics.g2d.elementclass.RouteGraphConnectionClass;
82 import org.simantics.g2d.participant.TransformUtil;
83 import org.simantics.g2d.utils.geom.DirectionSet;
84 import org.simantics.scenegraph.g2d.G2DParentNode;
85 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;
86 import org.simantics.scenegraph.g2d.events.KeyEvent;
87 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
88 import org.simantics.scenegraph.g2d.events.MouseEvent;
89 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;
90 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
91 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;
92 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
93 import org.simantics.scenegraph.g2d.events.command.CommandEvent;
94 import org.simantics.scenegraph.g2d.events.command.Commands;
95 import org.simantics.scenegraph.g2d.nodes.BranchPointNode;
96 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
97 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
98 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
99 import org.simantics.scenegraph.utils.GeometryUtils;
100 import org.simantics.structural2.modelingRules.ConnectionJudgement;
101 import org.simantics.utils.datastructures.Pair;
102 import org.simantics.utils.datastructures.Triple;
103 import org.simantics.utils.logging.TimeLogger;
104 import org.simantics.utils.ui.ErrorLogger;
105 import org.simantics.utils.ui.ExceptionUtils;
107 import gnu.trove.map.hash.THashMap;
110 * A basic tool for making connection on diagrams.
112 * This version defines the starting, ending and route points of a connection.
113 * The routing itself is left up to the diagram router employed by
114 * {@link DiagramUtils#validateAndFix(IDiagram, ICanvasContext)}.
118 * This tool is added to the diagram when a connection sequence is initiated by
119 * another participant. PointerInteractor is one such participant which adds the
120 * tool when a terminal or non-terminal-occupied canvas space is ALT+clicked
121 * (see {@link PointerInteractor#checkInitiateConnectTool(MouseEvent, Point2D)}
122 * ). The connection will be finished when another allowed terminal is clicked
123 * upon or empty canvas space is ALT+clicked. Route points for the connection
124 * can be created by clicking around on non-terminal-occupied canvas space while
128 * Connections can be started from and ended in flags by pressing ALT while
131 * @author Tuukka Lehtonen
133 public class RouteGraphConnectTool extends AbstractMode {
135 private static final String END_TERMINAL_DATA = "END";
137 public static final int PAINT_PRIORITY = ElementPainter.ELEMENT_PAINT_PRIORITY + 5;
140 protected TransformUtil util;
143 protected ElementPainter diagramPainter;
146 protected PointerInteractor pi;
149 * Starting point designation.
151 * The value is received by the constructor.
153 protected RouteGraphTarget startingPoint;
155 protected TerminalInfo startTerminal = new TerminalInfo();
158 * <code>true</code> if this tool should create connection continuation
159 * flags, <code>false</code> otherwise.
161 protected boolean createFlags;
166 protected IElementClassProvider elementClassProvider;
171 protected Deque<ControlPoint> controlPoints = new ArrayDeque<ControlPoint>();
174 * Element terminal of connection end element. <code>null</code> if
175 * connection cannot be ended where it is currently being attempted to end.
177 protected TerminalInfo endTerminal;
180 * The latest connectability judgment from the active
181 * {@link IConnectionAdvisor} should the connection happen between
182 * {@link #startTerminal} and {@link #endTerminal}.
184 protected ConnectionJudgement connectionJudgment;
187 * A temporary variable for use with
188 * {@link TerminalTopology#getTerminals(IElement, Collection)}.
190 protected Collection<Terminal> terminals = new ArrayList<Terminal>();
193 * Previous mouse canvas position recorded by
194 * {@link #processMouseMove(MouseMovedEvent)}.
196 protected Point2D lastMouseCanvasPos = new Point2D.Double();
199 * Set to true once {@link #processMouseMove(MouseMovedEvent)} has been
200 * invoked at least once. This is used to tell whether to allow creation of
201 * branch points or finising the connection in thin air. It will not be
202 * allowed if the mouse has not moved at all since starting the connection.
204 protected boolean mouseHasMoved = false;
206 protected TerminalHoverStrategy originalStrategy = null;
208 protected TerminalHoverStrategy terminalHoverStrategy = new TerminalHoverStrategy() {
210 public boolean highlightEnabled() {
211 return !isEndingInFlag();
215 public boolean highlight(TerminalInfo ti) {
216 return canConnect(ti.e, ti.t) != null;
220 protected final static Composite ALPHA_COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75f);
223 * Root scene graph node for all visualization performed by this tool.
225 protected ConnectionNode ghostNode;
227 protected RouteGraphNode rgNode;
229 protected RouteGraph routeGraph;
230 protected RouteTerminal endRouteTerminal;
231 private ILineEndStyle endTerminalStyle;
234 * Indicates whether the connection is about to be ended into a new
235 * flag/branchpoint or not.
237 protected TerminalInfo endFlag;
239 protected G2DParentNode endFlagNode;
241 private RouteLine attachedToRouteLine;
243 protected RouteGraph beforeRouteGraph;
245 private IRouteGraphRenderer beforeRenderer;
247 private boolean beforeEditable;
249 protected RouteGraphDelta routeGraphDelta;
251 private Set<Pair<IElement, Terminal>> alreadyConnected;
254 * @param startElement
255 * @param routeGraphConnection
257 * @param startCanvasPos
259 public RouteGraphConnectTool(RouteGraphTarget startingPoint, int mouseId) {
262 Point2D intersection = startingPoint.getIntersectionPosition();
264 this.startingPoint = startingPoint;
265 this.lastMouseCanvasPos.setLocation(intersection);
267 BranchPointTerminal t = BranchPointTerminal.existingTerminal(
268 AffineTransform.getTranslateInstance(intersection.getX(), intersection.getY()),
270 BranchPointNode.SHAPE);
272 Point2D p = startingPoint.getIntersectionPosition();
273 AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());
275 startTerminal.e = startingPoint.getElement();
277 startTerminal.posElem = at;
278 startTerminal.posDia = at;
279 startTerminal.shape = t.getShape();
281 controlPoints.add( newControlPoint( startingPoint.getIntersectionPosition() ) );
282 controlPoints.add( newControlPoint( startingPoint.getCanvasPosition() ) );
284 alreadyConnected = new HashSet<Pair<IElement,Terminal>>();
285 IElement connection = startingPoint.getElement();
286 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
287 Collection<Connection> tcs = ce.getTerminalConnections(null);
288 for (Connection tc : tcs)
289 alreadyConnected.add(Pair.make(tc.node, tc.terminal));
293 public void addedToContext(ICanvasContext ctx) {
294 super.addedToContext(ctx);
296 // Force terminals to always be highlighted without pressing certain
297 // keys or key combinations.
298 originalStrategy = getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
299 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
303 protected void onDiagramSet(IDiagram newDiagram, IDiagram oldDiagram) {
304 if (newDiagram != null) {
305 // Get IElementClassProvider
306 ISynchronizationContext ctx = newDiagram.getHint(SynchronizationHints.CONTEXT);
308 this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);
311 // See if flags should be created or not.
312 this.createFlags = Boolean.TRUE.equals(newDiagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));
317 public void removedFromContext(ICanvasContext ctx) {
318 if (getHint(TerminalPainter.TERMINAL_HOVER_STRATEGY) == terminalHoverStrategy) {
319 if (originalStrategy != null)
320 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, originalStrategy);
322 removeHint(TerminalPainter.TERMINAL_HOVER_STRATEGY);
325 super.removedFromContext(ctx);
328 int straightDirections(RouteLine line) {
329 return line.isHorizontal()
330 ? (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP)
331 : (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT);
334 private static RouteTerminal addTerminal(Object data, RouteGraph rg, double x, double y, Rectangle2D bounds, int allowedDirections, ILineEndStyle style) {
337 rt = rg.addTerminal(x, y, bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), allowedDirections, style);
339 rt = rg.addTerminal(x, y, x, y, x, y, allowedDirections, style);
344 private RouteTerminal setEndTerminal(double x, double y, Rectangle2D bounds, int allowedDirections) {
346 // First add then remove to prevent deletion of route lines in case there are 2 only terminals
348 RouteTerminal toRemove = endRouteTerminal;
350 endRouteTerminal = addTerminal( END_TERMINAL_DATA, routeGraph, x, y, bounds, allowedDirections, endTerminalStyle );
351 routeGraph.link( attachedToRouteLine, endRouteTerminal );
353 if (toRemove != null)
354 routeGraph.remove(toRemove);
356 return endRouteTerminal;
360 protected void setEndTerminal(TerminalInfo ti) {
361 Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e, new Rectangle2D.Double());
362 GeometryUtils.expandRectangle(bounds, 2);
363 int dir = RouteGraphConnectionClass.shortestDirectionOutOfBounds(
364 ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds);
365 setEndTerminal(ti.posDia.getTranslateX(), ti.posDia.getTranslateY(), bounds, dir);
369 public void initSG(G2DParentNode parent) {
370 ghostNode = parent.addNode("branched connection", ConnectionNode.class);
371 //ghostNode.setComposite(AlphaComposite.SrcOver.derive(0.5f));
372 ghostNode.setZIndex(PAINT_PRIORITY);
374 rgNode = ghostNode.addNode("branch", RouteGraphNode.class);
376 double ex = startingPoint.getCanvasPosition().getX();
377 double ey = startingPoint.getCanvasPosition().getY();
379 beforeRouteGraph = startingPoint.getNode().getRouteGraph();
380 beforeEditable = startingPoint.getNode().isEditable();
382 RouteGraphNode beforeRgNode = startingPoint.getNode();
383 beforeRenderer = beforeRgNode.getRenderer();
385 rgNode.setRenderer(beforeRenderer);
386 rgNode.setEditable(false);
388 beforeRgNode.setEditable(beforeEditable);
389 beforeRgNode.setRenderer(null);
395 public void initRG(double ex, double ey) {
397 THashMap<Object, Object> map = new THashMap<Object, Object>();
398 routeGraph = beforeRouteGraph.copy(map);
400 endTerminalStyle = PlainLineEndStyle.INSTANCE;
401 for (RouteTerminal t : routeGraph.getTerminals()) {
402 if (t.getRenderStyle() instanceof ArrowLineEndStyle) {
403 endTerminalStyle = t.getRenderStyle();
408 attachedToRouteLine = (RouteLine) map.get(startingPoint.getLine());
409 routeGraph.makePersistent(attachedToRouteLine);
410 for (RouteLine line : routeGraph.getAllLines()) {
411 if (!line.isTransient() && line.isHorizontal() == attachedToRouteLine.isHorizontal()
412 && line.getPosition() == attachedToRouteLine.getPosition()) {
413 attachedToRouteLine = line;
417 routeGraphDelta = new RouteGraphDelta(beforeRouteGraph, routeGraph);
419 // beforeRouteGraph.print();
420 // routeGraph.print();
421 // routeGraphDelta.print();
423 setEndTerminal(ex, ey, null, 0xf);
425 rgNode.setRouteGraph(routeGraph);
430 public void cleanupSG() {
431 RouteGraphNode beforeRgNode = startingPoint.getNode();
432 beforeRgNode.setRouteGraph(beforeRouteGraph);
433 beforeRgNode.setRenderer(beforeRenderer);
434 beforeRgNode.setEditable(beforeEditable);
442 @EventHandler(priority = 200)
443 public boolean handleCommandEvents(CommandEvent ce) {
444 if (ce.command.equals(Commands.CANCEL)) {
448 } else if (ce.command.equals(Commands.ROTATE_ELEMENT_CCW) || ce.command.equals(Commands.ROTATE_ELEMENT_CW)) {
449 // TODO: rotate flag?
454 @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
455 public boolean handleKeyEvents(KeyEvent ke) {
456 if (ke instanceof KeyPressedEvent) {
457 // Back-space, cancel prev bend
458 if (ke.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE)
459 return cancelPreviousBend();
462 if (ke.keyCode == java.awt.event.KeyEvent.VK_ALT) {
464 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(ke instanceof KeyPressedEvent));
472 @EventHandler(priority = PointerInteractor.TOOL_PRIORITY + 20)
473 public boolean handleEvent(MouseEvent me) {
474 // Only handle events for the connection-initiating mouse
475 if (me.mouseId != mouseId)
478 if (me instanceof MouseMovedEvent)
479 return processMouseMove((MouseMovedEvent) me);
481 if (me instanceof MouseButtonPressedEvent)
482 return processMouseButtonPress((MouseButtonPressedEvent) me);
484 // #7653: Support creating connections between terminals without lifting mouse button in between.
485 if (me instanceof MouseButtonReleasedEvent)
486 return processMouseButtonRelease((MouseButtonReleasedEvent) me);
491 protected boolean processMouseMove(MouseMovedEvent me) {
492 mouseHasMoved = true;
494 Point2D mouseControlPos = me.controlPosition;
495 Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());
497 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
498 if (snapAdvisor != null)
499 snapAdvisor.snap(mouseCanvasPos);
501 // Record last snapped canvas position of mouse.
502 this.lastMouseCanvasPos.setLocation(mouseCanvasPos);
504 if (isEndingInFlag()) {
505 endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));
508 List<TerminalInfo> tis = pi.pickTerminals(me.controlPosition);
509 tis = TerminalUtil.findNearestOverlappingTerminals(tis);
510 if (!tis.isEmpty()) {
511 for (TerminalInfo ti : tis) {
512 Object canConnect = canConnect(ti.e, ti.t);
514 if (canConnect != null) {
515 connectionJudgment = (ConnectionJudgement) canConnect;
517 if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
518 controlPoints.getLast().setPosition(ti.posDia).setAttachedToTerminal(ti);
526 // Make sure that we are ending with a flag if ALT is pressed
527 // and no end terminal is defined.
528 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
530 updateSG(new Point2D.Double(ti.posDia.getTranslateX(), ti.posDia.getTranslateY()));
536 connectionJudgment = null;
537 if (isEndTerminalDefined()) {
538 // CASE: Mouse was previously on top of a valid terminal to end
539 // the connection. Now the mouse has been moved where there is
540 // no longer a terminal to connect to.
542 // => Disconnect the last edge segment from the previous
543 // terminal, mark endElement/endTerminal non-existent
544 // and connect the disconnected edge to a new branch point.
546 disconnect(mouseCanvasPos);
548 controlPoints.getLast()
549 .setPosition(mouseCanvasPos)
550 .setAttachedToTerminal(null);
554 // CASE: Mouse was not previously on top of a valid ending
557 // => Move and re-orient last branch point.
559 controlPoints.getLast()
560 .setPosition(mouseCanvasPos);
563 // Make sure that we are ending with a flag if ALT is pressed and no end
564 // terminal is defined.
565 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
567 updateSG(lastMouseCanvasPos);
572 protected void connect(TerminalInfo ti) {
576 protected void disconnect(Point2D mouseCanvasPos) {
577 setEndTerminal(mouseCanvasPos.getX(), mouseCanvasPos.getY(), null, 0xf);
580 protected boolean processMouseButtonPress(MouseButtonEvent me) {
581 // Do nothing before the mouse has moved at least a little.
582 // This prevents the user from ending the connection right where
587 if (me.button == MouseEvent.LEFT_BUTTON) {
588 Point2D mouseControlPos = me.controlPosition;
589 Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());
591 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
592 if (snapAdvisor != null)
593 snapAdvisor.snap(mouseCanvasPos);
595 // Clicked on an allowed end terminal. End connection & end mode.
596 if (tryEndConnection()) {
599 // Finish connection in thin air only if the
600 // connection was started from a valid terminal.
601 if ((me.stateMask & MouseEvent.ALT_MASK) != 0 && startTerminal != null) {
602 connectionJudgment = (ConnectionJudgement) canConnect(null, null);
603 if (connectionJudgment == null)
608 } else if (routePointsAllowed()
609 && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
610 // Add new connection control point.
611 controlPoints.add(newControlPoint(mouseCanvasPos));
612 updateSG(mouseCanvasPos);
616 // Eat the event to prevent other participants from doing
617 // incompatible things while in this connection mode.
619 } else if (me.button == MouseEvent.RIGHT_BUTTON) {
620 return cancelPreviousBend();
626 private int mouseLeftReleaseCount = 0;
628 protected boolean processMouseButtonRelease(MouseButtonReleasedEvent me) {
629 if (me.button == MouseEvent.LEFT_BUTTON
630 && ++mouseLeftReleaseCount == 1) {
631 return tryEndConnection();
637 * @return <code>true</code> if connection was successfully ended
639 private boolean tryEndConnection() {
640 if (isEndTerminalDefined()) {
648 protected boolean cancelPreviousBend() {
649 if (!routePointsAllowed())
652 // Just to make this code more comprehensible, prevent an editing
653 // case that requires ugly code to work.
654 if (isEndingInFlag())
657 // If there are no real route points, cancel whole connection.
658 if (controlPoints.size() <= 2) {
665 controlPoints.removeLast();
666 controlPoints.getLast().setPosition(lastMouseCanvasPos);
668 updateSG(lastMouseCanvasPos);
672 protected void updateSG(Point2D mousePos) {
673 routeGraph.setLocation(endRouteTerminal, mousePos.getX(), mousePos.getY());
674 //routeGraph.print(System.err);
678 protected boolean shouldEndWithFlag(MouseEvent me) {
679 return shouldEndWithFlag((me.stateMask & MouseEvent.ALT_MASK) != 0);
682 protected boolean shouldEndWithFlag(boolean altPressed) {
683 return altPressed && !isEndTerminalDefined() && createFlags;
686 protected boolean isEndTerminalDefined() {
687 return endTerminal != null;
690 protected boolean isFlagTerminal(TerminalInfo ti) {
691 return ti.e.getElementClass().containsClass(FlagHandler.class);
695 protected boolean isEndingInFlag() {
696 return endFlag != null;
699 protected void endWithoutTerminal(Point2D mousePos, boolean altDown) {
700 // Just go with branch points if flags are not allowed.
704 boolean endTerminalDefined = isEndTerminalDefined();
707 if (!isEndingInFlag()) {
708 endFlag = createFlag(EdgeEnd.End);
709 endFlagNode = showElement(ghostNode, "endFlag", endFlag.e, mousePos);
710 controlPoints.getLast()
711 .setAttachedToTerminal(endFlag);
713 // TerminalPainter must refresh
714 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
719 if (isEndingInFlag()) {
720 // Currently ending with flag but ALT is no longer down
721 // so that flag must be removed.
723 endFlagNode.remove();
726 ControlPoint cp = controlPoints.getLast();
727 cp.setAttachedToTerminal(endTerminal);
729 if (endTerminalDefined) {
730 cp.setPosition(endTerminal.posDia);
732 cp.setPosition(mousePos);
735 // Force Terminalpainter refresh
736 setHint(TerminalPainter.TERMINAL_HOVER_STRATEGY, terminalHoverStrategy);
743 private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, Point2D pos) {
744 return showElement(parent, nodeId, element, AffineTransform.getTranslateInstance(pos.getX(), pos.getY()));
747 private G2DParentNode showElement(G2DParentNode parent, String nodeId, IElement element, AffineTransform tr) {
748 G2DParentNode elementParent = parent.getOrCreateNode(nodeId, G2DParentNode.class);
749 elementParent.setTransform(tr);
750 elementParent.removeNodes();
751 for (SceneGraph sg : element.getElementClass().getItemsByClass(SceneGraph.class))
752 sg.init(element, elementParent);
753 return elementParent;
760 protected ControlPoint newControlPoint(Point2D canvasPos) {
761 return new ControlPoint(canvasPos);
764 protected Triple<RouteGraph,RouteGraph,RouteGraphDelta> prepareRouteGraphDelta() {
765 // Prevent route graph connection synchronization from crashing on the
766 // transient route terminal that doesn't exist yet. It is created after
767 // persisting the route line, which is the only purpose of this route
768 // graph synchronization.
769 routeGraph.remove(endRouteTerminal);
770 return Triple.make(beforeRouteGraph, routeGraph, routeGraphDelta);
773 protected void createConnection() {
774 TimeLogger.resetTimeAndLog(getClass(), "createConnection");
775 final ConnectionJudgement judgment = this.connectionJudgment;
776 if (judgment == null) {
777 ErrorLogger.defaultLogError("Cannot create connection, no judgment available on connection validity", null);
781 final ConnectionBuilder builder = new ConnectionBuilder(this.diagram);
782 final Triple<RouteGraph,RouteGraph,RouteGraphDelta> rgs = prepareRouteGraphDelta();
784 Simantics.getSession().asyncRequest(new WriteRequest() {
786 public void perform(WriteGraph graph) throws DatabaseException {
787 graph.markUndoPoint();
788 Resource connection = ElementUtils.getObject(startTerminal.e);
789 if (!rgs.third.isEmpty()) {
790 new RouteGraphConnection(graph, connection).synchronize(graph, rgs.first, rgs.second, rgs.third);
792 Resource attachToLine = RouteGraphConnection.deserialize(graph, attachedToRouteLine.getData());
793 builder.attachToRouteGraph(graph, judgment, connection, attachToLine, controlPoints, endTerminal, FlagClass.Type.Out);
797 ExceptionUtils.logAndShowError(e);
801 protected boolean routePointsAllowed() {
802 return Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_ALLOW_ROUTE_POINTS));
805 protected Object canConnect(IElement endElement, Terminal endTerminal) {
806 IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
807 return canConnect(advisor, endElement, endTerminal);
810 protected Object canConnect(IConnectionAdvisor advisor, IElement endElement, Terminal endTerminal) {
812 return ConnectionJudgement.CANBEMADELEGAL;
813 if (alreadyConnected.contains(Pair.make(endElement, endTerminal)))
815 return advisor.canBeConnected(null, startTerminal.e, startTerminal.t, endElement, endTerminal);
818 protected static FlagClass.Type endToFlagType(EdgeEnd end) {
821 return FlagClass.Type.In;
823 return FlagClass.Type.Out;
825 throw new IllegalArgumentException("unrecognized edge end: " + end);
829 protected TerminalInfo createFlag(EdgeEnd connectionEnd) {
830 ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);
831 IElement e = Element.spawnNew(flagClass);
833 e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));
834 e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);
836 TerminalInfo ti = new TerminalInfo();
838 ti.t = ElementUtils.getSingleTerminal(e);
839 ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);
840 ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);
845 // ------------------------------------------------------------------------
847 static RouteGraphTarget pickRouteGraphConnection(ICanvasContext ctx, IDiagram diagram, Shape pickShape, double pickDistance) {
848 ArrayList<IElement> elements = new ArrayList<IElement>();
849 PickRequest req = new PickRequest(pickShape).context(ctx);
850 DiagramUtils.pick(diagram, req, elements);
851 for (Iterator<IElement> it = elements.iterator(); it.hasNext();) {
852 IElement e = it.next();
853 RouteGraphNode rgn = e.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
854 if (rgn == null || rgn.getRouteGraph() == null)
857 if (elements.isEmpty())
860 Rectangle2D pickRect = pickShape.getBounds2D();
861 final double x = pickRect.getCenterX();
862 final double y = pickRect.getCenterY();
864 return pickNearestRouteGraphConnection(elements, x, y, pickDistance);
867 private static RouteGraphTarget pickNearestRouteGraphConnection(ArrayList<IElement> elements, double x, double y, double pd) {
868 Segment nearestSegment = null;
869 RouteLine nearestLine = null;
870 IElement nearestConnection = null;
872 double minDistanceSq = Double.MAX_VALUE;
874 for (IElement connection : elements) {
875 RouteGraphNode rgn = connection.getHint(RouteGraphConnectionClass.KEY_RG_NODE);
876 for (RouteLine line : rgn.getRouteGraph().getAllLines()) {
877 ArrayList<Segment> segments = new ArrayList<Segment>();
878 line.collectSegments(segments);
879 for (Segment segment : segments) {
880 RoutePoint p1 = segment.p1;
881 RoutePoint p2 = segment.p2;
883 double distanceSq = Line2D.ptSegDistSq(p1.getX(), p1.getY(), p2.getX(), p2.getY(), x, y);
884 if (distanceSq < minDistanceSq && distanceSq < Math.pow(pd + rgn.getSelectionStrokeWidth() / 2, 2)) {
885 minDistanceSq = distanceSq;
886 nearestSegment = segment;
888 nearestConnection = connection;
894 if (nearestSegment != null) {
895 RoutePoint p1 = nearestSegment.p1;
896 RoutePoint p2 = nearestSegment.p2;
897 double d = Math.pow(p2.getX() - p1.getX(), 2.0) + Math.pow(p2.getY() - p1.getY(), 2.0);
900 p = new Point2D.Double(p1.getX(), p1.getY());
902 double u = ((x - p1.getX()) * (p2.getX() - p1.getX()) + (y - p1.getY()) * (p2.getY() - p1.getY())) / d;
904 p = new Point2D.Double(p2.getX(), p2.getY());
905 } else if (u <= 0.0) {
906 p = new Point2D.Double(p1.getX(), p1.getY());
908 p = new Point2D.Double(p2.getX() * u + p1.getX() * (1.0-u), (p2.getY() * u + p1.getY() * (1.0- u)));
911 return new RouteGraphTarget(nearestConnection, nearestConnection.getHint(RouteGraphConnectionClass.KEY_RG_NODE), nearestLine, new Point2D.Double(x, y), p);
917 static Point2D intersectionPoint(double x, double y, RouteLine line) {
918 Collection<RoutePoint> points = line.getPoints();
919 if (points.size() < 2)
921 RoutePoint s = line.getBegin();
922 RoutePoint e = line.getEnd();
923 return GeometryUtils.intersectionToLine(s.getX(), s.getY(), e.getX(), e.getY(), x, y);