Source code for QPolargraph.patterns.QScanPatternWidget

from __future__ import annotations

from dataclasses import dataclass
from qtpy import QtCore, QtWidgets
from QPolargraph.patterns.QScanPattern import QScanPattern


@dataclass
class FieldSpec:
    '''Specification for a single spinbox field in a scan pattern widget.

    Parameters
    ----------
    name : str
        Attribute name on both the widget and the scan pattern.
    label : str
        Display text for the label widget.
    suffix : str
        Spinbox suffix string (e.g. ``' m'``, ``' mm'``).
    minimum : float
        Spinbox minimum value.
    maximum : float
        Spinbox maximum value.
    single_step : float
        Spinbox increment per step.
    value : float
        Default value.
    decimals : int
        Number of decimal places displayed.  Default: 2.
    tooltip : str
        Optional tooltip text for the spinbox.  Default: empty.
    '''
    name: str
    label: str
    suffix: str
    minimum: float
    maximum: float
    single_step: float
    value: float
    decimals: int = 2
    tooltip: str = ''


[docs] class QScanPatternWidget(QtWidgets.QWidget): '''Widget for controlling scan-pattern parameters. Builds a compact grid of label/spinbox pairs from :attr:`_FIELD_SPECS` and keeps them in sync with a :class:`~QPolargraph.QScanPattern.QScanPattern` instance. Subclasses extend :attr:`_FIELD_SPECS` to add fields without needing a separate ``.ui`` file: .. code-block:: python class MyWidget(QScanPatternWidget): _FIELD_SPECS = QScanPatternWidget._FIELD_SPECS + [ FieldSpec('x0', 'x₀', ' m', -1., 1., 0.001, 0.), ] Signals ------- patternChanged() Emitted whenever a scan parameter is changed by the user. ''' patternChanged = QtCore.Signal() _FIELD_SPECS: list[FieldSpec] = [ FieldSpec('width', 'width', ' m', 0.1, 10.0, 0.01, 0.6), FieldSpec('height', 'height', ' m', 0.1, 10.0, 0.01, 0.6), FieldSpec('dx', 'Δx', ' m', -1.0, 1.0, 0.01, 0.0), FieldSpec('dy', 'Δy', ' m', -1.0, 1.0, 0.01, 0.1), FieldSpec('step', 'step', ' mm', 1.0, 100.0, 1.0, 5.0, decimals=1), ] def __init__(self, *args, pattern: QScanPattern | None = None, **kwargs): super().__init__(*args, **kwargs) self._pattern = None self.setupUi() self._connectSignals() if pattern is not None: self.pattern = pattern
[docs] def setupUi(self) -> None: layout = QtWidgets.QGridLayout(self) layout.setContentsMargins(2, 1, 2, 1) layout.setHorizontalSpacing(6) layout.setVerticalSpacing(1) for i, spec in enumerate(self._FIELD_SPECS): row, col = divmod(i, 2) label = QtWidgets.QLabel(spec.label) label.setAlignment( QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter) spinbox = QtWidgets.QDoubleSpinBox() spinbox.setSuffix(spec.suffix) spinbox.setMinimum(spec.minimum) spinbox.setMaximum(spec.maximum) spinbox.setSingleStep(spec.single_step) spinbox.setDecimals(spec.decimals) spinbox.setValue(spec.value) spinbox.setSizePolicy( QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) if spec.tooltip: spinbox.setToolTip(spec.tooltip) label.setBuddy(spinbox) layout.addWidget(label, row, col * 2) layout.addWidget(spinbox, row, col * 2 + 1) setattr(self, spec.name, spinbox)
@property def pattern(self) -> QScanPattern | None: '''The scan pattern controlled by this widget.''' return self._pattern @pattern.setter def pattern(self, value: QScanPattern | None) -> None: self._pattern = value if value is not None: self._syncFromPattern() def _syncFromPattern(self) -> None: for spec in self._FIELD_SPECS: widget = getattr(self, spec.name) with QtCore.QSignalBlocker(widget): widget.setValue(getattr(self.pattern, spec.name)) def _connectSignals(self) -> None: for spec in self._FIELD_SPECS: signal = getattr(self, spec.name).valueChanged signal.connect(self._updatePattern) def _updatePattern(self) -> None: if self._pattern is None: return for spec in self._FIELD_SPECS: setattr(self.pattern, spec.name, getattr(self, spec.name).value()) self.patternChanged.emit() @property def settings(self) -> dict: '''Current scan parameter values, suitable for save/restore.''' return {spec.name: getattr(self, spec.name).value() for spec in self._FIELD_SPECS} @settings.setter def settings(self, values: dict) -> None: for name, value in values.items(): widget = getattr(self, name, None) if isinstance(widget, QtWidgets.QDoubleSpinBox): with QtCore.QSignalBlocker(widget): widget.setValue(float(value)) self._updatePattern()
[docs] @classmethod def example(cls) -> None: import sys from QPolargraph.hardware.fake import FakePolargraph from QPolargraph.patterns.PolarScan import PolarScan app = QtWidgets.QApplication.instance() or QtWidgets.QApplication(sys.argv) widget = cls(pattern=PolarScan(polargraph=FakePolargraph())) widget.show() sys.exit(app.exec())
if __name__ == '__main__': QScanPatternWidget.example()