1 package org.simantics.sysdyn.ui.editor.participant;
\r
3 import java.awt.BasicStroke;
\r
4 import java.awt.Color;
\r
5 import java.awt.geom.AffineTransform;
\r
6 import java.awt.geom.Path2D;
\r
7 import java.awt.geom.Point2D;
\r
8 import java.awt.geom.Rectangle2D;
\r
9 import java.util.ArrayList;
\r
10 import java.util.Collection;
\r
11 import java.util.Collections;
\r
12 import java.util.Deque;
\r
13 import java.util.Iterator;
\r
14 import java.util.List;
\r
16 import org.simantics.db.ReadGraph;
\r
17 import org.simantics.db.Resource;
\r
18 import org.simantics.db.WriteGraph;
\r
19 import org.simantics.db.common.request.WriteRequest;
\r
20 import org.simantics.db.exception.DatabaseException;
\r
21 import org.simantics.db.request.Read;
\r
22 import org.simantics.diagram.participant.ConnectTool2;
\r
23 import org.simantics.diagram.participant.ConnectionBuilder;
\r
24 import org.simantics.diagram.participant.ControlPoint;
\r
25 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
\r
26 import org.simantics.g2d.connection.IConnectionAdvisor;
\r
27 import org.simantics.g2d.diagram.DiagramHints;
\r
28 import org.simantics.g2d.diagram.handler.Topology.Terminal;
\r
29 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
\r
30 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
\r
31 import org.simantics.g2d.element.ElementClass;
\r
32 import org.simantics.g2d.element.ElementClasses;
\r
33 import org.simantics.g2d.element.ElementUtils;
\r
34 import org.simantics.g2d.element.IElement;
\r
35 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
\r
36 import org.simantics.g2d.element.handler.impl.StaticObjectAdapter;
\r
37 import org.simantics.g2d.element.impl.Element;
\r
38 import org.simantics.g2d.elementclass.BranchPointClass;
\r
39 import org.simantics.g2d.elementclass.FlagClass;
\r
40 import org.simantics.g2d.routing.Constants;
\r
41 import org.simantics.g2d.routing.IConnection;
\r
42 import org.simantics.g2d.routing.IRouter2;
\r
43 import org.simantics.g2d.routing.TrivialRouter2;
\r
44 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
45 import org.simantics.scenegraph.g2d.events.MouseEvent;
\r
46 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonEvent;
\r
47 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
\r
48 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
\r
49 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
\r
50 import org.simantics.scenegraph.g2d.snap.ISnapAdvisor;
\r
51 import org.simantics.structural2.modelingRules.ConnectionJudgement;
\r
52 import org.simantics.sysdyn.SysdynResource;
\r
53 import org.simantics.sysdyn.ui.editor.routing.FlowRouter;
\r
54 import org.simantics.sysdyn.ui.elements.CloudFactory;
\r
55 import org.simantics.sysdyn.ui.elements.SysdynElementHints;
\r
56 import org.simantics.sysdyn.ui.elements.ValveFactory.ValveSceneGraph;
\r
57 import org.simantics.sysdyn.ui.elements.connections.ConnectionClasses;
\r
58 import org.simantics.ui.SimanticsUI;
\r
59 import org.simantics.utils.datastructures.Callback;
\r
60 import org.simantics.utils.datastructures.Pair;
\r
61 import org.simantics.utils.ui.ErrorLogger;
\r
62 import org.simantics.utils.ui.ExceptionUtils;
\r
64 public class SysdynConnectTool extends ConnectTool2 {
\r
66 public SysdynConnectTool(TerminalInfo startTerminal, int mouseId,
\r
67 Point2D startCanvasPos) {
\r
68 super(startTerminal, mouseId, startCanvasPos);
\r
73 public void initSG(G2DParentNode parent) {
\r
74 ghostNode = parent.addNode(G2DParentNode.class);
\r
75 ghostNode.setZIndex(PAINT_PRIORITY);
\r
77 ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
\r
78 pathNode.setColor(Color.BLACK);
\r
79 pathNode.setStroke(new BasicStroke(1f));
\r
80 pathNode.setScaleStroke(true);
\r
81 pathNode.setZIndex(0);
\r
83 G2DParentNode points = ghostNode.getOrCreateNode("points", G2DParentNode.class);
\r
84 points.setZIndex(1);
\r
90 protected TerminalInfo createFlag(EdgeEnd connectionEnd) {
\r
91 ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);
\r
92 IElement e = Element.spawnNew(flagClass);
\r
94 e.setHint(FlagClass.KEY_FLAG_TYPE, endToFlagType(connectionEnd));
\r
95 e.setHint(FlagClass.KEY_FLAG_MODE, FlagClass.Mode.Internal);
\r
97 TerminalInfo ti = new TerminalInfo();
\r
100 // start: this part changed to support overlapping terminals
\r
101 ArrayList<Terminal> terminals = new ArrayList<Terminal>();
\r
102 ElementUtils.getTerminals(e, terminals, false);
\r
103 ti.t = terminals.get(0);
\r
106 ti.posElem = TerminalUtil.getTerminalPosOnElement(e, ti.t);
\r
107 ti.posDia = TerminalUtil.getTerminalPosOnDiagram(e, ti.t);
\r
112 static class Segment {
\r
113 public final ControlPoint begin;
\r
114 public final ControlPoint end;
\r
115 public Path2D path;
\r
117 public Segment(ControlPoint begin, ControlPoint end) {
\r
118 this.begin = begin;
\r
123 public String toString() {
\r
124 return "Segment[begin=" + begin + ", end=" + end + ", path=" + path + "]";
\r
128 private List<Segment> toSegments(Deque<ControlPoint> points) {
\r
129 if (points.isEmpty())
\r
130 return Collections.emptyList();
\r
132 List<Segment> segments = new ArrayList<Segment>();
\r
134 Iterator<ControlPoint> it = points.iterator();
\r
135 ControlPoint prev = it.next();
\r
136 while (it.hasNext()) {
\r
137 ControlPoint next = it.next();
\r
138 segments.add(new Segment(prev, next));
\r
145 public interface SysdynConnection extends IConnection { }
\r
148 protected void updateSG() {
\r
149 if (controlPoints.isEmpty())
\r
152 // Route connection segments separately
\r
153 IRouter2 router = ElementUtils.getHintOrDefault(diagram, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);
\r
154 final List<Segment> segments = toSegments(controlPoints);
\r
155 //System.out.println("controlpoints: " + controlPoints);
\r
156 //System.out.println("segments: " + segments);
\r
157 router.route(new SysdynConnection() {
\r
159 public Collection<? extends Object> getSegments() {
\r
164 public Connector getBegin(Object seg) {
\r
165 return getConnector(((Segment) seg).begin);
\r
169 public Connector getEnd(Object seg) {
\r
170 return getConnector(((Segment) seg).end);
\r
173 private Connector getConnector(ControlPoint cp) {
\r
174 Connector c = new Connector();
\r
175 c.x = cp.getPosition().getX();
\r
176 c.y = cp.getPosition().getY();
\r
178 c.allowedDirections = Constants.EAST_FLAG | Constants.WEST_FLAG
\r
179 | Constants.NORTH_FLAG | Constants.SOUTH_FLAG;
\r
181 TerminalInfo ti = cp.getAttachedTerminal();
\r
182 if (ti != null && (ti != startFlag && ti != endFlag)) {
\r
183 if(ti.e.getElementClass().containsClass(ValveSceneGraph.class)) {
\r
184 Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D();
\r
185 c.parentObstacle = new Rectangle2D.Double(
\r
186 bounds.getCenterX() - FlowRouter.OFFSET,
\r
187 bounds.getCenterY() - FlowRouter.OFFSET,
\r
188 FlowRouter.OFFSET * 2,
\r
189 FlowRouter.OFFSET * 2);
\r
191 c.parentObstacle = ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D();
\r
193 } else if (ti != null && ti == startFlag) {
\r
194 c.parentObstacle = org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),
\r
195 ElementUtils.getElementBoundsOnDiagram(ti.e).getBounds2D());
\r
196 } else if (ti != null && isEndingInFlag()) {
\r
197 c.parentObstacle = org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),
\r
198 CloudFactory.CLOUD_IMAGE.getBounds());
\r
200 c.parentObstacle = org.simantics.scenegraph.utils.GeometryUtils.transformRectangle(AffineTransform.getTranslateInstance(c.x, c.y),
\r
201 BranchPointClass.DEFAULT_IMAGE2.getBounds());
\r
208 public void setPath(Object seg, Path2D path) {
\r
209 ((Segment) seg).path = (Path2D) path.clone();
\r
213 // Combine the routed paths
\r
214 Path2D path = new Path2D.Double();
\r
215 for (Segment seg : segments) {
\r
216 //System.out.println("SEG: " + seg);
\r
217 if (seg.path != null)
\r
218 path.append(seg.path.getPathIterator(null), true);
\r
221 // Create scene graph to visualize the connection.
\r
222 ShapeNode pathNode = ghostNode.getOrCreateNode("path", ShapeNode.class);
\r
223 pathNode.setShape(path);
\r
233 protected Object canConnect(final IConnectionAdvisor advisor, final IElement endElement, final Terminal endTerminal) {
\r
234 final IElement se = startTerminal != null ? startTerminal.e : startFlag.e;
\r
235 final Terminal st = startTerminal != null ? startTerminal.t : null;
\r
237 if(se.equals(endElement)) return null;
\r
238 if(Boolean.FALSE.equals(diagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS)) && endElement == null) {
\r
242 if(endElement == null && endTerminal == null)
\r
243 return advisor.canBeConnected(null, se, st, endElement, endTerminal);
\r
246 return SimanticsUI.getSession().syncRequest(new Read<Object>() {
\r
249 public Object perform(ReadGraph g) throws DatabaseException {
\r
251 // Checking if connection type can be connected to the intended endElement
\r
252 SysdynResource sr = SysdynResource.getInstance(g);
\r
253 StaticObjectAdapter soa = endElement.getElementClass().getSingleItem(StaticObjectAdapter.class);
\r
254 Resource end = soa.adapt(Resource.class);
\r
255 ElementClass dependency = elementClassProvider.get(ConnectionClasses.DEPENDENCY);
\r
256 ElementClass flow = elementClassProvider.get(ConnectionClasses.FLOW);
\r
257 ElementClass currentConnection = elementClassProvider.get(ElementClasses.CONNECTION);
\r
258 if(currentConnection.equals(dependency)) {
\r
259 if(end.equals(sr.CloudSymbol)) return null;
\r
260 soa = se.getElementClass().getSingleItem(StaticObjectAdapter.class);
\r
261 Resource start = soa.adapt(Resource.class);
\r
262 if(g.isInheritedFrom(start, sr.ModuleSymbol) && !end.equals(sr.InputSymbol)) return null;
\r
263 if(end.equals(sr.ShadowSymbol)) return null;
\r
264 } else if (currentConnection.equals(flow)) {
\r
265 if(!(end.equals(sr.StockSymbol) || end.equals(sr.ValveSymbol) || end.equals(sr.CloudSymbol))) return null;
\r
271 if (advisor == null)
\r
272 return Boolean.TRUE;
\r
273 return advisor.canBeConnected(g, se, st, endElement, endTerminal);
\r
277 } catch(DatabaseException e) {
\r
278 e.printStackTrace();
\r
285 protected boolean processMouseMove(MouseMovedEvent me) {
\r
286 mouseHasMoved = true;
\r
288 Point2D mouseControlPos = me.controlPosition;
\r
289 Point2D mouseCanvasPos = util.controlToCanvas(mouseControlPos, new Point2D.Double());
\r
291 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
\r
292 if (snapAdvisor != null)
\r
293 snapAdvisor.snap(mouseCanvasPos);
\r
295 // Record last snapped canvas position of mouse.
\r
296 this.lastMouseCanvasPos.setLocation(mouseCanvasPos);
\r
298 if (isEndingInFlag()) {
\r
299 endFlagNode.setTransform(AffineTransform.getTranslateInstance(mouseCanvasPos.getX(), mouseCanvasPos.getY()));
\r
302 List<TerminalInfo> tiList = ((SysdynPointerInteractor)pi).pickTerminals(me.controlPosition);
\r
303 TerminalInfo ti = null;
\r
305 IConnectionAdvisor advisor = diagram.getHint(DiagramHints.CONNECTION_ADVISOR);
\r
306 for(TerminalInfo info : tiList) {
\r
307 if(advisor == null || info.e == null || info.t == null)
\r
309 Object canConnect = canConnect(advisor, info.e, info.t);
\r
310 if (canConnect != null) {
\r
316 if (ti != null && !isStartTerminal(ti.e, ti.t)) {
\r
317 Pair<ConnectionJudgement, TerminalInfo> canConnect = canConnect(ti.e, ti.t);
\r
318 if (canConnect != null) {
\r
319 connectionJudgment = canConnect.first;
\r
321 if (!isEndingInFlag() || !TerminalUtil.isSameTerminal(ti, endTerminal)) {
\r
322 controlPoints.getLast()
\r
323 .setPosition(ti.posDia)
\r
324 .setAttachedToTerminal(ti);
\r
329 // Make sure that we are ending with a flag if ALT is pressed
\r
330 // and no end terminal is defined. If we are in flow creation
\r
331 // mode, we want to show the terminal cloud (or flag) even when
\r
332 // alt is not pressed.
\r
333 if (inFlowMode() && flowInProgress() && !startTerminals.isEmpty())
\r
334 endWithoutTerminal(lastMouseCanvasPos, true);
\r
336 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
\r
342 connectionJudgment = null;
\r
343 if (isEndTerminalDefined()) {
\r
344 // CASE: Mouse was previously on top of a valid terminal to end
\r
345 // the connection. Now the mouse has been moved where there is
\r
346 // no longer a terminal to connect to.
\r
348 // => Disconnect the last edge segment from the previous
\r
349 // terminal, mark endElement/endTerminal non-existent
\r
350 // and connect the disconnected edge to a new branch point.
\r
352 controlPoints.getLast()
\r
353 .setPosition(mouseCanvasPos)
\r
354 .setDirection(calculateCurrentBranchPointDirection())
\r
355 .setAttachedToTerminal(null);
\r
357 endTerminal = null;
\r
359 // CASE: Mouse was not previously on top of a valid ending
\r
360 // element terminal.
\r
362 // => Move and re-orient last branch point.
\r
364 controlPoints.getLast()
\r
365 .setPosition(mouseCanvasPos)
\r
366 .setDirection(calculateCurrentBranchPointDirection());
\r
369 // Make sure that we are ending with a flag if ALT is pressed and no end
\r
370 // terminal is defined. If we are in flow creation mode, we want to show
\r
371 // the terminal cloud (or flag) even when alt is not pressed.
\r
372 if (inFlowMode() && flowInProgress() && !startTerminals.isEmpty())
\r
373 endWithoutTerminal(lastMouseCanvasPos, true);
\r
375 endWithoutTerminal(lastMouseCanvasPos, shouldEndWithFlag(me));
\r
381 protected boolean processMouseButtonPress(MouseButtonPressedEvent e) {
\r
382 MouseButtonEvent me = e;
\r
384 // Do nothing before the mouse has moved at least a little.
\r
385 // This prevents the user from ending the connection right where
\r
387 if (!mouseHasMoved)
\r
390 if (me.button == MouseEvent.LEFT_BUTTON ||
\r
391 (me.button == MouseEvent.RIGHT_BUTTON && flowInProgress() && !inFlowMode())) {
\r
392 Point2D mouseControlPos = me.controlPosition;
\r
393 Point2D mouseCanvasPos = util.getInverseTransform().transform(mouseControlPos, new Point2D.Double());
\r
395 ISnapAdvisor snapAdvisor = getHint(DiagramHints.SNAP_ADVISOR);
\r
396 if (snapAdvisor != null)
\r
397 snapAdvisor.snap(mouseCanvasPos);
\r
399 // Clicked on an allowed end terminal. End connection & end mode.
\r
400 if (isEndTerminalDefined()) {
\r
401 createConnection();
\r
405 // Finish connection in thin air only if the
\r
406 // connection was started from a valid terminal.
\r
408 // If we are in flow creation mode, we want to be able to
\r
409 // create the terminal cloud (or flag) without having to
\r
412 if (!startTerminals.isEmpty() && ((me.stateMask & MouseEvent.ALT_MASK) != 0 ||
\r
413 (inFlowMode() && flowInProgress()))) {
\r
414 Pair<ConnectionJudgement, TerminalInfo> pair = canConnect(null, null);
\r
415 if (pair != null) {
\r
416 connectionJudgment = (ConnectionJudgement) pair.first;
\r
417 selectedStartTerminal = pair.second;
\r
418 createConnection();
\r
422 // Inform the user why connection couldn't be created.
\r
423 ErrorLogger.defaultLogWarning("Can't resolve connection type for new connection.", null);
\r
426 } else if (routePointsAllowed()
\r
427 && (me.stateMask & (MouseEvent.ALT_MASK | MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK)) == 0) {
\r
428 // Add new connection control point.
\r
429 controlPoints.add(newControlPointWithCalculatedDirection(mouseCanvasPos));
\r
430 resetForcedBranchPointDirection();
\r
439 private boolean inFlowMode() {
\r
440 return SysdynElementHints.FLOW_TOOL.equals(getHint(SysdynElementHints.SYSDYN_KEY_TOOL));
\r
443 private boolean flowInProgress() {
\r
444 return elementClassProvider.get(ElementClasses.CONNECTION).equals(elementClassProvider.get(ConnectionClasses.FLOW));
\r
448 protected void createConnection() {
\r
450 if(this.connectionJudgment == null) return;
\r
452 final ConnectionJudgement judgment = this.connectionJudgment;
\r
453 // ConnectionBuilder changed to SysdynconnectionBuilder to support overlapping terminals and valve creation
\r
454 final ConnectionBuilder builder = new SysdynConnectionBuilder(this.diagram);
\r
455 final Deque<ControlPoint> controlPoints = this.controlPoints;
\r
456 final TerminalInfo startTerminal = this.startTerminal;
\r
457 final TerminalInfo endTerminal = this.endTerminal;
\r
459 SimanticsUI.getSession().asyncRequest(new WriteRequest() {
\r
461 public void perform(WriteGraph graph) throws DatabaseException {
\r
462 builder.create(graph, judgment, controlPoints, startTerminal, endTerminal);
\r
464 }, new Callback<DatabaseException>() {
\r
466 public void run(DatabaseException parameter) {
\r
467 if (parameter != null)
\r
468 ExceptionUtils.logAndShowError(parameter);
\r