1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtWidgets module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qerrormessage.h" |
41 | |
42 | #include "qapplication.h" |
43 | #include "qcheckbox.h" |
44 | #include "qlabel.h" |
45 | #include "qlayout.h" |
46 | #if QT_CONFIG(messagebox) |
47 | #include "qmessagebox.h" |
48 | #endif |
49 | #include "qpushbutton.h" |
50 | #include "qstringlist.h" |
51 | #include "qtextedit.h" |
52 | #include "qdialog_p.h" |
53 | #include "qpixmap.h" |
54 | #include "qmetaobject.h" |
55 | #include "qthread.h" |
56 | #include "qset.h" |
57 | |
58 | #include <queue> |
59 | |
60 | #include <stdio.h> |
61 | #include <stdlib.h> |
62 | |
63 | QT_BEGIN_NAMESPACE |
64 | |
65 | namespace { |
66 | struct Message { |
67 | QString content; |
68 | QString type; |
69 | }; |
70 | } |
71 | |
72 | class QErrorMessagePrivate : public QDialogPrivate |
73 | { |
74 | Q_DECLARE_PUBLIC(QErrorMessage) |
75 | public: |
76 | QPushButton * ok; |
77 | QCheckBox * again; |
78 | QTextEdit * errors; |
79 | QLabel * icon; |
80 | std::queue<Message> pending; |
81 | QSet<QString> doNotShow; |
82 | QSet<QString> doNotShowType; |
83 | QString currentMessage; |
84 | QString currentType; |
85 | |
86 | bool isMessageToBeShown(const QString &message, const QString &type) const; |
87 | bool nextPending(); |
88 | void retranslateStrings(); |
89 | }; |
90 | |
91 | namespace { |
92 | class QErrorMessageTextView : public QTextEdit |
93 | { |
94 | public: |
95 | QErrorMessageTextView(QWidget *parent) |
96 | : QTextEdit(parent) { setReadOnly(true); } |
97 | |
98 | virtual QSize minimumSizeHint() const override; |
99 | virtual QSize sizeHint() const override; |
100 | }; |
101 | } // unnamed namespace |
102 | |
103 | QSize QErrorMessageTextView::minimumSizeHint() const |
104 | { |
105 | return QSize(50, 50); |
106 | } |
107 | |
108 | QSize QErrorMessageTextView::sizeHint() const |
109 | { |
110 | return QSize(250, 75); |
111 | } |
112 | |
113 | /*! |
114 | \class QErrorMessage |
115 | |
116 | \brief The QErrorMessage class provides an error message display dialog. |
117 | |
118 | \ingroup standard-dialog |
119 | \inmodule QtWidgets |
120 | |
121 | An error message widget consists of a text label and a checkbox. The |
122 | checkbox lets the user control whether the same error message will be |
123 | displayed again in the future, typically displaying the text, |
124 | "Show this message again" translated into the appropriate local |
125 | language. |
126 | |
127 | For production applications, the class can be used to display messages which |
128 | the user only needs to see once. To use QErrorMessage like this, you create |
129 | the dialog in the usual way, and show it by calling the showMessage() slot or |
130 | connecting signals to it. |
131 | |
132 | The static qtHandler() function installs a message handler |
133 | using qInstallMessageHandler() and creates a QErrorMessage that displays |
134 | qDebug(), qWarning() and qFatal() messages. This is most useful in |
135 | environments where no console is available to display warnings and |
136 | error messages. |
137 | |
138 | In both cases QErrorMessage will queue pending messages and display |
139 | them in order, with each new message being shown as soon as the user |
140 | has accepted the previous message. Once the user has specified that a |
141 | message is not to be shown again it is automatically skipped, and the |
142 | dialog will show the next appropriate message in the queue. |
143 | |
144 | The \l{dialogs/standarddialogs}{Standard Dialogs} example shows |
145 | how to use QErrorMessage as well as other built-in Qt dialogs. |
146 | |
147 | \image qerrormessage.png |
148 | |
149 | \sa QMessageBox, QStatusBar::showMessage(), {Standard Dialogs Example} |
150 | */ |
151 | |
152 | static QErrorMessage * qtMessageHandler = nullptr; |
153 | |
154 | static void deleteStaticcQErrorMessage() // post-routine |
155 | { |
156 | if (qtMessageHandler) { |
157 | delete qtMessageHandler; |
158 | qtMessageHandler = nullptr; |
159 | } |
160 | } |
161 | |
162 | static bool metFatal = false; |
163 | |
164 | static QString msgType2i18nString(QtMsgType t) |
165 | { |
166 | Q_STATIC_ASSERT(QtDebugMsg == 0); |
167 | Q_STATIC_ASSERT(QtWarningMsg == 1); |
168 | Q_STATIC_ASSERT(QtCriticalMsg == 2); |
169 | Q_STATIC_ASSERT(QtFatalMsg == 3); |
170 | Q_STATIC_ASSERT(QtInfoMsg == 4); |
171 | |
172 | // adjust the array below if any of the above fire... |
173 | |
174 | const char * const messages[] = { |
175 | QT_TRANSLATE_NOOP("QErrorMessage" , "Debug Message:" ), |
176 | QT_TRANSLATE_NOOP("QErrorMessage" , "Warning:" ), |
177 | QT_TRANSLATE_NOOP("QErrorMessage" , "Critical Error:" ), |
178 | QT_TRANSLATE_NOOP("QErrorMessage" , "Fatal Error:" ), |
179 | QT_TRANSLATE_NOOP("QErrorMessage" , "Information:" ), |
180 | }; |
181 | Q_ASSERT(size_t(t) < sizeof messages / sizeof *messages); |
182 | |
183 | return QCoreApplication::translate(context: "QErrorMessage" , key: messages[t]); |
184 | } |
185 | |
186 | static void jump(QtMsgType t, const QMessageLogContext & /*context*/, const QString &m) |
187 | { |
188 | if (!qtMessageHandler) |
189 | return; |
190 | |
191 | QString rich = QLatin1String("<p><b>" ) + msgType2i18nString(t) + QLatin1String("</b></p>" ) |
192 | + Qt::convertFromPlainText(plain: m, mode: Qt::WhiteSpaceNormal); |
193 | |
194 | // ### work around text engine quirk |
195 | if (rich.endsWith(s: QLatin1String("</p>" ))) |
196 | rich.chop(n: 4); |
197 | |
198 | if (!metFatal) { |
199 | if (QThread::currentThread() == qApp->thread()) { |
200 | qtMessageHandler->showMessage(message: rich); |
201 | } else { |
202 | QMetaObject::invokeMethod(obj: qtMessageHandler, |
203 | member: "showMessage" , |
204 | type: Qt::QueuedConnection, |
205 | Q_ARG(QString, rich)); |
206 | } |
207 | metFatal = (t == QtFatalMsg); |
208 | } |
209 | } |
210 | |
211 | |
212 | /*! |
213 | Constructs and installs an error handler window with the given \a |
214 | parent. |
215 | */ |
216 | |
217 | QErrorMessage::QErrorMessage(QWidget * parent) |
218 | : QDialog(*new QErrorMessagePrivate, parent) |
219 | { |
220 | Q_D(QErrorMessage); |
221 | |
222 | d->icon = new QLabel(this); |
223 | d->errors = new QErrorMessageTextView(this); |
224 | d->again = new QCheckBox(this); |
225 | d->ok = new QPushButton(this); |
226 | QGridLayout * grid = new QGridLayout(this); |
227 | |
228 | connect(sender: d->ok, SIGNAL(clicked()), receiver: this, SLOT(accept())); |
229 | |
230 | grid->addWidget(d->icon, row: 0, column: 0, Qt::AlignTop); |
231 | grid->addWidget(d->errors, row: 0, column: 1); |
232 | grid->addWidget(d->again, row: 1, column: 1, Qt::AlignTop); |
233 | grid->addWidget(d->ok, row: 2, column: 0, rowSpan: 1, columnSpan: 2, Qt::AlignCenter); |
234 | grid->setColumnStretch(column: 1, stretch: 42); |
235 | grid->setRowStretch(row: 0, stretch: 42); |
236 | |
237 | #if QT_CONFIG(messagebox) |
238 | d->icon->setPixmap(QMessageBox::standardIcon(icon: QMessageBox::Information)); |
239 | d->icon->setAlignment(Qt::AlignHCenter | Qt::AlignTop); |
240 | #endif |
241 | d->again->setChecked(true); |
242 | d->ok->setFocus(); |
243 | |
244 | d->retranslateStrings(); |
245 | } |
246 | |
247 | |
248 | /*! |
249 | Destroys the error message dialog. |
250 | */ |
251 | |
252 | QErrorMessage::~QErrorMessage() |
253 | { |
254 | if (this == qtMessageHandler) { |
255 | qtMessageHandler = nullptr; |
256 | QtMessageHandler tmp = qInstallMessageHandler(nullptr); |
257 | // in case someone else has later stuck in another... |
258 | if (tmp != jump) |
259 | qInstallMessageHandler(tmp); |
260 | } |
261 | } |
262 | |
263 | |
264 | /*! \reimp */ |
265 | |
266 | void QErrorMessage::done(int a) |
267 | { |
268 | Q_D(QErrorMessage); |
269 | if (!d->again->isChecked()) { |
270 | if (d->currentType.isEmpty()) { |
271 | if (!d->currentMessage.isEmpty()) |
272 | d->doNotShow.insert(value: d->currentMessage); |
273 | } else { |
274 | d->doNotShowType.insert(value: d->currentType); |
275 | } |
276 | } |
277 | d->currentMessage.clear(); |
278 | d->currentType.clear(); |
279 | if (!d->nextPending()) { |
280 | QDialog::done(a); |
281 | if (this == qtMessageHandler && metFatal) |
282 | exit(status: 1); |
283 | } |
284 | } |
285 | |
286 | |
287 | /*! |
288 | Returns a pointer to a QErrorMessage object that outputs the |
289 | default Qt messages. This function creates such an object, if there |
290 | isn't one already. |
291 | */ |
292 | |
293 | QErrorMessage * QErrorMessage::qtHandler() |
294 | { |
295 | if (!qtMessageHandler) { |
296 | qtMessageHandler = new QErrorMessage(nullptr); |
297 | qAddPostRoutine(deleteStaticcQErrorMessage); // clean up |
298 | qtMessageHandler->setWindowTitle(QCoreApplication::applicationName()); |
299 | qInstallMessageHandler(jump); |
300 | } |
301 | return qtMessageHandler; |
302 | } |
303 | |
304 | |
305 | /*! \internal */ |
306 | |
307 | bool QErrorMessagePrivate::isMessageToBeShown(const QString &message, const QString &type) const |
308 | { |
309 | return !message.isEmpty() |
310 | && (type.isEmpty() ? !doNotShow.contains(value: message) : !doNotShowType.contains(value: type)); |
311 | } |
312 | |
313 | bool QErrorMessagePrivate::nextPending() |
314 | { |
315 | while (!pending.empty()) { |
316 | QString message = std::move(pending.front().content); |
317 | QString type = std::move(pending.front().type); |
318 | pending.pop(); |
319 | if (isMessageToBeShown(message, type)) { |
320 | #ifndef QT_NO_TEXTHTMLPARSER |
321 | errors->setHtml(message); |
322 | #else |
323 | errors->setPlainText(message); |
324 | #endif |
325 | currentMessage = std::move(message); |
326 | currentType = std::move(type); |
327 | return true; |
328 | } |
329 | } |
330 | return false; |
331 | } |
332 | |
333 | |
334 | /*! |
335 | Shows the given message, \a message, and returns immediately. If the user |
336 | has requested for the message not to be shown again, this function does |
337 | nothing. |
338 | |
339 | Normally, the message is displayed immediately. However, if there are |
340 | pending messages, it will be queued to be displayed later. |
341 | */ |
342 | |
343 | void QErrorMessage::showMessage(const QString &message) |
344 | { |
345 | showMessage(message, type: QString()); |
346 | } |
347 | |
348 | /*! |
349 | \since 4.5 |
350 | \overload |
351 | |
352 | Shows the given message, \a message, and returns immediately. If the user |
353 | has requested for messages of type, \a type, not to be shown again, this |
354 | function does nothing. |
355 | |
356 | Normally, the message is displayed immediately. However, if there are |
357 | pending messages, it will be queued to be displayed later. |
358 | |
359 | \sa showMessage() |
360 | */ |
361 | |
362 | void QErrorMessage::showMessage(const QString &message, const QString &type) |
363 | { |
364 | Q_D(QErrorMessage); |
365 | if (!d->isMessageToBeShown(message, type)) |
366 | return; |
367 | d->pending.push(x: {.content: message, .type: type}); |
368 | if (!isVisible() && d->nextPending()) |
369 | show(); |
370 | } |
371 | |
372 | /*! |
373 | \reimp |
374 | */ |
375 | void QErrorMessage::changeEvent(QEvent *e) |
376 | { |
377 | Q_D(QErrorMessage); |
378 | if (e->type() == QEvent::LanguageChange) { |
379 | d->retranslateStrings(); |
380 | } |
381 | QDialog::changeEvent(e); |
382 | } |
383 | |
384 | void QErrorMessagePrivate::retranslateStrings() |
385 | { |
386 | again->setText(QErrorMessage::tr(s: "&Show this message again" )); |
387 | ok->setText(QErrorMessage::tr(s: "&OK" )); |
388 | } |
389 | |
390 | QT_END_NAMESPACE |
391 | |
392 | #include "moc_qerrormessage.cpp" |
393 | |