195 lines
6.1 KiB
Python
195 lines
6.1 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
novelWriter – Exception Handling
|
||
================================
|
||
Error handling function and error dialog
|
||
|
||
File History:
|
||
Created: 2020-08-02 [0.10.2]
|
||
|
||
This file is a part of novelWriter
|
||
Copyright 2018–2021, 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 <https://www.gnu.org/licenses/>.
|
||
"""
|
||
|
||
import sys
|
||
import logging
|
||
|
||
from PyQt5.QtCore import Qt
|
||
from PyQt5.QtWidgets import (
|
||
qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
|
||
QDialogButtonBox
|
||
)
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# =============================================================================================== #
|
||
# Utility Functions
|
||
# =============================================================================================== #
|
||
|
||
def logException():
|
||
"""Log the content of an exception message.
|
||
"""
|
||
exType, exValue, _ = sys.exc_info()
|
||
logger.error("%s: %s" % (exType.__name__, str(exValue).strip("'")))
|
||
|
||
# =============================================================================================== #
|
||
# Error Handler
|
||
# =============================================================================================== #
|
||
|
||
class NWErrorMessage(QDialog):
|
||
|
||
def __init__(self, parent):
|
||
QDialog.__init__(self, parent=parent)
|
||
self.setObjectName("NWErrorMessage")
|
||
|
||
# Widgets
|
||
self.msgIcon = QLabel()
|
||
self.msgIcon.setPixmap(
|
||
qApp.style().standardIcon(QStyle.SP_MessageBoxCritical).pixmap(64, 64)
|
||
)
|
||
self.msgHead = QLabel()
|
||
self.msgHead.setOpenExternalLinks(True)
|
||
self.msgHead.setWordWrap(True)
|
||
|
||
self.msgBody = QPlainTextEdit()
|
||
self.msgBody.setReadOnly(True)
|
||
|
||
self.btnBox = QDialogButtonBox(QDialogButtonBox.Close)
|
||
self.btnBox.button(QDialogButtonBox.Close).setText(self.tr("Close"))
|
||
self.btnBox.rejected.connect(self._doClose)
|
||
|
||
# Assemble
|
||
self.mainBox = QGridLayout()
|
||
self.mainBox.addWidget(self.msgIcon, 0, 0, 2, 1, Qt.AlignTop)
|
||
self.mainBox.addWidget(self.msgHead, 0, 1, 1, 1, Qt.AlignTop)
|
||
self.mainBox.addWidget(self.msgBody, 1, 1, 1, 1)
|
||
self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
|
||
self.mainBox.setSpacing(16)
|
||
|
||
self.setLayout(self.mainBox)
|
||
|
||
self.setMinimumWidth(500)
|
||
self.setMinimumHeight(300)
|
||
self.setSizeGripEnabled(True)
|
||
self.resize(800, 400)
|
||
|
||
return
|
||
|
||
def setMessage(self, exType, exValue, exTrace):
|
||
"""Generate a message and append session data, error info and
|
||
error traceback.
|
||
"""
|
||
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
|
||
|
||
self.msgHead.setText((
|
||
"<p>An unhandled error has been encountered.</p>"
|
||
"<p>Please report this error by submitting an issue report on "
|
||
"GitHub, providing a description and including the error "
|
||
"message and traceback shown below.</p>"
|
||
"<p>URL: <a href='{issueUrl}'>{issueUrl}</a></p>"
|
||
).format(
|
||
issueUrl = __issuesurl__,
|
||
))
|
||
|
||
try:
|
||
kernelVersion = QSysInfo.kernelVersion()
|
||
except Exception:
|
||
kernelVersion = "Unknown"
|
||
|
||
try:
|
||
self.msgBody.setPlainText((
|
||
"Environment:\n"
|
||
"novelWriter Version: {nwVersion}\n"
|
||
"Host OS: {osType} ({osKernel})\n"
|
||
"Python: {pyVersion} ({pyHexVer:#x})\n"
|
||
"Qt: {qtVers}, PyQt: {pyqtVers}\n"
|
||
"\n"
|
||
"{exType}:\n{exMessage}\n"
|
||
"\n"
|
||
"Traceback:\n{exTrace}\n"
|
||
).format(
|
||
nwVersion = __version__,
|
||
osType = sys.platform,
|
||
osKernel = kernelVersion,
|
||
pyVersion = sys.version.split()[0],
|
||
pyHexVer = sys.hexversion,
|
||
qtVers = QT_VERSION_STR,
|
||
pyqtVers = PYQT_VERSION_STR,
|
||
exType = exType.__name__,
|
||
exMessage = str(exValue),
|
||
exTrace = "\n".join(format_tb(exTrace)),
|
||
))
|
||
except Exception:
|
||
self.msgBody.setPlainText("Failed to generate error report ...")
|
||
|
||
return
|
||
|
||
##
|
||
# Slots
|
||
##
|
||
|
||
def _doClose(self):
|
||
"""Close the dialog.
|
||
"""
|
||
self.close()
|
||
return
|
||
|
||
# END Class NWErrorMessage
|
||
|
||
def exceptionHandler(exType, exValue, exTrace):
|
||
"""Function to catch unhandled global exceptions.
|
||
"""
|
||
from traceback import print_tb
|
||
from PyQt5.QtWidgets import qApp
|
||
|
||
logger.critical("%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 = NWErrorMessage(nwGUI)
|
||
errMsg.setMessage(exType, exValue, exTrace)
|
||
errMsg.exec_()
|
||
|
||
try:
|
||
# Try a controlled shudown
|
||
nwGUI.closeProject(isYes=True)
|
||
nwGUI.closeMain()
|
||
logger.info("Emergency shutdown successful")
|
||
|
||
except Exception as e:
|
||
logger.critical("Could not close the project before exiting")
|
||
logger.critical(str(e))
|
||
|
||
qApp.exit(1)
|
||
|
||
except Exception as e:
|
||
logger.critical(str(e))
|
||
|
||
return
|