""" 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 . """ 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.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(( "

An unhandled error has been encountered.

" "

Please report this error by submitting an issue report on " "GitHub, providing a description and including the error " "message and traceback shown below.

" "

URL: {issueUrl}

" ).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