diff --git a/CHANGELOG.md b/CHANGELOG.md index cf13fa8..7729fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### Version 0.0.4 (05-05-2024) +- Added display of multiple measurements that can be displayed in th plot window. +- Now using the FileManager class instead of QFileManager + ### Version 0.0.3 (26-04-2024) - Switched to new formbuilder. This should make implementation of signal processing methods more robust and easier. diff --git a/pyproject.toml b/pyproject.toml index 1e965e4..5ea2dd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ allow-direct-references = true [project] name = "nqrduck-measurement" -version = "0.0.3" +version = "0.0.4" authors = [ { name="jupfi", email="support@nqrduck.cool" }, ] diff --git a/src/nqrduck_measurement/controller.py b/src/nqrduck_measurement/controller.py index 0968fc2..7b26994 100644 --- a/src/nqrduck_measurement/controller.py +++ b/src/nqrduck_measurement/controller.py @@ -230,3 +230,43 @@ class MeasurementController(ModuleController): self.module.model.displayed_measurement = apodized_measurement self.module.model.add_measurement(apodized_measurement) + + @pyqtSlot() + def change_displayed_measurement(self, measurement=None) -> None: + """Change the displayed measurement.""" + + if not self.module.model.measurements: + logger.debug("No measurements to display.") + return + + if not measurement: + index = self.module.view._ui_form.selectionBox.value() + self.module.model.displayed_measurement = self.module.model.measurements[ + index + ] + logger.debug( + f"Changing displayed measurement to {self.module.model.displayed_measurement.name}." + ) + else: + logger.debug(f"Changing displayed measurement to {measurement.name}.") + self.module.model.displayed_measurement = measurement + + @pyqtSlot(Measurement) + def delete_measurement(self, measurement: Measurement) -> None: + """Delete a measurement. + + The measurement is removed from the list of measurements. Then the displayed measurement is updated. + + Args: + measurement (Measurement): The measurement to delete. + """ + logger.debug("Deleting measurement.") + self.module.model.remove_measurement(measurement) + + if measurement == self.module.model.displayed_measurement: + if self.module.model.measurements: + self.module.model.displayed_measurement = ( + self.module.model.measurements[-1] + ) + else: + self.module.model.displayed_measurement = None diff --git a/src/nqrduck_measurement/model.py b/src/nqrduck_measurement/model.py index efa8e2c..871a99b 100644 --- a/src/nqrduck_measurement/model.py +++ b/src/nqrduck_measurement/model.py @@ -50,6 +50,7 @@ class MeasurementModel(ModuleModel): displayed_measurement_changed = pyqtSignal(Measurement) measurements_changed = pyqtSignal(list) + view_mode_changed = pyqtSignal(str) measurement_frequency_changed = pyqtSignal(float) @@ -94,6 +95,14 @@ class MeasurementModel(ModuleModel): def add_measurement(self, measurement: Measurement): """Add a measurement to the list of measurements.""" self.measurements.append(measurement) + # Change the maximum value of the selectionBox. + self.measurements_changed.emit(self.measurements) + self.displayed_measurement_changed.emit(measurement) + + def remove_measurement(self, measurement : Measurement): + """Remove a measurement from the list of measurements.""" + self.measurements.remove(measurement) + # Change the maximum value of the selectionBox. self.measurements_changed.emit(self.measurements) @property @@ -108,7 +117,12 @@ class MeasurementModel(ModuleModel): @displayed_measurement.setter def displayed_measurement(self, value: Measurement): self._displayed_measurement = value - self.displayed_measurement_changed.emit(value) + if value is not None: + logger.debug("Displayed measurement: " + value.name) + self.displayed_measurement_changed.emit(value) + else: + self.module.view.update_displayed_measurement() + @property def measurement_frequency(self): diff --git a/src/nqrduck_measurement/resources/measurement_widget.ui b/src/nqrduck_measurement/resources/measurement_widget.ui index 005ea13..fa101a2 100644 --- a/src/nqrduck_measurement/resources/measurement_widget.ui +++ b/src/nqrduck_measurement/resources/measurement_widget.ui @@ -6,7 +6,7 @@ 0 0 - 1920 + 1807 1080 @@ -23,7 +23,7 @@ - + @@ -150,6 +150,22 @@ + + + + + 75 + true + + + + Measurements: + + + + + + @@ -184,7 +200,7 @@ - + @@ -195,6 +211,9 @@ + + + diff --git a/src/nqrduck_measurement/view.py b/src/nqrduck_measurement/view.py index 74ac994..5c1dcf6 100644 --- a/src/nqrduck_measurement/view.py +++ b/src/nqrduck_measurement/view.py @@ -2,7 +2,19 @@ import logging import numpy as np -from PyQt6.QtWidgets import QWidget, QDialog, QLabel, QVBoxLayout +from functools import partial +from PyQt6.QtWidgets import ( + QWidget, + QDialog, + QLabel, + QVBoxLayout, + QHBoxLayout, + QPushButton, + QListWidgetItem, + QSizePolicy, + QApplication, +) +from PyQt6.QtGui import QFontMetrics from PyQt6.QtCore import pyqtSlot, Qt from nqrduck.module.module_view import ModuleView from nqrduck.assets.icons import Logos @@ -50,6 +62,8 @@ class MeasurementView(ModuleView): ) self.module.model.view_mode_changed.connect(self.update_displayed_measurement) + self.module.model.measurements_changed.connect(self.on_measurements_changed) + self._ui_form.buttonStart.clicked.connect( self.on_measurement_start_button_clicked ) @@ -105,6 +119,11 @@ class MeasurementView(ModuleView): self._ui_form.averagesEdit.set_min_value(1) self._ui_form.averagesEdit.set_max_value(1e6) + # Connect selectionBox signal fors switching the displayed measurement + self._ui_form.selectionBox.valueChanged.connect( + self.module.controller.change_displayed_measurement + ) + def init_plotter(self) -> None: """Initialize plotter with the according units for time domain.""" plotter = self._ui_form.plotter @@ -143,6 +162,18 @@ class MeasurementView(ModuleView): plotter = self._ui_form.plotter plotter.canvas.ax.clear() try: + if self.module.model.displayed_measurement is None: + logger.debug("No measurement data to display. Clearing plotter.") + + if self.module.model.view_mode == self.module.model.FFT_VIEW: + self.change_to_fft_view() + else: + self.change_to_time_view() + + self._ui_form.plotter.canvas.draw() + + return + if self.module.model.view_mode == self.module.model.FFT_VIEW: self.change_to_fft_view() y = self.module.model.displayed_measurement.fdy @@ -173,6 +204,26 @@ class MeasurementView(ModuleView): # Add legend self._ui_form.plotter.canvas.ax.legend() + # Highlight the displayed measurement in the measurementsList + for i in range(self._ui_form.measurementsList.count()): + item = self._ui_form.measurementsList.item(i) + widget = self._ui_form.measurementsList.itemWidget(item) + button = widget.layout().itemAt(0).widget() + # Get the measurement by accessing measurement property + measurement = button.property("measurement") + if measurement == self.module.model.displayed_measurement: + item.setSelected(True) + else: + item.setSelected(False) + + # Update the number of the selectionBox + for measurement in self.module.model.measurements: + if measurement.name == self.module.model.displayed_measurement.name: + self._ui_form.selectionBox.setValue( + self.module.model.measurements.index(measurement) + ) + break + except AttributeError: logger.debug("No measurement data to display.") self._ui_form.plotter.canvas.draw() @@ -200,7 +251,7 @@ class MeasurementView(ModuleView): """Slot for when the measurement save button is clicked.""" logger.debug("Measurement save button clicked.") - file_manager = self.QFileManager( + file_manager = self.FileManager( self.module.model.FILE_EXTENSION, parent=self.widget ) file_name = file_manager.saveFileDialog() @@ -212,13 +263,84 @@ class MeasurementView(ModuleView): """Slot for when the measurement load button is clicked.""" logger.debug("Measurement load button clicked.") - file_manager = self.QFileManager( + file_manager = self.FileManager( self.module.model.FILE_EXTENSION, parent=self.widget ) file_name = file_manager.loadFileDialog() if file_name: self.module.controller.load_measurement(file_name) + @pyqtSlot() + def on_measurements_changed(self) -> None: + """Slot for when a measurement is added.""" + logger.debug("Measurement changed.") + + if len(self.module.model.measurements) == 0: + self.module.view._ui_form.selectionBox.setMaximum(0) + self.module.view._ui_form.selectionBox.setValue(0) + else: + self.module.view._ui_form.selectionBox.setMaximum( + len(self.module.model.measurements) - 1 + ) + + # Clear the measurements list + self._ui_form.measurementsList.clear() + + for measurement in self.module.model.measurements: + measurement_widget = QWidget() + layout = QHBoxLayout() + measurement_widget.setLayout(layout) + + delete_button = QPushButton() + delete_button.setIcon(Logos.Garbage12x12()) + delete_button.setFixedWidth(delete_button.iconSize().width()) + delete_button.clicked.connect( + lambda: self.module.controller.delete_measurement(measurement) + ) + + name_button = QPushButton() + name_button.clicked.connect( + partial(self.module.controller.change_displayed_measurement, measurement) + ) + + # Not sure if this is pretty + name_button.setProperty("measurement", measurement) + name_button.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred + ) # Set size policy + + layout.addWidget(name_button) + layout.addWidget(delete_button) + layout.addStretch() # Add stretch after delete button to ensure name button takes up space + + item = QListWidgetItem() + item.setSizeHint(measurement_widget.sizeHint()) + + self._ui_form.measurementsList.addItem(item) + self._ui_form.measurementsList.setItemWidget(item, measurement_widget) + + # Wait for the layout to be updated + QApplication.processEvents() + + # Get the contents margins (left, top, right, bottom) + content_margins = layout.contentsMargins() + + # Include the margins and spacing in the maxWidth calculation + maxWidth = ( + self._ui_form.measurementsList.width() + - delete_button.width() + - content_margins.left() + - content_margins.right() + - layout.spacing() + ) + + fontMetrics = QFontMetrics(name_button.font()) + elidedText = fontMetrics.elidedText( + measurement.name, Qt.TextElideMode.ElideRight, maxWidth + ) + name_button.setText(elidedText) + name_button.setToolTip(measurement.name) + class MeasurementDialog(QDialog): """This Dialog is shown when the measurement is started and therefore blocks the main window. diff --git a/src/nqrduck_measurement/widget.py b/src/nqrduck_measurement/widget.py index bffc892..5655546 100644 --- a/src/nqrduck_measurement/widget.py +++ b/src/nqrduck_measurement/widget.py @@ -1,6 +1,6 @@ -# Form implementation generated from reading ui file '../nqrduck-measurement/src/nqrduck_measurement/resources/measurement_widget.ui' +# Form implementation generated from reading ui file 'measurement_widget.ui' # -# Created by: PyQt6 UI code generator 6.5.1 +# Created by: PyQt6 UI code generator 6.7.0 # # WARNING: Any manual changes made to this file will be lost when pyuic6 is # run again. Do not edit this file unless you know what you are doing. @@ -12,7 +12,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") - Form.resize(1920, 1080) + Form.resize(1807, 1080) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -83,6 +83,16 @@ class Ui_Form(object): self.spsettingsButton = QtWidgets.QPushButton(parent=Form) self.spsettingsButton.setObjectName("spsettingsButton") self.settingsLayout.addWidget(self.spsettingsButton) + self.label = QtWidgets.QLabel(parent=Form) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label.setFont(font) + self.label.setObjectName("label") + self.settingsLayout.addWidget(self.label) + self.measurementsList = QtWidgets.QListWidget(parent=Form) + self.measurementsList.setObjectName("measurementsList") + self.settingsLayout.addWidget(self.measurementsList) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) self.settingsLayout.addItem(spacerItem1) self.dataLayout = QtWidgets.QVBoxLayout() @@ -105,6 +115,9 @@ class Ui_Form(object): self.plotter.setSizePolicy(sizePolicy) self.plotter.setObjectName("plotter") self.plotterLayout.addWidget(self.plotter) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.plotterLayout.addLayout(self.horizontalLayout_4) self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) @@ -119,7 +132,7 @@ class Ui_Form(object): self.horizontalLayout_3.addItem(spacerItem3) self.horizontalLayout_3.setStretch(1, 1) self.plotterLayout.addLayout(self.horizontalLayout_3) - self.plotterLayout.setStretch(1, 1) + self.plotterLayout.setStretch(2, 1) self.horizontalLayout_2.addLayout(self.plotterLayout) self.horizontalLayout_2.setStretch(1, 1) self.horizontalLayout.addLayout(self.horizontalLayout_2) @@ -142,6 +155,7 @@ class Ui_Form(object): self.peakButton.setText(_translate("Form", "Peak-Picking")) self.fittingButton.setText(_translate("Form", "Fitting")) self.spsettingsButton.setText(_translate("Form", "Settings")) + self.label.setText(_translate("Form", "Measurements:")) self.exportButton.setText(_translate("Form", "Export Measurement")) self.importButton.setText(_translate("Form", "Import Measurement")) self.fftButton.setText(_translate("Form", "FFT"))