]> gerrit.simantics Code Review - simantics/district.git/blob - 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
1 package org.simantics.district.network.ui.breakdown;
2
3 import java.util.ArrayList;
4 import java.util.List;
5 import java.util.concurrent.ConcurrentHashMap;
6 import java.util.concurrent.ConcurrentMap;
7
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;
70
71 /**
72  * @author Tuukka Lehtonen
73  * @since 1.35.0
74  */
75 public class DistrictNetworkBreakdownPanel extends Composite {
76
77     private static final Logger LOGGER = LoggerFactory.getLogger(DistrictNetworkBreakdownPanel.class);
78
79     private DisposableListener<NetworkDiagrams> inputListener;
80     //private NetworkDiagrams currentInput;
81     private ConcurrentMap<NetworkDiagram, Subgraph[]> subgraphs = new ConcurrentHashMap<>();
82
83     private ResourceManager rm;
84     private TreeViewer tree;
85
86     private TreeComparator treeComparator;
87
88     private ImageDescriptor mapImg;
89
90     public DistrictNetworkBreakdownPanel(Composite parent, int style) {
91         super(parent, style);
92         this.rm = new LocalResourceManager(JFaceResources.getResources(), this);
93
94         this.mapImg = BundleUtils.getImageDescriptorFromPlugin("com.famfamfam.silk", "icons/map.png");
95
96         addDisposeListener(e -> DistrictNetworkBreakdownPanel.this.widgetDisposed());
97         createUI(parent);
98         trackInput();
99     }
100
101     protected void widgetDisposed() {
102         if (inputListener != null)
103             inputListener.dispose();
104     }
105
106     private void createUI(Composite parent) {
107         GridLayoutFactory.fillDefaults().applyTo(this);
108
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);
113
114         treeComparator = new TreeComparator();
115
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);
122
123         tree.getTree().addKeyListener(new KeyAdapter() {
124             @Override
125             public void keyPressed(KeyEvent e) {
126                 if (e.keyCode == SWT.F5) {
127                     e.doit = false;
128                     refreshSelection();
129                 }
130             }
131         });
132         tree.getTree().addMouseListener(new MouseAdapter() {
133             @Override
134             public void mouseDoubleClick(MouseEvent e) {
135                 new ShowSubnetworkAction(selectedSubgraphs()).run();
136             }
137         });
138
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() {
144             @Override
145             public void menuAboutToShow(IMenuManager manager) {
146                 List<Subgraph> sel = selectedSubgraphs();
147                 if (sel.size() > 0 && fromSameDiagram(sel)) {
148                     manager.add(new ShowSubnetworkAction(sel));
149                 }
150                 //manager.add(new DeleteAction());
151             }
152
153             private boolean fromSameDiagram(List<Subgraph> list) {
154                 NetworkDiagram d = null;
155                 for (Subgraph sg : list) {
156                     if (d == null)
157                         d = sg.parent;
158                     else if (!d.equals(sg.parent))
159                         return false;
160                 }
161                 return true;
162             }
163         });
164     }
165
166     protected void refreshSelection() {
167         for (Object obj : tree.getStructuredSelection().toArray()) {
168             if (obj instanceof NetworkDiagram) {
169                 subgraphs.remove(obj);
170                 tree.refresh(obj);
171             }
172         }
173     }
174
175     protected void scheduleRefresh(NetworkDiagram diagram) {
176         SWTUtils.asyncExec(tree.getTree(), () -> {
177             if (!tree.getTree().isDisposed())
178                 tree.refresh(diagram);
179         });
180     }
181
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);
187     }
188
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));
199         return column;
200     }
201
202     private List<Subgraph> selectedSubgraphs() {
203         return ISelectionUtils.filterSelection(tree.getStructuredSelection(), Subgraph.class);
204     }
205
206     public class TreeComparator extends ViewerComparator {
207         private static final int DESCENDING = 1;
208
209         private int propertyIndex = -1;
210         private int direction = 0;
211
212         public int getDirection() {
213             return direction == 1 ? SWT.DOWN : SWT.UP;
214         }
215
216         public void setColumn(int column) {
217             if (column == this.propertyIndex) {
218                 // Same column as last sort; toggle the direction
219                 direction = 1 - direction;
220             } else {
221                 // New column; do an ascending sort
222                 this.propertyIndex = column;
223                 direction = 0;
224             }
225         }
226
227         @Override
228         public int compare(Viewer viewer, Object e1, Object e2) {
229             int rc = 0;
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) {
238                     case 0:
239                         break;
240                     case 1:
241                         rc = Integer.compare(sg1.vertices.size(), sg2.vertices.size());
242                         break;
243                     case 2:
244                         rc = Integer.compare(sg1.edges.size(), sg2.edges.size());
245                         break;
246                     default:
247                         rc = 0;
248                 }
249             }
250             if (direction == DESCENDING)
251                 rc = -rc;
252             return rc;
253         }
254     }
255
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);
261     }
262
263     private SelectionListener getSelectionAdapter(TreeColumn column, int index) {
264         return new SelectionAdapter() {
265             @Override
266             public void widgetSelected(SelectionEvent e) {
267                 setSortColumn(column, index);
268                 tree.refresh(true);
269             }
270         };
271     }
272
273     private Runnable setInput(Input.NetworkDiagrams input) {
274         return () -> {
275             if (!tree.getTree().isDisposed())
276                 tree.setInput(input);
277         };
278     }
279
280     private void configureInput(Input.NetworkDiagrams input) {
281         SWTUtils.asyncExec(DistrictNetworkBreakdownPanel.this, setInput(input));
282     }
283
284     private static final String PENDING = "Pending...";
285     private static final Subgraph[] PENDING_RESULT = {};
286
287     private class ContentProvider implements ITreeContentProvider {
288         @Override
289         public void dispose() {
290         }
291
292         @Override
293         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
294         }
295
296         @Override
297         public Object[] getElements(Object inputElement) {
298             return ((NetworkDiagrams) inputElement).diagrams.toArray();
299         }
300
301         @Override
302         public Object getParent(Object element) {
303             if (element instanceof Subgraph)
304                 return ((Subgraph) element).parent;
305             return null;
306         }
307
308         @Override
309         public Object[] getChildren(Object parentElement) {
310             if (parentElement instanceof NetworkDiagram) {
311                 NetworkDiagram nd = (NetworkDiagram) parentElement;
312                 Subgraph[] sgs = subgraphs.get(nd);
313                 if (sgs != null)
314                     return sgs;
315                 subgraphs.put(nd, PENDING_RESULT);
316                 new CalculateSubgraphs(nd).schedule();
317                 return new Object[] { PENDING };
318             }
319             return new Object[0];
320         }
321
322         @Override
323         public boolean hasChildren(Object element) {
324             if (element instanceof NetworkDiagram) {
325                 NetworkDiagram nd = (NetworkDiagram) element;
326                 Subgraph[] sgs = subgraphs.get(nd);
327                 if (sgs != null)
328                     return sgs.length > 0;
329                 // We don't know yet so there might be children
330                 return true;
331             }
332             return false;
333         }
334     }
335
336     private class NameLabeler extends ColumnLabelProvider {
337         @Override
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;
343             }
344             return element.toString();
345         }
346         @Override
347         public Image getImage(Object element) {
348             if (element instanceof NetworkDiagram) {
349                 return (Image) rm.get(mapImg);
350             }
351             return null;
352         }
353     }
354
355     private class NodeCountLabeler extends ColumnLabelProvider {
356         @Override
357         public String getText(Object element) {
358             if (element instanceof Subgraph) {
359                 Subgraph sg = (Subgraph) element;
360                 return Integer.toString(sg.vertices.size());
361             }
362             return "";
363         }
364     }
365
366     private class EdgeCountLabeler extends ColumnLabelProvider {
367         @Override
368         public String getText(Object element) {
369             if (element instanceof Subgraph) {
370                 Subgraph sg = (Subgraph) element;
371                 return Integer.toString(sg.edges.size());
372             }
373             return "";
374         }
375     }
376
377     private void trackInput() {
378         this.inputListener = new DisposableListener<NetworkDiagrams>() {
379             @Override
380             public void execute(NetworkDiagrams input) {
381                 //currentInput = input;
382                 configureInput(input);
383             }
384             @Override
385             public void exception(Throwable t) {
386                 LOGGER.error("Problems resolving active models", t);
387             }
388         };
389         Simantics.getSession().asyncRequest(new ResolveInput(Simantics.getProjectResource()), inputListener);
390     }
391
392     private static class ResolveInput extends UnaryRead<Resource, NetworkDiagrams> {
393
394         public ResolveInput(Resource parameter) {
395             super(parameter);
396         }
397
398         @Override
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();
404
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));
410                 }
411             }
412
413             return result;
414         }
415
416     }
417
418     private class CalculateSubgraphs extends Job {
419
420         private NetworkDiagram diagram;
421
422         public CalculateSubgraphs(NetworkDiagram diagram) {
423             super("Calculate subgraphs");
424             this.diagram = diagram;
425             setUser(true);
426             setRule(new ObjectIdentitySchedulingRule(diagram));
427         }
428
429         @Override
430         protected IStatus run(IProgressMonitor monitor) {
431             try {
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))
437                         result.add(sg);
438                 subgraphs.put(diagram, result.toArray(new Subgraph[result.size()]));
439                 scheduleRefresh(diagram);
440                 return Status.OK_STATUS;
441             } finally {
442                 monitor.done();
443             }
444         }
445
446     }
447
448     private static class ShowSubnetworkAction extends Action {
449
450         private final List<Subgraph> subgraphs;
451
452         public ShowSubnetworkAction(List<Subgraph> subgraphs) {
453             super("Show Subnetwork on Diagram");
454             this.subgraphs = subgraphs;
455         }
456
457         @Override
458         public void run() {
459             try {
460                 openDiagram(subgraphs);
461             } catch (DatabaseException e) {
462                 LOGGER.error("Failed to show selected subnetwork", e);
463             }
464         }
465
466     }
467
468     public static boolean openDiagram(List<Subgraph> subgraphs) throws DatabaseException {
469         if (subgraphs.isEmpty())
470             return false;
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;
477
478         if (in != null) {
479             List<Resource> selection = new ArrayList<>();
480             subgraphs.forEach(sg -> {
481                 sg.vertices.forEach(selection::add);
482                 sg.edges.forEach(selection::add);
483             });
484             DistrictNetworkUIUtil.openDNDiagramEditorWithSelection(in, selection.toArray());
485             return true;
486         }
487         return false;
488     }
489
490 }