1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing a specialized error message dialog.
8"""
9
10import contextlib
11
12from PyQt5.QtCore import (
13    qInstallMessageHandler, Qt, Q_ARG, QSettings, QtMsgType, QThread,
14    QMetaObject
15)
16from PyQt5.QtWidgets import QErrorMessage, QDialog
17
18from E5Gui.E5Application import e5App
19
20import Globals
21import Utilities
22import Preferences
23
24
25_msgHandlerDialog = None
26_origMsgHandler = None
27
28_filterSettings = QSettings(
29    QSettings.Format.IniFormat,
30    QSettings.Scope.UserScope,
31    Globals.settingsNameOrganization,
32    "eric6messagefilters")
33_defaultFilters = [
34    "QFont::",
35    "QCocoaMenu::removeMenuItem",
36    "QCocoaMenu::insertNative",
37    ",type id:",
38    "Remote debugging server started successfully",
39    "Uncaught SecurityError:",
40    "Content Security Policy",
41    "QXcbClipboard:",
42    "QXcbConnection: XCB error",
43    "libpng warning: iCCP:",
44    "Uncaught ReferenceError: $ is not defined",
45]
46
47
48def filterMessage(message):
49    """
50    Module function to filter messages.
51
52    @param message message to be checked
53    @type str
54    @return flag indicating that the message should be filtered out
55    @rtype bool
56    """
57    return any(
58        filterStr in message
59        for filterStr in Globals.toList(_filterSettings.value(
60            "MessageFilters", [])) + _defaultFilters
61    )
62
63
64class E5ErrorMessage(QErrorMessage):
65    """
66    Class implementing a specialized error message dialog.
67    """
68    def __init__(self, parent=None):
69        """
70        Constructor
71
72        @param parent reference to the parent widget
73        @type QWidget
74        """
75        super().__init__(parent)
76
77    def showMessage(self, message, msgType=""):
78        """
79        Public method to show a message.
80
81        @param message error message to be shown
82        @type str
83        @param msgType type of the error message
84        @type str
85        """
86        if not filterMessage(message):
87            if msgType:
88                super().showMessage(message, msgType)
89            else:
90                super().showMessage(message)
91
92    def editMessageFilters(self):
93        """
94        Public method to edit the list of message filters.
95        """
96        from .E5ErrorMessageFilterDialog import E5ErrorMessageFilterDialog
97        dlg = E5ErrorMessageFilterDialog(
98            Globals.toList(_filterSettings.value(
99                "MessageFilters", [])))
100        if dlg.exec() == QDialog.DialogCode.Accepted:
101            filters = dlg.getFilters()
102            _filterSettings.setValue("MessageFilters", filters)
103
104
105def messageHandler(msgType, context, message):
106    """
107    Module function handling messages.
108
109    @param msgType type of the message
110    @type  int, QtMsgType
111    @param context context information
112    @type QMessageLogContext
113    @param message message to be shown
114    @type bytes
115    """
116    if _msgHandlerDialog:
117        if msgType < Preferences.getUI("MinimumMessageTypeSeverity"):
118            # severity is lower than configured
119            # just ignore the message
120            return
121
122        with contextlib.suppress(RuntimeError):
123            if msgType == QtMsgType.QtDebugMsg:
124                messageType = "Debug Message:"
125            elif msgType == QtMsgType.QtInfoMsg:
126                messageType = "Info Message:"
127            elif msgType == QtMsgType.QtWarningMsg:
128                messageType = "Warning:"
129            elif msgType == QtMsgType.QtCriticalMsg:
130                messageType = "Critical:"
131            elif msgType == QtMsgType.QtFatalMsg:
132                messageType = "Fatal Error:"
133            if isinstance(message, bytes):
134                message = Utilities.decodeBytes(message)
135            if filterMessage(message):
136                return
137            message = (
138                message.replace("\r\n", "<br/>")
139                .replace("\n", "<br/>")
140                .replace("\r", "<br/>")
141            )
142            msg = (
143                (
144                    "<p><b>{0}</b></p><p>{1}</p><p>File: {2}</p>"
145                    "<p>Line: {3}</p><p>Function: {4}</p>"
146                ).format(messageType, Utilities.html_uencode(message),
147                         context.file, context.line, context.function)
148                if context.file is not None else
149                "<p><b>{0}</b></p><p>{1}</p>".format(
150                    messageType, Utilities.html_uencode(message))
151            )
152            if QThread.currentThread() == e5App().thread():
153                _msgHandlerDialog.showMessage(msg)
154            else:
155                QMetaObject.invokeMethod(
156                    _msgHandlerDialog,
157                    "showMessage",
158                    Qt.ConnectionType.QueuedConnection,
159                    Q_ARG(str, msg))
160            return
161    elif _origMsgHandler:
162        _origMsgHandler(msgType, message)
163        return
164
165    if msgType == QtMsgType.QtDebugMsg:
166        messageType = "Debug Message"
167    elif msgType == QtMsgType.QtInfoMsg:
168        messageType = "Info Message:"
169    elif msgType == QtMsgType.QtWarningMsg:
170        messageType = "Warning"
171    elif msgType == QtMsgType.QtCriticalMsg:
172        messageType = "Critical"
173    elif msgType == QtMsgType.QtFatalMsg:
174        messageType = "Fatal Error"
175    if isinstance(message, bytes):
176        message = message.decode()
177    print("{0}: {1} in {2} at line {3} ({4})".format(
178        messageType, message, context.file, context.line,
179        context.function))
180
181
182def qtHandler():
183    """
184    Module function to install an E5ErrorMessage dialog as the global
185    message handler.
186
187    @return reference to the message handler dialog
188    @rtype E5ErrorMessage
189    """
190    global _msgHandlerDialog, _origMsgHandler
191
192    if _msgHandlerDialog is None:
193        # Install an E5ErrorMessage dialog as the global message handler.
194        _msgHandlerDialog = E5ErrorMessage()
195        _origMsgHandler = qInstallMessageHandler(messageHandler)
196
197    return _msgHandlerDialog
198
199
200def editMessageFilters():
201    """
202    Module function to edit the list of message filters.
203    """
204    if _msgHandlerDialog:
205        _msgHandlerDialog.editMessageFilters()
206    else:
207        print("No message handler installed.")
208
209
210def messageHandlerInstalled():
211    """
212    Module function to check, if a message handler was installed.
213
214    @return flag indicating an installed message handler
215    @rtype bool
216    """
217    return _msgHandlerDialog is not None
218
219#
220# eflag: noqa = M801
221