from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import QWidget, QLabel, QFormLayout, QSpinBox, QDoubleSpinBox, QLineEdit, QHBoxLayout, QPushButton, \ QFileDialog, QCheckBox, QVBoxLayout, QGroupBox from PyQt6.QtCore import pyqtSignal, Qt from cpdctrl.utility.config_file import ConfigFile class FileSelection(QWidget): """ Widget allowing the user to select a file or directory. """ valueChanged = pyqtSignal(str) def __init__(self, init_file="", filemode=QFileDialog.FileMode.AnyFile, parent=None): super().__init__(parent) self.setLayout(QHBoxLayout()) self.w_edit = QLineEdit() self.w_edit.setText(init_file) self.w_btn = QPushButton() self.w_btn.setIcon(QIcon.fromTheme(QIcon.ThemeIcon.FolderOpen)) self.layout().addWidget(self.w_edit) self.layout().addWidget(self.w_btn) # open file dialog when button is clicked self.w_btn.clicked.connect(self._open_file_dialog) # emit value_changed on self when w_edit emits value_changed self.w_edit.textChanged.connect(self.valueChanged) self.file_mode = filemode # remove all spacing and padding from the layout self.layout().setContentsMargins(0, 0, 0, 0) def setValue(self, value: str): self.w_edit.setText(value) def value(self) -> str: return self.w_edit.text() def _open_file_dialog(self): dialog = QFileDialog(self) # only directories dialog.setFileMode(self.file_mode) if dialog.exec(): dirname = dialog.selectedFiles()[0] self.w_edit.setText(dirname) class SettingsForm(QWidget): """ Form that is connected to a config file instance """ def __init__(self, config_file: ConfigFile, parent=None): super().__init__(parent) self.setLayout(QVBoxLayout()) self.ws_form: dict[str, QWidget] = {} self.ls_form: dict[str, QLabel] = {} self.ws_groups: dict[str, QFormLayout] = {} self.config_file = config_file w_default = QWidget() w_default.setLayout(QFormLayout()) self.ws_groups["default"] = w_default.layout() self.layout().addWidget(w_default) def __contains__(self, item): return item in self.ws_form def add_group(self, key, name): """ Add a group box Parameters ---------- key The key to be used with add_form_row name The name to display """ w_group = QGroupBox(parent=None, title=name) w_group.setLayout(QFormLayout()) self.layout().addWidget(w_group) self.ws_groups[key] = w_group.layout() def add_form_row(self, key: str, label: str, default_value, widget: QWidget, tooltip: str = None, group:str="default"): """ Add a row to the form. Uses the value from the config file corresponding to or the default value. Parameters ---------- key label: str Label for the form widget default_value: The default value to use for the widget widget: QWidget Widget to add to the form tooltip: str Tooltip for the widget group: str Group to add the row to. Returns ------- """ if not group in self.ws_groups: raise ValueError(f"Can not add form row '{key}': Group '{group}' does not exist.") if tooltip: widget.setToolTip(tooltip) value = self.config_file.get_or(key, default_value) # set the value depending on the type of the widget if isinstance(widget, QSpinBox) or isinstance(widget, QDoubleSpinBox): widget.setValue(value) widget.valueChanged.connect(lambda v: self.value_updated(key, v)) elif isinstance(widget, QCheckBox): widget.setChecked(value) widget.stateChanged.connect(lambda v: self.value_updated(key, v)) elif isinstance(widget, FileSelection): widget.setValue(value) widget.valueChanged.connect(lambda v: self.value_updated(key, v)) elif isinstance(widget, QLineEdit): widget.setText(value) widget.textChanged.connect(lambda v: self.value_updated(key, v)) else: raise ValueError(f"Unknown widget type: {type(widget)}") l_label = QLabel(label) self.ws_groups[group].addRow(l_label, widget) self.ws_form[key] = widget self.ls_form[key] = l_label def value_updated(self, key, value): """ Update the value in the config file when it is updated in the widget Parameters ---------- key value """ self.config_file.set(key, value) def set_value(self, key, value): """ Set the value of the widget with the given key. "" Parameters ---------- key value Returns ------- """ if key in self.ws_form: # set depending on widget type if isinstance(self.ws_form[key], QSpinBox) or isinstance(self.ws_form[key], QDoubleSpinBox): self.ws_form[key].setValue(value) elif isinstance(self.ws_form[key], QCheckBox): self.ws_form[key].setChecked(value) elif isinstance(self.ws_form[key], FileSelection): self.ws_form[key].setValue(value) elif isinstance(self.ws_form[key], QLineEdit): self.ws_form[key].setText(value) else: raise ValueError(f"Unknown widget type: {type(self.ws_form[key])}") else: raise ValueError(f"Unknown key: {key}") def get_value(self, key): """ Get the value of the widget with the given key. Parameters ---------- key Returns ------- """ if key in self.ws_form: # get depending on widget type if isinstance(self.ws_form[key], QSpinBox) or isinstance(self.ws_form[key], QDoubleSpinBox) or isinstance(self.ws_form, FileSelection): return self.ws_form[key].value() elif isinstance(self.ws_form[key], QCheckBox): return self.ws_form[key].isChecked() elif isinstance(self.ws_form[key], QLineEdit): return self.ws_form[key].text() else: raise ValueError(f"Unknown widget type: {type(self.ws_form[key])}") else: raise ValueError(f"Unknown key: {key}") def update_alignment(self): """ Give all labels the min width of the widest label, so that all forms are aligned even when in different group boxes """ max_width = 0 for _, l in self.ls_form.items(): w = l.minimumSizeHint().width() if w > max_width: max_width = w for _, l in self.ls_form.items(): l.setMinimumWidth(max_width)