Merge pull request #2570 from pypeclub/feature/OP-2361_Store-settings-by-OpenPype-version

General: Store settings by OpenPype version
This commit is contained in:
Jakub Trllo 2022-02-10 11:37:32 +01:00 committed by GitHub
commit f1c2acdfd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1722 additions and 293 deletions

View file

@ -14,6 +14,15 @@ def get_resource(*args):
return os.path.normpath(os.path.join(RESOURCES_DIR, *args))
def get_image_path(*args):
"""Helper function to get images.
Args:
*<str>: Filepath part items.
"""
return get_resource("images", *args)
def get_liberation_font_path(bold=False, italic=False):
font_name = "LiberationSans"
suffix = ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -5,6 +5,8 @@ from .constants import (
PROJECT_ANATOMY_KEY,
LOCAL_SETTING_KEY,
LEGACY_SETTINGS_VERSION,
SCHEMA_KEY_SYSTEM_SETTINGS,
SCHEMA_KEY_PROJECT_SETTINGS,
@ -37,6 +39,8 @@ __all__ = (
"PROJECT_ANATOMY_KEY",
"LOCAL_SETTING_KEY",
"LEGACY_SETTINGS_VERSION",
"SCHEMA_KEY_SYSTEM_SETTINGS",
"SCHEMA_KEY_PROJECT_SETTINGS",

View file

@ -21,6 +21,8 @@ PROJECT_SETTINGS_KEY = "project_settings"
PROJECT_ANATOMY_KEY = "project_anatomy"
LOCAL_SETTING_KEY = "local_settings"
LEGACY_SETTINGS_VERSION = "legacy"
# Schema hub names
SCHEMA_KEY_SYSTEM_SETTINGS = "system_schema"
SCHEMA_KEY_PROJECT_SETTINGS = "projects_schema"

View file

@ -34,15 +34,24 @@ from openpype.settings.lib import (
reset_default_settings,
get_studio_system_settings_overrides,
get_studio_system_settings_overrides_for_version,
save_studio_settings,
get_available_studio_system_settings_overrides_versions,
get_studio_project_settings_overrides,
get_studio_project_settings_overrides_for_version,
get_studio_project_anatomy_overrides,
get_studio_project_anatomy_overrides_for_version,
get_project_settings_overrides,
get_project_settings_overrides_for_version,
get_project_anatomy_overrides,
save_project_settings,
save_project_anatomy,
get_available_project_settings_overrides_versions,
get_available_studio_project_settings_overrides_versions,
get_available_studio_project_anatomy_overrides_versions,
find_environments,
apply_overrides
)
@ -495,17 +504,27 @@ class SystemSettings(RootEntity):
root_key = SYSTEM_SETTINGS_KEY
def __init__(
self, set_studio_state=True, reset=True, schema_hub=None
self,
set_studio_state=True,
reset=True,
schema_hub=None,
source_version=None
):
if schema_hub is None:
# Load system schemas
schema_hub = SchemasHub(SCHEMA_KEY_SYSTEM_SETTINGS)
self._source_version = source_version
super(SystemSettings, self).__init__(schema_hub, reset)
if set_studio_state:
self.set_studio_state()
@property
def source_version(self):
return self._source_version
def get_entity_from_path(self, path):
"""Return system settings entity."""
path_parts = path.split("/")
@ -524,12 +543,24 @@ class SystemSettings(RootEntity):
value = default_value.get(key, NOT_SET)
child_obj.update_default_value(value)
studio_overrides = get_studio_system_settings_overrides()
if self._source_version is None:
studio_overrides, version = get_studio_system_settings_overrides(
return_version=True
)
self._source_version = version
else:
studio_overrides = (
get_studio_system_settings_overrides_for_version(
self._source_version
)
)
for key, child_obj in self.non_gui_children.items():
value = studio_overrides.get(key, NOT_SET)
child_obj.update_studio_value(value)
def reset(self, new_state=None):
def reset(self, new_state=None, source_version=None):
"""Discard changes and reset entit's values.
Reload default values and studio override values and update entities.
@ -547,9 +578,22 @@ class SystemSettings(RootEntity):
if new_state is OverrideState.PROJECT:
raise ValueError("System settings can't store poject overrides.")
if source_version is not None:
self._source_version = source_version
self._reset_values()
self.set_override_state(new_state)
def get_available_source_versions(self, sorted=None):
if self.is_in_studio_state():
return self.get_available_studio_versions(sorted=sorted)
return []
def get_available_studio_versions(self, sorted=None):
return get_available_studio_system_settings_overrides_versions(
sorted=sorted
)
def defaults_dir(self):
"""Path to defaults directory.
@ -566,6 +610,8 @@ class SystemSettings(RootEntity):
json.dumps(settings_value, indent=4)
))
save_studio_settings(settings_value)
# Reset source version after restart
self._source_version = None
def _validate_defaults_to_save(self, value):
"""Valiations of default values before save."""
@ -622,11 +668,15 @@ class ProjectSettings(RootEntity):
project_name=None,
change_state=True,
reset=True,
schema_hub=None
schema_hub=None,
source_version=None,
anatomy_source_version=None
):
self._project_name = project_name
self._system_settings_entity = None
self._source_version = source_version
self._anatomy_source_version = anatomy_source_version
if schema_hub is None:
# Load system schemas
@ -640,6 +690,14 @@ class ProjectSettings(RootEntity):
else:
self.set_project_state()
@property
def source_version(self):
return self._source_version
@property
def anatomy_source_version(self):
return self._anatomy_source_version
@property
def project_name(self):
return self._project_name
@ -682,23 +740,20 @@ class ProjectSettings(RootEntity):
output = output[path_part]
return output
def change_project(self, project_name):
def change_project(self, project_name, source_version=None):
if project_name == self._project_name:
return
if (
source_version is None
or source_version == self._source_version
):
if not self.is_in_project_state():
self.set_project_state()
return
self._project_name = project_name
if project_name is None:
self.set_studio_state()
return
project_override_value = {
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
}
for key, child_obj in self.non_gui_children.items():
value = project_override_value.get(key, NOT_SET)
child_obj.update_project_value(value)
self._source_version = source_version
self._anatomy_source_version = None
self._set_values_for_project(project_name)
self.set_project_state()
def _reset_values(self):
@ -710,27 +765,97 @@ class ProjectSettings(RootEntity):
value = default_values.get(key, NOT_SET)
child_obj.update_default_value(value)
self._set_values_for_project(self.project_name)
def _set_values_for_project(self, project_name):
self._project_name = project_name
if project_name:
project_settings_overrides = (
get_studio_project_settings_overrides()
)
project_anatomy_overrides = (
get_studio_project_anatomy_overrides()
)
else:
if self._source_version is None:
project_settings_overrides, version = (
get_studio_project_settings_overrides(return_version=True)
)
self._source_version = version
else:
project_settings_overrides = (
get_studio_project_settings_overrides_for_version(
self._source_version
)
)
if self._anatomy_source_version is None:
project_anatomy_overrides, anatomy_version = (
get_studio_project_anatomy_overrides(return_version=True)
)
self._anatomy_source_version = anatomy_version
else:
project_anatomy_overrides = (
get_studio_project_anatomy_overrides_for_version(
self._anatomy_source_version
)
)
studio_overrides = {
PROJECT_SETTINGS_KEY: get_studio_project_settings_overrides(),
PROJECT_ANATOMY_KEY: get_studio_project_anatomy_overrides()
PROJECT_SETTINGS_KEY: project_settings_overrides,
PROJECT_ANATOMY_KEY: project_anatomy_overrides
}
for key, child_obj in self.non_gui_children.items():
value = studio_overrides.get(key, NOT_SET)
child_obj.update_studio_value(value)
if not self.project_name:
if not project_name:
return
project_name = self.project_name
if self._source_version is None:
project_settings_overrides, version = (
get_project_settings_overrides(
project_name, return_version=True
)
)
self._source_version = version
else:
project_settings_overrides = (
get_project_settings_overrides_for_version(
project_name, self._source_version
)
)
project_override_value = {
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
PROJECT_SETTINGS_KEY: project_settings_overrides,
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
}
for key, child_obj in self.non_gui_children.items():
value = project_override_value.get(key, NOT_SET)
child_obj.update_project_value(value)
def get_available_source_versions(self, sorted=None):
if self.is_in_studio_state():
return self.get_available_studio_versions(sorted=sorted)
elif self.is_in_project_state():
return get_available_project_settings_overrides_versions(
self.project_name, sorted=sorted
)
return []
def get_available_studio_versions(self, sorted=None):
return get_available_studio_project_settings_overrides_versions(
sorted=sorted
)
def get_available_anatomy_source_versions(self, sorted=None):
if self.is_in_studio_state():
return get_available_studio_project_anatomy_overrides_versions(
sorted=sorted
)
return []
def reset(self, new_state=None):
"""Discard changes and reset entit's values.
@ -763,6 +888,9 @@ class ProjectSettings(RootEntity):
self._validate_values_to_save(settings_value)
self._source_version = None
self._anatomy_source_version = None
self.log.debug("Saving project settings: {}".format(
json.dumps(settings_value, indent=4)
))

File diff suppressed because it is too large Load diff

View file

@ -266,23 +266,31 @@ def save_project_anatomy(project_name, anatomy_data):
@require_handler
def get_studio_system_settings_overrides():
return _SETTINGS_HANDLER.get_studio_system_settings_overrides()
def get_studio_system_settings_overrides(return_version=False):
return _SETTINGS_HANDLER.get_studio_system_settings_overrides(
return_version
)
@require_handler
def get_studio_project_settings_overrides():
return _SETTINGS_HANDLER.get_studio_project_settings_overrides()
def get_studio_project_settings_overrides(return_version=False):
return _SETTINGS_HANDLER.get_studio_project_settings_overrides(
return_version
)
@require_handler
def get_studio_project_anatomy_overrides():
return _SETTINGS_HANDLER.get_studio_project_anatomy_overrides()
def get_studio_project_anatomy_overrides(return_version=False):
return _SETTINGS_HANDLER.get_studio_project_anatomy_overrides(
return_version
)
@require_handler
def get_project_settings_overrides(project_name):
return _SETTINGS_HANDLER.get_project_settings_overrides(project_name)
def get_project_settings_overrides(project_name, return_version=False):
return _SETTINGS_HANDLER.get_project_settings_overrides(
project_name, return_version
)
@require_handler
@ -290,6 +298,123 @@ def get_project_anatomy_overrides(project_name):
return _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name)
@require_handler
def get_studio_system_settings_overrides_for_version(version):
return (
_SETTINGS_HANDLER
.get_studio_system_settings_overrides_for_version(version)
)
@require_handler
def get_studio_project_anatomy_overrides_for_version(version):
return (
_SETTINGS_HANDLER
.get_studio_project_anatomy_overrides_for_version(version)
)
@require_handler
def get_studio_project_settings_overrides_for_version(version):
return (
_SETTINGS_HANDLER
.get_studio_project_settings_overrides_for_version(version)
)
@require_handler
def get_project_settings_overrides_for_version(
project_name, version
):
return (
_SETTINGS_HANDLER
.get_project_settings_overrides_for_version(project_name, version)
)
@require_handler
def get_available_studio_system_settings_overrides_versions(sorted=None):
return (
_SETTINGS_HANDLER
.get_available_studio_system_settings_overrides_versions(
sorted=sorted
)
)
@require_handler
def get_available_studio_project_anatomy_overrides_versions(sorted=None):
return (
_SETTINGS_HANDLER
.get_available_studio_project_anatomy_overrides_versions(
sorted=sorted
)
)
@require_handler
def get_available_studio_project_settings_overrides_versions(sorted=None):
return (
_SETTINGS_HANDLER
.get_available_studio_project_settings_overrides_versions(
sorted=sorted
)
)
@require_handler
def get_available_project_settings_overrides_versions(
project_name, sorted=None
):
return (
_SETTINGS_HANDLER
.get_available_project_settings_overrides_versions(
project_name, sorted=sorted
)
)
@require_handler
def find_closest_version_for_projects(project_names):
return (
_SETTINGS_HANDLER
.find_closest_version_for_projects(project_names)
)
@require_handler
def clear_studio_system_settings_overrides_for_version(version):
return (
_SETTINGS_HANDLER
.clear_studio_system_settings_overrides_for_version(version)
)
@require_handler
def clear_studio_project_settings_overrides_for_version(version):
return (
_SETTINGS_HANDLER
.clear_studio_project_settings_overrides_for_version(version)
)
@require_handler
def clear_studio_project_anatomy_overrides_for_version(version):
return (
_SETTINGS_HANDLER
.clear_studio_project_anatomy_overrides_for_version(version)
)
@require_handler
def clear_project_settings_overrides_for_version(
version, project_name
):
return _SETTINGS_HANDLER.clear_project_settings_overrides_for_version(
version, project_name
)
@require_local_handler
def save_local_settings(data):
return _LOCAL_SETTINGS_HANDLER.save_local_settings(data)

View file

@ -118,7 +118,10 @@
"image-btn-hover": "#189aea",
"image-btn-disabled": "#bfccd6",
"version-exists": "#458056",
"version-not-found": "#ffc671"
"version-not-found": "#ffc671",
"source-version": "#D3D8DE",
"source-version-outdated": "#ffc671"
}
}
}

View file

@ -1117,6 +1117,20 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
#ExpandLabel[state="invalid"]:hover, #SettingsLabel[state="invalid"]:hover {
color: {color:settings:invalid-dark};
}
#SettingsOutdatedSourceVersion {
color: {color:settings:source-version-outdated};
}
#SourceVersionLabel {
padding-left: 3px;
padding-right: 3px;
}
#SourceVersionLabel[state="same"] {
color: {color:settings:source-version};
}
#SourceVersionLabel[state="different"] {
color: {color:settings:source-version-outdated};
}
/* TODO Replace these with explicit widget types if possible */
#SettingsMainWidget QWidget[input-state="modified"] {
@ -1132,8 +1146,8 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
border-color: {color:settings:invalid-dark};
}
#GroupWidget {
border-bottom: 1px solid #21252B;
#SettingsFooter {
border-top: 1px solid #21252B;
}
#ProjectListWidget QLabel {
@ -1141,6 +1155,10 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
font-weight: bold;
}
#ProjectListContentWidget {
background: {color:bg-view};
}
#MultiSelectionComboBox {
font-size: 12px;
}

View file

@ -1,8 +1,8 @@
import os
from Qt import QtCore, QtGui
from openpype.style import get_objected_colors
from avalon.vendor import qtawesome
from openpype.tools.utils import paint_image_with_color
class ResourceCache:
@ -91,17 +91,6 @@ class ResourceCache:
icon.addPixmap(disabled_pix, QtGui.QIcon.Disabled, QtGui.QIcon.Off)
return icon
@classmethod
def get_warning_pixmap(cls):
src_image = get_warning_image()
colors = get_objected_colors()
color_value = colors["delete-btn-bg"]
return paint_image_with_color(
src_image,
color_value.get_qcolor()
)
def get_remove_image():
image_path = os.path.join(
@ -110,36 +99,3 @@ def get_remove_image():
"bin.png"
)
return QtGui.QImage(image_path)
def get_warning_image():
image_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"images",
"warning.png"
)
return QtGui.QImage(image_path)
def paint_image_with_color(image, color):
"""TODO: This function should be imported from utils.
At the moment of creation is not available yet.
"""
width = image.width()
height = image.height()
alpha_mask = image.createAlphaMask()
alpha_region = QtGui.QRegion(QtGui.QBitmap.fromImage(alpha_mask))
pixmap = QtGui.QPixmap(width, height)
pixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pixmap)
painter.setClipRegion(alpha_region)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(color)
painter.drawRect(QtCore.QRect(0, 0, width, height))
painter.end()
return pixmap

View file

@ -4,14 +4,16 @@ from .constants import (
NAME_ALLOWED_SYMBOLS,
NAME_REGEX
)
from .style import ResourceCache
from openpype.lib import (
create_project,
PROJECT_NAME_ALLOWED_SYMBOLS,
PROJECT_NAME_REGEX
)
from openpype.style import load_stylesheet
from openpype.tools.utils import PlaceholderLineEdit
from openpype.tools.utils import (
PlaceholderLineEdit,
get_warning_pixmap
)
from avalon.api import AvalonMongoDB
from Qt import QtWidgets, QtCore, QtGui
@ -338,7 +340,7 @@ class ConfirmProjectDeletion(QtWidgets.QDialog):
top_widget = QtWidgets.QWidget(self)
warning_pixmap = ResourceCache.get_warning_pixmap()
warning_pixmap = get_warning_pixmap()
warning_icon_label = PixmapLabel(warning_pixmap, top_widget)
message_label = QtWidgets.QLabel(top_widget)

View file

@ -3,8 +3,10 @@ import sys
import traceback
import contextlib
from enum import Enum
from Qt import QtWidgets, QtCore, QtGui
from Qt import QtWidgets, QtCore
from openpype.lib import get_openpype_version
from openpype.tools.utils import set_style_property
from openpype.settings.entities import (
SystemSettings,
ProjectSettings,
@ -34,7 +36,10 @@ from openpype.settings.entities.op_version_entity import (
)
from openpype.settings import SaveWarningExc
from .widgets import ProjectListWidget
from .widgets import (
ProjectListWidget,
VersionAction
)
from .breadcrumbs_widget import (
BreadcrumbsAddressBar,
SystemSettingsBreadcrumbs,
@ -88,6 +93,20 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
restart_required_trigger = QtCore.Signal()
full_path_requested = QtCore.Signal(str, str)
require_restart_label_text = (
"Your changes require restart of"
" all running OpenPype processes to take affect."
)
outdated_version_label_text = (
"Your settings are loaded from an older version."
)
source_version_tooltip = "Using settings of current OpenPype version"
source_version_tooltip_outdated = (
"Please check that all settings are still correct (blue colour\n"
"indicates potential changes in the new version) and save your\n"
"settings to update them to you current running OpenPype version."
)
def __init__(self, user_role, parent=None):
super(SettingsCategoryWidget, self).__init__(parent)
@ -98,6 +117,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
self._state = CategoryState.Idle
self._hide_studio_overrides = False
self._updating_root = False
self._use_version = None
self._current_version = get_openpype_version()
self.ignore_input_changes = IgnoreInputChangesObj(self)
self.keys = []
@ -183,77 +206,126 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
def initialize_attributes(self):
return
@property
def is_modifying_defaults(self):
if self.modify_defaults_checkbox is None:
return False
return self.modify_defaults_checkbox.isChecked()
def create_ui(self):
self.modify_defaults_checkbox = None
scroll_widget = QtWidgets.QScrollArea(self)
scroll_widget.setObjectName("GroupWidget")
content_widget = QtWidgets.QWidget(scroll_widget)
conf_wrapper_widget = QtWidgets.QWidget(self)
configurations_widget = QtWidgets.QWidget(conf_wrapper_widget)
breadcrumbs_label = QtWidgets.QLabel("Path:", content_widget)
breadcrumbs_widget = BreadcrumbsAddressBar(content_widget)
# Breadcrumbs/Path widget
breadcrumbs_widget = QtWidgets.QWidget(self)
breadcrumbs_label = QtWidgets.QLabel("Path:", breadcrumbs_widget)
breadcrumbs_bar = BreadcrumbsAddressBar(breadcrumbs_widget)
breadcrumbs_layout = QtWidgets.QHBoxLayout()
refresh_icon = qtawesome.icon("fa.refresh", color="white")
refresh_btn = QtWidgets.QPushButton(breadcrumbs_widget)
refresh_btn.setIcon(refresh_icon)
breadcrumbs_layout = QtWidgets.QHBoxLayout(breadcrumbs_widget)
breadcrumbs_layout.setContentsMargins(5, 5, 5, 5)
breadcrumbs_layout.setSpacing(5)
breadcrumbs_layout.addWidget(breadcrumbs_label)
breadcrumbs_layout.addWidget(breadcrumbs_widget)
breadcrumbs_layout.addWidget(breadcrumbs_label, 0)
breadcrumbs_layout.addWidget(breadcrumbs_bar, 1)
breadcrumbs_layout.addWidget(refresh_btn, 0)
# Widgets representing settings entities
scroll_widget = QtWidgets.QScrollArea(configurations_widget)
content_widget = QtWidgets.QWidget(scroll_widget)
scroll_widget.setWidgetResizable(True)
scroll_widget.setWidget(content_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(3, 3, 3, 3)
content_layout.setSpacing(5)
content_layout.setAlignment(QtCore.Qt.AlignTop)
scroll_widget.setWidgetResizable(True)
scroll_widget.setWidget(content_widget)
# Footer widget
footer_widget = QtWidgets.QWidget(self)
footer_widget.setObjectName("SettingsFooter")
refresh_icon = qtawesome.icon("fa.refresh", color="white")
refresh_btn = QtWidgets.QPushButton(self)
refresh_btn.setIcon(refresh_icon)
# Info labels
# TODO dynamic labels
labels_alignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
empty_label = QtWidgets.QLabel(footer_widget)
footer_layout = QtWidgets.QHBoxLayout()
outdated_version_label = QtWidgets.QLabel(
self.outdated_version_label_text, footer_widget
)
outdated_version_label.setToolTip(self.source_version_tooltip_outdated)
outdated_version_label.setAlignment(labels_alignment)
outdated_version_label.setVisible(False)
outdated_version_label.setObjectName("SettingsOutdatedSourceVersion")
require_restart_label = QtWidgets.QLabel(
self.require_restart_label_text, footer_widget
)
require_restart_label.setAlignment(labels_alignment)
require_restart_label.setVisible(False)
# Label showing source version of loaded settings
source_version_label = QtWidgets.QLabel("", footer_widget)
source_version_label.setObjectName("SourceVersionLabel")
set_style_property(source_version_label, "state", "")
source_version_label.setToolTip(self.source_version_tooltip)
save_btn = QtWidgets.QPushButton("Save", footer_widget)
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
footer_layout.setContentsMargins(5, 5, 5, 5)
if self.user_role == "developer":
self._add_developer_ui(footer_layout)
self._add_developer_ui(footer_layout, footer_widget)
save_btn = QtWidgets.QPushButton("Save", self)
require_restart_label = QtWidgets.QLabel(self)
require_restart_label.setAlignment(QtCore.Qt.AlignCenter)
footer_layout.addWidget(refresh_btn, 0)
footer_layout.addWidget(empty_label, 1)
footer_layout.addWidget(outdated_version_label, 1)
footer_layout.addWidget(require_restart_label, 1)
footer_layout.addWidget(source_version_label, 0)
footer_layout.addWidget(save_btn, 0)
configurations_layout = QtWidgets.QVBoxLayout()
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
configurations_layout.setContentsMargins(0, 0, 0, 0)
configurations_layout.setSpacing(0)
configurations_layout.addWidget(scroll_widget, 1)
configurations_layout.addLayout(footer_layout, 0)
conf_wrapper_layout = QtWidgets.QHBoxLayout()
conf_wrapper_layout = QtWidgets.QHBoxLayout(conf_wrapper_widget)
conf_wrapper_layout.setContentsMargins(0, 0, 0, 0)
conf_wrapper_layout.setSpacing(0)
conf_wrapper_layout.addLayout(configurations_layout, 1)
conf_wrapper_layout.addWidget(configurations_widget, 1)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.addLayout(breadcrumbs_layout, 0)
main_layout.addLayout(conf_wrapper_layout, 1)
main_layout.addWidget(breadcrumbs_widget, 0)
main_layout.addWidget(conf_wrapper_widget, 1)
main_layout.addWidget(footer_widget, 0)
save_btn.clicked.connect(self._save)
refresh_btn.clicked.connect(self._on_refresh)
breadcrumbs_widget.path_edited.connect(self._on_path_edit)
breadcrumbs_bar.path_edited.connect(self._on_path_edit)
self._require_restart_label = require_restart_label
self._outdated_version_label = outdated_version_label
self._empty_label = empty_label
self._is_loaded_version_outdated = False
self.save_btn = save_btn
self.refresh_btn = refresh_btn
self.require_restart_label = require_restart_label
self._source_version_label = source_version_label
self.scroll_widget = scroll_widget
self.content_layout = content_layout
self.content_widget = content_widget
self.breadcrumbs_widget = breadcrumbs_widget
self.breadcrumbs_bar = breadcrumbs_bar
self.breadcrumbs_model = None
self.refresh_btn = refresh_btn
self.conf_wrapper_layout = conf_wrapper_layout
self.main_layout = main_layout
@ -308,21 +380,17 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
pass
def set_path(self, path):
self.breadcrumbs_widget.set_path(path)
self.breadcrumbs_bar.set_path(path)
def _add_developer_ui(self, footer_layout):
modify_defaults_widget = QtWidgets.QWidget()
modify_defaults_checkbox = QtWidgets.QCheckBox(modify_defaults_widget)
def _add_developer_ui(self, footer_layout, footer_widget):
modify_defaults_checkbox = QtWidgets.QCheckBox(footer_widget)
modify_defaults_checkbox.setChecked(self._hide_studio_overrides)
label_widget = QtWidgets.QLabel(
"Modify defaults", modify_defaults_widget
"Modify defaults", footer_widget
)
modify_defaults_layout = QtWidgets.QHBoxLayout(modify_defaults_widget)
modify_defaults_layout.addWidget(label_widget)
modify_defaults_layout.addWidget(modify_defaults_checkbox)
footer_layout.addWidget(modify_defaults_widget, 0)
footer_layout.addWidget(label_widget, 0)
footer_layout.addWidget(modify_defaults_checkbox, 0)
modify_defaults_checkbox.stateChanged.connect(
self._on_modify_defaults
@ -361,6 +429,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
try:
self.entity.save()
self._use_version = None
# NOTE There are relations to previous entities and C++ callbacks
# so it is easier to just use new entity and recreate UI but
@ -420,13 +489,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
return
def _on_require_restart_change(self):
value = ""
if self.entity.require_restart:
value = (
"Your changes require restart of"
" all running OpenPype processes to take affect."
)
self.require_restart_label.setText(value)
self._update_labels_visibility()
def reset(self):
self.set_state(CategoryState.Working)
@ -444,6 +507,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
widget.deleteLater()
dialog = None
self._updating_root = True
source_version = ""
try:
self._create_root_entity()
@ -459,6 +524,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
input_field.set_entity_value()
self.ignore_input_changes.set_ignore(False)
source_version = self.entity.source_version
except DefaultsNotDefined:
dialog = QtWidgets.QMessageBox(self)
@ -502,6 +568,27 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
spacer, layout.rowCount(), 0, 1, layout.columnCount()
)
self._updating_root = False
# Update source version label
state_value = ""
tooltip = ""
outdated = False
if source_version:
if source_version != self._current_version:
state_value = "different"
tooltip = self.source_version_tooltip_outdated
outdated = True
else:
state_value = "same"
tooltip = self.source_version_tooltip
self._is_loaded_version_outdated = outdated
self._source_version_label.setText(source_version)
self._source_version_label.setToolTip(tooltip)
set_style_property(self._source_version_label, "state", state_value)
self._update_labels_visibility()
self.set_state(CategoryState.Idle)
if dialog:
@ -510,6 +597,36 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
else:
self._on_reset_success()
def _on_source_version_change(self, version):
if self._updating_root:
return
if version == self._current_version:
version = None
self._use_version = version
QtCore.QTimer.singleShot(20, self.reset)
def add_context_actions(self, menu):
if not self.entity or self.is_modifying_defaults:
return
versions = self.entity.get_available_studio_versions(sorted=True)
if not versions:
return
submenu = QtWidgets.QMenu("Use settings from version", menu)
for version in reversed(versions):
action = VersionAction(version, submenu)
action.version_triggered.connect(
self._on_context_version_trigger
)
submenu.addAction(action)
menu.addMenu(submenu)
def _on_context_version_trigger(self, version):
self._on_source_version_change(version)
def _on_reset_crash(self):
self.save_btn.setEnabled(False)
@ -521,10 +638,10 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
self.save_btn.setEnabled(True)
if self.breadcrumbs_model is not None:
path = self.breadcrumbs_widget.path()
self.breadcrumbs_widget.set_path("")
path = self.breadcrumbs_bar.path()
self.breadcrumbs_bar.set_path("")
self.breadcrumbs_model.set_entity(self.entity)
self.breadcrumbs_widget.change_path(path)
self.breadcrumbs_bar.change_path(path)
def add_children_gui(self):
for child_obj in self.entity.children:
@ -565,10 +682,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
def _save(self):
# Don't trigger restart if defaults are modified
if (
self.modify_defaults_checkbox
and self.modify_defaults_checkbox.isChecked()
):
if self.is_modifying_defaults:
require_restart = False
else:
require_restart = self.entity.require_restart
@ -584,7 +698,29 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
if require_restart:
self.restart_required_trigger.emit()
self.require_restart_label.setText("")
def _update_labels_visibility(self):
visible_label = None
labels = {
self._empty_label,
self._outdated_version_label,
self._require_restart_label,
}
if self.entity.require_restart:
visible_label = self._require_restart_label
elif self._is_loaded_version_outdated:
visible_label = self._outdated_version_label
else:
visible_label = self._empty_label
if visible_label.isVisible():
return
for label in labels:
if label is visible_label:
visible_label.setVisible(True)
else:
label.setVisible(False)
def _on_refresh(self):
self.reset()
@ -594,25 +730,29 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
class SystemWidget(SettingsCategoryWidget):
def __init__(self, *args, **kwargs):
self._actions = []
super(SystemWidget, self).__init__(*args, **kwargs)
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)
self.breadcrumbs_bar.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)
entity = SystemSettings(
set_studio_state=False, source_version=self._use_version
)
entity.on_change_callbacks.append(self._on_entity_change)
self.entity = entity
try:
if (
self.modify_defaults_checkbox
and self.modify_defaults_checkbox.isChecked()
):
self.entity.set_defaults_state()
if self.is_modifying_defaults:
entity.set_defaults_state()
else:
self.entity.set_studio_state()
entity.set_studio_state()
if self.modify_defaults_checkbox:
self.modify_defaults_checkbox.setEnabled(True)
@ -620,16 +760,16 @@ class SystemWidget(SettingsCategoryWidget):
if not self.modify_defaults_checkbox:
raise
self.entity.set_defaults_state()
entity.set_defaults_state()
self.modify_defaults_checkbox.setChecked(True)
self.modify_defaults_checkbox.setEnabled(False)
def ui_tweaks(self):
self.breadcrumbs_model = SystemSettingsBreadcrumbs()
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
self.breadcrumbs_bar.set_model(self.breadcrumbs_model)
def _on_modify_defaults(self):
if self.modify_defaults_checkbox.isChecked():
if self.is_modifying_defaults:
if not self.entity.is_in_defaults_state():
self.reset()
else:
@ -638,6 +778,9 @@ class SystemWidget(SettingsCategoryWidget):
class ProjectWidget(SettingsCategoryWidget):
def __init__(self, *args, **kwargs):
super(ProjectWidget, self).__init__(*args, **kwargs)
def contain_category_key(self, category):
if category in ("project_settings", "project_anatomy"):
return True
@ -651,28 +794,28 @@ class ProjectWidget(SettingsCategoryWidget):
else:
path = category
self.breadcrumbs_widget.change_path(path)
self.breadcrumbs_bar.change_path(path)
def initialize_attributes(self):
self.project_name = None
def ui_tweaks(self):
self.breadcrumbs_model = ProjectSettingsBreadcrumbs()
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
self.breadcrumbs_bar.set_model(self.breadcrumbs_model)
project_list_widget = ProjectListWidget(self)
self.conf_wrapper_layout.insertWidget(0, project_list_widget, 0)
project_list_widget.project_changed.connect(self._on_project_change)
project_list_widget.version_change_requested.connect(
self._on_source_version_change
)
self.project_list_widget = project_list_widget
def get_project_names(self):
if (
self.modify_defaults_checkbox
and self.modify_defaults_checkbox.isChecked()
):
if self.is_modifying_defaults:
return []
return self.project_list_widget.get_project_names()
@ -684,6 +827,10 @@ class ProjectWidget(SettingsCategoryWidget):
if self is saved_tab_widget:
return
def _on_context_version_trigger(self, version):
self.project_list_widget.select_project(None)
super(ProjectWidget, self)._on_context_version_trigger(version)
def _on_reset_start(self):
self.project_list_widget.refresh()
@ -696,32 +843,29 @@ class ProjectWidget(SettingsCategoryWidget):
super(ProjectWidget, self)._on_reset_success()
def _set_enabled_project_list(self, enabled):
if (
enabled
and self.modify_defaults_checkbox
and self.modify_defaults_checkbox.isChecked()
):
if enabled and self.is_modifying_defaults:
enabled = False
if self.project_list_widget.isEnabled() != enabled:
self.project_list_widget.setEnabled(enabled)
def _create_root_entity(self):
self.entity = ProjectSettings(change_state=False)
self.entity.on_change_callbacks.append(self._on_entity_change)
entity = ProjectSettings(
change_state=False, source_version=self._use_version
)
entity.on_change_callbacks.append(self._on_entity_change)
self.project_list_widget.set_entity(entity)
self.entity = entity
try:
if (
self.modify_defaults_checkbox
and self.modify_defaults_checkbox.isChecked()
):
if self.is_modifying_defaults:
self.entity.set_defaults_state()
elif self.project_name is None:
self.entity.set_studio_state()
elif self.project_name == self.entity.project_name:
self.entity.set_project_state()
else:
self.entity.change_project(self.project_name)
self.entity.change_project(
self.project_name, self._use_version
)
if self.modify_defaults_checkbox:
self.modify_defaults_checkbox.setEnabled(True)
@ -754,7 +898,7 @@ class ProjectWidget(SettingsCategoryWidget):
self.set_state(CategoryState.Idle)
def _on_modify_defaults(self):
if self.modify_defaults_checkbox.isChecked():
if self.is_modifying_defaults:
self._set_enabled_project_list(False)
if not self.entity.is_in_defaults_state():
self.reset()

View file

@ -5,6 +5,7 @@ DEFAULT_PROJECT_LABEL = "< Default >"
PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 1
PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2
PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3
PROJECT_VERSION_ROLE = QtCore.Qt.UserRole + 4
__all__ = (
@ -12,5 +13,6 @@ __all__ = (
"PROJECT_NAME_ROLE",
"PROJECT_IS_ACTIVE_ROLE",
"PROJECT_IS_SELECTED_ROLE"
"PROJECT_IS_SELECTED_ROLE",
"PROJECT_VERSION_ROLE",
)

View file

@ -1,5 +1,6 @@
import os
import copy
import uuid
from Qt import QtWidgets, QtCore, QtGui
from avalon.vendor import qtawesome
from avalon.mongodb import (
@ -12,8 +13,12 @@ from openpype.tools.utils.widgets import ImageButton
from openpype.tools.utils.lib import paint_image_with_color
from openpype.widgets.nice_checkbox import NiceCheckbox
from openpype.tools.utils import PlaceholderLineEdit
from openpype.settings.lib import get_system_settings
from openpype.tools.utils import (
PlaceholderLineEdit,
DynamicQThread
)
from openpype.settings.lib import find_closest_version_for_projects
from openpype.lib import get_openpype_version
from .images import (
get_pixmap,
get_image
@ -21,11 +26,40 @@ from .images import (
from .constants import (
DEFAULT_PROJECT_LABEL,
PROJECT_NAME_ROLE,
PROJECT_VERSION_ROLE,
PROJECT_IS_ACTIVE_ROLE,
PROJECT_IS_SELECTED_ROLE
)
class SettingsTabWidget(QtWidgets.QTabWidget):
context_menu_requested = QtCore.Signal(int)
def __init__(self, *args, **kwargs):
super(SettingsTabWidget, self).__init__(*args, **kwargs)
self._right_click_tab_idx = None
def mousePressEvent(self, event):
super(SettingsTabWidget, self).mousePressEvent(event)
if event.button() == QtCore.Qt.RightButton:
tab_bar = self.tabBar()
pos = tab_bar.mapFromGlobal(event.globalPos())
tab_idx = tab_bar.tabAt(pos)
if tab_idx < 0:
tab_idx = None
self._right_click_tab_idx = tab_idx
def mouseReleaseEvent(self, event):
super(SettingsTabWidget, self).mouseReleaseEvent(event)
if event.button() == QtCore.Qt.RightButton:
tab_bar = self.tabBar()
pos = tab_bar.mapFromGlobal(event.globalPos())
tab_idx = tab_bar.tabAt(pos)
if tab_idx == self._right_click_tab_idx:
self.context_menu_requested.emit(tab_idx)
self._right_click_tab = None
class CompleterFilter(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
super(CompleterFilter, self).__init__(*args, **kwargs)
@ -603,7 +637,7 @@ class UnsavedChangesDialog(QtWidgets.QDialog):
message = "You have unsaved changes. What do you want to do with them?"
def __init__(self, parent=None):
super().__init__(parent)
super(UnsavedChangesDialog, self).__init__(parent)
message_label = QtWidgets.QLabel(self.message)
btns_widget = QtWidgets.QWidget(self)
@ -735,19 +769,65 @@ class SettingsNiceCheckbox(NiceCheckbox):
class ProjectModel(QtGui.QStandardItemModel):
_update_versions = QtCore.Signal()
def __init__(self, only_active, *args, **kwargs):
super(ProjectModel, self).__init__(*args, **kwargs)
self.setColumnCount(2)
self.dbcon = None
self._only_active = only_active
self._default_item = None
self._items_by_name = {}
self._versions_by_project = {}
colors = get_objected_colors()
font_color = colors["font"].get_qcolor()
font_color.setAlpha(67)
self._version_font_color = font_color
self._current_version = get_openpype_version()
self._version_refresh_threads = []
self._version_refresh_id = None
self._update_versions.connect(self._on_update_versions_signal)
def _on_update_versions_signal(self):
for project_name, version in self._versions_by_project.items():
if project_name is None:
item = self._default_item
else:
item = self._items_by_name.get(project_name)
if item and version != self._current_version:
item.setData(version, PROJECT_VERSION_ROLE)
def _fetch_settings_versions(self):
"""Used versions per project are loaded in thread to not stuck UI."""
version_refresh_id = self._version_refresh_id
all_project_names = list(self._items_by_name.keys())
all_project_names.append(None)
closest_by_project_name = find_closest_version_for_projects(
all_project_names
)
if self._version_refresh_id == version_refresh_id:
self._versions_by_project = closest_by_project_name
self._update_versions.emit()
def flags(self, index):
if index.column() == 1:
index = self.index(index.row(), 0, index.parent())
return super(ProjectModel, self).flags(index)
def set_dbcon(self, dbcon):
self.dbcon = dbcon
def refresh(self):
# Change id of versions refresh
self._version_refresh_id = uuid.uuid4()
new_items = []
if self._default_item is None:
item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL)
@ -757,6 +837,7 @@ class ProjectModel(QtGui.QStandardItemModel):
new_items.append(item)
self._default_item = item
self._default_item.setData("", PROJECT_VERSION_ROLE)
project_names = set()
if self.dbcon is not None:
for project_doc in self.dbcon.projects(
@ -776,6 +857,7 @@ class ProjectModel(QtGui.QStandardItemModel):
is_active = project_doc.get("data", {}).get("active", True)
item.setData(project_name, PROJECT_NAME_ROLE)
item.setData(is_active, PROJECT_IS_ACTIVE_ROLE)
item.setData("", PROJECT_VERSION_ROLE)
item.setData(False, PROJECT_IS_SELECTED_ROLE)
if not is_active:
@ -792,15 +874,87 @@ class ProjectModel(QtGui.QStandardItemModel):
if new_items:
root_item.appendRows(new_items)
# Fetch versions per project in thread
thread = DynamicQThread(self._fetch_settings_versions)
self._version_refresh_threads.append(thread)
thread.start()
class ProjectListView(QtWidgets.QListView):
# Cleanup done threads
for thread in tuple(self._version_refresh_threads):
if thread.isFinished():
self._version_refresh_threads.remove(thread)
def data(self, index, role=QtCore.Qt.DisplayRole):
if index.column() == 1:
if role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignRight
if role == QtCore.Qt.ForegroundRole:
return self._version_font_color
index = self.index(index.row(), 0, index.parent())
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
role = PROJECT_VERSION_ROLE
return super(ProjectModel, self).data(index, role)
def setData(self, index, value, role=QtCore.Qt.EditRole):
if index.column() == 1:
index = self.index(index.row(), 0, index.parent())
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
role = PROJECT_VERSION_ROLE
return super(ProjectModel, self).setData(index, value, role)
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
if section == 0:
return "Project name"
elif section == 1:
return "Used version"
return ""
return super(ProjectModel, self).headerData(
section, orientation, role
)
class VersionAction(QtWidgets.QAction):
version_triggered = QtCore.Signal(str)
def __init__(self, version, *args, **kwargs):
super(VersionAction, self).__init__(version, *args, **kwargs)
self._version = version
self.triggered.connect(self._on_trigger)
def _on_trigger(self):
self.version_triggered.emit(self._version)
class ProjectView(QtWidgets.QTreeView):
left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
right_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
def __init__(self, *args, **kwargs):
super(ProjectView, self).__init__(*args, **kwargs)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.setIndentation(0)
# Do not allow editing
self.setEditTriggers(
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
)
# Do not automatically handle selection
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
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)
elif event.button() == QtCore.Qt.RightButton:
index = self.indexAt(event.pos())
self.right_mouse_released_at.emit(index)
super(ProjectView, self).mouseReleaseEvent(event)
class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
@ -846,18 +1000,21 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
class ProjectListWidget(QtWidgets.QWidget):
project_changed = QtCore.Signal()
version_change_requested = QtCore.Signal(str)
def __init__(self, parent, only_active=False):
self._parent = parent
self._entity = None
self.current_project = None
super(ProjectListWidget, self).__init__(parent)
self.setObjectName("ProjectListWidget")
label_widget = QtWidgets.QLabel("Projects")
content_frame = QtWidgets.QFrame(self)
content_frame.setObjectName("ProjectListContentWidget")
project_list = ProjectListView(self)
project_list = ProjectView(content_frame)
project_model = ProjectModel(only_active)
project_proxy = ProjectSortFilterProxy()
@ -865,33 +1022,37 @@ class ProjectListWidget(QtWidgets.QWidget):
project_proxy.setSourceModel(project_model)
project_list.setModel(project_proxy)
# Do not allow editing
project_list.setEditTriggers(
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
)
# Do not automatically handle selection
project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
content_layout = QtWidgets.QVBoxLayout(content_frame)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.setSpacing(0)
content_layout.addWidget(project_list, 1)
layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(3)
layout.addWidget(label_widget, 0)
layout.addWidget(project_list, 1)
inactive_chk = None
if not only_active:
checkbox_wrapper = QtWidgets.QWidget(content_frame)
checkbox_wrapper.setAttribute(QtCore.Qt.WA_TranslucentBackground)
if only_active:
inactive_chk = None
else:
inactive_chk = QtWidgets.QCheckBox(" Show Inactive Projects ")
inactive_chk = QtWidgets.QCheckBox(
"Show Inactive Projects", checkbox_wrapper
)
inactive_chk.setChecked(not project_proxy.is_filter_enabled())
layout.addSpacing(5)
layout.addWidget(inactive_chk, 0)
layout.addSpacing(5)
wrapper_layout = QtWidgets.QHBoxLayout(checkbox_wrapper)
wrapper_layout.addWidget(inactive_chk, 1)
content_layout.addWidget(checkbox_wrapper, 0)
inactive_chk.stateChanged.connect(self.on_inactive_vis_changed)
project_list.left_mouse_released_at.connect(self.on_item_clicked)
layout = QtWidgets.QVBoxLayout(self)
# Margins '3' are matching to configurables widget scroll area on right
layout.setContentsMargins(5, 3, 3, 3)
layout.addWidget(content_frame, 1)
self._default_project_item = None
project_list.left_mouse_released_at.connect(self.on_item_clicked)
project_list.right_mouse_released_at.connect(
self._on_item_right_clicked
)
self.project_list = project_list
self.project_proxy = project_proxy
@ -900,10 +1061,46 @@ class ProjectListWidget(QtWidgets.QWidget):
self.dbcon = None
def on_item_clicked(self, new_index):
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
if new_project_name is None:
def set_entity(self, entity):
self._entity = entity
def _on_item_right_clicked(self, index):
if not index.isValid():
return
project_name = index.data(PROJECT_NAME_ROLE)
if project_name is None:
project_name = DEFAULT_PROJECT_LABEL
if self.current_project != project_name:
self.on_item_clicked(index)
if self.current_project != project_name:
return
if not self._entity:
return
versions = self._entity.get_available_source_versions(sorted=True)
if not versions:
return
menu = QtWidgets.QMenu(self)
submenu = QtWidgets.QMenu("Use settings from version", menu)
for version in reversed(versions):
action = VersionAction(version, submenu)
action.version_triggered.connect(
self.version_change_requested
)
submenu.addAction(action)
menu.addMenu(submenu)
menu.exec_(QtGui.QCursor.pos())
def on_item_clicked(self, new_index):
if not new_index.isValid():
return
new_project_name = new_index.data(PROJECT_NAME_ROLE)
if new_project_name is None:
new_project_name = DEFAULT_PROJECT_LABEL
if self.current_project == new_project_name:
return
@ -963,12 +1160,30 @@ class ProjectListWidget(QtWidgets.QWidget):
index = model.indexFromItem(found_items[0])
model.setData(index, True, PROJECT_IS_SELECTED_ROLE)
index = proxy.mapFromSource(index)
src_indexes = []
col_count = model.columnCount()
if col_count > 1:
for col in range(col_count):
src_indexes.append(
model.index(index.row(), col, index.parent())
)
dst_indexes = []
for index in src_indexes:
dst_indexes.append(proxy.mapFromSource(index))
self.project_list.selectionModel().clear()
self.project_list.selectionModel().setCurrentIndex(
index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
)
selection_model = self.project_list.selectionModel()
selection_model.clear()
first = True
for index in dst_indexes:
if first:
selection_model.setCurrentIndex(
index,
QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
)
first = False
continue
selection_model.select(index, QtCore.QItemSelectionModel.Select)
def get_project_names(self):
output = []
@ -980,7 +1195,7 @@ class ProjectListWidget(QtWidgets.QWidget):
def refresh(self):
selected_project = None
for index in self.project_list.selectedIndexes():
selected_project = index.data(QtCore.Qt.DisplayRole)
selected_project = index.data(PROJECT_NAME_ROLE)
break
mongo_url = os.environ["OPENPYPE_MONGO"]
@ -1008,5 +1223,6 @@ class ProjectListWidget(QtWidgets.QWidget):
self.select_project(selected_project)
self.current_project = self.project_list.currentIndex().data(
QtCore.Qt.DisplayRole
PROJECT_NAME_ROLE
)
self.project_list.resizeColumnToContents(0)

View file

@ -4,7 +4,11 @@ from .categories import (
SystemWidget,
ProjectWidget
)
from .widgets import ShadowWidget, RestartDialog
from .widgets import (
ShadowWidget,
RestartDialog,
SettingsTabWidget
)
from openpype import style
from openpype.lib import is_admin_password_required
@ -34,7 +38,7 @@ class MainWidget(QtWidgets.QWidget):
self.setStyleSheet(stylesheet)
self.setWindowIcon(QtGui.QIcon(style.app_icon_path()))
header_tab_widget = QtWidgets.QTabWidget(parent=self)
header_tab_widget = SettingsTabWidget(parent=self)
studio_widget = SystemWidget(user_role, header_tab_widget)
project_widget = ProjectWidget(user_role, header_tab_widget)
@ -65,6 +69,10 @@ class MainWidget(QtWidgets.QWidget):
)
tab_widget.full_path_requested.connect(self._on_full_path_request)
header_tab_widget.context_menu_requested.connect(
self._on_context_menu_request
)
self._header_tab_widget = header_tab_widget
self.tab_widgets = tab_widgets
@ -100,6 +108,18 @@ class MainWidget(QtWidgets.QWidget):
tab_widget.set_category_path(category, path)
break
def _on_context_menu_request(self, tab_idx):
widget = self._header_tab_widget.widget(tab_idx)
if not widget:
return
menu = QtWidgets.QMenu(self)
widget.add_context_actions(menu)
if menu.actions():
result = menu.exec_(QtGui.QCursor.pos())
if result is not None:
self._header_tab_widget.setCurrentIndex(tab_idx)
def showEvent(self, event):
super(MainWidget, self).showEvent(event)
if self._reset_on_show:

View file

@ -33,7 +33,8 @@ from openpype.settings import (
)
from openpype.tools.utils import (
WrappedCallbackItem,
paint_image_with_color
paint_image_with_color,
get_warning_pixmap
)
from .pype_info_widget import PypeInfoWidget
@ -76,7 +77,7 @@ class PixmapLabel(QtWidgets.QLabel):
super(PixmapLabel, self).resizeEvent(event)
class VersionDialog(QtWidgets.QDialog):
class VersionUpdateDialog(QtWidgets.QDialog):
restart_requested = QtCore.Signal()
ignore_requested = QtCore.Signal()
@ -84,7 +85,7 @@ class VersionDialog(QtWidgets.QDialog):
_min_height = 130
def __init__(self, parent=None):
super(VersionDialog, self).__init__(parent)
super(VersionUpdateDialog, self).__init__(parent)
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
self.setWindowIcon(icon)
@ -152,11 +153,11 @@ class VersionDialog(QtWidgets.QDialog):
)
def showEvent(self, event):
super().showEvent(event)
super(VersionUpdateDialog, self).showEvent(event)
self._restart_accepted = False
def closeEvent(self, event):
super().closeEvent(event)
super(VersionUpdateDialog, self).closeEvent(event)
if self._restart_accepted or self._current_is_higher:
return
# Trigger ignore requested only if restart was not clicked and current
@ -202,6 +203,63 @@ class VersionDialog(QtWidgets.QDialog):
self.accept()
class BuildVersionDialog(QtWidgets.QDialog):
"""Build/Installation version is too low for current OpenPype version.
This dialog tells to user that it's build OpenPype is too old.
"""
def __init__(self, parent=None):
super(BuildVersionDialog, self).__init__(parent)
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowTitle("Outdated OpenPype installation")
self.setWindowFlags(
self.windowFlags()
| QtCore.Qt.WindowStaysOnTopHint
)
top_widget = QtWidgets.QWidget(self)
warning_pixmap = get_warning_pixmap()
warning_icon_label = PixmapLabel(warning_pixmap, top_widget)
message = (
"Your installation of OpenPype <b>does not match minimum"
" requirements</b>.<br/><br/>Please update OpenPype installation"
" to newer version."
)
content_label = QtWidgets.QLabel(message, self)
top_layout = QtWidgets.QHBoxLayout(top_widget)
top_layout.setContentsMargins(0, 0, 0, 0)
top_layout.addWidget(
warning_icon_label, 0,
QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter
)
top_layout.addWidget(content_label, 1)
footer_widget = QtWidgets.QWidget(self)
ok_btn = QtWidgets.QPushButton("I understand", footer_widget)
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
footer_layout.setContentsMargins(0, 0, 0, 0)
footer_layout.addStretch(1)
footer_layout.addWidget(ok_btn)
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(top_widget, 0)
main_layout.addStretch(1)
main_layout.addWidget(footer_widget, 0)
self.setStyleSheet(style.load_stylesheet())
ok_btn.clicked.connect(self._on_ok_clicked)
def _on_ok_clicked(self):
self.close()
class TrayManager:
"""Cares about context of application.
@ -272,7 +330,7 @@ class TrayManager:
return
if self._version_dialog is None:
self._version_dialog = VersionDialog()
self._version_dialog = VersionUpdateDialog()
self._version_dialog.restart_requested.connect(
self._restart_and_install
)
@ -383,6 +441,10 @@ class TrayManager:
self._validate_settings_defaults()
if not op_version_control_available():
dialog = BuildVersionDialog()
dialog.exec_()
def _validate_settings_defaults(self):
valid = True
try:

View file

@ -10,7 +10,10 @@ from .widgets import (
from .error_dialog import ErrorMessageBox
from .lib import (
WrappedCallbackItem,
paint_image_with_color
paint_image_with_color,
get_warning_pixmap,
set_style_property,
DynamicQThread,
)
from .models import (
@ -29,6 +32,9 @@ __all__ = (
"WrappedCallbackItem",
"paint_image_with_color",
"get_warning_pixmap",
"set_style_property",
"DynamicQThread",
"RecursiveSortFilterProxyModel",
)

View file

@ -7,7 +7,6 @@ import Qt
from Qt import QtWidgets, QtGui, QtCore
from avalon.lib import HeroVersionType
from openpype.style import get_objected_colors
from .models import TreeModel
from . import lib

View file

@ -14,6 +14,8 @@ from openpype.api import (
Logger
)
from openpype.lib import filter_profiles
from openpype.style import get_objected_colors
from openpype.resources import get_image_path
def center_window(window):
@ -28,6 +30,18 @@ def center_window(window):
window.move(geo.topLeft())
def set_style_property(widget, property_name, property_value):
"""Set widget's property that may affect style.
If current property value is different then style of widget is polished.
"""
cur_value = widget.property(property_name)
if cur_value == property_value:
return
widget.setProperty(property_name, property_value)
widget.style().polish(widget)
def paint_image_with_color(image, color):
"""Redraw image with single color using it's alpha.
@ -670,3 +684,19 @@ class WrappedCallbackItem:
finally:
self._done = True
def get_warning_pixmap(color=None):
"""Warning icon as QPixmap.
Args:
color(QtGui.QColor): Color that will be used to paint warning icon.
"""
src_image_path = get_image_path("warning.png")
src_image = QtGui.QImage(src_image_path)
if color is None:
colors = get_objected_colors()
color_value = colors["delete-btn-bg"]
color = color_value.get_qcolor()
return paint_image_with_color(src_image, color)

View file

@ -3,9 +3,6 @@ import logging
import Qt
from Qt import QtCore, QtGui
from avalon.vendor import qtawesome
from avalon import style, io
from . import lib
from .constants import (
PROJECT_IS_ACTIVE_ROLE,
PROJECT_NAME_ROLE,