diff --git a/apps/studio/config/locales/en_US.json b/apps/studio/config/locales/en_US.json index 1d03c0b..2881620 100644 --- a/apps/studio/config/locales/en_US.json +++ b/apps/studio/config/locales/en_US.json @@ -504,7 +504,7 @@ "finger_run_unavailable": "Run is not available for this sensor type", "video_required": "Select a video file first", "config_missing": "Sensor config not found for {type}", - "units_raw_fallback": "Calibration unavailable — showing raw units", + "units_raw_fallback": "No usable calibration; showing raw for now", "reveal_failed": "Could not reveal config: {error}" }, "scene3d": { diff --git a/apps/studio/config/locales/zh_CN.json b/apps/studio/config/locales/zh_CN.json index 970ec78..d652cac 100644 --- a/apps/studio/config/locales/zh_CN.json +++ b/apps/studio/config/locales/zh_CN.json @@ -504,7 +504,7 @@ "finger_run_unavailable": "此传感器类型暂不支持运行", "video_required": "请先选择视频文件", "config_missing": "未找到传感器配置: {type}", - "units_raw_fallback": "无标定数据 — 显示原始单位", + "units_raw_fallback": "无可用标定,暂显示原始单位", "reveal_failed": "无法打开配置位置: {error}" }, "scene3d": { diff --git a/apps/studio/src/orisys_studio/acquire_presenter.py b/apps/studio/src/orisys_studio/acquire_presenter.py index 16487b4..4db5c2b 100644 --- a/apps/studio/src/orisys_studio/acquire_presenter.py +++ b/apps/studio/src/orisys_studio/acquire_presenter.py @@ -152,6 +152,7 @@ class AcquirePresenter(QObject): if not self._host.session.raw_fallback_notified: self._host.session.raw_fallback_notified = True self._host.notify(t("status.units_raw_fallback")) + self._charts.show_unit_fallback() else: self._host.session.raw_fallback_notified = False self._host.session.last_sample_valid = True diff --git a/apps/studio/src/orisys_studio/replay_controller.py b/apps/studio/src/orisys_studio/replay_controller.py index f019a37..5931101 100644 --- a/apps/studio/src/orisys_studio/replay_controller.py +++ b/apps/studio/src/orisys_studio/replay_controller.py @@ -1411,6 +1411,7 @@ class ReplayController(QObject): self._host.set_display_unit(unit) if unit != wanted: self._host.notify(t("status.units_raw_fallback")) + self._charts.show_unit_fallback() self._charts.show_all() self._image_panel.set_source_kind("replay") return @@ -1483,6 +1484,7 @@ class ReplayController(QObject): wanted = "N" if self._charts.unit() == "N" else "raw" if wanted == "N" and not source.unit_available("N"): self._host.notify(t("status.units_raw_fallback")) + self._charts.show_unit_fallback() return "raw" return wanted diff --git a/apps/studio/src/orisys_studio/ui/force_plots.py b/apps/studio/src/orisys_studio/ui/force_plots.py index 645f9da..deaeab1 100644 --- a/apps/studio/src/orisys_studio/ui/force_plots.py +++ b/apps/studio/src/orisys_studio/ui/force_plots.py @@ -16,7 +16,7 @@ from orisys_app_shared.force.source import StoreCurveSource from orisys_app_shared.i18n import t from orisys_app_shared.record.sample import plottable_axis_mask from orisys_app_shared.theme import color -from PySide6.QtCore import QSize, Qt, Signal +from PySide6.QtCore import QSize, Qt, QTimer, Signal from PySide6.QtGui import QColor, QFont, QFontDatabase, QFontMetrics from PySide6.QtWidgets import ( QFrame, @@ -252,6 +252,13 @@ class ForcePlotsPanel(QFrame): self.btn_settings = self._header_button("settings") self.btn_settings.clicked.connect(self.plot_settings_requested.emit) header.addWidget(self.btn_settings) + self.unit_notice = QLabel() + self.unit_notice.setObjectName("hintText") + self.unit_notice.hide() + header.addWidget(self.unit_notice) + self._unit_notice_timer = QTimer(self) + self._unit_notice_timer.setSingleShot(True) + self._unit_notice_timer.timeout.connect(self.unit_notice.hide) # The unit chip is a button: switching between newton and raw is a chart # concern, so it lives on the chart. self.unit_badge = QPushButton() @@ -733,6 +740,12 @@ class ForcePlotsPanel(QFrame): self._last_overview_at = now self._sync_overview() + def show_unit_fallback(self) -> None: + """Explain a failed Newton switch beside the unit control.""" + self.unit_notice.setText(t("status.units_raw_fallback")) + self.unit_notice.show() + self._unit_notice_timer.start(6000) + def set_units(self, unit: str) -> None: """Switch display units without mixing units in the curve buffer. @@ -742,6 +755,9 @@ class ForcePlotsPanel(QFrame): cleared rather than reinterpreted. """ unit = "N" if str(unit).upper().startswith("N") else "raw" + if unit == "N": + self._unit_notice_timer.stop() + self.unit_notice.hide() if unit == self._unit: return self._unit = unit @@ -1450,6 +1466,7 @@ class ForcePlotsPanel(QFrame): self.btn_settings.setAccessibleName(t("chart.settings")) self.unit_badge.setToolTip(t("chart.unit_toggle_tip")) self.unit_badge.setAccessibleName(t("chart.unit_toggle")) + self.unit_notice.setText(t("status.units_raw_fallback")) self.window_caption.setText(t("chart.overview")) self.window_caption.setToolTip(t("chart.window_tip")) self._refresh_mode_label() diff --git a/apps/studio/tests/test_force_plots_display.py b/apps/studio/tests/test_force_plots_display.py index d18413b..998a5c4 100644 --- a/apps/studio/tests/test_force_plots_display.py +++ b/apps/studio/tests/test_force_plots_display.py @@ -3,6 +3,7 @@ from __future__ import annotations import numpy as np +from orisys_app_shared.i18n import t from orisys_studio.ui.force_plots import ForcePlotsPanel, _nice_ticks @@ -28,6 +29,19 @@ def _curve(panel: ForcePlotsPanel, channel: int) -> np.ndarray: # ------------------------------------------------------------- force output +def test_missing_calibration_notice_sits_beside_unit_button(qt_app): + panel = _panel("raw") + assert not panel.unit_notice.isVisible() + panel.show_unit_fallback() + assert panel.unit_notice.text() == t("status.units_raw_fallback") + assert not panel.unit_notice.isHidden() + header = panel.layout().itemAt(0).layout() + assert header.itemAt(header.count() - 2).widget() is panel.unit_notice + assert header.itemAt(header.count() - 1).widget() is panel.unit_badge + panel.set_units("N") + assert panel.unit_notice.isHidden() + + def test_raw_threshold_and_precision_match_cards_and_curve(qt_app): panel = _panel("raw") panel.set_deadband_floors(raw=50.0) diff --git a/apps/studio/tests/test_replay_controller.py b/apps/studio/tests/test_replay_controller.py index 8234e1e..1666988 100644 --- a/apps/studio/tests/test_replay_controller.py +++ b/apps/studio/tests/test_replay_controller.py @@ -111,6 +111,7 @@ class _Charts: self.followed = 0 self.shown = 0 self.cleared = 0 + self.unit_fallbacks = 0 def unit(self) -> str: return self._unit @@ -134,6 +135,9 @@ class _Charts: def show_all(self) -> None: self.shown += 1 + def show_unit_fallback(self) -> None: + self.unit_fallbacks += 1 + class _ImagePanel: def __init__(self) -> None: @@ -584,6 +588,7 @@ def test_the_charts_fall_back_to_raw_when_a_recording_has_no_newtons(qt_app): assert engine.units[-1] == "raw" assert host.units[-1] == "raw" assert t("status.units_raw_fallback") in host.notes + assert parts[1].unit_fallbacks == 1 engine._reader = _Reader(newtons=[1.0, 2.0, 3.0]) controller.apply_source(True)