Skip to content

TankWidget: dual scales, alarm limit lines, ScaleFormat enum, and ScaledPVWidget base class#3760

Open
emilioheredia-source wants to merge 7 commits intoControlSystemStudio:masterfrom
emilioheredia-source:tank_scale_refactor
Open

TankWidget: dual scales, alarm limit lines, ScaleFormat enum, and ScaledPVWidget base class#3760
emilioheredia-source wants to merge 7 commits intoControlSystemStudio:masterfrom
emilioheredia-source:tank_scale_refactor

Conversation

@emilioheredia-source
Copy link
Copy Markdown

This PR extends the Tank widget with several features inspired by CS-Studio BOY and adds a new ScaledPVWidget abstract base class to avoid property duplication across scale-based widgets.

New features:

  • Alarm limit lines — LOLO/LO/HI/HIHI lines drawn on the tank body, from PV alarm metadata or manually configured. PV-sourced lines are solid; manual lines are dashed so operators can tell the difference at a glance.
  • Second numeric scale — optional scale on the opposite side of the tank, sharing the same range, format, and ticks as the primary scale.
  • Format / precision control — new ScaleFormat enum (DEFAULT, SIGNIFICANT/%g, DECIMAL, EXPONENTIAL, ENGINEERING, COMPACT) applied to tick labels. SIGNIFICANT is also added to the shared FormatOption enum for TextUpdate etc.
  • Improved log-scale ticksLogTicks reworked to avoid overlapping labels at narrow widths. YAxisImpl gains a two-pass visibility algorithm that always preserves the first and last tick labels.
  • Perpendicular tick labels — option to draw labels as horizontal text instead of rotated 90°.
  • Optional tank body outlinetank_border_width (0–5 px, default 0). Border centred on the plot-area boundary; drawRoundRect uses w−1, h−1 to correct Java2D's asymmetric fill/draw space.

New: ScaledPVWidget base class — sits between PVWidget and TankWidget, consolidating limits_from_pv, minimum, maximum, format, precision, and all alarm-limit properties. ThermometerWidget and ProgressBarWidget already use the same CommonWidgetProperties descriptors for limits_from_pv/minimum/maximum, so migrating them to extend ScaledPVWidget will be a drop-in change with no .bob format impact.

Backward compatibility — every new property defaults to reproduce the existing behavior (show_alarm_limits=false, tank_border_width=0, opposite_scale_visible=false, format=DEFAULT). ModelWriter omits default-valued properties, so stock Phoebus silently ignores the new XML elements. TankWidgetUnitTest.testNewPropertiesAreOptional() confirms this.

Tests — 5 new TankWidgetUnitTest, 6 new RTTankTest, 1 new FormatOptionHandlerTest assertion. All 84 display/model and 37 rtplot pre-existing tests pass.

…ale and outline

LogTicks was producing overlapping labels on narrow widgets (such as Tank).
Rework the tick-thinning logic so sub-decade ticks (2..9) are suppressed
when available pixel height is too small, falling back to power-of-ten
labels only.  LinearTicks receives a matching minor adjustment for very
small ranges.  AxisPart gains a minor helper used by the new tank layout.

YAxisImpl gains a force_text_up flag (default false).  When true the tick
labels always read bottom-to-top regardless of the is_right setting.  This
is needed by RTTank's right-side scale, which is a true YAxisImpl instance
set to is_right=true; without force_text_up its labels read top-to-bottom
(upside-down from the operator's perspective).

RTTank is substantially extended:
- Dual independent YAxisImpl instances (left + right) replacing the
  previous single-axis design.  Both share the same range, log mode,
  format and tick settings.  Inspired by CS-Studio BOY's tank that could
  render markers on both sides of the bar.
- Alarm/warning limit lines (LOLO, LO, HI, HIHI) drawn as horizontal
  lines over the tank body.  Lines sourced from PV metadata are solid;
  manually configured lines are dashed (2 px dash / 4 px gap) so the
  operator can distinguish the source at a glance.
- Tank body outline via drawRoundRect in the foreground color.  Edges
  that lack a neighbouring scale receive a 1 px inset so the outline
  stroke is not clipped by the canvas boundary.
- setLimitsFromPV(), setAlarmColors(), setRightScaleVisible() API.
- Constructor init-order fix: right_scale.setOnRight(true) moved after
  update_throttle creation to avoid a NPE via requestUpdate().
…alarm limits, dual scale, and format controls

ScaledPVWidget (new abstract class, extends PVWidget):
  Consolidates properties common to scaled PV monitor widgets, following
  the pattern of CS-Studio BOY's AbstractScaledWidget hierarchy.
  Properties added on top of PVWidget:
    limits_from_pv        - use PV display range for min/max (existing)
    minimum / maximum     - manual display range (existing, moved here)
    border_alarm_sensitive - alarm border (existing, repositioned for grouping)
    alarm_limits_from_pv  - use PV alarm metadata for LOLO/LO/HI/HIHI
    show_alarm_limits     - draw limit lines on the widget body
    level_lolo/lo/hi/hihi - manual alarm thresholds (NaN = inactive)
    minor_alarm_color / major_alarm_color - themed alarm line colors

  Implementation notes:
  * All new properties use types that stock Phoebus already knows
    (boolean, double, color) so old versions silently skip unknown XML
    elements; no ordinal-based enums are used (BooleanWidgetProperty
    throws on ordinals >= 2, which broke round-trips in an earlier
    enum-based approach).
  * New properties default to values that reproduce existing behavior,
    so existing .bob files open with no visual difference.

TankWidget (now extends ScaledPVWidget):
  New own properties:
    opposite_scale_visible  - second scale on the right / bottom side
    show_minor_ticks        - toggle minor tick marks
    perpendicular_tick_labels - rotate tick labels 90 degrees
    format                  - numeric format (DEFAULT, DECIMAL, ...)
    precision               - decimal places (0-15)
    log_scale               - logarithmic axis
    horizontal              - horizontal tank orientation (existing)

TankRepresentation:
  Wires all new model properties to RTTank.  valueChanged() reads
  alarm metadata from the PV (VType -> Display -> warning/alarmRange)
  when alarm_limits_from_pv is true, falling back to the manual levels
  otherwise.  Passes a limitsFromPV flag to RTTank so it can render
  PV-sourced limits solid and manual limits dashed.
TankWidgetUnitTest (JUnit 5 + hamcrest, 5 tests):
  testScaledPVWidgetDefaults   - verifies all ScaledPVWidget property defaults
  testTankWidgetDefaults       - verifies TankWidget-specific property defaults
  testPropertyOrdering         - alarm props grouped after max; opposite_scale
                                 adjacent to scale_visible
  testXmlRoundTrip             - non-default values survive write/read cycle
  testNewPropertiesAreOptional - default-valued properties omitted from XML
                                 (backward-compatible with stock Phoebus)

PR_DESCRIPTION.md documents the changes, rationale, backward compatibility
analysis, and design decisions for the pull request.
Add a 'tank_border_width' integer property (default 0, range 0-100 px)
to TankWidget.  When zero (the default) no border is drawn, preserving
the original widget appearance and backward compatibility with existing
.bob files.

Changes:
- RTTank: add border_width field, setBorderWidth(int) setter, and
  conditional drawRoundRect guarded by border_width > 0
- TankWidget: define propTankBorderWidth descriptor (DISPLAY category,
  key 'tank_border_width') and expose via propBorderWidth() accessor
- TankRepresentation: wire propBorderWidth to tank.setBorderWidth()
  in register/unregister/updateChanges
- TankWidgetUnitTest: assert default=0, round-trip value=3, and that
  <tank_border_width> is absent at defaults (ModelWriter skips defaults)

Property is named 'tank_border_width' rather than re-using the generic
'border_width' (MISC) to avoid unintended interaction with the CSS
border mechanism in RegionBaseRepresentation and to keep all tank
display properties consistently in the DISPLAY category.
…ANT)

Add a new 'Significant' entry to the FormatOption enum that uses Java's
%g specifier, matching EDM's GFloat format.  Precision controls the total
number of significant digits rather than fraction digits.  The formatter
chooses decimal or exponential notation per value depending on magnitude:

  sig3: 0.00123 / 1.23 / 123 / 1.23e+04

Wired through FormatOptionHandler (for TextUpdate and other widgets) and
RTTank.setLabelFormat (for axis tick labels via a NumberFormat adapter).

Tests added in FormatOptionHandlerTest and RTTankTest.
…dgets

Introduce ScaleFormat enum in core/ui containing only the format
options meaningful for numeric scale axes: DEFAULT, SIGNIFICANT,
DECIMAL, EXPONENTIAL, ENGINEERING, COMPACT.  Text-only formats
(STRING, HEX, BINARY, SEXAGESIMAL) are excluded from the dropdown.

Move format and precision properties from TankWidget into
ScaledPVWidget so they are reusable by Thermometer, ProgressBar, etc.
RTTank.setLabelFormat() now takes ScaleFormat instead of FormatOption.

Files changed:
- NEW  core/ui/.../ScaleFormat.java
- MOD  app/rtplot/.../RTTank.java
- MOD  app/display/model/.../ScaledPVWidget.java
- MOD  app/display/model/.../TankWidget.java
- MOD  app/rtplot/test/.../RTTankTest.java
- MOD  app/display/model/test/.../TankWidgetUnitTest.java
When border_width > 0 the stroke is centred on the plot_bounds
path, so half the stroke (half_bw = border_width/2) bleeds inward
and the top/bottom ticks appeared displaced from the visible tank
edge.

Fix: expand the layout insets by half_bw on all sides in
computeLayout() so the scale's first/last ticks land on the inner
edge of the border.  The border rect is then shifted outward by
half_bw so its inner edge coincides with plot_bounds, keeping
ticks, fill, and border visually flush.

When border_width = 0 (the default), half_bw = 0 and behaviour
is identical to before.

fix(rtplot): align tank ticks with inner border edge

When border_width > 0 the stroke is centred on the plot_bounds
path, so half the stroke (half_bw = border_width/2) bleeds inward
and the top/bottom ticks appeared displaced from the visible tank
edge.

Fix: expand the layout insets by half_bw on all sides in
computeLayout() so the scale's first/last ticks land on the inner
edge of the border.  The border rect is then shifted outward by
half_bw so its inner edge coincides with plot_bounds, keeping
ticks, fill, and border visually flush.

When border_width = 0 (the default), half_bw = 0 and behaviour
is identical to before.
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 1, 2026

@emilioheredia-source
Copy link
Copy Markdown
Author

This is mostly about using the tank widget for now, as a progress bar, when converting old EDM screens, because the actual Phoebus Progress Bar widget, to which the progress bars form EDM convert, don't have a scale yet.

With these new features on the Tank Widget, I can now convert almost any existing EDM progress bar (typically displaying pressures), and give them an appearance close to the original.

Some of the new options are shown below:
tank_widget_new_features

Thanks for considering this. Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants