diff --git a/cpdctrl_gui/ui/main_window.py b/cpdctrl_gui/ui/main_window.py index 714f37f..dd1831b 100644 --- a/cpdctrl_gui/ui/main_window.py +++ b/cpdctrl_gui/ui/main_window.py @@ -112,10 +112,10 @@ class MainWindow(QMainWindow): self.w_right_tab.addTab(self.w_plot, "Plot") # LED SCRIPT - self.w_led_script = LedScriptViewer(LedScript(0)) - self.w_led_script.dataChanged.connect(self._led_script_updated) - self.w_lefttab.addTab(self.w_led_script, "LED Script") - self.w_measurement_settings.w_led_script.script_changed.connect(self.led_script_load) + self.w_led_script_viewer = LedScriptViewer(LedScript(0)) + self.w_led_script_viewer.scriptUpdated.connect(self._led_script_updated) + self.w_lefttab.addTab(self.w_led_script_viewer, "LED Script") + self.w_measurement_settings.w_led_script_load.loadScript.connect(self.led_script_load) self.verbose = True @@ -256,16 +256,14 @@ class MainWindow(QMainWindow): if self.leddev is None: raise ValueError("No led control device selected") - name = self.w_measurement_settings.get_value("name") script = self.w_measurement_settings.get_value("led_script") - led_script = LedScript(script=script) flush_after = self.w_measurement_settings.get_value("flush_after") use_buffer = self.w_measurement_settings.get_value("use_buffer") # check if device supports buffer mode if use_buffer and not callable(getattr(self.vmdev, 'buffer_measure', None)): # show warning dialog - ret = QMessageBox.warning(self, "Buffer mode not supported", "The selected voltage measurement device does not support the buffer measurement mode.\nClick Ok to continue without buffer measurement mode.", buttons=QMessageBox.StandardButton.Ok|QMessageBox.StandardButton.Cancel, defaultButton=QMessageBox.StandardButton.Ok) + ret = QMessageBox.warning(self, "Buffer mode not supported", "The selected voltage measurement device does not support the buffer measurement mode.\nClick OK to continue without buffer measurement mode.", buttons=QMessageBox.StandardButton.Ok|QMessageBox.StandardButton.Cancel, defaultButton=QMessageBox.StandardButton.Ok) if ret == QMessageBox.StandardButton.Ok: log.warning("Device does not support buffer mode, disabling") use_buffer = False @@ -290,6 +288,8 @@ class MainWindow(QMainWindow): self.topbar.disable_button("meas_save") self.topbar.disable_button("meas_load") self.topbar.enable_button("meas_stop") + self.w_measurement_settings.setEnabled(False) + self.w_metadata.setEnabled(False) self.w_plot.clear_data() # have the led script member be the only auto-updating script, @@ -326,7 +326,7 @@ class MainWindow(QMainWindow): # the time left estimation might be a little to short, since the actual measurement is started a few lines of # code later self.led_script.start() - self.w_led_script.update_time_predictions() + self.w_led_script_viewer.update_time_predictions() def measure_stop(self): log.info("Stopping measurement") @@ -339,7 +339,7 @@ class MainWindow(QMainWindow): self.set_status("Saving data...") self.data_collector.save_csv_in_dir() self.proc_measure = None - # dont update w_led_script, keep displaying the last values + # don't update w_led_script, keep displaying the last values self.led_script.reset() self.topbar.enable_button("meas_start") self.topbar.enable_button("connect_vmdev") @@ -347,10 +347,12 @@ class MainWindow(QMainWindow): self.topbar.enable_button("meas_save") self.topbar.enable_button("meas_load") self.topbar.disable_button("meas_stop") + self.w_measurement_settings.setEnabled(True) + self.w_metadata.setEnabled(True) self.set_status("Ready") def measure_update(self): - self.w_led_script.update_time(time.time()) + self.w_led_script_viewer.update_time(time.time()) if self.proc_measure.is_alive(): while not self.data_queue.empty(): # print(data_queue.qsize(), "\n\n") @@ -402,6 +404,14 @@ class MainWindow(QMainWindow): self.set_status(f"Aborted saving data, no file selected") def measurement_load(self, filepath): + """ + Load measurement data from the filepath. Only updates the plot. + Parameters + ---------- + filepath + The path to the file or directory to load the data from. + File may be csv or pickle containing (data, metadata) + """ log.info(f"Loading measurement data from '{filepath}'") if self.measurement_is_running(): QMessageBox.critical(self, "Measurement running", "Can not load data while measurement is running") @@ -421,6 +431,9 @@ class MainWindow(QMainWindow): self.w_plot.set_data(data[:,1], data[:,2], data[:,3]) def measurement_load_dialog(self): + """ + Open a file dialog to load measurement data. This only updates the plot + """ if self.measurement_is_running(): QMessageBox.critical(self, "Measurement running", "Can not load data while measurement is running") return @@ -436,6 +449,9 @@ class MainWindow(QMainWindow): self.measurement_load(file_path) def led_script_load(self): + """ + Load a new led script from the value in the measurement settings widget + """ script = self.w_measurement_settings.get_value("led_script") script_type = self.w_measurement_settings.get_value("led_script_type") auto_update = AppConfig.MAIN_CFG.get_or("led_script_watch_file", False) @@ -454,7 +470,7 @@ class MainWindow(QMainWindow): self.led_script_watcher.fileChanged.connect(self._led_script_update_from_file) else: self.led_script_watcher = None - self.w_led_script.set_script(self.led_script) + self.w_led_script_viewer.set_script(self.led_script) self._led_script_updated() def _led_script_update_from_file(self): @@ -468,9 +484,8 @@ class MainWindow(QMainWindow): """ Send the new led script to the measurement thread and update the gui widget. """ - print("Update script") # update gui - self.w_led_script.update_time_predictions() + self.w_led_script_viewer.update_time_predictions() # update the measurement led script if self.measurement_is_running(): self.command_queue.put(("led_script", self.led_script.copy())) diff --git a/cpdctrl_gui/ui/widgets/led_script.py b/cpdctrl_gui/ui/widgets/led_script.py index 150bd82..ea1f050 100644 --- a/cpdctrl_gui/ui/widgets/led_script.py +++ b/cpdctrl_gui/ui/widgets/led_script.py @@ -1,12 +1,14 @@ from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QScrollArea, QTableView, QFormLayout -from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt +from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt, pyqtSignal -import numpy as np -from cpdctrl.led_script import LedScript import time import datetime +import logging +log = logging.getLogger(__name__) +import numpy as np +from cpdctrl.led_script import LedScript timedelta = [("d", 24*3600), ("h", 3600), ("m", 60), ("s", 1)] def duration_to_string(duration: float) -> str: @@ -35,6 +37,12 @@ def duration_to_string(duration: float) -> str: return s.strip() class TimeLeft(QWidget): + """ + Widget that shows: + - Time passed + - Time left + - End time and date + """ def __init__(self, parent=None): super().__init__(parent) self.t_end = None @@ -88,6 +96,7 @@ class LedScriptTableModel(QAbstractTableModel): It only allows editing values that are in the future """ + scriptUpdated = pyqtSignal() def __init__(self, led_script: LedScript, parent=None): super().__init__(parent) self.led_script: LedScript = led_script @@ -117,8 +126,16 @@ class LedScriptTableModel(QAbstractTableModel): def setData(self, index: QModelIndex, value, role: int): if role == Qt.ItemDataRole.EditRole: - self.led_script.script[self.indices[index.column()]][index.row()] = value - self.dataChanged.emit(index, index, role) + newscript = self.led_script.script.copy() + newscript[self.indices[index.column()]][index.row()] = value + try: + log.info(f"Updating script from {self.led_script.script} to {newscript}") + self.led_script.update_from_script(newscript) + except ValueError as e: + raise e + # return False + self.dataChanged.emit(index, index) # this is for updating the view + self.scriptUpdated.emit() # this for notifying the main window return True return False @@ -149,6 +166,7 @@ class LedScriptTableModel(QAbstractTableModel): class LedScriptViewer(QWidget): + scriptUpdated = pyqtSignal() def __init__(self, led_script: LedScript, parent=None): super().__init__(parent) self.led_script = None @@ -173,14 +191,10 @@ class LedScriptViewer(QWidget): self.w_time_left = TimeLeft(self) self.l_vbox.addWidget(self.w_time_left) self.l_vbox.addStretch(1) - # TODO: this should emit when the script is changed - # TODO: apparently not working, also cant use on_update_callback since the file watcher watchdog thing - # TODO: when updating, the update notice comes twice so I suspect the model copies the other one? - # is not in a QThread, thus the callback cant interact with qt objects and signals - self.model.dataChanged.connect(self.update_time_predictions) def set_script(self, led_script: LedScript): self.model = LedScriptTableModel(led_script, self) + self.model.scriptUpdated.connect(self.scriptUpdated) self.w_table.setModel(self.model) self.w_table.show() @@ -199,8 +213,6 @@ class LedScriptViewer(QWidget): t_now Current time since epoch. """ - if self.model.led_script.has_updated(): - self.update_time_predictions() self.model.update_time(t_now) self.w_time_left.update_time(t_now) self.w_table.update() diff --git a/cpdctrl_gui/ui/widgets/settings/measurement_settings.py b/cpdctrl_gui/ui/widgets/settings/measurement_settings.py index 4ecf6c7..c9d2a29 100644 --- a/cpdctrl_gui/ui/widgets/settings/measurement_settings.py +++ b/cpdctrl_gui/ui/widgets/settings/measurement_settings.py @@ -34,7 +34,7 @@ class DeviceSelection(QGroupBox): class ScriptSelection(QGroupBox): - script_changed = pyqtSignal() + loadScript = pyqtSignal() def __init__(self, parent=None): super().__init__(parent=parent, title="LED Script") self.layout = QVBoxLayout() @@ -55,7 +55,7 @@ class ScriptSelection(QGroupBox): self.w_constant_value = QSpinBox() self.w_constant_value.setRange(0, 100) # signal when changed - self.w_constant_value.valueChanged.connect(lambda: self.script_changed.emit()) + self.w_constant_value.valueChanged.connect(lambda: self.loadScript.emit()) # Layouts l_constant_value = QVBoxLayout() @@ -98,7 +98,7 @@ class ScriptSelection(QGroupBox): self.file_path = file_path self.w_script_file.setText(self.file_path) # signal the change - self.script_changed.emit() + self.loadScript.emit() def get_script(self): @@ -130,8 +130,8 @@ class MeasurementSettings(QWidget): self.w_device_selection = DeviceSelection() self.l_vbox.addWidget(self.w_device_selection) # - script - self.w_led_script = ScriptSelection() - self.l_vbox.addWidget(self.w_led_script) + self.w_led_script_load = ScriptSelection() + self.l_vbox.addWidget(self.w_led_script_load) # key-value stuff in a form self.w_form = SettingsForm(AppConfig.MEAS_CFG) self.l_vbox.addWidget(self.w_form) @@ -155,7 +155,6 @@ class MeasurementSettings(QWidget): self.l_vbox.addStretch(1) - def set_value(self, key, value): if key in self.w_form: self.w_form.set_value(key, value) @@ -168,8 +167,8 @@ class MeasurementSettings(QWidget): if key in self.w_form: return self.w_form.get_value(key) elif key == "led_script": - return self.w_led_script.get_script() + return self.w_led_script_load.get_script() elif key == "led_script_type": - return self.w_led_script.get_script_type() + return self.w_led_script_load.get_script_type() else: - raise ValueError(f"Unknown key: {key}") \ No newline at end of file + raise ValueError(f"Unknown key: {key}")