Source code for schrodinger.utils.qapplication
"""
Functions for managing the global QApplication instance.
Typical usage:
if __name__ == '__main__':
af2.start_application(MyPanel.panel)
(Note that the start_application function is available from the af2 namespace)
"""
import enum
import sys
import traceback
import schrodinger
from schrodinger.infra import qt_message_handler
from schrodinger.Qt import QtCore
from schrodinger.utils.sysinfo import is_display_present
_APP_MODE = None
_APPLICATION = None
ApplicationMode = enum.Enum('RunMode', ['Standalone', 'Maestro', 'Canvas'])
[docs]class CantCreateQApplicationError(RuntimeError):
[docs] def __init__(self, msg="", *args, **kwargs):
msg = "Could not create a QtWidgets application. " + msg
super().__init__(msg, *args, **kwargs)
[docs]def require_application(func=None, create=False, use_qtcore_app=False):
"""
Use this decorator on functions that require a QApplication to run. When the
decorated function is called, this will check whether a QApplication exists.
If it does not, a RuntimeError will be raised unless create=True, which will
cause a QApplication to be created.
:param func: the function to decorate
:param create: whether to create a QApplication if one does not exist
:type create: bool
:param use_qtcore_app: Whether to create the application using the QtCore
module instead of the QtWidgets module.
:type use_qtcore_app: bool
"""
if func is None:
# func is None if decorator is called to specify options, i.e.
# @require_application(create=True)
# def foo(self):
# pass
return lambda func: require_application(
func, create=create, use_qtcore_app=use_qtcore_app)
def modified_func(*args, **kwargs):
app = get_application(create=create, use_qtcore_app=use_qtcore_app)
if app is None:
raise RuntimeError(f'{func} requires a QApplication.')
return func(*args, **kwargs)
return modified_func
[docs]def get_application(create=True, use_qtcore_app=False):
"""
Gets the global QApplication instance. By default, creates one if none
exists.
:param create: Whether to create a new application if none exists.
:type create: bool
:param use_qtcore_app: Whether to create the application
using the QtCore module instead of the QtWidgets module. Has no effect
if `create` is `False`.
:raises CantCreateQApplicationError: if use_qtcore_app is False and
importing QtWidgets fails (e.g. due to missing graphics libraries)
:return: the application
:rtype: QApplication or None
"""
global _APPLICATION
if _APPLICATION is None:
_APPLICATION = QtCore.QCoreApplication.instance()
if not _APPLICATION and create:
if use_qtcore_app:
_APPLICATION = QtCore.QCoreApplication(sys.argv)
else:
# Import here so we don't introduce a ui dependency
try:
from schrodinger.Qt import QtWidgets
from schrodinger.ui.qt import style as qtstyle
except ImportError:
raise CantCreateQApplicationError(
"Could not import QtWidgets.")
sysargs = list(sys.argv)
if not is_display_present():
# If there's no display, use the offscreen QPA platform
sysargs += ['-platform', 'offscreen']
_APPLICATION = QtWidgets.QApplication(sysargs)
# Windows: Hide the '?' button on QDialog
_APPLICATION.setAttribute(
QtCore.Qt.AA_DisableWindowContextHelpButton)
qtstyle.apply_styles()
qt_message_handler.install_handler()
return _APPLICATION
[docs]def is_core_application():
"""
Return whether the application instance is a QtCore application (as opposed
to a QtWidgets application).
:return: Whether there is a QtCore application or None if there's no application
:rtype: bool or NoneType
"""
app = QtCore.QCoreApplication.instance()
if app is None:
return None
return not app.inherits('QApplication')
[docs]def get_app_mode():
"""
Returns the current application mode, which is a member of the
ApplicationMode enum.
"""
global _APP_MODE
if _APP_MODE is None:
if schrodinger.get_maestro():
_APP_MODE = ApplicationMode.Maestro
elif in_canvas():
_APP_MODE = ApplicationMode.Canvas
else:
_APP_MODE = ApplicationMode.Standalone
[docs]def in_canvas():
"""
Checks whether we are currently running from within canvas
"""
try:
from schrodinger.application.canvas.packages import canvasapp # noqa: F401
except ImportError:
return False
return True
[docs]def start_application(main_callable, use_qtcore_app=False):
"""
Begins the application's event loop using the ``exec_`` method. The main
callable is called via timer from within the event loop. This function is
meant to be used when running in standalone scripts as follows:
if __name__ == '__main__':
application.start_application(main)
Using this function to launch standalone scripts/panels more closely mimics
the way Python is run under Maestro. For example, a panel may be presented
with MyPanel.show() without having to call application.exec_().
This is generally intended for use with GUI scripts.
:param main_callable: the function/method to be run in the event loop,
commonly a module level main function or MyPanelClass.panel
:type main_callable: callable
:param use_qtcore_app: Whether to create the application using the QtCore
module instead of the QtWidgets module. Set to True for scripts that are
non-GUI and/or need to run on machines that have no display.
:type use_qtcore_app: bool
"""
application = get_application(use_qtcore_app=use_qtcore_app)
QtCore.QTimer.singleShot(0, lambda: _main_wrapper(main_callable))
application.exec_()
[docs]def run_application(main_callable, use_qtcore_app=True):
"""
This function is the same as start_application with two important
differences:
1) Creates a QCoreApplication by default
2) Quits the application as soon as the main_callable function returns
and does a system exit with that return value
This is generally intended for use by non-GUI, commandline scripts.
"""
application = get_application(use_qtcore_app=use_qtcore_app)
QtCore.QTimer.singleShot(
0, lambda: _main_wrapper(main_callable, quit_on_return=True))
application.exec_()
def _main_wrapper(main_callable, quit_on_return=False):
"""
Wraps the main callable in an exception handler, so that unhandled
exceptions properly quit the application and display the traceback.
Optionally allows the application to automatically quit when the main
callable returns.
:param quit_on_return: whether to quit the application when the
main_callable returns (and system exit with the return value)
:type quit_on_return: bool
"""
# break an import cycle on cmdline
from schrodinger.utils import cmdline
try:
rc = cmdline.main_wrapper(main_callable, exit_on_return=False)
except: # Unhandled exceptions in main should quit the QApplication
quit_application()
raise
if quit_on_return:
quit_application()
sys.exit(rc)
def _truncate_traceback(tb, match_txt):
"""
Remove the top layers of a traceback for all layers that have `match_txt`
in the function name.
"""
tuple_tb = traceback.extract_tb(tb)
for entry in tuple_tb:
filename, lineno, func_name, text = entry
if match_txt in func_name:
tb = tb.tb_next
else:
break
return tb
[docs]def process_events(flags=None):
"""
Calls processEvents on the main event loop. Requires a QApplication.
:param flags: passed to processEvents. See QApplication.processEvents for
documentation
:raises RuntimeError: if there is no QApplication
"""
app = get_application(create=False)
if app is None:
raise RuntimeError('Must construct a QApplication to process events.')
if flags is None:
app.processEvents()
else:
app.processEvents(flags)
[docs]def quit_application():
"""
Quits the application.
"""
process_events()
application = get_application()
application.exit()