From 12410a51e7d386ff1a2db9630aad75adab2aa0a7 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 13 Jan 2026 15:52:19 +0100 Subject: [PATCH 1/5] added an offboarding --- .../controllers/mother_controller.py | 27 +++ src/petab_gui/views/other_views.py | 213 ++++++++++++++++++ src/petab_gui/views/task_bar.py | 2 + 3 files changed, 242 insertions(+) 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"]) From 34ab4835825da4573ca5ab548aee9bfd12b8c1be Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 20 Jan 2026 17:10:38 +0100 Subject: [PATCH 2/5] Changes to content to match next Steps tutorial --- .../controllers/mother_controller.py | 11 ++- src/petab_gui/views/other_views.py | 69 ++++++++++++++----- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/petab_gui/controllers/mother_controller.py b/src/petab_gui/controllers/mother_controller.py index 73300a6..d480d46 100644 --- a/src/petab_gui/controllers/mother_controller.py +++ b/src/petab_gui/controllers/mother_controller.py @@ -596,9 +596,11 @@ def save_model(self): ) # Show next steps panel if not disabled - if not settings_manager.get_value( + dont_show = settings_manager.get_value( "next_steps/dont_show_again", False, bool - ): + ) + if not dont_show: + self.next_steps_panel.set_dont_show_again(dont_show) self.next_steps_panel.show_panel() return True @@ -1435,6 +1437,11 @@ def about(self): 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): diff --git a/src/petab_gui/views/other_views.py b/src/petab_gui/views/other_views.py index 801f598..a33c41d 100644 --- a/src/petab_gui/views/other_views.py +++ b/src/petab_gui/views/other_views.py @@ -12,7 +12,6 @@ QPushButton, QTextBrowser, QVBoxLayout, - QWidget, ) @@ -64,21 +63,22 @@ 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(QWidget): +class NextStepsPanel(QDialog): """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) + super().__init__(parent) self.setWindowTitle("Possible next steps") + self.setModal(False) self.setMinimumWidth(450) self.setMaximumWidth(600) self.setMinimumHeight(360) @@ -99,6 +99,36 @@ def __init__(self, parent=None): suggestions_layout = QVBoxLayout() suggestions_layout.setSpacing(8) + # Benchmark Collection action + benchmark_frame = QFrame() + benchmark_frame.setStyleSheet( + "QFrame { background-color: rgba(255, 193, 7, 0.08); " + "border-radius: 4px; padding: 8px; }" + ) + benchmark_layout = QVBoxLayout(benchmark_frame) + benchmark_layout.setContentsMargins(8, 8, 8, 8) + benchmark_layout.setSpacing(4) + + benchmark_text = QTextBrowser() + benchmark_text.setOpenExternalLinks(True) + benchmark_text.setFrameStyle(QFrame.NoFrame) + benchmark_text.setStyleSheet( + "QTextBrowser { background: transparent; }" + ) + benchmark_text.setVerticalScrollBarPolicy( + Qt.ScrollBarPolicy.ScrollBarAlwaysOff + ) + benchmark_text.setHtml( + '

' + "📚 Contribute to Benchmark Collection
" + "Share your PEtab problem with the community to validate it, " + "enable reproducibility, and support benchmarking
" + 'Benchmark Collection

' + ) + benchmark_layout.addWidget(benchmark_text) + suggestions_layout.addWidget(benchmark_frame) + # pyPESTO action pypesto_frame = QFrame() pypesto_frame.setStyleSheet( @@ -118,9 +148,9 @@ def __init__(self, parent=None): ) pypesto_text.setHtml( '

' - "▶ Parameter estimation
" - "Use pyPESTO for parameter estimation and uncertainty " - "analysis
" + "▶ Parameter Estimation with pyPESTO
" + "Use pyPESTO for parameter estimation, uncertainty analysis, " + "and model selection
" 'pyPESTO documentation

' ) @@ -146,9 +176,9 @@ def __init__(self, parent=None): ) copasi_text.setHtml( '

' - "⚙ Model adjustment and simulation
" + "⚙ Advanced Model Adaptation and Simulation
" "Use COPASI for further model adjustment and advanced " - "simulation
" + "simulation with a graphical interface
" 'COPASI website

' ) copasi_layout.addWidget(copasi_text) @@ -202,14 +232,15 @@ def __init__(self, parent=None): '
  • ' 'AMICI - ' - "Advanced simulation and sensitivity analysis
  • " + "Efficient simulation and sensitivity analysis" + '
  • ' + '' + "PEtab.jl - " + "High-performance Julia parameter estimation
  • " '
  • ' '' "Data2Dynamics - " - "Comprehensive modeling environment
  • " - '
  • ' - 'parPE - ' - "Parallel parameter estimation
  • " + "MATLAB-based comprehensive modeling framework" '
  • ' 'PEtab documentation - ' @@ -237,10 +268,8 @@ def __init__(self, parent=None): 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 - ) + self._dont_show_checkbox.toggled.connect( + self.dont_show_again_changed.emit ) bottom_layout.addWidget(self._dont_show_checkbox) @@ -265,6 +294,10 @@ def _toggle_other_tools(self, checked): # 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(): From c3c37ae99530011750fab5d7fdcdde7ab083b963 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 20 Jan 2026 17:20:44 +0100 Subject: [PATCH 3/5] Unified cards in a function, added some description to functions --- .../controllers/mother_controller.py | 10 +- src/petab_gui/views/other_views.py | 184 ++++++++++-------- 2 files changed, 111 insertions(+), 83 deletions(-) diff --git a/src/petab_gui/controllers/mother_controller.py b/src/petab_gui/controllers/mother_controller.py index d480d46..98351c9 100644 --- a/src/petab_gui/controllers/mother_controller.py +++ b/src/petab_gui/controllers/mother_controller.py @@ -600,7 +600,6 @@ def save_model(self): "next_steps/dont_show_again", False, bool ) if not dont_show: - self.next_steps_panel.set_dont_show_again(dont_show) self.next_steps_panel.show_panel() return True @@ -1445,7 +1444,14 @@ def _show_next_steps_panel(self): 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.""" + """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): diff --git a/src/petab_gui/views/other_views.py b/src/petab_gui/views/other_views.py index a33c41d..712e137 100644 --- a/src/petab_gui/views/other_views.py +++ b/src/petab_gui/views/other_views.py @@ -75,18 +75,38 @@ class NextStepsPanel(QDialog): 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(450) - self.setMaximumWidth(600) - self.setMinimumHeight(360) + self.setMinimumWidth(self.MIN_WIDTH) + self.setMaximumWidth(self.MAX_WIDTH) + self.setMinimumHeight(self.MIN_HEIGHT) # Main layout main_layout = QVBoxLayout(self) - main_layout.setContentsMargins(12, 12, 12, 12) - main_layout.setSpacing(10) + main_layout.setContentsMargins( + self.LAYOUT_MARGIN, + self.LAYOUT_MARGIN, + self.LAYOUT_MARGIN, + self.LAYOUT_MARGIN, + ) + main_layout.setSpacing(self.LAYOUT_SPACING) # Description desc = QLabel( @@ -100,88 +120,44 @@ def __init__(self, parent=None): suggestions_layout.setSpacing(8) # Benchmark Collection action - benchmark_frame = QFrame() - benchmark_frame.setStyleSheet( - "QFrame { background-color: rgba(255, 193, 7, 0.08); " - "border-radius: 4px; padding: 8px; }" - ) - benchmark_layout = QVBoxLayout(benchmark_frame) - benchmark_layout.setContentsMargins(8, 8, 8, 8) - benchmark_layout.setSpacing(4) - - benchmark_text = QTextBrowser() - benchmark_text.setOpenExternalLinks(True) - benchmark_text.setFrameStyle(QFrame.NoFrame) - benchmark_text.setStyleSheet( - "QTextBrowser { background: transparent; }" + benchmark_frame = self._create_tool_card( + bg_color=self.COLOR_BENCHMARK, + html_content=( + '

    ' + "📚 Contribute to Benchmark Collection
    " + "Share your PEtab problem with the community to validate it, " + "enable reproducibility, and support benchmarking
    " + 'Benchmark Collection

    ' + ), ) - benchmark_text.setVerticalScrollBarPolicy( - Qt.ScrollBarPolicy.ScrollBarAlwaysOff - ) - benchmark_text.setHtml( - '

    ' - "📚 Contribute to Benchmark Collection
    " - "Share your PEtab problem with the community to validate it, " - "enable reproducibility, and support benchmarking
    " - 'Benchmark Collection

    ' - ) - benchmark_layout.addWidget(benchmark_text) suggestions_layout.addWidget(benchmark_frame) # 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_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

    ' + ), ) - pypesto_text.setHtml( - '

    ' - "▶ Parameter Estimation with pyPESTO
    " - "Use pyPESTO for parameter estimation, uncertainty analysis, " - "and model selection
    " - '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_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

    ' + ), ) - copasi_text.setHtml( - '

    ' - "⚙ Advanced Model Adaptation and Simulation
    " - "Use COPASI for further model adjustment and advanced " - "simulation with a graphical interface
    " - 'COPASI website

    ' - ) - copasi_layout.addWidget(copasi_text) suggestions_layout.addWidget(copasi_frame) main_layout.addLayout(suggestions_layout) @@ -203,12 +179,18 @@ def __init__(self, parent=None): # 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; }" + 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(8, 8, 8, 8) + other_tools_layout.setContentsMargins( + self.FRAME_PADDING, + self.FRAME_PADDING, + self.FRAME_PADDING, + self.FRAME_PADDING, + ) other_tools_layout.setSpacing(4) # Framing text @@ -282,6 +264,46 @@ def __init__(self, parent=None): 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) From 1ce383e5769c3488b0ade8d6e01dee5a2ce136e4 Mon Sep 17 00:00:00 2001 From: PaulJonasJost Date: Tue, 20 Jan 2026 17:33:49 +0100 Subject: [PATCH 4/5] Slight change to wording of benchmark --- src/petab_gui/views/other_views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/petab_gui/views/other_views.py b/src/petab_gui/views/other_views.py index 712e137..746dfb3 100644 --- a/src/petab_gui/views/other_views.py +++ b/src/petab_gui/views/other_views.py @@ -125,8 +125,9 @@ def __init__(self, parent=None): html_content=( '

    ' "📚 Contribute to Benchmark Collection
    " - "Share your PEtab problem with the community to validate it, " - "enable reproducibility, and support benchmarking
    " + "Share your publsihed PEtab problem with the community to " + "validate it, enable reproducibility, and support " + "benchmarking
    " 'Benchmark Collection

    ' ), From 2fd09fb80d8772387d8c8c4510311144501c28a4 Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Tue, 20 Jan 2026 22:42:13 +0100 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Daniel Weindl --- src/petab_gui/views/other_views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/petab_gui/views/other_views.py b/src/petab_gui/views/other_views.py index 746dfb3..f75561f 100644 --- a/src/petab_gui/views/other_views.py +++ b/src/petab_gui/views/other_views.py @@ -127,7 +127,7 @@ def __init__(self, parent=None): "📚 Contribute to Benchmark Collection
    " "Share your publsihed PEtab problem with the community to " "validate it, enable reproducibility, and support " - "benchmarking
    " + "benchmarking.
    " 'Benchmark Collection

    ' ), @@ -141,7 +141,7 @@ def __init__(self, parent=None): '

    ' "▶ Parameter Estimation with pyPESTO
    " "Use pyPESTO for parameter estimation, uncertainty analysis, " - "and model selection
    " + "and model selection.
    " 'pyPESTO documentation

    ' ), @@ -155,7 +155,7 @@ def __init__(self, parent=None): '

    ' "⚙ Advanced Model Adaptation and Simulation
    " "Use COPASI for further model adjustment and advanced " - "simulation with a graphical interface
    " + "simulation with a graphical interface.
    " 'COPASI website

    ' ), )