]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling/src/org/simantics/modeling/actions/NavigateToTarget.java
Add utility class org.simantics.modeling.help.HelpContexts
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / actions / NavigateToTarget.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.actions;
13
14 import java.awt.geom.AffineTransform;
15 import java.awt.geom.Rectangle2D;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.HashSet;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.concurrent.TimeUnit;
24 import java.util.function.Consumer;
25
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.jface.action.IStatusLineManager;
29 import org.eclipse.jface.window.Window;
30 import org.eclipse.swt.widgets.Shell;
31 import org.eclipse.ui.IEditorInput;
32 import org.eclipse.ui.IEditorPart;
33 import org.eclipse.ui.IWorkbenchPart;
34 import org.eclipse.ui.IWorkbenchWindow;
35 import org.eclipse.ui.PartInitException;
36 import org.simantics.Simantics;
37 import org.simantics.databoard.Bindings;
38 import org.simantics.databoard.util.URIStringUtils;
39 import org.simantics.db.ReadGraph;
40 import org.simantics.db.Resource;
41 import org.simantics.db.Session;
42 import org.simantics.db.common.NamedResource;
43 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
44 import org.simantics.db.common.request.ReadRequest;
45 import org.simantics.db.common.utils.NameUtils;
46 import org.simantics.db.common.utils.OrderedSetUtils;
47 import org.simantics.db.exception.DatabaseException;
48 import org.simantics.db.layer0.variable.RVI;
49 import org.simantics.db.layer0.variable.Variable;
50 import org.simantics.db.layer0.variable.Variables;
51 import org.simantics.diagram.content.ConnectionUtil;
52 import org.simantics.diagram.flag.FlagUtil;
53 import org.simantics.diagram.stubs.DiagramResource;
54 import org.simantics.g2d.canvas.ICanvasContext;
55 import org.simantics.g2d.diagram.DiagramHints;
56 import org.simantics.g2d.diagram.IDiagram;
57 import org.simantics.g2d.diagram.handler.DataElementMap;
58 import org.simantics.g2d.diagram.participant.Selection;
59 import org.simantics.g2d.element.ElementUtils;
60 import org.simantics.g2d.element.IElement;
61 import org.simantics.g2d.elementclass.FlagClass;
62 import org.simantics.g2d.participant.CanvasBoundsParticipant;
63 import org.simantics.g2d.participant.TransformUtil;
64 import org.simantics.g2d.utils.GeometryUtils;
65 import org.simantics.layer0.Layer0;
66 import org.simantics.layer0.utils.operations.Operation;
67 import org.simantics.modeling.ModelingOperationConstants;
68 import org.simantics.modeling.ModelingResources;
69 import org.simantics.modeling.utils.Monitors;
70 import org.simantics.structural.stubs.StructuralResource2;
71 import org.simantics.ui.workbench.ResourceEditorInput;
72 import org.simantics.ui.workbench.ResourceEditorInput2;
73 import org.simantics.utils.datastructures.persistent.IContextMap;
74 import org.simantics.utils.page.MarginUtils;
75 import org.simantics.utils.strings.AlphanumComparator;
76 import org.simantics.utils.threads.ThreadUtils;
77 import org.simantics.utils.ui.ErrorLogger;
78 import org.simantics.utils.ui.workbench.WorkbenchUtils;
79
80 public class NavigateToTarget extends Operation {
81
82     public NavigateToTarget() {
83         super("Navigate to Target");
84     }
85
86     @Override
87     public void exec(Session session, IContextMap parameters) {
88         final Resource r = (Resource) parameters.get(SUBJECT);
89         final IWorkbenchWindow window = (IWorkbenchWindow) parameters.get(ModelingOperationConstants.WORKBENCH_WINDOW);
90         final IWorkbenchPart part = (IWorkbenchPart) parameters.get(ModelingOperationConstants.WORKBENCH_PART);
91
92         session.asyncRequest(new ReadRequest() {
93             @Override
94             public void run(ReadGraph g) throws DatabaseException {
95                 final Resource thisDiagram = getOwnerList(g, r);
96                 if (thisDiagram == null)
97                     return;
98
99                 Set<Resource> counterparts = FlagUtil.getCounterparts(g, r);
100                 if (counterparts.size() > 1) {
101                     // Ask user which target to navigate to.
102                     asyncQueryTarget(window.getShell(), g, thisDiagram, counterparts,
103                             target -> navigateToTarget( window, part, thisDiagram, target)); 
104                 } else if (counterparts.size() == 1) {
105                     // Target is defined.
106                     final String error = navigateToTarget( g, window, part, thisDiagram, counterparts.iterator().next() );
107                     if (error != null && !error.isEmpty()) {
108                         window.getShell().getDisplay().asyncExec(new Runnable() {
109                             @Override
110                             public void run() {
111                                 IStatusLineManager status = WorkbenchUtils.getStatusLine(part);
112                                 status.setErrorMessage(error);
113                             }
114                         });
115                     }
116                 } else {
117                     // Try other methods of getting a navigation target besides flags.
118                     Resource target = Monitors.getMonitoredElement(g, r);
119                     if (target != null)
120                         navigateToTarget( g, window, part, thisDiagram, target );
121                 }
122             }
123         }, new ProcedureAdapter<Object>() {
124             @Override
125             public void exception(Throwable t) {
126                 ErrorLogger.defaultLogError(t);
127             }
128         });
129     }
130
131     protected void navigateToTarget(final IWorkbenchWindow window, final IWorkbenchPart sourceEditor,
132             final Resource sourceDiagram, final Resource target) {
133         Simantics.getSession().asyncRequest(new ReadRequest() {
134             @Override
135             public void run(ReadGraph graph) throws DatabaseException {
136                 navigateToTarget(graph, window, sourceEditor, sourceDiagram, target);
137             }
138         }, new ProcedureAdapter<Object>() {
139             public void exception(Throwable t) {
140                 ErrorLogger.defaultLogError(t);
141             }
142         });
143     }
144
145     /**
146      * @param graph
147      * @param window
148      * @param sourceEditor
149      * @param sourceDiagram
150      * @param target
151      * @return null if everything's OK
152      * @throws DatabaseException
153      */
154     protected String navigateToTarget(ReadGraph graph, IWorkbenchWindow window, IWorkbenchPart sourceEditor,
155             Resource sourceDiagram, final Resource target) throws DatabaseException {
156         ModelingResources MOD = ModelingResources.getInstance(graph);
157
158         final Resource otherDiagram = getOwnerList(graph, target);
159         if (otherDiagram == null)
160             return "";
161
162         if (!sourceDiagram.equals(otherDiagram)) {
163
164             // Find the structural path
165             Resource otherComposite = graph.getPossibleObject(otherDiagram, MOD.DiagramToComposite);
166             if (otherComposite == null)
167                 return "";
168
169             Variable compositeVariable = Variables.getVariable(graph, otherComposite);
170             final Resource model = Variables.getPossibleModel(graph, compositeVariable);
171             final RVI rvi = model == null ? null : compositeVariable.getPossibleRVI(graph);
172             if (model == null || rvi == null)
173                 return "Navigating via flags only possible under model configuration";
174
175             window.getShell().getDisplay().asyncExec(
176                     editorActivator(sourceEditor, otherDiagram, model, rvi, part -> {
177                         final ICanvasContext openedCanvas = (ICanvasContext) part.getAdapter(ICanvasContext.class);
178                         assert openedCanvas != null;
179                         // CanvasContext-wide denial of initial zoom-to-fit on diagram open.
180                         openedCanvas.getDefaultHintContext().setHint(DiagramHints.KEY_INITIAL_ZOOM_TO_FIT, Boolean.FALSE);
181                         ThreadUtils.asyncExec( openedCanvas.getThreadAccess(),
182                                 elementSelectorZoomer( openedCanvas, Collections.<Object>singleton( target ), false ) );
183                     }));
184         } else {
185             ICanvasContext canvas = (ICanvasContext) sourceEditor.getAdapter( ICanvasContext.class );
186             assert canvas != null;
187             ThreadUtils.asyncExec( canvas.getThreadAccess(),
188                     elementSelectorZoomer( canvas, Collections.<Object>singleton( target ), true ));
189         }
190         return null;
191     }
192
193     protected void asyncQueryTarget(final Shell parentShell, ReadGraph graph, Resource sourceDiagram,
194             Set<Resource> counterparts, final Consumer<Resource> navigationCallback) throws DatabaseException {
195         ModelingResources MOD = ModelingResources.getInstance(graph);
196         StructuralResource2 STR = StructuralResource2.getInstance(graph);
197         ConnectionUtil cu = new ConnectionUtil(graph);
198
199         final List<NamedResource> outputs = new ArrayList<NamedResource>(counterparts.size());
200         final List<NamedResource> inputs = new ArrayList<NamedResource>(counterparts.size());
201         for (Resource counterpart : counterparts) {
202             final Resource diagram = getOwnerList(graph, counterpart);
203             if (diagram == null)
204                 continue;
205
206             Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
207             if (composite == null)
208                 continue;
209             Variable v = Variables.getPossibleVariable(graph, composite);
210             if (v == null)
211                 continue;
212
213             String rvi = Variables.getRVI(graph, v);
214             rvi = URIStringUtils.unescape(rvi);
215
216             Resource connectedComponent = null;
217
218             for (Resource connector : graph.getObjects(counterpart, STR.IsConnectedTo)) {
219                 Resource diagramConnection = ConnectionUtil.tryGetConnection(graph, connector);
220                 if (diagramConnection != null) {
221                     Collection<Resource> connectors = cu.getConnectors(diagramConnection, new HashSet<Resource>());
222                     connectors.remove(connector);
223                     if (connectors.isEmpty()) {
224                         continue;
225                     } else {
226                         connectedComponent = graph.getPossibleObject(diagramConnection, MOD.ElementToComponent);
227                         if (connectedComponent == null) {
228                             for (Resource conn : connectors) {
229                                 Resource element = cu.getConnectedComponent(diagramConnection, conn);
230                                 if (element == null)
231                                     continue;
232                                 connectedComponent = graph.getPossibleObject(element, MOD.ElementToComponent);
233                                 if (connectedComponent != null)
234                                     break;
235                             }
236                         }
237                     }
238                 }
239             }
240             if (connectedComponent != null) {
241                 rvi += Variables.Role.CHILD.getIdentifier();
242                 rvi += NameUtils.getSafeName(graph, connectedComponent);
243             }
244
245             boolean localFlag = sourceDiagram.equals(diagram);
246             if (localFlag) {
247                 Layer0 L0 = Layer0.getInstance(graph);
248                 rvi += " (local";
249                 String label = graph.getPossibleRelatedValue2(counterpart, L0.HasLabel, Bindings.STRING);
250                 if (label != null && !label.isEmpty())
251                     rvi += " " + label;
252                 rvi += ")";
253             }
254
255             FlagClass.Type type = FlagUtil.getFlagType(graph, counterpart, FlagClass.Type.In);
256             switch (type) {
257             case In:
258                 inputs.add( new NamedResource(rvi, counterpart) );
259                 break;
260             case Out:
261                 outputs.add( new NamedResource(rvi, counterpart) );
262                 break;
263             }
264         }
265
266         // To make result ordering stable and logical
267         Collections.sort(outputs, COMPARATOR);
268         Collections.sort(inputs, COMPARATOR);
269
270         outputs.addAll(inputs);
271         if (outputs.isEmpty())
272             return;
273
274         parentShell.getDisplay().asyncExec(new Runnable() {
275             @Override
276             public void run() {
277                 if (parentShell.isDisposed())
278                     return;
279                 NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog(
280                         parentShell, outputs.toArray(new NamedResource[0]),
281                         "Choose Navigation Target",
282                         "Select single navigation target from list");
283                 if (dialog.open() != Window.OK)
284                     return;
285                 if (dialog.getSelection() != null)
286                     navigationCallback.accept( dialog.getSelection().getResource() );
287             }
288         });
289     }
290
291     private static final Comparator<? super NamedResource> COMPARATOR = new Comparator<NamedResource>() {
292         @Override
293         public int compare(NamedResource o1, NamedResource o2) {
294             return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());
295         }
296     };
297
298     public static Runnable editorActivator(final IWorkbenchPart part, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {
299         String sourcePartId = part.getSite().getId();
300         return editorActivator(sourcePartId, diagram, model, rvi, successCallback);
301     }
302
303     public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {
304         return () -> {
305             try {
306                 // open and activate new editor
307                 IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, createEditorInput(editorPartId, diagram, model, rvi));
308                 newPart.getSite().getPage().activate(newPart);
309                 successCallback.accept(newPart);
310             } catch (PartInitException e) {
311                 ErrorLogger.defaultLogError(e);
312             }
313         };
314     }
315     
316     private static IEditorInput createEditorInput(String editorPartId, Resource diagram, Resource model, RVI rvi) {
317          if (model != null)
318                 return new ResourceEditorInput2(editorPartId, diagram, model, rvi);
319          else
320                 return new ResourceEditorInput(editorPartId, diagram);
321     }
322
323     /**
324      * @param editorPartId
325      * @param diagram
326      * @param model
327      * @param rvi
328      * @param successCallback
329      * @return
330      * @deprecated use {@link #editorActivator(String, Resource, Resource, RVI, Callback)} instead
331      */
332     @Deprecated
333     public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final String rvi, final Consumer<IEditorPart> successCallback) {
334         return () -> {
335             try {
336                 // open and activate new editor
337                 IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, new ResourceEditorInput2(editorPartId, diagram, model, rvi));
338                 newPart.getSite().getPage().activate(newPart);
339                 successCallback.accept(newPart);
340             } catch (PartInitException e) {
341                 ErrorLogger.defaultLogError(e);
342             }
343         };
344     }
345     
346     public static Runnable elementSelectorZoomer(final ICanvasContext canvas, final Collection<? extends Object> elementObjects, final boolean keepZoom) {
347         return new Runnable() {
348             int tries = 0;
349             @Override
350             public void run() {
351                 //System.out.println(tries + ": elementSelectorZoomer: " + canvas.isDisposed() + ", " + elementObjects);
352                 if (canvas.isDisposed())
353                     return;
354
355                 // This will prevent eternal looping in unexpected situations.
356                 if (++tries > 10) {
357                     ErrorLogger.defaultLog(new Status(IStatus.INFO, "",
358                             "NavigateToTarget.elementSelectorZoomer failed to find any of the requested elements "
359                                     + elementObjects + ". Giving up."));
360                     return;
361                 }
362
363                 IDiagram diagram = canvas.getHintStack().getHint(DiagramHints.KEY_DIAGRAM);
364                 if (diagram == null || !zoomToSelection(canvas, diagram, selectElement(canvas, diagram, elementObjects), keepZoom)) {
365                     // Reschedule for later in hopes that initialization is complete.
366                     ThreadUtils.getNonBlockingWorkExecutor().schedule(this, 200, TimeUnit.MILLISECONDS);
367                 }
368             }
369         };
370     }
371
372     public static Set<IElement> selectElement(final ICanvasContext canvas, final IDiagram diagram, final Collection<? extends Object> elementObjects) {
373         // Select element
374         Set<IElement> selection = new HashSet<IElement>(elementObjects.size());
375         DataElementMap dataMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
376         for (Object obj : elementObjects) {
377             IElement element = dataMap.getElement(diagram, obj);
378             if (element != null) {
379                 selection.add(element);
380             }
381         }
382         if (!selection.isEmpty()) {
383             for (Selection s : canvas.getItemsByClass(Selection.class)) {
384                 s.setSelection(0, selection);
385             }
386         }
387         return selection;
388     }
389
390     public static boolean zoomToSelection(final ICanvasContext canvas, final IDiagram diagram, Set<IElement> selection, final boolean keepZoom) {
391         final TransformUtil util = canvas.getSingleItem(TransformUtil.class);
392         CanvasBoundsParticipant boundsParticipant = canvas.getAtMostOneItemOfClass(CanvasBoundsParticipant.class);
393         if (boundsParticipant == null)
394             return false;
395
396         final Rectangle2D controlBounds = boundsParticipant.getControlBounds().getFrame();
397         if (controlBounds == null || controlBounds.isEmpty())
398             return false;
399
400         Rectangle2D diagramRect = ElementUtils.getSurroundingElementBoundsOnDiagram(selection);
401         if (diagramRect == null)
402             return false;
403
404         ThreadUtils.asyncExec(canvas.getThreadAccess(), new Runnable() {
405             @Override
406             public void run() {
407                 if (canvas.isDisposed())
408                     return;
409
410                 // Make sure that even empty bounds can be zoomed into.
411                 org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 1);
412
413                 if (keepZoom) {
414                     double scaleFactor = GeometryUtils.getScale(util.getTransform());
415                     double cwh = controlBounds.getWidth() / (scaleFactor*2);
416                     double chh = controlBounds.getHeight() / (scaleFactor*2);
417
418                     AffineTransform view = new AffineTransform();
419                     view.scale(scaleFactor, scaleFactor);
420                     view.translate(-diagramRect.getCenterX()+cwh, -diagramRect.getCenterY()+chh);
421
422                     util.setTransform(view);
423                 } else {
424                     MarginUtils.Margin margin = MarginUtils.marginOf(40, 0, 0);
425                     MarginUtils.Margins margins = new MarginUtils.Margins(margin, margin, margin, margin);
426                     util.fitArea(controlBounds, diagramRect, margins);
427                 }
428             }
429         });
430         return true;
431     }
432
433     public static Resource getOwnerList(ReadGraph g, Resource listElement) throws DatabaseException {
434         return OrderedSetUtils.getSingleOwnerList(g, listElement, DiagramResource.getInstance(g).Composite);
435     }
436
437 }