]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - 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
diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/actions/NavigateToTarget.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/actions/NavigateToTarget.java
new file mode 100644 (file)
index 0000000..0599267
--- /dev/null
@@ -0,0 +1,440 @@
+/*******************************************************************************\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