From 405bf4935f9f76b3632744de8ab95544a2f26dec Mon Sep 17 00:00:00 2001 From: CPD Date: Tue, 4 Mar 2025 17:53:06 +0100 Subject: [PATCH] Add measurement config as extra file, with widget for settings --- app/ui/widgets/measurement_settings.py | 137 +++++++++++++++++++++++++ app/utility/config.py | 8 ++ 2 files changed, 145 insertions(+) create mode 100644 app/ui/widgets/measurement_settings.py diff --git a/app/ui/widgets/measurement_settings.py b/app/ui/widgets/measurement_settings.py new file mode 100644 index 0000000..4874bd3 --- /dev/null +++ b/app/ui/widgets/measurement_settings.py @@ -0,0 +1,137 @@ +from PyQt6.QtWidgets import QWidget, QRadioButton, QVBoxLayout, QHBoxLayout, QPushButton, QSpinBox, QFileDialog, QLabel +from PyQt6.QtWidgets import QFormLayout, QDoubleSpinBox, QCheckBox + +from ...utility.config import AppConfig + +class ScriptSelection(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.layout = QVBoxLayout() + + # Radio buttons + self.radio_script_file = QRadioButton("Script file") + self.radio_constant_value = QRadioButton("Constant value") + self.radio_script_file.toggled.connect(self.on_radio_button_toggled) + self.radio_constant_value.toggled.connect(self.on_radio_button_toggled) + + # Load from file button + self.load_button = QPushButton("Load from file") + self.load_button.clicked.connect(self.load_file) + + # QSpinBox for constant value + self.spin_box = QSpinBox() + self.spin_box.setRange(0, 100) + + # Layout for radio buttons + radio_layout = QHBoxLayout() + radio_layout.addWidget(self.radio_script_file) + radio_layout.addWidget(self.radio_constant_value) + + # Layout for load button and spin box + input_layout = QHBoxLayout() + input_layout.addWidget(self.load_button) + input_layout.addWidget(self.spin_box) + + # Add layouts to main layout + self.layout.addLayout(radio_layout) + self.layout.addLayout(input_layout) + + self.setLayout(self.layout) + + # Initial state + self.radio_script_file.setChecked(True) + self.on_radio_button_toggled() + + self.layout.addStretch(1) + + def on_radio_button_toggled(self): + if self.radio_script_file.isChecked(): + self.load_button.setEnabled(True) + self.spin_box.setEnabled(False) + else: + self.load_button.setEnabled(False) + self.spin_box.setEnabled(True) + + def load_file(self): + # options = QFileDialog.Options() + file_name, _ = QFileDialog.getOpenFileName(self, "Open Script File", "", "All Files (*);;Text files (*.led)") + if file_name: + with open(file_name, 'r') as file: + self.file_content = file.read() + + def get_script(self): + if self.radio_script_file.isChecked(): + return self.file_content + else: + return self.spin_box.value() + + +class MeasurementSettings(QWidget): + """ + Widget allowing the user to set the measurement settings. + It loads the values from AppConfig.MEAS_CFG and automatically + updates the values in AppConfig.MEAS_CFG when the user changes them. + """ + def __init__(self, parent=None): + super().__init__(parent) + self.l_vbox = QVBoxLayout() + # self.label = QLabel("Measurement Settings") + # self.layout.addWidget(self.label) + self.setLayout(self.l_vbox) + self.l_form = QFormLayout() + + # - script + self.l_vbox.addWidget(ScriptSelection()) + # key-value stuff in a form + self.l_vbox.addLayout(self.l_form) + self.ws_form = {} + self._add_form_field("interval", "Interval [s]", 1.0, QDoubleSpinBox(self), "Amount of seconds to wait between voltage measurements and LED device updates") + self._add_form_field("max_measurements", "Max Measurements", 0, QSpinBox(self), "Number of measurements to take. Set to 0 for infinite measurements") + self._add_form_field("stop_on_script_end", "Stop on Script End", False, QCheckBox(self), "Stop measurement when LED script ends") + self._add_form_field("use_buffer", "Use Buffer", False, QCheckBox(self), "If available, use device buffer for more accurate measurement timings.\nLeads to a lower accuracy of LED update timings, up to 1*interval") + self._add_form_field("flush_after", "Flush after", 0, QSpinBox(self), "Number of measurements to take before writing the data to an intermediate file") + + def _add_form_field(self, key: str, label: str, default_value, widget: QWidget, tooltip: str = None): + if tooltip: widget.setToolTip(tooltip) + value = AppConfig.MEAS_CFG.get_or(key, default_value) + # set the value depending on the type of the widget + if isinstance(widget, QSpinBox) or isinstance(widget, QDoubleSpinBox): + widget.setValue(value) + widget.valueChanged.connect(lambda value: self.value_updated(key, value)) + elif isinstance(widget, QCheckBox): + widget.setChecked(value) + widget.stateChanged.connect(lambda value: self.value_updated(key, value)) + else: + raise ValueError(f"Unknown widget type: {type(widget)}") + self.l_form.addRow(QLabel(label), widget) + self.ws_form[key] = widget + + + def value_updated(self, key, value): + AppConfig.MEAS_CFG.set(key, value) + + def set_value(self, key, value): + if key in self.ws_form: + # set depending on widget type + if isinstance(self.ws_form[key], QSpinBox) or isinstance(self.ws_form[key], QDoubleSpinBox): + self.ws_form[key].setValue(value) + elif isinstance(self.ws_form[key], QCheckBox): + self.ws_form[key].setChecked(value) + else: + raise ValueError(f"Unknown widget type: {type(self.ws_form[key])}") + else: + raise ValueError(f"Unknown key: {key}") + + def get_value(self, key): + if key in self.ws_form: + # get depending on widget type + if isinstance(self.ws_form[key], QSpinBox) or isinstance(self.ws_form[key], QDoubleSpinBox): + return self.ws_form[key].value() + elif isinstance(self.ws_form[key], QCheckBox): + return self.ws_form[key].isChecked() + else: + raise ValueError(f"Unknown widget type: {type(self.ws_form[key])}") + else: + raise ValueError(f"Unknown key: {key}") + + diff --git a/app/utility/config.py b/app/utility/config.py index ec83f60..7105d6a 100644 --- a/app/utility/config.py +++ b/app/utility/config.py @@ -10,6 +10,7 @@ class AppConfig: APP_NAME: str = "cpdctrl-gui" CONFIG_DIR: str = path.expanduser("~/.config/cpdctrl") MAIN_CFG: ConfigFile = None + MEAS_CFG: ConfigFile = None @classmethod def initialize(cls) -> None: """ @@ -18,18 +19,25 @@ class AppConfig: """ if 'XDG_CONFIG_HOME' in environ.keys(): AppConfig.CONFIG_DIR = path.join(environ["XDG_CONFIG_HOME"], "cpdctrl") + # Main CFG AppConfig.MAIN_CFG_PATH = path.join(AppConfig.CONFIG_DIR, "cpdctrl-gui.yaml") AppConfig.MAIN_CFG = ConfigFile(AppConfig.MAIN_CFG_PATH, { "voltage_measurement_device_auto_reconnect": True, "led_device_auto_reconnect": True, "datadir": "~/data2", }) + + # Measurement CFG + AppConfig.MEAS_CFG_PATH = path.join(AppConfig.CONFIG_DIR, "cpdctrl-gui-measurement.yaml") + AppConfig.MEAS_CFG = ConfigFile(AppConfig.MEAS_CFG_PATH, {}) + @classmethod def finalize(cls) -> None: """ Write configuration to file """ AppConfig.MAIN_CFG.save() + AppConfig.MEAS_CFG.save()