--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2017 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 - #7107 original implementation
+ * Semantum Oy - #7107 adaptation for general use
+ *******************************************************************************/
+package org.simantics.modeling.ui.diagram.style;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.primitiverequest.OrderedSet;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.diagram.adapter.RouteGraphUtils;
+import org.simantics.diagram.connection.RouteGraphConnectionClass;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.elements.TextNode;
+import org.simantics.diagram.profile.StyleBase;
+import org.simantics.diagram.synchronization.graph.BasicResources;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.g2d.utils.Alignment;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
+import org.simantics.scenegraph.profile.EvaluationContext;
+import org.simantics.scenegraph.profile.common.ProfileVariables;
+import org.simantics.scenegraph.utils.NodeUtil;
+import org.simantics.utils.datastructures.map.Tuple;
+
+/**
+ * @author Teemu Mätäsniemi
+ * @author Tuukka Lehtonen
+ * @since 1.28.0
+ */
+public class ConnectionPointNameStyle extends StyleBase<List<ConnectionPointNameStyle.Result>> {
+
+ protected static class Result extends Tuple {
+ public Result(String string, AffineTransform tr, Integer direction) {
+ super(string, tr, direction);
+ }
+ public String getString() {
+ return (String) getField(0);
+ }
+ public AffineTransform getTransform() {
+ return (AffineTransform) getField(1);
+ }
+ public Integer getAllowedDirections() {
+ return (Integer) getField(2);
+ }
+ }
+
+ protected static final String PARENT_NODE_NAME_PREFIX = "_tNames";
+ protected static final String NODE_NAME_PREFIX = "_";
+
+ protected static final Font FONT = Font.decode("Arial 6");
+
+ protected static final double DEFAULT_SCALE = 0.05;
+
+ private Color backgroundColor = Color.WHITE;
+ private Color textColor = Color.BLACK;
+
+ private double textScale;
+
+ public ConnectionPointNameStyle() {
+ this(DEFAULT_SCALE);
+ }
+
+ public ConnectionPointNameStyle(double textScale) {
+ this.textScale = textScale;
+ }
+
+ @Override
+ public List<Result> calculateStyle(
+ ReadGraph graph,
+ Resource runtimeDiagram,
+ Resource entry,
+ Resource element,
+ Variable configuration)
+ throws DatabaseException
+ {
+ BasicResources BR = BasicResources.getInstance(graph);
+ Layer0 L0 = BR.L0;
+ ModelingResources MOD = ModelingResources.getInstance(graph);
+
+ Resource comp = graph.getPossibleObject(element, MOD.ElementToComponent);
+ if (comp == null)
+ return Collections.emptyList();
+ String compName = graph.getPossibleRelatedValue(comp, L0.HasName, Bindings.STRING);
+ if (compName == null)
+ return Collections.emptyList();
+
+ // Only process defined elements since those can contain terminal definitions
+ Resource elementType = graph.getPossibleType(element, BR.DIA.DefinedElement);
+ if (elementType == null)
+ return Collections.emptyList();
+
+ // Need parent information to calculate absolute positions of terminals
+ // and to make the result unique for instances of the same symbol.
+ AffineTransform parentTransform = DiagramGraphUtil.getAffineTransform(graph, element);
+ List<Result> result = new ArrayList<>();
+ result.add(new Result(compName, parentTransform, null));
+
+ Resource orderedSet = graph.getPossibleObject(elementType, BR.STR.IsDefinedBy);
+ if (orderedSet != null) {
+ for (Resource el : graph.syncRequest(new OrderedSet(orderedSet))) {
+ Resource gcp = graph.getPossibleObject(el, BR.DIA.HasConnectionPoint);
+ if (gcp != null) {
+ Resource cpRel = graph.getPossibleObject(gcp, MOD.DiagramConnectionRelationToConnectionRelation);
+ if (cpRel == null)
+ continue;
+ String name = graph.getPossibleRelatedValue(cpRel, L0.HasName, Bindings.STRING);
+ if (name != null) {
+ Integer allowedDirections = graph.getPossibleRelatedValue(el, BR.DIA.Terminal_AllowedDirections, Bindings.INTEGER);
+ result.add(new Result(name, DiagramGraphUtil.getAffineTransform(graph, el), allowedDirections));
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ protected static AffineTransform translateAndScaleIfNeeded(AffineTransform tr, double rotation, double offsetX, double offsetY, double scale) {
+ if (rotation != 0 || offsetX != 0.0 || offsetY != 0.0 || scale != 1.0) {
+ tr = new AffineTransform(tr);
+ if (rotation != 0)
+ tr.rotate(rotation);
+ if (offsetX != 0 || offsetY != 0)
+ tr.translate(offsetX, offsetY);
+ if (scale != 1.0)
+ tr.scale(scale, scale);
+ }
+ return tr;
+ }
+
+ protected AffineTransform getTerminalTransform(AffineTransform transform, double rotation, double offsetX, double offsetY, double scale) {
+ return translateAndScaleIfNeeded(transform, rotation, offsetX, offsetY, scale);
+ }
+
+ @Override
+ public void applyStyleForNode(EvaluationContext observer, INode _node, List<Result> resultList) {
+ // always clean up old items before drawing new items
+ cleanupStyleForNode(_node);
+
+ int count = resultList.size();
+ if (resultList == null || count < 2)
+ return;
+
+ G2DParentNode parentNode = ProfileVariables.claimChild(_node, "", PARENT_NODE_NAME_PREFIX, G2DParentNode.class, observer);
+ parentNode.setTransform(resultList.get(0).getTransform());
+ parentNode.setZIndex(Integer.MAX_VALUE >> 1);
+
+ Rectangle2D eBounds = NodeUtil.getLocalElementBounds(_node);
+
+ for (int i = 1; i < count; ++i) {
+ Result result = resultList.get(i);
+ TextNode node = ProfileVariables.claimChild(parentNode, "", NODE_NAME_PREFIX + i, TextNode.class, observer);
+ node.setZIndex(i);
+ node.setBackgroundColor(backgroundColor);
+ node.setColor(textColor);
+ node.setText(result.getString());
+ node.setVerticalAlignment((byte) Alignment.CENTER.ordinal());
+ node.setAutomaticTextFlipping(TextNode.TextFlipping.VerticalTextDownwards);
+
+ Alignment hAlign = Alignment.LEADING;
+ AffineTransform tr = result.getTransform();
+ double trX = tr.getTranslateX(),
+ trY = tr.getTranslateY();
+ double dx = 0, dy = 0, r = 0;
+ double ts = 0.6;
+
+ Integer dir = result.getAllowedDirections();
+ int directions = dir != null
+ ? RouteGraphUtils.rotateDirection(dir, tr)
+ : RouteGraphConnectionClass.shortestDirectionOutOfBounds(trX, trY, eBounds);
+
+ //System.out.format("%24s: DIR %d (%s)%n", result.getString(), directions, tr.toString());
+
+ if (trX == 0 && trY == 0) {
+ hAlign = Alignment.CENTER;
+ } else {
+ boolean up = (directions & RouteTerminal.DIR_UP) != 0;
+ boolean down = (directions & RouteTerminal.DIR_DOWN) != 0;
+ boolean left = (directions & RouteTerminal.DIR_LEFT) != 0;
+ boolean right = (directions & RouteTerminal.DIR_RIGHT) != 0;
+
+ double ldx = Math.abs(eBounds.getMinX() - trX);
+ double rdx = Math.abs(eBounds.getMaxX() - trX);
+ double tdy = Math.abs(eBounds.getMinY() - trY);
+ double bdy = Math.abs(eBounds.getMaxY() - trY);
+
+ if (left && ldx <= rdx && ldx <= tdy && ldx <= bdy) {
+ dx = -ts;
+ hAlign = Alignment.TRAILING;
+ } else if (right && rdx <= ldx && rdx <= tdy && rdx <= bdy) {
+ dx = ts;
+ hAlign = Alignment.LEADING;
+ } else if (up && tdy <= ldx && tdy <= rdx && tdy <= bdy) {
+ dx = -ts;
+ r = Math.PI/2;
+ hAlign = Alignment.TRAILING;
+ } else if (down && bdy <= ldx && bdy <= rdx && bdy <= tdy) {
+ dx = ts;
+ r = Math.PI/2;
+ hAlign = Alignment.LEADING;
+ }
+ }
+
+ node.setHorizontalAlignment((byte) hAlign.ordinal());
+ node.setTransform(getTerminalTransform(tr, r, dx, dy, textScale));
+ }
+ }
+
+ @Override
+ protected void cleanupStyleForNode(INode node) {
+ if (node instanceof SingleElementNode) {
+ ProfileVariables.denyChild(node, "", PARENT_NODE_NAME_PREFIX);
+ }
+ }
+
+}