mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'develop' into bugfix/replace_published_scene_path_deepcopy
This commit is contained in:
commit
f6c7ee07fe
27 changed files with 1064 additions and 995 deletions
|
|
@ -370,67 +370,11 @@ def _load_ayon_addons(log):
|
|||
return all_addon_modules
|
||||
|
||||
|
||||
def _load_addons_in_core(log):
|
||||
# Add current directory at first place
|
||||
# - has small differences in import logic
|
||||
addon_modules = []
|
||||
modules_dir = os.path.join(AYON_CORE_ROOT, "modules")
|
||||
if not os.path.exists(modules_dir):
|
||||
log.warning(
|
||||
f"Could not find path when loading AYON addons \"{modules_dir}\""
|
||||
)
|
||||
return addon_modules
|
||||
|
||||
ignored_filenames = IGNORED_FILENAMES | IGNORED_DEFAULT_FILENAMES
|
||||
for filename in os.listdir(modules_dir):
|
||||
# Ignore filenames
|
||||
if filename in ignored_filenames:
|
||||
continue
|
||||
|
||||
fullpath = os.path.join(modules_dir, filename)
|
||||
basename, ext = os.path.splitext(filename)
|
||||
|
||||
# Validations
|
||||
if os.path.isdir(fullpath):
|
||||
# Check existence of init file
|
||||
init_path = os.path.join(fullpath, "__init__.py")
|
||||
if not os.path.exists(init_path):
|
||||
log.debug((
|
||||
"Addon directory does not contain __init__.py"
|
||||
f" file {fullpath}"
|
||||
))
|
||||
continue
|
||||
|
||||
elif ext != ".py":
|
||||
continue
|
||||
|
||||
# TODO add more logic how to define if folder is addon or not
|
||||
# - check manifest and content of manifest
|
||||
try:
|
||||
# Don't import dynamically current directory modules
|
||||
import_str = f"ayon_core.modules.{basename}"
|
||||
default_module = __import__(import_str, fromlist=("", ))
|
||||
addon_modules.append(default_module)
|
||||
|
||||
except Exception:
|
||||
log.error(
|
||||
f"Failed to import in-core addon '{basename}'.",
|
||||
exc_info=True
|
||||
)
|
||||
return addon_modules
|
||||
|
||||
|
||||
def _load_addons():
|
||||
log = Logger.get_logger("AddonsLoader")
|
||||
|
||||
addon_modules = _load_ayon_addons(log)
|
||||
# All addon in 'modules' folder are tray actions and should be moved
|
||||
# to tray tool.
|
||||
# TODO remove
|
||||
addon_modules.extend(_load_addons_in_core(log))
|
||||
|
||||
# Store modules to local cache
|
||||
_LoadCache.addon_modules = addon_modules
|
||||
_LoadCache.addon_modules = _load_ayon_addons(log)
|
||||
|
||||
|
||||
class AYONAddon(ABC):
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class ITrayAddon(AYONInterface):
|
|||
|
||||
tray_initialized = False
|
||||
_tray_manager = None
|
||||
_admin_submenu = None
|
||||
|
||||
@abstractmethod
|
||||
def tray_init(self):
|
||||
|
|
@ -198,6 +199,27 @@ class ITrayAddon(AYONInterface):
|
|||
if hasattr(self.manager, "add_doubleclick_callback"):
|
||||
self.manager.add_doubleclick_callback(self, callback)
|
||||
|
||||
@staticmethod
|
||||
def admin_submenu(tray_menu):
|
||||
if ITrayAddon._admin_submenu is None:
|
||||
from qtpy import QtWidgets
|
||||
|
||||
admin_submenu = QtWidgets.QMenu("Admin", tray_menu)
|
||||
admin_submenu.menuAction().setVisible(False)
|
||||
ITrayAddon._admin_submenu = admin_submenu
|
||||
return ITrayAddon._admin_submenu
|
||||
|
||||
@staticmethod
|
||||
def add_action_to_admin_submenu(label, tray_menu):
|
||||
from qtpy import QtWidgets
|
||||
|
||||
menu = ITrayAddon.admin_submenu(tray_menu)
|
||||
action = QtWidgets.QAction(label, menu)
|
||||
menu.addAction(action)
|
||||
if not menu.menuAction().isVisible():
|
||||
menu.menuAction().setVisible(True)
|
||||
return action
|
||||
|
||||
|
||||
class ITrayAction(ITrayAddon):
|
||||
"""Implementation of Tray action.
|
||||
|
|
@ -211,7 +233,6 @@ class ITrayAction(ITrayAddon):
|
|||
"""
|
||||
|
||||
admin_action = False
|
||||
_admin_submenu = None
|
||||
_action_item = None
|
||||
|
||||
@property
|
||||
|
|
@ -229,12 +250,7 @@ class ITrayAction(ITrayAddon):
|
|||
from qtpy import QtWidgets
|
||||
|
||||
if self.admin_action:
|
||||
menu = self.admin_submenu(tray_menu)
|
||||
action = QtWidgets.QAction(self.label, menu)
|
||||
menu.addAction(action)
|
||||
if not menu.menuAction().isVisible():
|
||||
menu.menuAction().setVisible(True)
|
||||
|
||||
action = self.add_action_to_admin_submenu(self.label, tray_menu)
|
||||
else:
|
||||
action = QtWidgets.QAction(self.label, tray_menu)
|
||||
tray_menu.addAction(action)
|
||||
|
|
@ -248,16 +264,6 @@ class ITrayAction(ITrayAddon):
|
|||
def tray_exit(self):
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def admin_submenu(tray_menu):
|
||||
if ITrayAction._admin_submenu is None:
|
||||
from qtpy import QtWidgets
|
||||
|
||||
admin_submenu = QtWidgets.QMenu("Admin", tray_menu)
|
||||
admin_submenu.menuAction().setVisible(False)
|
||||
ITrayAction._admin_submenu = admin_submenu
|
||||
return ITrayAction._admin_submenu
|
||||
|
||||
|
||||
class ITrayService(ITrayAddon):
|
||||
# Module's property
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
import os
|
||||
|
||||
from ayon_core import AYON_CORE_ROOT
|
||||
from ayon_core.addon import AYONAddon, ITrayAction
|
||||
|
||||
|
||||
class LauncherAction(AYONAddon, ITrayAction):
|
||||
label = "Launcher"
|
||||
name = "launcher_tool"
|
||||
version = "1.0.0"
|
||||
|
||||
def initialize(self, settings):
|
||||
|
||||
# Tray attributes
|
||||
self._window = None
|
||||
|
||||
def tray_init(self):
|
||||
self._create_window()
|
||||
|
||||
self.add_doubleclick_callback(self._show_launcher)
|
||||
|
||||
def tray_start(self):
|
||||
return
|
||||
|
||||
def connect_with_addons(self, enabled_modules):
|
||||
# Register actions
|
||||
if not self.tray_initialized:
|
||||
return
|
||||
|
||||
from ayon_core.pipeline.actions import register_launcher_action_path
|
||||
|
||||
actions_dir = os.path.join(AYON_CORE_ROOT, "plugins", "actions")
|
||||
if os.path.exists(actions_dir):
|
||||
register_launcher_action_path(actions_dir)
|
||||
|
||||
actions_paths = self.manager.collect_plugin_paths()["actions"]
|
||||
for path in actions_paths:
|
||||
if path and os.path.exists(path):
|
||||
register_launcher_action_path(path)
|
||||
|
||||
def on_action_trigger(self):
|
||||
"""Implementation for ITrayAction interface.
|
||||
|
||||
Show launcher tool on action trigger.
|
||||
"""
|
||||
|
||||
self._show_launcher()
|
||||
|
||||
def _create_window(self):
|
||||
if self._window:
|
||||
return
|
||||
from ayon_core.tools.launcher.ui import LauncherWindow
|
||||
self._window = LauncherWindow()
|
||||
|
||||
def _show_launcher(self):
|
||||
if self._window is None:
|
||||
return
|
||||
self._window.show()
|
||||
self._window.raise_()
|
||||
self._window.activateWindow()
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
from ayon_core.addon import AYONAddon, ITrayAddon
|
||||
|
||||
|
||||
class LoaderAddon(AYONAddon, ITrayAddon):
|
||||
name = "loader_tool"
|
||||
version = "1.0.0"
|
||||
|
||||
def initialize(self, settings):
|
||||
# Tray attributes
|
||||
self._loader_imported = None
|
||||
self._loader_window = None
|
||||
|
||||
def tray_init(self):
|
||||
# Add library tool
|
||||
self._loader_imported = False
|
||||
try:
|
||||
from ayon_core.tools.loader.ui import LoaderWindow # noqa F401
|
||||
|
||||
self._loader_imported = True
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Couldn't load Loader tool for tray.",
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
# Definition of Tray menu
|
||||
def tray_menu(self, tray_menu):
|
||||
if not self._loader_imported:
|
||||
return
|
||||
|
||||
from qtpy import QtWidgets
|
||||
# Actions
|
||||
action_loader = QtWidgets.QAction(
|
||||
"Loader", tray_menu
|
||||
)
|
||||
|
||||
action_loader.triggered.connect(self.show_loader)
|
||||
|
||||
tray_menu.addAction(action_loader)
|
||||
|
||||
def tray_start(self, *_a, **_kw):
|
||||
return
|
||||
|
||||
def tray_exit(self, *_a, **_kw):
|
||||
return
|
||||
|
||||
def show_loader(self):
|
||||
if self._loader_window is None:
|
||||
from ayon_core.pipeline import install_ayon_plugins
|
||||
|
||||
self._init_loader()
|
||||
|
||||
install_ayon_plugins()
|
||||
|
||||
self._loader_window.show()
|
||||
|
||||
# Raise and activate the window
|
||||
# for MacOS
|
||||
self._loader_window.raise_()
|
||||
# for Windows
|
||||
self._loader_window.activateWindow()
|
||||
|
||||
def _init_loader(self):
|
||||
from ayon_core.tools.loader.ui import LoaderWindow
|
||||
|
||||
libraryloader = LoaderWindow()
|
||||
|
||||
self._loader_window = libraryloader
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from .addon import (
|
||||
PythonInterpreterAction
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"PythonInterpreterAction",
|
||||
)
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
from ayon_core.addon import AYONAddon, ITrayAction
|
||||
|
||||
|
||||
class PythonInterpreterAction(AYONAddon, ITrayAction):
|
||||
label = "Console"
|
||||
name = "python_interpreter"
|
||||
version = "1.0.0"
|
||||
admin_action = True
|
||||
|
||||
def initialize(self, settings):
|
||||
self._interpreter_window = None
|
||||
|
||||
def tray_init(self):
|
||||
self.create_interpreter_window()
|
||||
|
||||
def tray_exit(self):
|
||||
if self._interpreter_window is not None:
|
||||
self._interpreter_window.save_registry()
|
||||
|
||||
def create_interpreter_window(self):
|
||||
"""Initializa Settings Qt window."""
|
||||
if self._interpreter_window:
|
||||
return
|
||||
|
||||
from ayon_core.modules.python_console_interpreter.window import (
|
||||
PythonInterpreterWidget
|
||||
)
|
||||
|
||||
self._interpreter_window = PythonInterpreterWidget()
|
||||
|
||||
def on_action_trigger(self):
|
||||
self.show_interpreter_window()
|
||||
|
||||
def show_interpreter_window(self):
|
||||
self.create_interpreter_window()
|
||||
|
||||
if self._interpreter_window.isVisible():
|
||||
self._interpreter_window.activateWindow()
|
||||
self._interpreter_window.raise_()
|
||||
return
|
||||
|
||||
self._interpreter_window.show()
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from .widgets import (
|
||||
PythonInterpreterWidget
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"PythonInterpreterWidget",
|
||||
)
|
||||
|
|
@ -1,660 +0,0 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
import collections
|
||||
from code import InteractiveInterpreter
|
||||
|
||||
import appdirs
|
||||
from qtpy import QtCore, QtWidgets, QtGui
|
||||
|
||||
from ayon_core import resources
|
||||
from ayon_core.style import load_stylesheet
|
||||
from ayon_core.lib import JSONSettingRegistry
|
||||
|
||||
|
||||
ayon_art = r"""
|
||||
|
||||
▄██▄
|
||||
▄███▄ ▀██▄ ▀██▀ ▄██▀ ▄██▀▀▀██▄ ▀███▄ █▄
|
||||
▄▄ ▀██▄ ▀██▄ ▄██▀ ██▀ ▀██▄ ▄ ▀██▄ ███
|
||||
▄██▀ ██▄ ▀ ▄▄ ▀ ██ ▄██ ███ ▀██▄ ███
|
||||
▄██▀ ▀██▄ ██ ▀██▄ ▄██▀ ███ ▀██ ▀█▀
|
||||
▄██▀ ▀██▄ ▀█ ▀██▄▄▄▄██▀ █▀ ▀██▄
|
||||
|
||||
· · - =[ by YNPUT ]:[ http://ayon.ynput.io ]= - · ·
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class PythonInterpreterRegistry(JSONSettingRegistry):
|
||||
"""Class handling OpenPype general settings registry.
|
||||
|
||||
Attributes:
|
||||
vendor (str): Name used for path construction.
|
||||
product (str): Additional name used for path construction.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.vendor = "Ynput"
|
||||
self.product = "AYON"
|
||||
name = "python_interpreter_tool"
|
||||
path = appdirs.user_data_dir(self.product, self.vendor)
|
||||
super(PythonInterpreterRegistry, self).__init__(name, path)
|
||||
|
||||
|
||||
class StdOEWrap:
|
||||
def __init__(self):
|
||||
self._origin_stdout_write = None
|
||||
self._origin_stderr_write = None
|
||||
self._listening = False
|
||||
self.lines = collections.deque()
|
||||
|
||||
if not sys.stdout:
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
|
||||
if not sys.stderr:
|
||||
sys.stderr = open(os.devnull, "w")
|
||||
|
||||
if self._origin_stdout_write is None:
|
||||
self._origin_stdout_write = sys.stdout.write
|
||||
|
||||
if self._origin_stderr_write is None:
|
||||
self._origin_stderr_write = sys.stderr.write
|
||||
|
||||
self._listening = True
|
||||
sys.stdout.write = self._stdout_listener
|
||||
sys.stderr.write = self._stderr_listener
|
||||
|
||||
def stop_listen(self):
|
||||
self._listening = False
|
||||
|
||||
def _stdout_listener(self, text):
|
||||
if self._listening:
|
||||
self.lines.append(text)
|
||||
if self._origin_stdout_write is not None:
|
||||
self._origin_stdout_write(text)
|
||||
|
||||
def _stderr_listener(self, text):
|
||||
if self._listening:
|
||||
self.lines.append(text)
|
||||
if self._origin_stderr_write is not None:
|
||||
self._origin_stderr_write(text)
|
||||
|
||||
|
||||
class PythonCodeEditor(QtWidgets.QPlainTextEdit):
|
||||
execute_requested = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent):
|
||||
super(PythonCodeEditor, self).__init__(parent)
|
||||
|
||||
self.setObjectName("PythonCodeEditor")
|
||||
|
||||
self._indent = 4
|
||||
|
||||
def _tab_shift_right(self):
|
||||
cursor = self.textCursor()
|
||||
selected_text = cursor.selectedText()
|
||||
if not selected_text:
|
||||
cursor.insertText(" " * self._indent)
|
||||
return
|
||||
|
||||
sel_start = cursor.selectionStart()
|
||||
sel_end = cursor.selectionEnd()
|
||||
cursor.setPosition(sel_end)
|
||||
end_line = cursor.blockNumber()
|
||||
cursor.setPosition(sel_start)
|
||||
while True:
|
||||
cursor.movePosition(QtGui.QTextCursor.StartOfLine)
|
||||
text = cursor.block().text()
|
||||
spaces = len(text) - len(text.lstrip(" "))
|
||||
new_spaces = spaces % self._indent
|
||||
if not new_spaces:
|
||||
new_spaces = self._indent
|
||||
|
||||
cursor.insertText(" " * new_spaces)
|
||||
if cursor.blockNumber() == end_line:
|
||||
break
|
||||
|
||||
cursor.movePosition(QtGui.QTextCursor.NextBlock)
|
||||
|
||||
def _tab_shift_left(self):
|
||||
tmp_cursor = self.textCursor()
|
||||
sel_start = tmp_cursor.selectionStart()
|
||||
sel_end = tmp_cursor.selectionEnd()
|
||||
|
||||
cursor = QtGui.QTextCursor(self.document())
|
||||
cursor.setPosition(sel_end)
|
||||
end_line = cursor.blockNumber()
|
||||
cursor.setPosition(sel_start)
|
||||
while True:
|
||||
cursor.movePosition(QtGui.QTextCursor.StartOfLine)
|
||||
text = cursor.block().text()
|
||||
spaces = len(text) - len(text.lstrip(" "))
|
||||
if spaces:
|
||||
spaces_to_remove = (spaces % self._indent) or self._indent
|
||||
if spaces_to_remove > spaces:
|
||||
spaces_to_remove = spaces
|
||||
|
||||
cursor.setPosition(
|
||||
cursor.position() + spaces_to_remove,
|
||||
QtGui.QTextCursor.KeepAnchor
|
||||
)
|
||||
cursor.removeSelectedText()
|
||||
|
||||
if cursor.blockNumber() == end_line:
|
||||
break
|
||||
|
||||
cursor.movePosition(QtGui.QTextCursor.NextBlock)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Backtab:
|
||||
self._tab_shift_left()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
if event.key() == QtCore.Qt.Key_Tab:
|
||||
if event.modifiers() == QtCore.Qt.NoModifier:
|
||||
self._tab_shift_right()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
if (
|
||||
event.key() == QtCore.Qt.Key_Return
|
||||
and event.modifiers() == QtCore.Qt.ControlModifier
|
||||
):
|
||||
self.execute_requested.emit()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
super(PythonCodeEditor, self).keyPressEvent(event)
|
||||
|
||||
|
||||
class PythonTabWidget(QtWidgets.QWidget):
|
||||
add_tab_requested = QtCore.Signal()
|
||||
before_execute = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent):
|
||||
super(PythonTabWidget, self).__init__(parent)
|
||||
|
||||
code_input = PythonCodeEditor(self)
|
||||
|
||||
self.setFocusProxy(code_input)
|
||||
|
||||
add_tab_btn = QtWidgets.QPushButton("Add tab...", self)
|
||||
add_tab_btn.setToolTip("Add new tab")
|
||||
|
||||
execute_btn = QtWidgets.QPushButton("Execute", self)
|
||||
execute_btn.setToolTip("Execute command (Ctrl + Enter)")
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
btns_layout.addWidget(add_tab_btn)
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(execute_btn)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(code_input, 1)
|
||||
layout.addLayout(btns_layout, 0)
|
||||
|
||||
add_tab_btn.clicked.connect(self._on_add_tab_clicked)
|
||||
execute_btn.clicked.connect(self._on_execute_clicked)
|
||||
code_input.execute_requested.connect(self.execute)
|
||||
|
||||
self._code_input = code_input
|
||||
self._interpreter = InteractiveInterpreter()
|
||||
|
||||
def _on_add_tab_clicked(self):
|
||||
self.add_tab_requested.emit()
|
||||
|
||||
def _on_execute_clicked(self):
|
||||
self.execute()
|
||||
|
||||
def get_code(self):
|
||||
return self._code_input.toPlainText()
|
||||
|
||||
def set_code(self, code_text):
|
||||
self._code_input.setPlainText(code_text)
|
||||
|
||||
def execute(self):
|
||||
code_text = self._code_input.toPlainText()
|
||||
self.before_execute.emit(code_text)
|
||||
self._interpreter.runcode(code_text)
|
||||
|
||||
|
||||
class TabNameDialog(QtWidgets.QDialog):
|
||||
default_width = 330
|
||||
default_height = 85
|
||||
|
||||
def __init__(self, parent):
|
||||
super(TabNameDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("Enter tab name")
|
||||
|
||||
name_label = QtWidgets.QLabel("Tab name:", self)
|
||||
name_input = QtWidgets.QLineEdit(self)
|
||||
|
||||
inputs_layout = QtWidgets.QHBoxLayout()
|
||||
inputs_layout.addWidget(name_label)
|
||||
inputs_layout.addWidget(name_input)
|
||||
|
||||
ok_btn = QtWidgets.QPushButton("Ok", self)
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel", self)
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(ok_btn)
|
||||
btns_layout.addWidget(cancel_btn)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addLayout(inputs_layout)
|
||||
layout.addStretch(1)
|
||||
layout.addLayout(btns_layout)
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
cancel_btn.clicked.connect(self._on_cancel_clicked)
|
||||
|
||||
self._name_input = name_input
|
||||
self._ok_btn = ok_btn
|
||||
self._cancel_btn = cancel_btn
|
||||
|
||||
self._result = None
|
||||
|
||||
self.resize(self.default_width, self.default_height)
|
||||
|
||||
def set_tab_name(self, name):
|
||||
self._name_input.setText(name)
|
||||
|
||||
def result(self):
|
||||
return self._result
|
||||
|
||||
def showEvent(self, event):
|
||||
super(TabNameDialog, self).showEvent(event)
|
||||
btns_width = max(
|
||||
self._ok_btn.width(),
|
||||
self._cancel_btn.width()
|
||||
)
|
||||
|
||||
self._ok_btn.setMinimumWidth(btns_width)
|
||||
self._cancel_btn.setMinimumWidth(btns_width)
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
self._result = self._name_input.text()
|
||||
self.accept()
|
||||
|
||||
def _on_cancel_clicked(self):
|
||||
self._result = None
|
||||
self.reject()
|
||||
|
||||
|
||||
class OutputTextWidget(QtWidgets.QTextEdit):
|
||||
v_max_offset = 4
|
||||
|
||||
def vertical_scroll_at_max(self):
|
||||
v_scroll = self.verticalScrollBar()
|
||||
return v_scroll.value() > v_scroll.maximum() - self.v_max_offset
|
||||
|
||||
def scroll_to_bottom(self):
|
||||
v_scroll = self.verticalScrollBar()
|
||||
return v_scroll.setValue(v_scroll.maximum())
|
||||
|
||||
|
||||
class EnhancedTabBar(QtWidgets.QTabBar):
|
||||
double_clicked = QtCore.Signal(QtCore.QPoint)
|
||||
right_clicked = QtCore.Signal(QtCore.QPoint)
|
||||
mid_clicked = QtCore.Signal(QtCore.QPoint)
|
||||
|
||||
def __init__(self, parent):
|
||||
super(EnhancedTabBar, self).__init__(parent)
|
||||
|
||||
self.setDrawBase(False)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
self.double_clicked.emit(event.globalPos())
|
||||
event.accept()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
self.right_clicked.emit(event.globalPos())
|
||||
event.accept()
|
||||
return
|
||||
|
||||
elif event.button() == QtCore.Qt.MidButton:
|
||||
self.mid_clicked.emit(event.globalPos())
|
||||
event.accept()
|
||||
|
||||
else:
|
||||
super(EnhancedTabBar, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class PythonInterpreterWidget(QtWidgets.QWidget):
|
||||
default_width = 1000
|
||||
default_height = 600
|
||||
|
||||
def __init__(self, allow_save_registry=True, parent=None):
|
||||
super(PythonInterpreterWidget, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("AYON Console")
|
||||
self.setWindowIcon(QtGui.QIcon(resources.get_ayon_icon_filepath()))
|
||||
|
||||
self.ansi_escape = re.compile(
|
||||
r"(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]"
|
||||
)
|
||||
|
||||
self._tabs = []
|
||||
|
||||
self._stdout_err_wrapper = StdOEWrap()
|
||||
|
||||
output_widget = OutputTextWidget(self)
|
||||
output_widget.setObjectName("PythonInterpreterOutput")
|
||||
output_widget.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
|
||||
output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
|
||||
tab_widget = QtWidgets.QTabWidget(self)
|
||||
tab_bar = EnhancedTabBar(tab_widget)
|
||||
tab_widget.setTabBar(tab_bar)
|
||||
tab_widget.setTabsClosable(False)
|
||||
tab_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
|
||||
widgets_splitter = QtWidgets.QSplitter(self)
|
||||
widgets_splitter.setOrientation(QtCore.Qt.Vertical)
|
||||
widgets_splitter.addWidget(output_widget)
|
||||
widgets_splitter.addWidget(tab_widget)
|
||||
widgets_splitter.setStretchFactor(0, 1)
|
||||
widgets_splitter.setStretchFactor(1, 1)
|
||||
height = int(self.default_height / 2)
|
||||
widgets_splitter.setSizes([height, self.default_height - height])
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(widgets_splitter)
|
||||
|
||||
line_check_timer = QtCore.QTimer()
|
||||
line_check_timer.setInterval(200)
|
||||
|
||||
line_check_timer.timeout.connect(self._on_timer_timeout)
|
||||
tab_bar.right_clicked.connect(self._on_tab_right_click)
|
||||
tab_bar.double_clicked.connect(self._on_tab_double_click)
|
||||
tab_bar.mid_clicked.connect(self._on_tab_mid_click)
|
||||
tab_widget.tabCloseRequested.connect(self._on_tab_close_req)
|
||||
|
||||
self._widgets_splitter = widgets_splitter
|
||||
self._output_widget = output_widget
|
||||
self._tab_widget = tab_widget
|
||||
self._line_check_timer = line_check_timer
|
||||
|
||||
self._append_lines([ayon_art])
|
||||
|
||||
self._first_show = True
|
||||
self._splitter_size_ratio = None
|
||||
self._allow_save_registry = allow_save_registry
|
||||
self._registry_saved = True
|
||||
|
||||
self._init_from_registry()
|
||||
|
||||
if self._tab_widget.count() < 1:
|
||||
self.add_tab("Python")
|
||||
|
||||
def _init_from_registry(self):
|
||||
setting_registry = PythonInterpreterRegistry()
|
||||
width = None
|
||||
height = None
|
||||
try:
|
||||
width = setting_registry.get_item("width")
|
||||
height = setting_registry.get_item("height")
|
||||
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if width is None or width < 200:
|
||||
width = self.default_width
|
||||
|
||||
if height is None or height < 200:
|
||||
height = self.default_height
|
||||
|
||||
self.resize(width, height)
|
||||
|
||||
try:
|
||||
self._splitter_size_ratio = (
|
||||
setting_registry.get_item("splitter_sizes")
|
||||
)
|
||||
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
tab_defs = setting_registry.get_item("tabs") or []
|
||||
for tab_def in tab_defs:
|
||||
widget = self.add_tab(tab_def["name"])
|
||||
widget.set_code(tab_def["code"])
|
||||
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def save_registry(self):
|
||||
# Window was not showed
|
||||
if not self._allow_save_registry or self._registry_saved:
|
||||
return
|
||||
|
||||
self._registry_saved = True
|
||||
setting_registry = PythonInterpreterRegistry()
|
||||
|
||||
setting_registry.set_item("width", self.width())
|
||||
setting_registry.set_item("height", self.height())
|
||||
|
||||
setting_registry.set_item(
|
||||
"splitter_sizes", self._widgets_splitter.sizes()
|
||||
)
|
||||
|
||||
tabs = []
|
||||
for tab_idx in range(self._tab_widget.count()):
|
||||
widget = self._tab_widget.widget(tab_idx)
|
||||
tab_code = widget.get_code()
|
||||
tab_name = self._tab_widget.tabText(tab_idx)
|
||||
tabs.append({
|
||||
"name": tab_name,
|
||||
"code": tab_code
|
||||
})
|
||||
|
||||
setting_registry.set_item("tabs", tabs)
|
||||
|
||||
def _on_tab_right_click(self, global_point):
|
||||
point = self._tab_widget.mapFromGlobal(global_point)
|
||||
tab_bar = self._tab_widget.tabBar()
|
||||
tab_idx = tab_bar.tabAt(point)
|
||||
last_index = tab_bar.count() - 1
|
||||
if tab_idx < 0 or tab_idx > last_index:
|
||||
return
|
||||
|
||||
menu = QtWidgets.QMenu(self._tab_widget)
|
||||
|
||||
add_tab_action = QtWidgets.QAction("Add tab...", menu)
|
||||
add_tab_action.setToolTip("Add new tab")
|
||||
|
||||
rename_tab_action = QtWidgets.QAction("Rename...", menu)
|
||||
rename_tab_action.setToolTip("Rename tab")
|
||||
|
||||
duplicate_tab_action = QtWidgets.QAction("Duplicate...", menu)
|
||||
duplicate_tab_action.setToolTip("Duplicate code to new tab")
|
||||
|
||||
close_tab_action = QtWidgets.QAction("Close", menu)
|
||||
close_tab_action.setToolTip("Close tab and lose content")
|
||||
close_tab_action.setEnabled(self._tab_widget.tabsClosable())
|
||||
|
||||
menu.addAction(add_tab_action)
|
||||
menu.addAction(rename_tab_action)
|
||||
menu.addAction(duplicate_tab_action)
|
||||
menu.addAction(close_tab_action)
|
||||
|
||||
result = menu.exec_(global_point)
|
||||
if result is None:
|
||||
return
|
||||
|
||||
if result is rename_tab_action:
|
||||
self._rename_tab_req(tab_idx)
|
||||
|
||||
elif result is add_tab_action:
|
||||
self._on_add_requested()
|
||||
|
||||
elif result is duplicate_tab_action:
|
||||
self._duplicate_requested(tab_idx)
|
||||
|
||||
elif result is close_tab_action:
|
||||
self._on_tab_close_req(tab_idx)
|
||||
|
||||
def _rename_tab_req(self, tab_idx):
|
||||
dialog = TabNameDialog(self)
|
||||
dialog.set_tab_name(self._tab_widget.tabText(tab_idx))
|
||||
dialog.exec_()
|
||||
tab_name = dialog.result()
|
||||
if tab_name:
|
||||
self._tab_widget.setTabText(tab_idx, tab_name)
|
||||
|
||||
def _duplicate_requested(self, tab_idx=None):
|
||||
if tab_idx is None:
|
||||
tab_idx = self._tab_widget.currentIndex()
|
||||
|
||||
src_widget = self._tab_widget.widget(tab_idx)
|
||||
dst_widget = self._add_tab()
|
||||
if dst_widget is None:
|
||||
return
|
||||
dst_widget.set_code(src_widget.get_code())
|
||||
|
||||
def _on_tab_mid_click(self, global_point):
|
||||
point = self._tab_widget.mapFromGlobal(global_point)
|
||||
tab_bar = self._tab_widget.tabBar()
|
||||
tab_idx = tab_bar.tabAt(point)
|
||||
last_index = tab_bar.count() - 1
|
||||
if tab_idx < 0 or tab_idx > last_index:
|
||||
return
|
||||
|
||||
self._on_tab_close_req(tab_idx)
|
||||
|
||||
def _on_tab_double_click(self, global_point):
|
||||
point = self._tab_widget.mapFromGlobal(global_point)
|
||||
tab_bar = self._tab_widget.tabBar()
|
||||
tab_idx = tab_bar.tabAt(point)
|
||||
last_index = tab_bar.count() - 1
|
||||
if tab_idx < 0 or tab_idx > last_index:
|
||||
return
|
||||
|
||||
self._rename_tab_req(tab_idx)
|
||||
|
||||
def _on_tab_close_req(self, tab_index):
|
||||
if self._tab_widget.count() == 1:
|
||||
return
|
||||
|
||||
widget = self._tab_widget.widget(tab_index)
|
||||
if widget in self._tabs:
|
||||
self._tabs.remove(widget)
|
||||
self._tab_widget.removeTab(tab_index)
|
||||
|
||||
if self._tab_widget.count() == 1:
|
||||
self._tab_widget.setTabsClosable(False)
|
||||
|
||||
def _append_lines(self, lines):
|
||||
at_max = self._output_widget.vertical_scroll_at_max()
|
||||
tmp_cursor = QtGui.QTextCursor(self._output_widget.document())
|
||||
tmp_cursor.movePosition(QtGui.QTextCursor.End)
|
||||
for line in lines:
|
||||
tmp_cursor.insertText(line)
|
||||
|
||||
if at_max:
|
||||
self._output_widget.scroll_to_bottom()
|
||||
|
||||
def _on_timer_timeout(self):
|
||||
if self._stdout_err_wrapper.lines:
|
||||
lines = []
|
||||
while self._stdout_err_wrapper.lines:
|
||||
line = self._stdout_err_wrapper.lines.popleft()
|
||||
lines.append(self.ansi_escape.sub("", line))
|
||||
self._append_lines(lines)
|
||||
|
||||
def _on_add_requested(self):
|
||||
self._add_tab()
|
||||
|
||||
def _add_tab(self):
|
||||
dialog = TabNameDialog(self)
|
||||
dialog.exec_()
|
||||
tab_name = dialog.result()
|
||||
if tab_name:
|
||||
return self.add_tab(tab_name)
|
||||
|
||||
return None
|
||||
|
||||
def _on_before_execute(self, code_text):
|
||||
at_max = self._output_widget.vertical_scroll_at_max()
|
||||
document = self._output_widget.document()
|
||||
tmp_cursor = QtGui.QTextCursor(document)
|
||||
tmp_cursor.movePosition(QtGui.QTextCursor.End)
|
||||
tmp_cursor.insertText("{}\nExecuting command:\n".format(20 * "-"))
|
||||
|
||||
code_block_format = QtGui.QTextFrameFormat()
|
||||
code_block_format.setBackground(QtGui.QColor(27, 27, 27))
|
||||
code_block_format.setPadding(4)
|
||||
|
||||
tmp_cursor.insertFrame(code_block_format)
|
||||
char_format = tmp_cursor.charFormat()
|
||||
char_format.setForeground(
|
||||
QtGui.QBrush(QtGui.QColor(114, 224, 198))
|
||||
)
|
||||
tmp_cursor.setCharFormat(char_format)
|
||||
tmp_cursor.insertText(code_text)
|
||||
|
||||
# Create new cursor
|
||||
tmp_cursor = QtGui.QTextCursor(document)
|
||||
tmp_cursor.movePosition(QtGui.QTextCursor.End)
|
||||
tmp_cursor.insertText("{}\n".format(20 * "-"))
|
||||
|
||||
if at_max:
|
||||
self._output_widget.scroll_to_bottom()
|
||||
|
||||
def add_tab(self, tab_name, index=None):
|
||||
widget = PythonTabWidget(self)
|
||||
widget.before_execute.connect(self._on_before_execute)
|
||||
widget.add_tab_requested.connect(self._on_add_requested)
|
||||
if index is None:
|
||||
if self._tab_widget.count() > 0:
|
||||
index = self._tab_widget.currentIndex() + 1
|
||||
else:
|
||||
index = 0
|
||||
|
||||
self._tabs.append(widget)
|
||||
self._tab_widget.insertTab(index, widget, tab_name)
|
||||
self._tab_widget.setCurrentIndex(index)
|
||||
|
||||
if self._tab_widget.count() > 1:
|
||||
self._tab_widget.setTabsClosable(True)
|
||||
widget.setFocus()
|
||||
return widget
|
||||
|
||||
def showEvent(self, event):
|
||||
self._line_check_timer.start()
|
||||
self._registry_saved = False
|
||||
super(PythonInterpreterWidget, self).showEvent(event)
|
||||
# First show setup
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self._on_first_show()
|
||||
|
||||
self._output_widget.scroll_to_bottom()
|
||||
|
||||
def _on_first_show(self):
|
||||
# Change stylesheet
|
||||
self.setStyleSheet(load_stylesheet())
|
||||
# Check if splitter size ratio is set
|
||||
# - first store value to local variable and then unset it
|
||||
splitter_size_ratio = self._splitter_size_ratio
|
||||
self._splitter_size_ratio = None
|
||||
# Skip if is not set
|
||||
if not splitter_size_ratio:
|
||||
return
|
||||
|
||||
# Skip if number of size items does not match to splitter
|
||||
splitters_count = len(self._widgets_splitter.sizes())
|
||||
if len(splitter_size_ratio) == splitters_count:
|
||||
self._widgets_splitter.setSizes(splitter_size_ratio)
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.save_registry()
|
||||
super(PythonInterpreterWidget, self).closeEvent(event)
|
||||
self._line_check_timer.stop()
|
||||
|
|
@ -5,6 +5,7 @@ Temporary folder operations
|
|||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import warnings
|
||||
|
||||
from ayon_core.lib import StringTemplate
|
||||
from ayon_core.pipeline import Anatomy
|
||||
|
|
@ -70,6 +71,21 @@ def _create_local_staging_dir(prefix, suffix, dirpath=None):
|
|||
)
|
||||
|
||||
|
||||
def create_custom_tempdir(project_name, anatomy=None):
|
||||
"""Backward compatibility deprecated since 2024/12/09.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Used deprecated 'create_custom_tempdir' "
|
||||
"use 'ayon_core.pipeline.tempdir.get_temp_dir' instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
if anatomy is None:
|
||||
anatomy = Anatomy(project_name)
|
||||
|
||||
return _create_custom_tempdir(project_name, anatomy)
|
||||
|
||||
|
||||
def _create_custom_tempdir(project_name, anatomy):
|
||||
""" Create custom tempdir
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import re
|
||||
import copy
|
||||
import platform
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
import ayon_api
|
||||
|
||||
|
|
@ -16,12 +17,12 @@ from ayon_core.pipeline.template_data import get_template_data
|
|||
|
||||
|
||||
def get_workfile_template_key_from_context(
|
||||
project_name,
|
||||
folder_path,
|
||||
task_name,
|
||||
host_name,
|
||||
project_settings=None
|
||||
):
|
||||
project_name: str,
|
||||
folder_path: str,
|
||||
task_name: str,
|
||||
host_name: str,
|
||||
project_settings: Optional[Dict[str, Any]] = None,
|
||||
) -> str:
|
||||
"""Helper function to get template key for workfile template.
|
||||
|
||||
Do the same as `get_workfile_template_key` but returns value for "session
|
||||
|
|
@ -34,15 +35,23 @@ def get_workfile_template_key_from_context(
|
|||
host_name (str): Host name.
|
||||
project_settings (Dict[str, Any]): Project settings for passed
|
||||
'project_name'. Not required at all but makes function faster.
|
||||
"""
|
||||
|
||||
Returns:
|
||||
str: Workfile template name.
|
||||
|
||||
"""
|
||||
folder_entity = ayon_api.get_folder_by_path(
|
||||
project_name, folder_path, fields={"id"}
|
||||
project_name,
|
||||
folder_path,
|
||||
fields={"id"},
|
||||
)
|
||||
task_entity = ayon_api.get_task_by_name(
|
||||
project_name, folder_entity["id"], task_name
|
||||
project_name,
|
||||
folder_entity["id"],
|
||||
task_name,
|
||||
fields={"taskType"},
|
||||
)
|
||||
task_type = task_entity.get("type")
|
||||
task_type = task_entity.get("taskType")
|
||||
|
||||
return get_workfile_template_key(
|
||||
project_name, task_type, host_name, project_settings
|
||||
|
|
|
|||
|
|
@ -71,15 +71,16 @@ class ExtractOTIOReview(
|
|||
# TODO: convert resulting image sequence to mp4
|
||||
|
||||
# get otio clip and other time info from instance clip
|
||||
# TODO: what if handles are different in `versionData`?
|
||||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
otio_review_clips = instance.data.get("otioReviewClips")
|
||||
|
||||
if otio_review_clips is None:
|
||||
self.log.info(f"Instance `{instance}` has no otioReviewClips")
|
||||
return
|
||||
|
||||
# TODO: what if handles are different in `versionData`?
|
||||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
|
||||
# add plugin wide attributes
|
||||
self.representation_files = []
|
||||
self.used_frames = []
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
"substancepainter",
|
||||
"nuke",
|
||||
"aftereffects",
|
||||
"unreal"
|
||||
"unreal",
|
||||
"houdini"
|
||||
]
|
||||
enabled = False
|
||||
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ class FilesModel(QtGui.QStandardItemModel):
|
|||
"""Make sure that removed items are removed from items mapping.
|
||||
|
||||
Connected with '_on_insert'. When user drag item and drop it to same
|
||||
view the item is actually removed and creted again but it happens in
|
||||
view the item is actually removed and created again but it happens in
|
||||
inner calls of Qt.
|
||||
"""
|
||||
|
||||
|
|
@ -841,7 +841,7 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
self._multivalue
|
||||
)
|
||||
widget.context_menu_requested.connect(
|
||||
self._on_context_menu_requested
|
||||
self._on_item_context_menu_request
|
||||
)
|
||||
self._files_view.setIndexWidget(index, widget)
|
||||
self._files_proxy_model.setData(
|
||||
|
|
@ -859,7 +859,7 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
for row in range(self._files_proxy_model.rowCount()):
|
||||
index = self._files_proxy_model.index(row, 0)
|
||||
item_id = index.data(ITEM_ID_ROLE)
|
||||
available_item_ids.add(index.data(ITEM_ID_ROLE))
|
||||
available_item_ids.add(item_id)
|
||||
|
||||
widget_ids = set(self._widgets_by_id.keys())
|
||||
for item_id in available_item_ids:
|
||||
|
|
@ -923,6 +923,9 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
if menu.actions():
|
||||
menu.popup(pos)
|
||||
|
||||
def _on_item_context_menu_request(self, pos):
|
||||
self._on_context_menu_requested(pos, True)
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
if self._multivalue:
|
||||
return
|
||||
|
|
|
|||
8
client/ayon_core/tools/console_interpreter/__init__.py
Normal file
8
client/ayon_core/tools/console_interpreter/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from .abstract import AbstractInterpreterController
|
||||
from .control import InterpreterController
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AbstractInterpreterController",
|
||||
"InterpreterController",
|
||||
)
|
||||
33
client/ayon_core/tools/console_interpreter/abstract.py
Normal file
33
client/ayon_core/tools/console_interpreter/abstract.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class TabItem:
|
||||
name: str
|
||||
code: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class InterpreterConfig:
|
||||
width: Optional[int]
|
||||
height: Optional[int]
|
||||
splitter_sizes: List[int] = field(default_factory=list)
|
||||
tabs: List[TabItem] = field(default_factory=list)
|
||||
|
||||
|
||||
class AbstractInterpreterController(ABC):
|
||||
@abstractmethod
|
||||
def get_config(self) -> InterpreterConfig:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save_config(
|
||||
self,
|
||||
width: int,
|
||||
height: int,
|
||||
splitter_sizes: List[int],
|
||||
tabs: List[Dict[str, str]],
|
||||
):
|
||||
pass
|
||||
63
client/ayon_core/tools/console_interpreter/control.py
Normal file
63
client/ayon_core/tools/console_interpreter/control.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from typing import List, Dict
|
||||
|
||||
from ayon_core.lib import JSONSettingRegistry
|
||||
from ayon_core.lib.local_settings import get_launcher_local_dir
|
||||
|
||||
from .abstract import (
|
||||
AbstractInterpreterController,
|
||||
TabItem,
|
||||
InterpreterConfig,
|
||||
)
|
||||
|
||||
|
||||
class InterpreterController(AbstractInterpreterController):
|
||||
def __init__(self):
|
||||
self._registry = JSONSettingRegistry(
|
||||
"python_interpreter_tool",
|
||||
get_launcher_local_dir(),
|
||||
)
|
||||
|
||||
def get_config(self):
|
||||
width = None
|
||||
height = None
|
||||
splitter_sizes = []
|
||||
tabs = []
|
||||
try:
|
||||
width = self._registry.get_item("width")
|
||||
height = self._registry.get_item("height")
|
||||
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
try:
|
||||
splitter_sizes = self._registry.get_item("splitter_sizes")
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
try:
|
||||
tab_defs = self._registry.get_item("tabs") or []
|
||||
for tab_def in tab_defs:
|
||||
tab_name = tab_def.get("name")
|
||||
if not tab_name:
|
||||
continue
|
||||
code = tab_def.get("code") or ""
|
||||
tabs.append(TabItem(tab_name, code))
|
||||
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
|
||||
return InterpreterConfig(
|
||||
width, height, splitter_sizes, tabs
|
||||
)
|
||||
|
||||
def save_config(
|
||||
self,
|
||||
width: int,
|
||||
height: int,
|
||||
splitter_sizes: List[int],
|
||||
tabs: List[Dict[str, str]],
|
||||
):
|
||||
self._registry.set_item("width", width)
|
||||
self._registry.set_item("height", height)
|
||||
self._registry.set_item("splitter_sizes", splitter_sizes)
|
||||
self._registry.set_item("tabs", tabs)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
from .window import (
|
||||
ConsoleInterpreterWindow
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"ConsoleInterpreterWindow",
|
||||
)
|
||||
42
client/ayon_core/tools/console_interpreter/ui/utils.py
Normal file
42
client/ayon_core/tools/console_interpreter/ui/utils.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import os
|
||||
import sys
|
||||
import collections
|
||||
|
||||
|
||||
class StdOEWrap:
|
||||
def __init__(self):
|
||||
self._origin_stdout_write = None
|
||||
self._origin_stderr_write = None
|
||||
self._listening = False
|
||||
self.lines = collections.deque()
|
||||
|
||||
if not sys.stdout:
|
||||
sys.stdout = open(os.devnull, "w")
|
||||
|
||||
if not sys.stderr:
|
||||
sys.stderr = open(os.devnull, "w")
|
||||
|
||||
if self._origin_stdout_write is None:
|
||||
self._origin_stdout_write = sys.stdout.write
|
||||
|
||||
if self._origin_stderr_write is None:
|
||||
self._origin_stderr_write = sys.stderr.write
|
||||
|
||||
self._listening = True
|
||||
sys.stdout.write = self._stdout_listener
|
||||
sys.stderr.write = self._stderr_listener
|
||||
|
||||
def stop_listen(self):
|
||||
self._listening = False
|
||||
|
||||
def _stdout_listener(self, text):
|
||||
if self._listening:
|
||||
self.lines.append(text)
|
||||
if self._origin_stdout_write is not None:
|
||||
self._origin_stdout_write(text)
|
||||
|
||||
def _stderr_listener(self, text):
|
||||
if self._listening:
|
||||
self.lines.append(text)
|
||||
if self._origin_stderr_write is not None:
|
||||
self._origin_stderr_write(text)
|
||||
251
client/ayon_core/tools/console_interpreter/ui/widgets.py
Normal file
251
client/ayon_core/tools/console_interpreter/ui/widgets.py
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
from code import InteractiveInterpreter
|
||||
|
||||
from qtpy import QtCore, QtWidgets, QtGui
|
||||
|
||||
|
||||
class PythonCodeEditor(QtWidgets.QPlainTextEdit):
|
||||
execute_requested = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName("PythonCodeEditor")
|
||||
|
||||
self._indent = 4
|
||||
|
||||
def _tab_shift_right(self):
|
||||
cursor = self.textCursor()
|
||||
selected_text = cursor.selectedText()
|
||||
if not selected_text:
|
||||
cursor.insertText(" " * self._indent)
|
||||
return
|
||||
|
||||
sel_start = cursor.selectionStart()
|
||||
sel_end = cursor.selectionEnd()
|
||||
cursor.setPosition(sel_end)
|
||||
end_line = cursor.blockNumber()
|
||||
cursor.setPosition(sel_start)
|
||||
while True:
|
||||
cursor.movePosition(QtGui.QTextCursor.StartOfLine)
|
||||
text = cursor.block().text()
|
||||
spaces = len(text) - len(text.lstrip(" "))
|
||||
new_spaces = spaces % self._indent
|
||||
if not new_spaces:
|
||||
new_spaces = self._indent
|
||||
|
||||
cursor.insertText(" " * new_spaces)
|
||||
if cursor.blockNumber() == end_line:
|
||||
break
|
||||
|
||||
cursor.movePosition(QtGui.QTextCursor.NextBlock)
|
||||
|
||||
def _tab_shift_left(self):
|
||||
tmp_cursor = self.textCursor()
|
||||
sel_start = tmp_cursor.selectionStart()
|
||||
sel_end = tmp_cursor.selectionEnd()
|
||||
|
||||
cursor = QtGui.QTextCursor(self.document())
|
||||
cursor.setPosition(sel_end)
|
||||
end_line = cursor.blockNumber()
|
||||
cursor.setPosition(sel_start)
|
||||
while True:
|
||||
cursor.movePosition(QtGui.QTextCursor.StartOfLine)
|
||||
text = cursor.block().text()
|
||||
spaces = len(text) - len(text.lstrip(" "))
|
||||
if spaces:
|
||||
spaces_to_remove = (spaces % self._indent) or self._indent
|
||||
if spaces_to_remove > spaces:
|
||||
spaces_to_remove = spaces
|
||||
|
||||
cursor.setPosition(
|
||||
cursor.position() + spaces_to_remove,
|
||||
QtGui.QTextCursor.KeepAnchor
|
||||
)
|
||||
cursor.removeSelectedText()
|
||||
|
||||
if cursor.blockNumber() == end_line:
|
||||
break
|
||||
|
||||
cursor.movePosition(QtGui.QTextCursor.NextBlock)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Backtab:
|
||||
self._tab_shift_left()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
if event.key() == QtCore.Qt.Key_Tab:
|
||||
if event.modifiers() == QtCore.Qt.NoModifier:
|
||||
self._tab_shift_right()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
if (
|
||||
event.key() == QtCore.Qt.Key_Return
|
||||
and event.modifiers() == QtCore.Qt.ControlModifier
|
||||
):
|
||||
self.execute_requested.emit()
|
||||
event.accept()
|
||||
return
|
||||
|
||||
super().keyPressEvent(event)
|
||||
|
||||
|
||||
class PythonTabWidget(QtWidgets.QWidget):
|
||||
add_tab_requested = QtCore.Signal()
|
||||
before_execute = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
code_input = PythonCodeEditor(self)
|
||||
|
||||
self.setFocusProxy(code_input)
|
||||
|
||||
add_tab_btn = QtWidgets.QPushButton("Add tab...", self)
|
||||
add_tab_btn.setDefault(False)
|
||||
add_tab_btn.setToolTip("Add new tab")
|
||||
|
||||
execute_btn = QtWidgets.QPushButton("Execute", self)
|
||||
execute_btn.setDefault(False)
|
||||
execute_btn.setToolTip("Execute command (Ctrl + Enter)")
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
btns_layout.addWidget(add_tab_btn)
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(execute_btn)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(code_input, 1)
|
||||
layout.addLayout(btns_layout, 0)
|
||||
|
||||
add_tab_btn.clicked.connect(self._on_add_tab_clicked)
|
||||
execute_btn.clicked.connect(self._on_execute_clicked)
|
||||
code_input.execute_requested.connect(self.execute)
|
||||
|
||||
self._code_input = code_input
|
||||
self._interpreter = InteractiveInterpreter()
|
||||
|
||||
def _on_add_tab_clicked(self):
|
||||
self.add_tab_requested.emit()
|
||||
|
||||
def _on_execute_clicked(self):
|
||||
self.execute()
|
||||
|
||||
def get_code(self):
|
||||
return self._code_input.toPlainText()
|
||||
|
||||
def set_code(self, code_text):
|
||||
self._code_input.setPlainText(code_text)
|
||||
|
||||
def execute(self):
|
||||
code_text = self._code_input.toPlainText()
|
||||
self.before_execute.emit(code_text)
|
||||
self._interpreter.runcode(code_text)
|
||||
|
||||
|
||||
class TabNameDialog(QtWidgets.QDialog):
|
||||
default_width = 330
|
||||
default_height = 85
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setWindowTitle("Enter tab name")
|
||||
|
||||
name_label = QtWidgets.QLabel("Tab name:", self)
|
||||
name_input = QtWidgets.QLineEdit(self)
|
||||
|
||||
inputs_layout = QtWidgets.QHBoxLayout()
|
||||
inputs_layout.addWidget(name_label)
|
||||
inputs_layout.addWidget(name_input)
|
||||
|
||||
ok_btn = QtWidgets.QPushButton("Ok", self)
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel", self)
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(ok_btn)
|
||||
btns_layout.addWidget(cancel_btn)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addLayout(inputs_layout)
|
||||
layout.addStretch(1)
|
||||
layout.addLayout(btns_layout)
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
cancel_btn.clicked.connect(self._on_cancel_clicked)
|
||||
|
||||
self._name_input = name_input
|
||||
self._ok_btn = ok_btn
|
||||
self._cancel_btn = cancel_btn
|
||||
|
||||
self._result = None
|
||||
|
||||
self.resize(self.default_width, self.default_height)
|
||||
|
||||
def set_tab_name(self, name):
|
||||
self._name_input.setText(name)
|
||||
|
||||
def result(self):
|
||||
return self._result
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
btns_width = max(
|
||||
self._ok_btn.width(),
|
||||
self._cancel_btn.width()
|
||||
)
|
||||
|
||||
self._ok_btn.setMinimumWidth(btns_width)
|
||||
self._cancel_btn.setMinimumWidth(btns_width)
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
self._result = self._name_input.text()
|
||||
self.accept()
|
||||
|
||||
def _on_cancel_clicked(self):
|
||||
self._result = None
|
||||
self.reject()
|
||||
|
||||
|
||||
class OutputTextWidget(QtWidgets.QTextEdit):
|
||||
v_max_offset = 4
|
||||
|
||||
def vertical_scroll_at_max(self):
|
||||
v_scroll = self.verticalScrollBar()
|
||||
return v_scroll.value() > v_scroll.maximum() - self.v_max_offset
|
||||
|
||||
def scroll_to_bottom(self):
|
||||
v_scroll = self.verticalScrollBar()
|
||||
return v_scroll.setValue(v_scroll.maximum())
|
||||
|
||||
|
||||
class EnhancedTabBar(QtWidgets.QTabBar):
|
||||
double_clicked = QtCore.Signal(QtCore.QPoint)
|
||||
right_clicked = QtCore.Signal(QtCore.QPoint)
|
||||
mid_clicked = QtCore.Signal(QtCore.QPoint)
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setDrawBase(False)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
self.double_clicked.emit(event.globalPos())
|
||||
event.accept()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
self.right_clicked.emit(event.globalPos())
|
||||
event.accept()
|
||||
return
|
||||
|
||||
elif event.button() == QtCore.Qt.MidButton:
|
||||
self.mid_clicked.emit(event.globalPos())
|
||||
event.accept()
|
||||
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
324
client/ayon_core/tools/console_interpreter/ui/window.py
Normal file
324
client/ayon_core/tools/console_interpreter/ui/window.py
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
import re
|
||||
from typing import Optional
|
||||
|
||||
from qtpy import QtWidgets, QtGui, QtCore
|
||||
|
||||
from ayon_core import resources
|
||||
from ayon_core.style import load_stylesheet
|
||||
from ayon_core.tools.console_interpreter import (
|
||||
AbstractInterpreterController,
|
||||
InterpreterController,
|
||||
)
|
||||
|
||||
from .utils import StdOEWrap
|
||||
from .widgets import (
|
||||
PythonTabWidget,
|
||||
OutputTextWidget,
|
||||
EnhancedTabBar,
|
||||
TabNameDialog,
|
||||
)
|
||||
|
||||
ANSI_ESCAPE = re.compile(
|
||||
r"(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]"
|
||||
)
|
||||
AYON_ART = r"""
|
||||
|
||||
▄██▄
|
||||
▄███▄ ▀██▄ ▀██▀ ▄██▀ ▄██▀▀▀██▄ ▀███▄ █▄
|
||||
▄▄ ▀██▄ ▀██▄ ▄██▀ ██▀ ▀██▄ ▄ ▀██▄ ███
|
||||
▄██▀ ██▄ ▀ ▄▄ ▀ ██ ▄██ ███ ▀██▄ ███
|
||||
▄██▀ ▀██▄ ██ ▀██▄ ▄██▀ ███ ▀██ ▀█▀
|
||||
▄██▀ ▀██▄ ▀█ ▀██▄▄▄▄██▀ █▀ ▀██▄
|
||||
|
||||
· · - =[ by YNPUT ]:[ http://ayon.ynput.io ]= - · ·
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ConsoleInterpreterWindow(QtWidgets.QWidget):
|
||||
default_width = 1000
|
||||
default_height = 600
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
controller: Optional[AbstractInterpreterController] = None,
|
||||
parent: Optional[QtWidgets.QWidget] = None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setWindowTitle("AYON Console")
|
||||
self.setWindowIcon(QtGui.QIcon(resources.get_ayon_icon_filepath()))
|
||||
|
||||
if controller is None:
|
||||
controller = InterpreterController()
|
||||
|
||||
output_widget = OutputTextWidget(self)
|
||||
output_widget.setObjectName("PythonInterpreterOutput")
|
||||
output_widget.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
|
||||
output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
|
||||
|
||||
tab_widget = QtWidgets.QTabWidget(self)
|
||||
tab_bar = EnhancedTabBar(tab_widget)
|
||||
tab_widget.setTabBar(tab_bar)
|
||||
tab_widget.setTabsClosable(False)
|
||||
tab_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
|
||||
widgets_splitter = QtWidgets.QSplitter(self)
|
||||
widgets_splitter.setOrientation(QtCore.Qt.Vertical)
|
||||
widgets_splitter.addWidget(output_widget)
|
||||
widgets_splitter.addWidget(tab_widget)
|
||||
widgets_splitter.setStretchFactor(0, 1)
|
||||
widgets_splitter.setStretchFactor(1, 1)
|
||||
height = int(self.default_height / 2)
|
||||
widgets_splitter.setSizes([height, self.default_height - height])
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(widgets_splitter)
|
||||
|
||||
line_check_timer = QtCore.QTimer()
|
||||
line_check_timer.setInterval(200)
|
||||
|
||||
line_check_timer.timeout.connect(self._on_timer_timeout)
|
||||
tab_bar.right_clicked.connect(self._on_tab_right_click)
|
||||
tab_bar.double_clicked.connect(self._on_tab_double_click)
|
||||
tab_bar.mid_clicked.connect(self._on_tab_mid_click)
|
||||
tab_widget.tabCloseRequested.connect(self._on_tab_close_req)
|
||||
|
||||
self._tabs = []
|
||||
|
||||
self._stdout_err_wrapper = StdOEWrap()
|
||||
|
||||
self._widgets_splitter = widgets_splitter
|
||||
self._output_widget = output_widget
|
||||
self._tab_widget = tab_widget
|
||||
self._line_check_timer = line_check_timer
|
||||
|
||||
self._append_lines([AYON_ART])
|
||||
|
||||
self._first_show = True
|
||||
self._controller = controller
|
||||
|
||||
def showEvent(self, event):
|
||||
self._line_check_timer.start()
|
||||
super().showEvent(event)
|
||||
# First show setup
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self._on_first_show()
|
||||
|
||||
if self._tab_widget.count() < 1:
|
||||
self.add_tab("Python")
|
||||
|
||||
self._output_widget.scroll_to_bottom()
|
||||
|
||||
def closeEvent(self, event):
|
||||
self._save_registry()
|
||||
super().closeEvent(event)
|
||||
self._line_check_timer.stop()
|
||||
|
||||
def add_tab(self, tab_name, index=None):
|
||||
widget = PythonTabWidget(self)
|
||||
widget.before_execute.connect(self._on_before_execute)
|
||||
widget.add_tab_requested.connect(self._on_add_requested)
|
||||
if index is None:
|
||||
if self._tab_widget.count() > 0:
|
||||
index = self._tab_widget.currentIndex() + 1
|
||||
else:
|
||||
index = 0
|
||||
|
||||
self._tabs.append(widget)
|
||||
self._tab_widget.insertTab(index, widget, tab_name)
|
||||
self._tab_widget.setCurrentIndex(index)
|
||||
|
||||
if self._tab_widget.count() > 1:
|
||||
self._tab_widget.setTabsClosable(True)
|
||||
widget.setFocus()
|
||||
return widget
|
||||
|
||||
def _on_first_show(self):
|
||||
config = self._controller.get_config()
|
||||
width = config.width
|
||||
height = config.height
|
||||
if width is None or width < 200:
|
||||
width = self.default_width
|
||||
if height is None or height < 200:
|
||||
height = self.default_height
|
||||
|
||||
for tab_item in config.tabs:
|
||||
widget = self.add_tab(tab_item.name)
|
||||
widget.set_code(tab_item.code)
|
||||
|
||||
self.resize(width, height)
|
||||
# Change stylesheet
|
||||
self.setStyleSheet(load_stylesheet())
|
||||
# Check if splitter sizes are set
|
||||
splitters_count = len(self._widgets_splitter.sizes())
|
||||
if len(config.splitter_sizes) == splitters_count:
|
||||
self._widgets_splitter.setSizes(config.splitter_sizes)
|
||||
|
||||
def _save_registry(self):
|
||||
tabs = []
|
||||
for tab_idx in range(self._tab_widget.count()):
|
||||
widget = self._tab_widget.widget(tab_idx)
|
||||
tabs.append({
|
||||
"name": self._tab_widget.tabText(tab_idx),
|
||||
"code": widget.get_code()
|
||||
})
|
||||
|
||||
self._controller.save_config(
|
||||
self.width(),
|
||||
self.height(),
|
||||
self._widgets_splitter.sizes(),
|
||||
tabs
|
||||
)
|
||||
|
||||
def _on_tab_right_click(self, global_point):
|
||||
point = self._tab_widget.mapFromGlobal(global_point)
|
||||
tab_bar = self._tab_widget.tabBar()
|
||||
tab_idx = tab_bar.tabAt(point)
|
||||
last_index = tab_bar.count() - 1
|
||||
if tab_idx < 0 or tab_idx > last_index:
|
||||
return
|
||||
|
||||
menu = QtWidgets.QMenu(self._tab_widget)
|
||||
|
||||
add_tab_action = QtWidgets.QAction("Add tab...", menu)
|
||||
add_tab_action.setToolTip("Add new tab")
|
||||
|
||||
rename_tab_action = QtWidgets.QAction("Rename...", menu)
|
||||
rename_tab_action.setToolTip("Rename tab")
|
||||
|
||||
duplicate_tab_action = QtWidgets.QAction("Duplicate...", menu)
|
||||
duplicate_tab_action.setToolTip("Duplicate code to new tab")
|
||||
|
||||
close_tab_action = QtWidgets.QAction("Close", menu)
|
||||
close_tab_action.setToolTip("Close tab and lose content")
|
||||
close_tab_action.setEnabled(self._tab_widget.tabsClosable())
|
||||
|
||||
menu.addAction(add_tab_action)
|
||||
menu.addAction(rename_tab_action)
|
||||
menu.addAction(duplicate_tab_action)
|
||||
menu.addAction(close_tab_action)
|
||||
|
||||
result = menu.exec_(global_point)
|
||||
if result is None:
|
||||
return
|
||||
|
||||
if result is rename_tab_action:
|
||||
self._rename_tab_req(tab_idx)
|
||||
|
||||
elif result is add_tab_action:
|
||||
self._on_add_requested()
|
||||
|
||||
elif result is duplicate_tab_action:
|
||||
self._duplicate_requested(tab_idx)
|
||||
|
||||
elif result is close_tab_action:
|
||||
self._on_tab_close_req(tab_idx)
|
||||
|
||||
def _rename_tab_req(self, tab_idx):
|
||||
dialog = TabNameDialog(self)
|
||||
dialog.set_tab_name(self._tab_widget.tabText(tab_idx))
|
||||
dialog.exec_()
|
||||
tab_name = dialog.result()
|
||||
if tab_name:
|
||||
self._tab_widget.setTabText(tab_idx, tab_name)
|
||||
|
||||
def _duplicate_requested(self, tab_idx=None):
|
||||
if tab_idx is None:
|
||||
tab_idx = self._tab_widget.currentIndex()
|
||||
|
||||
src_widget = self._tab_widget.widget(tab_idx)
|
||||
dst_widget = self._add_tab()
|
||||
if dst_widget is None:
|
||||
return
|
||||
dst_widget.set_code(src_widget.get_code())
|
||||
|
||||
def _on_tab_mid_click(self, global_point):
|
||||
point = self._tab_widget.mapFromGlobal(global_point)
|
||||
tab_bar = self._tab_widget.tabBar()
|
||||
tab_idx = tab_bar.tabAt(point)
|
||||
last_index = tab_bar.count() - 1
|
||||
if tab_idx < 0 or tab_idx > last_index:
|
||||
return
|
||||
|
||||
self._on_tab_close_req(tab_idx)
|
||||
|
||||
def _on_tab_double_click(self, global_point):
|
||||
point = self._tab_widget.mapFromGlobal(global_point)
|
||||
tab_bar = self._tab_widget.tabBar()
|
||||
tab_idx = tab_bar.tabAt(point)
|
||||
last_index = tab_bar.count() - 1
|
||||
if tab_idx < 0 or tab_idx > last_index:
|
||||
return
|
||||
|
||||
self._rename_tab_req(tab_idx)
|
||||
|
||||
def _on_tab_close_req(self, tab_index):
|
||||
if self._tab_widget.count() == 1:
|
||||
return
|
||||
|
||||
widget = self._tab_widget.widget(tab_index)
|
||||
if widget in self._tabs:
|
||||
self._tabs.remove(widget)
|
||||
self._tab_widget.removeTab(tab_index)
|
||||
|
||||
if self._tab_widget.count() == 1:
|
||||
self._tab_widget.setTabsClosable(False)
|
||||
|
||||
def _append_lines(self, lines):
|
||||
at_max = self._output_widget.vertical_scroll_at_max()
|
||||
tmp_cursor = QtGui.QTextCursor(self._output_widget.document())
|
||||
tmp_cursor.movePosition(QtGui.QTextCursor.End)
|
||||
for line in lines:
|
||||
tmp_cursor.insertText(line)
|
||||
|
||||
if at_max:
|
||||
self._output_widget.scroll_to_bottom()
|
||||
|
||||
def _on_timer_timeout(self):
|
||||
if self._stdout_err_wrapper.lines:
|
||||
lines = []
|
||||
while self._stdout_err_wrapper.lines:
|
||||
line = self._stdout_err_wrapper.lines.popleft()
|
||||
lines.append(ANSI_ESCAPE.sub("", line))
|
||||
self._append_lines(lines)
|
||||
|
||||
def _on_add_requested(self):
|
||||
self._add_tab()
|
||||
|
||||
def _add_tab(self):
|
||||
dialog = TabNameDialog(self)
|
||||
dialog.exec_()
|
||||
tab_name = dialog.result()
|
||||
if tab_name:
|
||||
return self.add_tab(tab_name)
|
||||
|
||||
return None
|
||||
|
||||
def _on_before_execute(self, code_text):
|
||||
at_max = self._output_widget.vertical_scroll_at_max()
|
||||
document = self._output_widget.document()
|
||||
tmp_cursor = QtGui.QTextCursor(document)
|
||||
tmp_cursor.movePosition(QtGui.QTextCursor.End)
|
||||
tmp_cursor.insertText("{}\nExecuting command:\n".format(20 * "-"))
|
||||
|
||||
code_block_format = QtGui.QTextFrameFormat()
|
||||
code_block_format.setBackground(QtGui.QColor(27, 27, 27))
|
||||
code_block_format.setPadding(4)
|
||||
|
||||
tmp_cursor.insertFrame(code_block_format)
|
||||
char_format = tmp_cursor.charFormat()
|
||||
char_format.setForeground(
|
||||
QtGui.QBrush(QtGui.QColor(114, 224, 198))
|
||||
)
|
||||
tmp_cursor.setCharFormat(char_format)
|
||||
tmp_cursor.insertText(code_text)
|
||||
|
||||
# Create new cursor
|
||||
tmp_cursor = QtGui.QTextCursor(document)
|
||||
tmp_cursor.movePosition(QtGui.QTextCursor.End)
|
||||
tmp_cursor.insertText("{}\n".format(20 * "-"))
|
||||
|
||||
if at_max:
|
||||
self._output_widget.scroll_to_bottom()
|
||||
|
|
@ -484,6 +484,34 @@ class LoadedFilesView(QtWidgets.QTreeView):
|
|||
self._time_delegate = time_delegate
|
||||
self._remove_btn = remove_btn
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
self._model.refresh()
|
||||
header = self.header()
|
||||
header.resizeSections(QtWidgets.QHeaderView.ResizeToContents)
|
||||
self._update_remove_btn()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
self._update_remove_btn()
|
||||
|
||||
def add_filepaths(self, filepaths):
|
||||
self._model.add_filepaths(filepaths)
|
||||
self._fill_selection()
|
||||
|
||||
def remove_item_by_id(self, item_id):
|
||||
self._model.remove_item_by_id(item_id)
|
||||
self._fill_selection()
|
||||
|
||||
def get_current_report(self):
|
||||
index = self.currentIndex()
|
||||
item_id = index.data(ITEM_ID_ROLE)
|
||||
return self._model.get_report_by_id(item_id)
|
||||
|
||||
def refresh(self):
|
||||
self._model.refresh()
|
||||
self._fill_selection()
|
||||
|
||||
def _update_remove_btn(self):
|
||||
viewport = self.viewport()
|
||||
height = viewport.height() + self.header().height()
|
||||
|
|
@ -496,28 +524,9 @@ class LoadedFilesView(QtWidgets.QTreeView):
|
|||
header.resizeSections(QtWidgets.QHeaderView.ResizeToContents)
|
||||
self._update_remove_btn()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super().resizeEvent(event)
|
||||
self._update_remove_btn()
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
self._model.refresh()
|
||||
header = self.header()
|
||||
header.resizeSections(QtWidgets.QHeaderView.ResizeToContents)
|
||||
self._update_remove_btn()
|
||||
|
||||
def _on_selection_change(self):
|
||||
self.selection_changed.emit()
|
||||
|
||||
def add_filepaths(self, filepaths):
|
||||
self._model.add_filepaths(filepaths)
|
||||
self._fill_selection()
|
||||
|
||||
def remove_item_by_id(self, item_id):
|
||||
self._model.remove_item_by_id(item_id)
|
||||
self._fill_selection()
|
||||
|
||||
def _on_remove_clicked(self):
|
||||
index = self.currentIndex()
|
||||
item_id = index.data(ITEM_ID_ROLE)
|
||||
|
|
@ -533,11 +542,6 @@ class LoadedFilesView(QtWidgets.QTreeView):
|
|||
if index.isValid():
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
def get_current_report(self):
|
||||
index = self.currentIndex()
|
||||
item_id = index.data(ITEM_ID_ROLE)
|
||||
return self._model.get_report_by_id(item_id)
|
||||
|
||||
|
||||
class LoadedFilesWidget(QtWidgets.QWidget):
|
||||
report_changed = QtCore.Signal()
|
||||
|
|
@ -577,15 +581,18 @@ class LoadedFilesWidget(QtWidgets.QWidget):
|
|||
self._add_filepaths(filepaths)
|
||||
event.accept()
|
||||
|
||||
def refresh(self):
|
||||
self._view.refresh()
|
||||
|
||||
def get_current_report(self):
|
||||
return self._view.get_current_report()
|
||||
|
||||
def _on_report_change(self):
|
||||
self.report_changed.emit()
|
||||
|
||||
def _add_filepaths(self, filepaths):
|
||||
self._view.add_filepaths(filepaths)
|
||||
|
||||
def get_current_report(self):
|
||||
return self._view.get_current_report()
|
||||
|
||||
|
||||
class PublishReportViewerWindow(QtWidgets.QWidget):
|
||||
default_width = 1200
|
||||
|
|
@ -624,9 +631,12 @@ class PublishReportViewerWindow(QtWidgets.QWidget):
|
|||
self.resize(self.default_width, self.default_height)
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def _on_report_change(self):
|
||||
report = self._loaded_files_widget.get_current_report()
|
||||
self.set_report(report)
|
||||
def refresh(self):
|
||||
self._loaded_files_widget.refresh()
|
||||
|
||||
def set_report(self, report_data):
|
||||
self._main_widget.set_report(report_data)
|
||||
|
||||
def _on_report_change(self):
|
||||
report = self._loaded_files_widget.get_current_report()
|
||||
self.set_report(report)
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ class PushToContextController:
|
|||
return False
|
||||
|
||||
if (
|
||||
not self._user_values.new_folder_name
|
||||
self._user_values.new_folder_name is None
|
||||
and not self._selection_model.get_selected_folder_id()
|
||||
):
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from ayon_core.pipeline import Anatomy
|
|||
from ayon_core.pipeline.version_start import get_versioning_start
|
||||
from ayon_core.pipeline.template_data import get_template_data
|
||||
from ayon_core.pipeline.publish import get_publish_template_name
|
||||
from ayon_core.pipeline.create import get_product_name
|
||||
from ayon_core.pipeline.create import get_product_name, TaskNotSetError
|
||||
|
||||
UNKNOWN = object()
|
||||
|
||||
|
|
@ -823,15 +823,25 @@ class ProjectPushItemProcess:
|
|||
task_name = task_info["name"]
|
||||
task_type = task_info["taskType"]
|
||||
|
||||
product_name = get_product_name(
|
||||
self._item.dst_project_name,
|
||||
task_name,
|
||||
task_type,
|
||||
self.host_name,
|
||||
product_type,
|
||||
self._item.variant,
|
||||
project_settings=self._project_settings
|
||||
)
|
||||
try:
|
||||
product_name = get_product_name(
|
||||
self._item.dst_project_name,
|
||||
task_name,
|
||||
task_type,
|
||||
self.host_name,
|
||||
product_type,
|
||||
self._item.variant,
|
||||
project_settings=self._project_settings
|
||||
)
|
||||
except TaskNotSetError:
|
||||
self._status.set_failed(
|
||||
"Target product name template requires task name. To continue"
|
||||
" you have to select target task or change settings"
|
||||
" <b>ayon+settings://core/tools/creator/product_name_profiles"
|
||||
f"?project={self._item.dst_project_name}</b>."
|
||||
)
|
||||
raise PushToProjectError(self._status.fail_reason)
|
||||
|
||||
self._log_info(
|
||||
f"Push will be integrating to product with name '{product_name}'"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -84,8 +84,11 @@ class UserPublishValuesModel:
|
|||
return
|
||||
|
||||
self._new_folder_name = folder_name
|
||||
is_valid = True
|
||||
if folder_name:
|
||||
if folder_name is None:
|
||||
is_valid = True
|
||||
elif not folder_name:
|
||||
is_valid = False
|
||||
else:
|
||||
is_valid = (
|
||||
self.folder_name_regex.match(folder_name) is not None
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,69 @@ from ayon_core.tools.utils import (
|
|||
ProjectsCombobox,
|
||||
FoldersWidget,
|
||||
TasksWidget,
|
||||
NiceCheckbox,
|
||||
)
|
||||
from ayon_core.tools.push_to_project.control import (
|
||||
PushToContextController,
|
||||
)
|
||||
|
||||
|
||||
class ErrorDetailDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setWindowTitle("Error detail")
|
||||
self.setWindowIcon(QtGui.QIcon(get_app_icon_path()))
|
||||
|
||||
title_label = QtWidgets.QLabel(self)
|
||||
|
||||
sep_1 = SeparatorWidget(parent=self)
|
||||
|
||||
detail_widget = QtWidgets.QTextBrowser(self)
|
||||
detail_widget.setReadOnly(True)
|
||||
detail_widget.setTextInteractionFlags(
|
||||
QtCore.Qt.TextBrowserInteraction
|
||||
)
|
||||
|
||||
sep_2 = SeparatorWidget(parent=self)
|
||||
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
|
||||
copy_btn = QtWidgets.QPushButton("Copy", btns_widget)
|
||||
close_btn = QtWidgets.QPushButton("Close", btns_widget)
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||
btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(copy_btn, 0)
|
||||
btns_layout.addWidget(close_btn, 0)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
main_layout.addWidget(title_label, 0)
|
||||
main_layout.addWidget(sep_1, 0)
|
||||
main_layout.addWidget(detail_widget, 1)
|
||||
main_layout.addWidget(sep_2, 0)
|
||||
main_layout.addWidget(btns_widget, 0)
|
||||
|
||||
copy_btn.clicked.connect(self._on_copy_click)
|
||||
close_btn.clicked.connect(self._on_close_click)
|
||||
|
||||
self._title_label = title_label
|
||||
self._detail_widget = detail_widget
|
||||
|
||||
def set_detail(self, title, detail):
|
||||
self._title_label.setText(title)
|
||||
self._detail_widget.setText(detail)
|
||||
|
||||
def _on_copy_click(self):
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
clipboard.setText(self._detail_widget.toPlainText())
|
||||
|
||||
def _on_close_click(self):
|
||||
self.close()
|
||||
|
||||
|
||||
class PushToContextSelectWindow(QtWidgets.QWidget):
|
||||
def __init__(self, controller=None):
|
||||
super(PushToContextSelectWindow, self).__init__()
|
||||
|
|
@ -66,9 +123,12 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
# --- Inputs widget ---
|
||||
inputs_widget = QtWidgets.QWidget(main_splitter)
|
||||
|
||||
new_folder_checkbox = NiceCheckbox(True, parent=inputs_widget)
|
||||
|
||||
folder_name_input = PlaceholderLineEdit(inputs_widget)
|
||||
folder_name_input.setPlaceholderText("< Name of new folder >")
|
||||
folder_name_input.setObjectName("ValidatedLineEdit")
|
||||
folder_name_input.setEnabled(new_folder_checkbox.isChecked())
|
||||
|
||||
variant_input = PlaceholderLineEdit(inputs_widget)
|
||||
variant_input.setPlaceholderText("< Variant >")
|
||||
|
|
@ -79,6 +139,7 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
|
||||
inputs_layout = QtWidgets.QFormLayout(inputs_widget)
|
||||
inputs_layout.setContentsMargins(0, 0, 0, 0)
|
||||
inputs_layout.addRow("Create new folder", new_folder_checkbox)
|
||||
inputs_layout.addRow("New folder name", folder_name_input)
|
||||
inputs_layout.addRow("Variant", variant_input)
|
||||
inputs_layout.addRow("Comment", comment_input)
|
||||
|
|
@ -113,6 +174,10 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
|
||||
overlay_label = QtWidgets.QLabel(overlay_widget)
|
||||
overlay_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
overlay_label.setWordWrap(True)
|
||||
overlay_label.setTextInteractionFlags(
|
||||
QtCore.Qt.TextBrowserInteraction
|
||||
)
|
||||
|
||||
overlay_btns_widget = QtWidgets.QWidget(overlay_widget)
|
||||
overlay_btns_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
|
@ -121,13 +186,28 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
overlay_try_btn = QtWidgets.QPushButton(
|
||||
"Try again", overlay_btns_widget
|
||||
)
|
||||
overlay_try_btn.setToolTip(
|
||||
"Hide overlay and modify submit information."
|
||||
)
|
||||
|
||||
show_detail_btn = QtWidgets.QPushButton(
|
||||
"Show error detail", overlay_btns_widget
|
||||
)
|
||||
show_detail_btn.setToolTip(
|
||||
"Show error detail dialog to copy full error."
|
||||
)
|
||||
|
||||
overlay_close_btn = QtWidgets.QPushButton(
|
||||
"Close", overlay_btns_widget
|
||||
)
|
||||
overlay_close_btn.setToolTip("Discard changes and close window.")
|
||||
|
||||
overlay_btns_layout = QtWidgets.QHBoxLayout(overlay_btns_widget)
|
||||
overlay_btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
overlay_btns_layout.setSpacing(10)
|
||||
overlay_btns_layout.addStretch(1)
|
||||
overlay_btns_layout.addWidget(overlay_try_btn, 0)
|
||||
overlay_btns_layout.addWidget(show_detail_btn, 0)
|
||||
overlay_btns_layout.addWidget(overlay_close_btn, 0)
|
||||
overlay_btns_layout.addStretch(1)
|
||||
|
||||
|
|
@ -156,12 +236,14 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
main_thread_timer.timeout.connect(self._on_main_thread_timer)
|
||||
show_timer.timeout.connect(self._on_show_timer)
|
||||
user_input_changed_timer.timeout.connect(self._on_user_input_timer)
|
||||
new_folder_checkbox.stateChanged.connect(self._on_new_folder_check)
|
||||
folder_name_input.textChanged.connect(self._on_new_folder_change)
|
||||
variant_input.textChanged.connect(self._on_variant_change)
|
||||
comment_input.textChanged.connect(self._on_comment_change)
|
||||
|
||||
publish_btn.clicked.connect(self._on_select_click)
|
||||
cancel_btn.clicked.connect(self._on_close_click)
|
||||
show_detail_btn.clicked.connect(self._on_show_detail_click)
|
||||
overlay_close_btn.clicked.connect(self._on_close_click)
|
||||
overlay_try_btn.clicked.connect(self._on_try_again_click)
|
||||
|
||||
|
|
@ -203,23 +285,28 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
self._tasks_widget = tasks_widget
|
||||
|
||||
self._variant_input = variant_input
|
||||
self._new_folder_checkbox = new_folder_checkbox
|
||||
self._folder_name_input = folder_name_input
|
||||
self._comment_input = comment_input
|
||||
|
||||
self._publish_btn = publish_btn
|
||||
|
||||
self._overlay_widget = overlay_widget
|
||||
self._show_detail_btn = show_detail_btn
|
||||
self._overlay_close_btn = overlay_close_btn
|
||||
self._overlay_try_btn = overlay_try_btn
|
||||
self._overlay_label = overlay_label
|
||||
|
||||
self._error_detail_dialog = ErrorDetailDialog(self)
|
||||
|
||||
self._user_input_changed_timer = user_input_changed_timer
|
||||
# Store current value on input text change
|
||||
# The value is unset when is passed to controller
|
||||
# The goal is to have controll over changes happened during user change
|
||||
# in UI and controller auto-changes
|
||||
self._variant_input_text = None
|
||||
self._new_folder_name_enabled = None
|
||||
self._new_folder_name_input_text = None
|
||||
self._variant_input_text = None
|
||||
self._comment_input_text = None
|
||||
|
||||
self._first_show = True
|
||||
|
|
@ -235,6 +322,7 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
self._folder_is_valid = None
|
||||
|
||||
publish_btn.setEnabled(False)
|
||||
show_detail_btn.setVisible(False)
|
||||
overlay_close_btn.setVisible(False)
|
||||
overlay_try_btn.setVisible(False)
|
||||
|
||||
|
|
@ -289,6 +377,11 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
|
||||
self.refresh()
|
||||
|
||||
def _on_new_folder_check(self):
|
||||
self._new_folder_name_enabled = self._new_folder_checkbox.isChecked()
|
||||
self._folder_name_input.setEnabled(self._new_folder_name_enabled)
|
||||
self._user_input_changed_timer.start()
|
||||
|
||||
def _on_new_folder_change(self, text):
|
||||
self._new_folder_name_input_text = text
|
||||
self._user_input_changed_timer.start()
|
||||
|
|
@ -302,9 +395,15 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
self._user_input_changed_timer.start()
|
||||
|
||||
def _on_user_input_timer(self):
|
||||
folder_name_enabled = self._new_folder_name_enabled
|
||||
folder_name = self._new_folder_name_input_text
|
||||
if folder_name is not None:
|
||||
if folder_name is not None or folder_name_enabled is not None:
|
||||
self._new_folder_name_input_text = None
|
||||
self._new_folder_name_enabled = None
|
||||
if not self._new_folder_checkbox.isChecked():
|
||||
folder_name = None
|
||||
elif folder_name is None:
|
||||
folder_name = self._folder_name_input.text()
|
||||
self._controller.set_user_value_folder_name(folder_name)
|
||||
|
||||
variant = self._variant_input_text
|
||||
|
|
@ -350,16 +449,13 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
self._header_label.setText(self._controller.get_source_label())
|
||||
|
||||
def _invalidate_new_folder_name(self, folder_name, is_valid):
|
||||
self._tasks_widget.setVisible(not folder_name)
|
||||
self._tasks_widget.setVisible(folder_name is None)
|
||||
if self._folder_is_valid is is_valid:
|
||||
return
|
||||
self._folder_is_valid = is_valid
|
||||
state = ""
|
||||
if folder_name:
|
||||
if is_valid is True:
|
||||
state = "valid"
|
||||
elif is_valid is False:
|
||||
state = "invalid"
|
||||
if folder_name is not None:
|
||||
state = "valid" if is_valid else "invalid"
|
||||
set_style_property(
|
||||
self._folder_name_input, "state", state
|
||||
)
|
||||
|
|
@ -374,6 +470,9 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
def _on_submission_change(self, event):
|
||||
self._publish_btn.setEnabled(event["enabled"])
|
||||
|
||||
def _on_show_detail_click(self):
|
||||
self._error_detail_dialog.show()
|
||||
|
||||
def _on_close_click(self):
|
||||
self.close()
|
||||
|
||||
|
|
@ -384,8 +483,11 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
self._process_item_id = None
|
||||
self._last_submit_message = None
|
||||
|
||||
self._error_detail_dialog.close()
|
||||
|
||||
self._overlay_close_btn.setVisible(False)
|
||||
self._overlay_try_btn.setVisible(False)
|
||||
self._show_detail_btn.setVisible(False)
|
||||
self._main_layout.setCurrentWidget(self._main_context_widget)
|
||||
|
||||
def _on_main_thread_timer(self):
|
||||
|
|
@ -401,13 +503,24 @@ class PushToContextSelectWindow(QtWidgets.QWidget):
|
|||
if self._main_thread_timer_can_stop:
|
||||
self._main_thread_timer.stop()
|
||||
self._overlay_close_btn.setVisible(True)
|
||||
if push_failed and not fail_traceback:
|
||||
if push_failed:
|
||||
self._overlay_try_btn.setVisible(True)
|
||||
if fail_traceback:
|
||||
self._show_detail_btn.setVisible(True)
|
||||
|
||||
if push_failed:
|
||||
message = "Push Failed:\n{}".format(process_status["fail_reason"])
|
||||
reason = process_status["fail_reason"]
|
||||
if fail_traceback:
|
||||
message += "\n{}".format(fail_traceback)
|
||||
message = (
|
||||
"Unhandled error happened."
|
||||
" Check error detail for more information."
|
||||
)
|
||||
self._error_detail_dialog.set_detail(
|
||||
reason, fail_traceback
|
||||
)
|
||||
else:
|
||||
message = f"Push Failed:\n{reason}"
|
||||
|
||||
self._overlay_label.setText(message)
|
||||
set_style_property(self._overlay_close_btn, "state", "error")
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,10 @@ from ayon_core.lib import (
|
|||
)
|
||||
from ayon_core.settings import get_studio_settings
|
||||
from ayon_core.addon import (
|
||||
ITrayAction,
|
||||
ITrayAddon,
|
||||
ITrayService,
|
||||
)
|
||||
from ayon_core.pipeline import install_ayon_plugins
|
||||
from ayon_core.tools.utils import (
|
||||
WrappedCallbackItem,
|
||||
get_ayon_qt_app,
|
||||
|
|
@ -32,6 +33,12 @@ from ayon_core.tools.tray.lib import (
|
|||
remove_tray_server_url,
|
||||
TrayIsRunningError,
|
||||
)
|
||||
from ayon_core.tools.launcher.ui import LauncherWindow
|
||||
from ayon_core.tools.loader.ui import LoaderWindow
|
||||
from ayon_core.tools.console_interpreter.ui import ConsoleInterpreterWindow
|
||||
from ayon_core.tools.publisher.publish_report_viewer import (
|
||||
PublishReportViewerWindow,
|
||||
)
|
||||
|
||||
from .addons_manager import TrayAddonsManager
|
||||
from .host_console_listener import HostListener
|
||||
|
|
@ -82,6 +89,11 @@ class TrayManager:
|
|||
|
||||
self._outdated_dialog = None
|
||||
|
||||
self._launcher_window = None
|
||||
self._browser_window = None
|
||||
self._console_window = ConsoleInterpreterWindow()
|
||||
self._publish_report_viewer_window = PublishReportViewerWindow()
|
||||
|
||||
self._update_check_timer = update_check_timer
|
||||
self._update_check_interval = update_check_interval
|
||||
self._main_thread_timer = main_thread_timer
|
||||
|
|
@ -109,12 +121,15 @@ class TrayManager:
|
|||
@property
|
||||
def doubleclick_callback(self):
|
||||
"""Double-click callback for Tray icon."""
|
||||
return self._addons_manager.get_doubleclick_callback()
|
||||
callback = self._addons_manager.get_doubleclick_callback()
|
||||
if callback is None:
|
||||
callback = self._show_launcher_window
|
||||
return callback
|
||||
|
||||
def execute_doubleclick(self):
|
||||
"""Execute double click callback in main thread."""
|
||||
callback = self.doubleclick_callback
|
||||
if callback:
|
||||
if callback is not None:
|
||||
self.execute_in_main_thread(callback)
|
||||
|
||||
def show_tray_message(self, title, message, icon=None, msecs=None):
|
||||
|
|
@ -144,8 +159,34 @@ class TrayManager:
|
|||
return
|
||||
|
||||
tray_menu = self.tray_widget.menu
|
||||
# Add launcher at first place
|
||||
launcher_action = QtWidgets.QAction(
|
||||
"Launcher", tray_menu
|
||||
)
|
||||
launcher_action.triggered.connect(self._show_launcher_window)
|
||||
tray_menu.addAction(launcher_action)
|
||||
|
||||
console_action = ITrayAddon.add_action_to_admin_submenu(
|
||||
"Console", tray_menu
|
||||
)
|
||||
console_action.triggered.connect(self._show_console_window)
|
||||
|
||||
publish_report_viewer_action = ITrayAddon.add_action_to_admin_submenu(
|
||||
"Publish report viewer", tray_menu
|
||||
)
|
||||
publish_report_viewer_action.triggered.connect(
|
||||
self._show_publish_report_viewer
|
||||
)
|
||||
|
||||
self._addons_manager.initialize(tray_menu)
|
||||
|
||||
# Add browser action after addon actions
|
||||
browser_action = QtWidgets.QAction(
|
||||
"Browser", tray_menu
|
||||
)
|
||||
browser_action.triggered.connect(self._show_browser_window)
|
||||
tray_menu.addAction(browser_action)
|
||||
|
||||
self._addons_manager.add_route(
|
||||
"GET", "/tray", self._web_get_tray_info
|
||||
)
|
||||
|
|
@ -153,7 +194,7 @@ class TrayManager:
|
|||
"POST", "/tray/message", self._web_show_tray_message
|
||||
)
|
||||
|
||||
admin_submenu = ITrayAction.admin_submenu(tray_menu)
|
||||
admin_submenu = ITrayAddon.admin_submenu(tray_menu)
|
||||
tray_menu.addMenu(admin_submenu)
|
||||
|
||||
# Add services if they are
|
||||
|
|
@ -522,6 +563,35 @@ class TrayManager:
|
|||
self._info_widget.raise_()
|
||||
self._info_widget.activateWindow()
|
||||
|
||||
def _show_launcher_window(self):
|
||||
if self._launcher_window is None:
|
||||
self._launcher_window = LauncherWindow()
|
||||
|
||||
self._launcher_window.show()
|
||||
self._launcher_window.raise_()
|
||||
self._launcher_window.activateWindow()
|
||||
|
||||
def _show_browser_window(self):
|
||||
if self._browser_window is None:
|
||||
self._browser_window = LoaderWindow()
|
||||
self._browser_window.setWindowTitle("AYON Browser")
|
||||
install_ayon_plugins()
|
||||
|
||||
self._browser_window.show()
|
||||
self._browser_window.raise_()
|
||||
self._browser_window.activateWindow()
|
||||
|
||||
def _show_console_window(self):
|
||||
self._console_window.show()
|
||||
self._console_window.raise_()
|
||||
self._console_window.activateWindow()
|
||||
|
||||
def _show_publish_report_viewer(self):
|
||||
self._publish_report_viewer_window.refresh()
|
||||
self._publish_report_viewer_window.show()
|
||||
self._publish_report_viewer_window.raise_()
|
||||
self._publish_report_viewer_window.activateWindow()
|
||||
|
||||
|
||||
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
|
||||
"""Tray widget.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue