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:
QErrorMessageTextView(QWidget * parent)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 
minimumSizeHint() const103 QSize QErrorMessageTextView::minimumSizeHint() const
104 {
105     return QSize(50, 50);
106 }
107 
sizeHint() const108 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 
deleteStaticcQErrorMessage()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 
msgType2i18nString(QtMsgType t)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("QErrorMessage", messages[t]);
184 }
185 
jump(QtMsgType t,const QMessageLogContext &,const QString & m)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(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 
QErrorMessage(QWidget * parent)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(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 
~QErrorMessage()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 
done(int a)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(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 
qtHandler()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 
isMessageToBeShown(const QString & message,const QString & type) const307 bool 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 
nextPending()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 
showMessage(const QString & message)343 void 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 
showMessage(const QString & message,const QString & type)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({message, type});
368     if (!isVisible() && d->nextPending())
369         show();
370 }
371 
372 /*!
373     \reimp
374 */
changeEvent(QEvent * e)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 
retranslateStrings()384 void QErrorMessagePrivate::retranslateStrings()
385 {
386     again->setText(QErrorMessage::tr("&Show this message again"));
387     ok->setText(QErrorMessage::tr("&OK"));
388 }
389 
390 QT_END_NAMESPACE
391 
392 #include "moc_qerrormessage.cpp"
393