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