From e00091515c3010d2d9e6531ba4e8762011525cfb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 14 Aug 2020 17:58:42 +0200 Subject: [PATCH] standalone publer moved to tools --- pype/modules/standalonepublish/__init__.py | 11 +- .../standalonepublish_module.py | 20 +- pype/tools/config_setting/widgets/base0.py | 404 ++++ pype/tools/config_setting/widgets/inputs0.py | 2145 +++++++++++++++++ pype/tools/config_setting/widgets/inputs1.py | 2131 ++++++++++++++++ pype/tools/standalonepublish/__init__.py | 8 + .../standalonepublish/__main__.py | 0 .../standalonepublish/app.py | 0 .../standalonepublish/publish.py | 0 .../standalonepublish/resources/__init__.py | 0 .../standalonepublish/resources/edit.svg | 0 .../standalonepublish/resources/file.png | Bin .../standalonepublish/resources/files.png | Bin .../standalonepublish/resources/houdini.png | Bin .../resources/image_file.png | Bin .../resources/image_files.png | Bin .../resources/information.svg | 0 .../standalonepublish/resources/maya.png | Bin .../standalonepublish/resources/menu.png | Bin .../resources/menu_disabled.png | Bin .../resources/menu_hover.png | Bin .../resources/menu_pressed.png | Bin .../resources/menu_pressed_hover.png | Bin .../standalonepublish/resources/nuke.png | Bin .../standalonepublish/resources/premiere.png | Bin .../standalonepublish/resources/trash.png | Bin .../resources/trash_disabled.png | Bin .../resources/trash_hover.png | Bin .../resources/trash_pressed.png | Bin .../resources/trash_pressed_hover.png | Bin .../resources/video_file.png | Bin .../standalonepublish/widgets/__init__.py | 0 .../widgets/button_from_svgs.py | 0 .../standalonepublish/widgets/model_asset.py | 0 .../widgets/model_filter_proxy_exact_match.py | 0 .../model_filter_proxy_recursive_sort.py | 0 .../standalonepublish/widgets/model_node.py | 0 .../widgets/model_tasks_template.py | 0 .../standalonepublish/widgets/model_tree.py | 0 .../widgets/model_tree_view_deselectable.py | 0 .../standalonepublish/widgets/widget_asset.py | 0 .../widgets/widget_component_item.py | 0 .../widgets/widget_components.py | 0 .../widgets/widget_components_list.py | 0 .../widgets/widget_drop_empty.py | 0 .../widgets/widget_drop_frame.py | 0 .../widgets/widget_family.py | 0 .../widgets/widget_family_desc.py | 0 .../widgets/widget_shadow.py | 0 49 files changed, 4698 insertions(+), 21 deletions(-) create mode 100644 pype/tools/config_setting/widgets/base0.py create mode 100644 pype/tools/config_setting/widgets/inputs0.py create mode 100644 pype/tools/config_setting/widgets/inputs1.py create mode 100644 pype/tools/standalonepublish/__init__.py rename pype/{modules => tools}/standalonepublish/__main__.py (100%) rename pype/{modules => tools}/standalonepublish/app.py (100%) rename pype/{modules => tools}/standalonepublish/publish.py (100%) rename pype/{modules => tools}/standalonepublish/resources/__init__.py (100%) rename pype/{modules => tools}/standalonepublish/resources/edit.svg (100%) rename pype/{modules => tools}/standalonepublish/resources/file.png (100%) rename pype/{modules => tools}/standalonepublish/resources/files.png (100%) rename pype/{modules => tools}/standalonepublish/resources/houdini.png (100%) rename pype/{modules => tools}/standalonepublish/resources/image_file.png (100%) rename pype/{modules => tools}/standalonepublish/resources/image_files.png (100%) rename pype/{modules => tools}/standalonepublish/resources/information.svg (100%) rename pype/{modules => tools}/standalonepublish/resources/maya.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_disabled.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_pressed.png (100%) rename pype/{modules => tools}/standalonepublish/resources/menu_pressed_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/nuke.png (100%) rename pype/{modules => tools}/standalonepublish/resources/premiere.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_disabled.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_pressed.png (100%) rename pype/{modules => tools}/standalonepublish/resources/trash_pressed_hover.png (100%) rename pype/{modules => tools}/standalonepublish/resources/video_file.png (100%) rename pype/{modules => tools}/standalonepublish/widgets/__init__.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/button_from_svgs.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_asset.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_filter_proxy_exact_match.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_filter_proxy_recursive_sort.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_node.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_tasks_template.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_tree.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/model_tree_view_deselectable.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_asset.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_component_item.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_components.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_components_list.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_drop_empty.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_drop_frame.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_family.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_family_desc.py (100%) rename pype/{modules => tools}/standalonepublish/widgets/widget_shadow.py (100%) diff --git a/pype/modules/standalonepublish/__init__.py b/pype/modules/standalonepublish/__init__.py index 8e615afbea..4038b696d9 100644 --- a/pype/modules/standalonepublish/__init__.py +++ b/pype/modules/standalonepublish/__init__.py @@ -1,14 +1,5 @@ -PUBLISH_PATHS = [] - from .standalonepublish_module import StandAlonePublishModule -from .app import ( - show, - cli -) -__all__ = [ - "show", - "cli" -] + def tray_init(tray_widget, main_widget): return StandAlonePublishModule(main_widget, tray_widget) diff --git a/pype/modules/standalonepublish/standalonepublish_module.py b/pype/modules/standalonepublish/standalonepublish_module.py index 64195bc271..b528642e8d 100644 --- a/pype/modules/standalonepublish/standalonepublish_module.py +++ b/pype/modules/standalonepublish/standalonepublish_module.py @@ -1,21 +1,19 @@ import os -from .app import show -from .widgets import QtWidgets import pype -from . import PUBLISH_PATHS class StandAlonePublishModule: - def __init__(self, main_parent=None, parent=None): self.main_parent = main_parent self.parent_widget = parent - PUBLISH_PATHS.clear() - PUBLISH_PATHS.append(os.path.sep.join( - [pype.PLUGINS_DIR, "standalonepublisher", "publish"] - )) + self.publish_paths = [ + os.path.join( + pype.PLUGINS_DIR, "standalonepublisher", "publish" + ) + ] def tray_menu(self, parent_menu): + from Qt import QtWidgets self.run_action = QtWidgets.QAction( "Publish", parent_menu ) @@ -24,9 +22,9 @@ class StandAlonePublishModule: def process_modules(self, modules): if "FtrackModule" in modules: - PUBLISH_PATHS.append(os.path.sep.join( - [pype.PLUGINS_DIR, "ftrack", "publish"] + self.publish_paths.append(os.path.join( + pype.PLUGINS_DIR, "ftrack", "publish" )) def show(self): - show(self.main_parent, False) + print("Running") diff --git a/pype/tools/config_setting/widgets/base0.py b/pype/tools/config_setting/widgets/base0.py new file mode 100644 index 0000000000..7f01f27ca8 --- /dev/null +++ b/pype/tools/config_setting/widgets/base0.py @@ -0,0 +1,404 @@ +import os +import json +import copy +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .widgets import UnsavedChangesDialog +from .lib import NOT_SET +from avalon import io +from queue import Queue + + +class TypeToKlass: + types = {} + + +class PypeConfigurationWidget: + default_state = "" + + def config_value(self): + raise NotImplementedError( + "Method `config_value` is not implemented for `{}`.".format( + self.__class__.__name__ + ) + ) + + def value_from_values(self, values, keys=None): + if not values: + return NOT_SET + + if keys is None: + keys = self.keys + + value = values + for key in keys: + if not isinstance(value, dict): + raise TypeError( + "Expected dictionary got {}.".format(str(type(value))) + ) + + if key not in value: + return NOT_SET + value = value[key] + return value + + def style_state(self, is_overriden, is_modified): + items = [] + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + return "-".join(items) or self.default_state + + def add_children_gui(self, child_configuration, values): + raise NotImplementedError(( + "Method `add_children_gui` is not implemented for `{}`." + ).format(self.__class__.__name__)) + + +class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): + is_overidable = False + is_overriden = False + is_group = False + any_parent_is_group = False + ignore_value_changes = False + + def __init__(self, parent=None): + super(StudioWidget, self).__init__(parent) + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + # scroll_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + # scroll_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + save_btn = QtWidgets.QPushButton("Save") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(save_btn, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(scroll_widget, 1) + layout.addWidget(footer_widget, 0) + + save_btn.clicked.connect(self._save) + + self.reset() + + def reset(self): + if self.content_layout.count() != 0: + for widget in self.input_fields: + self.content_layout.removeWidget(widget) + widget.deleteLater() + self.input_fields.clear() + + values = {"studio": config.studio_presets()} + schema = config.gui_schema("studio_schema", "studio_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + self.schema = schema + + def _save(self): + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + _all_values = {key: all_values} + all_values = _all_values + + # Skip first key + all_values = all_values["studio"] + + # Load studio data with metadata + current_presets = config.studio_presets() + + keys_to_file = config.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + origin_values = current_presets + for key in key_sequence: + if key not in origin_values: + origin_values = {} + break + origin_values = origin_values[key] + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + origin_values.update(new_values) + + output_path = os.path.join( + config.studio_presets_path, subpath + ) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + with open(output_path, "w") as file_stream: + json.dump(origin_values, file_stream, indent=4) + + 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 + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + +class ProjectListView(QtWidgets.QListView): + left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + index = self.indexAt(event.pos()) + self.left_mouse_released_at.emit(index) + super(ProjectListView, self).mouseReleaseEvent(event) + + +class ProjectListWidget(QtWidgets.QWidget): + default = "< Default >" + project_changed = QtCore.Signal() + + def __init__(self, parent): + self._parent = parent + + self.current_project = None + + super(ProjectListWidget, self).__init__(parent) + + label_widget = QtWidgets.QLabel("Projects") + project_list = ProjectListView(self) + project_list.setModel(QtGui.QStandardItemModel()) + + # Do not allow editing + project_list.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) + # Do not automatically handle selection + project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(3) + layout.addWidget(label_widget, 0) + layout.addWidget(project_list, 1) + + project_list.left_mouse_released_at.connect(self.on_item_clicked) + + self.project_list = project_list + + self.refresh() + + def on_item_clicked(self, new_index): + new_project_name = new_index.data(QtCore.Qt.DisplayRole) + if new_project_name is None: + return + + if self.current_project == new_project_name: + return + + save_changes = False + change_project = False + if self.validate_context_change(): + change_project = True + + else: + dialog = UnsavedChangesDialog(self) + result = dialog.exec_() + if result == 1: + save_changes = True + change_project = True + + elif result == 2: + change_project = True + + if save_changes: + self._parent._save() + + if change_project: + self.select_project(new_project_name) + self.current_project = new_project_name + self.project_changed.emit() + else: + self.select_project(self.current_project) + + def validate_context_change(self): + # TODO add check if project can be changed (is modified) + for item in self._parent.input_fields: + is_modified = item.child_modified + if is_modified: + return False + return True + + def project_name(self): + if self.current_project == self.default: + return None + return self.current_project + + def select_project(self, project_name): + model = self.project_list.model() + found_items = model.findItems(project_name) + if not found_items: + found_items = model.findItems(self.default) + + index = model.indexFromItem(found_items[0]) + self.project_list.selectionModel().clear() + self.project_list.selectionModel().setCurrentIndex( + index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent + ) + + def refresh(self): + selected_project = None + for index in self.project_list.selectedIndexes(): + selected_project = index.data(QtCore.Qt.DisplayRole) + break + + model = self.project_list.model() + model.clear() + items = [self.default] + io.install() + for project_doc in tuple(io.projects()): + items.append(project_doc["name"]) + + for item in items: + model.appendRow(QtGui.QStandardItem(item)) + + self.select_project(selected_project) + + self.current_project = self.project_list.currentIndex().data( + QtCore.Qt.DisplayRole + ) + + +class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): + is_overriden = False + is_group = False + any_parent_is_group = False + + def __init__(self, parent=None): + super(ProjectWidget, self).__init__(parent) + + self.is_overidable = False + self.ignore_value_changes = False + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + project_list_widget = ProjectListWidget(self) + content_layout.addWidget(project_list_widget) + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + save_btn = QtWidgets.QPushButton("Save") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(save_btn, 0) + + presets_widget = QtWidgets.QWidget() + presets_layout = QtWidgets.QVBoxLayout(presets_widget) + presets_layout.setContentsMargins(0, 0, 0, 0) + presets_layout.setSpacing(0) + + presets_layout.addWidget(scroll_widget, 1) + presets_layout.addWidget(footer_widget, 0) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(project_list_widget, 0) + layout.addWidget(presets_widget, 1) + + save_btn.clicked.connect(self._save) + project_list_widget.project_changed.connect(self._on_project_change) + + self.project_list_widget = project_list_widget + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + self.reset() + + def reset(self): + values = config.global_project_presets() + schema = config.gui_schema("projects_schema", "project_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + + 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 + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + def _on_project_change(self): + project_name = self.project_list_widget.project_name() + + if project_name is None: + overrides = None + self.is_overidable = False + else: + overrides = config.project_preset_overrides(project_name) + self.is_overidable = True + + self.ignore_value_changes = True + for item in self.input_fields: + item.apply_overrides(overrides) + self.ignore_value_changes = False + + def _save(self): + output = {} + for item in self.input_fields: + if hasattr(item, "override_value"): + print(item.override_value()) + else: + print("*** missing `override_value`", item) + + # for item in self.input_fields: + # output.update(item.config_value()) + # + # for key in reversed(self.keys): + # _output = {key: output} + # output = _output + + print(json.dumps(output, indent=4)) diff --git a/pype/tools/config_setting/widgets/inputs0.py b/pype/tools/config_setting/widgets/inputs0.py new file mode 100644 index 0000000000..7ab9c7fa01 --- /dev/null +++ b/pype/tools/config_setting/widgets/inputs0.py @@ -0,0 +1,2145 @@ +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, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._as_widget = 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._was_overriden = False + self._is_overriden = False + + self._state = None + + self.override_value = 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) + + 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 + + layout.addWidget(self.checkbox) + + self.default_value = self.item_value() + + self.checkbox.stateChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, bool): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + def set_value(self, value, *, default_value=False): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.checkbox.setChecked(value) + + if default_value: + self.default_value = self.item_value() + + self._on_value_change() + + def reset_value(self): + if self.is_overidable and self.override_value is not None: + self.set_value(self.override_value) + else: + self.set_value(self.default_value) + + def clear_value(self): + self.reset_value() + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + _value = self.item_value() + is_modified = None + if self.is_overidable: + self._is_overriden = True + if self.override_value is not None: + is_modified = _value != self.override_value + + if is_modified is None: + is_modified = _value != self.default_value + + self._is_modified = is_modified + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = 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._was_overriden = 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 + + self.default_value = self.item_value() + self.override_value = None + + self.int_input.valueChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, int): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.int_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def clear_value(self): + self.set_value(0) + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = 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._was_overriden = 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 + + self.default_value = self.item_value() + self.override_value = None + + self.float_input.valueChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, float): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.float_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value(0) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = 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._was_overriden = 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 + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, str): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = 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._was_overriden = 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 + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, str): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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 RawJsonInput(QtWidgets.QPlainTextEdit): + tab_length = 4 + + def __init__(self, *args, **kwargs): + super(RawJsonInput, self).__init__(*args, **kwargs) + self.setObjectName("RawJsonInput") + self.setTabStopDistance( + QtGui.QFontMetricsF( + self.font() + ).horizontalAdvance(" ") * self.tab_length + ) + + self.is_valid = None + + def set_value(self, value, *, default_value=False): + self.setPlainText(value) + + def setPlainText(self, *args, **kwargs): + super(RawJsonInput, self).setPlainText(*args, **kwargs) + self.validate() + + def focusOutEvent(self, event): + super(RawJsonInput, self).focusOutEvent(event) + self.validate() + + def validate_value(self, value): + if isinstance(value, str) and not value: + return True + + try: + json.loads(value) + return True + except Exception: + return False + + def update_style(self, is_valid=None): + if is_valid is None: + return self.validate() + + if is_valid != self.is_valid: + self.is_valid = is_valid + if is_valid: + state = "" + else: + state = "invalid" + self.setProperty("state", state) + self.style().polish(self) + + def value(self): + return self.toPlainText() + + def validate(self): + value = self.value() + is_valid = self.validate_value(value) + self.update_style(is_valid) + + +class RawJsonWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = 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._was_overriden = False + self._is_overriden = False + + self._state = None + + super(RawJsonWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = RawJsonInput() + + 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 + + self.default_value = self.item_value() + self.override_value = None + + self.text_input.textChanged.connect(self._on_value_change) + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, str): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value("") + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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, as_widget, parent_keys, parent): + super(TextListSubWidget, self).__init__(parent) + self.setObjectName("TextListSubWidget") + + self.as_widget = as_widget + + 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 + + self.default_value = self.item_value() + self.override_value = None + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, (list, tuple)): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + def set_value(self, value, *, default_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 default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_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, parent_keys, parent, + as_widget=False, label_widget=None + ): + self._parent = parent + self._as_widget = 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_modified = False + self.is_group = is_group + self._was_overriden = False + 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.default_value = self.item_value() + self.override_value = None + + def set_default_values(self, default_values): + value = self.value_from_values(default_values) + if isinstance(value, (list, tuple)): + self.set_value(value, default_value=True) + self.default_value = self.item_value() + else: + self.default_value = value + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def set_value(self, value, *, default_value=False): + self.value_widget.set_value(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value([]) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(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) + + 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, parent_keys, parent, + as_widget=False, label_widget=None + ): + if 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 set_default_values(self, default_values): + for input_field in self.input_fields: + input_field.set_default_values(default_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): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + # Make sure this is set to False + self._is_overriden = False + self._state = None + self._child_state = None + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + + # TODO update items + if item is not None: + for _item in self.input_fields: + if _item is not item: + _item.update_style() + + self.value_changed.emit(self) + + self.update_style() + + def update_style(self, is_overriden=None): + child_modified = self.child_modified + child_state = self.style_state(self.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 + + state = self.style_state(self.is_overriden, self.is_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 is_modified(self): + if self.is_group: + return self.child_modified + 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.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): + # TODO is not overridable by itself + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, 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_overriden = False + 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 = [] + + 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) + + def set_default_values(self, default_values): + for input_field in self.input_fields: + input_field.set_default_values(default_values) + + def update_style(self, *args, **kwargs): + return + + @property + def is_overriden(self): + return self._is_overriden or 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 + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + 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) + + item.value_changed.connect(self._on_value_change) + + self.input_fields.append(item) + return item + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + 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) + + def apply_overrides(self, override_value): + self._is_overriden = False + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + +class DictFormWidget(QtWidgets.QWidget): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, 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 set_default_values(self, default_values): + for key, input_field in self.input_fields.items(): + input_field.set_default_values(default_values) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + 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 + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + 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.default_key = self._key() + self.default_value = self.value_input.item_value() + + self.override_key = None + self.override_value = None + + 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 + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def is_key_modified(self): + return self._key() != self.default_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): + 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, as_widget, 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 + + if self.count() == 0: + self.add_row() + + self.default_value = self.config_value() + self.override_value = None + + def set_default_values(self, default_values): + for input_field in self.input_fields: + self.remove_row(input_field) + + value = self.value_from_values(default_values) + if value is NOT_SET: + self.defaul_value = value + return + + for item_key, item_value in value.items(): + self.add_row(key=item_key, value=item_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 ignore_value_changes(self): + return self._parent.ignore_value_changes + + @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.default_key = key + item_widget.key_input.setText(key) + item_widget.value_input.set_value(value, default_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): + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent_keys, parent, + as_widget=False, 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_group = is_group + self._is_modified = False + self._is_overriden = False + self._was_overriden = False + self._state = None + + super(ModifiableDict, self).__init__(input_data["label"], parent) + self.setObjectName("ModifiableDict") + + self.value_widget = ModifiableDictSubWidget( + input_data, as_widget, 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.default_value = self.item_value() + self.override_value = None + + def set_default_values(self, default_values): + self.value_widget.set_default_values(default_values) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + + if self.is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.default_value + + self.value_changed.emit(self) + + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def is_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): + return self._is_overriden or self._parent.is_overriden + + @property + def is_modified(self): + return self._is_modified + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + self._state = None + self._is_modified = False + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.setProperty("state", child_state) + self.style().polish(self) + + 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 diff --git a/pype/tools/config_setting/widgets/inputs1.py b/pype/tools/config_setting/widgets/inputs1.py new file mode 100644 index 0000000000..f9eb60f31a --- /dev/null +++ b/pype/tools/config_setting/widgets/inputs1.py @@ -0,0 +1,2131 @@ +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._was_overriden = 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.default_value = self.item_value() + self.override_value = None + + self.checkbox.stateChanged.connect(self._on_value_change) + + def set_value(self, value, *, default_value=False): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.checkbox.setChecked(value) + + if default_value: + self.default_value = self.item_value() + + self._on_value_change() + + def reset_value(self): + if self.is_overidable and self.override_value is not None: + self.set_value(self.override_value) + else: + self.set_value(self.default_value) + + def clear_value(self): + self.reset_value() + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + _value = self.item_value() + is_modified = None + if self.is_overidable: + self._is_overriden = True + if self.override_value is not None: + is_modified = _value != self.override_value + + if is_modified is None: + is_modified = _value != self.default_value + + self._is_modified = is_modified + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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()} + + def override_value(self): + if self.is_overriden: + output = { + "is_group": self.is_group, + "value": self.config_value() + } + return output + + +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._was_overriden = 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.default_value = self.item_value() + self.override_value = None + + 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_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.int_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def clear_value(self): + self.set_value(0) + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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._was_overriden = 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.default_value = self.item_value() + self.override_value = None + + 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_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.float_input.setValue(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value(0) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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._was_overriden = 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.default_value = self.item_value() + self.override_value = None + + 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_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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._was_overriden = 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.default_value = self.item_value() + self.override_value = None + + 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_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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 RawJsonInput(QtWidgets.QPlainTextEdit): + tab_length = 4 + + def __init__(self, *args, **kwargs): + super(RawJsonInput, self).__init__(*args, **kwargs) + self.setObjectName("RawJsonInput") + self.setTabStopDistance( + QtGui.QFontMetricsF( + self.font() + ).horizontalAdvance(" ") * self.tab_length + ) + + self.is_valid = None + + def set_value(self, value, *, default_value=False): + self.setPlainText(value) + + def setPlainText(self, *args, **kwargs): + super(RawJsonInput, self).setPlainText(*args, **kwargs) + self.validate() + + def focusOutEvent(self, event): + super(RawJsonInput, self).focusOutEvent(event) + self.validate() + + def validate_value(self, value): + if isinstance(value, str) and not value: + return True + + try: + json.loads(value) + return True + except Exception: + return False + + def update_style(self, is_valid=None): + if is_valid is None: + return self.validate() + + if is_valid != self.is_valid: + self.is_valid = is_valid + if is_valid: + state = "" + else: + state = "invalid" + self.setProperty("state", state) + self.style().polish(self) + + def value(self): + return self.toPlainText() + + def validate(self): + value = self.value() + is_valid = self.validate_value(value) + self.update_style(is_valid) + + +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._was_overriden = False + self._is_overriden = False + + self._state = None + + super(RawJsonWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = RawJsonInput() + + 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.default_value = self.item_value() + self.override_value = None + + 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_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def set_value(self, value, *, default_value=False): + self.text_input.setPlainText(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value("") + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def update_style(self): + state = self.style_state(self.is_overriden, self.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.default_value = self.item_value() + self.override_value = None + + def set_value(self, value, *, default_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 default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_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._was_overriden = False + 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.default_value = self.item_value() + self.override_value = None + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + return self._is_modified or (self._was_overriden != self.is_overriden) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_overriden(self): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + self._is_modified = self.item_value() != self.default_value + if self.is_overidable: + self._is_overriden = True + + self.update_style() + + self.value_changed.emit(self) + + def set_value(self, value, *, default_value=False): + self.value_widget.set_value(value) + if default_value: + self.default_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.default_value) + + def clear_value(self): + self.set_value([]) + + def apply_overrides(self, override_value): + self._is_modified = False + self._state = None + self.override_value = override_value + if override_value is None: + self._is_overriden = False + value = self.default_value + else: + self._is_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(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) + + 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): + return self._is_overriden or self._parent.is_overriden + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + # Make sure this is set to False + self._is_overriden = False + self._state = None + self._child_state = None + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + + # TODO update items + if item is not None: + for _item in self.input_fields: + if _item is not item: + _item.update_style() + + self.value_changed.emit(self) + + self.update_style() + + def update_style(self, is_overriden=None): + child_modified = self.child_modified + child_state = self.style_state(self.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 + + state = self.style_state(self.is_overriden, self.is_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 is_modified(self): + if self.is_group: + return self.child_modified + 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.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 + + def override_values(self): + if not self.is_overriden and not self.child_overriden: + return + + value = {} + for item in self.input_fields: + if hasattr(item, "override_values"): + print("*** HAVE `override_values`", item) + print(item.override_values()) + else: + print("*** missing `override_values`", item) + + if not value: + return + + output = { + "is_group": self.is_group, + "value": value + } + return output + + +class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + # TODO is not overridable by itself + 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() + + self.any_parent_is_group = any_parent_is_group + + self._is_overriden = False + 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) + + def update_style(self, *args, **kwargs): + return + + @property + def is_overriden(self): + return self._is_overriden or 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 + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + 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) + + item.value_changed.connect(self._on_value_change) + + self.input_fields.append(item) + return item + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + 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) + + def apply_overrides(self, override_value): + self._is_overriden = False + for item in self.input_fields: + if override_value is None: + child_value = None + else: + child_value = override_value.get(item.key) + item.apply_overrides(child_value) + + self._is_overriden = ( + self.is_group + and self.is_overidable + and ( + override_value is not None + or self.child_overriden + ) + ) + self.update_style() + + def override_values(self): + if not self.is_overriden and not self.child_overriden: + return + + value = {} + for item in self.input_fields: + if hasattr(item, "override_values"): + print("*** HAVE `override_values`", item) + print(item.override_values()) + else: + print("*** missing `override_values`", item) + + if not value: + return + + output = { + "is_group": self.is_group, + "value": value + } + return output + + +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): + if self.ignore_value_changes: + return + 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 + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + 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.default_key = self._key() + self.default_value = self.value_input.item_value() + + self.override_key = None + self.override_value = None + + 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 + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def is_key_modified(self): + return self._key() != self.default_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): + 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.default_value = self.config_value() + self.override_value = None + + @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 ignore_value_changes(self): + return self._parent.ignore_value_changes + + @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.default_key = key + item_widget.key_input.setText(key) + item_widget.value_input.set_value(value, default_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): + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) + 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.any_parent_is_group = any_parent_is_group + + self.is_group = is_group + self._is_modified = False + self._is_overriden = False + self._was_overriden = False + self._state = None + + 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.default_value = self.item_value() + self.override_value = None + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + + if self.is_overriden: + self._is_modified = self.item_value() != self.override_value + else: + self._is_modified = self.item_value() != self.default_value + + self.value_changed.emit(self) + + self.update_style() + + @property + def child_modified(self): + return self.is_modified + + @property + def is_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): + return self._is_overriden or self._parent.is_overriden + + @property + def is_modified(self): + return self._is_modified + + @property + def ignore_value_changes(self): + return self._parent.ignore_value_changes + + def apply_overrides(self, override_value): + self._state = None + self._is_modified = False + self.override_value = override_value + if override_value is None: + self._is_overriden = False + self._was_overriden = False + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + self.update_style() + + def update_style(self): + state = self.style_state(self.is_overriden, self.is_modified) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.setProperty("state", child_state) + self.style().polish(self) + + 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()} + + def override_value(self): + return + + +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 diff --git a/pype/tools/standalonepublish/__init__.py b/pype/tools/standalonepublish/__init__.py new file mode 100644 index 0000000000..29a4e52904 --- /dev/null +++ b/pype/tools/standalonepublish/__init__.py @@ -0,0 +1,8 @@ +from .app import ( + show, + cli +) +__all__ = [ + "show", + "cli" +] diff --git a/pype/modules/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py similarity index 100% rename from pype/modules/standalonepublish/__main__.py rename to pype/tools/standalonepublish/__main__.py diff --git a/pype/modules/standalonepublish/app.py b/pype/tools/standalonepublish/app.py similarity index 100% rename from pype/modules/standalonepublish/app.py rename to pype/tools/standalonepublish/app.py diff --git a/pype/modules/standalonepublish/publish.py b/pype/tools/standalonepublish/publish.py similarity index 100% rename from pype/modules/standalonepublish/publish.py rename to pype/tools/standalonepublish/publish.py diff --git a/pype/modules/standalonepublish/resources/__init__.py b/pype/tools/standalonepublish/resources/__init__.py similarity index 100% rename from pype/modules/standalonepublish/resources/__init__.py rename to pype/tools/standalonepublish/resources/__init__.py diff --git a/pype/modules/standalonepublish/resources/edit.svg b/pype/tools/standalonepublish/resources/edit.svg similarity index 100% rename from pype/modules/standalonepublish/resources/edit.svg rename to pype/tools/standalonepublish/resources/edit.svg diff --git a/pype/modules/standalonepublish/resources/file.png b/pype/tools/standalonepublish/resources/file.png similarity index 100% rename from pype/modules/standalonepublish/resources/file.png rename to pype/tools/standalonepublish/resources/file.png diff --git a/pype/modules/standalonepublish/resources/files.png b/pype/tools/standalonepublish/resources/files.png similarity index 100% rename from pype/modules/standalonepublish/resources/files.png rename to pype/tools/standalonepublish/resources/files.png diff --git a/pype/modules/standalonepublish/resources/houdini.png b/pype/tools/standalonepublish/resources/houdini.png similarity index 100% rename from pype/modules/standalonepublish/resources/houdini.png rename to pype/tools/standalonepublish/resources/houdini.png diff --git a/pype/modules/standalonepublish/resources/image_file.png b/pype/tools/standalonepublish/resources/image_file.png similarity index 100% rename from pype/modules/standalonepublish/resources/image_file.png rename to pype/tools/standalonepublish/resources/image_file.png diff --git a/pype/modules/standalonepublish/resources/image_files.png b/pype/tools/standalonepublish/resources/image_files.png similarity index 100% rename from pype/modules/standalonepublish/resources/image_files.png rename to pype/tools/standalonepublish/resources/image_files.png diff --git a/pype/modules/standalonepublish/resources/information.svg b/pype/tools/standalonepublish/resources/information.svg similarity index 100% rename from pype/modules/standalonepublish/resources/information.svg rename to pype/tools/standalonepublish/resources/information.svg diff --git a/pype/modules/standalonepublish/resources/maya.png b/pype/tools/standalonepublish/resources/maya.png similarity index 100% rename from pype/modules/standalonepublish/resources/maya.png rename to pype/tools/standalonepublish/resources/maya.png diff --git a/pype/modules/standalonepublish/resources/menu.png b/pype/tools/standalonepublish/resources/menu.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu.png rename to pype/tools/standalonepublish/resources/menu.png diff --git a/pype/modules/standalonepublish/resources/menu_disabled.png b/pype/tools/standalonepublish/resources/menu_disabled.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_disabled.png rename to pype/tools/standalonepublish/resources/menu_disabled.png diff --git a/pype/modules/standalonepublish/resources/menu_hover.png b/pype/tools/standalonepublish/resources/menu_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_hover.png rename to pype/tools/standalonepublish/resources/menu_hover.png diff --git a/pype/modules/standalonepublish/resources/menu_pressed.png b/pype/tools/standalonepublish/resources/menu_pressed.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_pressed.png rename to pype/tools/standalonepublish/resources/menu_pressed.png diff --git a/pype/modules/standalonepublish/resources/menu_pressed_hover.png b/pype/tools/standalonepublish/resources/menu_pressed_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/menu_pressed_hover.png rename to pype/tools/standalonepublish/resources/menu_pressed_hover.png diff --git a/pype/modules/standalonepublish/resources/nuke.png b/pype/tools/standalonepublish/resources/nuke.png similarity index 100% rename from pype/modules/standalonepublish/resources/nuke.png rename to pype/tools/standalonepublish/resources/nuke.png diff --git a/pype/modules/standalonepublish/resources/premiere.png b/pype/tools/standalonepublish/resources/premiere.png similarity index 100% rename from pype/modules/standalonepublish/resources/premiere.png rename to pype/tools/standalonepublish/resources/premiere.png diff --git a/pype/modules/standalonepublish/resources/trash.png b/pype/tools/standalonepublish/resources/trash.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash.png rename to pype/tools/standalonepublish/resources/trash.png diff --git a/pype/modules/standalonepublish/resources/trash_disabled.png b/pype/tools/standalonepublish/resources/trash_disabled.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_disabled.png rename to pype/tools/standalonepublish/resources/trash_disabled.png diff --git a/pype/modules/standalonepublish/resources/trash_hover.png b/pype/tools/standalonepublish/resources/trash_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_hover.png rename to pype/tools/standalonepublish/resources/trash_hover.png diff --git a/pype/modules/standalonepublish/resources/trash_pressed.png b/pype/tools/standalonepublish/resources/trash_pressed.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_pressed.png rename to pype/tools/standalonepublish/resources/trash_pressed.png diff --git a/pype/modules/standalonepublish/resources/trash_pressed_hover.png b/pype/tools/standalonepublish/resources/trash_pressed_hover.png similarity index 100% rename from pype/modules/standalonepublish/resources/trash_pressed_hover.png rename to pype/tools/standalonepublish/resources/trash_pressed_hover.png diff --git a/pype/modules/standalonepublish/resources/video_file.png b/pype/tools/standalonepublish/resources/video_file.png similarity index 100% rename from pype/modules/standalonepublish/resources/video_file.png rename to pype/tools/standalonepublish/resources/video_file.png diff --git a/pype/modules/standalonepublish/widgets/__init__.py b/pype/tools/standalonepublish/widgets/__init__.py similarity index 100% rename from pype/modules/standalonepublish/widgets/__init__.py rename to pype/tools/standalonepublish/widgets/__init__.py diff --git a/pype/modules/standalonepublish/widgets/button_from_svgs.py b/pype/tools/standalonepublish/widgets/button_from_svgs.py similarity index 100% rename from pype/modules/standalonepublish/widgets/button_from_svgs.py rename to pype/tools/standalonepublish/widgets/button_from_svgs.py diff --git a/pype/modules/standalonepublish/widgets/model_asset.py b/pype/tools/standalonepublish/widgets/model_asset.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_asset.py rename to pype/tools/standalonepublish/widgets/model_asset.py diff --git a/pype/modules/standalonepublish/widgets/model_filter_proxy_exact_match.py b/pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_filter_proxy_exact_match.py rename to pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py diff --git a/pype/modules/standalonepublish/widgets/model_filter_proxy_recursive_sort.py b/pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_filter_proxy_recursive_sort.py rename to pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py diff --git a/pype/modules/standalonepublish/widgets/model_node.py b/pype/tools/standalonepublish/widgets/model_node.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_node.py rename to pype/tools/standalonepublish/widgets/model_node.py diff --git a/pype/modules/standalonepublish/widgets/model_tasks_template.py b/pype/tools/standalonepublish/widgets/model_tasks_template.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_tasks_template.py rename to pype/tools/standalonepublish/widgets/model_tasks_template.py diff --git a/pype/modules/standalonepublish/widgets/model_tree.py b/pype/tools/standalonepublish/widgets/model_tree.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_tree.py rename to pype/tools/standalonepublish/widgets/model_tree.py diff --git a/pype/modules/standalonepublish/widgets/model_tree_view_deselectable.py b/pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py similarity index 100% rename from pype/modules/standalonepublish/widgets/model_tree_view_deselectable.py rename to pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py diff --git a/pype/modules/standalonepublish/widgets/widget_asset.py b/pype/tools/standalonepublish/widgets/widget_asset.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_asset.py rename to pype/tools/standalonepublish/widgets/widget_asset.py diff --git a/pype/modules/standalonepublish/widgets/widget_component_item.py b/pype/tools/standalonepublish/widgets/widget_component_item.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_component_item.py rename to pype/tools/standalonepublish/widgets/widget_component_item.py diff --git a/pype/modules/standalonepublish/widgets/widget_components.py b/pype/tools/standalonepublish/widgets/widget_components.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_components.py rename to pype/tools/standalonepublish/widgets/widget_components.py diff --git a/pype/modules/standalonepublish/widgets/widget_components_list.py b/pype/tools/standalonepublish/widgets/widget_components_list.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_components_list.py rename to pype/tools/standalonepublish/widgets/widget_components_list.py diff --git a/pype/modules/standalonepublish/widgets/widget_drop_empty.py b/pype/tools/standalonepublish/widgets/widget_drop_empty.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_drop_empty.py rename to pype/tools/standalonepublish/widgets/widget_drop_empty.py diff --git a/pype/modules/standalonepublish/widgets/widget_drop_frame.py b/pype/tools/standalonepublish/widgets/widget_drop_frame.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_drop_frame.py rename to pype/tools/standalonepublish/widgets/widget_drop_frame.py diff --git a/pype/modules/standalonepublish/widgets/widget_family.py b/pype/tools/standalonepublish/widgets/widget_family.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_family.py rename to pype/tools/standalonepublish/widgets/widget_family.py diff --git a/pype/modules/standalonepublish/widgets/widget_family_desc.py b/pype/tools/standalonepublish/widgets/widget_family_desc.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_family_desc.py rename to pype/tools/standalonepublish/widgets/widget_family_desc.py diff --git a/pype/modules/standalonepublish/widgets/widget_shadow.py b/pype/tools/standalonepublish/widgets/widget_shadow.py similarity index 100% rename from pype/modules/standalonepublish/widgets/widget_shadow.py rename to pype/tools/standalonepublish/widgets/widget_shadow.py