Files
sras-viewer/sras_viewer/common.py
T
Thomas Ales 26a34f7436 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>
2026-08-06 10:47:34 -05:00

116 lines
4.0 KiB
Python

"""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