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 const auto iconSize = style()->pixelMetric(metric: QStyle::PM_MessageBoxIconSize, option: nullptr, widget: this);
261 const auto icon = style()->standardIcon(standardIcon: QStyle::SP_MessageBoxInformation, option: nullptr, widget: this);
262 d->icon->setPixmap(icon.pixmap(size: QSize(iconSize, iconSize), devicePixelRatio: devicePixelRatio()));
263 d->icon->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
264#endif
265 d->again->setChecked(true);
266 d->ok->setFocus();
267
268 d->retranslateStrings();
269}
270
271
272/*!
273 Destroys the error message dialog.
274*/
275
276QErrorMessage::~QErrorMessage()
277{
278 if (this == qtMessageHandler) {
279 qtMessageHandler = nullptr;
280 QtMessageHandler currentMessagHandler = qInstallMessageHandler(nullptr);
281 if (currentMessagHandler != jump)
282 qInstallMessageHandler(currentMessagHandler);
283 else
284 qInstallMessageHandler(originalMessageHandler);
285 originalMessageHandler = nullptr;
286 }
287}
288
289
290/*! \reimp */
291
292void QErrorMessage::done(int a)
293{
294 Q_D(QErrorMessage);
295 if (!d->again->isChecked()) {
296 if (d->currentType.isEmpty()) {
297 if (!d->currentMessage.isEmpty())
298 d->doNotShow.insert(value: d->currentMessage);
299 } else {
300 d->doNotShowType.insert(value: d->currentType);
301 }
302 }
303 d->currentMessage.clear();
304 d->currentType.clear();
305
306 QDialog::done(a);
307
308 if (d->nextPending()) {
309 show();
310 } else {
311 if (this == qtMessageHandler && metFatal)
312 exit(status: 1);
313 }
314}
315
316
317/*!
318 Returns a pointer to a QErrorMessage object that outputs the
319 default Qt messages. This function creates such an object, if there
320 isn't one already.
321
322 The object will only output log messages of QLoggingCategory::defaultCategory().
323
324 The object will forward all messages to the original message handler.
325
326 \sa qInstallMessageHandler
327*/
328
329QErrorMessage * QErrorMessage::qtHandler()
330{
331 if (!qtMessageHandler) {
332 qtMessageHandler = new QErrorMessage(nullptr);
333 qAddPostRoutine(deleteStaticcQErrorMessage); // clean up
334 qtMessageHandler->setWindowTitle(QCoreApplication::applicationName());
335 originalMessageHandler = qInstallMessageHandler(jump);
336 }
337 return qtMessageHandler;
338}
339
340
341/*! \internal */
342
343bool QErrorMessagePrivate::isMessageToBeShown(const QString &message, const QString &type) const
344{
345 return !message.isEmpty()
346 && (type.isEmpty() ? !doNotShow.contains(value: message) : !doNotShowType.contains(value: type));
347}
348
349bool QErrorMessagePrivate::nextPending()
350{
351 while (!pending.empty()) {
352 QString message = std::move(pending.front().content);
353 QString type = std::move(pending.front().type);
354 pending.pop();
355 if (isMessageToBeShown(message, type)) {
356#ifndef QT_NO_TEXTHTMLPARSER
357 errors->setHtml(message);
358#else
359 errors->setPlainText(message);
360#endif
361 currentMessage = std::move(message);
362 currentType = std::move(type);
363 again->setChecked(true);
364 return true;
365 }
366 }
367 return false;
368}
369
370
371/*!
372 Shows the given message, \a message, and returns immediately. If the user
373 has requested for the message not to be shown again, this function does
374 nothing.
375
376 Normally, the message is displayed immediately. However, if there are
377 pending messages, it will be queued to be displayed later.
378*/
379
380void QErrorMessage::showMessage(const QString &message)
381{
382 showMessage(message, type: QString());
383}
384
385/*!
386 \overload
387
388 Shows the given message, \a message, and returns immediately. If the user
389 has requested for messages of type, \a type, not to be shown again, this
390 function does nothing.
391
392 Normally, the message is displayed immediately. However, if there are
393 pending messages, it will be queued to be displayed later.
394
395 \sa showMessage()
396*/
397
398void QErrorMessage::showMessage(const QString &message, const QString &type)
399{
400 Q_D(QErrorMessage);
401 if (!d->isMessageToBeShown(message, type))
402 return;
403 d->pending.push(x: {.content: message, .type: type});
404 if (!isVisible() && d->nextPending())
405 show();
406}
407
408void QErrorMessagePrivate::setVisible(bool visible)
409{
410 Q_Q(QErrorMessage);
411
412 if (canBeNativeDialog())
413 setNativeDialogVisible(visible);
414
415 // Update WA_DontShowOnScreen based on whether the native dialog was shown,
416 // so that QDialog::setVisible(visible) below updates the QWidget state correctly,
417 // but skips showing the non-native version.
418 q->setAttribute(Qt::WA_DontShowOnScreen, on: nativeDialogInUse);
419
420 QDialogPrivate::setVisible(visible);
421}
422
423/*!
424 \reimp
425*/
426void QErrorMessage::changeEvent(QEvent *e)
427{
428 Q_D(QErrorMessage);
429 if (e->type() == QEvent::LanguageChange) {
430 d->retranslateStrings();
431 }
432 QDialog::changeEvent(e);
433}
434
435void QErrorMessagePrivate::retranslateStrings()
436{
437 again->setText(QErrorMessage::tr(s: "&Show this message again"));
438 ok->setText(QErrorMessage::tr(s: "&OK"));
439}
440
441QT_END_NAMESPACE
442
443#include "moc_qerrormessage.cpp"
444

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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