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 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | class QSplashScreenPrivate : public QWidgetPrivate |
26 | { |
27 | Q_DECLARE_PUBLIC(QSplashScreen) |
28 | public: |
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 | */ |
97 | QSplashScreen::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 | */ |
112 | QSplashScreen::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 | */ |
123 | QSplashScreen::~QSplashScreen() |
124 | { |
125 | } |
126 | |
127 | /*! |
128 | \reimp |
129 | */ |
130 | void 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 | */ |
140 | void 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 | */ |
167 | void 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 | |
186 | QString 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 | */ |
197 | void 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(). |
205 | inline 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 | |
231 | void 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 | */ |
245 | void 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 | */ |
263 | const QPixmap QSplashScreen::pixmap() const |
264 | { |
265 | return d_func()->pixmap; |
266 | } |
267 | |
268 | /*! |
269 | \internal |
270 | */ |
271 | inline 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 | */ |
281 | void 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 */ |
315 | bool 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 | |
329 | QT_END_NAMESPACE |
330 | |
331 | #include "moc_qsplashscreen.cpp" |
332 | |