Skip to content

Commit 1a306b7

Browse files
Offboarding after Save (#209)
* added an offboarding * Changes to content to match next Steps tutorial * Unified cards in a function, added some description to functions * Slight change to wording of benchmark * Apply suggestions from code review Co-authored-by: Daniel Weindl <dweindl@users.noreply.github.com> --------- Co-authored-by: Daniel Weindl <dweindl@users.noreply.github.com>
1 parent 9c1d057 commit 1a306b7

File tree

3 files changed

+312
-1
lines changed

3 files changed

+312
-1
lines changed

src/petab_gui/controllers/mother_controller.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
process_file,
4343
)
4444
from ..views import TaskBar
45+
from ..views.other_views import NextStepsPanel
4546
from .logger_controller import LoggerController
4647
from .sbml_controller import SbmlController
4748
from .table_controllers import (
@@ -150,6 +151,11 @@ def __init__(self, view, model: PEtabModel):
150151
}
151152
self.sbml_checkbox_states = {"sbml": False, "antimony": False}
152153
self.unsaved_changes = False
154+
# Next Steps Panel
155+
self.next_steps_panel = NextStepsPanel(self.view)
156+
self.next_steps_panel.dont_show_again_changed.connect(
157+
self._handle_next_steps_dont_show_again
158+
)
153159
self.filter = QLineEdit()
154160
self.filter_active = {} # Saves which tables the filter applies to
155161
self.actions = self.setup_actions()
@@ -509,6 +515,12 @@ def setup_actions(self):
509515
)
510516
)
511517

518+
# Show next steps panel action
519+
actions["next_steps"] = QAction(
520+
qta.icon("mdi6.lightbulb-on"), "Possible next steps...", self.view
521+
)
522+
actions["next_steps"].triggered.connect(self._show_next_steps_panel)
523+
512524
# Undo / Redo
513525
actions["undo"] = QAction(qta.icon("mdi6.undo"), "&Undo", self.view)
514526
actions["undo"].setShortcut(QKeySequence.Undo)
@@ -612,6 +624,14 @@ def save_model(self):
612624
"Save Project",
613625
f"Project saved successfully to {file_name}",
614626
)
627+
628+
# Show next steps panel if not disabled
629+
dont_show = settings_manager.get_value(
630+
"next_steps/dont_show_again", False, bool
631+
)
632+
if not dont_show:
633+
self.next_steps_panel.show_panel()
634+
615635
return True
616636

617637
def save_single_table(self):
@@ -1502,6 +1522,26 @@ def about(self):
15021522
f"<a href='file://{config_file}'>{config_file}</a></small>",
15031523
)
15041524

1525+
def _show_next_steps_panel(self):
1526+
"""Show the next steps panel (ignores 'don't show again' preference)."""
1527+
# Sync checkbox state with current settings
1528+
dont_show = settings_manager.get_value(
1529+
"next_steps/dont_show_again", False, bool
1530+
)
1531+
self.next_steps_panel.set_dont_show_again(dont_show)
1532+
self.next_steps_panel.show_panel()
1533+
1534+
def _handle_next_steps_dont_show_again(self, dont_show: bool):
1535+
"""Handle the 'don't show again' checkbox state change.
1536+
1537+
Connected to the next steps panel's dont_show_again_changed signal.
1538+
Persists the user's preference to settings.
1539+
1540+
Args:
1541+
dont_show: Whether to suppress the panel on future saves
1542+
"""
1543+
settings_manager.set_value("next_steps/dont_show_again", dont_show)
1544+
15051545
def get_current_problem(self):
15061546
"""Get the current PEtab problem from the model."""
15071547
return self.model.current_petab_problem

src/petab_gui/views/other_views.py

Lines changed: 270 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
"""Collection of other views aside from the main ones."""
22

3+
from PySide6.QtCore import Qt, Signal
34
from PySide6.QtWidgets import (
5+
QCheckBox,
46
QComboBox,
57
QDialog,
8+
QFrame,
69
QHBoxLayout,
710
QLabel,
811
QLineEdit,
912
QPushButton,
13+
QTextBrowser,
1014
QVBoxLayout,
1115
)
1216

@@ -59,8 +63,273 @@ def __init__(
5963
btns.addWidget(ok)
6064
lay.addLayout(btns)
6165

62-
def get_result(self) -> tuple[str | None, str | None]:
66+
def get_result(self) -> tuple[str | None, str | None, str]:
6367
dose = self._dose.currentText() or None
6468
time_text = (self._time.text() or "").strip() or None
6569
preeq = (self._preeq_edit.text() or "").strip()
6670
return dose, time_text, preeq
71+
72+
73+
class NextStepsPanel(QDialog):
74+
"""Non-modal panel showing possible next steps after saving."""
75+
76+
dont_show_again_changed = Signal(bool)
77+
78+
# Styling constants
79+
MIN_WIDTH = 450
80+
MAX_WIDTH = 600
81+
MIN_HEIGHT = 360
82+
FRAME_PADDING = 8
83+
FRAME_BORDER_RADIUS = 4
84+
LAYOUT_MARGIN = 12
85+
LAYOUT_SPACING = 10
86+
87+
# Card background colors
88+
COLOR_BENCHMARK = "rgba(255, 193, 7, 0.08)"
89+
COLOR_PYPESTO = "rgba(100, 149, 237, 0.08)"
90+
COLOR_COPASI = "rgba(169, 169, 169, 0.08)"
91+
COLOR_OTHER_TOOLS = "rgba(144, 238, 144, 0.08)"
92+
93+
def __init__(self, parent=None):
94+
super().__init__(parent)
95+
self.setWindowTitle("Possible next steps")
96+
self.setModal(False)
97+
self.setMinimumWidth(self.MIN_WIDTH)
98+
self.setMaximumWidth(self.MAX_WIDTH)
99+
self.setMinimumHeight(self.MIN_HEIGHT)
100+
101+
# Main layout
102+
main_layout = QVBoxLayout(self)
103+
main_layout.setContentsMargins(
104+
self.LAYOUT_MARGIN,
105+
self.LAYOUT_MARGIN,
106+
self.LAYOUT_MARGIN,
107+
self.LAYOUT_MARGIN,
108+
)
109+
main_layout.setSpacing(self.LAYOUT_SPACING)
110+
111+
# Description
112+
desc = QLabel(
113+
"This parameter estimation problem can now be used in the following tools:"
114+
)
115+
desc.setWordWrap(True)
116+
main_layout.addWidget(desc)
117+
118+
# Main suggestions
119+
suggestions_layout = QVBoxLayout()
120+
suggestions_layout.setSpacing(8)
121+
122+
# Benchmark Collection action
123+
benchmark_frame = self._create_tool_card(
124+
bg_color=self.COLOR_BENCHMARK,
125+
html_content=(
126+
'<p style="margin:0; line-height:1.3;">'
127+
"<b>📚 Contribute to Benchmark Collection</b><br/>"
128+
"Share your publsihed PEtab problem with the community to "
129+
"validate it, enable reproducibility, and support "
130+
"benchmarking.<br/>"
131+
'<a href="https://github.com/Benchmarking-Initiative/'
132+
'Benchmark-Models-PEtab">Benchmark Collection</a></p>'
133+
),
134+
)
135+
suggestions_layout.addWidget(benchmark_frame)
136+
137+
# pyPESTO action
138+
pypesto_frame = self._create_tool_card(
139+
bg_color=self.COLOR_PYPESTO,
140+
html_content=(
141+
'<p style="margin:0; line-height:1.3;">'
142+
"<b>▶ Parameter Estimation with pyPESTO</b><br/>"
143+
"Use pyPESTO for parameter estimation, uncertainty analysis, "
144+
"and model selection.<br/>"
145+
'<a href="https://pypesto.readthedocs.io/en/latest/example/'
146+
'petab_import.html">pyPESTO documentation</a></p>'
147+
),
148+
)
149+
suggestions_layout.addWidget(pypesto_frame)
150+
151+
# COPASI action
152+
copasi_frame = self._create_tool_card(
153+
bg_color=self.COLOR_COPASI,
154+
html_content=(
155+
'<p style="margin:0; line-height:1.3;">'
156+
"<b>⚙ Advanced Model Adaptation and Simulation</b><br/>"
157+
"Use COPASI for further model adjustment and advanced "
158+
"simulation with a graphical interface.<br/>"
159+
'<a href="https://copasi.org">COPASI website</a></p>'
160+
),
161+
)
162+
suggestions_layout.addWidget(copasi_frame)
163+
164+
main_layout.addLayout(suggestions_layout)
165+
166+
# Collapsible section for other tools
167+
self._other_tools_btn = QPushButton(
168+
"📊 ▶ Other tools supporting PEtab"
169+
)
170+
self._other_tools_btn.setCheckable(True)
171+
self._other_tools_btn.setFlat(True)
172+
self._other_tools_btn.setStyleSheet(
173+
"QPushButton { text-align: left; padding: 6px; "
174+
"font-weight: normal; }"
175+
"QPushButton:checked { font-weight: bold; }"
176+
)
177+
self._other_tools_btn.clicked.connect(self._toggle_other_tools)
178+
main_layout.addWidget(self._other_tools_btn)
179+
180+
# Other tools frame (initially hidden)
181+
self._other_tools_frame = QFrame()
182+
self._other_tools_frame.setStyleSheet(
183+
f"QFrame {{ background-color: {self.COLOR_OTHER_TOOLS}; "
184+
f"border-radius: {self.FRAME_BORDER_RADIUS}px; "
185+
f"padding: {self.FRAME_PADDING}px; }}"
186+
)
187+
self._other_tools_frame.setVisible(False)
188+
other_tools_layout = QVBoxLayout(self._other_tools_frame)
189+
other_tools_layout.setContentsMargins(
190+
self.FRAME_PADDING,
191+
self.FRAME_PADDING,
192+
self.FRAME_PADDING,
193+
self.FRAME_PADDING,
194+
)
195+
other_tools_layout.setSpacing(4)
196+
197+
# Framing text
198+
framing_text = QLabel("Additional tools in the PEtab ecosystem:")
199+
framing_text.setWordWrap(True)
200+
other_tools_layout.addWidget(framing_text)
201+
202+
other_tools_text = QTextBrowser()
203+
other_tools_text.setOpenExternalLinks(True)
204+
other_tools_text.setMaximumHeight(120)
205+
other_tools_text.setFrameStyle(QFrame.NoFrame)
206+
other_tools_text.setStyleSheet(
207+
"QTextBrowser { background: transparent; }"
208+
)
209+
other_tools_text.setVerticalScrollBarPolicy(
210+
Qt.ScrollBarPolicy.ScrollBarAsNeeded
211+
)
212+
other_tools_text.setHtml(
213+
'<ul style="margin:4px 0; padding-left: 20px; '
214+
'line-height: 1.6;">'
215+
'<li style="margin-bottom: 4px;">'
216+
'<a href="https://amici.readthedocs.io/en/latest/examples/'
217+
'example_petab/petab.html">AMICI</a> - '
218+
"Efficient simulation and sensitivity analysis</li>"
219+
'<li style="margin-bottom: 4px;">'
220+
'<a href="https://sebapersson.github.io/PEtab.jl/stable/">'
221+
"PEtab.jl</a> - "
222+
"High-performance Julia parameter estimation</li>"
223+
'<li style="margin-bottom: 4px;">'
224+
'<a href="https://github.com/Data2Dynamics/d2d/wiki">'
225+
"Data2Dynamics</a> - "
226+
"MATLAB-based comprehensive modeling framework</li>"
227+
'<li style="margin-bottom: 4px;">'
228+
'<a href="https://petab.readthedocs.io/en/latest/v1/'
229+
'software_support.html">PEtab documentation</a> - '
230+
"Full list of supporting tools</li>"
231+
"</ul>"
232+
)
233+
other_tools_layout.addWidget(other_tools_text)
234+
235+
main_layout.addWidget(self._other_tools_frame)
236+
237+
# Spacer
238+
main_layout.addStretch()
239+
240+
# Reassurance text
241+
reassurance = QLabel(
242+
"<small><i>You can always access this dialog from the "
243+
"Help menu.</i></small>"
244+
)
245+
reassurance.setWordWrap(True)
246+
reassurance.setStyleSheet("QLabel { color: gray; padding: 0; }")
247+
main_layout.addWidget(reassurance)
248+
249+
# Bottom section with checkbox and close button
250+
bottom_layout = QHBoxLayout()
251+
bottom_layout.setSpacing(8)
252+
253+
self._dont_show_checkbox = QCheckBox("Don't show after saving")
254+
self._dont_show_checkbox.toggled.connect(
255+
self.dont_show_again_changed.emit
256+
)
257+
bottom_layout.addWidget(self._dont_show_checkbox)
258+
259+
bottom_layout.addStretch()
260+
261+
close_btn = QPushButton("Close")
262+
close_btn.clicked.connect(self.close)
263+
close_btn.setDefault(True)
264+
bottom_layout.addWidget(close_btn)
265+
266+
main_layout.addLayout(bottom_layout)
267+
268+
def _create_tool_card(
269+
self, bg_color: str, html_content: str, scrollbar_policy=None
270+
) -> QFrame:
271+
"""Create a styled card for displaying tool information.
272+
273+
Args:
274+
bg_color: Background color for the frame (rgba string)
275+
html_content: HTML content to display in the text browser
276+
scrollbar_policy: Optional scrollbar policy (defaults to AlwaysOff)
277+
278+
Returns:
279+
Configured QFrame containing the tool information
280+
"""
281+
frame = QFrame()
282+
frame.setStyleSheet(
283+
f"QFrame {{ background-color: {bg_color}; "
284+
f"border-radius: {self.FRAME_BORDER_RADIUS}px; "
285+
f"padding: {self.FRAME_PADDING}px; }}"
286+
)
287+
layout = QVBoxLayout(frame)
288+
layout.setContentsMargins(
289+
self.FRAME_PADDING,
290+
self.FRAME_PADDING,
291+
self.FRAME_PADDING,
292+
self.FRAME_PADDING,
293+
)
294+
layout.setSpacing(4)
295+
296+
text_browser = QTextBrowser()
297+
text_browser.setOpenExternalLinks(True)
298+
text_browser.setFrameStyle(QFrame.NoFrame)
299+
text_browser.setStyleSheet("QTextBrowser { background: transparent; }")
300+
if scrollbar_policy is None:
301+
scrollbar_policy = Qt.ScrollBarPolicy.ScrollBarAlwaysOff
302+
text_browser.setVerticalScrollBarPolicy(scrollbar_policy)
303+
text_browser.setHtml(html_content)
304+
layout.addWidget(text_browser)
305+
306+
return frame
307+
308+
def _toggle_other_tools(self, checked):
309+
"""Toggle visibility of other tools section."""
310+
self._other_tools_frame.setVisible(checked)
311+
# Update button text to show expand/collapse state
312+
arrow = "▼" if checked else "▶"
313+
icon = "📊"
314+
self._other_tools_btn.setText(
315+
f"{icon} {arrow} Other tools supporting PEtab"
316+
)
317+
# Adjust window size
318+
self.adjustSize()
319+
320+
def set_dont_show_again(self, dont_show: bool):
321+
"""Set the 'don't show again' checkbox state."""
322+
self._dont_show_checkbox.setChecked(dont_show)
323+
324+
def show_panel(self):
325+
"""Show the panel and center it on the parent."""
326+
if self.parent():
327+
# Center on parent window
328+
parent_geo = self.parent().geometry()
329+
self.move(
330+
parent_geo.center().x() - self.width() // 2,
331+
parent_geo.center().y() - self.height() // 2,
332+
)
333+
self.show()
334+
self.raise_()
335+
self.activateWindow()

src/petab_gui/views/task_bar.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ def __init__(self, parent, actions):
139139

140140
# Add actions to the menu for re-adding tables
141141
self.menu.addAction(actions["open_documentation"])
142+
self.menu.addAction(actions["next_steps"])
143+
self.menu.addSeparator()
142144
self.menu.addAction(actions["whats_this"])
143145
self.menu.addAction(actions["about"])
144146

0 commit comments

Comments
 (0)