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