diff --git a/src/aare/gui/new_gui/README.md b/src/aare/gui/new_gui/README.md index c65192fb..fa558294 100644 --- a/src/aare/gui/new_gui/README.md +++ b/src/aare/gui/new_gui/README.md @@ -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). diff --git a/src/aare/gui/new_gui/manual_view.py b/src/aare/gui/new_gui/manual_view.py index 588c95f6..2f478ff8 100644 --- a/src/aare/gui/new_gui/manual_view.py +++ b/src/aare/gui/new_gui/manual_view.py @@ -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) diff --git a/src/aare/gui/new_gui/widgets/bookmarks.py b/src/aare/gui/new_gui/widgets/bookmarks.py index 8fa22d3a..90cfe123 100644 --- a/src/aare/gui/new_gui/widgets/bookmarks.py +++ b/src/aare/gui/new_gui/widgets/bookmarks.py @@ -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)