feat(bec_widgets): add "Load into editor" to reuse a queued job's settings

Select a single tomo job (command jobs have no params, so they're
excluded) in the queue dialog and click "Load into editor" to enter
edit mode on the params panel populated from that job's saved
snapshot instead of the live backend. Reuses the existing edit-mode
machinery (_enter_edit_mode_with(), factored out of enter_edit_mode())
so the operator reviews/tweaks and then uses the normal Submit or "Add
to queue" paths -- no new write path, nothing touches live params or
the queue itself just by loading. If an edit is already in progress,
confirms before discarding it. The button is disabled outside a
single-tomo-job selection and during sort mode, and re-evaluated on
selection change and on every table refresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
x01dc
2026-07-14 12:44:28 +02:00
co-authored by Claude Sonnet 5
parent 8dc4a56b91
commit 97ac0acde9
@@ -858,9 +858,37 @@ class TomoParamsWidget(BECWidget, QWidget):
# ── edit-mode workflow ────────────────────────────────────────────────────
def enter_edit_mode(self) -> None:
"""Unlock all parameter fields for editing."""
# Refresh first so fields reflect latest backend state
params = self._load_params()
"""Unlock all parameter fields for editing, populated from the live
backend."""
self._enter_edit_mode_with(self._load_params())
def load_params_into_editor(self, params: dict[str, Any], source_label: str = "") -> None:
"""Enter edit mode populated from ``params`` (e.g. a queued job's
saved snapshot) instead of the live backend -- lets an operator
reuse an earlier job's settings as the starting point for a new
one. Never touches live global vars itself; from here the normal
Submit (writes live, blocked while busy) or "Add to queue"
(queue-only, always allowed) paths take over, same as any other
edit.
If an edit is already in progress, confirms before discarding it
-- a stray click on a queue row would otherwise silently throw
away unsaved changes.
"""
if self._edit_mode:
what = f"'{source_label}'" if source_label else "these settings"
reply = QMessageBox.question(
self,
"Discard current edit?",
f"An edit is already in progress. Loading {what} will discard "
"it. Continue?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel,
)
if reply != QMessageBox.StandardButton.Yes:
return
self._enter_edit_mode_with(params)
def _enter_edit_mode_with(self, params: dict[str, Any]) -> None:
self._populate_fields(params)
self._set_fields_enabled(True)
self._edit_mode = True
@@ -1177,11 +1205,19 @@ class TomoQueueDialog(QDialog):
self._table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
self._table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
self._table.itemSelectionChanged.connect(self._update_sort_buttons)
self._table.itemSelectionChanged.connect(self._update_load_button)
vbox.addWidget(self._table)
btn_row = QHBoxLayout()
self._btn_add = QPushButton("Add current params to queue")
self._btn_add_cmd = QPushButton("Add command…")
self._btn_load = QPushButton("Load into editor")
self._btn_load.setToolTip(
"Load the selected tomo job's saved settings into the params panel's "
"editor -- doesn't touch live params or the queue itself. Review, "
"tweak, then Submit or \"Add to queue\" from there."
)
self._btn_load.setEnabled(False)
self._btn_del = QPushButton("Delete selected")
self._btn_clr = QPushButton("Clear all")
self._btn_sort = QPushButton("Sort queue…")
@@ -1197,6 +1233,7 @@ class TomoQueueDialog(QDialog):
btn_exe = QPushButton("Execute queue…")
self._btn_add.clicked.connect(self.add_to_queue)
self._btn_add_cmd.clicked.connect(self.add_command_to_queue)
self._btn_load.clicked.connect(self._on_load_into_editor)
self._btn_del.clicked.connect(self._delete_selected)
self._btn_clr.clicked.connect(self._clear_queue)
self._btn_sort.toggled.connect(self._toggle_sort_mode)
@@ -1205,6 +1242,7 @@ class TomoQueueDialog(QDialog):
btn_exe.clicked.connect(self._show_execute_hint)
btn_row.addWidget(self._btn_add)
btn_row.addWidget(self._btn_add_cmd)
btn_row.addWidget(self._btn_load)
btn_row.addWidget(self._btn_del)
btn_row.addWidget(self._btn_clr)
btn_row.addWidget(self._btn_sort)
@@ -1262,6 +1300,10 @@ class TomoQueueDialog(QDialog):
item.setForeground(_color_from_hex(color))
item.setToolTip(tooltip)
self._table.setItem(row, col, item)
# A row's underlying job can change identity across a refresh (the
# queue was edited from elsewhere while a row stayed selected), so
# re-check eligibility here too, not just on selection-change.
self._update_load_button()
# ── queue actions ─────────────────────────────────────────────────────────
@@ -1299,6 +1341,35 @@ class TomoQueueDialog(QDialog):
self._save_queue(jobs)
self._refresh()
def _selected_single_tomo_job(self) -> Optional[dict]:
"""Return the single selected job if it's a tomo job, else None
(nothing selected, more than one row selected, or a command job --
command jobs have no `params` to load into the editor)."""
rows = sorted({idx.row() for idx in self._table.selectedIndexes()})
if len(rows) != 1:
return None
jobs = self._load_queue()
row = rows[0]
if not 0 <= row < len(jobs):
return None
job = jobs[row]
return job if _job_kind(job) == "tomo" else None
def _update_load_button(self) -> None:
self._btn_load.setEnabled(
not self._sort_mode and self._selected_single_tomo_job() is not None
)
def _on_load_into_editor(self) -> None:
job = self._selected_single_tomo_job()
if job is None:
return
parent = self.parent()
if parent is None or not hasattr(parent, "load_params_into_editor"):
QMessageBox.warning(self, "Can't load", "No params panel available to load into.")
return
parent.load_params_into_editor(dict(job.get("params", {})), job.get("label", ""))
# ── sort mode ─────────────────────────────────────────────────────────────
def _toggle_sort_mode(self, checked: bool) -> None:
@@ -1322,6 +1393,7 @@ class TomoQueueDialog(QDialog):
)
self._table.clearSelection()
self._update_sort_buttons()
self._update_load_button()
def _selected_single_row(self) -> int:
rows = sorted({idx.row() for idx in self._table.selectedIndexes()})