]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - 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
index 0599267ace8936f339e7d4c839826cb70c4e1d44..c35852d2a9db2a5803836aaa2f508e67e0eabc35 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.modeling.actions;\r
-\r
-import java.awt.Shape;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Set;\r
-import java.util.concurrent.TimeUnit;\r
-import java.util.function.Consumer;\r
-\r
-import org.eclipse.core.runtime.IStatus;\r
-import org.eclipse.core.runtime.Status;\r
-import org.eclipse.jface.action.IStatusLineManager;\r
-import org.eclipse.jface.window.Window;\r
-import org.eclipse.swt.widgets.Shell;\r
-import org.eclipse.ui.IEditorInput;\r
-import org.eclipse.ui.IEditorPart;\r
-import org.eclipse.ui.IWorkbenchPart;\r
-import org.eclipse.ui.IWorkbenchWindow;\r
-import org.eclipse.ui.PartInitException;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.util.URIStringUtils;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.common.NamedResource;\r
-import org.simantics.db.common.procedure.adapter.ProcedureAdapter;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.common.utils.OrderedSetUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.variable.RVI;\r
-import org.simantics.db.layer0.variable.Variable;\r
-import org.simantics.db.layer0.variable.Variables;\r
-import org.simantics.diagram.content.ConnectionUtil;\r
-import org.simantics.diagram.flag.FlagUtil;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.diagram.DiagramHints;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.handler.DataElementMap;\r
-import org.simantics.g2d.diagram.participant.Selection;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.elementclass.FlagClass;\r
-import org.simantics.g2d.participant.CanvasBoundsParticipant;\r
-import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.g2d.utils.GeometryUtils;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.layer0.utils.operations.Operation;\r
-import org.simantics.modeling.ModelingOperationConstants;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.modeling.utils.Monitors;\r
-import org.simantics.structural.stubs.StructuralResource2;\r
-import org.simantics.ui.workbench.ResourceEditorInput;\r
-import org.simantics.ui.workbench.ResourceEditorInput2;\r
-import org.simantics.utils.datastructures.persistent.IContextMap;\r
-import org.simantics.utils.page.MarginUtils;\r
-import org.simantics.utils.strings.AlphanumComparator;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-import org.simantics.utils.ui.ErrorLogger;\r
-import org.simantics.utils.ui.workbench.WorkbenchUtils;\r
-\r
-public class NavigateToTarget extends Operation {\r
-\r
-    public NavigateToTarget() {\r
-        super("Navigate to Target");\r
-    }\r
-\r
-    @Override\r
-    public void exec(Session session, IContextMap parameters) {\r
-        final Resource r = (Resource) parameters.get(SUBJECT);\r
-        final IWorkbenchWindow window = (IWorkbenchWindow) parameters.get(ModelingOperationConstants.WORKBENCH_WINDOW);\r
-        final IWorkbenchPart part = (IWorkbenchPart) parameters.get(ModelingOperationConstants.WORKBENCH_PART);\r
-\r
-        session.asyncRequest(new ReadRequest() {\r
-            @Override\r
-            public void run(ReadGraph g) throws DatabaseException {\r
-                final Resource thisDiagram = getOwnerList(g, r);\r
-                if (thisDiagram == null)\r
-                    return;\r
-\r
-                Set<Resource> counterparts = FlagUtil.getCounterparts(g, r);\r
-                if (counterparts.size() > 1) {\r
-                    // Ask user which target to navigate to.\r
-                    asyncQueryTarget(window.getShell(), g, thisDiagram, counterparts,\r
-                            target -> navigateToTarget( window, part, thisDiagram, target)); \r
-                } else if (counterparts.size() == 1) {\r
-                    // Target is defined.\r
-                    final String error = navigateToTarget( g, window, part, thisDiagram, counterparts.iterator().next() );\r
-                    if (error != null && !error.isEmpty()) {\r
-                        window.getShell().getDisplay().asyncExec(new Runnable() {\r
-                            @Override\r
-                            public void run() {\r
-                                IStatusLineManager status = WorkbenchUtils.getStatusLine(part);\r
-                                status.setErrorMessage(error);\r
-                            }\r
-                        });\r
-                    }\r
-                } else {\r
-                    // Try other methods of getting a navigation target besides flags.\r
-                    Resource target = Monitors.getMonitoredElement(g, r);\r
-                    if (target != null)\r
-                        navigateToTarget( g, window, part, thisDiagram, target );\r
-                }\r
-            }\r
-        }, new ProcedureAdapter<Object>() {\r
-            @Override\r
-            public void exception(Throwable t) {\r
-                ErrorLogger.defaultLogError(t);\r
-            }\r
-        });\r
-    }\r
-\r
-    protected void navigateToTarget(final IWorkbenchWindow window, final IWorkbenchPart sourceEditor,\r
-            final Resource sourceDiagram, final Resource target) {\r
-        Simantics.getSession().asyncRequest(new ReadRequest() {\r
-            @Override\r
-            public void run(ReadGraph graph) throws DatabaseException {\r
-                navigateToTarget(graph, window, sourceEditor, sourceDiagram, target);\r
-            }\r
-        }, new ProcedureAdapter<Object>() {\r
-            public void exception(Throwable t) {\r
-                ErrorLogger.defaultLogError(t);\r
-            }\r
-        });\r
-    }\r
-\r
-    /**\r
-     * @param graph\r
-     * @param window\r
-     * @param sourceEditor\r
-     * @param sourceDiagram\r
-     * @param target\r
-     * @return null if everything's OK\r
-     * @throws DatabaseException\r
-     */\r
-    protected String navigateToTarget(ReadGraph graph, IWorkbenchWindow window, IWorkbenchPart sourceEditor,\r
-            Resource sourceDiagram, final Resource target) throws DatabaseException {\r
-        ModelingResources MOD = ModelingResources.getInstance(graph);\r
-\r
-        final Resource otherDiagram = getOwnerList(graph, target);\r
-        if (otherDiagram == null)\r
-            return "";\r
-\r
-        if (!sourceDiagram.equals(otherDiagram)) {\r
-\r
-            // Find the structural path\r
-            Resource otherComposite = graph.getPossibleObject(otherDiagram, MOD.DiagramToComposite);\r
-            if (otherComposite == null)\r
-                return "";\r
-\r
-            Variable compositeVariable = Variables.getVariable(graph, otherComposite);\r
-            final Resource model = Variables.getPossibleModel(graph, compositeVariable);\r
-            final RVI rvi = model == null ? null : compositeVariable.getPossibleRVI(graph);\r
-            if (model == null || rvi == null)\r
-                return "Navigating via flags only possible under model configuration";\r
-\r
-            window.getShell().getDisplay().asyncExec(\r
-                    editorActivator(sourceEditor, otherDiagram, model, rvi, part -> {\r
-                        final ICanvasContext openedCanvas = (ICanvasContext) part.getAdapter(ICanvasContext.class);\r
-                        assert openedCanvas != null;\r
-                        // CanvasContext-wide denial of initial zoom-to-fit on diagram open.\r
-                        openedCanvas.getDefaultHintContext().setHint(DiagramHints.KEY_INITIAL_ZOOM_TO_FIT, Boolean.FALSE);\r
-                        ThreadUtils.asyncExec( openedCanvas.getThreadAccess(),\r
-                                elementSelectorZoomer( openedCanvas, Collections.<Object>singleton( target ), false ) );\r
-                    }));\r
-        } else {\r
-            ICanvasContext canvas = (ICanvasContext) sourceEditor.getAdapter( ICanvasContext.class );\r
-            assert canvas != null;\r
-            ThreadUtils.asyncExec( canvas.getThreadAccess(),\r
-                    elementSelectorZoomer( canvas, Collections.<Object>singleton( target ), true ));\r
-        }\r
-        return null;\r
-    }\r
-\r
-    protected void asyncQueryTarget(final Shell parentShell, ReadGraph graph, Resource sourceDiagram,\r
-            Set<Resource> counterparts, final Consumer<Resource> navigationCallback) throws DatabaseException {\r
-        ModelingResources MOD = ModelingResources.getInstance(graph);\r
-        StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
-        ConnectionUtil cu = new ConnectionUtil(graph);\r
-\r
-        final List<NamedResource> outputs = new ArrayList<NamedResource>(counterparts.size());\r
-        final List<NamedResource> inputs = new ArrayList<NamedResource>(counterparts.size());\r
-        for (Resource counterpart : counterparts) {\r
-            final Resource diagram = getOwnerList(graph, counterpart);\r
-            if (diagram == null)\r
-                continue;\r
-\r
-            Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);\r
-            if (composite == null)\r
-                continue;\r
-            Variable v = Variables.getPossibleVariable(graph, composite);\r
-            if (v == null)\r
-                continue;\r
-\r
-            String rvi = Variables.getRVI(graph, v);\r
-            rvi = URIStringUtils.unescape(rvi);\r
-\r
-            Resource connectedComponent = null;\r
-\r
-            for (Resource connector : graph.getObjects(counterpart, STR.IsConnectedTo)) {\r
-                Resource diagramConnection = ConnectionUtil.tryGetConnection(graph, connector);\r
-                if (diagramConnection != null) {\r
-                    Collection<Resource> connectors = cu.getConnectors(diagramConnection, new HashSet<Resource>());\r
-                    connectors.remove(connector);\r
-                    if (connectors.isEmpty()) {\r
-                        continue;\r
-                    } else {\r
-                        connectedComponent = graph.getPossibleObject(diagramConnection, MOD.ElementToComponent);\r
-                        if (connectedComponent == null) {\r
-                            for (Resource conn : connectors) {\r
-                                Resource element = cu.getConnectedComponent(diagramConnection, conn);\r
-                                if (element == null)\r
-                                    continue;\r
-                                connectedComponent = graph.getPossibleObject(element, MOD.ElementToComponent);\r
-                                if (connectedComponent != null)\r
-                                    break;\r
-                            }\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-            if (connectedComponent != null) {\r
-                rvi += Variables.Role.CHILD.getIdentifier();\r
-                rvi += NameUtils.getSafeName(graph, connectedComponent);\r
-            }\r
-\r
-            boolean localFlag = sourceDiagram.equals(diagram);\r
-            if (localFlag) {\r
-                Layer0 L0 = Layer0.getInstance(graph);\r
-                rvi += " (local";\r
-                String label = graph.getPossibleRelatedValue2(counterpart, L0.HasLabel, Bindings.STRING);\r
-                if (label != null && !label.isEmpty())\r
-                    rvi += " " + label;\r
-                rvi += ")";\r
-            }\r
-\r
-            FlagClass.Type type = FlagUtil.getFlagType(graph, counterpart, FlagClass.Type.In);\r
-            switch (type) {\r
-            case In:\r
-                inputs.add( new NamedResource(rvi, counterpart) );\r
-                break;\r
-            case Out:\r
-                outputs.add( new NamedResource(rvi, counterpart) );\r
-                break;\r
-            }\r
-        }\r
-\r
-        // To make result ordering stable and logical\r
-        Collections.sort(outputs, COMPARATOR);\r
-        Collections.sort(inputs, COMPARATOR);\r
-\r
-        outputs.addAll(inputs);\r
-        if (outputs.isEmpty())\r
-            return;\r
-\r
-        parentShell.getDisplay().asyncExec(new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                if (parentShell.isDisposed())\r
-                    return;\r
-                NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog(\r
-                        parentShell, outputs.toArray(new NamedResource[0]),\r
-                        "Choose Navigation Target",\r
-                        "Select single navigation target from list");\r
-                if (dialog.open() != Window.OK)\r
-                    return;\r
-                if (dialog.getSelection() != null)\r
-                    navigationCallback.accept( dialog.getSelection().getResource() );\r
-            }\r
-        });\r
-    }\r
-\r
-    private static final Comparator<? super NamedResource> COMPARATOR = new Comparator<NamedResource>() {\r
-        @Override\r
-        public int compare(NamedResource o1, NamedResource o2) {\r
-            return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());\r
-        }\r
-    };\r
-\r
-    public static Runnable editorActivator(final IWorkbenchPart part, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {\r
-        String sourcePartId = part.getSite().getId();\r
-        return editorActivator(sourcePartId, diagram, model, rvi, successCallback);\r
-    }\r
-\r
-    public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {\r
-        return () -> {\r
-            try {\r
-                // open and activate new editor\r
-                IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, createEditorInput(editorPartId, diagram, model, rvi));\r
-                newPart.getSite().getPage().activate(newPart);\r
-                successCallback.accept(newPart);\r
-            } catch (PartInitException e) {\r
-                ErrorLogger.defaultLogError(e);\r
-            }\r
-        };\r
-    }\r
-    \r
-    private static IEditorInput createEditorInput(String editorPartId, Resource diagram, Resource model, RVI rvi) {\r
-        if (model != null)\r
-               return new ResourceEditorInput2(editorPartId, diagram, model, rvi);\r
-         else\r
-               return new ResourceEditorInput(editorPartId, diagram);\r
-    }\r
-\r
-    /**\r
-     * @param editorPartId\r
-     * @param diagram\r
-     * @param model\r
-     * @param rvi\r
-     * @param successCallback\r
-     * @return\r
-     * @deprecated use {@link #editorActivator(String, Resource, Resource, RVI, Callback)} instead\r
-     */\r
-    @Deprecated\r
-    public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final String rvi, final Consumer<IEditorPart> successCallback) {\r
-        return () -> {\r
-            try {\r
-                // open and activate new editor\r
-                IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, new ResourceEditorInput2(editorPartId, diagram, model, rvi));\r
-                newPart.getSite().getPage().activate(newPart);\r
-                successCallback.accept(newPart);\r
-            } catch (PartInitException e) {\r
-                ErrorLogger.defaultLogError(e);\r
-            }\r
-        };\r
-    }\r
-    \r
-    public static Runnable elementSelectorZoomer(final ICanvasContext canvas, final Collection<? extends Object> elementObjects, final boolean keepZoom) {\r
-        return new Runnable() {\r
-            int tries = 0;\r
-            @Override\r
-            public void run() {\r
-                //System.out.println(tries + ": elementSelectorZoomer: " + canvas.isDisposed() + ", " + elementObjects);\r
-                if (canvas.isDisposed())\r
-                    return;\r
-\r
-                // This will prevent eternal looping in unexpected situations.\r
-                if (++tries > 10) {\r
-                    ErrorLogger.defaultLog(new Status(IStatus.INFO, "",\r
-                            "NavigateToTarget.elementSelectorZoomer failed to find any of the requested elements "\r
-                                    + elementObjects + ". Giving up."));\r
-                    return;\r
-                }\r
-\r
-                IDiagram diagram = canvas.getHintStack().getHint(DiagramHints.KEY_DIAGRAM);\r
-                if (diagram == null || !zoomToSelection(canvas, diagram, selectElement(canvas, diagram, elementObjects), keepZoom)) {\r
-                    // Reschedule for later in hopes that initialization is complete.\r
-                    ThreadUtils.getNonBlockingWorkExecutor().schedule(this, 200, TimeUnit.MILLISECONDS);\r
-                }\r
-            }\r
-        };\r
-    }\r
-\r
-    public static Set<IElement> selectElement(final ICanvasContext canvas, final IDiagram diagram, final Collection<? extends Object> elementObjects) {\r
-        // Select element\r
-        Set<IElement> selection = new HashSet<IElement>(elementObjects.size());\r
-        DataElementMap dataMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
-        for (Object obj : elementObjects) {\r
-            IElement element = dataMap.getElement(diagram, obj);\r
-            if (element != null) {\r
-                selection.add(element);\r
-            }\r
-        }\r
-        if (!selection.isEmpty()) {\r
-            for (Selection s : canvas.getItemsByClass(Selection.class)) {\r
-                s.setSelection(0, selection);\r
-            }\r
-        }\r
-        return selection;\r
-    }\r
-\r
-    public static boolean zoomToSelection(final ICanvasContext canvas, final IDiagram diagram, Set<IElement> selection, final boolean keepZoom) {\r
-        final TransformUtil util = canvas.getSingleItem(TransformUtil.class);\r
-        CanvasBoundsParticipant boundsParticipant = canvas.getAtMostOneItemOfClass(CanvasBoundsParticipant.class);\r
-        if (boundsParticipant == null)\r
-            return false;\r
-\r
-        final Rectangle2D controlBounds = boundsParticipant.getControlBounds().getFrame();\r
-        if (controlBounds == null || controlBounds.isEmpty())\r
-            return false;\r
-\r
-        final Shape shp = ElementUtils.getElementBoundsOnDiagram(selection);\r
-        if (shp == null)\r
-            return false;\r
-\r
-        ThreadUtils.asyncExec(canvas.getThreadAccess(), new Runnable() {\r
-            @Override\r
-            public void run() {\r
-                if (canvas.isDisposed())\r
-                    return;\r
-\r
-                Rectangle2D diagramRect = shp.getBounds2D();\r
-\r
-                // Make sure that even empty bounds can be zoomed into.\r
-                org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 1);\r
-\r
-                if (keepZoom) {\r
-                    double scaleFactor = GeometryUtils.getScale(util.getTransform());\r
-                    double cwh = controlBounds.getWidth() / (scaleFactor*2);\r
-                    double chh = controlBounds.getHeight() / (scaleFactor*2);\r
-\r
-                    AffineTransform view = new AffineTransform();\r
-                    view.scale(scaleFactor, scaleFactor);\r
-                    view.translate(-diagramRect.getCenterX()+cwh, -diagramRect.getCenterY()+chh);\r
-\r
-                    util.setTransform(view);\r
-                } else {\r
-                    MarginUtils.Margin margin = MarginUtils.marginOf(40, 0, 0);\r
-                    MarginUtils.Margins margins = new MarginUtils.Margins(margin, margin, margin, margin);\r
-                    util.fitArea(controlBounds, diagramRect, margins);\r
-                }\r
-            }\r
-        });\r
-        return true;\r
-    }\r
-\r
-    public static Resource getOwnerList(ReadGraph g, Resource listElement) throws DatabaseException {\r
-        return OrderedSetUtils.getSingleOwnerList(g, listElement, DiagramResource.getInstance(g).Composite);\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.modeling.actions;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.action.IStatusLineManager;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.util.URIStringUtils;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.common.NamedResource;
+import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.common.utils.OrderedSetUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.variable.RVI;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.layer0.variable.Variables;
+import org.simantics.diagram.content.ConnectionUtil;
+import org.simantics.diagram.flag.FlagUtil;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.DataElementMap;
+import org.simantics.g2d.diagram.participant.Selection;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.elementclass.FlagClass;
+import org.simantics.g2d.participant.CanvasBoundsParticipant;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.g2d.utils.GeometryUtils;
+import org.simantics.layer0.Layer0;
+import org.simantics.layer0.utils.operations.Operation;
+import org.simantics.modeling.ModelingOperationConstants;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.modeling.utils.Monitors;
+import org.simantics.structural.stubs.StructuralResource2;
+import org.simantics.ui.workbench.ResourceEditorInput;
+import org.simantics.ui.workbench.ResourceEditorInput2;
+import org.simantics.utils.datastructures.persistent.IContextMap;
+import org.simantics.utils.page.MarginUtils;
+import org.simantics.utils.strings.AlphanumComparator;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.ui.ErrorLogger;
+import org.simantics.utils.ui.workbench.WorkbenchUtils;
+
+public class NavigateToTarget extends Operation {
+
+    public NavigateToTarget() {
+        super("Navigate to Target");
+    }
+
+    @Override
+    public void exec(Session session, IContextMap parameters) {
+        final Resource r = (Resource) parameters.get(SUBJECT);
+        final IWorkbenchWindow window = (IWorkbenchWindow) parameters.get(ModelingOperationConstants.WORKBENCH_WINDOW);
+        final IWorkbenchPart part = (IWorkbenchPart) parameters.get(ModelingOperationConstants.WORKBENCH_PART);
+
+        session.asyncRequest(new ReadRequest() {
+            @Override
+            public void run(ReadGraph g) throws DatabaseException {
+                final Resource thisDiagram = getOwnerList(g, r);
+                if (thisDiagram == null)
+                    return;
+
+                Set<Resource> counterparts = FlagUtil.getCounterparts(g, r);
+                if (counterparts.size() > 1) {
+                    // Ask user which target to navigate to.
+                    asyncQueryTarget(window.getShell(), g, thisDiagram, counterparts,
+                            target -> navigateToTarget( window, part, thisDiagram, target)); 
+                } else if (counterparts.size() == 1) {
+                    // Target is defined.
+                    final String error = navigateToTarget( g, window, part, thisDiagram, counterparts.iterator().next() );
+                    if (error != null && !error.isEmpty()) {
+                        window.getShell().getDisplay().asyncExec(new Runnable() {
+                            @Override
+                            public void run() {
+                                IStatusLineManager status = WorkbenchUtils.getStatusLine(part);
+                                status.setErrorMessage(error);
+                            }
+                        });
+                    }
+                } else {
+                    // Try other methods of getting a navigation target besides flags.
+                    Resource target = Monitors.getMonitoredElement(g, r);
+                    if (target != null)
+                        navigateToTarget( g, window, part, thisDiagram, target );
+                }
+            }
+        }, new ProcedureAdapter<Object>() {
+            @Override
+            public void exception(Throwable t) {
+                ErrorLogger.defaultLogError(t);
+            }
+        });
+    }
+
+    protected void navigateToTarget(final IWorkbenchWindow window, final IWorkbenchPart sourceEditor,
+            final Resource sourceDiagram, final Resource target) {
+        Simantics.getSession().asyncRequest(new ReadRequest() {
+            @Override
+            public void run(ReadGraph graph) throws DatabaseException {
+                navigateToTarget(graph, window, sourceEditor, sourceDiagram, target);
+            }
+        }, new ProcedureAdapter<Object>() {
+            public void exception(Throwable t) {
+                ErrorLogger.defaultLogError(t);
+            }
+        });
+    }
+
+    /**
+     * @param graph
+     * @param window
+     * @param sourceEditor
+     * @param sourceDiagram
+     * @param target
+     * @return null if everything's OK
+     * @throws DatabaseException
+     */
+    protected String navigateToTarget(ReadGraph graph, IWorkbenchWindow window, IWorkbenchPart sourceEditor,
+            Resource sourceDiagram, final Resource target) throws DatabaseException {
+        ModelingResources MOD = ModelingResources.getInstance(graph);
+
+        final Resource otherDiagram = getOwnerList(graph, target);
+        if (otherDiagram == null)
+            return "";
+
+        if (!sourceDiagram.equals(otherDiagram)) {
+
+            // Find the structural path
+            Resource otherComposite = graph.getPossibleObject(otherDiagram, MOD.DiagramToComposite);
+            if (otherComposite == null)
+                return "";
+
+            Variable compositeVariable = Variables.getVariable(graph, otherComposite);
+            final Resource model = Variables.getPossibleModel(graph, compositeVariable);
+            final RVI rvi = model == null ? null : compositeVariable.getPossibleRVI(graph);
+            if (model == null || rvi == null)
+                return "Navigating via flags only possible under model configuration";
+
+            window.getShell().getDisplay().asyncExec(
+                    editorActivator(sourceEditor, otherDiagram, model, rvi, part -> {
+                        final ICanvasContext openedCanvas = (ICanvasContext) part.getAdapter(ICanvasContext.class);
+                        assert openedCanvas != null;
+                        // CanvasContext-wide denial of initial zoom-to-fit on diagram open.
+                        openedCanvas.getDefaultHintContext().setHint(DiagramHints.KEY_INITIAL_ZOOM_TO_FIT, Boolean.FALSE);
+                        ThreadUtils.asyncExec( openedCanvas.getThreadAccess(),
+                                elementSelectorZoomer( openedCanvas, Collections.<Object>singleton( target ), false ) );
+                    }));
+        } else {
+            ICanvasContext canvas = (ICanvasContext) sourceEditor.getAdapter( ICanvasContext.class );
+            assert canvas != null;
+            ThreadUtils.asyncExec( canvas.getThreadAccess(),
+                    elementSelectorZoomer( canvas, Collections.<Object>singleton( target ), true ));
+        }
+        return null;
+    }
+
+    protected void asyncQueryTarget(final Shell parentShell, ReadGraph graph, Resource sourceDiagram,
+            Set<Resource> counterparts, final Consumer<Resource> navigationCallback) throws DatabaseException {
+        ModelingResources MOD = ModelingResources.getInstance(graph);
+        StructuralResource2 STR = StructuralResource2.getInstance(graph);
+        ConnectionUtil cu = new ConnectionUtil(graph);
+
+        final List<NamedResource> outputs = new ArrayList<NamedResource>(counterparts.size());
+        final List<NamedResource> inputs = new ArrayList<NamedResource>(counterparts.size());
+        for (Resource counterpart : counterparts) {
+            final Resource diagram = getOwnerList(graph, counterpart);
+            if (diagram == null)
+                continue;
+
+            Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
+            if (composite == null)
+                continue;
+            Variable v = Variables.getPossibleVariable(graph, composite);
+            if (v == null)
+                continue;
+
+            String rvi = Variables.getRVI(graph, v);
+            rvi = URIStringUtils.unescape(rvi);
+
+            Resource connectedComponent = null;
+
+            for (Resource connector : graph.getObjects(counterpart, STR.IsConnectedTo)) {
+                Resource diagramConnection = ConnectionUtil.tryGetConnection(graph, connector);
+                if (diagramConnection != null) {
+                    Collection<Resource> connectors = cu.getConnectors(diagramConnection, new HashSet<Resource>());
+                    connectors.remove(connector);
+                    if (connectors.isEmpty()) {
+                        continue;
+                    } else {
+                        connectedComponent = graph.getPossibleObject(diagramConnection, MOD.ElementToComponent);
+                        if (connectedComponent == null) {
+                            for (Resource conn : connectors) {
+                                Resource element = cu.getConnectedComponent(diagramConnection, conn);
+                                if (element == null)
+                                    continue;
+                                connectedComponent = graph.getPossibleObject(element, MOD.ElementToComponent);
+                                if (connectedComponent != null)
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
+            if (connectedComponent != null) {
+                rvi += Variables.Role.CHILD.getIdentifier();
+                rvi += NameUtils.getSafeName(graph, connectedComponent);
+            }
+
+            boolean localFlag = sourceDiagram.equals(diagram);
+            if (localFlag) {
+                Layer0 L0 = Layer0.getInstance(graph);
+                rvi += " (local";
+                String label = graph.getPossibleRelatedValue2(counterpart, L0.HasLabel, Bindings.STRING);
+                if (label != null && !label.isEmpty())
+                    rvi += " " + label;
+                rvi += ")";
+            }
+
+            FlagClass.Type type = FlagUtil.getFlagType(graph, counterpart, FlagClass.Type.In);
+            switch (type) {
+            case In:
+                inputs.add( new NamedResource(rvi, counterpart) );
+                break;
+            case Out:
+                outputs.add( new NamedResource(rvi, counterpart) );
+                break;
+            }
+        }
+
+        // To make result ordering stable and logical
+        Collections.sort(outputs, COMPARATOR);
+        Collections.sort(inputs, COMPARATOR);
+
+        outputs.addAll(inputs);
+        if (outputs.isEmpty())
+            return;
+
+        parentShell.getDisplay().asyncExec(new Runnable() {
+            @Override
+            public void run() {
+                if (parentShell.isDisposed())
+                    return;
+                NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog(
+                        parentShell, outputs.toArray(new NamedResource[0]),
+                        "Choose Navigation Target",
+                        "Select single navigation target from list");
+                if (dialog.open() != Window.OK)
+                    return;
+                if (dialog.getSelection() != null)
+                    navigationCallback.accept( dialog.getSelection().getResource() );
+            }
+        });
+    }
+
+    private static final Comparator<? super NamedResource> COMPARATOR = new Comparator<NamedResource>() {
+        @Override
+        public int compare(NamedResource o1, NamedResource o2) {
+            return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());
+        }
+    };
+
+    public static Runnable editorActivator(final IWorkbenchPart part, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {
+        String sourcePartId = part.getSite().getId();
+        return editorActivator(sourcePartId, diagram, model, rvi, successCallback);
+    }
+
+    public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final RVI rvi, final Consumer<IEditorPart> successCallback) {
+        return () -> {
+            try {
+                // open and activate new editor
+                IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, createEditorInput(editorPartId, diagram, model, rvi));
+                newPart.getSite().getPage().activate(newPart);
+                successCallback.accept(newPart);
+            } catch (PartInitException e) {
+                ErrorLogger.defaultLogError(e);
+            }
+        };
+    }
+    
+    private static IEditorInput createEditorInput(String editorPartId, Resource diagram, Resource model, RVI rvi) {
+        if (model != null)
+               return new ResourceEditorInput2(editorPartId, diagram, model, rvi);
+         else
+               return new ResourceEditorInput(editorPartId, diagram);
+    }
+
+    /**
+     * @param editorPartId
+     * @param diagram
+     * @param model
+     * @param rvi
+     * @param successCallback
+     * @return
+     * @deprecated use {@link #editorActivator(String, Resource, Resource, RVI, Callback)} instead
+     */
+    @Deprecated
+    public static Runnable editorActivator(final String editorPartId, final Resource diagram, final Resource model, final String rvi, final Consumer<IEditorPart> successCallback) {
+        return () -> {
+            try {
+                // open and activate new editor
+                IEditorPart newPart = WorkbenchUtils.openEditor(editorPartId, new ResourceEditorInput2(editorPartId, diagram, model, rvi));
+                newPart.getSite().getPage().activate(newPart);
+                successCallback.accept(newPart);
+            } catch (PartInitException e) {
+                ErrorLogger.defaultLogError(e);
+            }
+        };
+    }
+    
+    public static Runnable elementSelectorZoomer(final ICanvasContext canvas, final Collection<? extends Object> elementObjects, final boolean keepZoom) {
+        return new Runnable() {
+            int tries = 0;
+            @Override
+            public void run() {
+                //System.out.println(tries + ": elementSelectorZoomer: " + canvas.isDisposed() + ", " + elementObjects);
+                if (canvas.isDisposed())
+                    return;
+
+                // This will prevent eternal looping in unexpected situations.
+                if (++tries > 10) {
+                    ErrorLogger.defaultLog(new Status(IStatus.INFO, "",
+                            "NavigateToTarget.elementSelectorZoomer failed to find any of the requested elements "
+                                    + elementObjects + ". Giving up."));
+                    return;
+                }
+
+                IDiagram diagram = canvas.getHintStack().getHint(DiagramHints.KEY_DIAGRAM);
+                if (diagram == null || !zoomToSelection(canvas, diagram, selectElement(canvas, diagram, elementObjects), keepZoom)) {
+                    // Reschedule for later in hopes that initialization is complete.
+                    ThreadUtils.getNonBlockingWorkExecutor().schedule(this, 200, TimeUnit.MILLISECONDS);
+                }
+            }
+        };
+    }
+
+    public static Set<IElement> selectElement(final ICanvasContext canvas, final IDiagram diagram, final Collection<? extends Object> elementObjects) {
+        // Select element
+        Set<IElement> selection = new HashSet<IElement>(elementObjects.size());
+        DataElementMap dataMap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
+        for (Object obj : elementObjects) {
+            IElement element = dataMap.getElement(diagram, obj);
+            if (element != null) {
+                selection.add(element);
+            }
+        }
+        if (!selection.isEmpty()) {
+            for (Selection s : canvas.getItemsByClass(Selection.class)) {
+                s.setSelection(0, selection);
+            }
+        }
+        return selection;
+    }
+
+    public static boolean zoomToSelection(final ICanvasContext canvas, final IDiagram diagram, Set<IElement> selection, final boolean keepZoom) {
+        final TransformUtil util = canvas.getSingleItem(TransformUtil.class);
+        CanvasBoundsParticipant boundsParticipant = canvas.getAtMostOneItemOfClass(CanvasBoundsParticipant.class);
+        if (boundsParticipant == null)
+            return false;
+
+        final Rectangle2D controlBounds = boundsParticipant.getControlBounds().getFrame();
+        if (controlBounds == null || controlBounds.isEmpty())
+            return false;
+
+        Rectangle2D diagramRect = ElementUtils.getSurroundingElementBoundsOnDiagram(selection);
+        if (diagramRect == null)
+            return false;
+
+        ThreadUtils.asyncExec(canvas.getThreadAccess(), new Runnable() {
+            @Override
+            public void run() {
+                if (canvas.isDisposed())
+                    return;
+
+                // Make sure that even empty bounds can be zoomed into.
+                org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(diagramRect, 1);
+
+                if (keepZoom) {
+                    double scaleFactor = GeometryUtils.getScale(util.getTransform());
+                    double cwh = controlBounds.getWidth() / (scaleFactor*2);
+                    double chh = controlBounds.getHeight() / (scaleFactor*2);
+
+                    AffineTransform view = new AffineTransform();
+                    view.scale(scaleFactor, scaleFactor);
+                    view.translate(-diagramRect.getCenterX()+cwh, -diagramRect.getCenterY()+chh);
+
+                    util.setTransform(view);
+                } else {
+                    MarginUtils.Margin margin = MarginUtils.marginOf(40, 0, 0);
+                    MarginUtils.Margins margins = new MarginUtils.Margins(margin, margin, margin, margin);
+                    util.fitArea(controlBounds, diagramRect, margins);
+                }
+            }
+        });
+        return true;
+    }
+
+    public static Resource getOwnerList(ReadGraph g, Resource listElement) throws DatabaseException {
+        return OrderedSetUtils.getSingleOwnerList(g, listElement, DiagramResource.getInstance(g).Composite);
+    }
+
+}