Make the rail reset zero the sensor in live and the display in replay
The replay view disabled a control whose display reset is exactly what an operator wants there, and the bare "Reset" hid that the live one zeroes the sensor tracker; the label, tooltip and status line now name the target, and Ctrl+R follows the view. Verified with the studio suite, ui_check (628 checks) and real-GL rail snapshots of both views.
This commit is contained in:
@@ -1187,13 +1187,18 @@ class StudioMainWindow(QMainWindow):
|
||||
or self._recompute_running
|
||||
or self._session_active
|
||||
)
|
||||
# The tracker reset belongs to a device session; this view has none.
|
||||
self.toolbar.set_reset_enabled(False)
|
||||
# The rail's reset in *this* view is the display reset: there is no
|
||||
# device session to zero, and a rail button that is dead in a view the
|
||||
# operator is working in is what the report called out. The label says
|
||||
# which target is live (``toolbar.reset_target``), and ``_on_reset``
|
||||
# routes on the same answer.
|
||||
self.toolbar.set_reset_enabled(True)
|
||||
self.toolbar.set_record_enabled(
|
||||
self._running or self.recorder.is_recording
|
||||
)
|
||||
if not self._running:
|
||||
self.toolbar.refresh_labels()
|
||||
self._sync_reset_action()
|
||||
self._sync_shortcut_actions()
|
||||
return
|
||||
sensor_ok = self.sensor_picker.sensor_type() in RUNNABLE_TYPES
|
||||
@@ -1222,6 +1227,7 @@ class StudioMainWindow(QMainWindow):
|
||||
self.toolbar.set_reset_enabled(self.session.reset_available)
|
||||
if not self._running:
|
||||
self.toolbar.refresh_labels()
|
||||
self._sync_reset_action()
|
||||
self._sync_shortcut_actions()
|
||||
|
||||
def retranslate(self) -> None:
|
||||
@@ -1249,7 +1255,9 @@ class StudioMainWindow(QMainWindow):
|
||||
self.act_run.setText(t("menu.run"))
|
||||
self.act_stop.setText(t("menu.stop"))
|
||||
self.act_preview.setText(t("menu.preview"))
|
||||
self.act_reset.setText(t("menu.reset"))
|
||||
# Ctrl+R is the rail's reset on a second surface, so its wording follows the
|
||||
# view the same way the button's does (see ``_sync_reset_action``).
|
||||
self._sync_reset_action()
|
||||
self.act_reset_view.setText(t("menu.reset_view"))
|
||||
self.act_reset_view.setToolTip(t("menu.reset_view_tip"))
|
||||
self.act_pause.setText(t("menu.pause"))
|
||||
@@ -1823,6 +1831,23 @@ class StudioMainWindow(QMainWindow):
|
||||
self._persist_prefs()
|
||||
|
||||
# ----------------------------------------------------------- shortcuts
|
||||
def _sync_reset_action(self) -> None:
|
||||
"""The View menu's Ctrl+R says what the rail's reset does in this view.
|
||||
|
||||
It is the *same* control on a second surface (``_on_reset`` is the one handler),
|
||||
so it is named from the rail's own answer rather than keeping a second copy of
|
||||
the rule. In the replay view it reads 复位显示 next to 复位视图 (F8), which stays
|
||||
the camera-and-window reset it always was.
|
||||
"""
|
||||
if getattr(self, "act_reset", None) is None:
|
||||
return
|
||||
if self.toolbar.reset_target() == "display":
|
||||
self.act_reset.setText(t("menu.reset_display"))
|
||||
self.act_reset.setToolTip(t("toolbar.reset_display_tip"))
|
||||
return
|
||||
self.act_reset.setText(t("menu.reset"))
|
||||
self.act_reset.setToolTip(t("toolbar.reset_sensor_tip"))
|
||||
|
||||
def _sync_shortcut_actions(self) -> None:
|
||||
if getattr(self, "act_run", None) is None:
|
||||
return
|
||||
@@ -2239,7 +2264,7 @@ class StudioMainWindow(QMainWindow):
|
||||
self._notify(t("toolbar.nothing_to_stop"))
|
||||
|
||||
def _on_reset(self) -> None:
|
||||
"""Zero the device tracker. Graphics are reset separately (``_on_reset_view``).
|
||||
"""Zero the *sensor tracker* — or, where there is no device, reset the display.
|
||||
|
||||
This used to call ``force_plots.reset()`` as well, so wanting a fresh baseline
|
||||
also threw away the history, the window and the cursor on screen — two different
|
||||
@@ -2248,22 +2273,60 @@ class StudioMainWindow(QMainWindow):
|
||||
a new device zero means the old idle estimate is stale, not that the operator
|
||||
wants to lose what they were looking at.
|
||||
|
||||
It is only ever this: ending a replay or a recompute pass is the rail's Stop
|
||||
button (the same act as Esc), so this button is disabled wherever there is no
|
||||
device session to zero.
|
||||
Which of the two this button means is the *view's* answer, and the label says
|
||||
it (``toolbar.reset_target``): a device session can be zeroed, a replay cannot
|
||||
— its display can, and that is what the operator reached for. Neither branch
|
||||
adds a second meaning: the live one is the tracker reset it always was, and the
|
||||
replay one is the display reset ``_reset_display`` states in full.
|
||||
"""
|
||||
if self.toolbar.reset_target() == "display":
|
||||
self._reset_display()
|
||||
return
|
||||
if self._session_phase != "running" or self._active_worker is None:
|
||||
return
|
||||
try:
|
||||
# A request, not the zeroing itself: the acquisition thread applies it
|
||||
# between two frames and reports the frame and time it took effect.
|
||||
self._active_worker.reset()
|
||||
# The side effect the tooltip states: a new zero makes the old idle
|
||||
# estimate stale, so the display baseline is re-learned from the samples
|
||||
# that follow. The curve history itself is kept.
|
||||
self.force_plots.request_presentation_reestimate()
|
||||
self._last_plot_packet_key = None
|
||||
self.statusBar().showMessage(t("status.reset_pending"))
|
||||
except Exception as exc:
|
||||
self.statusBar().showMessage(t("status.run_failed", error=str(exc)))
|
||||
|
||||
def _reset_display(self) -> None:
|
||||
"""The rail's reset in the replay view: clear the display and start the replay over.
|
||||
|
||||
Clearing the curves alone is not a display reset *here*: a recording is only
|
||||
drawn when its source is (re)attached — measured, the curves stay empty through
|
||||
playback — so the operator would be left with a blank chart where the trace used
|
||||
to be. Re-attaching the recording's own source is the path opening it takes: the
|
||||
history, curves, selection and window go back to their defaults, the whole
|
||||
recording is drawn again, and the playhead returns to the start so the chart
|
||||
cursor and the transport agree.
|
||||
|
||||
It touches nothing the device owns (there is no device session in this view) and
|
||||
no preference: the unit the charts are in is kept, unlike ``force_plots.reset``,
|
||||
which drops the display back to raw.
|
||||
"""
|
||||
reader = self.replay.reader()
|
||||
if reader is not None:
|
||||
self.force_plots.set_recording_source(reader)
|
||||
self.force_plots.show_all()
|
||||
# The chart cursor is this view's playhead: both go back to the start. The
|
||||
# cursor is set even without a reader, because a data-only recording has no
|
||||
# video for the engine to move to.
|
||||
self.force_plots.set_cursor(0.0)
|
||||
self.replay_ctl.set_position(0.0)
|
||||
if self.replay_ctl.is_playing():
|
||||
# Playing while the display starts over: restart the play clock, or the
|
||||
# next tick would jump the playhead back to where it already was.
|
||||
self.replay_ctl.start_playback()
|
||||
self.statusBar().showMessage(t("status.reset_display"))
|
||||
|
||||
def _on_pause_toggled(self, paused: bool) -> None:
|
||||
"""Pause the pipeline and the display; the device stays open.
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ Everything that is *configuration* rather than state — source, camera number,
|
||||
video file, units and the sensor/config pickers — lives in the settings area,
|
||||
collapsed by default. Both live inside one scroll area so a narrow window
|
||||
scrolls sideways instead of squeezing the controls into an unreadable row.
|
||||
|
||||
The reset is the one rail action whose meaning follows the view
|
||||
(``reset_target``): the live view zeroes the sensor tracker, the replay view —
|
||||
which owns no device — puts the display back. Its text and tooltip always name
|
||||
the target that is live *now*, so one control never means two things at once.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -118,6 +123,9 @@ class ToolbarWidget(QWidget):
|
||||
self._video_path = ""
|
||||
#: ``acquire`` while measuring, ``playback`` while replaying a recording.
|
||||
self._run_role = "acquire"
|
||||
#: Whether the control's target can be acted on *now* (see
|
||||
#: ``set_reset_enabled``): it decides which of the two live tooltips is shown.
|
||||
self._reset_available = False
|
||||
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(2, 2, 2, 2)
|
||||
@@ -421,6 +429,9 @@ class ToolbarWidget(QWidget):
|
||||
self.btn_run.blockSignals(False)
|
||||
self._refresh_run_label()
|
||||
self._refresh_stop_label()
|
||||
# The reset is the one rail control whose *target* moves with the view (see
|
||||
# ``reset_target``), so its label is refreshed with the other two.
|
||||
self._refresh_reset_label()
|
||||
|
||||
def run_role(self) -> str:
|
||||
return self._run_role
|
||||
@@ -490,7 +501,28 @@ class ToolbarWidget(QWidget):
|
||||
self.btn_stop.setEnabled(bool(enabled))
|
||||
|
||||
def set_reset_enabled(self, enabled: bool) -> None:
|
||||
self.btn_reset.setEnabled(bool(enabled))
|
||||
"""Offer the reset, and say in its tooltip what it can act on right now.
|
||||
|
||||
The button's *target* is the view's (``reset_target``); this is only whether
|
||||
that target is actionable — a live session for the tracker, always for the
|
||||
display — so the tip explains a disabled live button rather than leaving the
|
||||
operator to guess what is missing.
|
||||
"""
|
||||
self._reset_available = bool(enabled)
|
||||
self.btn_reset.setEnabled(self._reset_available)
|
||||
self._refresh_reset_label()
|
||||
|
||||
def reset_target(self) -> str:
|
||||
"""What the rail's reset control acts on in this view: ``sensor`` or ``display``.
|
||||
|
||||
One control, one meaning *per view*, and this is the single place that decides
|
||||
it: the live view has a device session, so its reset zeroes the tracker; the
|
||||
replay view has none, so the same button puts the display back instead of being
|
||||
a dead control ("the reset button cannot be used in replay mode"). The window
|
||||
routes its click on this answer and ``_refresh_reset_label`` names it, so the
|
||||
behaviour and the label cannot drift apart.
|
||||
"""
|
||||
return "display" if self._run_role == "playback" else "sensor"
|
||||
|
||||
def set_camera_controls_enabled(self, enabled: bool) -> None:
|
||||
live = self._source_view == "live"
|
||||
@@ -553,6 +585,7 @@ class ToolbarWidget(QWidget):
|
||||
self._refresh_preview_label()
|
||||
self._refresh_run_label()
|
||||
self._refresh_stop_label()
|
||||
self._refresh_reset_label()
|
||||
|
||||
# ------------------------------------------------------------- internals
|
||||
def _populate_units_combo(self) -> None:
|
||||
@@ -797,11 +830,24 @@ class ToolbarWidget(QWidget):
|
||||
self.btn_run.setToolTip(tip)
|
||||
|
||||
def _refresh_reset_label(self) -> None:
|
||||
self.btn_reset.setText(t("toolbar.reset"))
|
||||
# The button zeroes the *device* tracker; it no longer clears the curves.
|
||||
# That is now 视图 → 复位视图, because one button doing both meant an
|
||||
# operator who wanted a fresh baseline also lost the history on screen.
|
||||
self.btn_reset.setToolTip(t("toolbar.reset_tip"))
|
||||
"""Say which of the two things this button will do, where it is on screen.
|
||||
|
||||
The operator's complaint was that 重置 read as "reset the charts" when it zeroes
|
||||
the *sensor tracker* — and that in the replay view, where it is the display that
|
||||
can be reset, it was unavailable. The label therefore names the target and the
|
||||
tip states the side effect that is real but invisible: zeroing the tracker also
|
||||
restarts the display baseline (the curve history is kept).
|
||||
"""
|
||||
if self.reset_target() == "display":
|
||||
self.btn_reset.setText(t("toolbar.reset_display"))
|
||||
self.btn_reset.setToolTip(t("toolbar.reset_display_tip"))
|
||||
return
|
||||
self.btn_reset.setText(t("toolbar.reset_sensor"))
|
||||
self.btn_reset.setToolTip(
|
||||
t("toolbar.reset_sensor_tip")
|
||||
if self._reset_available
|
||||
else t("toolbar.reset_sensor_unavailable")
|
||||
)
|
||||
|
||||
def retranslate(self) -> None:
|
||||
self.cam_id_label.setText(t("toolbar.cam_id"))
|
||||
|
||||
Reference in New Issue
Block a user