X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fadapter%2FRouteGraphUtils.java;h=60f7a09dbb93d50a9ef7896b1e3703cd47a1945a;hp=a957377f81dea0c8deb6ec8e3df104af27f9de47;hb=32a6aa7b656804c95b8a2a2df06900955c6df44b;hpb=2cd17c03bb80e24055147a0901802f8d1b2e5179 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java index a957377f8..60f7a09db 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/RouteGraphUtils.java @@ -1,703 +1,708 @@ -/******************************************************************************* - * 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.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.NameUtils; -import org.simantics.db.exception.DatabaseException; -import org.simantics.diagram.connection.ConnectionVisuals; -import org.simantics.diagram.connection.RouteGraph; -import org.simantics.diagram.connection.RouteGraphConnectionClass; -import org.simantics.diagram.connection.RouteLine; -import org.simantics.diagram.connection.RouteNode; -import org.simantics.diagram.connection.RouteTerminal; -import org.simantics.diagram.connection.rendering.BasicConnectionStyle; -import org.simantics.diagram.connection.rendering.ConnectionStyle; -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.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.Topology.Connection; -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.elementclass.FlagClass.Type; -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 gnu.trove.map.hash.THashMap; -import gnu.trove.set.hash.THashSet; - -public class RouteGraphUtils { - - 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, modelingRules, null); - } - - public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, 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)) { - 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 : graph.getObjects(interiorNode, DIA.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) - System.out.println("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) - System.out.println("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) - System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation)); - } - - Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); - - // Load all node terminal connections as RouteTerminals - for (Statement toConnector : toConnectorStatements) { - Resource connector = toConnector.getObject(); - Resource attachmentRelation = toConnector.getPredicate(); - if (DEBUG) - System.out.println("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 }; - - if (DEBUG) { - System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm)); - System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate())); - } - AffineTransform terminalElementTr = diagramRuntime != null ? - DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement) : - DiagramGraphUtil.getWorldTransform(graph, terminalElement); - - if (DEBUG) - System.out.println("terminalElementTr: " + terminalElementTr); - - double x = terminalElementTr.getTranslateX(); - double y = terminalElementTr.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) - System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation)); - } else if (forcedAttachmentRelation != null) { - attachmentRelation = forcedAttachmentRelation; - if (DEBUG) - System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation)); - } - if (DEBUG) - System.out.println("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, terminalElementTr); - 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(); - - AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal); - //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal); - if (terminalPos != null) { - if (DEBUG) { - System.out.println("terminalPos: " + terminalPos); - //System.out.println("terminalPos2: " + terminalPos2); - } - terminalElementTr.concatenate(terminalPos); - if (DEBUG) - System.out.println("terminalElementTr: " + terminalElementTr); - x = terminalElementTr.getTranslateX(); - y = terminalElementTr.getTranslateY(); - } - - Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER); - if (allowedDirections != null) { - direction |= allowedDirections; - direction = rotateDirection(direction, terminalElementTr); - } else { - direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds); - } - //System.out.println("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) - System.out.println("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); - 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) { - System.err.println("Stray connection link found: " + link.toString(graph)); - continue; - } - rg.link(n1, n2); - } - - return rg; - - } - - private 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; - } - - private 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; - } - - private 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; - } - - private 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 - */ - protected 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); - } - } - - /** - * @param graph - * @param canvas - * @param modelingRules - * @param connection - * @return - * @throws DatabaseException - */ - protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR) throws DatabaseException { - Resource connectionType = null; - if (modelingRules != null) - connectionType = modelingRules.getConnectionType(graph, connection); - if (connectionType == null) - connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); - return readConnectionStyleFromConnectionType(graph, connectionType); - } - - 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 = 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); - - return new BasicConnectionStyle( - lineColor, - branchPointColor, - branchPointRadius, - lineStroke, - routeLineStroke, - degenerateLineLength); - } - - 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.; - - 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 + "]"; - } - } - -} +/******************************************************************************* + * 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.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.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.diagram.connection.ConnectionVisuals; +import org.simantics.diagram.connection.RouteGraph; +import org.simantics.diagram.connection.RouteGraphConnectionClass; +import org.simantics.diagram.connection.RouteLine; +import org.simantics.diagram.connection.RouteNode; +import org.simantics.diagram.connection.RouteTerminal; +import org.simantics.diagram.connection.rendering.BasicConnectionStyle; +import org.simantics.diagram.connection.rendering.ConnectionStyle; +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.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.Topology.Connection; +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.elementclass.FlagClass.Type; +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, modelingRules, null); + } + + public static RouteGraph load(ReadGraph graph, Resource diagramRuntime, Resource connection, ICanvasContext canvas, IDiagram diagram, 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)) { + 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 : graph.getObjects(interiorNode, DIA.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) + System.out.println("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) + System.out.println("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) + System.out.println("set forced attachment: " + NameUtils.getSafeLabel(graph, forcedAttachmentRelation)); + } + + Resource connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); + + // Load all node terminal connections as RouteTerminals + for (Statement toConnector : toConnectorStatements) { + Resource connector = toConnector.getObject(); + Resource attachmentRelation = toConnector.getPredicate(); + if (DEBUG) + System.out.println("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 }; + + if (DEBUG) { + System.out.println("terminalStm: " + NameUtils.toString(graph, terminalStm)); + System.out.println("terminal: " + graph.getURI(terminalStm.getPredicate())); + } + AffineTransform terminalElementTr = diagramRuntime != null ? + DiagramGraphUtil.getDynamicWorldTransform(graph, diagramRuntime, terminalElement) : + DiagramGraphUtil.getWorldTransform(graph, terminalElement); + + if (DEBUG) + System.out.println("terminalElementTr: " + terminalElementTr); + + double x = terminalElementTr.getTranslateX(); + double y = terminalElementTr.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) + System.out.println("modeling rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation)); + } else if (forcedAttachmentRelation != null) { + attachmentRelation = forcedAttachmentRelation; + if (DEBUG) + System.out.println("forced rules attachment: " + NameUtils.getSafeLabel(graph, attachmentRelation)); + } + if (DEBUG) + System.out.println("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, terminalElementTr); + 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(); + + AffineTransform terminalPos = DiagramGraphUtil.getDynamicAffineTransform(graph, terminalElement, terminal); + //AffineTransform terminalPos2 = DiagramGraphUtil.getAffineTransform(graph, terminal); + if (terminalPos != null) { + if (DEBUG) { + System.out.println("terminalPos: " + terminalPos); + //System.out.println("terminalPos2: " + terminalPos2); + } + terminalElementTr.concatenate(terminalPos); + if (DEBUG) + System.out.println("terminalElementTr: " + terminalElementTr); + x = terminalElementTr.getTranslateX(); + y = terminalElementTr.getTranslateY(); + } + + Integer allowedDirections = graph.getPossibleRelatedValue(terminal, DIA.Terminal_AllowedDirections, Bindings.INTEGER); + if (allowedDirections != null) { + direction |= allowedDirections; + direction = rotateDirection(direction, terminalElementTr); + } else { + direction |= RouteGraphConnectionClass.shortestDirectionOutOfBounds(x, y, bounds); + } + //System.out.println("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) + System.out.println("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); + 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); + } + } + + /** + * @param graph + * @param canvas + * @param modelingRules + * @param connection + * @return + * @throws DatabaseException + */ + protected static ConnectionStyle readConnectionStyle(ReadGraph graph, IModelingRules modelingRules, Resource connection, StructuralResource2 STR) throws DatabaseException { + Resource connectionType = null; + if (modelingRules != null) + connectionType = modelingRules.getConnectionType(graph, connection); + if (connectionType == null) + connectionType = graph.getPossibleObject(connection, STR.HasConnectionType); + return readConnectionStyleFromConnectionType(graph, connectionType); + } + + 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 = 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; + + return new BasicConnectionStyle( + lineColor, + branchPointColor, + branchPointRadius, + lineStroke, + routeLineStroke, + degenerateLineLength, + rounding); + } + + 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 + "]"; + } + } + +}