From 6e64e280f5eb25df3d2de751913bd20269f7ebcc Mon Sep 17 00:00:00 2001 From: jsimomaa Date: Thu, 21 Jun 2018 09:46:10 +0300 Subject: [PATCH] Generic OpenDiagramFromComponentAdapter gitlab #30 Change-Id: Ib17df6e2a29642aa2e98976e4ad8df0cafd4e72e --- bundles/org.simantics.modeling.ui/plugin.xml | 8 + .../OpenDiagramFromComponentAdapter.java | 372 ++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/OpenDiagramFromComponentAdapter.java diff --git a/bundles/org.simantics.modeling.ui/plugin.xml b/bundles/org.simantics.modeling.ui/plugin.xml index 578c752ba..039fed00a 100644 --- a/bundles/org.simantics.modeling.ui/plugin.xml +++ b/bundles/org.simantics.modeling.ui/plugin.xml @@ -1571,6 +1571,14 @@ id="org.simantics.modeling.ui.context"> --> + + + diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/OpenDiagramFromComponentAdapter.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/OpenDiagramFromComponentAdapter.java new file mode 100644 index 000000000..e015796c7 --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagramEditor/OpenDiagramFromComponentAdapter.java @@ -0,0 +1,372 @@ +package org.simantics.modeling.ui.diagramEditor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Consumer; + +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorPart; +import org.simantics.Simantics; +import org.simantics.databoard.util.URIStringUtils; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.NamedResource; +import org.simantics.db.common.request.ReadRequest; +import org.simantics.db.common.utils.NameUtils; +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.layer0.Layer0; +import org.simantics.modeling.ComponentUtils; +import org.simantics.modeling.ModelingResources; +import org.simantics.modeling.actions.NavigateToTarget; +import org.simantics.modeling.actions.NavigationTargetChooserDialog; +import org.simantics.modeling.ui.Activator; +import org.simantics.structural.stubs.StructuralResource2; +import org.simantics.ui.utils.ResourceAdaptionUtils; +import org.simantics.ui.workbench.editor.AbstractResourceEditorAdapter; +import org.simantics.utils.datastructures.MapSet; +import org.simantics.utils.datastructures.Pair; +import org.simantics.utils.strings.AlphanumComparator; +import org.simantics.utils.strings.EString; +import org.simantics.utils.threads.SWTThread; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.ui.AdaptionUtils; +import org.simantics.utils.ui.workbench.WorkbenchUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Tuukka Lehtonen + */ +public class OpenDiagramFromComponentAdapter extends AbstractResourceEditorAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(OpenDiagramFromComponentAdapter.class); + + private static final String EDITOR_ID = "org.simantics.modeling.ui.diagramEditor"; + + public OpenDiagramFromComponentAdapter() { + super("Open Diagram Containing This Component", Activator.SYMBOL_ICON); + } + + @Override + public boolean canHandle(ReadGraph graph, Object input) throws DatabaseException { + Pair p = tryGetResource(graph, input); + if (p == null) + return false; + Variable v = AdaptionUtils.adaptToSingle(input, Variable.class); + Collection rs = tryFindDiagram(graph, p.first, v, p.second); + return !rs.isEmpty(); + } + + private Pair tryGetResource(ReadGraph graph, Object input) throws DatabaseException { + Resource r = ResourceAdaptionUtils.toSingleResource(input); + if (r != null) + return Pair.make(r, ""); + Variable v = AdaptionUtils.adaptToSingle(input, Variable.class); + return findResource(graph, v); + } + + private Pair findResource(ReadGraph graph, Variable v) throws DatabaseException { + List path = null; + while (v != null) { + Resource r = v.getPossibleRepresents(graph); + if (r != null) { + String rvi = ""; + if (path != null) { + int pathLength = path.size(); + for (int i = 0; i < pathLength; i++) + path.set(i, URIStringUtils.escape(path.get(i))); + Collections.reverse(path); + rvi = EString.implode(path, "/"); + } + return Pair.make(r, rvi); + } + if (path == null) + path = new ArrayList<>(2); + path.add( v.getName(graph) ); + v = v.browsePossible(graph, "."); + } + return null; + } + + @Override + public void openEditor(final Object input) throws Exception { + final Display d = Display.getCurrent(); + if (d == null) + return; + + Simantics.getSession().syncRequest(new ReadRequest() { + @Override + public void run(ReadGraph graph) throws DatabaseException { + Pair r = tryGetResource(graph, input); + if (r == null) + return; + + Variable v = AdaptionUtils.adaptToSingle(input, Variable.class); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent resource URI: " + NameUtils.getURIOrSafeNameInternal(graph, r.first)); + LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent RVI: " + r.second); + LOGGER.debug(getClass().getSimpleName() + ".openEditor: input variable URI: " + (v != null ? v.getURI(graph) : "null")); + } + + final Collection rs = tryFindDiagram(graph, r.first, v, r.second); + if (rs.isEmpty()) + return; + + SWTThread.getThreadAccess(d).asyncExec(() -> rs.forEach(Runnable::run)); + } + }); + } + + private Collection tryFindDiagram(ReadGraph g, Resource component, Variable variable, String rviFromComponent) throws DatabaseException { + try { + return findDiagram(g, component, variable, rviFromComponent); + } catch (DatabaseException e) { + return Collections.emptyList(); + } + } + + private Collection findDiagram(ReadGraph g, Resource component, Variable variable, String rviFromComponent) throws DatabaseException { + Layer0 l0 = Layer0.getInstance(g); + StructuralResource2 STR = StructuralResource2.getInstance(g); + ModelingResources MOD = ModelingResources.getInstance(g); + + if (g.isInstanceOf(component, STR.Component)) { + Collection result = new ArrayList<>(1); + + Resource composite = g.getSingleObject(component, l0.PartOf); + Resource diagram = ComponentUtils.getPossibleCompositeDiagram(g, composite); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: component: " + NameUtils.getURIOrSafeNameInternal(g, component)); + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: composite: " + NameUtils.getURIOrSafeNameInternal(g, composite)); + } + + Collection referenceElements = diagram == null ? g.getObjects(component, MOD.HasParentComponent_Inverse) : Collections.emptyList(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: diagram: " + NameUtils.getURIOrSafeNameInternal(g, diagram)); + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: referenceElements: " + referenceElements.size()); + for (Object object : referenceElements) + LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); + } + if (diagram == null && referenceElements.isEmpty()) + return Collections.emptyList(); + + Variable compositeVariable = Variables.getPossibleVariable(g, composite); + if (compositeVariable == null) + return Collections.emptyList(); + final Resource indexRoot = Variables.getPossibleIndexRoot(g, compositeVariable); + if (indexRoot == null) + return Collections.emptyList(); + if (LOGGER.isDebugEnabled()) + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: Model: " + indexRoot); + + if (diagram != null) { + if(OpenDiagramFromConfigurationAdapter.isLocked(g, diagram)) + return Collections.emptyList(); + + RVI rvi = null; + boolean allowNullRvi = false; + if (variable != null) { + // Get proper RVI from variable if it exists. + Variable context = Variables.getPossibleContext(g, variable); + if (context != null) { + // We want the composite's RVI, not the component in it. + Variable parent = findFirstParentComposite(g, variable); + if (parent != null) { + rvi = parent.getPossibleRVI(g); + if (LOGGER.isDebugEnabled()) + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI: " + rvi); + } + } + } else { + allowNullRvi = true; + rvi = compositeVariable.getPossibleRVI(g); + if (LOGGER.isDebugEnabled()) + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI from resource path: " + rvi); + } + if (rvi == null && !allowNullRvi) + return Collections.emptyList(); + + Collection selectedObjects = findElementObjects(g, component, rviFromComponent); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: selected objects: " + selectedObjects.size()); + for (Object object : selectedObjects) + LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); + } + // Prevent diagram from opening if there's nothing to select + // on the diagram based on the received input. + if (!selectedObjects.isEmpty()) + result.add( NavigateToTarget.editorActivator(EDITOR_ID, diagram, indexRoot, rvi, editorActivationCallback(selectedObjects)) ); + } else { + final MapSet referencingDiagrams = listReferenceDiagrams(g, referenceElements); + final Set diagrams = referencingDiagrams.getKeys(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(getClass().getSimpleName() + ".findDiagram: selected objects: " + diagrams.size()); + for (NamedResource d : diagrams) { + LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, d.getResource()) + ":"); + for (Resource referenceElement : referencingDiagrams.getValues(d)) { + LOGGER.debug("\t\t" + NameUtils.getURIOrSafeNameInternal(g, referenceElement)); + } + } + } + switch (diagrams.size()) { + case 0: + // Prevent diagram from opening if there's nothing to select + // on the diagram based on the received input. + break; + + case 1: + // Open the one diagram straight away. + NamedResource singleDiagram = diagrams.iterator().next(); + RVI rvi = getDiagramCompositeRvi(g, singleDiagram.getResource()); + if (rvi != null) { + Collection selectedObjects = referencingDiagrams.getValues(singleDiagram); + result.add( NavigateToTarget.editorActivator(EDITOR_ID, singleDiagram.getResource(), indexRoot, rvi, editorActivationCallback(selectedObjects)) ); + } + break; + + default: + final Map diagramToRvi = new TreeMap<>(COMPARATOR); + for (NamedResource d : diagrams) { + RVI rvi2 = getDiagramCompositeRvi(g, d.getResource()); + if (rvi2 != null) + diagramToRvi.put(d, rvi2); + } + result.add(() -> { + NamedResource selected = queryTarget(WorkbenchUtils.getActiveWorkbenchWindowShell(), diagramToRvi.keySet()); + if (selected != null) { + Collection selectedObjects = referencingDiagrams.getValues(selected); + RVI drvi = diagramToRvi.get(selected); + NavigateToTarget.editorActivator(EDITOR_ID, selected.getResource(), indexRoot, drvi, editorActivationCallback(selectedObjects)).run(); + } + }); + break; + } + } + return result; + } + + // Nothing to open + return Collections.emptyList(); + } + + private RVI getDiagramCompositeRvi(ReadGraph graph, Resource diagram) throws DatabaseException { + ModelingResources MOD = ModelingResources.getInstance(graph); + Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite); + if (composite == null) + return null; + Variable v = Variables.getPossibleVariable(graph, composite); + return v != null ? v.getPossibleRVI(graph) : null; + } + + private Consumer editorActivationCallback(final Collection selectedObjects) { + return 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(), + NavigateToTarget.elementSelectorZoomer(openedCanvas, selectedObjects, false)); + }; + } + + private Variable findFirstParentComposite(ReadGraph graph, Variable v) throws DatabaseException { + Variable first = findFirstWithRepresentation(graph, v); + if (first == null) + return null; + Variable parent = first.getParent(graph); + return parent; + } + + private Variable findFirstWithRepresentation(ReadGraph graph, Variable v) throws DatabaseException { + while (v != null) { + Resource represents = v.getPossibleRepresents(graph); + if (LOGGER.isDebugEnabled()) + LOGGER.debug(v.getURI(graph) + " -> " + NameUtils.getURIOrSafeNameInternal(graph, represents)); + if (represents != null) + return v; + v = v.getParent(graph); + } + return null; + } + + public static Collection findElementObjects(ReadGraph g, Resource component, String rviFromComponent) throws DatabaseException { + DiagramResource DIA = DiagramResource.getInstance(g); + ModelingResources MOD = ModelingResources.getInstance(g); + final Collection selectedObjects = new ArrayList<>(4); + if (rviFromComponent.isEmpty()) { + // The selected objects are configuration objects + for (Resource element : g.getObjects(component, MOD.ComponentToElement)) { + if (g.isInstanceOf(element, DIA.Flag) && FlagUtil.isExternal(g, element)) { + // Use external flag primarily if one exists in the correspondences + selectedObjects.clear(); + selectedObjects.add(element); + break; + } else if (g.isInstanceOf(element, DIA.RouteGraphConnection)) { + selectedObjects.add(element); + } else if (g.isInstanceOf(element, DIA.Connection)) { + // Okay, we need to find a part of the connection + ConnectionUtil cu = new ConnectionUtil(g); + cu.gatherConnectionParts(element, selectedObjects); + } else { + selectedObjects.add(element); + } + } + } + return selectedObjects; + } + + protected MapSet listReferenceDiagrams(ReadGraph graph, Collection referenceElements) throws DatabaseException { + ModelingResources MOD = ModelingResources.getInstance(graph); + + // Make result diagram ordering stable and logical by using our own comparator. + MapSet diagrams = new MapSet.Tree<>(COMPARATOR); + + for (Resource referenceElement : referenceElements) { + final Resource diagram = NavigateToTarget.getOwnerList(graph, referenceElement); + 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 = URIStringUtils.unescape( Variables.getRVI(graph, v) ); + + diagrams.add(new NamedResource(rvi, diagram), referenceElement); + } + + return diagrams; + } + + private static final Comparator COMPARATOR = + (o1, o2) -> AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName()); + + protected NamedResource queryTarget(final Shell parentShell, Collection options) { + NavigationTargetChooserDialog dialog = new NavigationTargetChooserDialog( + parentShell, options.toArray(new NamedResource[options.size()]), + "Choose Diagram with Component Reference", + "Select single diagram from list"); + return dialog.open() != Window.OK ? null : dialog.getSelection(); + } + +} -- 2.47.1