feat: pending color while typing in Experiment configuration values

Values inside the Experiment configuration banner now show the same
pending color as the motor move boxes while the typed text differs from
the applied value; Enter (or focus-out) commits as before and clears it.
Opt-in via NumberLineEdit(track_pending=True) because MotorMoveGroup
owns the movestate property on its registered boxes; reuses the existing
movestate QSS, so no new styling. The Set Energy spinbox marks pending
until Change Energy is clicked, since its apply gate is the button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 13:40:03 +02:00
committed by duan_j
co-authored by Claude Fable 5
parent 7fdbfef06c
commit adc8514174
6 changed files with 198 additions and 14 deletions
+100 -1
View File
@@ -11,12 +11,13 @@ import pytest
from aarecommon.math.coordinate import Coordinate, SmargonCoordinate
from aarecommon.math.diffraction_geometry import DiffractionGeometry
from aarecommon.models.models import SampleGeometryModel
from PySide6.QtCore import Qt
from aare.gui.panels.data_collection_settings import DataCollectionSettings
from aare.gui.panels.raster_data_collection import RasterDataCollectionPanel
from aare.gui.panels.rotation_data_collection import RotationDataCollectionPanel
from aare.gui.scan_logic.raster_grid_manager import RasterGridManager
from aare.gui.widgets.number_line_edit import DbOverrideLineEdit
from aare.gui.widgets.number_line_edit import DbOverrideLineEdit, NumberLineEdit
def _edit(field: DbOverrideLineEdit, text: str):
@@ -267,3 +268,101 @@ def test_auto_center_fires_on_mount_only_when_armed(settings_panel):
settings_panel._track_sample(None)
settings_panel._track_sample(4)
assert len(clicks) == 3
# ---------------------------------------------------------------------------
# Pending color while typing (movestate property, committed on Enter/focus-out)
# ---------------------------------------------------------------------------
def _type(qtbot, box, text):
box.clear()
qtbot.keyClicks(box, text)
@pytest.fixture
def tracked_box(qtbot):
box = NumberLineEdit(-720, 720, default=100.0, decimals=2, track_pending=True)
qtbot.addWidget(box)
return box
def test_typing_marks_pending_and_enter_commits(tracked_box, qtbot):
seen = []
tracked_box.newValue.connect(seen.append)
_type(qtbot, tracked_box, "300")
assert tracked_box.property("movestate") == "pending"
assert not seen # nothing is applied until Enter/focus-out
qtbot.keyClick(tracked_box, Qt.Key.Key_Return)
assert seen == [300.0]
assert tracked_box.property("movestate") == ""
def test_typing_back_the_applied_value_cancels_pending(tracked_box, qtbot):
_type(qtbot, tracked_box, "300")
qtbot.keyClick(tracked_box, Qt.Key.Key_Return)
_type(qtbot, tracked_box, "300") # same as what is applied now
assert tracked_box.property("movestate") == ""
def test_incomplete_entry_is_pending(tracked_box, qtbot):
_type(qtbot, tracked_box, "-") # not a number (yet)
assert tracked_box.property("movestate") == "pending"
def test_untracked_box_never_touches_movestate(qtbot):
# MotorMoveGroup owns movestate on the boxes it registers; the default
# NumberLineEdit must therefore stay away from the property entirely.
box = NumberLineEdit(-720, 720, default=100.0, decimals=2)
qtbot.addWidget(box)
_type(qtbot, box, "300")
assert box.property("movestate") is None
def test_programmatic_update_clears_pending(tracked_box, qtbot):
_type(qtbot, tracked_box, "300")
tracked_box.force_update_value(120.0)
assert tracked_box.property("movestate") == ""
_type(qtbot, tracked_box, "300")
tracked_box.update_value(500.0)
assert tracked_box.property("movestate") == ""
def test_db_override_typing_pending_until_commit(qapp, qtbot):
w = DbOverrideLineEdit(0, 1000, default=200.0, decimals=2)
qtbot.addWidget(w)
_type(qtbot, w.editor, "300")
assert w.editor.property("movestate") == "pending"
qtbot.keyClick(w.editor, Qt.Key.Key_Return)
assert w.editor.property("movestate") == ""
assert w.value == 300.0
assert w.source() == DbOverrideLineEdit.SOURCE_MINE
def test_db_override_source_toggle_clears_pending(qapp, qtbot):
w = DbOverrideLineEdit(0, 1000, default=200.0, decimals=2)
qtbot.addWidget(w)
_type(qtbot, w.editor, "300") # typing, no commit
assert w.editor.property("movestate") == "pending"
# a panel refresh (radio toggle, DB push) discards the uncommitted text
w.set_source(DbOverrideLineEdit.SOURCE_DB, emit=False)
assert w.editor.property("movestate") == ""
assert w.value == 200.0
def test_energy_spin_pending_until_change_energy(settings_panel):
box = settings_panel.energy_spin.lineEdit()
settings_panel.energy_spin.setValue(12.4)
assert box.property("movestate") == "pending"
sent = []
settings_panel.change_energy.connect(sent.append)
settings_panel.change_energy_button.click()
assert sent and sent[-1] == pytest.approx(12400.0)
assert box.property("movestate") == ""
# dialing back to the last sent value clears without the button
settings_panel.energy_spin.setValue(12.5)
assert box.property("movestate") == "pending"
settings_panel.energy_spin.setValue(12.4)
assert box.property("movestate") == ""