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/error.py b/nw/error.py
new file mode 100644
index 00000000..0301f3ba
--- /dev/null
+++ b/nw/error.py
@@ -0,0 +1,111 @@
+# -*- 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:
+ import sys
+ from traceback import format_tb
+ 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):
+ 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. "
+ "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
+
+ 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 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():
+ 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))
+
+ 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()
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