diff --git a/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py b/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py index 69d2298..5789368 100644 --- a/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py +++ b/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py @@ -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()})