diff --git a/src/nqrduck_spectrometer/base_spectrometer_model.py b/src/nqrduck_spectrometer/base_spectrometer_model.py index a5b6b9c..2273f89 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_model.py +++ b/src/nqrduck_spectrometer/base_spectrometer_model.py @@ -2,6 +2,7 @@ import logging from collections import OrderedDict from PyQt6.QtCore import pyqtSlot, pyqtSignal, QObject from nqrduck.module.module_model import ModuleModel +from.settings import Setting logger = logging.getLogger(__name__) @@ -18,35 +19,7 @@ class BaseSpectrometerModel(ModuleModel): """ settings : OrderedDict pulse_parameter_options : OrderedDict - - class Setting(QObject): - """A setting for the spectrometer is a value that is the same for all events in a pulse sequence. - E.g. the number of averages or the number of points in a spectrum. - """ - settings_changed = pyqtSignal() - - def __init__(self, name : str, default : str, description : str) -> None: - """Initializes the setting. - - Arguments: - name (str) -- The name of the setting - default (str) -- The default value of the setting - description (str) -- The description of the setting - """ - super().__init__() - self.name = name - self.value = default - self.description = description - - @pyqtSlot(str) - def on_value_changed(self, value): - logger.debug("Setting %s changed to %s", self.name, value) - self.value = value - self.settings_changed.emit() - - def get_setting(self): - return float(self.value) - + class PulseParameter: """A pulse parameter is a value that can be different for each event in a pulse sequence. E.g. the transmit pulse power or the phase of the transmit pulse. @@ -115,18 +88,11 @@ class BaseSpectrometerModel(ModuleModel): self.settings = OrderedDict() self.pulse_parameter_options = OrderedDict() - def add_setting(self, name : str, value: str, description : str, category : str) -> None: - """Adds a setting to the spectrometer. - - Arguments: - name (str) -- The name of the setting - value (str) -- The default value of the setting - description (str) -- The description of the setting - category (str) -- The category of the setting - """ + def add_setting(self, setting : Setting, category : str) -> None: + if category not in self.settings.keys(): self.settings[category] = [] - self.settings[category].append(self.Setting(name, value, description)) + self.settings[category].append(setting) def get_setting_by_name(self, name : str) -> Setting: """Gets a setting by its name. diff --git a/src/nqrduck_spectrometer/base_spectrometer_view.py b/src/nqrduck_spectrometer/base_spectrometer_view.py index 8b01738..3834345 100644 --- a/src/nqrduck_spectrometer/base_spectrometer_view.py +++ b/src/nqrduck_spectrometer/base_spectrometer_view.py @@ -1,10 +1,11 @@ import logging from pathlib import Path -from PyQt6.QtWidgets import QWidget, QLabel, QLineEdit, QHBoxLayout, QSizePolicy, QSpacerItem, QVBoxLayout +from PyQt6.QtWidgets import QWidget, QLabel, QLineEdit, QHBoxLayout, QSizePolicy, QSpacerItem, QVBoxLayout, QCheckBox, QComboBox from PyQt6.QtGui import QIcon from PyQt6.QtCore import Qt, pyqtSlot from nqrduck.module.module_view import ModuleView from nqrduck.assets.icons import Logos +from .settings import FloatSetting, IntSetting, BooleanSetting, SelectionSetting, StringSetting logger = logging.getLogger(__name__) @@ -46,18 +47,31 @@ class BaseSpectrometerView(ModuleView): category_layout.addWidget(category_label) for setting in self.module.model.settings[category]: logger.debug("Adding setting to settings view: %s", setting.name) - + spacer = QSpacerItem(20, 20) # Create a label for the setting setting_label = QLabel(setting.name) setting_label.setMinimumWidth(200) - # Add an QLineEdit for the setting - line_edit = QLineEdit(str(setting.value)) - line_edit.setMinimumWidth(100) - # Add a horizontal layout for the setting - layout = QHBoxLayout() - # Connect the editingFinished signal to the on_value_changed slot of the setting - line_edit.editingFinished.connect(lambda x=line_edit, s=setting: s.on_value_changed(x.text())) + + # Depending on the setting type we add different widgets to the view + if isinstance(setting, FloatSetting) or isinstance(setting, IntSetting) or isinstance(setting, StringSetting): + edit_widget = QLineEdit(str(setting.value)) + edit_widget.setMinimumWidth(100) + # Connect the editingFinished signal to the on_value_changed slot of the setting + edit_widget.editingFinished.connect(lambda x=edit_widget, s=setting: s.on_value_changed(x.text())) + + elif isinstance(setting, BooleanSetting): + edit_widget = QCheckBox() + edit_widget.setChecked(setting.value) + edit_widget.stateChanged.connect(lambda x=edit_widget, s=setting: s.on_value_changed(x)) + + elif isinstance(setting, SelectionSetting): + edit_widget = QComboBox() + edit_widget.addItems(setting.options) + edit_widget.setCurrentText(setting.value) + edit_widget.currentTextChanged.connect(lambda x=edit_widget, s=setting: s.on_value_changed(x)) + + # Add a icon that can be used as a tooltip if setting.description is not None: logger.debug("Adding tooltip to setting: %s", setting.name) @@ -68,11 +82,13 @@ class BaseSpectrometerView(ModuleView): icon_label.setFixedSize(icon.availableSizes()[0]) icon_label.setToolTip(setting.description) - + + # Add a horizontal layout for the setting + layout = QHBoxLayout() # Add the label and the line edit to the layout layout.addItem(spacer) layout.addWidget(setting_label) - layout.addWidget(line_edit) + layout.addWidget(edit_widget) layout.addWidget(icon_label) layout.addStretch(1) # Add the layout to the vertical layout of the widget @@ -84,4 +100,5 @@ class BaseSpectrometerView(ModuleView): # Push all the settings to the top of the widget self._ui_form.verticalLayout.addStretch(1) + \ No newline at end of file diff --git a/src/nqrduck_spectrometer/settings.py b/src/nqrduck_spectrometer/settings.py new file mode 100644 index 0000000..36a3e07 --- /dev/null +++ b/src/nqrduck_spectrometer/settings.py @@ -0,0 +1,141 @@ +import logging +import ipaddress +from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot + +logger = logging.getLogger(__name__) + +class Setting(QObject): + """A setting for the spectrometer is a value that is the same for all events in a pulse sequence. + E.g. the number of averages or the number of points in a spectrum.""" + settings_changed = pyqtSignal() + + def __init__(self, name, description) -> None: + super().__init__() + self.name = name + self.description = description + + @pyqtSlot(str) + def on_value_changed(self, value): + logger.debug("Setting %s changed to %s", self.name, value) + self.value = value + self.settings_changed.emit() + + def get_setting(self): + return float(self.value) + +class FloatSetting(Setting): + """ A setting that is a Float. """ + def __init__(self, name : str, default : float, description : str) -> None: + super().__init__(name, description) + self.value = default + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + try: + self._value = float(value) + except ValueError: + raise ValueError("Value must be a float") + self.settings_changed.emit() + + +class IntSetting(Setting): + """ A setting that is an Integer.""" + def __init__(self, name : str, default : int, description : str) -> None: + super().__init__(name, description) + self.value = default + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + try: + self._value = int(value) + except ValueError: + raise ValueError("Value must be an int") + self.settings_changed.emit() + +class BooleanSetting(Setting): + """ A setting that is a Boolean.""" + + def __init__(self, name : str, default : bool, description : str) -> None: + super().__init__(name, description) + self.value = default + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + try: + self._value = bool(value) + except ValueError: + raise ValueError("Value must be a bool") + self.settings_changed.emit() + +class SelectionSetting(Setting): + """ A setting that is a selection from a list of options.""" + def __init__(self, name : str, options : list, default : str, description : str) -> None: + super().__init__(name, description) + # Check if default is in options + if default not in options: + raise ValueError("Default value must be one of the options") + + self.options = options + self.value = default + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + if value in self.options: + self._value = value + else: + raise ValueError("Value must be one of the options") + self.settings_changed.emit() + +class IPSetting(Setting): + """ A setting that is an IP address.""" + def __init__(self, name : str, default : str, description : str) -> None: + super().__init__(name, description) + self.value = default + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + try: + ipaddress.ip_address(value) + self._value = value + except ValueError: + raise ValueError("Value must be a valid IP address") + self.settings_changed.emit() + +class StringSetting(Setting): + """ A setting that is a string.""" + def __init__(self, name : str, default : str, description : str) -> None: + super().__init__(name, description) + self.value = default + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + try: + self._value = str(value) + except ValueError: + raise ValueError("Value must be a string") + + self.settings_changed.emit() \ No newline at end of file