/******************************************************************************* * 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> { 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"; //$NON-NLS-1$ protected static final String NODE_NAME_PREFIX = "_"; //$NON-NLS-1$ protected static final Font FONT = Font.decode("Arial 6"); //$NON-NLS-1$ 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 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 = 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 resultList) { // always clean up old items before drawing new items cleanupStyleForNode(_node); int count = resultList != null ? resultList.size() : 0; if (count < 2) return; G2DParentNode parentNode = ProfileVariables.claimChild(_node, "", PARENT_NODE_NAME_PREFIX, G2DParentNode.class, observer); //$NON-NLS-1$ 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); //$NON-NLS-1$ 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); //$NON-NLS-1$ } } }