Merge branch 'develop' into enhancement/OP-2075_version-handling

This commit is contained in:
iLLiCiTiT 2022-01-04 16:53:23 +01:00
commit 247c931c0f
310 changed files with 6874 additions and 1047 deletions

View file

@ -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):
@ -119,9 +125,10 @@ class BaseWidget(QtWidgets.QWidget):
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
@ -133,8 +140,11 @@ class BaseWidget(QtWidgets.QWidget):
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):
@ -142,9 +152,10 @@ class BaseWidget(QtWidgets.QWidget):
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)
@ -153,8 +164,12 @@ class BaseWidget(QtWidgets.QWidget):
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):
@ -162,9 +177,11 @@ class BaseWidget(QtWidgets.QWidget):
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)
@ -266,14 +283,16 @@ class BaseWidget(QtWidgets.QWidget):
# 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)
@ -281,6 +300,68 @@ class BaseWidget(QtWidgets.QWidget):
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
@ -299,6 +380,7 @@ class BaseWidget(QtWidgets.QWidget):
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))
@ -481,7 +563,9 @@ class GUIWidget(BaseWidget):
def _create_label_ui(self):
label = self.entity["label"]
label_widget = QtWidgets.QLabel(label, self)
label_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
label_widget.setObjectName("SettingsLabel")
label_widget.linkActivated.connect(self._on_link_activate)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 5, 0, 5)
@ -497,6 +581,14 @@ class GUIWidget(BaseWidget):
layout.setContentsMargins(5, 5, 5, 5)
layout.addWidget(splitter_item)
def _on_link_activate(self, url):
if not url.startswith("settings://"):
QtGui.QDesktopServices.openUrl(url)
return
path = url.replace("settings://", "")
self.category_widget.go_to_fullpath(path)
def set_entity_value(self):
pass

View file

@ -71,17 +71,35 @@ class SettingsBreadcrumbs(BreadcrumbsModel):
return True
return False
def get_valid_path(self, path):
if not path:
return ""
path_items = path.split("/")
new_path_items = []
entity = self.entity
for item in path_items:
if not entity.has_child_with_key(item):
break
new_path_items.append(item)
entity = entity[item]
return "/".join(new_path_items)
def is_valid_path(self, path):
if not path:
return True
path_items = path.split("/")
try:
entity = self.entity
for item in path_items:
entity = entity[item]
except Exception:
return False
entity = self.entity
for item in path_items:
if not entity.has_child_with_key(item):
return False
entity = entity[item]
return True
@ -436,6 +454,7 @@ class BreadcrumbsAddressBar(QtWidgets.QFrame):
self.change_path(path)
def change_path(self, path):
path = self._model.get_valid_path(path)
if self._model and not self._model.is_valid_path(path):
self._show_address_field()
else:

View file

@ -1,6 +1,7 @@
import os
import sys
import traceback
import contextlib
from enum import Enum
from Qt import QtWidgets, QtCore, QtGui
@ -85,6 +86,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
state_changed = QtCore.Signal()
saved = QtCore.Signal(QtWidgets.QWidget)
restart_required_trigger = QtCore.Signal()
full_path_requested = QtCore.Signal(str, str)
def __init__(self, user_role, parent=None):
super(SettingsCategoryWidget, self).__init__(parent)
@ -274,6 +276,37 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
# Scroll to widget
self.scroll_widget.ensureWidgetVisible(widget)
def go_to_fullpath(self, full_path):
"""Full path of settings entity which can lead to different category.
Args:
full_path (str): Full path to settings entity. It is expected that
path starts with category name ("system_setting" etc.).
"""
if not full_path:
return
items = full_path.split("/")
category = items[0]
path = ""
if len(items) > 1:
path = "/".join(items[1:])
self.full_path_requested.emit(category, path)
def contain_category_key(self, category):
"""Parent widget ask if category of full path lead to this widget.
Args:
category (str): The category name.
Returns:
bool: Passed category lead to this widget.
"""
return False
def set_category_path(self, category, path):
"""Change path of widget based on category full path."""
pass
def set_path(self, path):
self.breadcrumbs_widget.set_path(path)
@ -316,6 +349,12 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
)
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
@ -555,6 +594,14 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
class SystemWidget(SettingsCategoryWidget):
def contain_category_key(self, category):
if category == "system_settings":
return True
return False
def set_category_path(self, category, path):
self.breadcrumbs_widget.change_path(path)
def _create_root_entity(self):
self.entity = SystemSettings(set_studio_state=False)
self.entity.on_change_callbacks.append(self._on_entity_change)
@ -591,6 +638,21 @@ class SystemWidget(SettingsCategoryWidget):
class ProjectWidget(SettingsCategoryWidget):
def contain_category_key(self, category):
if category in ("project_settings", "project_anatomy"):
return True
return False
def set_category_path(self, category, path):
if path:
path_items = path.split("/")
if path_items[0] not in ("project_settings", "project_anatomy"):
path = "/".join([category, path])
else:
path = category
self.breadcrumbs_widget.change_path(path)
def initialize_attributes(self):
self.project_name = None
@ -606,6 +668,14 @@ class ProjectWidget(SettingsCategoryWidget):
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.

View file

@ -970,6 +970,13 @@ class ProjectListWidget(QtWidgets.QWidget):
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():

View file

@ -63,7 +63,9 @@ class MainWidget(QtWidgets.QWidget):
tab_widget.restart_required_trigger.connect(
self._on_restart_required
)
tab_widget.full_path_requested.connect(self._on_full_path_request)
self._header_tab_widget = header_tab_widget
self.tab_widgets = tab_widgets
def _on_tab_save(self, source_widget):
@ -90,6 +92,14 @@ class MainWidget(QtWidgets.QWidget):
if app:
app.processEvents()
def _on_full_path_request(self, category, path):
for tab_widget in self.tab_widgets:
if tab_widget.contain_category_key(category):
idx = self._header_tab_widget.indexOf(tab_widget)
self._header_tab_widget.setCurrentIndex(idx)
tab_widget.set_category_path(category, path)
break
def showEvent(self, event):
super(MainWidget, self).showEvent(event)
if self._reset_on_show: