From f82e72fad370e827bc6eb89e275c12c0e3ad3d63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Dec 2020 18:40:25 +0100 Subject: [PATCH 1/6] implemented base of wrapper items --- .../settings/settings/widgets/item_types.py | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 25bbe4a5159..a0d98fd1c5a 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -3505,6 +3505,216 @@ def overrides(self): return value, self.is_group +class WrapperItemWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + allow_actions = False + expand_in_grid = True + is_wrapper_item = True + + def __init__( + self, schema_data, parent, as_widget=False, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(WrapperItemWidget, self).__init__(parent_widget) + + self.input_fields = [] + + self.initial_attributes(schema_data, parent, as_widget) + + if self.as_widget: + raise TypeError( + "Wrapper items ({}) can't be used as widgets.".format( + self.__class__.__name__ + ) + ) + + if self.is_group: + raise TypeError( + "Wrapper items ({}) can't be used as groups.".format( + self.__class__.__name__ + ) + ) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.wrapper_initial_attributes(schema_data) + + def wrapper_initial_attributes(self, schema_data): + """Initialization of attributes for specific wrapper.""" + return + + def create_ui(self, label_widget=None): + """UI implementation.""" + raise NotImplementedError( + "Method `create_ui` not implemented." + ) + + def update_style(self): + """Update items styles.""" + return + + def apply_overrides(self, parent_values): + for item in self.input_fields: + item.apply_overrides(parent_values) + + def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override + + for input_field in self.input_fields: + input_field.discard_changes() + + self._is_modified = self.child_modified + if not self.is_overidable and self.as_widget: + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() + + self._state = None + self._is_overriden = self._was_overriden + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + + def update_default_values(self, value): + for item in self.input_fields: + item.update_default_values(value) + + def update_studio_values(self, value): + for item in self.input_fields: + item.update_studio_values(value) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self.value_changed.emit(self) + if self.any_parent_is_group: + self.hierarchical_style_update() + + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): + return True + return False + + @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.is_overriden or input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + 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.item_value() + + def studio_overrides(self): + if ( + not (self.as_widget or self.any_parent_as_widget) + and not self.has_studio_override + and not self.child_has_studio_override + ): + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return values, self.is_group + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + if METADATA_KEY not in values: + values[METADATA_KEY] = {} + values[METADATA_KEY]["groups"] = groups + return values, self.is_group + + # Proxy for form layout class FormLabel(QtWidgets.QLabel): def __init__(self, *args, **kwargs): From a417d39631424b90a76dc8841a45396e475fe9ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Dec 2020 18:41:39 +0100 Subject: [PATCH 2/6] fixed label click --- .../settings/settings/widgets/item_types.py | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index a0d98fd1c5a..1a04ac1a8a4 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -3717,9 +3717,14 @@ def overrides(self): # Proxy for form layout class FormLabel(QtWidgets.QLabel): - def __init__(self, *args, **kwargs): + def __init__(self, input_field, *args, **kwargs): super(FormLabel, self).__init__(*args, **kwargs) - self.item = None + self.input_field = input_field + + def mouseReleaseEvent(self, event): + if self.input_field: + return self.input_field.show_actions_menu(event) + return super(FormLabel, self).mouseReleaseEvent(event) class DictFormWidget(QtWidgets.QWidget, SettingObject): @@ -3767,9 +3772,10 @@ def add_children_gui(self, child_configuration): klass = TypeToKlass.types.get(item_type) - label_widget = FormLabel(label, self) - item = klass(child_configuration, self) + + label_widget = FormLabel(item, label, self) + item.create_ui(label_widget=label_widget) label_widget.item = item @@ -3781,16 +3787,6 @@ def add_children_gui(self, child_configuration): self.input_fields.append(item) return item - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.RightButton: - position = self.mapFromGlobal(QtGui.QCursor().pos()) - widget = self.childAt(position) - if widget and isinstance(widget, FormLabel): - widget.item.mouseReleaseEvent(event) - event.accept() - return - super(DictFormWidget, self).mouseReleaseEvent(event) - def apply_overrides(self, parent_values): for item in self.input_fields: item.apply_overrides(parent_values) From 5059064228c99aad375ecec00ce03439ac67f4a5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Dec 2020 18:47:09 +0100 Subject: [PATCH 3/6] DictFormWidget inherit from and renamed to FormItemWidget --- .../settings/settings/widgets/item_types.py | 188 +----------------- 1 file changed, 4 insertions(+), 184 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 1a04ac1a8a4..629ae415acf 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -3727,35 +3727,14 @@ def mouseReleaseEvent(self, event): return super(FormLabel, self).mouseReleaseEvent(event) -class DictFormWidget(QtWidgets.QWidget, SettingObject): - value_changed = QtCore.Signal(object) - allow_actions = False - expand_in_grid = True - is_wrapper_item = True - - def __init__( - self, schema_data, parent, as_widget=False, parent_widget=None - ): - if parent_widget is None: - parent_widget = parent - super(DictFormWidget, self).__init__(parent_widget) - - self.initial_attributes(schema_data, parent, as_widget) - - self._as_widget = False - self._is_group = False - - self.input_fields = [] - +class FormItemWidget(WrapperItemWidget): def create_ui(self, label_widget=None): self.content_layout = QtWidgets.QFormLayout(self) self.content_layout.setContentsMargins(0, 0, 0, 0) - for child_data in self.schema_data.get("children", []): + for child_data in self.schema_data["children"]: self.add_children_gui(child_data) - self.setAttribute(QtCore.Qt.WA_TranslucentBackground) - any_visible = False for input_field in self.input_fields: if not input_field.hidden_by_role: @@ -3763,6 +3742,7 @@ def create_ui(self, label_widget=None): break if not any_visible: + self.hidden_by_role = True self.hide() def add_children_gui(self, child_configuration): @@ -3777,7 +3757,6 @@ def add_children_gui(self, child_configuration): label_widget = FormLabel(item, label, self) item.create_ui(label_widget=label_widget) - label_widget.item = item if item.hidden_by_role: label_widget.hide() @@ -3787,165 +3766,6 @@ def add_children_gui(self, child_configuration): self.input_fields.append(item) return item - def apply_overrides(self, parent_values): - for item in self.input_fields: - item.apply_overrides(parent_values) - - def discard_changes(self): - self._is_modified = False - self._is_overriden = self._was_overriden - self._has_studio_override = self._had_studio_override - - for input_field in self.input_fields: - input_field.discard_changes() - - self._is_modified = self.child_modified - if not self.is_overidable and self.as_widget: - if self.has_studio_override: - self._is_modified = self.studio_value != self.item_value() - else: - self._is_modified = self.default_value != self.item_value() - - self._state = None - self._is_overriden = self._was_overriden - - def remove_overrides(self): - self._is_overriden = False - self._is_modified = False - for input_field in self.input_fields: - input_field.remove_overrides() - - def reset_to_pype_default(self): - for input_field in self.input_fields: - input_field.reset_to_pype_default() - self._has_studio_override = False - - def set_studio_default(self): - for input_field in self.input_fields: - input_field.set_studio_default() - - if self.is_group: - self._has_studio_override = True - - def set_as_overriden(self): - if self.is_overriden: - return - - if self.is_group: - self._is_overriden = True - return - - for item in self.input_fields: - item.set_as_overriden() - - def update_default_values(self, value): - for item in self.input_fields: - item.update_default_values(value) - - def update_studio_values(self, value): - for item in self.input_fields: - item.update_studio_values(value) - - def _on_value_change(self, item=None): - if self.ignore_value_changes: - return - - self.value_changed.emit(self) - if self.any_parent_is_group: - self.hierarchical_style_update() - - @property - def child_has_studio_override(self): - for input_field in self.input_fields: - if ( - input_field.has_studio_override - or input_field.child_has_studio_override - ): - return True - return False - - @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.is_overriden or input_field.child_overriden: - return True - return False - - @property - def child_invalid(self): - for input_field in self.input_fields: - if input_field.child_invalid: - return True - return False - - def get_invalid(self): - output = [] - for input_field in self.input_fields: - output.extend(input_field.get_invalid()) - return output - - def hierarchical_style_update(self): - for input_field in self.input_fields: - input_field.hierarchical_style_update() - - 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.item_value() - - def studio_overrides(self): - if ( - not (self.as_widget or self.any_parent_as_widget) - and not self.has_studio_override - and not self.child_has_studio_override - ): - return NOT_SET, False - - values = {} - groups = [] - for input_field in self.input_fields: - value, is_group = input_field.studio_overrides() - if value is not NOT_SET: - values.update(value) - if is_group: - groups.extend(value.keys()) - if groups: - if METADATA_KEY not in values: - values[METADATA_KEY] = {} - values[METADATA_KEY]["groups"] = groups - return values, self.is_group - - def overrides(self): - if not self.is_overriden and not self.child_overriden: - return NOT_SET, False - - values = {} - groups = [] - for input_field in self.input_fields: - value, is_group = input_field.overrides() - if value is not NOT_SET: - values.update(value) - if is_group: - groups.extend(value.keys()) - if groups: - if METADATA_KEY not in values: - values[METADATA_KEY] = {} - values[METADATA_KEY]["groups"] = groups - return values, self.is_group - class LabelWidget(QtWidgets.QWidget): is_input_type = False @@ -4016,7 +3836,7 @@ def __init__(self, configuration, parent): # --------------------------------------------- TypeToKlass.types["dict"] = DictWidget TypeToKlass.types["path-widget"] = PathWidget -TypeToKlass.types["form"] = DictFormWidget +TypeToKlass.types["form"] = FormItemWidget TypeToKlass.types["label"] = LabelWidget TypeToKlass.types["separator"] = SplitterWidget From 943e3e997a3c6503083c31e3308937de6259c4b6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Dec 2020 18:48:15 +0100 Subject: [PATCH 4/6] initial commit of collapsable wrapper --- .../settings/settings/widgets/item_types.py | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 629ae415acf..6eb995eb6c8 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -3767,6 +3767,91 @@ def add_children_gui(self, child_configuration): return item +class CollapsableWrapperItem(WrapperItemWidget): + def wrapper_initial_attributes(self, schema_data): + self.collapsable = schema_data.get("collapsable", True) + self.collapsed = schema_data.get("collapsed", True) + + def create_ui(self, label_widget=None): + content_widget = QtWidgets.QWidget(self) + content_widget.setObjectName("ContentWidget") + content_widget.setProperty("content_state", "") + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + + body_widget = ExpandingWidget(self.schema_data["label"], self) + body_widget.set_content_widget(content_widget) + + label_widget = body_widget.label_widget + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + if not body_widget: + main_layout.addWidget(content_widget) + else: + main_layout.addWidget(body_widget) + + self.label_widget = label_widget + self.body_widget = body_widget + self.content_layout = content_layout + + if self.collapsable: + if not self.collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + + for child_data in self.schema_data.get("children", []): + self.add_children_gui(child_data) + + any_visible = False + for input_field in self.input_fields: + if not input_field.hidden_by_role: + any_visible = True + break + + if not any_visible: + self.hide() + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + row = self.content_layout.rowCount() + if not getattr(klass, "is_input_type", False): + item = klass(child_configuration, self) + self.content_layout.addWidget(item, row, 0, 1, 2) + return item + + label_widget = None + item = klass(child_configuration, self) + if not item.expand_in_grid: + label = child_configuration.get("label") + if label is not None: + label_widget = GridLabelWidget(label, self) + self.content_layout.addWidget(label_widget, row, 0, 1, 1) + + item.create_ui(label_widget=label_widget) + item.value_changed.connect(self._on_value_change) + + if label_widget: + if item.hidden_by_role: + label_widget.hide() + label_widget.input_field = item + self.content_layout.addWidget(item, row, 1, 1, 1) + else: + self.content_layout.addWidget(item, row, 0, 1, 2) + + self.input_fields.append(item) + return item + + def update_style(self): + """Update items styles.""" + return + + class LabelWidget(QtWidgets.QWidget): is_input_type = False @@ -3836,7 +3921,11 @@ def __init__(self, configuration, parent): # --------------------------------------------- TypeToKlass.types["dict"] = DictWidget TypeToKlass.types["path-widget"] = PathWidget + +# Wrappers TypeToKlass.types["form"] = FormItemWidget +TypeToKlass.types["collapsable-wrap"] = CollapsableWrapperItem +# UI items TypeToKlass.types["label"] = LabelWidget TypeToKlass.types["separator"] = SplitterWidget From 04d3a0e9cb57f17ecd9e408cada845972820e9a1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Dec 2020 19:03:44 +0100 Subject: [PATCH 5/6] item update styles --- .../settings/settings/widgets/item_types.py | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 6eb995eb6c8..8ee2e6acb9b 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -3620,6 +3620,7 @@ def _on_value_change(self, item=None): self.value_changed.emit(self) if self.any_parent_is_group: self.hierarchical_style_update() + self.update_style() @property def child_has_studio_override(self): @@ -3847,9 +3848,39 @@ def add_children_gui(self, child_configuration): self.input_fields.append(item) return item - def update_style(self): - """Update items styles.""" - return + def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + self._child_state = child_state + + state = self.style_state( + self.had_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state class LabelWidget(QtWidgets.QWidget): From 6b73060a384c7ab4f7c39afd240788c8638a22d1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 1 Dec 2020 19:13:32 +0100 Subject: [PATCH 6/6] collapsible wrapper added to README and examples --- pype/tools/settings/settings/README.md | 25 ++++++++++++++++--- .../system_schema/example_schema.json | 10 ++++++++ .../settings/settings/widgets/item_types.py | 4 +-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/pype/tools/settings/settings/README.md b/pype/tools/settings/settings/README.md index 046f903f906..4b75dc38a55 100644 --- a/pype/tools/settings/settings/README.md +++ b/pype/tools/settings/settings/README.md @@ -436,11 +436,9 @@ ## Proxy wrappers - should wraps multiple inputs only visually - these does not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled +- can't be used as widget (first item in e.g. `list`, `dict-modifiable`, etc.) ### form -- DEPRECATED - - may be used only in `dict` and `dict-invisible` where is currently used grid layout so form is not needed - - item is kept as still may be used in specific cases - wraps inputs into form look layout - should be used only for Pure inputs @@ -462,3 +460,24 @@ ] } ``` + + +### collapsible-wrap +- wraps inputs into collapsible widget + - looks like `dict` but does not hold `"key"` +- should be used only for Pure inputs + +``` +{ + "type": "collapsible-wrap", + "label": "Collapsible example" + "children": [ + { + "type": "text", + "key": "_example_input_collapsible", + "label": "Example input in collapsible wrapper" + }, { + ... + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json index 00595fd3e4a..9cbb214d86c 100644 --- a/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json +++ b/pype/tools/settings/settings/gui_schemas/system_schema/example_schema.json @@ -418,6 +418,16 @@ ] } ] + }, { + "type": "collapsible-wrap", + "label": "Collapsible Wrapper without key", + "children": [ + { + "type": "text", + "key": "_example_input_collapsible", + "label": "Example input in collapsible wrapper" + } + ] } ] } diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py index 8ee2e6acb9b..013241d0cdf 100644 --- a/pype/tools/settings/settings/widgets/item_types.py +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -3768,7 +3768,7 @@ def add_children_gui(self, child_configuration): return item -class CollapsableWrapperItem(WrapperItemWidget): +class CollapsibleWrapperItem(WrapperItemWidget): def wrapper_initial_attributes(self, schema_data): self.collapsable = schema_data.get("collapsable", True) self.collapsed = schema_data.get("collapsed", True) @@ -3955,7 +3955,7 @@ def __init__(self, configuration, parent): # Wrappers TypeToKlass.types["form"] = FormItemWidget -TypeToKlass.types["collapsable-wrap"] = CollapsableWrapperItem +TypeToKlass.types["collapsible-wrap"] = CollapsibleWrapperItem # UI items TypeToKlass.types["label"] = LabelWidget