mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' of github.com:pypeclub/OpenPype into feature/OP-1117_Disable-workfile-autoload-in-Launcher
This commit is contained in:
commit
90642a191e
75 changed files with 2359 additions and 736 deletions
BIN
openpype/tools/tray/images/gifts.png
Normal file
BIN
openpype/tools/tray/images/gifts.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
|
|
@ -14,7 +14,15 @@ from openpype.api import (
|
|||
resources,
|
||||
get_system_settings
|
||||
)
|
||||
from openpype.lib import get_openpype_execute_args
|
||||
from openpype.lib import (
|
||||
get_openpype_execute_args,
|
||||
op_version_control_available,
|
||||
is_current_version_studio_latest,
|
||||
is_running_from_build,
|
||||
is_running_staging,
|
||||
get_expected_version,
|
||||
get_openpype_version
|
||||
)
|
||||
from openpype.modules import TrayModulesManager
|
||||
from openpype import style
|
||||
from openpype.settings import (
|
||||
|
|
@ -22,29 +30,177 @@ from openpype.settings import (
|
|||
ProjectSettings,
|
||||
DefaultsNotDefined
|
||||
)
|
||||
from openpype.tools.utils import (
|
||||
WrappedCallbackItem,
|
||||
paint_image_with_color
|
||||
)
|
||||
|
||||
from .pype_info_widget import PypeInfoWidget
|
||||
|
||||
|
||||
# TODO PixmapLabel should be moved to 'utils' in other future PR so should be
|
||||
# imported from there
|
||||
class PixmapLabel(QtWidgets.QLabel):
|
||||
"""Label resizing image to height of font."""
|
||||
def __init__(self, pixmap, parent):
|
||||
super(PixmapLabel, self).__init__(parent)
|
||||
self._empty_pixmap = QtGui.QPixmap(0, 0)
|
||||
self._source_pixmap = pixmap
|
||||
|
||||
def set_source_pixmap(self, pixmap):
|
||||
"""Change source image."""
|
||||
self._source_pixmap = pixmap
|
||||
self._set_resized_pix()
|
||||
|
||||
def _get_pix_size(self):
|
||||
size = self.fontMetrics().height() * 3
|
||||
return size, size
|
||||
|
||||
def _set_resized_pix(self):
|
||||
if self._source_pixmap is None:
|
||||
self.setPixmap(self._empty_pixmap)
|
||||
return
|
||||
width, height = self._get_pix_size()
|
||||
self.setPixmap(
|
||||
self._source_pixmap.scaled(
|
||||
width,
|
||||
height,
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation
|
||||
)
|
||||
)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self._set_resized_pix()
|
||||
super(PixmapLabel, self).resizeEvent(event)
|
||||
|
||||
|
||||
class VersionDialog(QtWidgets.QDialog):
|
||||
restart_requested = QtCore.Signal()
|
||||
ignore_requested = QtCore.Signal()
|
||||
|
||||
_min_width = 400
|
||||
_min_height = 130
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(VersionDialog, self).__init__(parent)
|
||||
self.setWindowTitle("OpenPype update is needed")
|
||||
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
self.setWindowFlags(
|
||||
self.windowFlags()
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
|
||||
self.setMinimumWidth(self._min_width)
|
||||
self.setMinimumHeight(self._min_height)
|
||||
|
||||
top_widget = QtWidgets.QWidget(self)
|
||||
|
||||
gift_pixmap = self._get_gift_pixmap()
|
||||
gift_icon_label = PixmapLabel(gift_pixmap, top_widget)
|
||||
|
||||
label_widget = QtWidgets.QLabel(top_widget)
|
||||
label_widget.setWordWrap(True)
|
||||
|
||||
top_layout = QtWidgets.QHBoxLayout(top_widget)
|
||||
# top_layout.setContentsMargins(0, 0, 0, 0)
|
||||
top_layout.setSpacing(10)
|
||||
top_layout.addWidget(gift_icon_label, 0, QtCore.Qt.AlignCenter)
|
||||
top_layout.addWidget(label_widget, 1)
|
||||
|
||||
ignore_btn = QtWidgets.QPushButton("Later", self)
|
||||
restart_btn = QtWidgets.QPushButton("Restart && Update", self)
|
||||
restart_btn.setObjectName("TrayRestartButton")
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(ignore_btn, 0)
|
||||
btns_layout.addWidget(restart_btn, 0)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(top_widget, 0)
|
||||
layout.addStretch(1)
|
||||
layout.addLayout(btns_layout, 0)
|
||||
|
||||
ignore_btn.clicked.connect(self._on_ignore)
|
||||
restart_btn.clicked.connect(self._on_reset)
|
||||
|
||||
self._label_widget = label_widget
|
||||
self._restart_accepted = False
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def _get_gift_pixmap(self):
|
||||
image_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"images",
|
||||
"gifts.png"
|
||||
)
|
||||
src_image = QtGui.QImage(image_path)
|
||||
colors = style.get_objected_colors()
|
||||
color_value = colors["font"]
|
||||
|
||||
return paint_image_with_color(
|
||||
src_image,
|
||||
color_value.get_qcolor()
|
||||
)
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
self._restart_accepted = False
|
||||
|
||||
def closeEvent(self, event):
|
||||
super().closeEvent(event)
|
||||
if not self._restart_accepted:
|
||||
self.ignore_requested.emit()
|
||||
|
||||
def update_versions(self, current_version, expected_version):
|
||||
message = (
|
||||
"Running OpenPype version is <b>{}</b>."
|
||||
" Your production has been updated to version <b>{}</b>."
|
||||
).format(str(current_version), str(expected_version))
|
||||
self._label_widget.setText(message)
|
||||
|
||||
def _on_ignore(self):
|
||||
self.reject()
|
||||
|
||||
def _on_reset(self):
|
||||
self._restart_accepted = True
|
||||
self.restart_requested.emit()
|
||||
self.accept()
|
||||
|
||||
|
||||
class TrayManager:
|
||||
"""Cares about context of application.
|
||||
|
||||
Load submenus, actions, separators and modules into tray's context.
|
||||
"""
|
||||
|
||||
def __init__(self, tray_widget, main_window):
|
||||
self.tray_widget = tray_widget
|
||||
self.main_window = main_window
|
||||
self.pype_info_widget = None
|
||||
self._restart_action = None
|
||||
|
||||
self.log = Logger.get_logger(self.__class__.__name__)
|
||||
|
||||
self.module_settings = get_system_settings()["modules"]
|
||||
system_settings = get_system_settings()
|
||||
self.module_settings = system_settings["modules"]
|
||||
|
||||
version_check_interval = system_settings["general"].get(
|
||||
"version_check_interval"
|
||||
)
|
||||
if version_check_interval is None:
|
||||
version_check_interval = 5
|
||||
self._version_check_interval = version_check_interval * 60 * 1000
|
||||
|
||||
self.modules_manager = TrayModulesManager()
|
||||
|
||||
self.errors = []
|
||||
|
||||
self._version_check_timer = None
|
||||
self._version_dialog = None
|
||||
|
||||
self.main_thread_timer = None
|
||||
self._main_thread_callbacks = collections.deque()
|
||||
self._execution_in_progress = None
|
||||
|
|
@ -61,21 +217,73 @@ class TrayManager:
|
|||
if callback:
|
||||
self.execute_in_main_thread(callback)
|
||||
|
||||
def execute_in_main_thread(self, callback):
|
||||
self._main_thread_callbacks.append(callback)
|
||||
def _on_version_check_timer(self):
|
||||
# Check if is running from build and stop future validations if yes
|
||||
if not is_running_from_build() or not op_version_control_available():
|
||||
self._version_check_timer.stop()
|
||||
return
|
||||
|
||||
self.validate_openpype_version()
|
||||
|
||||
def validate_openpype_version(self):
|
||||
using_requested = is_current_version_studio_latest()
|
||||
self._restart_action.setVisible(not using_requested)
|
||||
if using_requested:
|
||||
if (
|
||||
self._version_dialog is not None
|
||||
and self._version_dialog.isVisible()
|
||||
):
|
||||
self._version_dialog.close()
|
||||
return
|
||||
|
||||
if self._version_dialog is None:
|
||||
self._version_dialog = VersionDialog()
|
||||
self._version_dialog.restart_requested.connect(
|
||||
self._restart_and_install
|
||||
)
|
||||
self._version_dialog.ignore_requested.connect(
|
||||
self._outdated_version_ignored
|
||||
)
|
||||
|
||||
expected_version = get_expected_version()
|
||||
current_version = get_openpype_version()
|
||||
self._version_dialog.update_versions(
|
||||
current_version, expected_version
|
||||
)
|
||||
self._version_dialog.show()
|
||||
self._version_dialog.raise_()
|
||||
self._version_dialog.activateWindow()
|
||||
|
||||
def _restart_and_install(self):
|
||||
self.restart()
|
||||
|
||||
def _outdated_version_ignored(self):
|
||||
self.show_tray_message(
|
||||
"OpenPype version is outdated",
|
||||
(
|
||||
"Please update your OpenPype as soon as possible."
|
||||
" To update, restart OpenPype Tray application."
|
||||
)
|
||||
)
|
||||
|
||||
def execute_in_main_thread(self, callback, *args, **kwargs):
|
||||
if isinstance(callback, WrappedCallbackItem):
|
||||
item = callback
|
||||
else:
|
||||
item = WrappedCallbackItem(callback, *args, **kwargs)
|
||||
|
||||
self._main_thread_callbacks.append(item)
|
||||
|
||||
return item
|
||||
|
||||
def _main_thread_execution(self):
|
||||
if self._execution_in_progress:
|
||||
return
|
||||
self._execution_in_progress = True
|
||||
while self._main_thread_callbacks:
|
||||
try:
|
||||
callback = self._main_thread_callbacks.popleft()
|
||||
callback()
|
||||
except:
|
||||
self.log.warning(
|
||||
"Failed to execute {} in main thread".format(callback),
|
||||
exc_info=True)
|
||||
for _ in range(len(self._main_thread_callbacks)):
|
||||
if self._main_thread_callbacks:
|
||||
item = self._main_thread_callbacks.popleft()
|
||||
item.execute()
|
||||
|
||||
self._execution_in_progress = False
|
||||
|
||||
|
|
@ -119,6 +327,13 @@ class TrayManager:
|
|||
|
||||
self.main_thread_timer = main_thread_timer
|
||||
|
||||
version_check_timer = QtCore.QTimer()
|
||||
version_check_timer.timeout.connect(self._on_version_check_timer)
|
||||
if self._version_check_interval > 0:
|
||||
version_check_timer.setInterval(self._version_check_interval)
|
||||
version_check_timer.start()
|
||||
self._version_check_timer = version_check_timer
|
||||
|
||||
# For storing missing settings dialog
|
||||
self._settings_validation_dialog = None
|
||||
|
||||
|
|
@ -200,24 +415,47 @@ class TrayManager:
|
|||
|
||||
version_action = QtWidgets.QAction(version_string, self.tray_widget)
|
||||
version_action.triggered.connect(self._on_version_action)
|
||||
|
||||
restart_action = QtWidgets.QAction(
|
||||
"Restart && Update", self.tray_widget
|
||||
)
|
||||
restart_action.triggered.connect(self._on_restart_action)
|
||||
restart_action.setVisible(False)
|
||||
|
||||
self.tray_widget.menu.addAction(version_action)
|
||||
self.tray_widget.menu.addAction(restart_action)
|
||||
self.tray_widget.menu.addSeparator()
|
||||
|
||||
def restart(self):
|
||||
self._restart_action = restart_action
|
||||
|
||||
def _on_restart_action(self):
|
||||
self.restart()
|
||||
|
||||
def restart(self, reset_version=True):
|
||||
"""Restart Tray tool.
|
||||
|
||||
First creates new process with same argument and close current tray.
|
||||
"""
|
||||
args = get_openpype_execute_args()
|
||||
kwargs = {
|
||||
"env": dict(os.environ.items())
|
||||
}
|
||||
|
||||
# Create a copy of sys.argv
|
||||
additional_args = list(sys.argv)
|
||||
# Check last argument from `get_openpype_execute_args`
|
||||
# - when running from code it is the same as first from sys.argv
|
||||
if args[-1] == additional_args[0]:
|
||||
additional_args.pop(0)
|
||||
args.extend(additional_args)
|
||||
|
||||
kwargs = {}
|
||||
# Pop OPENPYPE_VERSION
|
||||
if reset_version:
|
||||
# Add staging flag if was running from staging
|
||||
if is_running_staging():
|
||||
args.append("--use-staging")
|
||||
kwargs["env"].pop("OPENPYPE_VERSION", None)
|
||||
|
||||
args.extend(additional_args)
|
||||
if platform.system().lower() == "windows":
|
||||
flags = (
|
||||
subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ from .widgets import (
|
|||
)
|
||||
|
||||
from .error_dialog import ErrorMessageBox
|
||||
from .lib import (
|
||||
WrappedCallbackItem,
|
||||
paint_image_with_color
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -14,5 +18,8 @@ __all__ = (
|
|||
"ClickableFrame",
|
||||
"ExpandBtn",
|
||||
|
||||
"ErrorMessageBox"
|
||||
"ErrorMessageBox",
|
||||
|
||||
"WrappedCallbackItem",
|
||||
"paint_image_with_color",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import avalon.api
|
|||
from avalon import style
|
||||
from avalon.vendor import qtawesome
|
||||
|
||||
from openpype.api import get_project_settings
|
||||
from openpype.api import (
|
||||
get_project_settings,
|
||||
Logger
|
||||
)
|
||||
from openpype.lib import filter_profiles
|
||||
|
||||
|
||||
|
|
@ -598,3 +601,68 @@ def is_remove_site_loader(loader):
|
|||
|
||||
def is_add_site_loader(loader):
|
||||
return hasattr(loader, "add_site_to_representation")
|
||||
|
||||
|
||||
class WrappedCallbackItem:
|
||||
"""Structure to store information about callback and args/kwargs for it.
|
||||
|
||||
Item can be used to execute callback in main thread which may be needed
|
||||
for execution of Qt objects.
|
||||
|
||||
Item store callback (callable variable), arguments and keyword arguments
|
||||
for the callback. Item hold information about it's process.
|
||||
"""
|
||||
not_set = object()
|
||||
_log = None
|
||||
|
||||
def __init__(self, callback, *args, **kwargs):
|
||||
self._done = False
|
||||
self._exception = self.not_set
|
||||
self._result = self.not_set
|
||||
self._callback = callback
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
def __call__(self):
|
||||
self.execute()
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
cls = self.__class__
|
||||
if cls._log is None:
|
||||
cls._log = Logger.get_logger(cls.__name__)
|
||||
return cls._log
|
||||
|
||||
@property
|
||||
def done(self):
|
||||
return self._done
|
||||
|
||||
@property
|
||||
def exception(self):
|
||||
return self._exception
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
return self._result
|
||||
|
||||
def execute(self):
|
||||
"""Execute callback and store it's result.
|
||||
|
||||
Method must be called from main thread. Item is marked as `done`
|
||||
when callback execution finished. Store output of callback of exception
|
||||
information when callback raise one.
|
||||
"""
|
||||
if self.done:
|
||||
self.log.warning("- item is already processed")
|
||||
return
|
||||
|
||||
self.log.debug("Running callback: {}".format(str(self._callback)))
|
||||
try:
|
||||
result = self._callback(*self._args, **self._kwargs)
|
||||
self._result = result
|
||||
|
||||
except Exception as exc:
|
||||
self._exception = exc
|
||||
|
||||
finally:
|
||||
self._done = True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue