diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ActionButtonRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ActionButtonRepresentation.java index 24a85d5887..d72c31f938 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ActionButtonRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ActionButtonRepresentation.java @@ -166,9 +166,17 @@ private ButtonBase makeBaseButton() { final Button button = new Button(); button.setOnAction(event -> confirm(() -> handleActions(actions.getActions()))); result = button; + if (actions.getActions().size() == 1) { + // If the ActionButton only has a single action and that is to + // write to a PV then is_writePV should be true. + // This means that if the PV is non-writable then the + // ActionButton will be disabled. + if (actions.getActions().get(0).getType().equals("write_pv")) + is_writePV = true; + } } else { // If there is at least one non-WritePVAction then is_writePV should be false - is_writePV = !has_non_writePVAction; + is_writePV = has_non_writePVAction; final MenuButton button = new MenuButton(); @@ -461,11 +469,7 @@ public void updateChanges() { // Don't disable the widget, because that would also remove the // tooltip // Just apply a style that matches the disabled look. - Styles.update(base, Styles.NOT_ENABLED, !enabled); - // Apply the cursor to the pane and not to the button - if (!toolkit.isEditMode()) { - jfx_node.setCursor(enabled ? Cursor.HAND : Cursors.NO_WRITE); - } + setDisabledLook(enabled, jfx_node.getChildren()); } } } diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BoolButtonRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BoolButtonRepresentation.java index 865b94b295..f452184e2c 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BoolButtonRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/BoolButtonRepresentation.java @@ -83,6 +83,7 @@ public class BoolButtonRepresentation extends RegionBaseRepresentation confirm(pressed)); + if (enabled) { + logger.log(Level.FINE, "{0} pressed", model_widget); + Platform.runLater(() -> confirm(pressed)); + } } /** Check for confirmation, then perform the button action @@ -403,10 +406,9 @@ public void updateChanges() } if (dirty_enablement.checkAndClear()) { - final boolean enabled = model_widget.propEnabled().getValue() && + enabled = model_widget.propEnabled().getValue() && model_widget.runtimePropPVWritable().getValue(); - button.setDisable(! enabled); - Styles.update(button, Styles.NOT_ENABLED, !enabled); + setDisabledLook(enabled, jfx_node.getChildren()); } if (update_value) { diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/CheckBoxRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/CheckBoxRepresentation.java index c9d471c8f6..db73a336b6 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/CheckBoxRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/CheckBoxRepresentation.java @@ -229,7 +229,7 @@ public void updateChanges() // Just apply a style that matches the disabled look. enabled = model_widget.propEnabled().getValue() && model_widget.runtimePropPVWritable().getValue(); - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); + setDisabledLook(enabled, jfx_node.getChildrenUnmodifiable()); if (model_widget.propAutoSize().getValue()) sizeChanged(null, null, null); } diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java index 08ca32577a..2e336eeb0d 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ChoiceButtonRepresentation.java @@ -181,6 +181,14 @@ private void selectionChanged(final ObservableValue obs, final { active = false; } + } + else if (!enabled && newval == null) + { + // If the choice button is not enabled (no write allowed) + // we still have to ensure the 'oldval' stays selected + // as otherwise clicking on the same value will set an + // unselected look on the ChoiceButton. + toggle.selectToggle(oldval); } } @@ -332,8 +340,7 @@ public void updateChanges() // Just apply a style that matches the disabled look. enabled = model_widget.propEnabled().getValue() && model_widget.runtimePropPVWritable().getValue(); - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); - jfx_node.setCursor(enabled ? Cursor.DEFAULT : Cursors.NO_WRITE); + setDisabledLook(enabled, jfx_node.getChildren()); for (Node node : jfx_node.getChildren()) { final ButtonBase b = (ButtonBase) node; diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ComboRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ComboRepresentation.java index 4897df72cd..b462490dc6 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ComboRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ComboRepresentation.java @@ -296,8 +296,7 @@ public void updateChanges() // and the cursor will be ignored // jfx_node.setDisable(! enabled); // So keep enabled, but indicate that trying to operate the widget is futile - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); - jfx_node.setCursor(enabled ? Cursor.DEFAULT : Cursors.NO_WRITE); + setDisabledLook(enabled, jfx_node.getChildrenUnmodifiable()); if (model_widget.propEditable().getValue()) { jfx_node.getEditor().setEditable(enabled ? model_widget.propEditable().getValue() : false); diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java index 8892ea4b67..c6ecdb754b 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/JFXBaseRepresentation.java @@ -26,14 +26,17 @@ import org.csstudio.display.builder.model.widgets.TabsWidget; import org.csstudio.display.builder.model.widgets.TabsWidget.TabItemProperty; import org.csstudio.display.builder.representation.WidgetRepresentation; +import org.csstudio.display.builder.representation.javafx.Cursors; import org.csstudio.display.builder.representation.javafx.JFXRepresentation; import javafx.collections.ObservableList; import javafx.event.EventHandler; +import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.Parent; import org.phoebus.core.types.ProcessVariable; import org.phoebus.ui.dnd.DataFormats; +import org.phoebus.ui.javafx.Styles; /** Base class for all JavaFX widget representations * @param JFX Widget @@ -356,4 +359,22 @@ public void updateOrder() addToParent(parent); } } + + /** + * We do not want to disable widgets if they cannot be written to as this + * removes the context menu. Instead we replicate the disabled look from Java FX + * and set the cursor to the 'NO_WRITE' cursor to indicate this. + * + * @param enabled boolean as to whether widget interaction is allowed + * @param children list of children nodes under the parent widget + */ + public void setDisabledLook(Boolean enabled, ObservableList children) { + jfx_node.setCursor(enabled ? Cursor.DEFAULT : Cursors.NO_WRITE); + if (children != null) { + for (Node node : children) + { + Styles.update(node, Styles.NOT_ENABLED, !enabled); + } + } + } } diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/RadioRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/RadioRepresentation.java index b9b1de5d74..c92fb5b4c8 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/RadioRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/RadioRepresentation.java @@ -299,7 +299,7 @@ public void updateChanges() // Just apply a style that matches the disabled look. enabled = model_widget.propEnabled().getValue() && model_widget.runtimePropPVWritable().getValue(); - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); + setDisabledLook(enabled, jfx_node.getChildren()); for (Node rb_node : jfx_node.getChildren()) { final RadioButton rb = (RadioButton) rb_node; diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScaledSliderRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScaledSliderRepresentation.java index 6d1abeb666..7217bdc386 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScaledSliderRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScaledSliderRepresentation.java @@ -407,8 +407,11 @@ private void valueChanged(final WidgetProperty property, final public void updateChanges() { super.updateChanges(); - if (dirty_enablement.checkAndClear()) + if (dirty_enablement.checkAndClear()) { slider.setDisable(!enabled); + setDisabledLook(enabled, jfx_node.getChildrenUnmodifiable()); + } + if (dirty_layout.checkAndClear()) { final boolean horizontal = model_widget.propHorizontal().getValue(); diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScrollBarRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScrollBarRepresentation.java index 2a02f560ea..847c2835fd 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScrollBarRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/ScrollBarRepresentation.java @@ -335,8 +335,7 @@ public void updateChanges() // Don't disable the widget, because that would also remove the // context menu etc. // Just apply a style that matches the disabled look. - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); - jfx_node.setCursor(enabled ? Cursor.DEFAULT : Cursors.NO_WRITE); + setDisabledLook(enabled, jfx_node.getChildrenUnmodifiable()); } if (dirty_size.checkAndClear()) { diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SlideButtonRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SlideButtonRepresentation.java index 2d157a8f80..03d92d4bc7 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SlideButtonRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SlideButtonRepresentation.java @@ -99,7 +99,7 @@ public void updateChanges ( ) { // Just apply a style that matches the disabled look. enabled = model_widget.propEnabled().getValue() && model_widget.runtimePropPVWritable().getValue(); - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); + setDisabledLook(enabled, jfx_node.getChildren()); // Since jfx_node.isManaged() == false, need to trigger layout jfx_node.layout(); diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SpinnerRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SpinnerRepresentation.java index 2b3b37ac49..74ec7a5c8a 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SpinnerRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/SpinnerRepresentation.java @@ -49,6 +49,7 @@ public class SpinnerRepresentation extends RegionBaseRepresentation value_max) - value = value_max; + if (enabled) { + //The value factory retains the old values, and will be updated as scheduled below. + final String text = jfx_node.getEditor().getText(); + Object value = + FormatOptionHandler.parse(model_widget.runtimePropValue().getValue(), text, model_widget.propFormat().getValue()); + if (value instanceof Number) + { + if (((Number)value).doubleValue() < value_min) + value = value_min; + else if (((Number)value).doubleValue() > value_max) + value = value_max; + } + logger.log(Level.FINE, "Writing '" + text + "' as " + value + " (" + value.getClass().getName() + ")"); + toolkit.fireWrite(model_widget, value); + + // Wrote value. Expected is either + // a) PV receives that value, PV updates to + // submitted value or maybe a 'clamped' value + // --> We'll receive contentChanged() and update the value factory. + // b) PV doesn't receive the value and never sends + // an update. The value factory retains the old value, + // --> Schedule an update to the new value. + // + // This could result in a little flicker: + // User enters "new_value". + // We send that, but retain "old_value" to handle case b) + // PV finally sends "new_value", and we show that. + // + // In practice, this rarely happens because we only schedule an update. + // By the time it executes, we already have case a. + // If it does turn into a problem, could introduce toolkit.scheduleDelayedUpdate() + // so that case b) only restores the old 'value_text' after some delay, + // increasing the chance of a) to happen. + dirty_content.mark(); + toolkit.scheduleUpdate(this); } - logger.log(Level.FINE, "Writing '" + text + "' as " + value + " (" + value.getClass().getName() + ")"); - toolkit.fireWrite(model_widget, value); - - // Wrote value. Expected is either - // a) PV receives that value, PV updates to - // submitted value or maybe a 'clamped' value - // --> We'll receive contentChanged() and update the value factory. - // b) PV doesn't receive the value and never sends - // an update. The value factory retains the old value, - // --> Schedule an update to the new value. - // - // This could result in a little flicker: - // User enters "new_value". - // We send that, but retain "old_value" to handle case b) - // PV finally sends "new_value", and we show that. - // - // In practice, this rarely happens because we only schedule an update. - // By the time it executes, we already have case a. - // If it does turn into a problem, could introduce toolkit.scheduleDelayedUpdate() - // so that case b) only restores the old 'value_text' after some delay, - // increasing the chance of a) to happen. - dirty_content.mark(); - toolkit.scheduleUpdate(this); } private SpinnerValueFactory createSVF() @@ -349,18 +352,20 @@ public void increment(int steps) private void writeResultingValue(double change) { - double value; - if (!(getVTypeValue() instanceof VNumber)) - { - scheduleContentUpdate(); - return; + if (enabled) { + double value; + if (!(getVTypeValue() instanceof VNumber)) + { + scheduleContentUpdate(); + return; + } + value = ((VNumber)getVTypeValue()).getValue().doubleValue(); + if (Double.isNaN(value) || Double.isInfinite(value)) return; + value += change; + if (value < getMin()) value = getMin(); + else if (value > getMax()) value = getMax(); + toolkit.fireWrite(model_widget, value); } - value = ((VNumber)getVTypeValue()).getValue().doubleValue(); - if (Double.isNaN(value) || Double.isInfinite(value)) return; - value += change; - if (value < getMin()) value = getMin(); - else if (value > getMax()) value = getMax(); - toolkit.fireWrite(model_widget, value); } }; @@ -505,11 +510,10 @@ public void updateChanges() jfx_node.resize(model_widget.propWidth().getValue(), model_widget.propHeight().getValue()); // Enable if enabled by user and there's write access - final boolean enabled = model_widget.propEnabled().getValue() && + enabled = model_widget.propEnabled().getValue() && model_widget.runtimePropPVWritable().getValue(); - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); + setDisabledLook(enabled, jfx_node.getChildrenUnmodifiable()); jfx_node.setEditable(!toolkit.isEditMode() && enabled); - jfx_node.getEditor().setCursor(enabled ? Cursor.DEFAULT : Cursors.NO_WRITE); jfx_node.getEditor().setFont(JFXUtil.convert(model_widget.propFont().getValue())); diff --git a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TextEntryRepresentation.java b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TextEntryRepresentation.java index e2d34d906b..3792dc2673 100644 --- a/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TextEntryRepresentation.java +++ b/app/display/representation-javafx/src/main/java/org/csstudio/display/builder/representation/javafx/widgets/TextEntryRepresentation.java @@ -49,6 +49,7 @@ public class TextEntryRepresentation extends RegionBaseRepresentation We'll receive contentChanged() and display PV's latest. - // b) PV doesn't receive the value and never sends - // an update. JFX control is stuck with the 'text' - // the user entered, not reflecting the actual PV - // --> Request an update to the last known 'value_text'. - // - // This could result in a little flicker: - // User enters "new_value". - // We send that, but restore "old_value" to handle case b) - // PV finally sends "new_value", and we show that. - // - // In practice, this rarely happens because we only schedule an update. - // By the time it executes, we already have case a. - // If it does turn into a problem, could introduce toolkit.scheduleDelayedUpdate() - // so that case b) only restores the old 'value_text' after some delay, - // increasing the chance of a) to happen. - dirty_content.mark(); - toolkit.scheduleUpdate(this); + if (enabled) { + // Strip 'units' etc. from text + final String text = jfx_node.getText(); + + final Object value = FormatOptionHandler.parse(model_widget.runtimePropValue().getValue(), text, + model_widget.propFormat().getValue()); + logger.log(Level.FINE, "Writing '" + text + "' as " + value + " (" + value.getClass().getName() + ")"); + toolkit.fireWrite(model_widget, value); + + // Wrote value. Expected is either + // a) PV receives that value, PV updates to + // submitted value or maybe a 'clamped' value + // --> We'll receive contentChanged() and display PV's latest. + // b) PV doesn't receive the value and never sends + // an update. JFX control is stuck with the 'text' + // the user entered, not reflecting the actual PV + // --> Request an update to the last known 'value_text'. + // + // This could result in a little flicker: + // User enters "new_value". + // We send that, but restore "old_value" to handle case b) + // PV finally sends "new_value", and we show that. + // + // In practice, this rarely happens because we only schedule an update. + // By the time it executes, we already have case a. + // If it does turn into a problem, could introduce toolkit.scheduleDelayedUpdate() + // so that case b) only restores the old 'value_text' after some delay, + // increasing the chance of a) to happen. + dirty_content.mark(); + toolkit.scheduleUpdate(this); + } } @Override @@ -399,14 +402,14 @@ public void updateChanges() jfx_node.setFont(JFXUtil.convert(model_widget.propFont().getValue())); // Enable if enabled by user and there's write access - final boolean enabled = model_widget.propEnabled().getValue() && + enabled = model_widget.propEnabled().getValue() && model_widget.runtimePropPVWritable().getValue(); // Don't disable the widget, because that would also remove the // context menu etc. // Just apply a style that matches the disabled look. jfx_node.setEditable(enabled); - Styles.update(jfx_node, Styles.NOT_ENABLED, !enabled); - jfx_node.setCursor(enabled ? Cursor.DEFAULT : Cursors.NO_WRITE); + setDisabledLook(enabled, jfx_node.getChildrenUnmodifiable()); + if(jfx_node instanceof TextField){ ((TextField)jfx_node).setAlignment(pos); diff --git a/app/display/thumbwheel/src/main/java/org/csstudio/display/widget/ThumbwheelWidgetRepresentation.java b/app/display/thumbwheel/src/main/java/org/csstudio/display/widget/ThumbwheelWidgetRepresentation.java index cd68aab556..3aa5b0e7d0 100644 --- a/app/display/thumbwheel/src/main/java/org/csstudio/display/widget/ThumbwheelWidgetRepresentation.java +++ b/app/display/thumbwheel/src/main/java/org/csstudio/display/widget/ThumbwheelWidgetRepresentation.java @@ -109,7 +109,7 @@ protected void unregisterListeners() { public void updateChanges() { super.updateChanges(); if (dirty_enablement.checkAndClear()) { - jfx_node.setDisable(!enabled); + setDisabledLook(enabled, jfx_node.getChildren()); } if (dirty_style.checkAndClear()) { @@ -150,7 +150,8 @@ private void styleChanged(final WidgetProperty property, final Object old_val // decrementing the values via buttons private void writeValueToPV(final Number new_value) { - toolkit.fireWrite(model_widget, new_value); + if (enabled) + toolkit.fireWrite(model_widget, new_value); } // Value change is triggered when the PV value changes diff --git a/core/ui/src/main/resources/org/phoebus/ui/javafx/csstudio.css b/core/ui/src/main/resources/org/phoebus/ui/javafx/csstudio.css index 442931148e..a03ecc4b3c 100644 --- a/core/ui/src/main/resources/org/phoebus/ui/javafx/csstudio.css +++ b/core/ui/src/main/resources/org/phoebus/ui/javafx/csstudio.css @@ -17,6 +17,9 @@ .not_enabled { -fx-opacity: 0.4; + -fx-background-insets: 0; + -fx-color: -fx-base; + -fx-focus-color: -fx-base; } /** Tracker */