1 package org.simantics.district.selection.ui.parts;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.HashMap;
8 import java.util.HashSet;
12 import java.util.UUID;
13 import java.util.function.Consumer;
15 import javax.inject.Inject;
17 import org.eclipse.core.runtime.IStatus;
18 import org.eclipse.core.runtime.Status;
19 import org.eclipse.jface.dialogs.Dialog;
20 import org.eclipse.jface.dialogs.ErrorDialog;
21 import org.eclipse.jface.layout.GridDataFactory;
22 import org.eclipse.jface.layout.GridLayoutFactory;
23 import org.eclipse.jface.layout.RowDataFactory;
24 import org.eclipse.jface.layout.RowLayoutFactory;
25 import org.eclipse.jface.resource.ImageDescriptor;
26 import org.eclipse.jface.resource.JFaceResources;
27 import org.eclipse.jface.resource.LocalResourceManager;
28 import org.eclipse.jface.resource.ResourceLocator;
29 import org.eclipse.swt.SWT;
30 import org.eclipse.swt.events.SelectionAdapter;
31 import org.eclipse.swt.events.SelectionEvent;
32 import org.eclipse.swt.widgets.Button;
33 import org.eclipse.swt.widgets.Combo;
34 import org.eclipse.swt.widgets.Composite;
35 import org.eclipse.swt.widgets.Control;
36 import org.eclipse.swt.widgets.Label;
37 import org.eclipse.swt.widgets.Shell;
38 import org.eclipse.swt.widgets.Text;
39 import org.eclipse.swt.widgets.Widget;
40 import org.simantics.Simantics;
41 import org.simantics.db.ReadGraph;
42 import org.simantics.db.Resource;
43 import org.simantics.db.WriteGraph;
44 import org.simantics.db.common.request.IndexRoot;
45 import org.simantics.db.common.request.ReadRequest;
46 import org.simantics.db.common.request.WriteRequest;
47 import org.simantics.db.exception.DatabaseException;
48 import org.simantics.db.exception.RuntimeDatabaseException;
49 import org.simantics.db.layer0.QueryIndexUtils;
50 import org.simantics.db.layer0.request.ActiveModels;
51 import org.simantics.db.layer0.request.PropertyInfo;
52 import org.simantics.db.layer0.request.PropertyInfoRequest;
53 import org.simantics.db.layer0.util.Layer0Utils;
54 import org.simantics.db.request.Read;
55 import org.simantics.diagram.stubs.DiagramResource;
56 import org.simantics.district.network.ontology.DistrictNetworkResource;
57 import org.simantics.district.region.ontology.DiagramRegionsResource;
58 import org.simantics.district.route.ontology.RouteResource;
59 import org.simantics.district.selection.ElementSelectionResource;
60 import org.simantics.district.selection.ElementSelectionUtils;
61 import org.simantics.district.selection.ElementSelector;
62 import org.simantics.district.selection.ElementSelector.AggregateCondition;
63 import org.simantics.district.selection.ElementSelector.AggregateCondition.Type;
64 import org.simantics.district.selection.ElementSelector.All;
65 import org.simantics.district.selection.ElementSelector.Condition;
66 import org.simantics.district.selection.ElementSelector.DiagramGenerator;
67 import org.simantics.district.selection.ElementSelector.ExplicitGenerator;
68 import org.simantics.district.selection.ElementSelector.Generator;
69 import org.simantics.district.selection.ElementSelector.ModelGenerator;
70 import org.simantics.district.selection.ElementSelector.PropertyCondition;
71 import org.simantics.district.selection.ElementSelector.PropertySelector;
72 import org.simantics.district.selection.ElementSelector.RegionCondition;
73 import org.simantics.district.selection.ElementSelector.RouteCondition;
74 import org.simantics.district.selection.ElementSelector.Selector;
75 import org.simantics.layer0.Layer0;
76 import org.simantics.layer0.utils.direct.GraphUtils;
77 import org.simantics.modeling.ModelingResources;
78 import org.simantics.structural.stubs.StructuralResource2;
79 import org.simantics.utils.datastructures.Arrays;
80 import org.simantics.utils.datastructures.Pair;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
84 public class EditSelectorDialog extends Dialog {
86 private static final RowLayoutFactory ROW_LAYOUT = RowLayoutFactory.fillDefaults().wrap(false);
87 private static final ImageDescriptor CROSS_IMAGE = ResourceLocator.imageDescriptorFromBundle("com.famfamfam.silk", "icons/cross.png").get();
88 private static final ImageDescriptor PLUS_IMAGE = ResourceLocator.imageDescriptorFromBundle("com.famfamfam.silk", "icons/add.png").get();
90 private static Logger LOGGER = LoggerFactory.getLogger(EditSelectorDialog.class);
92 private ElementSelector elementSelector;
94 // Currently selected elements
95 Collection<Resource> currentSelection;
97 // Data for comboboxes
98 private List<Resource> diagrams;
99 private ArrayList<String> diagramNames;
101 private String[] regionNames;
102 private Resource[] regionResources;
104 private String[] routeNames;
105 private Resource[] routeResources;
107 private List<Resource> componentTypes;
108 private List<String> componentTypeNames;
110 private List<String> propertyNames;
111 private List<String> propertyLabels;
113 private Composite conditionPanel;
116 private int generatorIndex;
117 private Combo sourceField;
120 private Text nameField;
122 private Resource diagram;
123 private Combo diagramField;
125 private int selectorIndex;
126 private Combo selectorField;
128 private Resource componentType;
129 private Combo componentTypeField;
131 private String propertyName;
132 private Combo propertyField;
134 private int numberOfItems;
137 private Condition condition;
139 // Dialog area component
140 private Composite content;
142 private int diagramIndex;
144 private LocalResourceManager resourceManager;
146 // Function type for updating condition objects with optional validation
147 static interface Updater {
148 // If 'validate' is true, a runtime exception may be thrown for invalid values
149 void update(boolean validate);
152 final static Updater NULL_UPDATE = validate -> {};
154 // Called to read values from controls into conditions
155 Updater updater = NULL_UPDATE;
158 public EditSelectorDialog(Shell shell, ElementSelector elementSelector, Collection<Resource> currentSelection) {
161 this.elementSelector = elementSelector;
162 if (elementSelector != null) {
164 Simantics.getSession().sync(new ReadRequest() {
166 public void run(ReadGraph graph) throws DatabaseException {
167 elementSelector.buildSelection(graph);
170 } catch (DatabaseException e1) {
171 LOGGER.error("Failed to read element selector resource " + elementSelector.getResource(), e1);
172 throw new RuntimeDatabaseException(e1);
176 this.currentSelection = currentSelection;
178 Map<Resource, String> diagramMap = ElementSelector.findDiagrams();
179 diagrams = new ArrayList<Resource>(diagramMap.size());
180 diagramNames = new ArrayList<String>(diagramMap.size());
181 diagramMap.entrySet()
183 .sorted(Comparator.comparing(e -> e.getValue()))
184 .forEachOrdered(e -> {
185 diagrams.add(e.getKey());
186 diagramNames.add(e.getValue());
189 final Map<Resource, String> regions = new HashMap<>();
190 final Map<Resource, String> routes = new HashMap<>();
193 Simantics.getSession().syncRequest(new Read<Void>() {
195 public Void perform(ReadGraph graph) throws DatabaseException {
196 Resource model = ActiveModels.getPossibleActiveModel(graph, Simantics.getProjectResource());
197 List<Resource> regionCollection = QueryIndexUtils.searchByType(graph, model, DiagramRegionsResource.getInstance(graph).Region);
198 for (Resource r : regionCollection) {
199 String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName);
200 regions.put(r, name);
203 List<Resource> routeCollection = QueryIndexUtils.searchByType(graph, model, RouteResource.getInstance(graph).Route);
204 for (Resource r : routeCollection) {
205 String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName);
211 } catch (DatabaseException e) {
212 LOGGER.error("Failed to read routes and/or regions in the model", e);
215 regionNames = regions.values().toArray(new String[regions.size()]);
216 regionResources = regions.keySet().toArray(new Resource[regions.size()]);
218 routeNames = routes.values().toArray(new String[routes.size()]);
219 routeResources = routes.keySet().toArray(new Resource[routes.size()]);
222 Simantics.getSession().syncRequest(new ReadRequest() {
224 public void run(ReadGraph graph) throws DatabaseException {
225 Layer0 L0 = Layer0.getInstance(graph);
226 List<Resource> types = findComponentTypes(graph);
228 componentTypes = new ArrayList<>(types.size() + 1);
229 componentTypeNames = new ArrayList<>(types.size() + 1);
231 componentTypes.add(null);
232 componentTypeNames.add("Any type");
233 componentTypes.addAll(types);
234 for (Resource t : types) {
235 componentTypeNames.add(graph.getValue2(t, L0.HasName));
239 } catch (DatabaseException e) {
240 LOGGER.error("Failed to read district component types", e);
243 componentType = elementSelector.getSelector().componentType;
245 propertyNames = new ArrayList<>();
246 propertyLabels = new ArrayList<>();
249 updatePropertyList();
250 } catch (DatabaseException e) {
251 LOGGER.error("Failed to read district component properties", e);
254 name = elementSelector != null ? elementSelector.getName() : "";
262 if (elementSelector != null) {
263 Generator generator = elementSelector.getGenerator();
264 if (generator instanceof ModelGenerator) {
267 else if (generator instanceof DiagramGenerator) {
269 diagram = ((DiagramGenerator)generator).diagram;
271 else if (generator instanceof ExplicitGenerator) {
275 throw new IllegalStateException("Unknown generator type " + generator.getClass().getName());
278 Selector selector = elementSelector.getSelector();
279 if (selector instanceof All) {
282 else if (selector instanceof PropertySelector) {
283 PropertySelector propertySelector = (PropertySelector)selector;
284 selectorIndex = propertySelector.smallest ? 1 : 2;
285 propertyName = propertySelector.propertyName;
286 numberOfItems = propertySelector.resultCount;
289 throw new IllegalStateException("Unknwon selector type " + selector.getClass().getName());
292 condition = elementSelector.getCondition();
296 private void updateDialog() {
297 updater.update(false);
298 updater = updateConditionPanel();
300 content.layout(true, true);
305 protected void okPressed() {
306 generatorIndex = sourceField.getSelectionIndex();
307 if (generatorIndex == 1) {
308 int selectionIndex = diagramField.getSelectionIndex();
309 if (selectionIndex < 0) {
310 ErrorDialog.openError(getShell(), "Error", "Please select a diagram", new Status(IStatus.ERROR, "org.simantics.district.selection.ui", "No diagram selected"));
314 diagram = diagrams.get(selectionIndex);
317 name = nameField.getText();
318 componentType = componentTypes.get(componentTypeField.getSelectionIndex());
319 selectorIndex = selectorField.getSelectionIndex();
320 int propertyIndex = propertyField.getSelectionIndex();
321 propertyName = propertyIndex >= 0 ? propertyNames.get(propertyIndex) : propertyField.getText();
323 // Try to parse number of items
324 if (useNumberOfItems()) {
326 numberOfItems = Integer.parseInt(nField.getText());
327 } catch (RuntimeException e) {
334 // To to update condition definitions
336 updater.update(true);
337 } catch (RuntimeException e) {
344 public void writeSelection() throws DatabaseException {
345 Simantics.getSession().syncRequest(new WriteRequest() {
347 public void perform(WriteGraph graph) throws DatabaseException {
348 Layer0 L0 = Layer0.getInstance(graph);
349 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
350 ModelingResources MOD = ModelingResources.getInstance(graph);
351 DiagramResource DIA = DiagramResource.getInstance(graph);
353 graph.markUndoPoint();
354 Layer0Utils.addCommentMetadata(graph, "Created new element selection");
356 Resource lib = ElementSelectionUtils.ensureSelectionLibrary(graph);
360 if (elementSelector != null) {
361 selection = elementSelector.getResource();
362 graph.deny(selection);
365 selection = graph.newResource();
368 graph.claim(selection, L0.InstanceOf, ES.Selection);
369 graph.claimLiteral(selection, L0.HasName, L0.String, UUID.randomUUID().toString());
370 graph.claimLiteral(selection, L0.HasLabel, L0.String, name);
371 graph.claim(selection, L0.PartOf, lib);
374 Resource generator = graph.newResource();
375 Resource generatorType;
376 switch (generatorIndex) {
378 generatorType = ES.Generator_Model;
381 generatorType = ES.Generator_Diagram;
382 Resource composite = graph.getPossibleObject(diagram, MOD.DiagramToComposite);
383 graph.claim(generator, ES.Generator_HasDiagram, composite != null ? composite : diagram);
386 generatorType = ES.Generator_Explicit;
387 for (Resource r : currentSelection) {
389 if (graph.isInstanceOf(r, DIA.Connection))
391 if (!graph.isInstanceOf(r, DIA.Element)) {
392 if (!graph.hasStatement(r, MOD.ComponentToElement))
395 r = graph.getPossibleObject(r, MOD.ComponentToElement);
400 graph.claim(generator, ES.Generator_HasSelectedElement, r);
403 default: throw new IllegalStateException("Invalid source index " + generatorIndex);
405 graph.claim(generator, L0.InstanceOf, generatorType);
406 graph.claim(selection, ES.Selection_HasGenerator, generator);
409 Resource selector = graph.newResource();
410 Resource selectorType;
411 switch (selectorIndex) {
412 case 0: selectorType = ES.Selector_All; break;
413 case 1: selectorType = ES.Selector_NLowest; break;
414 case 2: selectorType = ES.Selector_NHighest; break;
415 default: throw new IllegalStateException("Invalid selector index " + selectorIndex);
417 graph.claim(selector, L0.InstanceOf, selectorType);
418 graph.claim(selection, ES.Selection_HasSelector, selector);
419 graph.deny(selector, ES.Selector_HasMapping);
420 if (componentType != null)
421 graph.claim(selector, ES.Selector_HasMapping, componentType);
423 if (selectorIndex > 0) {
424 graph.claimLiteral(selector, ES.PropertySelector_HasSelectionPropertyName, L0.String, propertyName);
425 graph.claimLiteral(selector, ES.PropertySelector_HasResultCount, L0.Integer, numberOfItems);
429 if (condition != null) {
430 Resource conditionResource = condition.update(graph);
431 graph.claim(selection, ES.Selection_HasCondition, conditionResource);
437 private boolean isDiagramFieldVisible() {
438 return generatorIndex == 1;
441 private boolean useNumberOfItems() {
442 return selectorIndex != 0;
446 protected Control createDialogArea(Composite parent) {
447 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
450 getShell().setText("Edit element selector");
452 content = new Composite(parent, SWT.NONE);
453 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(content);
456 Label nameLabel = new Label(content, SWT.NONE);
457 nameLabel.setText("Name");
458 GridDataFactory.swtDefaults().applyTo(nameLabel);
460 nameField = new Text(content, SWT.BORDER);
461 nameField.setEditable(true);
462 nameField.setText(name);
463 GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT).applyTo(nameField);
466 Label selectorLabel = new Label(content, SWT.NONE);
467 selectorLabel.setText("Select");
468 GridDataFactory.swtDefaults().applyTo(selectorLabel);
470 Composite selectorComposite = new Composite(content, SWT.NONE);
471 GridDataFactory.swtDefaults().applyTo(selectorComposite);
472 RowLayoutFactory.swtDefaults().applyTo(selectorComposite);
474 nField = new Text(selectorComposite, SWT.BORDER);
475 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(nField);
476 if (useNumberOfItems())
477 nField.setText(Integer.toString(numberOfItems));
478 nField.setEnabled(useNumberOfItems());
480 componentTypeField = new Combo(selectorComposite, SWT.READ_ONLY);
481 RowDataFactory.swtDefaults().applyTo(componentTypeField);
482 componentTypeField.setItems(componentTypeNames.toArray(new String[] {}));
484 int index = componentTypes.indexOf(componentType);
485 componentTypeField.select(index >= 0 ? index : 0);
488 new Label(selectorComposite, SWT.NONE).setText("with");
490 selectorField = new Combo(selectorComposite, SWT.BORDER | SWT.READ_ONLY);
491 selectorField.setItems("All", "Lowest", "Highest");
492 selectorField.select(selectorIndex);
493 RowDataFactory.swtDefaults().hint(40, SWT.DEFAULT).applyTo(selectorField);
495 propertyField = new Combo(selectorComposite, SWT.NONE);
496 RowDataFactory.swtDefaults().hint(80, SWT.DEFAULT).applyTo(propertyField);
497 propertyField.setItems(propertyLabels.toArray(new String[] {}));
499 int index = propertyName != null ? propertyNames.indexOf(propertyName) : -1;
501 propertyField.select(index);
503 propertyField.setText(propertyName != null ? propertyName : "");
505 propertyField.setEnabled(useNumberOfItems());
507 selectorField.addSelectionListener(new SelectionAdapter() {
509 public void widgetSelected(SelectionEvent e) {
510 selectorIndex = selectorField.getSelectionIndex();
512 boolean enable = useNumberOfItems();
513 nField.setEnabled(enable);
514 propertyField.setEnabled(enable);
516 nField.setText(enable ? Integer.toString(numberOfItems) : "");
521 Label sourceLabel = new Label(content, SWT.NONE);
522 sourceLabel.setText("from");
523 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(sourceLabel);
525 Composite sourceComposite = new Composite(content, SWT.NONE);
526 GridDataFactory.swtDefaults().applyTo(sourceComposite);
527 RowLayoutFactory.swtDefaults().applyTo(sourceComposite);
529 sourceField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY);
530 RowDataFactory.swtDefaults().applyTo(sourceField);
531 sourceField.setItems("Whole model", "Diagram", "Current selection");
532 sourceField.select(generatorIndex);
534 diagramField = new Combo(sourceComposite, SWT.BORDER | SWT.READ_ONLY);
535 RowDataFactory.swtDefaults().applyTo(diagramField);
536 diagramField.setItems(diagramNames.toArray(new String[diagramNames.size()]));
537 diagramField.setEnabled(isDiagramFieldVisible());
539 diagramIndex = diagram != null ? diagrams.indexOf(diagram) : -1;
540 diagramField.select(diagramIndex);
542 sourceField.addSelectionListener(new SelectionAdapter() {
544 public void widgetSelected(SelectionEvent e) {
545 generatorIndex = sourceField.getSelectionIndex();
546 boolean enabled = isDiagramFieldVisible();
548 diagramIndex = diagramField.getSelectionIndex();
549 diagramField.clearSelection();
551 if (diagramIndex >= 0)
552 diagramField.select(diagramIndex);
554 diagramField.clearSelection();
556 diagramField.setEnabled(enabled);
560 sourceField.select(generatorIndex);
563 Label label = new Label(content, SWT.NONE);
564 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.CENTER).applyTo(label);
565 label.setText("where");
567 conditionPanel = new Composite(content, SWT.NONE);
568 GridDataFactory.swtDefaults().span(1, 2).minSize(400, SWT.DEFAULT).grab(true, false).applyTo(conditionPanel);
569 GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionPanel);
571 updater = updateConditionPanel();
576 private Updater updateConditionPanel() {
578 for (Widget c : conditionPanel.getChildren())
581 return createConditionPanelFor(conditionPanel, condition, cond -> condition = cond);
584 private Updater createConditionPanelFor(final Composite parent, final Condition condition, final Consumer<Condition> consumer) {
585 // Create new contents
586 Button notCheck = new Button(parent, SWT.CHECK);
587 GridDataFactory.swtDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(notCheck);
588 notCheck.setText("not");
589 notCheck.setSelection(condition.isInverse);
591 Composite conditionComp = new Composite(parent, SWT.NONE);
592 GridDataFactory.fillDefaults().applyTo(conditionComp);
594 Combo typeCombo = new Combo(conditionComp, SWT.BORDER | SWT.READ_ONLY);
604 final Updater updater;
605 if (condition instanceof PropertyCondition) {
607 updater = createPropertyConditionPanel(conditionComp, (PropertyCondition)condition);
608 } else if (condition instanceof RegionCondition) {
610 updater = createRegionConditionPanel(conditionComp, (RegionCondition)condition);
611 } else if (condition instanceof RouteCondition) {
613 updater = createRouteConditionPanel(conditionComp, (RouteCondition)condition);
614 } else if (condition instanceof AggregateCondition) {
615 AggregateCondition cond = (AggregateCondition) condition;
616 typeCombo.select(cond.type.equals(Type.CONJUNCTION) ? 4 : 5);
617 updater = createAggregateConditionPanel(conditionComp, cond);
619 ROW_LAYOUT.applyTo(conditionComp);
620 notCheck.setEnabled(false);
622 updater = validate -> {};
625 typeCombo.addSelectionListener(new ConditionTypeSelectionListener(typeCombo, consumer, condition));
628 updater.update(validate);
629 condition.isInverse = notCheck.getSelection();
633 private final class ConditionTypeSelectionListener extends SelectionAdapter {
634 private final Combo typeCombo;
635 private final Consumer<Condition> consumer;
636 private final Condition finalCondition;
638 private ConditionTypeSelectionListener(Combo typeCombo, Consumer<Condition> consumer, Condition finalCondition) {
639 this.typeCombo = typeCombo;
640 this.consumer = consumer;
641 this.finalCondition = finalCondition;
645 public void widgetSelected(SelectionEvent e) {
646 int index = typeCombo.getSelectionIndex();
647 Condition newCondition = finalCondition;
653 newCondition = createPropertyCondition("", null, null);
656 newCondition = createRegionCondition(null);
659 newCondition = createRouteCondition(null);
662 if (newCondition instanceof AggregateCondition)
663 ((AggregateCondition)newCondition).type = Type.CONJUNCTION;
665 newCondition = createAggregateCondition(null, new ArrayList<>(), true, false);
668 if (newCondition instanceof AggregateCondition)
669 ((AggregateCondition)newCondition).type = Type.DISJUNCTION;
671 newCondition = createAggregateCondition(null, new ArrayList<>(), false, false);
675 consumer.accept(newCondition);
681 private Updater createAggregateConditionPanel(Composite conditionComp, AggregateCondition cond) {
682 GridLayoutFactory.fillDefaults().numColumns(2).applyTo(conditionComp);
683 new Label(conditionComp, SWT.NONE); // Eat extra column
685 int n = cond.conditions.size();
686 final Updater[] updates = new Updater[n];
687 for (int i = 0; i < n; i++) {
688 updates[i] = createConditionRowPanel(conditionComp, cond, i);
691 Button addButton = new Button(conditionComp, SWT.PUSH);
692 GridDataFactory.swtDefaults().applyTo(addButton);
693 addButton.setImage(resourceManager.createImage(PLUS_IMAGE));
695 addButton.addSelectionListener(new SelectionAdapter() {
697 public void widgetSelected(SelectionEvent e) {
698 cond.conditions.add(createPropertyCondition("property", null, null));
704 for (Updater updater : updates)
705 updater.update(validate);
709 private Updater createConditionRowPanel(Composite parent, AggregateCondition parentCondition, final int i) {
710 GridLayoutFactory conditionLayout = GridLayoutFactory.fillDefaults().numColumns(2);
711 GridDataFactory swtDefaults = GridDataFactory.swtDefaults();
713 Condition c = parentCondition.conditions.get(i);
715 Composite row = new Composite(parent, SWT.NONE);
716 conditionLayout.applyTo(row);
717 swtDefaults.applyTo(row);
719 Consumer<Condition> update = cd -> {
721 parentCondition.conditions.set(i, cd);
723 parentCondition.conditions.remove(i);
726 Updater updater = createConditionPanelFor(row, c, update);
728 Button removeButton = new Button(parent, SWT.PUSH);
729 swtDefaults.align(SWT.BEGINNING, SWT.BEGINNING).applyTo(removeButton);
730 removeButton.setImage(resourceManager.createImage(CROSS_IMAGE));
732 removeButton.addSelectionListener(new SelectionAdapter() {
734 public void widgetSelected(SelectionEvent e) {
735 parentCondition.conditions.remove(i);
743 private Updater createRouteConditionPanel(Composite conditionComp, RouteCondition condition) {
744 ROW_LAYOUT.applyTo(conditionComp);
747 Combo routeCombo = new Combo(conditionComp, SWT.READ_ONLY);
748 RowDataFactory.swtDefaults().applyTo(routeCombo);
749 routeCombo.setItems(routeNames);
751 // Set current selection
752 int index = Arrays.indexOf(routeResources, condition.routeResource);
754 routeCombo.select(index);
758 int i = routeCombo.getSelectionIndex();
759 condition.routeResource = i >= 0 ? routeResources[i] : null;
763 private Updater createRegionConditionPanel(Composite conditionComp, RegionCondition condition) {
764 ROW_LAYOUT.applyTo(conditionComp);
767 Combo regionCombo = new Combo(conditionComp, SWT.READ_ONLY);
768 RowDataFactory.swtDefaults().applyTo(regionCombo);
769 regionCombo.setItems(regionNames);
771 // Set current selection
772 int index = Arrays.indexOf(regionResources, condition.regionResource);
774 regionCombo.select(index);
778 int i = regionCombo.getSelectionIndex();
779 condition.regionResource = i >= 0 ? regionResources[i] : null;
783 private Updater createPropertyConditionPanel(Composite conditionComp, PropertyCondition condition) {
784 ROW_LAYOUT.applyTo(conditionComp);
786 Text lowerLimitText = new Text(conditionComp, SWT.BORDER);
787 RowDataFactory.swtDefaults().applyTo(lowerLimitText);
788 lowerLimitText.setText(condition.lowerLimit != null ? Double.toString(condition.lowerLimit) : "");
790 new Label(conditionComp, SWT.NONE).setText("\u2264");
792 Combo propertyNameText = new Combo(conditionComp, SWT.NONE);
793 RowDataFactory.swtDefaults().applyTo(propertyNameText);
794 propertyNameText.setItems(propertyLabels.toArray(new String[] {}));
795 int index = propertyNames.indexOf(condition.propertyName);
797 propertyNameText.select(index);
799 propertyNameText.setText(condition.propertyName);
801 new Label(conditionComp, SWT.NONE).setText("\u2264");
803 Text upperLimitText = new Text(conditionComp, SWT.BORDER);
804 RowDataFactory.swtDefaults().applyTo(upperLimitText);
805 upperLimitText.setText(condition.upperLimit != null ? Double.toString(condition.upperLimit) : "");
810 String text = lowerLimitText.getText();
811 condition.lowerLimit = text.isEmpty() ? null : Double.parseDouble(text);
812 } catch (NumberFormatException e) {
814 lowerLimitText.selectAll();
815 lowerLimitText.forceFocus();
821 String text = upperLimitText.getText();
822 condition.upperLimit = text.isEmpty() ? null : Double.parseDouble(text);
823 } catch (NumberFormatException e) {
825 upperLimitText.selectAll();
826 upperLimitText.forceFocus();
831 int ind = propertyNameText.getSelectionIndex();
834 name = propertyNames.get(ind);
836 name = propertyNameText.getText();
838 if (validate && name.isEmpty()) {
839 propertyNameText.forceFocus();
840 throw new RuntimeException();
842 condition.propertyName = name;
847 private static Condition createPropertyCondition(String propertyName, Double lowerLimit, Double upperLimit) {
848 return new PropertyCondition(null, propertyName, lowerLimit, upperLimit);
851 private static Condition createRegionCondition(Resource regionResource) {
852 return new RegionCondition(null, regionResource, null);
855 private static Condition createRouteCondition(Resource route) {
856 return new RouteCondition(null, route, null);
859 private static Condition createAggregateCondition(Resource existingResource, List<Condition> subConditions, boolean isConjunction, boolean isInverse) {
860 Type type = isConjunction ? Type.CONJUNCTION : Type.DISJUNCTION;
861 AggregateCondition condition = new AggregateCondition(null, type, subConditions);
862 condition.isInverse = isInverse;
867 class ConditionDialog extends Dialog {
868 // Resource of the edited condition
869 private Resource existingResource;
871 // Inverse condition button
872 private boolean isInverse;
873 private Button inverseField;
876 private int typeIndex;
877 private Combo typeField;
879 // Type-specific control panels under a stack layout
880 private Composite stackPanel;
881 private StackLayout stack;
883 private Composite propertyPanel;
884 private Composite regionPanel;
885 private Composite routePanel;
886 private Composite aggregatePanel;
888 // Property condition
889 private Double lowerLimit;
890 private Double upperLimit;
891 private String propertyName;
893 private Text propertyNameField;
894 private Text lowerLimitField;
895 private Text upperLimitField;
898 private Resource region;
899 private Combo regionField;
902 private Resource route;
903 private Combo routeField;
905 // Aggregate condition
906 private List<Condition> subConditions;
907 private boolean isConjunction;
909 private org.eclipse.swt.widgets.List subConditionField;
910 private Combo operatorField;
912 public ConditionDialog(Shell shell, Condition condition) {
920 subConditions = new ArrayList<>();
922 existingResource = condition != null ? condition.resource : null;
924 if (condition != null) {
925 if (condition instanceof PropertyCondition) {
927 PropertyCondition propertyCondition = (PropertyCondition)condition;
928 propertyName = propertyCondition.propertyName;
929 upperLimit = propertyCondition.upperLimit;
930 lowerLimit = propertyCondition.lowerLimit;
932 else if (condition instanceof RegionCondition) {
934 region = ((RegionCondition)condition).regionResource;
936 else if (condition instanceof RouteCondition) {
938 route = ((RouteCondition)condition).routeResource;
940 else if (condition instanceof AggregateCondition) {
942 subConditions = new ArrayList<>(((AggregateCondition)condition).conditions);
943 isConjunction = ((AggregateCondition)condition).type == Type.CONJUNCTION;
944 isInverse = ((AggregateCondition)condition).type == Type.NEGATION;
950 protected Control createDialogArea(Composite parent) {
951 getShell().setText("Edit selector condition");
953 Composite content = (Composite)super.createDialogArea(parent);
954 GridLayoutFactory.swtDefaults().numColumns(1).applyTo(content);
956 GridDataFactory defaultWidth = GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT);
959 inverseField = new Button(content, SWT.CHECK);
960 inverseField.setText("Is inverse");
961 inverseField.setSelection(isInverse);
964 typeField = new Combo(content, SWT.BORDER | SWT.READ_ONLY);
965 typeField.setItems("Property value", "In region", "On route", "Combination");
966 typeField.select(typeIndex);
968 // Type-dependent stacked panels
969 stackPanel = new Composite(content, SWT.NONE);
970 stack = new StackLayout();
971 stackPanel.setLayout(stack);
973 // Property condition panel
974 propertyPanel = new Composite(stackPanel, SWT.NONE);
975 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(propertyPanel);
977 new Label(propertyPanel, SWT.NONE).setText("Property name");
978 propertyNameField = new Text(propertyPanel, SWT.BORDER);
979 propertyNameField.setText(propertyName);
980 defaultWidth.applyTo(propertyNameField);
982 new Label(propertyPanel, SWT.NONE).setText("Lower limit");
983 lowerLimitField = new Text(propertyPanel, SWT.BORDER);
984 defaultWidth.applyTo(lowerLimitField);
985 if (lowerLimit != null) lowerLimitField.setText(lowerLimit.toString());
987 new Label(propertyPanel, SWT.NONE).setText("Upper limit");
988 upperLimitField = new Text(propertyPanel, SWT.BORDER);
989 defaultWidth.applyTo(upperLimitField);
990 if (upperLimit != null) upperLimitField.setText(upperLimit.toString());
992 // Region condition panel
993 regionPanel = new Composite(stackPanel, SWT.NONE);
994 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(regionPanel);
996 new Label(regionPanel, SWT.NONE).setText("Region");
997 regionField = new Combo(regionPanel, SWT.BORDER | SWT.READ_ONLY);
998 regionField.setItems(regionNames);
999 defaultWidth.applyTo(regionField);
1001 if (region != null) {
1002 int regionIndex = Arrays.indexOf(regionResources, region);
1003 regionField.select(regionIndex);
1006 regionField.select(0);
1009 // Route condition panel
1010 routePanel = new Composite(stackPanel, SWT.NONE);
1011 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(routePanel);
1013 new Label(routePanel, SWT.NONE).setText("Route");
1014 routeField = new Combo(routePanel, SWT.BORDER | SWT.READ_ONLY);
1015 routeField.setItems(routeNames);
1016 defaultWidth.applyTo(routeField);
1018 if (route != null) {
1019 int routeIndex = Arrays.indexOf(routeResources, route);
1020 routeField.select(routeIndex);
1023 routeField.select(0);
1026 // Aggregate condition panel
1027 aggregatePanel = new Composite(stackPanel, SWT.NONE);
1028 GridLayoutFactory.swtDefaults().numColumns(2).applyTo(aggregatePanel);
1030 new Label(aggregatePanel, SWT.NONE).setText("Operator");
1031 operatorField = new Combo(aggregatePanel, SWT.READ_ONLY);
1032 operatorField.setItems("And", "Or");
1033 operatorField.select(isConjunction ? 0 : 1);
1035 new Label(aggregatePanel, SWT.NONE).setText("Sub-conditions");
1036 Composite buttons = new Composite(aggregatePanel, SWT.NONE);
1037 RowLayoutFactory.swtDefaults().justify(true).fill(true).extendedMargins(0, 0, 0, 0).type(SWT.HORIZONTAL).applyTo(buttons);
1039 Button addButton = new Button(buttons, SWT.PUSH);
1040 addButton.setText("Add");
1041 Button removeButton = new Button(buttons, SWT.PUSH);
1042 removeButton.setText("Remove");
1043 Button editButton = new Button(buttons, SWT.PUSH);
1044 editButton.setText("Edit");
1046 new Label(aggregatePanel, SWT.NONE);
1047 subConditionField = new org.eclipse.swt.widgets.List(aggregatePanel, SWT.BORDER);
1048 GridDataFactory.swtDefaults().hint(200, 150).applyTo(subConditionField);
1049 if (subConditions != null) {
1050 Session session = Simantics.getSession();
1051 List<String> items = new ArrayList<>();
1052 for (Condition c : subConditions) {
1054 items.add(ElementSelector.getExpression(session, c.resource));
1055 } catch (DatabaseException e1) {
1056 LOGGER.error("Condition expression read failed", e1);
1057 items.add("<Unknown expression>");
1061 subConditionField.setItems(items.toArray(new String[items.size()]));
1064 addButton.addSelectionListener(new SelectionAdapter() {
1066 public void widgetSelected(SelectionEvent e) {
1067 ConditionDialog conditionDialog = new ConditionDialog(getShell(), null);
1068 if (conditionDialog.open() == Window.OK) {
1069 Condition condition;
1071 condition = conditionDialog.createCondition();
1072 subConditions.add(condition);
1075 subConditionField.add(ElementSelector.getExpression(Simantics.getSession(), condition.resource));
1076 } catch (DatabaseException e1) {
1077 LOGGER.error("Condition expression read failed", e1);
1078 subConditionField.add("<Unknown expression>");
1080 } catch (DatabaseException e2) {
1081 LOGGER.error("Create condition failed", e2);
1087 removeButton.addSelectionListener(new SelectionAdapter() {
1089 public void widgetSelected(SelectionEvent e) {
1090 int index = subConditionField.getSelectionIndex();
1092 subConditionField.deselectAll();
1093 subConditionField.remove(index);
1094 subConditions.remove(index);
1096 if (index < subConditions.size())
1097 subConditionField.setSelection(index);
1100 boolean selected = subConditionField.getSelectionIndex() >= 0;
1101 removeButton.setEnabled(selected);
1102 editButton.setEnabled(selected);
1106 editButton.addSelectionListener(new SelectionAdapter() {
1108 public void widgetSelected(SelectionEvent e) {
1109 int index = subConditionField.getSelectionIndex();
1111 Condition condition = subConditions.get(index);
1112 ConditionDialog conditionDialog = new ConditionDialog(getShell(), condition);
1113 if (conditionDialog.open() == Window.OK) {
1115 condition = conditionDialog.createCondition();
1116 subConditions.set(index, condition);
1119 subConditionField.setItem(index, ElementSelector.getExpression(Simantics.getSession(), condition.resource));
1120 } catch (DatabaseException e1) {
1121 LOGGER.error("Condition expression read failed", e1);
1122 subConditionField.setItem(index, "<Unknown expression>");
1124 } catch (DatabaseException e2) {
1125 LOGGER.error("Create condition failed", e2);
1132 subConditionField.addSelectionListener(new SelectionAdapter() {
1134 public void widgetSelected(SelectionEvent e) {
1135 boolean selected = subConditionField.getSelectionIndex() >= 0;
1136 removeButton.setEnabled(selected);
1137 editButton.setEnabled(selected);
1141 // Stack layout update
1142 typeField.addSelectionListener(new SelectionAdapter() {
1144 public void widgetSelected(SelectionEvent e) {
1155 protected void okPressed() {
1156 isInverse = inverseField.getSelection();
1158 switch (typeIndex) {
1159 case 0: // Property condition
1160 propertyName = propertyNameField.getText();
1162 String lowerLimitText = lowerLimitField.getText();
1163 lowerLimit = lowerLimitText.equals("") ? null : Double.valueOf(lowerLimitText);
1164 String upperLimitText = upperLimitField.getText();
1165 upperLimit = upperLimitText.equals("") ? null : Double.valueOf(upperLimitText);
1167 catch (NumberFormatException e) {
1168 ErrorDialog.openError(getShell(), "Error", "Invalid numeric value: " + e.getMessage(), new Status(OK, "org.simantics.district.selection.ui", e.getMessage()));
1172 case 1: { // Region condition
1173 int selectionIndex = regionField.getSelectionIndex();
1174 if (selectionIndex < 0) {
1175 ErrorDialog.openError(getShell(), "Error", "Please select a region", new Status(OK, "org.simantics.district.selection.ui", "No region selection"));
1178 region = regionResources[selectionIndex];
1181 case 2: // Route condition
1182 route = routeResources[routeField.getSelectionIndex()];
1184 case 3: // Aggregate condition
1185 isConjunction = operatorField.getSelectionIndex() == 0;
1192 protected Condition createCondition() throws DatabaseException {
1193 if (isInverse && !(typeIndex == 3 && !isConjunction)) {
1194 Resource resource0 = createCondition0(typeIndex);
1196 // Create a negation
1197 Resource resource = Simantics.getSession().syncRequest(new WriteResult<Resource>() {
1199 public Resource perform(WriteGraph graph) throws DatabaseException {
1200 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
1201 Layer0 L0 = Layer0.getInstance(graph);
1203 Resource r = graph.newResource();
1204 graph.claim(r, L0.InstanceOf, ES.Negation);
1205 graph.claim(r, ES.HasSubcondition, resource0);
1210 return ElementSelector.getCondition(Simantics.getSession(), resource);
1213 return ElementSelector.getCondition(Simantics.getSession(), createCondition0(typeIndex));
1217 private Resource createCondition0(int index) throws DatabaseException {
1219 case 0: return createPropertyCondition();
1220 case 1: return createRegionCondition();
1221 case 2: return createRouteCondition();
1222 case 3: return createAggregateCondition();
1223 default: throw new IllegalStateException("Invalid condition type code " + index);
1227 private Resource createPropertyCondition() throws DatabaseException {
1228 return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
1230 public Resource perform(WriteGraph graph) throws DatabaseException {
1231 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
1232 Layer0 L0 = Layer0.getInstance(graph);
1234 Resource r = graph.newResource();
1235 graph.claim(r, L0.InstanceOf, ES.PropertyCondition);
1236 graph.claimLiteral(r, ES.PropertyCondition_HasPropertyName, propertyName);
1237 if (lowerLimit != null)
1238 graph.claimLiteral(r, ES.PropertyCondition_HasLowerLimit, L0.Double, lowerLimit);
1239 if (upperLimit != null)
1240 graph.claimLiteral(r, ES.PropertyCondition_HasUpperLimit, L0.Double, upperLimit);
1247 private Resource createAggregateCondition() throws DatabaseException {
1248 return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
1250 public Resource perform(WriteGraph graph) throws DatabaseException {
1251 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
1252 Layer0 L0 = Layer0.getInstance(graph);
1255 if (existingResource != null) {
1256 // Reuse existing resource
1257 r = existingResource;
1258 // Clear any previous statements
1259 graph.deny(existingResource);
1262 r = graph.newResource();
1266 type = isConjunction ? ES.Conjunction : isInverse ? ES.Negation : ES.Disjunction;
1268 graph.claim(r, L0.InstanceOf, type);
1269 for (Condition c : subConditions) {
1270 graph.claim(r, ES.HasSubcondition, c.resource);
1278 private Resource createRouteCondition() throws DatabaseException {
1279 return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
1281 public Resource perform(WriteGraph graph) throws DatabaseException {
1282 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
1283 Layer0 L0 = Layer0.getInstance(graph);
1285 Resource r = graph.newResource();
1286 graph.claim(r, L0.InstanceOf, ES.RouteCondition);
1287 graph.claim(r, ES.RouteCondition_HasRoute, route);
1293 private Resource createRegionCondition() throws DatabaseException {
1294 return Simantics.getSession().syncRequest(new WriteResult<Resource>() {
1296 public Resource perform(WriteGraph graph) throws DatabaseException {
1297 ElementSelectionResource ES = ElementSelectionResource.getInstance(graph);
1298 Layer0 L0 = Layer0.getInstance(graph);
1300 Resource r = graph.newResource();
1301 graph.claim(r, L0.InstanceOf, ES.RegionCondition);
1302 graph.claim(r, ES.RegionCondition_HasRegion, region);
1308 private void updateStack() {
1309 typeIndex = typeField.getSelectionIndex();
1310 switch (typeIndex) {
1311 case 0: stack.topControl = propertyPanel; break;
1312 case 1: stack.topControl = regionPanel; break;
1313 case 2: stack.topControl = routePanel; break;
1314 case 3: stack.topControl = aggregatePanel; break;
1317 stackPanel.layout();
1322 static List<Resource> findComponentTypes(ReadGraph graph) throws DatabaseException {
1323 DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph);
1324 Resource project = Simantics.getProjectResource();
1325 Resource model = ActiveModels.getPossibleActiveModel(graph, project);
1327 return QueryIndexUtils.searchByType(graph, model, DN.Mapping_Base);
1330 void updatePropertyList() throws DatabaseException {
1331 Collection<Resource> types = componentType != null ? Collections.singleton(componentType) : componentTypes;
1332 Set<Pair<String, String>> properties = new HashSet<>();
1334 Simantics.getSession().syncRequest(new ReadRequest() {
1336 public void run(ReadGraph graph) throws DatabaseException {
1337 Layer0 L0 = Layer0.getInstance(graph);
1339 for (Resource type : types) {
1343 Resource ct = graph.getPossibleObject(type, DistrictNetworkResource.getInstance(graph).Mapping_ComponentType);
1347 if (graph.isInstanceOf(ct, L0.String)) {
1348 Resource indexRoot = graph.syncRequest(new IndexRoot(type));
1349 String name = graph.getValue(ct);
1350 ct = GraphUtils.getPossibleChild(graph, indexRoot, name);
1355 for (Resource prop : graph.getObjects(ct, L0.DomainOf)) {
1356 if (!graph.isInstanceOf(prop, StructuralResource2.getInstance(graph).Property))
1359 // Filter only numeric properties
1360 PropertyInfo info = graph.syncRequest(new PropertyInfoRequest(prop));
1361 if (info != null && info.requiredValueType != null && !isNumericValueType(info.requiredValueType))
1364 String name = graph.getRelatedValue2(prop, L0.HasName);
1365 String label = graph.getPossibleRelatedValue2(prop, L0.HasLabel);
1366 if (label == null) label = name;
1368 properties.add(Pair.make(label, name));
1374 propertyNames.clear();
1375 propertyLabels.clear();
1376 properties.stream().sorted(Comparator.comparing(p -> p.first)).forEachOrdered(p -> {
1377 propertyLabels.add(p.first);
1378 propertyNames.add(p.second);
1382 static boolean isNumericValueType(String requiredValueType) {
1383 switch (requiredValueType) {