Split sras_viewer.py into a package (pure move)
The 2790-line module becomes sras_viewer/: common.py (constants + layout helpers), canvases.py (RoiQuad, ImageCanvas, WaveformCanvas, ManualAlignOverlayCanvas), dialogs.py (FftOptionsDialog, ManualAlignmentDialog), main_window.py (SrasViewerWindow + main), with __init__ re-exporting the public names and __main__ keeping `python -m sras_viewer` working. pyproject gains a `sras-viewer` console script. Code moved verbatim; only import headers are new (pyflakes-clean). tests/test_gui.py patch targets follow the classes to their new modules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""Shared constants and small layout helpers for the viewer widgets."""
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import (
|
||||
QFormLayout, QFrame, QGroupBox, QLabel, QScrollArea, QSizePolicy,
|
||||
QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
from sras_format import CH1_IDX, CH3_IDX, CH4_IDX
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Display constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CH_LABELS = [
|
||||
"CH1 — RF (FFT peak freq)",
|
||||
"CH3 — Bias A (DC mean)",
|
||||
"CH4 — Bias B (DC mean)",
|
||||
"CH1 — Velocity (SRAS)",
|
||||
]
|
||||
|
||||
# Combo index for the derived velocity mode (uses CH1_IDX data)
|
||||
VELOCITY_MODE_IDX = 3
|
||||
# All modes that operate on CH1 waveforms
|
||||
CH1_DERIVED_MODES = (CH1_IDX, VELOCITY_MODE_IDX)
|
||||
|
||||
CMAPS = ["gray", "viridis", "plasma", "inferno", "hot", "jet", "RdBu_r", "seismic"]
|
||||
|
||||
# (mode_str, status-bar unit, colorbar label) per channel index
|
||||
_CHANNEL_DISPLAY = {
|
||||
CH1_IDX: ("RF", "Peak frequency (MHz)", "MHz"),
|
||||
CH3_IDX: ("DC", "DC mean (mV)", "mV"),
|
||||
CH4_IDX: ("DC", "DC mean (mV)", "mV"),
|
||||
VELOCITY_MODE_IDX: ("Velocity", "Velocity (m/s)", "m/s"),
|
||||
}
|
||||
|
||||
_CSS_HINT = "font-size: 11px; color: #aaa;"
|
||||
_CSS_INFO = "font-size: 11px;"
|
||||
_CSS_MUTED = "color: #888; font-size: 11px;"
|
||||
_CSS_WARN = "color: #e07000; font-size: 11px;"
|
||||
_CSS_BUSY = "color: #4a90d9; font-size: 11px;"
|
||||
|
||||
# Side-panel column widths (the scroll areas that hold the controls).
|
||||
_LEFT_PANEL_W = 288
|
||||
_RIGHT_PANEL_W = 272
|
||||
|
||||
# Minimum width for a spin box so its value + suffix are never clipped.
|
||||
_SPIN_MIN_W = 96
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Small layout helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _wrap_label(text: str = "", css: str | None = None) -> QLabel:
|
||||
"""A word-wrapped QLabel that reports its *wrapped* height to the layout.
|
||||
|
||||
A plain word-wrapped QLabel advertises a single-line minimum height, so in a
|
||||
fixed-width column the layout happily shrinks it and the extra lines get
|
||||
clipped. Enabling height-for-width makes the box layout ask for the real
|
||||
height at the column's width instead.
|
||||
"""
|
||||
lbl = QLabel(text)
|
||||
lbl.setWordWrap(True)
|
||||
sp = lbl.sizePolicy()
|
||||
sp.setVerticalPolicy(QSizePolicy.Policy.Minimum)
|
||||
sp.setHeightForWidth(True)
|
||||
lbl.setSizePolicy(sp)
|
||||
if css:
|
||||
lbl.setStyleSheet(css)
|
||||
return lbl
|
||||
|
||||
|
||||
def _group(title: str) -> tuple[QGroupBox, QVBoxLayout]:
|
||||
"""A group box with consistent, non-cramped internal margins."""
|
||||
grp = QGroupBox(title)
|
||||
lay = QVBoxLayout(grp)
|
||||
lay.setContentsMargins(10, 8, 10, 10)
|
||||
lay.setSpacing(6)
|
||||
return grp, lay
|
||||
|
||||
|
||||
def _form() -> QFormLayout:
|
||||
"""A label/field form layout for a narrow side panel."""
|
||||
form = QFormLayout()
|
||||
form.setContentsMargins(0, 0, 0, 0)
|
||||
form.setHorizontalSpacing(8)
|
||||
form.setVerticalSpacing(6)
|
||||
form.setLabelAlignment(Qt.AlignmentFlag.AlignRight
|
||||
| Qt.AlignmentFlag.AlignVCenter)
|
||||
form.setFormAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
|
||||
form.setFieldGrowthPolicy(
|
||||
QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
form.setRowWrapPolicy(QFormLayout.RowWrapPolicy.DontWrapRows)
|
||||
return form
|
||||
|
||||
|
||||
def _scroll_panel(inner: QWidget, width: int) -> QScrollArea:
|
||||
"""Put a side panel in a fixed-width scroll area.
|
||||
|
||||
Without this the panels are sized by the window: a short window squeezes the
|
||||
controls past their minimum heights, which is what makes text overlap the
|
||||
widget below it. Scrolling keeps every control at its natural size.
|
||||
"""
|
||||
area = QScrollArea()
|
||||
area.setWidget(inner)
|
||||
area.setWidgetResizable(True)
|
||||
area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||
area.setFixedWidth(width)
|
||||
area.viewport().setAutoFillBackground(False)
|
||||
inner.setAutoFillBackground(False)
|
||||
return area
|
||||
|
||||
Reference in New Issue
Block a user