Merge pull request #3 from ynput/enhancement/move-dialogs-from-widgets

Move dialogs from widgets to tools/utils
This commit is contained in:
Jakub Trllo 2024-02-08 15:37:39 +01:00 committed by GitHub
commit 8745c2c9e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 324 additions and 474 deletions

View file

@ -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")

View file

@ -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_()

View file

@ -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()

View file

@ -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)

View file

@ -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():

View file

@ -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()

View file

@ -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):

View file

@ -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()

View file

@ -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"""

View file

@ -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()

View file

@ -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,

View file

@ -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")

View file

@ -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)

View file

@ -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",
)

View 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)

View file

@ -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`).

View file

@ -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()

View file

@ -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()