from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QSpacerItem, QSpinBox, QDoubleSpinBox from PyQt6.QtWidgets import QGridLayout, QMenu, QListWidget, QPushButton, QFormLayout, QDialog, QDialogButtonBox """ QWidget class that contains a variable amount of key - input pairs. One pair per line in a dense layout pairs. The value may be text - line edit, float - qdoublespinbox and int - qspinbox. """ class MetadataInput(QWidget): def __init__(self, elements: list[tuple[str, str]]|dict[str,str]=None): super().__init__() # set layout self.l_vbox = QVBoxLayout() self.l_vbox.addWidget(QLabel("Measurement Metadata")) self.l_grid = QGridLayout() self.l_grid.setColumnMinimumWidth(0, 100) self.l_grid.setColumnMinimumWidth(1, 100) self.l_grid.setContentsMargins(0, 0, 0, 0) self.l_grid.setSpacing(0) # first row: key value <new element button> 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 = {} if type(elements) == dict: for (n, v) in elements.items(): self.add_element(n, v) elif type(elements) == list: for (n, v) in elements: self.add_element(n, v) 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) self.layout_changed() # call even when no element was added def layout_changed(self): # add stretch to the last row for r in range(self.l_grid.rowCount()): self.l_grid.setRowStretch(r, 0) self.l_grid.setRowStretch(self.l_grid.rowCount(), 1) 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) # 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 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) 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): """ Returns ------- Dictionary of all key-values pairs """ d = {} for name, (w_label, w_input, _) in self.ws_elements.items(): key = w_label.text() value = w_input.text() d[key] = value return d