1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
"""
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 = ".labui"
PROFILE_FILTER = f"LabUI 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("~"), "labui_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 "LabUI" in (w.windowTitle() or ""):
title = "LabUI"
if name:
title += f" β {name}"
if self._current_path:
title += f" [{os.path.basename(self._current_path)}]"
w.setWindowTitle(title)
break
|