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