cpdctrl-gui/cpdctrl_gui/ui/widgets/metadata_input.py
2025-03-05 14:42:16 +01:00

149 lines
5.2 KiB
Python

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