Source code for pySimBlocks.gui.widgets.toolbar_view
# ******************************************************************************
# pySimBlocks
# Copyright (c) 2026 Université de Lille & INRIA
# ******************************************************************************
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# ******************************************************************************
# Authors: see Authors.txt
# ******************************************************************************
from __future__ import annotations
from PySide6.QtWidgets import QToolBar, QMessageBox, QProgressDialog, QApplication
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt
from pySimBlocks.gui.dialogs.display_yaml_dialog import DisplayYamlDialog
from pySimBlocks.gui.dialogs.plot_dialog import PlotDialog
from pySimBlocks.gui.dialogs.settings_dialog import SettingsDialog
from pySimBlocks.gui.project_controller import ProjectController
from pySimBlocks.gui.services.project_saver import ProjectSaver
from pySimBlocks.gui.services.simulation_runner import SimulationRunner
# Add ons
from pySimBlocks.gui.addons.sofa.sofa_dialog import SofaDialog
from pySimBlocks.gui.addons.sofa.sofa_service import SofaService
[docs]
class ToolBarView(QToolBar):
"""Application toolbar providing save, run, plot, and add-on actions.
Attributes:
saver: Service used to persist the project to disk.
runner: Service used to launch a simulation.
project_controller: Controller coordinating model and view mutations.
sofa_service: Service managing SOFA-specific operations.
sofa_action: Toolbar action for opening the SOFA dialog.
"""
def __init__(
self,
saver: ProjectSaver,
runner: SimulationRunner,
project_controller: ProjectController,
):
"""Initialize the ToolBarView and register all toolbar actions.
Args:
saver: Service used to save the project to YAML.
runner: Service used to launch a simulation.
project_controller: Controller coordinating model and view mutations.
Raises:
None.
"""
super().__init__()
self.saver = saver
self.runner = runner
self.project_controller = project_controller
save_action = QAction("Save", self)
save_action.triggered.connect(self.on_save)
self.addAction(save_action)
export_action = QAction("Export", self)
export_action.triggered.connect(self.on_export_project)
self.addAction(export_action)
display_action = QAction("Project YAML", self)
display_action.triggered.connect(self.on_open_display_yaml)
self.addAction(display_action)
sim_settings_action = QAction("Settings", self)
sim_settings_action.triggered.connect(self.on_open_simulation_settings)
self.addAction(sim_settings_action)
run_action = QAction("Run", self)
run_action.triggered.connect(self.on_run_sim)
self.addAction(run_action)
plot_action = QAction("Plot", self)
plot_action.triggered.connect(self.on_plot_logs)
self.addAction(plot_action)
# add ons
self.sofa_service = SofaService(self.project_controller.project_state, self.project_controller)
self.sofa_action = QAction("Sofa", self)
self.sofa_action.triggered.connect(self.on_open_sofa_dialog)
self.addAction(self.sofa_action)
# --------------------------------------------------------------------------
# Public Methods
# --------------------------------------------------------------------------
[docs]
def on_save(self) -> None:
"""Save the project and clear the dirty flag."""
self.saver.save(self.project_controller.project_state, self.project_controller.view.block_items)
self.project_controller.clear_dirty()
[docs]
def on_export_project(self) -> None:
"""Prompt to save if dirty, then export the project."""
window = self.parent()
if window.confirm_discard_or_save("exporting"):
self.saver.export(self.project_controller.project_state, self.project_controller.view.block_items)
[docs]
def on_open_display_yaml(self) -> None:
"""Open the YAML viewer dialog for the current project."""
dialog = DisplayYamlDialog(self.project_controller.project_state, self.project_controller.view)
dialog.exec()
[docs]
def on_open_simulation_settings(self) -> None:
"""Open the simulation settings dialog."""
dialog = SettingsDialog(self.project_controller.project_state, self.project_controller, self.parent())
dialog.exec()
[docs]
def on_run_sim(self) -> None:
"""Run the simulation with a busy progress dialog."""
dlg = QProgressDialog(self)
dlg.setWindowTitle("Simulation")
dlg.setLabelText("Running simulation...\nPlease wait.")
dlg.setRange(0, 0) # busy indicator
dlg.setCancelButton(None)
dlg.setWindowModality(Qt.ApplicationModal)
dlg.setMinimumWidth(300)
dlg.setMinimumHeight(120)
dlg.show()
QApplication.processEvents()
self.set_running(True)
logs, flag, msg = self.runner.run(self.project_controller.project_state)
dlg.close()
self.set_running(False)
self.project_controller.project_state.logs = logs
if not flag:
QMessageBox.warning(
self,
"Simulation failed with error",
msg,
QMessageBox.Ok,
)
[docs]
def on_plot_logs(self) -> None:
"""Open the plot dialog if simulation logs are available."""
flag, msg = self.project_controller.project_state.can_plot()
if not flag:
QMessageBox.warning(
self,
"Plot Error",
msg,
QMessageBox.Ok,
)
return
self._plot_dialog = PlotDialog(self.project_controller.project_state, self.parent()) # keep ref because of python garbage collector
self._plot_dialog.show()
[docs]
def set_running(self, running: bool) -> None:
"""Enable or disable all toolbar actions based on the running state.
Args:
running: True to disable all actions, False to re-enable them.
"""
for action in self.actions():
action.setEnabled(not running)
[docs]
def refresh_sofa_button(self) -> None:
"""Show or hide the SOFA toolbar button based on project contents."""
if self.project_controller.has_sofa_block():
if self.sofa_action not in self.actions():
self.addAction(self.sofa_action)
else:
if self.sofa_action in self.actions():
self.removeAction(self.sofa_action)
[docs]
def on_open_sofa_dialog(self) -> None:
"""Open the SOFA dialog if SOFA prerequisites are satisfied."""
ok, msg, details = self.sofa_service.can_use_sofa()
if not ok:
QMessageBox.warning(
self,
msg,
details,
QMessageBox.Ok
)
return
dialog = SofaDialog(self.sofa_service, self.parent())
dialog.exec()