/******************************************************************************* * Copyright (c) 2011 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 *******************************************************************************/ package org.simantics.diagram.profile; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Statement; import org.simantics.db.common.utils.OrderedSetUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.Variable; import org.simantics.diagram.content.ConnectionUtil; import org.simantics.diagram.flag.FlagUtil; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.g2d.connection.handler.ConnectionHandler; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.DataElementMap; import org.simantics.g2d.diagram.handler.PickRequest.PickFilter; import org.simantics.g2d.diagram.handler.Relationship; import org.simantics.g2d.diagram.handler.Topology.Connection; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.BendsHandler; import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; import org.simantics.g2d.utils.GeometryUtils; import org.simantics.modeling.ModelingResources; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.nodes.RelationshipNode2; import org.simantics.scenegraph.profile.DataNodeMap; import org.simantics.scenegraph.profile.EvaluationContext; import org.simantics.scenegraph.profile.common.ProfileVariables; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.datastructures.map.Tuple; /** * @author Tuukka Lehtonen */ public class ShowRelatedElements extends StyleBase { protected Collection terminalConnections = new ArrayList(4); protected final BasicStroke highlightStroke = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE, BasicStroke.CAP_SQUARE, 10.0f, new float[] { 4.0f, 6.0f }, 0); protected Line2D line = new Line2D.Double(); /** * One possible result of * {@link ShowRelatedElements#calculateStyle(ReadGraph, Resource, Resource, Variable)} */ public static class Relation extends Tuple { public Relation(String relation, Resource element, AffineTransform elementTransform, Resource otherElement, AffineTransform otherElementTransform) { super(relation, element, elementTransform, otherElement, otherElementTransform); } public String getRelation() { return (String) getField(0); } public Resource getElement() { return (Resource) getField(1); } public AffineTransform getTransform() { return (AffineTransform) getField(2); } public Resource getOtherElement() { return (Resource) getField(3); } public AffineTransform getOtherTransform() { return (AffineTransform) getField(4); } } /** * Calculates a result that is contains the styled element, its parent and * transforms of both. This takes care of the style updating the visuals * properly every time the related components move. */ @Override public Object calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource element, Variable configuration) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); if (graph.isInstanceOf(element, DIA.Monitor)) { return calculateFromComponent(graph, runtimeDiagram, element, DIA.HasMonitorComponent); } if (graph.isInstanceOf(element, DIA.Flag)) { return calculateFromFlag(graph, element); } // Just return anything, no relationship to visualize. return Boolean.TRUE; } protected Object calculateFromFlag(ReadGraph graph, Resource flag) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); Resource relatedFlag = FlagUtil.getPossibleCounterpart(graph, flag); if (relatedFlag != null) { Collection flagDiagrams = OrderedSetUtils.getOwnerLists(graph, flag, DIA.Diagram); Collection relatedDiagrams = OrderedSetUtils.getOwnerLists(graph, relatedFlag, DIA.Diagram); if (!Collections.disjoint(flagDiagrams, relatedDiagrams)) { AffineTransform flagAt = DiagramGraphUtil.getTransform(graph, flag); AffineTransform relatedFlagAt = DiagramGraphUtil.getTransform(graph, relatedFlag); return new Relation(Relationship.RELATED_TO.toString(), flag, flagAt, relatedFlag, relatedFlagAt); } } return Boolean.TRUE; } protected Object calculateFromComponent(ReadGraph graph, Resource runtime, Resource element, Resource elementToComponentRelation) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); Resource parentComponent = graph.getPossibleObject(element, elementToComponentRelation); if (parentComponent != null) { Resource parentElement = graph.getPossibleObject(parentComponent, MOD.ComponentToElement); if (parentElement != null) { AffineTransform at = DiagramGraphUtil.getTransform(graph, element); if (graph.isInstanceOf(parentElement, DIA.Connection)) return new Relation(Relationship.CHILD_OF.toString(), element, at, parentElement, getConnectionOutputNodePosition(graph, runtime, parentElement)); AffineTransform parentAt = DiagramGraphUtil.getTransform(graph, parentElement); return new Relation(Relationship.CHILD_OF.toString(), element, at, parentElement, parentAt); } } return Boolean.TRUE; } protected AffineTransform getConnectionOutputNodePosition(ReadGraph graph, Resource runtime, Resource connection) throws DatabaseException { Statement toTail = ConnectionUtil.getConnectionTailNodeStatement(graph, connection); if (toTail == null) return null; AffineTransform at = DiagramGraphUtil.getDynamicAffineTransform(graph, runtime, toTail.getObject()); Resource connectionPoint = graph.getPossibleInverse(toTail.getPredicate()); if (connectionPoint == null) return at; DiagramResource DIA = DiagramResource.getInstance(graph); for (Resource terminal : graph.getObjects(connectionPoint, DIA.HasConnectionPoint_Inverse)) { AffineTransform terminalAt = DiagramGraphUtil.getDynamicAffineTransform(graph, runtime, terminal); at.concatenate(terminalAt); } return at; } @Override public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, Object value) { INode node = map.getNode(item); if (node == null) { evaluationContext.update(this, item); return; } if (!(value instanceof Relation)) { cleanupStyleForNode(node); return; } IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM); Relation r = (Relation) value; DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class); IElement element = emap.getElement(diagram, item); IElement otherElement = emap.getElement(diagram, r.getOtherElement()); if (otherElement != null) addCorrespondence(r, (G2DParentNode) node, r.getRelation(), element, otherElement, highlightStroke, Color.GRAY); } protected void addCorrespondence(Relation r, G2DParentNode p, String id, IElement from, IElement to, Stroke lineStroke, Color lineColor) { Pair p1 = toPoint(r.getTransform(), from); Pair p2 = toPoint(r.getOtherTransform(), to); if (p1 != null && p2 != null) addCorrespondence(p, id, p1, p2, lineStroke, lineColor); } /** * @param parentTransform * @param e * @return origin and bounding box of the specified element in canvas * coordinate system */ protected Pair toPoint(AffineTransform parentTransform, IElement e) { if (PickFilter.FILTER_CONNECTIONS.accept(e)) { // For connections select the first found output terminal position as // the correspondence visualization point. ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class); terminalConnections.clear(); ch.getTerminalConnections(e, terminalConnections); for (Connection c : terminalConnections) { if (c.end != EdgeEnd.Begin) continue; BendsHandler bh = c.edge.getElementClass().getAtMostOneItemOfClass(BendsHandler.class); if (bh != null) { List points = new ArrayList(); GeometryUtils.getPoints(bh.getPath(c.edge), points); if (!points.isEmpty()) return Pair.make(points.get(0), null); } else { AffineTransform at = parentTransform; Point2D point = new Point2D.Double(at.getTranslateX(), at.getTranslateY()); return Pair.make(point, null); } } // Fall back to the default logic if connection output terminal // search fails. } // Using element origin prevents the relationship rubber band target // from changing every time the content of the node changes, e.g. the // text of a monitor. AffineTransform at = ElementUtils.getTransform(e); Point2D p = new Point2D.Double(at.getTranslateX(), at.getTranslateY()); Rectangle2D r = ElementUtils.getElementBoundsOnDiagram(e).getBounds2D(); return Pair.make(p, r); } protected void addCorrespondence(G2DParentNode p, String id, Pair p1, Pair p2, Stroke lineStroke, Color lineColor) { RelationshipNode2 node = p.getOrCreateNode(id, RelationshipNode2.class); node.setZIndex(-10); if (p1.second != null) { line.setLine(p1.first, p2.first); if (LineUtilities.clipLine(line, p1.second)) p1.first.setLocation(line.getX2(), line.getY2()); } if (p2.second != null) { line.setLine(p1.first, p2.first); if (LineUtilities.clipLine(line, p2.second)) p2.first.setLocation(line.getX1(), line.getY1()); } node.init(lineStroke, lineColor, p1.first, p2.first); } @Override protected void cleanupStyleForNode(INode node) { ProfileVariables.denyChild(node, "", Relationship.CHILD_OF.toString()); //ProfileVariables.denyChild(node, "", Relationship.PARENT_OF.toString()); ProfileVariables.denyChild(node, "", Relationship.RELATED_TO.toString()); } @Override public String toString() { return "show related elements"; } }