""" ui/device_panel.py — Left sidebar: device list with status indicators. """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QScrollArea, QFrame, QSizePolicy, ) from PyQt6.QtCore import Qt, pyqtSignal, QTimer from devices.base_device import DeviceStatus from devices.device_registry import DeviceRegistry from core.acquisition import AcquisitionEngine _STATUS_STYLE = { DeviceStatus.CONNECTED: "color:#22c55e;", DeviceStatus.SIMULATED: "color:#3b82f6;", DeviceStatus.DISCONNECTED: "color:#475569;", DeviceStatus.CONNECTING: "color:#f59e0b;", DeviceStatus.ERROR: "color:#ef4444;", } _STATUS_LABEL = { DeviceStatus.CONNECTED: "CONNECTED", DeviceStatus.SIMULATED: "SIMULATED", DeviceStatus.DISCONNECTED: "OFFLINE", DeviceStatus.CONNECTING: "CONNECTING", DeviceStatus.ERROR: "ERROR", } class DeviceCard(QFrame): config_requested = pyqtSignal(str) def __init__(self, device): super().__init__() self.device = device self.setObjectName("deviceCard") self._build() def _build(self): layout = QVBoxLayout(self) layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(5) # Header row: icon + name + status dot hdr = QHBoxLayout() icon_lbl = QLabel(self.device.info.icon) icon_lbl.setObjectName("deviceIcon") hdr.addWidget(icon_lbl) name_lbl = QLabel(self.device.info.name) name_lbl.setObjectName("deviceName") hdr.addWidget(name_lbl, 1) self._dot = QLabel("●") self._dot.setStyleSheet(_STATUS_STYLE.get(self.device.status, "color:#475569;") + " font-size:10px;") hdr.addWidget(self._dot) layout.addLayout(hdr) # Device ID id_lbl = QLabel(self.device.info.device_id) id_lbl.setObjectName("deviceSub") layout.addWidget(id_lbl) # Status text self._status_lbl = QLabel(_STATUS_LABEL.get(self.device.status, "UNKNOWN")) style = _STATUS_STYLE.get(self.device.status, "color:#475569;") self._status_lbl.setStyleSheet( style + " font-family:'IBM Plex Mono',monospace; font-size:10px; font-weight:700; letter-spacing:1px;" ) layout.addWidget(self._status_lbl) # Channel count n = sum(1 for c in self.device.info.channels if c.enabled) ch_lbl = QLabel(f"{n} ch · {self.device.info.device_type}") ch_lbl.setObjectName("deviceChannelCount") layout.addWidget(ch_lbl) # Config button cfg = QPushButton("Configure") cfg.setObjectName("configButton") cfg.clicked.connect(lambda: self.config_requested.emit(self.device.info.device_id)) layout.addWidget(cfg) def refresh(self): dot_style = _STATUS_STYLE.get(self.device.status, "color:#475569;") + " font-size:10px;" label_style = _STATUS_STYLE.get(self.device.status, "color:#475569;") + \ " font-family:'IBM Plex Mono',monospace; font-size:10px; font-weight:700; letter-spacing:1px;" self._dot.setStyleSheet(dot_style) self._status_lbl.setText(_STATUS_LABEL.get(self.device.status, "UNKNOWN")) self._status_lbl.setStyleSheet(label_style) class DevicePanel(QWidget): config_requested = pyqtSignal(str) def __init__(self, registry: DeviceRegistry, engine: AcquisitionEngine): super().__init__() self.registry = registry self.engine = engine self._cards = {} self._build() # Refresh status every 2 s self._timer = QTimer(self) self._timer.setInterval(2000) self._timer.timeout.connect(self._refresh_status) self._timer.start() def _build(self): layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) hdr = QLabel(" DEVICES") hdr.setObjectName("panelHeader") hdr.setMinimumHeight(28) layout.addWidget(hdr) scroll = QScrollArea() scroll.setObjectName("deviceScroll") scroll.setWidgetResizable(True) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self._container = QWidget() self._inner = QVBoxLayout(self._container) self._inner.setContentsMargins(8, 8, 8, 8) self._inner.setSpacing(8) self._inner.addStretch() scroll.setWidget(self._container) layout.addWidget(scroll) def refresh(self): for card in self._cards.values(): self._inner.removeWidget(card) card.deleteLater() self._cards.clear() for dev in self.registry.all_instances(): card = DeviceCard(dev) card.config_requested.connect(self.config_requested.emit) self._inner.insertWidget(self._inner.count() - 1, card) self._cards[dev.info.device_id] = card def _refresh_status(self): for dev_id, card in self._cards.items(): card.refresh()