From 0fa0568c06fd770ad104b464197b3bb03123f38e Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Tue, 9 Jun 2026 20:14:54 -0600 Subject: UI: streamline Add Device dialog and serial config widget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Device dialog: - Replace Available Devices QGroupBox with plain devWindowTitle label to match Configuration section style — no box/border - Auto-scan on open via QTimer.singleShot(0) - Rename Scan All → Refresh Serial config widget: - Fix gap between protocol panel and Available Serial Ports: introduce _AdaptiveStack (QStackedWidget subclass) that reports only the current panel's sizeHint; set Fixed vertical size policy so the stack never expands beyond its content regardless of window size - Stretch added between Available Serial Ports and Apply & Reconnect so the button stays pinned to the bottom - Rename Channels → Signals in _GenericPanel and _SCPIPanel (Signal Queries) Config dialog (device info tab): - Rename Channels → Signals Co-Authored-By: Claude Sonnet 4.6 --- devices/serial_device.py | 41 ++++++++++++++++++++++++++++++++++++----- ui/add_device_dialog.py | 20 +++++++++++--------- ui/config_dialog.py | 2 +- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/devices/serial_device.py b/devices/serial_device.py index 78f74a2..c3ce65f 100644 --- a/devices/serial_device.py +++ b/devices/serial_device.py @@ -27,7 +27,7 @@ from PyQt6.QtCore import Qt, QThread, pyqtSignal from PyQt6.QtWidgets import ( QCheckBox, QComboBox, QDoubleSpinBox, QFormLayout, QGroupBox, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QListWidget, - QListWidgetItem, QPushButton, QScrollArea, QSpinBox, + QListWidgetItem, QPushButton, QScrollArea, QSizePolicy, QSpinBox, QStackedWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -359,6 +359,30 @@ class SerialDevice(BaseDevice): # ── Config widget ───────────────────────────────────────────────────────────── +class _AdaptiveStack(QStackedWidget): + """QStackedWidget sized to its current panel only — no gap from hidden panels.""" + + def _current_height(self) -> int: + w = self.currentWidget() + if not w: + return 0 + lyt = w.layout() + if lyt: + m = lyt.contentsMargins() + return lyt.sizeHint().height() + m.top() + m.bottom() + return w.sizeHint().height() + + def sizeHint(self): + from PyQt6.QtCore import QSize + h = self._current_height() + w = self.currentWidget() + sw = w.sizeHint().width() if w else super().sizeHint().width() + return QSize(sw, h) if h > 0 else super().sizeHint() + + def minimumSizeHint(self): + return self.sizeHint() + + class SerialConfigWidget(QWidget): def __init__(self, device: SerialDevice): super().__init__() @@ -400,7 +424,7 @@ class SerialConfigWidget(QWidget): root.addWidget(conn_grp) # ── Protocol-specific settings (stacked) ────────────────────────── - self._proto_stack = QStackedWidget() + self._proto_stack = _AdaptiveStack() self._generic_panel = _GenericPanel(device=self.device) self._scpi_panel = _SCPIPanel(device=self.device) self._modbus_panel = _ModbusPanel(device=self.device) @@ -410,6 +434,10 @@ class SerialConfigWidget(QWidget): self._modbus_panel, self._mark10_panel, self._cml_panel): self._proto_stack.addWidget(panel) self._proto_stack.setCurrentIndex(_STACK_IDX.get(self.device._fmt, 0)) + sp = self._proto_stack.sizePolicy() + sp.setVerticalPolicy(QSizePolicy.Policy.Fixed) + self._proto_stack.setSizePolicy(sp) + self._proto_stack.currentChanged.connect(self._fix_stack_height) root.addWidget(self._proto_stack) # ── Port scanner ─────────────────────────────────────────────────── @@ -436,13 +464,16 @@ class SerialConfigWidget(QWidget): scan_lay.addWidget(self._port_list) scan_lay.addWidget(hint) root.addWidget(scan_grp) + root.addStretch() # ── Apply ────────────────────────────────────────────────────────── apply_btn = QPushButton("Apply & Reconnect") apply_btn.setObjectName("applyButton") apply_btn.clicked.connect(self._apply) root.addWidget(apply_btn) - root.addStretch() + + def _fix_stack_height(self, _: int): + self._proto_stack.updateGeometry() def _on_fmt_changed(self, _): fmt = _FORMAT_LABELS.get(self._fmt_cb.currentText(), "key:val") @@ -513,7 +544,7 @@ class _GenericPanel(QWidget): self._ch_spin = QSpinBox() self._ch_spin.setRange(1, 32) self._ch_spin.setValue(len(device.info.channels) if device._fmt in _GENERIC_FORMATS else 4) - lay.addRow("Channels:", self._ch_spin) + lay.addRow("Signals:", self._ch_spin) def collect(self) -> dict: return {"num_channels": self._ch_spin.value()} @@ -529,7 +560,7 @@ class _SCPIPanel(QWidget): lay.setContentsMargins(0, 4, 0, 4) lay.setSpacing(6) - grp = QGroupBox("Channel Queries") + grp = QGroupBox("Signal Queries") grp_lay = QVBoxLayout(grp) self._table = QTableWidget() diff --git a/ui/add_device_dialog.py b/ui/add_device_dialog.py index f0cbde1..2956a96 100644 --- a/ui/add_device_dialog.py +++ b/ui/add_device_dialog.py @@ -8,7 +8,7 @@ from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QComboBox, QLineEdit, QSpinBox, QDoubleSpinBox, QCheckBox, QPushButton, QLabel, QMessageBox, - QStackedWidget, QWidget, QGroupBox, QListWidget, + QStackedWidget, QWidget, QListWidget, QListWidgetItem, QFrame, QSizePolicy, ) from PyQt6.QtCore import Qt, QThread, pyqtSignal @@ -58,10 +58,10 @@ class _DeviceListScanner(QWidget): lay.setSpacing(4) top = QHBoxLayout() - self._scan_btn = QPushButton("🔍 Scan All") + self._scan_btn = QPushButton("🔍 Refresh") self._scan_btn.setObjectName("addTraceBtn") self._scan_btn.clicked.connect(self.scan_all) - self._status = QLabel("Click Scan to detect devices") + self._status = QLabel("Scanning…") self._status.setObjectName("traceSource") top.addWidget(self._scan_btn) top.addWidget(self._status, 1) @@ -248,13 +248,13 @@ class AddDeviceDialog(QDialog): root.addWidget(_divider()) # ── Available Devices ───────────────────────────────────────────── - avail_grp = QGroupBox("Available Devices") - avail_lay = QVBoxLayout(avail_grp) - avail_lay.setSpacing(6) + avail_hdr = QLabel("Available Devices") + avail_hdr.setObjectName("devWindowTitle") + root.addWidget(avail_hdr) self._scanner = _DeviceListScanner() self._scanner.device_selected.connect(self._on_device_selected) - avail_lay.addWidget(self._scanner) + root.addWidget(self._scanner) conn_form = QFormLayout() conn_form.setSpacing(6) @@ -278,8 +278,7 @@ class AddDeviceDialog(QDialog): self._sim_chk = QCheckBox("Simulation mode") conn_form.addRow(self._sim_chk) - avail_lay.addLayout(conn_form) - root.addWidget(avail_grp) + root.addLayout(conn_form) root.addWidget(_divider()) @@ -331,6 +330,9 @@ class AddDeviceDialog(QDialog): self._on_type_changed(0) + from PyQt6.QtCore import QTimer + QTimer.singleShot(0, self._scanner.scan_all) + # ── Handlers ────────────────────────────────────────────────────────── def _on_device_selected(self, kind: str, value): diff --git a/ui/config_dialog.py b/ui/config_dialog.py index bafac24..d9bc9df 100644 --- a/ui/config_dialog.py +++ b/ui/config_dialog.py @@ -66,5 +66,5 @@ class DeviceConfigDialog(QDialog): form.addRow("Model:", _ro(info.model)) form.addRow("Version:", _ro(info.version)) form.addRow("Status:", _ro(self.device.status.value)) - form.addRow("Channels:", _ro(len(info.channels))) + form.addRow("Signals:", _ro(len(info.channels))) return w -- cgit v1.2.3