import logging _log = logging.getLogger(__name__) import re from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtWidgets import QLineEdit, QTextEdit, QGridLayout, QVBoxLayout, QWidget, QDialog, QDialogButtonBox, QPushButton from PyQt5.QtWidgets import QDoubleSpinBox, QSpinBox, QCheckBox, QLabel def Spinner(value, min=None, max=None, decimals=None, single_step=None, prefix=None, suffix=None): if type(value) is float: s = QDoubleSpinBox() else: s = QSpinBox() if min is not None: s.setMinimum(min) if max is not None: s.setMaximum(max) try: if decimals is not None: s.setDecimals(decimals) except: pass if single_step is not None: s.setSingleStep(single_step) if prefix is not None: s.setPrefix(prefix) if suffix is not None: s.setSuffix(suffix) return s def Checkbox(value: bool, label: str): box = QCheckBox(label) box.setChecked(value) return box class MagicLabel(QLabel): entered = pyqtSignal(str) left = pyqtSignal() def __init__(self, label, help_str, help_buddy=None): super(MagicLabel, self).__init__(label) self._help_buddy = help_buddy self._help_str = help_str def enterEvent(self, event): print(self._help_str) if self._help_buddy: print(f"yay!: {self._help_str}") else: super().enterEvent(event) def leaveEvent(self, event): super().leaveEvent(event) class GenericDialog(QDialog): def __init__(self, parent=None, settings=None, title=None, message=None, inputs={}, use_buttons=True): super(GenericDialog, self).__init__() self.settings = settings # self.setModal(True) self.setAccessibleName("genericDialog") self.setLayout(QVBoxLayout()) bbox = QDialogButtonBox() doneButton = QPushButton("Done") doneButton.clicked.connect(self.accept) bbox.addButton(doneButton, QDialogButtonBox.AcceptRole) if use_buttons: undoButton = QPushButton("Undo") undoButton.clicked.connect(self.undo_all) discardButton = QPushButton("Discard") discardButton.clicked.connect(self.discard) bbox.addButton(undoButton, QDialogButtonBox.ActionRole) bbox.addButton(discardButton, QDialogButtonBox.RejectRole) lb = QLabel(title) lb.setAccessibleName("genericDialogTitle") lb.setAlignment(Qt.AlignHCenter) self.layout().addWidget(lb) gbox = QWidget() self.layout().addWidget(gbox) gbox.setLayout(QVBoxLayout()) mlabel = QLabel(message) mlabel.setAccessibleName("genericDialogMessage") gbox.layout().addWidget(mlabel) self.setWindowTitle(title) self.contents = QWidget() self.contents.setAccessibleName("genericDialogContents") self.layout().addWidget(self.contents) self.layout().addStretch() self.layout().addWidget(bbox) if not inputs: inputs = {'test': ('Text test', 'initial', QLineEdit()), 'float': ('Text test 2', 3.14, QDoubleSpinBox(), 5.0), 'integer': ('Text test 2', 42, QSpinBox()), } self.contents.setLayout(QGridLayout()) layout = self.contents.layout() layout.setColumnStretch(1, 2) self.results = {} self._undo = {} for row, config in enumerate(inputs.items()): key, config = config config = list(config) if len(config) == 3: config.extend([""]) label, value, widget, help_str = config labwig = MagicLabel(label, help_str) layout.addWidget(labwig, row, 0) layout.addWidget(widget, row, 1) if hasattr(widget, 'setChecked'): widget.setChecked(value) widget.stateChanged.connect(lambda v, k=key, w=widget.isChecked: self.update_results(k, w)) elif hasattr(widget, 'setValue'): widget.setValue(value) widget.valueChanged.connect(lambda v, k=key, w=widget.value: self.update_results(k, w)) elif hasattr(widget, 'setPlainText'): widget.setPlainText(value) widget.textChanged.connect(lambda k=key, w=widget.toPlainText: self.update_results(k, w)) elif hasattr(widget, 'setText'): widget.setText(value) widget.editingFinished.connect(lambda k=key, w=widget.text: self.update_results(k, w)) self.results[key] = value self._undo[key] = (widget, value) def undo_all(self): for k, uinfo in self._undo.items(): widget, value = uinfo if hasattr(widget, 'setChecked'): widget.setChecked(value) elif hasattr(widget, 'setValue'): widget.setValue(value) elif hasattr(widget, 'setPlainText'): widget.setPlainText(value) elif hasattr(widget, 'setText'): widget.setText(value) def discard(self): self.undo_all() self.reject() def set_value(self, widget, url): m = re.match("^(.*?)://(.*)", url, re.MULTILINE|re.DOTALL) method, value = m.groups() if method == "setValue": widget.setValue(float(value)) elif method == "setText": widget.setText(value) else: widget.setPlainText(value) if self.settings is not None: self.settings.setValue(k, value) def update_results(self, key, get_value): value = get_value() _log.debug(f"settigns {key} => {value}") self.results[key] = value if self.settings is not None: self.settings.setValue(k, value) def keyPressEvent(self, key_event): if key_event != Qt.Key_Escape: super().keyPressEvent(key_event) if __name__ == '__main__': import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) title = 'A Lovely Title' message = 'thisn sohudl explain what do tod ow with this dialog and in fact we should make sure that it will be wrapping aorufnnfb' main = GenericDialog(title=title, message=message, inputs={ 'bananas': ('Number of Bananas', 2, QSpinBox(), 10), 'apples': ('Number of Apples', 5.5, QDoubleSpinBox(), 23), 'really': ('Indeed?', True, Checkbox(True, "Contr")), 'words': ('Words', 'words go here', QLineEdit(), "a few words"), 'texts': ('Words', 'big words go here', QTextEdit(), """quite a drama\n to write here, really something awfull long"""), }) if main.exec(): print(main.results) for k, v in main.results.items(): print('{} {} = {}'.format(k, type(v), v)) sys.exit(app.exec_())