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 | */ |
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 | */ |
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(p: 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 | */ |
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(p: pixmap, screen: QSplashScreenPrivate::screenFor(w: parent)); |
176 | } |
177 | #endif |
178 | |
179 | /*! |
180 | Destructor. |
181 | */ |
182 | QSplashScreen::~QSplashScreen() |
183 | { |
184 | } |
185 | |
186 | /*! |
187 | \reimp |
188 | */ |
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 | */ |
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 | */ |
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(message: 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 | |
245 | 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 | */ |
256 | void QSplashScreen::clearMessage() |
257 | { |
258 | d_func()->currStatus.clear(); |
259 | emit messageChanged(message: d_func()->currStatus); |
260 | repaint(); |
261 | } |
262 | |
263 | // A copy of Qt Test's qWaitForWindowExposed() and qSleep(). |
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(flags: QEventLoop::AllEvents, maxtime: remaining); |
274 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: 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 = { .tv_sec: TimeOutMs / 1000, .tv_nsec: (TimeOutMs % 1000) * 1000 * 1000 }; |
281 | nanosleep(requested_time: &ts, remaining: 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 | |
292 | void QSplashScreen::finish(QWidget *mainWin) |
293 | { |
294 | if (mainWin) { |
295 | if (!mainWin->windowHandle()) |
296 | mainWin->createWinId(); |
297 | waitForWindowExposed(window: 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 | */ |
306 | void QSplashScreen::setPixmap(const QPixmap &pixmap) |
307 | { |
308 | d_func()->setPixmap(p: pixmap, screen: QSplashScreenPrivate::screenFor(w: 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) |
319 | static inline int screenNumberOf(const QDesktopScreenWidget *dsw) |
320 | { |
321 | auto desktopWidgetPrivate = |
322 | static_cast<QDesktopWidgetPrivate *>(qt_widget_private(widget: QApplication::desktop())); |
323 | return desktopWidgetPrivate->screens.indexOf(t: const_cast<QDesktopScreenWidget *>(dsw)); |
324 | } |
325 | #endif |
326 | |
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 *>(object: p)) |
335 | return QGuiApplication::screens().value(i: 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(point: QCursor::pos())) |
346 | return screenAtCursor; |
347 | } |
348 | #endif // cursor |
349 | return QGuiApplication::primaryScreen(); |
350 | } |
351 | |
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, on: 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 | */ |
371 | const QPixmap QSplashScreen::pixmap() const |
372 | { |
373 | return d_func()->pixmap; |
374 | } |
375 | |
376 | /*! |
377 | \internal |
378 | */ |
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 | */ |
389 | void QSplashScreen::drawContents(QPainter *painter) |
390 | { |
391 | Q_D(QSplashScreen); |
392 | painter->setPen(d->currColor); |
393 | QRect r = rect().adjusted(xp1: 5, yp1: 5, xp2: -5, yp2: -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(selection: QTextCursor::Document); |
404 | QTextBlockFormat fmt; |
405 | fmt.setAlignment(Qt::Alignment(d->currAlign)); |
406 | fmt.setLayoutDirection(layoutDirection()); |
407 | cursor.mergeBlockFormat(modifier: 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(offset: r.topLeft()); |
415 | doc.drawContents(painter); |
416 | painter->restore(); |
417 | } else { |
418 | painter->drawText(r, flags: d->currAlign, text: d->currStatus); |
419 | } |
420 | } |
421 | |
422 | /*! \reimp */ |
423 | bool QSplashScreen::event(QEvent *e) |
424 | { |
425 | if (e->type() == QEvent::Paint) { |
426 | Q_D(QSplashScreen); |
427 | QPainter painter(this); |
428 | painter.setRenderHints(hints: QPainter::SmoothPixmapTransform); |
429 | painter.setLayoutDirection(layoutDirection()); |
430 | if (!d->pixmap.isNull()) |
431 | painter.drawPixmap(p: QPoint(), pm: d->pixmap); |
432 | drawContents(painter: &painter); |
433 | } |
434 | return QWidget::event(event: e); |
435 | } |
436 | |
437 | QT_END_NAMESPACE |
438 | |
439 | #include "moc_qsplashscreen.cpp" |
440 | |