From: Tuukka Lehtonen Date: Fri, 12 Oct 2018 06:42:40 +0000 (+0300) Subject: Disconnected subgraph analysis for district network diagrams X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=37304f4caf1d4252797cbaf7b40a56e212e203b4;p=simantics%2Fdistrict.git Disconnected subgraph analysis for district network diagrams * There is a new view called District Network Breakdown that can be used to see the different disconnected subnetworks inside each district network diagram. * The view allows selecting the selected subnetworks on the actual diagram to give the user an idea where they are * The algorithm to calculate the disconnected subnetworks comes through OSGi services (interface SubgraphProvider) and this change has no implementation for the provider yet. It is on the Apros side for now because the logic contains Apros-specific analysis. * The district network diagram can now be opened and focused at certain elements e.g. through CTRL+R open resource dialog by entering a district network diagram element's name and pressing enter. gitlab #11 gitlab #13 Change-Id: I98b9adfa589026530d84e65d5db08c0670349fa2 --- diff --git a/org.simantics.district.network.ui/META-INF/MANIFEST.MF b/org.simantics.district.network.ui/META-INF/MANIFEST.MF index 8db22872..a2884512 100644 --- a/org.simantics.district.network.ui/META-INF/MANIFEST.MF +++ b/org.simantics.district.network.ui/META-INF/MANIFEST.MF @@ -6,6 +6,7 @@ Bundle-Version: 1.0.0.qualifier Bundle-Activator: org.simantics.district.network.ui.internal.Activator Export-Package: org.simantics.district.network.ui, org.simantics.district.network.ui.adapters, + org.simantics.district.network.ui.breakdown, org.simantics.district.network.ui.function Require-Bundle: org.eclipse.e4.ui.model.workbench;bundle-version="1.1.100.v20150407-1430", org.eclipse.swt, @@ -26,7 +27,10 @@ Require-Bundle: org.eclipse.e4.ui.model.workbench;bundle-version="1.1.100.v20150 org.eclipse.e4.core.di, org.eclipse.e4.ui.di, org.eclipse.e4.core.commands, - org.eclipse.e4.core.contexts + org.eclipse.e4.core.contexts, + org.eclipse.jface, + org.simantics.scl.osgi Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: javax.inject;version="1.0.0" +Import-Package: javax.annotation;version="1.0.0";resolution:=optional, + javax.inject;version="1.0.0" Bundle-ActivationPolicy: lazy diff --git a/org.simantics.district.network.ui/plugin.xml b/org.simantics.district.network.ui/plugin.xml index db59f043..f33e345a 100644 --- a/org.simantics.district.network.ui/plugin.xml +++ b/org.simantics.district.network.ui/plugin.xml @@ -19,6 +19,20 @@ id="org.simantics.district.network.ui.diagrameditor"> + + + + + + + + diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictNetworkUIUtil.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictNetworkUIUtil.java new file mode 100644 index 00000000..9ce6937a --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictNetworkUIUtil.java @@ -0,0 +1,168 @@ +package org.simantics.district.network.ui; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Consumer; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IEditorPart; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.UnaryRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.request.PossibleModel; +import org.simantics.db.layer0.variable.RVI; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.layer0.variable.Variables; +import org.simantics.district.network.DistrictNetworkUtil; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.diagram.DiagramHints; +import org.simantics.layer0.Layer0; +import org.simantics.modeling.ModelingResources; +import org.simantics.modeling.actions.NavigateToTarget; +import org.simantics.scl.runtime.tuple.Tuple4; +import org.simantics.ui.selection.WorkbenchSelectionUtils; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.ui.ISelectionUtils; + +/** + * @author Tuukka Lehtonen + * @since 1.35.0 + */ +public class DistrictNetworkUIUtil { + + public static Resource getInputResource(ReadGraph graph, Object input) throws DatabaseException { + if (input instanceof Resource) { + return (Resource) input; + } else if (input instanceof Variable) { + return ((Variable) input).getPossibleRepresents(graph); + } else if (input instanceof ISelection) { + return ISelectionUtils.filterSingleSelection((ISelection) input, Resource.class); + } else { + return WorkbenchSelectionUtils.getPossibleResource(graph, input); + } + } + + public static class GetInputResource extends UnaryRead { + + public GetInputResource(Object input) { + super(input); + } + + @Override + public Resource perform(ReadGraph graph) throws DatabaseException { + return getInputResource(graph, parameter); + } + + } + + public static class Input extends Tuple4 { + public Input(Resource model, Resource diagram, Resource element, RVI diagramCompositeRvi) { + super(model, diagram, element, diagramCompositeRvi); + } + public Resource model() { + return (Resource) get(0); + } + public Resource diagram() { + return (Resource) get(1); + } + public Resource element() { + return (Resource) get(2); + } + public RVI rvi() { + return (RVI) get(3); + } + } + + public static class FindMappedDNElement extends UnaryRead { + + public FindMappedDNElement(Object parameter) { + super(parameter); + } + + @Override + public Input perform(ReadGraph graph) throws DatabaseException { + Resource e = DistrictNetworkUtil.getMappedDNElement(graph, + DistrictNetworkUtil.getDiagramElement(graph, + getInputResource(graph, parameter))); + return e != null ? graph.syncRequest(new ElementToInput(e)) : null; + } + + } + + public static class FindMappedComponent extends UnaryRead { + + public FindMappedComponent(Object parameter) { + super(parameter); + } + + @Override + public Input perform(ReadGraph graph) throws DatabaseException { + Resource e = DistrictNetworkUtil.getMappedElement(graph, + getInputResource(graph, parameter)); + return e != null ? graph.syncRequest(new ElementToInput(e)) : null; + } + + } + + public static class ElementToInput extends UnaryRead { + + public ElementToInput(Resource element) { + super(element); + } + + @Override + public Input perform(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + Resource diagram = graph.getPossibleObject(parameter, L0.PartOf); + if (diagram == null) + return null; + + Resource model = graph.syncRequest(new PossibleModel(diagram)); + if (model == null) + return null; + + RVI rvi = getDiagramCompositeRvi(graph, diagram); + if (rvi == null) + return null; + + return new Input(model, diagram, parameter, rvi); + } + + private static 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; + } + + } + + public static 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)); + }; + } + + public static void openEditorWithSelection(String editorId, Input input, Object... selection) { + NavigateToTarget.editorActivator( + editorId, + input.diagram(), + input.model(), + input.rvi(), + DistrictNetworkUIUtil.editorActivationCallback(Arrays.asList(selection))) + .run(); + } + + public static void openDNDiagramEditorWithSelection(Input input, Object... selection) { + openEditorWithSelection(DistrictDiagramEditor.ID, input, selection); + } + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/OpenDiagramFromNetworkElementAdapter.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/OpenDiagramFromNetworkElementAdapter.java new file mode 100644 index 00000000..91303836 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/OpenDiagramFromNetworkElementAdapter.java @@ -0,0 +1,41 @@ +package org.simantics.district.network.ui; + +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.ui.workbench.editor.AbstractResourceEditorAdapter; +import org.simantics.utils.ui.BundleUtils; + +/** + * @author Tuukka Lehtonen + * @since 1.35.0 + */ +public class OpenDiagramFromNetworkElementAdapter extends AbstractResourceEditorAdapter { + + public OpenDiagramFromNetworkElementAdapter() { + super("District Network Diagram", + BundleUtils.getImageDescriptorFromPlugin("com.famfamfam.silk", "icons/map.png")); + } + + @Override + public boolean canHandle(ReadGraph graph, Object input) throws DatabaseException { + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + Resource r = DistrictNetworkUIUtil.getInputResource(graph, input); + return r != null + && graph.isInstanceOf(r, DN.Element) + && graph.getPossibleURI(r) != null; + } + + @Override + public void openEditor(Object input) throws Exception { + Session s = Simantics.getSession(); + Resource dhElement = s.syncRequest(new DistrictNetworkUIUtil.GetInputResource(input)); + DistrictNetworkUIUtil.Input in = dhElement != null ? s.syncRequest(new DistrictNetworkUIUtil.ElementToInput(dhElement)) : null; + if (in != null) + DistrictNetworkUIUtil.openDNDiagramEditorWithSelection(in, in.element()); + } + +} \ No newline at end of file diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPanel.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPanel.java new file mode 100644 index 00000000..39770b6b --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPanel.java @@ -0,0 +1,490 @@ +package org.simantics.district.network.ui.breakdown; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.layout.TreeColumnLayout; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.jface.viewers.CellLabelProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.TreeColumn; +import org.simantics.NameLabelMode; +import org.simantics.NameLabelUtil; +import org.simantics.ObjectIdentitySchedulingRule; +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.procedure.adapter.DisposableListener; +import org.simantics.db.common.request.UnaryRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.adapter.Instances; +import org.simantics.db.layer0.request.ActiveModels; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.district.network.ui.DistrictNetworkUIUtil; +import org.simantics.district.network.ui.breakdown.Input.NetworkDiagram; +import org.simantics.district.network.ui.breakdown.Input.NetworkDiagrams; +import org.simantics.district.network.ui.breakdown.Input.Subgraph; +import org.simantics.district.network.ui.internal.Activator; +import org.simantics.modeling.ModelingResources; +import org.simantics.utils.strings.AlphanumComparator; +import org.simantics.utils.strings.StringUtils; +import org.simantics.utils.ui.BundleUtils; +import org.simantics.utils.ui.ISelectionUtils; +import org.simantics.utils.ui.SWTUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Tuukka Lehtonen + * @since 1.35.0 + */ +public class DistrictNetworkBreakdownPanel extends Composite { + + private static final Logger LOGGER = LoggerFactory.getLogger(DistrictNetworkBreakdownPanel.class); + + private DisposableListener inputListener; + //private NetworkDiagrams currentInput; + private ConcurrentMap subgraphs = new ConcurrentHashMap<>(); + + private ResourceManager rm; + private TreeViewer tree; + + private TreeComparator treeComparator; + + private ImageDescriptor mapImg; + + public DistrictNetworkBreakdownPanel(Composite parent, int style) { + super(parent, style); + this.rm = new LocalResourceManager(JFaceResources.getResources(), this); + + this.mapImg = BundleUtils.getImageDescriptorFromPlugin("com.famfamfam.silk", "icons/map.png"); + + addDisposeListener(e -> DistrictNetworkBreakdownPanel.this.widgetDisposed()); + createUI(parent); + trackInput(); + } + + protected void widgetDisposed() { + if (inputListener != null) + inputListener.dispose(); + } + + private void createUI(Composite parent) { + GridLayoutFactory.fillDefaults().applyTo(this); + + Composite treeParent = new Composite(this, SWT.NONE); + TreeColumnLayout treeLayout = new TreeColumnLayout(); + treeParent.setLayout(treeLayout); + GridDataFactory.fillDefaults().grab(true, true).span(1, 1).applyTo(treeParent); + + treeComparator = new TreeComparator(); + + tree = new TreeViewer(treeParent, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.FULL_SELECTION); + tree.setUseHashlookup(true); + tree.getTree().setHeaderVisible(true); + tree.setContentProvider(new ContentProvider()); + tree.setComparator(treeComparator); + createColumns(treeLayout); + + tree.getTree().addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.F5) { + e.doit = false; + refreshSelection(); + } + } + }); + tree.getTree().addMouseListener(new MouseAdapter() { + @Override + public void mouseDoubleClick(MouseEvent e) { + new ShowSubnetworkAction(selectedSubgraphs()).run(); + } + }); + + MenuManager menuManager = new MenuManager("District Network Breakdown Actions", "#DistrictNetworkBreakdownPopup"); + menuManager.setRemoveAllWhenShown(true); + Menu menu = menuManager.createContextMenu(tree.getTree()); + tree.getTree().setMenu(menu); + menuManager.addMenuListener(new IMenuListener() { + @Override + public void menuAboutToShow(IMenuManager manager) { + List sel = selectedSubgraphs(); + if (sel.size() > 0 && fromSameDiagram(sel)) { + manager.add(new ShowSubnetworkAction(sel)); + } + //manager.add(new DeleteAction()); + } + + private boolean fromSameDiagram(List list) { + NetworkDiagram d = null; + for (Subgraph sg : list) { + if (d == null) + d = sg.parent; + else if (!d.equals(sg.parent)) + return false; + } + return true; + } + }); + } + + protected void refreshSelection() { + for (Object obj : tree.getStructuredSelection().toArray()) { + if (obj instanceof NetworkDiagram) { + subgraphs.remove(obj); + tree.refresh(obj); + } + } + } + + protected void scheduleRefresh(NetworkDiagram diagram) { + SWTUtils.asyncExec(tree.getTree(), () -> { + if (!tree.getTree().isDisposed()) + tree.refresh(diagram); + }); + } + + private void createColumns(TreeColumnLayout layout) { + TreeViewerColumn nameCol = createColumn(0, layout, "Diagram", "Diagram / Subgraph Number", new NameLabeler(), 1, 100, SWT.LEFT); + createColumn(1, layout, "Nodes", "Node Count of Subnetwork", new NodeCountLabeler(), 0, 100, SWT.LEFT); + createColumn(2, layout, "Edges", "Edge Count of Subnetwork", new EdgeCountLabeler(), 0, 100, SWT.LEFT); + setSortColumn(nameCol.getColumn(), 1); + } + + private TreeViewerColumn createColumn(int index, TreeColumnLayout layout, String text, String tooltip, + CellLabelProvider labelProvider, int weight, int minimumWidth, int style) { + TreeViewerColumn column = new TreeViewerColumn(tree, style); + column.getColumn().setText(text); + column.getColumn().setToolTipText(StringUtils.safeString(tooltip)); + column.getColumn().setResizable(true); + column.getColumn().setMoveable(true); + column.getColumn().addSelectionListener(getSelectionAdapter(column.getColumn(), index)); + column.setLabelProvider(labelProvider); + layout.setColumnData(column.getColumn(), new ColumnWeightData(weight, minimumWidth)); + return column; + } + + private List selectedSubgraphs() { + return ISelectionUtils.filterSelection(tree.getStructuredSelection(), Subgraph.class); + } + + public class TreeComparator extends ViewerComparator { + private static final int DESCENDING = 1; + + private int propertyIndex = -1; + private int direction = 0; + + public int getDirection() { + return direction == 1 ? SWT.DOWN : SWT.UP; + } + + public void setColumn(int column) { + if (column == this.propertyIndex) { + // Same column as last sort; toggle the direction + direction = 1 - direction; + } else { + // New column; do an ascending sort + this.propertyIndex = column; + direction = 0; + } + } + + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + int rc = 0; + if (e1 instanceof NetworkDiagram) { + NetworkDiagram nd1 = (NetworkDiagram) e1; + NetworkDiagram nd2 = (NetworkDiagram) e2; + rc = AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(nd1.name, nd2.name); + } else if (e1 instanceof Subgraph) { + Subgraph sg1 = (Subgraph) e1; + Subgraph sg2 = (Subgraph) e2; + switch (propertyIndex) { + case 0: + break; + case 1: + rc = Integer.compare(sg1.vertices.size(), sg2.vertices.size()); + break; + case 2: + rc = Integer.compare(sg1.edges.size(), sg2.edges.size()); + break; + default: + rc = 0; + } + } + if (direction == DESCENDING) + rc = -rc; + return rc; + } + } + + private void setSortColumn(TreeColumn column, int index) { + treeComparator.setColumn(index); + int dir = treeComparator.getDirection(); + tree.getTree().setSortDirection(dir); + tree.getTree().setSortColumn(column); + } + + private SelectionListener getSelectionAdapter(TreeColumn column, int index) { + return new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + setSortColumn(column, index); + tree.refresh(true); + } + }; + } + + private Runnable setInput(Input.NetworkDiagrams input) { + return () -> { + if (!tree.getTree().isDisposed()) + tree.setInput(input); + }; + } + + private void configureInput(Input.NetworkDiagrams input) { + SWTUtils.asyncExec(DistrictNetworkBreakdownPanel.this, setInput(input)); + } + + private static final String PENDING = "Pending..."; + private static final Subgraph[] PENDING_RESULT = {}; + + private class ContentProvider implements ITreeContentProvider { + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + @Override + public Object[] getElements(Object inputElement) { + return ((NetworkDiagrams) inputElement).diagrams.toArray(); + } + + @Override + public Object getParent(Object element) { + if (element instanceof Subgraph) + return ((Subgraph) element).parent; + return null; + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof NetworkDiagram) { + NetworkDiagram nd = (NetworkDiagram) parentElement; + Subgraph[] sgs = subgraphs.get(nd); + if (sgs != null) + return sgs; + subgraphs.put(nd, PENDING_RESULT); + new CalculateSubgraphs(nd).schedule(); + return new Object[] { PENDING }; + } + return new Object[0]; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof NetworkDiagram) { + NetworkDiagram nd = (NetworkDiagram) element; + Subgraph[] sgs = subgraphs.get(nd); + if (sgs != null) + return sgs.length > 0; + // We don't know yet so there might be children + return true; + } + return false; + } + } + + private class NameLabeler extends ColumnLabelProvider { + @Override + public String getText(Object element) { + if (element instanceof NetworkDiagram) { + return ((Input.NetworkDiagram) element).name; + } else if (element instanceof Subgraph) { + return "" + ((Subgraph) element).index; + } + return element.toString(); + } + @Override + public Image getImage(Object element) { + if (element instanceof NetworkDiagram) { + return (Image) rm.get(mapImg); + } + return null; + } + } + + private class NodeCountLabeler extends ColumnLabelProvider { + @Override + public String getText(Object element) { + if (element instanceof Subgraph) { + Subgraph sg = (Subgraph) element; + return Integer.toString(sg.vertices.size()); + } + return ""; + } + } + + private class EdgeCountLabeler extends ColumnLabelProvider { + @Override + public String getText(Object element) { + if (element instanceof Subgraph) { + Subgraph sg = (Subgraph) element; + return Integer.toString(sg.edges.size()); + } + return ""; + } + } + + private void trackInput() { + this.inputListener = new DisposableListener() { + @Override + public void execute(NetworkDiagrams input) { + //currentInput = input; + configureInput(input); + } + @Override + public void exception(Throwable t) { + LOGGER.error("Problems resolving active models", t); + } + }; + Simantics.getSession().asyncRequest(new ResolveInput(Simantics.getProjectResource()), inputListener); + } + + private static class ResolveInput extends UnaryRead { + + public ResolveInput(Resource parameter) { + super(parameter); + } + + @Override + public NetworkDiagrams perform(ReadGraph graph) throws DatabaseException { + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + ModelingResources MOD = ModelingResources.getInstance(graph); + Instances index = graph.getPossibleAdapter(DN.Diagram, Instances.class); + NetworkDiagrams result = new NetworkDiagrams(); + + for (Resource model : graph.syncRequest(new ActiveModels(parameter))) { + for (Resource diagram : index.find(graph, model)) { + Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite); + String name = NameLabelUtil.modalName(graph, composite != null ? composite : diagram, NameLabelMode.NAME_AND_LABEL); + result.diagrams.add(new NetworkDiagram(name, diagram)); + } + } + + return result; + } + + } + + private class CalculateSubgraphs extends Job { + + private NetworkDiagram diagram; + + public CalculateSubgraphs(NetworkDiagram diagram) { + super("Calculate subgraphs"); + this.diagram = diagram; + setUser(true); + setRule(new ObjectIdentitySchedulingRule(diagram)); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + SubgraphProvider[] sgps = Activator.getInstance().getSubgraphProviders(); + SubMonitor mon = SubMonitor.convert(monitor, NLS.bind("Calculating district network breakdown for {0}", diagram.name), sgps.length); + List result = new ArrayList<>(); + for (SubgraphProvider sgp : sgps) + for (Subgraph sg : sgp.getProvider(mon.split(1), diagram)) + result.add(sg); + subgraphs.put(diagram, result.toArray(new Subgraph[result.size()])); + scheduleRefresh(diagram); + return Status.OK_STATUS; + } finally { + monitor.done(); + } + } + + } + + private static class ShowSubnetworkAction extends Action { + + private final List subgraphs; + + public ShowSubnetworkAction(List subgraphs) { + super("Show Subnetwork on Diagram"); + this.subgraphs = subgraphs; + } + + @Override + public void run() { + try { + openDiagram(subgraphs); + } catch (DatabaseException e) { + LOGGER.error("Failed to show selected subnetwork", e); + } + } + + } + + public static boolean openDiagram(List subgraphs) throws DatabaseException { + if (subgraphs.isEmpty()) + return false; + Subgraph subgraph = subgraphs.get(0); + Resource dhElement = subgraph.vertices.size() > 0 + ? subgraph.vertices.get(0) + : subgraph.edges.size() > 0 ? subgraph.edges.get(0) : null; + DistrictNetworkUIUtil.Input in = dhElement != null + ? Simantics.getSession().syncRequest(new DistrictNetworkUIUtil.ElementToInput(dhElement)) : null; + + if (in != null) { + List selection = new ArrayList<>(); + subgraphs.forEach(sg -> { + sg.vertices.forEach(selection::add); + sg.edges.forEach(selection::add); + }); + DistrictNetworkUIUtil.openDNDiagramEditorWithSelection(in, selection.toArray()); + return true; + } + return false; + } + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPart.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPart.java new file mode 100644 index 00000000..441eca59 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPart.java @@ -0,0 +1,29 @@ +package org.simantics.district.network.ui.breakdown; + +import javax.annotation.PostConstruct; + +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +/** + * @author Tuukka Lehtonen + * @since 1.35.0 + */ +public class DistrictNetworkBreakdownPart { + + public static final String ID = "org.simantics.district.breakdown"; //$NON-NLS-1$ + + private DistrictNetworkBreakdownPanel panel; + + @PostConstruct + public void createPartControl(Composite parent) { + panel = new DistrictNetworkBreakdownPanel(parent, SWT.NONE); + } + + @Focus + public void setFocus() { + panel.setFocus(); + } + +} \ No newline at end of file diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/Input.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/Input.java new file mode 100644 index 00000000..dd80c2a6 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/Input.java @@ -0,0 +1,43 @@ +package org.simantics.district.network.ui.breakdown; + +import java.util.ArrayList; +import java.util.List; + +import org.simantics.databoard.util.Bean; +import org.simantics.db.Resource; + +/** + * @author Tuukka Lehtonen + * @since 1.35.0 + */ +public class Input { + + public static class NetworkDiagrams extends Bean { + public List diagrams = new ArrayList<>(); + } + + public static class NetworkDiagram extends Bean { + public String name; + public Resource diagram; + + public NetworkDiagram(String name, Resource diagram) { + this.name = name; + this.diagram = diagram; + } + } + + public static class Subgraph { + public NetworkDiagram parent; + public int index; + public List vertices; + public List edges; + + public Subgraph(NetworkDiagram parent, int index, List vertices, List edges) { + this.parent = parent; + this.index = index; + this.vertices = vertices; + this.edges = edges; + } + } + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/SubgraphProvider.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/SubgraphProvider.java new file mode 100644 index 00000000..5de7fd62 --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/SubgraphProvider.java @@ -0,0 +1,20 @@ +package org.simantics.district.network.ui.breakdown; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.simantics.district.network.ui.breakdown.Input.NetworkDiagram; +import org.simantics.district.network.ui.breakdown.Input.Subgraph; + +/** + * @author Tuukka Lehtonen + * @since 1.35.0 + */ +public interface SubgraphProvider { + + /** + * @param monitor + * @param diagram + * @return + */ + Subgraph[] getProvider(IProgressMonitor monitor, NetworkDiagram diagram); + +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java index 977fe30c..e0e7b475 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java @@ -2,24 +2,43 @@ package org.simantics.district.network.ui.internal; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; +import org.simantics.district.network.ui.breakdown.SubgraphProvider; public class Activator implements BundleActivator { public static final String PLUGIN_ID = "org.simantics.district.network.ui"; + private static Activator instance; private static BundleContext context; + private ServiceTracker subgraphProviderTracker; @Override public void start(BundleContext context) throws Exception { + Activator.instance = this; Activator.context = context; + + subgraphProviderTracker = new ServiceTracker<>(context, SubgraphProvider.class.getName(), null); + subgraphProviderTracker.open(); } @Override public void stop(BundleContext context) throws Exception { + subgraphProviderTracker.close(); + + Activator.instance = null; Activator.context = null; } - + + public static Activator getInstance() { + return instance; + } + public static BundleContext getContext() { return context; } + public SubgraphProvider[] getSubgraphProviders() { + return subgraphProviderTracker.getServices(new SubgraphProvider[0]); + } + } diff --git a/org.simantics.district.network/scl/Simantics/District/Algorithm.scl b/org.simantics.district.network/scl/Simantics/District/Algorithm.scl index 3cd5c431..f8852bdb 100644 --- a/org.simantics.district.network/scl/Simantics/District/Algorithm.scl +++ b/org.simantics.district.network/scl/Simantics/District/Algorithm.scl @@ -1,9 +1,19 @@ -type Subgraph = [([Resource], [Resource])] -data Subgraphs = Subgraphs Resource Subgraph +import "Simantics/Model" +import "http://www.simantics.org/DistrictNetwork-1.0" as DN + +import "Comparator" +@private +importJava "org.simantics.utils.strings.AlphanumComparator" where + @JavaName CASE_INSENSITIVE_COMPARATOR + alphanumericComparator :: Comparator String + +type Subgraph = ([Resource], [Resource]) +type Subgraphs = (Resource, [Subgraph]) +type ElementFilter = (Resource -> Boolean) @private -floodFill :: Resource -> Subgraph -floodFill fromVertex = do +floodFill :: ElementFilter -> Resource -> Subgraph +floodFill edgeFilter fromVertex = do processVertex fromVertex (MSet.toList vertices, MSet.toList edges) where @@ -15,12 +25,16 @@ floodFill fromVertex = do for starts processEdgeStart for ends processEdgeEnd else () - processEdgeStart edge = if MSet.add edges edge then for (edge # DN.HasEndVertex) processVertex else () - processEdgeEnd edge = if MSet.add edges edge then for (edge # DN.HasStartVertex) processVertex else () + processEdgeStart edge = if MSet.add edges edge && edgeFilter edge then + for (edge # DN.HasEndVertex) processVertex + else () + processEdgeEnd edge = if MSet.add edges edge && edgeFilter edge then + for (edge # DN.HasStartVertex) processVertex + else () @private -findDisconnectedSubnetworksFromDiagram :: Resource -> Subgraphs -findDisconnectedSubnetworksFromDiagram networkDiagram = let +findDisconnectedSubnetworksFromDiagram :: ElementFilter -> Resource -> Subgraphs +findDisconnectedSubnetworksFromDiagram edgeFilter networkDiagram = let all = networkDiagram # L0.ConsistsOf vertices = filter (flip isInstanceOf DN.Vertex) all edges = filter (flip isInstanceOf DN.Edge) all @@ -31,7 +45,7 @@ findDisconnectedSubnetworksFromDiagram networkDiagram = let loop Nothing = () loop (Just vertex) = do - subgraph = floodFill vertex + subgraph = floodFill edgeFilter vertex MList.add result subgraph (vs, es) = subgraph MSet.removeAll vertexSet vs @@ -40,38 +54,33 @@ findDisconnectedSubnetworksFromDiagram networkDiagram = let loop (take1 vertexSet) else () in do - print "Total number of vertices: \(length vertices)" - print "Total number of edges: \(length edges)" + //print "Total number of vertices: \(length vertices)" + //print "Total number of edges: \(length edges)" loop (take1 vertexSet) if MSet.size edgeSet > 0 then MList.add result ([], MSet.toList edgeSet) else () - print "Found \(MList.size result) disconnected sub-networks" - Subgraphs networkDiagram (MList.freeze result) + //print "Found \(MList.size result) disconnected sub-networks" + (networkDiagram, (MList.freeze result)) """ Finds disconnected district subnetworks from the provided district network diagram. The input can be either the network diagram resource or the configuration composite resource of the network diagram. -See [reportDisconnectedSubnetworks](#reportDisconnectedSubnetworks) for reporting +See [reportDisconnectedSubnetworks](#reportDisconnectedSubnetworks) for reporting the +result by printing. """ -findDisconnectedSubnetworks :: Resource -> Subgraphs -findDisconnectedSubnetworks networkDiagramOrComposite = findDisconnectedSubnetworksFromDiagram (toDiagram networkDiagramOrComposite) +findDisconnectedSubnetworks :: ElementFilter -> Resource -> Subgraphs +findDisconnectedSubnetworks edgeFilter networkDiagramOrComposite = findDisconnectedSubnetworksFromDiagram edgeFilter (toDiagram networkDiagramOrComposite) where toDiagram d = if isInstanceOf d DN.Diagram then d else match (possibleObject d MOD.CompositeToDiagram) with Just dia -> toDiagram dia Nothing -> fail "Provided diagram is not a district network diagram or configuration composite: \(possibleUriOf d)" -import "Comparator" -@private -importJava "org.simantics.utils.strings.AlphanumComparator" where - @JavaName CASE_INSENSITIVE_COMPARATOR - alphanumericComparator :: Comparator String - reportDisconnectedSubnetworks :: Integer -> Subgraphs -> () -reportDisconnectedSubnetworks vertexThreshold (Subgraphs diagram subgraphs) = do +reportDisconnectedSubnetworks vertexThreshold (diagram, subgraphs) = do print "## Disconnected sub-network analysis of district network \(relativeUri diagram)" print "* Detailed reporting vertex count threshold is <= \(vertexThreshold)" for subgraphs reportGraph @@ -97,6 +106,9 @@ reportDisconnectedSubnetworks vertexThreshold (Subgraphs diagram subgraphs) = do reportShort vs es = do reportSubgraphTitle vs es print "* Details not reported because vertex count exceeds threshold" + mapFirst (\s -> do print "* v0: \(s)"; Just s) + (sortStrings (map showVertex vs)) + print "* ..." reportFull vs es = do reportSubgraphTitle vs es forI (sortStrings (map showVertex vs)) (\i s -> print "* v\(i): \(s)") diff --git a/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java b/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java index 40e8fc1d..64aab473 100644 --- a/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java +++ b/org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java @@ -9,12 +9,15 @@ import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.utils.OrderedSetUtils; import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.request.PossibleVariable; +import org.simantics.db.layer0.variable.Variable; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.diagram.synchronization.graph.layer.GraphLayer; import org.simantics.diagram.synchronization.graph.layer.IGraphLayerUtil; import org.simantics.district.network.ontology.DistrictNetworkResource; import org.simantics.layer0.Layer0; +import org.simantics.modeling.ModelingResources; import org.simantics.operation.Layer0X; public class DistrictNetworkUtil { @@ -145,4 +148,60 @@ public class DistrictNetworkUtil { graph.claimLiteral(diagram, DIA.HasModCount, ++l, Bindings.LONG); return name; } + + public static Resource getDiagramElement(ReadGraph graph, Resource component) throws DatabaseException { + if (component == null) + return null; + DiagramResource DIA = DiagramResource.getInstance(graph); + if (graph.isInstanceOf(component, DIA.Element)) + return component; + ModelingResources MOD = ModelingResources.getInstance(graph); + Resource element = graph.getPossibleObject(component, MOD.ComponentToElement); + return element != null && graph.isInstanceOf(element, DIA.Element) ? element : null; + } + + public static Resource getMappedElement(ReadGraph graph, Resource element) throws DatabaseException { + if (element == null) + return null; + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + return graph.getPossibleObject(element, DN.MappedComponent); + } + + public static Resource getMappedComponent(ReadGraph graph, Resource element) throws DatabaseException { + if (element == null) + return null; + Resource mappedElement = getMappedElement(graph, element); + if (mappedElement == null) + return null; + ModelingResources MOD = ModelingResources.getInstance(graph); + return graph.getPossibleObject(mappedElement, MOD.ElementToComponent); + } + + public static Resource getMappedDNElement(ReadGraph graph, Resource element) throws DatabaseException { + if (element == null) + return null; + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + return graph.getPossibleObject(element, DN.MappedFromElement); + } + + public static Variable toMappedConfigurationModule(ReadGraph graph, Resource input) throws DatabaseException { + if (input == null) + return null; + + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + if (graph.isInstanceOf(input, DN.Element)) { + Resource mappedElement = getMappedElement(graph, input); + if (mappedElement == null) + return null; + + ModelingResources MOD = ModelingResources.getInstance(graph); + Resource mappedComponent = graph.getPossibleObject(mappedElement, MOD.ElementToComponent); + if (mappedComponent == null) + return null; + + return graph.syncRequest(new PossibleVariable(mappedComponent)); + } + return null; + } + }