]> gerrit.simantics Code Review - simantics/district.git/blob
b68c3fe11148504c43348c540b0b21ea51f2cb1b
[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.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.UUID;
9
10 import javax.inject.Inject;
11
12 import org.eclipse.core.runtime.IStatus;
13 import org.eclipse.core.runtime.Status;
14 import org.eclipse.jface.dialogs.Dialog;
15 import org.eclipse.jface.dialogs.ErrorDialog;
16 import org.eclipse.jface.layout.GridDataFactory;
17 import org.eclipse.jface.layout.GridLayoutFactory;
18 import org.eclipse.jface.layout.RowLayoutFactory;
19 import org.eclipse.jface.window.Window;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.custom.StackLayout;
22 import org.eclipse.swt.events.SelectionAdapter;
23 import org.eclipse.swt.events.SelectionEvent;
24 import org.eclipse.swt.widgets.Button;
25 import org.eclipse.swt.widgets.Combo;
26 import org.eclipse.swt.widgets.Composite;
27 import org.eclipse.swt.widgets.Control;
28 import org.eclipse.swt.widgets.Label;
29 import org.eclipse.swt.widgets.Shell;
30 import org.eclipse.swt.widgets.Text;
31 import org.simantics.Simantics;
32 import org.simantics.db.ReadGraph;
33 import org.simantics.db.Resource;
34 import org.simantics.db.Session;
35 import org.simantics.db.WriteGraph;
36 import org.simantics.db.common.request.ReadRequest;
37 import org.simantics.db.common.request.WriteRequest;
38 import org.simantics.db.exception.DatabaseException;
39 import org.simantics.db.exception.RuntimeDatabaseException;
40 import org.simantics.db.layer0.QueryIndexUtils;
41 import org.simantics.db.layer0.request.ActiveModels;
42 import org.simantics.db.layer0.util.Layer0Utils;
43 import org.simantics.db.request.Read;
44 import org.simantics.db.request.WriteResult;
45 import org.simantics.diagram.stubs.DiagramResource;
46 import org.simantics.district.region.ontology.DiagramRegionsResource;
47 import org.simantics.district.route.ontology.RouteResource;
48 import org.simantics.district.selection.ElementSelectionResource;
49 import org.simantics.district.selection.ElementSelectionUtils;
50 import org.simantics.district.selection.ElementSelector;
51 import org.simantics.district.selection.ElementSelector.AggregateCondition;
52 import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
53 import org.simantics.district.selection.ElementSelector.All;
54 import org.simantics.district.selection.ElementSelector.Condition;
55 import org.simantics.district.selection.ElementSelector.DiagramGenerator;
56 import org.simantics.district.selection.ElementSelector.ExplicitGenerator;
57 import org.simantics.district.selection.ElementSelector.Generator;
58 import org.simantics.district.selection.ElementSelector.ModelGenerator;
59 import org.simantics.district.selection.ElementSelector.PropertyCondition;
60 import org.simantics.district.selection.ElementSelector.PropertySelector;
61 import org.simantics.district.selection.ElementSelector.RegionCondition;
62 import org.simantics.district.selection.ElementSelector.RouteCondition;
63 import org.simantics.district.selection.ElementSelector.Selector;
64 import org.simantics.layer0.Layer0;
65 import org.simantics.modeling.ModelingResources;
66 import org.simantics.utils.datastructures.Arrays;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 public class EditSelectorDialog extends Dialog {
71
72         private static Logger LOGGER = LoggerFactory.getLogger(EditSelectorDialog.class);
73         
74         private ElementSelector elementSelector;
75         
76         // Currently selected elements
77         Collection<Resource> currentSelection;
78
79         // Data for comboboxes
80         Map<Resource, String> diagrams;
81         
82         private String[] regionNames;
83         private Resource[] regionResources;
84
85         private String[] routeNames;
86         private Resource[] routeResources;
87
88         // Dialog fields
89         private int generatorIndex;
90         private Combo sourceField;
91
92         private String name;
93         private Text nameField;
94
95         private Resource diagram;
96         private Combo diagramField;
97
98         private int selectorIndex;
99         private Combo selectorField;
100
101         private String propertyName;
102         private Text propertyField;
103
104         private int numberOfItems;
105         private Text nField;
106
107         private Condition condition;
108         private Button removeConditionButton;
109         private Text conditionLabel;
110
111         // Dialog area component
112         private Composite content;
113
114         @Inject
115         public EditSelectorDialog(Shell shell, ElementSelector elementSelector, Collection<Resource> currentSelection) {
116                 super(shell);
117                 
118                 this.elementSelector = elementSelector;
119                 if (elementSelector != null) {
120                         try {
121                                 Simantics.getSession().sync(new ReadRequest() {
122                                         @Override
123                                         public void run(ReadGraph graph) throws DatabaseException {
124                                                 elementSelector.buildSelection(graph);
125                                         }
126                                 });
127                         } catch (DatabaseException e1) {
128                                 LOGGER.error("Failed to read element selector resource " + elementSelector.getResource(), e1);
129                                 throw new RuntimeDatabaseException(e1);
130                         }
131                 }
132                 
133                 this.currentSelection = currentSelection;
134                 
135                 final Map<Resource, String> regions = new HashMap<>();
136                 final Map<Resource, String> routes = new HashMap<>();
137                 
138                 try {
139                         Simantics.getSession().syncRequest(new Read<Void>() {
140                                 @Override
141                                 public Void perform(ReadGraph graph) throws DatabaseException {
142                                         Resource model = ActiveModels.getPossibleActiveModel(graph, Simantics.getProjectResource());
143                                         List<Resource> regionCollection = QueryIndexUtils.searchByType(graph, model, DiagramRegionsResource.getInstance(graph).Region);
144                                         for (Resource r : regionCollection) {
145                                                 String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName);
146                                                 regions.put(r, name);
147                                         }
148                                         
149                                         List<Resource> routeCollection = QueryIndexUtils.searchByType(graph, model, RouteResource.getInstance(graph).Route);
150                                         for (Resource r : routeCollection) {
151                                                 String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName);
152                                                 routes.put(r, name);
153                                         }
154                                         return null;
155                                 }
156                         });
157                 } catch (DatabaseException e) {
158                         LOGGER.error("Failed to read routes and/or regions in the model", e);
159                 }
160                 
161                 regionNames = regions.values().toArray(new String[regions.size()]);
162                 regionResources = regions.keySet().toArray(new Resource[regions.size()]);
163                 
164                 routeNames = routes.values().toArray(new String[routes.size()]);
165                 routeResources = routes.keySet().toArray(new Resource[routes.size()]);
166         
167                 name = elementSelector != null ? elementSelector.getName() : "";
168                 propertyName = "";
169                 numberOfItems = 1;
170                 generatorIndex = 0;
171                 selectorIndex = 0;
172                 diagram = null;
173                 condition = null;
174                 
175                 if (elementSelector != null) {
176                         Generator generator = elementSelector.getGenerator();
177                         if (generator instanceof ModelGenerator) {
178                                 generatorIndex = 0;
179                         }
180                         else if (generator instanceof DiagramGenerator) {
181                                 generatorIndex = 1;
182                                 diagram = ((DiagramGenerator)generator).diagram;
183                         }
184                         else if (generator instanceof ExplicitGenerator) {
185                                 generatorIndex = 2;
186                                 // TODO: Management of explicit lists of elements
187                         }
188                         else {
189                                 throw new IllegalStateException("Unknown generator type " + generator.getClass().getName());
190                         }
191                         
192                         Selector selector = elementSelector.getSelector();
193                         if (selector instanceof All) {
194                                 selectorIndex = 0;
195                         }
196                         else if (selector instanceof PropertySelector) {
197                                 PropertySelector propertySelector = (PropertySelector)selector;
198                                 selectorIndex = propertySelector.smallest ? 1 : 2;
199                                 propertyName = propertySelector.propertyName;
200                                 numberOfItems = propertySelector.resultCount;
201                         }
202                         else {
203                                 throw new IllegalStateException("Unknwon selector type " + selector.getClass().getName());
204                         }
205                         
206                         condition = elementSelector.getCondition();
207                 }
208         }
209         
210         @Override
211         protected void okPressed() {
212                 generatorIndex = sourceField.getSelectionIndex();
213                 if (generatorIndex == 1) {
214                         int selectionIndex = diagramField.getSelectionIndex();
215                         if (selectionIndex < 0) {
216                                 ErrorDialog.openError(getShell(), "Error", "Please select a diagram", new Status(IStatus.ERROR, "org.simantics.district.selection.ui", "No diagram selected"));
217                                 return;
218                         }
219                         
220                         diagram = new ArrayList<Resource>(diagrams.keySet()).get(selectionIndex);
221                 }
222                 
223                 name = nameField.getText();
224                 
225                 selectorIndex = selectorField.getSelectionIndex();
226                 
227                 propertyName = propertyField.getText();
228                 String text = nField.getText();
229                 numberOfItems = "".equals(text) ? 0 : Integer.parseInt(text);
230                 
231                 super.okPressed();
232         }
233         
234         public void writeSelection() throws DatabaseException {
235                 Simantics.getSession().syncRequest(new WriteRequest() {
236                         @Override
237                         public void perform(WriteGraph graph) throws DatabaseException {
238                                 Layer0 L0 = Layer0.getInstance(graph);
239                                 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
240                                 ModelingResources MOD = ModelingResources.getInstance(graph);
241                                 DiagramResource DIA = DiagramResource.getInstance(graph);
242                                 
243                                 graph.markUndoPoint();
244                                 Layer0Utils.addCommentMetadata(graph, "Created new element selection");
245                                 
246                                 Resource lib = ElementSelectionUtils.ensureSelectionLibrary(graph);
247                                 
248                                 // Selection
249                                 Resource selection;
250                                 if (elementSelector != null) {
251                                         selection = elementSelector.getResource();
252                                         graph.deny(selection);
253                                 }
254                                 else {
255                                         selection = graph.newResource();
256                                 }
257                                 
258                                 graph.claim(selection, L0.InstanceOf, ES.Selection);
259                                 graph.claimLiteral(selection, L0.HasName, L0.String, UUID.randomUUID().toString());
260                                 graph.claimLiteral(selection, L0.HasLabel, L0.String, name);
261                                 graph.claim(selection, L0.PartOf, lib);
262                                 
263                                 // Generator
264                                 Resource generator = graph.newResource();
265                                 Resource generatorType;
266                                 switch (generatorIndex) {
267                                 case 0:
268                                         generatorType = ES.Generator_Model;
269                                         break;
270                                 case 1:
271                                         generatorType = ES.Generator_Diagram;
272                                         Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
273                                         graph.claim(generator, ES.Generator_HasDiagram, composite != null ? composite : diagram);
274                                         break;
275                                 case 2:
276                                         generatorType = ES.Generator_Explicit;
277                                         for (Resource r : currentSelection) {
278                                                 // No connections
279                                                 if (graph.isInstanceOf(r, DIA.Connection))
280                                                         continue;
281                                                 if (!graph.isInstanceOf(r, DIA.Element)) {
282                                                         if (!graph.hasStatement(r, MOD.ComponentToElement))
283                                                                 continue;
284                                                         
285                                                         r = graph.getPossibleObject(r, MOD.ComponentToElement);
286                                                         if (r == null)
287                                                                 continue;
288                                                 }
289                                                         
290                                                 graph.claim(generator, ES.Generator_HasSelectedElement, r);
291                                         }
292                                         break;
293                                 default: throw new IllegalStateException("Invalid source index " + generatorIndex);
294                                 }
295                                 graph.claim(generator, L0.InstanceOf, generatorType);
296                                 graph.claim(selection, ES.Selection_HasGenerator, generator);
297                                 
298                                 // Selector
299                                 Resource selector = graph.newResource();
300                                 Resource selectorType;
301                                 switch (selectorIndex) {
302                                 case 0: selectorType = ES.Selector_All; break;
303                                 case 1: selectorType = ES.Selector_NLowest; break;
304                                 case 2: selectorType = ES.Selector_NHighest; break;
305                                 default: throw new IllegalStateException("Invalid selector index " + selectorIndex);
306                                 }
307                                 graph.claim(selector, L0.InstanceOf, selectorType);
308                                 graph.claim(selection, ES.Selection_HasSelector, selector);
309                                 
310                                 if (selectorIndex > 0) {
311                                         graph.claimLiteral(selector, ES.PropertySelector_HasSelectionPropertyName, L0.String, propertyName);
312                                         graph.claimLiteral(selector, ES.PropertySelector_HasResultCount, L0.Integer, numberOfItems);
313                                 }
314                                 
315                                 // Condition
316                                 if (condition != null) {
317                                         graph.claim(selection, ES.Selection_HasCondition, condition.resource);
318                                 }
319                         }
320                 });
321         }
322
323         @Override
324         protected Control createDialogArea(Composite parent) {
325                 // Set dialog title
326                 getShell().setText("Edit element selector");
327                 
328                 content = new Composite(parent, SWT.NONE);
329                 GridLayoutFactory.swtDefaults().numColumns(3).applyTo(content);
330                 
331                 // Name
332                 Label nameLabel = new Label(content, SWT.NONE);
333                 nameLabel.setText("Name");
334                 GridDataFactory.swtDefaults().applyTo(nameLabel);
335                 
336                 nameField = new Text(content, SWT.BORDER);
337                 nameField.setEditable(true);
338                 nameField.setText(name);
339                 GridDataFactory.swtDefaults().span(2, 1).hint(200, SWT.DEFAULT).applyTo(nameField);
340                 
341                 // Source
342                 Label sourceLabel = new Label(content, SWT.NONE);
343                 sourceLabel.setText("Source");
344                 GridDataFactory.swtDefaults().applyTo(sourceLabel);
345                 
346                 sourceField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
347                 sourceField.setItems("Model", "Diagram", "Current selection");
348                 sourceField.select(generatorIndex);
349                 GridDataFactory.swtDefaults().span(1, 1).applyTo(sourceField);
350                 
351                 diagramField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
352                 GridDataFactory.swtDefaults().span(1, 1).applyTo(diagramField);
353                 diagrams = ElementSelector.findDiagrams();
354                 diagramField.setItems(diagrams.values().toArray(new String[diagrams.size()]));
355                 
356                 int diagramIndex = diagram != null ? new ArrayList<>(diagrams.keySet()).indexOf(diagram) : -1;
357                 diagramField.select(diagramIndex);
358                 
359                 sourceField.addSelectionListener(new SelectionAdapter() {
360                         @Override
361                         public void widgetSelected(SelectionEvent e) {
362                                 generatorIndex = sourceField.getSelectionIndex();
363                                 diagramField.setVisible(isDiagramFieldVisible());
364                         }
365                 });
366                 
367                 sourceField.select(generatorIndex);
368                 diagramField.setVisible(isDiagramFieldVisible());
369                 
370                 // Selector
371                 Label selectorLabel = new Label(content, SWT.NONE);
372                 selectorLabel.setText("Select");
373                 GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorLabel);
374                 
375                 selectorField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
376                 selectorField.setItems("All", "N lowest", "N highest");
377                 selectorField.select(selectorIndex);
378                 GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorField);
379                 
380                 Composite selectorComposite = new Composite(content, SWT.NONE);
381                 GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorComposite);
382                 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(selectorComposite);
383                 
384                 Label propertyLabel = new Label(selectorComposite, SWT.NONE);
385                 propertyLabel.setText("Property name");
386                 GridDataFactory.swtDefaults().applyTo(propertyLabel);
387                 
388                 propertyField = new Text(selectorComposite, SWT.BORDER);
389                 propertyField.setText(propertyName);
390                 GridDataFactory.swtDefaults().hint(80, SWT.DEFAULT).applyTo(propertyField);
391                 
392                 Label nLabel = new Label(selectorComposite, SWT.NONE);
393                 nLabel.setText("Number of elements");
394                 GridDataFactory.swtDefaults().applyTo(nLabel);
395                 
396                 nField = new Text(selectorComposite, SWT.BORDER);
397                 nField.setText(Integer.toString(numberOfItems));
398                 GridDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(nField);
399                 
400                 selectorField.addSelectionListener(new SelectionAdapter() {
401                         @Override
402                         public void widgetSelected(SelectionEvent e) {
403                                 selectorIndex = selectorField.getSelectionIndex();
404                                 selectorComposite.setVisible(isSelectorCompositeVisible());
405                         }
406                 });
407                 
408                 selectorField.select(selectorIndex);
409                 selectorComposite.setVisible(isSelectorCompositeVisible());
410                 
411                 // Condition
412                 new Label(content, SWT.NONE).setText("Condition");
413                 conditionLabel = new Text(content, SWT.READ_ONLY);
414                 GridDataFactory.swtDefaults().span(2, 1).applyTo(conditionLabel);
415                 
416                 new Label(content, SWT.NONE);
417                 Composite conditionPanel = new Composite(content, SWT.NONE);
418                 GridDataFactory.swtDefaults().span(2, 1).applyTo(conditionPanel);
419                 GridLayoutFactory.swtDefaults().margins(0, 0).numColumns(2).applyTo(conditionPanel);
420                 Button conditionButton = new Button(conditionPanel, SWT.PUSH);
421                 conditionButton.setText("Edit...");
422                 GridDataFactory.swtDefaults().span(1, 1).applyTo(conditionButton);
423                 removeConditionButton = new Button(conditionPanel, SWT.PUSH);
424                 removeConditionButton.setText("Remove");
425                 GridDataFactory.swtDefaults().span(1, 1).applyTo(removeConditionButton);
426                 
427                 updateCondition();
428                 
429                 conditionButton.addSelectionListener(new SelectionAdapter() {
430                         @Override
431                         public void widgetSelected(SelectionEvent e) {
432                                 ConditionDialog dialog = new ConditionDialog(getShell(), condition);
433                                 if (dialog.open() == Window.OK) {
434                                         try {
435                                                 condition = dialog.createCondition();
436                                         } catch (DatabaseException e1) {
437                                                 LOGGER.error("Creating a condition object failed", e1);
438                                         }
439                                         
440                                         updateCondition();
441                                 }
442                         }
443                 });
444                 
445                 removeConditionButton.addSelectionListener(new SelectionAdapter() {
446                         @Override
447                         public void widgetSelected(SelectionEvent e) {
448                                 condition = null;
449                                 updateCondition();
450                         }
451                 });
452                 
453                 return content;
454         }
455         
456         private void updateCondition() {
457                 if (condition != null) {
458                         removeConditionButton.setEnabled(true);
459                         try {
460                                 conditionLabel.setText(ElementSelector.getExpression(Simantics.getSession(), condition.resource));
461                         } catch (DatabaseException e) {
462                                 LOGGER.error("Error getting expression string for " + condition.resource);
463                         }
464                 }
465                 else {
466                         conditionLabel.setText("No condition");
467                         removeConditionButton.setEnabled(false);
468                 }
469                 
470                 content.layout();
471         }
472
473         private boolean isDiagramFieldVisible() {
474                 return generatorIndex == 1;
475         }
476
477         private boolean isSelectorCompositeVisible() {
478                 return selectorIndex != 0;
479         }
480
481         class ConditionDialog extends Dialog {
482                 // Resource of the edited condition
483                 private Resource existingResource;
484                 
485                 // Inverse condition button
486                 private boolean isInverse;
487                 private Button inverseField;
488
489                 // Condition type
490                 private int typeIndex;
491                 private Combo typeField;
492                 
493                 // Type-specific control panels under a stack layout
494                 private Composite stackPanel;
495                 private StackLayout stack;
496                 
497                 private Composite propertyPanel;
498                 private Composite regionPanel;
499                 private Composite routePanel;
500                 private Composite aggregatePanel;
501                 
502                 // Property condition
503                 private Double lowerLimit;
504                 private Double upperLimit;
505                 private String propertyName;
506         
507                 private Text propertyNameField;
508                 private Text lowerLimitField;
509                 private Text upperLimitField;
510                 
511                 // Region condition
512                 private Resource region;
513                 private Combo regionField;
514                 
515                 // Route condition
516                 private Resource route;
517                 private Combo routeField;
518                 
519                 // Aggregate condition
520                 private List<Condition> subConditions;
521                 private boolean isConjunction;
522                 
523                 private org.eclipse.swt.widgets.List subConditionField;
524                 private Combo operatorField;
525                 
526                 public ConditionDialog(Shell shell, Condition condition) {
527                         super(shell);
528                         
529                         typeIndex = 0;
530                         isInverse = false;
531                         propertyName = "";
532                         upperLimit = null;
533                         lowerLimit = null;
534                         subConditions = new ArrayList<>();
535                         
536                         existingResource = condition != null ? condition.resource : null;
537
538                         if (condition != null) {
539                                 if (condition instanceof PropertyCondition) {
540                                         typeIndex = 0;
541                                         PropertyCondition propertyCondition = (PropertyCondition)condition;
542                                         propertyName = propertyCondition.propertyName;
543                                         upperLimit = propertyCondition.upperLimit;
544                                         lowerLimit = propertyCondition.lowerLimit;
545                                 }
546                                 else if (condition instanceof RegionCondition) {
547                                         typeIndex = 1;
548                                         region = ((RegionCondition)condition).regionResource;
549                                 }
550                                 else if (condition instanceof RouteCondition) {
551                                         typeIndex = 2;
552                                         route = ((RouteCondition)condition).routeResource;
553                                 }
554                                 else if (condition instanceof AggregateCondition) {
555                                         typeIndex = 3;
556                                         subConditions = new ArrayList<>(((AggregateCondition)condition).conditions);
557                                         isConjunction = ((AggregateCondition)condition).type == Type.CONJUNCTION;
558                                         isInverse = ((AggregateCondition)condition).type == Type.NEGATION;
559                                 }
560                         }
561                 }
562                 
563                 @Override
564                 protected Control createDialogArea(Composite parent) {
565                         getShell().setText("Edit selector condition");
566                         
567                         Composite content = (Composite)super.createDialogArea(parent);
568                         GridLayoutFactory.swtDefaults().numColumns(1).applyTo(content);
569                         
570                         GridDataFactory defaultWidth = GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT);
571                         
572                         // Is inverse
573                         inverseField = new Button(content, SWT.CHECK);
574                         inverseField.setText("Is inverse");
575                         inverseField.setSelection(isInverse);
576                         
577                         // Condition type
578                         typeField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
579                         typeField.setItems("Property value", "In region", "On route", "Combination");
580                         typeField.select(typeIndex);
581                         
582                         // Type-dependent stacked panels
583                         stackPanel = new Composite(content, SWT.NONE);
584                         stack = new StackLayout();
585                         stackPanel.setLayout(stack);
586                         
587                         // Property condition panel
588                         propertyPanel = new Composite(stackPanel, SWT.NONE);
589                         GridLayoutFactory.swtDefaults().numColumns(2).applyTo(propertyPanel);
590                         
591                         new Label(propertyPanel, SWT.NONE).setText("Property name");
592                         propertyNameField = new Text(propertyPanel, SWT.BORDER);
593                         propertyNameField.setText(propertyName);
594                         defaultWidth.applyTo(propertyNameField);
595                         
596                         new Label(propertyPanel, SWT.NONE).setText("Lower limit");
597                         lowerLimitField = new Text(propertyPanel, SWT.BORDER);
598                         defaultWidth.applyTo(lowerLimitField);
599                         if (lowerLimit != null) lowerLimitField.setText(lowerLimit.toString());
600                         
601                         new Label(propertyPanel, SWT.NONE).setText("Upper limit");
602                         upperLimitField = new Text(propertyPanel, SWT.BORDER);
603                         defaultWidth.applyTo(upperLimitField);
604                         if (upperLimit != null) upperLimitField.setText(upperLimit.toString());
605                         
606                         // Region condition panel
607                         regionPanel = new Composite(stackPanel, SWT.NONE);
608                         GridLayoutFactory.swtDefaults().numColumns(2).applyTo(regionPanel);
609                         
610                         new Label(regionPanel, SWT.NONE).setText("Region");
611                         regionField = new Combo(regionPanel, SWT.BORDER | SWT.READ_ONLY);
612                         regionField.setItems(regionNames);
613                         defaultWidth.applyTo(regionField);
614                         
615                         if (region != null) {
616                                 int regionIndex = Arrays.indexOf(regionResources, region);
617                                 regionField.select(regionIndex);
618                         }
619                         else {
620                                 regionField.select(0);
621                         }
622                         
623                         // Route condition panel
624                         routePanel = new Composite(stackPanel, SWT.NONE);
625                         GridLayoutFactory.swtDefaults().numColumns(2).applyTo(routePanel);
626                         
627                         new Label(routePanel, SWT.NONE).setText("Route");
628                         routeField = new Combo(routePanel, SWT.BORDER | SWT.READ_ONLY);
629                         routeField.setItems(routeNames);
630                         defaultWidth.applyTo(routeField);
631                         
632                         if (route != null) {
633                                 int routeIndex = Arrays.indexOf(routeResources, route);
634                                 routeField.select(routeIndex);
635                         }
636                         else {
637                                 routeField.select(0);
638                         }
639                         
640                         // Aggregate condition panel
641                         aggregatePanel = new Composite(stackPanel, SWT.NONE);
642                         GridLayoutFactory.swtDefaults().numColumns(2).applyTo(aggregatePanel);
643                         
644                         new Label(aggregatePanel, SWT.NONE).setText("Operator");
645                         operatorField = new Combo(aggregatePanel, SWT.READ_ONLY);
646                         operatorField.setItems("And", "Or");
647                         operatorField.select(isConjunction ? 0 : 1);
648                         
649                         new Label(aggregatePanel, SWT.NONE).setText("Sub-conditions");
650                         Composite buttons = new Composite(aggregatePanel, SWT.NONE);
651                         RowLayoutFactory.swtDefaults().justify(true).fill(true).extendedMargins(0, 0, 0, 0).type(SWT.HORIZONTAL).applyTo(buttons);
652                         
653                         Button addButton = new Button(buttons, SWT.PUSH);
654                         addButton.setText("Add");
655                         Button removeButton = new Button(buttons, SWT.PUSH);
656                         removeButton.setText("Remove");
657                         Button editButton = new Button(buttons, SWT.PUSH);
658                         editButton.setText("Edit");
659
660                         new Label(aggregatePanel, SWT.NONE);
661                         subConditionField = new org.eclipse.swt.widgets.List(aggregatePanel, SWT.BORDER);
662                         GridDataFactory.swtDefaults().hint(200, 150).applyTo(subConditionField);
663                         if (subConditions != null) {
664                                 Session session = Simantics.getSession();
665                                 List<String> items = new ArrayList<>();
666                                 for (Condition c : subConditions) {
667                                         try {
668                                                 items.add(ElementSelector.getExpression(session, c.resource));
669                                         } catch (DatabaseException e1) {
670                                                 LOGGER.error("Condition expression read failed", e1);
671                                                 items.add("<Unknown expression>");
672                                         }
673                                 }
674                                 
675                                 subConditionField.setItems(items.toArray(new String[items.size()]));
676                         }
677                         
678                         addButton.addSelectionListener(new SelectionAdapter() {
679                                 @Override
680                                 public void widgetSelected(SelectionEvent e) {
681                                         ConditionDialog conditionDialog = new ConditionDialog(getShell(), null);
682                                         if (conditionDialog.open() == Window.OK) {
683                                                 Condition condition;
684                                                 try {
685                                                         condition = conditionDialog.createCondition();
686                                                         subConditions.add(condition);
687                                                         
688                                                         try {
689                                                                 subConditionField.add(ElementSelector.getExpression(Simantics.getSession(), condition.resource));
690                                                         } catch (DatabaseException e1) {
691                                                                 LOGGER.error("Condition expression read failed", e1);
692                                                                 subConditionField.add("<Unknown expression>");
693                                                         }
694                                                 } catch (DatabaseException e2) {
695                                                         LOGGER.error("Create condition failed", e2);
696                                                 }
697                                         }
698                                 }
699                         });
700                         
701                         removeButton.addSelectionListener(new SelectionAdapter() {
702                                 @Override
703                                 public void widgetSelected(SelectionEvent e) {
704                                         int index = subConditionField.getSelectionIndex();
705                                         if (index >= 0) {
706                                                 subConditionField.deselectAll();
707                                                 subConditionField.remove(index);
708                                                 subConditions.remove(index);
709                                                 
710                                                 if (index < subConditions.size())
711                                                         subConditionField.setSelection(index);
712                                         }
713                                         
714                                         boolean selected = subConditionField.getSelectionIndex() >= 0;
715                                         removeButton.setEnabled(selected);
716                                         editButton.setEnabled(selected);
717                                 }
718                         });
719                         
720                         editButton.addSelectionListener(new SelectionAdapter() {
721                                 @Override
722                                 public void widgetSelected(SelectionEvent e) {
723                                         int index = subConditionField.getSelectionIndex();
724                                         if (index >= 0) {
725                                                 Condition condition = subConditions.get(index);
726                                                 ConditionDialog conditionDialog = new ConditionDialog(getShell(), condition);
727                                                 if (conditionDialog.open() == Window.OK) {
728                                                         try {
729                                                                 condition = conditionDialog.createCondition();
730                                                                 subConditions.set(index, condition);
731                                                                 
732                                                                 try {
733                                                                         subConditionField.setItem(index, ElementSelector.getExpression(Simantics.getSession(), condition.resource));
734                                                                 } catch (DatabaseException e1) {
735                                                                         LOGGER.error("Condition expression read failed", e1);
736                                                                         subConditionField.setItem(index, "<Unknown expression>");
737                                                                 }
738                                                         } catch (DatabaseException e2) {
739                                                                 LOGGER.error("Create condition failed", e2);
740                                                         }
741                                                 }
742                                         }
743                                 }
744                         });
745                         
746                         subConditionField.addSelectionListener(new SelectionAdapter() {
747                                 @Override
748                                 public void widgetSelected(SelectionEvent e) {
749                                         boolean selected = subConditionField.getSelectionIndex() >= 0;
750                                         removeButton.setEnabled(selected);
751                                         editButton.setEnabled(selected);
752                                 }
753                         });
754
755                         // Stack layout update
756                         typeField.addSelectionListener(new SelectionAdapter() {
757                                 @Override
758                                 public void widgetSelected(SelectionEvent e) {
759                                         updateStack();
760                                 }
761                         });
762                         
763                         updateStack();
764                         
765                         return content;
766                 }
767                 
768                 @Override
769                 protected void okPressed() {
770                         isInverse = inverseField.getSelection();
771                         
772                         switch (typeIndex) {
773                         case 0: // Property condition
774                                 propertyName = propertyNameField.getText();
775                                 try {
776                                         String lowerLimitText = lowerLimitField.getText();
777                                         lowerLimit = lowerLimitText.equals("") ? null : Double.valueOf(lowerLimitText);
778                                         String upperLimitText = upperLimitField.getText();
779                                         upperLimit = upperLimitText.equals("") ? null : Double.valueOf(upperLimitText);
780                                 }
781                                 catch (NumberFormatException e) {
782                                         ErrorDialog.openError(getShell(), "Error", "Invalid numeric value: " + e.getMessage(), new Status(OK, "org.simantics.district.selection.ui", e.getMessage()));
783                                         return;
784                                 }
785                                 break;
786                         case 1: { // Region condition
787                                 int selectionIndex = regionField.getSelectionIndex();
788                                 if (selectionIndex < 0) {
789                                         ErrorDialog.openError(getShell(), "Error", "Please select a region", new Status(OK, "org.simantics.district.selection.ui", "No region selection"));
790                                         return;
791                                 }
792                                 region = regionResources[selectionIndex];
793                                 break;
794                         }
795                         case 2: // Route condition
796                                 route = routeResources[routeField.getSelectionIndex()];
797                                 break;
798                         case 3: // Aggregate condition
799                                 isConjunction = operatorField.getSelectionIndex() == 0;
800                                 break;
801                         }
802                         
803                         super.okPressed();
804                 }
805         
806                 protected Condition createCondition() throws DatabaseException {
807                         if (isInverse && !(typeIndex == 3 && !isConjunction)) {
808                                 Resource resource0 = createCondition0();
809                                 
810                                 // Create a negation
811                                 Resource resource = Simantics.getSession().syncRequest(new WriteResult<Resource>() {
812                                         @Override
813                                         public Resource perform(WriteGraph graph) throws DatabaseException {
814                                                 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
815                                                 Layer0 L0 = Layer0.getInstance(graph);
816                                                 
817                                                 Resource r = graph.newResource();
818                                                 graph.claim(r, L0.InstanceOf, ES.Negation);
819                                                 graph.claim(r, ES.HasSubcondition, resource0);
820                                                 return r;
821                                         }
822                                 });
823                                 
824                                 return ElementSelector.getCondition(Simantics.getSession(), resource);
825                         }
826                         else {
827                                 return ElementSelector.getCondition(Simantics.getSession(), createCondition0());
828                         }
829                 }
830         
831                 private Resource createCondition0() throws DatabaseException {
832                         switch (typeIndex) {
833                         case 0: return createPropertyCondition();
834                         case 1: return createRegionCondition();
835                         case 2: return createRouteCondition();
836                         case 3: return createAggregateCondition();
837                         default: throw new IllegalStateException("Invalid condition type code " + typeIndex);
838                         }
839                 }
840         
841                 private Resource createPropertyCondition() throws DatabaseException {
842                         return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
843                                 @Override
844                                 public Resource perform(WriteGraph graph) throws DatabaseException {
845                                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
846                                         Layer0 L0 = Layer0.getInstance(graph);
847                                         
848                                         Resource r = graph.newResource();
849                                         graph.claim(r, L0.InstanceOf, ES.PropertyCondition);
850                                         graph.claimLiteral(r, ES.PropertyCondition_HasPropertyName, propertyName);
851                                         if (lowerLimit != null)
852                                                 graph.claimLiteral(r, ES.PropertyCondition_HasLowerLimit, L0.Double, lowerLimit);
853                                         if (upperLimit != null)
854                                                 graph.claimLiteral(r, ES.PropertyCondition_HasUpperLimit, L0.Double, upperLimit);
855                                         
856                                         return r;
857                                 }
858                         });
859                 }
860                 
861                 private Resource createAggregateCondition() throws DatabaseException {
862                         return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
863                                 @Override
864                                 public Resource perform(WriteGraph graph) throws DatabaseException {
865                                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
866                                         Layer0 L0 = Layer0.getInstance(graph);
867                                         
868                                         Resource r;
869                                         if (existingResource != null) {
870                                                 // Reuse existing resource
871                                                 r = existingResource;
872                                                 // Clear any previous statements
873                                                 graph.deny(existingResource);
874                                         }
875                                         else {
876                                                 r = graph.newResource();
877                                         }
878                                         
879                                         Resource type;
880                                         type = isConjunction ? ES.Conjunction : isInverse ? ES.Negation : ES.Disjunction;
881                                         
882                                         graph.claim(r, L0.InstanceOf, type);
883                                         for (Condition c : subConditions) {
884                                                 graph.claim(r, ES.HasSubcondition, c.resource);
885                                         }
886                                         
887                                         return r;
888                                 }
889                         });
890                 }
891         
892                 private Resource createRouteCondition() throws DatabaseException {
893                         return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
894                                 @Override
895                                 public Resource perform(WriteGraph graph) throws DatabaseException {
896                                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
897                                         Layer0 L0 = Layer0.getInstance(graph);
898                                         
899                                         Resource r = graph.newResource();
900                                         graph.claim(r, L0.InstanceOf, ES.RouteCondition);
901                                         graph.claim(r, ES.RouteCondition_HasRoute, route);
902                                         return r;
903                                 }
904                         });
905                 }
906         
907                 private Resource createRegionCondition() throws DatabaseException {
908                         return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
909                                 @Override
910                                 public Resource perform(WriteGraph graph) throws DatabaseException {
911                                         ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
912                                         Layer0 L0 = Layer0.getInstance(graph);
913                                         
914                                         Resource r = graph.newResource();
915                                         graph.claim(r, L0.InstanceOf, ES.RegionCondition);
916                                         graph.claim(r, ES.RegionCondition_HasRegion, region);
917                                         return r;
918                                 }
919                         });
920                 }
921
922                 private void updateStack() {
923                         typeIndex = typeField.getSelectionIndex();
924                         switch (typeIndex) {
925                         case 0: stack.topControl = propertyPanel; break;
926                         case 1: stack.topControl = regionPanel; break;
927                         case 2: stack.topControl = routePanel; break;
928                         case 3: stack.topControl = aggregatePanel; break;
929                         }
930                         
931                         stackPanel.layout();
932                 }
933         }
934 }