Source code for pySimBlocks.gui.dialogs.settings.project

# ******************************************************************************
#                                  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 pathlib import Path
import os
from PySide6.QtWidgets import (
    QWidget, QFormLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QFileDialog, QHBoxLayout
)

from pySimBlocks.gui.models.project_state import ProjectState
from pySimBlocks.gui.project_controller import ProjectController
from pySimBlocks.gui.services.project_loader import ProjectLoaderYaml


[docs] class ProjectSettingsWidget(QWidget): """Edit project-level settings such as paths and external modules. Attributes: project_state: Project state edited by the widget. project_controller: Controller applying the edited settings. settings_dialog: Parent settings dialog coordinating tab refreshes. """ def __init__(self, project_state: ProjectState, project_controller: ProjectController, settings_dialg): """Initialize the project settings widget. Args: project_state: Project state edited by the widget. project_controller: Controller applying the edited settings. settings_dialg: Parent settings dialog coordinating refreshes. Raises: None. """ super().__init__() self.project_state = project_state self.project_controller = project_controller self.settings_dialog = settings_dialg layout = QFormLayout(self) layout.addRow(QLabel("<b>Project Settings</b>")) self.dir_edit = QLineEdit(str(project_state.directory_path)) self.dir_browse_btn = QPushButton("...") self.dir_browse_btn.setToolTip("Select a project directory from disk") self.dir_browse_btn.clicked.connect(self.browse_project_directory) dir_layout = QHBoxLayout() dir_layout.setContentsMargins(0, 0, 0, 0) dir_layout.addWidget(self.dir_edit) dir_layout.addWidget(self.dir_browse_btn) layout.addRow("Directory path:", dir_layout) load_btn = QPushButton("Load") load_btn.clicked.connect(self.load_project) label = QLabel("Load Project:") label.setToolTip("Auto load project from directory containing project.yaml.") layout.addRow(label, load_btn) ext = project_state.external or "" self.external_edit = QLineEdit(ext) self.external_browse_btn = QPushButton("...") self.external_browse_btn.setToolTip("Select a Python file from disk") self.external_browse_btn.clicked.connect(self.browse_external_file) external_layout = QHBoxLayout() external_layout.setContentsMargins(0, 0, 0, 0) external_layout.addWidget(self.external_edit) external_layout.addWidget(self.external_browse_btn) label = QLabel("Python file:") label.setToolTip("Relative path from project directory") layout.addRow(label, external_layout) # -------------------------------------------------------------------------- # Public Methods # --------------------------------------------------------------------------
[docs] def apply(self) -> bool: """Validate and apply the current project settings. Returns: True if the settings were applied successfully, otherwise False. """ path = Path(self.dir_edit.text()) if not path.exists(): QMessageBox.warning( self, "Invalid directory", f"The directory does not exist:\n{path}", ) return False ext = self.external_edit.text().strip() self.project_controller.update_project_param(path, ext) return True
[docs] def browse_external_file(self): """Select an external Python file relative to the project directory.""" base_dir = Path(self.dir_edit.text()).expanduser() if not base_dir.is_dir(): QMessageBox.warning( self, "Invalid directory", f"The directory does not exist:\n{base_dir}", ) return selected_file, _ = QFileDialog.getOpenFileName( self, "Select Python file", str(base_dir), "Python files (*.py);;All files (*)", ) if not selected_file: return selected_path = Path(selected_file).resolve() try: relative_path = selected_path.relative_to(base_dir.resolve()) except ValueError: try: relative_path = Path(os.path.relpath(str(selected_path), str(base_dir.resolve()))) except ValueError: # Windows cross-drive case (e.g. C: -> D:): keep absolute path. relative_path = selected_path self.external_edit.setText(relative_path.as_posix())
[docs] def browse_project_directory(self): """Select the project directory from the filesystem.""" current_dir = Path(self.dir_edit.text()).expanduser() start_dir = current_dir if current_dir.is_dir() else Path.cwd() selected_dir = QFileDialog.getExistingDirectory( self, "Select project directory", str(start_dir), ) if not selected_dir: return self.dir_edit.setText(str(Path(selected_dir).resolve()))
[docs] def load_project(self): """Load the project from the selected directory after confirmation.""" main_window = self.settings_dialog.parent() if not main_window.confirm_discard_or_save("loading a new project"): return self.apply() self.project_controller.load_project(ProjectLoaderYaml()) main_window.on_project_loaded(self.project_state.directory_path) ext = self.project_state.external self.external_edit.setText("" if ext is None else ext) self.settings_dialog.refresh_tabs_from_project()