diff --git a/docs/platform.rst b/docs/platform.rst
index 863db05eb5..329cc3d50b 100644
--- a/docs/platform.rst
+++ b/docs/platform.rst
@@ -861,7 +861,7 @@ portable between the two systems and it should be no problem writing code that w
In order to use QT, import it from Sgtk::
- from sgtk.platform.qt import QtCore, QtGui
+ from sgtk.platform.qt6 import QtCore, QtGui
Toolkit will make sure Qt is sourced in the correct way. Keep in mind that many applications (for example Nuke)
may not have a functional Qt that can be imported when they run in batch mode.
@@ -892,14 +892,14 @@ you from managing this by yourself, but for maximum compatibility and portabilty
et Toolkit handle it. When using Sgtk to set up your UI, just let your UI class derive from QtGui.QWidget and pass
it to one of the UI factory methods that the engine has. For example::
- from sgtk.platform.qt import QtCore, QtGui
+ from sgtk.platform.qt6 import QtWidgets
- # derive from QtGui.QWidget for your UI components.
+ # derive from QtWidgets.QWidget for your UI components.
- class AppDialog(QtGui.QWidget):
+ class AppDialog(QtWidgets.QWidget):
def __init__(self, param1, param2):
- QtGui.QWidget.__init__(self)
+ QtWidgets.QWidget.__init__(self)
# the engine is then used to correctly launch this dialog. In your app code
# you can now do create a window using the engine's factory methods.
@@ -924,12 +924,12 @@ property called ``exit_code``. Typically, your code for a modal dialog would loo
def on_ok_button_clicked(self):
# user clicked ok
- self.exit_code = QtGui.QDialog.Accepted
+ self.exit_code = QtWidgets.QDialog.Accepted
self.close()
def on_cancel_button_clicked(self):
# user clicked cancel
- self.exit_code = QtGui.QDialog.Rejected
+ self.exit_code = QtWidgets.QDialog.Rejected
self.close()
The call to self.engine.show_modal() will return the appropriate status code depending on which button was clicked.
diff --git a/python/tank/authentication/interactive_authentication.py b/python/tank/authentication/interactive_authentication.py
index 3e1df9f8b4..a7627acbba 100644
--- a/python/tank/authentication/interactive_authentication.py
+++ b/python/tank/authentication/interactive_authentication.py
@@ -42,9 +42,9 @@
# in the context of a DCC, but occur too early for the Toolkit logging to be
# fully in place to record it.
try:
- from .ui.qt_abstraction import QtGui
+ from .ui.qt_abstraction import QtWidgets
except Exception:
- QtGui = None
+ QtWidgets = None
logger = LogManager.get_logger(__name__)
@@ -77,7 +77,7 @@ def _get_ui_state():
Returns the state of UI: do we have a ui or not.
:returns: True or False)
"""
- if QtGui and QtGui.QApplication.instance() is not None:
+ if QtWidgets and QtWidgets.QApplication.instance() is not None:
return True
else:
return False
diff --git a/python/tank/authentication/invoker.py b/python/tank/authentication/invoker.py
index 09b2f85323..d2f21873fc 100644
--- a/python/tank/authentication/invoker.py
+++ b/python/tank/authentication/invoker.py
@@ -29,9 +29,9 @@
# in the context of a DCC, but occur too early for the Toolkit logging to be
# fully in place to record it.
try:
- from .ui.qt_abstraction import QtCore, QtGui
+ from .ui.qt_abstraction import QtCore, QtWidgets
except Exception:
- QtCore, QtGui = None, None
+ QtCore, QtWidgets = None, None
def create():
@@ -44,7 +44,7 @@ def create():
def show_ui():
# show QT dialog
dlg = MyQtDialog()
- result = dlg.exec_()
+ result = dlg.exec()
return result
# create invoker object
@@ -59,7 +59,7 @@ def show_ui():
thread will be produced.
"""
# If we are already in the main thread, no need for an invoker, invoke directly in this thread.
- if QtCore.QThread.currentThread() == QtGui.QApplication.instance().thread():
+ if QtCore.QThread.currentThread() == QtWidgets.QApplication.instance().thread():
return lambda fn, *args, **kwargs: fn(*args, **kwargs)
class MainThreadInvoker(QtCore.QObject):
@@ -80,7 +80,7 @@ def __init__(self):
self._res = None
self._exception = None
# Make sure that the invoker is bound to the main thread
- self.moveToThread(QtGui.QApplication.instance().thread())
+ self.moveToThread(QtWidgets.QApplication.instance().thread())
def __call__(self, fn, *args, **kwargs):
"""
diff --git a/python/tank/authentication/login_dialog.py b/python/tank/authentication/login_dialog.py
index fd24529620..3540241608 100644
--- a/python/tank/authentication/login_dialog.py
+++ b/python/tank/authentication/login_dialog.py
@@ -37,6 +37,8 @@
QtCore,
QtNetwork,
QtWebEngineWidgets,
+ QtWidgets,
+ QtWebEngineCore,
qt_version_tuple,
)
from . import app_session_launcher
@@ -114,7 +116,8 @@ def run(self):
"""
self._site_info.reload(self._url_to_test, self._http_proxy)
-class LoginDialog(QtGui.QDialog):
+
+class LoginDialog(QtWidgets.QDialog):
"""
Dialog for getting user credentials.
"""
@@ -144,13 +147,15 @@ def __init__(
:param parent: The Qt parent for the dialog (defaults to None)
:param session_metadata: Metadata used in the context of SSO. This is an obscure blob of data.
"""
- QtGui.QDialog.__init__(self, parent)
+ super().__init__(parent)
qt_modules = {
"QtCore": QtCore,
"QtGui": QtGui,
"QtNetwork": QtNetwork,
+ "QtWidgets": QtWidgets,
"QtWebEngineWidgets": QtWebEngineWidgets,
+ "QtWebEngineCore": QtWebEngineCore,
}
try:
self._sso_saml2 = SsoSaml2Toolkit(
@@ -245,7 +250,7 @@ def __init__(
self.ui.stackedWidget.setCurrentWidget(self.ui.login_page)
# Initialize Options menu
- menu = QtGui.QMenu(self.ui.button_options)
+ menu = QtWidgets.QMenu(self.ui.button_options)
self.ui.button_options.setMenu(menu)
self.ui.button_options.setVisible(False)
@@ -321,11 +326,11 @@ def __init__(
)
# Initialize exit confirm message box
- self.confirm_box = QtGui.QMessageBox(
- QtGui.QMessageBox.Question,
+ self.confirm_box = QtWidgets.QMessageBox(
+ QtWidgets.QMessageBox.Question,
"Flow Production Tracking Login", # title
"Would you like to cancel your request?", # text
- buttons=QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
+ buttons=QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
# parent=self,
# Passing the parent parameter here, in the constructor, makes
# Nuke versions<=13 crash.
@@ -353,7 +358,7 @@ def __del__(self):
self._query_task.wait()
def _confirm_exit(self):
- return self.confirm_box.exec_() == QtGui.QMessageBox.StandardButton.Yes
+ return self.confirm_box.exec_() == QtWidgets.QMessageBox.StandardButton.Yes
# PySide uses "exec_" instead of "exec" because "exec" is a reserved
# keyword in Python 2.
@@ -491,15 +496,21 @@ def _toggle_web(self, method_selected=None):
# - they need to use the legacy login / passphrase to use a PAT with
# Autodesk Identity authentication
if os.environ.get("SGTK_FORCE_STANDARD_LOGIN_DIALOG"):
- logger.info("Using the standard login dialog with the Flow Production Tracking")
+ logger.info(
+ "Using the standard login dialog with the Flow Production Tracking"
+ )
else:
if _is_running_in_desktop():
- can_use_web = can_use_web or self.site_info.autodesk_identity_enabled
+ can_use_web = (
+ can_use_web or self.site_info.autodesk_identity_enabled
+ )
# If we have full support for Web-based login, or if we enable it in our
# environment, use the Unified Login Flow for all authentication modes.
if get_shotgun_authenticator_support_web_login():
- can_use_web = can_use_web or self.site_info.unified_login_flow_enabled
+ can_use_web = (
+ can_use_web or self.site_info.unified_login_flow_enabled
+ )
if method_selected:
# Selecting requested mode (credentials, qt_web_login or app_session_launcher)
@@ -511,9 +522,7 @@ def _toggle_web(self, method_selected=None):
method_selected = session_cache.get_preferred_method(site)
# Make sure that the method_selected is currently supported
- if (
- method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web
- ) or (
+ if (method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web) or (
method_selected == auth_constants.METHOD_ASL and not can_use_asl
):
method_selected = None
@@ -525,9 +534,7 @@ def _toggle_web(self, method_selected=None):
)
# Make sure that the method_selected is currently supported
- if (
- method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web
- ) or (
+ if (method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web) or (
method_selected == auth_constants.METHOD_ASL and not can_use_asl
):
method_selected = None
@@ -667,7 +674,7 @@ def exec_(self):
# to freeze, so only set the WindowStaysOnTopHint flag as this appears to not disable the
# other flags.
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
- return QtGui.QDialog.exec_(self)
+ return QtWidgets.QDialog.exec_(self)
def result(self):
"""
@@ -688,13 +695,13 @@ def result(self):
profile_location=profile_location,
)
# If the offscreen session renewal failed, show the GUI as a failsafe
- if res != QtGui.QDialog.Accepted:
+ if res != QtWidgets.QDialog.Accepted:
return
return self._sso_saml2.get_session_data()
res = self.exec_()
- if res != QtGui.QDialog.Accepted:
+ if res != QtWidgets.QDialog.Accepted:
return
metrics_cache.log(
@@ -702,7 +709,9 @@ def result(self):
"Logged In",
properties={
"authentication_method": self.site_info.user_authentication_method,
- "authentication_experience": auth_constants.method_resolve.get(self.method_selected),
+ "authentication_experience": auth_constants.method_resolve.get(
+ self.method_selected
+ ),
"authentication_interface": "qt_dialog",
"authentication_renewal": self._is_session_renewal,
},
@@ -747,7 +756,7 @@ def _ok_pressed(self):
Validate the values, accepting if login is successful and display an error message if not.
"""
# Wait for any ongoing Site Configuration check thread.
- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
try:
if not self._query_task.wait(THREAD_WAIT_TIMEOUT_MS):
logger.warning(
@@ -755,7 +764,7 @@ def _ok_pressed(self):
% self._get_current_site()
)
finally:
- QtGui.QApplication.restoreOverrideCursor()
+ QtWidgets.QApplication.restoreOverrideCursor()
# pull values from the gui
site = self._get_current_site()
@@ -771,7 +780,10 @@ def _ok_pressed(self):
# Cleanup the URL and update the GUI.
if self.method_selected != auth_constants.METHOD_BASIC:
- if site.startswith("http://") and "SGTK_AUTH_ALLOW_NO_HTTPS" not in os.environ:
+ if (
+ site.startswith("http://")
+ and "SGTK_AUTH_ALLOW_NO_HTTPS" not in os.environ
+ ):
site = "https" + site[4:]
self.ui.site.setEditText(site)
@@ -827,7 +839,7 @@ def _authenticate(self, error_label, site, login, password, auth_code=None):
product=PRODUCT_IDENTIFIER,
profile_location=profile_location,
)
- if res == QtGui.QDialog.Accepted:
+ if res == QtWidgets.QDialog.Accepted:
self._new_session_token = self._sso_saml2.session_id
self._session_metadata = self._sso_saml2.cookies
else:
@@ -837,8 +849,8 @@ def _authenticate(self, error_label, site, login, password, auth_code=None):
return
else:
# set the wait cursor
- QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
- QtGui.QApplication.processEvents()
+ QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
+ QtWidgets.QApplication.processEvents()
# try and authenticate
self._new_session_token = session_cache.generate_session_token(
@@ -851,9 +863,9 @@ def _authenticate(self, error_label, site, login, password, auth_code=None):
success = True
finally:
# restore the cursor
- QtGui.QApplication.restoreOverrideCursor()
+ QtWidgets.QApplication.restoreOverrideCursor()
# dialog is done
- QtGui.QApplication.processEvents()
+ QtWidgets.QApplication.processEvents()
# Do not accept while the cursor is overriden, if freezes the dialog.
if success:
diff --git a/python/tank/authentication/sso_saml2/__init__.py b/python/tank/authentication/sso_saml2/__init__.py
index 6a1aae46bf..2c6a80750f 100644
--- a/python/tank/authentication/sso_saml2/__init__.py
+++ b/python/tank/authentication/sso_saml2/__init__.py
@@ -19,6 +19,7 @@
SsoSaml2MissingQtGui,
SsoSaml2MissingQtModuleError,
SsoSaml2MissingQtNetwork,
+ SsoSaml2MissingQtWebEngineWidgets,
SsoSaml2MultiSessionNotSupportedError,
)
diff --git a/python/tank/authentication/sso_saml2/core/sso_saml2_core.py b/python/tank/authentication/sso_saml2/core/sso_saml2_core.py
index e148aa7d91..68e9d0e1d4 100644
--- a/python/tank/authentication/sso_saml2/core/sso_saml2_core.py
+++ b/python/tank/authentication/sso_saml2/core/sso_saml2_core.py
@@ -157,6 +157,7 @@ def __init__(self, window_title="Web Login", qt_modules=None):
For Qt5/PySide2, we require modules QtCore, QtGui,
QtNetwork and QtWebEngineWidgets
+
:returns: The SsoSaml2Core oject.
"""
qt_modules = qt_modules or {}
@@ -171,9 +172,13 @@ def __init__(self, window_title="Web Login", qt_modules=None):
QtCore = self._QtCore = qt_modules.get("QtCore") # noqa
QtGui = self._QtGui = qt_modules.get("QtGui") # noqa
QtNetwork = self._QtNetwork = qt_modules.get("QtNetwork") # noqa
+ QtWidgets = self._QtWidgets = qt_modules.get("QtWidgets") # noqa
QtWebEngineWidgets = self._QtWebEngineWidgets = qt_modules.get(
"QtWebEngineWidgets"
) # noqa
+ QtWebEngineCore = self._QtWebEngineCore = qt_modules.get(
+ "QtWebEngineCore"
+ ) # noqa
if QtCore is None:
raise SsoSaml2MissingQtCore("The QtCore module is unavailable")
@@ -203,15 +208,15 @@ def __init__(self, window_title="Web Login", qt_modules=None):
# - Maya 2017
# missing the 'QSslConfiguration' class. Likely compiled without SSL
# support.
- if QtWebEngineWidgets and not hasattr(
- QtWebEngineWidgets.QWebEngineProfile, "cookieStore"
+ if QtWebEngineCore and not hasattr(
+ QtWebEngineCore.QWebEngineProfile, "cookieStore"
):
raise SsoSaml2IncompletePySide2(
"Missing method QtWebEngineWidgets.QWebEngineProfile.cookieStore()"
)
if QtNetwork and not hasattr(QtNetwork, "QSslConfiguration"):
raise SsoSaml2IncompletePySide2("Missing class QtNetwork.QSslConfiguration")
- class TKWebPageQtWebEngine(QtWebEngineWidgets.QWebEnginePage):
+ class TKWebPageQtWebEngine(QtWebEngineCore.QWebEnginePage):
"""
Wrapper class to better control the behaviour when clicking on links
in the Qt5 web browser. If we are asked to open a new tab/window, then
@@ -251,7 +256,7 @@ def acceptNavigationRequest(self, url, n_type, is_mainframe):
if self._profile is None:
QtGui.QDesktopServices.openUrl(url)
return False
- return QtWebEngineWidgets.QWebEnginePage.acceptNavigationRequest(
+ return QtWebEngineCore.QWebEnginePage.acceptNavigationRequest(
self, url, n_type, is_mainframe
)
@@ -279,17 +284,17 @@ def certificateError(self, certificate_error):
self._sessions_stack = []
self._session_renewal_active = False
- self._dialog = QtGui.QDialog()
+ self._dialog = QtWidgets.QDialog()
self._dialog.setWindowTitle(window_title)
self._dialog.finished.connect(self.on_dialog_closed)
# This is to ensure that we can resize the window nicely, and that the
# WebView will follow.
- self._layout = QtGui.QVBoxLayout(self._dialog)
+ self._layout = QtWidgets.QVBoxLayout(self._dialog)
self._layout.setSpacing(0)
self._layout.setContentsMargins(0, 0, 0, 0)
- self._profile = QtWebEngineWidgets.QWebEngineProfile.defaultProfile()
+ self._profile = QtWebEngineCore.QWebEngineProfile.defaultProfile()
self._logger.debug(
"Initial WebEngineProfile storage location: %s",
self._profile.persistentStoragePath(),
@@ -313,7 +318,7 @@ def certificateError(self, certificate_error):
# The cookies will be cleared if there are no prior session in
# method 'update_browser_from_session' if needed.
self._profile.setPersistentCookiesPolicy(
- QtWebEngineWidgets.QWebEngineProfile.ForcePersistentCookies
+ QtWebEngineCore.QWebEngineProfile.ForcePersistentCookies
)
self._cookie_jar = QtNetwork.QNetworkCookieJar()
self._profile.cookieStore().cookieAdded.connect(self._on_cookie_added)
@@ -857,14 +862,14 @@ def on_dialog_closed(self, result):
This can be the result of a callback, a timeout or user interaction.
:param result: Qt result following the closing of the dialog.
- QtGui.QDialog.Accepted or QtGui.QDialog.Rejected
+ QtWidgets.QDialog.Accepted or QtGui.QDialog.Rejected
"""
self._logger.debug("SSO dialog closed")
# pylint: disable=invalid-name
- QtGui = self._QtGui # noqa
+ QtWidgets = self._QtWidgets # noqa
if self.is_handling_event():
- if result == QtGui.QDialog.Rejected and self._session.cookies != "":
+ if result == QtWidgets.QDialog.Rejected and self._session.cookies != "":
# We got here because of a timeout attempting a GUI-less login.
# Let's clear the cookies, and force the use of the GUI.
self._session.cookies = ""
@@ -879,7 +884,7 @@ def on_dialog_closed(self, result):
self.resolve_event()
else:
# Should we get a rejected dialog, then we have had a timeout.
- if result == QtGui.QDialog.Rejected:
+ if result == QtWidgets.QDialog.Rejected:
# @FIXME: Figure out exactly what to do when we have a timeout.
self._logger.warn(
"Our QDialog got canceled outside of an event handling"
diff --git a/python/tank/authentication/sso_saml2/core/username_password_dialog.py b/python/tank/authentication/sso_saml2/core/username_password_dialog.py
index ef2856942f..3a0202c3bc 100644
--- a/python/tank/authentication/sso_saml2/core/username_password_dialog.py
+++ b/python/tank/authentication/sso_saml2/core/username_password_dialog.py
@@ -14,14 +14,14 @@
from __future__ import print_function
# pylint: disable=import-error
-from ...ui.qt_abstraction import QtCore, QtGui
+from ...ui.qt_abstraction import QtCore, QtWidgets
-# No point in proceeding if QtGui is None.
-if QtGui is None:
- raise ImportError("Unable to import QtGui")
+# No point in proceeding if QtWidgets is None.
+if QtWidgets is None:
+ raise ImportError("Unable to import QtWidgets")
-class UsernamePasswordDialog(QtGui.QDialog):
+class UsernamePasswordDialog(QtWidgets.QDialog):
"""Simple dialog to request a username and password from the user."""
def __init__(self, window_title=None, message=None):
@@ -39,28 +39,28 @@ def __init__(self, window_title=None, message=None):
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# set up the layout
- form_grid_layout = QtGui.QGridLayout(self)
+ form_grid_layout = QtWidgets.QGridLayout(self)
# initialize the username combo box so that it is editable
- self._edit_username = QtGui.QLineEdit(self)
+ self._edit_username = QtWidgets.QLineEdit(self)
self._edit_username.setPlaceholderText("Domain\\Username or email address")
# initialize the password field so that it does not echo characters
- self._edit_password = QtGui.QLineEdit(self)
- self._edit_password.setEchoMode(QtGui.QLineEdit.Password)
+ self._edit_password = QtWidgets.QLineEdit(self)
+ self._edit_password.setEchoMode(QtWidgets.QLineEdit.Password)
self._edit_password.setPlaceholderText("Password")
# initialize the labels
- label_message = QtGui.QLabel(self)
+ label_message = QtWidgets.QLabel(self)
label_message.setText(message)
label_message.setWordWrap(True)
# initialize buttons
- buttons = QtGui.QDialogButtonBox(self)
- buttons.addButton(QtGui.QDialogButtonBox.Ok)
- buttons.addButton(QtGui.QDialogButtonBox.Cancel)
- buttons.button(QtGui.QDialogButtonBox.Ok).setText("Login")
- buttons.button(QtGui.QDialogButtonBox.Cancel).setText("Cancel")
+ buttons = QtWidgets.QDialogButtonBox(self)
+ buttons.addButton(QtWidgets.QDialogButtonBox.Ok)
+ buttons.addButton(QtWidgets.QDialogButtonBox.Cancel)
+ buttons.button(QtWidgets.QDialogButtonBox.Ok).setText("Login")
+ buttons.button(QtWidgets.QDialogButtonBox.Cancel).setText("Cancel")
# place components into the dialog
form_grid_layout.addWidget(label_message, 0, 0)
@@ -71,10 +71,10 @@ def __init__(self, window_title=None, message=None):
self.setLayout(form_grid_layout)
- buttons.button(QtGui.QDialogButtonBox.Ok).clicked.connect(
+ buttons.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(
self._on_enter_credentials
)
- buttons.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.close)
+ buttons.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.close)
# On Qt4, this sets the look-and-feel to that of the toolkit.
self.setStyleSheet(
@@ -138,7 +138,7 @@ def _on_enter_credentials(self):
def main():
"""Simple test"""
- _ = QtGui.QApplication([])
+ _ = QtWidgets.QApplication([])
window_title = "A title"
message = "A message"
login_dialog = UsernamePasswordDialog(window_title=window_title, message=message)
diff --git a/python/tank/authentication/ui/aspect_preserving_label.py b/python/tank/authentication/ui/aspect_preserving_label.py
index 5e6db3483c..737755d3b9 100644
--- a/python/tank/authentication/ui/aspect_preserving_label.py
+++ b/python/tank/authentication/ui/aspect_preserving_label.py
@@ -8,11 +8,10 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from .qt_abstraction import QtGui
-from .qt_abstraction import QtCore
+from .qt_abstraction import QtCore, QtWidgets
-class AspectPreservingLabel(QtGui.QLabel):
+class AspectPreservingLabel(QtWidgets.QLabel):
"""
Label that displays a scaled down version of an image if it is bigger
than the label.
@@ -23,7 +22,7 @@ def __init__(self, parent=None):
:params parent: Parent widget.
"""
- QtGui.QLabel.__init__(self, parent)
+ QtWidgets.QLabel.__init__(self, parent)
self._pix = None
@@ -36,7 +35,7 @@ def setPixmap(self, pixmap):
self._pix = pixmap
scaled_pixmap = self._pix.scaled(
self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
- QtGui.QLabel.setPixmap(self, scaled_pixmap)
+ QtWidgets.QLabel.setPixmap(self, scaled_pixmap)
def heightForWidth(self, width):
"""
@@ -48,7 +47,7 @@ def heightForWidth(self, width):
"""
if self._pix is None:
return self._pix.height() * width / self._pix.width()
- return QtGui.QLabel.heightForWidth(self, width)
+ return QtWidgets.QLabel.heightForWidth(self, width)
def sizeHint(self):
"""
@@ -68,5 +67,5 @@ def resizeEvent(self, e):
scaled_pixmap = self._pix.scaled(
self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
- QtGui.QLabel.setPixmap(self, scaled_pixmap)
- QtGui.QApplication.instance().processEvents()
+ QtWidgets.QLabel.setPixmap(self, scaled_pixmap)
+ QtWidgets.QApplication.instance().processEvents()
diff --git a/python/tank/authentication/ui/completion_filter_proxy.py b/python/tank/authentication/ui/completion_filter_proxy.py
index 6d66136af7..ebf182de67 100644
--- a/python/tank/authentication/ui/completion_filter_proxy.py
+++ b/python/tank/authentication/ui/completion_filter_proxy.py
@@ -20,7 +20,7 @@
from tank.util import sgre as re
-from .qt_abstraction import QtGui
+from .qt_abstraction import QtCore
class FuzzyMatcher():
@@ -47,7 +47,7 @@ def score(self, string):
return 100.0 / ((1 + match.start()) * (match.end() - match.start() + 1))
-class CompletionFilterProxy(QtGui.QSortFilterProxyModel):
+class CompletionFilterProxy(QtCore.QSortFilterProxyModel):
"""
Filters rows based on fuzzy matching and sorts them based on their score.
"""
diff --git a/python/tank/authentication/ui/login_dialog.py b/python/tank/authentication/ui/login_dialog.py
index 8cf2f9fcc8..ff829d1e28 100644
--- a/python/tank/authentication/ui/login_dialog.py
+++ b/python/tank/authentication/ui/login_dialog.py
@@ -16,6 +16,10 @@
for name, cls in QtGui.__dict__.items():
if isinstance(cls, type): globals()[name] = cls
+from .qt_abstraction import QtWidgets
+for name, cls in QtWidgets.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
from .recent_box import RecentBox
from .aspect_preserving_label import AspectPreservingLabel
diff --git a/python/tank/authentication/ui/qt_abstraction.py b/python/tank/authentication/ui/qt_abstraction.py
index 140270450b..7e2f3b81eb 100644
--- a/python/tank/authentication/ui/qt_abstraction.py
+++ b/python/tank/authentication/ui/qt_abstraction.py
@@ -14,10 +14,12 @@
from ...util.qt_importer import QtImporter
-_importer = QtImporter()
+_importer = QtImporter(interface_version_requested=QtImporter.QT6)
QtCore = _importer.QtCore
QtGui = _importer.QtGui
QtNetwork = _importer.QtNetwork
+QtWidgets = _importer.QtWidgets
QtWebEngineWidgets = _importer.QtWebEngineWidgets
+QtWebEngineCore = _importer.QtWebEngineCore
qt_version_tuple = _importer.qt_version_tuple
del _importer
diff --git a/python/tank/authentication/ui/recent_box.py b/python/tank/authentication/ui/recent_box.py
index f39f0933d3..c7e1735e8d 100644
--- a/python/tank/authentication/ui/recent_box.py
+++ b/python/tank/authentication/ui/recent_box.py
@@ -18,11 +18,11 @@
--------------------------------------------------------------------------------
"""
-from .qt_abstraction import QtGui
+from .qt_abstraction import QtCore, QtWidgets
from .completion_filter_proxy import CompletionFilterProxy
-class RecentBox(QtGui.QComboBox):
+class RecentBox(QtWidgets.QComboBox):
"""
Combo box specialisation that handles all the filtering, sorting and auto-completion
for a list of recent items. Items are sorted alphabetically so they can be found easily
@@ -35,18 +35,20 @@ def __init__(self, parent):
self.setEditable(True)
# Using QLineEdit so we have a placeholder even when the line edit is selected.
- self.setLineEdit(QtGui.QLineEdit(self))
+ self.setLineEdit(QtWidgets.QLineEdit(self))
# Create a model sorted alphabetically for the recent items.
- self._recent_items_model = QtGui.QStringListModel(self)
- self._drop_down_model = QtGui.QSortFilterProxyModel(self)
+ self._recent_items_model = QtCore.QStringListModel(self)
+ self._drop_down_model = QtCore.QSortFilterProxyModel(self)
self._drop_down_model.setSourceModel(self._recent_items_model)
self.setModel(self._drop_down_model)
# We'll use a completer that shows all results and we'll do the matching ourselves, as the completion
# engine can only work from the beginning of a string...
- self._completer = QtGui.QCompleter(self)
- self._completer.setCompletionMode(QtGui.QCompleter.UnfilteredPopupCompletion)
+ self._completer = QtWidgets.QCompleter(self)
+ self._completer.setCompletionMode(
+ QtWidgets.QCompleter.UnfilteredPopupCompletion
+ )
# We'll do our own filtering.
self._filter_model = CompletionFilterProxy(self)
diff --git a/python/tank/authentication/ui_authentication.py b/python/tank/authentication/ui_authentication.py
index 486262e6a3..d56900b148 100644
--- a/python/tank/authentication/ui_authentication.py
+++ b/python/tank/authentication/ui_authentication.py
@@ -67,7 +67,6 @@ def authenticate(self, hostname, login, http_proxy):
logger.debug("Requesting password in a dialog.")
else:
logger.debug("Requesting username and password in a dialog.")
-
if LoginDialog is None:
logger.error("Unexpected state. LoginDialog should be available.")
raise ShotgunAuthenticationError("Could not instantiated login dialog.")
diff --git a/python/tank/bootstrap/async_bootstrap.py b/python/tank/bootstrap/async_bootstrap.py
index 39abebb9d6..b1fbc45f7f 100644
--- a/python/tank/bootstrap/async_bootstrap.py
+++ b/python/tank/bootstrap/async_bootstrap.py
@@ -11,9 +11,10 @@
# Import Qt without having to worry about the version to use.
from ..util.qt_importer import QtImporter
-importer = QtImporter()
+importer = QtImporter(interface_version_requested=QtImporter.QT6)
QtCore = importer.QtCore
-QtGui = importer.QtGui
+QtWidgets = importer.QtWidgets
+
if QtCore is None:
# Raise an exception when Qt is not available.
raise ImportError
@@ -273,8 +274,8 @@ def _get_thread_info_msg(caller):
:return: Generated information message.
"""
- if QtGui.QApplication.instance():
- if QtCore.QThread.currentThread() == QtGui.QApplication.instance().thread():
+ if QtWidgets.QApplication.instance():
+ if QtCore.QThread.currentThread() == QtWidgets.QApplication.instance().thread():
msg = "%s is running in main Qt thread."
else:
msg = "%s is running in background Qt thread."
diff --git a/python/tank/platform/engine.py b/python/tank/platform/engine.py
index 9ea604342d..2932166d02 100644
--- a/python/tank/platform/engine.py
+++ b/python/tank/platform/engine.py
@@ -182,11 +182,14 @@ def __init__(self, tk, context, engine_instance_name, env):
self.__has_qt5 = len(qt5_base) > 0
for name, value in qt5_base.items():
setattr(qt5, name, value)
+ qt_widgets = qt5_base.get("QtWidgets")
qt6_base = self.__define_qt6_base()
self.__has_qt6 = len(qt6_base) > 0
for name, value in qt6_base.items():
setattr(qt6, name, value)
+ qt_widgets = qt6_base.get("QtWidgets")
+ qt6.TankDialogBase = qt_widgets.QDialog if qt_widgets else None
# Update the authentication module to use the engine's Qt.
# @todo: can this import be untangled? Code references internal part of the auth module
@@ -324,7 +327,7 @@ def __open_log_folder(self):
if self.has_ui:
# only import QT if we have a UI
- from .qt import QtGui, QtCore
+ from .qt6 import QtGui, QtCore
url = QtCore.QUrl.fromLocalFile(LogManager().log_folder)
status = QtGui.QDesktopServices.openUrl(url)
@@ -415,8 +418,8 @@ def __show_busy(self, title, details):
if self.has_ui:
# we cannot import QT until here as non-ui engines don't have QT defined.
try:
- from .qt.busy_dialog import BusyDialog
- from .qt import QtGui, QtCore
+ from .qt6.busy_dialog import BusyDialog
+ from .qt6 import QtCore
except:
# QT import failed. This may be because someone has upgraded the core
@@ -1164,8 +1167,8 @@ def execute_in_main_thread(self, func, *args, **kwargs):
for the background thread to finish, Qt's event loop won't be able to process the request
to execute in the main thread::
- >>> from sgtk.platform.qt import QtGui
- >>> engine.execute_in_main_thread(QtGui.QMessageBox.information, None, "Hello", "Hello from the main thread!")
+ >>> from sgtk.platform.qt6 import QtWidgets
+ >>> engine.execute_in_main_thread(QtWidgets.QMessageBox.information, None, "Hello", "Hello from the main thread!")
.. note:: This currently only works if Qt is available, otherwise it just
executes immediately on the current thread.
@@ -1213,12 +1216,12 @@ def _execute_in_main_thread(self, invoker_id, func, *args, **kwargs):
self._invoker if invoker_id == self._SYNC_INVOKER else self._async_invoker
)
if invoker:
- from .qt import QtGui, QtCore
+ from .qt6 import QtCore, QtWidgets
if (
- QtGui.QApplication.instance()
+ QtWidgets.QApplication.instance()
and QtCore.QThread.currentThread()
- != QtGui.QApplication.instance().thread()
+ != QtWidgets.QApplication.instance().thread()
):
# invoke the function on the thread that the QtGui.QApplication was created on.
return invoker.invoke(func, *args, **kwargs)
@@ -1550,7 +1553,7 @@ def _ensure_core_fonts_loaded(self):
if not self.has_ui:
return
- from sgtk.platform.qt import QtGui
+ from sgtk.platform.qt6 import QtGui, QtWidgets
# if the fonts have been loaded, no need to do anything else
if self.__fonts_loaded:
@@ -1560,7 +1563,7 @@ def _ensure_core_fonts_loaded(self):
# it is possible that QtGui is not available (test suite).
return
- if not QtGui.QApplication.instance():
+ if not QtWidgets.QApplication.instance():
# there is a QApplication, so we can load fonts.
return
@@ -1628,12 +1631,12 @@ def _get_dialog_parent(self):
Can be overriden in derived classes to return the QWidget to be used as the parent
for all TankQDialog's.
- :return: QT Parent window (:class:`PySide.QtGui.QWidget`)
+ :return: QT Parent window (:class:`PySide.QtWidgets.QWidget`)
"""
# By default, this will return the QApplication's active window:
- from .qt import QtGui
+ from .qt6 import QtWidgets
- return QtGui.QApplication.activeWindow()
+ return QtWidgets.QApplication.activeWindow()
def _create_dialog(self, title, bundle, widget, parent):
"""
@@ -1645,9 +1648,9 @@ def _create_dialog(self, title, bundle, widget, parent):
:param title: The title of the window
:param bundle: The app, engine or framework object that is associated with this window
:param widget: A QWidget instance to be embedded in the newly created dialog.
- :type widget: :class:`PySide.QtGui.QWidget`
+ :type widget: :class:`PySide.QtWidgets.QWidget`
"""
- from .qt import tankqdialog
+ from .qt6 import tankqdialog
# TankQDialog uses the bundled core font. Make sure they are loaded
# since know we have a QApplication at this point.
@@ -1680,11 +1683,11 @@ def _create_widget(self, widget_class, *args, **kwargs):
.. note:: For more information, see the documentation for :meth:`show_dialog()`.
:param widget_class: The class of the UI to be constructed. This must derive from QWidget.
- :type widget_class: :class:`PySide.QtGui.QWidget`
+ :type widget_class: :class:`PySide.QtWidgets.QWidget`
Additional parameters specified will be passed through to the widget_class constructor.
"""
- from .qt import tankqdialog
+ from .qt6 import tankqdialog
# construct the widget object
try:
@@ -1699,28 +1702,28 @@ def _create_widget(self, widget_class, *args, **kwargs):
self.logger.exception(exc)
import traceback
- from sgtk.platform.qt import QtGui, QtCore
+ from sgtk.platform.qt6 import QtCore, QtWidgets
# A very simple widget that ensures that the exception is visible and
# selectable should the user need to copy/paste it into a support
# ticket.
- class _exc_widget(QtGui.QWidget):
+ class _exc_widget(QtWidgets.QWidget):
def __init__(self, msg, *args, **kwargs):
super(_exc_widget, self).__init__(*args, **kwargs)
self.setObjectName("SGTK_CORE_EXC_WIDGET")
- self._label = QtGui.QLabel(
+ self._label = QtWidgets.QLabel(
"The requested dialog could not be built "
"due to an exception that was raised:"
)
self._label.setTextFormat(QtCore.Qt.RichText)
- self._text = QtGui.QTextEdit()
+ self._text = QtWidgets.QTextEdit()
self._text.setReadOnly(True)
- self._text.setLineWrapMode(QtGui.QTextEdit.NoWrap)
+ self._text.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
self._text.setText(msg)
- self._layout = QtGui.QVBoxLayout(self)
+ self._layout = QtWidgets.QVBoxLayout(self)
self._layout.addWidget(self._label)
self._layout.addWidget(self._text)
@@ -1744,7 +1747,7 @@ def _create_dialog_with_widget(self, title, bundle, widget_class, *args, **kwarg
:param title: The title of the window
:param bundle: The app, engine or framework object that is associated with this window
:param widget_class: The class of the UI to be constructed. This must derive from QWidget.
- :type widget_class: :class:`PySide.QtGui.QWidget`
+ :type widget_class: :class:`PySide.QtWidgets.QWidget`
Additional parameters specified will be passed through to the widget_class constructor.
"""
@@ -1768,7 +1771,7 @@ def _on_dialog_closed(self, dlg):
Called when a dialog created by this engine is closed.
:param dlg: The dialog being closed
- :type dlg: :class:`PySide.QtGui.QWidget`
+ :type dlg: :class:`PySide.QtWidgets.QWidget`
Derived implementations of this method should be sure to call
the base implementation
@@ -1880,7 +1883,7 @@ def hide_tk_title_bar(self):
:param title: The title of the window. This will appear in the Toolkit title bar.
:param bundle: The app, engine or framework object that is associated with this window
:param widget_class: The class of the UI to be constructed. This must derive from QWidget.
- :type widget_class: :class:`PySide.QtGui.QWidget`
+ :type widget_class: :class:`PySide.QtWidgets.QWidget`
Additional parameters specified will be passed through to the widget_class constructor.
@@ -1924,7 +1927,7 @@ def hide_tk_title_bar(self):
:param title: The title of the window
:param bundle: The app, engine or framework object that is associated with this window
:param widget_class: The class of the UI to be constructed. This must derive from QWidget.
- :type widget_class: :class:`PySide.QtGui.QWidget`
+ :type widget_class: :class:`PySide.QtWidgets.QWidget`
Additional parameters specified will be passed through to the widget_class constructor.
@@ -1974,7 +1977,7 @@ def hide_tk_title_bar(self):
:param title: The title of the panel
:param bundle: The app, engine or framework object that is associated with this window
:param widget_class: The class of the UI to be constructed. This must derive from QWidget.
- :type widget_class: :class:`PySide.QtGui.QWidget`
+ :type widget_class: :class:`PySide.QtWidgets.QWidget`
Additional parameters specified will be passed through to the widget_class constructor.
@@ -2085,7 +2088,7 @@ def _add_stylesheet_file_watcher(self, qss_file, widget):
:param qss_file: Full path to the style sheet file.
:param widget: A QWidget to apply the stylesheet to.
"""
- from .qt import QtCore
+ from .qt6 import QtCore
# We don't keep any reference to the watcher to let it be deleted with
# the widget it is parented under.
@@ -2226,9 +2229,9 @@ def __initialize_dark_look_and_feel_qt5_qt6(self):
at the application level, and then constructs and applies a custom palette
that emulates Maya 2017's color scheme.
"""
- from .qt import QtGui
+ from .qt6 import QtGui, QtWidgets
- app = QtGui.QApplication.instance()
+ app = QtWidgets.QApplication.instance()
# Set the fusion style, which gives us a good base to build on. With
# this, we'll be sticking largely to the style and won't need to
@@ -2455,7 +2458,7 @@ def __create_invokers(self):
invoker = None
async_invoker = None
if self.has_ui:
- from .qt import QtGui, QtCore
+ from .qt6 import QtGui, QtCore
# Classes are defined locally since Qt might not be available.
if QtGui and QtCore:
diff --git a/python/tank/platform/qt/busy_dialog.py b/python/tank/platform/qt/busy_dialog.py
index c9b0a612c9..c57db3acdd 100644
--- a/python/tank/platform/qt/busy_dialog.py
+++ b/python/tank/platform/qt/busy_dialog.py
@@ -8,11 +8,11 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-from . import QtCore, QtGui
+from . import QtWidgets
from .ui_busy_dialog import Ui_BusyDialog
-class BusyDialog(QtGui.QWidget):
+class BusyDialog(QtWidgets.QWidget):
"""
Global progress dialog. Displays a dialog that contains a small progress message.
This is handled by the engine.display_global_progress() and engine.clear_global_progress()
@@ -25,7 +25,7 @@ def __init__(self):
Constructor
"""
# first, call the base class and let it do its thing.
- QtGui.QWidget.__init__(self)
+ QtWidgets.QWidget.__init__(self)
# now load in the UI that was created in the UI designer
self.ui = Ui_BusyDialog()
@@ -47,7 +47,7 @@ def mousePressEvent(self, event):
:param event: QEvent
"""
- QtGui.QWidget.mousePressEvent(self, event)
+ QtWidgets.QWidget.mousePressEvent(self, event)
# close the window if someone clicks it
self.close()
diff --git a/python/tank/platform/qt/config_item.py b/python/tank/platform/qt/config_item.py
index 44db298c76..3eb9e1442b 100644
--- a/python/tank/platform/qt/config_item.py
+++ b/python/tank/platform/qt/config_item.py
@@ -8,23 +8,19 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-import os
-import shutil
-import sys
-
-from . import QtCore, QtGui
+from . import QtWidgets
from .ui_item import Ui_Item
from ..bundle import resolve_default_value
from ..engine import current_engine
-class ConfigItem(QtGui.QWidget):
+class ConfigItem(QtWidgets.QWidget):
"""
Describes a configuration setting with data and methods
"""
def __init__(self, setting, params, value, bundle, parent=None):
- QtGui.QWidget.__init__(self, parent)
+ QtWidgets.QWidget.__init__(self, parent)
# set up the UI
self.ui = Ui_Item()
diff --git a/python/tank/platform/qt/tankqdialog.py b/python/tank/platform/qt/tankqdialog.py
index 460c281cf1..3299024ff6 100644
--- a/python/tank/platform/qt/tankqdialog.py
+++ b/python/tank/platform/qt/tankqdialog.py
@@ -13,7 +13,7 @@
"""
-from . import QtCore, QtGui
+from . import QtCore, QtGui, QtWidgets
from . import ui_tank_dialog
from . import TankDialogBase
from .config_item import ConfigItem
@@ -22,10 +22,8 @@
from .. import constants
from ...errors import TankError
-import sys
import os
import inspect
-from tank_vendor import six
class TankQDialog(TankDialogBase):
@@ -568,7 +566,7 @@ def _on_widget_closed(self):
This is called when the contained widget is closed - it
handles the event and then closes the dialog
"""
- exit_code = QtGui.QDialog.Accepted
+ exit_code = QtWidgets.QDialog.Accepted
# look if the hosted widget has an exit_code we should pick up
if self._widget and hasattr(self._widget, "exit_code"):
diff --git a/python/tank/platform/qt/ui_busy_dialog.py b/python/tank/platform/qt/ui_busy_dialog.py
index d81bbfb3eb..b5beba7045 100644
--- a/python/tank/platform/qt/ui_busy_dialog.py
+++ b/python/tank/platform/qt/ui_busy_dialog.py
@@ -16,6 +16,9 @@
for name, cls in QtGui.__dict__.items():
if isinstance(cls, type): globals()[name] = cls
+from . import QtWidgets
+for name, cls in QtWidgets.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
from . import resources_rc
diff --git a/python/tank/platform/qt/ui_item.py b/python/tank/platform/qt/ui_item.py
index 0f1bc56551..58e661d6e8 100644
--- a/python/tank/platform/qt/ui_item.py
+++ b/python/tank/platform/qt/ui_item.py
@@ -16,6 +16,9 @@
for name, cls in QtGui.__dict__.items():
if isinstance(cls, type): globals()[name] = cls
+from . import QtWidgets
+for name, cls in QtWidgets.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
from . import resources_rc
diff --git a/python/tank/platform/qt/ui_tank_dialog.py b/python/tank/platform/qt/ui_tank_dialog.py
index 344b2eb7b6..debcf9b66d 100644
--- a/python/tank/platform/qt/ui_tank_dialog.py
+++ b/python/tank/platform/qt/ui_tank_dialog.py
@@ -16,6 +16,9 @@
for name, cls in QtGui.__dict__.items():
if isinstance(cls, type): globals()[name] = cls
+from . import QtWidgets
+for name, cls in QtWidgets.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
from . import resources_rc
diff --git a/python/tank/platform/qt6/busy_dialog.py b/python/tank/platform/qt6/busy_dialog.py
new file mode 100644
index 0000000000..c57db3acdd
--- /dev/null
+++ b/python/tank/platform/qt6/busy_dialog.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2013 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.
+
+from . import QtWidgets
+from .ui_busy_dialog import Ui_BusyDialog
+
+
+class BusyDialog(QtWidgets.QWidget):
+ """
+ Global progress dialog. Displays a dialog that contains a small progress message.
+ This is handled by the engine.display_global_progress() and engine.clear_global_progress()
+ methods and is typically used when for example the Core API wants to display some progress
+ information back to the user during long running tasks or processing.
+ """
+
+ def __init__(self):
+ """
+ Constructor
+ """
+ # first, call the base class and let it do its thing.
+ QtWidgets.QWidget.__init__(self)
+
+ # now load in the UI that was created in the UI designer
+ self.ui = Ui_BusyDialog()
+ self.ui.setupUi(self)
+
+ def set_contents(self, title, details):
+ """
+ Set the message to be displayed in the progress dialog
+
+ :param title: Title text to display
+ :param details: detailed message to display
+ """
+ self.ui.title.setText(title)
+ self.ui.details.setText(details)
+
+ def mousePressEvent(self, event):
+ """
+ Called when the mouse is clicked in the widget
+
+ :param event: QEvent
+ """
+ QtWidgets.QWidget.mousePressEvent(self, event)
+ # close the window if someone clicks it
+ self.close()
+
+ @property
+ def hide_tk_title_bar(self):
+ """
+ Tell the system to not show the std toolbar
+ """
+ return True
diff --git a/python/tank/platform/qt6/config_item.py b/python/tank/platform/qt6/config_item.py
new file mode 100644
index 0000000000..3eb9e1442b
--- /dev/null
+++ b/python/tank/platform/qt6/config_item.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2013 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.
+
+from . import QtWidgets
+from .ui_item import Ui_Item
+from ..bundle import resolve_default_value
+from ..engine import current_engine
+
+
+class ConfigItem(QtWidgets.QWidget):
+ """
+ Describes a configuration setting with data and methods
+ """
+
+ def __init__(self, setting, params, value, bundle, parent=None):
+ QtWidgets.QWidget.__init__(self, parent)
+
+ # set up the UI
+ self.ui = Ui_Item()
+ self.ui.setupUi(self)
+
+ engine_name = None
+ if current_engine():
+ engine_name = current_engine().name
+
+ default_val = resolve_default_value(params, engine_name=engine_name)
+ param_type = params.get("type")
+
+ self.ui.name.setText("Setting %s" % setting)
+
+ self.ui.type.setText("Type: %s" % param_type)
+
+ desc = str(params.get("description", "No description given."))
+ self.ui.description.setText("Description: %s" % desc)
+
+ # special cases for some things:
+ value_str = ""
+
+ if type(value) == str and value.startswith("hook:"):
+ # this is the generic hook override that any type can have
+ value_str = "Value: %s" % value
+ value_str += "
"
+ value_str += "This value uses a dynamic, hook based setting. When the value is computed, "
+ value_str += "Toolkit is calling the core hook specified in the setting. "
+ value_str += "
The value is currently being computed by the "
+ value_str += "hook to '%s'" % str(bundle.get_setting(setting))
+
+ elif param_type == "hook":
+ # resolve the hook path
+ if value == "default":
+ value_str = "Value: Using the default hook that comes bundled with the app."
+ else:
+ # user hook
+ value_str = "Value: %s" % value
+
+ elif param_type == "template":
+ # resolve the template
+ value_str = "Value: %s
" % value
+ template_value = bundle.tank.templates.get(value)
+ template_def = template_value.definition if template_value else "None"
+ value_str += "Resolved Value: %s
" % template_def
+
+ elif param_type in ["dict", "list"]:
+ # code block
+ value_str = "Value: %s" % value
+
+ elif param_type == "str":
+ # value in quotes
+ value_str = "Value: '%s'" % value
+
+ else:
+ # all others
+ value_str = "Value: %s" % value
+
+ # colour all non-default values in blue
+ if default_val == value or (param_type == "hook" and value == "default"):
+ self.ui.value.setText(value_str)
+ self.ui.value.setToolTip("This setting is using the default value.")
+ else:
+ # non-default value - indicate in blue
+ self.ui.value.setText("
%s
" % value_str)
+ self.ui.value.setToolTip("This setting is using a non-default value.")
diff --git a/python/tank/platform/qt6/resources_rc.py b/python/tank/platform/qt6/resources_rc.py
new file mode 100644
index 0000000000..51f7b1fa13
--- /dev/null
+++ b/python/tank/platform/qt6/resources_rc.py
@@ -0,0 +1,1968 @@
+# Resource object code (Python 3)
+# Created by: object code
+# Created by: The Resource Compiler for Qt version 5.15.2
+# WARNING! All changes made in this file will be lost!
+
+from . import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x04\xe2\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x1f\x00\x00\x00%\x08\x04\x00\x00\x00\x10F\xfay\
+\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe1\x05\x0c\x0c00c\x7f\
+\xadt\x00\x00\x03\xd5IDATH\xc7\xe5V?l\
+\x9bE\x14\xff\xbdw\xf7}\xb6\xe38\xb6\xd3$m\xd2\
+\xd2\x9a\x0c\x91H \xa8\x0a\x02\x8a*\xea\x85\x81\x11!\
+g`\xe8\x04\xd9@ba`1\x9f\xc4\x80\xc4\x86\x90\
+*\x15!\x06\x98\xe2\x91!c#\xa4\xb2\xa0\xb4\x82\x86\
+J4%M\xd4\x84$\xb8J\x94?\xb6\xbf\xcf\xf7\xdd\
+=\x86\x94\xc4&v\x85\xd4\x05\x89{\xcb\x0d\xf7\xbb\xdf\
+\xef\xdd\xfb\xdd\xbb#\xc1\xd3\x0c~*\xf4\x7f\x0cN\x01\
+\x83\xc1\x15UQ`p\xc0\xa0\x7f\x0b\xa7\x80Ae\x02\
+]WPP\x0b\x0a|\x85\x03\x85'l\xd1\x02\x0fh\
+\x9c@\x1fh\xe8\x11/\xa5G5\xbc\x05\x7fE\x8f3\
+8PA\x97$\xe9\xa8p\x04\x02\xcd\xe9\x1c\xaf\xa9\xbc\
+\x8a\xb8\x9f\xf6\x90p\x9e\xad9\xdf\x16\xe3\x00pe\xf7\
+\x04\xf6\x80@\x15e\x95\xd3\xc3^\xc2\x1f\xf0\xabI$\
+\x904\x89\x1e\xbf\xe1\xafy\xe3|\xa5c\x0a-\xa2\xe6\
+\xe9\x22\xf5\xb2h\xf6l\x22\xea\x19\x1d\xc8'\x9b\xa9t\
+J\x92)\x7f\xdd\x1b\xd5\x19\x0a\xd4\xc9\x0d\x8e\xc5\xf3\x82\
+\xda\xf0\x94\x0e\x93y\x7flj\xe8C\xce\x99\xfb\xcb_\
+\xacnyF5\x1bQ\x7ft\xd6l\xc5\xcb\xae\xe4\xd0\
+fS\xf5\xc9\xe3I\x85w\xf8<\xb3\x97\xd3q\xe2\xb9\
+\xcf\xd0/\xcc\xa7\xfa_\xaa/\x88\x89\x98\xc0\xa8\xcb0\
+|\xb9\x89\x89\xeeu\xf7HQ\x93G\xb2\x9c\x07\x81\xc0\
+tf\xfc\xa3d\x9eS*e\x13\xda\xbf\xe3-\xeb\x12\
+MsG\xf6\x09\x0a\xc9\xb1\xd5u\xaf\x8eg.#\x0b\
+\x12\x22\xa2\xfc\xa9\xd1\xcd[\xcei4D\x89\x93ey\
+M\x0a\xd2\x91}_\x80=8\x89\xf0\xc75\xa9\x0b\x81\
+\x84\xc1\xfa\xf9\x8b\xef\xf9\xc90\xa1\xfd\xc8;\xaf^\xe0\
+L\x1b\xff\x11{@\x05\x22\x8e\xd9\xd7}\xeaQ]~\
+\xcf\xbe\x0c_\x88\x08\xd0g\xf3\xe9\xedE\xe5u*D\xaf\xe3\x98l-\xae\x9a\xc1\xb8\x18\
+\x07\x1d\x9aeK\xa7\xad\xf0\x0e\xcf\xf0\xb7zX\xef\xa8\
+A\xce\xd2\x01v\xe5\x5c\x1c9\x1d\xffh\xdf\xb7\xd32\
+kqb\x1c\xbbN\xee\xca\x8c\x0b\xe4R\x1c\x9a\x89\xa6\
+\x17mF\x1c\xba\xe8Os\xba\xd9\x1d\xdc&\xbe,\x01\
+\xca\x16\xb8->\x19:\xa0\x94x\xb2+\x05\xb7\x0d\xd8\
+\xd9n\xef\xb8\xb4\x07\x95\x94(Q\xa2n\xe8\x1bZT\
+I\x09\x09\xba\x07\xfd\x8f\xbf\x07\x7f\x01\x10-\xe0v\xba\
+\x8e\x7f\xee\x00\x00\x00%tEXtdate:\
+create\x002017-05-1\
+2T12:48:49-04:00\
+7\x98Z=\x00\x00\x00%tEXtdate\
+:modify\x002017-05-\
+12T12:48:48-04:0\
+0\xe0\xb2\xe95\x00\x00\x00\x00IEND\xaeB`\
+\x82\
+\x00\x00\x03\xaa\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x1f\x00\x00\x00%\x08\x04\x00\x00\x00\x10F\xfay\
+\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe8\x06\x0c\x17\x08)`W\
+\xde\xc6\x00\x00\x02\x9dIDATH\xc7\xd5\x96O\x88\
+MQ\x1c\xc7?\xbfs\xee{of(5\x93\xe4O\
+F6\xc8\x86\xcd,\xb0\x18\xb1\xa0\xa6h\xd44\xb2\xb4\
+R,m($Y\xd9\xb3R\x93P6(\xcd\x84\xa6\
+\xa4F\x22QJ\xd1\x10E#\x131\x84\x99w\xcf\xf9\
+Y\x9cs\xef{\xf3\xe6\xdd\x87f!\xf7.o\xdf\xdf\
+\xf7\xfc~\xdf\xef\xef{\xae(\xf3y\xcc\xbc\xd0\xff\x18\
+\x9e\xb4\xfe,RG\xe1A\x1bF%\xda\x0aj\x10@\
+\x10\xc0\xa3\x80\xa2\xf5%\x92\x96P\x83D0\xb1\x84\x07\
+\xa9;CR\x00\xb6\x80\xc5`0\x80\xa0(>k\x81\
+\xd6p,\x90`\xb1\x18l\x0ev\x08)\x8a\x8ad\xfc\
+M\xe0\x92\x81KX\xec\xba\xf6\x83[;\xba\xd3\x89\xab\
+7\x87\xa7H\x11\x0cZc\x9f3:\xb1\x08\x09\x09%\
+J$K*\xc7\xf7.\x5c\x01\xc6O\xbf\xda?D\x1a\
+_\xd5\xd0H\xa3\xeeb\x10,\x96\x12%J\x8b\xda\x8e\
+\xf6\xb7\xafL\xad3\xa9\xad\xac\xee.\xc7!\x16\xe9.\
+\x82\xc9\xc1e\xcaGv,X\xe3\x04D\xad\xab~}\
+\xe3s!\x0bF\x17\xc4\xb2\xe1\xe8'z\xbb6:\xa3\
+\x22j\xbc\xf2\xe86\x12t\xa7\xb9\xee\x124\x8e}\x1f\
+\xeeY\xbe\xc9\x19\x0d\xc7\xd5\x077\xce\xbeD\xa3u\x0a\
+\x84\x13$J\x96\x1cZ\xbfv[j|\xe0v\xf7F\
+\xce\xbf\xc0G\xdeY\xb3n\x84\x83\xc5\x1cX\xb5\xa1\xcf\
+Y\x15\x15\x04?vkh\xd7\xbf\xba\xe3\xfe\xe4\xf9\xaf\x7f\
+\x0f~\x01V>6\x1e\x1b],-\x00\x00\x00%t\
+EXtdate:create\x002\
+024-06-12T23:08:\
+03+00:00\xb3|\xe2\xcb\x00\x00\x00%\
+tEXtdate:modify\x00\
+2024-06-12T23:08\
+:03+00:00\xc2!Zw\x00\x00\x00\
+\x00IEND\xaeB`\x82\
+\x00\x00\x02m\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x03\x00\x00\x00(-\x0fS\
+\x00\x00\x00\x04gAMA\x00\x00\xaf\xc87\x05\x8a\xe9\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\xc3\
+PLTE\xff\xff\xff\xe4\xba/\xdb\xaaO\xca\x93P\
+\xd2\x9d/\xde\xa45\xc2\x821\xc9\x90O\xcb\x93S\x98\
+c \xba\x8b;\xc8\x90N\xd9\xa8P\xd8\xa5j\xf7\xe1\
+\xc9\xf8\xe3\xcb\xca\x95B\xe4\xba/\xf4\xd4\xa7\xf8\xe1\xc5\
+\xf9\xe3\xcc\xf7\xdb\xbd\xf8\xed\xc3\xf8\xe2\xa8\xdd\xael\xf4\
+\xcf\xa5\xfc\xf4\xb3\xf5\xd9h\xf3\xcfo\xd0\x9dY\xdd\xab\
+s\xc9\x91O\xf5\xdc`\xf4\xd6D\xfa\xee\x8e\xe6\xb4]\
+\xd9\x8f;\xbf\x90'\xb9\x89&\xf2\xe6\xbe\xe1\xcb\xb0\xc8\
+\xa9g\x93m\x1e\xf2\xe1\xc8\xfa\xe7\xd1\xb5}4\xc3\x99\
+1\xdc\xc4m\xcf\x9cJ\xc7\x834\xf9\xe3\xcb\xec\xaaa\
+\xe8\x98@\xc2g\x15\xaa`\x16\xb4v,YB\x12\xd4\
+\x9c]\xf5\xcf\xa6\xec\xcf\xa9\xc1\x8bHX@\x13^B\
+\x15\x9ci%\xff\xff\xff'\x8b#\xaf\x00\x00\x00\x0bt\
+RNS\x00\x09\xea\xfd\xdf\x19\xae\xfd\xf7 \xad\xe9Z\
+\xd6\x0b\x00\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\
+\x00\x00\x07tIME\x07\xe1\x05\x0c\x0c00c\x7f\
+\xadt\x00\x00\x00kIDAT\x18\xd3c`\xc0\x0d\
+\x18\x99\x98\xb9YX\x91\x04xx\xf9\xf8\x05\xd8\x10|\
+A!a\x11\x11Qv\x04_L\x5cBRR\x94\x03\
+\xc1\x97\x92\x96\x91\x95\x93G\xe2+(*)\xab\xe0\xe2\
+\xab\xa2\xf1\xd5\xd4Q\xf8\x9cj\x1a\x9a(\xf2Z\xda:\
+\xbaz\xfa\x06\x86p\x07h\x19\x19\x9b\x98\x9a\x99#\x5c\
+haiem\x83\xc4g\xb0\xb5\xb37\xe7\xc2\xe3g\
+\x00jt\x0c\x8d\x13\xc4\x0d\xca\x00\x00\x00%tEX\
+tdate:create\x00201\
+7-05-12T12:48:48\
+-04:00\x91\xefQ\x89\x00\x00\x00%tE\
+Xtdate:modify\x0020\
+17-05-12T12:48:4\
+8-04:00\xe0\xb2\xe95\x00\x00\x00\x19t\
+EXtSoftware\x00Adob\
+e ImageReadyq\xc9e<\
+\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x04\xca\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x1f\x00\x00\x00%\x08\x04\x00\x00\x00\x10F\xfay\
+\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe8\x06\x0c\x17\x08)`W\
+\xde\xc6\x00\x00\x03\xbdIDATH\xc7\xe5\x96=\x88\
+\x9cE\x18\xc7\xff\xcf33\xef\xbb\x9b\xdb\x8f\xbb\x98\x0f\
+\x13\xa3\x81\x88\x1a\x13\x92\xc2\xeb\xd2\xe4\x0a\x0b\x83\xb6{\
+\x88b\xa5\x88\x95\x82\xd8\x08\xc2f;\x1b\x11\x14-\x02\
+~\x14\xa2\xb8WK,7\x85\x82E\x0c\x89\x90\xc6x\
+$rx\xde]\xce\xbb\xdc\xd7\xee\xfb\xce\xc7\xdf\x22\x5c\
+\xd8\x98\xbd \xa4\x11\x9c\xa9\x06\xe6\xf7\xfc\x9fy\x9e\x99\
+?#\xc4\x83\x0c} \xfa?\x8d\xcb\xb4\x81\xe9\xd9\x9e\
+\x85\x81\x81\x996\x90\x7f\x8bK\xc7\xc2\xb4\xccy\x0b\xbb\
+\xe4z\xee\x9a\x9d1]@;:\x1cb\x07|\xda\xc0\
+\xec6\xd7m\xd3\xf9\xcc\xb9\x9as\xaetU{\xcdv\
+L[:C\xb8p$\xdc\x95\x8f\xcd)\x13l\xaes\
+\xb6)5\xdc\xe2R\x9a\x88\xf3\xe1\x95p.M\xa4V\
+\x02w\xc4;\xda\xd6\x9e]\xb2\xbb\x5c\xc3F\x03\xbb\xa1\
+\x15h\xac\xc7\xcd\xb0\x1a\xd6\xfd\x9ep&t\xd8N\xb7\
+\xf7\xda\xd1\xf0\x9a;j\x97s\xba\x94q\xec\xf0\xcbz\
+Lf\x8b\xcf\xcd\xe2\xfet\xc0\xec\x8f=\x01wJ^\
+`\xce\x1b\x9f\xeduky3\xef\xe7f\xd7C\xef\xea\
+S\x09\xc2t\xe5\xcf\xf7\xf6\x0df\xcbGK\xf8\xc9\x88\
+\x91\xea\xd23}Suu\xbb\x96W+\x1b\x95\xbc:\
+\xf1\x96\
+\x8d}\xe9\x99\x90\xc5\x0d\x1f\xcb~\xf9\xd7g\xbc\x02\x92\
+B\x10\xf5\xb1w*\x07WL!\xd1d\x02\xb4G\xa9\
+w\xd3:\x7fI\xbf\xc7\xdc\x87\xb2R\xc4\xad\x85O\xf8\
+\x1b\x92$P(\x8d\xdak9T\x1a\x00\xea;\xb8\x0d\
+?\xe5\x99X\x84\xd27\x8b\xd4\xc7\x00\xb7V?\x90?\
+p[\x9fxB\xa5B\xc0\xc9\xac\xcc\x8cJ\x1e\xe8\xa6\
+\x19\x1e\x09'|(M\x11\xfb\xa9/K\xcb\xef\xcb2\
+\x12Hb.\xf9,E\xfa\xbb\xfan\xce\x0e\xafp\x1c\
+? \xe3\x01lq\x0b9=\x07\x9b\x95K\xe6\xa84\
+ps\xf3C\xbf\xb0\x14\x93_K\x8f\xc5g\xefcV\
+\xd21/\x08\x5c\xb0\xfd\xcc\xb8\x8a]\xb1\xe3`\xbd\xbf\
+\x86\x90\x95\xc9\x07\x7f\xc8\xcf\xc5\x0bi\xdb\xac\xeeuZ\
+\xb6\xe3:g\xc3#\xbe_\xca`\xb3\x9f\xf5W\x06\x0b\
+7\xab}S$/a#]\x1a\xba\xf1\xa3\x8d\x9a\x17\
+\xd2\xd5t\xc8W\xcb\xad\xd2\x15\x18\xa0\xd8;\xb8Y\x16\
+\xe5\xbc\xd7`b+\x82m\xde\x0fG;!u0\x15\
+n\xf9_}\xbd\xdc3\xc8\x06,\x06\xbe\xe5\xbfIg\
+\xc2\x0c:\xc0(\xbb\xf8G\x00B\xae\xe2t\x9a\xc4E\
+\x99\x15`\x85H\x1f\x01\xbc\x8a\xf6p\xb9x\xbf)g\
+\x95J\xed\x9a\xae\xa1R\xcf\xea\xf6%\xd8\x9e\xf2?\xfe\
+\x1e\xfc\x0d}\xcb\xf5\xd6\x9b\xe4\xbc\xa8\x00\x00\x00%t\
+EXtdate:create\x002\
+024-06-12T23:08:\
+03+00:00\xb3|\xe2\xcb\x00\x00\x00%\
+tEXtdate:modify\x00\
+2024-06-12T23:08\
+:03+00:00\xc2!Zw\x00\x00\x00\
+\x00IEND\xaeB`\x82\
+\x00\x00\x03x\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00'\x00\x00\x00'\x08\x06\x00\x00\x00\x8c\xa3Q5\
+\x00\x00\x00\x09pHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\
+\x01\xc7o\xa8d\x00\x00\x00\x19tEXtSof\
+tware\x00www.inksca\
+pe.org\x9b\xee<\x1a\x00\x00\x03\x05ID\
+ATX\x85\xed\x98]HSa\x18\xc7\xff\xef\xd9\x87\
+\xcdl3\xc7R\x0a\xa5\x22\x82\xa4\xbc\x08o\x92\xee\xbc\
+(1\x0a\xa2\x0d+\x09\x0a\xb3\x0f\x89\xc4\x8f\xae\x0c\x91\
+\xc2>\x8d\x89\x10\x91T\x10Y\xa0CCE\x82\x8a\xa2\
+\x06%\x08\xb1\x12/\xac\xcd\xc9\xb4-\xc7\xe6f\x9e\xcd\
+\xcd\x9d\xf3v\xe5\xdc\x9c\x93\xcd\xcdm\x90\xbf\xabs\x9e\
+\xf3\x9c\xe7\xfc\xce\xfb\xf0\xbe\xe7\xf0\x12J)\x02Qu\
+UeP\xc1\x86\xba\xc1qC\xf5o\xde+C\xe2\xb0\
+\x80\x90z_M\xff\x8b\x85\x00Y\x90kjjb\x86\
+\x0bf\xcaAq\x97\x02\xd9C\xe3\x06\xef\x04\xef\x15'\
+P\x0e\x00<\xdc\xac \x8b6\xf6\xba\x00@\x08\x00J\
+Mm1\xddG\x1f\x80\x92\x82\xc5<*J\xb0\x18\x00\
+\xa4a#\xb2\x01\x8c\x01\x80P\xd9]\xd3O\x19R\x0a\
+\x90\xe04\xba4\x90x\x18\x0aR\x9al\x89p0\xc9\
+\x16X\x09a\xac\x05\xf2\xa4\x0a\xbcS\xdd\x8e(\xb7D\
+\xd3\x00\xbd\xc3\xbcbN\xa6\xde\xf8M^\xb1\x87\x8f\x8b\
+\x9c\x88\x11b\xa7,'\xa2\x5c1\x13\xd1\xe3\xfc\xcbW\
+\xccrK\xe1(\x0f\xd3_\xeb\xb2\xd7\xbc\xbc/\xaaZ\
+q\x97\xfb\xc3:\xb0\xab\xfd\x5c\x5cj\xa5\xf4\x84Hi\
+\xb9\xb8\xb7u\x93X\x82\xc6\xa2\xf2\x90\xf8c\xdd\x00\xcc\
+\xac=\xaaZk\x22w\xfd\xc0\xc9\x90x\xaf\xfeK\xd4\
+r\xffW[\xadn'\x0ek\x1aB\xe2\xa3\xf6\x89\xa8\
+k\xc5]n\x9e\xe3\xa0\x9b2\xc4\xa5VJ\xb7u]\
+n\xb5\xa4\xb4\x5c\xcc\x13\xc2\xc2N\xa3\xac\xef\x96\xff\xdc\
+\xed\xf3\xc6Z\xd2O\xccr\xec\xfc\x1c4\xa3\xdax\xb8\
+\x84\x90\xd2m]\x97[-)-\xb7\xec\x84p\xb3,\
+\xbcV\x1b$\xf3\xf1\x9by\x91B8\xce\x7f\x1c$\xc7\
+s\x9c\xc7\xf8\xebg\x9ai\xcc\x08\x9e\xe7\x91\xe8\xbd\x88\
+\xa5,\xc8\xf9\x00\xfat\xf0\xb3\x96\xcc\xb9\xd8\xf3I5\
+\x0a@H\x807\x84#\xb5\x9d\xca\x96\x11\xf9@\xbe:\
+\x92\x9b\x8av\x17\xa2\xedl3\x00\x80\xf5\xb8\xa0\xf9\xda\
+\x87\xf7\xc3Zt\x5cy\x18\x94\xf7\xec\xe3+l\xcb\xda\
+\x8a\xe2\xbd\x07\x01\x00&\xdb$nv\xab1d\xd0E\
+$\xc7t\x1do)\xe9T\xde\x1f\x89\xe6\x8d$b\x09\
+\xb6+r\xd1\xdc\xd3\x8a\xb7?>\xa1\xf1D\x1d\xd2D\
+b\xa8\xd4\x95\x182\xe8\x90\x99.\x85J]\x89\x0em\
+7\xb6H\xe5 \x84\xa0\xac\xf5\x22\xb226\xe3\xce\xa9\
+\xd0\x7f\xbdp\xc4\xf4\x85\xb08\xa7 K\x97\x02\x00x\
+J1j\xd6\xc3\xe9\x9a\xf1\x1f\x07\x22`\x18\x08\x18\x01\
+\xa6\xe7\x1c\x89\x91\xeb\xad\x7f\x0e\x97\xc7\x8d'\x1f^\xe2\
+\xfbx\xf8\xc1\xcf\x91)\xd0Y\xdd\x8e\xecL\x05N\xb7\
+]\x8e\xb8~L\xeb\x5cY\xeb\x05\xe4V\xed\xc7\xb5\x8e\
+\x1b\xe0)\x1f6o\xc2n\xc6\xd1{g@)\xc5\xb1\
+\xc2Ck+\xe7\xf6\xbaa\xb4\x9a`\x9b\x0dm\x91\x83\
+ub\xd2\xbe\xb8Y35c\xc3\xa4\xdd\x02\xa3\xd5\x84\
+\xe6\x1e5j\x8f\x5c\xc2\x0eE^D\xcf!\x81{\xc2\
+\xf2\x8a|5@\xaf\xaeFx-\x08\x1e9BMI\
+\xf2X\x96 9\x91G\xfc\x88\x82\xbe&\x80'YB\
+\x81\xfc\x03\xc6n\x13G\xc1U\xcb\xe6\x00\x00\x00\x00I\
+END\xaeB`\x82\
+\x00\x00\x06+\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00P\x00\x00\x00P\x08\x06\x00\x00\x00\x8e\x11\xf2\xad\
+\x00\x00\x00\x09pHYs\x00\x00\x0b\x12\x00\x00\x0b\x12\
+\x01\xd2\xdd~\xfc\x00\x00\x00\x19tEXtSof\
+tware\x00www.inksca\
+pe.org\x9b\xee<\x1a\x00\x00\x05\xb8ID\
+ATx\x9c\xed\x9d[LTG\x18\xc7\xffsvA\
+\xcbu\x0b\xb8\xa5\x82\xa8\xb1UAcm\xd3Z\xacb\
+\x1b}\xa9ikm\xb9<\xb4&}\x10kl\xe3C\
+QlL\x84\x94^lb\x8d\xd8bb\xa2MSc\
+\xd0\x06\xc5\xc6k\xa5\x8d\x18-M\xac\x15bIQ\xa2\
++\x08\xb2.\xac\x88\xcb\xee\xd2\xbd\x9e\x9d>\x80+\xb8\
+\xb7C\xe7\xb0{\x16\xe6\xf763g\xbe\xf3\xf1\xcb|\
+\x993'\xbb\x0b\xa1\x94B*\x05\xb5\x9b\x9e\xb7\xbb\x1c\
+Ug\xf5\xedK$O\x8aZ\xe8}\x10R'\x8a\xb4\
+\x9c\x96\x9e\xb9\x15\xe8*\x22E`\xfe\xb1O\x9e& \
+\x9f\x01d\xad\xe8\xf1\xd8Ov\xdc\x88\x973U\x85c\
+RQ\x92\xe7\xd8|\xea\x1f\x7f\x83\xea`3\x8b\x8eT\
+\xc4z\x04\xcb\x06\x02\xe1s\x10$\x01\x00(\x15\xc6 \
+I%\xa3\x11A\xf7\x00x\xcd\xdf`@\x19\x05\xb5%\
+oyT\xe6V\x10\xba\xdb+\x0f\x00\x01\x88\xfc9*\
+\x1c\x82ed\xc7\xdb\x89\xfe\x86|V`Am\xc9\x8b\
+\x94\x90JB\xc8R\x7f\x13(\x0d,}\x1cC\xa0\xb6\
+k\x00X\x1e\x1f\xf0\x0a,:R\x92\xe1Q\xa1\x1c\x84\
+\x14\x93 +\x93b\xc2\x95pP\xd4\xabN\xad\x8f\x9b\
+\xe4J\xd8HUd\x1b\x80\x84\xd0S\xe8\xc4+\xe1 \
+\xa8c\x9d\x097(\x90!u\xc2\x04-\xe1\x80\x08\x18\
+\x85\xbc!\xf8\x0a\x1c\x06_M\x8cp\x81\x8cp\x81\x8c\
+\x04=\x89\x84\x93\x9c\xd4\xe9xu\xda|Ycv\x98\
+\x8d8\xd3\xf6\x97,\xb1\x12\xdb\x8d\x1bR\x8as\xfa\x1f\
+\xefW\x8c\xc0\xbc\xccy\xa8Z\xf1\x91\xac1\xebn7\
+\xca&P\xe5vm%\xf0}o\xc0K\x98\x11.\x90\
+\x11.\x90\x11.\x90\x11\xc5l\x22\xa1\xe8\xb2\xf4\xe2\xdd\
+\xe3_\x8cj\x8e\xd9\xf1\xef\x18e\xf3\x88\xa8\x11\xe8\xf4\
+\xb8\xd1\xd4\xa3\x8bt\x1a>\xf0\x12f\x84\x0bd\x84\x0b\
+d\x84\x0bd\x84\x0bd$jv\xe1\x84\x98\xc9X\x93\
+\xb3\x5c\xd2\xb5w\xad}\xa8\xef\xbc:\xc6\x19\x0d\x125\
+\x02\xb5q\x1a\xfc\xb8r\x93\xa4k\xcfu\x5c\x0d\x9b@\
+^\xc2\x8cp\x81\x8cp\x81\x8cp\x81\x8cD\xcd&r\
+\xc7r\x0f+\x8fn\x93t\xed\x80\xcb1\xc6\xd9<\x22\
+j\x04\xba<\x22Z\xfb\xba\x22\x9d\x86\x0f\xbc\x84\x19\xe1\
+\x02\x19\xe1\x02\x19\xe1\x02\x19\xe1\x02\x19\xe1\x02\x19\xe1\x02\
+\x19\xe1\x02\x19\xe1\x02\x19\xe1\x02\x19Q\xccQ\xce0\xd0\
+\x87?\xf4-\x01\xc7\xf5\xd6\xfba\xccF:\x8a\x11x\
+Bw\x09't\x97\x22\x9d\xc6\xa8\xe1%\xcc\x08\x17\xc8\
+\x08\x17\xc8\x08\x17\xc8\x08\x17\xc8\x08\x17\xc8\x08\x17\xc8\x08\
+\x17\xc8\x08\x17\xc8\x08\x17\xc8\x08\x17\xc8\xc8\xa8\xce\xc2\xa2\
+(\xe2N{;\xe2\x0d\x86\xb1\xcaG\xb1\x10\xb7\xc7o\
+\xbf4\x81\x140v\x1bp\xab\xb5\x15v\x9bM9o\
+ \x14@H\x17f\x93\x09\xbak\xd7\xd1oz\x10\x8e\
+|\xa2\x8e`\x02\xef\xb467O1\xe8\xbb&\xfb\xf9\
+\x92\x22g\x08\x7f\x9b\xc8\x00\x01*\x12\xcdI\xb3\x0d]\
+]n./8\xc3W\xa0\x07\xa0\xd5\x82\xa8\xdeRS\
+\xb8\xa3\x1b\x00R\x1b~\x92\xfd\x86\xd9\x19\xcf\x22V\x1d\
+;\xa2\xcf4\xd0\x0f\x83\xa9\x07N\xb7\xcb\xdb\xa7MN\
+Cf\xca\xd4\x90\xf1nv\xb7\xc1\xee\xb4#'s\x8e\
+\xcf\x98\xd1\xdc\x0bc\x7f/D\x8f\xc8\x9ex\x00\x1e\x0a\
+\x08\x9e\x92\x9a\xd5\x95c\xfe\xc1\xe2\x83\x1f\xef\xc1\
+Lm\x96O\xbfKt\xe3\xe7\xcbg\xf0\xf5\xf1\xef\xd0\
+\xd9\xabGQ\xee*T\x14\x96\x86\x8c\x97\xbfk-Z\
+\xef\xeaP_v\xd4\xef\xb8\xd9f\xc1\x81\x0b5\xd8y\
+j/\xac\xf6\x01\xe6\xfc\x1fGM\x08]}\xe4\x9d]\
+\xc7e\x8f,\x81\x9e\xfe{p\x8b\x22\xb4\xc9i\x88Q\
+\xa9Q\xb4x\x15\x96\xccY\x84W\xca\xdf\x84\xcdi\xc3\
+}\xeb\xe0\xc6%\x10\x01O\xc6'{\xe7=\xec\x07\x00\
+\x97\xe8\xf2\x89\xdb\xdcy\x1d\xb1\xea\x18\xcczj\x06\x92\
+\x9eH\xc4\xc6\xd7\xd7b\xfe\xb4\xb9(\xa8,\x96\xfdo\
+\x08\xfa\xf3w\xa9\xc5\xd9\x16H\xfa5#\xe9\x5c\xd9^\
+\xe7]\x81\x85\x95\xebP\xdf\xd2\x80\x8c\x94t\x9c\xdbv\
+\x14S\x92R\x01\x00\x1f\xee\xdb\x8c\xda\xcb\xa7\xbdsf\
+j\xb3pe{\x1d\x00\x80R\x8a\xb4u9#b\xa6\
+k\xb4h\xd9y\xc1\xdb\x9e\xbaa!\x1c.\x07\x16L\
+\xcf\xc1\xf9\xb2Zo\xffs[\x96\xa3\xabO\xdegX\
+E\x9cD\xf4}\xddhl\xfb\xdb\xdbN\xd7he\x89\
+\xdb\xdcq\x0d\x16\x9b\xd5\xdbNI\xd0\xc8\x12w8\x8a\
+\x10\x08\x00\xc9\xc3J\xd4\xe9\xa7,\xff\x0f\xda\xe44\xc4\
+O\x8e\xf3\xb6{-\xf2?\xcbF\xf4P\xb1 +\x1b\
+*A\xc0\xe2\xd9/!\xf7\x99\x17\xbc\xfd\x8dm\xcdL\
+qw\xbcW\x06A \xc8\x9b\x9b\x0b\x81\x0c\xae\x91\x13\
+\x8du\xb8\xfb\xa0\x9b)\xae?\x22*\xb0,\xbfdD\
+\x9bR\x8ao\x7f\xd9\x8f\xa6v6\x81k\xf2\xf2G\xb4\
+\x0f\x5c\xac\xc1\xd6C_1\xc5\x0cDD\x05\xb6\xf5t\
+\xc0b\xb7\xa2\xa7\xbf\x17\xb7zn\xa3\xba\xa1\x16\xd7\xf5\
+7\x99\xe3\xae\xdf_\x8ae\xd9\xb9x\x7f\xe9\xa0\xc8\xf8\
+Iqp\xb8\x9d\xccq\xfd\x11Q\x81\x9f\x1e\xfa\x12\xf5\
+-\x0d\xb2\xc7=\xd9\xf4\x1b~m\xbe\x80\x95\x0bW \
+%A\x83\xfcEo\xe0\x87\xf3\x87\xf1\xa7\xaeI\xf6{\
+)f\x13\x91\x1b\xb3\xcd\x82\xaa\xb3\xdf\x03\x00\x08!\xf8\
+fM9\x16de\xcb~\x9fq+\x10\x00\xf6\xd5W\
+\xa3\xdd\xd8\x09\x00\x98\x979\x07\xbb?\x18\xdd\xaf~H\
+!\xec%|\xec\xf2i\xa4\x0d=0\xeb\x1fH{\xa8\
+\xb5\xd8\xac8\xf8\xfb\xe0Q\xcdC}_l\xda\x9c6\
+\x1c\xb8X\xe3m{\x86\xce\xbev\xa7\x1d\x85\xbb\xd7\xa1\
+\xa2\xa0\x14\xb3\xd2g\xe0\xca\xb0gM\xb9\x08\xfbId\
+\xbc1\xaeK8\x1c\x84\x12h\x0aK\x16QL(\x81\
+\xa7C\x8cOx\x82\x0a\x8c\x89q\x95\x13\x90\x80\xff\xc9\
+\x80\x13B`\xf7^\x9d\xd1\xe9\xa2/S\x82*\x00\xca\
+\xfb\xae\xa9\x02\xf8\x0fl\x04\xc9\xacG\xd5\xc0U\x00\x00\
+\x00\x00IEND\xaeB`\x82\
+\x00\x00\x04\xe4\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x1f\x00\x00\x00%\x08\x04\x00\x00\x00\x10F\xfay\
+\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe8\x06\x0c\x17\x08)`W\
+\xde\xc6\x00\x00\x03\xd7IDATH\xc7\xe5\x96Mh\
+\x9cU\x14\x86\xdfs\x7f\xbeo&\xc9L:\xa5ii\
+\xd3\x9f4\x94 --\xfd\x01A\x11\xed\xc2M@\x97\
+\x137\xba\x10\x11\x14\x5c\xa8\xe0z2[WR\xc1E\
+\xd5\x85\x05\x15&\x82\xcbn\xd3\xa2h\x17\xa5\xd8\xd6\x08\
+Z\xa7?\xa4\xa6$m'\x99\x99\xe4\xfb\xb9\xf7\x9e\xe3\
+\xa2m\x98\xdaI\x10\xba\x11\xfc\xee\xe6.\xees\xdes\
+\xdfsy\xf9H\xf04\x9fz*\xfa?\x8d\xd3\x94\x86\
+\x9e5\xb3\x06\x1a\x1azJ\x83\xfe-Nu\x03]\xd5\
+g\x0d\xcc\x92\x9d\xb5\xd7\xcc\x8cn\x00\xaa\xaezKl\
+\x80Oi\xe8\xad\xfa\x86\x19\xb6.\xb2v\xc8Z\x9b\xdb\
+\xa2\xb9f\xea\xbaF\xf5\x1e\x9c\xa4/\xdc\xa0O\xf5\xf3\
+\xda\x9bX\xcd\x9ba\x1a\xc2\x8a,q%,\xf87\xfc\
+i\xaep\x95!\x1b\xe2uUS\xb3f\xc9\x0c\xd8\xb2\
+\x09\x1a\xa6\xab\x0aP\xa1\x14V\xfd\xb2\xef\xb8m~\xd2\
+\xd7\xa5\xc6\x0f\xce\x9a\xfep\xdb>c\xee\xc5b9*\
+\x15\x9e}-:\x16\xe6\x17\xbf\xbcq{\x07\xef\xd4;\
+\xc2,A6j\x9e\xa0\xcfj\x17\x8d\xd8v<\x1c'\
+1\x0a\xcf}\x18\x1de(\xf1\xcd\xf3\xefoO\x9b\xf9\
+\x9e\x1c\xeeD@_u\x9a\xd5\x89.\xda\x92i\xc7\xc5\
+B\xb70\x10\x1fy\xc7\x1cg(\x01\xcc\xf8\x81\xad\x97\
+\x17\x8b\x1ehR\x93\xaa\xfd\x9c\x9fQK\xea\xb02\xd6\
+Fq\xec\x8a\xaax\xf0\xf5\xc2\x0b\xa2D\xb1b\xe2\xd5\
+\xebI\x11e\xd8\xc7&\xdf\x83\xd7UKA\xdf4\xca\
+\xfaX\x15\xa9p\xe4\xd5\xc1IQ \x90\x10\xb0|&\
+\xf6\x05\x19\x12'@u\xfd\xc6=\xcd\x1f\xa4C\xe4\xd5\
+\x82-\xdb/dyz\xe9s\x7f\x15L\x0c!\xa1\x81]\
+\xef\xc6PT\x06P\xda m\xe43\x99\x0c\x99\xcf\xdd\
+p\xc6\x09R\xd7\x9d;\xc5\xd7\x1f\xea\x8b\xda\xb5k\xb0\
+ \x80\xa5&\xcdP?\x1c\x0d\x9e\x91q\x7f\xd8\xf9\x5c\
+g!\xe1$m\xcd},w\xc0\x10\x08\xb7\xfeZ\x89\
+8\x88{l\xee=\xd6\x01\xc0!\xfc\x88HvbM\
+\xd6\x10\x8b\x93v\xc2\x17\xb7\x1c0\x15Y^\xfc\xe4\xde\
+\xed\xa5\xc0\xae\xcd{\xc3\xcb\x9b\x84\x15\xd5\xf5+\x04\xeb\
+M\x12i[0-\xb3\x05\x03\xa5\xfb\x1dvQ\xce\xce\
+\xbb\xddn>\x9c\xe3Ga\xf5d\xd2J-t\xa4\xe9\
+G]\x92S\xba\x9aDI+m\xde\x8d\xd7t\xc6\x8e\
+|\x97/\xf5\xbc\xf8\xfeA-\xe7x\x8ew\xbbb\xbe\
+\x96\xdb\x0c)\xb2\x91\xf4n\x9e\xe5\x0bNy\x1d\xaa\x01\
+R\x93\xcdp\xd4\x18\x5c\xc7I\xbf\xe2\xfep\xa5|[\
+\x1a\xa5\x92\xa5\xae\xea\xbe\xe5I?\x83:\xd0/.\xfe\
+Q@@sx\x89O\xe0\x225\x09h\x09\xf8\x14 \
+s\xa8\xf5\xda%\x9b-\x9aV\xa2D5tC\x8b\x12\
+5\xad\x1e>\x82\xf5E\xff\xe3\xdf\x83\xbf\x01\xfbS\xf5\
+Hv\xb9\xe0\x9b\x00\x00\x00%tEXtdat\
+e:create\x002024-06\
+-12T23:08:03+00:\
+00\xb3|\xe2\xcb\x00\x00\x00%tEXtda\
+te:modify\x002024-0\
+6-12T23:08:03+00\
+:00\xc2!Zw\x00\x00\x00\x00IEND\xae\
+B`\x82\
+\x00\x00\x04\xc5\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x1f\x00\x00\x00%\x08\x04\x00\x00\x00\x10F\xfay\
+\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe1\x05\x0c\x0c00c\x7f\
+\xadt\x00\x00\x03\xb8IDATH\xc7\xe5VMh\
+]E\x18=\xe7\x9b\xb9\xf7\xbe\x97\x97W\xf3\x1fR\xa3\
+\x0dqe\x8b-UAt\xd3\xb7\x11\x14\xd7\xe9B\x5c\
+(B6\xa2;\xc1]\xbc.EDAAJ]\x09\
+\x22}k\xa9\xbb\xd6M]\xa5B\xeb\x0f\x22\x06\x95\xd4\
+6MIL\xf2^\xee\xcf\xdc\x99\xcfEj\x92g^\
+\x8a\xd0\x8d\xe0\xccf\x16\xf7|\xe7\x9c\x99s\xbf\x19*\
+\xeeg\xc8}\xa1\xffcp\xa6\x02\x81\xb4M\xdb@ \
+\xa9\x80\xff\x16\xceT\xc0\x05\x82\xe7\x0c\x0c\xcc\xa2\x81\x9c\
+\x91\xd4\xe0\x1e%\xf6\xc1S\x1e'\xf8\x86\x85=\x1a\xd5\
+\xed\xacE\xb4\x18\xffj\x8f\x0b$5\xe9!&\xb9{\
+p\x04\xc1\x8bvH\x96\xcd\xb0)d\x84\x9bHB\xe4\
+\xbb!\xf6\xad*\x05\xc2B\xb8\x07{J\xb0m\xbc\x09\
+v*J\xe2\xb1x\xb5\x86\x045\x97\x0c\xc4Y\xbc\x1c\
+\x1d\x973}-\xec\x13u\x99\xa79(j%\xf2I\
+V\x9f\x1c\x1b\xae\x95\xf5F]k\xf5\xf8F4k\x9b\
+L\xcd\xc1\x02{\xe2e\xd1\xdc\x8c\x8c\xcdk\xc3\xf1\xc0\
+\x89\xc6\x9b\x1c\xd3\xdf\xcb\x0fW\x96#g\xca\xac\x18)\
+\x1et+\xd5R\x98\x0b\xd0\xbe\xf0\xb6\x81\x99\xb5Y\x12\
+\xc5Y}\xe2#}\x88\x04\xb8~\xe7\x1d\xb9\xb5]j\
+^\xcf\xe3\xbe\x05zv4\xa2a)\x12a\x9a\x04!\
+::\xfa\x96\x8eK\xdd\xd4}b\xe3\xeb\xd1\x92\x9d\xe3\
+\xd9\x1e\x84y\xfb\xee\xe2\x04s\x06\xf1v;\x92\xa8q\
+\x1a\xe3\xa0\x92\xd4f\xed\xd1\xee\x22\xbcE\xa6F\x83.\
+\xe93:\xd3\x9f}K\x81M\x04-\xd09\xaf\x9bJ\
+P\x05\xc2G&_3\x03yb\xe3\x22z\xd8<&\
+\xcd\x1e\xfe\xbd\x83\x03P\xaa\xf1\x89\x0e\xfb\xfc\x8f\xee{\
+\xd8\xda\xe1\x07yr\xe4\xd5zl\xe2\xc1\xa84W\xed\
+m\xb9\xd0O|\x0b3\xfc\x8e\xc14\xccf\x1cb\xe6\
+\xee\xe7\xe8II@\x100\xd3I\xbc\xfd\xbd\xf3\xa6j\
+\xfaU]\x0d3\xa1\x8f\xf86\x00\xa7\x1b\xda\xf0\x89w\
+p\xb7\xba\x9fh\x07$H\xa5ya\xe8\xb9`\xd4&\
+2\xbd\x93\xd0~;\x7fL\x01\xaf^U\xadV(V\
+\xba\x9fr[\xa9;\x0a^\x9ax\x0a\xe8\x10h\xb2\x8f\
+w`n\xc7\x0d\x0d\xc9\x8a\x00`&X\xfb;\x1aZ\
+T\xb7w\xbe[D\xda\x9f\xfd7F\x1c\x84\x13/\x80\
+g\xe3\xe4\xc0Y%\x15\x80\xa2r\xefo\xfc\xe2B=\
+,\x03X8\xe8=\xe5ev\x04\xd82B\x80\x1c;\
+5\xf0\x22D\x01UE(\xcfu\xae\x89\x8fC\x1e\xc6\
+u>\xf4\x11\xbf\x00`\x9dSb\xd8\x15/C\x8f'\
+\xaf\xa8\xa8\x22\xa8\x22\xb8\xcf\xff\xbc\xe2\xcb\xc4mU\xcd\
+0\x11\xda\xfb\xf4\xda\xbd\xe5\xaa<\x01\xc3\x01\x184N\
+\xd9y\x95\xbb\xd9V\xff\xe5\xdaW\xb60\xa5\xbaA\xf7\
+SX\xd7\x9b\xfdR\xd7&\x10\xb1\xc3\x9c[\xb4/S\
+\xa8P(\x83\xff\xfa\xce\x17\x9a\x87\xcc\x16\xc1\x1d\xab\xe0\
+\x87{\xda\xc6>v\xc0\xe9&\x0aL\x92\xa3T\x04\x10\
+\x08W7\xce\x87,\xce\xb3\xc2\x96\x95\xbb\x1e\xc6C\xab\
+\xa7\xe7\xec\xb2\xcf\xe9\xac\x02Y\x98\xa9\xb6\x8apM5\
+(\x83\xfe\xb8\xf6A\xd5\x1d\xcc\xb2\xfcH\xb1Ve\xee\
+y\xdf\xf2\xbd\xff\xfbnhSN\xf1()%\x84\xf6\
+\x9a\x1d\x81\xf8oV>\xd6\x8e\xcd]9Y\xdc\xa8\x8e\
+\xb8g}\x1aZ\xff\xb8\xd3\xf6\x89o\xe9E\xbd\xe3\x9b\
+2\xe4\xaa\x95\xf5w\x83\xc91\x18P\xd1w\xabU7\
+^\xb5\xaa\xb4O\xb3\xdc\xd7i\xdb\xb2.\xf3\xf2\x99\x9d\
+\xb2\xebf\x5c\x1e`\x07\x1b:]\x15\xc1VW\xfc\xeb\
+\xfe\xac^\xf080\xf6R\xa7?\xe8|H\xf5\xe9*\
+w'\xca\xa8\xb8UH\x1e\x8a\xdbn\xb2<\x1c\xdc#\
+~AS,x\xe0[\x8d\xe9\xd8a]#\xdd\xd0\x99\
+\xb0\x06\xf8\x0b\x87\xdd\xe3\xda;9g\xd4\xa8Qs\xc9\
+^\xb2j\xe6\x8cRq\xf8\xe4\xff\xf8y\xf0\x17\xfb\xea\
+\xe5\xfd\x825>\x0c\x00\x00\x00%tEXtda\
+te:create\x002017-0\
+5-12T12:48:49-04\
+:007\x98Z=\x00\x00\x00%tEXtd\
+ate:modify\x002017-\
+05-12T12:48:48-0\
+4:00\xe0\xb2\xe95\x00\x00\x00\x00IEND\
+\xaeB`\x82\
+\x00\x00\x011\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x0b\x00\x00\x00K\x08\x04\x00\x00\x00\x0c\xfd\xb5\xc1\
+\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe1\x05\x0c\x0c00c\x7f\
+\xadt\x00\x00\x00$IDAT8\xcbcd(c\
+\xf8\x8a\x06\xbf1|ab\xc0\x0aF\x85G\x85G\x85\
+G\x85G\x85G\x85\x07\x810\x00\x1e\xbb\x0a\x9e|\x9c\
+$\x8b\x00\x00\x00%tEXtdate:c\
+reate\x002017-05-12\
+T12:48:49-04:007\
+\x98Z=\x00\x00\x00%tEXtdate:\
+modify\x002017-05-1\
+2T12:48:48-04:00\
+\xe0\xb2\xe95\x00\x00\x00\x00IEND\xaeB`\x82\
+\
+\x00\x00J\xed\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x01\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x5cr\xa8f\
+\x00\x00\x00\xc4zTXtRaw prof\
+ile type exif\x00\x00x\
+\xdamP\xdb\x0d\xc3 \x0c\xfcg\x8a\x8e\xe0\x17\xc6\x8c\
+C\x9aT\xea\x06\x1d\xbf\x06\x9c*D=\xc9\xe7\x0b&\
+\x87\xedt|\xde\xaf\xf4\xe8 \x94$\xb9\x98VUp\
+H\x95J\xcd\x85\xc1D\x1b\x8c \x83O`\xf0r\x9e\
+~\x92<\xb3g\x9e\x05\xd3\xf8\xeb<\xc7\xc5\x06\xb0\xb9\
+\xca\x17#{Fa[\x0bUf&\xbb\x19\xc5C\xdc\
+;\x22\x17{\x18\xd50b\x9a\x05\x0c\x836\xc7\x02\xad\
+V\xae#l\x07\xac\xb0\x19\xa9\x93>a\x0c\x82\xe1~\
+\xff\x96\xe2\xdb\xdb\xb3\xbf\xc3D\x07#\x833\xb3\xce\x06\
+\xb8\x87$nC4\x0f\xf5\x8b~ihs\xce\x5c\xa2\
+\x13_\xc8\xbf=\x9dH_<{Y\xe0r\xbc\xee\xd4\
+\x00\x00\x01{iCCPicc\x00\x00x\x9c}\
+\x91=H\xc3@\x1c\xc5_SE\x91J\x07;Hq\
+\xc8P\x9d\xec\xa2\x22\x82K\xadB\x11*\x84Z\xa1U\
+\x07\x93K\xbf\xa0IC\x92\xe2\xe2(\xb8\x16\x1c\xfcX\
+\xac:\xb88\xeb\xea\xe0*\x08\x82\x1f \xce\x0eN\x8a\
+.R\xe2\xff\x92B\x8b\x18\x0f\x8e\xfb\xf1\xee\xde\xe3\xee\
+\x1d 4\xabL\xb3z\x12\x80\xa6\xdbf&\x95\x14s\
+\xf9U\xb1\xef\x15\x02\xa2\x08\x03\x98\x95\x99e\xccIR\
+\x1a\xbe\xe3\xeb\x1e\x01\xbe\xde\xc5y\x96\xff\xb9?\xc7\xa0\
+Z\xb0\x18\x10\x10\x89\x13\xcc0m\xe2\x0d\xe2\xe9M\xdb\
+\xe0\xbcO\x1caeY%>'\x1e7\xe9\x82\xc4\x8f\
+\x5cW<~\xe3\x5crY\xe0\x99\x113\x9b\x99'\x8e\
+\x10\x8b\xa5.V\xba\x98\x95M\x8dx\x8a8\xa6j:\
+\xe5\x0b9\x8fU\xce[\x9c\xb5j\x9d\xb5\xef\xc9_\x18\
+*\xe8+\xcb\x5c\xa79\x82\x14\x16\xb1\x04\x09\x22\x14\xd4\
+QA\x156\xe2\xb4\xea\xa4X\xc8\xd0~\xd2\xc7\x1fu\
+\xfd\x12\xb9\x14rU\xc0\xc8\xb1\x80\x1a4\xc8\xae\x1f\xfc\
+\x0f~wk\x15''\xbc\xa4P\x12\xe8}q\x9c\x8f\
+Q\xa0o\x17h5\x1c\xe7\xfb\xd8qZ'@\xf0\x19\
+\xb8\xd2;\xfeZ\x13\x98\xf9$\xbd\xd1\xd1bG@x\
+\x1b\xb8\xb8\xeeh\xca\x1ep\xb9\x03\x0c?\x19\xb2)\xbb\
+R\x90\xa6P,\x02\xefg\xf4My`\xe8\x16\x18X\
+\xf3zk\xef\xe3\xf4\x01\xc8RW\xe9\x1b\xe0\xe0\x10\x18\
++Q\xf6\xba\xcf\xbb\xfb\xbb{\xfb\xf7L\xbb\xbf\x1f\x9c\
+ar\xb7\x1d\xc5\xe8\xd2\x00\x00\x0e\xb7iTXtX\
+ML:com.adobe.xmp\
+\x00\x00\x00\x00\x00\x0a\x0a \
+\x0a \x0a \x0a \
+\x0a <\
+rdf:li\x0a stE\
+vt:action=\x22saved\
+\x22\x0a stEvt:ch\
+anged=\x22/\x22\x0a \
+stEvt:instanceID\
+=\x22xmp.iid:039265\
+64-4492-4141-bac\
+0-a675ce7af3de\x22\x0a\
+ stEvt:soft\
+wareAgent=\x22Gimp \
+2.10 (Mac OS)\x22\x0a \
+ stEvt:when=\
+\x222022-03-02T15:3\
+6:31-05:00\x22/>\x0a \
+ \x0a \
+\x0a \x0a \x0a \x0a\x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+\x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a\
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \
+ \
+ \
+ \
+ \x0a \
+ \
+ \x0a[Q\xc1\xc2\x00\x00\x00\x06bK\
+GD\x00\x00\x00\x00\x00\x00\xf9C\xbb\x7f\x00\x00\x00\x09\
+pHYs\x00\x00\x16%\x00\x00\x16%\x01IR$\
+\xf0\x00\x00\x00\x07tIME\x07\xe8\x02\x1a\x0f\x1c:\
+X\x80\x14\x06\x00\x00 \x00IDATx\xda\xed}\
+k\x8ce\xd9U\xde\xb7\xf79U]\xfd\x98\x99\x9e\xee\
+\x1e\xdb3x\x06\xbf\xb0\xc6\x9e8Cd\xf32\x89\xc1\
+`\x91`\x04H\x01!\x85\x04\x12\x92\xfc!$\x8a\x10\
+\x09!$\x80B\x84!R\x92\x1f\x84D(\x80!\xe0\
+\x04\x8d\x1c\xb0\x8c\x9d \xd9\x82\x10\x83\xf1\x0f\x07l0\
+\xf6\xd8n\x8f\xa7g\xdc\xf3\xea\xee\xe9\xc7TwUW\
+\xdd{\xf6\xce\x8f{\xcf\xa9}\xf6]k\xed}\xce=\
+\xf7Q\xd5k\xb5J]u\xefy\xec\xb3\xcf^\xafo\
+=6\xa0\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\
+\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4t\xd8\xc9\xdc!\xf7TR\
+:,\xe4\x0f#3\x9a\xe8w\xa3\xcc\xaf\xa44\x98\x10\
+\xf0\x99\xdfu\xa6r^\xa6/\x8a\x02_\xfd\xd5_]\
+<\xf8\xe0\x83w\x1bc\xb6\x00\xd8\xf1xl\xf5\x1d*\
+)\x0d\xac\xad\x8d\xf1\xde\xfb\xd1\xde\xde\xde\xee\xa7?\xfd\
+\xe9\x9d'\x9f|\xb2\x9aW\x10\x94\xf30?\x00\xfb\xc8\
+#\x8f\x94\xaf\x7f\xfd\xeb_\xb3\xb9\xb9\xf9C\x80\x7f\x9b\
+s\xfe\x1c\x00\xbb\xa4\x09\x99<\xb9\xf7k}\xcd\xc38\
+\x0fG\x88iZs\xd3u\xae\xa8\xe3\xfb\xccwxN\
+\xfd{\xc7kx\xef}e\x8cy\xaa\xaa\xaa\x0f>\xfa\
+\xe8\xa3\x8f\x1d;v\xec\xd9\xcf}\xeesc\xef\xbd\xeb\
++\x04\xca9\x98\xbf8w\xee\x5c\xf9\xf0\xc3\x0f\xbfr\
+kk\xeb\xbf8\xe7\xdebmq\xa2(\x8c5\xc6t\
+\x9a\xe0\xf0\xe5\xc4\x13]O\x18\xf5\x7f\xeaz\xd4\xdf\xd2\
+K\xe5\xfeN]#\xe7\x19\xa8g\xa2\xce\x8f\xaf\x9fZ\
+$=\x17\x139\xbf}\x8e\xa1\x9eOb\x98\xd4\xda\xe0\
+\xe6r\xde\xfb\xe7\xde\xb3\xcbz\x1d\xea\x1a]\xceu\xce\
+\xbd\x1c\xc0\x1b\xee={\xef_~\xf3[\xde\xfc\xe3\xbb\
+\xbb\xbb\xcf=\xf5\xd4S\xe3)\xf3\xbb\xaeB\xa0\xaf\x05\
+`\x01\x14o|\xe3\x1bO\x9c:u\xea;\x9ds_\
+y\xfc\xf8\xf1S\xf7\xdf\x7f?\xb6\xb6\xb6Z\x8b2\xe7\
+\x85RL\x11\x9e\xef\xdcD\xc0Yk\xe1\x9c\x831F\
+<\x9fc\x86\xfa\xb8\xf8\xd8\x9c\xfbs\xf7\x0c\x8f\xb1\xd6\
+6\x9f\xd5\xff\x87\x9fQ\xe7\x84\xd7\xa5\xc6\x9fz~\xef\
+=\x8c5\x80o\x0b\x03j\xcc9\x82 >\xbf\xcb\xfc\
+\x85\x0c\x98\x9a\xb3\xf09\xeb9\xa2\x84M\x9f\xf5#\xcd\
+_|N|\x7f\xea|\xee\x9c\xfa\xba\xe1\xfd\xc35\x14\
+_\x93\x9bK\xe9\xfe\x11\xf3cgg\xa7|\xfe\xf9\xe7\
+O\xef\xee\xee~KY\x94\x7f\xfe\xd0C\x0f\xfd\xd7\xa7\
+\x9ez\xea&\x80jY.\x80\x99\x9eg\x8a\xa28e\
+\x8c\xf9Zk\xed\xf13g\xce\xe0\xc4\x89\x13\xb0\xf6\xc0\
+\xfaw\xde\xc1\x1a\x0bc\x0c*W5\xbfK\x0b\xc2y\
+\x87\xc2\x16\xadI-\x8a\x02\x95\xabP\xd8\xa2\xb9~8\
+\xd1\xce;\x94E\xd9\xdc\x83\xbav}]\xc9\x14\xac\xc7\
+[\x7f__/\xbc?\xb78\x8a\xa2`\x85Nl\xfa\
+\xc5\xe7\xd4\xe3\xe7\xe6\xa2>.\x9e\x0b\x00\xa8\x5cu \
+\x1c\x1aK\x10\xd8\xd8\xd8\x98\x99\xf3\xfa\xfe\xf5\xf9\xe4\x22\
+\x9b\xce\x01u\x9f\xf8Z\xf5\xf9\xf1\xfc;\xef\xb0\xb1\xb1\
+A\xde\x93\x9b\x8b\xf0y)\xa6\x0e\xefO\x8d\xbf\xfe\x1c\
+\xc0\xccq\xf5\xdc\x84\xcfR\x9fO\xad\xd1x\x1c\xb1\xf5\
+\x12\xbf\x17\xe7\x1dJ[\xb6\xe6\xa3\xb0E\xeb\xbd\x86\x02\
+\x22V\x12\xe13\xd6\xe3\xa7\xd6OY\x96(\xca\x02U\
+U\x99g\x9f}\xf6\x1e\xe7\xdcw\x96e\xf9\x18\x80\x9d\
+\xc0\x05\xe8d\x05\x94=\x98\xbf\xfe\xd9\xac\xaa\xea\x98\xf7\
+\xfee\xde\xfb\xa2,Kx\xf8F*6\xcci\x5c\xa3\
+\xa5\x9a\xef\x0c\xe0\xddTs\xcdx.\x06\x0e\x0e\xbe\x8a\
+\xa4\xa7\x01\xaa\xaaj\xae\x15\x7f_U\xd5\x81\xe4\xc5\xc1\
+\x04\x1bk\x9a{5\xe7\xd7\xe7\x99\xc8s2\x13)\x0b\
+\x03\xf8j\xfa2\x83\xf1\x8f\xc7\xe3\xf6\xe2d\xc6\x0f\xe3\
+[\x9f5\xc7:\x90Z\xc1{\x8f\xcaW\x93\xf1\xf8\xf6\
+\xf9\xe137\xe7O\xc7\xd2\x12\x82f\xaa\x8d`\xe0\xe1\
+\x9b\xb1\x86\xe3o\xe6\xcc`r~8~S?\xc7d\
+\xfe\xe1\xda\xf3Z\xcf\xff\x0csN\xcf\xab\xaa\xaa5\x7f\
+\xe1y\xd4\xb3\x84c\x09\xd7\x82s\x1e0\xbe\x99\xff\xe6\
+\xfd\x19\x03\xe7\xdd\xec\xf3\x9b`\xfc\x98\xcc_=\xfef\
+^\xa7\xe3\x08\xcfi\xc67e\xd8\xd6\xfb\x9f2#\xe7\
+\xb7\xc7\xe7\x87\xf71\x98\x5c\xbf\xbef\xcc\xe4\x94[U\
+3~a\x0bTU5\x19\xbf\x9f]\xbf\xf5\x9c\x9c<\
+y\x12\xc6\x98\xc2\x18\xf3\x90\xf7~\x0b\xc0&\x80\xbd\xa9\
+e\xee\x16m\x01\x14\xd3\x9fr4\x1am\x183\x11W\
+\xde{x\xe7g\x02}\x8d\xb4s~\x06\xafl}\xd6\
+L\xce\xc1\xe7-\x0d\x15\xfeJ}\xdf\x1c\xe6\x93\xf7o\
+\xce\xe3\x02*>5~?Yw\xce\xd3\x11\x1c\xe2\xe3\
+\xf8\xfe&\x8e\xed\xf8\xfa\xbc\x8c\xf3\x89\xe7o\xc6\x99x\
+\xfe\x83\xfb\xd0\xef\x84\xbb?9'\xde\xd3\x01*/c\
+\xd3\xf2X|\xfa]\x13\xe7S\xe3oY\x81\x1e\xf4\x98\
+\x85\x80\x1a5\xbf\xec3\xf9\x03\x81\x05?\xb1\xc8Bk\
+\x95r\x05\x1a\x17\x00\x13w\xc2{\xdf\x16h>x~\
+GbD\xe5x<\xde\x08x\xd2\xb5E\xee\xe2,\x80\
+\x02\xc0\xc6\xde\xde\xdefUU\xb66\x85\x86@\xa1\x07\
+G\xb2\x17\x00\x8c\x07\x06C\xef\xcb\x0f1,\x83%g\
+\x8d\xac\x09\xf9U\xad%\xe1>-\xf3\xde`\x86\xf1c\
+!\x10Z\x155~T[\x1f-\xc1\xc8<\xc3\xf4s\
+3\x1a\x8d6\xa7\x16@5\xfd\xe9\x94k\xd3\x17\x03\xb0\
+\x98\xc4\xfb7\xbd\xf7E(\xcdB\xf0\xab\x06\x82(\xd4\
+;\x06\xc2r&8\x06\x8d(?I:6\x1cK8\
+\xb6\x10\x84\xa1\x00\xa3\xf0\x98\x10\x00\xe2\xa4zx~\x08\
+\x10\xc6\xe0\x1a\xf5\xfc\x12\xb0\x15\xfb\x8f\xf55Csr\
+\xf2\xbd\x811\x10\x81\xc7\xf09\xe2g\xe5\xdeU\xfc\xfc\
+\xf1{\x90\xde\x1ful\xea\xfdS\x00\x19\xf5NX\x90\
+r\xaaI\xc3\xb9\xe2\x9e\x97\xc2$\xa8\xf5\x13\xbf?\x0a\
+\xd440(\xca\x09\xf6`\x00\x18;\xc1T\xe2\xe7t\
+\xce5\xd7\xab\xd7\x9d\x1bG.41?\xe13VU\
+U[\x00\x16r\x12\xde \x02\xc0\x06?\x9b\xe3\xf1\xb8\
+\xa8\xaa\xca\xd4\x0fSU\xb5\xdfd\xe0}\xd5L\x86G\
+{!\xc5\xbe\xe4\xec\x027\xb0\xd6\xb4\xce\x0f\x99'\xf6\
+\xc5)\x19e\xa7\x98\x83w\x13L\xc0Z\xdb\x8c/\xf6\
+\xcb\xea\xeb\x85\xe7\xc7\xe3\x0f\xef3\x83\x05\xcc<\x87=\
+\xf0w\x83\xf17 Q\x84\x1e\xcf\x82\x92\x06\xc6\xf8\x99\
+\xf9\x8b\x05B}\x8f\xd9\xb9\x98\x08\x00j\xfe\xc3q\xd4\
+\xe7\xc7\xd6\xdb\xc4\xab\x9b2\xbb\xb1mL\xc5\x1c\xf8\xdf\
+\xe1\xfd\xdb\xc2\xd84\xe7S\xf3\xc7\x01\xb1\x07\x9f[\x98\
+\xda\x1f\xf7\x13\x7f:\x16\xd6\xe1\x98g\xdf\xc1\xec\xfc\x85\
+LS\xbf?\xeaZ\xf1\xfc\xd5\xeb\x87R\x1e\xe1\xbdk\
+\x01c\xadEQ\x940\xa5\x87-\x1d\xca\xa2\x00,`\
+M\x10\x15\xa9\x85\x8b\xf7p\x1e\xa8*\x07\xe3<\xb0g\
+\x1a\x1c\xc8{\xd7\xac_\x87\xb6\x22\x0d\xd7\xf1x<\xde\
+\x9c\xf2q\x11at~\x11\x02\xc0\x04\x82\xc0\x00(\xcd\
+\x84\xa2\x17\xea\xe0\xa7a\xa9x\xf2\x9ds\x07@Id\
+!\x1c\xbc\x04?\x99\x14\xd3>\xae\x11\x02\xd3\xf3\xa9\x17\
+R\xdf\xbf\xaa\xda\xf7o$\xacp\xff\xf0\xfcz\xfc5\
+H\xd5\x0aS\xc14\xa0&\x89\x8a\xbb@\xc0M\xef\x1f\
+\xbe\xb4\x9a\xa9\xc2\xf3\xdb\x12\x9e\x9e\xbfF\x1by\xcc\xdc\
+\x9f:?^\xe0\xe1\xf8C\xcd8\x13}\x88\xc6\x1f\x0a\
+\xacz\xfe\xeaq\xc5!\xb0\xc9\xbd\xdc\xcc\xf3\xcf\xd8\xf0\
+\xc2\xfcS\xf3\x17*\x8f.\xef\x8f[\x7f5(\x1c\x9f\
+\x7f\xa0\x8dMdU\xd1\xcf\xdf\x02\xf8\xa6\xacWlx\
+\xd8\xfb\x9fGq\xe6%\x18;\x01\xb6<\x0c`M\x9b\
++=`\xbcC\xe1=\xdc\xa8\x80\xfb\xfckQT\x05\
+*_5\xe3wp3\xf7\x0f\xde\xa9\x89\x94\xb2]\x86\
+\x0bP\xdf\xa8p\xce\x15\x00l=\xa8Z3\xb4^P\
+\xa0\x81Z\x9a\xcf\x09\x89\x19\xc4\xf931T\xe7\xe9d\
+\xa1\xe9\xb9!\xb3K\xe7\xf7\xbe\xff\xb8\xff\xf9\xdex9\
+\xc9\x889\xbf5\x7fc/&\x05\xd5\x96JlA4\
+s\xe5\xda\xd7\x9cw\xfes\xcfo\x94D\xc6\xfc\xd7\xc2\
+.\xe7\xfd\xb5\xd6Zb\xfc\xd6ZT\xe3*\x99\x17\x11\
+[p\xdc\xf379\x0f\xd3\x7f(<\x8a\xfb\xae\xc0\xdf\
+\xff4\xaa\xa2\x0a4\xe7\xf4\xfb\x06\xac\xad\x7fw\xc0\xde\
+\x09\xd8\x0b\xafA\xb1_6Q\x81\xc6\x02\xf0\x8e\x04Q\
+\xa7\xf7.\x22\xed\xbf\xf0<\x80F\x088\xe7\xec\xac\xff\
+9}8\xdfF\xe5c?N\xf2\x1d\xa9\xf3S\xe8,\
+u\xef\x96\xcdB\x1c;\xc4\xfdc\xc6k\xc2\x90\x1d\xc7\
+?\xcf\xfd\x93\xd7\x22\xee)%\x9d\xf4\x1d\x7f\xb8\xb8S\
+Q\x89\xdc\xf9\xef\x02\xee5\xcf\x9a\x18\x7f\x83\xb8g\x01\
+\x8e\x9e\xf4\xf39\xc1U\xd8\x02\xd6x\xd8\xc2\xc3\x15c\
+\xc0Vd\x80\xc4\xc0N\x18\xbf\x89\xad\x8da\xadAe\
+f\xf32\xa8\xfc\x90\x80\x8fl\xe4\xff\x9be\x08\x80`\
+\xed\x1b\xe3\xbd7\xcd\xa4\x06\xc0\x0b\x05^I\xa9\xa2\xa1\
+y\xd8'-\xb7\xcf\xf9!@\xd4\x98ea\xce\xc2\x82\
+\xef\xdf\xe5|.\xb3\xac\xc5\xccu\xcc8\x91~\xbc\x88\
+\xf17\x8cg\xc0\x82e\x8b\x98\xbf\x96\x1b\x93q~l\
+\xf5\xc4\xf7\xaf-\xb4\xd8\xba\x8c\x01C2\xdc:I\xc9\
+\x14\x1d\xf0\x9a\xf9\xcd\xb4df\xf2\xfbA\xa4\xc0\x1a\x8b\
+\xcaW\x8d\xa0\xa2\xd6b\xa0\xcczi\xfey\xc3\x80\xad\
+\xdf\x9b\x0c\xa9\xc0\x14\xa2,\x83x\x229\x1f6\x8e\xe3\
+r\xe7\xc7\xc8}(\x89s\xee\x1f\x83*\xcd\xf9.\xff\
+|\x92\xa1@\x03FC\x8c?\x06\xbd\xc2\xf3C\xf4;\
+>\x9f\x8b\x1a\x0c1\x7fq\xf2W(P\x97\xf5\xfe\xba\
+\x9e\x1fG\x22Z8\x00\xda\xa9\xdd\x5c\x9as\x08\x00\x86\
+\xd70\x06\x0d\xd8\xd76D'\x8c_k\xfe\xb6\x100\
+\xb0\xf5\xf5\xbdka\x17\xf1\xf8\x03W\xd0Dv\xee\xd2\
+\x5c\x00C\x85%\xe2xf\xbc((\xc9K}\xc6\xa5\
+\xd4\xa6\xce\xe7\xee\x93s\xac\xa4!r\xc6Je|\xc5\
+&w\xee\xf3\xc7\x0b2\xf7\xf9C\x13\x97\x1a\xff\x0c\xa3\
+t\xb8&\xc9(\x91\xf0i\xfc\xeb\xaa\xea\xf4\xfeBf\
+\xcd}\x7f\xd2\x98b\x81\xdee\xfd\x84\x9f\xc5Q\x16n\
+L3k\xd6\xcfB\xf1S\xc4e\xc6\xfc\xaf\xb5\x7f\x83\
+\x12\x04\x09@5O\xcd\x84\x0a\xa39\xef\xcb\xfc]\x05\
+@R\xda4hw\x00\xc4I\x8b\x92\xf4Y\x09\xad\xca\
+\x09\x05\xae`B\x0a5Icj\xce!\xc6\x9f{\xff\
+:l\xc4\xe5(Hc\xca\xf5\xedCMD\xdd'\x1b\
+\x1f\xe8\xf1\x19g\x954sZ\xb9\x16\x10'\xb9{\xb9\
+\xc2+\xf7\xfd\x85\xf3\xdfw\xfd\xc4\xc0#\x97\x1f\xc0)\
+\x81I*t\xad\xe9g\xe3q\x86p\x07\x1a\xac\x01\x80\
+\xf7\x04\xae\xe5gS\xec\x13\x8c\xbf\xb00 )\x14b\
+I\x95\x02q\xbaR\xe72W\x22\xf6\xbe\xe8{v\xb9\
+N6\x00\xd5\x93\x99)\xe4\xba\xef\xfc\xa5\xee-\x02r\
+\xbe\xdf3\xf5\x19\xbf\x08*\xcf\xf1.s\xce\xad\xaa\xaa\
+U\x18\xe4\xbd\x81\x81\x9d2w\xc5Z\x02M\x04 \xf8\
+.\xce\xd1\x08\xc3~\x891-\xdc\x02\xc8\x0a\x9b\xc4\xe6\
+\x94T\x12\xc9\xa1\xd2}\x8em\x81\x5c=\x16\x7f\xcb\xcf\
+\x22\x90k\xae\xb1\xc4\x90\x02\x22\x09\x90\xf6|~n\xcc\
+\x5ca\x12\x97\xb2\x9a;\x7f\xa9\xf7\x9f\xbaG8\xfe\xdc\
+uD=c\x97\xb1K@$w\xcd0\x8f\xa2V\x80\
+\xde\x1d\xfc\x8e\xc0\xefO\x03\x82\x06\xdeUp\xce\xb6\x5c\
+jJ\x00pV\xd2\xca\x04@=\xd8\xaa\xaaf\x00\xa0\
+Z\x9aQ5\xcf\x14\x8e\x90Z\xdc\x929M\xbd(\x09\
+\xbd\xa5\xee\x17\xc6\xda%f\xccM\x81\x95R\xa1\xb9\xc6\
+ \xd4\xb3\xa6j\xf5c\x93\x91K/\x96\xcch\xce\x8d\
+\x88\xdfk\x8a\xf1\xe31p\xf7\x88\x8f\xe1\x84l\xfcN\
+D\xe1\xed=\xdbD\xa6\x93\xa6\xf7`\xe7\x9b\xc2}\xea\
+9*\x8a\x02\xe3\xb1C\xe9=\x0c\x1c\xa8\xe2\xc8\x0dhA\x7f\x1e\xf0{\xc7\
+PM\x05\xc0h4j\xb9\xd5\xb9\x19\xac+w\x018\
+\xd31\xd5\x18\x93\xea\x1f\x972\xf7$\xe0\x89\xbao\xaa\
+\x07a\xaa\xaf\x5c\x0a8\xe34<%\xe0$\x97\x83\x13\
+\x869\xc2\x82s]8\xf3\x95\xd2j1\x86\x933\x1f\
+)\x9f[\xca\xbc\xe4\x84\x07W\xe8%Y\x05\x12`\x9b\
+\xf3\xbb$d%\xcbi\xa6\xa2\xd0\x94x\xe9\x13\xaf\xc4\
+\xb1\xf3\x0f\xc1\x16\x16\xe5\xb4-\x99\xb5f\x9a(\x07T\
+nR\xb04\xaa\xc6\xf0\xaeB5\xf2\xd8{\x09\xd8\xdf\
+\xdb\x83\xf7\x1e\xa3\xd1h\x06\x00\xa4* \xd7\xca\x05\x88\
+\xb3\xce8sK\xaa?\x0f\x85A\x98X$-d\xca\
+\xec\xa7p\x03\x0eP\x0b\xc7C%\x86P\x0b&f6\
+\x0a\x9c\xe4\xfa!\x84\xe7s\xcdD\xc3\xf3k\x13\x93\xba\
+&\x95\x98\x13W\xf9Q\xcd'\xe2\xef\xe2$\x13\xeaY\
+\xa9\xe7\x8e\xdf\x117\x7f\xdc{\x90\x18>\xa7\x13t|\
+<\xd7{\x8f\xb2\x22ca\x19^#\xfc.\x04\xb1\xa9\
+g\x0b3\x01\xeb\x9f\xa2*\xe0v\x0b\x14\xd6\xc2\x16u\
+?\x00\x0b\x1b\x94\x03O\x5c=\xa0\xaa,\x5cUa4\
+\x1a\xa1\xaa\xaa\xc6\xfc?8f\x16'\x19\xca\x22(\x87\
+d\xfe\xf0\xe5\xc7\x05\x17\xe1$S\xfe-WZ\x1a\x1f\
+\x9b\x13\x86\xa3&)^\xf4\xe1K\x0e\xfb\xebIZ\x84\
+\xcap\xa3:\xc3r\xcfF\xb9Ma\xcc7\xe8\x8c\xe5\x9c\x83\xab\x0e\x94QQ\x14\xcd\
+:\x0c\xd7M\x88\xf8\xd7\xc0_\x1c\x02\x8c\xf9im\xa3\
+\x009\xfen\xb2\x92.\x81\xd6\xe6\xa4\xder/\x94\x0a\
+OR\x96En\x9c>D\x80\xe3qI\xe1\xc4\x1c\xbf\
+S\xf2\x9dS\xccK\x81\xa8\x1c\x83u\x99/\x09\xc4\xcc\
+\xc1@Rk\x81\x13\x06T\x9a6\x97QHY\x03\xa9\
+kJ\xf3\xcb\xad\x0f\xcaR\x8b\xad\x80\xf1x<\xc3\xf8\
+\xd4\x18\xc2\xc6,\x14\xf2O\xb9zC\x09\x81\xc1\x04@\
+Nk/)L\x95\xf3}\x9f\xac\xba\x14Z:d\xea\
+l\x9f\xe7\xeb\x1b\xdf\xcd\xb1\x0223\xc8z\xbd\xab\x9c\
+\xeb\xa5\x22\x0e\xa99\xe9\xba\x16\xb8\x9c\x80>\xeb'\xbb\
+t\x9c\xf0\xcf\xeb\x02\xa1\xb8\xfb\x15'lB\xad\x9f\x0b\
+\x88rU\x89+\x15\x009\xc9>]\x16L\xea\xd8\xdc\
+\xc4\x90\xbe\xd6L\xd7\x09\x9e\xf7\xf9R\x89C]\x05X\
+\xcad\xe64\xa4\x04\xce\xa5\xe6)e~\xe7D4\xb8\
+\xe8P\x97\xf7\xd4\xf5\xbdK\xcf\x91\x9b\x05\x9a\xb2T$\
+k\x90\xcaYH%\xcd\xadU\x180\x94`\x1c\x12,\
+-\xea\x1c$Y\xdabJB\xb9\xfb\xde?\x07i\xee\
+:\xfeTZnN\xd6\x5c\x97\xfbs@\xa0\xe4\x0et\
+\x9d\xbf\xdcH@\xce\xf3s\xe7S\xe0`\x9f\xfb\xf7y\
+\xff\xf1;\xe12\x03)+*7\x15\xb9\xeb\xf8\xd7\x0a\
+\x030\xc6`sss\xa6\xf6]2Y8\xd0,\xf6\
+\x95\xba\x9e\xdf\xf7\xfe5\xe5\xdc\x9fC\xe0s\xc6\x9fJ\
+Q\xa52\x00%\x93\x8fz~\xaa\xcb-\xf7\xfc\xe1w\
+]\xe6Oz\x0f\x5c#\x10N{RZ\x92*\xe7\xe5\
+\xb0\x87\xf8\xbc\xdc{P\xcfK\xdd_\x1aK\x17@:\
+\xc5C9\xcfo\x8c\xc1\xc6\xc6\x06\xf6\xf7\xf7\xd7\xc7\x05\
+(\x8a\x02\xe7\xce\x9dk\xb2\xa1(\xa0NBP\xa9\x9e\
+\xe9]L\xcf\x1c\x939\x15\xc3\xce\xc9\xb5\xcf\xd5&9\
+\xe6}.\x98\x16\xb7\xb4\x8eC\xa4]Mu\xca2\xb0\
+\xc6\xb6\x9a\xacR-\xb3\xb9h\xcd\x22\x92SR\xd9\x92\
+\x92\xa5&\xbd\xef\x5c\xd7\xa9\x0bc\x89y!Q?\xc6\
+\x1c\x9cE\xaa\x844\xc6\xa0,Klll`gg\
+\x87\xe8f\xbd\xc20`Q\x14\xb8\xfb\xee\xbb\xb3\xfcM\
+9\xad\x17M\xe6Tn?\xf8\x96&\x83I\xf6\xa1\x0b\
+\xf7x\xab'=\xd6\x84-|\xc3\xbbf\xf3\xcd\xba\xd6\
+\xbdu\x8c\x89^\x5c\xb8UU3\x88\x94P\xb03\x07\
+\xe5\xec\x22\xbc\x08\xcaI\xe1\xcd-\xdf\xcd\xad\x97\x90\x5c\
+\xba\xae\xc0n\x8a\xd1\xbb\x82\x91\xb98Dn\x22W\xdf\
+\xf1\xe7\x84/W\x86\x01\xc4\xa6g\x8e\xd6Li}I\
+\xcb\xc7\xa6r\xcb\xfd J\xa3\xe3\x0d%\xad\xb5\xb0\xde\
+\x8aYv\xb5\x80\xb0\xc66\x1b6\xd6\xc2\xa0\x0e\xef\x84\
+B\xc2\xf8\xe9}\xa7C\xab7\x9al\x09\x80\xc4\xbe\x88\
+\x07\xff\x1fl\x13\xc5\x99\xaa\xec\x8e\xc6\xb5\x0c\x9a\x13\x04\
+\xa5\xdc\x91\xd8\xd5\x09\x85@\xee\x02\xa6\x92p\xbc\xf7\x93\
+\xf7\x03\xcf\xae\x81\xd0j\xabQv\xaa^$\xe5\x9fs\
+\xcf\x96\xda\xa58\xc4\x00\xa8$\xa2\xf8\xbc8\x89\xad\xcb\
+\xdcR\xe7\x85\x02`\xa8(\x80\x1d^\x7f\xc8\xa6\xd8\xcc\
+\xa2\xf5\xed\x98y\xb8\x90\xb8\x18v\x9c\x98!\xf9\xbe5\
+~S\xef\xf6:\xd3bYjP\x89I\xbdv\x8b\xf9\
+\xd1N\xe6p\xde5B\x01f\xfa\x9dG\xfb^\x81E\
+\xd00\xbfo\xef{\xd7l)e\xd0\xf4\xd5\x87\xe7}\
+xq\x8e{\x22\xe1\xa4Fcr3r@-\xa9\x93\
+\x10\x09\x80\x81\xc7Vbw-\xce\x99\xe0\x12\xb0(@\
+\x8e\xc2)(\x05Fv<\x16J\xd3\xeb\xb5\x11\x8f\x8b\
+j\xefFaJ\xf1\x9a\xef\xd2\x18v\xe5\x16\xc0\xc1\x83\
+$\xf6EG\xf4b!\x87Yf4\xcd\xcc&$|\
+\xbe\xfb\xe4\xfc\x08\xaf \xb6\xc5ne)\x06[\x8c\xc7\
+[H\x87\x82\xa0\xf6\x9b\x1b\xe66\xedph}LK\
+\xe0\x98\xd9\xd0WK3T\xfe\xc0J\xa06\xb5d\xc2\
+g}\xc3\xa8Rx\xaa\xf5{\xe2\x1ab\x15\x1f\x0c\xdb\
+Z;\xa7\xcdVj\x8cu\xcc=l\xc5FeQr\
+\x8c\xcbEG\xa4F\xae\x00\xbdE[\x98\x86ME\x0a\
+\xea\xf5p \x5c\xc1\xae\xff\x9c6\xfak\x13\x06\xe4\x10\
+nvqx\x1a\xe5Le\xf959\xe9\x9eF\xb2c\
+\xb3iv!\xb5\xf7\x92\xa6\x84T\xfd\x92\xac\xb1-\x9c\
+`r\xff\xc9\xd6\xd5\x1cj\xef\xfc\x01\xf3\x87\x16@\xdd\
+\x1ek\xd7\xef\xe1\x89\x93W\xf0\xde\xed\xc7qs\xc0\xae\
+.+'\xbf\xf8\xeb\xdd\xe7\x0b|\xe5\xf8^\xbc\x09\xaf\
+\xc4\xcb7\xce\xc0\xc0\xb4\xe6\xdeX\xd3\x08\xe8\x18\xbc\x0c\
+A\xcePC\xc7\xf9\xf5!\xc0\xcaE\x8a\xe2F\xbc\xe1\
+\xf5\xc2T\xe0F01nl\xf3\x1d\xf8\xd4\xf1\xfa\xb3\
+\xa2(X\xab`\xad,\x80z@\xfb\xfb\xfb\x9d\x00\x9c\
+\x9cd\x94\xdc:\xf3.\x88\xaet~\xec*\xb4\xfe\xf6\
+\x98a\xf2\xe68\xb4\xcdw\x0f\x8f\xb1\xabp\xf9\xe46\
+.\x9e\xdd\xc1\xb6\xdf\x87{\x09J\x1d\xe92*|\xb8\
+\xbc\x82\x0f\xdb\x17\xf1\x9d\xee\x01\xbc\xb5x\x18\x9bv\xa3\
+3z\x9f\xc2E\xa4\xf5'\xc5\xea\x87^\x7f\x9c\xbb\xb1\
+\xb7\xb7\x17\xef\xe9hV.\x00\xa8\xde\xf39\xe8nN\
+\xba\xa6\xd4AWJ\xd8\xc9I\xf6\xe8\x1a\xca\x9b\xd1N\
+T\xefU\xdf\x9e\x8f\xeb\x9b\xb7\xf0\xa5\xb37\xb1\xbd5\
+\x9e\xf8\xfc\xce+7\xcfC\xce\xe3\xfd\xe6\x19\x98\xb1\xc1\
+\xdb6\x1e!\xf3.r\x14N\xae0\x90r\x06bk\
+\x82*\xe8\xa2\xdc%j\xfdQ-\xd7R\x19\x8e\xd3\xef\
+\xe7\xc2\xf1\x06\x01\x01\xb9\xed\x959\x86\xe6&\x95K\xd6\
+\xc89N\x92\xac\xa9d\x94\xde\xa6\x94o\x03\xf7\xe1f\
+\x0e\xbb~\x0f_8\xfb\x22>\xfb\xc0\x0d\xbc\xb49\x12\
+\xf7\xc2S\xea>\xef\x7f`\x9e\xc3\xf3\xe3\xab\x9d\xb0\x0e\
+J\xc3\x0f\xb9\xfe8\x5c*f\xf8\xdc\xceP\x92%\xbc\
+\x16\x18\x80\xf7\xde\x84&H\x97\xe6\x8b\x5c\x97\x1f)\xac\
+\x22i\x7f.[\xadK\x12\xd1<\xfej\xbd\x91C\xe5\
++\xbc\xb0\xf5\x12.\xbe|\x17\xfb\xa8\x86\xf7\x8f\x95\x00\
+\x00\xd7}\x85\x0b\xee\x12\x1e\xc0\xb9\xce\xd6\xaa\x04\xa6J\
+\xad\xe3\xba\xae\xbf\x94\x05J\xad\xbfT\x16#\xf1<\x9d\
+w\x04\x1e\xd2\x050\xe1\xe4q\x9dc9\xa0\x8f\xcbL\
+\x93\x10Qnbr\xbb\xefrR;K\x18\x10\xbb<\
+\x84\xa1\xcc\x1b\xe5\x0e\x9e\xbeo\xfb\xc0\xdcWZ(\xbd\
+hw\x9a\x9e\xfc\xb9\x96j\xaao`\x0e\xa3K\xebO\
+Z\x8b\xa94\xe3\x94bb\xd6\xa6Y\xa5\x00\x98\xc1\x01\
+B\x9f,\xd5*K\xf2\xf7\xfb4\xe3\xe4p\x80\x5c\xe0\
+\x86e\xfe\x9a\xf1}\xfb\xefZ\xeb\xef\xfa=<{z\
+\x1b\x97\xee\xd9C\x05\x078e\xcee\xd0\xd8\xba,f\
+O\xed\x11(\xe1H9\x99|\x8bX\x7f9\xfe\xff\xf4\
+\x183\x8f+?\xb7\x0b\x90\xa3\xe1S\xda\x9d3\xcfS\
+B@\x12\x1e\x9ci\x96\xc5\xf0\x12\xf0\x17\x98\xfb\xe3j\
+\x8cK'o\xe2\xd9s\xbb\xb8]T\x93\xeb/\xb7\x8e\xa3K\x02\x13q\xec\
+\x5c~\xc4\xe0\xc5@\x5cC\x09\xc9\xb7\x97\xcc4\xa9\xa9\
+\x04\xe5\xdb\x0f&]M{\xec\xdey\xb8\xd3\xa5\xfa\xfa\
+kl\xf6\xe7\x84\xe0\xa4\xe3\xb8\xb5&E\xa4R\xf7\xc9\
+mN2\x8f\xa0[\x99\x00\xe0\xd2\x10\xb9\xee2\xa9\xa6\
+ \xb9=\xdd%\x90'\xb7\xf3mrB#\xed?\xa9\
+\x0bP\xc6_/\xed/\xef\xd8\x93\xdb\xb5\x98\xcb\xc1\xcf\
+\xd9\xb3\x92\x13.]v\x91\x9a\x93\xb9\xcd<\x96\xc0\xbc\
+\x16\x80\xa1\x0c\x11\xa9\x05x\xaa\x1bN\x8e4\xcdI\x17\
+\x96ZP%\xfd~\xe29\x86l\xc5\xac4\xbc\x0b \
+\xd5\x92p\xcc\xc9E\x878\xc5\x92\xb3\x0d\x1b\xb7\xfer\
+[\x9c\xa5\xda\x9d\x0dM\xe5\xa2^L*\xc9\x22\x95\xf1\
+\x97\xe3k\xc5\x16B\xa7\xc2\x0c*\x8f\x1f\xb3\x9fy\xf8\
+f\xcf\xf7I\xc4Ei\x9d`\xc0\x94e7\xe4\xfa\xa3\
+\x98\xb4\xeb\xfa\xeb\xd3\xe5\x99\x1b\xff\xca]\x80\xbeZ<\
+7\xbb\x8f\xcb\xa3NIG\xc9\x22 \xd7\x8e\xa1-\x81\
+\xa6f\xbb\xc6\x01\x14\xf2?T\xd8@J\x8bw]\x7f\
+\x5c\xb3\xd5.\xeb/\x97q%\xcbu\xc8\xd6p\x0bS\
+i\xd2\xf6\xd5R\x0b\xebT(\xb0\xcbF\x0f\x9d\xd0\xfe\
+X>\x84\xddz\xa0.\xc0\xba\xea\x7f*\xd1L\xc2\x85\
+\xb8]\xa6r\xd6\x1f\xb5~s\xee;\x8fk\xb3(\xd3\
+\x7fP\x17 \xe5\x93\xa7\xfa\xb4q\xe8\xaa\xd4\xb6\x99\x13\
+8sU\xf6\x85\xd6@\x08\x02N\xbb\xcd\x1cf\xf6/\
+\x9d\x83\xf5\xcb\xcbQv\xc6`l\x8b\x05\x0b\x00?\xe9\
+\xe0\xe4\x8a\xa6\xb3\x8eA\xa0\xa9=m~\xe7\x84\xf8\xa4\
+6\xe9\x92\xbb\xdb\xa9\xb6$q\xbd\x81@\xc2\xe5a\x00\
+\xa9\x09\x96\x8e]Z=\x7f\x0c\xf8y\xde\xadl\x04\xd8\
+\x14\x078\xac\xe1\xbfMc\xf0\xfeo\xff)\xbc\xe6e\
+\x0f-\xed\x9e\x17_\xba\x84\xef\xfd\xed\x9f\xc4\x0b\xbeZ\
+\xd8=\xfe\xec\xf6\x15\x5cy\xf2\xb7\x9a&Mqc\xd2\
+7\x9c}\x1d\xde\xfe\xe5_\xcf\xa6\xa3\xe7n\x1f\xcf\xe1\
+P]\xdc\x89\x94u\x9c\xd2\xfe\x8b\x12\x02\x0b\xe9\x08$\
+\x99\x5c\xd2\xde\xed\xd4\x04\xe5 \xb6s\xd7\xf3{\x90\x95\
+~\xcd\xfd\xa7\xbd\xdb\x0e\xab\x0d`a\xf0\xe0\xdd\xf7\xe1\
+\xb5g\x1eX\xaa\x81^\xc2,\xf4\x0e\xb7\xe1\xf1\x02n\
+\x82K\xc7|\x0dFB\xfby\x9e\xf1S{7\xe4l\
+\xa2\x221\xbc\xb4\xc1J\x0e(8\xec\xdaX\x90\xff\x1f\
+\x0b\x04\xae\xcc2\x955%m\xaa8\xc8\x04y\xc1\x05\
+0m7@1\xc0C\x88\x13$\xea\xf9\xa5\xf5\x17\x83\
+\xd6\x94K\x9bS\xb7\x92\xbbq\x0c\x07&\xa6\x8e[\xb9\
+\x00\xe0j\x008\x93\x9dbn\xc9\xef\x91&\xb9S\x87\
+\x14\xc3\xfcmh\x17\xa0\xd1\xfcM\x14@\xe9\x90q\xbf\
+hmR\xebO\xea\x0b\x90S\xcf\xdfE1\xa5r\x0f\
+$\xcba\xad\xa2\x00\xa9\xed\xa9$\xc4\x95\x93\xaa\x9c\x7f\
+\x96+\x18HFg\x80\xbe\xd6\xe7\x94P0\xcaK\x87\
+\x92\xff\xc17\xd6\xe0\xd6\x1f\x97\x13\x90Z\x7f}R\xd0\
+s\x14\xdb\x90\x85C\x0bw\x01\xc2f\x89\x92%\x90[\
+5\x95\xda\x5c\xb2o=\xff\xcc\xdf\xa1@\x88\xf1\x00\x0f\
+z\xab/\xa5C\xe9\x06\xa4\xea\xf1s\xd6_\xae\xb9\xde\
+\x95or\x8b\x8b\xd6N\x00\x84=\xd0C\xf0/g\x83\
+F\xae[\xcb<\x16H'\xf3_\xb2\x0cf>S'\
+\xe0\xb0\x0a\x80\x5c| \xa7'@N\x8d\xc1<\x98\x99\
+T\xc72t~\xc0`.\x80\x8b6\xba\xa0\x00\x14)\
+\x8b/%Y{\x99\xfeH0yl\xe6\xab\xa6\x1f\xd4\
+\x1a4k&\x04\xba\xac?\xa9\x90\xad\xd7\xfaK\xe0W\
+\x9c\xb9\x9f\xcaS\x98\x97\x06\xdb\x19\xa8\xde\xac1%\xd9\
+\x96Z\xcf\x0f\x82\xc1=#\x14|\x07\xeb\xe1\x88\xd3{\
+?\xf1!\xbc\xe7/>4\xd75v\xc6{x\xce\x8f\
+W\xce\xf4\x94o\x9f\xbb\xfe\x16Q\xcf\x9f\xeaG \xf5\
+\xd3\x88\xb7;[\x9b\x9d\x81\xc2j9\xc9\xaf\x97\xe2\xa7\
+\x0b\xa9\xe7\x8f\x99\xdd0\xfe\x7fJ\x10\xdca\x96\xff\xf6\
+\xce6>x\xe9\xf1#!\xf7\xa4\xf8}J\x0b\xe7T\
+\x92\xce[\xcf\x9fJF\xa2\x04\xc1P\xae\xc7\xa0\x18\x00\
+\x17\xd2\x93\xea\xa9%\xbff\xa8z~\xd1\x0d\x80`\x11\
+\xdc\xc1\x16\xc0Q\xf4\xff\xd7\xa9\x9e?\x15:\x94\xfai\
+\xe4X%K\x17\x00\x12`\xb1\xcaz\xfe\x99\xcf\xa8p\
+\xa0a\x8eQ:z\x98\xc4\x9a\xd4\xf3KXCj{\
+\xf3\xa1S\x83\xcb!'\x98\xf3\x8b\x16R\xcf\x9f\xab\xf9\
+\xe3\x90^W\x17\xe1\x0et\x01\x8e\xb2%\xc0\xad\xd5U\
+\xd4\xf3\xe7\xee6\x94c\x85\xac\xdc\x02\xc8\x01a$I\
+\xda\xb9\x9e?W\xeb\xc7LOE\x01L\xe6\xb5\x94\x0e\
+\x1b\xdb\xcfh\xdb\x9cz\xfe\x1c\x8b\xa0\x8bB\xcc\xb5v\
+9\x1eZdG ;4\xc3\xa7\xac\x00.\xdd2%\
+\xa9\x93Z\xdf\x08Z;N\xf4\xa1,\x03*J\xa0t\
+\xd8\x0d\xff\x19e\x94S\xcf?\x04\xc3\xa56\xf4\xe4\xac\
+\x10\xc9\xe2\x9d'\xfc\xb8\x14\x17@\xea\xd0\xdb\xa7\x9e\xbf\
+\xd7\xb6\xdd\x94\xe6\x97\xaa\xfeba\xc0\x9d\xaft(-\
+\x80u\xab\xe7\xa7\xc2\x81R+2\xc9\xedXk\x17@\
+\x92`\xa9\xfc\x80l\x09g\x18A@\x99\xfd1\x0e\xe0\
+\x19w\xc0\xab\x0bp\x94-\x80!\xd6_\x8a\xf1\xb8\x82\
+#\xce2\xa0\xa2c\x9c\xd0\x18\x0a\xfd\x1f\x5c\x00H\xbb\
+\x9c\xa6\xfc\xad\xb9\xbb\xf8PLJ\xe5\xf7K8\x00\x04\
+!p'Z\x00G\xa2\xfd\x99g\x11\xf7\x94\x89\xde\xd5\
+\xf4\x96\x84En_BN \xb4\x8e\x1b\xf8\xb5\x0c\xe6\
+\x02\x84\xa9\xc0\xa9\xfd\xd1R\x88\xeb\x5c\xc2\xc0\x80/\xea\
+\x01\xe3\xf3\x87\xee\x01\xe7\x22\xdcAt\xcf\xf1\xbb\xf0\xbd\
+\xf7=\xd2}\x0d\xc0\xe3\xa3\xd7/\xe0\xe2xwm,\
+\x80\xdcB\x9b>\xccM\x09\x89\xd4\x8eW\xb9[\x94\xc5\
+c\x0d\xbbS\x0d\xe5\xff/\x04\x03\x90\xca{\xa9\x09\xed\
+\xe4sQ]{r\x19]:\x9f\x13\x22w\xa8\x05\xf0\
+\xed\x8f~\x13\xbe\xf5Mo\xeb|\xde\xc8U\xf8\xa7\xef\
+\xfbwx\xcf3\x7f\xb2~\xce@\xc7\xf5\x97\xa3\x98R\
+[\x8a\xa5\xca\x8a\xa5nXR\xe8|(\xff\x7fP\x01\
+@\x81\x16\xf3\xec\x8f\x9e\xad\xe59\xc6\xe5,\x01\x80\x8f\
+\xf7s.\xc0\x1df\x01l\x16%6\x8b\xeeKcT\
+\x8dQ\x98u\xda;\xc1\x8b\x80\xda\xbc\x1bvJ{\x10\
+\xa4\xda\x8bQ\x15\x879\xfd0\x86\xee\x0d0x&\xe0\
+B\xea\xf9\x91\xf0\xc9\x0d#\x08\x0ca\x09\xc4\xe6>u\
+m\xa3\x18\xc0Q\x01\x01WQ\xcf\xcf\x09\x17i\xf3\x11\
+\xea\xbcT%\xedZE\x01\xe2\xf2\xdf>\xc2\xa3\xe3\xbb\
+\xa5-\x030\xee\x002\x5c\x04(\x06pG\x88\x85\x05\
+\xd7\xf3K\xd7\x95\xfc{\xce:\xc8)I^\x0b\x0b`\
+\xa1\xf5\xfc\x94\xb6\x97\xea\xf9\x0dh\xc4\x9f\xb3\x0a\xa8\x5c\
+\x00\xa5#\xc3\xec\xebR\xcf\xcfY\x05\x5cdB:~\
+\xad\x04@\xdd\x0f@\xaay\x9e\xcb\xdc\x92\x12t\xb8\xfe\
+\xfe\x1e<\xba\xcf\xd5\x020\x96\x83\xb6\x04=\x8c\x94\xd7\
+\x95j^A\x22U\xf7I\x0c\x9e\x93\xf9\xc7Y\x0ck\
+k\x01\x0c\xd2\xbc\x03\x8c\x1f\xce\xe5\xe9s `\xf8\x99\
+\x07\xdd\xf5\x87\xbb\x8f\x89o\xa3f\xc1a\xc4\x00Ra\
+\xbd\xa1\xeb\xf9\xa9\xebr}1SnC\xaaS\xd6\xda\
+\xa5\x02S\xe6\x90\x94\xc6(>@\xaa\x9e\xdfgX\x04\
+\xf1\xff\xa9\x10!d+\xe0N\xa1k\xbb\xdb\xb8\xb9\xb7\
+\xd3\xd9\x15\x1aW\x15v\xc6{\xebe\x03\x08{\xfd\xf5\
+\xcdE\xc9\xe9\xe0\xc3\xed{)\x09\x8f\x14pHY\x17\
+k#\x00\xc2vE\x94\x0f\x93\x12\x18I\x10\x8ebd\
+\xce\x9f\xa7\x84\x88\xf49\x95\x1el\xee\x5c\x17\xe0}\x7f\
+\xfa!\xfc\xc3\x8f\xfdr/0\xc4\x9a\xf5\xb2\x94\xba\xd6\
+\xf3K\x0c\x9f\xca\xe3\x97\x84\x03\xe7Fp\xa0yjo\
+\xc2\xb5L\x04\x92\xcc\xa5\xde\xf5\xfc@\xbfz~#\x5c\
+\xd30\xe7\x99\x8c1\xdc1\xe0\x99=RNOn=\
+\xbfd\xfew-\xea\xe1\xaa\x0e\xa5\xecD*uy\x11\
+\xbb\x0f\x0f\x8e\x01\xd4\xbb\xb1rm\xbfz\xd7\xf3\xc7\x9f\
+\xa5\xea\xf9) \xd0\x80\x8e\x0a@\xb8\x86b~G\x02\
+\x04\xcc\xb5H\xbb*\xba\xbe\xf5\xfc\xa9\xc4#\xaet\x98\
+\xea`\xbcVy\x00\xde{\xc0\x0c\xb4q\x07g\xbe\xe7\
+\xd4\xf3\x83\x10\x10\xe1\xb9R\x9d\x80\x87\x98$\xe4U(\
+\x1c:\x10pH\xa6\xa7\xb4xl]H\x16/\xd5\x8f\
+ \xb6\x10\xb8D\xbaEm\x17^\x0e=\xe5\xa9I\xc8\
+~o\xf3\xd6\xf3K\xd8\x00\x04\x8c\x81\x11P\x93\xe7R\
+\x09p\xb8\xf4\xbf\xef\xcd\xf0\x8b\xac\xe7\xe7\xc0\xc0T\x87\
+\xe2\xb5\xdd\x19\xa8/\xb8\x22\x9a\xee\xb1\xd9\xdf\xb5\x9e?\
+v\x11\xb8\xda\x01 {C\x10e\xff\xc3\xa8\xff\xbb\xf7\
+\xd2[d=?\xc5\xdc\xdc\xf9\xbd\xfbb\xae\x8b\x00\xe8\
+\xc4QC\xd7\xf3\x1b\xd0\xc9@\x94\x85A\xb9\x0a\x1d\x16\
+\x8c\xd2!\x13\x0c\xab\xac\xe7\xcf\xe0\x11\xa9\x9f\xc6\xd0k\
+qp\x01\xd0{\x80)\xad,e\xedq.\x80\x89,\
+\x08\x9f\xb0\x18\xc0\xfb\xffj\x02\x1cF\x17\xa0\xfb\xbaM\
+1*\xb7\xbf\x80\xc4\xe8R\x07\xe0\xd4.DR\xc2\xcf\
+ZE\x01:\xd9d\xf1\xdf9\x8c\x9e\xd2\xdc\xb1\xb5@\
+1\xbc\xd4\x058\xd1\x03@1\x80\xa3\xa5\xf1%\x06\xe7\
+L\xee\x9c\xde\x15\x1csS\x8c,u)\x920\x88!\
+\xf7\x06X\xae\x0b\x90\xaa\xda\x93\xccy\x10\x82\x80\x02\x03\
+MBp\xf8\x0e\xc2F\xe9h[\x08BQ\x90\x94%\
+(mp\xc3m?\xc65\xca\xe1|~\x09X\x1cJ\
+\xfb\xaf\xce\x05XV=\xbfa0\x04I\xe0\x0cdR\
+*\xad/\xa3sJk\x19\xf5\xfc]v!6K\xc8\
+\xaa\x5cHS\xd0\xce\xee\xc0\x22\xea\xf9}B\xc0H\xf7\
+\x09\xfe\xf6\xce\x0fZ}\xa5\xb4\x5c\x8a\xb7\xad\xcf\xf5\xff\
+9\xc11T=\x7f\xf8w\xaaJ\x90\x18\xc4zV\x03\
+f\x09\x82e\xd4\xf3S\xf8\x82G~\x16\xa0\xe1\xb4\x83\
+\x0a\x81\xc3\xee\xff\xaf\xba\x9e\x9f\x1aK\xd7:\x05\x83\xe1\
+v\x0a\x1a\xbc#\x10+\x99\x96Y\xcf\x1f_\x83\xbbG\
+\x07\xbb\xde\x18\x05\x05\x8e\x82 Hi\xd7T=?\x17\
+\x92\xcb\xad\xe7\xe7\xbe\xcf\xcd\xf43\xc6\x1c\x18\xae\xeb\x06\
+\x02\xb6&a\xd5\xf5\xfc p\x82\x9c\xbd\x00\xfd\x1c\xd8\
+\x86\xd2\xa1\xc3\x01r\xbb\x05\xa7z]R\xf7\xe1\xea\xf9\
+\xa9\xda\x80\xdc\xd2dc\xccL\xca\xfdZ\xc0\xb5.\x1e\
+\x00\x00\x19TIDAT\xb9\x00\x22#\xa5*\xf5\xa4\
+z~\xc9\xff\xf7\xe0\xeb\xf93\xfc|1\xb4\xa8t\xa4\
+\xdd\x82T=\xbf\xe4\xc3K\xe1>\xaa\xfdwn\x98O\
+\xe2\xab\xba\xec~\xed0\x00\xce\xcca\xfd\xf1XC\xa7\
+r\x02L\xc2\xd7\x07\xf8=\x03\x12~~V\x04@-\
+\x80C\xcf\xf09H{\xaa\x9e_\xdaU\x88\xbaN\x97\
+J\xc0\x5c\x0cc\xc8P`\xb9\xa8\xc9&\xb5\xed2\xeb\
+\xf9\x0d\xfa\x15\x06u\xc0\x05\x0e\x8d\xf9\x0b`w\xff6\
+n\xed\xedd\x1d\xbf?\x1e\x1d9\xf3?\x17\xad_d\
+=\xbf\xd4\x99(\xb7C\xd1\xd0\xf5\x00\x83o\x0c\xc22\
+xl\x01\xa4*\xfc8\x97\x01\x0c\x93\xe7\xe4\x04\xe4\x9a\
+\xf8\xc4x\x0e3\x08\xb8\xe7\x1d\xde\xf1\xfe\x7f\x8d\xd2\x14\
+Y\xc7\xefT\xa3#\xeb\x09I\x8c/5\xb2\x91\x187\
+\xb62b\xf3_\xd2\xf6]\xf7\xcb\x90,\x94\x95\x0b\x80\
+\xd6\xc0sb\xf39\xf5\xfc^\xb0\x028!b\x187\
+\x00\xe0\xab\x08\x8f8\xdd\x18\xef\xab\xcf\x0fdo\xd3\xc5\
+}F\x99\xf8)a\x90\xbb3\xd6 \xd6\xf6\xaa]\x80\
+\xba1\xc8Z\xd4\xf3\xfb\xf9}\x7f\xa5;\xc7\x02\xa0\x84\
+\xc5\x90\xf5\xfc\x92\xa6\x9f\xa71\xe9<4x&`\xdd\
+\x1al\x06\xbc[E=\xbf\xc9\xc0\x22r\xdc\x01\xa5#\
+\xa1\xfd9\x00p\x99\xf5\xfc\x9c\xe9\xde\xb5\xe2o\xfd3\
+\x01\xd7\xa8\x9e?\x99\xf9\x07\xc0R\x9bZ\x06\xc7k\x22\
+\xd0\xd1\x11\x08\xab\xac\xe7\xefj\xbas\x05KkY\x0c\
+\xd4\x1a\xec:\xd5\xf3g\xcc\x95\xf3nF\x104)\x97\
+\xcd\x06\x13*\x04\x0e;\xadC=\xff<\xda{H\x00\
+pp\x010\xe3\x97\xacK=\x7f\x87\xb9\xaa\x05\xc1\xe4\
+4\xaf\xde\xc0\x11u\x07VU\xcf?\x8f\xf6\x1eZ\xfb\
+/\x0e\x03H\xf9\xe7\xcb\xae\xe77\xdd\x85\x01\xb9\x15\x98\
+\xba\x01GJ\x10\xac\xb2\x9e\xbf\x8b\xb0\x92\xf0\x89\xb5\xc3\
+\x00ZQ\x805\xab\xe7\xef\xe2\xff{\xf8\x96\x10\x98\x01\
+7\x95\xd6\x9f\xd1\x05\xed\xcei\xd2E\xd7\xf3\xf7\xd9\x8b\
+\x90\xbb\xe6\xfam\x0c\x02\xc6/GBk/\xb0\x9e?\
+\x87i)\xff\x9fr\x01\xd4\x078d\xfe\xbe\xa0\xf5\xb9\
+z\xfeP\xb3s\x99\x7f\x92?\x9eU\xcf\x9f\xc3C\x99\
+V\xcb\xdaY\x00-\x17`\x0d\xea\xf9\xbb0\xad\xf3n\
+\x06\x044\xc1\x0e\xb3\xca\xff\x87\xcd\x02\xa0\xd3qck\
+\x802\xfd\xeb\xcf8\x80P\xf2\xc9\xfbji\x09+H\
+m\x19\xb6r\x010\xf3\x10kV\xcfO\x86\xf9\x04k\
+ \xb4\x00\x8c5\x1a\x06<\xacV@\x22\x7f\x9f2\xfd\
+cK\x80\xebu\xd1\xa7\x9e\xbf\x17/aqa\xe8\xc5\
+u\x05\x9e\xa7\x9e?\xfcn\xa0z\xfe\x90\xb19\xc1@\
+\x85\x00=\xfc\xe0\x8d\x18\x95V#\x08\xd6\xa1\x9e?\xd7\
+\x05\xe0\xb2\x0a\xa9{\xaf\x85\x00\xb0\xd6\x1e\x0c|\xdez\
+~.s0\xc7\xcf\xcf\x98\x93\x90\xd1C\xc1\x10b\x01\
+5\x08h`\xb2\xad\x07\xa5\xb5\xf3\x01\x92&\xf6\xb2\xeb\
+\xf9s5>g\x91\x84=\x0e\xd7\xca\x05\xa8\x076\x93\
+\x07\x10k\xf3!\xea\xf9;\xb6\xf0\x8e\x198d\xfa\xf8\
+;kl\xf3\xbd\xc7r;\xb4*\x0d\xad\xf6g5\xec\
+\xaa\xeb\xf9\xbbX\x01\x94\xe514\x188xO\xc0&\
+\x5cF\xb9\x00TZ\xafd)P\xbb\x02K\x19\x86\xcc\
+g\x94\xf9_3~,\x0c\xea\xbf\xc3\xc6\x8b\xce\xbb\xde\
+\x1bM*\xad\xd8\x08`Z{q\xda\x9fC\xff\xb9x\
+\x7f_\x0c 7\xc7?\xb5{0\xe6\xc4\xa6\x17\x97\x0a\
+\x1ckk**\x80\x84\x16\x97:\x07I\xe7uH\xfd\
+\xe5\x84A\xf8L\xd6X\xb5\x00\x0e\xb1\xef\x1f+)I\
+\x8b\xa7\x90\xf7y\xeb\xf9s\xdc\x86X\xf8P\x9f\x0dU\
+\x0d\xb8\xd8~\x00C\xd6\xf3K\x9d\x7f2|\xfe\x98\xb9\
+\xc3\xcf\xea\xf0_\x1c\x010\x0b\x08\xfcy\xe7a\xaa\x0a\
+6\xa3_\xbdRb\xbdUUo\x0c`Q\xf5\xfc}\
+@\xc0\xdc\xde\x03\x8b\xd8\x1d\xb8\x5c\x08\xf3K\xe6=\xf7\
+\xa2R\xf5\xfc9\x11\x80Lm\xef\xbccM\xff\xd0\x1a\
+ph\x0b\x88y\x05\xc2\xad\x1b\xdb\xb8\xf0\xf9\xf3(_\
+|\x01\xa7\x94\x7f\xe7_s\x1e\x80\xa4\x05\xbd\xac\xd99\
+\x7f\x9fb\xe6\xdcz\xfe\x14\x83r\x9a[J:\xe2\x84\
+\xcfZ\x09\x00\xb2+0\x97\xbe\xcb\xe1\x00)S\xbeK\
+_?F\xfb\xa7\x84A\x0c\x00\x1a3\x89\x02\x8c\xfc\xa8\
+\x17\x0a`\x8c\xc1ho\x1f\xcf|\xf1\x02\x9e~\xeaI\
+\x8c\xabq\x17\xd9\xa54\xf0\xfa\xe4J~\xb9\x04!N\
+SK\xc9?\x12\xc3KY\x83\x9cp\xe2\xb0\x86\xb5\xb3\
+\x00\xbc\xf7\x93p \x97\xe2\xcbi|i\x9b0 \xab\
+\xff\x1fe\xe6S~}K\xc3\x07\xa6\x7f\x8c\x03Xc\
+\xe1\xe0&\xa1\x97(C\xb0\x0b]\xbe\xf8\x1c\xbe\xf0\xd9\
+\xc7\xb1\xbb\xb7\xab\x5c\xb8\x22\xc6\xa7\xe2\xf9\x12#Q\x9a\
+V\xc2\x01\xba(F.\xc4\xc8%*\xe5\x8eym,\
+\x80\x99\xc6\xa0\x14c\xfb\x0c\xab`\x0ePO\x12\x0a-\
+\x06\x0f\xb4\x7f\xfc\xf9\xe4\xb6\x93\x9c\x06k,\xc6\x18w\
+*\x06\xdc\xbdy\x0bO|\xfaq\x5c\xba\xf2\x82\x02\x88\
++\xf3\x11hF\xa4|~\xa9\xe47% \xe6\x11\x08\
+T\xc7a.:\xb1\xd6\x16@k\xb2\x0c\x00'\xf8\xf7\
+&\xc3*\x18\xa0yg.\xf0\x17\x7f^\xff\xef\xe1\x1b\
+\xe6\xcf\xc1\x00\x8c1\xc0\xa8\xc0\xd3_\xfc\x1c\xbe\xf8\xc5\
+\xf3\xcd&\x0eJ\xabR\xffr\x0d=U\xbe\x9br\x11\
+$\x1c \xd7J\xe6\x80\xc8\x1cAU'\xdc\x0dE\x83\
+\xa7\xb8\xcd\xe4\x01\xc4\x18@\x9c\xc1\xc7\xc5\xf7\xe7\xac\xe7\
+\xe7R|c7 \xfe\xbc\xfeN:\x9f\xa2\xc2l\x00\
+\x17G\xb8\xf9??\x83/\x9c\xffl\xd6\xae\xb4J\xcb\
+\xc7\x00\xd6\xa1\x9e\x9f\xf3\xebsR\x8b\xd7\xda\x02h\x06\
+\x98\x1b\xda\x1b\xa8\x9e\x9f\xd3\xfe\x9c\x99O\x99\xfb\xf1\xe7\
+u\xfc\x7f\x8c\xf1\xc4\x12\xb0\x96l\x08b\xacEy\xb3\
+\x80\xf9\xd3K\xc03\xb7\xb0q\xdb\xc0\xdcm4mh\
+M\x5c\x00*\x87^\xea\xd7O!\xff9`\x9fd\xea\
+sf\xbf\x94\x80Du&Z\x84R)\x17#n\xc1\
+\xef\xd7\x97\xca\x02\x9c\xc3\xfc\xcfA\xfc\xe3\xbf\xe3\x10`\
+(0\xeaH@a\x8bI\xfc>\xba\xdf\x06\x8ea\xeb\
+\xb3\xbb\xd8\xbc\xb0\x8d\xf1\xc8`\xbc\xb5\xb5\x12\xcd\xff\x8a\
+\xe3\xf7\xe2e'\xce\x90\xdfU\xae\xc2\x8d\xddm\x5c\xbe\
+}\x0d{h\x8f\xed\x15[\xf7\xe2\x15\xa7\xceu\xba\xd7\
+\xad\xd1.\xce\xdf\xb8\x08c\x0c\xdet\xf6\xb5\xb0\xc4\xcb\
+q\xdecw\x7f\x17/\xde\xba\x86\xab\xd5\xad\xd5uR\
+\xf2\xbc\xef\xce\x81p\xf3\xf4\xee\x97\x80:\xce\xfc\x97\x80\
+?\xb2{\xb0\x8f\xb2n\xd7E\x00\xcc\x0c&U\xcf\x9f\
+\x9b\xdd\xd7\xa3\x9e\x9f\xd2\xf0\x14\x93S\xc7\xd6\xff\xd7E\
+@\xdex\x8c1\x9e\x82\x81\x93\xc1\x14(q\xd7\xf5\x0d\
+\xdcu\xfe6\xec\xbe\xc5\xf8\xd81\x8c\xac\x85\xb5\x16U\
+*9e\x01\x16\xd7\xbf\xf8\xb6\x7f\x82\xef\xfb\x1b\x7f\x8b\
+\x9e\x13W\xe1\xa5\x9dm\x5c\xbc\xf4,>\xf2\x89?\xc2\
+\xbb\xff\xe0\xbf\xe3\xc2\xee%8\xef\xf0\xa3\xef\xfc!\xfc\
+\xddw\xfe\xedN\xf7{\xfc\xc9\xcf\xe1\x1b\xde\xf57q\
+\xea\xd8I|\xf8g\xde\x87\xb2(\xc91\xed\x8d\xf7q\
+\xe9\xfa\x15|\xfe\xc2\xe7\xf1\x9b\xbf\xff^\xfc\xce\xf9?\
+\x80[\xb6]d\xe4*=\xce\xdcNY\x06\xb9\x00_\
+\x8a\xc9\xa5\xad\xc8\xd8\xdd\x89\xad\x81\x1f{v'\xa3\x95\
+\x09\x00\xb2\xbc2N\xe5\xcd\xdd\xbcc\x0e\xe2L\xfa\xfa\
+oJ8\x88!D3\xb1\x00\x0c\x0c\x8c5\xb8{\xa7\
+\xc0\xd9g\x0b\x9c\xb8U\xa0*\xb60\xde\x18\xa3(\x0a\
+\xd8\xa9\x00X\x95\xef\xcfMaa\x0b\xdc{\xea4\xee\
+=u\x1a\x7f\xe9\xd5o\xc0_}\xf4\xeb\xf0\x83\xff\xf9\
+G\xf0\xf8KO\x0d\xa3V\x19e\xb0\xb5q\x0c\x0f\xdd\
+\xf7ex\xe8\xbe/\xc3\xd7<\xf2\x16<\xfa\x81\xdf\xc0\
+\xbf\xfd\xf0/,W\x08x\xb0\x0d?8S<%,\
+\xfa\xf0\x04\xa5\xdd\xa9\xfbK\xe7N\x84\xf9!p\x01\xd8\
+0 \x90_\xcf\xdfa\xae9F\x96\xb4?\x95\xf9\x17\
+\x9fS'\x00yxT\xa8`\xad\xc5\xd6\x0b\x0e\xaf\xbb\
+u/\x8c\x07\xdc\xa6CUU(\xcb\x12\xe3\xf1\xb8Y\
+d\xfb\xa3}\xa0\xc2Jio\xb4\x87\x17\xae]\x06\x0c\
+p\xfa\xd4i\xdc}\xfcT\xf3L\x8f\xbe\xeeMx\xd7\
+\xf7\xfd\x04\xbe\xe3?\xfd}\xdc\xd8\xdd\xc6\xf3\xd7.5\
+Lm\x8c\xc5\xb9\xbb\xcf\xa0\xb0\x07{\x08^\xbby\x1d\
+{\xa3\xbd\xe6e]\xbdy\x9d\xbd\xef\x13\xcf>\x89\xd1\
+x\x84\xb2\xdc\xc0\x83\xf7=\x80c\x1b\xc7\x00\x00w\x9d\
+\xb8\x0b?\xf0\xed\xdf\x8f?|\xfcc\xf8\xfd\x8b\xffo\
+%\x16\x00\xa5\xf1\xfbj\xfa>\xee@*\xed\x98\x02\x1e\
+\xb9\xae\xc4C\xa6\xa8\x97Cy\x1a\xbf\xf9c\xbf\x82\xb3\
+\xf7\x1c\xe0\x09\xff\xedw\xde\x83\xff\xf1\xb1\xdfj\xde\xcb\
+\xedj\x1f\x15\xd3\x5c\xe5\x9f\xbd\xfb'\xf1\x89\xa7?\x85\
+cv\x03\x7f\xfd\xe1o\xc4\xcf\xfd\xe0\xbf\xc1\x89c'\
+\xa6B\xe0\x14\xbe\xe3k\xde\x89\xdf\xbb\xf8\xf1\x85\xd4X\
+H\x0a\x89b8N\xe3\x0f\x91\xdbO\xdd\x97\xf3\xe9S\
+\xbb\x0d\x91\xfd\x08\x06\x9c\xbe\xb9\xc2\x80\xd6\x1eT\xc9\xd5\
+\x80Y\xc3\x10kP\xcf\xcf\x85\xf9\xa4\xf3c\x0dR\x9f\
+S\xd8\x02\x85\x9d\x98\xfa\xc6\x1a\x14E\xd1\xfclnn\
+\xa2,K\x94e\x89\x8d\x8d\x0d\xac:\xd1\xd7{\xe0\xe2\
+\xfeU\x5c\xdc\xbb\x8a?\xbdv\x1e\xbf\xfa\xbf\x7f\x1d\xa3\
+\xea`\xcb\xef\x8d\xa2\xc4\x1b^\xfe\x15\xb86\xde\xc1\x13\
+;\xcf7?Wn_\x9f){\xde\x1f\x8f\xf0\x85\x9d\
+\xe7\x9ac\x9e\xd9\xbb\xca\xde\xb7\xf2\x15\xb6\xdd\x1e\xae\x8c\
+o\xe2\xd7>\xf9>|\xe6\xc2\xe7Z\xdf?p\xf6\xfe\
+\xe5\xbaH>\xaf\x96~\x99\xf5\xfc\xf1\xe7R\xc4\x81\xab\
+d\xac_\xd1Zn\x0e\xdaj\x9fM\xd5\xf7\xfb\x840\
+ >\x93\xb2\xfc\xa4\xa2\x1e.\xc9'\xe7\xd8Z0x\
+xT\xaej\x84\x81s\x0eeQ\x1eH\xe4\xe98\x8b\
+\xb2\x80sn\xf0D\x8d!\xe8\xd6\xed\x1d\xec\x8fG\xd8\
+(6\x9a\xf7P\xdab\xb175\xc0h<\x8e\xd6\xc6\
+\xea\x80\x91\x9cm\xbe\xbb\xb8\x01\xa9\xa2\x1e\x09\x08L\x99\
+\xfb\x9c i\xba\x15cM\xb6\x07w\xce\xc9&NN\
+\xbc\x7f\xa0z~\xca\xc4\x8f#\x03q+\xb0\x9c2\xe1\
+\x1aH\xab\x9f-d\xf0\xfa\xa5U\xa8\xe0\x9cCe+\
+\x14E\xb1v\x02\xe0\xd8\xc6\xe6\x0c\xc3/\x22\xaf<~\
+\x89\xf7\x9c\xba\xab\xf5\xc9\xee\xde\xed\xe5fFz\x1e\xf1\
+\x97>\xefS\xcf/\xb5\x1b\xa3p\x06\xae\xa6\x9f\x8b\x1c\
+\x84\xdf\x87\x96\xf7\xdaX\x00\xc9\x9e\x80\x03\xd7\xf3sL\
+\x9e\xfa,\xbe\xa6\x94\x0f\xd0\x84\x04\x8di\xfa\x03x\xf8\
+\xc6\xca\xa9\xbf\xab\x7f\xd6\x87\xf9\xa7V\x0b&\xfd\x07\xbe\
+\xedk\xbf\xb5\x01\xe4j\xc1\xfd\xe4\xd5/\x0d~\xd7\xad\
+c\xc7q\xf6\xf8=\xb8w\xebn\xfc\xe3o\xfe\x01\xbc\
+\xfe\x95\xaf\x0b\xe6\xde\xe3#\x9f\xfa\xe3\xe5\xf6W4X\
+\xbbz~\x8e\xd1%\xd0\x90\x12\xd8kQ\x0e\x5cK\xa2\
+0K\x89\xdd\x1b0\x15\x01\x10\x84\x81\x94\xd0C\x99\xf3\
+\xf1y\x94\xf6\xa7\xce\xa7\x22\x02qjpa\x8af\xec\
+a\xc4\xa0\xa6u\x10\x02/\xbb\xf7>\xfc\xf6?\xfa%\
+Xc\xf1\xb2s\xf7\xe1\x8d_\xfep\xeb\xfb\xcf>\xf5\
+y|\xea\xf2\x17\x06wW\x1e\xfb\xe7\xbf|`\xf4E\
+&\xecG>\xf9Gx\xec\x13\xbf\xb3l98\x934\
+\xb3\x0e\xf5\xfc\xa9-\xca)a4OV\xe2\xc2\x04@\
+\x12L\xe9R\xcfo\xf2\xb4\x7fn=\x7f\x0c\xf2QU\
+\x81\x5c#\x10J P\xdf\x85\xf7_\xa7\xa2\x9f\xd3\xa7\
+\xee\xc17\xbd\xe5\x1b\xc8\xef\x9e\xbd\xf2\x1c~\xfa7~\
+n!X\x851\xb3\xf8\xfeh<\xc2\x07\xfe\xe8w\xf1\
+\x13\xef\xfdYl\xbb\xbd\x95`\x00\x92\x15\xd0\x95\x99\x86\
+\xaa\xe7\xe7\x12\x81bL\x80\xba\xee\xda\xb4\x04c\x0b\x1e\
+\xfe\xd4\x9f\xe1W~\xe0?\xe0[\
+\xdf\xfa-\x00\x80\xcdr\x03\xdf\xf3\xcd\xdf\x85\xc7\xfe\xe4\
+\x03x~\xef\xda\xd2\xe7b\xdd\xea\xf9%\xed\xcf\xa5!\
+\x87\xc0\xa1\x04 \xae\xcc\x02\xa0\xe2\x9aC\xd4\xf3\xa7\xac\
+\x84T=?\xe7\xdfS\xccMY\x08\xd4\xef\xe1y\xf1\
+\x0b[\x97\xd6\xe1/\xdd\xda\xc6\xbf\xfa\xc0\xbfo\xe6|\
+Y\x897\xde;\xec\xf91~\xe5C\xbf\x8e\xaf\xff\xca\
+\xaf\xc3\xdd'&Q\x80\xafx\xf0\xb5\xf8{o\xfd\x1e\
+\xfc\xec\xef\xff\xe2Z\xb8J\xab\xac\xe7\x97v\xfa\xcdI\
+M\xae\xa3\x00k\xb1/\x808\x885\xab\xe7\xa7\x1a\x81\
+\xc4\xa5\xc2\x14\x0e@\x9d\xcfY#f\x8d:\xfdM\xfc\
+q\xb3\x921\xfd\xde\xd3\x1f\xc7G\xff\xecc\x07\xc0\xa8\
+-\xf0=\xdf\xfc]x\xf3\x99\xd7\x93\xd5\x83\x8b\x04\x01\
+\xa55+\xad\xdfE\xd5\xf3\xc7VB\xecNp9\x01\
+\xf3X-\x0b\x13\x00\xbd|\xaa\x1e\x9d|%\xed\x1e\x9f\
+\x13#\xf7\x14V@\x99\xfb\x94\xb6\xa7@BI0i\
+\xf7\x9f\xe9\xbcX\x8b_\xf8\xe0/\xe1\xe6\xee\xad\xe6\xb3\
+/\x7f\xc5\x83\xf8\xb5\x1f\xfdE\xfc\xc8\xdb\xfe\xc1\xf2\xec\
+$#k|\xe9}\xe5\xd6\xf3\xc7\xcc.\x85\x1b\xb9\xdc\
+~\x09\xb3H\xd5\x0c\xacT\x00\x84\x89@-\x89\xe6\xbb\
+k|\xce\xdf\x96\xac\x00N \xc4\xda\x9d\xbb.U'\
+\x90\xca9\x98\x07\xbf\xb8\x93\xe8\x8f\x9f\xfbs|\xf0\xa3\
+\xbf\xdb\xfa\xec\xcb\xee{\x00\x0f\x9c\xbb_n\xe5\xbd\x00\
+\x10\xb0O\x17\x9f\xd4wR\xc2Nn\xff>\x09\xec\xe3\
+B\x8dC\xef\x0fP\x0e9\xc1\xad\x9e\x80\xbe\x9b\xc6\xe7\
+L\xfa\xbe\xf5\xfcT\xdc\x9fs%b\xed\x9f\xb2\x0c\xc8\
+\x9c\x01\xb8\x95Y\x01W\xb6\xaf\xe2\x89\xe7/4\x7f?\
+{\xf5\xb9^\xd7\x19\xbb1.\x5c\xfa\x12n\xecn7\
+\x9f]\xdd\xb9F\xbeC\xe7\x1d.\x5c\xfaR\xebyo\
+\x8f\xf6[V\xc0\x8f\xbf\xf7]8\xb9u\x02\xef\xf8\xaa\
+\xb7\xe3\xf8\xb1\xe3\xf0\xde\xe3\xd6\xed[+\xf1\xf7sL\
+\xe8\xa5\xd4\xf3\x0b\x16t\xaa\x1f\x01\x85;\xad\x5c\x00\x0c\
+= J \xccU\xcf\xcf0\xb1\x04\x14R\xbf\xb3\xdb\
+\x88\x07\x89&\xab`~c\x0c~\xfaw\x7f\x1e?\xf5\
+\xbf\xfeck|T\xa3\x8e\x14=s\xeb\x0a\xbe\xe9g\
+\xbe\xbb\x05f\x96EIZP\xdb{\xb7\xf0W\xfe\xe5\
+;Z\x9fm\x94\x1b-\xcc\xe1F\xb5\x8b\xef\x7f\xf7\x0f\
+\xe3\xe5\x8f\x9d\xc1k\xce=\x84\x17o]\xc3\xf9\x17\x9f\
+^\xfa\x16@\xca\xef\
+\x0f\xbf\xebR\xcf\x1f\x0b\x11I\x90p\x82\x80\xbb~\x9c\
+\x0bP3\xbf\xb5\x16\x85\x8a\x80\xb5\xd2\xe4}\x8f_D\
+=\xbf\x84\xe0S\x96t\xeb\x7f\xb3f\x02\xa0\xd5\x10\xc4\
+\x07\xfe\x89\xcfg\xfc\xd4&\x9d\xb1@\x90\xc0\xc0\xf83\
+\x0a;\xa0\xbe\xa3\xfc\xfe\x94\xc6\x8f_N\xfd\xb3\xb9\xb9\
+\x89\xfb\xedi\xe5\xbcu`~\xc8\x1b\x82\xa6\x98\x90\x03\
+\x02900\xfe,d\xf8\x18\xc1\x97\xf6\x22\xcc\xe93\
+\xb0V.@\xeb\x81\xe0{\xd5\xf3K\x8c\x17k\xf1\xdc\
+\xec>J\x18\xa4\x22\x01)\x8b\x80\x03\x98B\x0b\xe0\x8d\
+'_\xab\xbb\x7f\xae\xb9%\x90\xc2\xad(f\xa4\x187\
+eA\xc4\xc2\x80K*\xca\xb1 \xd6\xd2\x05\x883\x9a\
+\xfa\x0cT\xaa\xbc\x8b?\x93\x12{\xb8\x02\xa1X\xd3K\
+\xfe}v\xbb0\xc2\x1a\xaa\x7f^{\xcf\xab\xf1\xaa\xe2\
+\x9cr\xdd!q\x01R\xfe>\xb5\x93\x10W\x14\xc4\xed\
+4$\xe5\xf2s\xd8@f\xc2\x8f_\xa5\x00\xf0]\x99\
+\x9e\xd3\xdaq\xd5\x1d\x05\xe4Q\xc5:1h\x18\xbb\x0e\
+\xb1\x86\xe7\xae!\xf5\x13\xe8\x82\x7f\x14E\x81\x93\xc7O\
+\xe0\xedg\xbf\x16\xaf.\xefSK`\xa5.@\xbe@\
+\x90\xe2\xec)d>\xce\xee\xa3\xda\x90K\xe1b\xa9\x15\
+X\x07\xde\xea%\x08\xe6-\x06\xf2\xa9\x01\xae{=?\
+gm\xe4X\x00T\xab\xa6\xfa\xc5\x9d\xbb\xeb,\xde\xb9\
+\xf1\x8dx\xe2\xfa\x05|z\xe7\x0bx\xd6]G\xa5\x1b\
+\x86-[\xfd\xb3\xc0\x1e\xa7a\x17]\xcfO\xa5\x02\xe7\
+\xb8\x13\x8b\xb2\x00\x06i\x09&\xe5Mw\xd9\xa17\xd6\
+\xc4\x0b\xaf\xe7\xefa\xeas\xcf]\xb7\x08\xaf\xad\x00c\
+\x0cN\x1e?\x89G6\x1f\xc6\x1b\xdc\xeb\xe1\xbdGU\
+U3.\xd3\xbc\x00\x8f\xc7A\xea\xb5\x87?H\x16\x19\
+\x00@\xe2\xb4\x1d\xf7\xdd\xb2\xcc\xf9\x18s\xa9\x7f\xca\xb2\
+\x9cDa\xa6\xf3\x9fjz\x92\xea\xd43t=\x7f|\
+m\xc3\x08\xa8\xdc6e+\xef\x07\x10F\x01\xb2\x8b,\
+\xe0gJh\xeb\x9e{\xc0\xa4r\xac\xee\xb0S\x98b\
+\xa67\x9f1\x06\xce\xbb\xe6\xb8\xf0\x1ck\xecA\xcb\xae\
+)\xc5\x7fS\x02b\x88>u\xde\xfbf>jAP\
+/\xc2\x1a,\xad\xdb\x85\x91\xed\xd3\xa2\x05\x14Z\x13\xa9\
+\x05+\xed\x1e\xeb\xe1\xe9f\xad\xc2;mm\x92:\xed\
+\x85\xc8\x99\xbd\xd4\xa2Le\xb5\xe5$\xc8\xe4\xae=J\
+\x10t\xd9F;7\xd5w\x88z~i~\xfa\x00\x92\
+\xd3\xbf3\xe3n\xf3\x0b\x00/I\xe5\xf0A\xb6\x8em\
+\xcd\xf4\x0a\x0c7\xd1\xe0\xa4n\x0a\xa0\x91\x16Q8\x9e\
+\x9a\x01\xb9\xf3s\x999d\xd6\xdc\xb1\xc6?5\xf3\xd7\
+\xbf\xc7`R\xae\x86\xea:o\xed\x85g`\xea\x1e\x86\
+\xad\x8d\x9b\x0c\xdb\xc3\xa05\x7f\xd1q\xcd8\x84\xf3\xfb\
+\x08\xd0\xfa\xba\x92\x80\x0e\xb3\xe1\xc2\x5c\x0c\xe0`G\xe6\
+z\xaeke\x91\xec#!\xb4\xb1\xf7\xce\xc3X\xa6\xc7\
+E\xb8&\xadiY_]\xc3|\xb9\xef\xb4>&P\
+2+q\x01j\xa9S\xf9`\xd4\xa1\x16\xa4\x98/\xd4\
+\x1caS\x83\xfa\xf7\xd9I\x9a,\xdc\xf8\x1c\xce\xfc\x8c\
+\xaf\x17\x82.\xa1PH-B\x0a\xd4i\xfeN,z\
+\xcat\x8c\x99\xbeOvW\x1f\xc6\xe7\xb4-5\x17R\
+\xbf|i\xa1J\x08z}\xaf\xf0]\xb7,\xa4\xc4\x5c\
+\xcehe\xf8\xa6E\xbb\xf7\x93\x1d\x9b\xeam\xdc\xe3P\
+t\xfdY-Pj\xe6\xad\xf7v Q\xc2\xa6u\x9d\
+\x05\xa6\xe7\xd4\x1d\xa0\x9b\xe7\xf4i\xf5\x98\x9b\xcd\x97\xb3\
+g\x80d\xf6O\x8fq5/.K\x00x\xca\x22\x90\
+\xfc\xc3\x901c-(UD\xc5\x82\x22\x9c\x98F\x8a\
+G\xccN\x09\x1dJ\x8bSZM2e9+\x86\xf3\
+Q9&J\xb6t\x12\xba'Q\x80\x95A^\x86X\
+\x8eY.\xf9\x9bC\x9c\xdf\xb8eS\x1f=twj\
+\xa1 5\xee$\xc72e\xf6P\x01\xf9\xe9\xbfz\x87\
+\xe7z\x9d\xb40\xa9\xc0\x22j\xdc\x9efeG\xbb[\
+M5j\
+>\xc2]\x9c\xc2u\xd3h\xff\xa85}#\x04\xa6[\
+\xc1\xb7]\x81H\xfb\xe3\xe0\xbd4\xaa\xd0\xf0.\x00'\
+\xf4\xa5\xd2\xe2\x1c\xe6\xbfq\xe3\xc6\xed\xaa\xaa6\xab\xaa\
+z\xc19\xb7\xd7G\xf3\xcf\xeb\x02x\x00~ggg\
+\xf7\xc6\x8d\x1b\x9f=y\xf2\xe4[_x\xe1\x85\x8dc\
+\xc7\x8e\x99\x93'Of\x01pR\x8dt\xca\xafL\x85\
+Yb\xbf3\x85\xc2\xa6\xfc\xe7.a\xa3\x16S1\xc2\
+n\xc8\xae\xae\xd4\xdbi\xae\x8f\xfc\xf1K\x1b[\xa4\xcc\
+Ui\xc1\x1f\xcc\xc9\xec\xfd\xab\xaa\xea\x04nV\xaeB\
+a\x0bT\xae\x9a-\x1d\x87c\xc3\xcf3\x16A5\xf9\
+l\x5c\x8d\x0f\xbe\x0f\xad\x00j\xffJ\xcc\x02\x83\xde\xf9\
+\xac\xb6b9@n\x0eP\xed\x9d\xc7\xcd[7\xab\xcb\
+\x97/c4\x1a\xddz\xf1\xc5\x17?z\xe3\xc6\x8d\xdd\
+\xc0\x12\xe8,\x04\xe6\xb1\x00\xaa\xcb\x97/\xdf\xbep\xe1\
+\xc2\x1f\x9f>}\xfak\x8c1\x8f\x9e?\x7f~\xcbZ\
+kS \x86\xa45\xbb\xb4o\xea\xc2@9\xda[Z\
+\xf8R\xf8*'\x8e\x9e\xda\x9e\xba/su\x9d\xa3T\
+\xf8.\xe7z\xb9\x96P\xee\x5cK~2\xf7\xacR<\
+>\x87\xa9r\xd7O*\xee\x9f\xab\x9c\xfa\x8c\x81q\x87\
+\xedh4\xaavvv>y\xfe\xfc\xf9\xffs\xe9\xd2\
+\xa5[\x00F}\xc3\x81}0\x007E\x1c\x9dsn\
+\xff\x89'\x9ex\xde\x18\xf3\xeeW\xbd\xeaU\xdfv\xcf\
+=\xf7\xbcicc\xe3\x9c\xb5\xb6\xc0\xec\xb6\xa0\xbd\x18\
+7\x97\xbfsP\xd3\xae`K.\x03\x85\xa1\x98>\xcf\
+\x97#\xfc\xe6\xf2\xefe\xb327\xf8n$\xf7Hb\
+\xec\x9cqO\xe7\xcd\xe72\x8c\x09..\x08\xd3>\x99\
+2^\x8a\xdf\xe7\xae\xe7\x1c\x01%\xbc{\x1f\x08\x93\xc6\
+\xe2\xf6\xdeW\xa3\xd1\xe8\xf2\xd5\xabW?y\xfe\xfc\xf9\
+\x0f?\xfd\xf4\xd3\x97\x9cs\xa3)O\xba\xd0:G\x97\
+\x97\xda\x81\x0a\x00\x1b\x00\xb6\x00\x9c\x00p\x17\x80\xbb\xad\
+\xb5g\xce\x9c9\xf3\xf2\xcd\xcd\xcdsEQ\xdc\xe5\xbd\
+?\xe1\xbd?\xee\xbd\xdf\x04Pz\xefk\xab`\x19\x99\
+\xf1\xa6\xc7\xf3\xf9\x81\x8e9\xccd\x02aZ\x0bo\x8b\
+\xf6~N6\xf8\xdd\x00\xb0\x02\xb3{\x0a8\xce\xf8\xde\
+\x0b\xf3\x1e\xde\xc4\x06c\xb2\xd3q\xdb`\x9d6\x0a(\
+XwT\xe0\x8f\x8b\xa5\xfb\xbec\x0d\xaf\x17F\xc9#\
+!\xe0\x82\xe3\xe2\xeb\xfb\xe9\xf1\x0e\xc0\xc8\x18\xb3o\x8c\
+\xb9m\x8c\xb9\xe5\x9c\xbb\xb1\xbb\xbb{\xe9\xfa\xf5\xeb\x97\
+\xbc\xf7W\x01\x5c\x07\xb0\x0d`w\xfa3\x020^t\
+\x14\xa0\x8e;\xee\x01\xb8\xed\x9c\xbby\xe5\xca\x15;\xbd\
+\xf9\x0d\x00'\x01\x1c\x9f^\xbf\x8c\x16\xd2\xb2\x99\xde\xcc\
+\xc1\xd4~@\x01\xb2\xf6\x8c\x8f\xd9\x0d\xdcbA\x10\x0a\
+\x81y\xe6\xd6g\xfc\xef\x98\xb1R\xe3*\x22\x81\x10\xff\
+/\x8d\x95b>*\xea\xe5\x13\xcf\x90#4\xa4k\xc5\
+\xcf^[\xda\xe3\x9a\xcf\x00\xdc\x9a2\xfb6\x80\x9bS\
+\x86\xdf\x9b\x1eS\xf5\x89\x08\xf4\xc1\x00\xdc\x94\xd1\xedt\
+\xe2w\x03\x89\xeb\xa7\x03\xdd\x01plj-\x14\x18\xb0\
+\xf3\xd0\x82,\x9b;E\xcbw\x15\x04\xa1@\xb0\xcc\xe7\
+&c\x1e9\xa6\xca\x11\x06\x10,\x00\xe9\x87\xb2`\xba\
+j\xf9\x5c&\xf6\x99\x82@z\xe6\xf0\x1a.\x12\x04{\
+\xd3\x9f\x9d\xe0g;\xd2\xf8\x15z\x84\x03\xcb\x1e\xcco\
+\x02\xe9\xb4\x1fL\xb8\x9f\x0e\xe46\x80\xcd)\xf3\x97\xc1\
+\xf7J\x87G\x08\x80`\x9eX\x18t\xb5\x00\xe6\xd5\xa4\
+1#\xc7V\x89\x0d\x94\x0de\xb1\xc4\x98T\x8e\xb6\xf7\
+\x1d\x05\x96\xef\xe1\xe6x\xe1\xb8j\xfa3\x9a\xfe\xd4\x96\
+\xc0\xeeT\x08\xecN\xff\x1eO\x7f\x16.\x00\xc2A\x8e\
+\xd0\x8e\x82\xd6\xa6\xca\xb1\xa9\x00(\x22\xd3L+\xe3\x0f\
+\xb7E\x80\x8e\x02\xa0\x0f\x16\x90k\x89\x19F\xd3[\xe2\
+w0V@\x17M\xee3\xbeKY\x16\xdc\xef.!\
+\x18\xaa\xc0\x15\xd8\x9f\xf2X\xf8\xffh\xfa\xbf_\x96\x0b\
+\x80\xe9`\xca\xe9\x8d\x11\x0c\xb4\x16\x02e\xe0\x16Xe\
+\xfeC-\x00l\x07\xbc\xa5+\x06\x90c5\xf8\x0c\xac\
+\xc2\x12>\x7fm\x11\xe4\x0a\x00\x8e\xd9%\xe6v\x99c\
+O\x097\xcfX\xdb\xa1ru\x81\xa6\xdf\x0fL\xff\xfd\
+\xc0\x22\xf7}_rW\xaa'\xbb\x0c@\x98\x8d\xe9\xe7\
+%\xe1\x8b\xa9\x008\x1anA\xce\xdf\xbe\xa7P\xe8\xa2\
+\xfd%A`\x18\xf7\x05\x98\xcd\xdb\xeb\xa2\xa9}\xc7\xf3\
+r\xbfO\x9d\xe7\x03\xff>T\xb4U \x18\xaa\x00/\
+X\x8a\x00\x00cv\x15\xd1\xffP\xe6?\xb2B\xa1K\
+\x88\xd5/p\x0c\x1c>\x91;V\x9f\xc9\x9c}\x98\xdb\
+'\xf0\xb4.nw\x08\x0cV\x91\x8b\xe0\xfb\xce\xb3\x19\
+\xe0%@\x00]T\x00(-\x13\xab\xe0,\x83.V\
+\x87\xef\xc8\xf8]\xac\x9b>\xe4\x09\xcc\xc0\x13?\x83\x98\
+wC\x99\x8a\x92\xd4U\x81\xa0\xb4hW\xa5K2X\
+\xd7\xbd\xac\x17\x1d*\xee\x03(b\x1d\x04\x80\x92\xd2:\
+c\x17\xabbh%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%%%%%%%%%%%%%%\
+%%%\xa5C@\xff\x1ft\x15\xc0\x0e;\x04\x9c\xe7\
+\x00\x00\x00\x00IEND\xaeB`\x82\
+\x00\x00\x03\xb6\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x00\x1f\x00\x00\x00%\x08\x04\x00\x00\x00\x10F\xfay\
+\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\
+\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\
+\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\
+\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02\
+bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09pH\
+Ys\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\x00\
+\x00\x00\x07tIME\x07\xe1\x05\x0c\x0c00c\x7f\
+\xadt\x00\x00\x02\xa9IDATH\xc7\xd5\x96MH\
+\x15Q\x18\x86\x9f\xef\x9cso\x12y\x8b\xc4H\x8a\x0a\
+\x94\x82 ($\x10\x8a\x08\xa4B\x09\xaa\x8dP\x10A\
+\xab\x82\x16-ZED\x04\xb5iY\xd0\xaae \x82\
+\x96 \xd5\xa2\xc8M\x04\xae\xdcDA\x10\x19R\x0bM\
+\x90@\xbb\xde9\xe7k1\xe7^\xe7\xfe\x92\xb8\x88f\
+10\x03\xcf\xf7\x9e\xf7\xfb\x9b\x11e=\x97Y\x17\xfd\
+\x8fqW\xfbB\x04\x90\xf8\xa0\xa0-\x93#Z\x8d\xae\
+\xc2\x02(\x0ah\xf3\x10\x19\x5c\x84\x147\x15KJ\x00\
+ 4\x0b\x91=|\x0a;\xc0\xc6S\x04\x02\x90`\x08\
+\xd2\xd0\x86\xcbh\xa7\xcf\x82\xc5\x22\x18@\xf1(\x10\xd2\
+\x13\xb4\xce|\x8aX\x1c\xb9M\xf9\xa3\xed\xe4\xc8\x93\xc7\
+\x91\xc7b1\xd2\xa0J\x15\xefb\x10,\x0e\x87\xbb\xbe\
+\xeb\xe0\x90+\x14\xe7^<\x1b\x9d\xc7\x93P\x22\xc1\xe3\
+\x09\x1a\x9a\xab\x13\x1d\xbbCg\xcc\x96\xc4\xda\xed\x03\x17\
+\x8fu\x90\xc7\x91\xc3a\x10\x8cHs<\xcd\xba\xed4\
+f[\x90`\xbc\x95\xcd\xe7/\xec/\xd0F\x9e\x1c\x0e\
+\x07\xb5\x01\xb2x\xf41GqVE%\x98`\x5c\xe7\
+\x95\xa1\xae\x8d\xe4\x22\xeebu\x1a\xe0\x1a\xef\x8aN\xbc\
+\xf4K\xe5\x00m;o\x9c-l G\x1e\x87\xc5 \
+Y\xfdj\xef!\xc5\x9f/\x0c\x8f$\xcb)\x1eL{\
+\xcf\xcd\xc1\xe8\xde`\x9a\xa8\xab\xa6h\xdag\xaf\x17\x86\
+\xc7\xfc\x12\xa4!:\x0e\xdc=\x8e\x8d\xfd Y\xc9\xda\
+Z*i\xab\xea\xe4\xcf\xa7\xe3+E\x04Q\x09fG\
+\xdf\xed\xc3\x11n\x9a\xba\xb2\xf7\x90\x86y\xb702\x9e\
+\x14A\x05\xa0\xa7\xff\xd6>\xa0E\xe1\xd2\xe2e\xa6n\
+\xcfV\x9b/w\x96_\xf9\xb1X\xdfu\xae\x0e/\x8f\
+\x0e\x97\xba\xfb\xfaUPQQ\xfc\xd4\xe8\x93\xef\x95\x09\
+\xacW\x8f\xe3Zqw\xb9\xe7\xc8I\x22l\xfd\xf4\xc4\
+\xe3\xaf\x04<\xe5\x1d\xd0P\xbd\xacm\xae\xee\xed\x1d\xf0\
+FTT\xd4\x84Oo\x1e~ !\x01|3\xbc\xbc\
+c\x0cr\xad\xbb\xf7\xb4\x17Q\x105a\xf6\xfd\x83)\
+J\x94HH\xd0\xea\xc5Q\xbb.\x04\xd0\xde\x13\x82\xa8\
+\x09 :?}g2\xc2\x9eP\xeb>\x8b\xc7\x9a\xef\
+6\xb9\xf6\x04\x13TD\x7f}\xbe\xff\x8a\x22%J\x94\
+\xe2\xc8V-\x8d\xfa\x15\xa03+\xc5/\xce\xdb\xe0\xfc\
+\xf2\xb7{c\x8b\xbf3\xb0\xd6\xce\xfb\xea\xba\x10\x0c\x16\
+C\x0e7X8w\xcau-\xcd DEV" % title
+ )
+ else:
+ self.ui.label.setText(title)
+
+ self.setWindowTitle("Flow Production Tracking: %s" % title)
+ if os.path.exists(bundle.icon_256):
+ self._window_icon = QtGui.QIcon(bundle.icon_256)
+ self.setWindowIcon(self._window_icon)
+
+ # set the visibility of the title bar:
+ show_tk_title_bar = (
+ not hasattr(self._widget, "hide_tk_title_bar")
+ or not self._widget.hide_tk_title_bar
+ )
+ self.ui.top_group.setVisible(show_tk_title_bar)
+
+ if show_tk_title_bar:
+
+ ########################################################################################
+ # set up the title bar and configuration panel
+
+ self.ui.tank_logo.setToolTip(
+ "This is part of the PTR desktop app %s" % self._bundle.name
+ )
+ self.ui.label.setToolTip(
+ "This is part of the PTR desktop app %s" % self._bundle.name
+ )
+
+ # Add our context to the header
+ # two lines - top line covers pipeline config and Project
+ # second line covers context (entity, step etc)
+
+ pc = self._bundle.context.tank.pipeline_configuration
+
+ if self._bundle.context.entity is None:
+ # this is a project only context
+
+ # top line can contain the Pipeline Config
+ if pc.get_name() and pc.get_name() not in (
+ constants.PRIMARY_PIPELINE_CONFIG_NAME,
+ constants.UNMANAGED_PIPELINE_CONFIG_NAME,
+ ):
+ # we are using a non-default pipeline config
+ first_line = (
+ "Config %s" % pc.get_name()
+ )
+ else:
+ first_line = "Toolkit %s" % self._bundle.context.tank.version
+
+ # second line contains the project
+ if self._bundle.context.project:
+ second_line = "Project %s" % self._bundle.context.project.get(
+ "name", "Undefined"
+ )
+ else:
+ second_line = "No Project Set"
+
+ else:
+ # this is a standard context with an entity
+
+ # top line will contain the project name
+ if self._bundle.context.project:
+ first_line = "Project %s" % self._bundle.context.project.get(
+ "name", "Undefined"
+ )
+ else:
+ first_line = "No Project Set" # can this happen?
+
+ # ...unless we are running a non-Primary PC
+ pc = self._bundle.context.tank.pipeline_configuration
+ if pc.get_name() and pc.get_name() not in (
+ constants.PRIMARY_PIPELINE_CONFIG_NAME,
+ constants.UNMANAGED_PIPELINE_CONFIG_NAME,
+ ):
+ # we are using a non-default pipeline config
+ first_line = (
+ "Config %s" % pc.get_name()
+ )
+
+ # second line contains the entity and task, step
+ second_line = str(self._bundle.context)
+
+ self.ui.lbl_context.setText("%s
%s" % (first_line, second_line))
+
+ ########################################################################################
+ # add more detailed context info for the tooltip
+
+ def _format_context_property(p, show_type=False):
+ if p is None:
+ formatted = "Undefined"
+ elif show_type:
+ formatted = "{} {}".format(p.get("type"), p.get("name"))
+ else:
+ formatted = "{}".format(p.get("name"))
+
+ return formatted
+
+ tooltip = ""
+ tooltip += "Your Current Context"
+ tooltip += "
"
+ tooltip += "Project: %s
" % _format_context_property(
+ self._bundle.context.project
+ )
+ tooltip += "Entity: %s
" % _format_context_property(
+ self._bundle.context.entity, True
+ )
+ tooltip += "Pipeline Step: %s
" % _format_context_property(
+ self._bundle.context.step
+ )
+ tooltip += "Task: %s
" % _format_context_property(
+ self._bundle.context.task
+ )
+ tooltip += "User: %s
" % _format_context_property(
+ self._bundle.context.user
+ )
+ for e in self._bundle.context.additional_entities:
+ tooltip += "Additional Item: %s
" % _format_context_property(
+ e, True
+ )
+
+ tooltip += "
"
+ tooltip += "System Information"
+ tooltip += "
"
+ tooltip += (
+ "Flow Production Tracking Toolkit Version: %s
" % self._bundle.tank.version
+ )
+ tooltip += "Pipeline Config: %s
" % pc.get_name()
+ tooltip += "Config Path: %s
" % pc.get_path()
+
+ self.ui.lbl_context.setToolTip(tooltip)
+
+ ########################################################################################
+ # now setup the info page with all the details
+
+ self.ui.details_show.clicked.connect(self._on_arrow)
+ self.ui.details_hide.clicked.connect(self._on_arrow)
+ self.ui.app_name.setText(self._bundle.display_name)
+ self.ui.app_description.setText(self._bundle.description)
+ # get the descriptor type (eg. git/app store/dev etc)
+ descriptor_type = self._bundle.descriptor.get_dict().get(
+ "type", "Undefined"
+ )
+ self.ui.app_tech_details.setText(
+ "Location: %s %s (Source: %s)"
+ % (self._bundle.name, self._bundle.version, descriptor_type)
+ )
+
+ context_info = "Your current work area is %s. " % self._bundle.context
+ # try get the environment - may not work - not all bundle classes have a .engine method
+ try:
+ context_info += "You are currently running in the %s environment." % (
+ self._bundle.engine.environment["name"]
+ )
+ except:
+ pass
+
+ self.ui.app_work_area_info.setText(context_info)
+
+ # see if there is an app icon, in that case display it
+ self.ui.app_icon.setPixmap(QtGui.QPixmap(self._bundle.icon_256))
+
+ self.ui.btn_documentation.clicked.connect(self._on_doc)
+ self.ui.btn_support.clicked.connect(self._on_support)
+ self.ui.btn_file_system.clicked.connect(self._on_filesystem)
+ self.ui.btn_shotgun.clicked.connect(self._on_shotgun)
+ self.ui.btn_reload.clicked.connect(self._on_reload)
+
+ # When there is no file system locations, hide the "Jump to File System" button.
+ if not self._bundle.context.filesystem_locations:
+ self.ui.btn_file_system.setVisible(False)
+
+ if len(self._bundle.descriptor.configuration_schema) == 0:
+ # no configuration for this app!
+ self.ui.config_header.setVisible(False)
+ self.ui.config_line.setVisible(False)
+ self.ui.config_label.setVisible(False)
+
+ else:
+ # enumerate configuration items
+ for (
+ setting,
+ params,
+ ) in self._bundle.descriptor.configuration_schema.items():
+ value = self._bundle.get_setting(setting, None)
+ self._add_settings_item(setting, params, value)
+
+ ########################################################################################
+ # parent the widget we are hosting into the dialog area
+ self._widget.setParent(self.ui.page_1)
+ self.ui.target.insertWidget(0, self._widget)
+
+ # adjust size of the outer window to match the hosted widget size
+ dlg_height = self._widget.height()
+ if show_tk_title_bar:
+ dlg_height += TankQDialog.TOOLBAR_HEIGHT
+ self.resize(self._widget.width(), dlg_height)
+
+ ########################################################################################
+ # keep track of widget so that when
+ # it closes we also close this dialog
+ self._orig_widget_closeEvent = None
+ if hasattr(self._widget, "_tk_widgetwrapper_widget_closed"):
+ # This is a wrapped widget so we can cleanly connect to the closed
+ # signal it provides.
+ #
+ # Doing things this way will result in gc being able to clean up
+ # properly when the widget object is no longer referenced!
+ self._widget._tk_widgetwrapper_widget_closed.connect(self._on_widget_closed)
+ else:
+ # This is a non-wrapped widget so lets use the less
+ # memory friendly version by bypassing the closeEvent method!
+ # Note, as soon as we do this, python thinks there is a circular
+ # reference between the widget and the bound method so it won't clean
+ # it up straight away...
+ #
+ # If widget also has a __del__ method then this will stop it
+ # being gc'd at all... ever..!
+ self._orig_widget_closeEvent = self._widget.closeEvent
+ self._widget.closeEvent = self._widget_closeEvent
+
+ def event(self, event):
+ """
+ To avoid key press events being posted to the host application (e.g. hotkeys
+ in Maya), we need to filter them out.
+
+ Events will still be handled by child controls (e.g. text edits) correctly,
+ this just stops those events being posted any further up than this widget.
+ """
+ if (
+ event.type() == QtCore.QEvent.KeyPress
+ and event.key() != QtCore.Qt.Key_Escape
+ ):
+ # Don't let the event go any further!
+ # self._bundle.log_debug("Suppressing key press '%s' in Toolkit dialog!" % event.key())
+ return True
+ else:
+ # standard event processing
+ return TankDialogBase.event(self, event)
+
+ def closeEvent(self, event):
+ """
+ Override the dialog closeEvent handler so that it first tries
+ to close the enclosed widget.
+
+ If the enclosed widget doesn't close then we should ignore the
+ event so the dialog doesn't close.
+
+ :param event: The close event to handle
+ """
+ if self._widget:
+ if not self._widget.close():
+ # failed to close the widget which means we
+ # shouldn't close the dialog!
+ event.ignore()
+
+ def done(self, exit_code):
+ """
+ Override 'done' method to emit the dialog_closed
+ event. This method is called regardless of how
+ the dialog is closed.
+
+ :param exit_code: The exit code to use if this is
+ being shown as a modal dialog.
+ """
+ if self._widget:
+ # explicitly call close on the widget - this ensures
+ # any custom closeEvent code is executed properly
+ if self._widget.close():
+ # Note that this will indirectly call _do_done()
+ # so there is no need to call it explicitly from
+ # here!
+ pass
+ else:
+ # widget suppressed the close!
+ return
+ else:
+ # process 'done' so that the exit code
+ # gets correctly propogated and the close
+ # event is emitted.
+ self._do_done(exit_code)
+
+ def _do_done(self, exit_code):
+ """
+ Internal method used to execute the base class done() method
+ and emit the dialog_closed signal.
+
+ This may get called directly from 'done' but may also get called
+ when the embedded widget is closed and the dialog is modal.
+
+ :param exit_code: The exit code to use if this is
+ being shown as a modal dialog.
+ """
+ # call base done() implementation - this sets
+ # the exit code returned from exec()/show_modal():
+ TankDialogBase.done(self, exit_code)
+
+ # and emit dialog closed signal:
+ self.dialog_closed.emit(self)
+
+ def detach_widget(self):
+ """
+ Detach the widget from the dialog so that it
+ remains alive when the dialog is removed gc'd
+ """
+ if not self._widget:
+ return None
+
+ # stop watching for the widget being closed:
+ if hasattr(self._widget, "_tk_widgetwrapper_widget_closed"):
+ # disconnect from the widget closed signal
+ self._widget._tk_widgetwrapper_widget_closed.disconnect(
+ self._on_widget_closed
+ )
+
+ elif self._orig_widget_closeEvent:
+ # apply fix to make sure all workers in pre v0.1.17 tk-framework-widget
+ # BrowserWidgets are stopped correctly!
+ # Note, this is the only place this can be done for non-wrapped
+ # widgets as once it's detached we have no further access to it!
+ TankQDialog._stop_buggy_background_worker_qthreads(self)
+
+ # reset the widget closeEvent function. Note that
+ # python still thinks there is a circular reference
+ # (inst->bound method->inst) so this will get gc'd
+ # but not straight away!
+ self._widget.closeEvent = self._orig_widget_closeEvent
+ self._orig_widget_closeEvent = None
+
+ # unparent the widget from the dialog:
+ if self._widget.parent() == self.ui.page_1:
+ self._widget.setParent(None)
+
+ # clear self._widget and return it
+ widget = self._widget
+ self._widget = None
+ return widget
+
+ def _widget_closeEvent(self, event):
+ """
+ Called if the contained widget isn't a wrapped widget
+ and it's closed by calling widget.close()
+ """
+ if self._orig_widget_closeEvent:
+ # call the original closeEvent
+ self._orig_widget_closeEvent(event)
+
+ if not event.isAccepted():
+ # widget didn't accept the close
+ # so stop!
+ return
+
+ # the widget is going to close so
+ # lets handle it!
+ self._on_widget_closed()
+
+ def _on_widget_closed(self):
+ """
+ This is called when the contained widget is closed - it
+ handles the event and then closes the dialog
+ """
+ exit_code = QtWidgets.QDialog.Accepted
+
+ # look if the hosted widget has an exit_code we should pick up
+ if self._widget and hasattr(self._widget, "exit_code"):
+ exit_code = self._widget.exit_code
+
+ # and call done to close the dialog with the correct exit
+ # code and emit the dialog closed signal.
+ #
+ # Note that we don't call done() directly as it would
+ # recursively call close on our widget again!
+ self._do_done(exit_code)
+
+ def _on_arrow(self):
+ """
+ callback when someone clicks the 'details' > arrow icon
+ """
+ if not hasattr(QtCore, "QAbstractAnimation"):
+ # This version of Qt doesn't expose the Q*Animation classes (probably
+ # and old version of PyQt) so just move the pages manually:
+ self.setUpdatesEnabled(False)
+ try:
+ if self._info_mode:
+ # hide the info panel:
+ # activate page 1 again - note that this will reset all positions!
+ self.ui.stackedWidget.setCurrentIndex(0)
+
+ # Flip arrow icon
+ self.ui.details_show.setVisible(True)
+ self.ui.details_hide.setVisible(False)
+ else:
+ # show the info panel:
+ # activate page 2 - note that this will reset all positions!
+ self.ui.stackedWidget.setCurrentIndex(1)
+
+ # Flip arrow icon
+ self.ui.details_show.setVisible(False)
+ self.ui.details_hide.setVisible(True)
+
+ # this hides page page 1, so let's show it again
+ self.ui.page_1.show()
+ # make sure page1 stays on top
+ self.ui.page_1.raise_()
+ # and move the page 1 window to allow room for page 2, the info panel:
+ self.ui.page_1.move(
+ self.ui.page_1.x()
+ - (TankQDialog.GRADIENT_WIDTH + TankQDialog.INFO_WIDTH),
+ self.ui.page_1.y(),
+ )
+ finally:
+ self.setUpdatesEnabled(True)
+
+ self._info_mode = not (self._info_mode)
+ else:
+ # lets animate the transition:
+ self.__animate_toggle_info_panel()
+
+ def __animate_toggle_info_panel(self):
+ """
+ Toggle the visibility of the info panel, animating the transition.
+ """
+ if self._info_mode:
+ # Flip arrow icon
+ self.ui.details_show.setVisible(True)
+ self.ui.details_hide.setVisible(False)
+
+ self.setUpdatesEnabled(False)
+ try:
+ # activate page 1 again - note that this will reset all positions!
+ self.ui.stackedWidget.setCurrentIndex(0)
+ # this hides page page 2, but let's show it again
+ self.ui.page_2.show()
+ # put this window top most to avoid flickering
+ self.ui.page_2.raise_()
+ # and move the page 1 window back to its current position
+ self.ui.page_1.move(
+ self.ui.page_1.x()
+ - (TankQDialog.GRADIENT_WIDTH + TankQDialog.INFO_WIDTH),
+ self.ui.page_1.y(),
+ )
+ # now that the first window is positioned correctly, make it top most again.
+ self.ui.page_1.raise_()
+ finally:
+ self.setUpdatesEnabled(True)
+
+ self.anim = QtCore.QPropertyAnimation(self.ui.page_1, b"pos")
+ self.anim.setDuration(600)
+ self.anim.setStartValue(
+ QtCore.QPoint(self.ui.page_1.x(), self.ui.page_1.y())
+ )
+ self.anim.setEndValue(
+ QtCore.QPoint(
+ self.ui.page_1.x()
+ + (TankQDialog.GRADIENT_WIDTH + TankQDialog.INFO_WIDTH),
+ self.ui.page_1.y(),
+ )
+ )
+ self.anim.setEasingCurve(QtCore.QEasingCurve.OutCubic)
+ self.anim.finished.connect(self._finished_show_anim)
+
+ self.anim2 = QtCore.QPropertyAnimation(self.ui.page_2, b"pos")
+ self.anim2.setDuration(600)
+ self.anim2.setStartValue(
+ QtCore.QPoint(self.ui.page_2.x(), self.ui.page_2.y())
+ )
+ self.anim2.setEndValue(
+ QtCore.QPoint(
+ self.ui.page_2.x()
+ + TankQDialog.GRADIENT_WIDTH
+ + TankQDialog.INFO_WIDTH,
+ self.ui.page_2.y(),
+ )
+ )
+ self.anim2.setEasingCurve(QtCore.QEasingCurve.OutCubic)
+
+ self.grp = QtCore.QParallelAnimationGroup()
+ self.grp.addAnimation(self.anim)
+ self.grp.addAnimation(self.anim2)
+ self.grp.start()
+
+ else:
+ # Flip arrow icon
+ self.ui.details_show.setVisible(False)
+ self.ui.details_hide.setVisible(True)
+
+ # activate page 2 - note that this will reset all positions!
+ self.ui.stackedWidget.setCurrentIndex(1)
+ # this hides page page 1, but let's show it again
+ self.ui.page_1.show()
+ # but make sure page1 stays on top
+ self.ui.page_1.raise_()
+
+ self.anim = QtCore.QPropertyAnimation(self.ui.page_2, b"pos")
+ self.anim.setDuration(600)
+ self.anim.setStartValue(
+ QtCore.QPoint(
+ self.ui.page_2.x()
+ + (TankQDialog.GRADIENT_WIDTH + TankQDialog.INFO_WIDTH),
+ self.ui.page_2.y(),
+ )
+ )
+ self.anim.setEndValue(QtCore.QPoint(self.ui.page_2.x(), self.ui.page_2.y()))
+ self.anim.setEasingCurve(QtCore.QEasingCurve.OutCubic)
+
+ self.anim2 = QtCore.QPropertyAnimation(self.ui.page_1, b"pos")
+ self.anim2.setDuration(600)
+ self.anim2.setStartValue(
+ QtCore.QPoint(self.ui.page_1.x(), self.ui.page_1.y())
+ )
+ self.anim2.setEndValue(
+ QtCore.QPoint(
+ self.ui.page_1.x()
+ - (TankQDialog.GRADIENT_WIDTH + TankQDialog.INFO_WIDTH),
+ self.ui.page_1.y(),
+ )
+ )
+ self.anim2.setEasingCurve(QtCore.QEasingCurve.OutCubic)
+ self.anim2.finished.connect(self._finished_show_anim)
+
+ self.grp = QtCore.QParallelAnimationGroup()
+ self.grp.addAnimation(self.anim)
+ self.grp.addAnimation(self.anim2)
+ self.grp.start()
+
+ def _finished_show_anim(self):
+ """
+ Callback called when the animation is complete
+ """
+ # now set the new info mode representing
+ # the state we have just arrived at
+ self._info_mode = not (self._info_mode)
+
+ if not self._info_mode:
+ # no longer want to display the side bar
+ self.ui.page_2.hide()
+
+ def _on_doc(self):
+ """
+ Launch doc url
+ """
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(self._bundle.documentation_url))
+
+ def _on_support(self):
+ """
+ Launch support url
+ """
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(self._bundle.support_url))
+
+ def _on_filesystem(self):
+ """
+ Show the context in the file system
+ """
+ # launch one window for each location on disk
+ paths = self._bundle.context.filesystem_locations
+ for disk_location in paths:
+
+ url = QtCore.QUrl.fromLocalFile(disk_location)
+ status = QtGui.QDesktopServices.openUrl(url)
+
+ if not status:
+ self._engine.log_error("Failed to open '%s'!" % disk_location)
+
+ def _on_shotgun(self):
+ """
+ Show the context in shotgun
+ """
+ url = self._bundle.context.shotgun_url
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
+
+ def _on_reload(self):
+ """
+ Reloads the engine and apps
+ """
+ try:
+ # first, reload the template defs
+ self._bundle.tank.reload_templates()
+ except TankError as e:
+ self._bundle.log_error(e)
+
+ try:
+
+ # now restart the engine
+ current_context = self._bundle.context
+ current_engine_name = self._bundle.engine.name
+ if engine.current_engine():
+ engine.current_engine().destroy()
+ engine.start_engine(
+ current_engine_name, current_context.tank, current_context
+ )
+ except TankError as e:
+ self._bundle.log_error("Could not restart the engine: %s" % e)
+ except Exception:
+ self._bundle.log_exception("Could not restart the engine!")
+
+ def _on_edit_config(self):
+ """
+ Future use
+ """
+ pass
+
+ def _on_add_param(self):
+ """
+ Future use
+ """
+ pass
+
+ def _add_settings_item(self, setting, params, value):
+ """
+ Adds a settings item to the list of settings.
+ """
+ widget = ConfigItem(setting, params, value, self._bundle, self)
+ self.ui.config_layout.addWidget(widget)
+ self._config_items.append(widget)
diff --git a/python/tank/platform/qt6/ui_busy_dialog.py b/python/tank/platform/qt6/ui_busy_dialog.py
new file mode 100644
index 0000000000..deff8a4de7
--- /dev/null
+++ b/python/tank/platform/qt6/ui_busy_dialog.py
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'busy_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from . import QtCore
+for name, cls in QtCore.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import QtGui
+for name, cls in QtGui.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import QtWidgets
+for name, cls in QtWidgets.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import resources_rc
+
+class Ui_BusyDialog(object):
+ def setupUi(self, BusyDialog):
+ if not BusyDialog.objectName():
+ BusyDialog.setObjectName(u"BusyDialog")
+ BusyDialog.resize(500, 110)
+ BusyDialog.setStyleSheet(u"/* Style for the window itself */\n"
+"#frame {\n"
+"border-color: #30A7E3;\n"
+"border-style: solid;\n"
+"border-width: 2px;\n"
+"}\n"
+"\n"
+"/* Style for the header text */\n"
+"#title {\n"
+"color: #30A7E3;\n"
+"margin-top: 15px;\n"
+"margin-bottom: 0px;\n"
+"margin-left: 1px;\n"
+"font-size: 16px;\n"
+"font-weight: bold;\n"
+"}\n"
+"\n"
+"/* Style for the details text */\n"
+"#details {\n"
+"margin-top: 1px;\n"
+"margin-left: 3px;\n"
+"margin-bottom: 0px;\n"
+"font-size: 11px;\n"
+"}\n"
+"")
+ self.horizontalLayout_2 = QHBoxLayout(BusyDialog)
+ self.horizontalLayout_2.setSpacing(2)
+ self.horizontalLayout_2.setContentsMargins(2, 2, 2, 2)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.frame = QFrame(BusyDialog)
+ self.frame.setObjectName(u"frame")
+ self.frame.setFrameShape(QFrame.StyledPanel)
+ self.frame.setFrameShadow(QFrame.Raised)
+ self.horizontalLayout = QHBoxLayout(self.frame)
+ self.horizontalLayout.setSpacing(5)
+ self.horizontalLayout.setContentsMargins(5, 5, 5, 5)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.label = QLabel(self.frame)
+ self.label.setObjectName(u"label")
+ self.label.setPixmap(QPixmap(u":/Tank.Platform.Resources/sg_logo_80px.png"))
+
+ self.horizontalLayout.addWidget(self.label)
+
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.title = QLabel(self.frame)
+ self.title.setObjectName(u"title")
+ sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.title.sizePolicy().hasHeightForWidth())
+ self.title.setSizePolicy(sizePolicy)
+
+ self.verticalLayout.addWidget(self.title)
+
+ self.details = QLabel(self.frame)
+ self.details.setObjectName(u"details")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.details.sizePolicy().hasHeightForWidth())
+ self.details.setSizePolicy(sizePolicy1)
+ self.details.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop)
+ self.details.setWordWrap(True)
+
+ self.verticalLayout.addWidget(self.details)
+
+ self.horizontalLayout.addLayout(self.verticalLayout)
+
+ self.horizontalLayout_2.addWidget(self.frame)
+
+ self.retranslateUi(BusyDialog)
+
+ QMetaObject.connectSlotsByName(BusyDialog)
+ # setupUi
+
+ def retranslateUi(self, BusyDialog):
+ BusyDialog.setWindowTitle(QCoreApplication.translate("BusyDialog", u"Dialog", None))
+ self.label.setText("")
+ self.title.setText(QCoreApplication.translate("BusyDialog", u"Doing something, hang on!", None))
+ self.details.setText(QCoreApplication.translate("BusyDialog", u"Lots of interesting details about what is going on", None))
+ # retranslateUi
diff --git a/python/tank/platform/qt6/ui_item.py b/python/tank/platform/qt6/ui_item.py
new file mode 100644
index 0000000000..58e661d6e8
--- /dev/null
+++ b/python/tank/platform/qt6/ui_item.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'item.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from . import QtCore
+for name, cls in QtCore.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import QtGui
+for name, cls in QtGui.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import QtWidgets
+for name, cls in QtWidgets.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import resources_rc
+
+class Ui_Item(object):
+ def setupUi(self, Item):
+ if not Item.objectName():
+ Item.setObjectName(u"Item")
+ Item.resize(335, 110)
+ sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(Item.sizePolicy().hasHeightForWidth())
+ Item.setSizePolicy(sizePolicy)
+ Item.setStyleSheet(u"QLabel{\n"
+" font-size: 11px;\n"
+" margin-bottom: 3px\n"
+"}\n"
+"")
+ self.verticalLayout = QVBoxLayout(Item)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout_2 = QVBoxLayout()
+ self.verticalLayout_2.setSpacing(0)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.name = QLabel(Item)
+ self.name.setObjectName(u"name")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.name.sizePolicy().hasHeightForWidth())
+ self.name.setSizePolicy(sizePolicy1)
+ self.name.setStyleSheet(u"font-size: 13px;")
+ self.name.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.name.setWordWrap(True)
+
+ self.verticalLayout_2.addWidget(self.name)
+
+ self.line = QFrame(Item)
+ self.line.setObjectName(u"line")
+ self.line.setStyleSheet(u"border: none;\n"
+"border-bottom-color: rgba(150,150,150,100);\n"
+"border-bottom-width: 1px;\n"
+"border-bottom-style: solid;")
+ self.line.setFrameShape(QFrame.HLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_2.addWidget(self.line)
+
+ self.verticalLayout.addLayout(self.verticalLayout_2)
+
+ self.value = QLabel(Item)
+ self.value.setObjectName(u"value")
+ sizePolicy1.setHeightForWidth(self.value.sizePolicy().hasHeightForWidth())
+ self.value.setSizePolicy(sizePolicy1)
+ self.value.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.value.setWordWrap(True)
+ self.value.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse)
+
+ self.verticalLayout.addWidget(self.value)
+
+ self.type = QLabel(Item)
+ self.type.setObjectName(u"type")
+ sizePolicy1.setHeightForWidth(self.type.sizePolicy().hasHeightForWidth())
+ self.type.setSizePolicy(sizePolicy1)
+ self.type.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.type.setWordWrap(True)
+ self.type.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse)
+
+ self.verticalLayout.addWidget(self.type)
+
+ self.description = QLabel(Item)
+ self.description.setObjectName(u"description")
+ self.description.setMaximumSize(QSize(350, 16777215))
+ self.description.setTextFormat(Qt.RichText)
+ self.description.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.description.setWordWrap(True)
+ self.description.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse)
+
+ self.verticalLayout.addWidget(self.description)
+
+ self.verticalSpacer = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout.addItem(self.verticalSpacer)
+
+ self.verticalLayout.setStretch(4, 1)
+
+ self.retranslateUi(Item)
+
+ QMetaObject.connectSlotsByName(Item)
+ # setupUi
+
+ def retranslateUi(self, Item):
+ Item.setWindowTitle(QCoreApplication.translate("Item", u"Form", None))
+ self.name.setText(QCoreApplication.translate("Item", u"Settings Name", None))
+ self.value.setText(QCoreApplication.translate("Item", u"Value: foo bar", None))
+ self.type.setText(QCoreApplication.translate("Item", u"Type: bool", None))
+ self.description.setText(QCoreApplication.translate("Item", u"\n"
+"\n"
+"description
", None))
+ # retranslateUi
diff --git a/python/tank/platform/qt6/ui_tank_dialog.py b/python/tank/platform/qt6/ui_tank_dialog.py
new file mode 100644
index 0000000000..ae986de533
--- /dev/null
+++ b/python/tank/platform/qt6/ui_tank_dialog.py
@@ -0,0 +1,423 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'tank_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from . import QtCore
+for name, cls in QtCore.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import QtGui
+for name, cls in QtGui.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import QtWidgets
+for name, cls in QtWidgets.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from . import resources_rc
+
+class Ui_TankDialog(object):
+ def setupUi(self, TankDialog):
+ if not TankDialog.objectName():
+ TankDialog.setObjectName(u"TankDialog")
+ TankDialog.resize(879, 551)
+ TankDialog.setStyleSheet(u"")
+ self.verticalLayout_3 = QVBoxLayout(TankDialog)
+ self.verticalLayout_3.setSpacing(0)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
+ self.stackedWidget = QStackedWidget(TankDialog)
+ self.stackedWidget.setObjectName(u"stackedWidget")
+ self.page_1 = QWidget()
+ self.page_1.setObjectName(u"page_1")
+ self.page_1.setStyleSheet(u"QWidget#page_1 {\n"
+"margin: 0px;\n"
+"}")
+ self.verticalLayout = QVBoxLayout(self.page_1)
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.top_group = QGroupBox(self.page_1)
+ self.top_group.setObjectName(u"top_group")
+ self.top_group.setMinimumSize(QSize(0, 45))
+ self.top_group.setMaximumSize(QSize(16777215, 45))
+ self.top_group.setStyleSheet(u"#top_group {\n"
+"background-color: #2D2D2D;\n"
+"border: none;\n"
+"border-bottom:1px solid #202020;\n"
+"}\n"
+"")
+ self.top_group.setFlat(False)
+ self.horizontalLayout = QHBoxLayout(self.top_group)
+ self.horizontalLayout.setSpacing(0)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setContentsMargins(4, 0, 1, 1)
+ self.tank_logo = QLabel(self.top_group)
+ self.tank_logo.setObjectName(u"tank_logo")
+ sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.tank_logo.sizePolicy().hasHeightForWidth())
+ self.tank_logo.setSizePolicy(sizePolicy)
+ self.tank_logo.setPixmap(QPixmap(u":/Tank.Platform.Resources/tank_logo.png"))
+
+ self.horizontalLayout.addWidget(self.tank_logo)
+
+ self.label = QLabel(self.top_group)
+ self.label.setObjectName(u"label")
+ self.label.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n"
+"QLabel{\n"
+" color: white;\n"
+" font-size: 20px;\n"
+" margin-left: 5px;\n"
+" font-family: \"Open Sans\";\n"
+" font-style: \"Regular\";\n"
+"}")
+
+ self.horizontalLayout.addWidget(self.label)
+
+ self.lbl_context = QLabel(self.top_group)
+ self.lbl_context.setObjectName(u"lbl_context")
+ self.lbl_context.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n"
+"QLabel {\n"
+" color: rgba(250,250,250,180);\n"
+" font-size: 11px;\n"
+" margin-right: 8px;\n"
+" font-family: \"Open Sans\";\n"
+" font-style: \"Regular\";\n"
+"}\n"
+"\n"
+"\n"
+"")
+ self.lbl_context.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
+
+ self.horizontalLayout.addWidget(self.lbl_context)
+
+ self.details_show = QToolButton(self.top_group)
+ self.details_show.setObjectName(u"details_show")
+ self.details_show.setMinimumSize(QSize(34, 34))
+ self.details_show.setFocusPolicy(Qt.ClickFocus)
+ self.details_show.setStyleSheet(u"QToolButton{\n"
+"width: 12px;\n"
+"height: 20px;\n"
+"background-image: url(:/Tank.Platform.Resources/arrow.png);\n"
+"border: none;\n"
+"background-color: none;\n"
+"}\n"
+"\n"
+"QToolButton:hover{\n"
+"background-image: url(:/Tank.Platform.Resources/arrow_hover.png);\n"
+"}\n"
+"\n"
+"QToolButton:pressed{\n"
+"background-image: url(:/Tank.Platform.Resources/arrow_pressed.png);\n"
+"}\n"
+"")
+ self.details_show.setAutoRaise(True)
+
+ self.horizontalLayout.addWidget(self.details_show)
+
+ self.details_hide = QToolButton(self.top_group)
+ self.details_hide.setObjectName(u"details_hide")
+ self.details_hide.setMinimumSize(QSize(34, 34))
+ self.details_hide.setFocusPolicy(Qt.ClickFocus)
+ self.details_hide.setVisible(False)
+ self.details_hide.setStyleSheet(u"QToolButton{\n"
+" width: 12px;\n"
+" height: 20px;\n"
+" background-image: url(:/Tank.Platform.Resources/arrow_flipped.png);\n"
+" border: none;\n"
+" background-color: none;\n"
+" }\n"
+"\n"
+" QToolButton:hover{\n"
+" background-image: url(:/Tank.Platform.Resources/arrow_flipped_hover.png);\n"
+" }\n"
+"\n"
+" QToolButton:pressed{\n"
+" background-image: url(:/Tank.Platform.Resources/arrow_flipped_pressed.png);\n"
+" }\n"
+" ")
+ self.details_hide.setAutoRaise(True)
+
+ self.horizontalLayout.addWidget(self.details_hide)
+
+ self.verticalLayout.addWidget(self.top_group)
+
+ self.target = QVBoxLayout()
+ self.target.setSpacing(4)
+ self.target.setObjectName(u"target")
+
+ self.verticalLayout.addLayout(self.target)
+
+ self.stackedWidget.addWidget(self.page_1)
+ self.page_2 = QWidget()
+ self.page_2.setObjectName(u"page_2")
+ self.page_2.setStyleSheet(u"QWidget {\n"
+" font-family: \"Open Sans\";\n"
+" font-style: \"Regular\";\n"
+"}")
+ self.verticalLayout_2 = QVBoxLayout(self.page_2)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(1, 1, 1, 1)
+ self.page_2_group = QGroupBox(self.page_2)
+ self.page_2_group.setObjectName(u"page_2_group")
+ self.page_2_group.setMinimumSize(QSize(0, 100))
+ self.page_2_group.setStyleSheet(u"QGroupBox {\n"
+"margin: 0px;\n"
+"}")
+ self.horizontalLayout_2 = QHBoxLayout(self.page_2_group)
+ self.horizontalLayout_2.setSpacing(0)
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
+ self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
+ self.horizontalSpacer = QSpacerItem(145, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_2.addItem(self.horizontalSpacer)
+
+ self.label_3 = QLabel(self.page_2_group)
+ self.label_3.setObjectName(u"label_3")
+ self.label_3.setMinimumSize(QSize(40, 0))
+ self.label_3.setMaximumSize(QSize(40, 16777215))
+
+ self.horizontalLayout_2.addWidget(self.label_3)
+
+ self.gradient = QGroupBox(self.page_2_group)
+ self.gradient.setObjectName(u"gradient")
+ self.gradient.setMinimumSize(QSize(11, 0))
+ self.gradient.setMaximumSize(QSize(11, 16777215))
+ self.gradient.setStyleSheet(u"#gradient {\n"
+"background-image: url(:/Tank.Platform.Resources/gradient.png);\n"
+"border: none;\n"
+"}")
+
+ self.horizontalLayout_2.addWidget(self.gradient)
+
+ self.scrollArea = QScrollArea(self.page_2_group)
+ self.scrollArea.setObjectName(u"scrollArea")
+ self.scrollArea.setMinimumSize(QSize(400, 0))
+ self.scrollArea.setMaximumSize(QSize(400, 16777215))
+ self.scrollArea.setStyleSheet(u"/*\n"
+"All labels inside this scroll area should be 12px font.\n"
+"This is to avoid the UI looking different in different app like\n"
+"maya and nuke which all use slightly different style sheets.\n"
+" */\n"
+"QLabel{\n"
+" font-size: 11px;\n"
+" margin-bottom: 8px\n"
+"}\n"
+"")
+ self.scrollArea.setWidgetResizable(True)
+ self.scrollAreaWidgetContents = QWidget()
+ self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
+ self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 398, 550))
+ self.verticalLayout_4 = QVBoxLayout(self.scrollAreaWidgetContents)
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
+ self.horizontalLayout_4 = QHBoxLayout()
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
+ self.app_icon = QLabel(self.scrollAreaWidgetContents)
+ self.app_icon.setObjectName(u"app_icon")
+ self.app_icon.setMinimumSize(QSize(64, 64))
+ self.app_icon.setMaximumSize(QSize(64, 64))
+ self.app_icon.setPixmap(QPixmap(u":/Tank.Platform.Resources/default_app_icon_256.png"))
+ self.app_icon.setScaledContents(True)
+ self.app_icon.setAlignment(Qt.AlignCenter)
+
+ self.horizontalLayout_4.addWidget(self.app_icon)
+
+ self.verticalLayout_8 = QVBoxLayout()
+ self.verticalLayout_8.setSpacing(1)
+ self.verticalLayout_8.setObjectName(u"verticalLayout_8")
+ self.app_name = QLabel(self.scrollAreaWidgetContents)
+ self.app_name.setObjectName(u"app_name")
+ self.app_name.setStyleSheet(u"font-size: 16px;\n"
+"")
+ self.app_name.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+
+ self.verticalLayout_8.addWidget(self.app_name)
+
+ self.horizontalLayout_4.addLayout(self.verticalLayout_8)
+
+ self.verticalLayout_4.addLayout(self.horizontalLayout_4)
+
+ self.app_description = QLabel(self.scrollAreaWidgetContents)
+ self.app_description.setObjectName(u"app_description")
+ self.app_description.setMaximumSize(QSize(350, 16777215))
+ self.app_description.setWordWrap(True)
+
+ self.verticalLayout_4.addWidget(self.app_description)
+
+ self.app_tech_details = QLabel(self.scrollAreaWidgetContents)
+ self.app_tech_details.setObjectName(u"app_tech_details")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ sizePolicy1.setHeightForWidth(self.app_tech_details.sizePolicy().hasHeightForWidth())
+ self.app_tech_details.setSizePolicy(sizePolicy1)
+ self.app_tech_details.setMinimumSize(QSize(0, 22))
+ self.app_tech_details.setMaximumSize(QSize(16777215, 22))
+ self.app_tech_details.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
+ self.app_tech_details.setWordWrap(True)
+
+ self.verticalLayout_4.addWidget(self.app_tech_details)
+
+ self.horizontalLayout_9 = QHBoxLayout()
+ self.horizontalLayout_9.setSpacing(2)
+ self.horizontalLayout_9.setObjectName(u"horizontalLayout_9")
+ self.btn_documentation = QToolButton(self.scrollAreaWidgetContents)
+ self.btn_documentation.setObjectName(u"btn_documentation")
+
+ self.horizontalLayout_9.addWidget(self.btn_documentation)
+
+ self.btn_support = QToolButton(self.scrollAreaWidgetContents)
+ self.btn_support.setObjectName(u"btn_support")
+
+ self.horizontalLayout_9.addWidget(self.btn_support)
+
+ self.horizontalSpacer_5 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_9.addItem(self.horizontalSpacer_5)
+
+ self.verticalLayout_4.addLayout(self.horizontalLayout_9)
+
+ self.label_5 = QLabel(self.scrollAreaWidgetContents)
+ self.label_5.setObjectName(u"label_5")
+ self.label_5.setStyleSheet(u"font-size: 16px;\n"
+"margin-top: 30px;")
+
+ self.verticalLayout_4.addWidget(self.label_5)
+
+ self.line = QFrame(self.scrollAreaWidgetContents)
+ self.line.setObjectName(u"line")
+ self.line.setFrameShape(QFrame.HLine)
+ self.line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_4.addWidget(self.line)
+
+ self.app_work_area_info = QLabel(self.scrollAreaWidgetContents)
+ self.app_work_area_info.setObjectName(u"app_work_area_info")
+ self.app_work_area_info.setMaximumSize(QSize(350, 16777215))
+ self.app_work_area_info.setWordWrap(True)
+
+ self.verticalLayout_4.addWidget(self.app_work_area_info)
+
+ self.horizontalLayout_10 = QHBoxLayout()
+ self.horizontalLayout_10.setSpacing(2)
+ self.horizontalLayout_10.setObjectName(u"horizontalLayout_10")
+ self.btn_file_system = QToolButton(self.scrollAreaWidgetContents)
+ self.btn_file_system.setObjectName(u"btn_file_system")
+
+ self.horizontalLayout_10.addWidget(self.btn_file_system)
+
+ self.btn_shotgun = QToolButton(self.scrollAreaWidgetContents)
+ self.btn_shotgun.setObjectName(u"btn_shotgun")
+
+ self.horizontalLayout_10.addWidget(self.btn_shotgun)
+
+ self.horizontalSpacer_6 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
+
+ self.horizontalLayout_10.addItem(self.horizontalSpacer_6)
+
+ self.verticalLayout_4.addLayout(self.horizontalLayout_10)
+
+ self.app_work_area_info_2 = QLabel(self.scrollAreaWidgetContents)
+ self.app_work_area_info_2.setObjectName(u"app_work_area_info_2")
+ self.app_work_area_info_2.setMaximumSize(QSize(350, 16777215))
+ self.app_work_area_info_2.setWordWrap(True)
+
+ self.verticalLayout_4.addWidget(self.app_work_area_info_2)
+
+ self.btn_reload = QToolButton(self.scrollAreaWidgetContents)
+ self.btn_reload.setObjectName(u"btn_reload")
+
+ self.verticalLayout_4.addWidget(self.btn_reload)
+
+ self.config_header = QLabel(self.scrollAreaWidgetContents)
+ self.config_header.setObjectName(u"config_header")
+ self.config_header.setStyleSheet(u"font-size: 16px;\n"
+"margin-top: 30px;")
+
+ self.verticalLayout_4.addWidget(self.config_header)
+
+ self.config_line = QFrame(self.scrollAreaWidgetContents)
+ self.config_line.setObjectName(u"config_line")
+ self.config_line.setFrameShape(QFrame.HLine)
+ self.config_line.setFrameShadow(QFrame.Sunken)
+
+ self.verticalLayout_4.addWidget(self.config_line)
+
+ self.config_label = QLabel(self.scrollAreaWidgetContents)
+ self.config_label.setObjectName(u"config_label")
+ self.config_label.setMaximumSize(QSize(350, 16777215))
+ self.config_label.setWordWrap(True)
+
+ self.verticalLayout_4.addWidget(self.config_label)
+
+ self.config_layout = QVBoxLayout()
+ self.config_layout.setSpacing(20)
+ self.config_layout.setObjectName(u"config_layout")
+
+ self.verticalLayout_4.addLayout(self.config_layout)
+
+ self.verticalSpacer_2 = QSpacerItem(328, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_4.addItem(self.verticalSpacer_2)
+
+ self.scrollArea.setWidget(self.scrollAreaWidgetContents)
+
+ self.horizontalLayout_2.addWidget(self.scrollArea)
+
+ self.verticalLayout_2.addWidget(self.page_2_group)
+
+ self.stackedWidget.addWidget(self.page_2)
+
+ self.verticalLayout_3.addWidget(self.stackedWidget)
+
+ self.retranslateUi(TankDialog)
+
+ self.stackedWidget.setCurrentIndex(0)
+
+ QMetaObject.connectSlotsByName(TankDialog)
+ # setupUi
+
+ def retranslateUi(self, TankDialog):
+ TankDialog.setWindowTitle(QCoreApplication.translate("TankDialog", u"Dialog", None))
+ self.top_group.setTitle("")
+ self.tank_logo.setText("")
+ self.label.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None))
+#if QT_CONFIG(tooltip)
+ self.lbl_context.setToolTip(QCoreApplication.translate("TankDialog", u"foo bar", None))
+#endif // QT_CONFIG(tooltip)
+ self.lbl_context.setText(QCoreApplication.translate("TankDialog", u"Current Work Area:\n"
+"TextLabel", None))
+#if QT_CONFIG(tooltip)
+ self.details_show.setToolTip(QCoreApplication.translate("TankDialog", u"Click for App Details", None))
+#endif // QT_CONFIG(tooltip)
+ self.details_show.setText("")
+#if QT_CONFIG(tooltip)
+ self.details_hide.setToolTip(QCoreApplication.translate("TankDialog", u"Hide App Details", None))
+#endif // QT_CONFIG(tooltip)
+ self.details_hide.setText("")
+ self.page_2_group.setTitle("")
+ self.label_3.setText("")
+ self.gradient.setTitle("")
+ self.app_icon.setText("")
+ self.app_name.setText(QCoreApplication.translate("TankDialog", u"Publish And Snapshot", None))
+ self.app_description.setText(QCoreApplication.translate("TankDialog", u"Tools to see what is out of date in your scene etc etc.", None))
+ self.app_tech_details.setText(QCoreApplication.translate("TankDialog", u"tk-multi-snapshot, v1.2.3", None))
+ self.btn_documentation.setText(QCoreApplication.translate("TankDialog", u"Documentation", None))
+ self.btn_support.setText(QCoreApplication.translate("TankDialog", u"Help && Support", None))
+ self.label_5.setText(QCoreApplication.translate("TankDialog", u"Your Current Work Area", None))
+ self.app_work_area_info.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None))
+ self.btn_file_system.setText(QCoreApplication.translate("TankDialog", u"Jump to File System", None))
+ self.btn_shotgun.setText(QCoreApplication.translate("TankDialog", u"Jump to Flow Production Tracking", None))
+ self.app_work_area_info_2.setText(QCoreApplication.translate("TankDialog", u"If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:", None))
+ self.btn_reload.setText(QCoreApplication.translate("TankDialog", u"Reload Engine and Apps", None))
+ self.config_header.setText(QCoreApplication.translate("TankDialog", u"Configuration", None))
+ self.config_label.setText(QCoreApplication.translate("TankDialog", u"Below is a list of all the configuration settings for this app, as defined in your environment file:", None))
+ # retranslateUi
diff --git a/python/tank/platform/resources/arrow.png b/python/tank/platform/resources/arrow.png
new file mode 100644
index 0000000000..0f3a92c097
Binary files /dev/null and b/python/tank/platform/resources/arrow.png differ
diff --git a/python/tank/platform/resources/arrow_flipped.png b/python/tank/platform/resources/arrow_flipped.png
new file mode 100644
index 0000000000..2107eaef57
Binary files /dev/null and b/python/tank/platform/resources/arrow_flipped.png differ
diff --git a/python/tank/platform/resources/arrow_flipped_hover.png b/python/tank/platform/resources/arrow_flipped_hover.png
new file mode 100644
index 0000000000..25a93bd22f
Binary files /dev/null and b/python/tank/platform/resources/arrow_flipped_hover.png differ
diff --git a/python/tank/platform/resources/arrow_flipped_pressed.png b/python/tank/platform/resources/arrow_flipped_pressed.png
new file mode 100644
index 0000000000..96c1fd02ea
Binary files /dev/null and b/python/tank/platform/resources/arrow_flipped_pressed.png differ
diff --git a/python/tank/platform/resources/arrow_hover.png b/python/tank/platform/resources/arrow_hover.png
new file mode 100644
index 0000000000..57d9643075
Binary files /dev/null and b/python/tank/platform/resources/arrow_hover.png differ
diff --git a/python/tank/platform/resources/arrow_pressed.png b/python/tank/platform/resources/arrow_pressed.png
new file mode 100644
index 0000000000..463a085dbb
Binary files /dev/null and b/python/tank/platform/resources/arrow_pressed.png differ
diff --git a/python/tank/platform/resources/book_256.png b/python/tank/platform/resources/book_256.png
new file mode 100644
index 0000000000..8dbad66c8e
Binary files /dev/null and b/python/tank/platform/resources/book_256.png differ
diff --git a/python/tank/platform/resources/busy_dialog.ui b/python/tank/platform/resources/busy_dialog.ui
new file mode 100644
index 0000000000..26b5425a62
--- /dev/null
+++ b/python/tank/platform/resources/busy_dialog.ui
@@ -0,0 +1,123 @@
+
+
+ BusyDialog
+
+
+
+ 0
+ 0
+ 500
+ 110
+
+
+
+ Dialog
+
+
+ /* Style for the window itself */
+#frame {
+border-color: #30A7E3;
+border-style: solid;
+border-width: 2px;
+}
+
+/* Style for the header text */
+#title {
+color: #30A7E3;
+margin-top: 15px;
+margin-bottom: 0px;
+margin-left: 1px;
+font-size: 16px;
+font-weight: bold;
+}
+
+/* Style for the details text */
+#details {
+margin-top: 1px;
+margin-left: 3px;
+margin-bottom: 0px;
+font-size: 11px;
+}
+
+
+
+
+ 2
+
+
+ 2
+
+ -
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ 5
+
+
+ 5
+
+
-
+
+
+
+
+
+ :/Tank.Platform.Qt6/sg_logo_80px.png
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Doing something, hang on!
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Lots of interesting details about what is going on
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/tank/platform/resources/default_app_icon_256.png b/python/tank/platform/resources/default_app_icon_256.png
new file mode 100644
index 0000000000..9de31db578
Binary files /dev/null and b/python/tank/platform/resources/default_app_icon_256.png differ
diff --git a/python/tank/platform/resources/folder_256.png b/python/tank/platform/resources/folder_256.png
new file mode 100644
index 0000000000..bd2ef4c2fa
Binary files /dev/null and b/python/tank/platform/resources/folder_256.png differ
diff --git a/python/tank/platform/resources/fonts/OpenSans/OpenSans-Bold.ttf b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Bold.ttf
new file mode 100644
index 0000000000..fd79d43bea
Binary files /dev/null and b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Bold.ttf differ
diff --git a/python/tank/platform/resources/fonts/OpenSans/OpenSans-CondLight.ttf b/python/tank/platform/resources/fonts/OpenSans/OpenSans-CondLight.ttf
new file mode 100644
index 0000000000..97c355b9f6
Binary files /dev/null and b/python/tank/platform/resources/fonts/OpenSans/OpenSans-CondLight.ttf differ
diff --git a/python/tank/platform/resources/fonts/OpenSans/OpenSans-Italic.ttf b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Italic.ttf
new file mode 100644
index 0000000000..c90da48ff3
Binary files /dev/null and b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Italic.ttf differ
diff --git a/python/tank/platform/resources/fonts/OpenSans/OpenSans-Light.ttf b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Light.ttf
new file mode 100644
index 0000000000..0d381897da
Binary files /dev/null and b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Light.ttf differ
diff --git a/python/tank/platform/resources/fonts/OpenSans/OpenSans-Regular.ttf b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Regular.ttf
new file mode 100644
index 0000000000..db433349b7
Binary files /dev/null and b/python/tank/platform/resources/fonts/OpenSans/OpenSans-Regular.ttf differ
diff --git a/python/tank/platform/resources/gradient.png b/python/tank/platform/resources/gradient.png
new file mode 100644
index 0000000000..ae6c274eb9
Binary files /dev/null and b/python/tank/platform/resources/gradient.png differ
diff --git a/python/tank/platform/resources/item.ui b/python/tank/platform/resources/item.ui
new file mode 100644
index 0000000000..412d12ef62
--- /dev/null
+++ b/python/tank/platform/resources/item.ui
@@ -0,0 +1,167 @@
+
+
+ Item
+
+
+
+ 0
+ 0
+ 335
+ 110
+
+
+
+
+ 0
+ 0
+
+
+
+ Form
+
+
+ QLabel{
+ font-size: 11px;
+ margin-bottom: 3px
+}
+
+
+
+
+ 0
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ font-size: 13px;
+
+
+ Settings Name
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ true
+
+
+
+ -
+
+
+ border: none;
+border-bottom-color: rgba(150,150,150,100);
+border-bottom-width: 1px;
+border-bottom-style: solid;
+
+
+ Qt::Horizontal
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Value: foo bar
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ true
+
+
+ Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Type: bool
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ true
+
+
+ Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse
+
+
+
+ -
+
+
+
+ 350
+ 16777215
+
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'Lucida Grande'; font-weight:400; font-style:normal;">
+<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">description</p></body></html>
+
+
+ Qt::RichText
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ true
+
+
+ Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/tank/platform/resources/pencil.png b/python/tank/platform/resources/pencil.png
new file mode 100644
index 0000000000..ea8bae6492
Binary files /dev/null and b/python/tank/platform/resources/pencil.png differ
diff --git a/python/tank/platform/resources/reload_256.png b/python/tank/platform/resources/reload_256.png
new file mode 100644
index 0000000000..2be5fd1de0
Binary files /dev/null and b/python/tank/platform/resources/reload_256.png differ
diff --git a/python/tank/platform/resources/resources.qrc b/python/tank/platform/resources/resources.qrc
new file mode 100644
index 0000000000..e6b9573cc7
--- /dev/null
+++ b/python/tank/platform/resources/resources.qrc
@@ -0,0 +1,15 @@
+
+
+ sg_logo_80px.png
+ pencil.png
+ gradient.png
+ arrow_hover.png
+ arrow_pressed.png
+ arrow.png
+ arrow_flipped_hover.png
+ arrow_flipped_pressed.png
+ arrow_flipped.png
+ tank_logo.png
+ default_app_icon_256.png
+
+
diff --git a/python/tank/platform/resources/sg_logo_80px.png b/python/tank/platform/resources/sg_logo_80px.png
new file mode 100644
index 0000000000..ffe721d84c
Binary files /dev/null and b/python/tank/platform/resources/sg_logo_80px.png differ
diff --git a/python/tank/platform/resources/tank_dialog.ui b/python/tank/platform/resources/tank_dialog.ui
new file mode 100644
index 0000000000..836c10de3a
--- /dev/null
+++ b/python/tank/platform/resources/tank_dialog.ui
@@ -0,0 +1,708 @@
+
+
+ TankDialog
+
+
+
+ 0
+ 0
+ 879
+ 551
+
+
+
+ Dialog
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ 0
+
+
+
+ QWidget#page_1 {
+margin: 0px;
+}
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 45
+
+
+
+
+ 16777215
+ 45
+
+
+
+ #top_group {
+background-color: #2D2D2D;
+border: none;
+border-bottom:1px solid #202020;
+}
+
+
+
+
+
+
+ false
+
+
+
+ 0
+
+
+ 4
+
+
+ 0
+
+
+ 1
+
+
+ 1
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ :/Tank.Platform.Qt6/tank_logo.png
+
+
+
+ -
+
+
+ /* want this stylesheet to apply to the label but not the tooltip */
+QLabel{
+ color: white;
+ font-size: 20px;
+ margin-left: 5px;
+ font-family: "Open Sans";
+ font-style: "Regular";
+}
+
+
+ TextLabel
+
+
+
+ -
+
+
+ foo bar
+
+
+ /* want this stylesheet to apply to the label but not the tooltip */
+QLabel {
+ color: rgba(250,250,250,180);
+ font-size: 11px;
+ margin-right: 8px;
+ font-family: "Open Sans";
+ font-style: "Regular";
+}
+
+
+
+
+
+ Current Work Area:
+TextLabel
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 34
+ 34
+
+
+
+ Qt::ClickFocus
+
+
+ Click for App Details
+
+
+ QToolButton{
+width: 12px;
+height: 20px;
+background-image: url(:/Tank.Platform.Qt6/arrow.png);
+border: none;
+background-color: none;
+}
+
+QToolButton:hover{
+background-image: url(:/Tank.Platform.Qt6/arrow_hover.png);
+}
+
+QToolButton:pressed{
+background-image: url(:/Tank.Platform.Qt6/arrow_pressed.png);
+}
+
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 34
+ 34
+
+
+
+ Qt::ClickFocus
+
+
+ Hide App Details
+
+
+ false
+
+
+ QToolButton{
+ width: 12px;
+ height: 20px;
+ background-image: url(:/Tank.Platform.Qt6/arrow_flipped.png);
+ border: none;
+ background-color: none;
+ }
+
+ QToolButton:hover{
+ background-image: url(:/Tank.Platform.Qt6/arrow_flipped_hover.png);
+ }
+
+ QToolButton:pressed{
+ background-image: url(:/Tank.Platform.Qt6/arrow_flipped_pressed.png);
+ }
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ 4
+
+
+
+
+
+
+
+ QWidget {
+ font-family: "Open Sans";
+ font-style: "Regular";
+}
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+ -
+
+
+
+ 0
+ 100
+
+
+
+ QGroupBox {
+margin: 0px;
+}
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Expanding
+
+
+
+ 145
+ 20
+
+
+
+
+ -
+
+
+
+ 40
+ 0
+
+
+
+
+ 40
+ 16777215
+
+
+
+
+
+
+
+ -
+
+
+
+ 11
+ 0
+
+
+
+
+ 11
+ 16777215
+
+
+
+ #gradient {
+background-image: url(:/Tank.Platform.Qt6/gradient.png);
+border: none;
+}
+
+
+
+
+
+
+ -
+
+
+
+ 400
+ 0
+
+
+
+
+ 400
+ 16777215
+
+
+
+ /*
+All labels inside this scroll area should be 12px font.
+This is to avoid the UI looking different in different app like
+maya and nuke which all use slightly different style sheets.
+ */
+QLabel{
+ font-size: 11px;
+ margin-bottom: 8px
+}
+
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 398
+ 550
+
+
+
+
-
+
+
-
+
+
+
+ 64
+ 64
+
+
+
+
+ 64
+ 64
+
+
+
+
+
+
+ :/Tank.Platform.Qt6/default_app_icon_256.png
+
+
+ true
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ 1
+
+
-
+
+
+ font-size: 16px;
+
+
+
+ Publish And Snapshot
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+
+
+
+
+ -
+
+
+
+ 350
+ 16777215
+
+
+
+ Tools to see what is out of date in your scene etc etc.
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 22
+
+
+
+
+ 16777215
+ 22
+
+
+
+ tk-multi-snapshot, v1.2.3
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ true
+
+
+
+ -
+
+
+ 2
+
+
-
+
+
+ Documentation
+
+
+
+ -
+
+
+ Help && Support
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 20
+
+
+
+
+
+
+ -
+
+
+ font-size: 16px;
+margin-top: 30px;
+
+
+ Your Current Work Area
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 350
+ 16777215
+
+
+
+ TextLabel
+
+
+ true
+
+
+
+ -
+
+
+ 2
+
+
-
+
+
+ Jump to File System
+
+
+
+ -
+
+
+ Jump to Flow Production Tracking
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 20
+
+
+
+
+
+
+ -
+
+
+
+ 350
+ 16777215
+
+
+
+ If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:
+
+
+ true
+
+
+
+ -
+
+
+ Reload Engine and Apps
+
+
+
+ -
+
+
+ font-size: 16px;
+margin-top: 30px;
+
+
+ Configuration
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 350
+ 16777215
+
+
+
+ Below is a list of all the configuration settings for this app, as defined in your environment file:
+
+
+ true
+
+
+
+ -
+
+
+ 20
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 328
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/tank/platform/resources/tank_logo.png b/python/tank/platform/resources/tank_logo.png
new file mode 100644
index 0000000000..f57628025d
Binary files /dev/null and b/python/tank/platform/resources/tank_logo.png differ
diff --git a/python/tank/platform/resources/toolkit_std_dark.css b/python/tank/platform/resources/toolkit_std_dark.css
new file mode 100644
index 0000000000..5652b38743
--- /dev/null
+++ b/python/tank/platform/resources/toolkit_std_dark.css
@@ -0,0 +1,298 @@
+/*
+Copyright (c) 2013 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.
+
+*/
+
+QWidget
+{
+ background-color: rgb(52, 52, 52);
+ color: rgb(185, 185, 185);
+ border-radius: 2px;
+ selection-background-color: rgb(167, 167, 167);
+ selection-color: rgb(26, 26, 26);
+ font-size: 11px;
+}
+
+QSplashScreen
+{
+ background-color: none;
+ color: none;
+}
+
+QFrame, QLineEdit, QComboBox, QSpinBox
+{
+ background-color: rgb(52, 52, 52);
+}
+
+QLabel
+{
+ background-color: none;
+ border:none;
+}
+
+QComboBox:on
+{
+ padding-top: 3px;
+ padding-left: 4px;
+}
+
+QAbstractScrollArea
+{
+ border: 1px solid rgb(20, 20, 20);
+}
+
+QScrollArea > QWidget
+{
+ background-color: rgb(52, 52, 52);
+}
+
+
+QPlainTextEdit:Focus, QComboBox:Focus, QLineEdit:Focus, QSpinBox:Focus
+{
+ background-color: rgb(70, 70, 70);
+}
+
+QPlainTextEdit:disabled, QComboBox:disabled QLineEdit:disabled, QSpinBox:disabled, QPushButton:disabled
+{
+ background-color: rgb(60, 60, 60);
+ color:rgb(100, 100, 100);
+}
+
+
+/*listviews+treeview+tableview*/
+QListView, QTreeView, QTableView
+{
+ alternate-background-color: rgb(60, 60, 60);
+}
+
+QListView::item:selected, QTreeView::item:selected, QTableView::item:selected
+{
+ border: 1px solid rgb(160, 160, 160);
+ background-color: rgb(160, 160, 160);
+}
+
+QListView::item:selected:hover, QTreeView::item:selected:hover
+{
+ background: rgb(180, 180, 180);
+}
+QListView::item:hover, QTreeView::item:hover
+{
+ background: rgb(100, 100, 100);
+}
+
+QTreeView QWidget, QTableView .QWidget
+{
+ background-color: rgb(52, 52, 52);
+ border: none
+}
+
+QHeaderView
+{
+ background: rgb(20, 20, 20);
+}
+
+QHeaderView::section
+{
+ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(55, 55, 55, 255),
+ stop:0.130682 rgba(86, 86, 86, 255), stop:0.886364 rgba(86, 86, 86, 255),
+ stop:1 rgba(66, 66, 66, 255));
+ color: rgb(180 ,180,180);
+ padding-left: 4px;
+ border: 1px solid rgba(50, 50, 50);
+}
+QHeaderView::section:checked
+{
+ background-color: rgb(120, 120, 120);
+}
+
+QTableView QTableCornerButton::section
+{
+ border-top-left-radius: 7px;
+ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(55, 55, 55, 255),
+ stop:0.130682 rgba(86, 86, 86, 255), stop:0.886364 rgba(86, 86, 86, 255),
+ stop:1 rgba(66, 66, 66, 255));
+ margin: 1px 0 0 1px;
+ border: 1px solid rgba(55, 55, 55);
+}
+
+
+/*tabwidget*/
+QTabWidget::pane
+{
+ border-radius: 2px;
+ border: 1px solid rgb(20, 20, 20);
+}
+
+QTabBar::tab
+{
+ border-radius: 1px;
+ padding: 3px 3px 3px 3px;
+}
+
+QTabBar::tab:selected, QTabBar::tab:hover
+{
+ background-color: rgb(90, 90, 90);
+}
+
+QTabBar::tab:!selected
+{
+ background-color: rgb(60, 60, 60);
+}
+
+/*radiobuttons+checkboxes*/
+QRadioButton, QCheckBox
+{
+ background:transparent;
+ border:none
+}
+
+QRadioButton:disabled, QCheckBox:disabled
+{
+ background:transparent;
+ color:rgb(120, 120, 120)
+}
+
+
+QAbstractItemView
+{
+ background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #4A4A4A, stop: 1 #454545);
+}
+
+
+/*Push Button*/
+
+QPushButton
+{
+ background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #565656, stop: 0.1 #525252, stop: 0.5 #4e4e4e, stop: 0.9 #4a4a4a, stop: 1 #464646);
+ border-width: 1px;
+ border-color: rgb(20, 20, 20);
+ border-style: solid;
+ border-radius: 1;
+ padding: 3px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+QToolButton
+{
+ border-color: rgb(20, 20, 20);
+ border-style: solid;
+ border-radius: 1;
+ border-width: 1px;
+ padding: 3px;
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+QPushButton:pressed, QToolButton:pressed
+{
+ background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2d2d2d, stop: 0.1 #2b2b2b, stop: 0.5 #292929, stop: 0.9 #282828, stop: 1 #252525);
+}
+
+QComboBox
+{
+ selection-background-color: #ffaa00;
+ background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #565656, stop: 0.1 #525252, stop: 0.5 #4e4e4e, stop: 0.9 #4a4a4a, stop: 1 #464646);
+ border-style: solid;
+ border: 1px solid #1e1e1e;
+ border-radius: 5;
+}
+
+QComboBox:hover, QPushButton:hover, QLineEdit:focus, QToolButton:hover
+{
+ border: 1px solid QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #ffa02f, stop: 1 #d7801a);
+}
+
+
+/*ScrollBar*/
+QScrollBar
+{
+ background: rgb(50, 50, 50);
+ border-radius: 6px;
+}
+QScrollBar::add-page, QScrollBar::sub-page
+{
+ background: rgb(70, 70, 70);
+}
+
+QScrollBar::add-page:hover, QScrollBar::sub-page:hover
+{
+ background: rgb(70, 70, 70);
+}
+QScrollBar:vertical {
+ margin: 15px 2px 15px 2px;
+ border: 2px solid rgb(60, 60, 60);
+}
+QScrollBar:horizontal {
+ margin: 2px 15px 2px 15px;
+ border: 2px solid rgb(60, 60, 60);
+}
+QScrollBar::handle{
+ background: rgb(25, 25, 25);
+ border-radius: 3px;
+}
+QScrollBar::handle:horizontal {
+ margin: 0 -1px 0 -1px;
+}
+QScrollBar::handle:vertical {
+ margin: -1px 0 -1px 0;
+}
+
+QScrollBar::sub-line,QScrollBar::add-line
+{
+ background-color: rgb(52, 52, 52);
+ height: 13px;
+ width: 13px;
+}
+
+
+QScrollBar::sub-line:vertical, QScrollBar::add-line:vertical
+{
+ subcontrol-position: top;
+ subcontrol-origin: margin;
+}
+QScrollBar::add-line:vertical
+{
+ subcontrol-position: bottom;
+}
+
+QScrollBar::sub-line:horizontal, QScrollBar::add-line:horizontal
+{
+ subcontrol-position: left;
+ subcontrol-origin: margin;
+}
+
+QScrollBar::add-line:horizontal
+{
+ subcontrol-position: right;
+}
+
+
+/*menu*/
+ QMenuBar
+ {
+ background-color: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 rgba(55, 55, 55, 255), stop:1 rgba(97, 97, 97, 255));
+ border-radius: 0;
+ }
+
+ QMenuBar::item
+ {
+ spacing: 3px;
+ padding: 1px 4px;
+ background: transparent;
+ border-radius: 4px;
+ }
+ QMenuBar::item:selected {
+ background: rgb(100, 100, 100)
+ }
+ QMenuBar::item:pressed {
+ background: rgb(50, 50, 50)
+ }
diff --git a/python/tank/util/pyside2_as_pyside6_patcher.py b/python/tank/util/pyside2_as_pyside6_patcher.py
new file mode 100644
index 0000000000..6be3d86773
--- /dev/null
+++ b/python/tank/util/pyside2_as_pyside6_patcher.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2025 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.
+
+
+class PySide2asPySide6Patcher:
+ @staticmethod
+ def _patch_QtWebEngineCore(qt_webengine_core, classes):
+ for cls in classes:
+ setattr(qt_webengine_core, cls.__name__, cls)
+ return qt_webengine_core
+
+ @staticmethod
+ def _patch_QtGui(qt_gui, classes):
+ for cls in classes:
+ setattr(qt_gui, cls.__name__, cls)
+ return qt_gui
diff --git a/python/tank/util/qt_importer.py b/python/tank/util/qt_importer.py
index b0fc282e5d..12a64b35ef 100644
--- a/python/tank/util/qt_importer.py
+++ b/python/tank/util/qt_importer.py
@@ -77,7 +77,21 @@ def QtWebEngineWidgets(self):
"""
:returns: QtWebEngineWidgets module, if available.
"""
- return self._modules["QtWebEngineWidgets"] if self._modules else None
+ return self._modules.get("QtWebEngineWidgets") if self._modules else None
+
+ @property
+ def QtWidgets(self):
+ """
+ :returns: QtWidgets module, if available.
+ """
+ return self._modules.get("QtWidgets") if self._modules else None
+
+ @property
+ def QtWebEngineCore(self):
+ """
+ :returns: QtWebEngineCore module, if available.
+ """
+ return self._modules.get("QtWebEngineCore") if self._modules else None
@property
def binding(self):
@@ -117,10 +131,7 @@ def base(self):
return {}
qt_base = {}
-
qt_base.update(self._modules)
- qt_base["__name__"] = self._binding_name
- qt_base["__version__"] = self._binding_version
return qt_base
@@ -148,7 +159,6 @@ def _import_module_by_name(self, parent_module_name, module_name):
except Exception as e:
logger.debug("Unable to import module '%s': %s", module_name, e)
-
def _import_pyside2(self):
"""
This will be called at initialization to discover every PySide 2 modules.
@@ -186,12 +196,6 @@ def _import_pyside2(self):
"QtMultimedia",
]
- # We have the potential for a deadlock in Maya 2018 on Windows if this
- # is imported. We set the env var from the tk-maya engine when we
- # detect that we are in this situation.
- if "SHOTGUN_SKIP_QTWEBENGINEWIDGETS_IMPORT" not in os.environ:
- sub_modules.append("QtWebEngineWidgets")
-
modules_dict = {"QtCore": QtCore}
# Add shiboken2 to the modules dict
@@ -309,17 +313,10 @@ def _import_pyside6(self):
import PySide6
import shiboken6
- sub_modules = pkgutil.iter_modules(PySide6.__path__)
-
- if "SHOTGUN_SKIP_QTWEBENGINEWIDGETS_IMPORT" in os.environ:
- sub_modules = [
- m for m in sub_modules if not m.name.startswith("QtWebEngine")
- ]
-
modules_dict = {}
# Add shiboken6 to the modules dict
modules_dict["shiboken"] = shiboken6
- for module in sub_modules:
+ for module in pkgutil.iter_modules(PySide6.__path__):
module_name = module.name
try:
wrapper = __import__("PySide6", globals(), locals(), [module_name])
@@ -337,6 +334,54 @@ def _import_pyside6(self):
self._to_version_tuple(PySide6.__version__),
)
+ def _import_pyside2_as_pyside6(self):
+ """
+ Imports PySide2 and makes it compatible with PySide6.
+
+ Returns a tuple containing the version information, the PySide2 module,
+ and a dictionary of imported modules.
+
+ :returns: A tuple containing the version information, the PySide2 module,
+ and a dictionary of imported modules.
+ """
+
+ import PySide2
+ import shiboken2
+ from .pyside2_as_pyside6_patcher import PySide2asPySide6Patcher
+
+ QtCore = self._import_module_by_name("PySide2", "QtCore")
+ QtGui = self._import_module_by_name("PySide2", "QtGui")
+ QtWidgets = self._import_module_by_name("PySide2", "QtWidgets")
+ QtNetwork = self._import_module_by_name("PySide2", "QtNetwork")
+ QtWebEngineWidgets = self._import_module_by_name(
+ "PySide2", "QtWebEngineWidgets"
+ )
+ QtWebEngineCore = self._import_module_by_name("PySide2", "QtWebEngineCore")
+ QtWebEngineCore = PySide2asPySide6Patcher._patch_QtWebEngineCore(
+ QtWebEngineCore,
+ [
+ QtWebEngineWidgets.QWebEnginePage,
+ QtWebEngineWidgets.QWebEngineProfile,
+ ],
+ )
+ QtGui = PySide2asPySide6Patcher._patch_QtGui(QtGui, [QtWidgets.QAction])
+
+ return (
+ "PySide2",
+ PySide2.__version__,
+ PySide2,
+ {
+ "QtCore": QtCore,
+ "QtGui": QtGui,
+ "QtNetwork": QtNetwork,
+ "QtWebEngineWidgets": QtWebEngineWidgets,
+ "QtWidgets": QtWidgets,
+ "QtWebEngineCore": QtWebEngineCore,
+ "shiboken6": shiboken2,
+ },
+ self._to_version_tuple(QtCore.qVersion()),
+ )
+
def _to_version_tuple(self, version_str):
"""
Converts a version string with the dotted notation into a tuple
@@ -350,16 +395,17 @@ def _to_version_tuple(self, version_str):
def _import_modules(self, interface_version_requested):
"""
- Tries to import different Qt binding implementation in the following order:
- - PySide2
- - PySide6
+ Attempts to import different Qt binding implementations in the following order:
+ - PySide6 (for Qt6 interface)
+ - PySide2 (for Qt5 and Qt4 interfaces)
- PySide6 is attempted to be imported last at the moment because it is is not yet fully
- supported. If a DCC requires PySide6, it can run with the current level of support,
+ If a DCC requires PySide6, it can run with the current level of support,
but be warned that you may encounter issues.
- :returns: The (binding name, binding version, modules) tuple or (None, None, None) if
- no binding is avaialble.
+ :param interface_version_requested: The requested Qt interface version (QT4, QT5, or QT6)
+
+ :returns: A tuple containing the binding name, binding version,and imported
+ modules, or (None, None, None, None, None) if no binding is available.
"""
interface = {
@@ -369,6 +415,7 @@ def _import_modules(self, interface_version_requested):
}.get(interface_version_requested)
logger.debug("Requesting %s-like interface", interface)
+ # TODO: Remove this condition sgtk.platform.qt6 is fully supported across all Toolkit repositories.
if interface_version_requested == self.QT4:
# First, try PySide 2 since Toolkit ships with PySide2.
try:
@@ -395,8 +442,6 @@ def _import_modules(self, interface_version_requested):
except ImportError:
pass
- # We do not test for PyQt5 since it is supported on Python 3 only at the moment.
-
elif interface_version_requested == self.QT6:
try:
pyside6 = self._import_pyside6()
@@ -405,8 +450,12 @@ def _import_modules(self, interface_version_requested):
except ImportError:
pass
- # TODO migrate qt base from Qt4 interface to Qt6 will require patching Qt5 as Qt6
- logger.debug("Qt6 interface not implemented for Qt5")
+ try:
+ pyside2_as_pyside6 = self._import_pyside2_as_pyside6()
+ logger.debug("Imported PySide2 as PySide6.")
+ return pyside2_as_pyside6
+ except ImportError:
+ pass
logger.debug("No Qt matching that interface was found.")
diff --git a/tests/authentication_tests/test_interactive_authentication.py b/tests/authentication_tests/test_interactive_authentication.py
index 3fce6dcca4..87158b0144 100644
--- a/tests/authentication_tests/test_interactive_authentication.py
+++ b/tests/authentication_tests/test_interactive_authentication.py
@@ -61,20 +61,20 @@ def setUp(self, *args, **kwargs):
"""
Adds Qt modules to tank.platform.qt and initializes QApplication
"""
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
# See if a QApplication instance exists, and if not create one. Use the
# QApplication.instance() method, since qApp can contain a non-None
# value even if no QApplication has been constructed on PySide2.
- if not QtGui.QApplication.instance():
- self._app = QtGui.QApplication(sys.argv)
+ if not QtWidgets.QApplication.instance():
+ self._app = QtWidgets.QApplication(sys.argv)
super(InteractiveTests, self).setUp()
def tearDown(self):
super(InteractiveTests, self).tearDown()
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
- QtGui.QApplication.processEvents()
+ QtWidgets.QApplication.processEvents()
@suppress_generated_code_qt_warnings
def test_site_and_user_disabled_on_session_renewal(self):
@@ -90,13 +90,13 @@ def _prepare_window(self, ld):
Prepares the dialog so the events get processed and focus is attributed to the right
widget.
"""
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
ld.show()
ld.raise_()
ld.activateWindow()
- QtGui.QApplication.processEvents()
+ QtWidgets.QApplication.processEvents()
@contextlib.contextmanager
def _login_dialog(self, is_session_renewal=False, **kwargs):
@@ -178,9 +178,9 @@ def _print_message(self, text, test_console):
print(text)
print("=" * len(text))
else:
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
- mb = QtGui.QMessageBox()
+ mb = QtWidgets.QMessageBox()
mb.setText(text)
mb.exec_()
@@ -259,18 +259,21 @@ class FromMainThreadException(Exception):
pass
- from tank.authentication.ui.qt_abstraction import QtCore, QtGui
+ from tank.authentication.ui.qt_abstraction import QtCore, QtWidgets
# Create a QApplication instance.
- if not QtGui.QApplication.instance():
- QtGui.QApplication(sys.argv)
+ if not QtWidgets.QApplication.instance():
+ QtWidgets.QApplication(sys.argv)
def thrower():
"""
Method that will throw.
:throws: FromMainThreadException
"""
- if QtGui.QApplication.instance().thread() != QtCore.QThread.currentThread():
+ if (
+ QtWidgets.QApplication.instance().thread()
+ != QtCore.QThread.currentThread()
+ ):
raise Exception("This should have been invoked in the main thread.")
raise FromMainThreadException()
@@ -296,13 +299,16 @@ def run(self):
# Make sure we have a QObject derived object and not a regular Python function.
if not isinstance(invoker_obj, QtCore.QObject):
raise Exception("Invoker is not a QObject")
- if invoker_obj.thread() != QtGui.QApplication.instance().thread():
+ if (
+ invoker_obj.thread()
+ != QtWidgets.QApplication.instance().thread()
+ ):
raise Exception(
"Invoker should be of the same thread as the QApplication."
)
if QtCore.QThread.currentThread() != self:
raise Exception("Current thread not self.")
- if QtGui.QApplication.instance().thread == self:
+ if QtWidgets.QApplication.instance().thread == self:
raise Exception(
"QApplication should be in the main thread, not self."
)
@@ -310,7 +316,7 @@ def run(self):
except Exception as e:
self._exception = e
finally:
- QtGui.QApplication.instance().exit()
+ QtWidgets.QApplication.instance().exit()
def wait(self):
"""
@@ -325,7 +331,7 @@ def wait(self):
bg = BackgroundThread()
bg.start()
# process events
- QtGui.QApplication.instance().exec_()
+ QtWidgets.QApplication.instance().exec_()
# Make sure the thread got the exception that was thrown from the main thread.
with self.assertRaises(FromMainThreadException):
@@ -492,7 +498,7 @@ def test_ui_auth_with_whitespace(self):
Makes sure that the ui strips out whitespaces.
"""
# Import locally since login_dialog has a dependency on Qt and it might be missing
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
with self._login_dialog() as ld:
# For each widget in the ui, make sure that the text is properly cleaned
@@ -500,14 +506,14 @@ def test_ui_auth_with_whitespace(self):
for widget in [ld.ui._2fa_code, ld.ui.backup_code, ld.ui.site, ld.ui.login]:
# Give the focus, so that editingFinished can be triggered.
widget.setFocus()
- if isinstance(widget, QtGui.QLineEdit):
+ if isinstance(widget, QtWidgets.QLineEdit):
widget.setText(" text ")
else:
widget.lineEdit().setText(" text ")
# Give the focus to another widget, which should trigger the editingFinished
# signal and the dialog will clear the extra spaces in it.
ld.ui.password.setFocus()
- if isinstance(widget, QtGui.QLineEdit):
+ if isinstance(widget, QtWidgets.QLineEdit):
# Text should be cleaned of spaces now.
self.assertEqual(widget.text(), "text")
else:
@@ -587,22 +593,22 @@ def test_login_dialog_exit_confirmation(self):
Make sure that the site and user fields are disabled when doing session renewal
"""
- from tank.authentication.ui.qt_abstraction import QtGui, QtCore
+ from tank.authentication.ui.qt_abstraction import QtGui, QtCore, QtWidgets
# Test window close event
with self._login_dialog() as ld:
# First, simulate user clicks on the No button
- ld.confirm_box.exec_ = lambda: QtGui.QMessageBox.StandardButton.No
+ ld.confirm_box.exec_ = lambda: QtWidgets.QMessageBox.StandardButton.No
self.assertEqual(ld.close(), False)
self.assertIsNone(ld.my_result)
self.assertEqual(ld.isVisible(), True)
# Then, simulate user clicks on the Yes button
- ld.confirm_box.exec_ = lambda: QtGui.QMessageBox.StandardButton.Yes
+ ld.confirm_box.exec_ = lambda: QtWidgets.QMessageBox.StandardButton.Yes
self.assertEqual(ld.close(), True)
- self.assertEqual(ld.my_result, QtGui.QDialog.Rejected)
+ self.assertEqual(ld.my_result, QtWidgets.QDialog.Rejected)
self.assertEqual(ld.isVisible(), False)
# Test escape key event
@@ -617,21 +623,21 @@ def test_login_dialog_exit_confirmation(self):
)
# First, simulate user clicks on the No button
- ld.confirm_box.exec_ = lambda: QtGui.QMessageBox.StandardButton.No
+ ld.confirm_box.exec_ = lambda: QtWidgets.QMessageBox.StandardButton.No
self.assertIsNone(ld.keyPressEvent(event))
self.assertIsNone(ld.my_result)
self.assertEqual(ld.isVisible(), True)
# Then, simulate user clicks on the Yes button
- ld.confirm_box.exec_ = lambda: QtGui.QMessageBox.StandardButton.Yes
+ ld.confirm_box.exec_ = lambda: QtWidgets.QMessageBox.StandardButton.Yes
# Initialize the ASL process - mostly for coverage
ld._asl_process("https://host.shotgunstudio.com")
# Test Escape key
self.assertIsNone(ld.keyPressEvent(event))
- self.assertEqual(ld.my_result, QtGui.QDialog.Rejected)
+ self.assertEqual(ld.my_result, QtWidgets.QDialog.Rejected)
self.assertEqual(ld.isVisible(), False)
@suppress_generated_code_qt_warnings
@@ -707,12 +713,12 @@ def test_login_dialog_method_selected(self, *unused_mocks):
],
)
def test_ui_auth_2fa(self, *mocks):
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
with mock.patch.object(
- QtGui.QDialog,
+ QtWidgets.QDialog,
"exec_",
- return_value=QtGui.QDialog.Accepted,
+ return_value=QtWidgets.QDialog.Accepted,
), self._login_dialog(
is_session_renewal=True,
hostname="https://host.shotgunstudio.com",
@@ -763,8 +769,8 @@ def test_ui_auth_2fa(self, *mocks):
# This is supposed to work
self.assertEqual(
- QtGui.QDialog.result(ld),
- QtGui.QDialog.Accepted,
+ QtWidgets.QDialog.result(ld),
+ QtWidgets.QDialog.Accepted,
)
self.assertEqual(
@@ -806,12 +812,12 @@ def test_ui_auth_web_login(self, *mocks):
Not doing much at the moment. Just try to increase code coverage
"""
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
with mock.patch.object(
- QtGui.QDialog,
+ QtWidgets.QDialog,
"exec_",
- return_value=QtGui.QDialog.Accepted,
+ return_value=QtWidgets.QDialog.Accepted,
), self._login_dialog(
is_session_renewal=True,
hostname="https://host.shotgunstudio.com",
@@ -1041,7 +1047,7 @@ def test_login_dialog_method_selected_session_cache(self):
return_value=["john", "bob"],
)
def test_login_dialog_app_session_launcher(self, *unused_mocks):
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
# First basic and ASL methods
with mock.patch(
@@ -1050,9 +1056,9 @@ def test_login_dialog_app_session_launcher(self, *unused_mocks):
"authentication_app_session_launcher_enabled": True,
},
), mock.patch.object(
- QtGui.QDialog,
+ QtWidgets.QDialog,
"exec_",
- return_value=QtGui.QDialog.Accepted,
+ return_value=QtWidgets.QDialog.Accepted,
), self._login_dialog(
is_session_renewal=True,
hostname="http://host.shotgunstudio.com", # HTTP only for code coverage
@@ -1107,8 +1113,8 @@ def test_login_dialog_app_session_launcher(self, *unused_mocks):
# Verify that the dialog succeeded
self.assertEqual(
- QtGui.QDialog.result(ld),
- QtGui.QDialog.Accepted,
+ QtWidgets.QDialog.result(ld),
+ QtWidgets.QDialog.Accepted,
)
self.assertEqual(
@@ -1533,7 +1539,10 @@ def setUp(self, *args, **kwargs):
"QtGui": mock.Mock(),
"QtNetwork": mock.Mock(),
"QtWebEngineWidgets": mock.Mock(),
+ "QtWidgets": mock.Mock(),
+ "QtWebEngineCore": mock.Mock(),
}
+ qt_modules_mock["QtWebEngineCore"].QWebEnginePage = mock.Mock()
self.sso_saml2 = SsoSaml2Core(qt_modules=qt_modules_mock)
super(SsoSaml2CoreTests, self).setUp()
diff --git a/tests/authentication_tests/test_web_login.py b/tests/authentication_tests/test_web_login.py
index 19af6048e6..06a1c6bbb7 100644
--- a/tests/authentication_tests/test_web_login.py
+++ b/tests/authentication_tests/test_web_login.py
@@ -33,8 +33,8 @@ def test_web_login(self):
if not qt_abstraction.QtWebEngineWidgets:
self.skipTest("This tests requires QtWebEngineWidgets")
- if qt_abstraction.QtGui.QApplication.instance() is None:
- self._app = qt_abstraction.QtGui.QApplication([])
+ if qt_abstraction.QtWidgets.QApplication.instance() is None:
+ self._app = qt_abstraction.QtWidgets.QApplication([])
obj = SsoSaml2Toolkit(
"Test Web Login",
diff --git a/tests/fixtures/config/bundles/test_app/app.py b/tests/fixtures/config/bundles/test_app/app.py
index d1a92be0fe..f91e44e5d3 100644
--- a/tests/fixtures/config/bundles/test_app/app.py
+++ b/tests/fixtures/config/bundles/test_app/app.py
@@ -35,13 +35,13 @@ def _show_app(self):
"""
Shows an app with a button in it.
"""
- from sgtk.platform.qt import QtGui
+ from sgtk.platform.qt6 import QtWidgets
- class AppDialog(QtGui.QWidget):
+ class AppDialog(QtWidgets.QWidget):
def __init__(self, parent=None):
super(AppDialog, self).__init__(parent)
- self._layout = QtGui.QVBoxLayout(self)
- self.button = QtGui.QPushButton("Close", parent=self)
+ self._layout = QtWidgets.QVBoxLayout(self)
+ self.button = QtWidgets.QPushButton("Close", parent=self)
self.button.clicked.connect(self.close)
self._layout.addWidget(self.button)
diff --git a/tests/platform_tests/test_engine.py b/tests/platform_tests/test_engine.py
index f7bd1cd7df..b8c3f640a3 100644
--- a/tests/platform_tests/test_engine.py
+++ b/tests/platform_tests/test_engine.py
@@ -94,10 +94,10 @@ def setUp(self):
super(TestDialogCreation, self).setUp()
# Engine is not started yet, so can't rely on sgtk.platform.qt for imports.
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
- if QtGui.QApplication.instance() is None:
- QtGui.QApplication([])
+ if QtWidgets.QApplication.instance() is None:
+ QtWidgets.QApplication([])
sgtk.platform.start_engine("test_engine", self.tk, self.context)
@@ -107,9 +107,9 @@ def test_create_widget(self):
Ensures that the _create_widget method is exception safe.
"""
# Engine is not started yet, so can't rely on sgtk.platform.qt for imports.
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
- class _test_widget(QtGui.QWidget):
+ class _test_widget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
raise Exception("Testing...")
@@ -210,15 +210,15 @@ def setUp(self):
super(TestExecuteInMainThread, self).setUp()
# Engine is not started yet, so can't rely on sgtk.platform.qt for imports.
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
# See if a QApplication instance exists, and if not create one. Use the
# QApplication.instance() method, since qApp can contain a non-None
# value even if no QApplication has been constructed on PySide2.
- if not QtGui.QApplication.instance():
- self._app = QtGui.QApplication(sys.argv)
+ if not QtWidgets.QApplication.instance():
+ self._app = QtWidgets.QApplication(sys.argv)
else:
- self._app = QtGui.QApplication.instance()
+ self._app = QtWidgets.QApplication.instance()
tank.platform.start_engine("test_engine", self.tk, self.context)
@@ -261,7 +261,7 @@ def _test_exec_in_main_thread(self, exec_in_main_thread_func):
t.join()
def _assert_run_in_main_thread_and_quit(self):
- from sgtk.platform.qt import QtCore
+ from sgtk.platform.qt6 import QtCore
# Make sure we are running in the main thread.
self.assertEqual(
@@ -302,7 +302,7 @@ def _test_thead_safe_exec_in_main_thread(self):
runs a simple test a number of times in multiple threads and asserts the result
returned is as expected.
"""
- from sgtk.platform.qt import QtCore
+ from sgtk.platform.qt6 import QtCore
num_test_threads = 20
num_thread_iterations = 30
@@ -669,14 +669,14 @@ def setUp(self):
self.engine = sgtk.platform.start_engine("test_engine", self.tk, self.context)
# Engine is not started yet, so can't rely on sgtk.platform.qt for imports.
- from tank.authentication.ui.qt_abstraction import QtGui
+ from tank.authentication.ui.qt_abstraction import QtWidgets
# Create an application instance so we can take control of the execution
# of the dialog.
- if QtGui.QApplication.instance() is None:
- self._app = QtGui.QApplication(sys.argv)
+ if QtWidgets.QApplication.instance() is None:
+ self._app = QtWidgets.QApplication(sys.argv)
else:
- self._app = QtGui.QApplication.instance()
+ self._app = QtWidgets.QApplication.instance()
self._dialog_dimissed = False
diff --git a/tests/util_tests/test_qt_importer.py b/tests/util_tests/test_qt_importer.py
index c53681028d..9dd7316f53 100644
--- a/tests/util_tests/test_qt_importer.py
+++ b/tests/util_tests/test_qt_importer.py
@@ -48,8 +48,6 @@ def test_qt_importer_with_pyside2_interface_qt4(self):
# Expect PySide2 as the binding
assert qt.binding_name == "PySide2"
assert qt.base
- assert qt.base["__name__"] is qt.binding_name
- assert qt.base["__version__"] is qt.binding_version
@skip_if_pyside2(found=False)
def test_qt_importer_with_pyside2_interface_qt5(self):
@@ -72,8 +70,6 @@ def test_qt_importer_with_pyside2_interface_qt5(self):
# Expect PySide2 as the binding
assert qt.binding_name == "PySide2"
assert qt.base
- assert qt.base["__name__"] is qt.binding_name
- assert qt.base["__version__"] is qt.binding_version
@skip_if_pyside6(found=False)
@skip_if_pyside2(found=True)
@@ -97,8 +93,6 @@ def test_qt_importer_with_pyside6_interface_qt4(self):
# Expect PySide2 as the binding
assert qt.binding_name == "PySide6"
assert qt.base
- assert qt.base["__name__"] is qt.binding_name
- assert qt.base["__version__"] is qt.binding_version
@skip_if_pyside6(found=False)
@skip_if_pyside2(found=True)
@@ -122,8 +116,6 @@ def test_qt_importer_with_pyside6_interface_qt6(self):
# Expect PySide2 as the binding
assert qt.binding_name == "PySide6"
assert qt.base
- assert qt.base["__name__"] is qt.binding_name
- assert qt.base["__version__"] is qt.binding_version
@skip_if_pyside2(found=False)
@skip_if_pyside6(found=True)