diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/DisplayEditorInstance.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/DisplayEditorInstance.java index c27afe5939..4d368df3a8 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/DisplayEditorInstance.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/app/DisplayEditorInstance.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017-2020 Oak Ridge National Laboratory. + * Copyright (c) 2017-2022 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -22,6 +22,7 @@ import org.csstudio.display.builder.editor.EditorGUI; import org.csstudio.display.builder.editor.EditorUtil; import org.csstudio.display.builder.editor.Messages; +import org.csstudio.display.builder.editor.WidgetSelectionHandler; import org.csstudio.display.builder.editor.actions.ActionDescription; import org.csstudio.display.builder.model.DisplayModel; import org.csstudio.display.builder.model.ModelPlugin; @@ -386,9 +387,20 @@ void loadWidgetClasses() ModelThreadPool.getExecutor().execute(() -> { // get widget classes and apply to model + // (which triggers editor UI updates, so perform in UI thread) final DisplayModel model = editor_gui.getDisplayEditor().getModel(); if (model != null) - WidgetClassesService.getWidgetClasses().apply(model); + Platform.runLater( () -> + { + // Save/restore selection to force update of property panel + final WidgetSelectionHandler selection = editor_gui.getDisplayEditor().getWidgetSelectionHandler(); + final List save = selection.getSelection(); + selection.clear(); + // Apply class settings + WidgetClassesService.getWidgetClasses().apply(model); + // Restore selection + selection.setSelection(save); + }); }); } diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanelSection.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanelSection.java index d1a00c7f09..e8633986f0 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanelSection.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/PropertyPanelSection.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2018 Oak Ridge National Laboratory. + * Copyright (c) 2015-2022 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -499,19 +499,22 @@ else if (property instanceof PointsWidgetProperty) * @param property Property (on primary widget) * @param other Zero or more additional widgets that have same type of property * @param structureIndex Index of the array structure (element) being added. It is meaningful - * only for properties instance of {@link StructuredWidgetProperty}. + * only for properties instance of {@link StructuredWidgetProperty} + * @param indentationLevel Indentation level */ private void createPropertyUI( final UndoableActionManager undo, final WidgetProperty property, final List other, final int structureIndex, - final int indentationLevel - ) { + final int indentationLevel) + { // Skip runtime properties if (property.getCategory() == WidgetPropertyCategory.RUNTIME) return; + // System.out.println("Index " + structureIndex + ", level " + indentationLevel + ": " + property.getPath()); + final Label label = new Label(property.getDescription()); label.setMaxWidth(Double.MAX_VALUE); final String tooltip = property.getDescription() + " (" + property.getPath() + ")"; @@ -567,9 +570,7 @@ else if (property instanceof RulesWidgetProperty) field = rules_field; } else if (property instanceof StructuredWidgetProperty) - { // Don't allow editing structures and their elements in class mode - if (class_mode) - return; + { final StructuredWidgetProperty struct = (StructuredWidgetProperty) property; final Label header = new Label(struct.getDescription() + ( structureIndex > 0 ? " " + String.valueOf(1 + structureIndex) : "")); header.getStyleClass().add("structure_property_name"); @@ -589,9 +590,7 @@ else if (property instanceof StructuredWidgetProperty) return; } else if (property instanceof ArrayWidgetProperty) - { // Don't allow editing arrays and their elements in class mode - if (class_mode) - return; + { @SuppressWarnings("unchecked") final ArrayWidgetProperty> array = (ArrayWidgetProperty>) property; @@ -611,6 +610,28 @@ else if (property instanceof ArrayWidgetProperty) fillHeaderIndent(indentationLevel, row); add(label, indentationLevel, row, 4 - indentationLevel, 1); + + if (class_mode) + { // Checkbox to select if array is included in class definition + final CheckBox check = new CheckBox(); + check.setPadding(new Insets(0, 5, 0, 0)); + check.setTooltip(use_class_tooltip); + final WidgetPropertyBinding binding = new UseWidgetClassBinding(undo, check, spinner, property, other); + bindings.add(binding); + binding.bind(); + add(check, 3, row); + } + else + { // Show if property is set by the class, not editable. + final Label indicator = new Label(); + indicator.setPadding(new Insets(0, 5, 0, 0)); + indicator.setTooltip(using_class_tooltip); + final WidgetPropertyBinding binding = new ShowWidgetClassBinding(spinner, property, indicator); + bindings.add(binding); + binding.bind(); + add(indicator, 3, row); + } + add(spinner, 4, row, 2 - indentationLevel, 1); Separator separator = new Separator(); @@ -683,14 +704,20 @@ else if (property instanceof ArrayWidgetProperty) { if (class_mode) { // Class definition mode: - // Check box for 'use_class' - final CheckBox check = new CheckBox(); - check.setPadding(new Insets(0, 5, 0, 0)); - check.setTooltip(use_class_tooltip); - final WidgetPropertyBinding binding = new UseWidgetClassBinding(undo, check, field, property, other); - bindings.add(binding); - binding.bind(); - add(check, 3, row); + // Check box for 'use_class', but only on the top level + // For nested properties inside an array or struct, + // the class behavior is controlled at the top-level + // for the complete array or struct + if (indentationLevel == 0) + { + final CheckBox check = new CheckBox(); + check.setPadding(new Insets(0, 5, 0, 0)); + check.setTooltip(use_class_tooltip); + final WidgetPropertyBinding binding = new UseWidgetClassBinding(undo, check, field, property, other); + bindings.add(binding); + binding.bind(); + add(check, 3, row); + } } else { // Display file mode: diff --git a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/UseWidgetClassBinding.java b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/UseWidgetClassBinding.java index 1f2da3bd2b..842de91ec1 100644 --- a/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/UseWidgetClassBinding.java +++ b/app/display/editor/src/main/java/org/csstudio/display/builder/editor/properties/UseWidgetClassBinding.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2017 Oak Ridge National Laboratory. + * Copyright (c) 2015-2022 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -55,13 +55,14 @@ public void bind() updateFromModel(); jfx_node.setOnAction(event -> { + final boolean use_class = jfx_node.isSelected(); updating = true; - property_field.setDisable(! jfx_node.isSelected()); - undo.execute(new UseClassAction(widget_property, jfx_node.isSelected())); + property_field.setDisable(! use_class); + undo.execute(new UseClassAction(widget_property, use_class)); for (Widget w : other) { final WidgetProperty other_prop = w.getProperty(widget_property.getName()); - undo.execute(new UseClassAction(other_prop, jfx_node.isSelected())); + undo.execute(new UseClassAction(other_prop, use_class)); } updating = false; }); diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/ArrayWidgetProperty.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/ArrayWidgetProperty.java index aac9308291..7caac64a7b 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/ArrayWidgetProperty.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/ArrayWidgetProperty.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2016 Oak Ridge National Laboratory. + * Copyright (c) 2015-2022 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -111,15 +111,6 @@ protected ArrayWidgetProperty(final Descriptor descriptor, value = new CopyOnWriteArrayList<>(elements); } - @Override - public boolean isUsingWidgetClass() - { // Array uses class if any elements use it - for (WidgetProperty element : value) - if (element.isUsingWidgetClass()) - return true; - return false; - } - @Override public boolean isDefaultValue() { @@ -130,6 +121,15 @@ public boolean isDefaultValue() value.get(0).isDefaultValue(); } + @Override + public void useWidgetClass(boolean use_class) + { + // Update overall array as well as each element + super.useWidgetClass(use_class); + for (WPE element : value) + element.useWidgetClass(use_class); + } + @Override protected List restrictValue(final List requested_value) { @@ -195,6 +195,8 @@ public WPE removeElement() /** @param element Element to add to end of list */ public void addElement(final WPE element) { + // New elements get same class behavior as the array + element.useWidgetClass(use_class); value.add(element); firePropertyChange(null, Arrays.asList(element)); } @@ -239,8 +241,7 @@ public void setValueFromObject(final Object value) throws Exception element.setValueFromObject(el_value); } - // Notify listeners of the whole array - firePropertyChange(this, null, this.value); + // Listeners already received remove/add/set events } catch (Throwable ex) { diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/StructuredWidgetProperty.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/StructuredWidgetProperty.java index bf0069f96e..7ab4aa8c7f 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/StructuredWidgetProperty.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/StructuredWidgetProperty.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2016 Oak Ridge National Laboratory. + * Copyright (c) 2015-2022 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -66,16 +66,6 @@ protected StructuredWidgetProperty(final Descriptor descriptor, super(descriptor, widget, elements); } - /** @return true if any element is using class support */ - @Override - public boolean isUsingWidgetClass() - { - for (WidgetProperty element : value) - if (element.isUsingWidgetClass()) - return true; - return false; - } - /** @return true if all elements have default value */ @Override public boolean isDefaultValue() @@ -86,6 +76,15 @@ public boolean isDefaultValue() return true; } + @Override + public void useWidgetClass(boolean use_class) + { + // Update overall structure as well as each element + super.useWidgetClass(use_class); + for (WidgetProperty element : value) + element.useWidgetClass(use_class); + } + /** @return true if all elements are read-only */ @Override public boolean isReadonly() @@ -142,35 +141,42 @@ public void setValue(final List> value) @Override public void setValueFromObject(final Object new_value) throws Exception { - if (new_value instanceof List) - { // Allow assignment of another structure's value, i.e. list with same elements - final List new_elements = (List)new_value; - if (new_elements.size() != value.size()) - throw new Exception("Elements of structure " + getName() + " must provide " + value.size() + " elements, got " + new_elements.size()); - for (int i=0; i element = value.get(i); - final Object new_object = new_elements.get(i); - if (element.getClass() != new_object.getClass()) - throw new Exception("Cannot set structure " + getName() + "." + element.getName() + " to " + new_object); - - final WidgetProperty new_element = (WidgetProperty)new_object; - if (element.getName() != new_element.getName()) - throw new Exception("Cannot set structure " + getName() + "." + element.getName() + " to " + new_element.getName()); - try - { - element.setValueFromObject(new_element.getValue()); - } - catch (Exception ex) - { - throw new Exception("Cannot set structure " + getName() + "." + element.getName() + " to " + new_element, ex); - } - } - // Notify listeners of the whole array - firePropertyChange(this, null, this.value); + final List new_elements; + + // May use other struct or list with elements + if (new_value instanceof StructuredWidgetProperty) + { + final StructuredWidgetProperty other = (StructuredWidgetProperty) new_value; + new_elements = other.value; } + else if (new_value instanceof List) + new_elements = (List)new_value; else throw new Exception("Elements of structure " + getName() + " cannot be assigned from " + new_value); + + // Either way, element count and names of elements must match this struct + if (new_elements.size() != value.size()) + throw new Exception("Elements of structure " + getName() + " must provide " + value.size() + " elements, got " + new_elements.size()); + for (int i=0; i element = value.get(i); + final Object new_object = new_elements.get(i); + if (element.getClass() != new_object.getClass()) + throw new Exception("Cannot set structure " + getName() + "." + element.getName() + " to " + new_object); + + final WidgetProperty new_element = (WidgetProperty)new_object; + if (element.getName() != new_element.getName()) + throw new Exception("Cannot set structure " + getName() + "." + element.getName() + " to " + new_element.getName()); + try + { + element.setValueFromObject(new_element.getValue()); + } + catch (Exception ex) + { + throw new Exception("Cannot set structure " + getName() + "." + element.getName() + " to " + new_element, ex); + } + } + // Listeners have been notified about each element in setValueFromObject } @Override diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/WidgetProperty.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/WidgetProperty.java index c8fb9c72ca..fe6473379d 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/WidgetProperty.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/WidgetProperty.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2015-2016 Oak Ridge National Laboratory. + * Copyright (c) 2015-2022 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -227,15 +227,7 @@ public boolean isDefaultValue() */ public void useWidgetClass(final boolean use_class) { - if (this.use_class == use_class) - return; - this.use_class = use_class; - - // Editor for class model needs update, runtime doesn't - final DisplayModel model = getWidget().checkDisplayModel(); - if (model != null && model.isClassModel()) - firePropertyChange(null, null); } /** @return Is value of this property following diff --git a/app/display/model/src/main/resources/examples/classes.bcf b/app/display/model/src/main/resources/examples/classes.bcf index 5fc3b53a48..6be918103d 100644 --- a/app/display/model/src/main/resources/examples/classes.bcf +++ b/app/display/model/src/main/resources/examples/classes.bcf @@ -153,4 +153,47 @@ Properties are marked to be included in the class definition. + + Label_2 + GOOD_BAD_ERROR + 340 + 120 + + + + + + + GOOD_BAD_ERROR + 130 + 340 + 50 + 30 + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + diff --git a/app/display/model/src/main/resources/examples/monitors_led.bob b/app/display/model/src/main/resources/examples/monitors_led.bob index 13e2bcf531..2913db904b 100644 --- a/app/display/model/src/main/resources/examples/monitors_led.bob +++ b/app/display/model/src/main/resources/examples/monitors_led.bob @@ -1,8 +1,8 @@ LED - 650 - 650 + 980 + 682 Label TITLE @@ -532,7 +532,7 @@ except that the border follows the outline of the LED. 26 26 - + @@ -549,14 +549,14 @@ except that the border follows the outline of the LED. BoolLED_12 - FAILURE + ERROR sim://flipflop(2) 893 70 26 26 - + @@ -567,17 +567,19 @@ except that the border follows the outline of the LED. Label_26 - FAILURE + ERROR 880 50 Label_27 - Predefined LED classes can be used to get consistent indications for "Good/Bad", "On/Off" type of indicators. + Predefined LED classes can be used to get consistent indications for "Good/Bad", "On/Off" type of indicators. + +For Multi-State LEDs, the states can be predefined via a class. 610 144 360 - 60 + 96 @@ -596,4 +598,47 @@ except that the border follows the outline of the LED. 680 110 + + LED (Multi State) + GOOD_BAD_ERROR + sim://ramp(0, 2, 1) + 660 + 280 + 50 + 30 + false + + + 0 + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + + + Label_29 + GOOD_BAD_ERROR + 610 + 250 + 150 +