/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.diagram.content; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Statement; import org.simantics.db.WriteGraph; import org.simantics.db.WriteOnlyGraph; import org.simantics.db.common.CommentMetadata; import org.simantics.db.common.request.IndexRoot; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.common.utils.OrderedSetUtils; import org.simantics.db.exception.AssumptionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ValidationException; import org.simantics.db.layer0.adapter.impl.EntityRemover; import org.simantics.db.layer0.util.RemoverUtil; import org.simantics.diagram.connection.ConnectionSegmentEnd; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.graph.BasicResources; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.g2d.connection.handler.ConnectionHandler; import org.simantics.g2d.diagram.handler.PickRequest.PickFilter; import org.simantics.g2d.diagram.handler.Topology.Terminal; import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.BendsHandler; import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; import org.simantics.g2d.element.handler.impl.BranchPointTerminal; import org.simantics.g2d.elementclass.BranchPoint; import org.simantics.g2d.elementclass.BranchPoint.Direction; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingResources; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.scl.commands.Commands; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.structural2.modelingRules.CPConnection; import org.simantics.structural2.modelingRules.CPConnectionJoin; import org.simantics.structural2.modelingRules.CPTerminal; import org.simantics.structural2.modelingRules.IConnectionPoint; import org.simantics.utils.Development; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.datastructures.Triple; import org.simantics.utils.ui.AdaptionUtils; /** * @author Tuukka Lehtonen */ public final class ConnectionUtil { private final ReadGraph rg; private final WriteGraph g; private final BasicResources br; /** * Construct utility with read-only support. * @param g */ public ConnectionUtil(ReadGraph g) { this.rg = g; this.g = (g instanceof WriteGraph) ? (WriteGraph) g : null; this.br = BasicResources.getInstance(g); } /** * Construct utility with read-write support. * @param g */ public ConnectionUtil(WriteGraph g) { this.rg = g; this.g = g; this.br = BasicResources.getInstance(g); } void assertWriteSupport() { if (g == null) throw new UnsupportedOperationException("no write support, this ConnectionUtil is read-only"); } /** * Creates a new connection element of the specified type and attaches it to * the specified diagram composite. * * @param composite diagram composite * @param type connection element type * @return created connection * @throws DatabaseException */ public Resource newConnection(Resource composite, Resource type) throws DatabaseException { assertWriteSupport(); Resource connection = newConnection(type); // By default, add connections to the front of the diagram since most // often it is visually the expected result. OrderedSetUtils.addFirst(g, composite, connection); g.claim(composite, br.L0.ConsistsOf, br.L0.PartOf, connection); return connection; } /** * Creates a new connection element of the specified type without attaching * it to any diagram composite. * * @param type connection element type * @return created connection * @throws DatabaseException */ public Resource newConnection(Resource type) throws DatabaseException { assertWriteSupport(); return newInstance(g, type); } /** * Creates a new element terminal connector (DiagramResource) for the specified connector * @param connection * @param hasConnector * @return * @throws DatabaseException */ public Resource newConnector(Resource connection, Resource hasConnector) throws DatabaseException { assertWriteSupport(); Resource connector = newInstance(g, br.DIA.Connector); g.claim(connection, hasConnector, connector); return connector; } public Resource newBranchPoint(Resource connection, AffineTransform tr) throws DatabaseException { return newBranchPoint(connection, tr, null); } public Resource newBranchPoint(Resource connection, AffineTransform tr, Direction direction) throws DatabaseException { assertWriteSupport(); Resource bp = g.newResource(); g.claim(bp, br.L0.InstanceOf, null, br.DIA.BranchPoint); if (tr != null) { double[] mat = new double[6]; tr.getMatrix(mat); Resource transform = g.newResource(); g.claim(transform, br.L0.InstanceOf, null, br.G2D.Transform); g.claimValue(transform, mat); g.claim(bp, br.DIA.HasTransform, transform); } Resource tag = toDirectionTag(g, direction); if (tag != null) { g.claim(bp, tag, tag, bp); } g.claim(connection, br.DIA.HasBranchPoint, bp); return bp; } /** * @param connection * @param position * @param isHorizontal * @return * @throws DatabaseException */ public Resource newRouteLine(Resource connection, Double position, Boolean isHorizontal) throws DatabaseException { assertWriteSupport(); Resource rl = newInstance(g, br.DIA.RouteLine); if (position != null) { g.addLiteral(rl, br.DIA.HasPosition, br.DIA.HasPosition_Inverse, br.L0.Double, position, Bindings.DOUBLE); } if (isHorizontal != null) { g.addLiteral(rl, br.DIA.IsHorizontal, br.DIA.IsHorizontal_Inverse, br.L0.Boolean, isHorizontal, Bindings.BOOLEAN); } g.claim(connection, br.DIA.HasInteriorRouteNode, br.DIA.HasInteriorRouteNode_Inverse, rl); return rl; } ConnectionSegmentEnd getTerminalType(Terminal terminal, ConnectionSegmentEnd defaultValue) { ConnectionSegmentEnd type = getTerminalType(terminal); return type != null ? type : defaultValue; } ConnectionSegmentEnd getTerminalType(Terminal t) { if (t == null) return null; if (t instanceof ResourceTerminal) { return ConnectionSegmentEnd.CONNECTOR; } else if (t instanceof BranchPointTerminal) { return ConnectionSegmentEnd.BRANCH; } else { throw new IllegalArgumentException("unsupported terminal '" + t + "'"); } } Resource resolveTerminalRelation(ReadGraph g, Terminal t) throws DatabaseException { if (t == null) return null; if (t instanceof ResourceTerminal) { ResourceTerminal rt = (ResourceTerminal) t; Resource terminalRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, rt.getResource()); if (!g.isSubrelationOf(terminalRelation, br.STR.IsConnectedTo)) { // debug... } return terminalRelation; } else { throw new IllegalArgumentException("unsupported terminal '" + t + "' for terminal relation resolution"); } } public Resource toHasConnectorRelation(EdgeEnd end) { switch (end) { case Begin: return br.DIA.HasPlainConnector; case End: return br.DIA.HasArrowConnector; default: throw new IllegalArgumentException("unsupported edge end: " + end); } } public EdgeEnd toEdgeEnd(Resource attachmentRelation, EdgeEnd defaultValue) throws DatabaseException { if (g.isSubrelationOf(attachmentRelation, br.DIA.HasPlainConnector)) return EdgeEnd.Begin; if (g.isSubrelationOf(attachmentRelation, br.DIA.HasArrowConnector)) return EdgeEnd.End; return defaultValue; } public Resource getAttachmentRelationForConnector(Resource connector) throws DatabaseException { Statement connection = g.getPossibleStatement(connector, br.DIA.IsConnectorOf); if (connection == null) return null; Resource attachment = g.getInverse(connection.getPredicate()); return attachment; } /** * @param connection * @param node * @param c * @param end * @param attachmentRelation the relation used for attaching the connector to the connector * @return * @throws DatabaseException */ public Resource getOrCreateConnector(Resource connection, Resource node, Terminal terminal, EdgeEnd end, Resource attachmentRelation) throws DatabaseException { assertWriteSupport(); ConnectionSegmentEnd connectorType = getTerminalType(terminal, null); if (connectorType == null) throw new AssumptionException("Invalid connection node", connection, node); switch (connectorType) { case BRANCH: // NOTE: should we ensure here that (connection, br.dr.HasBranchPoint, node) exists? return node; case CONNECTOR: { Resource terminalRelation = resolveTerminalRelation(g, terminal); if (attachmentRelation == null) attachmentRelation = toHasConnectorRelation(end); if (!g.isSubrelationOf(attachmentRelation, br.DIA.HasConnector)) throw new AssumptionException("attachment relation not a subrelation of Has Connector", attachmentRelation); // Create new connector for the specified node terminal Resource terminalConnector = newConnector(connection, attachmentRelation); g.claim(node, terminalRelation, terminalConnector); return terminalConnector; } default: throw new Error("this should be unreachable code"); } } public void connect(Resource connector1, Resource connector2) throws DatabaseException { assertWriteSupport(); g.claim(connector1, br.DIA.AreConnected, br.DIA.AreConnected, connector2); } public void disconnect(Resource connector1, Resource connector2) throws DatabaseException { assertWriteSupport(); g.denyStatement(connector1, br.DIA.AreConnected, connector2); } public void disconnectFromAllRouteNodes(Resource connector) throws DatabaseException { assertWriteSupport(); g.deny(connector, br.DIA.AreConnected); } public void disconnect(EdgeResource segment) throws DatabaseException { assertWriteSupport(); disconnect(segment.first(), segment.second()); } public boolean isConnected(Resource connector) throws DatabaseException { return rg.hasStatement(connector, br.DIA.AreConnected); } public boolean isConnected(Resource connector, Resource toConnector) throws DatabaseException { return rg.hasStatement(connector, br.DIA.AreConnected, toConnector); } public boolean isConnectionEmpty(Resource connection) throws DatabaseException { return !rg.hasStatement(connection, br.DIA.HasConnector) && !rg.hasStatement(connection, br.DIA.HasInteriorRouteNode); } private void removeConnectorOrBranchPoint(Resource connectorOrBranchPoint) throws DatabaseException { // // Handle correct removal of route points // if(g.isInstanceOf(connectorOrBranchPoint, dr.BranchPoint)) { // Collection connectedConnectors = g.getObjects(connectorOrBranchPoint, dr.AreConnected); // if(connectedConnectors.size() == 2) { // Iterator it = connectedConnectors.iterator(); // g.claim(it.next(), dr.AreConnected, it.next()); // } // } g.deny(connectorOrBranchPoint, br.DIA.AreConnected); // Removes both the terminal relation and the HasConnector relation // to the :Connection g.deny(connectorOrBranchPoint, br.STR.Connects); // If this is a branch point/route node, remove it from the connection too. g.deny(connectorOrBranchPoint, br.DIA.IsBranchPointOf); g.deny(connectorOrBranchPoint, br.DIA.HasInteriorRouteNode_Inverse); } /** * Removes a complete connection along with all its branch points and terminal connectors. * * @param connection the connection to remove */ public void removeConnection(Resource connection) throws DatabaseException { assertWriteSupport(); // Add comment to change set. CommentMetadata cm = g.getMetadata(CommentMetadata.class); g.addMetadata(cm.add("Remove connection " + connection)); // 1. Get all connectors/branch points Collection connectors = new ArrayList(); connectors.addAll(rg.getObjects(connection, br.DIA.HasConnector)); connectors.addAll(rg.getObjects(connection, br.DIA.HasInteriorRouteNode)); // 2. Remove all connectors/branch points for (Resource connector : connectors) { removeConnectorOrBranchPoint(connector); RemoverUtil.remove(g, connector); } // 3. Remove whole connection for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, br.DIA.Diagram)) OrderedSetUtils.remove(g, owner, connection); EntityRemover.remove(g, connection); } /** * Removes a single connector part from the graph. A connection part can be * either a branch point or a terminal connector. * * @param connectorOrBranchPoint * @throws DatabaseException */ public void removeConnectionPart(Resource connectorOrBranchPoint) throws DatabaseException { removeConnectorOrBranchPoint(connectorOrBranchPoint); RemoverUtil.remove(g, connectorOrBranchPoint); } /** * Removes the specified connection segment. Checks that both ends of the * edge segment are part of to the same connection. Steps taken: *
    *
  • Minimally this only disconnects the connection segment ends from each * other and nothing more.
  • *
  • After disconnecting, we check whether the segment ends are still * connected to something. If not, the :Connector/:BranchPoint at the * segment's end is destroyed and detached from the connection entity.
  • *
  • Finally, if the connection entity is empty (has no connectors/branch * points), it is also destroyed and removed from the diagram.
  • * * @param segment the connection segment to remove */ public void remove(EdgeResource segment) throws DatabaseException { remove(segment, false); } /** * Removes the specified connection segment. Steps taken: *
      *
    • Minimally this only disconnects the connection segment ends from each * other and nothing more.
    • *
    • After disconnecting, we check whether the segment ends are still * connected to something. If not, the :Connector/:BranchPoint at the * segment's end is destroyed and detached from the connection entity.
    • *
    • Finally, if the connection entity is empty (has no connectors/branch * points), it is also destroyed and removed from the diagram.
    • * * @param segment the connection segment to remove * @param unchecked false to check that both ends of the * segment are part of the same connection before removing, * true to just remove the segment without checking * this. Using true may help in cases where the * connection model has become corrupted for some reason, e.g. the * other end of the edge has lost its link to the connection while * the other has not, */ public void remove(EdgeResource segment, boolean unchecked) throws DatabaseException { assertWriteSupport(); if (!unchecked) { @SuppressWarnings("unused") Resource connection = getConnection(g, segment); } // 1. disconnect segment ends disconnect(segment); // 2. Remove connectors/branch points if they become fully disconnected if (!isConnected(segment.first())) { removeConnectorOrBranchPoint(segment.first()); } if (!isConnected(segment.second())) { removeConnectorOrBranchPoint(segment.second()); } // 3. Remove whole connection entity if it becomes empty // if (isConnectionEmpty(connection)) { // for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, dr.Diagram)) // OrderedSetUtils.remove(g, owner, connection); // RemoverUtil.remove(g, connection); // } } /** * Removes all DIA.Connector instances from the specified connection that * are not used for anything. * * @param connection connection to examine * @return the amount of unused connectors removed */ public int removeUnusedConnectors(Resource connection) throws DatabaseException { int result = 0; for (Resource connector : getConnectors(connection, null)) { if (!g.getObjects(connector, br.DIA.AreConnected).isEmpty()) continue; Collection connects = g.getObjects(connector, br.STR.Connects); if (connects.size() > 1) continue; removeConnectionPart(connector); ++result; } return result; } /** * Removes all DIA.InteriorRouteNode instances from the specified connection that * are not used for anything, i.e. connect to less than 2 other route nodes. * * @param connection connection to examine * @return the amount of unused connectors removed */ public int removeExtraInteriorRouteNodes(Resource connection) throws DatabaseException { int result = 0; for (Resource interiorRouteNode : g.getObjects(connection, br.DIA.HasInteriorRouteNode)) { Collection connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected); if (connectedTo.size() > 1) continue; removeConnectionPart(interiorRouteNode); ++result; } return result; } /** * Splits the specified connection segment by adding a new branch point in * between the segment ends. * * @param segment * @return the branch (route) point created by the split operation. */ public Resource split(EdgeResource segment, AffineTransform splitPos) throws DatabaseException { assertWriteSupport(); Resource connection = getConnection(g, segment); disconnect(segment); Resource bp = newBranchPoint(connection, splitPos); connect(segment.first(), bp); connect(bp, segment.second()); return bp; } /** * Joins the connection at the selected branch point if and only if the * branch point is a route point, i.e. it is connected to two other branch * points or connector. * * @param interiorRouteNode * @return */ public void join(Resource interiorRouteNode) throws DatabaseException { assertWriteSupport(); if (!g.isInstanceOf(interiorRouteNode, br.DIA.InteriorRouteNode)) throw new ValidationException("'" + NameUtils.getSafeName(g, interiorRouteNode) + "' is not an instance of DIA.InteriorRouteNode"); @SuppressWarnings("unused") Resource connection = getConnection(g, interiorRouteNode); Collection connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected); if (connectedTo.size() != 2) throw new ValidationException("Interior route node is not a discardable route line/point. It is not connected to 2 route nodes, but " + connectedTo.size() + "."); Iterator it = connectedTo.iterator(); Resource connector1 = it.next(); Resource connector2 = it.next(); //System.out.println("removing branch point " + GraphUtils.getReadableName(g, routeBranchPoint) + " which is connected to " + GraphUtils.getReadableName(g, connector1) + " and " + GraphUtils.getReadableName(g, connector2)); removeConnectorOrBranchPoint(interiorRouteNode); connect(connector1, connector2); } public void getConnectionSegments(Resource connection, Collection result) throws DatabaseException { ArrayList edges = new ArrayList(); Set visited = new HashSet(); Stack todo = new Stack(); // Try to select input as root, this ensures correct order for simple paths Collection seeds = rg.getObjects(connection, br.DIA.HasArrowConnector); if(seeds.isEmpty()) seeds = rg.getObjects(connection, br.DIA.HasPlainConnector); assert(!seeds.isEmpty()); Resource seed = seeds.iterator().next(); todo.push(seed); while(!todo.isEmpty()) { Resource location = todo.pop(); if(!visited.contains(location)) { visited.add(location); for (Resource connectedTo : rg.getObjects(location, br.DIA.AreConnected)) { todo.add(connectedTo); EdgeResource edge = new EdgeResource(location, connectedTo); if(!edges.contains(edge)) edges.add(edge); } } } for(EdgeResource uer : edges) { // System.out.println("loaded edge " + uer.first() + " " + uer.second()); result.add(uer); } } public Collection getBranchPoints(Resource connection, Collection result) throws DatabaseException { if (result == null) result = new ArrayList(); result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint)); return result; } public Collection getConnectors(Resource connection, Collection result) throws DatabaseException { if (result == null) result = new ArrayList(); result.addAll(rg.getObjects(connection, br.DIA.HasConnector)); return result; } public Resource getConnectedComponent(Resource connection, Resource connector) throws DatabaseException { for (Resource connects : rg.getObjects(connector, br.STR.Connects)) if (!connects.equals(connection)) return connects; return null; } public Statement getConnectedComponentStatement(Resource connection, Resource connector) throws DatabaseException { for (Statement connects : rg.getStatements(connector, br.STR.Connects)) if (!connects.getObject().equals(connection)) return connects; return null; } public void gatherEdges(Resource connection, Collection result) throws DatabaseException { Set visited = new HashSet(); for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) { for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) { EdgeResource p = new EdgeResource(connector, connectedTo); if (visited.add(p)) { result.add(p); } } } Collection routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode); for (Resource routeNode : routeNodes) { for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) { EdgeResource p = new EdgeResource(routeNode, connectedTo); if (visited.add(p)) { result.add(p); } } } } public void gatherConnectionParts(Resource connection, Collection result) throws DatabaseException { Set visited = new HashSet(); for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) { for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) { EdgeResource p = new EdgeResource(connector, connectedTo); if (visited.add(p)) { result.add(p); } } } Collection routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode); for (Resource routeNode : routeNodes) { result.add(routeNode); for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) { EdgeResource p = new EdgeResource(routeNode, connectedTo); if (visited.add(p)) { result.add(p); } } } } public Collection getConnectedComponents(Resource connection, Collection result) throws DatabaseException { if (result == null) result = new ArrayList(2); for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) { for (Resource connects : rg.getObjects(connector, br.STR.Connects)) { if (connects.equals(connection)) continue; result.add(connects); } } return result; } public Collection getConnectedConnectors(Resource connection, Collection result) throws DatabaseException { if (result == null) result = new ArrayList(2); for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) { Resource connects = getConnectedComponent(connection, connector); if (connects != null) result.add( connector ); } return result; } public Collection getTerminalConnectors(Resource node, Collection result) throws DatabaseException { if (result == null) result = new ArrayList(2); result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) ); return result; } /** * @param g * @param routeNode * @return null if the specified resource is not part of any * connection */ public static Resource tryGetConnection(ReadGraph g, Resource routeNode) throws DatabaseException { DiagramResource dr = DiagramResource.getInstance(g); Resource conn = g.getPossibleObject(routeNode, dr.IsConnectorOf); if (conn == null) conn = g.getPossibleObject(routeNode, dr.HasInteriorRouteNode_Inverse); return conn; } public static Resource tryGetConnection(ReadGraph g, EdgeResource segment) throws DatabaseException { Resource first = tryGetConnection(g, segment.first()); Resource second = tryGetConnection(g, segment.second()); if (first == null || second == null || !first.equals(second)) return null; return first; } public static Resource tryGetMappedConnection(ReadGraph g, Resource connector) throws DatabaseException { ModelingResources MOD = ModelingResources.getInstance(g); return g.getPossibleObject(connector, MOD.ConnectorToConnection); } public static Resource getConnection(ReadGraph g, EdgeResource segment) throws DatabaseException { Resource first = tryGetConnection(g, segment.first()); Resource second = tryGetConnection(g, segment.second()); if (first == null && second == null) throw new ValidationException( "neither connection segment end is attached to a Connection entity instance: " + segment.toString(g) + " - " + segment.toString()); if (first != null ^ second != null) throw new ValidationException("both ends of connection segment " + segment.toString(g) + " - " + segment.toString() + " are not connected (first=" + first + ", second=" + second + ")"); if (!first.equals(second)) throw new ValidationException("connectors of connection segment " + segment.toString(g) + " - " + segment.toString() + " are part of different connections: " + first + " vs. " + second); return first; } public static Resource getConnection(ReadGraph g, Resource routeNode) throws DatabaseException { Resource connection = tryGetConnection(g, routeNode); if (connection == null) throw new ValidationException("route node '" + NameUtils.getSafeName(g, routeNode) + "' is not part of any connection"); return connection; } public static IConnectionPoint toConnectionPoint(ReadGraph g, Resource element, Terminal terminal) throws DatabaseException { if (terminal instanceof ResourceTerminal) { Resource t = ((ResourceTerminal) terminal).getResource(); // TODO: remove this hack if (element == null) { // Flag? DiagramResource DIA = DiagramResource.getInstance(g); Resource join = g.getPossibleObject(t, DIA.FlagIsJoinedBy); if (join == null) return null; return new CPConnectionJoin(join); } // element should be :Element Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, t); return new CPTerminal(element, bindingRelation); } else if (terminal instanceof BranchPointTerminal) { // element should be either : DIA.InteriorRouteNode or DIA.Connection Resource connection = null; DiagramResource DIA = DiagramResource.getInstance(g); if (g.isInstanceOf(element, DIA.Connection)) connection = element; else connection = getConnection(g, element); return new CPConnection(connection); } throw new IllegalArgumentException("Unrecognized Terminal class: " + terminal); } public static IConnectionPoint toConnectionPoint(ReadGraph graph, IElement element, Terminal terminal) throws DatabaseException { Resource r = (Resource) ElementUtils.getObject(element); return ConnectionUtil.toConnectionPoint(graph, r, terminal); } public static IConnectionPoint toConnectionPoint(ReadGraph graph, TerminalInfo ti) throws DatabaseException { return ConnectionUtil.toConnectionPoint(graph, ti.e, ti.t); } public static IConnectionPoint toConnectionPoint(ReadGraph graph, DesignatedTerminal t) throws DatabaseException { return toConnectionPoint(graph, t.element, t.terminal); } public static Resource toDirectionTag(ReadGraph graph, BranchPoint.Direction direction) { if (direction == null) return null; DiagramResource DIA = DiagramResource.getInstance(graph); switch (direction) { case Horizontal: return DIA.Horizontal; case Vertical: return DIA.Vertical; default: return null; } } /** * Copied from ConnectionCommandHandler#splitConnection, duplicate code. * * @param toCanvasPos * @param onEdge * @return */ public static Line2D resolveNearestEdgeLineSegment(Point2D toCanvasPos, IElement onEdge) { // Try to find an initial preferred direction for the new // branch point based on the direction of the edge's line // segment on which the split is done. List points = new ArrayList(); BendsHandler bh = onEdge.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); if (bh != null) org.simantics.g2d.utils.GeometryUtils.getPoints(bh.getPath(onEdge), points); Line2D nearestLine = null; double nearestDistanceToLine = Double.MAX_VALUE; for (int i = 0; i < points.size() - 1; ++i) { Point2D p1 = points.get(i); Point2D p2 = points.get(i+1); double distanceToLine = GeometryUtils.distanceFromLine(toCanvasPos, p1, p2); if (distanceToLine < nearestDistanceToLine) { nearestDistanceToLine = distanceToLine; if (nearestLine == null) nearestLine = new Line2D.Double(); nearestLine.setLine(p1, p2); } } return nearestLine; } /** * @param object * @return * @throws DatabaseException */ public static IElement getSingleEdge(Object object) throws DatabaseException { IElement e = AdaptionUtils.adaptToSingle(object, IElement.class); if (e == null) return null; if (PickFilter.FILTER_CONNECTION_EDGES.accept(e)) return e; if (PickFilter.FILTER_CONNECTIONS.accept(e)) { ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class); Collection bps = ch.getBranchPoints(e, null); Collection segs = ch.getSegments(e, null); if (bps.isEmpty() && segs.size() == 1) return segs.iterator().next(); } return null; } /** * @param object * @return * @throws DatabaseException */ public static Collection getEdges(Object object) throws DatabaseException { IElement e = AdaptionUtils.adaptToSingle(object, IElement.class); if (e == null) return null; if (PickFilter.FILTER_CONNECTION_EDGES.accept(e)) return Collections.singleton(e); if (PickFilter.FILTER_CONNECTIONS.accept(e)) { ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class); return ch.getSegments(e, null); } return null; } /** * @param graph * @param connection * @return * @throws DatabaseException */ public static Resource getConnectionTailNode(ReadGraph graph, Resource connection) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) { for (Resource node : graph.getObjects(connector, STR.Connects)) { if (node.equals(connection)) continue; return node; } } return null; } /** * @param graph * @param connection * @return the STR.Connects statement from the tail DIA.Connector to the * tail node (DIA.Element) or null if no tail node * exists * @throws DatabaseException */ public static Statement getConnectionTailNodeStatement(ReadGraph graph, Resource connection) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) { for (Statement connects : graph.getStatements(connector, STR.Connects)) { if (connects.getObject().equals(connection)) continue; return connects; } } return null; } /** * @param connection * @param dx * @param dy * @throws DatabaseException */ public void translateRouteNodes(Resource connection, double dx, double dy) throws DatabaseException { Commands.get(g, "Simantics/Diagram/translateRouteNodes") .execute(g, g.syncRequest(new IndexRoot(connection)), connection, dx, dy); } public static void translateRouteNodes(WriteGraph g, Resource connection, double dx, double dy) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(g); for (Resource routeNode : g.getObjects(connection, DIA.HasInteriorRouteNode)) { if (g.isInstanceOf(routeNode, DIA.RouteLine)) { Double pos = g.getRelatedValue(routeNode, DIA.HasPosition, Bindings.DOUBLE); Boolean isHorizontal = g.getRelatedValue(routeNode, DIA.IsHorizontal, Bindings.BOOLEAN); pos += isHorizontal ? dy : dx; g.claimLiteral(routeNode, DIA.HasPosition, pos); } } } /** * Creates a route graph connection which has corners at the specified * locations, starting/ending from/at the specified element terminals. * * if {@link Development#DEVELOPMENT} is true the code will * verify that both element end-points are part of the same diagram. * * @param graph * database write access * @param startElement * element to start connecting from * @param startConnectionPoint * STR.ConnectedTo relation of the start element terminal * @param endElement * element to end the connection at * @param endConnectionPoint * STR.ConnectedTo relation of the end element terminal * @param corners * the corners to create for the connection * @return the created diagram connection resource * @throws DatabaseException */ public Resource createConnectionWithCorners(WriteGraph graph, Resource startElement, Resource startConnectionPoint, Resource endElement, Resource endConnectionPoint, List corners) throws DatabaseException { DiagramResource DIA = br.DIA; // Verify that both elements are part of the same diagram before connecting. if (Development.DEVELOPMENT) { Collection startDiagram = OrderedSetUtils.getOwnerLists(graph, startElement, DIA.Diagram); Collection endDiagram = OrderedSetUtils.getOwnerLists(graph, endElement, DIA.Diagram); if (Collections.disjoint(startDiagram, endDiagram)) throw new IllegalArgumentException("start element " + startElement + " is not on same diagram as end element " + endElement + ". start diagram: " + startDiagram + ", end diagram: " + endDiagram); } return createConnectionWithCorners(graph, DIA.RouteGraphConnection, startElement, startConnectionPoint, DIA.HasPlainConnector, endElement, endConnectionPoint, DIA.HasArrowConnector, corners); } /** * Creates a route graph connection which has corners at the specified * locations, starting/ending from/at the specified element terminals. * Verifies that both element end-points are part of the same diagram. * * @param graph database write access * @param connectionType type of created connection (e.g. DIA.RouteGraphConnection) * @param element1 element to start connecting from * @param connectionPoint1 STR.ConnectedTo relation of the start element terminal * @param hasConnector1 connector to connection attachment relation to use for element1 and connectionPoint1 * @param element2 element to end the connection at * @param connectionPoint2 STR.ConnectedTo relation of the end element terminal * @param hasConnector2 connector to connection attachment relation to use for element2 and connectionPoint2 * @param corners the corners to create for the connection * @return the created diagram connection resource * @throws DatabaseException */ public Resource createConnectionWithCorners(WriteGraph graph, Resource connectionType, Resource element1, Resource connectionPoint1, Resource hasConnector1, Resource element2, Resource connectionPoint2, Resource hasConnector2, List corners) throws DatabaseException { DiagramResource DIA = br.DIA; if (corners.size() == 1) throw new UnsupportedOperationException("1 corner currently not supported"); Resource connection = newInstance(g, connectionType); Resource connector1 = newConnector(connection, hasConnector1); Resource connector2 = newConnector(connection, hasConnector2); graph.claim(element1, connectionPoint1, connector1); graph.claim(element2, connectionPoint2, connector2); if (corners.size() > 1) { boolean horizontal; Resource previousRouteNode = connector1; for (int i=0; i> endpoints, double coordinate, boolean horizontal) throws DatabaseException { DiagramResource DIA = br.DIA; Resource connection = newInstance(g, connectionType); Resource routeLine = ConnectionUtil.createRouteline(graph, connection, coordinate, horizontal); for(Triple endpoint : endpoints) { Resource connector = newConnector(connection, endpoint.third); graph.claim(endpoint.first, endpoint.second, connector); graph.claim(routeLine, DIA.AreConnected, DIA.AreConnected, connector); } return connection; } public Resource createConnection(WriteGraph graph, Resource connectionType, List> terminals, List> routeLines, List> connections) throws DatabaseException { DiagramResource DIA = br.DIA; Resource connection = newInstance(g, connectionType); Resource[] parts = new Resource[terminals.size() + routeLines.size()]; int index = 0; for(Triple terminal : terminals) { Resource connector = newConnector(connection, terminal.third); graph.claim(terminal.first, terminal.second, connector); parts[index++] = connector; } for(Pair routeLine : routeLines) { Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second); parts[index++] = r; } // System.err.println("Connect " + parts.length + " parts."); for(Pair conn : connections) { // System.err.println("-" + conn.first + " " + conn.second); Resource part1 = parts[conn.first]; Resource part2 = parts[conn.second]; graph.claim(part1, DIA.AreConnected, DIA.AreConnected, part2); } return connection; } /** * @param graph * @param connection * @param pos * @param isHorizontal * @return new route line that is attached to the specified diagram connection * @throws DatabaseException */ public static Resource createRouteline(WriteGraph graph, Resource connection, double pos, boolean isHorizontal) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); DiagramResource DIA = DiagramResource.getInstance(graph); Resource routeLine = graph.newResource(); graph.claim(routeLine, L0.InstanceOf, null, DIA.RouteLine); graph.addLiteral(routeLine, DIA.HasPosition, DIA.HasPosition_Inverse, L0.Double, pos, Bindings.DOUBLE); graph.addLiteral(routeLine, DIA.IsHorizontal, DIA.IsHorizontal_Inverse, L0.Boolean, isHorizontal, Bindings.BOOLEAN); graph.claim(connection, DIA.HasInteriorRouteNode, DIA.HasInteriorRouteNode_Inverse, routeLine); return routeLine; } /** * @param graph database write-only access * @param type type of the created resource * @return new instance of type * @throws DatabaseException */ private Resource newInstance(WriteOnlyGraph graph, Resource type) throws DatabaseException { Resource connection = graph.newResource(); g.claim(connection, br.L0.InstanceOf, null, type); return connection; } }