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 "qsplashscreen.h"
41 
42 #include "qapplication.h"
43 #include "qdesktopwidget.h"
44 #include <private/qdesktopwidget_p.h>
45 #include "qpainter.h"
46 #include "qpixmap.h"
47 #include "qtextdocument.h"
48 #include "qtextcursor.h"
49 #include <QtGui/qscreen.h>
50 #include <QtGui/qwindow.h>
51 #include <QtCore/qdebug.h>
52 #include <QtCore/qelapsedtimer.h>
53 #include <private/qwidget_p.h>
54 
55 #ifdef Q_OS_WIN
56 #  include <QtCore/qt_windows.h>
57 #else
58 #  include <time.h>
59 #endif
60 
61 QT_BEGIN_NAMESPACE
62 
63 class QSplashScreenPrivate : public QWidgetPrivate
64 {
65     Q_DECLARE_PUBLIC(QSplashScreen)
66 public:
67     QPixmap pixmap;
68     QString currStatus;
69     QColor currColor;
70     int currAlign;
71 
72     inline QSplashScreenPrivate();
73 
74     void setPixmap(const QPixmap &p, const QScreen *screen = nullptr);
75 
76     static const QScreen *screenFor(const QWidget *w);
77 };
78 
79 /*!
80    \class QSplashScreen
81    \brief The QSplashScreen widget provides a splash screen that can
82    be shown during application startup.
83 
84    \inmodule QtWidgets
85 
86    A splash screen is a widget that is usually displayed when an
87    application is being started. Splash screens are often used for
88    applications that have long start up times (e.g. database or
89    networking applications that take time to establish connections) to
90    provide the user with feedback that the application is loading.
91 
92    The splash screen appears in the center of the screen. It may be
93    useful to add the Qt::WindowStaysOnTopHint to the splash widget's
94    window flags if you want to keep it above all the other windows on
95    the desktop.
96 
97    Some X11 window managers do not support the "stays on top" flag. A
98    solution is to set up a timer that periodically calls raise() on
99    the splash screen to simulate the "stays on top" effect.
100 
101    The most common usage is to show a splash screen before the main
102    widget is displayed on the screen. This is illustrated in the
103    following code snippet in which a splash screen is displayed and
104    some initialization tasks are performed before the application's
105    main window is shown:
106 
107    \snippet qsplashscreen/main.cpp 0
108    \dots
109    \snippet qsplashscreen/main.cpp 1
110 
111    The user can hide the splash screen by clicking on it with the
112    mouse. Since the splash screen is typically displayed before the
113    event loop has started running, it is necessary to periodically
114    call QCoreApplication::processEvents() to receive the mouse clicks.
115 
116    It is sometimes useful to update the splash screen with messages,
117    for example, announcing connections established or modules loaded
118    as the application starts up:
119 
120    \snippet code/src_gui_widgets_qsplashscreen.cpp 0
121 
122    QSplashScreen supports this with the showMessage() function. If you
123    wish to do your own drawing you can get a pointer to the pixmap
124    used in the splash screen with pixmap().  Alternatively, you can
125    subclass QSplashScreen and reimplement drawContents().
126 
127    In case of having multiple screens, it is also possible to show the
128    splash screen on a different screen than the primary one. For example:
129 
130    \snippet qsplashscreen/main.cpp 2
131 */
132 
133 /*!
134     Construct a splash screen that will display the \a pixmap.
135 
136     There should be no need to set the widget flags, \a f, except
137     perhaps Qt::WindowStaysOnTopHint.
138 */
QSplashScreen(const QPixmap & pixmap,Qt::WindowFlags f)139 QSplashScreen::QSplashScreen(const QPixmap &pixmap, Qt::WindowFlags f)
140     : QWidget(*(new QSplashScreenPrivate()), nullptr, Qt::SplashScreen | Qt::FramelessWindowHint | f)
141 {
142     setPixmap(pixmap);  // Does an implicit repaint
143 }
144 
145 /*!
146     \overload
147     \since 5.15
148 
149     This function allows you to specify the screen for your splashscreen. The
150     typical use for this constructor is if you have multiple screens and
151     prefer to have the splash screen on a different screen than your primary
152     one. In that case pass the proper \a screen.
153 */
QSplashScreen(QScreen * screen,const QPixmap & pixmap,Qt::WindowFlags f)154 QSplashScreen::QSplashScreen(QScreen *screen, const QPixmap &pixmap, Qt::WindowFlags f)
155     : QWidget(*(new QSplashScreenPrivate()), nullptr, Qt::SplashScreen | Qt::FramelessWindowHint | f)
156 {
157     d_func()->setPixmap(pixmap, screen);
158 }
159 
160 #if QT_DEPRECATED_SINCE(5, 15)
161 /*!
162     \overload
163     \obsolete Use the constructor taking a \c {QScreen *} instead
164 
165     This function allows you to specify a parent for your splashscreen. The
166     typical use for this constructor is if you have a multiple screens and
167     prefer to have the splash screen on a different screen than your primary
168     one. In that case pass the proper desktop() as the \a parent.
169 */
QSplashScreen(QWidget * parent,const QPixmap & pixmap,Qt::WindowFlags f)170 QSplashScreen::QSplashScreen(QWidget *parent, const QPixmap &pixmap, Qt::WindowFlags f)
171     : QWidget(*new QSplashScreenPrivate, parent, Qt::SplashScreen | Qt::FramelessWindowHint | f)
172 {
173     // Does an implicit repaint. Explicitly pass parent as QObject::parent()
174     // is still 0 here due to QWidget's special handling.
175     d_func()->setPixmap(pixmap, QSplashScreenPrivate::screenFor(parent));
176 }
177 #endif
178 
179 /*!
180   Destructor.
181 */
~QSplashScreen()182 QSplashScreen::~QSplashScreen()
183 {
184 }
185 
186 /*!
187     \reimp
188 */
mousePressEvent(QMouseEvent *)189 void QSplashScreen::mousePressEvent(QMouseEvent *)
190 {
191     hide();
192 }
193 
194 /*!
195     This overrides QWidget::repaint(). It differs from the standard repaint
196     function in that it also calls QCoreApplication::processEvents() to ensure
197     the updates are displayed, even when there is no event loop present.
198 */
repaint()199 void QSplashScreen::repaint()
200 {
201     QWidget::repaint();
202     QCoreApplication::processEvents();
203 }
204 
205 /*!
206     \fn QSplashScreen::messageChanged(const QString &message)
207 
208     This signal is emitted when the message on the splash screen
209     changes. \a message is the new message and is a null-string
210     when the message has been removed.
211 
212     \sa showMessage(), clearMessage()
213 */
214 
215 
216 
217 /*!
218     Draws the \a message text onto the splash screen with color \a
219     color and aligns the text according to the flags in \a alignment.
220     This function calls repaint() to make sure the splash screen is
221     repainted immediately. As a result the message is kept up
222     to date with what your application is doing (e.g. loading files).
223 
224     \sa Qt::Alignment, clearMessage(), message()
225 */
showMessage(const QString & message,int alignment,const QColor & color)226 void QSplashScreen::showMessage(const QString &message, int alignment,
227                                 const QColor &color)
228 {
229     Q_D(QSplashScreen);
230     d->currStatus = message;
231     d->currAlign = alignment;
232     d->currColor = color;
233     emit messageChanged(d->currStatus);
234     repaint();
235 }
236 
237 /*!
238     \since 5.2
239 
240     Returns the message that is currently displayed on the splash screen.
241 
242     \sa showMessage(), clearMessage()
243 */
244 
message() const245 QString QSplashScreen::message() const
246 {
247     Q_D(const QSplashScreen);
248     return d->currStatus;
249 }
250 
251 /*!
252     Removes the message being displayed on the splash screen
253 
254     \sa showMessage()
255  */
clearMessage()256 void QSplashScreen::clearMessage()
257 {
258     d_func()->currStatus.clear();
259     emit messageChanged(d_func()->currStatus);
260     repaint();
261 }
262 
263 // A copy of Qt Test's qWaitForWindowExposed() and qSleep().
waitForWindowExposed(QWindow * window,int timeout=1000)264 inline static bool waitForWindowExposed(QWindow *window, int timeout = 1000)
265 {
266     enum { TimeOutMs = 10 };
267     QElapsedTimer timer;
268     timer.start();
269     while (!window->isExposed()) {
270         const int remaining = timeout - int(timer.elapsed());
271         if (remaining <= 0)
272             break;
273         QCoreApplication::processEvents(QEventLoop::AllEvents, remaining);
274         QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
275 #if defined(Q_OS_WINRT)
276         WaitForSingleObjectEx(GetCurrentThread(), TimeOutMs, false);
277 #elif defined(Q_OS_WIN)
278         Sleep(uint(TimeOutMs));
279 #else
280         struct timespec ts = { TimeOutMs / 1000, (TimeOutMs % 1000) * 1000 * 1000 };
281         nanosleep(&ts, nullptr);
282 #endif
283     }
284     return window->isExposed();
285 }
286 
287 /*!
288     Makes the splash screen wait until the widget \a mainWin is displayed
289     before calling close() on itself.
290 */
291 
finish(QWidget * mainWin)292 void QSplashScreen::finish(QWidget *mainWin)
293 {
294     if (mainWin) {
295         if (!mainWin->windowHandle())
296             mainWin->createWinId();
297         waitForWindowExposed(mainWin->windowHandle());
298     }
299     close();
300 }
301 
302 /*!
303     Sets the pixmap that will be used as the splash screen's image to
304     \a pixmap.
305 */
setPixmap(const QPixmap & pixmap)306 void QSplashScreen::setPixmap(const QPixmap &pixmap)
307 {
308     d_func()->setPixmap(pixmap, QSplashScreenPrivate::screenFor(this));
309 }
310 
311 // In setPixmap(), resize and try to position on a screen according to:
312 // 1) If the screen for the given widget is available, use that
313 // 2) If a QDesktopScreenWidget is found in the parent hierarchy, use that (see docs on
314 //    QSplashScreen(QWidget *, QPixmap).
315 // 3) If a widget with associated QWindow is found, use that
316 // 4) When nothing can be found, try to center it over the cursor
317 
318 #if QT_DEPRECATED_SINCE(5, 15)
screenNumberOf(const QDesktopScreenWidget * dsw)319 static inline int screenNumberOf(const QDesktopScreenWidget *dsw)
320 {
321     auto desktopWidgetPrivate =
322         static_cast<QDesktopWidgetPrivate *>(qt_widget_private(QApplication::desktop()));
323     return desktopWidgetPrivate->screens.indexOf(const_cast<QDesktopScreenWidget *>(dsw));
324 }
325 #endif
326 
screenFor(const QWidget * w)327 const QScreen *QSplashScreenPrivate::screenFor(const QWidget *w)
328 {
329     if (w && w->screen())
330         return w->screen();
331 
332     for (const QWidget *p = w; p !=nullptr ; p = p->parentWidget()) {
333 #if QT_DEPRECATED_SINCE(5, 15)
334         if (auto dsw = qobject_cast<const QDesktopScreenWidget *>(p))
335             return QGuiApplication::screens().value(screenNumberOf(dsw));
336 #endif
337         if (QWindow *window = p->windowHandle())
338             return window->screen();
339     }
340 
341 #if QT_CONFIG(cursor)
342     // Note: We could rely on QPlatformWindow::initialGeometry() to center it
343     // over the cursor, but not all platforms (namely Android) use that.
344     if (QGuiApplication::screens().size() > 1) {
345         if (auto screenAtCursor = QGuiApplication::screenAt(QCursor::pos()))
346             return screenAtCursor;
347     }
348 #endif // cursor
349     return QGuiApplication::primaryScreen();
350 }
351 
setPixmap(const QPixmap & p,const QScreen * screen)352 void QSplashScreenPrivate::setPixmap(const QPixmap &p, const QScreen *screen)
353 {
354     Q_Q(QSplashScreen);
355 
356     pixmap = p;
357     q->setAttribute(Qt::WA_TranslucentBackground, pixmap.hasAlpha());
358 
359     QRect r(QPoint(), pixmap.size() / pixmap.devicePixelRatio());
360     q->resize(r.size());
361     if (screen)
362         q->move(screen->geometry().center() - r.center());
363     if (q->isVisible())
364         q->repaint();
365 }
366 
367 /*!
368     Returns the pixmap that is used in the splash screen. The image
369     does not have any of the text drawn by showMessage() calls.
370 */
pixmap() const371 const QPixmap QSplashScreen::pixmap() const
372 {
373     return d_func()->pixmap;
374 }
375 
376 /*!
377     \internal
378 */
QSplashScreenPrivate()379 inline QSplashScreenPrivate::QSplashScreenPrivate() : currAlign(Qt::AlignLeft)
380 {
381 }
382 
383 /*!
384     Draw the contents of the splash screen using painter \a painter.
385     The default implementation draws the message passed by showMessage().
386     Reimplement this function if you want to do your own drawing on
387     the splash screen.
388 */
drawContents(QPainter * painter)389 void QSplashScreen::drawContents(QPainter *painter)
390 {
391     Q_D(QSplashScreen);
392     painter->setPen(d->currColor);
393     QRect r = rect().adjusted(5, 5, -5, -5);
394     if (Qt::mightBeRichText(d->currStatus)) {
395         QTextDocument doc;
396 #ifdef QT_NO_TEXTHTMLPARSER
397         doc.setPlainText(d->currStatus);
398 #else
399         doc.setHtml(d->currStatus);
400 #endif
401         doc.setTextWidth(r.width());
402         QTextCursor cursor(&doc);
403         cursor.select(QTextCursor::Document);
404         QTextBlockFormat fmt;
405         fmt.setAlignment(Qt::Alignment(d->currAlign));
406         fmt.setLayoutDirection(layoutDirection());
407         cursor.mergeBlockFormat(fmt);
408         const QSizeF txtSize = doc.size();
409         if (d->currAlign & Qt::AlignBottom)
410             r.setTop(r.height() - txtSize.height());
411         else if (d->currAlign & Qt::AlignVCenter)
412             r.setTop(r.height() / 2 - txtSize.height() / 2);
413         painter->save();
414         painter->translate(r.topLeft());
415         doc.drawContents(painter);
416         painter->restore();
417     } else {
418         painter->drawText(r, d->currAlign, d->currStatus);
419     }
420 }
421 
422 /*! \reimp */
event(QEvent * e)423 bool QSplashScreen::event(QEvent *e)
424 {
425     if (e->type() == QEvent::Paint) {
426         Q_D(QSplashScreen);
427         QPainter painter(this);
428         painter.setRenderHints(QPainter::SmoothPixmapTransform);
429         painter.setLayoutDirection(layoutDirection());
430         if (!d->pixmap.isNull())
431             painter.drawPixmap(QPoint(), d->pixmap);
432         drawContents(&painter);
433     }
434     return QWidget::event(e);
435 }
436 
437 QT_END_NAMESPACE
438 
439 #include "moc_qsplashscreen.cpp"
440