1 package org.simantics.modeling.ui.diagramEditor;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
10 import java.util.TreeMap;
11 import java.util.function.Consumer;
13 import org.eclipse.jface.window.Window;
14 import org.eclipse.swt.widgets.Display;
15 import org.eclipse.swt.widgets.Shell;
16 import org.eclipse.ui.IEditorPart;
17 import org.simantics.Simantics;
18 import org.simantics.databoard.util.URIStringUtils;
19 import org.simantics.db.ReadGraph;
20 import org.simantics.db.Resource;
21 import org.simantics.db.common.NamedResource;
22 import org.simantics.db.common.request.ReadRequest;
23 import org.simantics.db.common.utils.NameUtils;
24 import org.simantics.db.exception.DatabaseException;
25 import org.simantics.db.layer0.variable.RVI;
26 import org.simantics.db.layer0.variable.Variable;
27 import org.simantics.db.layer0.variable.Variables;
28 import org.simantics.diagram.content.ConnectionUtil;
29 import org.simantics.diagram.flag.FlagUtil;
30 import org.simantics.diagram.stubs.DiagramResource;
31 import org.simantics.g2d.canvas.ICanvasContext;
32 import org.simantics.g2d.diagram.DiagramHints;
33 import org.simantics.layer0.Layer0;
34 import org.simantics.modeling.ComponentUtils;
35 import org.simantics.modeling.ModelingResources;
36 import org.simantics.modeling.actions.NavigateToTarget;
37 import org.simantics.modeling.actions.NavigationTargetChooserDialog;
38 import org.simantics.modeling.ui.Activator;
39 import org.simantics.structural.stubs.StructuralResource2;
40 import org.simantics.ui.utils.ResourceAdaptionUtils;
41 import org.simantics.ui.workbench.editor.AbstractResourceEditorAdapter;
42 import org.simantics.utils.datastructures.MapSet;
43 import org.simantics.utils.datastructures.Pair;
44 import org.simantics.utils.strings.AlphanumComparator;
45 import org.simantics.utils.strings.EString;
46 import org.simantics.utils.threads.SWTThread;
47 import org.simantics.utils.threads.ThreadUtils;
48 import org.simantics.utils.ui.AdaptionUtils;
49 import org.simantics.utils.ui.workbench.WorkbenchUtils;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * @author Tuukka Lehtonen
56 public class OpenDiagramFromComponentAdapter extends AbstractResourceEditorAdapter {
58 private static final Logger LOGGER = LoggerFactory.getLogger(OpenDiagramFromComponentAdapter.class);
60 private static final String EDITOR_ID = "org.simantics.modeling.ui.diagramEditor";
62 public OpenDiagramFromComponentAdapter() {
63 super("Open Diagram Containing This Component", Activator.SYMBOL_ICON);
67 public boolean canHandle(ReadGraph graph, Object input) throws DatabaseException {
68 Pair<Resource, String> p = tryGetResource(graph, input);
71 Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
72 Collection<Runnable> rs = tryFindDiagram(graph, p.first, v, p.second);
76 private Pair<Resource, String> tryGetResource(ReadGraph graph, Object input) throws DatabaseException {
77 Resource r = ResourceAdaptionUtils.toSingleResource(input);
79 return Pair.make(r, "");
80 Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
81 return findResource(graph, v);
84 private Pair<Resource, String> findResource(ReadGraph graph, Variable v) throws DatabaseException {
85 List<String> path = null;
87 Resource r = v.getPossibleRepresents(graph);
91 int pathLength = path.size();
92 for (int i = 0; i < pathLength; i++)
93 path.set(i, URIStringUtils.escape(path.get(i)));
94 Collections.reverse(path);
95 rvi = EString.implode(path, "/");
97 return Pair.make(r, rvi);
100 path = new ArrayList<>(2);
101 path.add( v.getName(graph) );
102 v = v.browsePossible(graph, ".");
108 public void openEditor(final Object input) throws Exception {
109 final Display d = Display.getCurrent();
113 Simantics.getSession().syncRequest(new ReadRequest() {
115 public void run(ReadGraph graph) throws DatabaseException {
116 Pair<Resource, String> r = tryGetResource(graph, input);
120 Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
122 if (LOGGER.isDebugEnabled()) {
123 LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent resource URI: " + NameUtils.getURIOrSafeNameInternal(graph, r.first));
124 LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent RVI: " + r.second);
125 LOGGER.debug(getClass().getSimpleName() + ".openEditor: input variable URI: " + (v != null ? v.getURI(graph) : "null"));
128 final Collection<Runnable> rs = tryFindDiagram(graph, r.first, v, r.second);
132 SWTThread.getThreadAccess(d).asyncExec(() -> rs.forEach(Runnable::run));
137 private Collection<Runnable> tryFindDiagram(ReadGraph g, Resource component, Variable variable, String rviFromComponent) throws DatabaseException {
139 return findDiagram(g, component, variable, rviFromComponent);
140 } catch (DatabaseException e) {
141 return Collections.emptyList();
145 private Collection<Runnable> findDiagram(ReadGraph g, Resource component, Variable variable, String rviFromComponent) throws DatabaseException {
146 Layer0 l0 = Layer0.getInstance(g);
147 StructuralResource2 STR = StructuralResource2.getInstance(g);
148 ModelingResources MOD = ModelingResources.getInstance(g);
150 if (g.isInstanceOf(component, STR.Component)) {
151 Collection<Runnable> result = new ArrayList<>(1);
153 Resource composite = g.getSingleObject(component, l0.PartOf);
154 Resource diagram = ComponentUtils.getPossibleCompositeDiagram(g, composite);
156 if (LOGGER.isDebugEnabled()) {
157 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: component: " + NameUtils.getURIOrSafeNameInternal(g, component));
158 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: composite: " + NameUtils.getURIOrSafeNameInternal(g, composite));
161 Collection<Resource> referenceElements = diagram == null ? g.getObjects(component, MOD.HasParentComponent_Inverse) : Collections.<Resource>emptyList();
162 if (LOGGER.isDebugEnabled()) {
163 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: diagram: " + NameUtils.getURIOrSafeNameInternal(g, diagram));
164 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: referenceElements: " + referenceElements.size());
165 for (Object object : referenceElements)
166 LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object));
168 if (diagram == null && referenceElements.isEmpty())
169 return Collections.emptyList();
171 Variable compositeVariable = Variables.getPossibleVariable(g, composite);
172 if (compositeVariable == null)
173 return Collections.emptyList();
174 final Resource indexRoot = Variables.getPossibleIndexRoot(g, compositeVariable);
175 if (indexRoot == null)
176 return Collections.emptyList();
177 if (LOGGER.isDebugEnabled())
178 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: Model: " + indexRoot);
180 if (diagram != null) {
181 if(OpenDiagramFromConfigurationAdapter.isLocked(g, diagram))
182 return Collections.emptyList();
185 boolean allowNullRvi = false;
186 if (variable != null) {
187 // Get proper RVI from variable if it exists.
188 Variable context = Variables.getPossibleContext(g, variable);
189 if (context != null) {
190 // We want the composite's RVI, not the component in it.
191 Variable parent = findFirstParentComposite(g, variable);
192 if (parent != null) {
193 rvi = parent.getPossibleRVI(g);
194 if (LOGGER.isDebugEnabled())
195 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI: " + rvi);
200 rvi = compositeVariable.getPossibleRVI(g);
201 if (LOGGER.isDebugEnabled())
202 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI from resource path: " + rvi);
204 if (rvi == null && !allowNullRvi)
205 return Collections.emptyList();
207 Collection<Object> selectedObjects = findElementObjects(g, component, rviFromComponent);
208 if (LOGGER.isDebugEnabled()) {
209 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: selected objects: " + selectedObjects.size());
210 for (Object object : selectedObjects)
211 LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object));
213 // Prevent diagram from opening if there's nothing to select
214 // on the diagram based on the received input.
215 if (!selectedObjects.isEmpty())
216 result.add( NavigateToTarget.editorActivator(EDITOR_ID, diagram, indexRoot, rvi, editorActivationCallback(selectedObjects)) );
218 final MapSet<NamedResource, Resource> referencingDiagrams = listReferenceDiagrams(g, referenceElements);
219 final Set<NamedResource> diagrams = referencingDiagrams.getKeys();
220 if (LOGGER.isDebugEnabled()) {
221 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: selected objects: " + diagrams.size());
222 for (NamedResource d : diagrams) {
223 LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, d.getResource()) + ":");
224 for (Resource referenceElement : referencingDiagrams.getValues(d)) {
225 LOGGER.debug("\t\t" + NameUtils.getURIOrSafeNameInternal(g, referenceElement));
229 switch (diagrams.size()) {
231 // Prevent diagram from opening if there's nothing to select
232 // on the diagram based on the received input.
236 // Open the one diagram straight away.
237 NamedResource singleDiagram = diagrams.iterator().next();
238 RVI rvi = getDiagramCompositeRvi(g, singleDiagram.getResource());
240 Collection<Resource> selectedObjects = referencingDiagrams.getValues(singleDiagram);
241 result.add( NavigateToTarget.editorActivator(EDITOR_ID, singleDiagram.getResource(), indexRoot, rvi, editorActivationCallback(selectedObjects)) );
246 final Map<NamedResource, RVI> diagramToRvi = new TreeMap<>(COMPARATOR);
247 for (NamedResource d : diagrams) {
248 RVI rvi2 = getDiagramCompositeRvi(g, d.getResource());
250 diagramToRvi.put(d, rvi2);
253 NamedResource selected = queryTarget(WorkbenchUtils.getActiveWorkbenchWindowShell(), diagramToRvi.keySet());
254 if (selected != null) {
255 Collection<Resource> selectedObjects = referencingDiagrams.getValues(selected);
256 RVI drvi = diagramToRvi.get(selected);
257 NavigateToTarget.editorActivator(EDITOR_ID, selected.getResource(), indexRoot, drvi, editorActivationCallback(selectedObjects)).run();
267 return Collections.emptyList();
270 private RVI getDiagramCompositeRvi(ReadGraph graph, Resource diagram) throws DatabaseException {
271 ModelingResources MOD = ModelingResources.getInstance(graph);
272 Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
273 if (composite == null)
275 Variable v = Variables.getPossibleVariable(graph, composite);
276 return v != null ? v.getPossibleRVI(graph) : null;
279 private Consumer<IEditorPart> editorActivationCallback(final Collection<? extends Object> selectedObjects) {
281 final ICanvasContext openedCanvas = (ICanvasContext) part.getAdapter(ICanvasContext.class);
282 assert openedCanvas != null;
283 // CanvasContext-wide denial of initial zoom-to-fit on diagram open.
284 openedCanvas.getDefaultHintContext().setHint(DiagramHints.KEY_INITIAL_ZOOM_TO_FIT, Boolean.FALSE);
285 ThreadUtils.asyncExec(openedCanvas.getThreadAccess(),
286 NavigateToTarget.elementSelectorZoomer(openedCanvas, selectedObjects, false));
290 private Variable findFirstParentComposite(ReadGraph graph, Variable v) throws DatabaseException {
291 Variable first = findFirstWithRepresentation(graph, v);
294 Variable parent = first.getParent(graph);
298 private Variable findFirstWithRepresentation(ReadGraph graph, Variable v) throws DatabaseException {
300 Resource represents = v.getPossibleRepresents(graph);
301 if (LOGGER.isDebugEnabled())
302 LOGGER.debug(v.getURI(graph) + " -> " + NameUtils.getURIOrSafeNameInternal(graph, represents));
303 if (represents != null)
305 v = v.getParent(graph);
310 public static Collection<Object> findElementObjects(ReadGraph g, Resource component, String rviFromComponent) throws DatabaseException {
311 DiagramResource DIA = DiagramResource.getInstance(g);
312 ModelingResources MOD = ModelingResources.getInstance(g);
313 final Collection<Object> selectedObjects = new ArrayList<>(4);
314 if (rviFromComponent.isEmpty()) {
315 // The selected objects are configuration objects
316 for (Resource element : g.getObjects(component, MOD.ComponentToElement)) {
317 if (g.isInstanceOf(element, DIA.Flag) && FlagUtil.isExternal(g, element)) {
318 // Use external flag primarily if one exists in the correspondences
319 selectedObjects.clear();
320 selectedObjects.add(element);
322 } else if (g.isInstanceOf(element, DIA.RouteGraphConnection)) {
323 selectedObjects.add(element);
324 } else if (g.isInstanceOf(element, DIA.Connection)) {
325 // Okay, we need to find a part of the connection
326 ConnectionUtil cu = new ConnectionUtil(g);
327 cu.gatherConnectionParts(element, selectedObjects);
329 selectedObjects.add(element);
333 return selectedObjects;
336 protected MapSet<NamedResource, Resource> listReferenceDiagrams(ReadGraph graph, Collection<Resource> referenceElements) throws DatabaseException {
337 ModelingResources MOD = ModelingResources.getInstance(graph);
339 // Make result diagram ordering stable and logical by using our own comparator.
340 MapSet<NamedResource, Resource> diagrams = new MapSet.Tree<>(COMPARATOR);
342 for (Resource referenceElement : referenceElements) {
343 final Resource diagram = NavigateToTarget.getOwnerList(graph, referenceElement);
346 Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
347 if (composite == null)
349 Variable v = Variables.getPossibleVariable(graph, composite);
353 String rvi = URIStringUtils.unescape( Variables.getRVI(graph, v) );
355 diagrams.add(new NamedResource(rvi, diagram), referenceElement);
361 private static final Comparator<? super NamedResource> COMPARATOR =
362 (o1, o2) -> AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());
364 protected NamedResource queryTarget(final Shell parentShell, Collection<NamedResource> options) {
365 NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog(
366 parentShell, options.toArray(new NamedResource[options.size()]),
367 "Choose Diagram with Component Reference",
368 "Select single diagram from list");
369 return dialog.open() != Window.OK ? null : dialog.getSelection();