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