1// Copyright (C) 2020 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 "qsplashscreen.h"
5
6#include "qapplication.h"
7#include "qpainter.h"
8#include "qpixmap.h"
9#include "qtextdocument.h"
10#include "qtextcursor.h"
11#include <QtGui/qscreen.h>
12#include <QtGui/qwindow.h>
13#include <QtCore/qdebug.h>
14#include <QtCore/qelapsedtimer.h>
15#include <private/qwidget_p.h>
16
17#ifdef Q_OS_WIN
18# include <QtCore/qt_windows.h>
19#else
20# include <time.h>
21#endif
22
23QT_BEGIN_NAMESPACE
24
25class QSplashScreenPrivate : public QWidgetPrivate
26{
27 Q_DECLARE_PUBLIC(QSplashScreen)
28public:
29 QPixmap pixmap;
30 QString currStatus;
31 QColor currColor;
32 int currAlign;
33
34 inline QSplashScreenPrivate();
35};
36
37/*!
38 \class QSplashScreen
39 \brief The QSplashScreen widget provides a splash screen that can
40 be shown during application startup.
41
42 \inmodule QtWidgets
43
44 A splash screen is a widget that is usually displayed when an
45 application is being started. Splash screens are often used for
46 applications that have long start up times (e.g. database or
47 networking applications that take time to establish connections) to
48 provide the user with feedback that the application is loading.
49
50 The splash screen appears in the center of the screen. It may be
51 useful to add the Qt::WindowStaysOnTopHint to the splash widget's
52 window flags if you want to keep it above all the other windows on
53 the desktop.
54
55 Some X11 window managers do not support the "stays on top" flag. A
56 solution is to set up a timer that periodically calls raise() on
57 the splash screen to simulate the "stays on top" effect.
58
59 The most common usage is to show a splash screen before the main
60 widget is displayed on the screen. This is illustrated in the
61 following code snippet in which a splash screen is displayed and
62 some initialization tasks are performed before the application's
63 main window is shown:
64
65 \snippet qsplashscreen/main.cpp 0
66 \dots
67 \snippet qsplashscreen/main.cpp 1
68
69 The user can hide the splash screen by clicking on it with the
70 mouse. Since the splash screen is typically displayed before the
71 event loop has started running, it is necessary to periodically
72 call QCoreApplication::processEvents() to receive the mouse clicks.
73
74 It is sometimes useful to update the splash screen with messages,
75 for example, announcing connections established or modules loaded
76 as the application starts up:
77
78 \snippet code/src_gui_widgets_qsplashscreen.cpp 0
79
80 QSplashScreen supports this with the showMessage() function. If you
81 wish to do your own drawing you can get a pointer to the pixmap
82 used in the splash screen with pixmap(). Alternatively, you can
83 subclass QSplashScreen and reimplement drawContents().
84
85 In case of having multiple screens, it is also possible to show the
86 splash screen on a different screen than the primary one. For example:
87
88 \snippet qsplashscreen/main.cpp 2
89*/
90
91/*!
92 Construct a splash screen that will display the \a pixmap.
93
94 There should be no need to set the widget flags, \a f, except
95 perhaps Qt::WindowStaysOnTopHint.
96*/
97QSplashScreen::QSplashScreen(const QPixmap &pixmap, Qt::WindowFlags f)
98 : QWidget(*(new QSplashScreenPrivate()), nullptr, Qt::SplashScreen | Qt::FramelessWindowHint | f)
99{
100 setPixmap(pixmap); // Does an implicit repaint
101}
102
103/*!
104 \overload
105 \since 5.15
106
107 This function allows you to specify the screen for your splashscreen. The
108 typical use for this constructor is if you have multiple screens and
109 prefer to have the splash screen on a different screen than your primary
110 one. In that case pass the proper \a screen.
111*/
112QSplashScreen::QSplashScreen(QScreen *screen, const QPixmap &pixmap, Qt::WindowFlags f)
113 : QWidget(*(new QSplashScreenPrivate()), nullptr, Qt::SplashScreen | Qt::FramelessWindowHint | f)
114{
115 Q_D(QSplashScreen);
116 d->setScreen(screen);
117 setPixmap(pixmap);
118}
119
120/*!
121 Destructor.
122*/
123QSplashScreen::~QSplashScreen()
124{
125}
126
127/*!
128 \reimp
129*/
130void QSplashScreen::mousePressEvent(QMouseEvent *)
131{
132 hide();
133}
134
135/*!
136 This overrides QWidget::repaint(). It differs from the standard repaint
137 function in that it also calls QCoreApplication::processEvents() to ensure
138 the updates are displayed, even when there is no event loop present.
139*/
140void QSplashScreen::repaint()
141{
142 QWidget::repaint();
143 QCoreApplication::processEvents();
144}
145
146/*!
147 \fn QSplashScreen::messageChanged(const QString &message)
148
149 This signal is emitted when the message on the splash screen
150 changes. \a message is the new message and is a null-string
151 when the message has been removed.
152
153 \sa showMessage(), clearMessage()
154*/
155
156
157
158/*!
159 Draws the \a message text onto the splash screen with color \a
160 color and aligns the text according to the flags in \a alignment.
161 This function calls repaint() to make sure the splash screen is
162 repainted immediately. As a result the message is kept up
163 to date with what your application is doing (e.g. loading files).
164
165 \sa Qt::Alignment, clearMessage(), message()
166*/
167void QSplashScreen::showMessage(const QString &message, int alignment,
168 const QColor &color)
169{
170 Q_D(QSplashScreen);
171 d->currStatus = message;
172 d->currAlign = alignment;
173 d->currColor = color;
174 emit messageChanged(message: d->currStatus);
175 repaint();
176}
177
178/*!
179 \since 5.2
180
181 Returns the message that is currently displayed on the splash screen.
182
183 \sa showMessage(), clearMessage()
184*/
185
186QString QSplashScreen::message() const
187{
188 Q_D(const QSplashScreen);
189 return d->currStatus;
190}
191
192/*!
193 Removes the message being displayed on the splash screen
194
195 \sa showMessage()
196 */
197void QSplashScreen::clearMessage()
198{
199 d_func()->currStatus.clear();
200 emit messageChanged(message: d_func()->currStatus);
201 repaint();
202}
203
204// A copy of Qt Test's qWaitForWindowExposed() and qSleep().
205inline static bool waitForWindowExposed(QWindow *window, int timeout = 1000)
206{
207 enum { TimeOutMs = 10 };
208 QElapsedTimer timer;
209 timer.start();
210 while (!window->isExposed()) {
211 const int remaining = timeout - int(timer.elapsed());
212 if (remaining <= 0)
213 break;
214 QCoreApplication::processEvents(flags: QEventLoop::AllEvents, maxtime: remaining);
215 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
216#if defined(Q_OS_WIN)
217 Sleep(uint(TimeOutMs));
218#else
219 struct timespec ts = { .tv_sec: TimeOutMs / 1000, .tv_nsec: (TimeOutMs % 1000) * 1000 * 1000 };
220 nanosleep(requested_time: &ts, remaining: nullptr);
221#endif
222 }
223 return window->isExposed();
224}
225
226/*!
227 Makes the splash screen wait until the widget \a mainWin is displayed
228 before calling close() on itself.
229*/
230
231void QSplashScreen::finish(QWidget *mainWin)
232{
233 if (mainWin) {
234 if (!mainWin->windowHandle())
235 mainWin->createWinId();
236 waitForWindowExposed(window: mainWin->windowHandle());
237 }
238 close();
239}
240
241/*!
242 Sets the pixmap that will be used as the splash screen's image to
243 \a pixmap.
244*/
245void QSplashScreen::setPixmap(const QPixmap &pixmap)
246{
247 Q_D(QSplashScreen);
248 d->pixmap = pixmap;
249 setAttribute(Qt::WA_TranslucentBackground, on: pixmap.hasAlpha());
250
251 const QRect r(QPoint(), pixmap.deviceIndependentSize().toSize());
252 resize(r.size());
253
254 move(screen()->geometry().center() - r.center());
255 if (isVisible())
256 repaint();
257}
258
259/*!
260 Returns the pixmap that is used in the splash screen. The image
261 does not have any of the text drawn by showMessage() calls.
262*/
263const QPixmap QSplashScreen::pixmap() const
264{
265 return d_func()->pixmap;
266}
267
268/*!
269 \internal
270*/
271inline QSplashScreenPrivate::QSplashScreenPrivate() : currAlign(Qt::AlignLeft)
272{
273}
274
275/*!
276 Draw the contents of the splash screen using painter \a painter.
277 The default implementation draws the message passed by showMessage().
278 Reimplement this function if you want to do your own drawing on
279 the splash screen.
280*/
281void QSplashScreen::drawContents(QPainter *painter)
282{
283 Q_D(QSplashScreen);
284 painter->setPen(d->currColor);
285 QRect r = rect().adjusted(xp1: 5, yp1: 5, xp2: -5, yp2: -5);
286 if (Qt::mightBeRichText(d->currStatus)) {
287 QTextDocument doc;
288#ifdef QT_NO_TEXTHTMLPARSER
289 doc.setPlainText(d->currStatus);
290#else
291 doc.setHtml(d->currStatus);
292#endif
293 doc.setTextWidth(r.width());
294 QTextCursor cursor(&doc);
295 cursor.select(selection: QTextCursor::Document);
296 QTextBlockFormat fmt;
297 fmt.setAlignment(Qt::Alignment(d->currAlign));
298 fmt.setLayoutDirection(layoutDirection());
299 cursor.mergeBlockFormat(modifier: fmt);
300 const QSizeF txtSize = doc.size();
301 if (d->currAlign & Qt::AlignBottom)
302 r.setTop(r.height() - txtSize.height());
303 else if (d->currAlign & Qt::AlignVCenter)
304 r.setTop(r.height() / 2 - txtSize.height() / 2);
305 painter->save();
306 painter->translate(offset: r.topLeft());
307 doc.drawContents(painter);
308 painter->restore();
309 } else {
310 painter->drawText(r, flags: d->currAlign, text: d->currStatus);
311 }
312}
313
314/*! \reimp */
315bool QSplashScreen::event(QEvent *e)
316{
317 if (e->type() == QEvent::Paint) {
318 Q_D(QSplashScreen);
319 QPainter painter(this);
320 painter.setRenderHints(hints: QPainter::SmoothPixmapTransform);
321 painter.setLayoutDirection(layoutDirection());
322 if (!d->pixmap.isNull())
323 painter.drawPixmap(p: QPoint(), pm: d->pixmap);
324 drawContents(painter: &painter);
325 }
326 return QWidget::event(event: e);
327}
328
329QT_END_NAMESPACE
330
331#include "moc_qsplashscreen.cpp"
332

source code of qtbase/src/widgets/widgets/qsplashscreen.cpp