]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/OpenDiagramFromComponentAdapter.java
Improved Copy Visible Data usability in time series chart editor
[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     protected String getEditorId(ReadGraph g, Resource diagram) throws DatabaseException {
67         ModelingResources MOD = ModelingResources.getInstance(g);
68         String preferredEditorId = g.getPossibleRelatedValue(diagram, MOD.PreferredDiagramEditorID);
69         if(preferredEditorId != null)
70             return preferredEditorId;
71         else
72             return EDITOR_ID;
73     }
74
75     @Override
76     public boolean canHandle(ReadGraph graph, Object input) throws DatabaseException {
77         Pair<Resource, String> p = tryGetResource(graph, input);
78         if (p == null)
79             return false;
80         Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
81         Collection<Runnable> rs = tryFindDiagram(graph, p.first, v, p.second);
82         return !rs.isEmpty();
83     }
84
85     private Pair<Resource, String> tryGetResource(ReadGraph graph, Object input) throws DatabaseException {
86         Resource r = ResourceAdaptionUtils.toSingleResource(input);
87         if (r != null)
88             return Pair.make(r, ""); //$NON-NLS-1$
89         Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
90         return findResource(graph, v);
91     }
92
93     private Pair<Resource, String> findResource(ReadGraph graph, Variable v) throws DatabaseException {
94         List<String> path = null;
95         while (v != null) {
96             Resource r = v.getPossibleRepresents(graph);
97             if (r != null) {
98                 String rvi = ""; //$NON-NLS-1$
99                 if (path != null) {
100                     int pathLength = path.size();
101                     for (int i = 0; i < pathLength; i++)
102                         path.set(i, URIStringUtils.escape(path.get(i)));
103                     Collections.reverse(path);
104                     rvi = EString.implode(path, "/"); //$NON-NLS-1$
105                 }
106                 return Pair.make(r, rvi);
107             }
108             if (path == null)
109                 path = new ArrayList<>(2);
110             path.add( v.getName(graph) );
111             v = v.browsePossible(graph, "."); //$NON-NLS-1$
112         }
113         return null;
114     }
115
116     @Override
117     public void openEditor(final Object input) throws Exception {
118         final Display d = Display.getCurrent();
119         if (d == null)
120             return;
121
122         Simantics.getSession().syncRequest(new ReadRequest() {
123             @Override
124             public void run(ReadGraph graph) throws DatabaseException {
125                 Pair<Resource, String> r = tryGetResource(graph, input);
126                 if (r == null)
127                     return;
128
129                 Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
130
131                 if (LOGGER.isDebugEnabled()) {
132                     LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent resource URI: " + NameUtils.getURIOrSafeNameInternal(graph, r.first)); //$NON-NLS-1$
133                     LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent RVI: " + r.second); //$NON-NLS-1$
134                     LOGGER.debug(getClass().getSimpleName() + ".openEditor: input variable URI: " + (v != null ? v.getURI(graph) : "null")); //$NON-NLS-1$ //$NON-NLS-2$
135                 }
136
137                 final Collection<Runnable> rs = tryFindDiagram(graph, r.first, v, r.second);
138                 if (rs.isEmpty())
139                     return;
140
141                 SWTThread.getThreadAccess(d).asyncExec(() -> rs.forEach(Runnable::run));
142             }
143         });
144     }
145
146     private Collection<Runnable> tryFindDiagram(ReadGraph g, Resource component, Variable variable, String rviFromComponent) throws DatabaseException {
147         try {
148             return findDiagram(g, component, variable, rviFromComponent);
149         } catch (DatabaseException e) {
150             return Collections.emptyList();
151         }
152     }
153
154     private Collection<Runnable> findDiagram(ReadGraph g, Resource component, Variable variable, String rviFromComponent) throws DatabaseException {
155         Layer0 l0 = Layer0.getInstance(g);
156         StructuralResource2 STR = StructuralResource2.getInstance(g);
157         ModelingResources MOD = ModelingResources.getInstance(g);
158
159         if (g.isInstanceOf(component, STR.Component)) {
160             Collection<Runnable> result = new ArrayList<>(1);
161
162             Resource composite = g.getSingleObject(component, l0.PartOf);
163             Resource diagram = ComponentUtils.getPossibleCompositeDiagram(g, composite);
164
165             String editorId = getEditorId(g, composite);
166
167             if (LOGGER.isDebugEnabled()) {
168                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: component: " + NameUtils.getURIOrSafeNameInternal(g, component)); //$NON-NLS-1$
169                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: composite: " + NameUtils.getURIOrSafeNameInternal(g, composite)); //$NON-NLS-1$
170             }
171
172             Collection<Resource> referenceElements = diagram == null ? g.getObjects(component, MOD.HasParentComponent_Inverse) : Collections.<Resource>emptyList();
173             if (LOGGER.isDebugEnabled()) {
174                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: diagram: " + NameUtils.getURIOrSafeNameInternal(g, diagram)); //$NON-NLS-1$
175                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: referenceElements: " + referenceElements.size()); //$NON-NLS-1$
176                 for (Object object : referenceElements)
177                     LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); //$NON-NLS-1$
178             }
179             if (diagram == null && referenceElements.isEmpty())
180                 return Collections.emptyList();
181
182             Variable compositeVariable = Variables.getPossibleVariable(g, composite);
183             if (compositeVariable == null)
184                 return Collections.emptyList();
185             final Resource indexRoot = Variables.getPossibleIndexRoot(g, compositeVariable);
186             if (indexRoot == null)
187                 return Collections.emptyList();
188             if (LOGGER.isDebugEnabled())
189                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: Model: " + indexRoot); //$NON-NLS-1$
190
191             if (diagram != null) {
192                 if(OpenDiagramFromConfigurationAdapter.isLocked(g, diagram))
193                     return Collections.emptyList();
194
195                 RVI rvi = null;
196                 boolean allowNullRvi = false;
197                 if (variable != null) {
198                     // Get proper RVI from variable if it exists.
199                     Variable context = Variables.getPossibleContext(g, variable);
200                     if (context != null) {
201                         // We want the composite's RVI, not the component in it.
202                         Variable parent = findFirstParentComposite(g, variable);
203                         if (parent != null) {
204                             rvi = parent.getPossibleRVI(g);
205                             if (LOGGER.isDebugEnabled())
206                                 LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI: " + rvi); //$NON-NLS-1$
207                         }
208                     }
209                 } else {
210                     allowNullRvi = true;
211                     rvi = compositeVariable.getPossibleRVI(g);
212                     if (LOGGER.isDebugEnabled())
213                         LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI from resource path: " + rvi); //$NON-NLS-1$
214                 }
215                 if (rvi == null && !allowNullRvi)
216                     return Collections.emptyList();
217
218                 Collection<Object> selectedObjects = findElementObjects(g, component, rviFromComponent);
219                 if (LOGGER.isDebugEnabled()) {
220                     LOGGER.debug(getClass().getSimpleName() + ".findDiagram: selected objects: " + selectedObjects.size()); //$NON-NLS-1$
221                     for (Object object : selectedObjects)
222                         LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); //$NON-NLS-1$
223                 }
224                 // Prevent diagram from opening if there's nothing to select
225                 // on the diagram based on the received input.
226                 if (!selectedObjects.isEmpty())
227                     result.add( NavigateToTarget.editorActivator(editorId, diagram, indexRoot, rvi, editorActivationCallback(selectedObjects)) );
228             } else {
229                 final MapSet<NamedResource, Resource> referencingDiagrams = listReferenceDiagrams(g, referenceElements);
230                 final Set<NamedResource> diagrams = referencingDiagrams.getKeys();
231                 if (LOGGER.isDebugEnabled()) {
232                     LOGGER.debug(getClass().getSimpleName() + ".findDiagram: selected objects: " + diagrams.size()); //$NON-NLS-1$
233                     for (NamedResource d : diagrams) {
234                         LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, d.getResource()) + ":"); //$NON-NLS-1$ //$NON-NLS-2$
235                         for (Resource referenceElement : referencingDiagrams.getValues(d)) {
236                             LOGGER.debug("\t\t" + NameUtils.getURIOrSafeNameInternal(g, referenceElement)); //$NON-NLS-1$
237                         }
238                     }
239                 }
240                 switch (diagrams.size()) {
241                 case 0:
242                     // Prevent diagram from opening if there's nothing to select
243                     // on the diagram based on the received input.
244                     break;
245
246                 case 1:
247                     // Open the one diagram straight away.
248                     NamedResource singleDiagram = diagrams.iterator().next();
249                     RVI rvi = getDiagramCompositeRvi(g, singleDiagram.getResource());
250                     if (rvi != null) {
251                         Collection<Resource> selectedObjects = referencingDiagrams.getValues(singleDiagram);
252                         result.add( NavigateToTarget.editorActivator(editorId, singleDiagram.getResource(), indexRoot, rvi, editorActivationCallback(selectedObjects)) );
253                     }
254                     break;
255
256                 default:
257                     final Map<NamedResource, RVI> diagramToRvi = new TreeMap<>(COMPARATOR);
258                     for (NamedResource d : diagrams) {
259                         RVI rvi2 = getDiagramCompositeRvi(g, d.getResource());
260                         if (rvi2 != null)
261                             diagramToRvi.put(d, rvi2);
262                     }
263                     result.add(() -> {
264                         NamedResource selected = queryTarget(WorkbenchUtils.getActiveWorkbenchWindowShell(), diagramToRvi.keySet());
265                         if (selected != null) {
266                             Collection<Resource> selectedObjects = referencingDiagrams.getValues(selected);
267                             RVI drvi = diagramToRvi.get(selected);
268                             NavigateToTarget.editorActivator(editorId, selected.getResource(), indexRoot, drvi, editorActivationCallback(selectedObjects)).run();
269                         }
270                     });
271                     break;
272                 }
273             }
274             return result;
275         }
276
277         // Nothing to open
278         return Collections.emptyList();
279     }
280
281     private RVI getDiagramCompositeRvi(ReadGraph graph, Resource diagram) throws DatabaseException {
282         ModelingResources MOD = ModelingResources.getInstance(graph);
283         Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
284         if (composite == null)
285             return null;
286         Variable v = Variables.getPossibleVariable(graph, composite);
287         return v != null ? v.getPossibleRVI(graph) : null;
288     }
289
290     private Consumer<IEditorPart> editorActivationCallback(final Collection<? extends Object> selectedObjects) {
291         return part -> {
292             final ICanvasContext openedCanvas = (ICanvasContext) part.getAdapter(ICanvasContext.class);
293             assert openedCanvas != null;
294             // CanvasContext-wide denial of initial zoom-to-fit on diagram open.
295             openedCanvas.getDefaultHintContext().setHint(DiagramHints.KEY_INITIAL_ZOOM_TO_FIT, Boolean.FALSE);
296             ThreadUtils.asyncExec(openedCanvas.getThreadAccess(),
297                     NavigateToTarget.elementSelectorZoomer(openedCanvas, selectedObjects, false));
298         };
299     }
300
301     private Variable findFirstParentComposite(ReadGraph graph, Variable v) throws DatabaseException {
302         Variable first = findFirstWithRepresentation(graph, v);
303         if (first == null)
304             return null;
305         Variable parent = first.getParent(graph);
306         return parent;
307     }
308
309     private Variable findFirstWithRepresentation(ReadGraph graph, Variable v) throws DatabaseException {
310         while (v != null) {
311             Resource represents = v.getPossibleRepresents(graph);
312             if (LOGGER.isDebugEnabled())
313                 LOGGER.debug(v.getURI(graph) + " -> " + NameUtils.getURIOrSafeNameInternal(graph, represents)); //$NON-NLS-1$
314             if (represents != null)
315                 return v;
316             v = v.getParent(graph);
317         }
318         return null;
319     }
320
321     public static Collection<Object> findElementObjects(ReadGraph g, Resource component, String rviFromComponent) throws DatabaseException {
322         DiagramResource DIA = DiagramResource.getInstance(g);
323         ModelingResources MOD = ModelingResources.getInstance(g);
324         final Collection<Object> selectedObjects = new ArrayList<>(4);
325         if (rviFromComponent.isEmpty()) {
326             // The selected objects are configuration objects
327             for (Resource element : g.getObjects(component, MOD.ComponentToElement)) {
328                 if (g.isInstanceOf(element, DIA.Flag) && FlagUtil.isExternal(g, element)) {
329                     // Use external flag primarily if one exists in the correspondences
330                     selectedObjects.clear();
331                     selectedObjects.add(element);
332                     break;
333                 } else if (g.isInstanceOf(element, DIA.RouteGraphConnection)) {
334                     selectedObjects.add(element);
335                 } else if (g.isInstanceOf(element, DIA.Connection)) {
336                     // Okay, we need to find a part of the connection
337                     ConnectionUtil cu = new ConnectionUtil(g);
338                     cu.gatherConnectionParts(element, selectedObjects);
339                 } else {
340                     selectedObjects.add(element);
341                 }
342             }
343         }
344         return selectedObjects;
345     }
346
347     protected MapSet<NamedResource, Resource> listReferenceDiagrams(ReadGraph graph, Collection<Resource> referenceElements) throws DatabaseException {
348         ModelingResources MOD = ModelingResources.getInstance(graph);
349
350         // Make result diagram ordering stable and logical by using our own comparator.
351         MapSet<NamedResource, Resource> diagrams = new MapSet.Tree<>(COMPARATOR);
352
353         for (Resource referenceElement : referenceElements) {
354             final Resource diagram = NavigateToTarget.getOwnerList(graph, referenceElement);
355             if (diagram == null)
356                 continue;
357             Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
358             if (composite == null)
359                 continue;
360             Variable v = Variables.getPossibleVariable(graph, composite);
361             if (v == null)
362                 continue;
363
364             String rvi = URIStringUtils.unescape( Variables.getRVI(graph, v) );
365
366             diagrams.add(new NamedResource(rvi, diagram), referenceElement);
367         }
368
369         return diagrams;
370     }
371
372     private static final Comparator<? super NamedResource> COMPARATOR =
373             (o1, o2) -> AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());
374
375     protected NamedResource queryTarget(final Shell parentShell, Collection<NamedResource> options) {
376         NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog(
377                 parentShell, options.toArray(new NamedResource[options.size()]),
378                 Messages.OpenDiagramFromComponentAdapter_ChooseDiagramComponetReference,
379                 Messages.OpenDiagramFromComponentAdapter_SelectSingleDiagramfromList);
380         return dialog.open() != Window.OK ? null : dialog.getSelection();
381     }
382
383 }