]> gerrit.simantics Code Review - simantics/district.git/blob
b708d8e978695dc18919f05a80a824409d2b1564
[simantics/district.git] /
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                         selectorIndex = selectorField.getSelectionIndex();
362                         int propertyIndex = propertyField.getSelectionIndex();
363                         propertyName = propertyIndex >= 0 ? propertyNames.get(propertyIndex) : propertyField.getText();
364                         if (propertyName.isEmpty()) {
365                                 propertyField.setFocus();
366                                 throw new ValidationException("Please select a property");
367                         }
368                         
369                         // Try to parse number of items
370                         if (useNumberOfItems()) {
371                                 try {
372                                         numberOfItems = Integer.parseInt(nField.getText());
373                                         if (numberOfItems <= 0) {
374                                                 nField.selectAll();
375                                                 nField.setFocus();
376                                                 throw new ValidationException("Number of elements must be positive");
377                                         }
378                                 } catch (NumberFormatException e) {
379                                         nField.selectAll();
380                                         nField.setFocus();
381                                         throw new ValidationException("Please enter a valid number of elements");
382                                 }
383                         }
384         
385                         // To to update condition definitions
386                         updater.update(true);
387                 } catch (ValidationException e) {
388                         MessageDialog.openError(this.getShell(), "Missing data", e.getMessage());
389                         return;
390                 }
391                 
392                 super.okPressed();
393         }
394         
395         public void writeSelection() throws DatabaseException {
396                 Simantics.getSession().syncRequest(new WriteRequest() {
397                         @Override
398                         public void perform(WriteGraph graph) throws DatabaseException {
399                                 Layer0 L0 = Layer0.getInstance(graph);
400                                 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
401                                 ModelingResources MOD = ModelingResources.getInstance(graph);
402                                 DiagramResource DIA = DiagramResource.getInstance(graph);
403                                 
404                                 graph.markUndoPoint();
405                                 Layer0Utils.addCommentMetadata(graph, "Created new element selection");
406                                 
407                                 Resource lib = ElementSelectionUtils.ensureSelectionLibrary(graph);
408                                 
409                                 // Selection
410                                 Resource selection;
411                                 if (elementSelector != null) {
412                                         selection = elementSelector.getResource();
413                                         graph.deny(selection);
414                                 }
415                                 else {
416                                         selection = graph.newResource();
417                                 }
418                                 
419                                 graph.claim(selection, L0.InstanceOf, ES.Selection);
420                                 graph.claimLiteral(selection, L0.HasName, L0.String, UUID.randomUUID().toString());
421                                 graph.claimLiteral(selection, L0.HasLabel, L0.String, name);
422                                 graph.claim(selection, L0.PartOf, lib);
423                                 
424                                 // Generator
425                                 Resource generator = graph.newResource();
426                                 Resource generatorType;
427                                 switch (generatorIndex) {
428                                 case 0:
429                                         generatorType = ES.Generator_Model;
430                                         break;
431                                 case 1:
432                                         generatorType = ES.Generator_Diagram;
433                                         Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
434                                         graph.claim(generator, ES.Generator_HasDiagram, composite != null ? composite : diagram);
435                                         break;
436                                 case 2:
437                                         generatorType = ES.Generator_Explicit;
438                                         for (Resource r : currentSelection) {
439                                                 // No connections
440                                                 if (graph.isInstanceOf(r, DIA.Connection))
441                                                         continue;
442                                                 if (!graph.isInstanceOf(r, DIA.Element)) {
443                                                         if (!graph.hasStatement(r, MOD.ComponentToElement))
444                                                                 continue;
445                                                         
446                                                         r = graph.getPossibleObject(r, MOD.ComponentToElement);
447                                                         if (r == null)
448                                                                 continue;
449                                                 }
450                                                         
451                                                 graph.claim(generator, ES.Generator_HasSelectedElement, r);
452                                         }
453                                         break;
454                                 default: throw new IllegalStateException("Invalid source index " + generatorIndex);
455                                 }
456                                 graph.claim(generator, L0.InstanceOf, generatorType);
457                                 graph.claim(selection, ES.Selection_HasGenerator, generator);
458                                 
459                                 // Selector
460                                 Resource selector = graph.newResource();
461                                 Resource selectorType;
462                                 switch (selectorIndex) {
463                                 case 0: selectorType = ES.Selector_All; break;
464                                 case 1: selectorType = ES.Selector_NLowest; break;
465                                 case 2: selectorType = ES.Selector_NHighest; break;
466                                 default: throw new IllegalStateException("Invalid selector index " + selectorIndex);
467                                 }
468                                 graph.claim(selector, L0.InstanceOf, selectorType);
469                                 graph.claim(selection, ES.Selection_HasSelector, selector);
470                                 graph.deny(selector, ES.Selector_HasMapping);
471                                 if (componentType != null)
472                                         graph.claim(selector, ES.Selector_HasMapping, componentType);
473                                 
474                                 if (selectorIndex > 0) {
475                                         graph.claimLiteral(selector, ES.PropertySelector_HasSelectionPropertyName, L0.String, propertyName);
476                                         graph.claimLiteral(selector, ES.PropertySelector_HasResultCount, L0.Integer, numberOfItems);
477                                 }
478                                 
479                                 // Condition
480                                 if (condition != null) {
481                                         Resource conditionResource = condition.update(graph);
482                                         graph.claim(selection, ES.Selection_HasCondition, conditionResource);
483                                 }
484                         }
485                 });
486         }
487
488         private boolean isDiagramFieldVisible() {
489                 return generatorIndex == 1;
490         }
491
492         private boolean useNumberOfItems() {
493                 return selectorIndex != 0;
494         }
495
496         @Override
497         protected Control createDialogArea(Composite parent) {
498                 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
499                 
500                 // Set dialog title
501                 getShell().setText("Edit element selector");
502                 
503                 content = new Composite(parent, SWT.NONE);
504                 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(content);
505                 
506                 // Name
507                 Label nameLabel = new Label(content, SWT.NONE);
508                 nameLabel.setText("Name");
509                 GridDataFactory.swtDefaults().applyTo(nameLabel);
510                 
511                 nameField = new Text(content, SWT.BORDER);
512                 nameField.setEditable(true);
513                 nameField.setText(name);
514                 GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT).applyTo(nameField);
515                 
516                 // Selector
517                 Label selectorLabel = new Label(content, SWT.NONE);
518                 selectorLabel.setText("Select");
519                 GridDataFactory.swtDefaults().applyTo(selectorLabel);
520
521                 Composite selectorComposite = new Composite(content, SWT.NONE);
522                 GridDataFactory.swtDefaults().applyTo(selectorComposite);
523                 RowLayoutFactory.fillDefaults().applyTo(selectorComposite);
524                 
525                 nField = new Text(selectorComposite, SWT.BORDER);
526                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(nField);
527                 if (useNumberOfItems())
528                         nField.setText(Integer.toString(numberOfItems));
529                 nField.setEnabled(useNumberOfItems());
530                 
531                 componentTypeField = new Combo(selectorComposite, SWT.READ_ONLY);
532                 RowDataFactory.swtDefaults().applyTo(componentTypeField);
533                 componentTypeField.setItems(componentTypeNames.toArray(new String[] {}));
534                 {
535                         int index = componentTypes.indexOf(componentType);
536                         componentTypeField.select(index >= 0 ? index : 0);
537                 }
538                 
539                 // Update property selection controls when component type changes
540                 componentTypeField.addSelectionListener(new SelectionAdapter() {
541                         @Override
542                         public void widgetSelected(SelectionEvent e) {
543                                 int index = componentTypeField.getSelectionIndex();
544                                 componentType = index >= 0 ? componentTypes.get(index) : null;
545                                 updatePropertyList();
546                                 propertyField.setItems(propertyLabels.toArray(new String[] {}));
547                                 
548                                 updateDialog();
549                         }
550                 });
551                 
552                 new Label(selectorComposite, SWT.NONE).setText("with");
553                 
554                 selectorField = new Combo(selectorComposite, SWT.BORDER | SWT.READ_ONLY);
555                 selectorField.setItems("All", "Lowest", "Highest");
556                 selectorField.select(selectorIndex);
557                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(selectorField);
558                 
559                 propertyField = new Combo(selectorComposite, SWT.NONE);
560                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(propertyField);
561                 propertyField.setItems(propertyLabels.toArray(new String[] {}));
562                 {
563                         int index = propertyName != null ? propertyNames.indexOf(propertyName) : -1;
564                         if (index >= 0)
565                                 propertyField.select(index);
566                         else
567                                 propertyField.setText(propertyName != null ? propertyName : "");
568                 }
569                 propertyField.setEnabled(useNumberOfItems());
570                 
571                 selectorField.addSelectionListener(new SelectionAdapter() {
572                         @Override
573                         public void widgetSelected(SelectionEvent e) {
574                                 selectorIndex = selectorField.getSelectionIndex();
575                                 
576                                 boolean enable = useNumberOfItems();
577                                 nField.setEnabled(enable);
578                                 propertyField.setEnabled(enable);
579                                 
580                                 nField.setText(enable ? Integer.toString(numberOfItems) : "");
581                         }
582                 });
583                 
584                 // Source
585                 Label sourceLabel = new Label(content, SWT.NONE);
586                 sourceLabel.setText("from");
587                 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(sourceLabel);
588                 
589                 Composite sourceComposite = new Composite(content, SWT.NONE);
590                 GridDataFactory.swtDefaults().applyTo(sourceComposite);
591                 RowLayoutFactory.fillDefaults().applyTo(sourceComposite);
592                 
593                 sourceField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY);
594                 RowDataFactory.swtDefaults().applyTo(sourceField);
595                 sourceField.setItems("Whole model", "Diagram", "Current selection");
596                 sourceField.select(generatorIndex);
597                 
598                 diagramField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY);
599                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(diagramField);
600                 diagramField.setItems(diagramNames.toArray(new String[diagramNames.size()]));
601                 diagramField.setEnabled(isDiagramFieldVisible());
602                 
603                 diagramIndex = diagram != null ? diagrams.indexOf(diagram) : -1;
604                 diagramField.select(diagramIndex);
605                 
606                 sourceField.addSelectionListener(new SelectionAdapter() {
607                         @Override
608                         public void widgetSelected(SelectionEvent e) {
609                                 generatorIndex = sourceField.getSelectionIndex();
610                                 boolean enabled = isDiagramFieldVisible();
611                                 if (!enabled) {
612                                         diagramIndex = diagramField.getSelectionIndex();
613                                         diagramField.clearSelection();
614                                 } else {
615                                         if (diagramIndex >= 0)
616                                                 diagramField.select(diagramIndex);
617                                         else
618                                                 diagramField.clearSelection();
619                                 }
620                                 diagramField.setEnabled(enabled);
621
622                                 // Refresh list of regions for current diagram
623                                 diagram = enabled ? (diagramIndex >= 0 ? diagrams.get(diagramIndex) : null) : null;
624                                 readRegions(diagram);
625                                 updateDialog();
626                         }
627                 });
628                 
629                 sourceField.select(generatorIndex);
630                 
631                 // Condition
632                 Label label = new Label(content, SWT.NONE);
633                 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(label);
634                 label.setText("where");
635                 
636                 conditionPanel = new Composite(content, SWT.NONE);
637                 GridDataFactory.swtDefaults().span(1, 2).minSize(400, SWT.DEFAULT).grab(true, false).applyTo(conditionPanel);
638                 GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionPanel);
639                 
640                 updater = updateConditionPanel();
641                 
642                 return content;
643         }
644         
645         private Updater updateConditionPanel() {
646                 // Erase contents
647                 for (Widget c : conditionPanel.getChildren())
648                         c.dispose();
649                 
650                 return createConditionPanelFor(conditionPanel, condition, cond -> condition = cond);
651         }
652
653         private Updater createConditionPanelFor(final Composite parent, final Condition condition, final Consumer<Condition> consumer) {
654                 // Create new contents
655                 Button notCheck = new Button(parent, SWT.CHECK);
656                 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(notCheck);
657                 notCheck.setText("not");
658                 notCheck.setSelection(condition != null && condition.isInverse);
659                 
660                 Composite conditionComp = new Composite(parent, SWT.NONE);
661                 GridDataFactory.fillDefaults().applyTo(conditionComp);
662                 
663                 Combo typeCombo = new Combo(conditionComp, SWT.BORDER | SWT.READ_ONLY);
664                 typeCombo.setItems(
665                                 "No condition",
666                                 "Property",
667                                 "In region",
668                                 "On route",
669                                 "All of",
670                                 "Any of"
671                         );
672                 
673                 typeCombo.addSelectionListener(new ConditionTypeSelectionListener(typeCombo, consumer, condition));
674
675                 final Updater updater;
676                 if (condition instanceof PropertyCondition) {
677                         typeCombo.select(1);
678                         updater = createPropertyConditionPanel(conditionComp, (PropertyCondition)condition);
679                 } else if (condition instanceof RegionCondition) {
680                         typeCombo.select(2);
681                         updater = createRegionConditionPanel(conditionComp, (RegionCondition)condition);
682                 } else if (condition instanceof RouteCondition) {
683                         typeCombo.select(3);
684                         updater = createRouteConditionPanel(conditionComp, (RouteCondition)condition);
685                 } else if (condition instanceof AggregateCondition) {
686                         AggregateCondition cond = (AggregateCondition) condition;
687                         typeCombo.select(cond.type.equals(Type.CONJUNCTION) ? 4 : 5);
688                         updater = createAggregateConditionPanel(conditionComp, cond);
689                 } else {
690                         ROW_LAYOUT.applyTo(conditionComp);
691                         notCheck.setEnabled(false);
692                         typeCombo.select(0);
693                         return NULL_UPDATE;
694                 }
695                 
696                 return validate -> {
697                         updater.update(validate);
698                         condition.isInverse = notCheck.getSelection();
699                 };
700         }
701         
702         private final class ConditionTypeSelectionListener extends SelectionAdapter {
703                 private final Combo typeCombo;
704                 private final Consumer<Condition> consumer;
705                 private final Condition finalCondition;
706         
707                 private ConditionTypeSelectionListener(Combo typeCombo, Consumer<Condition> consumer, Condition finalCondition) {
708                         this.typeCombo = typeCombo;
709                         this.consumer = consumer;
710                         this.finalCondition = finalCondition;
711                 }
712         
713                 @Override
714                 public void widgetSelected(SelectionEvent e) {
715                         int index = typeCombo.getSelectionIndex();
716                         Condition newCondition = finalCondition;
717                         switch (index) {
718                         case 0:
719                                 newCondition = null;
720                                 break;
721                         case 1:
722                                 newCondition = createPropertyCondition("", null, null);
723                                 break;
724                         case 2:
725                                 newCondition = createRegionCondition(null);
726                                 break;
727                         case 3:
728                                 newCondition = createRouteCondition(null);
729                                 break;
730                         case 4:
731                                 if (newCondition instanceof AggregateCondition)
732                                         ((AggregateCondition)newCondition).type = Type.CONJUNCTION;
733                                 else
734                                         newCondition = createAggregateCondition(null, new ArrayList<>(), true, false);
735                                 break;
736                         case 5:
737                                 if (newCondition instanceof AggregateCondition)
738                                         ((AggregateCondition)newCondition).type = Type.DISJUNCTION;
739                                 else
740                                         newCondition = createAggregateCondition(null, new ArrayList<>(), false, false);
741                                 break;
742                         }
743                 
744                         consumer.accept(newCondition);
745                         
746                         updateDialog();
747                 }
748         }
749
750         private Updater createAggregateConditionPanel(Composite conditionComp, AggregateCondition cond) {
751                 GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionComp);
752                 new Label(conditionComp, SWT.NONE); // Eat extra column
753                 
754                 int n = cond.conditions.size();
755                 final Updater[] updates = new Updater[n];
756                 for (int i = 0; i < n; i++) {
757                         updates[i] = createConditionRowPanel(conditionComp, cond, i);
758                 }
759                 
760                 Button addButton = new Button(conditionComp, SWT.PUSH);
761                 GridDataFactory.swtDefaults().applyTo(addButton);
762                 addButton.setImage(resourceManager.createImage(PLUS_IMAGE));
763                 
764                 addButton.addSelectionListener(new SelectionAdapter() {
765                         @Override
766                         public void widgetSelected(SelectionEvent e) {
767                                 cond.conditions.add(createPropertyCondition("", null, null));
768                                 updateDialog();
769                         }
770                 });
771                 
772                 return validate -> {
773                         for (Updater updater : updates)
774                                 updater.update(validate);
775                 };
776         }
777
778         private Updater createConditionRowPanel(Composite parent, AggregateCondition parentCondition, final int i) {
779                 GridLayoutFactory conditionLayout = GridLayoutFactory.fillDefaults().numColumns(2);
780                 GridDataFactory swtDefaults = GridDataFactory.swtDefaults();
781                 
782                 Condition c = parentCondition.conditions.get(i);
783                 
784                 Composite row = new Composite(parent, SWT.NONE);
785                 conditionLayout.applyTo(row);
786                 swtDefaults.applyTo(row);
787                 
788                 Consumer<Condition> update = cd -> {
789                         if (cd != null)
790                                 parentCondition.conditions.set(i, cd);
791                         else
792                                 parentCondition.conditions.remove(i);
793                 };
794                 
795                 Updater updater = createConditionPanelFor(row, c, update);
796                 
797                 Button removeButton = new Button(parent, SWT.PUSH);
798                 swtDefaults.align(SWT.BEGINNING, SWT.BEGINNING).applyTo(removeButton);
799                 removeButton.setImage(resourceManager.createImage(CROSS_IMAGE));
800                 
801                 removeButton.addSelectionListener(new SelectionAdapter() {
802                         @Override
803                         public void widgetSelected(SelectionEvent e) {
804                                 parentCondition.conditions.remove(i);
805                                 updateDialog();
806                         }
807                 });
808                 
809                 return updater;
810         }
811
812         private Updater createRouteConditionPanel(Composite conditionComp, RouteCondition condition) {
813                 ROW_LAYOUT.applyTo(conditionComp);
814
815                 // Create combo-box
816                 Combo routeCombo = new Combo(conditionComp, SWT.READ_ONLY);
817                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(routeCombo);
818                 routeCombo.setItems(routeNames);
819                 
820                 // Set current selection
821                 int index = Arrays.indexOf(routeResources, condition.routeResource);
822                 if (index >= 0)
823                         routeCombo.select(index);
824
825                 // Register update
826                 return validate -> {
827                         int i = routeCombo.getSelectionIndex();
828                         if (validate && i < 0) {
829                                 routeCombo.forceFocus();
830                                 throw new RuntimeException("Must select a route");
831                         }
832                                 
833                         condition.routeResource = i >= 0 ? routeResources[i] : null;
834                 };
835         }
836
837         private Updater createRegionConditionPanel(Composite conditionComp, RegionCondition condition) {
838                 ROW_LAYOUT.applyTo(conditionComp);
839                 
840                 // Create combo-box
841                 Combo regionCombo = new Combo(conditionComp, SWT.READ_ONLY);
842                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(regionCombo);
843                 regionCombo.setItems(regionNames);
844                 
845                 // Set current selection
846                 int index = Arrays.indexOf(regionResources, condition.regionResource);
847                 if (index >= 0)
848                         regionCombo.select(index);
849                 
850                 // Register update
851                 return validate -> {
852                         int i = regionCombo.getSelectionIndex();
853                         if (validate && i < 0) {
854                                 regionCombo.forceFocus();
855                                 throw new ValidationException("Please select a region");
856                         }
857                         
858                         condition.regionResource = i >= 0 ? regionResources[i] : null;
859                 };
860         }
861
862         private Updater createPropertyConditionPanel(Composite conditionComp, PropertyCondition condition) {
863                 ROW_LAYOUT.applyTo(conditionComp);
864                 
865                 Text lowerLimitText = new Text(conditionComp, SWT.BORDER);
866                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(lowerLimitText);
867                 lowerLimitText.setText(condition.lowerLimit != null ? Double.toString(condition.lowerLimit) : "");
868                 
869                 new Label(conditionComp, SWT.NONE).setText("\u2264");
870                 
871                 Combo propertyNameText = new Combo(conditionComp, SWT.NONE);
872                 RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(propertyNameText);
873                 propertyNameText.setItems(propertyLabels.toArray(new String[] {}));
874                 int index = propertyNames.indexOf(condition.propertyName);
875                 if (index >= 0)
876                         propertyNameText.select(index);
877                 else
878                         propertyNameText.setText(condition.propertyName);
879                 
880                 new Label(conditionComp, SWT.NONE).setText("\u2264");
881                 
882                 Text upperLimitText = new Text(conditionComp, SWT.BORDER);
883                 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(upperLimitText);
884                 upperLimitText.setText(condition.upperLimit != null ? Double.toString(condition.upperLimit) : "");
885                 
886                 // Register update
887                 return validate ->  {
888                         try {
889                                 String text = lowerLimitText.getText();
890                                 condition.lowerLimit = text.isEmpty() ? null : Double.parseDouble(text);
891                         } catch (NumberFormatException e) {
892                                 if (validate) {
893                                         lowerLimitText.selectAll();
894                                         lowerLimitText.forceFocus();
895                                         throw new ValidationException("Please enter a valid lower limit");
896                                 }
897                         }
898                         
899                         try {
900                                 String text = upperLimitText.getText();
901                                 condition.upperLimit = text.isEmpty() ? null : Double.parseDouble(text);
902                         } catch (NumberFormatException e) {
903                                 if (validate) {
904                                         upperLimitText.selectAll();
905                                         upperLimitText.forceFocus();
906                                         throw new ValidationException("Please enter a valid upper limit");
907                                 }
908                         }
909                         
910                         int ind = propertyNameText.getSelectionIndex();
911                         String name;
912                         if (ind >= 0)
913                                 name = propertyNames.get(ind);
914                         else
915                                 name = propertyNameText.getText();
916                         
917                         if (validate && name.isEmpty()) {
918                                 propertyNameText.forceFocus();
919                                 throw new ValidationException("Please select a property");
920                         } else {
921                                 condition.propertyName = name;
922                         }
923                 };
924         }
925
926         private static Condition createPropertyCondition(String propertyName, Double lowerLimit, Double upperLimit) {
927                 return new PropertyCondition(null, propertyName, lowerLimit, upperLimit);
928         }
929         
930         private static Condition createRegionCondition(Resource regionResource) {
931                 return new RegionCondition(null, regionResource, null);
932         }
933         
934         private static Condition createRouteCondition(Resource route) {
935                 return new RouteCondition(null, route, null);
936         }
937         
938         private static Condition createAggregateCondition(Resource existingResource, List<Condition> subConditions, boolean isConjunction, boolean isInverse) {
939                 Type type = isConjunction ? Type.CONJUNCTION : Type.DISJUNCTION;
940                 AggregateCondition condition = new AggregateCondition(null, type, subConditions);
941                 condition.isInverse = isInverse;
942                 return condition;
943         }
944         
945         static List<Resource> findComponentTypes(ReadGraph graph) throws DatabaseException {
946                 DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
947                 Resource project = Simantics.getProjectResource();
948                 Resource model = ActiveModels.getPossibleActiveModel(graph, project);
949                 
950                 return QueryIndexUtils.searchByType(graph, model, DN.Mapping_Base);
951         }
952         
953         void updatePropertyList() {
954                 propertyNames = new ArrayList<>();
955                 propertyLabels = new ArrayList<>();
956                 
957                 Collection<Resource> types = componentType != null ? Collections.singleton(componentType) : componentTypes;
958                 Set<Pair<String, String>> properties = new HashSet<>();
959                 
960                 try {
961                         Simantics.getSession().syncRequest(new ReadRequest() {
962                                 @Override
963                                 public void run(ReadGraph graph) throws DatabaseException {
964                                         Layer0 L0 = Layer0.getInstance(graph);
965                                         
966                                         for (Resource type : types) {
967                                                 if (type == null)
968                                                         continue;
969                                                 
970                                                 Resource ct = graph.getPossibleObject(type, DistrictNetworkResource.getInstance(graph).Mapping_ComponentType);
971                                                 if (ct == null)
972                                                         continue;
973                                                 
974                                                 if (graph.isInstanceOf(ct, L0.String)) {
975                                                         Resource indexRoot = graph.syncRequest(new IndexRoot(type));
976                                                         String name = graph.getValue(ct);
977                                                         ct = GraphUtils.getPossibleChild(graph, indexRoot, name);
978                                                         if (ct == null)
979                                                                 continue;
980                                                 }
981                                                 
982                                                 for (Resource prop : graph.getObjects(ct, L0.DomainOf)) {
983                                                         if (!graph.isInstanceOf(prop, StructuralResource2.getInstance(graph).Property))
984                                                                 continue;
985                                                         
986                                                         // Filter only numeric properties
987                                                         PropertyInfo info = graph.syncRequest(new PropertyInfoRequest(prop));
988                                                         if (info != null && info.requiredValueType != null && !isNumericValueType(info.requiredValueType))
989                                                                 continue;
990                                                         
991                                                         String name = graph.getRelatedValue2(prop, L0.HasName);
992                                                         String label = graph.getPossibleRelatedValue2(prop, L0.HasLabel);
993                                                         if (label == null) label = name;
994                                                         
995                                                         properties.add(Pair.make(label, name));
996                                                 }
997                                         }
998                                 }
999                         });
1000                 } catch (DatabaseException e) {
1001                         LOGGER.error("Failed to read district component properties", e);
1002                 }
1003                 
1004                 propertyNames.clear();
1005                 propertyLabels.clear();
1006                 properties.stream().sorted(Comparator.comparing(p -> p.first)).forEachOrdered(p -> {
1007                         propertyLabels.add(p.first);
1008                         propertyNames.add(p.second);
1009                 });
1010         }
1011         
1012         static boolean isNumericValueType(String requiredValueType) {
1013                 switch (requiredValueType) {
1014                 case "Integer":
1015                 case "Long":
1016                 case "Double":
1017                 case "Float":
1018                         return true;
1019                 default:
1020                         return false;
1021                 }
1022         }
1023 }