""" ui/readout_panel.py — Right-side numeric readout panel. """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QScrollArea, QFrame, QProgressBar, ) from PyQt6.QtCore import Qt, pyqtSlot from devices.device_registry import DeviceRegistry class ChannelReadout(QFrame): def __init__(self, ch_config): super().__init__() self.ch = ch_config self.setObjectName("channelReadout") self._alarm = False self._build() def _build(self): layout = QVBoxLayout(self) layout.setContentsMargins(8, 6, 8, 6) layout.setSpacing(2) top = QHBoxLayout() self._name = QLabel(self.ch.name) self._name.setObjectName("readoutName") top.addWidget(self._name, 1) self._val = QLabel("— — —") self._val.setObjectName("readoutValue") self._val.setStyleSheet(f"color:{self.ch.color};") top.addWidget(self._val) unit = QLabel(f" {self.ch.unit}") unit.setObjectName("readoutUnit") top.addWidget(unit) layout.addLayout(top) self._bar = QProgressBar() self._bar.setObjectName("readoutBar") self._bar.setRange(0, 1000) self._bar.setValue(500) self._bar.setTextVisible(False) self._bar.setMaximumHeight(3) self._bar.setStyleSheet( f"QProgressBar::chunk {{ background:{self.ch.color}; border-radius:1px; }}" ) layout.addWidget(self._bar) def update_value(self, v: float): self._val.setText(f"{v:>10.4f}".strip()) rng = self.ch.max_value - self.ch.min_value norm = int(((v - self.ch.min_value) / rng) * 1000) if rng else 500 self._bar.setValue(max(0, min(1000, norm))) alarm = ( (self.ch.alarm_low is not None and v < self.ch.alarm_low) or (self.ch.alarm_high is not None and v > self.ch.alarm_high) ) if alarm != self._alarm: self._alarm = alarm self.setProperty("alarm", alarm) self.style().unpolish(self) self.style().polish(self) class ReadoutPanel(QWidget): def __init__(self, registry: DeviceRegistry): super().__init__() self.registry = registry self._readouts = {} # (dev_id, ch_id) -> ChannelReadout self._build() self.refresh() def _build(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) hdr = QLabel(" CHANNELS") hdr.setObjectName("panelHeader") hdr.setMinimumHeight(28) layout.addWidget(hdr) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self._container = QWidget() self._inner = QVBoxLayout(self._container) self._inner.setContentsMargins(6, 6, 6, 6) self._inner.setSpacing(4) self._inner.addStretch() scroll.setWidget(self._container) layout.addWidget(scroll) def refresh(self): for w in self._readouts.values(): w.deleteLater() self._readouts.clear() # Clear layout while self._inner.count() > 1: item = self._inner.takeAt(0) if item and item.widget(): item.widget().deleteLater() for dev in self.registry.all_instances(): for ch in dev.info.channels: if not ch.enabled: continue ro = ChannelReadout(ch) self._inner.insertWidget(self._inner.count() - 1, ro) self._readouts[(dev.info.device_id, ch.channel_id)] = ro @pyqtSlot(str, str, float, float) def on_new_data(self, dev_id: str, ch_id: str, _ts: float, value: float): ro = self._readouts.get((dev_id, ch_id)) if ro: ro.update_value(value)