diff --git a/qt_ui/uncaughtexceptionhandler.py b/qt_ui/uncaughtexceptionhandler.py new file mode 100644 index 00000000..6e046fcf --- /dev/null +++ b/qt_ui/uncaughtexceptionhandler.py @@ -0,0 +1,42 @@ +# From https://timlehr.com/python-exception-hooks-with-qt-message-box/ +import logging +import sys +import traceback + +from PySide2.QtCore import Signal, QObject +from PySide2.QtWidgets import QMessageBox, QApplication + + +class UncaughtExceptionHandler(QObject): + _exception_caught = Signal(str, str) + + def __init__(self, parent: QObject): + super().__init__(parent) + sys.excepthook = self.exception_hook + # Use a signal so that the message box always comes from the main thread. + self._exception_caught.connect(self.show_exception_box) + + def exception_hook(self, exc_type, exc_value, exc_traceback): + if issubclass(exc_type, KeyboardInterrupt): + # Ignore keyboard interrupt to support console applications. + sys.__excepthook__(exc_type, exc_value, exc_traceback) + return + + logging.exception( + "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) + ) + self._exception_caught.emit( + str(exc_value), + "".join(traceback.format_exception(exc_type, exc_value, exc_traceback)), + ) + + def show_exception_box(self, message: str, exception: str) -> None: + if QApplication.instance() is not None: + QMessageBox().critical( + self.parent(), + "An unexpected error occurred", + "\n".join([message, "", exception]), + QMessageBox.Ok, + ) + else: + logging.critical("No QApplication instance available.") diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index a2c984d2..5fa02dac 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -24,6 +24,7 @@ from qt_ui import liberation_install from qt_ui.dialogs import Dialog from qt_ui.models import GameModel from qt_ui.uiconstants import URLS +from qt_ui.uncaughtexceptionhandler import UncaughtExceptionHandler from qt_ui.widgets.QTopPanel import QTopPanel from qt_ui.widgets.ato import QAirTaskingOrderPanel from qt_ui.widgets.map.QLiberationMap import QLiberationMap @@ -42,7 +43,9 @@ from qt_ui.windows.logs.QLogsWindow import QLogsWindow class QLiberationWindow(QMainWindow): def __init__(self, game: Optional[Game]) -> None: - super(QLiberationWindow, self).__init__() + super().__init__() + + self._uncaught_exception_handler = UncaughtExceptionHandler(self) self.game = game self.game_model = GameModel(game)