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