]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/OpenDiagramFromComponentAdapter.java
d5071303e4924b22a3f812f05b312141d03a5ed1
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / diagramEditor / OpenDiagramFromComponentAdapter.java
1 package org.simantics.modeling.ui.diagramEditor;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Set;
10 import java.util.TreeMap;
11 import java.util.function.Consumer;
12
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;
52
53 /**
54  * @author Tuukka Lehtonen
55  */
56 public class OpenDiagramFromComponentAdapter extends AbstractResourceEditorAdapter {
57
58     private static final Logger LOGGER = LoggerFactory.getLogger(OpenDiagramFromComponentAdapter.class);
59
60     private static final String EDITOR_ID = "org.simantics.modeling.ui.diagramEditor"; //$NON-NLS-1$
61
62     public OpenDiagramFromComponentAdapter() {
63         super(Messages.OpenDiagramFromComponentAdapter_OpenDiagramContainingComponent, Activator.SYMBOL_ICON);
64     }
65
66     @Override
67     public boolean canHandle(ReadGraph graph, Object input) throws DatabaseException {
68         Pair<Resource, String> p = tryGetResource(graph, input);
69         if (p == null)
70             return false;
71         Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
72         Collection<Runnable> rs = tryFindDiagram(graph, p.first, v, p.second);
73         return !rs.isEmpty();
74     }
75
76     private Pair<Resource, String> tryGetResource(ReadGraph graph, Object input) throws DatabaseException {
77         Resource r = ResourceAdaptionUtils.toSingleResource(input);
78         if (r != null)
79             return Pair.make(r, ""); //$NON-NLS-1$
80         Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
81         return findResource(graph, v);
82     }
83
84     private Pair<Resource, String> findResource(ReadGraph graph, Variable v) throws DatabaseException {
85         List<String> path = null;
86         while (v != null) {
87             Resource r = v.getPossibleRepresents(graph);
88             if (r != null) {
89                 String rvi = ""; //$NON-NLS-1$
90                 if (path != null) {
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, "/"); //$NON-NLS-1$
96                 }
97                 return Pair.make(r, rvi);
98             }
99             if (path == null)
100                 path = new ArrayList<>(2);
101             path.add( v.getName(graph) );
102             v = v.browsePossible(graph, "."); //$NON-NLS-1$
103         }
104         return null;
105     }
106
107     @Override
108     public void openEditor(final Object input) throws Exception {
109         final Display d = Display.getCurrent();
110         if (d == null)
111             return;
112
113         Simantics.getSession().syncRequest(new ReadRequest() {
114             @Override
115             public void run(ReadGraph graph) throws DatabaseException {
116                 Pair<Resource, String> r = tryGetResource(graph, input);
117                 if (r == null)
118                     return;
119
120                 Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
121
122                 if (LOGGER.isDebugEnabled()) {
123                     LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent resource URI: " + NameUtils.getURIOrSafeNameInternal(graph, r.first)); //$NON-NLS-1$
124                     LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent RVI: " + r.second); //$NON-NLS-1$
125                     LOGGER.debug(getClass().getSimpleName() + ".openEditor: input variable URI: " + (v != null ? v.getURI(graph) : "null")); //$NON-NLS-1$ //$NON-NLS-2$
126                 }
127
128                 final Collection<Runnable> rs = tryFindDiagram(graph, r.first, v, r.second);
129                 if (rs.isEmpty())
130                     return;
131
132                 SWTThread.getThreadAccess(d).asyncExec(() -> rs.forEach(Runnable::run));
133             }
134         });
135     }
136
137     private Collection<Runnable> tryFindDiagram(ReadGraph g, Resource component, Variable variable, String rviFromComponent) throws DatabaseException {
138         try {
139             return findDiagram(g, component, variable, rviFromComponent);
140         } catch (DatabaseException e) {
141             return Collections.emptyList();
142         }
143     }
144
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);
149
150         if (g.isInstanceOf(component, STR.Component)) {
151             Collection<Runnable> result = new ArrayList<>(1);
152
153             Resource composite = g.getSingleObject(component, l0.PartOf);
154             Resource diagram = ComponentUtils.getPossibleCompositeDiagram(g, composite);
155
156             if (LOGGER.isDebugEnabled()) {
157                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: component: " + NameUtils.getURIOrSafeNameInternal(g, component)); //$NON-NLS-1$
158                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: composite: " + NameUtils.getURIOrSafeNameInternal(g, composite)); //$NON-NLS-1$
159             }
160
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)); //$NON-NLS-1$
164                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: referenceElements: " + referenceElements.size()); //$NON-NLS-1$
165                 for (Object object : referenceElements)
166                     LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); //$NON-NLS-1$
167             }
168             if (diagram == null && referenceElements.isEmpty())
169                 return Collections.emptyList();
170
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); //$NON-NLS-1$
179
180             if (diagram != null) {
181                 if(OpenDiagramFromConfigurationAdapter.isLocked(g, diagram))
182                     return Collections.emptyList();
183
184                 RVI rvi = null;
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); //$NON-NLS-1$
196                         }
197                     }
198                 } else {
199                     allowNullRvi = true;
200                     rvi = compositeVariable.getPossibleRVI(g);
201                     if (LOGGER.isDebugEnabled())
202                         LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI from resource path: " + rvi); //$NON-NLS-1$
203                 }
204                 if (rvi == null && !allowNullRvi)
205                     return Collections.emptyList();
206
207                 Collection<Object> selectedObjects = findElementObjects(g, component, rviFromComponent);
208                 if (LOGGER.isDebugEnabled()) {
209                     LOGGER.debug(getClass().getSimpleName() + ".findDiagram: selected objects: " + selectedObjects.size()); //$NON-NLS-1$
210                     for (Object object : selectedObjects)
211                         LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); //$NON-NLS-1$
212                 }
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)) );
217             } else {
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()); //$NON-NLS-1$
222                     for (NamedResource d : diagrams) {
223                         LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, d.getResource()) + ":"); //$NON-NLS-1$ //$NON-NLS-2$
224                         for (Resource referenceElement : referencingDiagrams.getValues(d)) {
225                             LOGGER.debug("\t\t" + NameUtils.getURIOrSafeNameInternal(g, referenceElement)); //$NON-NLS-1$
226                         }
227                     }
228                 }
229                 switch (diagrams.size()) {
230                 case 0:
231                     // Prevent diagram from opening if there's nothing to select
232                     // on the diagram based on the received input.
233                     break;
234
235                 case 1:
236                     // Open the one diagram straight away.
237                     NamedResource singleDiagram = diagrams.iterator().next();
238                     RVI rvi = getDiagramCompositeRvi(g, singleDiagram.getResource());
239                     if (rvi != null) {
240                         Collection<Resource> selectedObjects = referencingDiagrams.getValues(singleDiagram);
241                         result.add( NavigateToTarget.editorActivator(EDITOR_ID, singleDiagram.getResource(), indexRoot, rvi, editorActivationCallback(selectedObjects)) );
242                     }
243                     break;
244
245                 default:
246                     final Map<NamedResource, RVI> diagramToRvi = new TreeMap<>(COMPARATOR);
247                     for (NamedResource d : diagrams) {
248                         RVI rvi2 = getDiagramCompositeRvi(g, d.getResource());
249                         if (rvi2 != null)
250                             diagramToRvi.put(d, rvi2);
251                     }
252                     result.add(() -> {
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();
258                         }
259                     });
260                     break;
261                 }
262             }
263             return result;
264         }
265
266         // Nothing to open
267         return Collections.emptyList();
268     }
269
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)
274             return null;
275         Variable v = Variables.getPossibleVariable(graph, composite);
276         return v != null ? v.getPossibleRVI(graph) : null;
277     }
278
279     private Consumer<IEditorPart> editorActivationCallback(final Collection<? extends Object> selectedObjects) {
280         return part -> {
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));
287         };
288     }
289
290     private Variable findFirstParentComposite(ReadGraph graph, Variable v) throws DatabaseException {
291         Variable first = findFirstWithRepresentation(graph, v);
292         if (first == null)
293             return null;
294         Variable parent = first.getParent(graph);
295         return parent;
296     }
297
298     private Variable findFirstWithRepresentation(ReadGraph graph, Variable v) throws DatabaseException {
299         while (v != null) {
300             Resource represents = v.getPossibleRepresents(graph);
301             if (LOGGER.isDebugEnabled())
302                 LOGGER.debug(v.getURI(graph) + " -> " + NameUtils.getURIOrSafeNameInternal(graph, represents)); //$NON-NLS-1$
303             if (represents != null)
304                 return v;
305             v = v.getParent(graph);
306         }
307         return null;
308     }
309
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);
321                     break;
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);
328                 } else {
329                     selectedObjects.add(element);
330                 }
331             }
332         }
333         return selectedObjects;
334     }
335
336     protected MapSet<NamedResource, Resource> listReferenceDiagrams(ReadGraph graph, Collection<Resource> referenceElements) throws DatabaseException {
337         ModelingResources MOD = ModelingResources.getInstance(graph);
338
339         // Make result diagram ordering stable and logical by using our own comparator.
340         MapSet<NamedResource, Resource> diagrams = new MapSet.Tree<>(COMPARATOR);
341
342         for (Resource referenceElement : referenceElements) {
343             final Resource diagram = NavigateToTarget.getOwnerList(graph, referenceElement);
344             if (diagram == null)
345                 continue;
346             Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
347             if (composite == null)
348                 continue;
349             Variable v = Variables.getPossibleVariable(graph, composite);
350             if (v == null)
351                 continue;
352
353             String rvi = URIStringUtils.unescape( Variables.getRVI(graph, v) );
354
355             diagrams.add(new NamedResource(rvi, diagram), referenceElement);
356         }
357
358         return diagrams;
359     }
360
361     private static final Comparator<? super NamedResource> COMPARATOR =
362             (o1, o2) -> AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());
363
364     protected NamedResource queryTarget(final Shell parentShell, Collection<NamedResource> options) {
365         NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog(
366                 parentShell, options.toArray(new NamedResource[options.size()]),
367                 Messages.OpenDiagramFromComponentAdapter_ChooseDiagramComponetReference,
368                 Messages.OpenDiagramFromComponentAdapter_SelectSingleDiagramfromList);
369         return dialog.open() != Window.OK ? null : dialog.getSelection();
370     }
371
372 }