/******************************************************************************* * Copyright (c) 2012, 2016 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 * Semantum Oy - refactoring *******************************************************************************/ package org.simantics.diagram.adapter; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.Statement; import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.common.request.ResourceRead2; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.common.utils.ListUtils; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.diagram.connection.ConnectionVisuals; import org.simantics.diagram.connection.RouteGraph; import org.simantics.diagram.connection.RouteLine; import org.simantics.diagram.connection.RouteNode; import org.simantics.diagram.connection.RouteTerminal; import org.simantics.diagram.connection.RouteTerminalPosition; import org.simantics.diagram.connection.rendering.AggregateConnectionStyle; import org.simantics.diagram.connection.rendering.BasicConnectionStyle; import org.simantics.diagram.connection.rendering.ConnectionStyle; import org.simantics.diagram.connection.rendering.ExampleConnectionStyle; import org.simantics.diagram.connection.rendering.StyledRouteGraphRenderer; import org.simantics.diagram.connection.rendering.arrows.ArrowLineEndStyle; import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle; import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle; import org.simantics.diagram.content.EdgeResource; import org.simantics.diagram.content.ResourceTerminal; import org.simantics.diagram.content.TerminalMap; import org.simantics.diagram.query.DiagramRequests; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.stubs.G2DResource; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.diagram.synchronization.graph.RouteGraphConnection; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.CanvasContext; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.DataElementMap; import org.simantics.g2d.diagram.handler.Topology.Connection; import org.simantics.g2d.diagram.handler.Topology.Terminal; import org.simantics.g2d.diagram.impl.ElementDiagram; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; import org.simantics.g2d.element.handler.TerminalLayout; import org.simantics.g2d.elementclass.FlagClass.Type; import org.simantics.g2d.elementclass.RouteGraphConnectionClass; import org.simantics.layer0.Layer0; import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.structural.stubs.StructuralResource2; import org.simantics.structural2.modelingRules.CPTerminal; import org.simantics.structural2.modelingRules.IAttachmentRelationMap; import org.simantics.structural2.modelingRules.IModelingRules; import org.simantics.utils.threads.CurrentThread; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gnu.trove.map.hash.THashMap; import gnu.trove.set.hash.THashSet; public class RouteGraphUtils { private static final Logger LOGGER = LoggerFactory.getLogger(RouteGraph.class); public static boolean DEBUG = false; public static final ILineEndStyle HEAD = new ArrowLineEndStyle("fill 2 1 0"); public static final ILineEndStyle TAIL = PlainLineEndStyle.INSTANCE; public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection) throws DatabaseException { ICanvasContext canvas = new CanvasContext(CurrentThread.getThreadAccess()); IDiagram diagram = new ElementDiagram(canvas); return load(graph, diagramRuntime, connection, canvas, diagram); } public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram) throws DatabaseException { Layer0 L0 = Layer0.getInstance(graph); Resource diagramResource = graph.getPossibleObject(connection, L0.PartOf); IModelingRules modelingRules = graph.syncRequest(DiagramRequests.getModelingRules(diagramResource, null), TransientCacheListener.instance()); return load(graph, diagramRuntime, connection, canvas, diagram, null, modelingRules, null); } public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, IElement element, IModelingRules modelingRules, Set backendConnections) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); RouteGraph rg = new RouteGraph(); // Default capacity should be enough for common cases. Set links = new THashSet<>(); Map nodeByData = new THashMap<>(); // Load all route graph interior RouteNodes: route lines and points for (Resource interiorNode : graph.getObjects(connection, DIA.HasInteriorRouteNode)) { if (graph.isInstanceOf(interiorNode, DIA.RouteLine)) { Collection areConnected = graph.getObjects(interiorNode, DIA.AreConnected); if (areConnected.size() < 2) { // Degenerated route line encountered, most likely due to a bug somewhere else. // Ignoring them because adding them to the RouteGraph structure would cause // problems during rendering. LOGGER.warn("Stray RouteLine found: " + NameUtils.getSafeName(graph, interiorNode)); continue; } Boolean isHorizontal = graph.getRelatedValue(interiorNode, DIA.IsHorizontal, Bindings.BOOLEAN); Double position = graph.getRelatedValue(interiorNode, DIA.HasPosition, Bindings.DOUBLE); RouteLine line = rg.addLine(isHorizontal, position); line.setData( RouteGraphConnection.serialize(graph, interiorNode) ); nodeByData.put( interiorNode, line ); for (Resource connectedTo : areConnected) { links.add( new EdgeResource(interiorNode, connectedTo) ); } } else if (graph.isInstanceOf(interiorNode, DIA.RoutePoint)) { // Not supported yet. Ignore. } } Rectangle2D bounds = new Rectangle2D.Double(); Map connectorToModeledAttachment = null; // Primarily the loader will believe what modeling rules say about // connector attachment relations. // // If modeling rules decide nothing, then we simply believe what the // the attachment relations in the graph say. // // Special case 1: connection with two (2) terminals // If the attachment of one of two terminals is decided by modeling // rules, the other attachment shall be the opposite of the decided // attachment (see forcedAttachmentRelation below). // // Special case 2: connected to a flag // If the attached element is a flag and modeling rules say nothing // about it, believe the direction stated by the flag type. Collection toConnectorStatements = graph.getStatements(connection, DIA.HasConnector); int terminalCount = 0; // See if modeling rules judge any of the connection terminal attachments. if (modelingRules != null) { for (Statement toConnector : toConnectorStatements) { Resource connector = toConnector.getObject(); Statement terminalStm = findTerminalStatement(graph, connection, connector, STR); if (terminalStm == null) // Ignore broken connector: attached to the connection but not to any terminal. continue; Resource terminalElement = terminalStm.getObject(); Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate()); if (connectionRelation == null) continue; ++terminalCount; IAttachmentRelationMap map = modelingRules.getAttachmentRelations(graph, connection); Resource attachment = map.get(graph, new CPTerminal(terminalElement, connectionRelation)); if (attachment != null) { // Primary: believe modeling rules if (connectorToModeledAttachment == null) connectorToModeledAttachment = new THashMap(toConnectorStatements.size()); connectorToModeledAttachment.put(connector, attachment); if (DEBUG) LOGGER.debug("modeling rules decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")"); } else if (graph.isInstanceOf(terminalElement, DIA.Flag)) { // Secondary: believe flag type attachment = resolveFlagAttachment(graph, connection, terminalElement, modelingRules, DIA); if (attachment != null) { if (connectorToModeledAttachment == null) connectorToModeledAttachment = new THashMap(toConnectorStatements.size()); connectorToModeledAttachment.put(connector, attachment); if (DEBUG) LOGGER.debug("flag type decided attachment: " + NameUtils.getSafeName(graph, attachment, true) + " for (" + NameUtils.toString(graph, toConnector, true) + ") & (" + NameUtils.toString(graph, terminalStm, true) + ")"); } } } } if (connectorToModeledAttachment == null) connectorToModeledAttachment = Collections.emptyMap(); Resource forcedAttachmentRelation = null; if (terminalCount == 2 && connectorToModeledAttachment.size() == 1) { forcedAttachmentRelation = getInverseAttachment(graph, connectorToModeledAttachment.values().iterator().next(), DIA); if (DEBUG) LOGGER.debug("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation)); } Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); DataElementMap diagramDataElementMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class); // Load all node terminal connections as RouteTerminals for (Statement toConnector : toConnectorStatements) { Resource connector = toConnector.getObject(); Resource attachmentRelation = toConnector.getPredicate(); if (DEBUG) LOGGER.debug("original attachment relation: " + NameUtils.getSafeLabel(graph, attachmentRelation)); Statement terminalStm = findTerminalStatement(graph, connection, connector, STR); if (terminalStm == null) // Ignore broken connector: attached to the connection but not to any terminal. continue; Resource terminalElement = terminalStm.getObject(); Resource terminalElementType = graph.getPossibleType(terminalElement, DIA.Element); if (terminalElementType == null) // Ignore non-element terminal elements continue; Resource connectionRelation = graph.getPossibleInverse(terminalStm.getPredicate()); if (connectionRelation == null) continue; // Discover node and terminal this connector is connected to. TerminalMap terminals = graph.syncRequest(DiagramRequests.elementTypeTerminals(terminalElementType), TransientCacheListener.instance()); Resource terminal = terminals.getTerminal(connectionRelation); if (terminal == null) { System.err.println( "RouteGraphUtils: Could not find terminal for connection point " + NameUtils.getSafeName(graph, connectionRelation, true) + " in element " + NameUtils.getSafeName(graph, terminalElement, true)); continue; } double[] position = graph.getRelatedValue(connector, DIA.HasRelativeLocation, Bindings.DOUBLE_ARRAY); if (position.length != 2) position = new double[] { 0, 0 }; AffineTransform terminalTr = DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement); final AffineTransform terminalElementTransform = new AffineTransform(terminalTr); if (DEBUG) { LOGGER.debug("terminalStm: " + NameUtils.toString(graph, terminalStm)); LOGGER.debug("terminal: " + NameUtils.getURIOrSafeNameInternal(graph, terminalStm.getPredicate())); LOGGER.debug("terminalElement: " + NameUtils.getURIOrSafeNameInternal(graph, terminalElement) + " : " + NameUtils.getURIOrSafeNameInternal(graph, terminalElementType)); LOGGER.debug("terminalElementTr: " + terminalTr); } double x = terminalTr.getTranslateX(); double y = terminalTr.getTranslateY(); double minx = x-1, miny = y-1, maxx = x+1, maxy = y+1; int direction = 0x0; // Use modelingRules to ascertain the proper attachmentRelation // for this terminal connection, if available. Resource att = connectorToModeledAttachment.get(connector); if (att != null) { attachmentRelation = att; if (DEBUG) LOGGER.debug("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation)); } else if (forcedAttachmentRelation != null) { attachmentRelation = forcedAttachmentRelation; if (DEBUG) LOGGER.debug("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation)); } if (DEBUG) LOGGER.debug("decided attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation)); // Get element bounds to decide allowed terminal direction(s) IElement te = graph.syncRequest(DiagramRequests.getElement(canvas, diagram, terminalElement, null)); ElementUtils.getElementBounds(te, bounds); { Shape shp = org.simantics.g2d.utils.GeometryUtils.transformShape(bounds, terminalTr); bounds.setFrame(shp.getBounds2D()); } // Expand bounds by 2mm to make the connections enter the terminals // at a straight angle and from a distance instead of coming in // "horizontally". GeometryUtils.expandRectangle(bounds, 2); minx = bounds.getMinX(); miny = bounds.getMinY(); maxx = bounds.getMaxX(); maxy = bounds.getMaxY(); final ResourceTerminal rt = new ResourceTerminal(terminal); final TerminalLayout tl = te.getElementClass().getSingleItem(TerminalLayout.class); AffineTransform terminalPos = tl.getTerminalPosition(te, rt); if (terminalPos != null) { terminalTr.concatenate(terminalPos); x = terminalTr.getTranslateX(); y = terminalTr.getTranslateY(); if (DEBUG) LOGGER.debug("terminalPos/Tr: " + terminalPos + ", " + terminalTr); } Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER); if (allowedDirections != null) { direction |= allowedDirections; direction = rotateDirection(direction, terminalTr); } else { direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds); } //LOGGER.debug("DIR(" + x + ", " + y + ", " + bounds + "): " + Integer.toHexString(direction)); if (backendConnections != null) { backendConnections.add( new BackendConnection( toEdgeEnd(graph, attachmentRelation, EdgeEnd.Begin, DIA), terminalElement, terminal) ); } if (direction == 0) // Accept any horizontal/vertical direction if nothing is defined direction = 0xf; if (graph.getRelatedValue(connector, DIA.Connector_straight, Bindings.BOOLEAN)) direction |= RouteTerminal.DIR_DIRECT; // FIXME: routegraph crashes if this is done for all terminals regardless of the amount of terminals. if (DEBUG) LOGGER.debug("load line style: " + NameUtils.getSafeLabel(graph, attachmentRelation)); ILineEndStyle endStyle = loadLineEndStyle(graph, attachmentRelation, connectionType, TAIL); RouteTerminal routeTerminal = rg.addTerminal(x, y, minx, miny, maxx, maxy, direction, endStyle, new RouteTerminalPositionImpl(diagram, diagramDataElementMap, terminalElement, terminalElementTransform, tl, rt)); routeTerminal.setData( RouteGraphConnection.serialize(graph, connector) ); nodeByData.put( connector, routeTerminal ); for (Resource connectedTo : graph.getObjects(connector, DIA.AreConnected)) { links.add( new EdgeResource(connectedTo, connector) ); } } // Finish route graph loading by Linking route nodes together for (EdgeResource link : links) { RouteNode n1 = nodeByData.get(link.first()); RouteNode n2 = nodeByData.get(link.second()); if (n1 == null || n2 == null) { LOGGER.warn("Stray connection link found: " + link.toString(graph)); continue; } rg.link(n1, n2); } return rg; } public static EdgeEnd toEdgeEnd(ReadGraph graph, Resource attachmentRelation, EdgeEnd defaultValue, DiagramResource DIA) throws DatabaseException { if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector)) return EdgeEnd.Begin; if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) return EdgeEnd.End; return defaultValue; } public static Resource resolveFlagAttachment(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException { Type type = resolveFlagType(graph, connection, flag, modelingRules, DIA); if (type != null) { switch (type) { case In: return DIA.HasPlainConnector; case Out: return DIA.HasArrowConnector; } } return null; } private static Type resolveFlagType(ReadGraph graph, Resource connection, Resource flag, IModelingRules modelingRules, DiagramResource DIA) throws DatabaseException { return readFlagType(graph, flag, DIA); } private static Type readFlagType(ReadGraph graph, Resource flag, DiagramResource DIA) throws DatabaseException { Resource flagType = graph.getPossibleObject(flag, DIA.HasFlagType); Type type = DiagramGraphUtil.toFlagType(DIA, flagType); return type; } public static Statement findTerminalStatement(ReadGraph graph, Resource connection, Resource connector, StructuralResource2 STR) throws DatabaseException { for (Statement stm : graph.getStatements(connector, STR.Connects)) { if (connection.equals(stm.getObject())) continue; return stm; } return null; } public static Resource getInverseAttachment(ReadGraph graph, Resource attachmentRelation, DiagramResource DIA) throws DatabaseException { Resource inverse = graph.getPossibleObject(attachmentRelation, DIA.HasInverseAttachment); if (inverse != null) return inverse; if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) return DIA.HasPlainConnector; if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector)) return DIA.HasArrowConnector; return null; } public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, ILineEndStyle defaultValue) throws DatabaseException { ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation), TransientCacheListener.instance()); return style != null ? style : defaultValue; } public static ILineEndStyle loadLineEndStyle(ReadGraph graph, Resource attachmentRelation, Resource connectionType, ILineEndStyle defaultValue) throws DatabaseException { if(connectionType != null) { ILineEndStyle style = graph.syncRequest(new LineEndStyleWithType(attachmentRelation, connectionType), TransientCacheListener.instance()); return style != null ? style : defaultValue; } else { ILineEndStyle style = graph.syncRequest(new LineEndStyle(attachmentRelation), TransientCacheListener.instance()); return style != null ? style : defaultValue; } } /** * A request for caching ILineEndStyle results. */ public static class LineEndStyle extends UnaryRead { public LineEndStyle(Resource attachmentRelation) { super(attachmentRelation); } @Override public ILineEndStyle perform(ReadGraph graph) throws DatabaseException { return loadLineEndStyle0(graph, parameter); } } public static class LineEndStyleWithType extends ResourceRead2 { public LineEndStyleWithType(Resource attachmentRelation, Resource connectionType) { super(attachmentRelation, connectionType); } @Override public ILineEndStyle perform(ReadGraph graph) throws DatabaseException { return loadLineEndStyle0(graph, resource, resource2); } } private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation) throws DatabaseException { ILineEndStyle style = graph.getPossibleAdapter(attachmentRelation, ILineEndStyle.class); if (style != null) return style; DiagramResource DIA = DiagramResource.getInstance(graph); if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) return HEAD; if (graph.isSubrelationOf(attachmentRelation, DIA.HasTailConnector)) return TAIL; return null; } private static ILineEndStyle loadLineEndStyle0(ReadGraph graph, Resource attachmentRelation, Resource connectionType) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); if (graph.isSubrelationOf(attachmentRelation, DIA.HasHeadConnector)) { if(connectionType != null) { G2DResource G2D = G2DResource.getInstance(graph); Resource end = graph.getPossibleObject(connectionType, G2D.HasEndArrow); if(end != null) { Double size = graph.getPossibleRelatedValue(end, G2D.HasSize, Bindings.DOUBLE); if(size == null) size = 0.0; Double widthRatio = graph.getPossibleRelatedValue(end, G2D.HasWidthRatio, Bindings.DOUBLE); if(widthRatio == null) widthRatio = 1.0; Double space = graph.getPossibleRelatedValue(end, G2D.HasSpace, Bindings.DOUBLE); if(space == null) space = 0.0; Resource c = graph.getPossibleObject(end, G2D.HasColor); Color color = null; if (c != null) { float[] col = graph.getPossibleValue(c, Bindings.FLOAT_ARRAY); if (col != null && col.length >= 3) { color = new Color(col[0], col[1], col[2]); } } return new ArrowLineEndStyle(size, widthRatio*size, space, color); } } } return loadLineEndStyle0(graph, attachmentRelation); } /** * @param graph * @param canvas * @param style * @return * @throws DatabaseException */ public static StyledRouteGraphRenderer getRenderer(ReadGraph graph, ConnectionStyle style) throws DatabaseException { return graph.syncRequest(new Renderer(style), TransientCacheListener.instance()); } /** * A request for caching StyledRouteGraphRenderer results. */ public static class Renderer extends UnaryRead { public Renderer(ConnectionStyle style) { super(style); } @Override public StyledRouteGraphRenderer perform(ReadGraph graph) throws DatabaseException { return new StyledRouteGraphRenderer(parameter); } } private static final ConnectionStyle DEFAULT_CONNECTION_STYLE = new BasicConnectionStyle(Color.BLACK, Color.BLACK, 3, ExampleConnectionStyle.SOLID, ExampleConnectionStyle.SOLID, 8); /** * @param graph * @param canvas * @param modelingRules * @param connection * @return * @throws DatabaseException */ protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR, DiagramResource DIA) throws DatabaseException { Resource connectionStyle = graph.getPossibleObject(connection, DIA.HasConnectionStyle); Resource connectionType = null; if (connectionStyle == null) { if (modelingRules != null) connectionType = modelingRules.getConnectionType(graph, connection); if (connectionType == null) connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); connectionStyle = graph.getPossibleObject(connectionType, DIA.HasConnectionStyle); } if (connectionStyle != null) { List lineStyles = ListUtils.toList(graph, connectionStyle); if (lineStyles.size() != 1) { AggregateConnectionStyle aggregate = new AggregateConnectionStyle(); for (Resource connectionLine : ListUtils.toList(graph, connectionStyle)) { aggregate.addStyle(readConnectionStyleFromConnectionType(graph, connectionLine)); } return aggregate; } else { return readConnectionStyleFromConnectionType(graph, lineStyles.get(0)); } } else { return connectionType != null ? readConnectionStyleFromConnectionType(graph, connectionType) : DEFAULT_CONNECTION_STYLE; } } protected static ConnectionStyle readConnectionStyleFromConnectionType(ReadGraph graph, Resource connectionType) throws DatabaseException { return graph.syncRequest(new ReadConnectionStyleFromConnectionType(connectionType), TransientCacheListener.instance()); } /** * A request for caching ConnectionStyle results. */ public static class ReadConnectionStyleFromConnectionType extends UnaryRead { public ReadConnectionStyleFromConnectionType(Resource connectionType) { super(connectionType); } @Override public ConnectionStyle perform(ReadGraph graph) throws DatabaseException { return readConnectionStyleFromConnectionType0(graph, parameter); } } protected static ConnectionStyle readConnectionStyleFromConnectionType0(ReadGraph graph, Resource connectionType) throws DatabaseException { ConnectionVisuals cv = null; if (connectionType != null) cv = graph.syncRequest(DiagramRequests.getConnectionVisuals(connectionType), TransientCacheListener.instance()); // Fixed style settings Color branchPointColor = Color.BLACK; double branchPointRadius = cv != null && cv.branchPointRadius != null ? cv.branchPointRadius : 0.5; double degenerateLineLength = 0.8; Color lineColor = cv != null ? cv.toColor() : null; if (lineColor == null) lineColor = Color.DARK_GRAY; Stroke lineStroke = cv != null ? cv.stroke : null; if (lineStroke == null) lineStroke = new BasicStroke(0.1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, null, 0); Stroke routeLineStroke = GeometryUtils.scaleStrokeWidth(lineStroke, 2); double rounding = cv.rounding == null ? 0.0 : cv.rounding; double offset = cv.offset == null ? 0.0 : cv.offset; return new BasicConnectionStyle( lineColor, branchPointColor, branchPointRadius, lineStroke, routeLineStroke, degenerateLineLength, rounding, offset); } public static void scheduleSynchronize(Session session, Resource connection, RouteGraphChangeEvent event) { session.asyncRequest(RouteGraphConnection.synchronizer(connection, event)); } // ------------------------------------------------------------------------ // RouteGraph RouteTerminal allowed direction rotation support // ------------------------------------------------------------------------ public static int rotateDirection(int direction, AffineTransform at) { // When direct routing is enabled, no point in wasting time rotating. if ((direction & RouteTerminal.DIR_DIRECT) != 0) return direction; final int mask = (AffineTransform.TYPE_MASK_ROTATION | AffineTransform.TYPE_FLIP); boolean rotatedOrFlipped = (at.getType() & mask) != 0; if (rotatedOrFlipped) { double xAxisAngle = Math.atan2( at.getShearY(), at.getScaleX() ); double yAxisAngle = Math.atan2( at.getScaleY(), at.getShearX() ); int xQuadrant = mainQuadrant(xAxisAngle); int yQuadrant = mainQuadrant(yAxisAngle); int xDirMask = direction & (RouteTerminal.DIR_LEFT | RouteTerminal.DIR_RIGHT); int yDirMask = direction & (RouteTerminal.DIR_DOWN | RouteTerminal.DIR_UP); int xDirMaskRotated = rotl4(xDirMask, xQuadrant); int yDirMaskRotated = rotl4(yDirMask, yQuadrant-1); direction = xDirMaskRotated | yDirMaskRotated; } return direction; } /** * 4-bit rotate left without carry operation. * Operates on the 4 least sensitive bits of the integer * and leaves the higher bits untouched. * @param x the bits to rotate * @param n the amount of rotation [0..3] * @return */ private static int rotl4(int x, int n) { n &= 3; if (n == 0) return x; int hx = x & 0xfffffff0; int lx = x & 0xf; int xh = (lx << n) & 0xf; int xl = (lx >>> (4-n)); return xh | xl | hx; } /** *
     *  33
     * 2\/0
     * 2/\0
     *  11
     * 
* * @param theta angle in radians * @return the quadrant based on the ASCII art above */ private static int mainQuadrant(double theta) { if (theta > -DEG_45 && theta <= DEG_45) { return 0; } else if ((theta > DEG_45 && theta <= DEG_135)) { return 1; } else if (theta >= -DEG_135 && theta < -DEG_45) { return 3; } return 2; } private static final double DEG_45 = Math.PI/4.; private static final double DEG_135 = Math.PI*3./4.; public static class BackendConnection { public final Resource node; public final Resource terminal; public final EdgeEnd end; public final int hash; public BackendConnection(EdgeEnd end, Resource node, Resource terminal) { assert end != null; assert node != null; assert terminal != null; this.end = end; this.node = node; this.terminal = terminal; this.hash = makeHash(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Connection)) return false; Connection other = (Connection) obj; return other.terminal == terminal && other.node == node && other.end == end; } private int makeHash() { final int prime = 31; int result = 1; result = prime * result + end.hashCode(); result = prime * result + ((node == null) ? 0 : node.hashCode()); result = prime * result + ((terminal == null) ? 0 : terminal.hashCode()); return result; } @Override public int hashCode() { return hash; } @Override public String toString() { return "BackendConnection[node=" + node + ", terminal=" + terminal + ", end=" + end + "]"; } } private static class RouteTerminalPositionImpl implements RouteTerminalPosition { private IDiagram diagram; private DataElementMap dataElementMap; private Resource element; private AffineTransform elementTransform; private TerminalLayout terminalLayout; private Terminal elementTerminal; private transient AffineTransform lastTerminalTr; private transient AffineTransform transform; public RouteTerminalPositionImpl(IDiagram diagram, DataElementMap dem, Resource element, AffineTransform elementTransform, TerminalLayout terminalLayout, Terminal terminal) { this.diagram = diagram; this.dataElementMap = dem; this.element = element; this.elementTransform = elementTransform; this.terminalLayout = terminalLayout; this.elementTerminal = terminal; } @Override public AffineTransform getTransform() { IElement actualElement = dataElementMap.getElement(diagram, element); AffineTransform terminalTr = actualElement != null ? terminalLayout.getTerminalPosition(actualElement, elementTerminal) : null; if (terminalTr == null) return elementTransform; // Return cached transform if terminal transform has not changed. AffineTransform result = this.transform; AffineTransform lastTerminalTr = this.lastTerminalTr; if (lastTerminalTr != null) { if (terminalTr.equals(lastTerminalTr)) return result; lastTerminalTr.setTransform(terminalTr); } else { lastTerminalTr = this.lastTerminalTr = new AffineTransform(terminalTr); result = this.transform = new AffineTransform(); } result.setTransform(elementTransform); result.concatenate(terminalTr); return result; } } }