1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qerrormessage.h"
5
6#include "qapplication.h"
7#include "qcheckbox.h"
8#include "qlabel.h"
9#include "qlayout.h"
10#if QT_CONFIG(messagebox)
11#include "qmessagebox.h"
12#endif
13#include "qpushbutton.h"
14#include "qstringlist.h"
15#include "qtextedit.h"
16#include "qdialog_p.h"
17#include "qpixmap.h"
18#include "qmetaobject.h"
19#include "qthread.h"
20#include "qset.h"
21
22#include <queue>
23
24#include <stdio.h>
25#include <stdlib.h>
26
27QT_BEGIN_NAMESPACE
28
29using namespace Qt::StringLiterals;
30
31class QErrorMessagePrivate : public QDialogPrivate
32{
33 Q_DECLARE_PUBLIC(QErrorMessage)
34public:
35 struct Message {
36 QString content;
37 QString type;
38 };
39
40 QPushButton * ok;
41 QCheckBox * again;
42 QTextEdit * errors;
43 QLabel * icon;
44 std::queue<Message> pending;
45 QSet<QString> doNotShow;
46 QSet<QString> doNotShowType;
47 QString currentMessage;
48 QString currentType;
49
50 bool isMessageToBeShown(const QString &message, const QString &type) const;
51 bool nextPending();
52 void retranslateStrings();
53
54 void setVisible(bool) override;
55
56private:
57 void initHelper(QPlatformDialogHelper *) override;
58 void helperPrepareShow(QPlatformDialogHelper *) override;
59};
60
61
62void QErrorMessagePrivate::initHelper(QPlatformDialogHelper *helper)
63{
64 Q_Q(QErrorMessage);
65 auto *messageDialogHelper = static_cast<QPlatformMessageDialogHelper *>(helper);
66 QObject::connect(sender: messageDialogHelper, signal: &QPlatformMessageDialogHelper::checkBoxStateChanged, context: q,
67 slot: [this](Qt::CheckState state) {
68 again->setCheckState(state);
69 }
70 );
71 QObject::connect(sender: messageDialogHelper, signal: &QPlatformMessageDialogHelper::clicked, context: q,
72 slot: [this](QPlatformDialogHelper::StandardButton, QPlatformDialogHelper::ButtonRole) {
73 Q_Q(QErrorMessage);
74 q->accept();
75 }
76 );
77}
78
79void QErrorMessagePrivate::helperPrepareShow(QPlatformDialogHelper *helper)
80{
81 Q_Q(QErrorMessage);
82 auto *messageDialogHelper = static_cast<QPlatformMessageDialogHelper *>(helper);
83 QSharedPointer<QMessageDialogOptions> options = QMessageDialogOptions::create();
84 options->setText(currentMessage);
85 options->setWindowTitle(q->windowTitle());
86 options->setText(QErrorMessage::tr(s: "An error occurred"));
87 options->setInformativeText(currentMessage);
88 options->setStandardIcon(QMessageDialogOptions::Critical);
89 options->setCheckBox(label: again->text(), state: again->checkState());
90 messageDialogHelper->setOptions(options);
91}
92
93namespace {
94class QErrorMessageTextView : public QTextEdit
95{
96public:
97 QErrorMessageTextView(QWidget *parent)
98 : QTextEdit(parent) { setReadOnly(true); }
99
100 virtual QSize minimumSizeHint() const override;
101 virtual QSize sizeHint() const override;
102};
103} // unnamed namespace
104
105QSize QErrorMessageTextView::minimumSizeHint() const
106{
107 return QSize(50, 50);
108}
109
110QSize QErrorMessageTextView::sizeHint() const
111{
112 return QSize(250, 75);
113}
114
115/*!
116 \class QErrorMessage
117
118 \brief The QErrorMessage class provides an error message display dialog.
119
120 \ingroup standard-dialog
121 \inmodule QtWidgets
122
123 An error message widget consists of a text label and a checkbox. The
124 checkbox lets the user control whether the same error message will be
125 displayed again in the future, typically displaying the text,
126 "Show this message again" translated into the appropriate local
127 language.
128
129 For production applications, the class can be used to display messages which
130 the user only needs to see once. To use QErrorMessage like this, you create
131 the dialog in the usual way, and show it by calling the showMessage() slot or
132 connecting signals to it.
133
134 The static qtHandler() function installs a message handler
135 using qInstallMessageHandler() and creates a QErrorMessage that displays
136 qDebug(), qWarning() and qFatal() messages. This is most useful in
137 environments where no console is available to display warnings and
138 error messages.
139
140 In both cases QErrorMessage will queue pending messages and display
141 them in order, with each new message being shown as soon as the user
142 has accepted the previous message. Once the user has specified that a
143 message is not to be shown again it is automatically skipped, and the
144 dialog will show the next appropriate message in the queue.
145
146 The \l{dialogs/standarddialogs}{Standard Dialogs} example shows
147 how to use QErrorMessage as well as other built-in Qt dialogs.
148
149 \image qerrormessage.png
150
151 \sa QMessageBox, QStatusBar::showMessage(), {Standard Dialogs Example}
152*/
153
154static QErrorMessage * qtMessageHandler = nullptr;
155
156static void deleteStaticcQErrorMessage() // post-routine
157{
158 if (qtMessageHandler) {
159 delete qtMessageHandler;
160 qtMessageHandler = nullptr;
161 }
162}
163
164static bool metFatal = false;
165
166static QString msgType2i18nString(QtMsgType t)
167{
168 static_assert(QtDebugMsg == 0);
169 static_assert(QtWarningMsg == 1);
170 static_assert(QtCriticalMsg == 2);
171 static_assert(QtFatalMsg == 3);
172 static_assert(QtInfoMsg == 4);
173
174 // adjust the array below if any of the above fire...
175
176 const char * const messages[] = {
177 QT_TRANSLATE_NOOP("QErrorMessage", "Debug Message:"),
178 QT_TRANSLATE_NOOP("QErrorMessage", "Warning:"),
179 QT_TRANSLATE_NOOP("QErrorMessage", "Critical Error:"),
180 QT_TRANSLATE_NOOP("QErrorMessage", "Fatal Error:"),
181 QT_TRANSLATE_NOOP("QErrorMessage", "Information:"),
182 };
183 Q_ASSERT(size_t(t) < sizeof messages / sizeof *messages);
184
185 return QCoreApplication::translate(context: "QErrorMessage", key: messages[t]);
186}
187
188static QtMessageHandler originalMessageHandler = nullptr;
189
190static void jump(QtMsgType t, const QMessageLogContext &context, const QString &m)
191{
192 const auto forwardToOriginalHandler = qScopeGuard(f: [&] {
193 if (originalMessageHandler)
194 originalMessageHandler(t, context, m);
195 });
196
197 if (!qtMessageHandler)
198 return;
199
200 auto *defaultCategory = QLoggingCategory::defaultCategory();
201 if (context.category && defaultCategory
202 && qstrcmp(str1: context.category, str2: defaultCategory->categoryName()) != 0)
203 return;
204
205 QString rich = "<p><b>"_L1 + msgType2i18nString(t) + "</b></p>"_L1
206 + Qt::convertFromPlainText(plain: m, mode: Qt::WhiteSpaceNormal);
207
208 // ### work around text engine quirk
209 if (rich.endsWith(s: "</p>"_L1))
210 rich.chop(n: 4);
211
212 if (!metFatal) {
213 if (QThread::currentThread() == qApp->thread()) {
214 qtMessageHandler->showMessage(message: rich);
215 } else {
216 QMetaObject::invokeMethod(obj: qtMessageHandler,
217 member: "showMessage",
218 c: Qt::QueuedConnection,
219 Q_ARG(QString, rich));
220 }
221 metFatal = (t == QtFatalMsg);
222 }
223}
224
225
226/*!
227 Constructs and installs an error handler window with the given \a
228 parent.
229
230 The default \l{Qt::WindowModality} {window modality} of the dialog
231 depends on the platform. The window modality can be overridden via
232 setWindowModality() before calling showMessage().
233*/
234
235QErrorMessage::QErrorMessage(QWidget * parent)
236 : QDialog(*new QErrorMessagePrivate, parent)
237{
238 Q_D(QErrorMessage);
239
240#if defined(Q_OS_MACOS)
241 setWindowModality(parent ? Qt::WindowModal : Qt::ApplicationModal);
242#endif
243
244 d->icon = new QLabel(this);
245 d->errors = new QErrorMessageTextView(this);
246 d->again = new QCheckBox(this);
247 d->ok = new QPushButton(this);
248 QGridLayout * grid = new QGridLayout(this);
249
250 connect(sender: d->ok, SIGNAL(clicked()), receiver: this, SLOT(accept()));
251
252 grid->addWidget(d->icon, row: 0, column: 0, Qt::AlignTop);
253 grid->addWidget(d->errors, row: 0, column: 1);
254 grid->addWidget(d->again, row: 1, column: 1, Qt::AlignTop);
255 grid->addWidget(d->ok, row: 2, column: 0, rowSpan: 1, columnSpan: 2, Qt::AlignCenter);
256 grid->setColumnStretch(column: 1, stretch: 42);
257 grid->setRowStretch(row: 0, stretch: 42);
258
259#if QT_CONFIG(messagebox)
260 d->icon->setPixmap(style()->standardPixmap(standardPixmap: QStyle::SP_MessageBoxInformation));
261 d->icon->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
262#endif
263 d->again->setChecked(true);
264 d->ok->setFocus();
265
266 d->retranslateStrings();
267}
268
269
270/*!
271 Destroys the error message dialog.
272*/
273
274QErrorMessage::~QErrorMessage()
275{
276 if (this == qtMessageHandler) {
277 qtMessageHandler = nullptr;
278 QtMessageHandler currentMessagHandler = qInstallMessageHandler(nullptr);
279 if (currentMessagHandler != jump)
280 qInstallMessageHandler(currentMessagHandler);
281 else
282 qInstallMessageHandler(originalMessageHandler);
283 originalMessageHandler = nullptr;
284 }
285}
286
287
288/*! \reimp */
289
290void QErrorMessage::done(int a)
291{
292 Q_D(QErrorMessage);
293 if (!d->again->isChecked()) {
294 if (d->currentType.isEmpty()) {
295 if (!d->currentMessage.isEmpty())
296 d->doNotShow.insert(value: d->currentMessage);
297 } else {
298 d->doNotShowType.insert(value: d->currentType);
299 }
300 }
301 d->currentMessage.clear();
302 d->currentType.clear();
303
304 QDialog::done(a);
305
306 if (d->nextPending()) {
307 show();
308 } else {
309 if (this == qtMessageHandler && metFatal)
310 exit(status: 1);
311 }
312}
313
314
315/*!
316 Returns a pointer to a QErrorMessage object that outputs the
317 default Qt messages. This function creates such an object, if there
318 isn't one already.
319
320 The object will only output log messages of QLoggingCategory::defaultCategory().
321
322 The object will forward all messages to the original message handler.
323
324 \sa qInstallMessageHandler
325*/
326
327QErrorMessage * QErrorMessage::qtHandler()
328{
329 if (!qtMessageHandler) {
330 qtMessageHandler = new QErrorMessage(nullptr);
331 qAddPostRoutine(deleteStaticcQErrorMessage); // clean up
332 qtMessageHandler->setWindowTitle(QCoreApplication::applicationName());
333 originalMessageHandler = qInstallMessageHandler(jump);
334 }
335 return qtMessageHandler;
336}
337
338
339/*! \internal */
340
341bool QErrorMessagePrivate::isMessageToBeShown(const QString &message, const QString &type) const
342{
343 return !message.isEmpty()
344 && (type.isEmpty() ? !doNotShow.contains(value: message) : !doNotShowType.contains(value: type));
345}
346
347bool QErrorMessagePrivate::nextPending()
348{
349 while (!pending.empty()) {
350 QString message = std::move(pending.front().content);
351 QString type = std::move(pending.front().type);
352 pending.pop();
353 if (isMessageToBeShown(message, type)) {
354#ifndef QT_NO_TEXTHTMLPARSER
355 errors->setHtml(message);
356#else
357 errors->setPlainText(message);
358#endif
359 currentMessage = std::move(message);
360 currentType = std::move(type);
361 again->setChecked(true);
362 return true;
363 }
364 }
365 return false;
366}
367
368
369/*!
370 Shows the given message, \a message, and returns immediately. If the user
371 has requested for the message not to be shown again, this function does
372 nothing.
373
374 Normally, the message is displayed immediately. However, if there are
375 pending messages, it will be queued to be displayed later.
376*/
377
378void QErrorMessage::showMessage(const QString &message)
379{
380 showMessage(message, type: QString());
381}
382
383/*!
384 \since 4.5
385 \overload
386
387 Shows the given message, \a message, and returns immediately. If the user
388 has requested for messages of type, \a type, not to be shown again, this
389 function does nothing.
390
391 Normally, the message is displayed immediately. However, if there are
392 pending messages, it will be queued to be displayed later.
393
394 \sa showMessage()
395*/
396
397void QErrorMessage::showMessage(const QString &message, const QString &type)
398{
399 Q_D(QErrorMessage);
400 if (!d->isMessageToBeShown(message, type))
401 return;
402 d->pending.push(x: {.content: message, .type: type});
403 if (!isVisible() && d->nextPending())
404 show();
405}
406
407void QErrorMessagePrivate::setVisible(bool visible)
408{
409 Q_Q(QErrorMessage);
410 if (q->testAttribute(attribute: Qt::WA_WState_ExplicitShowHide) && q->testAttribute(attribute: Qt::WA_WState_Hidden) != visible)
411 return;
412
413 if (canBeNativeDialog())
414 setNativeDialogVisible(visible);
415
416 // Update WA_DontShowOnScreen based on whether the native dialog was shown,
417 // so that QDialog::setVisible(visible) below updates the QWidget state correctly,
418 // but skips showing the non-native version.
419 q->setAttribute(Qt::WA_DontShowOnScreen, on: nativeDialogInUse);
420
421 QDialogPrivate::setVisible(visible);
422}
423
424/*!
425 \reimp
426*/
427void QErrorMessage::changeEvent(QEvent *e)
428{
429 Q_D(QErrorMessage);
430 if (e->type() == QEvent::LanguageChange) {
431 d->retranslateStrings();
432 }
433 QDialog::changeEvent(e);
434}
435
436void QErrorMessagePrivate::retranslateStrings()
437{
438 again->setText(QErrorMessage::tr(s: "&Show this message again"));
439 ok->setText(QErrorMessage::tr(s: "&OK"));
440}
441
442QT_END_NAMESPACE
443
444#include "moc_qerrormessage.cpp"
445

source code of qtbase/src/widgets/dialogs/qerrormessage.cpp