1 package org.simantics.district.network.ui.breakdown;
3 import java.util.ArrayList;
4 import java.util.HashSet;
7 import java.util.concurrent.ConcurrentHashMap;
8 import java.util.concurrent.ConcurrentMap;
10 import org.eclipse.core.runtime.IProgressMonitor;
11 import org.eclipse.core.runtime.IStatus;
12 import org.eclipse.core.runtime.Status;
13 import org.eclipse.core.runtime.SubMonitor;
14 import org.eclipse.jface.action.Action;
15 import org.eclipse.jface.action.IMenuListener;
16 import org.eclipse.jface.action.IMenuManager;
17 import org.eclipse.jface.action.MenuManager;
18 import org.eclipse.jface.layout.GridDataFactory;
19 import org.eclipse.jface.layout.GridLayoutFactory;
20 import org.eclipse.jface.layout.TreeColumnLayout;
21 import org.eclipse.jface.resource.ImageDescriptor;
22 import org.eclipse.jface.resource.JFaceResources;
23 import org.eclipse.jface.resource.LocalResourceManager;
24 import org.eclipse.jface.resource.ResourceManager;
25 import org.eclipse.jface.viewers.CellLabelProvider;
26 import org.eclipse.jface.viewers.ColumnLabelProvider;
27 import org.eclipse.jface.viewers.ColumnWeightData;
28 import org.eclipse.jface.viewers.ITreeContentProvider;
29 import org.eclipse.jface.viewers.TreeViewer;
30 import org.eclipse.jface.viewers.TreeViewerColumn;
31 import org.eclipse.jface.viewers.Viewer;
32 import org.eclipse.jface.viewers.ViewerComparator;
33 import org.eclipse.osgi.util.NLS;
34 import org.eclipse.swt.SWT;
35 import org.eclipse.swt.events.KeyAdapter;
36 import org.eclipse.swt.events.KeyEvent;
37 import org.eclipse.swt.events.MouseAdapter;
38 import org.eclipse.swt.events.MouseEvent;
39 import org.eclipse.swt.events.SelectionAdapter;
40 import org.eclipse.swt.events.SelectionEvent;
41 import org.eclipse.swt.events.SelectionListener;
42 import org.eclipse.swt.graphics.Image;
43 import org.eclipse.swt.widgets.Composite;
44 import org.eclipse.swt.widgets.Menu;
45 import org.eclipse.swt.widgets.TreeColumn;
46 import org.simantics.DatabaseJob;
47 import org.simantics.NameLabelMode;
48 import org.simantics.NameLabelUtil;
49 import org.simantics.ObjectIdentitySchedulingRule;
50 import org.simantics.Simantics;
51 import org.simantics.db.ReadGraph;
52 import org.simantics.db.Resource;
53 import org.simantics.db.common.procedure.adapter.DisposableListener;
54 import org.simantics.db.common.request.UnaryRead;
55 import org.simantics.db.exception.DatabaseException;
56 import org.simantics.db.layer0.adapter.Instances;
57 import org.simantics.db.layer0.request.ActiveModels;
58 import org.simantics.district.network.ontology.DistrictNetworkResource;
59 import org.simantics.district.network.ui.DistrictNetworkUIUtil;
60 import org.simantics.district.network.ui.breakdown.Input.NetworkDiagram;
61 import org.simantics.district.network.ui.breakdown.Input.NetworkDiagrams;
62 import org.simantics.district.network.ui.breakdown.Input.Subgraph;
63 import org.simantics.district.network.ui.internal.Activator;
64 import org.simantics.modeling.ModelingResources;
65 import org.simantics.utils.strings.AlphanumComparator;
66 import org.simantics.utils.strings.StringUtils;
67 import org.simantics.utils.ui.BundleUtils;
68 import org.simantics.utils.ui.ISelectionUtils;
69 import org.simantics.utils.ui.SWTUtils;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
74 * @author Tuukka Lehtonen
77 public class DistrictNetworkBreakdownPanel extends Composite {
79 private static final Logger LOGGER = LoggerFactory.getLogger(DistrictNetworkBreakdownPanel.class);
81 private DisposableListener<NetworkDiagrams> inputListener;
82 //private NetworkDiagrams currentInput;
83 private ConcurrentMap<NetworkDiagram, Subgraph[]> subgraphs = new ConcurrentHashMap<>();
85 private ResourceManager rm;
86 private TreeViewer tree;
88 private TreeComparator treeComparator;
90 private ImageDescriptor mapImg;
92 public DistrictNetworkBreakdownPanel(Composite parent, int style) {
94 this.rm = new LocalResourceManager(JFaceResources.getResources(), this);
96 this.mapImg = BundleUtils.getImageDescriptorFromPlugin("com.famfamfam.silk", "icons/map.png");
98 addDisposeListener(e -> DistrictNetworkBreakdownPanel.this.widgetDisposed());
103 protected void widgetDisposed() {
104 if (inputListener != null)
105 inputListener.dispose();
108 private void createUI(Composite parent) {
109 GridLayoutFactory.fillDefaults().applyTo(this);
111 Composite treeParent = new Composite(this, SWT.NONE);
112 TreeColumnLayout treeLayout = new TreeColumnLayout();
113 treeParent.setLayout(treeLayout);
114 GridDataFactory.fillDefaults().grab(true, true).span(1, 1).applyTo(treeParent);
116 treeComparator = new TreeComparator();
118 tree = new TreeViewer(treeParent, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
119 tree.setUseHashlookup(true);
120 tree.getTree().setHeaderVisible(true);
121 tree.setContentProvider(new ContentProvider());
122 tree.setComparator(treeComparator);
123 createColumns(treeLayout);
125 tree.getTree().addKeyListener(new KeyAdapter() {
127 public void keyPressed(KeyEvent e) {
128 if (e.keyCode == SWT.F5) {
134 tree.getTree().addMouseListener(new MouseAdapter() {
136 public void mouseDoubleClick(MouseEvent e) {
137 new ShowSubnetworkAction(selectedSubgraphs()).run();
141 MenuManager menuManager = new MenuManager("District Network Breakdown Actions", "#DistrictNetworkBreakdownPopup");
142 menuManager.setRemoveAllWhenShown(true);
143 Menu menu = menuManager.createContextMenu(tree.getTree());
144 tree.getTree().setMenu(menu);
145 menuManager.addMenuListener(new IMenuListener() {
147 public void menuAboutToShow(IMenuManager manager) {
148 List<Subgraph> sel = selectedSubgraphs();
149 if (sel.size() > 0 && fromSameDiagram(sel)) {
150 manager.add(new ShowSubnetworkAction(sel));
152 Set<NetworkDiagram> diagrams = new HashSet<>();
153 if (sel.size() > 0) {
154 for (Subgraph graph : sel) {
155 diagrams.add(graph.parent);
158 if (diagrams.isEmpty())
159 diagrams.addAll(subgraphs.keySet());
160 manager.add(new RefreshAction(diagrams));
161 //manager.add(new DeleteAction());
164 private boolean fromSameDiagram(List<Subgraph> list) {
165 NetworkDiagram d = null;
166 for (Subgraph sg : list) {
169 else if (!d.equals(sg.parent))
177 protected void refreshSelection() {
178 for (Object obj : tree.getStructuredSelection().toArray()) {
179 if (obj instanceof NetworkDiagram) {
180 subgraphs.remove(obj);
186 protected void scheduleRefresh(NetworkDiagram diagram) {
187 SWTUtils.asyncExec(tree.getTree(), () -> {
188 if (!tree.getTree().isDisposed())
189 tree.refresh(diagram);
193 private void createColumns(TreeColumnLayout layout) {
194 TreeViewerColumn nameCol = createColumn(0, layout, "Diagram", "Diagram / Subgraph Number", new NameLabeler(), 1, 100, SWT.LEFT);
195 createColumn(1, layout, "Nodes", "Node Count of Subnetwork", new NodeCountLabeler(), 0, 100, SWT.LEFT);
196 createColumn(2, layout, "Edges", "Edge Count of Subnetwork", new EdgeCountLabeler(), 0, 100, SWT.LEFT);
197 setSortColumn(nameCol.getColumn(), 1);
200 private TreeViewerColumn createColumn(int index, TreeColumnLayout layout, String text, String tooltip,
201 CellLabelProvider labelProvider, int weight, int minimumWidth, int style) {
202 TreeViewerColumn column = new TreeViewerColumn(tree, style);
203 column.getColumn().setText(text);
204 column.getColumn().setToolTipText(StringUtils.safeString(tooltip));
205 column.getColumn().setResizable(true);
206 column.getColumn().setMoveable(true);
207 column.getColumn().addSelectionListener(getSelectionAdapter(column.getColumn(), index));
208 column.setLabelProvider(labelProvider);
209 layout.setColumnData(column.getColumn(), new ColumnWeightData(weight, minimumWidth));
213 private List<Subgraph> selectedSubgraphs() {
214 return ISelectionUtils.filterSelection(tree.getStructuredSelection(), Subgraph.class);
217 public class TreeComparator extends ViewerComparator {
218 private static final int DESCENDING = 1;
220 private int propertyIndex = -1;
221 private int direction = 0;
223 public int getDirection() {
224 return direction == 1 ? SWT.DOWN : SWT.UP;
227 public void setColumn(int column) {
228 if (column == this.propertyIndex) {
229 // Same column as last sort; toggle the direction
230 direction = 1 - direction;
232 // New column; do an ascending sort
233 this.propertyIndex = column;
239 public int compare(Viewer viewer, Object e1, Object e2) {
241 if (e1 instanceof NetworkDiagram) {
242 NetworkDiagram nd1 = (NetworkDiagram) e1;
243 NetworkDiagram nd2 = (NetworkDiagram) e2;
244 rc = AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(nd1.name, nd2.name);
245 } else if (e1 instanceof Subgraph) {
246 Subgraph sg1 = (Subgraph) e1;
247 Subgraph sg2 = (Subgraph) e2;
248 switch (propertyIndex) {
252 rc = Integer.compare(sg1.vertices.size(), sg2.vertices.size());
255 rc = Integer.compare(sg1.edges.size(), sg2.edges.size());
261 if (direction == DESCENDING)
267 private void setSortColumn(TreeColumn column, int index) {
268 treeComparator.setColumn(index);
269 int dir = treeComparator.getDirection();
270 tree.getTree().setSortDirection(dir);
271 tree.getTree().setSortColumn(column);
274 private SelectionListener getSelectionAdapter(TreeColumn column, int index) {
275 return new SelectionAdapter() {
277 public void widgetSelected(SelectionEvent e) {
278 setSortColumn(column, index);
284 private Runnable setInput(Input.NetworkDiagrams input) {
286 if (!tree.getTree().isDisposed())
287 tree.setInput(input);
291 private void configureInput(Input.NetworkDiagrams input) {
292 SWTUtils.asyncExec(DistrictNetworkBreakdownPanel.this, setInput(input));
295 private static final String PENDING = "Pending...";
296 private static final Subgraph[] PENDING_RESULT = {};
298 private class ContentProvider implements ITreeContentProvider {
300 public void dispose() {
304 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
308 public Object[] getElements(Object inputElement) {
309 return ((NetworkDiagrams) inputElement).diagrams.toArray();
313 public Object getParent(Object element) {
314 if (element instanceof Subgraph)
315 return ((Subgraph) element).parent;
320 public Object[] getChildren(Object parentElement) {
321 if (parentElement instanceof NetworkDiagram) {
322 NetworkDiagram nd = (NetworkDiagram) parentElement;
323 Subgraph[] sgs = subgraphs.get(nd);
326 subgraphs.put(nd, PENDING_RESULT);
327 new CalculateSubgraphs(nd).schedule();
328 return new Object[] { PENDING };
330 return new Object[0];
334 public boolean hasChildren(Object element) {
335 if (element instanceof NetworkDiagram) {
336 NetworkDiagram nd = (NetworkDiagram) element;
337 Subgraph[] sgs = subgraphs.get(nd);
339 return sgs.length > 0;
340 // We don't know yet so there might be children
347 private class NameLabeler extends ColumnLabelProvider {
349 public String getText(Object element) {
350 if (element instanceof NetworkDiagram) {
351 return ((Input.NetworkDiagram) element).name;
352 } else if (element instanceof Subgraph) {
353 return "" + ((Subgraph) element).index;
355 return element.toString();
358 public Image getImage(Object element) {
359 if (element instanceof NetworkDiagram) {
360 return (Image) rm.get(mapImg);
366 private class NodeCountLabeler extends ColumnLabelProvider {
368 public String getText(Object element) {
369 if (element instanceof Subgraph) {
370 Subgraph sg = (Subgraph) element;
371 return Integer.toString(sg.vertices.size());
377 private class EdgeCountLabeler extends ColumnLabelProvider {
379 public String getText(Object element) {
380 if (element instanceof Subgraph) {
381 Subgraph sg = (Subgraph) element;
382 return Integer.toString(sg.edges.size());
388 private void trackInput() {
389 this.inputListener = new DisposableListener<NetworkDiagrams>() {
391 public void execute(NetworkDiagrams input) {
392 //currentInput = input;
393 configureInput(input);
396 public void exception(Throwable t) {
397 LOGGER.error("Problems resolving active models", t);
400 Simantics.getSession().asyncRequest(new ResolveInput(Simantics.getProjectResource()), inputListener);
403 private static class ResolveInput extends UnaryRead<Resource, NetworkDiagrams> {
405 public ResolveInput(Resource parameter) {
410 public NetworkDiagrams perform(ReadGraph graph) throws DatabaseException {
411 DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
412 ModelingResources MOD = ModelingResources.getInstance(graph);
413 Instances index = graph.getPossibleAdapter(DN.Diagram, Instances.class);
414 NetworkDiagrams result = new NetworkDiagrams();
416 for (Resource model : graph.syncRequest(new ActiveModels(parameter))) {
417 for (Resource diagram : index.find(graph, model)) {
418 Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
419 String name = NameLabelUtil.modalName(graph, composite != null ? composite : diagram, NameLabelMode.NAME_AND_LABEL);
420 result.diagrams.add(new NetworkDiagram(name, diagram));
429 private class CalculateSubgraphs extends DatabaseJob {
431 private NetworkDiagram diagram;
433 public CalculateSubgraphs(NetworkDiagram diagram) {
434 super("Calculate subgraphs");
435 this.diagram = diagram;
437 setRule(new ObjectIdentitySchedulingRule(diagram));
441 protected IStatus run(IProgressMonitor monitor) {
443 SubgraphProvider[] sgps = Activator.getInstance().getSubgraphProviders();
444 SubMonitor mon = SubMonitor.convert(monitor, NLS.bind("Calculating district network breakdown for {0}", diagram.name), sgps.length);
445 List<Subgraph> result = new ArrayList<>();
446 for (SubgraphProvider sgp : sgps)
447 for (Subgraph sg : sgp.getProvider(mon.split(1), diagram))
449 subgraphs.put(diagram, result.toArray(new Subgraph[result.size()]));
450 scheduleRefresh(diagram);
451 return Status.OK_STATUS;
459 private class RefreshAction extends Action {
461 private Set<NetworkDiagram> diagram;
463 public RefreshAction(Set<NetworkDiagram> diagrams) {
465 this.diagram = diagrams;
470 diagram.forEach(d -> {
477 private static class ShowSubnetworkAction extends Action {
479 private final List<Subgraph> subgraphs;
481 public ShowSubnetworkAction(List<Subgraph> subgraphs) {
482 super("Show Subnetwork on Diagram");
483 this.subgraphs = subgraphs;
489 openDiagram(subgraphs);
490 } catch (DatabaseException e) {
491 LOGGER.error("Failed to show selected subnetwork", e);
497 public static boolean openDiagram(List<Subgraph> subgraphs) throws DatabaseException {
498 if (subgraphs.isEmpty())
500 Subgraph subgraph = subgraphs.get(0);
501 Resource dhElement = subgraph.vertices.size() > 0
502 ? subgraph.vertices.get(0)
503 : subgraph.edges.size() > 0 ? subgraph.edges.get(0) : null;
504 DistrictNetworkUIUtil.Input in = dhElement != null
505 ? Simantics.getSession().syncRequest(new DistrictNetworkUIUtil.ElementToInput(dhElement)) : null;
508 List<Resource> selection = new ArrayList<>();
509 subgraphs.forEach(sg -> {
510 sg.vertices.forEach(selection::add);
511 sg.edges.forEach(selection::add);
513 DistrictNetworkUIUtil.openDNDiagramEditorWithSelection(in, selection.toArray());