from pathlib import Path from PySide6.QtCore import QSettings, Qt, Signal from PySide6.QtGui import QColor from PySide6.QtWidgets import QColorDialog, QDialog, QFileDialog, QHeaderView, QTableWidgetItem from bedit.core.libraries import bundled_library_path, default_library_paths from bedit.gui.preferences import application_settings from bedit.gui.generated.ui_settings_dialog import Ui_SettingsDialog from bedit.gui.editors.openmodelica import HIGHLIGHT_STYLES class SettingsDialog(QDialog): """Edit application preferences defined in the Designer form.""" settingsChanged = Signal() def __init__(self, parent=None) -> None: super().__init__(parent) self.ui = Ui_SettingsDialog() self.ui.setupUi(self) self.ui.graphGridSpinBox.valueChanged.connect( self.ui.graphSnapSpinBox.setMaximum ) self.settings = application_settings() self.ui.addLibraryFileButton.clicked.connect(self._add_library_file) self.ui.addLibraryFolderButton.clicked.connect(self._add_library_folder) self.ui.removeLibraryPathButton.clicked.connect(self._remove_library_path) self.ui.browseOpenModelicaButton.clicked.connect(self._browse_openmodelica) self.ui.libraryPathsList.itemSelectionChanged.connect(self._update_remove_button) self.ui.syntaxStylesTable.cellDoubleClicked.connect(self._choose_syntax_color) self._load_settings() def _load_settings(self) -> None: enabled = self.settings.value("autosave/enabled", None) if enabled is None: enabled = self.settings.value("General/autosaveEnabled", False) interval = self.settings.value("autosave/intervalMinutes", None) if interval is None: interval = self.settings.value("General/autosaveInterval", 5) self.ui.autosaveGroupBox.setChecked( self._as_bool(enabled) ) self.ui.autosaveIntervalSpinBox.setValue(int(interval)) self.ui.libraryPathsList.clear() self.ui.libraryPathsList.addItems(self.library_paths(self.settings)) self.ui.graphGridSpinBox.setValue(self.graph_grid_size(self.settings)) self.ui.graphSnapSpinBox.setValue(self.graph_snap_size(self.settings)) self.ui.iconGridSpinBox.setValue(self.icon_grid_size(self.settings)) self.ui.automaticCausalityCheckBox.setChecked( self.infer_causality_on_connection_change(self.settings) ) self.ui.openModelicaPathEdit.setText(self.openmodelica_path(self.settings)) self._load_syntax_styles() self._update_remove_button() def _load_syntax_styles(self) -> None: table = self.ui.syntaxStylesTable table.setRowCount(len(HIGHLIGHT_STYLES)) table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) for row, (category, defaults) in enumerate(HIGHLIGHT_STYLES.items()): label, default_color, default_bold, default_italic = defaults name_item = QTableWidgetItem(label) name_item.setData(Qt.ItemDataRole.UserRole, category) name_item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable) table.setItem(row, 0, name_item) color = str(self.settings.value(f"syntax/{category}/color", default_color)) color_item = QTableWidgetItem(color) color_item.setBackground(QColor(color)) color_item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable) table.setItem(row, 1, color_item) for column, key, default in ( (2, "bold", default_bold), (3, "italic", default_italic), ): item = QTableWidgetItem() item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) enabled = self._as_bool( self.settings.value(f"syntax/{category}/{key}", default) ) item.setCheckState( Qt.CheckState.Checked if enabled else Qt.CheckState.Unchecked ) table.setItem(row, column, item) def _choose_syntax_color(self, row: int, column: int) -> None: if column != 1: return item = self.ui.syntaxStylesTable.item(row, column) color = QColorDialog.getColor(QColor(item.text()), self, "Highlight colour") if color.isValid(): item.setText(color.name()) item.setBackground(color) @staticmethod def _as_bool(value) -> bool: if isinstance(value, str): return value.strip().lower() in {"1", "true", "yes", "on"} return bool(value) @staticmethod def library_paths(settings: QSettings | None = None) -> list[str]: settings = settings if settings is not None else application_settings() value = settings.value("libraries/paths", default_library_paths()) if isinstance(value, str): paths = [value] else: paths = [str(path) for path in value] bundled = bundled_library_path() migrated = [ str(bundled) if not Path(path).exists() and Path(path).parent == bundled.parent and Path(path).suffix.lower() == ".json" else path for path in paths ] if migrated != paths: settings.setValue("libraries/paths", migrated) settings.sync() return migrated @staticmethod def graph_grid_size(settings: QSettings | None = None) -> int: settings = settings if settings is not None else application_settings() return settings.value("grid/graphSize", 64, type=int) @staticmethod def graph_snap_size(settings: QSettings | None = None) -> int: settings = settings if settings is not None else application_settings() return settings.value("grid/graphSnapSize", 8, type=int) @staticmethod def icon_grid_size(settings: QSettings | None = None) -> int: settings = settings if settings is not None else application_settings() return settings.value("grid/iconSize", 8, type=int) @staticmethod def infer_causality_on_connection_change( settings: QSettings | None = None, ) -> bool: settings = settings if settings is not None else application_settings() return settings.value( "bondGraph/inferCausalityOnConnectionChange", True, type=bool ) @staticmethod def openmodelica_path(settings: QSettings | None = None) -> str: settings = settings if settings is not None else application_settings() return str(settings.value("simulation/openModelicaPath", "") or "") def _browse_openmodelica(self) -> None: current = self.ui.openModelicaPathEdit.text().strip() start = str(Path(current).expanduser().parent) if current else "" path, _ = QFileDialog.getOpenFileName( self, "Select OpenModelica executable", start, "OpenModelica compiler (omc omc.exe);;All files (*)", ) if path: # Keep symlink paths such as ~/.local/bin/omc intact; resolving them # could turn the selected launcher into an unrelated container script. self.ui.openModelicaPathEdit.setText(str(Path(path).expanduser().absolute())) def _add_library_file(self) -> None: path, _ = QFileDialog.getOpenFileName( self, "Add library", "", "BEdit libraries (*.beb *.bedit.json *.json);;All files (*)", ) if path: self._append_unique_path(path) def _add_library_folder(self) -> None: path = QFileDialog.getExistingDirectory(self, "Add library folder") if path: self._append_unique_path(path) def _append_unique_path(self, path: str) -> None: normalized = str(Path(path).expanduser().resolve()) existing = { self.ui.libraryPathsList.item(row).text() for row in range(self.ui.libraryPathsList.count()) } if normalized not in existing: self.ui.libraryPathsList.addItem(normalized) def _remove_library_path(self) -> None: for item in self.ui.libraryPathsList.selectedItems(): self.ui.libraryPathsList.takeItem(self.ui.libraryPathsList.row(item)) def _update_remove_button(self) -> None: self.ui.removeLibraryPathButton.setEnabled(bool(self.ui.libraryPathsList.selectedItems())) def accept(self) -> None: self.settings.setValue("autosave/enabled", self.ui.autosaveGroupBox.isChecked()) self.settings.setValue( "autosave/intervalMinutes", self.ui.autosaveIntervalSpinBox.value() ) self.settings.remove("General/autosaveEnabled") self.settings.remove("General/autosaveInterval") paths = [ self.ui.libraryPathsList.item(row).text() for row in range(self.ui.libraryPathsList.count()) ] self.settings.setValue("libraries/paths", paths) self.settings.setValue("grid/graphSize", self.ui.graphGridSpinBox.value()) self.settings.setValue("grid/graphSnapSize", self.ui.graphSnapSpinBox.value()) self.settings.setValue("grid/iconSize", self.ui.iconGridSpinBox.value()) self.settings.setValue( "bondGraph/inferCausalityOnConnectionChange", self.ui.automaticCausalityCheckBox.isChecked(), ) self.settings.setValue( "simulation/openModelicaPath", self.ui.openModelicaPathEdit.text().strip(), ) for row in range(self.ui.syntaxStylesTable.rowCount()): category = self.ui.syntaxStylesTable.item(row, 0).data( Qt.ItemDataRole.UserRole ) self.settings.setValue( f"syntax/{category}/color", self.ui.syntaxStylesTable.item(row, 1).text(), ) self.settings.setValue( f"syntax/{category}/bold", self.ui.syntaxStylesTable.item(row, 2).checkState() == Qt.CheckState.Checked, ) self.settings.setValue( f"syntax/{category}/italic", self.ui.syntaxStylesTable.item(row, 3).checkState() == Qt.CheckState.Checked, ) self.settings.sync() self.settingsChanged.emit() super().accept()