feat(ring_progress_bar): add set_progress_state for one-call ring + label updates

This commit is contained in:
2026-07-10 14:54:10 +02:00
committed by Jan Wyzula
parent f6666a6262
commit 5b1dd82c1b
3 changed files with 127 additions and 0 deletions
+29
View File
@@ -5285,6 +5285,35 @@ class RingProgressBar(RPCBase):
text(str): Text for the center label.
"""
@rpc_call
def set_progress_state(
self,
values: list[float | None] | dict[int, float | None] | None = None,
center_label: str | None = None,
**kwargs,
):
"""
Update several rings and the center label in a single call.
Updating each ring value and the center label individually is one RPC
round-trip per call; over a long-running scan that is a steady stream of
round-trips, and each one also triggers a server-side registry
broadcast. ``set_progress_state`` applies all of them within a single
method call, i.e. one RPC round-trip and one broadcast.
Args:
values: Ring values to apply. Either a list aligned to ring index,
where ``None`` leaves that ring unchanged (e.g.
``[12.0, None, 80.0]`` updates rings 0 and 2), or a
``{index: value}`` mapping to update only specific rings.
``None`` leaves all ring values unchanged.
center_label: New center-label text, or ``None`` to leave the
current text unchanged.
**kwargs: Ignored. Absorbs RPC-layer pass-through keyword arguments
(e.g. ``_rpc_wait_for_response``) so the call does not fail when
extra control kwargs are supplied by the client.
"""
class SBBMonitor(RPCBase):
"""A widget to display the SBB monitor website."""
@@ -393,6 +393,7 @@ class RingProgressBar(BECWidget, QWidget):
"remove_ring",
"set_gap",
"set_center_label",
"set_progress_state",
]
def __init__(self, parent: QWidget | None = None, client=None, **kwargs):
@@ -511,6 +512,44 @@ class RingProgressBar(BECWidget, QWidget):
"""
self.center_label = text
def set_progress_state(
self,
values: list[float | None] | dict[int, float | None] | None = None,
center_label: str | None = None,
**kwargs,
):
"""Update several rings and the center label in a single call.
Updating each ring value and the center label individually is one RPC
round-trip per call; over a long-running scan that is a steady stream of
round-trips, and each one also triggers a server-side registry
broadcast. ``set_progress_state`` applies all of them within a single
method call, i.e. one RPC round-trip and one broadcast.
Args:
values: Ring values to apply. Either a list aligned to ring index,
where ``None`` leaves that ring unchanged (e.g.
``[12.0, None, 80.0]`` updates rings 0 and 2), or a
``{index: value}`` mapping to update only specific rings.
``None`` leaves all ring values unchanged.
center_label: New center-label text, or ``None`` to leave the
current text unchanged.
**kwargs: Ignored. Absorbs RPC-layer pass-through keyword arguments
(e.g. ``_rpc_wait_for_response``) so the call does not fail when
extra control kwargs are supplied by the client.
"""
rings = self.rings
if isinstance(values, dict):
for index, value in values.items():
if value is not None and 0 <= int(index) < len(rings):
rings[int(index)].set_value(value)
elif values is not None:
for ring, value in zip(rings, values):
if value is not None:
ring.set_value(value)
if center_label is not None:
self.center_label = center_label
@property
def rings(self) -> list[Ring]:
return self.ring_progress_bar.rings
@@ -741,3 +741,62 @@ def test_leave_event_clears_hover_and_hides_tooltip(qtbot, container):
assert ring._hovered is False
assert container._last_hover_global_pos is None
container._hover_tooltip.hide.assert_called()
def _ensure_three_rings(ring_progress_bar):
while len(ring_progress_bar.rings) < 3:
ring_progress_bar.add_ring()
rings = ring_progress_bar.rings
for ring in rings:
ring.set_value(1.0) # sentinel so "unchanged" assertions are meaningful
return rings
def test_set_progress_state_in_user_access():
assert "set_progress_state" in RingProgressBar.USER_ACCESS
def test_set_progress_state_list_updates_selected_rings_and_label(ring_progress_bar):
rings = _ensure_three_rings(ring_progress_bar)
ring_progress_bar.set_progress_state([12.0, None, 80.0], center_label="batched")
assert rings[0].config.value == 12.0 # updated
assert rings[1].config.value == 1.0 # None -> unchanged
assert rings[2].config.value == 80.0 # updated
assert ring_progress_bar.center_label == "batched"
def test_set_progress_state_accepts_index_mapping(ring_progress_bar):
rings = _ensure_three_rings(ring_progress_bar)
ring_progress_bar.set_progress_state({0: 5.0, 2: 95.0})
assert rings[0].config.value == 5.0
assert rings[1].config.value == 1.0 # untouched index
assert rings[2].config.value == 95.0
def test_set_progress_state_label_only_leaves_rings_untouched(ring_progress_bar):
rings = _ensure_three_rings(ring_progress_bar)
ring_progress_bar.set_progress_state(center_label="only label")
assert [r.config.value for r in rings] == [1.0, 1.0, 1.0]
assert ring_progress_bar.center_label == "only label"
def test_set_progress_state_ignores_rpc_passthrough_kwargs(ring_progress_bar):
_ensure_three_rings(ring_progress_bar)
# the client may pass control kwargs (e.g. _rpc_wait_for_response); must not raise
ring_progress_bar.set_progress_state([3.0], center_label="kw", _rpc_wait_for_response=False)
assert ring_progress_bar.rings[0].config.value == 3.0
assert ring_progress_bar.center_label == "kw"
def test_set_progress_state_no_rings_is_noop(ring_progress_bar):
# a freshly created RingProgressBar has no rings; must not raise
assert ring_progress_bar.rings == []
ring_progress_bar.set_progress_state([1.0, 2.0], center_label="safe")
assert ring_progress_bar.center_label == "safe"
def test_set_progress_state_clamps_and_ignores_out_of_range_index(ring_progress_bar):
rings = _ensure_three_rings(ring_progress_bar)
# value above max clamps to 100; out-of-range index is ignored, not an error
ring_progress_bar.set_progress_state({0: 150.0, 99: 42.0})
assert rings[0].config.value == 100.0