From 6496ec6824da33fa41a38e26fdcac9a03ceb51fb Mon Sep 17 00:00:00 2001 From: jupfi Date: Fri, 24 May 2024 17:28:57 +0200 Subject: [PATCH] Implemented loading and saving of settings. --- .../base_spectrometer_controller.py | 42 ++++++++++++ .../base_spectrometer_model.py | 2 + .../base_spectrometer_view.py | 43 ++++++++++++- src/nqrduck_spectrometer/settings.py | 64 ++++++++++++++----- 4 files changed, 133 insertions(+), 18 deletions(-) diff --git a/src/nqrduck_spectrometer/base_spectrometer_controller.py b/src/nqrduck_spectrometer/base_spectrometer_controller.py index a96e368..6eb0f9a 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_controller.py +++ b/src/nqrduck_spectrometer/base_spectrometer_controller.py @@ -1,5 +1,6 @@ """Base class for all spectrometer controllers.""" +import ast from nqrduck.module.module_controller import ModuleController @@ -10,6 +11,47 @@ class BaseSpectrometerController(ModuleController): """Initializes the spectrometer controller.""" super().__init__(module) + def save_settings(self, path: str) -> None: + """Saves the settings of the spectrometer.""" + # We get the different settings objects from the model + settings = self.module.model.settings + + json = {} + json["name"] = self.module.model.name + + for category in settings.keys(): + for setting in settings[category]: + json[setting.name] = setting.value + + with open(path, "w") as f: + f.write(str(json)) + + def load_settings(self, path: str) -> None: + """Loads the settings of the spectrometer.""" + with open(path, "r") as f: + json = f.read() + + # string to dict + json = ast.literal_eval(json) + + module_name = self.module.model.name + json_name = json["name"] + + # For some reason the notification is shown twice + if module_name != json_name: + message = f"Module: {module_name} not compatible with module specified in settings file: {json_name}. Did you select the correct settings file?" + self.module.nqrduck_signal.emit("notification", ["Error", message]) + return + + settings = self.module.model.settings + for category in settings.keys(): + for setting in settings[category]: + if setting.name in json: + setting.value = json[setting.name] + else: + message = f"Setting {setting.name} not found in settings file. A change in settings might have broken compatibility." + self.module.nqrduck_signal.emit("notification", ["Error", message]) + def start_measurement(self): """Starts the measurement. diff --git a/src/nqrduck_spectrometer/base_spectrometer_model.py b/src/nqrduck_spectrometer/base_spectrometer_model.py index 8e5e1dd..688ddee 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_model.py +++ b/src/nqrduck_spectrometer/base_spectrometer_model.py @@ -22,6 +22,8 @@ class BaseSpectrometerModel(ModuleModel): pulse_parameter_options (OrderedDict) : The pulse parameter options of the spectrometer """ + SETTING_FILE_EXTENSION = "setduck" + settings: OrderedDict pulse_parameter_options: OrderedDict diff --git a/src/nqrduck_spectrometer/base_spectrometer_view.py b/src/nqrduck_spectrometer/base_spectrometer_view.py index 1b53600..e09b3bc 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_view.py +++ b/src/nqrduck_spectrometer/base_spectrometer_view.py @@ -8,6 +8,7 @@ from PyQt6.QtWidgets import ( QSizePolicy, QSpacerItem, QVBoxLayout, + QPushButton, ) from nqrduck.module.module_view import ModuleView from nqrduck.assets.icons import Logos @@ -82,7 +83,7 @@ class BaseSpectrometerView(ModuleView): layout.addWidget(edit_widget) layout.addStretch(1) layout.addWidget(icon_label) - + # Add the layout to the vertical layout of the widget category_layout.addLayout(layout) @@ -91,3 +92,43 @@ class BaseSpectrometerView(ModuleView): # Push all the settings to the top of the widget self._ui_form.verticalLayout.addStretch(1) + + # Now we add a save and load button to the widget + self.button_layout = QHBoxLayout() + self.save_button = QPushButton("Save Settings") + self.save_button.setIcon(Logos.Save16x16()) + #self.save_button.setIconSize(self.save_button.size()) + self.save_button.clicked.connect(self.on_save_button_clicked) + self.button_layout.addWidget(self.save_button) + + self.load_button = QPushButton("Load Settings") + self.load_button.setIcon(Logos.Load16x16()) + #self.load_button.setIconSize(self.load_button.size()) + self.load_button.clicked.connect(self.on_load_button_clicked) + self.button_layout.addWidget(self.load_button) + + self._ui_form.verticalLayout.addLayout(self.button_layout) + + + def on_save_button_clicked(self): + """This method is called when the save button is clicked.""" + logger.debug("Save button clicked") + # Open a dialog to save the settings to a file + file_manager = self.FileManager( + extension=self.module.model.SETTING_FILE_EXTENSION, parent=self + ) + path = file_manager.saveFileDialog() + if path: + self.module.controller.save_settings(path) + + def on_load_button_clicked(self): + """This method is called when the load button is clicked.""" + logger.debug("Load button clicked") + # Open a dialog to load the settings from a file + file_manager = self.FileManager( + extension=self.module.model.SETTING_FILE_EXTENSION, parent=self + ) + path = file_manager.loadFileDialog() + self.module.controller.load_settings(path) + if path: + self.module.controller.load_settings(path) diff --git a/src/nqrduck_spectrometer/settings.py b/src/nqrduck_spectrometer/settings.py index 7187f1d..4f39529 100644 --- a/src/nqrduck_spectrometer/settings.py +++ b/src/nqrduck_spectrometer/settings.py @@ -36,6 +36,7 @@ class Setting(QObject): description (str): A description of the setting. default: The default value of the setting. """ + self.widget = None super().__init__() self.name = name self.description = description @@ -81,34 +82,41 @@ class Setting(QObject): lambda x=widget, s=self: s.on_value_changed(x.text()) ) return widget - + class NumericalSetting(Setting): """A setting that is a numerical value. It can additionally have a minimum and maximum value. """ - def __init__(self, name: str, description: str, default, min_value = None, max_value = None ) -> None: + + def __init__( + self, name: str, description: str, default, min_value=None, max_value=None + ) -> None: """Create a new numerical setting.""" - super().__init__(name, self.description_limit_info(description, min_value, max_value), default) + super().__init__( + name, + self.description_limit_info(description, min_value, max_value), + default, + ) def description_limit_info(self, description: str, min_value, max_value) -> str: """Updates the description with the limits of the setting if there are any. - + Args: description (str): The description of the setting. min_value: The minimum value of the setting. max_value: The maximum value of the setting. - + Returns: str: The description of the setting with the limits. """ if min_value is not None and max_value is not None: - description += (f"\n (min: {min_value}, max: {max_value})") + description += f"\n (min: {min_value}, max: {max_value})" elif min_value is not None: - description += (f"\n (min: {min_value})") + description += f"\n (min: {min_value})" elif max_value is not None: - description += (f"\n (max: {max_value})") - + description += f"\n (max: {max_value})" + return description @@ -121,7 +129,7 @@ class FloatSetting(NumericalSetting): description (str) : A description of the setting min_value : The minimum value of the setting max_value : The maximum value of the setting - spin_box : A tuple with two booleans that determine if a spin box is used if the second value is True, a slider will be created as well. + spin_box : A tuple with two booleans that determine if a spin box is used if the second value is True, a slider will be created as well. """ DEFAULT_LENGTH = 100 @@ -133,13 +141,19 @@ class FloatSetting(NumericalSetting): description: str, min_value: float = None, max_value: float = None, - spin_box: tuple = (False, False) + spin_box: tuple = (False, False), ) -> None: """Create a new float setting.""" + self.spin_box = spin_box super().__init__(name, description, default, min_value, max_value) if spin_box[0]: - self.widget = DuckSpinBox(min_value=min_value, max_value=max_value, slider=spin_box[1], double_box=True) + self.widget = DuckSpinBox( + min_value=min_value, + max_value=max_value, + slider=spin_box[1], + double_box=True, + ) self.widget.spin_box.setValue(default) else: self.widget = DuckFloatEdit(min_value=min_value, max_value=max_value) @@ -169,6 +183,12 @@ class FloatSetting(NumericalSetting): self._value = float(value) self.settings_changed.emit() + if self.widget: + if self.spin_box[0]: + self.widget.spin_box.setValue(self._value) + else: + self.widget.setText(str(self._value)) + class IntSetting(NumericalSetting): """A setting that is an Integer. @@ -189,13 +209,15 @@ class IntSetting(NumericalSetting): description: str, min_value=None, max_value=None, - spin_box: tuple = (False, False) + spin_box: tuple = (False, False), ) -> None: """Create a new int setting.""" + self.spin_box = spin_box super().__init__(name, description, default, min_value, max_value) - - if spin_box[0]: - self.widget = DuckSpinBox(min_value=min_value, max_value=max_value, slider=spin_box[1]) + if self.spin_box[0]: + self.widget = DuckSpinBox( + min_value=min_value, max_value=max_value, slider=spin_box[1] + ) self.widget.spin_box.setValue(default) else: self.widget = DuckIntEdit(min_value=min_value, max_value=max_value) @@ -225,7 +247,11 @@ class IntSetting(NumericalSetting): value = int(float(value)) self._value = value self.settings_changed.emit() - + if self.widget: + if self.spin_box[0]: + self.widget.spin_box.setValue(value) + else: + self.widget.setText(str(value)) class BooleanSetting(Setting): @@ -253,6 +279,8 @@ class BooleanSetting(Setting): def value(self, value): try: self._value = bool(value) + if self.widget: + self.widget.setChecked(self._value) self.settings_changed.emit() except ValueError: raise ValueError("Value must be a bool") @@ -307,6 +335,8 @@ class SelectionSetting(Setting): try: if value in self.options: self._value = value + if self.widget: + self.widget.setCurrentText(value) self.settings_changed.emit() else: raise ValueError("Value must be one of the options")