]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/parts/EditSelectorDialog.java
Hide "enabled" column for non-component type tech type tables
[simantics/district.git] / org.simantics.district.selection.ui / src / org / simantics / district / selection / ui / parts / EditSelectorDialog.java
1 package org.simantics.district.selection.ui.parts;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Set;
12 import java.util.UUID;
13 import java.util.function.Consumer;
14
15 import javax.inject.Inject;
16
17 import org.eclipse.jface.dialogs.Dialog;
18 import org.eclipse.jface.dialogs.MessageDialog;
19 import org.eclipse.jface.layout.GridDataFactory;
20 import org.eclipse.jface.layout.GridLayoutFactory;
21 import org.eclipse.jface.layout.RowDataFactory;
22 import org.eclipse.jface.layout.RowLayoutFactory;
23 import org.eclipse.jface.resource.ImageDescriptor;
24 import org.eclipse.jface.resource.JFaceResources;
25 import org.eclipse.jface.resource.LocalResourceManager;
26 import org.eclipse.jface.resource.ResourceLocator;
27 import org.eclipse.jface.window.Window;
28 import org.eclipse.nebula.widgets.tablecombo.TableCombo;
29 import org.eclipse.swt.SWT;
30 import org.eclipse.swt.events.SelectionAdapter;
31 import org.eclipse.swt.events.SelectionEvent;
32 import org.eclipse.swt.widgets.Button;
33 import org.eclipse.swt.widgets.Combo;
34 import org.eclipse.swt.widgets.Composite;
35 import org.eclipse.swt.widgets.Control;
36 import org.eclipse.swt.widgets.Display;
37 import org.eclipse.swt.widgets.Label;
38 import org.eclipse.swt.widgets.Shell;
39 import org.eclipse.swt.widgets.Table;
40 import org.eclipse.swt.widgets.TableItem;
41 import org.eclipse.swt.widgets.Text;
42 import org.eclipse.swt.widgets.Widget;
43 import org.simantics.Simantics;
44 import org.simantics.databoard.Bindings;
45 import org.simantics.db.ReadGraph;
46 import org.simantics.db.Resource;
47 import org.simantics.db.WriteGraph;
48 import org.simantics.db.common.request.IndexRoot;
49 import org.simantics.db.common.request.ReadRequest;
50 import org.simantics.db.common.request.WriteRequest;
51 import org.simantics.db.exception.DatabaseException;
52 import org.simantics.db.exception.RuntimeDatabaseException;
53 import org.simantics.db.layer0.QueryIndexUtils;
54 import org.simantics.db.layer0.request.ActiveModels;
55 import org.simantics.db.layer0.request.PropertyInfo;
56 import org.simantics.db.layer0.request.PropertyInfoRequest;
57 import org.simantics.db.layer0.util.Layer0Utils;
58 import org.simantics.diagram.stubs.DiagramResource;
59 import org.simantics.diagram.stubs.G2DResource;
60 import org.simantics.district.network.ontology.DistrictNetworkResource;
61 import org.simantics.district.region.ontology.DiagramRegionsResource;
62 import org.simantics.district.route.ontology.RouteResource;
63 import org.simantics.district.selection.ElementSelectionResource;
64 import org.simantics.district.selection.ElementSelectionUtils;
65 import org.simantics.district.selection.ElementSelector;
66 import org.simantics.district.selection.ElementSelector.AggregateCondition;
67 import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
68 import org.simantics.district.selection.ElementSelector.All;
69 import org.simantics.district.selection.ElementSelector.Condition;
70 import org.simantics.district.selection.ElementSelector.DiagramGenerator;
71 import org.simantics.district.selection.ElementSelector.ExplicitGenerator;
72 import org.simantics.district.selection.ElementSelector.Generator;
73 import org.simantics.district.selection.ElementSelector.ModelGenerator;
74 import org.simantics.district.selection.ElementSelector.PropertyCondition;
75 import org.simantics.district.selection.ElementSelector.PropertySelector;
76 import org.simantics.district.selection.ElementSelector.RegionCondition;
77 import org.simantics.district.selection.ElementSelector.RouteCondition;
78 import org.simantics.district.selection.ElementSelector.Selector;
79 import org.simantics.district.selection.ui.ElementSelectionTools;
80 import org.simantics.layer0.Layer0;
81 import org.simantics.layer0.utils.direct.GraphUtils;
82 import org.simantics.modeling.ModelingResources;
83 import org.simantics.structural.stubs.StructuralResource2;
84 import org.simantics.utils.datastructures.Arrays;
85 import org.simantics.utils.datastructures.Pair;
86 import org.simantics.utils.ui.color.Color;
87 import org.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
89
90 public class EditSelectorDialog extends Dialog {
91
92         private static final RowLayoutFactory ROW_LAYOUT = RowLayoutFactory.fillDefaults().wrap(false);
93         private static final ImageDescriptor CROSS_IMAGE = ResourceLocator.imageDescriptorFromBundle("com.famfamfam.silk", "icons/cross.png").get();
94         private static final ImageDescriptor PLUS_IMAGE = ResourceLocator.imageDescriptorFromBundle("com.famfamfam.silk", "icons/add.png").get();
95
96         private static Logger LOGGER = LoggerFactory.getLogger(EditSelectorDialog.class);
97         
98         private ElementSelector elementSelector;
99         
100         // Currently selected elements
101         Collection<Resource> currentSelection;
102
103         // Data for comboboxes
104         private List<Resource> diagrams;
105         private ArrayList<String> diagramNames;
106         
107         private String[] regionNames;
108         private Resource[] regionResources;
109
110         private String[] routeNames;
111         private Resource[] routeResources;
112         
113         private List<Resource> componentTypes;
114         private List<String> componentTypeNames;
115         
116         private List<String> propertyNames;
117         private List<String> propertyLabels;
118         
119         private Composite conditionPanel;
120
121         // Dialog fields
122         private int generatorIndex;
123         private Combo sourceField;
124
125         private String name;
126         private Text nameField;
127
128         private Color color;
129         private TableCombo colorCombo;
130         
131         private Resource diagram;
132         private Combo diagramField;
133
134         private int selectorIndex;
135         private Combo selectorField;
136
137         private Resource componentType;
138         private Combo componentTypeField;
139         
140         private String propertyName;
141         private Combo propertyField;
142
143         private int numberOfItems;
144         private Text nField;
145
146         private Condition condition;
147
148         // Dialog area component
149         private Composite content;
150
151         private int diagramIndex;
152
153         private LocalResourceManager resourceManager;
154         
155         static class ValidationException extends Exception {
156                 private static final long serialVersionUID = 1L;
157
158                 public ValidationException(String message) {
159                         super(message);
160                 }
161         }
162         
163         // Function type for updating condition objects with optional validation
164         static interface Updater {
165                 // If 'validate' is true, a runtime exception may be thrown for invalid values
166                 void update(boolean validate) throws ValidationException;
167         }
168         
169         final static Updater NULL_UPDATE = validate -> {};
170         
171         // Called to read values from controls into conditions
172         Updater updater = NULL_UPDATE;
173         @Inject
174         public EditSelectorDialog(Shell shell, ElementSelector elementSelector, Collection<Resource> currentSelection) {
175                 super(shell);
176                 
177                 this.elementSelector = elementSelector;
178                 if (elementSelector != null) {
179                         try {
180                                 Simantics.getSession().sync(new ReadRequest() {
181                                         @Override
182                                         public void run(ReadGraph graph) throws DatabaseException {
183                                                 elementSelector.buildSelection(graph);
184                                         }
185                                 });
186                         } catch (DatabaseException e1) {
187                                 LOGGER.error("Failed to read element selector resource " + elementSelector.getResource(), e1);
188                                 throw new RuntimeDatabaseException(e1);
189                         }
190                 }
191                 
192                 this.currentSelection = currentSelection;
193                 
194                 Map<Resource, String> diagramMap = ElementSelector.findDiagrams();
195                 diagrams = new ArrayList<Resource>(diagramMap.size());
196                 diagramNames = new ArrayList<String>(diagramMap.size());
197                 diagramMap.entrySet()
198                         .stream()
199                         .sorted(Comparator.comparing(e -> e.getValue()))
200                         .forEachOrdered(e -> {
201                                 diagrams.add(e.getKey());
202                                 diagramNames.add(e.getValue());
203                         });
204                 
205                 name = elementSelector != null ? elementSelector.getName() : "";
206                 propertyName = "";
207                 numberOfItems = 1;
208                 generatorIndex = 0;
209                 selectorIndex = 0;
210                 diagram = null;
211                 condition = null;
212                 
213                 if (elementSelector != null) {
214                         float[] color = elementSelector.getColor();
215                         if (color != null) {
216                                 this.color = ElementSelectionTools.literalToColor(color);
217                         }
218                         
219                         Generator generator = elementSelector.getGenerator();
220                         if (generator instanceof ModelGenerator) {
221                                 generatorIndex = 0;
222                         }
223                         else if (generator instanceof DiagramGenerator) {
224                                 generatorIndex = 1;
225                                 diagram = ((DiagramGenerator)generator).diagram;
226                         }
227                         else if (generator instanceof ExplicitGenerator) {
228                                 generatorIndex = 2;
229                         }
230                         else {
231                                 throw new IllegalStateException("Unknown generator type " + generator.getClass().getName());
232                         }
233                         
234                         Selector selector = elementSelector.getSelector();
235                         if (selector instanceof All) {
236                                 selectorIndex = 0;
237                         }
238                         else if (selector instanceof PropertySelector) {
239                                 PropertySelector propertySelector = (PropertySelector)selector;
240                                 selectorIndex = propertySelector.smallest ? 1 : 2;
241                                 propertyName = propertySelector.propertyName;
242                                 numberOfItems = propertySelector.resultCount;
243                         }
244                         else {
245                                 throw new IllegalStateException("Unknwon selector type " + selector.getClass().getName());
246                         }
247                         
248                         condition = elementSelector.getCondition();
249                         
250                         componentType = elementSelector.getSelector().componentType;
251                 }
252                 
253                 readRegions(diagram);
254                 readRoutes();
255                 readComponentTypes();
256                 updatePropertyList();
257         }
258
259         private void readComponentTypes() {
260                 try {
261                         Simantics.getSession().syncRequest(new ReadRequest() {
262                                 @Override
263                                 public void run(ReadGraph graph) throws DatabaseException {
264                                         Layer0 L0 = Layer0.getInstance(graph);
265                                         List<Resource> types = findComponentTypes(graph);
266                                         
267                                         componentTypes = new ArrayList<>(types.size() + 1);
268                                         componentTypeNames = new ArrayList<>(types.size() + 1);
269                                         
270                                         componentTypes.add(null);
271                                         componentTypeNames.add("Any type");
272                                         componentTypes.addAll(types);
273                                         for (Resource t : types) {
274                                                 componentTypeNames.add(graph.getValue2(t, L0.HasName));
275                                         }
276                                 }
277                         });
278                 } catch (DatabaseException e) {
279                         LOGGER.error("Failed to read district component types", e);
280                 }
281         }
282
283         private void readRoutes() {
284                 final Map<Resource, String> routes = new HashMap<>();
285                 
286                 try {
287                         Simantics.getSession().syncRequest(new ReadRequest() {
288                                 @Override
289                                 public void run(ReadGraph graph) throws DatabaseException {
290                                         Layer0 L0 = Layer0.getInstance(graph);
291                                         RouteResource ROUTE = RouteResource.getInstance(graph);
292                                         
293                                         Resource model = ActiveModels.getPossibleActiveModel(graph, Simantics.getProjectResource());
294                                         List<Resource> routeCollection = QueryIndexUtils.searchByType(graph, model, ROUTE.Route);
295                                         for (Resource r : routeCollection) {
296                                                 String name = graph.getRelatedValue(r, L0.HasLabel);
297                                                 routes.put(r, name);
298                                         }
299                                 }
300                         });
301                 } catch (DatabaseException e) {
302                         LOGGER.error("Failed to read routes in the model", e);
303                 }
304                 
305                 routeNames = routes.values().toArray(new String[routes.size()]);
306                 routeResources = routes.keySet().toArray(new Resource[routes.size()]);
307         }
308
309         private void readRegions(Resource diagram) {
310                 final Map<Resource, String> regions = new HashMap<>();
311                 
312                 try {
313                         Simantics.getSession().syncRequest(new ReadRequest() {
314                                 @Override
315                                 public void run(ReadGraph graph) throws DatabaseException {
316                                         Layer0 L0 = Layer0.getInstance(graph);
317                                         ModelingResources MOD = ModelingResources.getInstance(graph);
318                                         DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph);
319                                         
320                                         // If a specific diagram is given, use that
321                                         Collection<Resource> ds = diagram != null ? Collections.singleton(diagram) : diagrams;
322                                         
323                                         for (Resource composite : ds) {
324                                                 Resource diagram = graph.getSingleObject(composite, MOD.CompositeToDiagram);
325                                                 for (Resource r : graph.getObjects(diagram, DR.hasRegion)) {
326                                                         if (!graph.isInstanceOf(r, DR.Region))
327                                                                 continue;
328                                                         String name = graph.getRelatedValue(r, L0.HasLabel);
329                                                         regions.put(r, name);
330                                                 }
331                                         }
332                                 }
333                         });
334                 } catch (DatabaseException e) {
335                         LOGGER.error("Failed to read regions in the model", e);
336                 }
337                 
338                 regionNames = regions.values().toArray(new String[regions.size()]);
339                 regionResources = regions.keySet().toArray(new Resource[regions.size()]);
340         }
341         
342         private void updateDialog() {
343                 try {
344                         updater.update(false);
345                 } catch (ValidationException e) {
346                         // Should not happend with argument false
347                         assert(false);
348                 }
349                 
350                 updater = updateConditionPanel();
351                 
352                 content.layout(true, true);
353                 getShell().pack();
354         }
355
356         @Override
357         protected void okPressed() {
358                 try {
359                         generatorIndex = sourceField.getSelectionIndex();
360                         if (generatorIndex == 1) {
361                                 int selectionIndex = diagramField.getSelectionIndex();
362                                 if (selectionIndex < 0) {
363                                         diagramField.setFocus();
364                                         throw new ValidationException("Please select a diagram");
365                                 }
366                                 
367                                 diagram = diagrams.get(selectionIndex);
368                         }
369                         
370                         name = nameField.getText();
371                         if (name.isEmpty()) {
372                                 nameField.setFocus();
373                                 throw new ValidationException("Please enter a name");
374                         }
375                         
376                         {
377                                 int index = colorCombo.getSelectionIndex();
378                                 if (index >= 0) {
379                                         color = new Color(colorCombo.getTable().getItem(index).getBackground().getRGB());
380                                 }
381                         }
382                         
383                         componentType = componentTypes.get(componentTypeField.getSelectionIndex());
384                         
385                         selectorIndex = selectorField.getSelectionIndex();
386                         if (selectorIndex != 0) {
387                                 int propertyIndex = propertyField.getSelectionIndex();
388                                 propertyName = propertyIndex >= 0 ? propertyNames.get(propertyIndex) : propertyField.getText();
389                                 if (propertyName.isEmpty()) {
390                                         propertyField.setFocus();
391                                         throw new ValidationException("Please select a property");
392                                 }
393                         }
394                         
395                         // Try to parse number of items
396                         if (useNumberOfItems()) {
397                                 try {
398                                         numberOfItems = Integer.parseInt(nField.getText());
399                                         if (numberOfItems <= 0) {
400                                                 nField.selectAll();
401                                                 nField.setFocus();
402                                                 throw new ValidationException("Number of elements must be positive");
403                                         }
404                                 } catch (NumberFormatException e) {
405                                         nField.selectAll();
406                                         nField.setFocus();
407                                         throw new ValidationException("Please enter a valid number of elements");
408                                 }
409                         }
410         
411                         // To to update condition definitions
412                         updater.update(true);
413                 } catch (ValidationException e) {
414                         MessageDialog.openError(this.getShell(), "Missing data", e.getMessage());
415                         return;
416                 }
417                 
418                 super.okPressed();
419         }
420         
421         public void writeSelection() throws DatabaseException {
422                 Simantics.getSession().syncRequest(new WriteRequest() {
423                         @Override
424                         public void perform(WriteGraph graph) throws DatabaseException {
425                                 Layer0 L0 = Layer0.getInstance(graph);
426                                 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
427                                 ModelingResources MOD = ModelingResources.getInstance(graph);
428                                 DiagramResource DIA = DiagramResource.getInstance(graph);
429                                 
430                                 graph.markUndoPoint();
431                                 Layer0Utils.addCommentMetadata(graph, "Created new element selection");
432                                 
433                                 Resource lib = ElementSelectionUtils.ensureSelectionLibrary(graph);
434                                 
435                                 // Selection
436                                 Resource selection;
437                                 if (elementSelector != null) {
438                                         selection = elementSelector.getResource();
439                                         graph.deny(selection);
440                                 }
441                                 else {
442                                         selection = graph.newResource();
443                                 }
444                                 
445                                 graph.claim(selection, L0.InstanceOf, ES.Selection);
446                                 graph.claimLiteral(selection, L0.HasName, L0.String, UUID.randomUUID().toString());
447                                 graph.claimLiteral(selection, L0.HasLabel, L0.String, name);
448                                 graph.claim(selection, L0.PartOf, lib);
449                                 
450                                 if (color != null) {
451                                         float[] value = ElementSelectionTools.colorToLiteral(color);
452                                         graph.claimLiteral(selection, ES.Selection_HasHighlightColor, G2DResource.getInstance(graph).Color, value, Bindings.FLOAT_ARRAY);
453                                 }
454                                 
455                                 // Generator
456                                 Resource generator = graph.newResource();
457                                 Resource generatorType;
458                                 switch (generatorIndex) {
459                                 case 0:
460                                         generatorType = ES.Generator_Model;
461                                         break;
462                                 case 1:
463                                         generatorType = ES.Generator_Diagram;
464                                         Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
465                                         graph.claim(generator, ES.Generator_HasDiagram, composite != null ? composite : diagram);
466                                         break;
467                                 case 2:
468                                         generatorType = ES.Generator_Explicit;
469                                         for (Resource r : currentSelection) {
470                                                 // No connections
471                                                 if (graph.isInstanceOf(r, DIA.Connection))
472                                                         continue;
473                                                 if (!graph.isInstanceOf(r, DIA.Element)) {
474                                                         if (!graph.hasStatement(r, MOD.ComponentToElement))
475                                                                 continue;
476                                                         
477                                                         r = graph.getPossibleObject(r, MOD.ComponentToElement);
478                                                         if (r == null)
479                                                                 continue;
480                                                 }
481                                                         
482                                                 graph.claim(generator, ES.Generator_HasSelectedElement, r);
483                                         }
484                                         break;
485                                 default: throw new IllegalStateException("Invalid source index " + generatorIndex);
486                                 }
487                                 graph.claim(generator, L0.InstanceOf, generatorType);
488                                 graph.claim(selection, ES.Selection_HasGenerator, generator);
489                                 
490                                 // Selector
491                                 Resource selector = graph.newResource();
492                                 Resource selectorType;
493                                 switch (selectorIndex) {
494                                 case 0: selectorType = ES.Selector_All; break;
495                                 case 1: selectorType = ES.Selector_NLowest; break;
496                                 case 2: selectorType = ES.Selector_NHighest; break;
497                                 default: throw new IllegalStateException("Invalid selector index " + selectorIndex);
498                                 }
499                                 graph.claim(selector, L0.InstanceOf, selectorType);
500                                 graph.claim(selection, ES.Selection_HasSelector, selector);
501                                 graph.deny(selector, ES.Selector_HasMapping);
502                                 if (componentType != null)
503                                         graph.claim(selector, ES.Selector_HasMapping, componentType);
504                                 
505                                 if (selectorIndex > 0) {
506                                         graph.claimLiteral(selector, ES.PropertySelector_HasSelectionPropertyName, L0.String, propertyName);
507                                         graph.claimLiteral(selector, ES.PropertySelector_HasResultCount, L0.Integer, numberOfItems);
508                                 }
509                                 
510                                 // Condition
511                                 if (condition != null) {
512                                         Resource conditionResource = condition.update(graph);
513                                         graph.claim(selection, ES.Selection_HasCondition, conditionResource);
514                                 }
515                         }
516                 });
517         }
518
519         private boolean isDiagramFieldVisible() {
520                 return generatorIndex == 1;
521         }
522
523         private boolean useNumberOfItems() {
524                 return selectorIndex != 0;
525         }
526
527         @Override
528         protected Control createDialogArea(Composite parent) {
529                 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
530                 
531                 // Set dialog title
532                 getShell().setText("Edit element selector");
533                 
534                 content = new Composite(parent, SWT.NONE);
535                 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(content);
536                 
537                 // Name
538                 Label nameLabel = new Label(content, SWT.NONE);
539                 nameLabel.setText("Name");
540                 GridDataFactory.swtDefaults().applyTo(nameLabel);
541                 
542                 nameField = new Text(content, SWT.BORDER);
543                 nameField.setEditable(true);
544                 nameField.setText(name);
545                 GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT).applyTo(nameField);
546                 
547                 // Highlight style
548                 Label label = new Label(content, SWT.NONE);
549                 GridDataFactory.swtDefaults().applyTo(label);
550                 label.setText("Highlight color");
551                 
552                 colorCombo = new TableCombo(content, SWT.BORDER | SWT.READ_ONLY);
553                 GridDataFactory.swtDefaults().hint(80, SWT.DEFAULT).applyTo(colorCombo);
554                 Table colorTable = colorCombo.getTable();
555                 colorCombo.getTextControl().setEnabled(false);
556                 Display display = getShell().getDisplay();
557                 colorTable.setFont(display.getSystemFont());
558                 createColorItem(colorTable, "Red", SWT.COLOR_RED);
559                 createColorItem(colorTable, "Green", SWT.COLOR_GREEN);
560                 createColorItem(colorTable, "Blue", SWT.COLOR_BLUE);
561                 createColorItem(colorTable, "Yellow", SWT.COLOR_YELLOW);
562                 createColorItem(colorTable, "Cyan", SWT.COLOR_CYAN);
563                 createColorItem(colorTable, "Magenta", SWT.COLOR_MAGENTA);
564                 TableItem other = new TableItem(colorTable, SWT.NONE);
565                 other.setText("Other...");
566
567                 if (color != null) {
568                         int i;
569                         for (i = 0; i < 6; i++) {
570                                 if (colorTable.getItem(i).getBackground().getRGB().equals(color.getRgb())) {
571                                         colorCombo.select(i);
572                                         break;
573                                 }
574                         }
575                         
576                         if (i == 6) {
577                                 other.setBackground(resourceManager.createColor(color.getRgb()));
578                                 colorCombo.select(6);
579                         }
580                 }
581                 
582                 colorCombo.addSelectionListener(new SelectionAdapter() {
583                         @Override
584                         public void widgetSelected(SelectionEvent e) {
585                                 int index = colorCombo.getSelectionIndex();
586                                 if (index == 6) {
587                                         org.simantics.utils.ui.color.ColorDialog colorDialog = new org.simantics.utils.ui.workbench.dialogs.ColorDialog(getParentShell());
588                                         int result = colorDialog.open();
589                                         if (result == Window.OK) {
590                                                 other.setBackground(resourceManager.createColor(colorDialog.getColor().getRgb()));
591                                                 colorCombo.clearSelection();
592                                                 colorCombo.select(6);
593                                                 colorTable.redraw();
594                                         }
595                                 }
596                         }
597                 });
598                 
599                 // Selector
600                 Label selectorLabel = new Label(content, SWT.NONE);
601                 selectorLabel.setText("Select");
602                 GridDataFactory.swtDefaults().applyTo(selectorLabel);
603
604                 Composite selectorComposite = new Composite(content, SWT.NONE);
605                 GridDataFactory.swtDefaults().applyTo(selectorComposite);
606                 RowLayoutFactory.fillDefaults().applyTo(selectorComposite);
607                 
608                 nField = new Text(selectorComposite, SWT.BORDER);
609                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(nField);
610                 if (useNumberOfItems())
611                         nField.setText(Integer.toString(numberOfItems));
612                 nField.setEnabled(useNumberOfItems());
613                 
614                 componentTypeField = new Combo(selectorComposite, SWT.READ_ONLY);
615                 RowDataFactory.swtDefaults().applyTo(componentTypeField);
616                 componentTypeField.setItems(componentTypeNames.toArray(new String[] {}));
617                 {
618                         int index = componentTypes.indexOf(componentType);
619                         componentTypeField.select(index >= 0 ? index : 0);
620                 }
621                 
622                 // Update property selection controls when component type changes
623                 componentTypeField.addSelectionListener(new SelectionAdapter() {
624                         @Override
625                         public void widgetSelected(SelectionEvent e) {
626                                 int index = componentTypeField.getSelectionIndex();
627                                 componentType = index >= 0 ? componentTypes.get(index) : null;
628                                 updatePropertyList();
629                                 propertyField.setItems(propertyLabels.toArray(new String[] {}));
630                                 
631                                 updateDialog();
632                         }
633                 });
634                 
635                 new Label(selectorComposite, SWT.NONE).setText("with");
636                 
637                 selectorField = new Combo(selectorComposite, SWT.BORDER | SWT.READ_ONLY);
638                 selectorField.setItems("All", "Lowest", "Highest");
639                 selectorField.select(selectorIndex);
640                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(selectorField);
641                 
642                 propertyField = new Combo(selectorComposite, SWT.NONE);
643                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(propertyField);
644                 propertyField.setItems(propertyLabels.toArray(new String[] {}));
645                 {
646                         int index = propertyName != null ? propertyNames.indexOf(propertyName) : -1;
647                         if (index >= 0)
648                                 propertyField.select(index);
649                         else
650                                 propertyField.setText(propertyName != null ? propertyName : "");
651                 }
652                 propertyField.setEnabled(useNumberOfItems());
653                 
654                 selectorField.addSelectionListener(new SelectionAdapter() {
655                         @Override
656                         public void widgetSelected(SelectionEvent e) {
657                                 selectorIndex = selectorField.getSelectionIndex();
658                                 
659                                 boolean enable = useNumberOfItems();
660                                 nField.setEnabled(enable);
661                                 propertyField.setEnabled(enable);
662                                 
663                                 nField.setText(enable ? Integer.toString(numberOfItems) : "");
664                         }
665                 });
666                 
667                 // Source
668                 Label sourceLabel = new Label(content, SWT.NONE);
669                 sourceLabel.setText("from");
670                 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(sourceLabel);
671                 
672                 Composite sourceComposite = new Composite(content, SWT.NONE);
673                 GridDataFactory.swtDefaults().applyTo(sourceComposite);
674                 RowLayoutFactory.fillDefaults().applyTo(sourceComposite);
675                 
676                 sourceField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY);
677                 RowDataFactory.swtDefaults().applyTo(sourceField);
678                 sourceField.setItems("Whole model", "Diagram", "Current selection");
679                 sourceField.select(generatorIndex);
680                 
681                 diagramField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY);
682                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(diagramField);
683                 diagramField.setItems(diagramNames.toArray(new String[diagramNames.size()]));
684                 diagramField.setEnabled(isDiagramFieldVisible());
685                 
686                 diagramIndex = diagram != null ? diagrams.indexOf(diagram) : -1;
687                 diagramField.select(diagramIndex);
688                 
689                 sourceField.addSelectionListener(new SelectionAdapter() {
690                         @Override
691                         public void widgetSelected(SelectionEvent e) {
692                                 generatorIndex = sourceField.getSelectionIndex();
693                                 boolean enabled = isDiagramFieldVisible();
694                                 if (!enabled) {
695                                         diagramIndex = diagramField.getSelectionIndex();
696                                         diagramField.clearSelection();
697                                 } else {
698                                         if (diagramIndex >= 0)
699                                                 diagramField.select(diagramIndex);
700                                         else
701                                                 diagramField.clearSelection();
702                                 }
703                                 diagramField.setEnabled(enabled);
704
705                                 // Refresh list of regions for current diagram
706                                 diagram = enabled ? (diagramIndex >= 0 ? diagrams.get(diagramIndex) : null) : null;
707                                 readRegions(diagram);
708                                 updateDialog();
709                         }
710                 });
711                 
712                 sourceField.select(generatorIndex);
713                 
714                 // Condition
715                 label = new Label(content, SWT.NONE);
716                 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(label);
717                 label.setText("where");
718                 
719                 conditionPanel = new Composite(content, SWT.NONE);
720                 GridDataFactory.swtDefaults().minSize(400, SWT.DEFAULT).grab(true, false).applyTo(conditionPanel);
721                 GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionPanel);
722                 
723                 updater = updateConditionPanel();
724                 
725                 return content;
726         }
727
728         private TableItem createColorItem(Table colorTable, String name, int color) {
729                 TableItem item = new TableItem(colorTable, SWT.BORDER);
730                 item.setText(name);
731                 item.setBackground(colorTable.getDisplay().getSystemColor(color));
732                 return item;
733         }
734         
735         private Updater updateConditionPanel() {
736                 // Erase contents
737                 for (Widget c : conditionPanel.getChildren())
738                         c.dispose();
739                 
740                 return createConditionPanelFor(conditionPanel, condition, cond -> condition = cond);
741         }
742
743         private Updater createConditionPanelFor(final Composite parent, final Condition condition, final Consumer<Condition> consumer) {
744                 // Create new contents
745                 Button notCheck = new Button(parent, SWT.CHECK);
746                 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(notCheck);
747                 notCheck.setText("not");
748                 notCheck.setSelection(condition != null && condition.isInverse);
749                 
750                 Composite conditionComp = new Composite(parent, SWT.NONE);
751                 GridDataFactory.fillDefaults().applyTo(conditionComp);
752                 
753                 Combo typeCombo = new Combo(conditionComp, SWT.BORDER | SWT.READ_ONLY);
754                 typeCombo.setItems(
755                                 "No condition",
756                                 "Property",
757                                 "In region",
758                                 "On route",
759                                 "All of",
760                                 "Any of"
761                         );
762                 
763                 typeCombo.addSelectionListener(new ConditionTypeSelectionListener(typeCombo, consumer, condition));
764
765                 final Updater updater;
766                 if (condition instanceof PropertyCondition) {
767                         typeCombo.select(1);
768                         updater = createPropertyConditionPanel(conditionComp, (PropertyCondition)condition);
769                 } else if (condition instanceof RegionCondition) {
770                         typeCombo.select(2);
771                         updater = createRegionConditionPanel(conditionComp, (RegionCondition)condition);
772                 } else if (condition instanceof RouteCondition) {
773                         typeCombo.select(3);
774                         updater = createRouteConditionPanel(conditionComp, (RouteCondition)condition);
775                 } else if (condition instanceof AggregateCondition) {
776                         AggregateCondition cond = (AggregateCondition) condition;
777                         typeCombo.select(cond.type.equals(Type.CONJUNCTION) ? 4 : 5);
778                         updater = createAggregateConditionPanel(conditionComp, cond);
779                 } else {
780                         ROW_LAYOUT.applyTo(conditionComp);
781                         notCheck.setEnabled(false);
782                         typeCombo.select(0);
783                         return NULL_UPDATE;
784                 }
785                 
786                 return validate -> {
787                         updater.update(validate);
788                         condition.isInverse = notCheck.getSelection();
789                 };
790         }
791         
792         private final class ConditionTypeSelectionListener extends SelectionAdapter {
793                 private final Combo typeCombo;
794                 private final Consumer<Condition> consumer;
795                 private final Condition finalCondition;
796         
797                 private ConditionTypeSelectionListener(Combo typeCombo, Consumer<Condition> consumer, Condition finalCondition) {
798                         this.typeCombo = typeCombo;
799                         this.consumer = consumer;
800                         this.finalCondition = finalCondition;
801                 }
802         
803                 @Override
804                 public void widgetSelected(SelectionEvent e) {
805                         int index = typeCombo.getSelectionIndex();
806                         Condition newCondition = finalCondition;
807                         switch (index) {
808                         case 0:
809                                 newCondition = null;
810                                 break;
811                         case 1:
812                                 newCondition = createPropertyCondition("", null, null);
813                                 break;
814                         case 2:
815                                 newCondition = createRegionCondition(null);
816                                 break;
817                         case 3:
818                                 newCondition = createRouteCondition(null);
819                                 break;
820                         case 4:
821                                 if (newCondition instanceof AggregateCondition)
822                                         ((AggregateCondition)newCondition).type = Type.CONJUNCTION;
823                                 else
824                                         newCondition = createAggregateCondition(null, new ArrayList<>(), true, false);
825                                 break;
826                         case 5:
827                                 if (newCondition instanceof AggregateCondition)
828                                         ((AggregateCondition)newCondition).type = Type.DISJUNCTION;
829                                 else
830                                         newCondition = createAggregateCondition(null, new ArrayList<>(), false, false);
831                                 break;
832                         }
833                 
834                         consumer.accept(newCondition);
835                         
836                         updateDialog();
837                 }
838         }
839
840         private Updater createAggregateConditionPanel(Composite conditionComp, AggregateCondition cond) {
841                 GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionComp);
842                 new Label(conditionComp, SWT.NONE); // Eat extra column
843                 
844                 int n = cond.conditions.size();
845                 final Updater[] updates = new Updater[n];
846                 for (int i = 0; i < n; i++) {
847                         updates[i] = createConditionRowPanel(conditionComp, cond, i);
848                 }
849                 
850                 Button addButton = new Button(conditionComp, SWT.PUSH);
851                 GridDataFactory.swtDefaults().applyTo(addButton);
852                 addButton.setImage(resourceManager.createImage(PLUS_IMAGE));
853                 
854                 addButton.addSelectionListener(new SelectionAdapter() {
855                         @Override
856                         public void widgetSelected(SelectionEvent e) {
857                                 cond.conditions.add(createPropertyCondition("", null, null));
858                                 updateDialog();
859                         }
860                 });
861                 
862                 return validate -> {
863                         for (Updater updater : updates)
864                                 updater.update(validate);
865                 };
866         }
867
868         private Updater createConditionRowPanel(Composite parent, AggregateCondition parentCondition, final int i) {
869                 GridLayoutFactory conditionLayout = GridLayoutFactory.fillDefaults().numColumns(2);
870                 GridDataFactory swtDefaults = GridDataFactory.swtDefaults();
871                 
872                 Condition c = parentCondition.conditions.get(i);
873                 
874                 Composite row = new Composite(parent, SWT.NONE);
875                 conditionLayout.applyTo(row);
876                 swtDefaults.applyTo(row);
877                 
878                 Consumer<Condition> update = cd -> {
879                         if (cd != null)
880                                 parentCondition.conditions.set(i, cd);
881                         else
882                                 parentCondition.conditions.remove(i);
883                 };
884                 
885                 Updater updater = createConditionPanelFor(row, c, update);
886                 
887                 Button removeButton = new Button(parent, SWT.PUSH);
888                 swtDefaults.align(SWT.BEGINNING, SWT.BEGINNING).applyTo(removeButton);
889                 removeButton.setImage(resourceManager.createImage(CROSS_IMAGE));
890                 
891                 removeButton.addSelectionListener(new SelectionAdapter() {
892                         @Override
893                         public void widgetSelected(SelectionEvent e) {
894                                 parentCondition.conditions.remove(i);
895                                 updateDialog();
896                         }
897                 });
898                 
899                 return updater;
900         }
901
902         private Updater createRouteConditionPanel(Composite conditionComp, RouteCondition condition) {
903                 ROW_LAYOUT.applyTo(conditionComp);
904
905                 // Create combo-box
906                 Combo routeCombo = new Combo(conditionComp, SWT.READ_ONLY);
907                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(routeCombo);
908                 routeCombo.setItems(routeNames);
909                 
910                 // Set current selection
911                 int index = Arrays.indexOf(routeResources, condition.routeResource);
912                 if (index >= 0)
913                         routeCombo.select(index);
914
915                 // Register update
916                 return validate -> {
917                         int i = routeCombo.getSelectionIndex();
918                         if (validate && i < 0) {
919                                 routeCombo.forceFocus();
920                                 throw new RuntimeException("Must select a route");
921                         }
922                                 
923                         condition.routeResource = i >= 0 ? routeResources[i] : null;
924                 };
925         }
926
927         private Updater createRegionConditionPanel(Composite conditionComp, RegionCondition condition) {
928                 ROW_LAYOUT.applyTo(conditionComp);
929                 
930                 // Create combo-box
931                 Combo regionCombo = new Combo(conditionComp, SWT.READ_ONLY);
932                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(regionCombo);
933                 regionCombo.setItems(regionNames);
934                 
935                 // Set current selection
936                 int index = Arrays.indexOf(regionResources, condition.regionResource);
937                 if (index >= 0)
938                         regionCombo.select(index);
939                 
940                 // Register update
941                 return validate -> {
942                         int i = regionCombo.getSelectionIndex();
943                         if (validate && i < 0) {
944                                 regionCombo.forceFocus();
945                                 throw new ValidationException("Please select a region");
946                         }
947                         
948                         condition.regionResource = i >= 0 ? regionResources[i] : null;
949                 };
950         }
951
952         private Updater createPropertyConditionPanel(Composite conditionComp, PropertyCondition condition) {
953                 ROW_LAYOUT.applyTo(conditionComp);
954                 
955                 Text lowerLimitText = new Text(conditionComp, SWT.BORDER);
956                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(lowerLimitText);
957                 lowerLimitText.setText(condition.lowerLimit != null ? Double.toString(condition.lowerLimit) : "");
958                 
959                 new Label(conditionComp, SWT.NONE).setText("\u2264");
960                 
961                 Combo propertyNameText = new Combo(conditionComp, SWT.NONE);
962                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(propertyNameText);
963                 propertyNameText.setItems(propertyLabels.toArray(new String[] {}));
964                 int index = propertyNames.indexOf(condition.propertyName);
965                 if (index >= 0)
966                         propertyNameText.select(index);
967                 else
968                         propertyNameText.setText(condition.propertyName);
969                 
970                 new Label(conditionComp, SWT.NONE).setText("\u2264");
971                 
972                 Text upperLimitText = new Text(conditionComp, SWT.BORDER);
973                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(upperLimitText);
974                 upperLimitText.setText(condition.upperLimit != null ? Double.toString(condition.upperLimit) : "");
975                 
976                 // Register update
977                 return validate ->  {
978                         try {
979                                 String text = lowerLimitText.getText();
980                                 condition.lowerLimit = text.isEmpty() ? null : Double.parseDouble(text);
981                         } catch (NumberFormatException e) {
982                                 if (validate) {
983                                         lowerLimitText.selectAll();
984                                         lowerLimitText.forceFocus();
985                                         throw new ValidationException("Please enter a valid lower limit");
986                                 }
987                         }
988                         
989                         try {
990                                 String text = upperLimitText.getText();
991                                 condition.upperLimit = text.isEmpty() ? null : Double.parseDouble(text);
992                         } catch (NumberFormatException e) {
993                                 if (validate) {
994                                         upperLimitText.selectAll();
995                                         upperLimitText.forceFocus();
996                                         throw new ValidationException("Please enter a valid upper limit");
997                                 }
998                         }
999                         
1000                         int ind = propertyNameText.getSelectionIndex();
1001                         String name;
1002                         if (ind >= 0)
1003                                 name = propertyNames.get(ind);
1004                         else
1005                                 name = propertyNameText.getText();
1006                         
1007                         if (validate && name.isEmpty()) {
1008                                 propertyNameText.forceFocus();
1009                                 throw new ValidationException("Please select a property");
1010                         } else {
1011                                 condition.propertyName = name;
1012                         }
1013                 };
1014         }
1015
1016         private static Condition createPropertyCondition(String propertyName, Double lowerLimit, Double upperLimit) {
1017                 return new PropertyCondition(null, propertyName, lowerLimit, upperLimit);
1018         }
1019         
1020         private static Condition createRegionCondition(Resource regionResource) {
1021                 return new RegionCondition(null, regionResource, null);
1022         }
1023         
1024         private static Condition createRouteCondition(Resource route) {
1025                 return new RouteCondition(null, route, null);
1026         }
1027         
1028         private static Condition createAggregateCondition(Resource existingResource, List<Condition> subConditions, boolean isConjunction, boolean isInverse) {
1029                 Type type = isConjunction ? Type.CONJUNCTION : Type.DISJUNCTION;
1030                 AggregateCondition condition = new AggregateCondition(null, type, subConditions);
1031                 condition.isInverse = isInverse;
1032                 return condition;
1033         }
1034         
1035         static List<Resource> findComponentTypes(ReadGraph graph) throws DatabaseException {
1036                 DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
1037                 Resource project = Simantics.getProjectResource();
1038                 Resource model = ActiveModels.getPossibleActiveModel(graph, project);
1039                 
1040                 return QueryIndexUtils.searchByType(graph, model, DN.Mapping_Base);
1041         }
1042         
1043         void updatePropertyList() {
1044                 propertyNames = new ArrayList<>();
1045                 propertyLabels = new ArrayList<>();
1046                 
1047                 Collection<Resource> types = componentType != null ? Collections.singleton(componentType) : componentTypes;
1048                 Set<Pair<String, String>> properties = new HashSet<>();
1049                 
1050                 try {
1051                         Simantics.getSession().syncRequest(new ReadRequest() {
1052                                 @Override
1053                                 public void run(ReadGraph graph) throws DatabaseException {
1054                                         Layer0 L0 = Layer0.getInstance(graph);
1055                                         
1056                                         for (Resource type : types) {
1057                                                 if (type == null)
1058                                                         continue;
1059                                                 
1060                                                 Resource ct = graph.getPossibleObject(type, DistrictNetworkResource.getInstance(graph).Mapping_ComponentType);
1061                                                 if (ct == null)
1062                                                         continue;
1063                                                 
1064                                                 if (graph.isInstanceOf(ct, L0.String)) {
1065                                                         Resource indexRoot = graph.syncRequest(new IndexRoot(type));
1066                                                         String name = graph.getValue(ct);
1067                                                         ct = GraphUtils.getPossibleChild(graph, indexRoot, name);
1068                                                         if (ct == null)
1069                                                                 continue;
1070                                                 }
1071                                                 
1072                                                 for (Resource prop : graph.getObjects(ct, L0.DomainOf)) {
1073                                                         if (!graph.isInstanceOf(prop, StructuralResource2.getInstance(graph).Property))
1074                                                                 continue;
1075                                                         
1076                                                         // Filter only numeric properties
1077                                                         PropertyInfo info = graph.syncRequest(new PropertyInfoRequest(prop));
1078                                                         if (info != null && info.requiredValueType != null && !isNumericValueType(info.requiredValueType))
1079                                                                 continue;
1080                                                         
1081                                                         String name = graph.getRelatedValue2(prop, L0.HasName);
1082                                                         String label = graph.getPossibleRelatedValue2(prop, L0.HasLabel);
1083                                                         if (label == null) label = name;
1084                                                         
1085                                                         properties.add(Pair.make(label, name));
1086                                                 }
1087                                         }
1088                                 }
1089                         });
1090                 } catch (DatabaseException e) {
1091                         LOGGER.error("Failed to read district component properties", e);
1092                 }
1093                 
1094                 propertyNames.clear();
1095                 propertyLabels.clear();
1096                 properties.stream().sorted(Comparator.comparing(p -> p.first)).forEachOrdered(p -> {
1097                         propertyLabels.add(p.first);
1098                         propertyNames.add(p.second);
1099                 });
1100         }
1101         
1102         static boolean isNumericValueType(String requiredValueType) {
1103                 switch (requiredValueType) {
1104                 case "Integer":
1105                 case "Long":
1106                 case "Double":
1107                 case "Float":
1108                         return true;
1109                 default:
1110                         return false;
1111                 }
1112         }
1113 }