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