--- /dev/null
+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<Resource, String> p = tryGetResource(graph, input);
+ if (p == null)
+ return false;
+ Variable v = AdaptionUtils.adaptToSingle(input, Variable.class);
+ Collection<Runnable> rs = tryFindDiagram(graph, p.first, v, p.second);
+ return !rs.isEmpty();
+ }
+
+ private Pair<Resource, String> 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<Resource, String> findResource(ReadGraph graph, Variable v) throws DatabaseException {
+ List<String> 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<Resource, String> 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<Runnable> rs = tryFindDiagram(graph, r.first, v, r.second);
+ if (rs.isEmpty())
+ return;
+
+ SWTThread.getThreadAccess(d).asyncExec(() -> rs.forEach(Runnable::run));
+ }
+ });
+ }
+
+ private Collection<Runnable> 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<Runnable> 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<Runnable> 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<Resource> referenceElements = diagram == null ? g.getObjects(component, MOD.HasParentComponent_Inverse) : Collections.<Resource>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<Object> 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<NamedResource, Resource> referencingDiagrams = listReferenceDiagrams(g, referenceElements);
+ final Set<NamedResource> 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<Resource> selectedObjects = referencingDiagrams.getValues(singleDiagram);
+ result.add( NavigateToTarget.editorActivator(EDITOR_ID, singleDiagram.getResource(), indexRoot, rvi, editorActivationCallback(selectedObjects)) );
+ }
+ break;
+
+ default:
+ final Map<NamedResource, RVI> 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<Resource> 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<IEditorPart> editorActivationCallback(final Collection<? extends Object> 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<Object> findElementObjects(ReadGraph g, Resource component, String rviFromComponent) throws DatabaseException {
+ DiagramResource DIA = DiagramResource.getInstance(g);
+ ModelingResources MOD = ModelingResources.getInstance(g);
+ final Collection<Object> 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<NamedResource, Resource> listReferenceDiagrams(ReadGraph graph, Collection<Resource> referenceElements) throws DatabaseException {
+ ModelingResources MOD = ModelingResources.getInstance(graph);
+
+ // Make result diagram ordering stable and logical by using our own comparator.
+ MapSet<NamedResource, Resource> 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<? super NamedResource> COMPARATOR =
+ (o1, o2) -> AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName());
+
+ protected NamedResource queryTarget(final Shell parentShell, Collection<NamedResource> 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();
+ }
+
+}