Source code for overlay_widget.shotgun_overlay_widget
# Copyright (c) 2018 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
import os
from tank.platform.qt import QtCore, QtGui
# load resources
from .ui import resources_rc # noqa
from .shotgun_spinning_widget import ShotgunSpinningWidget
[docs]class ShotgunOverlayWidget(QtGui.QLabel):
"""
Overlay widget that can be placed on top over any QT widget.
Once you have placed the overlay widget, you can use it to
display information, errors, a spinner etc.
The :meth:`show_message` and :meth:`show_error_message` accept
both regular text and HTML to format the error message.
Constants ``INFO_COLOR`` and ``ERROR_COLOR`` are provided on the class
as shorthand for the colors employed by the :meth:`show_message` and
:meth:`show_error_message` methods.
"""
MODE_OFF = 0
MODE_SPIN = 1
MODE_ERROR = 2
MODE_INFO_TEXT = 3
MODE_INFO_PIXMAP = 4
MODE_PROGRESS = 5
ERROR_COLOR = "#C8534A;"
INFO_COLOR = "#888888;"
def __init__(self, parent):
"""
:param parent: Widget to attach the overlay to
:type parent: :class:`PySide.QtGui.QWidget`
"""
QtGui.QLabel.__init__(self, parent)
# hook up a listener to the parent window so we
# can resize the overlay at the same time as the parent window
# is being resized.
filter = ResizeEventFilter(parent)
filter.resized.connect(self._on_parent_resized)
parent.installEventFilter(filter)
self._shotgun_spinning_widget = ShotgunSpinningWidget(self)
# We want text to be centered and wrapping words.
self.setAlignment(
QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter | QtCore.Qt.TextWordWrap
)
self.setWordWrap(True)
# Allow to open hyperlinks
self.setOpenExternalLinks(True)
with open(os.path.join(os.path.dirname(__file__), "style.qss"), "r") as fh:
style = fh.read()
# Dark gray background.
self.setStyleSheet(style)
# turn off the widget by default.
self.hide()
############################################################################################
# public interface
[docs] def start_spin(self):
"""
Enables the overlay and shows an animated spinner.
If you want to stop the spinning, call :meth:`hide`.
"""
self._set_mode(self.MODE_SPIN)
[docs] def show_error_message(self, msg):
"""
Enables the overlay and displays an
a error message centered in the middle of the overlay.
:param msg: Message to display
"""
self._set_mode(self.MODE_ERROR, msg)
[docs] def show_message(self, msg):
"""
Display a message centered on the overlay.
If an error is already being displayed by the overlay at this point,
nothing will happen.
:param msg: Message to display
:returns: True if message was displayed, False otherwise
"""
if self._mode == self.MODE_ERROR:
return False
else:
self._set_mode(self.MODE_INFO_TEXT, msg)
return True
def _set_mode(self, mode, payload=None):
"""
Handles the state of the widget. It will set or reset text/pixmap/spinner
depending on the state we're moving to.
:param mode: Mode we're switching to.
:param payload: Can be a string or a QtGui.QPixmap, that needs to be set
on the widget.
"""
# Decide if we need to show the spinning cursor or not.
if mode == self.MODE_SPIN:
self._shotgun_spinning_widget.start_spin()
else:
self._shotgun_spinning_widget.hide()
# Decide if we need to show the pixmap or not.
if mode == self.MODE_INFO_PIXMAP:
self.setPixmap(payload)
else:
self.setPixmap(None)
# Decide which kind of string we need to show.
if mode == self.MODE_ERROR:
self.setText(
"<font style='color: %s'>%s</font>"
% (self.ERROR_COLOR, payload.replace("\n", "<br>"))
)
elif mode == self.MODE_INFO_TEXT:
self.setText(
"<font style='color: #%s;'>%s</font>"
% (self.INFO_COLOR, payload.replace("\n", "<br>"))
)
else:
self.setText("")
# If the widget is not off, make it visible
if mode == self.MODE_OFF:
self.setVisible(False)
else:
self.setVisible(True)
self._mode = mode
[docs] def show_message_pixmap(self, pixmap):
"""
Show an info message in the form of a pixmap.
If an error is already being displayed by the overlay,
the pixmap will not be shown.
:param pixamp: Image to display
:type pixmap: :class:`PySide.QtGui.QPixmap`
:returns: True if pixmap was displayed, False otherwise
"""
if self._mode == self.MODE_ERROR:
return False
else:
self._set_mode(self.MODE_INFO_PIXMAP, pixmap)
return True
[docs] def hide(self, hide_errors=True):
"""
Hides the overlay.
:param hide_errors: If set to False, errors are not hidden.
"""
if hide_errors is False and self._mode == self.MODE_ERROR:
# an error is displayed - leave it up.
return
self._shotgun_spinning_widget.hide()
self._mode = self.MODE_OFF
self.setVisible(False)
############################################################################################
# internal methods
def _on_parent_resized(self):
"""
Special slot hooked up to the event filter.
When associated widget is resized this slot is being called.
"""
# resize overlay
self.resize(self.parentWidget().size())
self._shotgun_spinning_widget.resize(self.parentWidget().size())
class ResizeEventFilter(QtCore.QObject):
"""
Event filter which emits a resized signal whenever
the monitored widget resizes. This is so that the overlay wrapper
class can be informed whenever the Widget gets a resize event.
"""
resized = QtCore.Signal()
def eventFilter(self, obj, event):
# peek at the message
if event.type() == QtCore.QEvent.Resize:
# re-broadcast any resize events
self.resized.emit()
# pass it on!
return False