diff --git a/app/ui/widgets/metadata_input.py b/app/ui/widgets/metadata_input.py index 64a856d..f9441f7 100644 --- a/app/ui/widgets/metadata_input.py +++ b/app/ui/widgets/metadata_input.py @@ -1,23 +1,5 @@ from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QSpacerItem, QSpinBox, QDoubleSpinBox -from PyQt6.QtWidgets import QGridLayout, QMenu, QListWidget, QPushButton - -class MetadataInput(QWidget): - def __init__(self, elements: list[tuple[str, str]]=None): - super().__init__() - self.layout = QVBoxLayout() - self.elements = [] - if elements is not None: - for (n, v) in elements: - self.addElement(n, v) - self.layout.addStretch() - self.setLayout(self.layout) - - def addElement(self, name, init_val=""): - self.elements.append((name, init_val)) - self.layout.addWidget(QLabel(name)) - self.layout.addWidget(QLineEdit(init_val)) - self.layout.addItem(QSpacerItem(0, 1)) - +from PyQt6.QtWidgets import QGridLayout, QMenu, QListWidget, QPushButton, QFormLayout, QDialog, QDialogButtonBox """ QWidget class that contains a variable amount of key - input pairs. @@ -25,51 +7,125 @@ One pair per line in a dense layout pairs. The value may be text - line edit, float - qdoublespinbox and int - qspinbox. """ -class MetadataInput2(QWidget): +class MetadataInput(QWidget): def __init__(self, elements: list[tuple[str, str]]=None): super().__init__() - self.layout = QGridLayout() - self.layout.setContentsMargins(0, 0, 0, 0) - self.layout.setSpacing(0) + self.l_vbox = QVBoxLayout() + self.l_vbox.addWidget(QLabel("Measurement Metadata")) + self.l_grid = QGridLayout() + self.l_grid.setContentsMargins(0, 0, 0, 0) + self.l_grid.setSpacing(0) + # first row: key value + self.l_grid.addWidget(QLabel("Key"), 0, 0) + self.l_grid.addWidget(QLabel("Value"), 0, 1) + self.btn_new_element = QPushButton("+") + self.btn_new_element.setFixedSize(20, 20) + self.btn_new_element.clicked.connect(self.add_element_dialog) + self.l_grid.addWidget(self.btn_new_element, 0, 2) + + # key-value widgets + self.ws_elements = {} for (n, v) in elements: self.add_element(n, v) - self.setLayout(self.layout) + self.l_grid.setContentsMargins(4, 4, 4, 4) + self.l_grid.setSpacing(4) + self.l_vbox.addLayout(self.l_grid) + self.setLayout(self.l_vbox) - def add_element(self, name, init_val=""): - row = self.layout.rowCount() - label_widget = QLabel(name) - self.layout.addWidget(label_widget, row, 0) + def layout_changed(self): + # align at the top with space at the bottom + for r in range(self.l_grid.rowCount()): + self.l_grid.setRowStretch(r, 0) + self.l_grid.setRowStretch(self.l_grid.rowCount(), 1) - if isinstance(init_val, str): - input_widget = QLineEdit(init_val) - elif isinstance(init_val, int): - input_widget = QSpinBox() - input_widget.setValue(init_val) - elif isinstance(init_val, float): - input_widget = QDoubleSpinBox() - input_widget.setValue(init_val) - else: - input_widget = QLineEdit(str(init_val)) + def add_element_dialog(self): + """ + Prompt for a new key-value pair using a Dialog having a form + """ + dialog = QDialog(self) + dialog.setWindowTitle("Add new field") + dialog.layout = QFormLayout() + dialog.key_input = QLineEdit() + dialog.value_input = QLineEdit() + dialog.layout.addRow("Key", dialog.key_input) + dialog.layout.addRow("Value", dialog.value_input) - self.layout.addWidget(input_widget, row, 1) + # ok and cancel + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) + buttons.accepted.connect(dialog.accept) + buttons.rejected.connect(dialog.reject) + dialog.layout.addRow(buttons) + dialog.setLayout(dialog.layout) + + ret = dialog.exec() + if ret == QDialog.DialogCode.Accepted: + self.add_element(dialog.key_input.text(), dialog.value_input.text()) + + def add_element(self, key, init_val=""): + if key in self.ws_elements: + raise RuntimeError(f"Can not add field '{key}', it already exists") + if key == "": + raise RuntimeError(f"Can not add field with empty key") + row = self.l_grid.rowCount() + w_label = QLabel(key) + self.l_grid.addWidget(w_label, row, 0) + + w_input= QLineEdit(str(init_val)) + + self.l_grid.addWidget(w_input, row, 1) # Add a small button with an X from the theme - remove_button = QPushButton("X") - remove_button.setFixedSize(20, 20) - remove_button.clicked.connect(lambda: self.remove_element(label_widget)) - self.layout.addWidget(remove_button, row, 2) + btn_remove = QPushButton("X") + btn_remove.setFixedSize(20, 20) + btn_remove.clicked.connect(lambda: self.remove_element(key)) + self.l_grid.addWidget(btn_remove, row, 2) - def remove_element(self, label_widget): - # get position of label widget - # TODO Fix - row = self.layout.indexOf(label_widget) - # remove all widgets in row - for i in range(self.layout.columnCount()): - widget = self.layout.itemAtPosition(row, i).widget() - print(widget) - self.layout.removeWidget(widget) + self.ws_elements[key] = (w_label, w_input, btn_remove) + self.layout_changed() + + def remove_element(self, name): + if name not in self.ws_elements: + raise RuntimeError(f"Can not remove field '{name}', it does not exist") + for w in self.ws_elements[name]: + self.l_grid.removeWidget(w) + del self.ws_elements[name] + self.layout_changed() + def set_from_dict(self, d: dict[str, str]): + """ + Set the widgets from the dictionary + Parameters + ---------- + d + Dictionary of key-value pairs + Returns + ------- + None + """ + # first remove widgets not in new dict + for key in self.ws_elements.keys(): + if key not in d: + self.remove_element(key) + self.update_from_dict(d) + + def update_from_dict(self, d: dict[str, str]): + """ + Update widgets from the dictionary. + New values will be added, no values will be removed + Parameters + ---------- + d + Dictionary of key-value pairs + Returns + ------- + None + """ + for key, value in d.items(): + if key not in self.ws_elements: + self.add_element(key, value) + else: + self.ws_elements[key][1].setText(value) def get_dict(self): """ @@ -78,8 +134,8 @@ class MetadataInput2(QWidget): Dictionary of all key-values pairs """ d = {} - for row in range(self.layout.rowCount()): - key = self.layout.itemAtPosition(row, 0).widget().text() - value = self.layout.itemAtPosition(row, 1).widget().text() + for name, (w_label, w_input, _) in self.ws_elements.items(): + key = w_label.text() + value = w_input.text() d[key] = value return d \ No newline at end of file