]> gerrit.simantics Code Review - simantics/district.git/blobdiff - org.simantics.district.network.ui/src/org/simantics/district/network/ui/breakdown/DistrictNetworkBreakdownPanel.java
Disconnected subgraph analysis for district network diagrams
[simantics/district.git] / org.simantics.district.network.ui / src / org / simantics / district / network / ui / breakdown / DistrictNetworkBreakdownPanel.java
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;
+    }
+
+}