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