diff --git a/src/petab_gui/controllers/mother_controller.py b/src/petab_gui/controllers/mother_controller.py index fc2ea85..73300a6 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() @@ -489,6 +495,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) @@ -582,6 +594,13 @@ def save_model(self): "Save Project", f"Project saved successfully to {file_name}", ) + + # Show next steps panel if not disabled + if not settings_manager.get_value( + "next_steps/dont_show_again", False, bool + ): + self.next_steps_panel.show_panel() + return True def save_single_table(self): @@ -1414,6 +1433,14 @@ def about(self): f"{config_file}", ) + def _show_next_steps_panel(self): + """Show the next steps panel (ignores 'don't show again' preference).""" + self.next_steps_panel.show_panel() + + def _handle_next_steps_dont_show_again(self, dont_show: bool): + """Handle the 'don't show again' checkbox in the next steps panel.""" + 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..801f598 100644 --- a/src/petab_gui/views/other_views.py +++ b/src/petab_gui/views/other_views.py @@ -1,13 +1,18 @@ """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, + QWidget, ) @@ -64,3 +69,211 @@ def get_result(self) -> tuple[str | None, str | None]: time_text = (self._time.text() or "").strip() or None preeq = (self._preeq_edit.text() or "").strip() return dose, time_text, preeq + + +class NextStepsPanel(QWidget): + """Non-modal panel showing possible next steps after saving.""" + + dont_show_again_changed = Signal(bool) + + def __init__(self, parent=None): + super().__init__(parent, Qt.Window | Qt.WindowStaysOnTopHint) + self.setWindowTitle("Possible next steps") + self.setMinimumWidth(450) + self.setMaximumWidth(600) + self.setMinimumHeight(360) + + # Main layout + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(12, 12, 12, 12) + main_layout.setSpacing(10) + + # 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) + + # pyPESTO action + pypesto_frame = QFrame() + pypesto_frame.setStyleSheet( + "QFrame { background-color: rgba(100, 149, 237, 0.08); " + "border-radius: 4px; padding: 8px; }" + ) + pypesto_layout = QVBoxLayout(pypesto_frame) + pypesto_layout.setContentsMargins(8, 8, 8, 8) + pypesto_layout.setSpacing(4) + + pypesto_text = QTextBrowser() + pypesto_text.setOpenExternalLinks(True) + pypesto_text.setFrameStyle(QFrame.NoFrame) + pypesto_text.setStyleSheet("QTextBrowser { background: transparent; }") + pypesto_text.setVerticalScrollBarPolicy( + Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + pypesto_text.setHtml( + '

' + "▶ Parameter estimation
" + "Use pyPESTO for parameter estimation and uncertainty " + "analysis
" + 'pyPESTO documentation

' + ) + pypesto_layout.addWidget(pypesto_text) + suggestions_layout.addWidget(pypesto_frame) + + # COPASI action + copasi_frame = QFrame() + copasi_frame.setStyleSheet( + "QFrame { background-color: rgba(169, 169, 169, 0.08); " + "border-radius: 4px; padding: 8px; }" + ) + copasi_layout = QVBoxLayout(copasi_frame) + copasi_layout.setContentsMargins(8, 8, 8, 8) + copasi_layout.setSpacing(4) + + copasi_text = QTextBrowser() + copasi_text.setOpenExternalLinks(True) + copasi_text.setFrameStyle(QFrame.NoFrame) + copasi_text.setStyleSheet("QTextBrowser { background: transparent; }") + copasi_text.setVerticalScrollBarPolicy( + Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + copasi_text.setHtml( + '

' + "⚙ Model adjustment and simulation
" + "Use COPASI for further model adjustment and advanced " + "simulation
" + 'COPASI website

' + ) + copasi_layout.addWidget(copasi_text) + 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( + "QFrame { background-color: rgba(144, 238, 144, 0.08); " + "border-radius: 4px; padding: 8px; }" + ) + self._other_tools_frame.setVisible(False) + other_tools_layout = QVBoxLayout(self._other_tools_frame) + other_tools_layout.setContentsMargins(8, 8, 8, 8) + 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( + '" + ) + 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.stateChanged.connect( + lambda state: self.dont_show_again_changed.emit( + state == Qt.CheckState.Checked + ) + ) + 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 _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 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 408221c..2e787c3 100644 --- a/src/petab_gui/views/task_bar.py +++ b/src/petab_gui/views/task_bar.py @@ -136,6 +136,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"])