]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/IssueDecorationStyle.java
Allow Issues and Profiles to be used on non standard diagram viewers
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / diagram / style / IssueDecorationStyle.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.modeling.ui.diagram.style;
13
14 import java.awt.geom.AffineTransform;
15 import java.awt.geom.Point2D;
16 import java.awt.geom.Rectangle2D;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24
25 import org.simantics.db.ReadGraph;
26 import org.simantics.db.Resource;
27 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
28 import org.simantics.db.exception.DatabaseException;
29 import org.simantics.db.layer0.variable.Variable;
30 import org.simantics.db.layer0.variable.Variables;
31 import org.simantics.diagram.connection.RouteGraph;
32 import org.simantics.diagram.elements.DecorationSVGNode;
33 import org.simantics.diagram.elements.SVGNode;
34 import org.simantics.diagram.handler.Paster;
35 import org.simantics.diagram.handler.Paster.RouteLine;
36 import org.simantics.diagram.profile.StyleBase;
37 import org.simantics.diagram.stubs.DiagramResource;
38 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
39 import org.simantics.issues.Severity;
40 import org.simantics.issues.common.IssueResourcesContexts;
41 import org.simantics.issues.common.ListModelIssuesBySeverity;
42 import org.simantics.modeling.ModelingResources;
43 import org.simantics.modeling.ui.Activator;
44 import org.simantics.modeling.ui.diagram.style.IssueDecorationStyle.IssueResult;
45 import org.simantics.scenegraph.INode;
46 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
47 import org.simantics.scenegraph.g2d.nodes.Decoration;
48 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
49 import org.simantics.scenegraph.profile.EvaluationContext;
50 import org.simantics.scenegraph.profile.common.ProfileVariables;
51 import org.simantics.scenegraph.utils.NodeUtil;
52 import org.simantics.structural.stubs.StructuralResource2;
53 import org.simantics.utils.datastructures.map.Tuple;
54
55
56 /**
57  * @author Tuukka Lehtonen
58  */
59 public class IssueDecorationStyle extends StyleBase<IssueResult> {
60
61     private static final String DECORATION_NODE_NAME = "issueDecorations"; //$NON-NLS-1$
62
63     protected List<Resource> getContexts(ReadGraph graph, Resource element) throws DatabaseException {
64
65         ModelingResources MOD = ModelingResources.getInstance(graph);
66         List<Resource> result = new ArrayList<Resource>(3);
67         result.add(element);
68         Resource config = graph.getPossibleObject(element, MOD.ElementToComponent);
69         if (config != null && result.indexOf(config) == -1) result.add(config);
70         config = graph.getPossibleObject(element, MOD.DiagramConnectionToConnection);
71         if (config != null && result.indexOf(config) == -1) result.add(config);
72         // For diagram reference element support
73         config = graph.getPossibleObject(element, MOD.HasParentComponent);
74         if (config != null && result.indexOf(config) == -1) result.add(config);
75         return result;
76
77     }
78
79     @Override
80     public IssueResult calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource element, Variable configuration) throws DatabaseException {
81
82         Resource model = Variables.getModel(graph, configuration);
83         if (model == null)
84             return null;
85
86         List<Resource> contexts = getContexts(graph, element);
87         Map<Severity, List<Resource>> issuesBySeverity = graph.syncRequest(new ListModelIssuesBySeverity(model, true, true, Severity.NOTE),
88                 TransientCacheListener.<Map<Severity, List<Resource>>>instance());
89
90         for (Severity severity : Severity.values()) {
91             List<Resource> issues = issuesBySeverity.get(severity);
92             if (issues != null) {
93                 Set<Resource> issueContexts = graph.syncRequest(new IssueResourcesContexts(issues));
94                 if (!Collections.disjoint(issueContexts, contexts)) {
95                     return new IssueResult(severity, getIdentifier(graph, runtimeDiagram, element));
96                 }
97             }
98         }
99
100         return null;
101     }
102
103     private static Object getIdentifier(ReadGraph graph, Resource runtimeDiagram, Resource element) throws DatabaseException {
104         DiagramResource DIA = DiagramResource.getInstance(graph);
105         StructuralResource2 STR = StructuralResource2.getInstance(graph);
106         if (graph.isInstanceOf(element, DIA.RouteGraphConnection)) {
107             Collection<Resource> connectors = graph.getObjects(element, DIA.HasConnector);
108             Collection<Resource> routeNodes = graph.getObjects(element, DIA.HasInteriorRouteNode);
109
110             // This is needed to make this query result change every time the underlying element changes visually.
111             Set<Object> identifier = new HashSet<Object>(connectors.size() + routeNodes.size());
112
113             for (Resource connector : connectors) {
114                 for (Resource connectedTo : graph.getObjects(connector, STR.Connects)) {
115                     if (!connectedTo.equals(element)) {
116                         AffineTransform at = DiagramGraphUtil.getDynamicAffineTransform(graph, runtimeDiagram, connectedTo, DIA.HasDynamicTransform, false);
117                         identifier.add(at);
118                     }
119                 }
120             }
121             for (Resource routeLine : routeNodes) {
122                 RouteLine rl = Paster.readRouteLine(graph, routeLine);
123                 identifier.add(rl);
124             }
125             return identifier;
126         } else {
127             return DiagramGraphUtil.getAffineTransform(graph, element);
128         }
129     }
130
131     @Override
132     public void applyStyleForNode(EvaluationContext observer, INode node, IssueResult result) {
133         if (result == null) {
134             ProfileVariables.denyChild(node, "", DECORATION_NODE_NAME); //$NON-NLS-1$
135             return;
136         }
137
138         SVGNode svgNode = ProfileVariables.claimChild(node, "", DECORATION_NODE_NAME, DecorationSVGNode.class, observer); //$NON-NLS-1$
139
140         svgNode.setZIndex( Integer.MAX_VALUE );
141         svgNode.setTransform(getDecorationPosition(node)); 
142
143         String svgData = svgDataForSeverity(result.getSeverity());
144         if (svgData != null)
145             svgNode.setData(svgData);
146     }
147
148     /**
149      * Returns position of the decoration.
150      * By default decoration is placed to the top left corner.  Override this method to change the position.
151      *  
152      * @param node
153      * @return
154      */
155     protected AffineTransform getDecorationPosition(INode node) {
156         Rectangle2D bounds = NodeUtil.getLocalBounds(node, Decoration.class);
157
158         if (node instanceof ConnectionNode) {
159             for (INode child : ((ConnectionNode)node).getSortedNodes()) {
160                 if (child instanceof RouteGraphNode) {
161                     RouteGraphNode rgn = (RouteGraphNode) child;
162                     RouteGraph rg = rgn.getRouteGraph();
163                     Point2D nearest = rg.findNearestPoint(bounds.getCenterX(), bounds.getCenterY());
164                     if (nearest != null) {
165                         return AffineTransform.getTranslateInstance(nearest.getX(), nearest.getY());
166                     }
167                 }
168             }
169         }
170         double tx = bounds.getX();
171         double ty = bounds.getY();
172         return AffineTransform.getTranslateInstance(tx, ty);
173     }
174
175     protected String svgDataForSeverity(Severity s) {
176         switch (s) {
177         case FATAL: return Activator.FATAL_SVG_TEXT;
178         case ERROR: return Activator.ERROR_SVG_TEXT;
179         case WARNING: return Activator.WARNING_SVG_TEXT;
180         case INFO: return Activator.INFO_SVG_TEXT;
181         case NOTE: return Activator.NOTE_SVG_TEXT;
182         default: return null;
183         }
184     }
185
186     @Override
187     protected void cleanupStyleForNode(INode node) {
188         ProfileVariables.denyChild(node, "", DECORATION_NODE_NAME); //$NON-NLS-1$
189     }
190
191     @Override
192     public String toString() {
193         return "Issue decoration";  //$NON-NLS-1$
194     }
195     
196     /**
197      * This is needed to keep the issue decoration up-to-date when its parent
198      * element moves.
199      */
200     public static class IssueResult extends Tuple {
201         public IssueResult(Severity severity, Object identifier) {
202             super(severity, identifier);
203         }
204         public Severity getSeverity() {
205             return (Severity) getField(0);
206         }
207     }
208
209 }
210
211