diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java index b57afd78f1..375c09a1bc 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterRepresentation.java @@ -42,24 +42,16 @@ public Pane createJFXNode() double initialValue = toolkit.isEditMode() ? (model_widget.propMinimum().getValue() + model_widget.propMaximum().getValue()) / 2.0 : Double.NaN; - double minimum, maximum; - double loLo, low, high, hiHi; - if (model_widget.propLimitsFromPV().getValue() && !toolkit.isEditMode()) { - minimum = Double.NaN; - maximum = Double.NaN; - loLo = Double.NaN; - low = Double.NaN; - high = Double.NaN; - hiHi = Double.NaN; - } - else { - minimum = model_widget.propMinimum().getValue(); - maximum = model_widget.propMaximum().getValue(); - loLo = model_widget.propLevelLoLo().getValue(); - low = model_widget.propLevelLow().getValue(); - high = model_widget.propLevelHigh().getValue(); - hiHi = model_widget.propLevelHiHi().getValue(); - } + // Set min, max, and alarm limits according to settings. + // Some or all of these may be overridden by the PV when + // values are received, if the option "Limits from PV" + // is not set to "No limits from PV". + double minimum = model_widget.propMinimum().getValue(); + double maximum = model_widget.propMaximum().getValue(); + double loLo = model_widget.propLevelLoLo().getValue(); + double low = model_widget.propLevelLow().getValue(); + double high = model_widget.propLevelHigh().getValue(); + double hiHi = model_widget.propLevelHiHi().getValue(); double minMaxTolerance = model_widget.propMinMaxTolerance().getValue(); @@ -86,6 +78,7 @@ public Pane createJFXNode() model_widget.propKnobSize().getValue(), widgetColorToAWTColor(model_widget.propKnobColor().getValue()), model_widget.propShowWarnings().getValue()); + meter.setDisplayMode(model_widget.propDisplayMode().getValue()); meter.setSize(model_widget.propWidth().getValue(),model_widget.propHeight().getValue()); meter.setHorizontal(model_widget.propDisplayHorizontal().getValue()); meter.setManaged(false); @@ -167,26 +160,26 @@ protected void registerListeners() addWidgetPropertyListener(model_widget.propMinimum(), (property, old_value, new_value) -> { - synchronized (meter) { + meter.withWriteLock(() -> { boolean validRange = Double.isFinite(new_value) && Double.isFinite(model_widget.propMaximum().getValue()); meter.setRange(new_value, model_widget.propMaximum().getValue(), validRange); if (toolkit.isEditMode() && validRange) { meter.setCurrentValue((new_value + model_widget.propMaximum().getValue()) / 2.0); } - } + }); layoutChanged(null, null, null); }); addWidgetPropertyListener(model_widget.propMaximum(), (property, old_value, new_value) -> { - synchronized (meter) { + meter.withWriteLock(() -> { boolean validRange = Double.isFinite(new_value) && Double.isFinite(model_widget.propMinimum().getValue()); meter.setRange(model_widget.propMinimum().getValue(), new_value, validRange); if (toolkit.isEditMode() && validRange) { meter.setCurrentValue((new_value + model_widget.propMinimum().getValue()) / 2.0); } - } + }); layoutChanged(null, null, null); }); @@ -229,6 +222,11 @@ protected void registerListeners() meter.setKnobSize(new_value); layoutChanged(null, null, null); }); + + addWidgetPropertyListener(model_widget.propDisplayMode(), (property, old_value, new_value) -> { + meter.setDisplayMode(new_value); + layoutChanged(null, null, null); + }); } @Override @@ -244,7 +242,7 @@ protected void unregisterListeners() super.unregisterListeners(); } - int minimumSize = 25; + int minimumSize = 2; private void widthChanged(WidgetProperty prop, Integer old_value, Integer new_value) { if (new_value < minimumSize) { @@ -267,13 +265,13 @@ private void orientationChanged(WidgetProperty prop, Boolean old, Boole { if (toolkit.isEditMode()) { - synchronized(meter) { + meter.withWriteLock(() -> { int w = model_widget.propWidth().getValue(); int h = model_widget.propHeight().getValue(); model_widget.propWidth().setValue(h); model_widget.propHeight().setValue(w); meter.setHorizontal(horizontal); - } + }); layoutChanged(null, null, null); } } @@ -290,81 +288,88 @@ private void layoutChanged(WidgetProperty property, Object old_value, Object private void valueChanged(WidgetProperty property, Object old_value, Object new_value) { if (new_value instanceof VDouble) { - VDouble vDouble = ((VDouble) new_value); - double newValue = vDouble.getValue(); - meter.setCurrentValue(newValue); - - if (!Double.isNaN(newValue)) { - if (Double.isNaN(observedMin) || newValue < observedMin) { - observedMin = newValue; - newObservedMinAndMaxValues = true; - } - - if (Double.isNaN(observedMax) || newValue > observedMax) { - observedMax = newValue; - newObservedMinAndMaxValues = true; - } - } - - Display display = vDouble.getDisplay(); - - // Set the units: - if (model_widget != null && model_widget.propShowUnits().getValue()) { - meter.setUnits(display.getUnit()); - } - - if (model_widget != null && model_widget.propLimitsFromPV().getValue()) { - Range displayRange = display.getDisplayRange(); - if (displayRange != null - && Double.isFinite(displayRange.getMinimum()) - && Double.isFinite(displayRange.getMaximum()) - && displayRange.getMaximum() - displayRange.getMinimum() > 0.0) { - if (meter.linearMeterScale.getValueRange().getLow() != displayRange.getMinimum() || meter.linearMeterScale.getValueRange().getHigh() != displayRange.getMaximum() || !meter.getValidRange()) { - meter.setRange(displayRange.getMinimum(), displayRange.getMaximum(), true); + meter.withWriteLock(() -> { + VDouble vDouble = ((VDouble) new_value); + double newValue = vDouble.getValue(); + + if (!Double.isNaN(newValue)) { + if (Double.isNaN(observedMin) || newValue < observedMin) { + observedMin = newValue; + newObservedMinAndMaxValues = true; } - } else { - Range controlRange = display.getControlRange(); - if (controlRange != null - && Double.isFinite(controlRange.getMinimum()) - && Double.isFinite(controlRange.getMaximum()) - && controlRange.getMaximum() - controlRange.getMinimum() > 0.0) { - if (meter.linearMeterScale.getValueRange().getLow() != controlRange.getMinimum() || meter.linearMeterScale.getValueRange().getHigh() != controlRange.getMaximum() || !meter.getValidRange()) { - meter.setRange(controlRange.getMinimum(), controlRange.getMaximum(), true); - } - } else if (newObservedMinAndMaxValues && !Double.isNaN(observedMin) && !Double.isNaN(observedMax)) { - meter.setRange(observedMin - 1, observedMax + 1, false); - newObservedMinAndMaxValues = false; - } else if (meter.linearMeterScale.getValueRange().getLow() != 0.0 || meter.linearMeterScale.getValueRange().getHigh() != 100) { - meter.setRange(0.0, 100.0, false); + + if (Double.isNaN(observedMax) || newValue > observedMax) { + observedMax = newValue; + newObservedMinAndMaxValues = true; } } - { - Range warningRange = display.getWarningRange(); - if (warningRange != null) { - if (!Double.isNaN(warningRange.getMinimum()) && meter.getLow() != warningRange.getMinimum()) { - meter.setLow(warningRange.getMinimum()); - } + Display display = vDouble.getDisplay(); - if (!Double.isNaN(warningRange.getMaximum()) && meter.getHigh() != warningRange.getMaximum()) { - meter.setHigh(warningRange.getMaximum()); + // Set the units: + if (model_widget != null && model_widget.propShowUnits().getValue()) { + meter.setUnits(display.getUnit()); + } + if (model_widget != null) { + LinearMeterWidget.LimitsFromPV limitsFromPVSetting = model_widget.propLimitsFromPV().getValue(); + if (limitsFromPVSetting.equals(LinearMeterWidget.LimitsFromPV.LimitsFromPV) || + limitsFromPVSetting.equals(LinearMeterWidget.LimitsFromPV.MinAndMaxFromPV)) { + Range displayRange = display.getDisplayRange(); + if (displayRange != null + && Double.isFinite(displayRange.getMinimum()) + && Double.isFinite(displayRange.getMaximum()) + && displayRange.getMaximum() - displayRange.getMinimum() > 0.0) { + if (meter.linearMeterScale.getValueRange().getLow() != displayRange.getMinimum() || meter.linearMeterScale.getValueRange().getHigh() != displayRange.getMaximum() || !meter.getValidRange()) { + meter.setRange(displayRange.getMinimum(), displayRange.getMaximum(), true); + } + } else { + Range controlRange = display.getControlRange(); + if (controlRange != null + && Double.isFinite(controlRange.getMinimum()) + && Double.isFinite(controlRange.getMaximum()) + && controlRange.getMaximum() - controlRange.getMinimum() > 0.0) { + if (meter.linearMeterScale.getValueRange().getLow() != controlRange.getMinimum() || meter.linearMeterScale.getValueRange().getHigh() != controlRange.getMaximum() || !meter.getValidRange()) { + meter.setRange(controlRange.getMinimum(), controlRange.getMaximum(), true); + } + } else if (newObservedMinAndMaxValues && !Double.isNaN(observedMin) && !Double.isNaN(observedMax)) { + meter.setRange(observedMin - 1, observedMax + 1, false); + newObservedMinAndMaxValues = false; + } else if (meter.linearMeterScale.getValueRange().getLow() != 0.0 || meter.linearMeterScale.getValueRange().getHigh() != 100) { + meter.setRange(0.0, 100.0, false); + } } } - } - - { - Range alarmRange = display.getAlarmRange(); - if (alarmRange != null) { - if (!Double.isNaN(alarmRange.getMinimum()) && meter.getLoLo() != alarmRange.getMinimum()) { - meter.setLoLo(alarmRange.getMinimum()); + if (limitsFromPVSetting.equals(LinearMeterWidget.LimitsFromPV.LimitsFromPV) || + limitsFromPVSetting.equals(LinearMeterWidget.LimitsFromPV.AlarmLimitsFromPV)) { + { + Range warningRange = display.getWarningRange(); + if (warningRange != null) { + if (!Double.isNaN(warningRange.getMinimum()) && meter.getLow() != warningRange.getMinimum()) { + meter.setLow(warningRange.getMinimum()); + } + + if (!Double.isNaN(warningRange.getMaximum()) && meter.getHigh() != warningRange.getMaximum()) { + meter.setHigh(warningRange.getMaximum()); + } + } } - if (!Double.isNaN(alarmRange.getMaximum()) && meter.getHiHi() != alarmRange.getMaximum()) { - meter.setHiHi(alarmRange.getMaximum()); + { + Range alarmRange = display.getAlarmRange(); + if (alarmRange != null) { + if (!Double.isNaN(alarmRange.getMinimum()) && meter.getLoLo() != alarmRange.getMinimum()) { + meter.setLoLo(alarmRange.getMinimum()); + } + + if (!Double.isNaN(alarmRange.getMaximum()) && meter.getHiHi() != alarmRange.getMaximum()) { + meter.setHiHi(alarmRange.getMaximum()); + } + } } } } - } + meter.setCurrentValue(newValue); + }); } } @@ -375,7 +380,7 @@ public void updateChanges() if (dirty_look.checkAndClear()) { - synchronized (meter) { + meter.withWriteLock(() -> { boolean horizontal = model_widget.propDisplayHorizontal().getValue(); int width = model_widget.propWidth().getValue(); int height = model_widget.propHeight().getValue(); @@ -391,7 +396,7 @@ public void updateChanges() jfx_node.setPrefSize(width, height); meter.setSize(width, height); - } + }); } } diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java index 68f643b3fe..736a949023 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/LinearMeterWidget.java @@ -10,6 +10,7 @@ import org.csstudio.display.builder.model.persist.NamedWidgetFonts; import org.csstudio.display.builder.model.persist.WidgetColorService; import org.csstudio.display.builder.model.persist.WidgetFontService; +import org.csstudio.display.builder.model.properties.EnumWidgetProperty; import org.csstudio.display.builder.model.properties.WidgetColor; import org.csstudio.display.builder.model.properties.WidgetFont; import org.csstudio.display.builder.model.widgets.PVWidget; @@ -20,55 +21,50 @@ import static org.csstudio.display.builder.model.properties.CommonWidgetProperties.*; @SuppressWarnings("nls") -public class LinearMeterWidget extends PVWidget -{ - public LinearMeterWidget() - { +public class LinearMeterWidget extends PVWidget { + public LinearMeterWidget() { super(WIDGET_DESCRIPTOR.getType(), 240, 120); } public static WidgetDescriptor WIDGET_DESCRIPTOR = - new WidgetDescriptor("linearmeter", WidgetCategory.MONITOR, - "LinearMeter", - "/icons/linear-meter.png", - "Compact monitor widget for the value of a PV.", - Arrays.asList("")) - { - @Override - public Widget createWidget() - { - return new LinearMeterWidget(); - } - }; + new WidgetDescriptor("linearmeter", WidgetCategory.MONITOR, + "LinearMeter", + "/icons/linear-meter.png", + Messages.LinearMeterDescription, + Arrays.asList("")) { + @Override + public Widget createWidget() { + return new LinearMeterWidget(); + } + }; /** * 1.0.0: Linear meter by Claudio Rosatti based on 3rd party library * 2.0.0: Simple linear meter, based on meter v 3.0.0, drawn in background + * 3.0.0: Improvements to the Linear Meter and the introduction of a "Bar" mode. */ - public static Version METER_VERSION = new Version(2, 0, 0); + public static Version METER_VERSION = new Version(3, 0, 0); - /** Custom configurator to read legacy files */ - protected static class LinearMeterConfigurator extends WidgetConfigurator - { + /** + * Custom configurator to read legacy files + */ + protected static class LinearMeterConfigurator extends WidgetConfigurator { //TODO: This has to be fixed for the Linear Meter. Current implementation is // for the Meter widget and is not valid. No version 3. - public LinearMeterConfigurator(Version xmlVersion) - { + public LinearMeterConfigurator(Version xmlVersion) { super(xmlVersion); } @Override public boolean configureFromXML(ModelReader reader, Widget widget, - Element xml) throws Exception - { + Element xml) throws Exception { if (!super.configureFromXML(reader, widget, xml)) return false; LinearMeterWidget meter = (LinearMeterWidget) widget; - if (xml_version.getMajor() < 2) - { // BOY + if (xml_version.getMajor() < 2) { // BOY Element e = XMLUtil.getChildElement(xml, "scale_font"); if (e != null) @@ -76,23 +72,21 @@ public boolean configureFromXML(ModelReader reader, Widget widget, // Are any of the limits disabled, or 'Show Ramp' disabled? if ((!XMLUtil.getChildBoolean(xml, "show_hihi").orElse(true) && - !XMLUtil.getChildBoolean(xml, "show_hi").orElse(true) && - !XMLUtil.getChildBoolean(xml, "show_lo").orElse(true) && - !XMLUtil.getChildBoolean(xml, "show_lolo").orElse(true) + !XMLUtil.getChildBoolean(xml, "show_hi").orElse(true) && + !XMLUtil.getChildBoolean(xml, "show_lo").orElse(true) && + !XMLUtil.getChildBoolean(xml, "show_lolo").orElse(true) ) - || - !XMLUtil.getChildBoolean(xml, "show_markers").orElse(true)) + || + !XMLUtil.getChildBoolean(xml, "show_markers").orElse(true)) meter.propShowLimits().setValue(false); - } - else if (xml_version.getMajor() < 3) - { // Display Builder meter based on 3rd party JFX lib + } else if (xml_version.getMajor() < 3) { // Display Builder meter based on 3rd party JFX lib XMLUtil.getChildBoolean(xml, "unit_from_pv") - .ifPresent(meter.propShowUnits()::setValue); + .ifPresent(meter.propShowUnits()::setValue); if (!XMLUtil.getChildBoolean(xml, "show_hihi").orElse(true) && - !XMLUtil.getChildBoolean(xml, "show_high").orElse(true) && - !XMLUtil.getChildBoolean(xml, "show_low").orElse(true) && - !XMLUtil.getChildBoolean(xml, "show_lolo").orElse(true)) + !XMLUtil.getChildBoolean(xml, "show_high").orElse(true) && + !XMLUtil.getChildBoolean(xml, "show_low").orElse(true) && + !XMLUtil.getChildBoolean(xml, "show_lolo").orElse(true)) meter.propShowLimits().setValue(false); } @@ -101,63 +95,119 @@ else if (xml_version.getMajor() < 3) } public static WidgetPropertyDescriptor propShowLimits = - newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "show_limits", Messages.WidgetProperties_ShowLimits); + newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "show_limits", Messages.WidgetProperties_ShowLimits); public static WidgetPropertyDescriptor propShowWarnings = newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "show_warnings", Messages.WidgetProperties_ShowWarnings); public static WidgetPropertyDescriptor propDisplayHorizontal = - newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "displayHorizontal", Messages.WidgetProperties_Horizontal); + newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "displayHorizontal", Messages.WidgetProperties_Horizontal); + + public static WidgetPropertyDescriptor propScaleVisible = + newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "scale_visible", Messages.WidgetProperties_ScaleVisible); + + + enum LimitsFromPV { + LimitsFromPV(Messages.AllLimitsFromPV), + MinAndMaxFromPV(Messages.MinAndMaxFromPV), + AlarmLimitsFromPV(Messages.AlarmLimitsFromPV), + NoLimitsFromPV(Messages.NoLimitsFromPV); - public static WidgetPropertyDescriptor propScaleVisible = - newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "scale_visible", Messages.WidgetProperties_ScaleVisible); + private final String displayName; + + private LimitsFromPV(String displayName) { + this.displayName = displayName; + } + + ; + + @Override + public String toString() { + return this.displayName; + } + } + + public static final WidgetPropertyDescriptor propLimitsFromPV = + new WidgetPropertyDescriptor<>(WidgetPropertyCategory.BEHAVIOR, "limits_from_pv", Messages.WidgetProperties_LimitsFromPV) { + @Override + public EnumWidgetProperty createProperty(final Widget widget, LimitsFromPV default_value) { + EnumWidgetProperty widgetProperty = new EnumWidgetProperty<>(this, widget, LimitsFromPV.LimitsFromPV) { + @Override + public void setSpecification(final String specification) { + // Backwards compatibility to previous version of the Linear Meter, where + // propLimitsFromPV was a boolean: + if (specification.equals("true")) { + super.setSpecification(LimitsFromPV.LimitsFromPV.ordinal() + ""); + } else if (specification.equals("false")) { + super.setSpecification(LimitsFromPV.NoLimitsFromPV.ordinal() + ""); + } else { + // If not a boolean, set according to enum LimitsFromPV: + super.setSpecification(specification); + } + } + }; + return widgetProperty; + } + }; + + private WidgetProperty display_mode; + public static WidgetPropertyDescriptor propDisplayMode = + new WidgetPropertyDescriptor<>( + WidgetPropertyCategory.DISPLAY, "display_mode", Messages.DisplayMode) { + @Override + public EnumWidgetProperty createProperty(Widget widget, RTLinearMeter.DisplayMode default_value) { + return new EnumWidgetProperty<>(this, widget, default_value); + } + }; public static WidgetPropertyDescriptor propNeedleColor = - newColorPropertyDescriptor(WidgetPropertyCategory.MISC, "needle_color", Messages.WidgetProperties_NeedleColor); + newColorPropertyDescriptor(WidgetPropertyCategory.MISC, "needle_color", Messages.WidgetProperties_NeedleColor); public static WidgetPropertyDescriptor knobColor_descriptor = - newColorPropertyDescriptor(WidgetPropertyCategory.MISC, "knob_color", Messages.WidgetProperties_KnobColor); + newColorPropertyDescriptor(WidgetPropertyCategory.MISC, "knob_color", Messages.WidgetProperties_KnobColor); public static WidgetPropertyDescriptor knobSize_descriptor = - newIntegerPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "knob_size", "Knob Size"); + newIntegerPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "knob_size", Messages.KnobSize); public static WidgetPropertyDescriptor propLevelHiHi = - newDoublePropertyDescriptor (WidgetPropertyCategory.BEHAVIOR, "level_hihi", Messages.WidgetProperties_LevelHiHi); + newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "level_hihi", Messages.WidgetProperties_LevelHiHi); public static WidgetPropertyDescriptor propLevelHigh = - newDoublePropertyDescriptor (WidgetPropertyCategory.BEHAVIOR, "level_high", Messages.WidgetProperties_LevelHigh); + newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "level_high", Messages.WidgetProperties_LevelHigh); public static WidgetPropertyDescriptor propLevelLoLo = - newDoublePropertyDescriptor (WidgetPropertyCategory.BEHAVIOR, "level_lolo", Messages.WidgetProperties_LevelLoLo); + newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "level_lolo", Messages.WidgetProperties_LevelLoLo); public static WidgetPropertyDescriptor propLevelLow = - newDoublePropertyDescriptor (WidgetPropertyCategory.BEHAVIOR, "level_low", Messages.WidgetProperties_LevelLow); + newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "level_low", Messages.WidgetProperties_LevelLow); - /** 'min_max_tolerance' property: Treat the value range [min - min_max_tolerance, max + min_max_tolerance] as the valid value range for the widget (can be used to avoid warnings due to precision errors in cases such as when a PV sends -0.0000001 when the value is actually 0.0. */ + /** + * 'min_max_tolerance' property: Treat the value range [min - min_max_tolerance, max + min_max_tolerance] as the valid value range for the widget (can be used to avoid warnings due to precision errors in cases such as when a PV sends -0.0000001 when the value is actually 0.0. + */ public static final WidgetPropertyDescriptor propMinMaxTolerance = newDoublePropertyDescriptor(WidgetPropertyCategory.BEHAVIOR, "min_max_tolerance", Messages.WidgetProperties_MinMaxTolerance); public static StructuredWidgetProperty.Descriptor colorsStructuredWidget_descriptor = - new StructuredWidgetProperty.Descriptor(WidgetPropertyCategory.DISPLAY, "colors", "Colors"); + new StructuredWidgetProperty.Descriptor(WidgetPropertyCategory.DISPLAY, "colors", Messages.Colors); public static WidgetPropertyDescriptor propNormalStatusColor_descriptor = - newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "normal_status_color", "Normal Status Color"); + newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "normal_status_color", Messages.NormalStatusColor); public static WidgetPropertyDescriptor minorWarningColor_descriptor = - newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "minor_warning_color", "Low & High Warning Color"); + newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "minor_warning_color", Messages.LowAndHighWarningColor); public static WidgetPropertyDescriptor majorWarningColor_descriptor = - newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "major_warning_color", "LoLo & HiHi Warning Color"); + newColorPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "major_warning_color", Messages.LoLoAndHiHiWarningColor); public static WidgetPropertyDescriptor isGradientEnabled_descriptor = - newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "is_gradient_enabled", "Enable Gradient"); + newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "is_gradient_enabled", Messages.EnableGradient); public static WidgetPropertyDescriptor isHighlightingOfInactiveRegionsEnabled_descriptor = - newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "is_highlighting_of_active_regions_enabled", "Highlight Active Region"); + newBooleanPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "is_highlighting_of_active_regions_enabled", Messages.HighlightActiveRegion); public static WidgetPropertyDescriptor needleWidth_descriptor = - newIntegerPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "needle_width", "Needle Width"); + newIntegerPropertyDescriptor(WidgetPropertyCategory.DISPLAY, "needle_width", Messages.NeedleWidth); private WidgetProperty foreground; private WidgetProperty background; @@ -170,7 +220,7 @@ else if (xml_version.getMajor() < 3) private WidgetProperty scale_visible; private WidgetProperty knob_color; private WidgetProperty knobSize; - private WidgetProperty limits_from_pv; + private WidgetProperty limits_from_pv; private WidgetProperty minimum; private WidgetProperty maximum; private WidgetProperty level_high; @@ -189,29 +239,27 @@ else if (xml_version.getMajor() < 3) private WidgetProperty majorWarningColor; @Override - public Version getVersion() - { + public Version getVersion() { return METER_VERSION; } @Override - public WidgetConfigurator getConfigurator(Version persistedVersion) throws Exception - { + public WidgetConfigurator getConfigurator(Version persistedVersion) throws Exception { return new LinearMeterConfigurator(persistedVersion); } @Override - protected void defineProperties(List> properties) - { + protected void defineProperties(List> properties) { super.defineProperties(properties); + properties.add(display_mode = propDisplayMode.createProperty(this, RTLinearMeter.DisplayMode.NEEDLE)); properties.add(font = propFont.createProperty(this, WidgetFontService.get(NamedWidgetFonts.DEFAULT))); properties.add(format = propFormat.createProperty(this, FormatOption.DEFAULT)); properties.add(show_units = propShowUnits.createProperty(this, true)); properties.add(scale_visible = propScaleVisible.createProperty(this, true)); properties.add(show_limits = propShowLimits.createProperty(this, true)); properties.add(show_warnings = propShowWarnings.createProperty(this, true)); - properties.add(limits_from_pv = propLimitsFromPV.createProperty(this, true)); + properties.add(limits_from_pv = propLimitsFromPV.createProperty(this, LimitsFromPV.LimitsFromPV)); properties.add(minimum = propMinimum.createProperty(this, 0.0)); properties.add(maximum = propMaximum.createProperty(this, 100.0)); properties.add(displayHorizontal = propDisplayHorizontal.createProperty(this, true)); @@ -220,23 +268,23 @@ protected void defineProperties(List> properties) background = propBackgroundColor.createProperty(this, new WidgetColor(0, 0, 0, 0)); needle_color = propNeedleColor.createProperty(this, new WidgetColor(0, 0, 0, 255)); knob_color = knobColor_descriptor.createProperty(this, new WidgetColor(0, 0, 0, 255)); - normalStatusColor = propNormalStatusColor_descriptor.createProperty(this, new WidgetColor(194,198,195)); + normalStatusColor = propNormalStatusColor_descriptor.createProperty(this, new WidgetColor(194, 198, 195)); minorWarningColor = minorWarningColor_descriptor.createProperty(this, new WidgetColor(242, 148, 141)); majorWarningColor = majorWarningColor_descriptor.createProperty(this, new WidgetColor(240, 60, 46)); isGradientEnabled = isGradientEnabled_descriptor.createProperty(this, false); isHighlightingOfInactiveRegionsEnabled = isHighlightingOfInactiveRegionsEnabled_descriptor.createProperty(this, true); properties.add(needleWidth = needleWidth_descriptor.createProperty(this, 1)); List> colorSelectionWidgets = Arrays.asList(foreground, - background, - needle_color, - knob_color, - normalStatusColor, - minorWarningColor, - majorWarningColor, - isGradientEnabled, - isHighlightingOfInactiveRegionsEnabled); + background, + needle_color, + knob_color, + normalStatusColor, + minorWarningColor, + majorWarningColor, + isGradientEnabled, + isHighlightingOfInactiveRegionsEnabled); properties.add(colorsStructuredWidget = colorsStructuredWidget_descriptor.createProperty(this, - colorSelectionWidgets)); + colorSelectionWidgets)); properties.add(level_lolo = propLevelLoLo.createProperty(this, 10.0)); properties.add(level_low = propLevelLow.createProperty(this, 20.0)); properties.add(level_high = propLevelHigh.createProperty(this, 80.0)); @@ -244,113 +292,130 @@ protected void defineProperties(List> properties) properties.add(minMaxTolerance = propMinMaxTolerance.createProperty(this, 0.0)); } - /** @return 'foreground_color' property */ - public WidgetProperty propForeground() - { + /** + * @return 'foreground_color' property + */ + public WidgetProperty propForeground() { return foreground; } - /** @return 'background_color' property */ - public WidgetProperty propBackground() - { + /** + * @return 'background_color' property + */ + public WidgetProperty propBackground() { return background; } - /** @return 'font' property */ - public WidgetProperty propFont() - { + /** + * @return 'font' property + */ + public WidgetProperty propFont() { return font; } - /** @return 'format' property */ - public WidgetProperty propFormat() - { + /** + * @return 'format' property + */ + public WidgetProperty propFormat() { return format; } - /** @return 'show_units' property */ - public WidgetProperty propShowUnits() - { + /** + * @return 'show_units' property + */ + public WidgetProperty propShowUnits() { return show_units; } - /** @return 'scale_visible' property */ - public WidgetProperty propScaleVisible() - { + /** + * @return 'scale_visible' property + */ + public WidgetProperty propScaleVisible() { return scale_visible; } - /** @return 'show_limits' property */ - public WidgetProperty propShowLimits() - { + /** + * @return 'show_limits' property + */ + public WidgetProperty propShowLimits() { return show_limits; } - /** @return 'show_warnings' property */ - public WidgetProperty propShowWarnings() - { + /** + * @return 'show_warnings' property + */ + public WidgetProperty propShowWarnings() { return show_warnings; } - /** @return 'needle_color' property */ - public WidgetProperty propNeedleColor() - { + /** + * @return 'needle_color' property + */ + public WidgetProperty propNeedleColor() { return needle_color; } - /** @return 'knob_color' property */ - public WidgetProperty propKnobColor() - { + /** + * @return 'knob_color' property + */ + public WidgetProperty propKnobColor() { return knob_color; } - public WidgetProperty propKnobSize() { return knobSize; } + public WidgetProperty propKnobSize() { + return knobSize; + } - /** @return 'limits_from_pv' property */ - public WidgetProperty propLimitsFromPV() - { + /** + * @return 'limits_from_pv' property + */ + public WidgetProperty propLimitsFromPV() { return limits_from_pv; } - /** @return 'minimum' property */ - public WidgetProperty propMinimum() - { + /** + * @return 'minimum' property + */ + public WidgetProperty propMinimum() { return minimum; } - /** @return 'maximum' property */ - public WidgetProperty propMaximum() - { + /** + * @return 'maximum' property + */ + public WidgetProperty propMaximum() { return maximum; } - public WidgetProperty propDisplayHorizontal() { return displayHorizontal; } + public WidgetProperty propDisplayHorizontal() { + return displayHorizontal; + } - public WidgetProperty propLevelHiHi ( ) { + public WidgetProperty propLevelHiHi() { return level_hihi; } - public WidgetProperty propLevelHigh ( ) { + public WidgetProperty propLevelHigh() { return level_high; } - public WidgetProperty propLevelLoLo ( ) { + public WidgetProperty propLevelLoLo() { return level_lolo; } - public WidgetProperty propLevelLow ( ) { + public WidgetProperty propLevelLow() { return level_low; } - public WidgetProperty propMinMaxTolerance ( ) { + public WidgetProperty propMinMaxTolerance() { return minMaxTolerance; } - public WidgetProperty propIsGradientEnabled () { + public WidgetProperty propIsGradientEnabled() { return isGradientEnabled; } - public WidgetProperty propIsHighlightActiveRegionEnabled () { + public WidgetProperty propIsHighlightActiveRegionEnabled() { return isHighlightingOfInactiveRegionsEnabled; } @@ -361,11 +426,17 @@ public WidgetProperty propNormalStatusColor() { public WidgetProperty propMinorWarningColor() { return minorWarningColor; } - + public WidgetProperty propMajorWarningColor() { return majorWarningColor; } - public WidgetProperty propNeedleWidth() { return needleWidth; } + public WidgetProperty propNeedleWidth() { + return needleWidth; + } + + public WidgetProperty propDisplayMode() { + return display_mode; + } } diff --git a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java index 812957e1ac..95b3a21190 100644 --- a/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java +++ b/app/display/linearmeter/src/main/java/org/csstudio/display/extra/widgets/linearmeter/RTLinearMeter.java @@ -21,6 +21,11 @@ import java.awt.image.DataBufferInt; import java.io.IOException; import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; import java.util.logging.Level; import javafx.application.Platform; @@ -37,10 +42,11 @@ /** * @author European Spallation Source ERIC - * @version 1.1 + * @version 1.2 * * Version 1.0 implemented by Fredrik Söderberg. * Version 1.1 (some fixes and improvements) by Abraham Wolk. + * Version 1.2 ("Bar" mode in addition to "Needle" mode, more fine-grained concurrency, and some improvements) by Abraham Wolk. * * The Linear Meter graphics design is by Dirk Nordt and Fredrik Söderberg. * @@ -49,9 +55,19 @@ @SuppressWarnings("nls") public class RTLinearMeter extends ImageView { - // Note: All methods that are called from different threads must ensure thread-safety by running - // relevant code on the JavaFX application thread. (The helper function runOnJavaFXThread() can - // be used for this.) + // Note: All methods must ensure thread-safety by acquiring + // readWriteLock.readLock() when reading fields, and by acquiring + // readWriteLock.writeLock() when writing to one or more fields. + // + // The helper functions + // + // - void withReadLock(Runnable runnable) + // - T withReadLock(Supplier supplier) + // - void withWriteLock(Runnable runnable) + // + // have been implemented to facilitate the acquiring and releasing + // of locks. When a read-lock is held, care must be taken not + // to try to acquire the write-lock, since it leads to a deadlock. public RTLinearMeter(double initialValue, int width, @@ -128,41 +144,74 @@ public RTLinearMeter(double initialValue, requestLayout(); } - private boolean showUnits; + private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + protected void withReadLock(Runnable runnable) { + readWriteLock.readLock().lock(); + try { + runnable.run(); + } finally { + readWriteLock.readLock().unlock(); + } + } + protected T withReadLock(Supplier supplier) { + readWriteLock.readLock().lock(); + try { + return supplier.get(); + } finally { + readWriteLock.readLock().unlock(); + } + } + protected void withWriteLock(Runnable runnable) { + readWriteLock.writeLock().lock(); + try { + runnable.run(); + } finally { + readWriteLock.writeLock().unlock(); + } + } + + private boolean showUnits = true; private String units = ""; - private boolean showLimits; + private boolean showLimits = true; - private double loLo; - private double low; - private double high; - private double hiHi; + private double loLo = 0.; + private double low = .0; + private double high = 100.; + private double hiHi = 100.; private static Image warningTriangle = null; public void redrawLinearMeterScale() { - boolean isHorizontal = linearMeterScale.isHorizontal(); - linearMeterScale = new LinearMeterScale(plot_part_listener, - linearMeterScale.getBounds().width, - linearMeterScale.getBounds().height, - linearMeterScale.isHorizontal(), - linearMeterScale.getValueRange().getLow(), - linearMeterScale.getValueRange().getHigh()); - linearMeterScale.setHorizontal(isHorizontal); - if (font != null) { - linearMeterScale.setScaleFont(GraphicsUtils.convert(font)); - } + withReadLock(() -> { + boolean isHorizontal = linearMeterScale.isHorizontal(); + linearMeterScale = new LinearMeterScale(plot_part_listener, + linearMeterScale.getBounds().width, + linearMeterScale.getBounds().height, + linearMeterScale.isHorizontal(), + linearMeterScale.getValueRange().getLow(), + linearMeterScale.getValueRange().getHigh()); + linearMeterScale.setHorizontal(isHorizontal); + }); } private enum WARNING { - NONE, - VALUE_LESS_THAN_MIN, - VALUE_GREATER_THAN_MAX, - MIN_AND_MAX_NOT_DEFINED, - LAG, - NO_UNIT - } + NONE(""), + VALUE_LESS_THAN_MIN("Value < Min"), + VALUE_GREATER_THAN_MAX("Value > Max"), + MIN_AND_MAX_NOT_DEFINED("Min and max are not set"), + LAG("Lag"); + + private final String displayName; + + private WARNING(String displayName) { + this.displayName = displayName; + }; - private WARNING currentWarning = WARNING.NONE; + @Override + public String toString() { + return this.displayName; + } + } /** Colors */ private Color foreground = Color.BLACK; @@ -181,11 +230,13 @@ public void layoutPlotPart(PlotPart plotPart) { } public void refreshPlotPart(PlotPart plotPart) { } }; - private boolean validRange; - private double minMaxTolerance; + private boolean validRange = false; + private double minMaxTolerance = 0.0; public boolean getValidRange() { - return validRange; + return withReadLock(() -> { + return validRange; + }); } /** Optional scale of this linear meter */ @@ -215,17 +266,34 @@ public boolean getValidRange() { private Color majorAlarmColorGradientStartPoint_highlighted = Color.RED; private Color majorAlarmColorGradientEndPoint_highlighted = Color.RED; - Paint normalStatusActiveColor_lowlighted, minorAlarmActiveColor_lowlighted, majorAlarmActiveColor_lowlighted; + Paint normalStatusActiveColor_lowlighted = Color.WHITE; + Paint minorAlarmActiveColor_lowlighted = Color.YELLOW; + Paint majorAlarmActiveColor_lowlighted = Color.RED; - Paint normalStatusActiveColor_highlighted, majorAlarmActiveColor_highlighted, minorAlarmActiveColor_highlighted; + Paint normalStatusActiveColor_highlighted = Color.WHITE; + Paint majorAlarmActiveColor_highlighted = Color.YELLOW; + Paint minorAlarmActiveColor_highlighted = Color.RED; - private int needleWidth; + private int needleWidth = 2; - private Color needleColor; + private Color needleColor = Color.BLACK; - private Boolean isGradientEnabled; + private boolean isGradientEnabled = false; - private Boolean isHighlightActiveRegionEnabled; + private boolean isHighlightActiveRegionEnabled = true; + + enum DisplayMode { + NEEDLE, + BAR + } + + public void setDisplayMode(DisplayMode newDisplayMode) { + withWriteLock(() -> { + this.displayMode = newDisplayMode; + }); + } + + private DisplayMode displayMode = DisplayMode.NEEDLE; private void runOnJavaFXThread(Runnable runnable) { if (Platform.isFxApplicationThread()) { @@ -237,45 +305,49 @@ private void runOnJavaFXThread(Runnable runnable) { } public void setIsGradientEnabled(boolean isGradientEnabled) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.isGradientEnabled = isGradientEnabled; updateActiveColors(); }); } public void setIsHighlightActiveRegionEnabled(boolean isHighlightActiveRegionEnabled) { - runOnJavaFXThread(() -> this.isHighlightActiveRegionEnabled = isHighlightActiveRegionEnabled); + withWriteLock(() -> { + this.isHighlightActiveRegionEnabled = isHighlightActiveRegionEnabled; + }); } private void updateActiveColors() { - if (isGradientEnabled) { - if (linearMeterScale.isHorizontal()) { - majorAlarmActiveColor_lowlighted = createVerticalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_lowlighted, majorAlarmColorGradientEndPoint_lowlighted); - minorAlarmActiveColor_lowlighted = createVerticalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_lowlighted, minorAlarmColorGradientEndPoint_lowlighted); - normalStatusActiveColor_lowlighted = createVerticalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); // The normal status region is never lowlighted. + withWriteLock(() -> { + if (isGradientEnabled) { + if (linearMeterScale.isHorizontal()) { + majorAlarmActiveColor_lowlighted = createVerticalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_lowlighted, majorAlarmColorGradientEndPoint_lowlighted); + minorAlarmActiveColor_lowlighted = createVerticalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_lowlighted, minorAlarmColorGradientEndPoint_lowlighted); + normalStatusActiveColor_lowlighted = createVerticalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); // The normal status region is never lowlighted. - minorAlarmActiveColor_highlighted = createVerticalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_highlighted, minorAlarmColorGradientEndPoint_highlighted); - majorAlarmActiveColor_highlighted = createVerticalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_highlighted, majorAlarmColorGradientEndPoint_highlighted); - normalStatusActiveColor_highlighted = createVerticalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); - } else { - majorAlarmActiveColor_lowlighted = createHorizontalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_lowlighted, majorAlarmColorGradientEndPoint_lowlighted); - minorAlarmActiveColor_lowlighted = createHorizontalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_lowlighted, minorAlarmColorGradientEndPoint_lowlighted); - normalStatusActiveColor_lowlighted = createHorizontalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); // The normal status region is never lowlighted. + minorAlarmActiveColor_highlighted = createVerticalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_highlighted, minorAlarmColorGradientEndPoint_highlighted); + majorAlarmActiveColor_highlighted = createVerticalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_highlighted, majorAlarmColorGradientEndPoint_highlighted); + normalStatusActiveColor_highlighted = createVerticalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); + } else { + majorAlarmActiveColor_lowlighted = createHorizontalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_lowlighted, majorAlarmColorGradientEndPoint_lowlighted); + minorAlarmActiveColor_lowlighted = createHorizontalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_lowlighted, minorAlarmColorGradientEndPoint_lowlighted); + normalStatusActiveColor_lowlighted = createHorizontalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); // The normal status region is never lowlighted. - minorAlarmActiveColor_highlighted = createHorizontalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_highlighted, minorAlarmColorGradientEndPoint_highlighted); - majorAlarmActiveColor_highlighted = createHorizontalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_highlighted, majorAlarmColorGradientEndPoint_highlighted); - normalStatusActiveColor_highlighted = createHorizontalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); + minorAlarmActiveColor_highlighted = createHorizontalGradientPaint(lowRectangle, minorAlarmColorGradientStartPoint_highlighted, minorAlarmColorGradientEndPoint_highlighted); + majorAlarmActiveColor_highlighted = createHorizontalGradientPaint(loLoRectangle, majorAlarmColorGradientStartPoint_highlighted, majorAlarmColorGradientEndPoint_highlighted); + normalStatusActiveColor_highlighted = createHorizontalGradientPaint(normalRectangle, normalStatusColorGradientStartPoint_highlighted, normalStatusColorGradientEndPoint_highlighted); + } } - } - else { - normalStatusActiveColor_lowlighted = normalStatusColor_highlighted; // The normal status region is never lowlighted. - majorAlarmActiveColor_lowlighted = majorAlarmColor_lowlighted; - minorAlarmActiveColor_lowlighted = minorAlarmColor_lowlighted; + else { + normalStatusActiveColor_lowlighted = normalStatusColor_highlighted; // The normal status region is never lowlighted. + majorAlarmActiveColor_lowlighted = majorAlarmColor_lowlighted; + minorAlarmActiveColor_lowlighted = minorAlarmColor_lowlighted; - normalStatusActiveColor_highlighted = normalStatusColor_highlighted; - minorAlarmActiveColor_highlighted = minorAlarmColor_highlighted; - majorAlarmActiveColor_highlighted = majorAlarmColor_highlighted; - } + normalStatusActiveColor_highlighted = normalStatusColor_highlighted; + minorAlarmActiveColor_highlighted = minorAlarmColor_highlighted; + majorAlarmActiveColor_highlighted = majorAlarmColor_highlighted; + } + }); } private Color computeGradientStartPoint(Color color) { @@ -332,7 +404,7 @@ private Color computeLowlightedColor(Color color) { } public void setNormalStatusColor(Color normalStatusColor) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.normalStatusColor_lowlighted = computeLowlightedColor(normalStatusColor); this.normalStatusColor_highlighted = normalStatusColor; @@ -347,7 +419,7 @@ public void setNormalStatusColor(Color normalStatusColor) { } public void setMinorAlarmColor(Color minorAlarmColor) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.minorAlarmColor_lowlighted = computeLowlightedColor(minorAlarmColor); this.minorAlarmColor_highlighted = minorAlarmColor; @@ -362,7 +434,7 @@ public void setMinorAlarmColor(Color minorAlarmColor) { } public void setMajorAlarmColor(Color majorAlarmColor) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.majorAlarmColor_lowlighted = computeLowlightedColor(majorAlarmColor); this.majorAlarmColor_highlighted = majorAlarmColor; @@ -377,64 +449,74 @@ public void setMajorAlarmColor(Color majorAlarmColor) { } public void setNeedleWidth(int needleWidth) { - runOnJavaFXThread(() -> this.needleWidth = needleWidth); + withWriteLock(() -> { + this.needleWidth = needleWidth; + }); } public void setNeedleColor(Color needleColor) { - runOnJavaFXThread(() -> this.needleColor = needleColor); + withWriteLock(() -> { + this.needleColor = needleColor; + }); } public void setShowUnits(boolean newValue) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { showUnits = newValue; - updateMeterBackground(); - redrawIndicator(currentValue, currentWarning); + redraw(); }); } public void setUnits(String newValue) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { if (!units.equals(newValue)) { units = newValue; - updateMeterBackground(); - redrawIndicator(currentValue, currentWarning); + redraw(); } }); } + private void logNewWarningIfDifferent(WARNING oldWarning, WARNING newWarning) { + if (oldWarning != newWarning) { + logger.log(Level.WARNING, newWarning.toString() + " on Linear Meter!"); + } + } public void setShowLimits(boolean newValue) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { + WARNING oldWarning = determineWarning(); showLimits = newValue; - updateMeterBackground(); - determineWarning(); - redrawIndicator(currentValue, currentWarning); + WARNING newWarning = determineWarning(); + logNewWarningIfDifferent(oldWarning, newWarning); + redraw(); }); } public void setRange(double minimum, double maximum, boolean validRange) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.validRange = validRange; linearMeterScale.setValueRange(minimum, maximum); - - updateMeterBackground(); - redrawIndicator(currentValue, currentWarning); }); + redraw(); } public void setMinMaxTolerance(double minMaxTolerance) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { + WARNING oldWarning = determineWarning(); this.minMaxTolerance = minMaxTolerance; - determineWarning(); - redrawIndicator(currentValue, currentWarning); + WARNING newWarning = determineWarning(); + logNewWarningIfDifferent(oldWarning, newWarning); + redrawIndicator(currentValue, newWarning); }); } public double getLoLo() { - return loLo; + return withReadLock(() -> { + return loLo; + }); } public void setLoLo(double loLo) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.loLo = loLo; layout(); updateMeterBackground(); @@ -442,11 +524,13 @@ public void setLoLo(double loLo) { } public double getLow() { - return low; + return withReadLock(() -> { + return low; + }); } public void setLow(double low) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.low = low; layout(); updateMeterBackground(); @@ -454,11 +538,13 @@ public void setLow(double low) { } public double getHigh() { - return high; + return withReadLock(() -> { + return high; + }); } public void setHigh(double high) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.high = high; layout(); updateMeterBackground(); @@ -466,11 +552,13 @@ public void setHigh(double high) { } public double getHiHi() { - return hiHi; + return withReadLock(() -> { + return hiHi; + }); } public void setHiHi(double hiHi) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.hiHi = hiHi; layout(); updateMeterBackground(); @@ -480,72 +568,120 @@ public void setHiHi(double hiHi) { private Color knobColor = new Color(0, 0, 0, 255); public void setKnobColor(Color knobColor) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.knobColor = knobColor; requestLayout(); }); } - private int knobSize; + private int knobSize = 1; public void setKnobSize(int knobSize) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { this.knobSize = knobSize; requestLayout(); }); } - private BufferedImage meter_background = null; + private void redraw() { + withWriteLock(() -> { + updateMeterBackground(); + redrawIndicator(currentValue, determineWarning()); + }); + } - private WritableImage awt_jfx_convert_buffer = null; + private record ImageBuffers(BufferedImage meterBackground, + BufferedImage combinedBufferedImage, + WritableImage awtJFXConvertBuffer, + int width, + int height) { } - /** Redraw on UI thread by adding needle to 'meter_background' */ - private void redrawIndicator (double value, WARNING warning) - { - runOnJavaFXThread(() -> { - if (meter_background != null) - { - if (meter_background.getType() != BufferedImage.TYPE_INT_ARGB){ - throw new IllegalPathStateException("Need TYPE_INT_ARGB for direct buffer access, not " + meter_background.getType()); + private Optional maybeImageBuffers = Optional.empty(); + /** + * Redraw on UI thread by adding needle to 'meter_background' + */ + private void redrawIndicator(double value, WARNING warning) { + withReadLock(() -> { + if (maybeImageBuffers.isEmpty()) { + return; + } + else { + ImageBuffers imageBuffers = maybeImageBuffers.get(); + BufferedImage meterBackground = imageBuffers.meterBackground(); + BufferedImage combined = imageBuffers.combinedBufferedImage(); + WritableImage awtJFXConvertBuffer = imageBuffers.awtJFXConvertBuffer; + int width = imageBuffers.width(); + int height = imageBuffers.height(); + if (meterBackground.getType() != BufferedImage.TYPE_INT_ARGB) { + throw new IllegalPathStateException("Need TYPE_INT_ARGB for direct buffer access, not " + meterBackground.getType()); } - BufferedImage combined = new BufferedImage(linearMeterScale.getBounds().width, linearMeterScale.getBounds().height, BufferedImage.TYPE_INT_ARGB); - int[] src = ((DataBufferInt) meter_background.getRaster().getDataBuffer()).getData(); + int[] src = ((DataBufferInt) meterBackground.getRaster().getDataBuffer()).getData(); int[] dest = ((DataBufferInt) combined.getRaster().getDataBuffer()).getData(); - System.arraycopy(src, 0, dest, 0, linearMeterScale.getBounds().width * linearMeterScale.getBounds().height); + System.arraycopy(src, 0, dest, 0, width * height); // Add needle & label Graphics2D gc = combined.createGraphics(); - drawValue(gc, value); - drawWarning(gc, warning); + DisplayMode displayMode = this.displayMode; + if (displayMode.equals(DisplayMode.NEEDLE)) { + redrawBorderAroundLinearMeter(gc); // When drawing a needle, redraw the border *before* drawing the needle. + drawNeedle(gc, value); + } + else if (displayMode.equals(DisplayMode.BAR)) { + drawBar(gc, value); + redrawBorderAroundLinearMeter(gc); // When drawing a bar, redraw the border *after* drawing the bar. + } + else { + throw new RuntimeException("Unhandled case"); + } + + if (warning != WARNING.NONE) { + drawWarningText(gc, warning.toString()); + } if (showUnits) { drawUnit(gc); } // Convert to JFX image and show - if (awt_jfx_convert_buffer == null || - awt_jfx_convert_buffer.getWidth() != linearMeterScale.getBounds().width || - awt_jfx_convert_buffer.getHeight() != linearMeterScale.getBounds().height) - awt_jfx_convert_buffer = new WritableImage(linearMeterScale.getBounds().width, linearMeterScale.getBounds().height); - - awt_jfx_convert_buffer.getPixelWriter().setPixels(0, 0, linearMeterScale.getBounds().width, linearMeterScale.getBounds().height, PixelFormat.getIntArgbInstance(), dest, 0, linearMeterScale.getBounds().width); + runOnJavaFXThread(() -> { + withWriteLock(() -> { + awtJFXConvertBuffer.getPixelWriter().setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), dest, 0, width); + setImage(awtJFXConvertBuffer); + }); + }); - setImage(awt_jfx_convert_buffer); logger.log(Level.FINE, "Redraw meter"); } }); - }; + } + + private void redrawBorderAroundLinearMeter(Graphics2D gc) { + { + int width; + int height; + + // Re-draw border around widget: + width = linearMeterScale.getBounds().width - marginLeft - marginRight - 1; + height = linearMeterScale.getBounds().height - marginAbove - marginBelow - 1; + paintRectangle(gc, + new Rectangle(marginLeft, + marginAbove, + width, + height), + TRANSPARENT); + } + } /** Call to update size of meter * * @param width * @param height */ - public void setSize(int width, int height) - { - runOnJavaFXThread(() -> { + public void setSize(int width, int height) { + withWriteLock(() -> { linearMeterScale.setBounds(0, 0, width, height); + updateMeterBackground(); layout(); updateActiveColors(); requestLayout(); @@ -553,9 +689,8 @@ public void setSize(int width, int height) } /** @param color Foreground (labels, tick marks) color */ - public void setForeground(javafx.scene.paint.Color color) - { - runOnJavaFXThread(() -> { + public void setForeground(javafx.scene.paint.Color color) { + withWriteLock(() -> { foreground = GraphicsUtils.convert(color); linearMeterScale.setColor(color); }); @@ -564,13 +699,15 @@ public void setForeground(javafx.scene.paint.Color color) /** @param color Background color */ public void setBackground(javafx.scene.paint.Color color) { - runOnJavaFXThread(() -> background = GraphicsUtils.convert(color)); + withWriteLock(() -> { + background = GraphicsUtils.convert(color); + }); } /** @param font Label font */ public void setFont(javafx.scene.text.Font font) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { linearMeterScale.setScaleFont(font); this.font = GraphicsUtils.convert(font); }); @@ -579,16 +716,18 @@ public void setFont(javafx.scene.text.Font font) private boolean showWarnings = true; public void setShowWarnings(boolean showWarnings) { - runOnJavaFXThread(() -> { this.showWarnings = showWarnings; }); + withWriteLock(() -> { + this.showWarnings = showWarnings; + }); } private boolean lag = false; - private Boolean isValueWaitingToBeDrawn = false; - private double valueWaitingToBeDrawn; + private boolean isValueWaitingToBeDrawn = false; + private double valueWaitingToBeDrawn = Double.NaN; /** @param newValue Current value */ public void setCurrentValue(double newValue) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { valueWaitingToBeDrawn = newValue; if (isValueWaitingToBeDrawn) { @@ -597,104 +736,87 @@ public void setCurrentValue(double newValue) else { isValueWaitingToBeDrawn = true; - Platform.runLater(() -> { - drawNewValue(valueWaitingToBeDrawn); - isValueWaitingToBeDrawn = false; - lag = false; - }); + drawNewValue(valueWaitingToBeDrawn); + isValueWaitingToBeDrawn = false; + lag = false; + } + }); + } + + private Optional computeIndicatorPosition(double value) { + if (Double.isNaN(value)) { + return Optional.empty(); + } + return withReadLock(() -> { + if (linearMeterScale.isHorizontal()) { + return Optional.of((int) Math.round(marginLeft + pixelsPerScaleUnit * (value - linearMeterScale.getValueRange().getLow()))); + } + else { + return Optional.of((int) Math.round(linearMeterScale.getBounds().height - marginBelow - pixelsPerScaleUnit * (value - linearMeterScale.getValueRange().getLow()))); } }); } private void drawNewValue(double newValue) { - double oldValue = currentValue; - currentValue = newValue; + withWriteLock(() -> { + WARNING oldWarning = determineWarning(); + AtomicReference newValueAtomicReference = new AtomicReference<>(newValue); // Workaround, since captured variables need to be effectively final in Java. + double oldValue = currentValue; + Optional maybeOldIndicatorPosition = computeIndicatorPosition(oldValue); + currentValue = newValueAtomicReference.get(); + + if (newValueAtomicReference.get() > linearMeterScale.getValueRange().getHigh() && newValueAtomicReference.get() <= linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { + newValueAtomicReference.set(linearMeterScale.getValueRange().getHigh()); + } + if (newValueAtomicReference.get() < linearMeterScale.getValueRange().getLow() && newValueAtomicReference.get() >= linearMeterScale.getValueRange().getLow() - minMaxTolerance) { + newValueAtomicReference.set(linearMeterScale.getValueRange().getLow()); + } - if (newValue > linearMeterScale.getValueRange().getHigh() && newValue <= linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { - newValue = linearMeterScale.getValueRange().getHigh(); - } - if (newValue < linearMeterScale.getValueRange().getLow() && newValue >= linearMeterScale.getValueRange().getLow() - minMaxTolerance) { - newValue = linearMeterScale.getValueRange().getLow(); - } + WARNING newWarning = determineWarning(); + logNewWarningIfDifferent(oldWarning, newWarning); - if (oldValue != newValue) { - if (!Double.isNaN(newValue)){ - int newIndicatorPosition; - if (linearMeterScale.isHorizontal()) { - newIndicatorPosition = (int) (marginLeft + pixelsPerScaleUnit * (newValue - linearMeterScale.getValueRange().getLow())); - } - else { - newIndicatorPosition = (int) (linearMeterScale.getBounds().height - marginBelow - pixelsPerScaleUnit * (newValue - linearMeterScale.getValueRange().getLow())); + if (oldValue != newValueAtomicReference.get()) { + if (!Double.isNaN(newValueAtomicReference.get())){ + Optional maybeNewIndicatorPosition = computeIndicatorPosition(newValue); + boolean indicatorPositionHasChanged = maybeNewIndicatorPosition.isPresent() != maybeOldIndicatorPosition.isPresent() || maybeOldIndicatorPosition.isPresent() && maybeNewIndicatorPosition.isPresent() && maybeOldIndicatorPosition.get() != maybeNewIndicatorPosition.get(); + if (indicatorPositionHasChanged || determineWarning() != newWarning) { + redrawIndicator(newValueAtomicReference.get(), newWarning); + } } - WARNING newWarning = determineWarning(); - if (currentIndicatorPosition == null || currentIndicatorPosition != newIndicatorPosition || currentWarning != newWarning) { - redrawIndicator(newValue, newWarning); + else if (!Double.isNaN(oldValue)) { + redrawIndicator(newValueAtomicReference.get(), newWarning); } } - else if (!Double.isNaN(oldValue)) { - redrawIndicator(newValue, determineWarning()); - } - } + }); } private WARNING determineWarning() { - if (!showWarnings) { - return WARNING.NONE; - } - else if (lag) { - return WARNING.LAG; - } - else if (showUnits && units.equals("")) { - return WARNING.NO_UNIT; - } - else if (!validRange) { - return WARNING.MIN_AND_MAX_NOT_DEFINED; - } - else if (currentValue < linearMeterScale.getValueRange().getLow() - minMaxTolerance) { - return WARNING.VALUE_LESS_THAN_MIN; - } - else if (currentValue > linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { - return WARNING.VALUE_GREATER_THAN_MAX; - } - else { - return WARNING.NONE; - } - } - - private void drawWarning(Graphics2D gc, WARNING warning) { - if (warning != WARNING.NONE) { - String warningText = ""; - if (warning == WARNING.VALUE_LESS_THAN_MIN) { - warningText = "VALUE < MIN"; - + return withReadLock(() -> { + if (!showWarnings) { + return WARNING.NONE; } - else if (warning == WARNING.VALUE_GREATER_THAN_MAX) { - warningText = "VALUE > MAX"; - + else if (lag) { + return WARNING.LAG; } - else if (warning == WARNING.NO_UNIT) { - warningText = "NO UNIT DEFINED"; + else if (!validRange) { + return WARNING.MIN_AND_MAX_NOT_DEFINED; } - else if (warning == WARNING.MIN_AND_MAX_NOT_DEFINED) { - warningText = "MIN AND MAX ARE NOT SET"; - + else if (currentValue < linearMeterScale.getValueRange().getLow() - minMaxTolerance) { + return WARNING.VALUE_LESS_THAN_MIN; } - else if (warning == WARNING.LAG) { - warningText = "LAG"; + else if (currentValue > linearMeterScale.getValueRange().getHigh() + minMaxTolerance) { + return WARNING.VALUE_GREATER_THAN_MAX; } - - drawWarningText(gc, warningText); - if (currentWarning != warning) { - logger.log(Level.WARNING, warningText + " on Linear Meter!"); + else { + return WARNING.NONE; } - } - currentWarning = warning; + }); } /** @param visible Whether the scale must be displayed or not. */ public void setScaleVisible (boolean visible) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { linearMeterScale.setVisible(visible); updateMeterBackground(); }); @@ -703,26 +825,31 @@ public void setScaleVisible (boolean visible) /** Request a complete redraw with new layout */ private void requestLayout() { - updateMeterBackground(); - redrawIndicator(currentValue, currentWarning); + withWriteLock(() -> { + updateMeterBackground(); + WARNING warning = determineWarning(); + redrawIndicator(currentValue, warning); + }); } private void computeLayout() { - logger.log(Level.FINE, "computeLayout"); - layout(); + withReadLock(() -> { + logger.log(Level.FINE, "computeLayout"); + layout(); - if (linearMeterScale.isHorizontal()) { - linearMeterScale.configure( - loLoRectangle.x, - lowRectangle.y + loLoRectangle.height, - pixelsPerScaleUnit); - } else { - linearMeterScale.configure( - linearMeterScale.getBounds().width - marginRight, - linearMeterScale.getBounds().height - marginBelow, - pixelsPerScaleUnit); - } + if (linearMeterScale.isHorizontal()) { + linearMeterScale.configure( + loLoRectangle.x, + lowRectangle.y + loLoRectangle.height, + pixelsPerScaleUnit); + } else { + linearMeterScale.configure( + linearMeterScale.getBounds().width - marginRight, + linearMeterScale.getBounds().height - marginBelow, + pixelsPerScaleUnit); + } + }); } /** Draw meter background (scale) into image buffer @@ -730,60 +857,68 @@ private void computeLayout() */ private void updateMeterBackground() { - int width = linearMeterScale.getBounds().width; - int height = linearMeterScale.getBounds().height; + withWriteLock(() -> { + int width = linearMeterScale.getBounds().width; + int height = linearMeterScale.getBounds().height; - if (width <= 0 || height <= 0){ - return; - } + if (width <= 0 || height <= 0){ + return; + } - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D gc = image.createGraphics(); + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D gc = image.createGraphics(); - gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gc.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - gc.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); - gc.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gc.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + gc.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + gc.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - linearMeterScale.computeTicks(gc); - computeLayout(); + linearMeterScale.computeTicks(gc); + computeLayout(); - gc.setBackground(background); - gc.clearRect(0, 0, width, height); + gc.setBackground(background); + gc.clearRect(0, 0, width, height); - linearMeterScale.paint(gc, new Rectangle(0,0,0,0)); - paintMeter(gc); + linearMeterScale.paint(gc, new Rectangle(0,0,0,0)); + paintMeter(gc); - meter_background = image; + { + BufferedImage combined = new BufferedImage(linearMeterScale.getBounds().width, linearMeterScale.getBounds().height, BufferedImage.TYPE_INT_ARGB); + WritableImage awtJFXConvertBuffer = new WritableImage(linearMeterScale.getBounds().width, linearMeterScale.getBounds().height); + maybeImageBuffers = Optional.of(new ImageBuffers(image, combined, awtJFXConvertBuffer, width, height)); + } + }); } private void paintMeter(Graphics2D graphics) { - Color color = graphics.getColor(); - if (showLimits) { - if (isHighlightActiveRegionEnabled) { - paintRectangle(graphics, normalRectangle, normalStatusActiveColor_lowlighted); - paintRectangle(graphics, lowRectangle, minorAlarmActiveColor_lowlighted); - paintRectangle(graphics, highRectangle, minorAlarmActiveColor_lowlighted); - paintRectangle(graphics, loLoRectangle, majorAlarmActiveColor_lowlighted); - paintRectangle(graphics, hiHiRectangle, majorAlarmActiveColor_lowlighted); + withReadLock(() -> { + Color color = graphics.getColor(); + if (showLimits) { + if (isHighlightActiveRegionEnabled) { + paintRectangle(graphics, normalRectangle, normalStatusActiveColor_lowlighted); + paintRectangle(graphics, lowRectangle, minorAlarmActiveColor_lowlighted); + paintRectangle(graphics, highRectangle, minorAlarmActiveColor_lowlighted); + paintRectangle(graphics, loLoRectangle, majorAlarmActiveColor_lowlighted); + paintRectangle(graphics, hiHiRectangle, majorAlarmActiveColor_lowlighted); + } + else { + paintRectangle(graphics, normalRectangle, normalStatusActiveColor_highlighted); + paintRectangle(graphics, lowRectangle, minorAlarmActiveColor_highlighted); + paintRectangle(graphics, highRectangle, minorAlarmActiveColor_highlighted); + paintRectangle(graphics, loLoRectangle, majorAlarmActiveColor_highlighted); + paintRectangle(graphics, hiHiRectangle, majorAlarmActiveColor_highlighted); + } } else { - paintRectangle(graphics, normalRectangle, normalStatusActiveColor_highlighted); - paintRectangle(graphics, lowRectangle, minorAlarmActiveColor_highlighted); - paintRectangle(graphics, highRectangle, minorAlarmActiveColor_highlighted); - paintRectangle(graphics, loLoRectangle, majorAlarmActiveColor_highlighted); - paintRectangle(graphics, hiHiRectangle, majorAlarmActiveColor_highlighted); + paintRectangle(graphics, + new Rectangle(marginLeft, + marginAbove, + linearMeterScale.getBounds().width - marginLeft - marginRight, + linearMeterScale.getBounds().height - marginAbove - marginBelow), + normalStatusActiveColor_lowlighted); } - } - else { - paintRectangle(graphics, - new Rectangle(marginLeft, - marginAbove, - linearMeterScale.getBounds().width - marginLeft - marginRight, - linearMeterScale.getBounds().height - marginAbove - marginBelow), - normalStatusActiveColor_lowlighted); - } - graphics.setColor(color); + graphics.setColor(color); + }); } private GradientPaint createHorizontalGradientPaint(Rectangle rectangle, @@ -804,228 +939,327 @@ private GradientPaint createVerticalGradientPaint(Rectangle rectangle, return gradientPaint; } - Integer currentIndicatorPosition; /** Draw needle and label for current value */ - private void drawValue(Graphics2D gc, double value) { + private void drawNeedle(Graphics2D gc, double value) { + withReadLock(() -> { + if (Double.isNaN(value)) { + return; + } + else { + Stroke oldStroke = gc.getStroke(); + Paint oldPaint = gc.getPaint(); + RenderingHints oldrenderingHints = gc.getRenderingHints(); + + gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gc.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + gc.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + gc.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + + if (showLimits) { + if (isHighlightActiveRegionEnabled) { + if (value <= loLo) { + paintRectangle(gc, loLoRectangle, majorAlarmColor_highlighted); + } + else if (value >= hiHi) { + paintRectangle(gc, hiHiRectangle, majorAlarmColor_highlighted); + } + else if (value <= low && value > loLo) { + paintRectangle(gc, lowRectangle, minorAlarmActiveColor_highlighted); + } + else if (value >= high && value < hiHi) { + paintRectangle(gc, highRectangle, minorAlarmActiveColor_highlighted); + } + else { + paintRectangle(gc, normalRectangle, normalStatusActiveColor_highlighted); + } + } + } - if (Double.isNaN(value)) { - currentIndicatorPosition = null; - } - else { - Stroke oldStroke = gc.getStroke(); - Paint oldPaint = gc.getPaint(); - RenderingHints oldrenderingHints = gc.getRenderingHints(); + if (linearMeterScale.isHorizontal()) { + if (value >= linearMeterScale.getValueRange().getLow() && value <= linearMeterScale.getValueRange().getHigh()) { - gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gc.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - gc.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); - gc.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + Optional maybeCurrentIndicatorPosition = computeIndicatorPosition(value); - if (showLimits) { - if (isHighlightActiveRegionEnabled) { - if (value <= loLo) { - paintRectangle(gc, loLoRectangle, majorAlarmColor_highlighted); - } - else if (value >= hiHi) { - paintRectangle(gc, hiHiRectangle, majorAlarmColor_highlighted); - } - else if (value <= low && value > loLo) { - paintRectangle(gc, lowRectangle, minorAlarmActiveColor_highlighted); - } - else if (value >= high && value < hiHi) { - paintRectangle(gc, highRectangle, minorAlarmActiveColor_highlighted); - } - else { - paintRectangle(gc, normalRectangle, normalStatusActiveColor_highlighted); - } - } - } + if (maybeCurrentIndicatorPosition.isPresent()) { + int currentIndicatorPosition = maybeCurrentIndicatorPosition.get(); - if (linearMeterScale.isHorizontal()) { - if (value >= linearMeterScale.getValueRange().getLow() && value <= linearMeterScale.getValueRange().getHigh()) { + if (knobSize > 0) { + int[] XVal = { currentIndicatorPosition - (int) Math.round((1.0 * knobSize) / 4.0), + currentIndicatorPosition + (int) Math.round((1.0 * knobSize) / 4.0), + currentIndicatorPosition }; - currentIndicatorPosition = (int) (marginLeft + pixelsPerScaleUnit * (value - linearMeterScale.getValueRange().getLow())); + int[] YVal = { 0, 0, marginAbove - 2 }; - if (knobSize > 0) { - int[] XVal = { currentIndicatorPosition - (int) Math.round((1.0 * knobSize) / 4.0), - currentIndicatorPosition + (int) Math.round((1.0 * knobSize) / 4.0), - currentIndicatorPosition }; + gc.setStroke(AxisPart.TICK_STROKE); + gc.setColor(knobColor); + gc.fillPolygon(XVal, YVal, 3); + gc.setColor(knobColor); + gc.drawPolygon(XVal, YVal, 3); + } - int[] YVal = { 0, 0, marginAbove - 2 }; + if (needleWidth > 0) { + gc.setStroke(new BasicStroke((float) needleWidth)); + gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + gc.setPaint(needleColor); - gc.setStroke(AxisPart.TICK_STROKE); - gc.setColor(knobColor); - gc.fillPolygon(XVal, YVal, 3); - gc.setColor(knobColor); - gc.drawPolygon(XVal, YVal, 3); + int y1 = marginAbove + needleWidth / 2 + 1; + int y2 = linearMeterScale.getBounds().height - marginBelow - (needleWidth - 1) / 2 - 2; + + gc.drawLine(currentIndicatorPosition, y1, currentIndicatorPosition, y2); + } + } } + } else { + if (value >= linearMeterScale.getValueRange().getLow() && value <= linearMeterScale.getValueRange().getHigh()) { - if (needleWidth > 0) { - gc.setStroke(new BasicStroke((float) needleWidth)); - gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - gc.setPaint(needleColor); + Optional maybeCurrentIndicatorPosition = computeIndicatorPosition(value); + if (maybeCurrentIndicatorPosition.isPresent()) { + int currentIndicatorPosition = maybeCurrentIndicatorPosition.get() - 1; - int y1 = marginAbove + needleWidth / 2 + 1; - int y2 = linearMeterScale.getBounds().height - marginBelow - (needleWidth - 1) / 2 - 1; + if (knobSize > 0) { + int[] YVal = { currentIndicatorPosition + (int) Math.round((1.0 * knobSize / 4.0)), + currentIndicatorPosition - (int) Math.round((1.0 * knobSize / 4.0)), + currentIndicatorPosition }; - gc.drawLine(currentIndicatorPosition, y1, currentIndicatorPosition, y2); - } - } - } else { - if (value >= linearMeterScale.getValueRange().getLow() && value <= linearMeterScale.getValueRange().getHigh()) { + int[] XVal = { 0, 0, marginLeft - 2 }; - currentIndicatorPosition = (int) (linearMeterScale.getBounds().height - marginBelow - pixelsPerScaleUnit * (value - linearMeterScale.getValueRange().getLow())); + gc.setStroke(AxisPart.TICK_STROKE); + gc.setColor(knobColor); + gc.fillPolygon(XVal, YVal, 3); + gc.setColor(knobColor); + gc.drawPolygon(XVal, YVal, 3); + } - if (knobSize > 0) { - int[] YVal = { currentIndicatorPosition + (int) Math.round((1.0 * knobSize / 4.0)), - currentIndicatorPosition - (int) Math.round((1.0 * knobSize / 4.0)), - currentIndicatorPosition }; + if (needleWidth > 0) { + gc.setStroke(new BasicStroke((float) needleWidth)); + gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + gc.setPaint(needleColor); - int[] XVal = { 0, 0, marginLeft - 2 }; + int x1 = marginLeft + (needleWidth)/2 + 1; + int x2 = linearMeterScale.getBounds().width - marginRight - (needleWidth+1)/2 - 1; - gc.setStroke(AxisPart.TICK_STROKE); - gc.setColor(knobColor); - gc.fillPolygon(XVal, YVal, 3); - gc.setColor(knobColor); - gc.drawPolygon(XVal, YVal, 3); + gc.drawLine(x1, currentIndicatorPosition, x2, currentIndicatorPosition); + } + } } + } - if (needleWidth > 0) { - gc.setStroke(new BasicStroke((float) needleWidth)); - gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); - gc.setPaint(needleColor); - - int x1 = marginLeft + (needleWidth)/2 + 1; - int x2 = linearMeterScale.getBounds().width - marginRight - (needleWidth+1)/2; + gc.setRenderingHints(oldrenderingHints); + gc.setStroke(oldStroke); + gc.setPaint(oldPaint); + } + }); + } + private final Color TRANSPARENT = new Color(0, 0, 0, 0); - gc.drawLine(x1, currentIndicatorPosition, x2, currentIndicatorPosition); + private void drawBar(Graphics2D gc, double value) { + withReadLock(() -> { + if (Double.isNaN(value)) { + return; + } + else { + Stroke oldStroke = gc.getStroke(); + Paint oldPaint = gc.getPaint(); + RenderingHints oldrenderingHints = gc.getRenderingHints(); + + gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gc.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + gc.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); + gc.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + + Optional maybeCurrentIndicatorPosition = computeIndicatorPosition(value <= linearMeterScale.getValueRange().getHigh() ? value : linearMeterScale.getValueRange().getHigh()); + + if (maybeCurrentIndicatorPosition.isPresent()) { + if (linearMeterScale.isHorizontal()) { + int currentIndicatorPosition = maybeCurrentIndicatorPosition.get() + 1; + if (value > linearMeterScale.getValueRange().getLow()) { + if (isHighlightActiveRegionEnabled) { + if (value <= loLo) { + gc.setPaint(majorAlarmColor_highlighted); + } + else if (value >= hiHi) { + gc.setPaint(majorAlarmColor_highlighted); + } + else if (value <= low && value > loLo) { + gc.setPaint(minorAlarmActiveColor_highlighted); + } + else if (value >= high && value < hiHi) { + gc.setPaint(minorAlarmActiveColor_highlighted); + } + else { + gc.setPaint(needleColor); + } + } + else { + gc.setPaint(needleColor); + } + // Draw the bar: + gc.fillRect(marginLeft, marginAbove, currentIndicatorPosition - marginLeft, meterBreadth); + } + } else { + int currentIndicatorPosition = maybeCurrentIndicatorPosition.get() - 1; + if (value > linearMeterScale.getValueRange().getLow()) { + if (isHighlightActiveRegionEnabled) { + if (value <= loLo) { + gc.setPaint(majorAlarmColor_highlighted); + } + else if (value >= hiHi) { + gc.setPaint(majorAlarmColor_highlighted); + } + else if (value <= low && value > loLo) { + gc.setPaint(minorAlarmActiveColor_highlighted); + } + else if (value >= high && value < hiHi) { + gc.setPaint(minorAlarmActiveColor_highlighted); + } + else { + gc.setPaint(needleColor); + } + } + else { + gc.setPaint(needleColor); + } + // Draw the bar: + gc.fillRect(marginLeft, currentIndicatorPosition, meterBreadth - 1, linearMeterScale.getBounds().height - currentIndicatorPosition - marginBelow); + } } } + + gc.setRenderingHints(oldrenderingHints); + gc.setStroke(oldStroke); + gc.setPaint(oldPaint); } - gc.setRenderingHints(oldrenderingHints); - gc.setStroke(oldStroke); - gc.setPaint(oldPaint); - } + }); } /** Should be invoked when meter no longer used to release resources */ public void dispose() { - // Release memory ASAP - meter_background = null; + withWriteLock(() -> { + // Release memory ASAP + maybeImageBuffers = Optional.empty(); + }); } public void setHorizontal(boolean horizontal) { - runOnJavaFXThread(() -> { + withWriteLock(() -> { linearMeterScale.setHorizontal(horizontal); redrawLinearMeterScale(); updateMeterBackground(); - redrawIndicator(currentValue, currentWarning); + redrawIndicator(currentValue, determineWarning()); }); } private void drawUnit(Graphics2D gc) { - int center_x = marginLeft + (linearMeterScale.getBounds().width - marginLeft - marginRight) / 2; - int center_y = linearMeterScale.getBounds().height; - RenderingHints renderingHints = gc.getRenderingHints(); - gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gc.setFont(font); - gc.setColor(Color.BLACK); - FontMetrics fontMetrics = gc.getFontMetrics(gc.getFont()); - String stringToPrint = "[" + units + "]"; - int delta_x = fontMetrics.stringWidth(stringToPrint) / 2; - int delta_y = fontMetrics.getMaxDescent(); - - gc.drawString(stringToPrint, - center_x - delta_x, - center_y - delta_y); - gc.setRenderingHints(renderingHints); + withReadLock(() -> { + int center_x = marginLeft + (linearMeterScale.getBounds().width - marginLeft - marginRight) / 2; + int center_y = linearMeterScale.getBounds().height; + RenderingHints renderingHints = gc.getRenderingHints(); + gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gc.setFont(font); + gc.setColor(Color.BLACK); + FontMetrics fontMetrics = gc.getFontMetrics(gc.getFont()); + String stringToPrint = "[" + units + "]"; + int delta_x = fontMetrics.stringWidth(stringToPrint) / 2; + int delta_y = fontMetrics.getMaxDescent(); + + gc.drawString(stringToPrint, + center_x - delta_x, + center_y - delta_y); + gc.setRenderingHints(renderingHints); + }); } private void drawWarning_horizontal(Graphics2D gc, String warningText) { - int center_x = marginLeft + (linearMeterScale.getBounds().width - marginLeft - marginRight) / 2; - int center_y = marginAbove + (linearMeterScale.getBounds().height - marginAbove - marginBelow) / 2; - gc.setFont(font); - gc.setColor(Color.BLACK); - FontMetrics fontMetrics = gc.getFontMetrics(gc.getFont()); - int delta_x = (warningTriangle.getWidth(null) + fontMetrics.stringWidth(warningText)) / 2; - int delta_y = fontMetrics.getAscent() / 2; - - gc.drawImage(warningTriangle, - center_x - delta_x, - center_y + delta_y - warningTriangle.getHeight(null) / 2 - 3 * fontMetrics.getAscent() / 8, - null); - gc.drawString(warningText, - center_x - delta_x + warningTriangle.getWidth(null) + 2, - center_y + delta_y); + withReadLock(() -> { + int center_x = marginLeft + (linearMeterScale.getBounds().width - marginLeft - marginRight) / 2; + int center_y = marginAbove + (linearMeterScale.getBounds().height - marginAbove - marginBelow) / 2; + gc.setFont(font); + gc.setColor(Color.BLACK); + FontMetrics fontMetrics = gc.getFontMetrics(gc.getFont()); + int delta_x = (warningTriangle.getWidth(null) + fontMetrics.stringWidth(warningText)) / 2; + int delta_y = fontMetrics.getAscent() / 2; + + gc.drawImage(warningTriangle, + center_x - delta_x, + center_y + delta_y - warningTriangle.getHeight(null) / 2 - 3 * fontMetrics.getAscent() / 8, + null); + gc.drawString(warningText, + center_x - delta_x + warningTriangle.getWidth(null) + 2, + center_y + delta_y); + }); } private void drawWarningText(Graphics2D gc, String warningText) { - RenderingHints oldRenderingHints = gc.getRenderingHints(); - gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - if (linearMeterScale.isHorizontal()) { - drawWarning_horizontal(gc, warningText); - } - else { - drawWarning_vertical(gc, warningText); - } - gc.setRenderingHints(oldRenderingHints); + withReadLock(() -> { + RenderingHints oldRenderingHints = gc.getRenderingHints(); + gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + if (linearMeterScale.isHorizontal()) { + drawWarning_horizontal(gc, warningText); + } + else { + drawWarning_vertical(gc, warningText); + } + gc.setRenderingHints(oldRenderingHints); + }); } private void drawWarning_vertical(Graphics2D gc, String warningText) { - - gc.setFont(font); - gc.setColor(Color.BLACK); - FontMetrics fontMetrics = gc.getFontMetrics(gc.getFont()); - String[] warningText_split; - if (fontMetrics.stringWidth(warningText) <= meterBreadth) { - warningText_split = new String[] { warningText }; - } else { - String[] warningText_splitByWhitespace = warningText.split("\\s+"); - if (Arrays.stream(warningText_splitByWhitespace).allMatch(subString -> fontMetrics.stringWidth(subString) <= meterBreadth)) { - warningText_split = warningText_splitByWhitespace; - } - else { - warningText_split = warningText.split(""); // Split on every character + withReadLock(() -> { + gc.setFont(font); + gc.setColor(Color.BLACK); + FontMetrics fontMetrics = gc.getFontMetrics(gc.getFont()); + String[] warningText_split; + if (fontMetrics.stringWidth(warningText) <= meterBreadth) { + warningText_split = new String[] { warningText }; + } else { + String[] warningText_splitByWhitespace = warningText.split("\\s+"); + if (Arrays.stream(warningText_splitByWhitespace).allMatch(subString -> fontMetrics.stringWidth(subString) <= meterBreadth)) { + warningText_split = warningText_splitByWhitespace; + } + else { + warningText_split = warningText.split(""); // Split on every character + } } - } - int center_x = marginLeft + (linearMeterScale.getBounds().width - marginLeft - marginRight) / 2; - int center_y = marginAbove + (linearMeterScale.getBounds().height - marginAbove - marginBelow) / 2; - int warningTriangleHeight = warningTriangle.getHeight(null); - int fontSizeInPixels = fontMetrics.getHeight(); - int delta_y = (warningTriangleHeight + warningText_split.length * fontSizeInPixels) / 2; - - gc.drawImage(warningTriangle, - center_x - warningTriangle.getWidth(null) / 2, - center_y - delta_y, - null); - - for (int i = 0; i < warningText_split.length; i++) { - gc.drawString(warningText_split[i], - center_x - fontMetrics.stringWidth(warningText_split[i]) / 2, - center_y - delta_y + warningTriangleHeight + (fontSizeInPixels + 4) * (i + 1)); - } + int center_x = marginLeft + (linearMeterScale.getBounds().width - marginLeft - marginRight) / 2; + int center_y = marginAbove + (linearMeterScale.getBounds().height - marginAbove - marginBelow) / 2; + int warningTriangleHeight = warningTriangle.getHeight(null); + int fontSizeInPixels = fontMetrics.getHeight(); + int delta_y = (warningTriangleHeight + warningText_split.length * fontSizeInPixels) / 2; + + gc.drawImage(warningTriangle, + center_x - warningTriangle.getWidth(null) / 2, + center_y - delta_y, + null); + + for (int i = 0; i < warningText_split.length; i++) { + gc.drawString(warningText_split[i], + center_x - fontMetrics.stringWidth(warningText_split[i]) / 2, + center_y - delta_y + warningTriangleHeight + (fontSizeInPixels + 4) * (i + 1)); + } + }); } private void paintRectangle(Graphics2D gc, Rectangle rectangle, Paint paint){ - - //Store old values of clip and color - Shape oldClip = gc.getClip(); - Color oldColor = gc.getColor(); - - //Paint rectangle with specified gradient - gc.setClip(rectangle); - gc.setPaint(paint); - gc.fill(rectangle); - gc.setClip(oldClip); - - //Draw border of rectangle. - //TODO: can this be included in the paint? - gc.setColor(foreground); - gc.draw(rectangle); - - gc.setColor(oldColor); + withReadLock(() -> { + //Store old values of clip and color + Shape oldClip = gc.getClip(); + Color oldColor = gc.getColor(); + + //Paint rectangle with specified gradient + gc.setClip(rectangle); + gc.setPaint(paint); + gc.fill(rectangle); + gc.setClip(oldClip); + + //Draw border of rectangle. + //TODO: can this be included in the paint? + gc.setColor(foreground); + gc.draw(rectangle); + + gc.setColor(oldColor); + }); } private Rectangle loLoRectangle; @@ -1041,137 +1275,145 @@ private void paintRectangle(Graphics2D gc, Rectangle rectangle, Paint paint){ private int meterBreadth = 0; private void layout() { + withReadLock(() -> { + double displayedLoLo; + double displayedLow; + double displayedHiHi; + double displayedHigh; + + double loLoValue = loLo; + double lowValue = low; + double highValue = high; + double hiHiValue = hiHi; + + displayedLoLo = Double.isFinite(loLoValue) ? Math.max(loLoValue, linearMeterScale.getValueRange().getLow()) : linearMeterScale.getValueRange().getLow(); + displayedLow = Double.isFinite(lowValue) ? Math.max(Math.max(lowValue, linearMeterScale.getValueRange().getLow()), displayedLoLo) : linearMeterScale.getValueRange().getLow(); + + displayedHiHi = Double.isFinite(highValue) ? Math.min(hiHiValue, linearMeterScale.getValueRange().getHigh()) : linearMeterScale.getValueRange().getHigh(); + displayedHigh = Double.isFinite(hiHiValue) ? Math.min(Math.min(highValue, linearMeterScale.getValueRange().getHigh()), displayedHiHi) : linearMeterScale.getValueRange().getHigh(); + + FontMetrics fontMetrics = null; + if (font != null) { + Canvas canvas = new Canvas(); + fontMetrics = canvas.getFontMetrics(font); + } - double displayedLoLo; - double displayedLow; - double displayedHiHi; - double displayedHigh; - - displayedLoLo = Double.isFinite(loLo) ? Math.max(loLo, linearMeterScale.getValueRange().getLow()) : linearMeterScale.getValueRange().getLow(); - displayedLow = Double.isFinite(low) ? Math.max(Math.max(low, linearMeterScale.getValueRange().getLow()), displayedLoLo) : linearMeterScale.getValueRange().getLow(); - - displayedHiHi = Double.isFinite(high) ? Math.min(hiHi, linearMeterScale.getValueRange().getHigh()) : linearMeterScale.getValueRange().getHigh(); - displayedHigh = Double.isFinite(hiHi) ? Math.min(Math.min(high, linearMeterScale.getValueRange().getHigh()), displayedHiHi) : linearMeterScale.getValueRange().getHigh(); - - FontMetrics fontMetrics = null; - if (font != null) { - Canvas canvas = new Canvas(); - fontMetrics = canvas.getFontMetrics(font); - } - - if (linearMeterScale.isHorizontal()) { - marginAbove = knobSize >= 1 ? knobSize + 2 : 0; - if (linearMeterScale.isVisible() && fontMetrics != null) { - var majorTicks = linearMeterScale.getTicks().getMajorTicks(); - if (majorTicks.size() >= 2) { - marginLeft = fontMetrics.stringWidth(majorTicks.get(0).getLabel()) / 2; - marginRight = fontMetrics.stringWidth(majorTicks.get(majorTicks.size() - 1).getLabel()) / 2; - } else if (majorTicks.size() == 1) { - marginRight = marginLeft = fontMetrics.stringWidth(majorTicks.get(0).getLabel()) / 2; + if (linearMeterScale.isHorizontal()) { + int knobSizeValue = knobSize; + marginAbove = displayMode == DisplayMode.NEEDLE && knobSizeValue >= 1 ? knobSizeValue + 2 : 0; + if (linearMeterScale.isVisible() && fontMetrics != null) { + var majorTicks = linearMeterScale.getTicks().getMajorTicks(); + if (majorTicks.size() >= 2) { + marginLeft = fontMetrics.stringWidth(majorTicks.get(0).getLabel()) / 2; + marginRight = fontMetrics.stringWidth(majorTicks.get(majorTicks.size() - 1).getLabel()) / 2 - 1; + } else if (majorTicks.size() == 1) { + marginRight = marginLeft = fontMetrics.stringWidth(majorTicks.get(0).getLabel()) / 2; + } else { + marginRight = 0; + marginLeft = 0; + } + marginBelow = (int) (0.5 * linearMeterScale.getTickLength() + 4 + fontMetrics.getAscent() + fontMetrics.getDescent()); } else { - marginRight = 0; marginLeft = 0; + marginRight = 0; + marginBelow = 0; } - marginBelow = (int) (0.5 * linearMeterScale.getTickLength() + 4 + fontMetrics.getAscent() + fontMetrics.getDescent()); - } else { - marginLeft = 0; - marginRight = 1; - marginBelow = 1; - } - if (showUnits && fontMetrics != null) { - marginBelow += 1 + fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); + if (showUnits && fontMetrics != null) { + marginBelow += 1 + fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); + } + + pixelsPerScaleUnit = (linearMeterScale.getBounds().width - marginLeft - marginRight - 1) / (linearMeterScale.getValueRange().getHigh() - linearMeterScale.getValueRange().getLow()); + meterBreadth = Math.round(linearMeterScale.getBounds().height - marginAbove - marginBelow) - 1; + + double x_loLoRectangle = marginLeft; + double x_lowRectangle = marginLeft + pixelsPerScaleUnit * (displayedLoLo - linearMeterScale.getValueRange().getLow()); + double x_normalRectangle = marginLeft + pixelsPerScaleUnit * (displayedLow - linearMeterScale.getValueRange().getLow()); + double x_highRectangle = marginLeft + pixelsPerScaleUnit * (displayedHigh - linearMeterScale.getValueRange().getLow()); + double x_hiHiRectangle = marginLeft + pixelsPerScaleUnit * (displayedHiHi - linearMeterScale.getValueRange().getLow()); + + loLoRectangle = new Rectangle((int) Math.round(x_loLoRectangle), + marginAbove, + (int) (Math.round(x_lowRectangle) - Math.round(x_loLoRectangle)), + meterBreadth); + + lowRectangle = new Rectangle((int) Math.round(x_lowRectangle), + marginAbove, + (int) (Math.round(x_normalRectangle) - Math.round(x_lowRectangle)), + meterBreadth); + + normalRectangle = new Rectangle((int) Math.round(x_normalRectangle), + marginAbove, + (int) (Math.round(x_highRectangle) - Math.round(x_normalRectangle)), + meterBreadth); + + highRectangle = new Rectangle((int) Math.round(x_highRectangle), + marginAbove, + (int) (Math.round(x_hiHiRectangle) - Math.round(x_highRectangle)), + meterBreadth); + + hiHiRectangle = new Rectangle((int) Math.round(x_hiHiRectangle), + marginAbove, + (int) (Math.round(pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedHiHi))), + meterBreadth); } + else { + int knobSizeValue = knobSize; + marginLeft = displayMode == DisplayMode.NEEDLE && knobSizeValue >= 1 ? knobSizeValue + 2 : 0; + if (linearMeterScale.isVisible() && fontMetrics != null) { + int maxTickLabelWidth = 0; + maxTickLabelWidth = 0; + var majorTicks = linearMeterScale.getTicks().getMajorTicks(); + for (var majorTick : majorTicks) { + int labelStringWidth = fontMetrics.stringWidth(majorTick.getLabel()); + maxTickLabelWidth = Math.max(maxTickLabelWidth, labelStringWidth); + } + marginRight = RTLinearMeter.this.linearMeterScale.getTickLength() + maxTickLabelWidth + 1; + marginAbove = fontMetrics.getAscent() / 2 + 1; + marginBelow = fontMetrics.getAscent() / 2 + 1; + } else { + marginRight = 0; + marginAbove = 0; + marginBelow = 0; + } - pixelsPerScaleUnit = (linearMeterScale.getBounds().width - marginLeft - marginRight) / (linearMeterScale.getValueRange().getHigh() - linearMeterScale.getValueRange().getLow()); - meterBreadth = Math.round(linearMeterScale.getBounds().height - marginAbove - marginBelow); - - double x_loLoRectangle = marginLeft; - double x_lowRectangle = marginLeft + pixelsPerScaleUnit * (displayedLoLo - linearMeterScale.getValueRange().getLow()); - double x_normalRectangle = marginLeft + pixelsPerScaleUnit * (displayedLow - linearMeterScale.getValueRange().getLow()); - double x_highRectangle = marginLeft + pixelsPerScaleUnit * (displayedHigh - linearMeterScale.getValueRange().getLow()); - double x_hiHiRectangle = marginLeft + pixelsPerScaleUnit * (displayedHiHi - linearMeterScale.getValueRange().getLow()); - - loLoRectangle = new Rectangle((int) Math.round(x_loLoRectangle), - marginAbove, - (int) (Math.round(x_lowRectangle) - Math.round(x_loLoRectangle)), - meterBreadth); - - lowRectangle = new Rectangle((int) Math.round(x_lowRectangle), - marginAbove, - (int) (Math.round(x_normalRectangle) - Math.round(x_lowRectangle)), - meterBreadth); - - normalRectangle = new Rectangle((int) Math.round(x_normalRectangle), - marginAbove, - (int) (Math.round(x_highRectangle) - Math.round(x_normalRectangle)), - meterBreadth); - - highRectangle = new Rectangle((int) Math.round(x_highRectangle), - marginAbove, - (int) (Math.round(x_hiHiRectangle) - Math.round(x_highRectangle)), - meterBreadth); - - hiHiRectangle = new Rectangle((int) Math.round(x_hiHiRectangle), - marginAbove, - (int) (Math.round(pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedHiHi))), - meterBreadth); - } - else { - marginLeft = knobSize >= 1 ? knobSize + 2 : 0; - if (linearMeterScale.isVisible() && fontMetrics != null) { - int maxTickLabelWidth = 0; - maxTickLabelWidth = 0; - var majorTicks = linearMeterScale.getTicks().getMajorTicks(); - for (var majorTick : majorTicks) { - int labelStringWidth = fontMetrics.stringWidth(majorTick.getLabel()); - maxTickLabelWidth = Math.max(maxTickLabelWidth, labelStringWidth); + if (showUnits && fontMetrics != null) { + marginBelow += 1 + fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); } - marginRight = RTLinearMeter.this.linearMeterScale.getTickLength() + maxTickLabelWidth + 1; - marginAbove = fontMetrics.getAscent() / 2 + 1; - marginBelow = fontMetrics.getAscent() / 2 + 1; - } else { - marginRight = 1; - marginAbove = 0; - marginBelow = 1; - } - if (showUnits && fontMetrics != null) { - marginBelow += 1 + fontMetrics.getMaxAscent() + fontMetrics.getMaxDescent(); + pixelsPerScaleUnit = (linearMeterScale.getBounds().height - marginAbove - marginBelow - 1) / (linearMeterScale.getValueRange().getHigh() - linearMeterScale.getValueRange().getLow()); + meterBreadth = Math.round(linearMeterScale.getBounds().width - marginLeft - marginRight); + + double y_loLoRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedLoLo); + double y_lowRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedLow); + double y_normalRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedHigh); + double y_highRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedHiHi); + + loLoRectangle = new Rectangle(marginLeft, + (int) Math.round(y_loLoRectangle), + meterBreadth - 1, + (int) (Math.round(pixelsPerScaleUnit * (displayedLoLo - linearMeterScale.getValueRange().getLow())))); + + lowRectangle = new Rectangle(marginLeft, + (int) Math.round(y_lowRectangle), + meterBreadth - 1, + (int) (Math.round(y_loLoRectangle) - Math.round(y_lowRectangle))); + + normalRectangle = new Rectangle(marginLeft, + (int) Math.round(y_normalRectangle), + meterBreadth - 1, + (int) (Math.round(y_lowRectangle) - Math.round(y_normalRectangle))); + + highRectangle = new Rectangle(marginLeft, + (int) Math.round(y_highRectangle), + meterBreadth - 1, + (int) (Math.round(y_normalRectangle) - Math.round(y_highRectangle))); + + hiHiRectangle = new Rectangle(marginLeft, + marginAbove, + meterBreadth - 1, + (int) Math.round(y_highRectangle) - marginAbove); } - - pixelsPerScaleUnit = (linearMeterScale.getBounds().height - marginAbove - marginBelow) / (linearMeterScale.getValueRange().getHigh() - linearMeterScale.getValueRange().getLow()); - meterBreadth = Math.round(linearMeterScale.getBounds().width - marginLeft - marginRight); - - double y_loLoRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedLoLo); - double y_lowRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedLow); - double y_normalRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedHigh); - double y_highRectangle = marginAbove + pixelsPerScaleUnit * (linearMeterScale.getValueRange().getHigh() - displayedHiHi); - - loLoRectangle = new Rectangle(marginLeft, - (int) Math.round(y_loLoRectangle), - meterBreadth, - (int) (Math.round(pixelsPerScaleUnit * (displayedLoLo - linearMeterScale.getValueRange().getLow()) ))); - - lowRectangle = new Rectangle(marginLeft, - (int) Math.round(y_lowRectangle), - meterBreadth, - (int) (Math.round(y_loLoRectangle) - Math.round(y_lowRectangle))); - - normalRectangle = new Rectangle(marginLeft, - (int) Math.round(y_normalRectangle), - meterBreadth, - (int) (Math.round(y_lowRectangle) - Math.round(y_normalRectangle))); - - highRectangle = new Rectangle(marginLeft, - (int) Math.round(y_highRectangle), - meterBreadth, - (int) (Math.round(y_normalRectangle) - Math.round(y_highRectangle))); - - hiHiRectangle = new Rectangle(marginLeft, - marginAbove, - meterBreadth, - (int) Math.round(y_highRectangle) - marginAbove); - } + }); } } diff --git a/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java b/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java index 7e0295fcbc..feff995562 100644 --- a/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java +++ b/app/display/model/src/main/java/org/csstudio/display/builder/model/Messages.java @@ -18,6 +18,8 @@ public class Messages public static String Actions_N_Fmt, ActiveTab, + AlarmLimitsFromPV, + AllLimitsFromPV, ArrayWidget_Description, ArrayWidget_Name, ArrowLength, @@ -39,19 +41,23 @@ public class Messages Bottom, Checkbox_Label, Center, + Colors, ComboWidget_Item, ComboWidget_Items, Confirm_NONE, Confirm_BOTH, Confirm_PUSH, Confirm_RELEASE, + DisplayMode, EmbeddedDisplayWidget_GroupName, + EnableGradient, FontStyle_Bold, FontStyle_BoldItalic, FontStyle_Italic, FontStyle_Regular, GroupWidget_Description, GroupWidget_Name, + HighlightActiveRegion, InformativeTooltipActions, InformativeTooltipAlarmBorder, InformativeTooltipHeight, @@ -68,15 +74,23 @@ public class Messages Interpolation_Automatic, Interpolation_Interpolate, Interpolation_None, + KnobSize, LabelWidget_Text, Left, + LinearMeterDescription, LineStyle, LineStyle_Solid, LineStyle_Dash, LineStyle_DashDot, LineStyle_DashDotDot, LineStyle_Dot, + LoLoAndHiHiWarningColor, + LowAndHighWarningColor, + NeedleWidth, Middle, + MinAndMaxFromPV, + NoLimitsFromPV, + NormalStatusColor, PlotWidget_AutoScale, PlotWidget_Color, PlotWidget_ErrorPV, diff --git a/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties b/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties index 121766405e..9f2619903c 100644 --- a/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties +++ b/app/display/model/src/main/resources/org/csstudio/display/builder/model/messages.properties @@ -1,5 +1,7 @@ Actions_N_Fmt={0,choice,0#No actions|1#1 action|1<{0,number,integer} actions} ActiveTab=Active Tab +AlarmLimitsFromPV=Alarm limits from PV +AllLimitsFromPV=All limits from PV ArrayWidget_Name=Array ArrayWidget_Description=Array of widgets ArrowLength=Arrow Length @@ -21,19 +23,23 @@ ByteMonitor_NumBits=Number of bits (1-32) ByteMonitor_StartBit=Start bit (0-31) Checkbox_Label=Label Center=Center +Colors=Colors ComboWidget_Item=Item ComboWidget_Items=Items Confirm_NONE=No Confirm_BOTH=On Both Confirm_PUSH=On Set Confirm_RELEASE=On Clear +DisplayMode=Display Mode EmbeddedDisplayWidget_GroupName=Group name +EnableGradient=Enable Gradient FontStyle_Bold=Bold FontStyle_BoldItalic=Bold & Italic FontStyle_Italic=Italic FontStyle_Regular=Regular GroupWidget_Description=Group of widgets GroupWidget_Name=Group +HighlightActiveRegion=Highlight Active Region InformativeTooltipActions=Actions to execute when the widget is clicked on. InformativeTooltopAlarmBorder=Should an alarm border be shown around the widget when the displayed PV signals an alarm? InformativeTooltipHeight=The height of the widget in pixels. @@ -50,15 +56,23 @@ InformativeTooltipY=The y-coordinate of the widget counted in pixels from the to Interpolation_Automatic=Automatic Interpolation_Interpolate=Interpolate Interpolation_None=None +KnobSize=Knob Size LabelWidget_Text=Label text Left=Left +LinearMeterDescription=Compact monitor widget for the value of a PV. LineStyle=Line Style LineStyle_Solid=Solid LineStyle_Dash=Dashed LineStyle_DashDot=Dash-Dot LineStyle_DashDotDot=Dash-Dot-Dot LineStyle_Dot=Dot +LoLoAndHiHiWarningColor=LoLo & HiHi Warning Color +LowAndHighWarningColor=Low & High Warning Color Middle=Middle +MinAndMaxFromPV=Min & max from PV +NeedleWidth=Needle Width +NoLimitsFromPV=No limits from PV +NormalStatusColor=Normal Status Color PlotWidget_AutoScale=Auto-scale PlotWidget_Color=Color PlotWidget_ErrorPV=Error PV @@ -247,7 +261,7 @@ WidgetProperties_MinuteTickMarkColor=Minute Tick Mark Color WidgetProperties_MinuteTickMarkVisible=Minute Tick Mark Visible WidgetProperties_MultiLine=Multi-Line WidgetProperties_Name=Name -WidgetProperties_NeedleColor=Needle Color +WidgetProperties_NeedleColor=Needle/Bar Color WidgetProperties_OffColor='Off' Color WidgetProperties_OffImage='Off' Image WidgetProperties_OffLabel='Off' Label