From: Reino Ruusu Date: Thu, 26 Mar 2020 14:17:42 +0000 (+0200) Subject: New element selector configuration dialog X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F78%2F4078%2F2;p=simantics%2Fdistrict.git New element selector configuration dialog Includes element type selection and property selection from drop-down menus. Also includes some refactoring of the underlying selector code. gitlab #84 Change-Id: If5e42a7962d6d17b97a9fb6a3d207ed860858b8c Remove block of commented-out code gitlab #84 Change-Id: Ia75eeb9e9a2a4dc5a339244837cd6e6f01f97891 Layout adjustments in element selector dialog. gitlab #84 Change-Id: I28de9ca5e1bc22eef607126ec75dcd3a93263188 More rigorous content validation for element selection dialog gitlab #84 Change-Id: Iadb875ea9db3db8350095e21c39ecfad6cc6af36 Remove misplaced pop-up menu command gitlab #84 Change-Id: I4412390556daacc4172c34018fa9d0fea65bf586 Rename element selection view in UI to "District Finder" gitlab #84 Change-Id: I668eb468c169ccd8dfd8575daff9c1f1a0f01f2d Manage drop-down menu contents by context in district finder dialog gitlab #84 Also fix failure in creation of new queries. Change-Id: I122ec005119a7efd8878b24815dc09a226e8b6d9 Fix use of static ontology variables out of context gitlab #84 Change-Id: I94d17685f44e4bbfdaccaf9b3bfabefacb4e1c8e Fix validation of selection by property when not used gitlab #84 Change-Id: I6fd1b5d297ac2a07536edcd51a5f1b1099e3d06d --- diff --git a/org.simantics.district.selection.ui/META-INF/MANIFEST.MF b/org.simantics.district.selection.ui/META-INF/MANIFEST.MF index 2dc51ac9..86962fc5 100644 --- a/org.simantics.district.selection.ui/META-INF/MANIFEST.MF +++ b/org.simantics.district.selection.ui/META-INF/MANIFEST.MF @@ -29,7 +29,9 @@ Require-Bundle: javax.inject, org.simantics.district.network.ui, org.simantics.structural.ontology, org.simantics.diagram.ontology, - org.eclipse.e4.core.services + org.eclipse.e4.core.services, + org.simantics.district.network.ontology;bundle-version="1.0.0", + org.simantics.layer0.utils Bundle-ManifestVersion: 2 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-Vendor: Semantum oy diff --git a/org.simantics.district.selection.ui/fragment.e4xmi b/org.simantics.district.selection.ui/fragment.e4xmi index 3b6a9456..b58825a1 100644 --- a/org.simantics.district.selection.ui/fragment.e4xmi +++ b/org.simantics.district.selection.ui/fragment.e4xmi @@ -1,15 +1,12 @@ - + View categoryTag:District - - - @@ -18,7 +15,7 @@ - + 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 index b68c3fe1..074cdfeb 100644 --- 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 @@ -2,23 +2,28 @@ package org.simantics.district.selection.ui.parts; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; 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.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.layout.RowDataFactory; import org.eclipse.jface.layout.RowLayoutFactory; -import org.eclipse.jface.window.Window; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; 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; @@ -28,21 +33,23 @@ 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.eclipse.swt.widgets.Widget; 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.IndexRoot; 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.layer0.request.PropertyInfo; +import org.simantics.db.layer0.request.PropertyInfoRequest; import org.simantics.db.layer0.util.Layer0Utils; -import org.simantics.db.request.Read; -import org.simantics.db.request.WriteResult; import org.simantics.diagram.stubs.DiagramResource; +import org.simantics.district.network.ontology.DistrictNetworkResource; import org.simantics.district.region.ontology.DiagramRegionsResource; import org.simantics.district.route.ontology.RouteResource; import org.simantics.district.selection.ElementSelectionResource; @@ -62,13 +69,21 @@ 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.layer0.utils.direct.GraphUtils; import org.simantics.modeling.ModelingResources; +import org.simantics.structural.stubs.StructuralResource2; import org.simantics.utils.datastructures.Arrays; +import org.simantics.utils.datastructures.Pair; +import org.simantics.utils.ui.BundleUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class EditSelectorDialog extends Dialog { + private static final RowLayoutFactory ROW_LAYOUT = RowLayoutFactory.fillDefaults().wrap(false); + private static final ImageDescriptor CROSS_IMAGE = BundleUtils.getImageDescriptorFromPlugin("com.famfamfam.silk", "icons/cross.png"); + private static final ImageDescriptor PLUS_IMAGE = BundleUtils.getImageDescriptorFromPlugin("com.famfamfam.silk", "icons/add.png"); + private static Logger LOGGER = LoggerFactory.getLogger(EditSelectorDialog.class); private ElementSelector elementSelector; @@ -77,13 +92,22 @@ public class EditSelectorDialog extends Dialog { Collection currentSelection; // Data for comboboxes - Map diagrams; + private List diagrams; + private ArrayList diagramNames; private String[] regionNames; private Resource[] regionResources; private String[] routeNames; private Resource[] routeResources; + + private List componentTypes; + private List componentTypeNames; + + private List propertyNames; + private List propertyLabels; + + private Composite conditionPanel; // Dialog fields private int generatorIndex; @@ -98,19 +122,43 @@ public class EditSelectorDialog extends Dialog { private int selectorIndex; private Combo selectorField; + private Resource componentType; + private Combo componentTypeField; + private String propertyName; - private Text propertyField; + private Combo propertyField; private int numberOfItems; private Text nField; private Condition condition; - private Button removeConditionButton; - private Text conditionLabel; // Dialog area component private Composite content; + private int diagramIndex; + + private LocalResourceManager resourceManager; + + static class ValidationException extends Exception { + private static final long serialVersionUID = 1L; + + public ValidationException(String message) { + super(message); + } + } + + // Function type for updating condition objects with optional validation + static interface Updater { + // If 'validate' is true, a runtime exception may be thrown for invalid values + void update(boolean validate) throws ValidationException; + } + + final static Updater NULL_UPDATE = validate -> {}; + + // Called to read values from controls into conditions + Updater updater = NULL_UPDATE; + @Inject public EditSelectorDialog(Shell shell, ElementSelector elementSelector, Collection currentSelection) { super(shell); @@ -132,38 +180,17 @@ public class EditSelectorDialog extends Dialog { this.currentSelection = currentSelection; - final Map regions = new HashMap<>(); - final Map routes = new HashMap<>(); - - try { - Simantics.getSession().syncRequest(new Read() { - @Override - public Void perform(ReadGraph graph) throws DatabaseException { - Resource model = ActiveModels.getPossibleActiveModel(graph, Simantics.getProjectResource()); - List 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 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; - } + Map diagramMap = ElementSelector.findDiagrams(); + diagrams = new ArrayList(diagramMap.size()); + diagramNames = new ArrayList(diagramMap.size()); + diagramMap.entrySet() + .stream() + .sorted(Comparator.comparing(e -> e.getValue())) + .forEachOrdered(e -> { + diagrams.add(e.getKey()); + diagramNames.add(e.getValue()); }); - } 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; @@ -183,7 +210,6 @@ public class EditSelectorDialog extends Dialog { } else if (generator instanceof ExplicitGenerator) { generatorIndex = 2; - // TODO: Management of explicit lists of elements } else { throw new IllegalStateException("Unknown generator type " + generator.getClass().getName()); @@ -204,30 +230,168 @@ public class EditSelectorDialog extends Dialog { } condition = elementSelector.getCondition(); + + componentType = elementSelector.getSelector().componentType; + } + + readRegions(diagram); + readRoutes(); + readComponentTypes(); + updatePropertyList(); + } + + private void readComponentTypes() { + try { + Simantics.getSession().syncRequest(new ReadRequest() { + @Override + public void run(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + List types = findComponentTypes(graph); + + componentTypes = new ArrayList<>(types.size() + 1); + componentTypeNames = new ArrayList<>(types.size() + 1); + + componentTypes.add(null); + componentTypeNames.add("Any type"); + componentTypes.addAll(types); + for (Resource t : types) { + componentTypeNames.add(graph.getValue2(t, L0.HasName)); + } + } + }); + } catch (DatabaseException e) { + LOGGER.error("Failed to read district component types", e); + } + } + + private void readRoutes() { + final Map routes = new HashMap<>(); + + try { + Simantics.getSession().syncRequest(new ReadRequest() { + @Override + public void run(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + RouteResource ROUTE = RouteResource.getInstance(graph); + + Resource model = ActiveModels.getPossibleActiveModel(graph, Simantics.getProjectResource()); + List routeCollection = QueryIndexUtils.searchByType(graph, model, ROUTE.Route); + for (Resource r : routeCollection) { + String name = graph.getRelatedValue(r, L0.HasLabel); + routes.put(r, name); + } + } + }); + } catch (DatabaseException e) { + LOGGER.error("Failed to read routes in the model", e); } + + routeNames = routes.values().toArray(new String[routes.size()]); + routeResources = routes.keySet().toArray(new Resource[routes.size()]); + } + + private void readRegions(Resource diagram) { + final Map regions = new HashMap<>(); + + try { + Simantics.getSession().syncRequest(new ReadRequest() { + @Override + public void run(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + ModelingResources MOD = ModelingResources.getInstance(graph); + DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph); + + // If a specific diagram is given, use that + Collection ds = diagram != null ? Collections.singleton(diagram) : diagrams; + + for (Resource composite : ds) { + Resource diagram = graph.getSingleObject(composite, MOD.CompositeToDiagram); + for (Resource r : graph.getObjects(diagram, DR.hasRegion)) { + if (!graph.isInstanceOf(r, DR.Region)) + continue; + String name = graph.getRelatedValue(r, L0.HasLabel); + regions.put(r, name); + } + } + } + }); + } catch (DatabaseException e) { + LOGGER.error("Failed to read regions in the model", e); + } + + regionNames = regions.values().toArray(new String[regions.size()]); + regionResources = regions.keySet().toArray(new Resource[regions.size()]); } + private void updateDialog() { + try { + updater.update(false); + } catch (ValidationException e) { + // Should not happend with argument false + assert(false); + } + + updater = updateConditionPanel(); + + content.layout(true, true); + getShell().pack(); + } + @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; + try { + generatorIndex = sourceField.getSelectionIndex(); + if (generatorIndex == 1) { + int selectionIndex = diagramField.getSelectionIndex(); + if (selectionIndex < 0) { + diagramField.setFocus(); + throw new ValidationException("Please select a diagram"); + } + + diagram = diagrams.get(selectionIndex); + } + + name = nameField.getText(); + if (name.isEmpty()) { + nameField.setFocus(); + throw new ValidationException("Please enter a name"); + } + + componentType = componentTypes.get(componentTypeField.getSelectionIndex()); + + selectorIndex = selectorField.getSelectionIndex(); + if (selectorIndex != 0) { + int propertyIndex = propertyField.getSelectionIndex(); + propertyName = propertyIndex >= 0 ? propertyNames.get(propertyIndex) : propertyField.getText(); + if (propertyName.isEmpty()) { + propertyField.setFocus(); + throw new ValidationException("Please select a property"); + } } - diagram = new ArrayList(diagrams.keySet()).get(selectionIndex); + // Try to parse number of items + if (useNumberOfItems()) { + try { + numberOfItems = Integer.parseInt(nField.getText()); + if (numberOfItems <= 0) { + nField.selectAll(); + nField.setFocus(); + throw new ValidationException("Number of elements must be positive"); + } + } catch (NumberFormatException e) { + nField.selectAll(); + nField.setFocus(); + throw new ValidationException("Please enter a valid number of elements"); + } + } + + // To to update condition definitions + updater.update(true); + } catch (ValidationException e) { + MessageDialog.openError(this.getShell(), "Missing data", e.getMessage()); + return; } - name = nameField.getText(); - - selectorIndex = selectorField.getSelectionIndex(); - - propertyName = propertyField.getText(); - String text = nField.getText(); - numberOfItems = "".equals(text) ? 0 : Integer.parseInt(text); - super.okPressed(); } @@ -306,6 +470,9 @@ public class EditSelectorDialog extends Dialog { } graph.claim(selector, L0.InstanceOf, selectorType); graph.claim(selection, ES.Selection_HasSelector, selector); + graph.deny(selector, ES.Selector_HasMapping); + if (componentType != null) + graph.claim(selector, ES.Selector_HasMapping, componentType); if (selectorIndex > 0) { graph.claimLiteral(selector, ES.PropertySelector_HasSelectionPropertyName, L0.String, propertyName); @@ -314,19 +481,30 @@ public class EditSelectorDialog extends Dialog { // Condition if (condition != null) { - graph.claim(selection, ES.Selection_HasCondition, condition.resource); + Resource conditionResource = condition.update(graph); + graph.claim(selection, ES.Selection_HasCondition, conditionResource); } } }); } + private boolean isDiagramFieldVisible() { + return generatorIndex == 1; + } + + private boolean useNumberOfItems() { + return selectorIndex != 0; + } + @Override protected Control createDialogArea(Composite parent) { + this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent); + // Set dialog title getShell().setText("Edit element selector"); content = new Composite(parent, SWT.NONE); - GridLayoutFactory.swtDefaults().numColumns(3).applyTo(content); + GridLayoutFactory.swtDefaults().numColumns(2).applyTo(content); // Name Label nameLabel = new Label(content, SWT.NONE); @@ -336,599 +514,513 @@ public class EditSelectorDialog extends Dialog { nameField = new Text(content, SWT.BORDER); nameField.setEditable(true); nameField.setText(name); - GridDataFactory.swtDefaults().span(2, 1).hint(200, SWT.DEFAULT).applyTo(nameField); + GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT).applyTo(nameField); + + // Selector + Label selectorLabel = new Label(content, SWT.NONE); + selectorLabel.setText("Select"); + GridDataFactory.swtDefaults().applyTo(selectorLabel); + + Composite selectorComposite = new Composite(content, SWT.NONE); + GridDataFactory.swtDefaults().applyTo(selectorComposite); + RowLayoutFactory.fillDefaults().applyTo(selectorComposite); + + nField = new Text(selectorComposite, SWT.BORDER); + RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(nField); + if (useNumberOfItems()) + nField.setText(Integer.toString(numberOfItems)); + nField.setEnabled(useNumberOfItems()); + + componentTypeField = new Combo(selectorComposite, SWT.READ_ONLY); + RowDataFactory.swtDefaults().applyTo(componentTypeField); + componentTypeField.setItems(componentTypeNames.toArray(new String[] {})); + { + int index = componentTypes.indexOf(componentType); + componentTypeField.select(index >= 0 ? index : 0); + } + + // Update property selection controls when component type changes + componentTypeField.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + int index = componentTypeField.getSelectionIndex(); + componentType = index >= 0 ? componentTypes.get(index) : null; + updatePropertyList(); + propertyField.setItems(propertyLabels.toArray(new String[] {})); + + updateDialog(); + } + }); + + new Label(selectorComposite, SWT.NONE).setText("with"); + + selectorField = new Combo(selectorComposite, SWT.BORDER | SWT.READ_ONLY); + selectorField.setItems("All", "Lowest", "Highest"); + selectorField.select(selectorIndex); + RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(selectorField); + + propertyField = new Combo(selectorComposite, SWT.NONE); + RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(propertyField); + propertyField.setItems(propertyLabels.toArray(new String[] {})); + { + int index = propertyName != null ? propertyNames.indexOf(propertyName) : -1; + if (index >= 0) + propertyField.select(index); + else + propertyField.setText(propertyName != null ? propertyName : ""); + } + propertyField.setEnabled(useNumberOfItems()); + + selectorField.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + selectorIndex = selectorField.getSelectionIndex(); + + boolean enable = useNumberOfItems(); + nField.setEnabled(enable); + propertyField.setEnabled(enable); + + nField.setText(enable ? Integer.toString(numberOfItems) : ""); + } + }); // Source Label sourceLabel = new Label(content, SWT.NONE); - sourceLabel.setText("Source"); - GridDataFactory.swtDefaults().applyTo(sourceLabel); + sourceLabel.setText("from"); + GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(sourceLabel); + + Composite sourceComposite = new Composite(content, SWT.NONE); + GridDataFactory.swtDefaults().applyTo(sourceComposite); + RowLayoutFactory.fillDefaults().applyTo(sourceComposite); - sourceField = new Combo(content, SWT.BORDER | SWT.READ_ONLY); - sourceField.setItems("Model", "Diagram", "Current selection"); + sourceField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY); + RowDataFactory.swtDefaults().applyTo(sourceField); + sourceField.setItems("Whole 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()])); + diagramField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY); + RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(diagramField); + diagramField.setItems(diagramNames.toArray(new String[diagramNames.size()])); + diagramField.setEnabled(isDiagramFieldVisible()); - int diagramIndex = diagram != null ? new ArrayList<>(diagrams.keySet()).indexOf(diagram) : -1; + diagramIndex = diagram != null ? diagrams.indexOf(diagram) : -1; diagramField.select(diagramIndex); sourceField.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { generatorIndex = sourceField.getSelectionIndex(); - diagramField.setVisible(isDiagramFieldVisible()); + boolean enabled = isDiagramFieldVisible(); + if (!enabled) { + diagramIndex = diagramField.getSelectionIndex(); + diagramField.clearSelection(); + } else { + if (diagramIndex >= 0) + diagramField.select(diagramIndex); + else + diagramField.clearSelection(); + } + diagramField.setEnabled(enabled); + + // Refresh list of regions for current diagram + diagram = enabled ? (diagramIndex >= 0 ? diagrams.get(diagramIndex) : null) : null; + readRegions(diagram); + updateDialog(); } }); sourceField.select(generatorIndex); - diagramField.setVisible(isDiagramFieldVisible()); - // Selector - Label selectorLabel = new Label(content, SWT.NONE); - selectorLabel.setText("Select"); - GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorLabel); + // Condition + Label label = new Label(content, SWT.NONE); + GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(label); + label.setText("where"); - 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); + conditionPanel = new Composite(content, SWT.NONE); + GridDataFactory.swtDefaults().span(1, 2).minSize(400, SWT.DEFAULT).grab(true, false).applyTo(conditionPanel); + GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionPanel); - Composite selectorComposite = new Composite(content, SWT.NONE); - GridDataFactory.swtDefaults().span(1, 1).applyTo(selectorComposite); - GridLayoutFactory.swtDefaults().numColumns(2).applyTo(selectorComposite); + updater = updateConditionPanel(); - Label propertyLabel = new Label(selectorComposite, SWT.NONE); - propertyLabel.setText("Property name"); - GridDataFactory.swtDefaults().applyTo(propertyLabel); + return content; + } + + private Updater updateConditionPanel() { + // Erase contents + for (Widget c : conditionPanel.getChildren()) + c.dispose(); - propertyField = new Text(selectorComposite, SWT.BORDER); - propertyField.setText(propertyName); - GridDataFactory.swtDefaults().hint(80, SWT.DEFAULT).applyTo(propertyField); + return createConditionPanelFor(conditionPanel, condition, cond -> condition = cond); + } + + private Updater createConditionPanelFor(final Composite parent, final Condition condition, final Consumer consumer) { + // Create new contents + Button notCheck = new Button(parent, SWT.CHECK); + GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(notCheck); + notCheck.setText("not"); + notCheck.setSelection(condition != null && condition.isInverse); - Label nLabel = new Label(selectorComposite, SWT.NONE); - nLabel.setText("Number of elements"); - GridDataFactory.swtDefaults().applyTo(nLabel); + Composite conditionComp = new Composite(parent, SWT.NONE); + GridDataFactory.fillDefaults().applyTo(conditionComp); - nField = new Text(selectorComposite, SWT.BORDER); - nField.setText(Integer.toString(numberOfItems)); - GridDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(nField); + Combo typeCombo = new Combo(conditionComp, SWT.BORDER | SWT.READ_ONLY); + typeCombo.setItems( + "No condition", + "Property", + "In region", + "On route", + "All of", + "Any of" + ); - selectorField.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - selectorIndex = selectorField.getSelectionIndex(); - selectorComposite.setVisible(isSelectorCompositeVisible()); + typeCombo.addSelectionListener(new ConditionTypeSelectionListener(typeCombo, consumer, condition)); + + final Updater updater; + if (condition instanceof PropertyCondition) { + typeCombo.select(1); + updater = createPropertyConditionPanel(conditionComp, (PropertyCondition)condition); + } else if (condition instanceof RegionCondition) { + typeCombo.select(2); + updater = createRegionConditionPanel(conditionComp, (RegionCondition)condition); + } else if (condition instanceof RouteCondition) { + typeCombo.select(3); + updater = createRouteConditionPanel(conditionComp, (RouteCondition)condition); + } else if (condition instanceof AggregateCondition) { + AggregateCondition cond = (AggregateCondition) condition; + typeCombo.select(cond.type.equals(Type.CONJUNCTION) ? 4 : 5); + updater = createAggregateConditionPanel(conditionComp, cond); + } else { + ROW_LAYOUT.applyTo(conditionComp); + notCheck.setEnabled(false); + typeCombo.select(0); + return NULL_UPDATE; + } + + return validate -> { + updater.update(validate); + condition.isInverse = notCheck.getSelection(); + }; + } + + private final class ConditionTypeSelectionListener extends SelectionAdapter { + private final Combo typeCombo; + private final Consumer consumer; + private final Condition finalCondition; + + private ConditionTypeSelectionListener(Combo typeCombo, Consumer consumer, Condition finalCondition) { + this.typeCombo = typeCombo; + this.consumer = consumer; + this.finalCondition = finalCondition; + } + + @Override + public void widgetSelected(SelectionEvent e) { + int index = typeCombo.getSelectionIndex(); + Condition newCondition = finalCondition; + switch (index) { + case 0: + newCondition = null; + break; + case 1: + newCondition = createPropertyCondition("", null, null); + break; + case 2: + newCondition = createRegionCondition(null); + break; + case 3: + newCondition = createRouteCondition(null); + break; + case 4: + if (newCondition instanceof AggregateCondition) + ((AggregateCondition)newCondition).type = Type.CONJUNCTION; + else + newCondition = createAggregateCondition(null, new ArrayList<>(), true, false); + break; + case 5: + if (newCondition instanceof AggregateCondition) + ((AggregateCondition)newCondition).type = Type.DISJUNCTION; + else + newCondition = createAggregateCondition(null, new ArrayList<>(), false, false); + break; } - }); - selectorField.select(selectorIndex); - selectorComposite.setVisible(isSelectorCompositeVisible()); + consumer.accept(newCondition); + + updateDialog(); + } + } + + private Updater createAggregateConditionPanel(Composite conditionComp, AggregateCondition cond) { + GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionComp); + new Label(conditionComp, SWT.NONE); // Eat extra column - // 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() { + int n = cond.conditions.size(); + final Updater[] updates = new Updater[n]; + for (int i = 0; i < n; i++) { + updates[i] = createConditionRowPanel(conditionComp, cond, i); + } + + Button addButton = new Button(conditionComp, SWT.PUSH); + GridDataFactory.swtDefaults().applyTo(addButton); + addButton.setImage(resourceManager.createImage(PLUS_IMAGE)); + + addButton.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(); - } + cond.conditions.add(createPropertyCondition("", null, null)); + updateDialog(); } }); - removeConditionButton.addSelectionListener(new SelectionAdapter() { + return validate -> { + for (Updater updater : updates) + updater.update(validate); + }; + } + + private Updater createConditionRowPanel(Composite parent, AggregateCondition parentCondition, final int i) { + GridLayoutFactory conditionLayout = GridLayoutFactory.fillDefaults().numColumns(2); + GridDataFactory swtDefaults = GridDataFactory.swtDefaults(); + + Condition c = parentCondition.conditions.get(i); + + Composite row = new Composite(parent, SWT.NONE); + conditionLayout.applyTo(row); + swtDefaults.applyTo(row); + + Consumer update = cd -> { + if (cd != null) + parentCondition.conditions.set(i, cd); + else + parentCondition.conditions.remove(i); + }; + + Updater updater = createConditionPanelFor(row, c, update); + + Button removeButton = new Button(parent, SWT.PUSH); + swtDefaults.align(SWT.BEGINNING, SWT.BEGINNING).applyTo(removeButton); + removeButton.setImage(resourceManager.createImage(CROSS_IMAGE)); + + removeButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - condition = null; - updateCondition(); + parentCondition.conditions.remove(i); + updateDialog(); } }); - 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(); + return updater; } - private boolean isDiagramFieldVisible() { - return generatorIndex == 1; - } + private Updater createRouteConditionPanel(Composite conditionComp, RouteCondition condition) { + ROW_LAYOUT.applyTo(conditionComp); - private boolean isSelectorCompositeVisible() { - return selectorIndex != 0; + // Create combo-box + Combo routeCombo = new Combo(conditionComp, SWT.READ_ONLY); + RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(routeCombo); + routeCombo.setItems(routeNames); + + // Set current selection + int index = Arrays.indexOf(routeResources, condition.routeResource); + if (index >= 0) + routeCombo.select(index); + + // Register update + return validate -> { + int i = routeCombo.getSelectionIndex(); + if (validate && i < 0) { + routeCombo.forceFocus(); + throw new RuntimeException("Must select a route"); + } + + condition.routeResource = i >= 0 ? routeResources[i] : null; + }; } - class ConditionDialog extends Dialog { - // Resource of the edited condition - private Resource existingResource; + private Updater createRegionConditionPanel(Composite conditionComp, RegionCondition condition) { + ROW_LAYOUT.applyTo(conditionComp); - // Inverse condition button - private boolean isInverse; - private Button inverseField; - - // Condition type - private int typeIndex; - private Combo typeField; + // Create combo-box + Combo regionCombo = new Combo(conditionComp, SWT.READ_ONLY); + RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(regionCombo); + regionCombo.setItems(regionNames); - // Type-specific control panels under a stack layout - private Composite stackPanel; - private StackLayout stack; + // Set current selection + int index = Arrays.indexOf(regionResources, condition.regionResource); + if (index >= 0) + regionCombo.select(index); - private Composite propertyPanel; - private Composite regionPanel; - private Composite routePanel; - private Composite aggregatePanel; + // Register update + return validate -> { + int i = regionCombo.getSelectionIndex(); + if (validate && i < 0) { + regionCombo.forceFocus(); + throw new ValidationException("Please select a region"); + } + + condition.regionResource = i >= 0 ? regionResources[i] : null; + }; + } + + private Updater createPropertyConditionPanel(Composite conditionComp, PropertyCondition condition) { + ROW_LAYOUT.applyTo(conditionComp); - // Property condition - private Double lowerLimit; - private Double upperLimit; - private String propertyName; - - private Text propertyNameField; - private Text lowerLimitField; - private Text upperLimitField; + Text lowerLimitText = new Text(conditionComp, SWT.BORDER); + RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(lowerLimitText); + lowerLimitText.setText(condition.lowerLimit != null ? Double.toString(condition.lowerLimit) : ""); - // Region condition - private Resource region; - private Combo regionField; + new Label(conditionComp, SWT.NONE).setText("\u2264"); - // Route condition - private Resource route; - private Combo routeField; + Combo propertyNameText = new Combo(conditionComp, SWT.NONE); + RowDataFactory.swtDefaults().hint(120, SWT.DEFAULT).applyTo(propertyNameText); + propertyNameText.setItems(propertyLabels.toArray(new String[] {})); + int index = propertyNames.indexOf(condition.propertyName); + if (index >= 0) + propertyNameText.select(index); + else + propertyNameText.setText(condition.propertyName); - // Aggregate condition - private List subConditions; - private boolean isConjunction; + new Label(conditionComp, SWT.NONE).setText("\u2264"); - private org.eclipse.swt.widgets.List subConditionField; - private Combo operatorField; + Text upperLimitText = new Text(conditionComp, SWT.BORDER); + RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(upperLimitText); + upperLimitText.setText(condition.upperLimit != null ? Double.toString(condition.upperLimit) : ""); - 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; + // Register update + return validate -> { + try { + String text = lowerLimitText.getText(); + condition.lowerLimit = text.isEmpty() ? null : Double.parseDouble(text); + } catch (NumberFormatException e) { + if (validate) { + lowerLimitText.selectAll(); + lowerLimitText.forceFocus(); + throw new ValidationException("Please enter a valid lower limit"); } } - } - - @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 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(""); - } + try { + String text = upperLimitText.getText(); + condition.upperLimit = text.isEmpty() ? null : Double.parseDouble(text); + } catch (NumberFormatException e) { + if (validate) { + upperLimitText.selectAll(); + upperLimitText.forceFocus(); + throw new ValidationException("Please enter a valid upper limit"); } - - 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(""); - } - } catch (DatabaseException e2) { - LOGGER.error("Create condition failed", e2); - } - } - } - }); + int ind = propertyNameText.getSelectionIndex(); + String name; + if (ind >= 0) + name = propertyNames.get(ind); + else + name = propertyNameText.getText(); - 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, ""); - } - } 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; + if (validate && name.isEmpty()) { + propertyNameText.forceFocus(); + throw new ValidationException("Please select a property"); + } else { + condition.propertyName = name; } - - super.okPressed(); - } + }; + } + + private static Condition createPropertyCondition(String propertyName, Double lowerLimit, Double upperLimit) { + return new PropertyCondition(null, propertyName, lowerLimit, upperLimit); + } - protected Condition createCondition() throws DatabaseException { - if (isInverse && !(typeIndex == 3 && !isConjunction)) { - Resource resource0 = createCondition0(); - - // Create a negation - Resource resource = Simantics.getSession().syncRequest(new WriteResult() { - @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 static Condition createRegionCondition(Resource regionResource) { + return new RegionCondition(null, regionResource, null); + } - 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 static Condition createRouteCondition(Resource route) { + return new RouteCondition(null, route, null); + } - private Resource createPropertyCondition() throws DatabaseException { - return Simantics.getSession().syncRequest(new WriteResult() { - @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 static Condition createAggregateCondition(Resource existingResource, List subConditions, boolean isConjunction, boolean isInverse) { + Type type = isConjunction ? Type.CONJUNCTION : Type.DISJUNCTION; + AggregateCondition condition = new AggregateCondition(null, type, subConditions); + condition.isInverse = isInverse; + return condition; + } + + static List findComponentTypes(ReadGraph graph) throws DatabaseException { + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + Resource project = Simantics.getProjectResource(); + Resource model = ActiveModels.getPossibleActiveModel(graph, project); - private Resource createAggregateCondition() throws DatabaseException { - return Simantics.getSession().syncRequest(new WriteResult() { - @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; - } - }); - } + return QueryIndexUtils.searchByType(graph, model, DN.Mapping_Base); + } - private Resource createRouteCondition() throws DatabaseException { - return Simantics.getSession().syncRequest(new WriteResult() { + void updatePropertyList() { + propertyNames = new ArrayList<>(); + propertyLabels = new ArrayList<>(); + + Collection types = componentType != null ? Collections.singleton(componentType) : componentTypes; + Set> properties = new HashSet<>(); + + try { + Simantics.getSession().syncRequest(new ReadRequest() { @Override - public Resource perform(WriteGraph graph) throws DatabaseException { - ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + public void run(ReadGraph graph) throws DatabaseException { 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; + for (Resource type : types) { + if (type == null) + continue; + + Resource ct = graph.getPossibleObject(type, DistrictNetworkResource.getInstance(graph).Mapping_ComponentType); + if (ct == null) + continue; + + if (graph.isInstanceOf(ct, L0.String)) { + Resource indexRoot = graph.syncRequest(new IndexRoot(type)); + String name = graph.getValue(ct); + ct = GraphUtils.getPossibleChild(graph, indexRoot, name); + if (ct == null) + continue; + } + + for (Resource prop : graph.getObjects(ct, L0.DomainOf)) { + if (!graph.isInstanceOf(prop, StructuralResource2.getInstance(graph).Property)) + continue; + + // Filter only numeric properties + PropertyInfo info = graph.syncRequest(new PropertyInfoRequest(prop)); + if (info != null && info.requiredValueType != null && !isNumericValueType(info.requiredValueType)) + continue; + + String name = graph.getRelatedValue2(prop, L0.HasName); + String label = graph.getPossibleRelatedValue2(prop, L0.HasLabel); + if (label == null) label = name; + + properties.add(Pair.make(label, name)); + } + } } }); + } catch (DatabaseException e) { + LOGGER.error("Failed to read district component properties", e); } + + propertyNames.clear(); + propertyLabels.clear(); + properties.stream().sorted(Comparator.comparing(p -> p.first)).forEachOrdered(p -> { + propertyLabels.add(p.first); + propertyNames.add(p.second); + }); + } - private Resource createRegionCondition() throws DatabaseException { - return Simantics.getSession().syncRequest(new WriteResult() { - @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(); + static boolean isNumericValueType(String requiredValueType) { + switch (requiredValueType) { + case "Integer": + case "Long": + case "Double": + case "Float": + return true; + default: + return false; } } } diff --git a/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/parts/ElementSelectionView.java b/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/parts/ElementSelectionView.java index 5a0767f8..dfc53328 100644 --- a/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/parts/ElementSelectionView.java +++ b/org.simantics.district.selection.ui/src/org/simantics/district/selection/ui/parts/ElementSelectionView.java @@ -20,7 +20,6 @@ import org.eclipse.e4.ui.model.application.ui.menu.MToolBar; import org.eclipse.e4.ui.model.application.ui.menu.MToolBarElement; import org.eclipse.e4.ui.services.EMenuService; import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.e4.ui.workbench.modeling.ISelectionListener; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; diff --git a/org.simantics.district.selection/graph/DiagramElementSelection.pgraph b/org.simantics.district.selection/graph/DiagramElementSelection.pgraph index 55e01958..22071403 100644 --- a/org.simantics.district.selection/graph/DiagramElementSelection.pgraph +++ b/org.simantics.district.selection/graph/DiagramElementSelection.pgraph @@ -42,6 +42,8 @@ ES.PropertySelector -- ES.Condition.IsInverse --> ES.Condition >() { @Override public Map perform(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + StructuralResource2 STR = StructuralResource2.getInstance(graph); + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + ModelingResources MOD = ModelingResources.getInstance(graph); + Map result = new HashMap<>(); Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); - List composites = QueryIndexUtils.searchByType(graph, model, StructuralResource2.getInstance(graph).Composite); + List composites = QueryIndexUtils.searchByType(graph, model, STR.Composite); for (Resource r : composites) { // Get diagram - Resource diagram = graph.getPossibleObject(r, ModelingResources.getInstance(graph).CompositeToDiagram); - if (diagram == null) continue; + Resource diagram = graph.getPossibleObject(r, MOD.CompositeToDiagram); + if (diagram == null || !graph.isInstanceOf(diagram, DN.Diagram)) + continue; // Filter out user component diagrams - Resource parent = graph.getPossibleObject(r, Layer0.getInstance(graph).PartOf); - if (parent == null || graph.isInheritedFrom(parent, StructuralResource2.getInstance(graph).Component)) + Resource parent = graph.getPossibleObject(r, L0.PartOf); + if (parent == null || graph.isInheritedFrom(parent, STR.Component)) continue; - result.put(r, graph.getRelatedValue(r, Layer0.getInstance(graph).HasName)); + result.put(r, graph.getRelatedValue(r, L0.HasName)); } return result; @@ -246,18 +259,14 @@ public class ElementSelector { if (!graph.isInstanceOf(resource, ES.Generator)) throw new IllegalArgumentException("Resource " + resource + " is not a valid generator"); - if (graph.isInstanceOf(resource, ES.Generator_Model)) { + if (graph.isInstanceOf(resource, ES.Generator_Model)) return new ModelGenerator(); - } - else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) { + else if (graph.isInstanceOf(resource, ES.Generator_Diagram)) return new DiagramGenerator(graph.getSingleObject(resource, ES.Generator_HasDiagram)); - } - else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) { + else if (graph.isInstanceOf(resource, ES.Generator_Explicit)) return new ExplicitGenerator(graph.getObjects(resource, ES.Generator_HasSelectedElement)); - } - else { + else throw new IllegalArgumentException("Unknown generator type " + graph.getURI(graph.getSingleType(resource))); - } } private static Selector buildSelector(ReadGraph graph, Resource resource) throws DatabaseException { @@ -265,18 +274,16 @@ public class ElementSelector { throw new IllegalArgumentException("Resource " + resource + " is not a valid selector"); Selector s; - if (graph.isInstanceOf(resource, ES.Selector_All)) { + if (graph.isInstanceOf(resource, ES.Selector_All)) s = new All(); - } - else if (graph.isInstanceOf(resource, ES.PropertySelector)) { - String propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName); - Integer resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount); - boolean isSmallest = graph.isInstanceOf(resource, ES.Selector_NLowest); - s = new PropertySelector(isSmallest, propertyName, resultCount); - } - else { + else if (graph.isInstanceOf(resource, ES.PropertySelector)) + s = new PropertySelector(graph, resource); + else throw new IllegalArgumentException("Unknown selector type " + graph.getURI(graph.getSingleType(resource))); - } + + Resource mapping = graph.getPossibleObject(resource, ES.Selector_HasMapping); + s.componentType = mapping; + return s; } @@ -288,145 +295,179 @@ public class ElementSelector { throw new IllegalArgumentException("Resource " + resource + " is not a valid condition"); Condition cond; - if (graph.isInstanceOf(resource, ES.PropertyCondition)) { - String propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName); - Double lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit); - Double upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit); - cond = new PropertyCondition(resource, propertyName, lowerLimit, upperLimit); + if (graph.isInstanceOf(resource, ES.PropertyCondition)) + cond = new PropertyCondition(graph, resource); + else if (graph.isInstanceOf(resource, ES.RegionCondition)) + cond = new RegionCondition(graph, resource); + else if (graph.isInstanceOf(resource, ES.RouteCondition)) + cond = new RouteCondition(graph, resource); + else if (graph.isInstanceOf(resource, ES.AggregateCondition)) + cond = new AggregateCondition(graph, resource); + else + throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource))); + + return cond; + } + + private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException { + if (graph.isInstanceOf(r, ES.Selection)) + return buildSelectionExpression(graph, r); + else if (graph.isInstanceOf(r, ES.Condition)) + return buildConditionExpression(graph, r); + else if (graph.isInstanceOf(r, ES.Selector)) + return buildSelectorExpression(graph, r); + else if (graph.isInstanceOf(r, ES.Generator)) + return buildGeneratorExpression(graph, r); + else + throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + } + + private static String buildSelectionExpression(ReadGraph graph, Resource r) throws DatabaseException, + NoSingleResultException, ManyObjectsForFunctionalRelationException, ServiceException { + String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + + " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator)); + + Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition); + return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp; + } + + private static String buildGeneratorExpression(ReadGraph graph, Resource r) + throws ServiceException, NoSingleResultException, DoesNotContainValueException, + ManyObjectsForFunctionalRelationException, DatabaseException, AssumptionException, ValidationException { + if (graph.isInstanceOf(r, ES.Generator_Model)) { + return "model"; } - else if (graph.isInstanceOf(resource, ES.RegionCondition)) { - DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph); - Resource regionResource = graph.getSingleObject(resource, ES.RegionCondition_HasRegion); - double[] region = graph.getRelatedValue(regionResource, DR.Region_area); - cond = new RegionCondition(resource, regionResource, region); + else if (graph.isInstanceOf(r, ES.Generator_Diagram)) { + return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\""; } - else if (graph.isInstanceOf(resource, ES.RouteCondition)) { - Resource routeResource = graph.getSingleObject(resource, ES.RouteCondition_HasRoute); - Set routePoints = new HashSet<>(ListUtils.toList(graph, routeResource)); - cond = new RouteCondition(resource, routeResource, routePoints); + else if (graph.isInstanceOf(r, ES.Generator_Explicit)) { + return ""; } - else if (graph.isInstanceOf(resource, ES.AggregateCondition)) { - Collection conditionResources = graph.getObjects(resource, ES.HasSubcondition); - List conditions = new ArrayList<>(conditionResources.size()); - for (Resource c : conditionResources) { - conditions.add(buildCondition(graph, c)); - } - Type type; - if (graph.isInstanceOf(resource, ES.Conjunction)) - type = AggregateCondition.Type.CONJUNCTION; - else if (graph.isInstanceOf(resource, ES.Negation)) - type = AggregateCondition.Type.NEGATION; - else if (graph.isInstanceOf(resource, ES.Disjunction)) - type = AggregateCondition.Type.DISJUNCTION; - else - throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource))); - - cond = new AggregateCondition(resource, type, conditions); + else { + throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + } + } + + private static String buildSelectorExpression(ReadGraph graph, Resource r) + throws ServiceException, NoSingleResultException, DoesNotContainValueException, DatabaseException, + AssumptionException, ValidationException, ManyObjectsForFunctionalRelationException { + String exp; + if (graph.isInstanceOf(r, ES.Selector_All)) { + exp = "all"; + } + else if (graph.isInstanceOf(r, ES.PropertySelector)) { + Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount); + exp = count.toString(); } else { - throw new IllegalArgumentException("Unknown condition type " + graph.getURI(graph.getSingleType(resource))); + throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); } - return cond; + Resource mapping = graph.getPossibleObject(r, ES.Selector_HasMapping); + if (mapping != null) { + String name = graph.getRelatedValue2(mapping, L0.HasName); + exp = exp + " " + name; + } else { + exp = exp + " elements"; + } + + if (graph.isInstanceOf(r, ES.PropertySelector)) { + String op; + if (graph.isInstanceOf(r, ES.Selector_NLowest)) + op = "lowest"; + else if (graph.isInstanceOf(r, ES.Selector_NHighest)) + op = "highest"; + else + throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + + String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName); + exp = exp + " with " + op + " " + name; + } + + return exp; } - private static String buildExpression(ReadGraph graph, Resource r) throws DatabaseException { - if (graph.isInstanceOf(r, ES.Selection)) { - String exp = "select " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasSelector)) + - " from " + getExpression(graph, graph.getSingleObject(r, ES.Selection_HasGenerator)); - - Resource cond = graph.getPossibleObject(r, ES.Selection_HasCondition); - return cond != null ? exp + " where {" + getExpression(graph, cond) + "}" : exp; + private static String buildConditionExpression(ReadGraph graph, Resource r) throws ServiceException, + DatabaseException, NoSingleResultException, ManyObjectsForFunctionalRelationException, + DoesNotContainValueException, AssumptionException, ValidationException { + String result; + boolean isInverse = graph.hasStatement(r, ES.Condition_IsInverse, r); + if (graph.isInstanceOf(r, ES.PropertyCondition)) { + result = buildPropertyConditionExpression(graph, r); } - else if (graph.isInstanceOf(r, ES.Condition)) { - if (graph.isInstanceOf(r, ES.PropertyCondition)) { - return buildPropertyConditionExpression(graph, r); - } - else if (graph.isInstanceOf(r, ES.RegionCondition)) { - Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion); - String name = graph.getRelatedValue(region, L0.HasLabel); - return "in region " + name; - } - else if (graph.isInstanceOf(r, ES.RouteCondition)) { - Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute); - String name = graph.getRelatedValue(route, L0.HasLabel); - return "in route " + name; - } - else if (graph.isInstanceOf(r, ES.AggregateCondition)) { - String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or "; - List exps = new ArrayList<>(); - Collection objects = graph.getObjects(r, ES.HasSubcondition); - for (Resource c : objects) { - String exp = getExpression(graph, c); - exps.add(objects.size() > 1 ? "{" + exp + "}" : exp); - } - String result = String.join(op, exps); - if (graph.isInstanceOf(r, ES.Negation)) - result = "not {" + result + "}"; - return result; - } - else { - throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); - } + else if (graph.isInstanceOf(r, ES.RegionCondition)) { + result = buildRegionConditionExpression(graph, r); } - else if (graph.isInstanceOf(r, ES.Selector)) { - if (graph.isInstanceOf(r, ES.Selector_All)) { - return "all"; - } - else if (graph.isInstanceOf(r, ES.PropertySelector)) { - String op; - if (graph.isInstanceOf(r, ES.Selector_NLowest)) - op = "bottom"; - else if (graph.isInstanceOf(r, ES.Selector_NHighest)) - op = "top"; - else - throw new DatabaseException("Unsupported property selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); - - String name = graph.getRelatedValue(r, ES.PropertySelector_HasSelectionPropertyName); - Integer count = graph.getRelatedValue(r, ES.PropertySelector_HasResultCount); - return op + " " + count + " of " + name; - } - else { - throw new DatabaseException("Unsupported selector resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); - } + else if (graph.isInstanceOf(r, ES.RouteCondition)) { + result = buildRouteConditionExpression(graph, r); } - else if (graph.isInstanceOf(r, ES.Generator)) { - if (graph.isInstanceOf(r, ES.Generator_Model)) { - return "model"; - } - else if (graph.isInstanceOf(r, ES.Generator_Diagram)) { - return "diagram \"" + graph.getRelatedValue(graph.getSingleObject(r, ES.Generator_HasDiagram), L0.HasName) + "\""; - } - else if (graph.isInstanceOf(r, ES.Generator_Explicit)) { - return ""; - } - else { - throw new DatabaseException("Unsupported generator resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); - } + else if (graph.isInstanceOf(r, ES.AggregateCondition)) { + // This handles isInverse internally + return buildAggregateConditionExpression(graph, r, isInverse); } else { - throw new DatabaseException("Unsupported resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); + throw new DatabaseException("Unsupported condition resource type <" + graph.getURI(graph.getSingleType(r)) + ">"); } + + if (isInverse) + result = "not {" + result + "}"; + + return result; + } + + private static String buildAggregateConditionExpression(ReadGraph graph, Resource r, boolean isInverse) + throws ServiceException, DatabaseException { + String result; + String op = graph.isInstanceOf(r, ES.Conjunction) ? " and " : " or "; + List exps = new ArrayList<>(); + Collection objects = graph.getObjects(r, ES.HasSubcondition); + for (Resource c : objects) { + String exp = getExpression(graph, c); + exps.add(objects.size() > 1 ? "{" + exp + "}" : exp); + } + result = String.join(op, exps); + if (graph.isInstanceOf(r, ES.Negation) ^ isInverse) + result = "not {" + result + "}"; + return result; + } + + private static String buildRouteConditionExpression(ReadGraph graph, Resource r) throws NoSingleResultException, + ManyObjectsForFunctionalRelationException, ServiceException, DoesNotContainValueException { + String result; + Resource route = graph.getSingleObject(r, ES.RouteCondition_HasRoute); + String name = graph.getRelatedValue(route, L0.HasLabel); + result = "in route " + name; + return result; + } + + private static String buildRegionConditionExpression(ReadGraph graph, Resource r) throws NoSingleResultException, + ManyObjectsForFunctionalRelationException, ServiceException, DoesNotContainValueException { + String result; + Resource region = graph.getSingleObject(r, ES.RegionCondition_HasRegion); + String name = graph.getRelatedValue(region, L0.HasLabel); + result = "in region " + name; + return result; } private static String buildPropertyConditionExpression(ReadGraph graph, Resource r) throws DatabaseException { String propertyName = graph.getRelatedValue(r, ES.PropertyCondition_HasPropertyName); Double lowerLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasLowerLimit); Double upperLimit = graph.getPossibleRelatedValue(r, ES.PropertyCondition_HasUpperLimit); - if (lowerLimit == null && upperLimit == null) { - return "has property " + propertyName; - } - else { + if (upperLimit == null) { + if (lowerLimit == null) { + return "has property " + propertyName; + } else { + return propertyName + " \u2265 " + lowerLimit; + } + } else { StringBuilder result = new StringBuilder(); if (lowerLimit != null) { result.append(lowerLimit); - result.append(" < "); + result.append(" \u2264 "); } result.append(propertyName); - if (upperLimit != null) { - result.append(" < "); - result.append(upperLimit); - } + result.append(" \u2264 "); + result.append(upperLimit); return result.toString(); } } @@ -538,7 +579,26 @@ public class ElementSelector { } public static abstract class Selector { + public Resource componentType = null; + abstract SelectionResult select(ReadGraph graph, Collection elements); + + Collection filterElements(ReadGraph graph, Collection elements) { + if (componentType == null) + return elements; + + Collection selected = new HashSet<>(elements.size()); + for (Resource r : elements) { + try { + if (graph.hasStatement(r, DN.HasMapping, componentType)) + selected.add(r); + } catch (DatabaseException e) { + // Just leave it out of the result + } + } + + return selected; + } } public static class All extends Selector { @@ -547,7 +607,8 @@ public class ElementSelector { @Override SelectionResult select(ReadGraph graph, Collection elements) { - return new SelectionResult(elements, 0, 0); + Collection selected = filterElements(graph, elements); + return new SelectionResult(selected, 0, 0); } } @@ -558,12 +619,20 @@ public class ElementSelector { this.resultCount = resultCount; } + public PropertySelector(ReadGraph graph, Resource resource) throws DatabaseException { + this.propertyName = graph.getRelatedValue(resource, ES.PropertySelector_HasSelectionPropertyName); + this.resultCount = graph.getRelatedValue(resource, ES.PropertySelector_HasResultCount); + this.smallest = graph.isInstanceOf(resource, ES.Selector_NLowest); + } + public boolean smallest; public String propertyName; public int resultCount; @Override SelectionResult select(ReadGraph graph, Collection elements) { + elements = filterElements(graph, elements); + // Select sorting direction Comparator> comparator = smallest ? (p1, p2) -> Double.compare(p1.second, p2.second) : @@ -611,15 +680,42 @@ public class ElementSelector { public static abstract class Condition { public Resource resource; + public boolean isInverse; Condition(Resource r) { resource = r; + isInverse = false; } - abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException; + Condition(ReadGraph graph, Resource r) throws DatabaseException { + this(r); + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + isInverse = graph.hasStatement(r, ES.Condition_IsInverse, r); + } + + public abstract boolean match(ReadGraph graph, Resource r) throws DatabaseException; + public Resource update(WriteGraph graph) throws DatabaseException { + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + + assert(resource != null); + if (isInverse) + graph.claim(resource, ES.Condition_IsInverse, resource); + else + graph.deny(resource, ES.Condition_IsInverse, resource); + return resource; + } } public static class PropertyCondition extends Condition { + public PropertyCondition(ReadGraph graph, Resource r) throws DatabaseException { + super(graph, r); + + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + this.propertyName = graph.getRelatedValue(resource, ES.PropertyCondition_HasPropertyName); + this.lowerLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasLowerLimit); + this.upperLimit = graph.getPossibleRelatedValue(resource, ES.PropertyCondition_HasUpperLimit); + } + public PropertyCondition(Resource r, String propertyName, Double lowerLimit, Double upperLimit) { super(r); @@ -633,27 +729,68 @@ public class ElementSelector { public Double upperLimit; @Override - boolean match(ReadGraph graph, Resource r) { + public boolean match(ReadGraph graph, Resource r) { Double value = getPropertyValue(graph, r, propertyName); - return value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit); + boolean result = value != null && (lowerLimit == null || value >= lowerLimit) && (upperLimit == null || value <= upperLimit); + return result ^ isInverse; + } + + @Override + public Resource update(WriteGraph graph) throws DatabaseException { + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + Layer0 L0 = Layer0.getInstance(graph); + + if (resource == null) { + resource = graph.newResource(); + graph.claim(resource, L0.InstanceOf, ES.PropertyCondition); + } + + super.update(graph); + + graph.claimLiteral(resource, ES.PropertyCondition_HasPropertyName, propertyName); + if (lowerLimit != null) + graph.claimLiteral(resource, ES.PropertyCondition_HasLowerLimit, L0.Double, lowerLimit); + else + graph.deny(resource, ES.PropertyCondition_HasLowerLimit); + + if (upperLimit != null) + graph.claimLiteral(resource, ES.PropertyCondition_HasUpperLimit, L0.Double, upperLimit); + else + graph.deny(resource, ES.PropertyCondition_HasUpperLimit); + return resource; } } public static class RegionCondition extends Condition { - public RegionCondition(Resource r, Resource regionResoruce, double[] region) { + public RegionCondition(Resource r, Resource regionResource, double[] region) { super(r); this.region = region; + this.path = createPathForRegion(region); + this.regionResource = regionResource; + } + + public RegionCondition(ReadGraph graph, Resource r) throws DatabaseException { + super(graph, r); + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph); + this.regionResource = graph.getPossibleObject(resource, ES.RegionCondition_HasRegion); + this.region = regionResource != null ? graph.getRelatedValue(regionResource, DR.Region_area) : null; + this.path = createPathForRegion(region); + } + + public static Path2D createPathForRegion(double[] region) { Path2D path = new Path2D.Double(); - double startX = region[0]; - double startY = region[1]; - path.moveTo(startX, startY); - for (int i = 2; i < region.length; i+=2) - path.lineTo(region[i], region[i+1]); - path.closePath(); + if (region != null) { + double startX = region[0]; + double startY = region[1]; + path.moveTo(startX, startY); + for (int i = 2; i < region.length; i+=2) + path.lineTo(region[i], region[i+1]); + path.closePath(); + } - this.path = path; - this.regionResource = regionResoruce; + return path; } public Resource regionResource; @@ -661,12 +798,34 @@ public class ElementSelector { Path2D path; @Override - boolean match(ReadGraph graph, Resource r) throws DatabaseException { + public boolean match(ReadGraph graph, Resource r) throws DatabaseException { double[] transform = graph.getRelatedValue(r, DIA.HasTransform); double x = transform[4]; double y = transform[5]; - return path.contains(x, y); - } + return path.contains(x, y) ^ isInverse; + } + + @Override + public Resource update(WriteGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + DiagramRegionsResource DR = DiagramRegionsResource.getInstance(graph); + + if (resource == null) { + resource = graph.newResource(); + graph.claim(resource, L0.InstanceOf, ES.RegionCondition); + } + + super.update(graph); + + graph.claim(resource, ES.RegionCondition_HasRegion, regionResource); + + // Re-read region data to match DB + this.region = regionResource != null ? graph.getRelatedValue(regionResource, DR.Region_area, Bindings.DOUBLE_ARRAY) : null; + this.path = createPathForRegion(region); + + return resource; + } } public static class RouteCondition extends Condition { @@ -676,12 +835,44 @@ public class ElementSelector { this.routeResource = routeResource; } + public RouteCondition(ReadGraph graph, Resource r) throws DatabaseException { + super(graph, r); + this.routeResource = graph.getPossibleObject(resource, ES.RouteCondition_HasRoute); + this.routePoints = getRoutePoints(graph, routeResource); + } + + public static Set getRoutePoints(ReadGraph graph, Resource routeResource) throws DatabaseException { + return routeResource != null ? + new HashSet<>(ListUtils.toList(graph, routeResource)) : + Collections.emptySet(); + } + public Resource routeResource; Set routePoints; @Override - boolean match(ReadGraph graph, Resource r) throws DatabaseException { - return routePoints.contains(r); + public boolean match(ReadGraph graph, Resource r) throws DatabaseException { + return routePoints.contains(r) ^ isInverse; + } + + @Override + public Resource update(WriteGraph graph) throws DatabaseException { + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + Layer0 L0 = Layer0.getInstance(graph); + + if (resource == null) { + resource = graph.newResource(); + graph.claim(resource, L0.InstanceOf, ES.RouteCondition); + } + + super.update(graph); + + if (routeResource != null) + graph.claim(resource, ES.RouteCondition_HasRoute, routeResource); + + this.routePoints = getRoutePoints(graph, routeResource); + + return resource; } } @@ -694,11 +885,32 @@ public class ElementSelector { this.conditions = conditions; } + public AggregateCondition(ReadGraph graph, Resource r) throws DatabaseException { + super(graph, r); + Collection conditionResources = graph.getObjects(resource, ES.HasSubcondition); + conditions = new ArrayList<>(conditionResources.size()); + for (Resource c : conditionResources) { + conditions.add(buildCondition(graph, c)); + } + if (graph.isInstanceOf(resource, ES.Conjunction)) + this.type = AggregateCondition.Type.CONJUNCTION; + else if (graph.isInstanceOf(resource, ES.Negation)) + this.type = AggregateCondition.Type.NEGATION; + else if (graph.isInstanceOf(resource, ES.Disjunction)) + this.type = AggregateCondition.Type.DISJUNCTION; + else + throw new IllegalArgumentException("Unknown aggreate condition type " + graph.getURI(graph.getSingleType(resource))); + } + public Type type; public List conditions; @Override - boolean match(ReadGraph graph, Resource r) throws DatabaseException { + public boolean match(ReadGraph graph, Resource r) throws DatabaseException { + return doMatch(graph, r) ^ isInverse; + } + + private boolean doMatch(ReadGraph graph, Resource r) throws DatabaseException { switch (type) { case DISJUNCTION: for (Condition c : conditions) @@ -717,6 +929,41 @@ public class ElementSelector { throw new IllegalArgumentException("Unknown aggregate condition type " + type); } } + + @Override + public Resource update(WriteGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + ElementSelectionResource ES = ElementSelectionResource.getInstance(graph); + + if (resource == null) { + resource = graph.newResource(); + } else { + graph.deny(resource, L0.InstanceOf); + graph.deny(resource, ES.HasSubcondition); + } + + Resource type; + switch (this.type) { + case CONJUNCTION: + type = ES.Conjunction; break; + case DISJUNCTION: + type = ES.Disjunction; break; + case NEGATION: + type = ES.Negation; break; + default: + throw new IllegalStateException("Unknown condition type " + this.type); + } + + graph.claim(resource, L0.InstanceOf, type); + + super.update(graph); + + for (Condition c : conditions) { + graph.claim(resource, ES.HasSubcondition, c.update(graph)); + } + + return resource; + } } public static class ElementSelectorQuery extends ResourceRead {