import json from Qt import QtWidgets, QtCore, QtGui from . import config from .base import PypeConfigurationWidget, TypeToKlass from .widgets import ( ClickableWidget, ExpandingWidget, ModifiedIntSpinBox, ModifiedFloatSpinBox ) from .lib import NOT_SET, AS_WIDGET class SchemeGroupHierarchyBug(Exception): def __init__(self, msg=None): if not msg: # TODO better message msg = "SCHEME BUG: Attribute `is_group` is mixed in the hierarchy" super(SchemeGroupHierarchyBug, self).__init(msg) class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._as_widget = values is AS_WIDGET self._parent = parent any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.is_group = is_group self.is_modified = False self._is_overriden = False self._state = None super(BooleanWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.checkbox = QtWidgets.QCheckBox() self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) layout.addWidget(label_widget) layout.addWidget(self.checkbox) if not self._as_widget: self.label_widget = label_widget self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: self.checkbox.setChecked(value) self.origin_value = self.item_value() self.checkbox.stateChanged.connect(self._on_value_change) def set_value(self, value, origin_value=False): self.checkbox.setChecked(value) if origin_value: self.origin_value = self.item_value() self._on_value_change() def reset_value(self): self.set_value(self.origin_value) def clear_value(self): self.reset_value() @property def child_modified(self): return self.is_modified @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True self.update_style() self.value_changed.emit(self) def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden is_modified = self.is_modified state = self.style_state(is_overriden, is_modified) if self._state == state: return if self._as_widget: property_name = "input-state" else: property_name = "state" self.label_widget.setProperty(property_name, state) self.label_widget.style().polish(self.label_widget) self._state = state def item_value(self): return self.checkbox.isChecked() def config_value(self): return {self.key: self.item_value()} class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent self._as_widget = values is AS_WIDGET any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.is_group = is_group self.is_modified = False self._is_overriden = False self._state = None super(IntegerWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.int_input = ModifiedIntSpinBox() if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget) layout.addWidget(self.int_input) if not self._as_widget: self.label_widget = label_widget self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: self.int_input.setValue(value) self.origin_value = self.item_value() self.int_input.valueChanged.connect(self._on_value_change) @property def child_modified(self): return self.is_modified @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def set_value(self, value, origin_value=False): self.int_input.setValue(value) if origin_value: self.origin_value = self.item_value() self._on_value_change() def clear_value(self): self.set_value(0) def reset_value(self): self.set_value(self.origin_value) def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True self.update_style() self.value_changed.emit(self) def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden is_modified = self.is_modified state = self.style_state(is_overriden, is_modified) if self._state == state: return if self._as_widget: property_name = "input-state" widget = self.int_input else: property_name = "state" widget = self.label_widget widget.setProperty(property_name, state) widget.style().polish(widget) def item_value(self): return self.int_input.value() def config_value(self): return {self.key: self.item_value()} class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent self._as_widget = values is AS_WIDGET any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.is_group = is_group self.is_modified = False self._is_overriden = False self._state = None super(FloatWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.float_input = ModifiedFloatSpinBox() decimals = input_data.get("decimals", 5) maximum = input_data.get("maximum") minimum = input_data.get("minimum") self.float_input.setDecimals(decimals) if maximum is not None: self.float_input.setMaximum(float(maximum)) if minimum is not None: self.float_input.setMinimum(float(minimum)) if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget) layout.addWidget(self.float_input) if not self._as_widget: self.label_widget = label_widget self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: self.float_input.setValue(value) self.origin_value = self.item_value() self.float_input.valueChanged.connect(self._on_value_change) @property def child_modified(self): return self.is_modified @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def set_value(self, value, origin_value=False): self.float_input.setValue(value) if origin_value: self.origin_value = self.item_value() self._on_value_change() def reset_value(self): self.set_value(self.origin_value) def clear_value(self): self.set_value(0) def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True self.update_style() self.value_changed.emit(self) def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden is_modified = self.is_modified state = self.style_state(is_overriden, is_modified) if self._state == state: return if self._as_widget: property_name = "input-state" widget = self.float_input else: property_name = "state" widget = self.label_widget widget.setProperty(property_name, state) widget.style().polish(widget) def item_value(self): return self.float_input.value() def config_value(self): return {self.key: self.item_value()} class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent self._as_widget = values is AS_WIDGET any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.is_group = is_group self.is_modified = False self._is_overriden = False self._state = None super(TextSingleLineWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.text_input = QtWidgets.QLineEdit() if not self._as_widget and not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget) layout.addWidget(self.text_input) if not self._as_widget: self.label_widget = label_widget self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: self.text_input.setText(value) self.origin_value = self.item_value() self.text_input.textChanged.connect(self._on_value_change) @property def child_modified(self): return self.is_modified @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def set_value(self, value, origin_value=False): self.text_input.setText(value) if origin_value: self.origin_value = self.item_value() self._on_value_change() def reset_value(self): self.set_value(self.origin_value) def clear_value(self): self.set_value("") def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True self.update_style() self.value_changed.emit(self) def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden is_modified = self.is_modified state = self.style_state(is_overriden, is_modified) if self._state == state: return if self._as_widget: property_name = "input-state" widget = self.text_input else: property_name = "state" widget = self.label_widget widget.setProperty(property_name, state) widget.style().polish(widget) def item_value(self): return self.text_input.text() def config_value(self): return {self.key: self.item_value()} class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent self._as_widget = values is AS_WIDGET any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.is_group = is_group self.is_modified = False self._is_overriden = False self._state = None super(TextMultiLineWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.text_input = QtWidgets.QPlainTextEdit() if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget) layout.addWidget(self.text_input) self.label_widget = label_widget self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: self.text_input.setPlainText(value) self.origin_value = self.item_value() self.text_input.textChanged.connect(self._on_value_change) @property def child_modified(self): return self.is_modified @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def set_value(self, value, origin_value=False): self.text_input.setPlainText(value) if origin_value: self.origin_value = self.item_value() self._on_value_change() def reset_value(self): self.set_value(self.origin_value) def clear_value(self): self.set_value("") def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True self.update_style() self.value_changed.emit(self) def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden is_modified = self.is_modified state = self.style_state(is_overriden, is_modified) if self._state == state: return if self._as_widget: property_name = "input-state" widget = self.text_input else: property_name = "state" widget = self.label_widget widget.setProperty(property_name, state) widget.style().polish(widget) def item_value(self): return self.text_input.toPlainText() def config_value(self): return {self.key: self.item_value()} class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent self._as_widget = values is AS_WIDGET any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.is_group = is_group self.is_modified = False self._is_overriden = False self._state = None self.is_valid = None super(RawJsonWidget, self).__init__(parent) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.text_input = QtWidgets.QPlainTextEdit() self.text_input.setObjectName("RawJson") self.text_input.setTabStopDistance( QtGui.QFontMetricsF( self.text_input.font() ).horizontalAdvance(" ") * 4 ) if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget) layout.addWidget(self.text_input) self.label_widget = label_widget self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: self.text_input.setPlainText(value) self.origin_value = self.item_value() self.text_input.textChanged.connect(self._on_value_change) @property def child_modified(self): return self.is_modified @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def validate_value(self, value): if not value: return True try: json.dumps(value) return True except Exception: return False def set_value(self, value, origin_value=False): is_valid = self.validate_value(value) if is_valid: value = json.dumps(value, indent=4) self.text_input.setPlainText(value) if origin_value: self.origin_value = self.item_value() self._on_value_change() def reset_value(self): self.set_value(self.origin_value) def clear_value(self): self.set_value("") def _on_value_change(self, item=None): value = self.item_value() self.is_modified = value != self.origin_value if self.is_overidable: self._is_overriden = True is_valid = self.validate_value(value) if is_valid != self.is_valid: self.is_valid = is_valid if is_valid: state = "" else: state = "invalid" self.text_input.setProperty("state", state) self.text_input.style().polish(self.text_input) self.update_style() self.value_changed.emit(self) def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden is_modified = self.is_modified state = self.style_state(is_overriden, is_modified) if self._state == state: return if self._as_widget: property_name = "input-state" widget = self.text_input else: property_name = "state" widget = self.label_widget widget.setProperty(property_name, state) widget.style().polish(widget) def item_value(self): return self.text_input.toPlainText() def config_value(self): return {self.key: self.item_value()} class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 value_changed = QtCore.Signal(object) def __init__(self, parent): super(TextListItem, self).__init__(parent) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) self.text_input = QtWidgets.QLineEdit() self.add_btn = QtWidgets.QPushButton("+") self.remove_btn = QtWidgets.QPushButton("-") self.add_btn.setProperty("btn-type", "text-list") self.remove_btn.setProperty("btn-type", "text-list") layout.addWidget(self.text_input, 1) layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) self.add_btn.setFixedSize(self._btn_size, self._btn_size) self.remove_btn.setFixedSize(self._btn_size, self._btn_size) self.add_btn.clicked.connect(self.on_add_clicked) self.remove_btn.clicked.connect(self.on_remove_clicked) self.text_input.textChanged.connect(self._on_value_change) self.is_single = False def _on_value_change(self, item=None): self.value_changed.emit(self) def row(self): return self.parent().input_fields.index(self) def on_add_clicked(self): self.parent().add_row(row=self.row() + 1) def on_remove_clicked(self): if self.is_single: self.text_input.setText("") else: self.parent().remove_row(self) def config_value(self): return self.text_input.text() class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): super(TextListSubWidget, self).__init__(parent) self.setObjectName("TextListSubWidget") layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(5, 5, 5, 5) layout.setSpacing(5) self.setLayout(layout) self.input_fields = [] self.add_row() self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: self.set_value(value) self.origin_value = self.item_value() def set_value(self, value, origin_value=False): for input_field in self.input_fields: self.remove_row(input_field) for item_text in value: self.add_row(text=item_text) if origin_value: self.origin_value = self.item_value() self._on_value_change() def reset_value(self): self.set_value(self.origin_value) def clear_value(self): self.set_value([]) def _on_value_change(self, item=None): self.value_changed.emit(self) def count(self): return len(self.input_fields) def add_row(self, row=None, text=None): # Create new item item_widget = TextListItem(self) # Set/unset if new item is single item current_count = self.count() if current_count == 0: item_widget.is_single = True elif current_count == 1: for _input_field in self.input_fields: _input_field.is_single = False item_widget.value_changed.connect(self._on_value_change) if row is None: self.layout().addWidget(item_widget) self.input_fields.append(item_widget) else: self.layout().insertWidget(row, item_widget) self.input_fields.insert(row, item_widget) # Set text if entered text is not None # else (when add button clicked) trigger `_on_value_change` if text is not None: item_widget.text_input.setText(text) else: self._on_value_change() self.parent().updateGeometry() def remove_row(self, item_widget): item_widget.value_changed.disconnect() self.layout().removeWidget(item_widget) self.input_fields.remove(item_widget) item_widget.setParent(None) item_widget.deleteLater() current_count = self.count() if current_count == 0: self.add_row() elif current_count == 1: for _input_field in self.input_fields: _input_field.is_single = True self._on_value_change() self.parent().updateGeometry() def item_value(self): output = [] for item in self.input_fields: text = item.config_value() if text: output.append(text) return output def config_value(self): return {self.key: self.item_value()} class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.is_modified = False self.is_group = is_group self._is_overriden = False self._state = None super(TextListWidget, self).__init__(parent) self.setObjectName("TextListWidget") layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) if not label_widget: label = input_data["label"] label_widget = QtWidgets.QLabel(label) layout.addWidget(label_widget) self.label_widget = label_widget # keys = list(parent_keys) # keys.append(input_data["key"]) # self.keys = keys self.value_widget = TextListSubWidget( input_data, values, parent_keys, self ) self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) self.value_widget.value_changed.connect(self._on_value_change) # self.value_widget.se self.key = input_data["key"] layout.addWidget(self.value_widget) self.setLayout(layout) self.origin_value = self.item_value() @property def child_modified(self): return self.is_modified @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def _on_value_change(self, item=None): self.is_modified = self.item_value() != self.origin_value if self.is_overidable: self._is_overriden = True self.update_style() self.value_changed.emit(self) def set_value(self, value, origin_value=False): self.value_widget.set_value(value) if origin_value: self.origin_value = self.item_value() self._on_value_change() def reset_value(self): self.set_value(self.origin_value) def clear_value(self): self.set_value([]) def update_style(self, is_overriden=None): if is_overriden is None: is_overriden = self.is_overriden is_modified = self.is_modified state = self.style_state(is_overriden, is_modified) if self._state == state: return self.label_widget.setProperty("state", state) self.label_widget.style().polish(self.label_widget) def item_value(self): return self.value_widget.config_value() def config_value(self): return {self.key: self.item_value()} class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): if values is AS_WIDGET: raise TypeError("Can't use \"{}\" as widget item.".format( self.__class__.__name__ )) self._parent = parent any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() self.any_parent_is_group = any_parent_is_group self.is_modified = False self._is_overriden = False self.is_group = is_group self._state = None self._child_state = None super(DictExpandWidget, self).__init__(parent) self.setObjectName("DictExpandWidget") top_part = ClickableWidget(parent=self) button_size = QtCore.QSize(5, 5) button_toggle = QtWidgets.QToolButton(parent=top_part) button_toggle.setProperty("btn-type", "expand-toggle") button_toggle.setIconSize(button_size) button_toggle.setArrowType(QtCore.Qt.RightArrow) button_toggle.setCheckable(True) button_toggle.setChecked(False) label = input_data["label"] button_toggle_text = QtWidgets.QLabel(label, parent=top_part) button_toggle_text.setObjectName("ExpandLabel") layout = QtWidgets.QHBoxLayout(top_part) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) layout.addWidget(button_toggle) layout.addWidget(button_toggle_text) top_part.setLayout(layout) main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(9, 9, 9, 9) content_widget = QtWidgets.QWidget(self) content_widget.setVisible(False) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 3, 3) main_layout.addWidget(top_part) main_layout.addWidget(content_widget) self.setLayout(main_layout) self.setAttribute(QtCore.Qt.WA_StyledBackground) self.top_part = top_part self.button_toggle = button_toggle self.button_toggle_text = button_toggle_text self.content_widget = content_widget self.content_layout = content_layout self.top_part.clicked.connect(self._top_part_clicked) self.button_toggle.clicked.connect(self.toggle_content) self.input_fields = [] self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) def _top_part_clicked(self): self.toggle_content(not self.button_toggle.isChecked()) def toggle_content(self, *args): if len(args) > 0: checked = args[0] else: checked = self.button_toggle.isChecked() arrow_type = QtCore.Qt.RightArrow if checked: arrow_type = QtCore.Qt.DownArrow self.button_toggle.setChecked(checked) self.button_toggle.setArrowType(arrow_type) self.content_widget.setVisible(checked) self.parent().updateGeometry() def resizeEvent(self, event): super(DictExpandWidget, self).resizeEvent(event) self.content_widget.updateGeometry() @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def _on_value_change(self, item=None): if self.is_group: if self.is_overidable: self._is_overriden = True # TODO update items if item is not None: is_overriden = self.is_overriden for _item in self.input_fields: if _item is not item: _item.update_style(is_overriden) self.value_changed.emit(self) self.update_style() def update_style(self, is_overriden=None): child_modified = self.child_modified if is_overriden is None: child_overriden = self.child_overriden child_state = self.style_state(child_overriden, child_modified) if child_state: child_state = "child-{}".format(child_state) if child_state != self._child_state: self.setProperty("state", child_state) self.style().polish(self) self._child_state = child_state if is_overriden is None: is_overriden = self.is_overriden if child_modified and not is_overriden: state = self.default_state else: state = self.style_state(is_overriden, child_modified) if self._state == state: return self.button_toggle_text.setProperty("state", state) self.button_toggle_text.style().polish(self.button_toggle_text) self._state = state @property def child_modified(self): for input_field in self.input_fields: if input_field.child_modified: return True return False @property def child_overriden(self): for input_field in self.input_fields: if input_field.child_overriden: return True return False def item_value(self): output = {} for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts output.update(input_field.config_value()) return output def config_value(self): return {self.key: self.item_value()} @property def is_overidable(self): return self._parent.is_overidable def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] klass = TypeToKlass.types.get(item_type) item = klass( child_configuration, values, self.keys, self ) item.value_changed.connect(self._on_value_change) self.content_layout.addWidget(item) self.input_fields.append(item) return item class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() self.any_parent_is_group = any_parent_is_group self.is_modified = False self.is_group = is_group super(DictInvisible, self).__init__(parent) self.setObjectName("DictInvisible") self.setAttribute(QtCore.Qt.WA_StyledBackground) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(5) self.input_fields = [] if "key" not in input_data: print(json.dumps(input_data, indent=4)) self.key = input_data["key"] self.keys = list(parent_keys) self.keys.append(self.key) for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) @property def is_overriden(self): return self._parent.is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def child_modified(self): for input_field in self.input_fields: if input_field.child_modified: return True return False @property def child_overriden(self): for input_field in self.input_fields: if input_field.child_overriden: return True return False def item_value(self): output = {} for input_field in self.input_fields: # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts output.update(input_field.config_value()) return output def config_value(self): return {self.key: self.item_value()} def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] if item_type == "schema": for _schema in child_configuration["children"]: children = config.gui_schema(_schema) self.add_children_gui(children, values) return klass = TypeToKlass.types.get(item_type) item = klass( child_configuration, values, self.keys, self ) self.layout().addWidget(item) self.input_fields.append(item) return item class DictFormWidget(QtWidgets.QWidget): value_changed = QtCore.Signal(object) def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group self.any_parent_is_group = any_parent_is_group self.is_modified = False self.is_overriden = False self.is_group = False super(DictFormWidget, self).__init__(parent) self.input_fields = {} self.content_layout = QtWidgets.QFormLayout(self) self.keys = list(parent_keys) for child_data in input_data.get("children", []): self.add_children_gui(child_data, values) def _on_value_change(self, item=None): self.value_changed.emit(self) def item_value(self): output = {} for input_field in self.input_fields.values(): # TODO maybe merge instead of update should be used # NOTE merge is custom function which merges 2 dicts output.update(input_field.config_value()) return output @property def child_modified(self): for input_field in self.input_fields.values(): if input_field.child_modified: return True return False @property def child_overriden(self): for input_field in self.input_fields.values(): if input_field.child_overriden: return True return False @property def is_overidable(self): return self._parent.is_overidable def config_value(self): return self.item_value() def add_children_gui(self, child_configuration, values): item_type = child_configuration["type"] key = child_configuration["key"] # Pop label to not be set in child label = child_configuration["label"] klass = TypeToKlass.types.get(item_type) label_widget = QtWidgets.QLabel(label) item = klass( child_configuration, values, self.keys, self, label_widget ) item.value_changed.connect(self._on_value_change) self.content_layout.addRow(label_widget, item) self.input_fields[key] = item return item class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): _btn_size = 20 value_changed = QtCore.Signal(object) def __init__(self, object_type, parent): self._parent = parent super(ModifiableDictItem, self).__init__(parent) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(3) ItemKlass = TypeToKlass.types[object_type] self.key_input = QtWidgets.QLineEdit() self.key_input.setObjectName("DictKey") self.value_input = ItemKlass( {}, AS_WIDGET, [], self, None ) self.add_btn = QtWidgets.QPushButton("+") self.remove_btn = QtWidgets.QPushButton("-") self.add_btn.setProperty("btn-type", "text-list") self.remove_btn.setProperty("btn-type", "text-list") layout.addWidget(self.key_input, 0) layout.addWidget(self.value_input, 1) layout.addWidget(self.add_btn, 0) layout.addWidget(self.remove_btn, 0) self.add_btn.setFixedSize(self._btn_size, self._btn_size) self.remove_btn.setFixedSize(self._btn_size, self._btn_size) self.add_btn.clicked.connect(self.on_add_clicked) self.remove_btn.clicked.connect(self.on_remove_clicked) self.key_input.textChanged.connect(self._on_value_change) self.value_input.value_changed.connect(self._on_value_change) self.origin_key = self._key() self.origin_value = self.value_input.item_value() self.is_single = False def _key(self): return self.key_input.text() def _on_value_change(self, item=None): self.update_style() self.value_changed.emit(self) @property def is_group(self): return self._parent.is_group @property def any_parent_is_group(self): return self._parent.any_parent_is_group @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): return self._parent.is_overriden def is_key_modified(self): return self._key() != self.origin_key def is_value_modified(self): return self.value_input.is_modified @property def is_modified(self): return self.is_value_modified() or self.is_key_modified() def update_style(self, is_overriden=None): if self.is_key_modified(): state = "modified" else: state = "" self.key_input.setProperty("state", state) self.key_input.style().polish(self.key_input) def row(self): return self.parent().input_fields.index(self) def on_add_clicked(self): self.parent().add_row(row=self.row() + 1) def on_remove_clicked(self): if self.is_single: self.value_input.clear_value() self.key_input.setText("") else: self.parent().remove_row(self) def config_value(self): key = self.key_input.text() value = self.value_input.item_value() if not key: return {} return {key: value} class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): value_changed = QtCore.Signal(object) def __init__(self, input_data, values, parent_keys, parent): self._parent = parent super(ModifiableDictSubWidget, self).__init__(parent) self.setObjectName("ModifiableDictSubWidget") layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(5, 5, 5, 5) layout.setSpacing(5) self.setLayout(layout) self.input_fields = [] self.object_type = input_data["object_type"] self.key = input_data["key"] keys = list(parent_keys) keys.append(self.key) self.keys = keys value = self.value_from_values(values) if value is not NOT_SET: for item_key, item_value in value.items(): self.add_row(key=item_key, value=item_value) if self.count() == 0: self.add_row() self.origin_value = self.config_value() @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): return self._parent.is_overriden @property def is_group(self): return self._parent.is_group @property def any_parent_is_group(self): return self._parent.any_parent_is_group def _on_value_change(self, item=None): self.value_changed.emit(self) def count(self): return len(self.input_fields) def add_row(self, row=None, key=None, value=None): # Create new item item_widget = ModifiableDictItem(self.object_type, self) # Set/unset if new item is single item current_count = self.count() if current_count == 0: item_widget.is_single = True elif current_count == 1: for _input_field in self.input_fields: _input_field.is_single = False item_widget.value_changed.connect(self._on_value_change) if row is None: self.layout().addWidget(item_widget) self.input_fields.append(item_widget) else: self.layout().insertWidget(row, item_widget) self.input_fields.insert(row, item_widget) # Set value if entered value is not None # else (when add button clicked) trigger `_on_value_change` if value is not None and key is not None: item_widget.origin_key = key item_widget.key_input.setText(key) item_widget.value_input.set_value(value, origin_value=True) else: self._on_value_change() self.parent().updateGeometry() def remove_row(self, item_widget): item_widget.value_changed.disconnect() self.layout().removeWidget(item_widget) self.input_fields.remove(item_widget) item_widget.setParent(None) item_widget.deleteLater() current_count = self.count() if current_count == 0: self.add_row() elif current_count == 1: for _input_field in self.input_fields: _input_field.is_single = True self._on_value_change() self.parent().updateGeometry() def config_value(self): output = {} for item in self.input_fields: item_value = item.config_value() if item_value: output.update(item_value) return output class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): def __init__( self, input_data, values, parent_keys, parent, label_widget=None ): self._parent = parent any_parent_is_group = parent.is_group if not any_parent_is_group: any_parent_is_group = parent.any_parent_is_group is_group = input_data.get("is_group", False) if is_group and any_parent_is_group: raise SchemeGroupHierarchyBug() if not any_parent_is_group and not is_group: is_group = True self.any_parent_is_group = any_parent_is_group self.is_modified = False self.child_modified = False self._is_overriden = False self.is_group = is_group super(ModifiableDict, self).__init__(input_data["label"], parent) self.setObjectName("ModifiableDict") self.value_widget = ModifiableDictSubWidget( input_data, values, parent_keys, self ) self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) self.value_widget.value_changed.connect(self._on_value_change) self.set_content_widget(self.value_widget) self.key = input_data["key"] self.origin_value = self.item_value() def _on_value_change(self, item=None): self.child_modified = self.item_value() != self.origin_value if self.is_group and self.is_overidable: self._is_overriden = True self.update_style() @property def child_overriden(self): return self._is_overriden @property def is_overidable(self): return self._parent.is_overidable @property def is_overriden(self): if self._is_overriden: return self._is_overriden return self._parent.is_overriden def update_style(self, is_overriden=None): child_modified = self.child_modified if is_overriden is None: child_overriden = self.child_overriden child_state = self.style_state(child_overriden, child_modified) if child_state != self._child_state: self.setProperty("state", child_state) self.style().polish(self) self._child_state = child_state if is_overriden is None: is_overriden = self.is_overriden if child_modified and not is_overriden: state = self.default_state else: state = self.style_state(self.is_overriden, child_modified) self.label_widget.setProperty("state", state) self.label_widget.style().polish(self.label_widget) self._state = state def item_value(self): return self.value_widget.config_value() def config_value(self): return {self.key: self.item_value()} TypeToKlass.types["boolean"] = BooleanWidget TypeToKlass.types["text-singleline"] = TextSingleLineWidget TypeToKlass.types["text-multiline"] = TextMultiLineWidget TypeToKlass.types["raw-json"] = RawJsonWidget TypeToKlass.types["int"] = IntegerWidget TypeToKlass.types["float"] = FloatWidget TypeToKlass.types["dict-expanding"] = DictExpandWidget TypeToKlass.types["dict-form"] = DictFormWidget TypeToKlass.types["dict-invisible"] = DictInvisible TypeToKlass.types["dict-modifiable"] = ModifiableDict TypeToKlass.types["list-text"] = TextListWidget