""" ui/profile_manager_ui.py Profile menu button (πŸ“) in the top-right toolbar. Handles New / Load / Save / Save As via file dialogs. Thin UI layer β€” all serialisation is in core/profile.py. """ import os from PyQt6.QtWidgets import ( QMenu, QPushButton, QFileDialog, QInputDialog, QMessageBox, QApplication, ) from PyQt6.QtCore import QPoint from PyQt6.QtGui import QAction from core.profile import Profile, ProfileManager PROFILE_EXT = ".labdaq" PROFILE_FILTER = f"LabDAQ Profile (*{PROFILE_EXT});;All files (*)" class ProfileButton(QPushButton): """ A 'πŸ“ File' button that shows New / Load / Save / Save As. Placed in the toolbar top-right. Caller provides callbacks: on_new() β€” reset to blank state get_profile() β€” return a Profile representing current state apply_profile(p) β€” restore state from a Profile """ def __init__(self, on_new, get_profile, apply_profile, parent=None): super().__init__("πŸ“ File", parent) self.setObjectName("toolbarSectionBtn") self._on_new = on_new self._get_profile = get_profile self._apply_profile = apply_profile self._current_path: str = "" self.clicked.connect(self._show_menu) # ── Menu ────────────────────────────────────────────────────────────────── def _show_menu(self): menu = QMenu(self) menu.setObjectName("profileMenu") a_new = menu.addAction("πŸ“„ New Profile") menu.addSeparator() a_open = menu.addAction("πŸ“‚ Load Profile…") menu.addSeparator() a_save = menu.addAction("πŸ’Ύ Save") a_save_as = menu.addAction("πŸ’Ύ Save As…") a_save.setEnabled(bool(self._current_path)) a_new.triggered.connect(self._do_new) a_open.triggered.connect(self._do_load) a_save.triggered.connect(self._do_save) a_save_as.triggered.connect(self._do_save_as) # Show below the button pos = self.mapToGlobal(QPoint(0, self.height())) menu.exec(pos) # ── Actions ─────────────────────────────────────────────────────────────── def _do_new(self): reply = QMessageBox.question( self, "New Profile", "Discard current configuration and start a blank profile?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, ) if reply == QMessageBox.StandardButton.Yes: self._current_path = "" self._on_new() self._update_title() def _do_load(self): path, _ = QFileDialog.getOpenFileName( self, "Load Profile", self._default_dir(), PROFILE_FILTER, ) if not path: return try: profile = Profile.load(path) self._apply_profile(profile) self._current_path = path self._update_title(profile.name) except Exception as e: QMessageBox.critical(self, "Load Failed", str(e)) def _do_save(self): if not self._current_path: self._do_save_as() return self._save_to(self._current_path) def _do_save_as(self): # Ask for profile name name, ok = QInputDialog.getText( self, "Profile Name", "Profile name:", text=os.path.splitext(os.path.basename(self._current_path))[0] if self._current_path else "My Profile", ) if not ok or not name.strip(): return name = name.strip() path, _ = QFileDialog.getSaveFileName( self, "Save Profile As", os.path.join(self._default_dir(), name + PROFILE_EXT), PROFILE_FILTER, ) if not path: return if not path.endswith(PROFILE_EXT): path += PROFILE_EXT self._save_to(path, name) def _save_to(self, path: str, name: str = ""): try: profile = self._get_profile(name or os.path.splitext( os.path.basename(path))[0]) profile.save(path) self._current_path = path self._update_title(profile.name) except Exception as e: QMessageBox.critical(self, "Save Failed", str(e)) # ── Helpers ─────────────────────────────────────────────────────────────── def _default_dir(self) -> str: d = os.path.join(os.path.expanduser("~"), "labdaq_profiles") os.makedirs(d, exist_ok=True) return d def _update_title(self, name: str = ""): app = QApplication.instance() if app and hasattr(app, "topLevelWidgets"): for w in app.topLevelWidgets(): if hasattr(w, "setWindowTitle") and "LabDAQ" in (w.windowTitle() or ""): title = "LabDAQ" if name: title += f" β€” {name}" if self._current_path: title += f" [{os.path.basename(self._current_path)}]" w.setWindowTitle(title) break