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