package org.simantics.modeling.ui.diagram.style; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Paint; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.request.PossibleTypedParent; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.Variable; import org.simantics.diagram.connection.RouteGraph; import org.simantics.diagram.connection.RouteGraphConnectionClass; import org.simantics.diagram.connection.RouteTerminal; import org.simantics.diagram.content.ConnectionUtil; import org.simantics.diagram.handler.Paster; import org.simantics.diagram.handler.Paster.RouteLine; import org.simantics.diagram.profile.ProfileKeys; import org.simantics.diagram.profile.StyleBase; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.DataElementMap; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.SelectionOutline; import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline; import org.simantics.modeling.ModelingResources; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.ParentNode; import org.simantics.scenegraph.g2d.IdentityAffineTransform; import org.simantics.scenegraph.g2d.nodes.ConnectionNode; import org.simantics.scenegraph.g2d.nodes.DecorationShapeNode; import org.simantics.scenegraph.g2d.nodes.ShapeNode; import org.simantics.scenegraph.profile.DataNodeMap; import org.simantics.scenegraph.profile.EvaluationContext; import org.simantics.scenegraph.profile.common.ProfileVariables; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.scl.runtime.tuple.Tuple5; import org.simantics.structural.stubs.StructuralResource2; import gnu.trove.set.TLongSet; import gnu.trove.set.hash.TLongHashSet; /** * @author Tuukka Lehtonen */ public class TypicalInheritanceStyle extends StyleBase { private static final TypicalInheritanceResult NOT_INHERITED = new TypicalInheritanceResult(Boolean.FALSE, null, null, Boolean.FALSE, null); private static final Paint PAINT = new Color(128, 128, 128, 64); private static final Paint PAINT_WITHOUT_SOURCE = new Color(255, 128, 128, 64); private static final Stroke STROKE = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); @Override public TypicalInheritanceResult calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource element, Variable configuration) throws DatabaseException { DiagramResource DIA = DiagramResource.getInstance(graph); ModelingResources MOD = ModelingResources.getInstance(graph); StructuralResource2 STR = StructuralResource2.getInstance(graph); boolean templatized = graph.hasStatement(element, MOD.IsTemplatized); boolean hasElementSource = graph.hasStatement(element, MOD.HasElementSource); if (templatized) { if (graph.isInstanceOf(element, DIA.RouteGraphConnection)) { Collection connectors = graph.getObjects(element, DIA.HasConnector); Collection routeNodes = graph.getObjects(element, DIA.HasInteriorRouteNode); TLongHashSet nonTemplatizedConnectors = null; // This is needed to make this query result change every time the underlying element changes visually. Set identifier = new HashSet(connectors.size() + routeNodes.size()); for (Resource connector : connectors) { for (Resource connectedTo : graph.getObjects(connector, STR.Connects)) { if (!connectedTo.equals(element)) { AffineTransform at = DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, connectedTo, DIA.HasDynamicTransform, false); identifier.add(at); boolean connectedToTemplatized = graph.hasStatement(connectedTo, MOD.IsTemplatized); if (!connectedToTemplatized) { if (nonTemplatizedConnectors == null) nonTemplatizedConnectors = new TLongHashSet(); nonTemplatizedConnectors.add(connector.getResourceId()); } break; } } } if (!routeNodes.isEmpty()) { for (Resource routeLine : routeNodes) { RouteLine rl = Paster.readRouteLine(graph, routeLine); identifier.add(rl); } } return new TypicalInheritanceResult(templatized, nonTemplatizedConnectors, IdentityAffineTransform.INSTANCE, hasElementSource, identifier); } else if (graph.isInstanceOf(element, DIA.Monitor)) { AffineTransform worldTransform = DiagramGraphUtil.getWorldTransform(graph, element); Resource monitoredComponent = graph.getPossibleObject(element, DIA.HasMonitorComponent); if (monitoredComponent != null) { Resource monitoredElement = graph.getPossibleObject(monitoredComponent, MOD.ComponentToElement); if (graph.isInstanceOf(monitoredElement, DIA.Connection)) { Resource tailNode = ConnectionUtil.getConnectionTailNode(graph, monitoredElement); if (tailNode != null) { monitoredElement = tailNode; } } if (monitoredElement != null) { Resource diagram = graph.syncRequest(new PossibleTypedParent(element, DIA.Diagram)); if (diagram != null) { Resource monitoredDiagram = graph.syncRequest(new PossibleTypedParent(monitoredElement, DIA.Diagram)); if (diagram.equals(monitoredDiagram)) { AffineTransform monitoredElementWorldTransform = DiagramGraphUtil.getWorldTransform(graph, monitoredElement); worldTransform.preConcatenate(monitoredElementWorldTransform); } } } } return new TypicalInheritanceResult(templatized, null, worldTransform, hasElementSource, null); } AffineTransform worldTransform = DiagramGraphUtil.getDynamicWorldTransform(graph, runtimeDiagram, element); return new TypicalInheritanceResult(templatized, null, worldTransform, hasElementSource, null); } return NOT_INHERITED; } public void applyStyleForItem(EvaluationContext context, DataNodeMap map, Object item, TypicalInheritanceResult result) { final INode _node = map.getNode(item); if (result != null && Boolean.TRUE.equals(result.isTemplatized())) { boolean fill = true; Stroke stroke = null; ShapeNode node = null; if (_node instanceof ParentNode) { node = ProfileVariables.claimChild(_node, "", "typical", DecorationShapeNode.class, context); } else { // Ignore, cannot create decoration. return; } if (_node instanceof ConnectionNode) { fill = false; stroke = STROKE; } Shape shape = null; IDiagram diagram = context.getConstant(ProfileKeys.DIAGRAM); if (diagram != null) { DataElementMap dem = diagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class); if (dem != null) { IElement element = dem.getElement(diagram, item); if (element != null) { SelectionOutline so = element.getElementClass().getAtMostOneItemOfClass(SelectionOutline.class); if (so != null) { RouteGraph rg = element.getHint(RouteGraphConnectionClass.KEY_ROUTEGRAPH); if (rg != null) { RouteGraph rgc = rg; TLongSet nonTemplatizedConnectors = result.getNonTemplatizedConnectors(); if (nonTemplatizedConnectors != null) { rgc = rg.copy(); // Must copy the RouteTerminal to an array before // invoking rgc.remove(RouteTerminal), otherwise // ConcurrentModificationExceptions will arise. Collection rtc = rgc.getTerminals(); if (nonTemplatizedConnectors.size() > (rtc.size() - 2)) { // Cannot make a RouteGraph any simpler // than a simple connection between two // terminals. // Fall back to highlighting the whole // connection. } else { RouteTerminal[] rts = rtc.toArray(new RouteTerminal[rtc.size()]); for (RouteTerminal rt : rts) { Object data = rt.getData(); if (data instanceof Long) { if (nonTemplatizedConnectors.contains(((Long) data).longValue())) rgc.remove(rt); } } } } Path2D path = rgc.getPath2D(); Stroke connectionStroke = ConnectionSelectionOutline.INSTANCE.resolveStroke(element, ConnectionSelectionOutline.defaultStroke); shape = connectionStroke.createStrokedShape(path); } else { shape = so.getSelectionShape(element); } } else { Rectangle2D rect = ElementUtils.getElementBounds(element); shape = GeometryUtils.expandRectangle( rect, 0.5 ); } } } } AffineTransform at = result.getWorldTransform(); if (at != null) node.setTransform(at); node.setZIndex(-1000); node.setColor(result.hasElementSource() ? PAINT : PAINT_WITHOUT_SOURCE); node.setFill(fill); node.setScaleStroke(false); node.setScaleShape(false); node.setStroke(stroke); node.setShape(shape); } else { cleanupStyleForNode(context, _node); } } @Override protected void cleanupStyleForNode(EvaluationContext context, INode node) { ProfileVariables.denyChild(node, "*", "typical"); ProfileVariables.denyChild(node, "", "typical"); } } class TypicalInheritanceResult extends Tuple5 { public TypicalInheritanceResult(Boolean templatized, TLongSet nonTemplatizedConnectors, AffineTransform worldTransform, Boolean hasElementSource, Set queryIdentifier) { super(templatized, nonTemplatizedConnectors, worldTransform, hasElementSource, queryIdentifier); } public boolean isTemplatized() { return (Boolean) get(0); } public TLongSet getNonTemplatizedConnectors() { return (TLongSet) get(1); } public AffineTransform getWorldTransform() { return (AffineTransform) get(2); } public boolean hasElementSource() { return (Boolean) get(3); } }