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
63QT_BEGIN_NAMESPACE
64
65namespace {
66struct Message {
67 QString content;
68 QString type;
69};
70}
71
72class QErrorMessagePrivate : public QDialogPrivate
73{
74 Q_DECLARE_PUBLIC(QErrorMessage)
75public:
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
91namespace {
92class QErrorMessageTextView : public QTextEdit
93{
94public:
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
103QSize QErrorMessageTextView::minimumSizeHint() const
104{
105 return QSize(50, 50);
106}
107
108QSize 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
152static QErrorMessage * qtMessageHandler = nullptr;
153
154static void deleteStaticcQErrorMessage() // post-routine
155{
156 if (qtMessageHandler) {
157 delete qtMessageHandler;
158 qtMessageHandler = nullptr;
159 }
160}
161
162static bool metFatal = false;
163
164static QString msgType2i18nString(QtMsgType t)
165{
166 static_assert(QtDebugMsg == 0);
167 static_assert(QtWarningMsg == 1);
168 static_assert(QtCriticalMsg == 2);
169 static_assert(QtFatalMsg == 3);
170 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("QErrorMessage", messages[t]);
184}
185
186static 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(m, Qt::WhiteSpaceNormal);
193
194 // ### work around text engine quirk
195 if (rich.endsWith(QLatin1String("</p>")))
196 rich.chop(4);
197
198 if (!metFatal) {
199 if (QThread::currentThread() == qApp->thread()) {
200 qtMessageHandler->showMessage(rich);
201 } else {
202 QMetaObject::invokeMethod(qtMessageHandler,
203 "showMessage",
204 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
217QErrorMessage::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(d->ok, SIGNAL(clicked()), this, SLOT(accept()));
229
230 grid->addWidget(d->icon, 0, 0, Qt::AlignTop);
231 grid->addWidget(d->errors, 0, 1);
232 grid->addWidget(d->again, 1, 1, Qt::AlignTop);
233 grid->addWidget(d->ok, 2, 0, 1, 2, Qt::AlignCenter);
234 grid->setColumnStretch(1, 42);
235 grid->setRowStretch(0, 42);
236
237#if QT_CONFIG(messagebox)
238 d->icon->setPixmap(QMessageBox::standardIcon(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
252QErrorMessage::~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
266void 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(d->currentMessage);
273 } else {
274 d->doNotShowType.insert(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(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
293QErrorMessage * 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
307bool QErrorMessagePrivate::isMessageToBeShown(const QString &message, const QString &type) const
308{
309 return !message.isEmpty()
310 && (type.isEmpty() ? !doNotShow.contains(message) : !doNotShowType.contains(type));
311}
312
313bool 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
343void QErrorMessage::showMessage(const QString &message)
344{
345 showMessage(message, 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
362void 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({message, type});
368 if (!isVisible() && d->nextPending())
369 show();
370}
371
372/*!
373 \reimp
374*/
375void 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
384void QErrorMessagePrivate::retranslateStrings()
385{
386 again->setText(QErrorMessage::tr("&Show this message again"));
387 ok->setText(QErrorMessage::tr("&OK"));
388}
389
390QT_END_NAMESPACE
391
392#include "moc_qerrormessage.cpp"
393