"""
madgui - interactive GUI application for MAD-X via cpymad.
Usage:
madgui [-c CONFIG] [FILE]
madgui [--help | --version]
Options:
-c FILE, --config FILE Set config file
-h, --help Show this help
-v, --version Show version information
Arguments:
FILE Load this file initially
Contact information:
Thomas Gläßle <t_glaessle@gmx.de>
Website:
https://github.com/hibtc/madgui
"""
__all__ = [
'main',
'init_app',
]
import warnings
import traceback
import signal
import sys
from functools import partial
# Must import Qt *before* matplotlib!
from PyQt5.QtCore import QTimer, QCoreApplication
from madgui import __version__
[docs]def init_app(argv=None, gui=True):
"""
Initialize qt runtime, deal with common issues (such as installing an
exception handler), and return a ``QApplication`` object. If ``gui`` is
false, return a ``QCoreApplication`` instead.
"""
warnings.filterwarnings(
"default", module='(madgui|cpymad|minrpc|pydicti).*')
set_app_id('hit.madgui')
init_stdio()
# QApplication needs a valid argument list:
if argv is None:
argv = sys.argv
if gui:
from PyQt5.QtWidgets import QApplication
from madgui.util.qt import load_icon_resource
from importlib_resources import read_text
app = QApplication(argv)
app.setWindowIcon(load_icon_resource('madgui.data', 'icon.xpm'))
app.setStyleSheet(read_text('madgui.data', 'style.css'))
# matplotlib must be imported *after* Qt;
# must be selected before importing matplotlib.backends:
import matplotlib
matplotlib.use('Qt5Agg')
else:
app = QCoreApplication(argv)
app.setApplicationName('madgui')
app.setApplicationVersion(__version__)
app.setOrganizationName('HIT Betriebs GmbH')
app.setOrganizationDomain('https://www.klinikum.uni-heidelberg.de/hit')
# Print uncaught exceptions. This changes the default behaviour on PyQt5,
# where an uncaught exception would usually cause the program to abort.
sys.excepthook = traceback.print_exception
setup_interrupt_handling(app)
return app
def init_stdio():
"""Prepare sys.stdout according to requirements in madgui's dependencies."""
# If started as GUI script, there is usually no stdout. Some packages
# don't like this and raise exceptions, e.g. pyqtconsole. Let's keep them
# satisfied:
sys.stdout = sys.stdout or open('madgui.log', 'at', encoding='utf-8')
sys.stderr = sys.stderr or sys.stdout
# Fix issue with utf-8 output on STDOUT in non utf-8 terminal.
if sys.stdout.encoding.lower() not in ('utf-8', 'utf8'):
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
[docs]def main(argv=None, MainWindow=None):
"""Run madgui mainloop and exit process when finished."""
import madgui.core.config as config
from madgui.core.session import Session
from docopt import docopt
if MainWindow is None:
from madgui.widget.mainwindow import MainWindow
app = init_app(argv)
# Filter arguments understood by Qt before doing our own processing:
args = app.arguments()[1:]
opts = docopt(__doc__, args, version=__version__)
conf = config.load(opts['--config'])
config.number = conf.number
session = Session(conf)
try:
window = MainWindow(session)
session.window.set(window)
window.show()
# Defer `loadDefault` to avoid creation of a AsyncRead thread before
# the main loop is entered: (Being in the mainloop simplifies
# terminating the AsyncRead thread via the QApplication.aboutToQuit
# signal. Without this, if the setup code excepts after creating the
# thread the main loop will never be entered and thus aboutToQuit
# never be emitted, even when pressing Ctrl+C.)
QTimer.singleShot(0, partial(session.load_default, opts['FILE']))
exit_code = app.exec_()
finally:
session.terminate()
return sys.exit(exit_code)
def setup_interrupt_handling(app):
"""
Setup handling of KeyboardInterrupt (Ctrl-C) for PyQt.
By default Ctrl-C has no effect in PyQt. For more information, see:
https://riverbankcomputing.com/pipermail/pyqt/2008-May/019242.html
https://docs.python.org/3/library/signal.html#execution-of-python-signal-handlers
http://stackoverflow.com/questions/4938723/what-is-the-correct-way-to-make-my-pyqt-application-quit-when-killed-from-the-console
"""
signal.signal(signal.SIGINT, interrupt_handler)
safe_timer(50, lambda: None)
def interrupt_handler(signum, frame):
"""Handle KeyboardInterrupt: quit application."""
QCoreApplication.quit()
def safe_timer(timeout, func, *args, **kwargs):
"""
Create a timer that is safe against garbage collection and overlapping
calls. See: http://ralsina.me/weblog/posts/BB974.html
"""
def timer_event():
try:
func(*args, **kwargs)
finally:
QTimer.singleShot(timeout, timer_event)
QTimer.singleShot(timeout, timer_event)
def set_app_id(appid):
"""
Set application ID on windows.
This is needed so that madgui windows will have their own taskbar group
and not be counted as generic "python" applications.
See: https://stackoverflow.com/a/1552105/650222
"""
try:
from ctypes import windll
except ImportError:
return
windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)