]> gerrit.simantics Code Review - simantics/district.git/blobdiff - 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
diff --git a/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/parts/EditSelectorDialog.java b/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/parts/EditSelectorDialog.java
new file mode 100644 (file)
index 0000000..42270da
--- /dev/null
@@ -0,0 +1,909 @@
+package org.simantics.district.selection.ui.parts;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.inject.Inject;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.layout.RowLayoutFactory;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StackLayout;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.RuntimeDatabaseException;
+import org.simantics.db.layer0.QueryIndexUtils;
+import org.simantics.db.layer0.request.ActiveModels;
+import org.simantics.db.request.Read;
+import org.simantics.db.request.WriteResult;
+import org.simantics.district.region.ontology.DiagramRegionsResource;
+import org.simantics.district.route.ontology.RouteResource;
+import org.simantics.district.selection.ElementSelectionResource;
+import org.simantics.district.selection.ElementSelectionUtils;
+import org.simantics.district.selection.ElementSelector;
+import org.simantics.district.selection.ElementSelector.AggregateCondition;
+import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
+import org.simantics.district.selection.ElementSelector.All;
+import org.simantics.district.selection.ElementSelector.Condition;
+import org.simantics.district.selection.ElementSelector.DiagramGenerator;
+import org.simantics.district.selection.ElementSelector.ExplicitGenerator;
+import org.simantics.district.selection.ElementSelector.Generator;
+import org.simantics.district.selection.ElementSelector.ModelGenerator;
+import org.simantics.district.selection.ElementSelector.PropertyCondition;
+import org.simantics.district.selection.ElementSelector.PropertySelector;
+import org.simantics.district.selection.ElementSelector.RegionCondition;
+import org.simantics.district.selection.ElementSelector.RouteCondition;
+import org.simantics.district.selection.ElementSelector.Selector;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.utils.datastructures.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EditSelectorDialog extends Dialog {
+
+       private static Logger LOGGER = LoggerFactory.getLogger(EditSelectorDialog.class);
+       
+       private ElementSelector elementSelector;
+
+       // Data for comboboxes
+       Map<Resource, String> diagrams;
+       
+       private String[] regionNames;
+       private Resource[] regionResources;
+
+       private String[] routeNames;
+       private Resource[] routeResources;
+
+       // Dialog fields
+       private int generatorIndex;
+       private Combo sourceField;
+
+       private String name;
+       private Text nameField;
+
+       private Resource diagram;
+       private Combo diagramField;
+
+       private int selectorIndex;
+       private Combo selectorField;
+
+       private String propertyName;
+       private Text propertyField;
+
+       private int numberOfItems;
+       private Text nField;
+
+       private Condition condition;
+       private Button removeConditionButton;
+       private Text conditionLabel;
+
+       // Dialog area component
+       private Composite content;
+
+       @Inject
+       public EditSelectorDialog(Shell shell, ElementSelector elementSelector) {
+               super(shell);
+               
+               this.elementSelector = elementSelector;
+               if (elementSelector != null) {
+                       try {
+                               Simantics.getSession().sync(new ReadRequest() {
+                                       @Override
+                                       public void run(ReadGraph graph) throws DatabaseException {
+                                               elementSelector.buildSelection(graph);
+                                       }
+                               });
+                       } catch (DatabaseException e1) {
+                               LOGGER.error("Failed to read element selector resource " + elementSelector.getResource(), e1);
+                               throw new RuntimeDatabaseException(e1);
+                       }
+               }
+               
+               final Map<Resource, String> regions = new HashMap<>();
+               final Map<Resource, String> routes = new HashMap<>();
+               
+               try {
+                       Simantics.getSession().syncRequest(new Read<Void>() {
+                               @Override
+                               public Void perform(ReadGraph graph) throws DatabaseException {
+                                       Resource model = ActiveModels.getPossibleActiveModel(graph, Simantics.getProjectResource());
+                                       List<Resource> regionCollection = QueryIndexUtils.searchByType(graph, model, DiagramRegionsResource.getInstance(graph).Region);
+                                       for (Resource r : regionCollection) {
+                                               String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName);
+                                               regions.put(r, name);
+                                       }
+                                       
+                                       List<Resource> routeCollection = QueryIndexUtils.searchByType(graph, model, RouteResource.getInstance(graph).Route);
+                                       for (Resource r : routeCollection) {
+                                               String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName);
+                                               routes.put(r, name);
+                                       }
+                                       return null;
+                               }
+                       });
+               } catch (DatabaseException e) {
+                       LOGGER.error("Failed to read routes and/or regions in the model", e);
+               }
+               
+               regionNames = regions.values().toArray(new String[regions.size()]);
+               regionResources = regions.keySet().toArray(new Resource[regions.size()]);
+               
+               routeNames = routes.values().toArray(new String[routes.size()]);
+               routeResources = routes.keySet().toArray(new Resource[routes.size()]);
+       
+               name = elementSelector != null ? elementSelector.getName() : "";
+               propertyName = "";
+               numberOfItems = 1;
+               generatorIndex = 0;
+               selectorIndex = 0;
+               diagram = null;
+               condition = null;
+               
+               if (elementSelector != null) {
+                       Generator generator = elementSelector.getGenerator();
+                       if (generator instanceof ModelGenerator) {
+                               generatorIndex = 0;
+                       }
+                       else if (generator instanceof DiagramGenerator) {
+                               generatorIndex = 1;
+                               diagram = ((DiagramGenerator)generator).diagram;
+                       }
+                       else if (generator instanceof ExplicitGenerator) {
+                               generatorIndex = 2;
+                               // TODO: Management of explicit lists of elements
+                       }
+                       else {
+                               throw new IllegalStateException("Unknown generator type " + generator.getClass().getName());
+                       }
+                       
+                       Selector selector = elementSelector.getSelector();
+                       if (selector instanceof All) {
+                               selectorIndex = 0;
+                       }
+                       else if (selector instanceof PropertySelector) {
+                               PropertySelector propertySelector = (PropertySelector)selector;
+                               selectorIndex = propertySelector.smallest ? 1 : 2;
+                               propertyName = propertySelector.propertyName;
+                               numberOfItems = propertySelector.resultCount;
+                       }
+                       else {
+                               throw new IllegalStateException("Unknwon selector type " + selector.getClass().getName());
+                       }
+                       
+                       condition = elementSelector.getCondition();
+               }
+       }
+       
+       @Override
+       protected void okPressed() {
+               generatorIndex = sourceField.getSelectionIndex();
+               if (generatorIndex == 1) {
+                       int selectionIndex = diagramField.getSelectionIndex();
+                       if (selectionIndex < 0) {
+                               ErrorDialog.openError(getShell(), "Error", "Please select a diagram", new Status(IStatus.ERROR, "org.simantics.district.selection.ui", "No diagram selected"));
+                               return;
+                       }
+                       
+                       diagram = new ArrayList<Resource>(diagrams.keySet()).get(selectionIndex);
+               }
+               
+               name = nameField.getText();
+               
+               selectorIndex = selectorField.getSelectionIndex();
+               
+               propertyName = propertyField.getText();
+               String text = nField.getText();
+               numberOfItems = "".equals(text) ? 0 : Integer.parseInt(text);
+               
+               super.okPressed();
+       }
+       
+       public void writeSelection() throws DatabaseException {
+               Simantics.getSession().syncRequest(new WriteRequest() {
+                       @Override
+                       public void perform(WriteGraph graph) throws DatabaseException {
+                               Layer0 L0 = Layer0.getInstance(graph);
+                               ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                               
+                               graph.markUndoPoint();
+                               
+                               Resource lib = ElementSelectionUtils.ensureSelectionLibrary(graph);
+                               
+                               // Selection
+                               Resource selection;
+                               if (elementSelector != null) {
+                                       selection = elementSelector.getResource();
+                                       graph.deny(selection);
+                               }
+                               else {
+                                       selection = graph.newResource();
+                               }
+                               
+                               graph.claim(selection, L0.InstanceOf, ES.Selection);
+                               graph.claimLiteral(selection, L0.HasName, L0.String, UUID.randomUUID().toString());
+                               graph.claimLiteral(selection, L0.HasLabel, L0.String, name);
+                               graph.claim(selection, L0.PartOf, lib);
+                               
+                               // Generator
+                               Resource generator = graph.newResource();
+                               Resource generatorType;
+                               switch (generatorIndex) {
+                               case 0:
+                                       generatorType = ES.Generator_Model;
+                                       break;
+                               case 1:
+                                       generatorType = ES.Generator_Diagram;
+                                       Resource composite = graph.getPossibleObject(diagram, ModelingResources.getInstance(graph).DiagramToComposite);
+                                       graph.claim(generator, ES.Generator_HasDiagram, composite != null ? composite : diagram);
+                                       break;
+                               case 2:
+                                       generatorType = ES.Generator_Explicit;
+                                       // TODO: Claim relations to current selection elements
+                                       break;
+                               default: throw new IllegalStateException("Invalid source index " + generatorIndex);
+                               }
+                               graph.claim(generator, L0.InstanceOf, generatorType);
+                               graph.claim(selection, ES.Selection_HasGenerator, generator);
+                               
+                               // Selector
+                               Resource selector = graph.newResource();
+                               Resource selectorType;
+                               switch (selectorIndex) {
+                               case 0: selectorType = ES.Selector_All; break;
+                               case 1: selectorType = ES.Selector_NLowest; break;
+                               case 2: selectorType = ES.Selector_NHighest; break;
+                               default: throw new IllegalStateException("Invalid selector index " + selectorIndex);
+                               }
+                               graph.claim(selector, L0.InstanceOf, selectorType);
+                               graph.claim(selection, ES.Selection_HasSelector, selector);
+                               
+                               if (selectorIndex > 0) {
+                                       graph.claimLiteral(selector, ES.PropertySelector_HasSelectionPropertyName, L0.String, propertyName);
+                                       graph.claimLiteral(selector, ES.PropertySelector_HasResultCount, L0.Integer, numberOfItems);
+                               }
+                               
+                               // Condition
+                               if (condition != null) {
+                                       graph.claim(selection, ES.Selection_HasCondition, condition.resource);
+                               }
+                       }
+               });
+       }
+
+       @Override
+       protected Control createDialogArea(Composite parent) {
+               // Set dialog title
+               getShell().setText("Edit element selector");
+               
+               content = new Composite(parent, SWT.NONE);
+               GridLayoutFactory.swtDefaults().numColumns(3).applyTo(content);
+               
+               // Name
+               Label nameLabel = new Label(content, SWT.NONE);
+               nameLabel.setText("Name");
+               GridDataFactory.swtDefaults().applyTo(nameLabel);
+               
+               nameField = new Text(content, SWT.BORDER);
+               nameField.setEditable(true);
+               nameField.setText(name);
+               GridDataFactory.swtDefaults().span(2, 1).hint(200, SWT.DEFAULT).applyTo(nameField);
+               
+               // Source
+               Label sourceLabel = new Label(content, SWT.NONE);
+               sourceLabel.setText("Source");
+               GridDataFactory.swtDefaults().applyTo(sourceLabel);
+               
+               sourceField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
+               sourceField.setItems("Model", "Diagram", "Current selection");
+               sourceField.select(generatorIndex);
+               GridDataFactory.swtDefaults().span(1, 1).applyTo(sourceField);
+               
+               diagramField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
+               GridDataFactory.swtDefaults().span(1, 1).applyTo(diagramField);
+               diagrams = ElementSelector.findDiagrams();
+               diagramField.setItems(diagrams.values().toArray(new String[diagrams.size()]));
+               
+               int diagramIndex = diagram != null ? new ArrayList<>(diagrams.keySet()).indexOf(diagram) : -1;
+               diagramField.select(diagramIndex);
+               
+               sourceField.addSelectionListener(new SelectionAdapter() {
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               generatorIndex = sourceField.getSelectionIndex();
+                               diagramField.setVisible(isDiagramFieldVisible());
+                       }
+               });
+               
+               sourceField.select(generatorIndex);
+               diagramField.setVisible(isDiagramFieldVisible());
+               
+               // Selector
+               Label selectorLabel = new Label(content, SWT.NONE);
+               selectorLabel.setText("Select");
+               GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorLabel);
+               
+               selectorField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
+               selectorField.setItems("All", "N lowest", "N highest");
+               selectorField.select(selectorIndex);
+               GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorField);
+               
+               Composite selectorComposite = new Composite(content, SWT.NONE);
+               GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorComposite);
+               GridLayoutFactory.swtDefaults().numColumns(2).applyTo(selectorComposite);
+               
+               Label propertyLabel = new Label(selectorComposite, SWT.NONE);
+               propertyLabel.setText("Property name");
+               GridDataFactory.swtDefaults().applyTo(propertyLabel);
+               
+               propertyField = new Text(selectorComposite, SWT.BORDER);
+               propertyField.setText(propertyName);
+               GridDataFactory.swtDefaults().hint(80, SWT.DEFAULT).applyTo(propertyField);
+               
+               Label nLabel = new Label(selectorComposite, SWT.NONE);
+               nLabel.setText("Number of elements");
+               GridDataFactory.swtDefaults().applyTo(nLabel);
+               
+               nField = new Text(selectorComposite, SWT.BORDER);
+               nField.setText(Integer.toString(numberOfItems));
+               GridDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(nField);
+               
+               selectorField.addSelectionListener(new SelectionAdapter() {
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               selectorIndex = selectorField.getSelectionIndex();
+                               selectorComposite.setVisible(isSelectorCompositeVisible());
+                       }
+               });
+               
+               selectorField.select(selectorIndex);
+               selectorComposite.setVisible(isSelectorCompositeVisible());
+               
+               // Condition
+               new Label(content, SWT.NONE).setText("Condition");
+               conditionLabel = new Text(content, SWT.READ_ONLY);
+               GridDataFactory.swtDefaults().span(2, 1).applyTo(conditionLabel);
+               
+               new Label(content, SWT.NONE);
+               Composite conditionPanel = new Composite(content, SWT.NONE);
+               GridDataFactory.swtDefaults().span(2, 1).applyTo(conditionPanel);
+               GridLayoutFactory.swtDefaults().margins(0, 0).numColumns(2).applyTo(conditionPanel);
+               Button conditionButton = new Button(conditionPanel, SWT.PUSH);
+               conditionButton.setText("Edit...");
+               GridDataFactory.swtDefaults().span(1, 1).applyTo(conditionButton);
+               removeConditionButton = new Button(conditionPanel, SWT.PUSH);
+               removeConditionButton.setText("Remove");
+               GridDataFactory.swtDefaults().span(1, 1).applyTo(removeConditionButton);
+               
+               updateCondition();
+               
+               conditionButton.addSelectionListener(new SelectionAdapter() {
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               ConditionDialog dialog = new ConditionDialog(getShell(), condition);
+                               if (dialog.open() == Window.OK) {
+                                       try {
+                                               condition = dialog.createCondition();
+                                       } catch (DatabaseException e1) {
+                                               LOGGER.error("Creating a condition object failed", e1);
+                                       }
+                                       
+                                       updateCondition();
+                               }
+                       }
+               });
+               
+               removeConditionButton.addSelectionListener(new SelectionAdapter() {
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               condition = null;
+                               updateCondition();
+                       }
+               });
+               
+               return content;
+       }
+       
+       private void updateCondition() {
+               if (condition != null) {
+                       removeConditionButton.setEnabled(true);
+                       try {
+                               conditionLabel.setText(ElementSelector.getExpression(Simantics.getSession(), condition.resource));
+                       } catch (DatabaseException e) {
+                               LOGGER.error("Error getting expression string for " + condition.resource);
+                       }
+               }
+               else {
+                       conditionLabel.setText("No condition");
+                       removeConditionButton.setEnabled(false);
+               }
+               
+               content.layout();
+       }
+
+       private boolean isDiagramFieldVisible() {
+               return generatorIndex == 1;
+       }
+
+       private boolean isSelectorCompositeVisible() {
+               return selectorIndex != 0;
+       }
+
+       class ConditionDialog extends Dialog {
+               // Resource of the edited condition
+               private Resource existingResource;
+               
+               // Inverse condition button
+               private boolean isInverse;
+               private Button inverseField;
+
+               // Condition type
+               private int typeIndex;
+               private Combo typeField;
+               
+               // Type-specific control panels under a stack layout
+               private Composite stackPanel;
+               private StackLayout stack;
+               
+               private Composite propertyPanel;
+               private Composite regionPanel;
+               private Composite routePanel;
+               private Composite aggregatePanel;
+               
+               // Property condition
+               private Double lowerLimit;
+               private Double upperLimit;
+               private String propertyName;
+       
+               private Text propertyNameField;
+               private Text lowerLimitField;
+               private Text upperLimitField;
+               
+               // Region condition
+               private Resource region;
+               private Combo regionField;
+               
+               // Route condition
+               private Resource route;
+               private Combo routeField;
+               
+               // Aggregate condition
+               private List<Condition> subConditions;
+               private boolean isConjunction;
+               
+               private org.eclipse.swt.widgets.List subConditionField;
+               private Combo operatorField;
+               
+               public ConditionDialog(Shell shell, Condition condition) {
+                       super(shell);
+                       
+                       typeIndex = 0;
+                       isInverse = false;
+                       propertyName = "";
+                       upperLimit = null;
+                       lowerLimit = null;
+                       subConditions = new ArrayList<>();
+                       
+                       existingResource = condition != null ? condition.resource : null;
+
+                       if (condition != null) {
+                               if (condition instanceof PropertyCondition) {
+                                       typeIndex = 0;
+                                       PropertyCondition propertyCondition = (PropertyCondition)condition;
+                                       propertyName = propertyCondition.propertyName;
+                                       upperLimit = propertyCondition.upperLimit;
+                                       lowerLimit = propertyCondition.lowerLimit;
+                               }
+                               else if (condition instanceof RegionCondition) {
+                                       typeIndex = 1;
+                                       region = ((RegionCondition)condition).regionResource;
+                               }
+                               else if (condition instanceof RouteCondition) {
+                                       typeIndex = 2;
+                                       route = ((RouteCondition)condition).routeResource;
+                               }
+                               else if (condition instanceof AggregateCondition) {
+                                       typeIndex = 3;
+                                       subConditions = new ArrayList<>(((AggregateCondition)condition).conditions);
+                                       isConjunction = ((AggregateCondition)condition).type == Type.CONJUNCTION;
+                                       isInverse = ((AggregateCondition)condition).type == Type.NEGATION;
+                               }
+                       }
+               }
+               
+               @Override
+               protected Control createDialogArea(Composite parent) {
+                       getShell().setText("Edit selector condition");
+                       
+                       Composite content = (Composite)super.createDialogArea(parent);
+                       GridLayoutFactory.swtDefaults().numColumns(1).applyTo(content);
+                       
+                       GridDataFactory defaultWidth = GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT);
+                       
+                       // Is inverse
+                       inverseField = new Button(content, SWT.CHECK);
+                       inverseField.setText("Is inverse");
+                       inverseField.setSelection(isInverse);
+                       
+                       // Condition type
+                       typeField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
+                       typeField.setItems("Property value", "In region", "On route", "Combination");
+                       typeField.select(typeIndex);
+                       
+                       // Type-dependent stacked panels
+                       stackPanel = new Composite(content, SWT.NONE);
+                       stack = new StackLayout();
+                       stackPanel.setLayout(stack);
+                       
+                       // Property condition panel
+                       propertyPanel = new Composite(stackPanel, SWT.NONE);
+                       GridLayoutFactory.swtDefaults().numColumns(2).applyTo(propertyPanel);
+                       
+                       new Label(propertyPanel, SWT.NONE).setText("Property name");
+                       propertyNameField = new Text(propertyPanel, SWT.BORDER);
+                       propertyNameField.setText(propertyName);
+                       defaultWidth.applyTo(propertyNameField);
+                       
+                       new Label(propertyPanel, SWT.NONE).setText("Lower limit");
+                       lowerLimitField = new Text(propertyPanel, SWT.BORDER);
+                       defaultWidth.applyTo(lowerLimitField);
+                       if (lowerLimit != null) lowerLimitField.setText(lowerLimit.toString());
+                       
+                       new Label(propertyPanel, SWT.NONE).setText("Upper limit");
+                       upperLimitField = new Text(propertyPanel, SWT.BORDER);
+                       defaultWidth.applyTo(upperLimitField);
+                       if (upperLimit != null) upperLimitField.setText(upperLimit.toString());
+                       
+                       // Region condition panel
+                       regionPanel = new Composite(stackPanel, SWT.NONE);
+                       GridLayoutFactory.swtDefaults().numColumns(2).applyTo(regionPanel);
+                       
+                       new Label(regionPanel, SWT.NONE).setText("Region");
+                       regionField = new Combo(regionPanel, SWT.BORDER | SWT.READ_ONLY);
+                       regionField.setItems(regionNames);
+                       defaultWidth.applyTo(regionField);
+                       
+                       if (region != null) {
+                               int regionIndex = Arrays.indexOf(regionResources, region);
+                               regionField.select(regionIndex);
+                       }
+                       else {
+                               regionField.select(0);
+                       }
+                       
+                       // Route condition panel
+                       routePanel = new Composite(stackPanel, SWT.NONE);
+                       GridLayoutFactory.swtDefaults().numColumns(2).applyTo(routePanel);
+                       
+                       new Label(routePanel, SWT.NONE).setText("Route");
+                       routeField = new Combo(routePanel, SWT.BORDER | SWT.READ_ONLY);
+                       routeField.setItems(routeNames);
+                       defaultWidth.applyTo(routeField);
+                       
+                       if (route != null) {
+                               int routeIndex = Arrays.indexOf(routeResources, route);
+                               routeField.select(routeIndex);
+                       }
+                       else {
+                               routeField.select(0);
+                       }
+                       
+                       // Aggregate condition panel
+                       aggregatePanel = new Composite(stackPanel, SWT.NONE);
+                       GridLayoutFactory.swtDefaults().numColumns(2).applyTo(aggregatePanel);
+                       
+                       new Label(aggregatePanel, SWT.NONE).setText("Operator");
+                       operatorField = new Combo(aggregatePanel, SWT.READ_ONLY);
+                       operatorField.setItems("And", "Or");
+                       operatorField.select(isConjunction ? 0 : 1);
+                       
+                       new Label(aggregatePanel, SWT.NONE).setText("Sub-conditions");
+                       Composite buttons = new Composite(aggregatePanel, SWT.NONE);
+                       RowLayoutFactory.swtDefaults().justify(true).fill(true).extendedMargins(0, 0, 0, 0).type(SWT.HORIZONTAL).applyTo(buttons);
+                       
+                       Button addButton = new Button(buttons, SWT.PUSH);
+                       addButton.setText("Add");
+                       Button removeButton = new Button(buttons, SWT.PUSH);
+                       removeButton.setText("Remove");
+                       Button editButton = new Button(buttons, SWT.PUSH);
+                       editButton.setText("Edit");
+
+                       new Label(aggregatePanel, SWT.NONE);
+                       subConditionField = new org.eclipse.swt.widgets.List(aggregatePanel, SWT.BORDER);
+                       GridDataFactory.swtDefaults().hint(200, 150).applyTo(subConditionField);
+                       if (subConditions != null) {
+                               Session session = Simantics.getSession();
+                               List<String> items = new ArrayList<>();
+                               for (Condition c : subConditions) {
+                                       try {
+                                               items.add(ElementSelector.getExpression(session, c.resource));
+                                       } catch (DatabaseException e1) {
+                                               LOGGER.error("Condition expression read failed", e1);
+                                               items.add("<Unknown expression>");
+                                       }
+                               }
+                               
+                               subConditionField.setItems(items.toArray(new String[items.size()]));
+                       }
+                       
+                       addButton.addSelectionListener(new SelectionAdapter() {
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       ConditionDialog conditionDialog = new ConditionDialog(getShell(), null);
+                                       if (conditionDialog.open() == Window.OK) {
+                                               Condition condition;
+                                               try {
+                                                       condition = conditionDialog.createCondition();
+                                                       subConditions.add(condition);
+                                                       
+                                                       try {
+                                                               subConditionField.add(ElementSelector.getExpression(Simantics.getSession(), condition.resource));
+                                                       } catch (DatabaseException e1) {
+                                                               LOGGER.error("Condition expression read failed", e1);
+                                                               subConditionField.add("<Unknown expression>");
+                                                       }
+                                               } catch (DatabaseException e2) {
+                                                       LOGGER.error("Create condition failed", e2);
+                                               }
+                                       }
+                               }
+                       });
+                       
+                       removeButton.addSelectionListener(new SelectionAdapter() {
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       int index = subConditionField.getSelectionIndex();
+                                       if (index >= 0) {
+                                               subConditionField.deselectAll();
+                                               subConditionField.remove(index);
+                                               subConditions.remove(index);
+                                               
+                                               if (index < subConditions.size())
+                                                       subConditionField.setSelection(index);
+                                       }
+                                       
+                                       boolean selected = subConditionField.getSelectionIndex() >= 0;
+                                       removeButton.setEnabled(selected);
+                                       editButton.setEnabled(selected);
+                               }
+                       });
+                       
+                       editButton.addSelectionListener(new SelectionAdapter() {
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       int index = subConditionField.getSelectionIndex();
+                                       if (index >= 0) {
+                                               Condition condition = subConditions.get(index);
+                                               ConditionDialog conditionDialog = new ConditionDialog(getShell(), condition);
+                                               if (conditionDialog.open() == Window.OK) {
+                                                       try {
+                                                               condition = conditionDialog.createCondition();
+                                                               subConditions.set(index, condition);
+                                                               
+                                                               try {
+                                                                       subConditionField.setItem(index, ElementSelector.getExpression(Simantics.getSession(), condition.resource));
+                                                               } catch (DatabaseException e1) {
+                                                                       LOGGER.error("Condition expression read failed", e1);
+                                                                       subConditionField.setItem(index, "<Unknown expression>");
+                                                               }
+                                                       } catch (DatabaseException e2) {
+                                                               LOGGER.error("Create condition failed", e2);
+                                                       }
+                                               }
+                                       }
+                               }
+                       });
+                       
+                       subConditionField.addSelectionListener(new SelectionAdapter() {
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       boolean selected = subConditionField.getSelectionIndex() >= 0;
+                                       removeButton.setEnabled(selected);
+                                       editButton.setEnabled(selected);
+                               }
+                       });
+
+                       // Stack layout update
+                       typeField.addSelectionListener(new SelectionAdapter() {
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       updateStack();
+                               }
+                       });
+                       
+                       updateStack();
+                       
+                       return content;
+               }
+               
+               @Override
+               protected void okPressed() {
+                       isInverse = inverseField.getSelection();
+                       
+                       switch (typeIndex) {
+                       case 0: // Property condition
+                               propertyName = propertyNameField.getText();
+                               try {
+                                       String lowerLimitText = lowerLimitField.getText();
+                                       lowerLimit = lowerLimitText.equals("") ? null : Double.valueOf(lowerLimitText);
+                                       String upperLimitText = upperLimitField.getText();
+                                       upperLimit = upperLimitText.equals("") ? null : Double.valueOf(upperLimitText);
+                               }
+                               catch (NumberFormatException e) {
+                                       ErrorDialog.openError(getShell(), "Error", "Invalid numeric value: " + e.getMessage(), new Status(OK, "org.simantics.district.selection.ui", e.getMessage()));
+                                       return;
+                               }
+                               break;
+                       case 1: { // Region condition
+                               int selectionIndex = regionField.getSelectionIndex();
+                               if (selectionIndex < 0) {
+                                       ErrorDialog.openError(getShell(), "Error", "Please select a region", new Status(OK, "org.simantics.district.selection.ui", "No region selection"));
+                                       return;
+                               }
+                               region = regionResources[selectionIndex];
+                               break;
+                       }
+                       case 2: // Route condition
+                               route = routeResources[routeField.getSelectionIndex()];
+                               break;
+                       case 3: // Aggregate condition
+                               isConjunction = operatorField.getSelectionIndex() == 0;
+                               break;
+                       }
+                       
+                       super.okPressed();
+               }
+       
+               protected Condition createCondition() throws DatabaseException {
+                       if (isInverse && !(typeIndex == 3 && !isConjunction)) {
+                               Resource resource0 = createCondition0();
+                               
+                               // Create a negation
+                               Resource resource = Simantics.getSession().syncRequest(new WriteResult<Resource>() {
+                                       @Override
+                                       public Resource perform(WriteGraph graph) throws DatabaseException {
+                                               ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                                               Layer0 L0 = Layer0.getInstance(graph);
+                                               
+                                               Resource r = graph.newResource();
+                                               graph.claim(r, L0.InstanceOf, ES.Negation);
+                                               graph.claim(r, ES.HasSubcondition, resource0);
+                                               return r;
+                                       }
+                               });
+                               
+                               return ElementSelector.getCondition(Simantics.getSession(), resource);
+                       }
+                       else {
+                               return ElementSelector.getCondition(Simantics.getSession(), createCondition0());
+                       }
+               }
+       
+               private Resource createCondition0() throws DatabaseException {
+                       switch (typeIndex) {
+                       case 0: return createPropertyCondition();
+                       case 1: return createRegionCondition();
+                       case 2: return createRouteCondition();
+                       case 3: return createAggregateCondition();
+                       default: throw new IllegalStateException("Invalid condition type code " + typeIndex);
+                       }
+               }
+       
+               private Resource createPropertyCondition() throws DatabaseException {
+                       return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
+                               @Override
+                               public Resource perform(WriteGraph graph) throws DatabaseException {
+                                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                                       Layer0 L0 = Layer0.getInstance(graph);
+                                       
+                                       Resource r = graph.newResource();
+                                       graph.claim(r, L0.InstanceOf, ES.PropertyCondition);
+                                       graph.claimLiteral(r, ES.PropertyCondition_HasPropertyName, propertyName);
+                                       if (lowerLimit != null)
+                                               graph.claimLiteral(r, ES.PropertyCondition_HasLowerLimit, L0.Double, lowerLimit);
+                                       if (upperLimit != null)
+                                               graph.claimLiteral(r, ES.PropertyCondition_HasUpperLimit, L0.Double, upperLimit);
+                                       
+                                       return r;
+                               }
+                       });
+               }
+               
+               private Resource createAggregateCondition() throws DatabaseException {
+                       return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
+                               @Override
+                               public Resource perform(WriteGraph graph) throws DatabaseException {
+                                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                                       Layer0 L0 = Layer0.getInstance(graph);
+                                       
+                                       Resource r;
+                                       if (existingResource != null) {
+                                               // Reuse existing resource
+                                               r = existingResource;
+                                               // Clear any previous statements
+                                               graph.deny(existingResource);
+                                       }
+                                       else {
+                                               r = graph.newResource();
+                                       }
+                                       
+                                       Resource type;
+                                       type = isConjunction ? ES.Conjunction : isInverse ? ES.Negation : ES.Disjunction;
+                                       
+                                       graph.claim(r, L0.InstanceOf, type);
+                                       for (Condition c : subConditions) {
+                                               graph.claim(r, ES.HasSubcondition, c.resource);
+                                       }
+                                       
+                                       return r;
+                               }
+                       });
+               }
+       
+               private Resource createRouteCondition() throws DatabaseException {
+                       return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
+                               @Override
+                               public Resource perform(WriteGraph graph) throws DatabaseException {
+                                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                                       Layer0 L0 = Layer0.getInstance(graph);
+                                       
+                                       Resource r = graph.newResource();
+                                       graph.claim(r, L0.InstanceOf, ES.RouteCondition);
+                                       graph.claim(r, ES.RouteCondition_HasRoute, route);
+                                       return r;
+                               }
+                       });
+               }
+       
+               private Resource createRegionCondition() throws DatabaseException {
+                       return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
+                               @Override
+                               public Resource perform(WriteGraph graph) throws DatabaseException {
+                                       ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
+                                       Layer0 L0 = Layer0.getInstance(graph);
+                                       
+                                       Resource r = graph.newResource();
+                                       graph.claim(r, L0.InstanceOf, ES.RegionCondition);
+                                       graph.claim(r, ES.RegionCondition_HasRegion, region);
+                                       return r;
+                               }
+                       });
+               }
+
+               private void updateStack() {
+                       typeIndex = typeField.getSelectionIndex();
+                       switch (typeIndex) {
+                       case 0: stack.topControl = propertyPanel; break;
+                       case 1: stack.topControl = regionPanel; break;
+                       case 2: stack.topControl = routePanel; break;
+                       case 3: stack.topControl = aggregatePanel; break;
+                       }
+                       
+                       stackPanel.layout();
+               }
+       }
+}