From d9ca58e48cf92438087ac24ee261c2c4911316b1 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Wed, 29 Jul 2026 13:03:24 -0600 Subject: Add editable device display name, persist it across profile save/load DeviceInfo.name existed but was hardcoded per device type and never operator-editable. Adds a Display Name field to Add Device and makes the Info-tab Name field in the device config dialog editable, and threads the name through get_save_config()/ProfileManager.apply() so a custom name survives a .labdaq save/reload instead of reverting to the type default. Name is applied post-construction rather than as a constructor kwarg, since no device factory declares a "name" param. Co-Authored-By: Claude Sonnet 5 --- ui/windows/devices_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ui/windows/devices_window.py') diff --git a/ui/windows/devices_window.py b/ui/windows/devices_window.py index ac9d13c..2c484e8 100644 --- a/ui/windows/devices_window.py +++ b/ui/windows/devices_window.py @@ -349,7 +349,7 @@ class DevicesWindow(QWidget): dev = self.registry.get_instance(device_id) if dev: DeviceConfigDialog(dev, self).exec() - self._ch_tab.refresh() + self.refresh() # rebuilds device rows (picks up a renamed display name) + Signals tab self.device_reconfigured.emit(device_id) def _on_remove(self, device_id: str): -- cgit v1.2.3 From 2c6b4b751ffdd1156254b31745597654b5f2e286 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Wed, 29 Jul 2026 13:10:35 -0600 Subject: Rework channel labeling and add source picker to virtual-channel creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relabel every channel picker/header to "NAME (DEVICE/SIGNAL)" instead of the previous "DEVICE/SIGNAL (NAME)" ordering, consistently across channels_window.py (_channel_combo, ChannelPickerDialog, ChannelPipelineBlock header), plot_builder.py, plot_config.py, control_editor.py, and plot_window.py. While touching each of those pickers, added the missing `.enabled` filter that _make_picker()/_x_cb/_build_channel_picker lacked (the default-layout builders already filtered disabled channels; these manual "add channel to pane" pickers didn't). Devices window Signals tab: removed the separate "Signal ID" column (folded into the existing non-editable Device column instead of the editable Name column, to avoid corrupting the in-place channel rename feature that column already supports). Channels window Virtual Channels pane: added a "Source" dropdown next to "+ Add Channel" — picking a source pre-seeds the new derived channel with it via DerivedBlock._add_src(); leaving it on the default "none" entry keeps today's behavior of creating an empty channel. Note: docs/ToDo.md's "Channels window UI" items referred to the *live* UI (this window's PipelineTab + the Devices window's Signals tab), not SignalsListTab in this same file, which its own module docstring flags as dead/unused code kept only for old-profile compatibility — verified by grepping for any instantiation of it (none exist). Co-Authored-By: Claude Sonnet 5 --- ui/control_editor.py | 2 +- ui/plot_builder.py | 8 ++++++-- ui/plot_config.py | 4 +++- ui/windows/channels_window.py | 20 +++++++++++++++----- ui/windows/devices_window.py | 25 ++++++++++--------------- ui/windows/plot_window.py | 16 ++++++++++++---- 6 files changed, 47 insertions(+), 28 deletions(-) (limited to 'ui/windows/devices_window.py') diff --git a/ui/control_editor.py b/ui/control_editor.py index 48de21d..65aa96e 100644 --- a/ui/control_editor.py +++ b/ui/control_editor.py @@ -362,7 +362,7 @@ class ControlEditorDialog(QDialog): return # Add actual channels for ch in dev.info.channels: - self._ch_cb.addItem(f"{ch.channel_id} ({ch.name})", + self._ch_cb.addItem(f"{ch.name} ({ch.channel_id})", userData=ch.channel_id) # For Arduino backends also suggest digital pins for output if hasattr(dev, "backend") and dev.backend == "arduino": diff --git a/ui/plot_builder.py b/ui/plot_builder.py index ad6d231..7445bda 100644 --- a/ui/plot_builder.py +++ b/ui/plot_builder.py @@ -298,10 +298,14 @@ class PlotBlock(QFrame): cb.setPlaceholderText("Select channel…") for dev in self.registry.all_instances(): for ch in dev.info.channels: - cb.addItem(f"{dev.info.device_id} / {ch.channel_id} ({ch.name})", + if not ch.enabled: + continue + cb.addItem(f"{ch.name} ({dev.info.device_id}/{ch.channel_id})", userData=(dev.info.device_id, ch.channel_id, ch.name, ch.color)) for dc in self.processor.get_derived(): - cb.addItem(f"[derived] {dc.channel_id} ({dc.name})", + if not dc.enabled: + continue + cb.addItem(f"{dc.name} ([derived]/{dc.channel_id})", userData=("derived", dc.channel_id, dc.name, dc.color)) return cb diff --git a/ui/plot_config.py b/ui/plot_config.py index 058915f..c6ecaa4 100644 --- a/ui/plot_config.py +++ b/ui/plot_config.py @@ -333,7 +333,9 @@ class PlotBlock(QFrame): cb.setPlaceholderText("Select channel…") for dev in self.registry.all_instances(): for ch in dev.info.channels: - label = f"{dev.info.device_id} / {ch.channel_id} ({ch.name})" + if not ch.enabled: + continue + label = f"{ch.name} ({dev.info.device_id}/{ch.channel_id})" cb.addItem(label, userData=(dev.info.device_id, ch.channel_id, ch.name, ch.color)) return cb diff --git a/ui/windows/channels_window.py b/ui/windows/channels_window.py index 381800a..38d477b 100644 --- a/ui/windows/channels_window.py +++ b/ui/windows/channels_window.py @@ -74,7 +74,7 @@ def _channel_combo(registry: DeviceRegistry, for ch in dev.info.channels: if not ch.enabled: continue - label = f"{dev.info.device_id} / {ch.channel_id} ({ch.name})" + label = f"{ch.name} ({dev.info.device_id}/{ch.channel_id})" if show_unit and ch.unit: label += f" [{ch.unit}]" cb.addItem(label, userData=(dev.info.device_id, ch.channel_id)) @@ -115,7 +115,7 @@ class ChannelPickerDialog(QDialog): if (dev.info.device_id, ch.channel_id) in already_shown: continue any_available = True - label = f"{dev.info.device_id} / {ch.channel_id} ({ch.name})" + label = f"{ch.name} ({dev.info.device_id}/{ch.channel_id})" if ch.unit: label += f" [{ch.unit}]" chk = QCheckBox(label) @@ -236,7 +236,7 @@ class ChannelPipelineBlock(QFrame): # Header hdr = QWidget(); hdr.setObjectName("plotBlockHeader"); hdr.setFixedHeight(32) hl = QHBoxLayout(hdr); hl.setContentsMargins(8, 0, 6, 0) - title = f"{self.dev_id} / {self.ch_id} ({ch_name})" + title = f"{ch_name} ({self.dev_id}/{self.ch_id})" if unit: title += f" [{unit}]" self._title_lbl = QLabel(title); self._title_lbl.setObjectName("traceSource") @@ -307,7 +307,7 @@ class ChannelPipelineBlock(QFrame): self._body.setVisible(False) def _refresh_title(self): - title = f"{self.dev_id} / {self.ch_id} ({self._ch_name})" + title = f"{self._ch_name} ({self.dev_id}/{self.ch_id})" if self._unit: title += f" [{self._unit}]" self._title_lbl.setText(title) @@ -444,7 +444,12 @@ class PipelineTab(QWidget): virt_bar = QWidget(); virt_bar.setObjectName("cfgGlobalBar") vb_lay = QHBoxLayout(virt_bar); vb_lay.setContentsMargins(10, 7, 10, 7); vb_lay.setSpacing(6) vb_lbl = QLabel("Channels"); vb_lbl.setObjectName("devWindowTitle") - vb_lay.addWidget(vb_lbl, 1) + vb_lay.addWidget(vb_lbl) + self._src_cb = _channel_combo(self.registry, self.processor, include_derived=True) + self._src_cb.setObjectName("channelPickerCb") + self._src_cb.insertItem(0, "Source: none (empty channel)", userData=None) + self._src_cb.setCurrentIndex(0) + vb_lay.addWidget(self._src_cb, 1) add_virt = QPushButton("+ Add Channel"); add_virt.setObjectName("addTraceBtn") add_virt.clicked.connect(self._add_virtual) vb_lay.addWidget(add_virt) @@ -579,6 +584,11 @@ class PipelineTab(QWidget): kind="expression", color=color) blk = self._make_derived_block(dc) self._virt_inner.insertWidget(self._virt_inner.count() - 1, blk) + # Pre-seed the source picked in the bar above, if any — otherwise the + # channel is created empty and sources can be added manually. + src = self._src_cb.currentData() + if src: + blk._add_src(src) def _make_derived_block(self, dc: DerivedChannel) -> DerivedBlock: blk = DerivedBlock(dc, self.registry, self.processor) diff --git a/ui/windows/devices_window.py b/ui/windows/devices_window.py index ac9d13c..3b193b2 100644 --- a/ui/windows/devices_window.py +++ b/ui/windows/devices_window.py @@ -124,12 +124,11 @@ class ChannelsTab(QWidget): # Col indices _C_DEVICE = 0 - _C_CH_ID = 1 - _C_ENABLED = 2 - _C_NAME = 3 - _C_UNIT = 4 - _C_MIN = 5 - _C_MAX = 6 + _C_ENABLED = 1 + _C_NAME = 2 + _C_UNIT = 3 + _C_MIN = 4 + _C_MAX = 5 def __init__(self, registry: DeviceRegistry): super().__init__() @@ -142,14 +141,13 @@ class ChannelsTab(QWidget): self._table = QTableWidget() self._table.setObjectName("channelTable") - self._table.setColumnCount(7) + self._table.setColumnCount(6) self._table.setHorizontalHeaderLabels( - ["Device", "Signal ID", "On", "Name", "Unit", "Min", "Max"] + ["Device", "On", "Name", "Unit", "Min", "Max"] ) hdr = self._table.horizontalHeader() hdr.setSectionResizeMode(self._C_NAME, QHeaderView.ResizeMode.Stretch) hdr.setSectionResizeMode(self._C_DEVICE, QHeaderView.ResizeMode.ResizeToContents) - hdr.setSectionResizeMode(self._C_CH_ID, QHeaderView.ResizeMode.ResizeToContents) hdr.setSectionResizeMode(self._C_ENABLED, QHeaderView.ResizeMode.ResizeToContents) self._table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self._table.setAlternatingRowColors(True) @@ -165,16 +163,13 @@ class ChannelsTab(QWidget): for ch in dev.info.channels: self._table.insertRow(row) - dev_item = QTableWidgetItem(f"{dev.info.icon} {dev.info.device_id}") + # Device + signal ID folded into one non-editable column — + # the separate "Signal ID" column was removed as redundant. + dev_item = QTableWidgetItem(f"{dev.info.icon} {dev.info.device_id} / {ch.channel_id}") dev_item.setFlags(dev_item.flags() & ~Qt.ItemFlag.ItemIsEditable) dev_item.setForeground(QColor("#64748b")) self._table.setItem(row, self._C_DEVICE, dev_item) - ch_item = QTableWidgetItem(ch.channel_id) - ch_item.setFlags(ch_item.flags() & ~Qt.ItemFlag.ItemIsEditable) - ch_item.setForeground(QColor(ch.color)) - self._table.setItem(row, self._C_CH_ID, ch_item) - # Enabled checkbox — centred in cell chk_container = QWidget() chk_lay = QHBoxLayout(chk_container) diff --git a/ui/windows/plot_window.py b/ui/windows/plot_window.py index 9edf046..f311e9e 100644 --- a/ui/windows/plot_window.py +++ b/ui/windows/plot_window.py @@ -355,10 +355,14 @@ class PaneBlock(QFrame): self._x_cb.addItem("⏱ Time (elapsed s)", userData="time") for dev in self.registry.all_instances(): for ch in dev.info.channels: - self._x_cb.addItem(f"{dev.info.device_id}/{ch.channel_id} ({ch.name})", + if not ch.enabled: + continue + self._x_cb.addItem(f"{ch.name} ({dev.info.device_id}/{ch.channel_id})", userData=f"{dev.info.device_id}/{ch.channel_id}") for dc in self.processor.get_derived(): - self._x_cb.addItem(f"[virtual] {dc.channel_id}", + if not dc.enabled: + continue + self._x_cb.addItem(f"{dc.name} ([virtual]/{dc.channel_id})", userData=f"derived/{dc.channel_id}") for i in range(self._x_cb.count()): if self._x_cb.itemData(i) == self.spec.x_source: @@ -417,10 +421,14 @@ class PaneBlock(QFrame): cb = QComboBox(); cb.setObjectName("channelPickerCb") for dev in self.registry.all_instances(): for ch in dev.info.channels: - cb.addItem(f"{dev.info.device_id} / {ch.channel_id} ({ch.name})", + if not ch.enabled: + continue + cb.addItem(f"{ch.name} ({dev.info.device_id}/{ch.channel_id})", userData=(dev.info.device_id, ch.channel_id, ch.name, ch.color)) for dc in self.processor.get_derived(): - cb.addItem(f"[virtual] {dc.channel_id} ({dc.name})", + if not dc.enabled: + continue + cb.addItem(f"{dc.name} ([virtual]/{dc.channel_id})", userData=("derived", dc.channel_id, dc.name, dc.color)) return cb -- cgit v1.2.3