new_gui bookmarks: editable labels + per-sample persistence

- each bookmark has an inline-editable label (rename signal)
- bookmarks persist per-sample via QSettings (keyed by sample db_id): saved on
  add/remove/rename, reloaded when the same sample is remounted (and across
  GUI restarts)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
appleb_m
2026-06-25 09:52:49 +02:00
co-authored by Claude Opus 4.8
parent 05421bf944
commit c09c2e31e3
3 changed files with 91 additions and 16 deletions
+4 -2
View File
@@ -80,8 +80,10 @@ Backend contract is documented in the `new-gui-backend-paths` memory.
`sample_missing` shows a modal (suppressed during automation).
- **Bookmarks** (mounted): Bookmark captures the current smargon position
(shown as numbered markers on the camera, projected via `smargon_to_picture`);
**Go** returns there (`move_smargon` + `set_omega`); **Collect** launches a
rotation scan with `start` = the bookmark (data collection *from* that point).
each has an **editable label**; **Go** returns there (`move_smargon` +
`set_omega`); **Collect** launches a rotation scan with `start` = the bookmark
(data collection *from* that point). Bookmarks **persist per-sample** (QSettings,
keyed by sample db_id) so they survive unmount/remount and GUI restart.
- **Mount / Unmount** → `mount(SampleShortInfo)` / `unmount()`.
- **Motors** → `set_omega_rel`, `move_smargon`, `zoom`, `front_light`, `back_light`,
and a staff-only **Energy** control → `change_energy` (keV).
+68 -6
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
import json
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
QButtonGroup,
@@ -49,7 +51,8 @@ class ManualView(QWidget):
self._grid_rect_img: tuple[float, float, float, float] | None = None
self._raster_grids: list = []
self._grids_dialog = None
self._bookmarks: list = [] # [{coord: SmargonCoordinate, omega: float}]
self._bookmarks: list = [] # [{coord, omega, label}]
self._bookmark_loaded_key = None
root = QHBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
@@ -167,6 +170,7 @@ class ManualView(QWidget):
bm.bookmark_goto.connect(self._on_bookmark_goto)
bm.bookmark_collect.connect(self._on_bookmark_collect)
bm.bookmark_remove.connect(self._on_bookmark_remove)
bm.bookmark_rename.connect(self._on_bookmark_rename)
# ------------------------------------------------------- run handlers
def _on_run_center(self, method: str) -> None:
@@ -307,6 +311,9 @@ class ManualView(QWidget):
self.status_message.emit("Grid has no stored position to go to.")
# ----------------------------------------------------- bookmarks
def _refresh_bookmark_bar(self) -> None:
self.pipeline.bookmarks.set_bookmarks([b["label"] for b in self._bookmarks])
def _on_bookmark_add(self) -> None:
from copy import deepcopy
geom = getattr(self._last_status, "geom", None)
@@ -315,9 +322,13 @@ class ManualView(QWidget):
self.status_message.emit("No smargon position yet; cannot bookmark.")
return
omega = getattr(geom, "omega_deg", 0.0) or 0.0
self._bookmarks.append({"coord": deepcopy(smg), "omega": float(omega)})
self.pipeline.bookmarks.set_bookmarks(len(self._bookmarks))
self._bookmarks.append({
"coord": deepcopy(smg), "omega": float(omega),
"label": f"Spot {len(self._bookmarks) + 1}",
})
self._refresh_bookmark_bar()
self._project_bookmarks()
self._save_bookmarks()
def _on_bookmark_goto(self, index: int) -> None:
if 0 <= index < len(self._bookmarks):
@@ -344,8 +355,53 @@ class ManualView(QWidget):
def _on_bookmark_remove(self, index: int) -> None:
if 0 <= index < len(self._bookmarks):
del self._bookmarks[index]
self.pipeline.bookmarks.set_bookmarks(len(self._bookmarks))
self._refresh_bookmark_bar()
self._project_bookmarks()
self._save_bookmarks()
def _on_bookmark_rename(self, index: int, label: str) -> None:
if 0 <= index < len(self._bookmarks):
self._bookmarks[index]["label"] = label or f"Spot {index + 1}"
self._save_bookmarks()
# ---- per-sample persistence (QSettings) ----
def _bookmark_key(self) -> str | None:
sample = self._state.mount_sample
if sample is None:
return None
return f"bookmarks/{sample_id(sample)}"
def _save_bookmarks(self) -> None:
from PySide6.QtCore import QSettings
key = self._bookmark_key()
if key is None:
return
data = []
for b in self._bookmarks:
coord = b["coord"]
dump = coord.model_dump() if hasattr(coord, "model_dump") else None
data.append({"coord": dump, "omega": b["omega"], "label": b["label"]})
QSettings("PSI", "AareGUI-new").setValue(key, json.dumps(data))
def _load_bookmarks(self) -> None:
from PySide6.QtCore import QSettings
from aare.common.coordinate import SmargonCoordinate
self._bookmarks = []
key = self._bookmark_key()
raw = QSettings("PSI", "AareGUI-new").value(key) if key else None
if raw:
try:
for entry in json.loads(raw):
coord = SmargonCoordinate.model_validate(entry["coord"])
self._bookmarks.append({
"coord": coord,
"omega": float(entry.get("omega", 0.0)),
"label": entry.get("label", "Spot"),
})
except Exception as exc:
self.status_message.emit(f"Could not load bookmarks: {exc}")
self._refresh_bookmark_bar()
self._project_bookmarks()
def _project_bookmarks(self) -> None:
from aare.gui.new_gui.widgets.bookmarks import BOOKMARK_COLORS
@@ -381,11 +437,17 @@ class ManualView(QWidget):
params = getattr(sample, "aaredb_params", None)
if params is not None:
self.pipeline.settings.set_db_params(params)
# load this sample's saved bookmarks (once per sample)
key = self._bookmark_key()
if key != self._bookmark_loaded_key:
self._bookmark_loaded_key = key
self._load_bookmarks()
if phase != "mounted":
self.camera.clear_grid()
self._grid_rect_img = None
self._bookmarks = [] # bookmarks are per-sample
self.pipeline.bookmarks.set_bookmarks(0)
self._bookmarks = [] # cleared from view (persisted)
self._bookmark_loaded_key = None
self.pipeline.bookmarks.set_bookmarks([])
self.camera.set_bookmarks([])
self.pipeline.update_from_state(self._state)
+19 -8
View File
@@ -1,14 +1,15 @@
"""Data-collection bookmarks: mark smargon positions, return to them or collect.
A compact bar: " Bookmark" captures the current position; each saved bookmark
is a chip with Go (move there), Collect (launch data collection from there) and
remove. Markers are drawn on the camera by the Manual view.
is a chip with an editable label, Go (move there), Collect (launch data
collection from there) and remove. Markers are drawn on the camera by the
Manual view. Bookmarks persist per-sample (see ManualView).
"""
from __future__ import annotations
from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QWidget
from PySide6.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget
# Bookmark colours (match the old SmargonBookmark palette).
BOOKMARK_COLORS = ("#cf222e", "#1a7f37", "#4d8bff", "#6d4bd1", "#9bcc3f")
@@ -19,6 +20,7 @@ class BookmarksBar(QWidget):
bookmark_goto = Signal(int)
bookmark_collect = Signal(int)
bookmark_remove = Signal(int)
bookmark_rename = Signal(int, str)
def __init__(self, palette, parent=None):
super().__init__(parent)
@@ -41,18 +43,18 @@ class BookmarksBar(QWidget):
self._chips_from = self._lay.count() # where chips start
self._lay.addStretch(1)
def set_bookmarks(self, count: int) -> None:
# remove existing chips (keep label + add button + trailing stretch)
def set_bookmarks(self, labels: list[str]) -> None:
"""Rebuild chips from the list of labels (index = bookmark index)."""
while self._lay.count() > self._chips_from + 1:
item = self._lay.takeAt(self._chips_from)
w = item.widget()
if w:
w.setParent(None)
w.deleteLater()
for i in range(count):
self._lay.insertWidget(self._lay.count() - 1, self._make_chip(i))
for i, label in enumerate(labels):
self._lay.insertWidget(self._lay.count() - 1, self._make_chip(i, label))
def _make_chip(self, i: int) -> QWidget:
def _make_chip(self, i: int, label: str) -> QWidget:
p = self._p
color = BOOKMARK_COLORS[i % len(BOOKMARK_COLORS)]
chip = QWidget()
@@ -69,6 +71,15 @@ class BookmarksBar(QWidget):
f"background:{color}; color:#fff; border-radius:8px; font-size:10px;"
f" font-weight:700;")
cl.addWidget(dot)
name = QLineEdit(label)
name.setFixedWidth(96)
name.setToolTip("Rename bookmark")
name.setStyleSheet(
f"QLineEdit {{ border:none; background:transparent; font-size:12px;"
f" color:{p.text_primary}; }}")
name.editingFinished.connect(
lambda ix=i, le=name: self.bookmark_rename.emit(ix, le.text().strip()))
cl.addWidget(name)
go = self._mini("Go", p)
go.clicked.connect(lambda _=False, ix=i: self.bookmark_goto.emit(ix))
col = self._mini("Collect", p, accent=True)