Skip to content

Commit

Permalink
Merge pull request ynput#2382 from pypeclub/feature/OP-2001_Settings-…
Browse files Browse the repository at this point in the history
…Copypaste-project-settings-to-another-project

Enhancement: Settings: Use project settings values from another project
  • Loading branch information
iLLiCiTiT authored Dec 14, 2021
2 parents b8fadc6 + 68b9939 commit a4be22c
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 13 deletions.
108 changes: 95 additions & 13 deletions openpype/tools/settings/settings/base.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -124,18 +131,22 @@ 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):
if not self.entity.can_trigger_remove_from_studio_default:
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)
Expand All @@ -144,18 +155,24 @@ 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):
if not self.entity.can_trigger_remove_from_project_override:
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)
Expand Down Expand Up @@ -257,21 +274,85 @@ 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)
output.append((action, 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
Expand All @@ -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))
Expand Down
15 changes: 15 additions & 0 deletions openpype/tools/settings/settings/categories.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import sys
import traceback
import contextlib
from enum import Enum
from Qt import QtWidgets, QtCore, QtGui

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions openpype/tools/settings/settings/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down

0 comments on commit a4be22c

Please sign in to comment.