mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #3 from ynput/enhancement/move-dialogs-from-widgets
Move dialogs from widgets to tools/utils
This commit is contained in:
commit
8745c2c9e5
18 changed files with 324 additions and 474 deletions
|
|
@ -198,13 +198,12 @@ def uninstall():
|
|||
|
||||
|
||||
def show_message(title, message):
|
||||
from ayon_core.widgets.message_window import Window
|
||||
from ayon_core.tools.utils import show_message_dialog
|
||||
from .ops import BlenderApplication
|
||||
|
||||
BlenderApplication.get_app()
|
||||
|
||||
Window(
|
||||
parent=None,
|
||||
show_message_dialog(
|
||||
title=title,
|
||||
message=message,
|
||||
level="warning")
|
||||
|
|
|
|||
|
|
@ -165,15 +165,15 @@ def validate_comp_prefs(comp=None, force_repair=False):
|
|||
return
|
||||
|
||||
from . import menu
|
||||
from ayon_core.widgets import popup
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
from ayon_core.style import load_stylesheet
|
||||
dialog = popup.Popup(parent=menu.menu)
|
||||
dialog = SimplePopup(parent=menu.menu)
|
||||
dialog.setWindowTitle("Fusion comp has invalid configuration")
|
||||
|
||||
msg = "Comp preferences mismatches '{}'".format(asset_doc["name"])
|
||||
msg += "\n" + "\n".join(invalid)
|
||||
dialog.setMessage(msg)
|
||||
dialog.setButtonText("Repair")
|
||||
dialog.set_message(msg)
|
||||
dialog.set_button_text("Repair")
|
||||
dialog.on_clicked.connect(_on_repair)
|
||||
dialog.show()
|
||||
dialog.raise_()
|
||||
|
|
|
|||
|
|
@ -189,11 +189,11 @@ def on_after_open(event):
|
|||
frame.ActivateFrame() # raise comp window
|
||||
host_tools.show_scene_inventory()
|
||||
|
||||
from ayon_core.widgets import popup
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
from ayon_core.style import load_stylesheet
|
||||
dialog = popup.Popup(parent=menu.menu)
|
||||
dialog = SimplePopup(parent=menu.menu)
|
||||
dialog.setWindowTitle("Fusion comp has outdated content")
|
||||
dialog.setMessage("There are outdated containers in "
|
||||
dialog.set_message("There are outdated containers in "
|
||||
"your Fusion comp.")
|
||||
dialog.on_clicked.connect(_on_show_scene_inventory)
|
||||
dialog.show()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import errno
|
|||
import re
|
||||
import uuid
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
import json
|
||||
|
||||
import six
|
||||
|
|
@ -24,7 +23,7 @@ from ayon_core.pipeline import (
|
|||
from ayon_core.pipeline.create import CreateContext
|
||||
from ayon_core.pipeline.template_data import get_template_data
|
||||
from ayon_core.pipeline.context_tools import get_current_project_asset
|
||||
from ayon_core.widgets import popup
|
||||
from ayon_core.tools.utils import PopupUpdateKeys, SimplePopup
|
||||
from ayon_core.tools.utils.host_tools import get_tool_by_name
|
||||
|
||||
import hou
|
||||
|
|
@ -209,12 +208,12 @@ def validate_fps():
|
|||
if parent is None:
|
||||
pass
|
||||
else:
|
||||
dialog = popup.PopupUpdateKeys(parent=parent)
|
||||
dialog = PopupUpdateKeys(parent=parent)
|
||||
dialog.setModal(True)
|
||||
dialog.setWindowTitle("Houdini scene does not match project FPS")
|
||||
dialog.setMessage("Scene %i FPS does not match project %i FPS" %
|
||||
dialog.set_message("Scene %i FPS does not match project %i FPS" %
|
||||
(current_fps, fps))
|
||||
dialog.setButtonText("Fix")
|
||||
dialog.set_button_text("Fix")
|
||||
|
||||
# on_show is the Fix button clicked callback
|
||||
dialog.on_clicked_state.connect(lambda: set_scene_fps(fps))
|
||||
|
|
@ -950,11 +949,11 @@ def update_houdini_vars_context_dialog():
|
|||
|
||||
# TODO: Use better UI!
|
||||
parent = hou.ui.mainQtWindow()
|
||||
dialog = popup.Popup(parent=parent)
|
||||
dialog = SimplePopup(parent=parent)
|
||||
dialog.setModal(True)
|
||||
dialog.setWindowTitle("Houdini scene has outdated asset variables")
|
||||
dialog.setMessage(message)
|
||||
dialog.setButtonText("Fix")
|
||||
dialog.set_message(message)
|
||||
dialog.set_button_text("Fix")
|
||||
|
||||
# on_show is the Fix button clicked callback
|
||||
dialog.on_clicked.connect(update_houdini_vars_context)
|
||||
|
|
|
|||
|
|
@ -305,20 +305,21 @@ def _show_outdated_content_popup():
|
|||
if parent is None:
|
||||
log.info("Skipping outdated content pop-up "
|
||||
"because Houdini window can't be found.")
|
||||
else:
|
||||
from ayon_core.widgets import popup
|
||||
return
|
||||
|
||||
# Show outdated pop-up
|
||||
def _on_show_inventory():
|
||||
from ayon_core.tools.utils import host_tools
|
||||
host_tools.show_scene_inventory(parent=parent)
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
|
||||
dialog = popup.Popup(parent=parent)
|
||||
dialog.setWindowTitle("Houdini scene has outdated content")
|
||||
dialog.setMessage("There are outdated containers in "
|
||||
"your Houdini scene.")
|
||||
dialog.on_clicked.connect(_on_show_inventory)
|
||||
dialog.show()
|
||||
# Show outdated pop-up
|
||||
def _on_show_inventory():
|
||||
from ayon_core.tools.utils import host_tools
|
||||
host_tools.show_scene_inventory(parent=parent)
|
||||
|
||||
dialog = SimplePopup(parent=parent)
|
||||
dialog.setWindowTitle("Houdini scene has outdated content")
|
||||
dialog.set_message("There are outdated containers in "
|
||||
"your Houdini scene.")
|
||||
dialog.on_clicked.connect(_on_show_inventory)
|
||||
dialog.show()
|
||||
|
||||
|
||||
def on_open():
|
||||
|
|
|
|||
|
|
@ -405,12 +405,12 @@ def check_colorspace():
|
|||
project_name, "max", project_settings)
|
||||
if max_config_data and color_mgr.Mode != rt.Name("OCIO_Custom"):
|
||||
if not is_headless():
|
||||
from ayon_core.widgets import popup
|
||||
dialog = popup.Popup(parent=parent)
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
dialog = SimplePopup(parent=parent)
|
||||
dialog.setWindowTitle("Warning: Wrong OCIO Mode")
|
||||
dialog.setMessage("This scene has wrong OCIO "
|
||||
dialog.set_message("This scene has wrong OCIO "
|
||||
"Mode setting.")
|
||||
dialog.setButtonText("Fix")
|
||||
dialog.set_button_text("Fix")
|
||||
dialog.setStyleSheet(load_stylesheet())
|
||||
dialog.on_clicked.connect(reset_colorspace)
|
||||
dialog.show()
|
||||
|
|
|
|||
|
|
@ -2667,31 +2667,29 @@ def validate_fps():
|
|||
"""
|
||||
|
||||
expected_fps = get_fps_for_current_context()
|
||||
current_fps = mel.eval('currentTimeUnitToFPS()')
|
||||
current_fps = mel.eval("currentTimeUnitToFPS()")
|
||||
|
||||
fps_match = current_fps == expected_fps
|
||||
if not fps_match and not IS_HEADLESS:
|
||||
from ayon_core.widgets import popup
|
||||
from ayon_core.tools.utils import PopupUpdateKeys
|
||||
|
||||
parent = get_main_window()
|
||||
|
||||
dialog = popup.PopupUpdateKeys(parent=parent)
|
||||
dialog = PopupUpdateKeys(parent=parent)
|
||||
dialog.setModal(True)
|
||||
dialog.setWindowTitle("Maya scene does not match project FPS")
|
||||
dialog.setMessage(
|
||||
dialog.set_message(
|
||||
"Scene {} FPS does not match project {} FPS".format(
|
||||
current_fps, expected_fps
|
||||
)
|
||||
)
|
||||
dialog.setButtonText("Fix")
|
||||
dialog.set_button_text("Fix")
|
||||
|
||||
# Set new text for button (add optional argument for the popup?)
|
||||
toggle = dialog.widgets["toggle"]
|
||||
update = toggle.isChecked()
|
||||
dialog.on_clicked_state.connect(
|
||||
lambda: set_scene_fps(expected_fps, update)
|
||||
)
|
||||
def on_click(update):
|
||||
set_scene_fps(expected_fps, update)
|
||||
|
||||
dialog.on_clicked_state.connect(on_click)
|
||||
dialog.show()
|
||||
|
||||
return False
|
||||
|
|
@ -3284,17 +3282,15 @@ def update_content_on_context_change():
|
|||
|
||||
def show_message(title, msg):
|
||||
from qtpy import QtWidgets
|
||||
from ayon_core.widgets import message_window
|
||||
from ayon_core.tools.utils import show_message_dialog
|
||||
|
||||
# Find maya main window
|
||||
top_level_widgets = {w.objectName(): w for w in
|
||||
QtWidgets.QApplication.topLevelWidgets()}
|
||||
|
||||
parent = top_level_widgets.get("MayaWindow", None)
|
||||
if parent is None:
|
||||
pass
|
||||
else:
|
||||
message_window.message(title=title, message=msg, parent=parent)
|
||||
if parent is not None:
|
||||
show_message_dialog(title=title, message=msg, parent=parent)
|
||||
|
||||
|
||||
def iter_shader_edits(relationships, shader_nodes, nodes_by_id, label=None):
|
||||
|
|
|
|||
|
|
@ -583,7 +583,7 @@ def on_save():
|
|||
def on_open():
|
||||
"""On scene open let's assume the containers have changed."""
|
||||
|
||||
from ayon_core.widgets import popup
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
|
||||
# Validate FPS after update_task_from_path to
|
||||
# ensure it is using correct FPS for the asset
|
||||
|
|
@ -604,9 +604,9 @@ def on_open():
|
|||
def _on_show_inventory():
|
||||
host_tools.show_scene_inventory(parent=parent)
|
||||
|
||||
dialog = popup.Popup(parent=parent)
|
||||
dialog = SimplePopup(parent=parent)
|
||||
dialog.setWindowTitle("Maya scene has outdated content")
|
||||
dialog.setMessage("There are outdated containers in "
|
||||
dialog.set_message("There are outdated containers in "
|
||||
"your Maya scene.")
|
||||
dialog.on_clicked.connect(_on_show_inventory)
|
||||
dialog.show()
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ from ayon_core.pipeline import (
|
|||
)
|
||||
import ayon_core.hosts.maya.api.plugin
|
||||
from ayon_core.hosts.maya.api import lib
|
||||
from ayon_core.widgets.message_window import ScrollMessageBox
|
||||
|
||||
from ayon_core.hosts.maya.api.lib import get_reference_node
|
||||
|
||||
from ayon_core.tools.utils import ScrollMessageBox
|
||||
|
||||
|
||||
class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader):
|
||||
"""Specific loader for lookdev"""
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ def on_open():
|
|||
log.info("Running callback on open..")
|
||||
|
||||
if any_outdated_containers():
|
||||
from ayon_core.widgets import popup
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
|
||||
log.warning("Scene has outdated content.")
|
||||
|
||||
|
|
@ -301,9 +301,9 @@ def on_open():
|
|||
from ayon_core.tools.utils import host_tools
|
||||
host_tools.show_scene_inventory(parent=parent)
|
||||
|
||||
dialog = popup.Popup(parent=parent)
|
||||
dialog = SimplePopup(parent=parent)
|
||||
dialog.setWindowTitle("Substance scene has outdated content")
|
||||
dialog.setMessage("There are outdated containers in "
|
||||
dialog.set_message("There are outdated containers in "
|
||||
"your Substance scene.")
|
||||
dialog.on_clicked.connect(_on_show_inventory)
|
||||
dialog.show()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class UnrealAddon(OpenPypeModule, IHostAddon):
|
|||
|
||||
from .lib import get_compatible_integration
|
||||
|
||||
from ayon_core.widgets.message_window import Window
|
||||
from ayon_core.tools.utils import show_message_dialog
|
||||
|
||||
pattern = re.compile(r'^\d+-\d+$')
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ class UnrealAddon(OpenPypeModule, IHostAddon):
|
|||
"Unreal application key in the settings must be in format"
|
||||
"'5-0' or '5-1'"
|
||||
)
|
||||
Window(
|
||||
show_message_dialog(
|
||||
parent=None,
|
||||
title="Unreal application name format",
|
||||
message=msg,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import unreal
|
|||
from ayon_core.settings import get_project_settings
|
||||
from ayon_core.pipeline import Anatomy
|
||||
from ayon_core.hosts.unreal.api import pipeline
|
||||
from ayon_core.widgets.message_window import Window
|
||||
from ayon_core.tools.utils import show_message_dialog
|
||||
|
||||
|
||||
queue = None
|
||||
|
|
@ -40,8 +40,7 @@ def start_rendering():
|
|||
assets = unreal.EditorUtilityLibrary.get_selected_assets()
|
||||
|
||||
if not assets:
|
||||
Window(
|
||||
parent=None,
|
||||
show_message_dialog(
|
||||
title="No assets selected",
|
||||
message="No assets selected. Select a render instance.",
|
||||
level="warning")
|
||||
|
|
|
|||
|
|
@ -34,21 +34,21 @@ class DeleteUnusedAssets(InventoryAction):
|
|||
|
||||
def _show_confirmation_dialog(self, containers):
|
||||
from qtpy import QtCore
|
||||
from ayon_core.widgets import popup
|
||||
from ayon_core.tools.utils import SimplePopup
|
||||
from ayon_core.style import load_stylesheet
|
||||
|
||||
dialog = popup.Popup()
|
||||
dialog = SimplePopup()
|
||||
dialog.setWindowFlags(
|
||||
QtCore.Qt.Window
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
dialog.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
dialog.setWindowTitle("Delete all unused assets")
|
||||
dialog.setMessage(
|
||||
dialog.set_message(
|
||||
"You are about to delete all the assets in the project that \n"
|
||||
"are not used in any level. Are you sure you want to continue?"
|
||||
)
|
||||
dialog.setButtonText("Delete")
|
||||
dialog.set_button_text("Delete")
|
||||
|
||||
dialog.on_clicked.connect(
|
||||
lambda: self._delete_unused_assets(containers)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ from .multiselection_combobox import MultiSelectionComboBox
|
|||
from .thumbnail_paint_widget import ThumbnailPainterWidget
|
||||
from .sliders import NiceSlider
|
||||
from .nice_checkbox import NiceCheckbox
|
||||
from .dialogs import (
|
||||
show_message_dialog,
|
||||
ScrollMessageBox,
|
||||
SimplePopup,
|
||||
PopupUpdateKeys,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -110,4 +116,9 @@ __all__ = (
|
|||
"NiceSlider",
|
||||
|
||||
"NiceCheckbox",
|
||||
|
||||
"show_message_dialog",
|
||||
"ScrollMessageBox",
|
||||
"SimplePopup",
|
||||
"PopupUpdateKeys",
|
||||
)
|
||||
|
|
|
|||
253
client/ayon_core/tools/utils/dialogs.py
Normal file
253
client/ayon_core/tools/utils/dialogs.py
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
import logging
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def show_message_dialog(title, message, level=None, parent=None):
|
||||
"""
|
||||
|
||||
Args:
|
||||
title (str): Title of dialog.
|
||||
message (str): Message to display.
|
||||
level (Literal["info", "warning", "critical"]): Level of dialog.
|
||||
parent (Optional[QtCore.QObject]): Parent widget.
|
||||
|
||||
"""
|
||||
if level is None:
|
||||
level = "info"
|
||||
|
||||
if level == "info":
|
||||
function = QtWidgets.QMessageBox.information
|
||||
elif level == "warning":
|
||||
function = QtWidgets.QMessageBox.warning
|
||||
elif level == "critical":
|
||||
function = QtWidgets.QMessageBox.critical
|
||||
else:
|
||||
raise ValueError(f"Invalid level: {level}")
|
||||
function(parent, title, message)
|
||||
|
||||
|
||||
class ScrollMessageBox(QtWidgets.QDialog):
|
||||
"""Basic version of scrollable QMessageBox.
|
||||
|
||||
No other existing dialog implementation is scrollable.
|
||||
|
||||
Args:
|
||||
icon (QtWidgets.QMessageBox.Icon): Icon to display.
|
||||
title (str): Window title.
|
||||
messages (list[str]): List of messages.
|
||||
cancelable (Optional[bool]): True if Cancel button should be added.
|
||||
|
||||
"""
|
||||
def __init__(self, icon, title, messages, cancelable=False):
|
||||
super(ScrollMessageBox, self).__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.icon = icon
|
||||
|
||||
self._messages = messages
|
||||
|
||||
self.setWindowFlags(QtCore.Qt.WindowTitleHint)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
scroll_widget = QtWidgets.QScrollArea(self)
|
||||
scroll_widget.setWidgetResizable(True)
|
||||
content_widget = QtWidgets.QWidget(self)
|
||||
scroll_widget.setWidget(content_widget)
|
||||
|
||||
message_len = 0
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
for message in messages:
|
||||
label_widget = QtWidgets.QLabel(message, content_widget)
|
||||
content_layout.addWidget(label_widget)
|
||||
message_len = max(message_len, len(message))
|
||||
|
||||
# guess size of scrollable area
|
||||
# WARNING: 'desktop' method probably won't work in PySide6
|
||||
desktop = QtWidgets.QApplication.desktop()
|
||||
max_width = desktop.availableGeometry().width()
|
||||
scroll_widget.setMinimumWidth(
|
||||
min(max_width, message_len * 6)
|
||||
)
|
||||
layout.addWidget(scroll_widget)
|
||||
|
||||
buttons = QtWidgets.QDialogButtonBox.Ok
|
||||
if cancelable:
|
||||
buttons |= QtWidgets.QDialogButtonBox.Cancel
|
||||
|
||||
btn_box = QtWidgets.QDialogButtonBox(buttons)
|
||||
btn_box.accepted.connect(self.accept)
|
||||
|
||||
if cancelable:
|
||||
btn_box.reject.connect(self.reject)
|
||||
|
||||
btn = QtWidgets.QPushButton("Copy to clipboard")
|
||||
btn.clicked.connect(self._on_copy_click)
|
||||
btn_box.addButton(btn, QtWidgets.QDialogButtonBox.NoRole)
|
||||
|
||||
layout.addWidget(btn_box)
|
||||
|
||||
def _on_copy_click(self):
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
clipboard.setText("\n".join(self._messages))
|
||||
|
||||
|
||||
class SimplePopup(QtWidgets.QDialog):
|
||||
"""A Popup that moves itself to bottom right of screen on show event.
|
||||
|
||||
The UI contains a message label and a red highlighted button to "show"
|
||||
or perform another custom action from this pop-up.
|
||||
|
||||
"""
|
||||
|
||||
on_clicked = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None, *args, **kwargs):
|
||||
super(SimplePopup, self).__init__(parent=parent, *args, **kwargs)
|
||||
|
||||
# Set default title
|
||||
self.setWindowTitle("Popup")
|
||||
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
message_label = QtWidgets.QLabel("", self)
|
||||
message_label.setStyleSheet("""
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
}
|
||||
""")
|
||||
confirm_btn = QtWidgets.QPushButton("Show", self)
|
||||
confirm_btn.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.Maximum,
|
||||
QtWidgets.QSizePolicy.Maximum
|
||||
)
|
||||
confirm_btn.setStyleSheet(
|
||||
"""QPushButton { background-color: #BB0000 }"""
|
||||
)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 5, 10, 10)
|
||||
|
||||
# Increase spacing slightly for readability
|
||||
layout.setSpacing(10)
|
||||
layout.addWidget(message_label)
|
||||
layout.addWidget(confirm_btn)
|
||||
|
||||
# Signals
|
||||
confirm_btn.clicked.connect(self._on_clicked)
|
||||
|
||||
# Default size
|
||||
self.resize(400, 40)
|
||||
|
||||
self._message_label = message_label
|
||||
self._confirm_btn = confirm_btn
|
||||
|
||||
def set_message(self, message):
|
||||
self._message_label.setText(message)
|
||||
|
||||
def set_button_text(self, text):
|
||||
self._confirm_btn.setText(text)
|
||||
|
||||
def setMessage(self, message):
|
||||
self.set_message(message)
|
||||
|
||||
def setButtonText(self, text):
|
||||
self.set_button_text(text)
|
||||
|
||||
def showEvent(self, event):
|
||||
# Position popup based on contents on show event
|
||||
geo = self._calculate_window_geometry()
|
||||
self.setGeometry(geo)
|
||||
|
||||
return super(SimplePopup, self).showEvent(event)
|
||||
|
||||
def _on_clicked(self):
|
||||
"""Callback for when the 'show' button is clicked.
|
||||
|
||||
Raises the parent (if any)
|
||||
|
||||
"""
|
||||
|
||||
parent = self.parent()
|
||||
self.close()
|
||||
|
||||
# Trigger the signal
|
||||
self.on_clicked.emit()
|
||||
|
||||
if parent:
|
||||
parent.raise_()
|
||||
|
||||
def _calculate_window_geometry(self):
|
||||
"""Respond to status changes
|
||||
|
||||
On creation, align window with screen bottom right.
|
||||
|
||||
"""
|
||||
|
||||
window = self
|
||||
|
||||
width = window.width()
|
||||
width = max(width, window.minimumWidth())
|
||||
|
||||
height = window.height()
|
||||
height = max(height, window.sizeHint().height())
|
||||
|
||||
try:
|
||||
screen = window.screen()
|
||||
desktop_geometry = screen.availableGeometry()
|
||||
except AttributeError:
|
||||
# Backwards compatibility for older Qt versions
|
||||
# PySide6 removed QDesktopWidget
|
||||
desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry()
|
||||
|
||||
window_geometry = window.geometry()
|
||||
|
||||
screen_width = window_geometry.width()
|
||||
screen_height = window_geometry.height()
|
||||
|
||||
# Calculate width and height of system tray
|
||||
systray_width = window_geometry.width() - desktop_geometry.width()
|
||||
systray_height = window_geometry.height() - desktop_geometry.height()
|
||||
|
||||
padding = 10
|
||||
|
||||
x = screen_width - width
|
||||
y = screen_height - height
|
||||
|
||||
x -= systray_width + padding
|
||||
y -= systray_height + padding
|
||||
|
||||
return QtCore.QRect(x, y, width, height)
|
||||
|
||||
|
||||
class PopupUpdateKeys(SimplePopup):
|
||||
"""Simple popup with checkbox."""
|
||||
|
||||
on_clicked_state = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, parent=None, *args, **kwargs):
|
||||
super(PopupUpdateKeys, self).__init__(
|
||||
parent=parent, *args, **kwargs
|
||||
)
|
||||
|
||||
layout = self.layout()
|
||||
|
||||
# Insert toggle for Update keys
|
||||
toggle = QtWidgets.QCheckBox("Update Keys", self)
|
||||
layout.insertWidget(1, toggle)
|
||||
|
||||
self.on_clicked.connect(self.emit_click_with_state)
|
||||
|
||||
layout.insertStretch(1, 1)
|
||||
|
||||
self._toggle_checkbox = toggle
|
||||
|
||||
def is_toggle_checked(self):
|
||||
return self._toggle_checkbox.isChecked()
|
||||
|
||||
def emit_click_with_state(self):
|
||||
"""Emit the on_clicked_state signal with the toggled state"""
|
||||
checked = self._toggle_checkbox.isChecked()
|
||||
self.on_clicked_state.emit(checked)
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
# Widgets
|
||||
|
||||
## Splash Screen
|
||||
|
||||
This widget is used for executing a monitoring progress of a process which has been executed on a different thread.
|
||||
|
||||
To properly use this widget certain preparation has to be done in order to correctly execute the process and show the
|
||||
splash screen.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
In order to run a function or an operation on another thread, a `QtCore.QObject` class needs to be created with the
|
||||
desired code. The class has to have a method as an entry point for the thread to execute the code.
|
||||
|
||||
For utilizing the functionalities of the splash screen, certain signals need to be declared to let it know what is
|
||||
happening in the thread and how is it progressing. It is also recommended to have a function to set up certain variables
|
||||
which are needed in the worker's code
|
||||
|
||||
For example:
|
||||
```python
|
||||
from qtpy import QtCore
|
||||
|
||||
class ExampleWorker(QtCore.QObject):
|
||||
|
||||
finished = QtCore.Signal()
|
||||
failed = QtCore.Signal(str)
|
||||
progress = QtCore.Signal(int)
|
||||
log = QtCore.Signal(str)
|
||||
stage_begin = QtCore.Signal(str)
|
||||
|
||||
foo = None
|
||||
bar = None
|
||||
|
||||
def run(self):
|
||||
# The code goes here
|
||||
print("Hello world!")
|
||||
self.finished.emit()
|
||||
|
||||
def setup(self,
|
||||
foo: str,
|
||||
bar: str,):
|
||||
self.foo = foo
|
||||
self.bar = bar
|
||||
```
|
||||
|
||||
### Creating the splash screen
|
||||
|
||||
```python
|
||||
import os
|
||||
from qtpy import QtCore
|
||||
from pathlib import Path
|
||||
from ayon_core.widgets.splash_screen import SplashScreen
|
||||
from ayon_core import resources
|
||||
|
||||
|
||||
def exec_plugin_install( engine_path: Path, env: dict = None):
|
||||
env = env or os.environ
|
||||
q_thread = QtCore.QThread()
|
||||
example_worker = ExampleWorker()
|
||||
|
||||
q_thread.started.connect(example_worker.run)
|
||||
example_worker.setup(engine_path, env)
|
||||
example_worker.moveToThread(q_thread)
|
||||
|
||||
splash_screen = SplashScreen("Executing process ...",
|
||||
resources.get_ayon_icon_filepath())
|
||||
|
||||
# set up the splash screen with necessary events
|
||||
example_worker.installing.connect(splash_screen.update_top_label_text)
|
||||
example_worker.progress.connect(splash_screen.update_progress)
|
||||
example_worker.log.connect(splash_screen.append_log)
|
||||
example_worker.finished.connect(splash_screen.quit_and_close)
|
||||
example_worker.failed.connect(splash_screen.fail)
|
||||
|
||||
splash_screen.start_thread(q_thread)
|
||||
splash_screen.show_ui()
|
||||
```
|
||||
|
||||
In this example code, before executing the process the worker needs to be instantiated and moved onto a newly created
|
||||
`QtCore.QThread` object. After this, needed signals have to be connected to the desired slots to make full use of
|
||||
the splash screen. Finally, the `start_thread` and `show_ui` is called.
|
||||
|
||||
**Note that when the `show_ui` function is called the thread is blocked until the splash screen quits automatically, or
|
||||
it is closed by the user in case the process fails! The `start_thread` method in that case must be called before
|
||||
showing the UI!**
|
||||
|
||||
The most important signals are
|
||||
```python
|
||||
q_thread.started.connect(example_worker.run)
|
||||
```
|
||||
and
|
||||
```python
|
||||
example_worker.finished.connect(splash_screen.quit_and_close)
|
||||
```
|
||||
|
||||
These ensure that when the `start_thread` method is called (which takes as a parameter the `QtCore.QThread` object and
|
||||
saves it as a reference), the `QThread` object starts and signals the worker to
|
||||
start executing its own code. Once the worker is done and emits a signal that it has finished with the `quit_and_close`
|
||||
slot, the splash screen quits the `QtCore.QThread` and closes itself.
|
||||
|
||||
It is highly recommended to also use the `fail` slot in case an exception or other error occurs during the execution of
|
||||
the worker's code (You would use in this case the `failed` signal in the `ExampleWorker`).
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
import sys
|
||||
import logging
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Window(QtWidgets.QWidget):
|
||||
def __init__(self, parent, title, message, level):
|
||||
super(Window, self).__init__()
|
||||
self.parent = parent
|
||||
self.title = title
|
||||
self.message = message
|
||||
self.level = level
|
||||
|
||||
if self.level == "info":
|
||||
self._info()
|
||||
elif self.level == "warning":
|
||||
self._warning()
|
||||
elif self.level == "critical":
|
||||
self._critical()
|
||||
|
||||
def _info(self):
|
||||
self.setWindowTitle(self.title)
|
||||
rc = QtWidgets.QMessageBox.information(
|
||||
self, self.title, self.message)
|
||||
if rc:
|
||||
self.exit()
|
||||
|
||||
def _warning(self):
|
||||
self.setWindowTitle(self.title)
|
||||
rc = QtWidgets.QMessageBox.warning(
|
||||
self, self.title, self.message)
|
||||
if rc:
|
||||
self.exit()
|
||||
|
||||
def _critical(self):
|
||||
self.setWindowTitle(self.title)
|
||||
rc = QtWidgets.QMessageBox.critical(
|
||||
self, self.title, self.message)
|
||||
if rc:
|
||||
self.exit()
|
||||
|
||||
def exit(self):
|
||||
self.hide()
|
||||
# self.parent.exec_()
|
||||
# self.parent.hide()
|
||||
return
|
||||
|
||||
|
||||
def message(title=None, message=None, level="info", parent=None):
|
||||
"""
|
||||
Produces centered dialog with specific level denoting severity
|
||||
Args:
|
||||
title: (string) dialog title
|
||||
message: (string) message
|
||||
level: (string) info|warning|critical
|
||||
parent: (QtWidgets.QApplication)
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
app = parent
|
||||
if not app:
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
ex = Window(app, title, message, level)
|
||||
ex.show()
|
||||
|
||||
# Move widget to center of screen
|
||||
try:
|
||||
desktop_rect = QtWidgets.QApplication.desktop().availableGeometry(ex)
|
||||
center = desktop_rect.center()
|
||||
ex.move(
|
||||
center.x() - (ex.width() * 0.5),
|
||||
center.y() - (ex.height() * 0.5)
|
||||
)
|
||||
except Exception:
|
||||
# skip all possible issues that may happen feature is not crutial
|
||||
log.warning("Couldn't center message.", exc_info=True)
|
||||
# sys.exit(app.exec_())
|
||||
|
||||
|
||||
class ScrollMessageBox(QtWidgets.QDialog):
|
||||
"""
|
||||
Basic version of scrollable QMessageBox. No other existing dialog
|
||||
implementation is scrollable.
|
||||
Args:
|
||||
icon: <QtWidgets.QMessageBox.Icon>
|
||||
title: <string>
|
||||
messages: <list> of messages
|
||||
cancelable: <boolean> - True if Cancel button should be added
|
||||
"""
|
||||
def __init__(self, icon, title, messages, cancelable=False):
|
||||
super(ScrollMessageBox, self).__init__()
|
||||
self.setWindowTitle(title)
|
||||
self.icon = icon
|
||||
|
||||
self.setWindowFlags(QtCore.Qt.WindowTitleHint)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
scroll_widget = QtWidgets.QScrollArea(self)
|
||||
scroll_widget.setWidgetResizable(True)
|
||||
content_widget = QtWidgets.QWidget(self)
|
||||
scroll_widget.setWidget(content_widget)
|
||||
|
||||
message_len = 0
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
for message in messages:
|
||||
label_widget = QtWidgets.QLabel(message, content_widget)
|
||||
content_layout.addWidget(label_widget)
|
||||
message_len = max(message_len, len(message))
|
||||
|
||||
# guess size of scrollable area
|
||||
desktop = QtWidgets.QApplication.desktop()
|
||||
max_width = desktop.availableGeometry().width()
|
||||
scroll_widget.setMinimumWidth(
|
||||
min(max_width, message_len * 6)
|
||||
)
|
||||
layout.addWidget(scroll_widget)
|
||||
|
||||
if not cancelable: # if no specific buttons OK only
|
||||
buttons = QtWidgets.QDialogButtonBox.Ok
|
||||
else:
|
||||
buttons = QtWidgets.QDialogButtonBox.Ok | \
|
||||
QtWidgets.QDialogButtonBox.Cancel
|
||||
|
||||
btn_box = QtWidgets.QDialogButtonBox(buttons)
|
||||
btn_box.accepted.connect(self.accept)
|
||||
|
||||
if cancelable:
|
||||
btn_box.reject.connect(self.reject)
|
||||
|
||||
btn = QtWidgets.QPushButton('Copy to clipboard')
|
||||
btn.clicked.connect(lambda: QtWidgets.QApplication.
|
||||
clipboard().setText("\n".join(messages)))
|
||||
btn_box.addButton(btn, QtWidgets.QDialogButtonBox.NoRole)
|
||||
|
||||
layout.addWidget(btn_box)
|
||||
self.show()
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
import sys
|
||||
import contextlib
|
||||
|
||||
from qtpy import QtCore, QtWidgets
|
||||
|
||||
|
||||
class Popup(QtWidgets.QDialog):
|
||||
"""A Popup that moves itself to bottom right of screen on show event.
|
||||
|
||||
The UI contains a message label and a red highlighted button to "show"
|
||||
or perform another custom action from this pop-up.
|
||||
|
||||
"""
|
||||
|
||||
on_clicked = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None, *args, **kwargs):
|
||||
super(Popup, self).__init__(parent=parent, *args, **kwargs)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 5, 10, 10)
|
||||
|
||||
# Increase spacing slightly for readability
|
||||
layout.setSpacing(10)
|
||||
|
||||
message = QtWidgets.QLabel("")
|
||||
message.setStyleSheet("""
|
||||
QLabel {
|
||||
font-size: 12px;
|
||||
}
|
||||
""")
|
||||
button = QtWidgets.QPushButton("Show")
|
||||
button.setSizePolicy(QtWidgets.QSizePolicy.Maximum,
|
||||
QtWidgets.QSizePolicy.Maximum)
|
||||
button.setStyleSheet("""QPushButton { background-color: #BB0000 }""")
|
||||
|
||||
layout.addWidget(message)
|
||||
layout.addWidget(button)
|
||||
|
||||
# Default size
|
||||
self.resize(400, 40)
|
||||
|
||||
self.widgets = {
|
||||
"message": message,
|
||||
"button": button,
|
||||
}
|
||||
|
||||
# Signals
|
||||
button.clicked.connect(self._on_clicked)
|
||||
|
||||
# Set default title
|
||||
self.setWindowTitle("Popup")
|
||||
|
||||
def setMessage(self, message):
|
||||
self.widgets['message'].setText(message)
|
||||
|
||||
def setButtonText(self, text):
|
||||
self.widgets["button"].setText(text)
|
||||
|
||||
def _on_clicked(self):
|
||||
"""Callback for when the 'show' button is clicked.
|
||||
|
||||
Raises the parent (if any)
|
||||
|
||||
"""
|
||||
|
||||
parent = self.parent()
|
||||
self.close()
|
||||
|
||||
# Trigger the signal
|
||||
self.on_clicked.emit()
|
||||
|
||||
if parent:
|
||||
parent.raise_()
|
||||
|
||||
def showEvent(self, event):
|
||||
|
||||
# Position popup based on contents on show event
|
||||
geo = self.calculate_window_geometry()
|
||||
self.setGeometry(geo)
|
||||
|
||||
return super(Popup, self).showEvent(event)
|
||||
|
||||
def calculate_window_geometry(self):
|
||||
"""Respond to status changes
|
||||
|
||||
On creation, align window with screen bottom right.
|
||||
|
||||
"""
|
||||
|
||||
window = self
|
||||
|
||||
width = window.width()
|
||||
width = max(width, window.minimumWidth())
|
||||
|
||||
height = window.height()
|
||||
height = max(height, window.sizeHint().height())
|
||||
|
||||
try:
|
||||
screen = window.screen()
|
||||
desktop_geometry = screen.availableGeometry()
|
||||
except AttributeError:
|
||||
# Backwards compatibility for older Qt versions
|
||||
# PySide6 removed QDesktopWidget
|
||||
desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry()
|
||||
|
||||
window_geometry = window.geometry()
|
||||
|
||||
screen_width = window_geometry.width()
|
||||
screen_height = window_geometry.height()
|
||||
|
||||
# Calculate width and height of system tray
|
||||
systray_width = window_geometry.width() - desktop_geometry.width()
|
||||
systray_height = window_geometry.height() - desktop_geometry.height()
|
||||
|
||||
padding = 10
|
||||
|
||||
x = screen_width - width
|
||||
y = screen_height - height
|
||||
|
||||
x -= systray_width + padding
|
||||
y -= systray_height + padding
|
||||
|
||||
return QtCore.QRect(x, y, width, height)
|
||||
|
||||
|
||||
class PopupUpdateKeys(Popup):
|
||||
"""Popup with Update Keys checkbox (intended for Maya)"""
|
||||
|
||||
on_clicked_state = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, parent=None, *args, **kwargs):
|
||||
Popup.__init__(self, parent=parent, *args, **kwargs)
|
||||
|
||||
layout = self.layout()
|
||||
|
||||
# Insert toggle for Update keys
|
||||
toggle = QtWidgets.QCheckBox("Update Keys")
|
||||
layout.insertWidget(1, toggle)
|
||||
self.widgets["toggle"] = toggle
|
||||
|
||||
self.on_clicked.connect(self.emit_click_with_state)
|
||||
|
||||
layout.insertStretch(1, 1)
|
||||
|
||||
def emit_click_with_state(self):
|
||||
"""Emit the on_clicked_state signal with the toggled state"""
|
||||
checked = self.widgets["toggle"].isChecked()
|
||||
self.on_clicked_state.emit(checked)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def application():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
yield
|
||||
app.exec_()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with application():
|
||||
dialog = Popup()
|
||||
dialog.setMessage("There are outdated containers in your Maya scene.")
|
||||
dialog.show()
|
||||
Loading…
Add table
Add a link
Reference in a new issue