from typing import Callable, Optional, Any from PyQt5.QtWidgets import (QWidget, QPushButton, QLineEdit, QHBoxLayout, QVBoxLayout, QLabel, QTableWidget, QTableWidgetItem, QStyledItemDelegate) from PyQt5.QtGui import QIntValidator, QDoubleValidator from utils.json_tools import read_json, write_json from utils import qt_settings as qts class settingsWindow(QWidget): def __init__(self, path: str, name: str, upd_func: Callable[[], None]): """ Окно настроек для редактирования параметров. :param path: Путь к файлу настроек (JSON). :param name: Название набора настроек. :param upd_func: Функция обновления (коллбэк). """ super().__init__() self._settingsPath = path self._name = name self._data: dict[str, list[Any]] = {} self._upd_func = upd_func self._num_points: Optional[QLineEdit] = None self._param_table: Optional[QTableWidget] = None self.load_settings() self._init_ui() def load_settings(self) -> None: """Загружает настройки из JSON-файла.""" data = read_json(self._settingsPath) if isinstance(data, dict): self._data = data else: self._data = {} def write_settings(self) -> None: """Записывает текущие настройки в JSON-файл.""" write_json(self._settingsPath, self._data) def getParams(self) -> dict: """Возвращает текущий словарь параметров.""" return self._data def _init_ui(self) -> None: """Инициализирует UI: кнопки, поля ввода, таблицу.""" save_button = QPushButton("Save") restore_button = QPushButton("Restore") self._num_points = QLineEdit() self._num_points.setPlaceholderText("Enter the number of welding points") self._num_points.setValidator(QIntValidator()) control_layout = QHBoxLayout() control_layout.addWidget(save_button) control_layout.addWidget(restore_button) control_layout.addWidget(self._num_points) save_button.pressed.connect(self._save) restore_button.pressed.connect(self._restore) self._num_points.editingFinished.connect(self._expand) self._param_table = QTableWidget() self._populate_table() layout = QVBoxLayout() header = QLabel(self._name) layout.addWidget(header) layout.addLayout(control_layout) layout.addWidget(self._param_table) self.setLayout(layout) self.setStyleSheet(qts.dark_style) def _populate_table(self) -> None: """Заполняет таблицу значениями из self._data.""" # Если нет данных для заполнения if not self._data: self._param_table.setRowCount(0) self._param_table.setColumnCount(0) return # Предполагаем, что у всех ключей одинаковая длина списков параметров. first_key = next(iter(self._data), None) if first_key is None: self._param_table.setRowCount(0) self._param_table.setColumnCount(0) return column_count = len(self._data[first_key]) self._param_table.setRowCount(len(self._data)) self._param_table.setColumnCount(column_count) self._param_table.setVerticalHeaderLabels(self._data.keys()) int_delegate = ValidatorDelegate(data_type='int', parent=self._param_table) float_delegate = ValidatorDelegate(data_type='float', parent=self._param_table) str_delegate = ValidatorDelegate(data_type='str', parent=self._param_table) for i, (_, items) in enumerate(self._data.items()): for j, item in enumerate(items): self._param_table.setItem(i, j, QTableWidgetItem(str(item))) if type(item) == int: self._param_table.setItemDelegateForRow(i, int_delegate) elif type(item) == float: self._param_table.setItemDelegateForRow(i, float_delegate) else: self._param_table.setItemDelegateForRow(i, str_delegate) def _save(self) -> None: """Сохраняет текущие параметры из таблицы в self._data и вызывает _upd_func().""" new_data = {} col_count = self._param_table.columnCount() for i, key in enumerate(self._data.keys()): row_data = [] for j in range(col_count): cell_item = self._param_table.item(i, j) if cell_item is None: continue param_str = cell_item.text() # Если ключ не trace_storage_path, конвертируем в float if key != "trace_storage_path": try: param = float(param_str) except ValueError: param = 0.0 else: param = param_str row_data.append(param) new_data[key] = row_data self._data = new_data self.write_settings() self._upd_func() def _restore(self) -> None: """Перезагружает данные из файла и обновляет таблицу.""" self.load_settings() self._populate_table() def _expand(self) -> None: """Расширяет количество столбцов таблицы в зависимости от введённого значения.""" if not self._num_points: return num_points_text = self._num_points.text() if not num_points_text.isdigit(): return num_points = int(num_points_text) if num_points < 0: return prev_columns = self._param_table.columnCount() desired_columns = num_points if desired_columns <= 0: return self._param_table.setColumnCount(desired_columns) # Новые столбцы заполняем последним известным параметром для каждого ключа for i, (key, items) in enumerate(self._data.items()): # Если нет данных, пропускаем if not items: continue last_value = str(items[-1]) for col in range(prev_columns, desired_columns): self._param_table.setItem(i, col, QTableWidgetItem(last_value)) # Добавляем новый элемент также в self._data для консистентности # После этого можно будет сохранить при нажатии Save # Дополним также и в self._data additional_count = desired_columns - prev_columns self._data[key].extend([float(last_value) if key != "trace_storage_path" else last_value] * additional_count) class SystemSettings(settingsWindow): def __init__(self, path, name, upd_func): super().__init__(path, name, upd_func) self._num_points.setVisible(False) def _expand(self): pass class OperatorSettings(settingsWindow): pass class ValidatorDelegate(QStyledItemDelegate): def __init__(self, data_type='str', parent=None): super().__init__(parent) self.data_type = data_type if self.data_type == 'int': self.validator = QIntValidator() elif self.data_type == 'float': self.validator = QDoubleValidator() self.validator.setNotation(QDoubleValidator.StandardNotation) else: self.validator = None def createEditor(self, parent, option, index): editor = QLineEdit(parent) if self.validator: editor.setValidator(self.validator) return editor if __name__ == '__main__': import pyqtgraph as pg app = pg.mkQApp("Parameter Tree Example") window = settingsWindow('params\operator_params.json', 'operator') app.exec()