summaryrefslogtreecommitdiff
path: root/ui/windows/channels_window.py
diff options
context:
space:
mode:
Diffstat (limited to 'ui/windows/channels_window.py')
-rw-r--r--ui/windows/channels_window.py114
1 files changed, 102 insertions, 12 deletions
diff --git a/ui/windows/channels_window.py b/ui/windows/channels_window.py
index 31d1458..cc83b3b 100644
--- a/ui/windows/channels_window.py
+++ b/ui/windows/channels_window.py
@@ -431,8 +431,18 @@ class PipelineTab(QWidget):
phys_bar = QWidget(); phys_bar.setObjectName("cfgGlobalBar")
pb_lay = QHBoxLayout(phys_bar); pb_lay.setContentsMargins(10, 7, 10, 7); pb_lay.setSpacing(6)
pb_lbl = QLabel("Signals"); pb_lbl.setObjectName("devWindowTitle")
- pb_lay.addWidget(pb_lbl, 1)
+ pb_lay.addWidget(pb_lbl)
+ self._src_cb = _channel_combo(self.registry, self.processor, include_derived=True)
+ self._src_cb.setObjectName("channelPickerCb")
+ self._src_cb.insertItem(0, "Signal: none (empty channel)", userData=None)
+ self._src_cb.setCurrentIndex(0)
+ pb_lay.addWidget(self._src_cb, 1)
+ add_virt = QPushButton("+ New Channel"); add_virt.setObjectName("addTraceBtn")
+ add_virt.setToolTip("Create a Channel from the selected Signal")
+ add_virt.clicked.connect(self._add_virtual)
+ pb_lay.addWidget(add_virt)
add_btn = QPushButton("+ Add"); add_btn.setObjectName("addTraceBtn")
+ add_btn.setToolTip("Add an existing Signal to this list")
add_btn.clicked.connect(self._on_add_channel)
pb_lay.addWidget(add_btn)
phys_l.addWidget(phys_bar)
@@ -458,15 +468,7 @@ 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)
- self._src_cb = _channel_combo(self.registry, self.processor, include_derived=True)
- self._src_cb.setObjectName("channelPickerCb")
- self._src_cb.insertItem(0, "Signal: 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)
+ vb_lay.addWidget(vb_lbl, 1)
virt_l.addWidget(virt_bar)
div2 = QFrame(); div2.setFrameShape(QFrame.Shape.HLine)
@@ -861,15 +863,45 @@ class ChannelBlock(QFrame):
outer.addWidget(hdr)
hdr.mousePressEvent = lambda e: self._toggle()
+ # Subtitle — always visible (collapsed or expanded): what this
+ # Channel is currently wired to, plus a one-click rebind for the
+ # common single-source case. Lets a rewire be fixed (or a channel
+ # proactively repointed at a different Signal) without expanding
+ # the block or hunting for the per-source combo in the editor body.
+ sub = QWidget(); sub.setObjectName("plotBlockSubtitle")
+ sub_lay = QHBoxLayout(sub)
+ sub_lay.setContentsMargins(30, 0, 6, 4); sub_lay.setSpacing(6)
+ self._src_lbl = QLabel(""); self._src_lbl.setObjectName("traceSource")
+ sub_lay.addWidget(self._src_lbl, 1)
+ self._reconnect_cb = _channel_combo(self.registry, include_derived=False)
+ self._reconnect_cb.setFixedWidth(220)
+ sub_lay.addWidget(self._reconnect_cb)
+ self._reconnect_btn = QPushButton("↺ Reconnect")
+ self._reconnect_btn.setObjectName("addTraceBtn")
+ self._reconnect_btn.setToolTip("Point this channel at a different physical Signal")
+ self._reconnect_btn.clicked.connect(self._do_reconnect)
+ sub_lay.addWidget(self._reconnect_btn)
+ outer.addWidget(sub)
+
# Body
body = QWidget(); body.setObjectName("plotBlockSettings")
bl = QVBoxLayout(body); bl.setContentsMargins(10, 8, 10, 10); bl.setSpacing(6)
- # ID / unit row
+ # ID / unit row — the id is auto-derived from the name at creation
+ # (_slug_id()) and normally never needs to be seen, let alone typed;
+ # tucked behind a toggle so Name is the only identity field most
+ # users ever interact with.
meta = QHBoxLayout()
- meta.addWidget(QLabel("ID:"))
+ self._id_toggle = QToolButton(); self._id_toggle.setText("ID ▸")
+ self._id_toggle.setObjectName("traceRemoveBtn"); self._id_toggle.setCheckable(True)
+ self._id_toggle.setToolTip(f"Technical id: {self.dc.channel_id} — only needed to "
+ f"reference this channel from an expression/script by id, "
+ f"or to rename it. Click to edit.")
+ self._id_toggle.toggled.connect(self._toggle_id_box)
+ meta.addWidget(self._id_toggle)
self._id = QLineEdit(self.dc.channel_id)
self._id.setObjectName("traceLabel"); self._id.setFixedWidth(100)
+ self._id.setVisible(False)
self._id.textChanged.connect(lambda t: setattr(self.dc, "channel_id", t))
meta.addWidget(self._id)
meta.addWidget(QLabel("Unit:"))
@@ -1023,6 +1055,58 @@ class ChannelBlock(QFrame):
self._update_vars_hint()
self._update_visibility()
self.refresh_missing_sources()
+ self._refresh_source_subtitle()
+
+ def _refresh_source_subtitle(self):
+ """Update the always-visible '← wired-to' line and show/hide the
+ inline quick-reconnect control — only offered for the unambiguous
+ single-source case; multi-source channels use the per-source combos
+ in the expanded editor instead."""
+ names = [f"{_channel_display_name(self.registry, self.processor, d, c)} ({d}/{c})"
+ for d, c in self.dc.sources]
+ self._src_lbl.setText("← " + ", ".join(names) if names else "no source")
+ single = len(self.dc.sources) == 1
+ self._reconnect_cb.setVisible(single)
+ self._reconnect_btn.setVisible(single)
+ # Pre-select the actual current source — otherwise the combo always
+ # shows whatever landed first in registry iteration order, which
+ # looks like that's what's connected even when it isn't.
+ if single:
+ cur = self.dc.sources[0]
+ self._reconnect_cb.blockSignals(True)
+ for i in range(self._reconnect_cb.count()):
+ if self._reconnect_cb.itemData(i) == cur:
+ self._reconnect_cb.setCurrentIndex(i)
+ break
+ else:
+ self._reconnect_cb.setCurrentIndex(-1)
+ self._reconnect_cb.blockSignals(False)
+
+ def _do_reconnect(self):
+ """Inline one-click rebind: point this channel's sole source at
+ whatever Signal is currently selected in the quick-reconnect combo —
+ no need to expand the block or use the per-source editor combos."""
+ new_src = self._reconnect_cb.currentData()
+ if not new_src or len(self.dc.sources) != 1:
+ return
+ self.dc.sources = [new_src]
+ self.dc.source_names = [_channel_display_name(self.registry, self.processor, *new_src)]
+ if self._src_combos:
+ cb = self._src_combos[0]
+ for i in range(cb.count()):
+ if cb.itemData(i) == new_src:
+ cb.setCurrentIndex(i)
+ break
+ err = self.processor.add_derived(self.dc)
+ self._refresh_source_subtitle()
+ self.refresh_missing_sources()
+ if err:
+ self._st.setText(f"⚠ {err[:100]}")
+ self._st.setStyleSheet("color:#ef4444;")
+ else:
+ self._st.setText("✓ Reconnected")
+ self._st.setStyleSheet("color:#22c55e;")
+ self.applied.emit(self.dc)
def refresh_missing_sources(self):
"""Update the header warning icon for a source signal that no longer
@@ -1046,6 +1130,10 @@ class ChannelBlock(QFrame):
self._body.setVisible(self._expanded)
self._toggle_btn.setText("▼" if self._expanded else "▶")
+ def _toggle_id_box(self, checked: bool):
+ self._id.setVisible(checked)
+ self._id_toggle.setText("ID ▾" if checked else "ID ▸")
+
def _add_src(self, src=None):
row = QHBoxLayout()
cb = _channel_combo(self.registry, self.processor,
@@ -1247,6 +1335,8 @@ class ChannelBlock(QFrame):
elif self.dc.kind in ("function", "custom_script"):
self.dc.script = self._code.toPlainText()
err = self.processor.add_derived(self.dc)
+ self._refresh_source_subtitle()
+ self.refresh_missing_sources()
# Emitted regardless of whether this channel's own expression/script
# compiles — propagate_rename() (via id_renamed) still needs to run
# so other channels' sources / plot traces / controls don't dangle.