]> gerrit.simantics Code Review - simantics/district.git/commitdiff
Disconnected subgraph analysis for district network diagrams 15/2315/2
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 12 Oct 2018 06:42:40 +0000 (09:42 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Tue, 16 Oct 2018 09:43:31 +0000 (12:43 +0300)
* 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

org.simantics.district.network.ui/META-INF/MANIFEST.MF
org.simantics.district.network.ui/plugin.xml
org.simantics.district.network.ui/src/org/simantics/district/network/ui/DistrictNetworkUIUtil.java [new file with mode: 0644]
org.simantics.district.network.ui/src/org/simantics/district/network/ui/OpenDiagramFromNetworkElementAdapter.java [new file with mode: 0644]
org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPanel.java [new file with mode: 0644]
org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPart.java [new file with mode: 0644]
org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/Input.java [new file with mode: 0644]
org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/SubgraphProvider.java [new file with mode: 0644]
org.simantics.district.network.ui/src/org/simantics/district/network/ui/internal/Activator.java
org.simantics.district.network/scl/Simantics/District/Algorithm.scl
org.simantics.district.network/src/org/simantics/district/network/DistrictNetworkUtil.java

index 8db22872e7e7d39d6097d9d345fe1a17a01f4643..a2884512377bf9f9e08c12848bd65663847b84d4 100644 (file)
@@ -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
index db59f043e7acf5b71b3ba987acb1390e6123fee2..f33e345a7186f7f2b833ddc061b2f77fd480d57e 100644 (file)
             id="org.simantics.district.network.ui.diagrameditor">
       </editor>
    </extension>
+   <extension
+         point="org.eclipse.ui.views">
+      <category
+            id="org.simantics.district" name="District Network">
+      </category>
+      <e4view
+            category="org.simantics.district"
+            class="org.simantics.district.network.ui.breakdown.DistrictNetworkBreakdownPart"
+            icon="platform:/plugin/com.famfamfam.silk/icons/table.png"
+            id="org.simantics.district.breakdown"
+            name="District Network Breakdown"
+            restorable="true">
+      </e4view>
+   </extension>
   <extension
          point="org.eclipse.ui.preferencePages">
       <page
             priority="300"
             class="org.simantics.district.network.ui.OpenDiagramFromConfigurationAdapter">
       </adapterClass>
+      <adapterClass
+            groupId="org.simantics.diagramEditor.group"
+            priority="300"
+            class="org.simantics.district.network.ui.OpenDiagramFromNetworkElementAdapter">
+      </adapterClass>
    </extension>
    <extension
          point="org.simantics.scl.reflection.binding">
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 (file)
index 0000000..9ce6937
--- /dev/null
@@ -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<Object, Resource> {
+
+        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<Object, Input> {
+
+        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<Object, Input> {
+
+        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<Resource, Input> {
+
+        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<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));
+        };
+    }
+
+    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 (file)
index 0000000..9130383
--- /dev/null
@@ -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 (file)
index 0000000..39770b6
--- /dev/null
@@ -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<NetworkDiagrams> inputListener;
+    //private NetworkDiagrams currentInput;
+    private ConcurrentMap<NetworkDiagram, Subgraph[]> 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<Subgraph> sel = selectedSubgraphs();
+                if (sel.size() > 0 && fromSameDiagram(sel)) {
+                    manager.add(new ShowSubnetworkAction(sel));
+                }
+                //manager.add(new DeleteAction());
+            }
+
+            private boolean fromSameDiagram(List<Subgraph> 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<Subgraph> 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<NetworkDiagrams>() {
+            @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<Resource, NetworkDiagrams> {
+
+        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<Subgraph> 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<Subgraph> subgraphs;
+
+        public ShowSubnetworkAction(List<Subgraph> 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<Subgraph> 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<Resource> 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 (file)
index 0000000..441eca5
--- /dev/null
@@ -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 (file)
index 0000000..dd80c2a
--- /dev/null
@@ -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<NetworkDiagram> 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<Resource> vertices;
+        public List<Resource> edges;
+
+        public Subgraph(NetworkDiagram parent, int index, List<Resource> vertices, List<Resource> 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 (file)
index 0000000..5de7fd6
--- /dev/null
@@ -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);
+
+}
index 977fe30c670ec13fd6d1c179faff03716c20befa..e0e7b47564f917a7ca416eda8286cd0d896e3f44 100644 (file)
@@ -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<SubgraphProvider, SubgraphProvider> 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]);
+    }
+
 }
index 3cd5c431e5d1b724da6e9246ea92c8cabeb012fb..f8852bdba69f2a97e69e33781f9065fb2d695aff 100644 (file)
@@ -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 -> <ReadGraph> Boolean)
 
 @private
-floodFill :: Resource -> <ReadGraph, Proc> Subgraph
-floodFill fromVertex = do
+floodFill :: ElementFilter -> Resource -> <ReadGraph, Proc> 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 -> <ReadGraph, Proc> Subgraphs
-findDisconnectedSubnetworksFromDiagram networkDiagram = let
+findDisconnectedSubnetworksFromDiagram :: ElementFilter -> Resource -> <ReadGraph, Proc> 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 -> <ReadGraph, Proc> Subgraphs
-findDisconnectedSubnetworks networkDiagramOrComposite = findDisconnectedSubnetworksFromDiagram (toDiagram networkDiagramOrComposite)
+findDisconnectedSubnetworks :: ElementFilter -> Resource -> <ReadGraph, Proc> 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 -> <ReadGraph, Proc> ()
-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)")
index 40e8fc1d7825e3f47c877030b64ac3dcd632c9fe..64aab473a8a726ba6f4244ea1ce91c3367af9168 100644 (file)
@@ -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;
+    }
+
 }