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"; //$NON-NLS-1$ public OpenDiagramFromComponentAdapter() { super(Messages.OpenDiagramFromComponentAdapter_OpenDiagramContainingComponent, Activator.SYMBOL_ICON); } protected String getEditorId(ReadGraph g, Resource diagram) throws DatabaseException { ModelingResources MOD = ModelingResources.getInstance(g); String preferredEditorId = g.getPossibleRelatedValue(diagram, MOD.PreferredDiagramEditorID); if(preferredEditorId != null) return preferredEditorId; else return EDITOR_ID; } @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, ""); //$NON-NLS-1$ 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 = ""; //$NON-NLS-1$ 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, "/"); //$NON-NLS-1$ } return Pair.make(r, rvi); } if (path == null) path = new ArrayList<>(2); path.add( v.getName(graph) ); v = v.browsePossible(graph, "."); //$NON-NLS-1$ } 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)); //$NON-NLS-1$ LOGGER.debug(getClass().getSimpleName() + ".openEditor: input's nearest parent RVI: " + r.second); //$NON-NLS-1$ LOGGER.debug(getClass().getSimpleName() + ".openEditor: input variable URI: " + (v != null ? v.getURI(graph) : "null")); //$NON-NLS-1$ //$NON-NLS-2$ } 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); String editorId = getEditorId(g, composite); if (LOGGER.isDebugEnabled()) { LOGGER.debug(getClass().getSimpleName() + ".findDiagram: component: " + NameUtils.getURIOrSafeNameInternal(g, component)); //$NON-NLS-1$ LOGGER.debug(getClass().getSimpleName() + ".findDiagram: composite: " + NameUtils.getURIOrSafeNameInternal(g, composite)); //$NON-NLS-1$ } 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)); //$NON-NLS-1$ LOGGER.debug(getClass().getSimpleName() + ".findDiagram: referenceElements: " + referenceElements.size()); //$NON-NLS-1$ for (Object object : referenceElements) LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); //$NON-NLS-1$ } 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); //$NON-NLS-1$ 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); //$NON-NLS-1$ } } } else { allowNullRvi = true; rvi = compositeVariable.getPossibleRVI(g); if (LOGGER.isDebugEnabled()) LOGGER.debug(getClass().getSimpleName() + ".findDiagram: resolved RVI from resource path: " + rvi); //$NON-NLS-1$ } 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()); //$NON-NLS-1$ for (Object object : selectedObjects) LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, (Resource) object)); //$NON-NLS-1$ } // 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(editorId, 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()); //$NON-NLS-1$ for (NamedResource d : diagrams) { LOGGER.debug("\t" + NameUtils.getURIOrSafeNameInternal(g, d.getResource()) + ":"); //$NON-NLS-1$ //$NON-NLS-2$ for (Resource referenceElement : referencingDiagrams.getValues(d)) { LOGGER.debug("\t\t" + NameUtils.getURIOrSafeNameInternal(g, referenceElement)); //$NON-NLS-1$ } } } 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(editorId, 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(editorId, 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)); //$NON-NLS-1$ 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()]), Messages.OpenDiagramFromComponentAdapter_ChooseDiagramComponetReference, Messages.OpenDiagramFromComponentAdapter_SelectSingleDiagramfromList); return dialog.open() != Window.OK ? null : dialog.getSelection(); } }