曲线图表头加齿轮,绘图设置对话框里可以直接调滤波 - What: ui/icons.py 新增 settings(齿轮)图标(八个齿 + 圆环,颜色取自主题 token,与另外三个图标同一套画法);force_plots.py 表头新增 btn_settings 与 plot_settings_requested 信号(放在缩放按钮之后、单位芯片之前),换主题时一并重绘图标,retranslate 里补 tooltip/可访问名;plot_settings_dialog.py 增加"滤波"分区:预设下拉(off/low_latency/balanced/low_noise + 自定义,显示的就是写进 sensor.json 的那个名字)、光流 EMA α(0.05–1.00,1.00 = 关)、中值窗口(1–31,1 = 关)、低通截止(0–60 Hz,0 = 关)、低通阶数(1–6)、采样率("自动跟随帧率"复选框 + 固定值)、"同时显示未滤波曲线";选预设即写入该预设的数值,手改任一数值自动切到"自定义",选中的预设名会随 filter_values() 一起带出(preset 字段),"_reset_defaults" 回到"什么都不滤";对话框底部按当前数值给提示:光流 EMA 打开时提示"这是测量设置、两次录制不可比"、全关时提示"当前不滤波"、录制中显示"录制中不能更改滤波"并禁用整个分区;截止频率达到或超过奈奎斯特(帧率一半)时用 dialog.filter_nyquist 明确说出"设备不会滤",其中 cutoff/nyquist/rate 三个数字由 FilterSettings.nyquist_issue() 提供(problems() 仍返回英文串供日志与测试用,判断条件只有一处)。filters.py 新增 FALLBACK_PRESETS 与 available_presets()(SDK 能导入就用 SDK 的 FILTER_PRESETS,导入不了才用这份文档副本,测试保证两者逐值一致)。app_window.py:把 plot_settings_requested 接到 _on_plot_settings,打开对话框时传入当前 _filter_settings()、filters_locked(正在跑或正在录)、以及 _device_sample_rate()(实测帧率优先,否则 30 fps,用于奈奎斯特检查);接受后把 filter_values().as_dict() 逐键写成 plot_* 偏好(与 _filter_settings() 读回的是同一组键),未在采集时立刻 set_filter_settings() 交给两个 worker,并在状态栏回报"已保存:{describe}(下次采集生效)"。两个 locale 文件补齐 21+3 个新键,中英各 343 键一一对应。tests/test_ui_smoke.py 新增 4 项:齿轮确实触发设置信号、预设/自定义切换与数值一致、录制中分区被禁用且给出理由、对话框写的键与 _filter_settings 读回的键是同一组。 - Why: 用户要求"建议曲线图增加一个齿轮的配置按键,可以调节曲线图的上限和滤波等"。此前滤波只能靠手改 preferences.json 的 plot_* 键(上一轮留下的口子),而 app 默认已改成"什么都不滤"——没有这个入口,等于把默认变成了唯一选择。 - Verification: studio 全量 89 passed(原 85 + 新增 4);shared 全量 158 passed;ui_check 554 项检查 0 失败;.repro/locale_check.py 扫描 64 个源文件里 287 个 t("…") 字面键,两个 locale 全部命中且键集合完全相同(343/343);离屏实测对话框:预设 balanced→flowEMA(0.5)+median(3)、改中值→自定义、选 low_noise→带低通、30 Hz 采样率下填 30 Hz 截止→弹出奈奎斯特警告、恢复默认→off/不滤波。 - Not in this change: CSV 的 17 列与未滤波第二 buffer(SessionStore/记录格式版本)、回放界面显示滤波前曲线、标定面板显示 filters 块、发布说明里"默认不再滤波"的措辞。
This commit is contained in:
@@ -658,6 +658,8 @@ class StudioMainWindow(QMainWindow):
|
||||
self.arrow_panel.view_mode_changed.connect(lambda _mode: self._render_replay_frame())
|
||||
# The unit chip on the chart header switches the same unit everywhere.
|
||||
self.force_plots.unit_toggle_requested.connect(self._on_chart_unit_toggle)
|
||||
# The gear on the chart header opens the same dialog as 视图 ▸ 曲线图设置.
|
||||
self.force_plots.plot_settings_requested.connect(self._on_plot_settings)
|
||||
|
||||
def _wire_replay(self) -> None:
|
||||
"""The controller feeds the views; the chart is the only timeline."""
|
||||
@@ -1595,6 +1597,11 @@ class StudioMainWindow(QMainWindow):
|
||||
self.prefs.get("plot_shear_raw_max") or DEFAULT_PLOT_SHEAR_RAW_MAX
|
||||
),
|
||||
units=self.toolbar.units(),
|
||||
filters=self._filter_settings(),
|
||||
# Asked *now*: the answer is a property of this moment, and the
|
||||
# dialog is the place that has to say "next session, not this one".
|
||||
filters_locked=bool(self._running or self.recorder.is_recording),
|
||||
sample_rate_hz=self._device_sample_rate(),
|
||||
)
|
||||
if dialog.exec_() != PlotSettingsDialog.Accepted:
|
||||
return
|
||||
@@ -1604,10 +1611,40 @@ class StudioMainWindow(QMainWindow):
|
||||
self.prefs["plot_shear_max"] = shear_max
|
||||
self.prefs["plot_fn_raw_max"] = fn_raw_max
|
||||
self.prefs["plot_shear_raw_max"] = shear_raw_max
|
||||
filters = dialog.filter_values()
|
||||
# The same keys ``_filter_settings()`` reads back, so what the dialog
|
||||
# shows next time is what the session will actually be given.
|
||||
for key, value in filters.as_dict().items():
|
||||
self.prefs[f"plot_{key}"] = value
|
||||
self.force_plots.set_window_sec(window_sec)
|
||||
self.force_plots.set_axis_limits(fn_max, shear_max, fn_raw_max, shear_raw_max)
|
||||
self._last_plot_packet_key = None
|
||||
self._persist_prefs()
|
||||
if not self._running and not self.recorder.is_recording:
|
||||
# Nothing is streaming: hand the choice over now, so the next session
|
||||
# starts with it even if the operator never touches the dialog again.
|
||||
self._ensure_workers()
|
||||
for worker in (self._box_worker, self._finger_worker):
|
||||
if worker is not None:
|
||||
worker.set_filter_settings(filters)
|
||||
self.statusBar().showMessage(
|
||||
t("status.filters_saved", describe=filters.describe())
|
||||
)
|
||||
|
||||
def _device_sample_rate(self) -> float:
|
||||
"""The frame rate the low-pass coefficients would be designed for.
|
||||
|
||||
The device's measured rate once it has one, else the configured 30 fps —
|
||||
the dialog needs a number to check a cutoff against Nyquist.
|
||||
"""
|
||||
for source in (self._latest_fps, self.prefs.get("measured_fps")):
|
||||
try:
|
||||
rate = float(source or 0.0)
|
||||
except (TypeError, ValueError):
|
||||
rate = 0.0
|
||||
if rate > 0:
|
||||
return rate
|
||||
return float(FORCE_SAMPLE_RATE_HZ)
|
||||
|
||||
def _apply_plot_prefs_to_panel(self) -> None:
|
||||
self.force_plots.set_window_sec(
|
||||
|
||||
@@ -118,6 +118,8 @@ class ForcePlotsPanel(QFrame):
|
||||
mode_changed = pyqtSignal(str)
|
||||
#: The unit chip was clicked: switch between newton and raw.
|
||||
unit_toggle_requested = pyqtSignal()
|
||||
#: The gear was clicked: open the plot settings (window, range, filters).
|
||||
plot_settings_requested = pyqtSignal()
|
||||
#: The operator took the timeline over (dragged the cursor, box-zoomed, panned
|
||||
#: or moved the overview marker): playback stops where it is.
|
||||
timeline_taken_over = pyqtSignal()
|
||||
@@ -194,6 +196,12 @@ class ForcePlotsPanel(QFrame):
|
||||
self.btn_box_zoom.setCheckable(True)
|
||||
self.btn_box_zoom.toggled.connect(self._on_box_zoom_toggled)
|
||||
header.addWidget(self.btn_box_zoom)
|
||||
# Window, range *and filters*. The filters belong here because they are
|
||||
# what the curves are made of: the chain runs in the SDK, so this opens
|
||||
# the one place the operator can say what the acquisition should do.
|
||||
self.btn_settings = self._header_button("settings")
|
||||
self.btn_settings.clicked.connect(self.plot_settings_requested.emit)
|
||||
header.addWidget(self.btn_settings)
|
||||
# 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()
|
||||
@@ -396,7 +404,8 @@ class ForcePlotsPanel(QFrame):
|
||||
text = color(theme, "text_primary")
|
||||
muted = color(theme, "text_muted")
|
||||
# Icons are painted in the theme's own colour, so they follow a switch.
|
||||
for button in (self.btn_latest, self.btn_overview, self.btn_box_zoom):
|
||||
for button in (self.btn_latest, self.btn_overview, self.btn_box_zoom,
|
||||
self.btn_settings):
|
||||
name = getattr(button, "_icon_name", "")
|
||||
if name:
|
||||
button.setIcon(icons.icon(name, theme))
|
||||
@@ -1220,6 +1229,8 @@ class ForcePlotsPanel(QFrame):
|
||||
self.btn_overview.setAccessibleName(t("chart.overview"))
|
||||
self.btn_box_zoom.setToolTip(t("chart.box_zoom_tip"))
|
||||
self.btn_box_zoom.setAccessibleName(t("chart.box_zoom"))
|
||||
self.btn_settings.setToolTip(t("chart.settings_tip"))
|
||||
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.window_caption.setText(t("chart.overview"))
|
||||
|
||||
@@ -20,7 +20,7 @@ from PyQt5.QtGui import QColor, QIcon, QPainter, QPen, QPixmap
|
||||
from orisys_app_shared.theme import color
|
||||
|
||||
#: Names of the icons this module can paint.
|
||||
NAMES = ("zoom_fit", "box_zoom", "latest")
|
||||
NAMES = ("zoom_fit", "box_zoom", "latest", "settings")
|
||||
|
||||
ICON_SIZE = 16
|
||||
_PEN_WIDTH = 1.4
|
||||
@@ -76,10 +76,28 @@ def _paint_latest(painter: QPainter, size: int) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _paint_settings(painter: QPainter, size: int) -> None:
|
||||
"""A cog: eight teeth around a ring, with the hole left open."""
|
||||
import math
|
||||
|
||||
centre = size / 2.0
|
||||
ring = size * 0.20
|
||||
tooth = size * 0.34
|
||||
painter.drawEllipse(QRectF(centre - ring, centre - ring, ring * 2, ring * 2))
|
||||
for index in range(8):
|
||||
angle = index * math.pi / 4.0
|
||||
dx, dy = math.cos(angle), math.sin(angle)
|
||||
painter.drawLine(
|
||||
int(round(centre + dx * ring)), int(round(centre + dy * ring)),
|
||||
int(round(centre + dx * tooth)), int(round(centre + dy * tooth)),
|
||||
)
|
||||
|
||||
|
||||
_PAINTERS = {
|
||||
"zoom_fit": _paint_zoom_fit,
|
||||
"box_zoom": _paint_box_zoom,
|
||||
"latest": _paint_latest,
|
||||
"settings": _paint_settings,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
"""Modal dialog for force-plot window and Y-axis limits."""
|
||||
"""Modal dialog for the force-plot window, Y-axis limits and the filters.
|
||||
|
||||
Two things live here because they are answered together, at the chart: how much
|
||||
time and range the curves show, and what the acquisition is allowed to filter.
|
||||
The filter half is a *request*: it is handed to the SDK through
|
||||
``FilterSettings.apply_to`` at the start of the next session, and what the device
|
||||
reports as done is what the record will say (see ``force/filters.py``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QDoubleSpinBox,
|
||||
QFormLayout,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSpinBox,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_app_shared.acquisition.constants import FORCE_SAMPLE_RATE_HZ
|
||||
from orisys_app_shared.force.filters import (
|
||||
SUGGESTED_LOWPASS_HZ,
|
||||
FilterSettings,
|
||||
available_presets,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
DEFAULT_PLOT_WINDOW_SEC = 4.0
|
||||
@@ -35,10 +52,17 @@ class PlotSettingsDialog(QDialog):
|
||||
fn_raw_max: float = DEFAULT_PLOT_FN_RAW_MAX,
|
||||
shear_raw_max: float = DEFAULT_PLOT_SHEAR_RAW_MAX,
|
||||
units: str = "newton",
|
||||
filters: FilterSettings | None = None,
|
||||
filters_locked: bool = False,
|
||||
sample_rate_hz: float = FORCE_SAMPLE_RATE_HZ,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.setModal(True)
|
||||
self.resize(380, 260)
|
||||
self.resize(420, 520)
|
||||
self._sample_rate_hz = float(sample_rate_hz)
|
||||
#: True while the widgets are being filled in from a preset, so the
|
||||
#: "edited -> custom" handler does not fire on our own writes.
|
||||
self._syncing_filters = False
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
form = QFormLayout()
|
||||
@@ -102,6 +126,8 @@ class PlotSettingsDialog(QDialog):
|
||||
for widget in raw_widgets:
|
||||
widget.setEnabled(not newton)
|
||||
|
||||
self._build_filter_section(root, filters or FilterSettings(), filters_locked)
|
||||
|
||||
buttons_row = QHBoxLayout()
|
||||
self.btn_reset = QPushButton()
|
||||
self.btn_reset.clicked.connect(self._reset_defaults)
|
||||
@@ -115,6 +141,195 @@ class PlotSettingsDialog(QDialog):
|
||||
|
||||
self.retranslate()
|
||||
|
||||
# ---------------------------------------------------------------- filters
|
||||
def _build_filter_section(
|
||||
self, root: QVBoxLayout, filters: FilterSettings, locked: bool
|
||||
) -> None:
|
||||
"""The filter half: a preset, its values, and what they cost."""
|
||||
self.filters_group = QGroupBox()
|
||||
group_form = QFormLayout(self.filters_group)
|
||||
|
||||
# The preset is shown by its own name — ``balanced`` is the string that
|
||||
# goes into ``sensor.json``, so the dialog and the config use one word for
|
||||
# one thing.
|
||||
self.preset_combo = QComboBox()
|
||||
self._preset_names = list(available_presets())
|
||||
for name in self._preset_names:
|
||||
self.preset_combo.addItem(name)
|
||||
self.preset_combo.addItem(t("dialog.filter_preset_custom"))
|
||||
self.preset_combo.currentIndexChanged.connect(self._on_preset_chosen)
|
||||
|
||||
self.flow_ema_spin = QDoubleSpinBox()
|
||||
self.flow_ema_spin.setRange(0.05, 1.0)
|
||||
self.flow_ema_spin.setSingleStep(0.05)
|
||||
self.flow_ema_spin.setDecimals(2)
|
||||
self.flow_ema_spin.setValue(float(filters.flow_ema_alpha))
|
||||
|
||||
self.median_spin = QSpinBox()
|
||||
self.median_spin.setRange(1, 31)
|
||||
self.median_spin.setValue(int(filters.median_window))
|
||||
|
||||
self.lowpass_spin = QDoubleSpinBox()
|
||||
self.lowpass_spin.setRange(0.0, 60.0)
|
||||
self.lowpass_spin.setSingleStep(1.0)
|
||||
self.lowpass_spin.setDecimals(1)
|
||||
self.lowpass_spin.setSuffix(" Hz")
|
||||
self.lowpass_spin.setValue(float(filters.lowpass_hz))
|
||||
|
||||
self.lowpass_order_spin = QSpinBox()
|
||||
self.lowpass_order_spin.setRange(1, 6)
|
||||
self.lowpass_order_spin.setValue(int(filters.lowpass_order))
|
||||
|
||||
self.rate_auto_check = QCheckBox()
|
||||
self.rate_auto_check.setChecked(str(filters.sample_rate_hz) == "auto")
|
||||
self.rate_spin = QDoubleSpinBox()
|
||||
self.rate_spin.setRange(1.0, 240.0)
|
||||
self.rate_spin.setSingleStep(1.0)
|
||||
self.rate_spin.setDecimals(1)
|
||||
self.rate_spin.setSuffix(" Hz")
|
||||
rate = filters.sample_rate_hz
|
||||
self.rate_spin.setValue(
|
||||
float(rate) if str(rate) != "auto" else float(FORCE_SAMPLE_RATE_HZ)
|
||||
)
|
||||
self.rate_spin.setEnabled(not self.rate_auto_check.isChecked())
|
||||
self.rate_auto_check.toggled.connect(
|
||||
lambda checked: self.rate_spin.setEnabled(not checked)
|
||||
)
|
||||
|
||||
self.show_unfiltered_check = QCheckBox()
|
||||
self.show_unfiltered_check.setChecked(bool(filters.show_unfiltered))
|
||||
|
||||
self.filter_preset_label = QLabel()
|
||||
self.flow_ema_label = QLabel()
|
||||
self.median_label = QLabel()
|
||||
self.lowpass_label = QLabel()
|
||||
self.lowpass_order_label = QLabel()
|
||||
self.rate_label = QLabel()
|
||||
|
||||
group_form.addRow(self.filter_preset_label, self.preset_combo)
|
||||
group_form.addRow(self.flow_ema_label, self.flow_ema_spin)
|
||||
group_form.addRow(self.median_label, self.median_spin)
|
||||
group_form.addRow(self.lowpass_label, self.lowpass_spin)
|
||||
group_form.addRow(self.lowpass_order_label, self.lowpass_order_spin)
|
||||
group_form.addRow(self.rate_auto_check, self.rate_spin)
|
||||
group_form.addRow(self.show_unfiltered_check)
|
||||
|
||||
for widget in (self.flow_ema_spin, self.median_spin, self.lowpass_spin,
|
||||
self.rate_spin, self.rate_auto_check,
|
||||
self.show_unfiltered_check):
|
||||
widget.setToolTip("") # filled in by retranslate()
|
||||
self.flow_ema_spin.valueChanged.connect(self._on_filter_value_edited)
|
||||
self.median_spin.valueChanged.connect(self._on_filter_value_edited)
|
||||
self.lowpass_spin.valueChanged.connect(self._on_filter_value_edited)
|
||||
self.lowpass_order_spin.valueChanged.connect(self._on_filter_value_edited)
|
||||
self.rate_spin.valueChanged.connect(self._on_filter_value_edited)
|
||||
self.rate_auto_check.toggled.connect(self._on_filter_value_edited)
|
||||
|
||||
# Start on the preset the settings came from, or on "custom" when they
|
||||
# are a shape no preset has (a config edited by hand, a read-back state).
|
||||
# A named preset also *means* its values: showing the name next to
|
||||
# different numbers would be the same lie the config file used to tell.
|
||||
if filters.preset in self._preset_names:
|
||||
self._on_preset_chosen(self._preset_names.index(filters.preset))
|
||||
self._show_preset(filters.preset)
|
||||
|
||||
self.filter_note = QLabel()
|
||||
self.filter_note.setObjectName("hintText")
|
||||
self.filter_note.setWordWrap(True)
|
||||
self.filter_warning = QLabel()
|
||||
self.filter_warning.setObjectName("warnText")
|
||||
self.filter_warning.setWordWrap(True)
|
||||
root.addWidget(self.filters_group)
|
||||
root.addWidget(self.filter_note)
|
||||
root.addWidget(self.filter_warning)
|
||||
|
||||
self._filters_locked = bool(locked)
|
||||
self.filters_group.setEnabled(not self._filters_locked)
|
||||
self._refresh_filter_note()
|
||||
|
||||
def _show_preset(self, name: str | None) -> None:
|
||||
"""Point the combo at a preset (or at "custom") without re-applying it."""
|
||||
index = self._preset_names.index(name) if name in self._preset_names else -1
|
||||
self._syncing_filters = True
|
||||
try:
|
||||
self.preset_combo.setCurrentIndex(
|
||||
index if index >= 0 else self.preset_combo.count() - 1
|
||||
)
|
||||
finally:
|
||||
self._syncing_filters = False
|
||||
|
||||
def _on_preset_chosen(self, index: int) -> None:
|
||||
if self._syncing_filters:
|
||||
return
|
||||
if not (0 <= index < len(self._preset_names)):
|
||||
return # "custom": leave the numbers as the operator left them
|
||||
values = available_presets()[self._preset_names[index]]
|
||||
self._syncing_filters = True
|
||||
try:
|
||||
self.flow_ema_spin.setValue(float(values.get("flow_ema_alpha", 1.0)))
|
||||
self.median_spin.setValue(int(values.get("force_median_window", 1)))
|
||||
self.lowpass_spin.setValue(float(values.get("force_lowpass_cutoff_hz", 0.0)))
|
||||
finally:
|
||||
self._syncing_filters = False
|
||||
self._refresh_filter_note()
|
||||
|
||||
def _on_filter_value_edited(self, *_args) -> None:
|
||||
if self._syncing_filters:
|
||||
return
|
||||
# Any hand edit is no longer the preset the combo used to name.
|
||||
self._show_preset(None)
|
||||
self._refresh_filter_note()
|
||||
|
||||
def selected_preset(self) -> str | None:
|
||||
"""The preset the values still are, or ``None`` for a custom shape."""
|
||||
index = self.preset_combo.currentIndex()
|
||||
if 0 <= index < len(self._preset_names):
|
||||
return self._preset_names[index]
|
||||
return None
|
||||
|
||||
def filter_values(self) -> FilterSettings:
|
||||
rate: float | str = "auto"
|
||||
if not self.rate_auto_check.isChecked():
|
||||
rate = float(self.rate_spin.value())
|
||||
return FilterSettings(
|
||||
flow_ema_alpha=float(self.flow_ema_spin.value()),
|
||||
median_window=int(self.median_spin.value()),
|
||||
lowpass_hz=float(self.lowpass_spin.value()),
|
||||
lowpass_order=int(self.lowpass_order_spin.value()),
|
||||
sample_rate_hz=rate,
|
||||
show_unfiltered=self.show_unfiltered_check.isChecked(),
|
||||
preset=self.selected_preset(),
|
||||
).normalised()
|
||||
|
||||
def _refresh_filter_note(self) -> None:
|
||||
"""Say what these numbers amount to, and where they would not work."""
|
||||
if not hasattr(self, "filter_note"):
|
||||
return # still being built
|
||||
settings = self.filter_values()
|
||||
rate = self._sample_rate_hz
|
||||
if str(settings.sample_rate_hz) != "auto":
|
||||
rate = float(settings.sample_rate_hz)
|
||||
issue = settings.nyquist_issue(rate)
|
||||
self.filter_warning.setText(
|
||||
""
|
||||
if issue is None
|
||||
else t(
|
||||
"dialog.filter_nyquist",
|
||||
cutoff=f"{issue['cutoff_hz']:g}",
|
||||
nyquist=f"{issue['nyquist_hz']:g}",
|
||||
rate=f"{issue['rate_hz']:g}",
|
||||
)
|
||||
)
|
||||
self.filter_warning.setVisible(issue is not None)
|
||||
if self._filters_locked:
|
||||
self.filter_note.setText(t("dialog.filter_locked"))
|
||||
elif settings.flow_ema_on:
|
||||
self.filter_note.setText(t("dialog.filter_measurement_note"))
|
||||
elif not settings.active():
|
||||
self.filter_note.setText(t("dialog.filter_off"))
|
||||
else:
|
||||
self.filter_note.setText("")
|
||||
|
||||
def values(self) -> tuple[float, float, float, float, float]:
|
||||
return (
|
||||
float(self.window_spin.value()),
|
||||
@@ -130,6 +345,16 @@ class PlotSettingsDialog(QDialog):
|
||||
self.shear_max_spin.setValue(DEFAULT_PLOT_SHEAR_MAX)
|
||||
self.fn_raw_max_spin.setValue(DEFAULT_PLOT_FN_RAW_MAX)
|
||||
self.shear_raw_max_spin.setValue(DEFAULT_PLOT_SHEAR_RAW_MAX)
|
||||
# "Reset" means the shipped defaults, and those filter nothing — the same
|
||||
# answer the dialog gives for a preference file that says nothing.
|
||||
self.flow_ema_spin.setValue(1.0)
|
||||
self.median_spin.setValue(1)
|
||||
self.lowpass_spin.setValue(0.0)
|
||||
self.lowpass_order_spin.setValue(2)
|
||||
self.rate_auto_check.setChecked(True)
|
||||
self.show_unfiltered_check.setChecked(False)
|
||||
self._show_preset("off")
|
||||
self._refresh_filter_note()
|
||||
|
||||
def retranslate(self) -> None:
|
||||
self.setWindowTitle(t("dialog.plot_settings_title"))
|
||||
@@ -144,6 +369,25 @@ class PlotSettingsDialog(QDialog):
|
||||
self.shear_raw_max_label.setText(t("dialog.plot_shear_raw_max"))
|
||||
self.shear_raw_max_spin.setToolTip(t("dialog.plot_shear_raw_max_tip"))
|
||||
self.btn_reset.setText(t("dialog.reset_defaults"))
|
||||
self.filters_group.setTitle(t("dialog.plot_filters"))
|
||||
self.filter_preset_label.setText(t("dialog.filter_preset"))
|
||||
self.preset_combo.setToolTip(t("dialog.filter_preset_tip"))
|
||||
self.preset_combo.setItemText(self.preset_combo.count() - 1, t("dialog.filter_preset_custom"))
|
||||
self.flow_ema_label.setText(t("dialog.filter_flow_ema"))
|
||||
self.flow_ema_spin.setToolTip(t("dialog.filter_flow_ema_tip"))
|
||||
self.median_label.setText(t("dialog.filter_median"))
|
||||
self.median_spin.setToolTip(t("dialog.filter_median_tip"))
|
||||
self.lowpass_label.setText(t("dialog.filter_lowpass"))
|
||||
self.lowpass_spin.setToolTip(t("dialog.filter_lowpass_tip"))
|
||||
self.lowpass_order_label.setText(t("dialog.filter_order"))
|
||||
self.lowpass_order_spin.setToolTip(t("dialog.filter_order_tip"))
|
||||
self.rate_auto_check.setText(t("dialog.filter_rate_auto"))
|
||||
self.rate_auto_check.setToolTip(t("dialog.filter_rate_auto_tip"))
|
||||
self.rate_label.setText(t("dialog.filter_rate"))
|
||||
self.rate_spin.setToolTip(t("dialog.filter_rate_tip"))
|
||||
self.show_unfiltered_check.setText(t("dialog.filter_show_unfiltered"))
|
||||
self.show_unfiltered_check.setToolTip(t("dialog.filter_show_unfiltered_tip"))
|
||||
self._refresh_filter_note()
|
||||
ok_btn = self.button_box.button(QDialogButtonBox.Ok)
|
||||
cancel_btn = self.button_box.button(QDialogButtonBox.Cancel)
|
||||
if ok_btn is not None:
|
||||
|
||||
Reference in New Issue
Block a user