summaryrefslogtreecommitdiff
path: root/ui/device_panel.py
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-04-13 15:22:56 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-04-13 15:22:56 -0600
commit2ed9d37da7b27d25173535550fb92702225ac14e (patch)
treed6ff360a656e577629d3cefdaa1ae02af7255864 /ui/device_panel.py
parent8d6acf3a8ea4b37f86b321dbf430be5be01b1267 (diff)
Removed QtPy5 snippet and fixed directories
Diffstat (limited to 'ui/device_panel.py')
-rw-r--r--ui/device_panel.py148
1 files changed, 148 insertions, 0 deletions
diff --git a/ui/device_panel.py b/ui/device_panel.py
new file mode 100644
index 0000000..932f536
--- /dev/null
+++ b/ui/device_panel.py
@@ -0,0 +1,148 @@
+"""
+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()