1 package org.simantics.district.network.ui.breakdown;
3 import java.util.ArrayList;
5 import java.util.concurrent.ConcurrentHashMap;
6 import java.util.concurrent.ConcurrentMap;
8 import org.eclipse.core.runtime.IProgressMonitor;
9 import org.eclipse.core.runtime.IStatus;
10 import org.eclipse.core.runtime.Status;
11 import org.eclipse.core.runtime.SubMonitor;
12 import org.eclipse.core.runtime.jobs.Job;
13 import org.eclipse.jface.action.Action;
14 import org.eclipse.jface.action.IMenuListener;
15 import org.eclipse.jface.action.IMenuManager;
16 import org.eclipse.jface.action.MenuManager;
17 import org.eclipse.jface.layout.GridDataFactory;
18 import org.eclipse.jface.layout.GridLayoutFactory;
19 import org.eclipse.jface.layout.TreeColumnLayout;
20 import org.eclipse.jface.resource.ImageDescriptor;
21 import org.eclipse.jface.resource.JFaceResources;
22 import org.eclipse.jface.resource.LocalResourceManager;
23 import org.eclipse.jface.resource.ResourceManager;
24 import org.eclipse.jface.viewers.CellLabelProvider;
25 import org.eclipse.jface.viewers.ColumnLabelProvider;
26 import org.eclipse.jface.viewers.ColumnWeightData;
27 import org.eclipse.jface.viewers.ITreeContentProvider;
28 import org.eclipse.jface.viewers.TreeViewer;
29 import org.eclipse.jface.viewers.TreeViewerColumn;
30 import org.eclipse.jface.viewers.Viewer;
31 import org.eclipse.jface.viewers.ViewerComparator;
32 import org.eclipse.osgi.util.NLS;
33 import org.eclipse.swt.SWT;
34 import org.eclipse.swt.events.KeyAdapter;
35 import org.eclipse.swt.events.KeyEvent;
36 import org.eclipse.swt.events.MouseAdapter;
37 import org.eclipse.swt.events.MouseEvent;
38 import org.eclipse.swt.events.SelectionAdapter;
39 import org.eclipse.swt.events.SelectionEvent;
40 import org.eclipse.swt.events.SelectionListener;
41 import org.eclipse.swt.graphics.Image;
42 import org.eclipse.swt.widgets.Composite;
43 import org.eclipse.swt.widgets.Menu;
44 import org.eclipse.swt.widgets.TreeColumn;
45 import org.simantics.NameLabelMode;
46 import org.simantics.NameLabelUtil;
47 import org.simantics.ObjectIdentitySchedulingRule;
48 import org.simantics.Simantics;
49 import org.simantics.db.ReadGraph;
50 import org.simantics.db.Resource;
51 import org.simantics.db.common.procedure.adapter.DisposableListener;
52 import org.simantics.db.common.request.UnaryRead;
53 import org.simantics.db.exception.DatabaseException;
54 import org.simantics.db.layer0.adapter.Instances;
55 import org.simantics.db.layer0.request.ActiveModels;
56 import org.simantics.district.network.ontology.DistrictNetworkResource;
57 import org.simantics.district.network.ui.DistrictNetworkUIUtil;
58 import org.simantics.district.network.ui.breakdown.Input.NetworkDiagram;
59 import org.simantics.district.network.ui.breakdown.Input.NetworkDiagrams;
60 import org.simantics.district.network.ui.breakdown.Input.Subgraph;
61 import org.simantics.district.network.ui.internal.Activator;
62 import org.simantics.modeling.ModelingResources;
63 import org.simantics.utils.strings.AlphanumComparator;
64 import org.simantics.utils.strings.StringUtils;
65 import org.simantics.utils.ui.BundleUtils;
66 import org.simantics.utils.ui.ISelectionUtils;
67 import org.simantics.utils.ui.SWTUtils;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
72 * @author Tuukka Lehtonen
75 public class DistrictNetworkBreakdownPanel extends Composite {
77 private static final Logger LOGGER = LoggerFactory.getLogger(DistrictNetworkBreakdownPanel.class);
79 private DisposableListener<NetworkDiagrams> inputListener;
80 //private NetworkDiagrams currentInput;
81 private ConcurrentMap<NetworkDiagram, Subgraph[]> subgraphs = new ConcurrentHashMap<>();
83 private ResourceManager rm;
84 private TreeViewer tree;
86 private TreeComparator treeComparator;
88 private ImageDescriptor mapImg;
90 public DistrictNetworkBreakdownPanel(Composite parent, int style) {
92 this.rm = new LocalResourceManager(JFaceResources.getResources(), this);
94 this.mapImg = BundleUtils.getImageDescriptorFromPlugin("com.famfamfam.silk", "icons/map.png");
96 addDisposeListener(e -> DistrictNetworkBreakdownPanel.this.widgetDisposed());
101 protected void widgetDisposed() {
102 if (inputListener != null)
103 inputListener.dispose();
106 private void createUI(Composite parent) {
107 GridLayoutFactory.fillDefaults().applyTo(this);
109 Composite treeParent = new Composite(this, SWT.NONE);
110 TreeColumnLayout treeLayout = new TreeColumnLayout();
111 treeParent.setLayout(treeLayout);
112 GridDataFactory.fillDefaults().grab(true, true).span(1, 1).applyTo(treeParent);
114 treeComparator = new TreeComparator();
116 tree = new TreeViewer(treeParent, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
117 tree.setUseHashlookup(true);
118 tree.getTree().setHeaderVisible(true);
119 tree.setContentProvider(new ContentProvider());
120 tree.setComparator(treeComparator);
121 createColumns(treeLayout);
123 tree.getTree().addKeyListener(new KeyAdapter() {
125 public void keyPressed(KeyEvent e) {
126 if (e.keyCode == SWT.F5) {
132 tree.getTree().addMouseListener(new MouseAdapter() {
134 public void mouseDoubleClick(MouseEvent e) {
135 new ShowSubnetworkAction(selectedSubgraphs()).run();
139 MenuManager menuManager = new MenuManager("District Network Breakdown Actions", "#DistrictNetworkBreakdownPopup");
140 menuManager.setRemoveAllWhenShown(true);
141 Menu menu = menuManager.createContextMenu(tree.getTree());
142 tree.getTree().setMenu(menu);
143 menuManager.addMenuListener(new IMenuListener() {
145 public void menuAboutToShow(IMenuManager manager) {
146 List<Subgraph> sel = selectedSubgraphs();
147 if (sel.size() > 0 && fromSameDiagram(sel)) {
148 manager.add(new ShowSubnetworkAction(sel));
150 //manager.add(new DeleteAction());
153 private boolean fromSameDiagram(List<Subgraph> list) {
154 NetworkDiagram d = null;
155 for (Subgraph sg : list) {
158 else if (!d.equals(sg.parent))
166 protected void refreshSelection() {
167 for (Object obj : tree.getStructuredSelection().toArray()) {
168 if (obj instanceof NetworkDiagram) {
169 subgraphs.remove(obj);
175 protected void scheduleRefresh(NetworkDiagram diagram) {
176 SWTUtils.asyncExec(tree.getTree(), () -> {
177 if (!tree.getTree().isDisposed())
178 tree.refresh(diagram);
182 private void createColumns(TreeColumnLayout layout) {
183 TreeViewerColumn nameCol = createColumn(0, layout, "Diagram", "Diagram / Subgraph Number", new NameLabeler(), 1, 100, SWT.LEFT);
184 createColumn(1, layout, "Nodes", "Node Count of Subnetwork", new NodeCountLabeler(), 0, 100, SWT.LEFT);
185 createColumn(2, layout, "Edges", "Edge Count of Subnetwork", new EdgeCountLabeler(), 0, 100, SWT.LEFT);
186 setSortColumn(nameCol.getColumn(), 1);
189 private TreeViewerColumn createColumn(int index, TreeColumnLayout layout, String text, String tooltip,
190 CellLabelProvider labelProvider, int weight, int minimumWidth, int style) {
191 TreeViewerColumn column = new TreeViewerColumn(tree, style);
192 column.getColumn().setText(text);
193 column.getColumn().setToolTipText(StringUtils.safeString(tooltip));
194 column.getColumn().setResizable(true);
195 column.getColumn().setMoveable(true);
196 column.getColumn().addSelectionListener(getSelectionAdapter(column.getColumn(), index));
197 column.setLabelProvider(labelProvider);
198 layout.setColumnData(column.getColumn(), new ColumnWeightData(weight, minimumWidth));
202 private List<Subgraph> selectedSubgraphs() {
203 return ISelectionUtils.filterSelection(tree.getStructuredSelection(), Subgraph.class);
206 public class TreeComparator extends ViewerComparator {
207 private static final int DESCENDING = 1;
209 private int propertyIndex = -1;
210 private int direction = 0;
212 public int getDirection() {
213 return direction == 1 ? SWT.DOWN : SWT.UP;
216 public void setColumn(int column) {
217 if (column == this.propertyIndex) {
218 // Same column as last sort; toggle the direction
219 direction = 1 - direction;
221 // New column; do an ascending sort
222 this.propertyIndex = column;
228 public int compare(Viewer viewer, Object e1, Object e2) {
230 if (e1 instanceof NetworkDiagram) {
231 NetworkDiagram nd1 = (NetworkDiagram) e1;
232 NetworkDiagram nd2 = (NetworkDiagram) e2;
233 rc = AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(nd1.name, nd2.name);
234 } else if (e1 instanceof Subgraph) {
235 Subgraph sg1 = (Subgraph) e1;
236 Subgraph sg2 = (Subgraph) e2;
237 switch (propertyIndex) {
241 rc = Integer.compare(sg1.vertices.size(), sg2.vertices.size());
244 rc = Integer.compare(sg1.edges.size(), sg2.edges.size());
250 if (direction == DESCENDING)
256 private void setSortColumn(TreeColumn column, int index) {
257 treeComparator.setColumn(index);
258 int dir = treeComparator.getDirection();
259 tree.getTree().setSortDirection(dir);
260 tree.getTree().setSortColumn(column);
263 private SelectionListener getSelectionAdapter(TreeColumn column, int index) {
264 return new SelectionAdapter() {
266 public void widgetSelected(SelectionEvent e) {
267 setSortColumn(column, index);
273 private Runnable setInput(Input.NetworkDiagrams input) {
275 if (!tree.getTree().isDisposed())
276 tree.setInput(input);
280 private void configureInput(Input.NetworkDiagrams input) {
281 SWTUtils.asyncExec(DistrictNetworkBreakdownPanel.this, setInput(input));
284 private static final String PENDING = "Pending...";
285 private static final Subgraph[] PENDING_RESULT = {};
287 private class ContentProvider implements ITreeContentProvider {
289 public void dispose() {
293 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
297 public Object[] getElements(Object inputElement) {
298 return ((NetworkDiagrams) inputElement).diagrams.toArray();
302 public Object getParent(Object element) {
303 if (element instanceof Subgraph)
304 return ((Subgraph) element).parent;
309 public Object[] getChildren(Object parentElement) {
310 if (parentElement instanceof NetworkDiagram) {
311 NetworkDiagram nd = (NetworkDiagram) parentElement;
312 Subgraph[] sgs = subgraphs.get(nd);
315 subgraphs.put(nd, PENDING_RESULT);
316 new CalculateSubgraphs(nd).schedule();
317 return new Object[] { PENDING };
319 return new Object[0];
323 public boolean hasChildren(Object element) {
324 if (element instanceof NetworkDiagram) {
325 NetworkDiagram nd = (NetworkDiagram) element;
326 Subgraph[] sgs = subgraphs.get(nd);
328 return sgs.length > 0;
329 // We don't know yet so there might be children
336 private class NameLabeler extends ColumnLabelProvider {
338 public String getText(Object element) {
339 if (element instanceof NetworkDiagram) {
340 return ((Input.NetworkDiagram) element).name;
341 } else if (element instanceof Subgraph) {
342 return "" + ((Subgraph) element).index;
344 return element.toString();
347 public Image getImage(Object element) {
348 if (element instanceof NetworkDiagram) {
349 return (Image) rm.get(mapImg);
355 private class NodeCountLabeler extends ColumnLabelProvider {
357 public String getText(Object element) {
358 if (element instanceof Subgraph) {
359 Subgraph sg = (Subgraph) element;
360 return Integer.toString(sg.vertices.size());
366 private class EdgeCountLabeler extends ColumnLabelProvider {
368 public String getText(Object element) {
369 if (element instanceof Subgraph) {
370 Subgraph sg = (Subgraph) element;
371 return Integer.toString(sg.edges.size());
377 private void trackInput() {
378 this.inputListener = new DisposableListener<NetworkDiagrams>() {
380 public void execute(NetworkDiagrams input) {
381 //currentInput = input;
382 configureInput(input);
385 public void exception(Throwable t) {
386 LOGGER.error("Problems resolving active models", t);
389 Simantics.getSession().asyncRequest(new ResolveInput(Simantics.getProjectResource()), inputListener);
392 private static class ResolveInput extends UnaryRead<Resource, NetworkDiagrams> {
394 public ResolveInput(Resource parameter) {
399 public NetworkDiagrams perform(ReadGraph graph) throws DatabaseException {
400 DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
401 ModelingResources MOD = ModelingResources.getInstance(graph);
402 Instances index = graph.getPossibleAdapter(DN.Diagram, Instances.class);
403 NetworkDiagrams result = new NetworkDiagrams();
405 for (Resource model : graph.syncRequest(new ActiveModels(parameter))) {
406 for (Resource diagram : index.find(graph, model)) {
407 Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
408 String name = NameLabelUtil.modalName(graph, composite != null ? composite : diagram, NameLabelMode.NAME_AND_LABEL);
409 result.diagrams.add(new NetworkDiagram(name, diagram));
418 private class CalculateSubgraphs extends Job {
420 private NetworkDiagram diagram;
422 public CalculateSubgraphs(NetworkDiagram diagram) {
423 super("Calculate subgraphs");
424 this.diagram = diagram;
426 setRule(new ObjectIdentitySchedulingRule(diagram));
430 protected IStatus run(IProgressMonitor monitor) {
432 SubgraphProvider[] sgps = Activator.getInstance().getSubgraphProviders();
433 SubMonitor mon = SubMonitor.convert(monitor, NLS.bind("Calculating district network breakdown for {0}", diagram.name), sgps.length);
434 List<Subgraph> result = new ArrayList<>();
435 for (SubgraphProvider sgp : sgps)
436 for (Subgraph sg : sgp.getProvider(mon.split(1), diagram))
438 subgraphs.put(diagram, result.toArray(new Subgraph[result.size()]));
439 scheduleRefresh(diagram);
440 return Status.OK_STATUS;
448 private static class ShowSubnetworkAction extends Action {
450 private final List<Subgraph> subgraphs;
452 public ShowSubnetworkAction(List<Subgraph> subgraphs) {
453 super("Show Subnetwork on Diagram");
454 this.subgraphs = subgraphs;
460 openDiagram(subgraphs);
461 } catch (DatabaseException e) {
462 LOGGER.error("Failed to show selected subnetwork", e);
468 public static boolean openDiagram(List<Subgraph> subgraphs) throws DatabaseException {
469 if (subgraphs.isEmpty())
471 Subgraph subgraph = subgraphs.get(0);
472 Resource dhElement = subgraph.vertices.size() > 0
473 ? subgraph.vertices.get(0)
474 : subgraph.edges.size() > 0 ? subgraph.edges.get(0) : null;
475 DistrictNetworkUIUtil.Input in = dhElement != null
476 ? Simantics.getSession().syncRequest(new DistrictNetworkUIUtil.ElementToInput(dhElement)) : null;
479 List<Resource> selection = new ArrayList<>();
480 subgraphs.forEach(sg -> {
481 sg.vertices.forEach(selection::add);
482 sg.edges.forEach(selection::add);
484 DistrictNetworkUIUtil.openDNDiagramEditorWithSelection(in, selection.toArray());