diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index f8378ed18cb..b10c9588809 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -1,9 +1,15 @@ +import sys import json +import traceback from Qt import QtWidgets, QtGui, QtCore + +from openpype.settings.entities import ProjectSettings from openpype.tools.settings import CHILD_OFFSET + from .widgets import ExpandingWidget from .lib import create_deffered_value_change_timer +from .constants import DEFAULT_PROJECT_LABEL class BaseWidget(QtWidgets.QWidget): @@ -110,9 +116,10 @@ def _discard_changes_action(self, menu, actions_mapping): return def discard_changes(): - self.ignore_input_changes.set_ignore(True) - self.entity.discard_changes() - self.ignore_input_changes.set_ignore(False) + with self.category_widget.working_state_context(): + self.ignore_input_changes.set_ignore(True) + self.entity.discard_changes() + self.ignore_input_changes.set_ignore(False) action = QtWidgets.QAction("Discard changes") actions_mapping[action] = discard_changes @@ -124,8 +131,11 @@ def _add_to_studio_default(self, menu, actions_mapping): if not self.entity.can_trigger_add_to_studio_default: return + def add_to_studio_default(): + with self.category_widget.working_state_context(): + self.entity.add_to_studio_default() action = QtWidgets.QAction("Add to studio default") - actions_mapping[action] = self.entity.add_to_studio_default + actions_mapping[action] = add_to_studio_default menu.addAction(action) def _remove_from_studio_default_action(self, menu, actions_mapping): @@ -133,9 +143,10 @@ def _remove_from_studio_default_action(self, menu, actions_mapping): return def remove_from_studio_default(): - self.ignore_input_changes.set_ignore(True) - self.entity.remove_from_studio_default() - self.ignore_input_changes.set_ignore(False) + with self.category_widget.working_state_context(): + self.ignore_input_changes.set_ignore(True) + self.entity.remove_from_studio_default() + self.ignore_input_changes.set_ignore(False) action = QtWidgets.QAction("Remove from studio default") actions_mapping[action] = remove_from_studio_default menu.addAction(action) @@ -144,8 +155,12 @@ def _add_to_project_override_action(self, menu, actions_mapping): if not self.entity.can_trigger_add_to_project_override: return + def add_to_project_override(): + with self.category_widget.working_state_context(): + self.entity.add_to_project_override + action = QtWidgets.QAction("Add to project project override") - actions_mapping[action] = self.entity.add_to_project_override + actions_mapping[action] = add_to_project_override menu.addAction(action) def _remove_from_project_override_action(self, menu, actions_mapping): @@ -153,9 +168,11 @@ def _remove_from_project_override_action(self, menu, actions_mapping): return def remove_from_project_override(): - self.ignore_input_changes.set_ignore(True) - self.entity.remove_from_project_override() - self.ignore_input_changes.set_ignore(False) + with self.category_widget.working_state_context(): + self.ignore_input_changes.set_ignore(True) + self.entity.remove_from_project_override() + self.ignore_input_changes.set_ignore(False) + action = QtWidgets.QAction("Remove from project override") actions_mapping[action] = remove_from_project_override menu.addAction(action) @@ -257,14 +274,16 @@ def _set_entity_value(_entity, _value): # Simple paste value method def paste_value(): - _set_entity_value(self.entity, value) + with self.category_widget.working_state_context(): + _set_entity_value(self.entity, value) action = QtWidgets.QAction("Paste", menu) output.append((action, paste_value)) # Paste value to matchin entity def paste_value_to_path(): - _set_entity_value(matching_entity, value) + with self.category_widget.working_state_context(): + _set_entity_value(matching_entity, value) if matching_entity is not None: action = QtWidgets.QAction("Paste to same place", menu) @@ -272,6 +291,68 @@ def paste_value_to_path(): return output + def _apply_values_from_project_action(self, menu, actions_mapping): + for attr_name in ("project_name", "get_project_names"): + if not hasattr(self.category_widget, attr_name): + return + + if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item: + return + + current_project_name = self.category_widget.project_name + project_names = [] + for project_name in self.category_widget.get_project_names(): + if project_name != current_project_name: + project_names.append(project_name) + + if not project_names: + return + + submenu = QtWidgets.QMenu("Apply values from", menu) + + for project_name in project_names: + if project_name is None: + project_name = DEFAULT_PROJECT_LABEL + + action = QtWidgets.QAction(project_name) + submenu.addAction(action) + actions_mapping[action] = lambda: self._apply_values_from_project( + project_name + ) + menu.addMenu(submenu) + + def _apply_values_from_project(self, project_name): + with self.category_widget.working_state_context(): + try: + path_keys = [ + item + for item in self.entity.path.split("/") + if item + ] + entity = ProjectSettings(project_name) + for key in path_keys: + entity = entity[key] + self.entity.set(entity.value) + + except Exception: + if project_name is None: + project_name = DEFAULT_PROJECT_LABEL + + # TODO better message + title = "Applying values failed" + msg = "Applying values from project \"{}\" failed.".format( + project_name + ) + detail_msg = "".join( + traceback.format_exception(*sys.exc_info()) + ) + dialog = QtWidgets.QMessageBox(self) + dialog.setWindowTitle(title) + dialog.setIcon(QtWidgets.QMessageBox.Warning) + dialog.setText(msg) + dialog.setDetailedText(detail_msg) + dialog.exec_() + def show_actions_menu(self, event=None): if event and event.button() != QtCore.Qt.RightButton: return @@ -290,6 +371,7 @@ def show_actions_menu(self, event=None): self._remove_from_studio_default_action(menu, actions_mapping) self._add_to_project_override_action(menu, actions_mapping) self._remove_from_project_override_action(menu, actions_mapping) + self._apply_values_from_project_action(menu, actions_mapping) ui_actions = [] ui_actions.extend(self._copy_value_actions(menu)) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index a6e4154b2b7..029619849ed 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -1,6 +1,7 @@ import os import sys import traceback +import contextlib from enum import Enum from Qt import QtWidgets, QtCore, QtGui @@ -309,6 +310,12 @@ def add_widget_to_layout(self, widget, label_widget=None): ) self.content_layout.addWidget(widget, 0) + @contextlib.contextmanager + def working_state_context(self): + self.set_state(CategoryState.Working) + yield + self.set_state(CategoryState.Idle) + def save(self): if not self.items_are_valid(): return @@ -599,6 +606,14 @@ def ui_tweaks(self): self.project_list_widget = project_list_widget + def get_project_names(self): + if ( + self.modify_defaults_checkbox + and self.modify_defaults_checkbox.isChecked() + ): + return [] + return self.project_list_widget.get_project_names() + def on_saved(self, saved_tab_widget): """Callback on any tab widget save. diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index ac9870287b6..4c7bf87ce89 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -747,6 +747,13 @@ def select_project(self, project_name): index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent ) + def get_project_names(self): + output = [] + for row in range(self.project_proxy.rowCount()): + index = self.project_proxy.index(row, 0) + output.append(index.data(PROJECT_NAME_ROLE)) + return output + def refresh(self): selected_project = None for index in self.project_list.selectedIndexes():