package org.simantics.district.network.ui.breakdown; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; 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.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.DatabaseJob; 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)); } Set diagrams = new HashSet<>(); if (sel.size() > 0) { for (Subgraph graph : sel) { diagrams.add(graph.parent); } } if (diagrams.isEmpty()) diagrams.addAll(subgraphs.keySet()); manager.add(new RefreshAction(diagrams)); //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 DatabaseJob { 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 class RefreshAction extends Action { private Set diagram; public RefreshAction(Set diagrams) { super("Refresh"); this.diagram = diagrams; } @Override public void run() { diagram.forEach(d -> { subgraphs.remove(d); tree.refresh(); }); } } 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; } }