diff --git a/src/petab_gui/controllers/mother_controller.py b/src/petab_gui/controllers/mother_controller.py
index ae8f253..6293bb5 100644
--- a/src/petab_gui/controllers/mother_controller.py
+++ b/src/petab_gui/controllers/mother_controller.py
@@ -42,6 +42,7 @@
process_file,
)
from ..views import TaskBar
+from ..views.other_views import NextStepsPanel
from .logger_controller import LoggerController
from .sbml_controller import SbmlController
from .table_controllers import (
@@ -150,6 +151,11 @@ def __init__(self, view, model: PEtabModel):
}
self.sbml_checkbox_states = {"sbml": False, "antimony": False}
self.unsaved_changes = False
+ # Next Steps Panel
+ self.next_steps_panel = NextStepsPanel(self.view)
+ self.next_steps_panel.dont_show_again_changed.connect(
+ self._handle_next_steps_dont_show_again
+ )
self.filter = QLineEdit()
self.filter_active = {} # Saves which tables the filter applies to
self.actions = self.setup_actions()
@@ -509,6 +515,12 @@ def setup_actions(self):
)
)
+ # Show next steps panel action
+ actions["next_steps"] = QAction(
+ qta.icon("mdi6.lightbulb-on"), "Possible next steps...", self.view
+ )
+ actions["next_steps"].triggered.connect(self._show_next_steps_panel)
+
# Undo / Redo
actions["undo"] = QAction(qta.icon("mdi6.undo"), "&Undo", self.view)
actions["undo"].setShortcut(QKeySequence.Undo)
@@ -612,6 +624,14 @@ def save_model(self):
"Save Project",
f"Project saved successfully to {file_name}",
)
+
+ # Show next steps panel if not disabled
+ dont_show = settings_manager.get_value(
+ "next_steps/dont_show_again", False, bool
+ )
+ if not dont_show:
+ self.next_steps_panel.show_panel()
+
return True
def save_single_table(self):
@@ -1502,6 +1522,26 @@ def about(self):
f"{config_file}",
)
+ def _show_next_steps_panel(self):
+ """Show the next steps panel (ignores 'don't show again' preference)."""
+ # Sync checkbox state with current settings
+ dont_show = settings_manager.get_value(
+ "next_steps/dont_show_again", False, bool
+ )
+ self.next_steps_panel.set_dont_show_again(dont_show)
+ self.next_steps_panel.show_panel()
+
+ def _handle_next_steps_dont_show_again(self, dont_show: bool):
+ """Handle the 'don't show again' checkbox state change.
+
+ Connected to the next steps panel's dont_show_again_changed signal.
+ Persists the user's preference to settings.
+
+ Args:
+ dont_show: Whether to suppress the panel on future saves
+ """
+ settings_manager.set_value("next_steps/dont_show_again", dont_show)
+
def get_current_problem(self):
"""Get the current PEtab problem from the model."""
return self.model.current_petab_problem
diff --git a/src/petab_gui/views/other_views.py b/src/petab_gui/views/other_views.py
index cb5480b..f75561f 100644
--- a/src/petab_gui/views/other_views.py
+++ b/src/petab_gui/views/other_views.py
@@ -1,12 +1,16 @@
"""Collection of other views aside from the main ones."""
+from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import (
+ QCheckBox,
QComboBox,
QDialog,
+ QFrame,
QHBoxLayout,
QLabel,
QLineEdit,
QPushButton,
+ QTextBrowser,
QVBoxLayout,
)
@@ -59,8 +63,273 @@ def __init__(
btns.addWidget(ok)
lay.addLayout(btns)
- def get_result(self) -> tuple[str | None, str | None]:
+ def get_result(self) -> tuple[str | None, str | None, str]:
dose = self._dose.currentText() or None
time_text = (self._time.text() or "").strip() or None
preeq = (self._preeq_edit.text() or "").strip()
return dose, time_text, preeq
+
+
+class NextStepsPanel(QDialog):
+ """Non-modal panel showing possible next steps after saving."""
+
+ dont_show_again_changed = Signal(bool)
+
+ # Styling constants
+ MIN_WIDTH = 450
+ MAX_WIDTH = 600
+ MIN_HEIGHT = 360
+ FRAME_PADDING = 8
+ FRAME_BORDER_RADIUS = 4
+ LAYOUT_MARGIN = 12
+ LAYOUT_SPACING = 10
+
+ # Card background colors
+ COLOR_BENCHMARK = "rgba(255, 193, 7, 0.08)"
+ COLOR_PYPESTO = "rgba(100, 149, 237, 0.08)"
+ COLOR_COPASI = "rgba(169, 169, 169, 0.08)"
+ COLOR_OTHER_TOOLS = "rgba(144, 238, 144, 0.08)"
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle("Possible next steps")
+ self.setModal(False)
+ self.setMinimumWidth(self.MIN_WIDTH)
+ self.setMaximumWidth(self.MAX_WIDTH)
+ self.setMinimumHeight(self.MIN_HEIGHT)
+
+ # Main layout
+ main_layout = QVBoxLayout(self)
+ main_layout.setContentsMargins(
+ self.LAYOUT_MARGIN,
+ self.LAYOUT_MARGIN,
+ self.LAYOUT_MARGIN,
+ self.LAYOUT_MARGIN,
+ )
+ main_layout.setSpacing(self.LAYOUT_SPACING)
+
+ # Description
+ desc = QLabel(
+ "This parameter estimation problem can now be used in the following tools:"
+ )
+ desc.setWordWrap(True)
+ main_layout.addWidget(desc)
+
+ # Main suggestions
+ suggestions_layout = QVBoxLayout()
+ suggestions_layout.setSpacing(8)
+
+ # Benchmark Collection action
+ benchmark_frame = self._create_tool_card(
+ bg_color=self.COLOR_BENCHMARK,
+ html_content=(
+ '
'
+ "📚 Contribute to Benchmark Collection
"
+ "Share your publsihed PEtab problem with the community to "
+ "validate it, enable reproducibility, and support "
+ "benchmarking.
"
+ 'Benchmark Collection
'
+ ),
+ )
+ suggestions_layout.addWidget(benchmark_frame)
+
+ # pyPESTO action
+ pypesto_frame = self._create_tool_card(
+ bg_color=self.COLOR_PYPESTO,
+ html_content=(
+ ''
+ "â–¶ Parameter Estimation with pyPESTO
"
+ "Use pyPESTO for parameter estimation, uncertainty analysis, "
+ "and model selection.
"
+ 'pyPESTO documentation
'
+ ),
+ )
+ suggestions_layout.addWidget(pypesto_frame)
+
+ # COPASI action
+ copasi_frame = self._create_tool_card(
+ bg_color=self.COLOR_COPASI,
+ html_content=(
+ ''
+ "âš™ Advanced Model Adaptation and Simulation
"
+ "Use COPASI for further model adjustment and advanced "
+ "simulation with a graphical interface.
"
+ 'COPASI website
'
+ ),
+ )
+ suggestions_layout.addWidget(copasi_frame)
+
+ main_layout.addLayout(suggestions_layout)
+
+ # Collapsible section for other tools
+ self._other_tools_btn = QPushButton(
+ "📊 ▶ Other tools supporting PEtab"
+ )
+ self._other_tools_btn.setCheckable(True)
+ self._other_tools_btn.setFlat(True)
+ self._other_tools_btn.setStyleSheet(
+ "QPushButton { text-align: left; padding: 6px; "
+ "font-weight: normal; }"
+ "QPushButton:checked { font-weight: bold; }"
+ )
+ self._other_tools_btn.clicked.connect(self._toggle_other_tools)
+ main_layout.addWidget(self._other_tools_btn)
+
+ # Other tools frame (initially hidden)
+ self._other_tools_frame = QFrame()
+ self._other_tools_frame.setStyleSheet(
+ f"QFrame {{ background-color: {self.COLOR_OTHER_TOOLS}; "
+ f"border-radius: {self.FRAME_BORDER_RADIUS}px; "
+ f"padding: {self.FRAME_PADDING}px; }}"
+ )
+ self._other_tools_frame.setVisible(False)
+ other_tools_layout = QVBoxLayout(self._other_tools_frame)
+ other_tools_layout.setContentsMargins(
+ self.FRAME_PADDING,
+ self.FRAME_PADDING,
+ self.FRAME_PADDING,
+ self.FRAME_PADDING,
+ )
+ other_tools_layout.setSpacing(4)
+
+ # Framing text
+ framing_text = QLabel("Additional tools in the PEtab ecosystem:")
+ framing_text.setWordWrap(True)
+ other_tools_layout.addWidget(framing_text)
+
+ other_tools_text = QTextBrowser()
+ other_tools_text.setOpenExternalLinks(True)
+ other_tools_text.setMaximumHeight(120)
+ other_tools_text.setFrameStyle(QFrame.NoFrame)
+ other_tools_text.setStyleSheet(
+ "QTextBrowser { background: transparent; }"
+ )
+ other_tools_text.setVerticalScrollBarPolicy(
+ Qt.ScrollBarPolicy.ScrollBarAsNeeded
+ )
+ other_tools_text.setHtml(
+ ''
+ '- '
+ 'AMICI - '
+ "Efficient simulation and sensitivity analysis
"
+ '- '
+ ''
+ "PEtab.jl - "
+ "High-performance Julia parameter estimation
"
+ '- '
+ ''
+ "Data2Dynamics - "
+ "MATLAB-based comprehensive modeling framework
"
+ '- '
+ 'PEtab documentation - '
+ "Full list of supporting tools
"
+ "
"
+ )
+ other_tools_layout.addWidget(other_tools_text)
+
+ main_layout.addWidget(self._other_tools_frame)
+
+ # Spacer
+ main_layout.addStretch()
+
+ # Reassurance text
+ reassurance = QLabel(
+ "You can always access this dialog from the "
+ "Help menu."
+ )
+ reassurance.setWordWrap(True)
+ reassurance.setStyleSheet("QLabel { color: gray; padding: 0; }")
+ main_layout.addWidget(reassurance)
+
+ # Bottom section with checkbox and close button
+ bottom_layout = QHBoxLayout()
+ bottom_layout.setSpacing(8)
+
+ self._dont_show_checkbox = QCheckBox("Don't show after saving")
+ self._dont_show_checkbox.toggled.connect(
+ self.dont_show_again_changed.emit
+ )
+ bottom_layout.addWidget(self._dont_show_checkbox)
+
+ bottom_layout.addStretch()
+
+ close_btn = QPushButton("Close")
+ close_btn.clicked.connect(self.close)
+ close_btn.setDefault(True)
+ bottom_layout.addWidget(close_btn)
+
+ main_layout.addLayout(bottom_layout)
+
+ def _create_tool_card(
+ self, bg_color: str, html_content: str, scrollbar_policy=None
+ ) -> QFrame:
+ """Create a styled card for displaying tool information.
+
+ Args:
+ bg_color: Background color for the frame (rgba string)
+ html_content: HTML content to display in the text browser
+ scrollbar_policy: Optional scrollbar policy (defaults to AlwaysOff)
+
+ Returns:
+ Configured QFrame containing the tool information
+ """
+ frame = QFrame()
+ frame.setStyleSheet(
+ f"QFrame {{ background-color: {bg_color}; "
+ f"border-radius: {self.FRAME_BORDER_RADIUS}px; "
+ f"padding: {self.FRAME_PADDING}px; }}"
+ )
+ layout = QVBoxLayout(frame)
+ layout.setContentsMargins(
+ self.FRAME_PADDING,
+ self.FRAME_PADDING,
+ self.FRAME_PADDING,
+ self.FRAME_PADDING,
+ )
+ layout.setSpacing(4)
+
+ text_browser = QTextBrowser()
+ text_browser.setOpenExternalLinks(True)
+ text_browser.setFrameStyle(QFrame.NoFrame)
+ text_browser.setStyleSheet("QTextBrowser { background: transparent; }")
+ if scrollbar_policy is None:
+ scrollbar_policy = Qt.ScrollBarPolicy.ScrollBarAlwaysOff
+ text_browser.setVerticalScrollBarPolicy(scrollbar_policy)
+ text_browser.setHtml(html_content)
+ layout.addWidget(text_browser)
+
+ return frame
+
+ def _toggle_other_tools(self, checked):
+ """Toggle visibility of other tools section."""
+ self._other_tools_frame.setVisible(checked)
+ # Update button text to show expand/collapse state
+ arrow = "â–¼" if checked else "â–¶"
+ icon = "📊"
+ self._other_tools_btn.setText(
+ f"{icon} {arrow} Other tools supporting PEtab"
+ )
+ # Adjust window size
+ self.adjustSize()
+
+ def set_dont_show_again(self, dont_show: bool):
+ """Set the 'don't show again' checkbox state."""
+ self._dont_show_checkbox.setChecked(dont_show)
+
+ def show_panel(self):
+ """Show the panel and center it on the parent."""
+ if self.parent():
+ # Center on parent window
+ parent_geo = self.parent().geometry()
+ self.move(
+ parent_geo.center().x() - self.width() // 2,
+ parent_geo.center().y() - self.height() // 2,
+ )
+ self.show()
+ self.raise_()
+ self.activateWindow()
diff --git a/src/petab_gui/views/task_bar.py b/src/petab_gui/views/task_bar.py
index 3bc6a61..6c4e8b1 100644
--- a/src/petab_gui/views/task_bar.py
+++ b/src/petab_gui/views/task_bar.py
@@ -139,6 +139,8 @@ def __init__(self, parent, actions):
# Add actions to the menu for re-adding tables
self.menu.addAction(actions["open_documentation"])
+ self.menu.addAction(actions["next_steps"])
+ self.menu.addSeparator()
self.menu.addAction(actions["whats_this"])
self.menu.addAction(actions["about"])