From e5cdb2e8caa99175728f0b4858155dac46878a74 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 2 Aug 2020 14:34:09 +0200 Subject: [PATCH 1/4] Added error.py file for error handling functions --- nw/error.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 nw/error.py diff --git a/nw/error.py b/nw/error.py new file mode 100644 index 00000000..dd0cb109 --- /dev/null +++ b/nw/error.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +"""novelWriter Init + + novelWriter – Exception Handling +================================== + Error handling functions + + File History: + Created: 2020-08-02 [0.10.2] + + This file is a part of novelWriter + Copyright 2020, Veronica Berglyd Olsen + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + +def formatHtmlErrMsg(exType, exValue, exTrace): + """Generates a HTML version of an exception. + """ + try: + from traceback import format_tb + from nw import __issuesurl__ + + fmtTrace = "" + for trEntry in format_tb(exTrace): + for trLine in trEntry.split("\n"): + stripLine = trLine.lstrip(" ") + nIndent = len(trLine) - len(stripLine) + fmtTrace += " "*nIndent + stripLine + "
" + + theMessage = ( + "

Please report this error by submitting an issue report on " + "GitHub, providing a description and this error message.

" + "

Issue Tracker
%s

" + "

Error Type
%s: %s

" + "

Traceback
%s

" + ) % (__issuesurl__, exType.__name__, str(exValue), fmtTrace) + + return theMessage + + except Exception as e: + return "Could not generate error message.
%s" % str(e) + + return "Could not generate error message." + + +def exceptionHandler(exType, exValue, exTrace): + """Function to catch unhandled global exceptions. + """ + import logging + from traceback import print_tb, format_tb + from nw import __issuesurl__ + from PyQt5.QtWidgets import qApp, QApplication, QErrorMessage, QMessageBox + + logger = logging.getLogger(__name__) + logger.error("%s: %s" % (exType.__name__, str(exValue))) + print_tb(exTrace) + + try: + nwGUI = None + for qWin in qApp.topLevelWidgets(): + if qWin.objectName() == "GuiMain": + nwGUI = qWin + break + + if nwGUI is None: + logger.warning("Could not find main GUI window so cannot open error dialog") + return + + errMsg = QErrorMessage(nwGUI) + errMsg.setWindowTitle("Unhandled Error") + errMsg.resize(800, 400) + errMsg.showMessage(( + "

An unhandled error has been encountered

%s" + ) % formatHtmlErrMsg(exType, exValue, exTrace)) + + except Exception as e: + logger.error(str(e)) From 95a5ee7b8aadb3c0833854e248690536a2f21e6f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 2 Aug 2020 14:34:44 +0200 Subject: [PATCH 2/4] Set up global error handler and modified error handling for initial GUI build --- nw/__init__.py | 58 ++++++++++++++++++++++++++++++-------------------- nw/guimain.py | 1 + 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/nw/__init__.py b/nw/__init__.py index 2271157a..5ffae592 100644 --- a/nw/__init__.py +++ b/nw/__init__.py @@ -34,6 +34,7 @@ from os import path, remove, rename from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QErrorMessage +from nw.error import exceptionHandler from nw.config import Config __package__ = "nw" @@ -249,11 +250,12 @@ def main(sysArgs=None): if errorData: errApp = QApplication([]) errMsg = QErrorMessage() - errMsg.setMinimumWidth(500) - errMsg.setMinimumHeight(300) + errMsg.resize(500, 300) errMsg.showMessage(( - "ERROR: novelWriter cannot start due to the following issues:

" - " - %s

Exiting." + "

A critical error has been encountered

" + "

novelWriter cannot start due to the following issues:

" + "

 - %s

" + "

Shutting down ...

" ) % ( "
 - ".join(errorData) )) @@ -276,34 +278,44 @@ def main(sysArgs=None): nwApp.setWindowIcon(QIcon(CONFIG.appIcon)) nwApp.setOrganizationDomain(__domain__) + # We try to catch critical errors while setting up the main GUI + # by wrapping the main GUI in a try/except structure. This will + # not catch all exceptions for other parts of the application. + # For all other unhandled exceptions, we use a custom exception + # handler that pops a dialog box with the error message. + sys.excepthook = exceptionHandler + try: nwGUI = GuiMain() sys.exit(nwApp.exec_()) except Exception: - # novelWriter has crashed! - from traceback import print_tb, format_tb - eInfo = sys.exc_info() - logger.critical("%s: %s" % (eInfo[0].__name__, eInfo[1])) - print_tb(eInfo[2]) + from traceback import print_tb + from nw.error import formatHtmlErrMsg - del nwApp + exType, exValue, exTrace = sys.exc_info() - errApp = QApplication([]) - errMsg = QErrorMessage() - errMsg.setWindowTitle("Critical Error") - errMsg.setMinimumWidth(500) - errMsg.setMinimumHeight(300) - errMsg.showMessage(( - "

novelWriter has encountered a critical error!

" - "

%s:
%s

" - "

Traceback:
%s

" - "

Shutting down ...

" - ) % (eInfo[0].__name__, eInfo[1], "
".join(format_tb(eInfo[2])))) - errApp.exec_() + logger.critical("%s: %s" % (exType.__name__, str(exValue))) + print_tb(exTrace) - del eInfo + try: + del nwApp + + errApp = QApplication([]) + errMsg = QErrorMessage() + errMsg.setWindowTitle("Critical Error") + errMsg.resize(800, 400) + errMsg.showMessage(( + "

A critical error has been encountered

" + "%s" + "

Shutting down ...

" + ) % formatHtmlErrMsg(exType, exValue, exTrace)) + errApp.exec_() + + except Exception as e: + logger.critical("Could not create error message dialog.") + logger.critical(str(e)) sys.exit(1) diff --git a/nw/guimain.py b/nw/guimain.py index 6ea48c9b..12655f20 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -56,6 +56,7 @@ class GuiMain(QMainWindow): QMainWindow.__init__(self) logger.debug("Initialising GUI ...") + self.setObjectName("GuiMain") self.mainConf = nw.CONFIG # Some runtime info useful for debugging From 46c9f0fb5f5c435c59ca63d87534a9b87ce20f3f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 2 Aug 2020 15:03:17 +0200 Subject: [PATCH 3/4] Added more infor to the error dialog --- nw/error.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/nw/error.py b/nw/error.py index dd0cb109..cb276752 100644 --- a/nw/error.py +++ b/nw/error.py @@ -29,8 +29,11 @@ def formatHtmlErrMsg(exType, exValue, exTrace): """Generates a HTML version of an exception. """ try: + import sys from traceback import format_tb - from nw import __issuesurl__ + from nw import __issuesurl__, __version__ + from PyQt5.Qt import PYQT_VERSION_STR + from PyQt5.QtCore import QT_VERSION_STR, QSysInfo fmtTrace = "" for trEntry in format_tb(exTrace): @@ -41,11 +44,25 @@ def formatHtmlErrMsg(exType, exValue, exTrace): theMessage = ( "

Please report this error by submitting an issue report on " - "GitHub, providing a description and this error message.

" - "

Issue Tracker
%s

" - "

Error Type
%s: %s

" - "

Traceback
%s

" - ) % (__issuesurl__, exType.__name__, str(exValue), fmtTrace) + "GitHub, providing a description and this error message. " + "URL: <{issueUrl}>.

" + "

Environment
Version: {nwVersion}, OS: {osType} ({osKernel})," + "Python: {pyVersion} ({pyHexVer:#x}), Qt: {qtVers}, PyQt: {pyqtVers}

" + "

Error Type
{exType}: {exMessage}

" + "

Traceback
{exTrace}

" + ).format( + nwVersion = __version__, + osType = sys.platform, + osKernel = QSysInfo.kernelVersion(), + pyVersion = sys.version.split()[0], + pyHexVer = sys.hexversion, + qtVers = QT_VERSION_STR, + pyqtVers = PYQT_VERSION_STR, + issueUrl = __issuesurl__, + exType = exType.__name__, + exMessage = str(exValue), + exTrace = fmtTrace + ) return theMessage From d3ab1caf3a3f5a6206b50e944e478cededa91bb7 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 2 Aug 2020 15:14:46 +0200 Subject: [PATCH 4/4] Block the error handler dialog in test mode --- nw/error.py | 7 ++++++- nw/gui/build.py | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/nw/error.py b/nw/error.py index cb276752..0301f3ba 100644 --- a/nw/error.py +++ b/nw/error.py @@ -77,13 +77,16 @@ def exceptionHandler(exType, exValue, exTrace): """ import logging from traceback import print_tb, format_tb - from nw import __issuesurl__ + from nw import CONFIG from PyQt5.QtWidgets import qApp, QApplication, QErrorMessage, QMessageBox logger = logging.getLogger(__name__) logger.error("%s: %s" % (exType.__name__, str(exValue))) print_tb(exTrace) + if not CONFIG.showGUI: + return + try: nwGUI = None for qWin in qApp.topLevelWidgets(): @@ -104,3 +107,5 @@ def exceptionHandler(exType, exValue, exTrace): except Exception as e: logger.error(str(e)) + + return diff --git a/nw/gui/build.py b/nw/gui/build.py index 34a01f05..93aa8271 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -432,7 +432,6 @@ class GuiBuildNovel(QDialog): def _buildPreview(self): """Build a preview of the project in the document viewer. """ - # Get Settings fmtTitle = self.fmtTitle.text().strip() fmtChapter = self.fmtChapter.text().strip()