+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;
+ }
+
+}