cpdctrl-gui/cpdctrl_gui/ui/widgets/metadata_input.py
2025-03-17 15:30:25 +01:00

154 lines
5.4 KiB
Python

from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QSpacerItem, QSpinBox, QDoubleSpinBox
from PyQt6.QtWidgets import QGridLayout, QMenu, QListWidget, QPushButton, QFormLayout, QDialog, QDialogButtonBox
from PyQt6.QtCore import pyqtSignal
"""
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):
metadataChanged = pyqtSignal()
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())
self.metadataChanged.emit()
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))
w_input.editingFinished.connect(self.metadataChanged)
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()
self.metadataChanged.emit()
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