Implemented input validation.

This commit is contained in:
jupfi 2024-03-13 10:16:12 +01:00
parent 837524b69f
commit e314f51ae4
2 changed files with 83 additions and 35 deletions

View file

@ -53,7 +53,7 @@ class BaseSpectrometerView(ModuleView):
setting_label = QLabel(setting.name) setting_label = QLabel(setting.name)
setting_label.setMinimumWidth(200) setting_label.setMinimumWidth(200)
edit_widget = setting.get_widget() edit_widget = setting.widget
logger.debug("Setting widget: %s", edit_widget) logger.debug("Setting widget: %s", edit_widget)
# Add a icon that can be used as a tooltip # Add a icon that can be used as a tooltip

View file

@ -1,7 +1,8 @@
import logging import logging
import ipaddress import ipaddress
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot, QRegularExpression
from PyQt6.QtWidgets import QLineEdit, QComboBox, QCheckBox from PyQt6.QtWidgets import QLineEdit, QComboBox, QCheckBox
from PyQt6.QtGui import QValidator, QRegularExpressionValidator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -10,10 +11,21 @@ class Setting(QObject):
E.g. the number of averages or the number of points in a spectrum.""" E.g. the number of averages or the number of points in a spectrum."""
settings_changed = pyqtSignal() settings_changed = pyqtSignal()
def __init__(self, name, description) -> None: def __init__(self, name : str, description : str, default = None) -> None:
""" Create a new setting.
Args:
name (str): The name of the setting.
description (str): A description of the setting.
"""
super().__init__() super().__init__()
self.name = name self.name = name
self.description = description self.description = description
if default is not None:
self.value = default
# This can be overriden by subclasses
self.widget = self.get_widget()
@pyqtSlot(str) @pyqtSlot(str)
def on_value_changed(self, value): def on_value_changed(self, value):
@ -38,11 +50,34 @@ class Setting(QObject):
widget.editingFinished.connect(lambda x=widget, s=self: s.on_value_changed(x.text())) widget.editingFinished.connect(lambda x=widget, s=self: s.on_value_changed(x.text()))
return widget return widget
def update_widget_style(self):
""" Update the style of the QLineEdit widget to indicate if the value is valid."""
logger.debug("Updating widget style")
if self.validator.validate(self.widget.text(), 0)[0] == QValidator.State.Acceptable:
self.widget.setStyleSheet("QLineEdit { background-color: white; }")
elif self.validator.validate(self.widget.text(), 0)[0] == QValidator.State.Intermediate:
self.widget.setStyleSheet("QLineEdit { background-color: yellow; }")
else:
self.widget.setStyleSheet("QLineEdit { background-color: red; }")
class FloatSetting(Setting): class FloatSetting(Setting):
""" A setting that is a Float. """ """ A setting that is a Float. """
def __init__(self, name : str, default : float, description : str) -> None: DEFAULT_LENGTH = 100
super().__init__(name, description) def __init__(self, name : str, default : float, description : str, validator : QValidator = None) -> None:
self.value = default super().__init__(name, description, default)
# If a validator is given, set it for the QLineEdit widget
if validator:
self.validator = validator
else:
# Create a regex validator that only allows floats
regex = "[-+]?[0-9]*\.?[0-9]+"
self.validator = QRegularExpressionValidator(QRegularExpression(regex))
self.widget = self.get_widget()
# self.widget.setValidator(self.validator)
# Connect the update_widget_style method to the textChanged signal
self.widget.textChanged.connect(self.update_widget_style)
@property @property
def value(self): def value(self):
@ -51,16 +86,33 @@ class FloatSetting(Setting):
@value.setter @value.setter
def value(self, value): def value(self, value):
try: try:
if self.validator.validate(value, 0)[0] == QValidator.State.Acceptable:
self._value = float(value) self._value = float(value)
self.settings_changed.emit()
# This should never be reached because the validator should prevent this
except ValueError: except ValueError:
raise ValueError("Value must be a float") raise ValueError("Value must be a float")
# This happens when the validator has not yet been set
except AttributeError:
self._value = float(value)
self.settings_changed.emit() self.settings_changed.emit()
class IntSetting(Setting): class IntSetting(Setting):
""" A setting that is an Integer.""" """ A setting that is an Integer."""
def __init__(self, name : str, default : int, description : str) -> None: def __init__(self, name : str, default : int, description : str, validator : QValidator = None) -> None:
super().__init__(name, description) super().__init__(name, description, default)
self.value = default
# If a validator is given, set it for the QLineEdit widget
if validator:
self.validator = validator
else:
# Create a regex validator that only allows integers
regex = "[-+]?[0-9]+"
self.validator = QRegularExpressionValidator(QRegularExpression(regex))
self.widget = self.get_widget()
# Connect the update_widget_style method to the textChanged signal
self.widget.textChanged.connect(self.update_widget_style)
@property @property
def value(self): def value(self):
@ -78,8 +130,10 @@ class BooleanSetting(Setting):
""" A setting that is a Boolean.""" """ A setting that is a Boolean."""
def __init__(self, name : str, default : bool, description : str) -> None: def __init__(self, name : str, default : bool, description : str) -> None:
super().__init__(name, description) super().__init__(name, description, default)
self.value = default
# Overrides the default widget
self.widget = self.get_widget()
@property @property
def value(self): def value(self):
@ -109,13 +163,15 @@ class BooleanSetting(Setting):
class SelectionSetting(Setting): class SelectionSetting(Setting):
""" A setting that is a selection from a list of options.""" """ A setting that is a selection from a list of options."""
def __init__(self, name : str, options : list, default : str, description : str) -> None: def __init__(self, name : str, options : list, default : str, description : str) -> None:
super().__init__(name, description) super().__init__(name, description, default)
# Check if default is in options # Check if default is in options
if default not in options: if default not in options:
raise ValueError("Default value must be one of the options") raise ValueError("Default value must be one of the options")
self.options = options self.options = options
self.value = default
# Overrides the default widget
self.widget = self.get_widget()
@property @property
def value(self): def value(self):
@ -123,10 +179,16 @@ class SelectionSetting(Setting):
@value.setter @value.setter
def value(self, value): def value(self, value):
try:
if value in self.options: if value in self.options:
self._value = value self._value = value
else: else:
raise ValueError("Value must be one of the options") raise ValueError("Value must be one of the options")
# This fixes a bug when creating the widget when the options are not yet set
except AttributeError:
self._value = value
self.options = [value]
self.settings_changed.emit() self.settings_changed.emit()
def get_widget(self): def get_widget(self):
@ -164,8 +226,7 @@ class IPSetting(Setting):
class StringSetting(Setting): class StringSetting(Setting):
""" A setting that is a string.""" """ A setting that is a string."""
def __init__(self, name : str, default : str, description : str) -> None: def __init__(self, name : str, default : str, description : str) -> None:
super().__init__(name, description) super().__init__(name, description, default)
self.value = default
@property @property
def value(self): def value(self):
@ -179,16 +240,3 @@ class StringSetting(Setting):
raise ValueError("Value must be a string") raise ValueError("Value must be a string")
self.settings_changed.emit() self.settings_changed.emit()
def get_widget(self):
"""Return a widget for the setting.
The default widget is simply a QLineEdit.
This method can be overwritten by subclasses to return a different widget.
Returns:
QLineEdit: A QLineEdit widget that can be used to change the setting.
"""
widget = QLineEdit(str(self.value))
widget.setMinimumWidth(100)
widget.editingFinished.connect(lambda x=widget, s=self: s.on_value_changed(x.text()))
return widget