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