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 "qapplication.h" |
41 | #include "qdesktopwidget.h" |
42 | #include "qeffects_p.h" |
43 | #include "qevent.h" |
44 | #include "qimage.h" |
45 | #include "qpainter.h" |
46 | #include "qscreen.h" |
47 | #include "qpixmap.h" |
48 | #include "qpointer.h" |
49 | #include "qtimer.h" |
50 | #include "qelapsedtimer.h" |
51 | #include "qdebug.h" |
52 | |
53 | #include <private/qdesktopwidget_p.h> |
54 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | static QWidget *effectParent(const QWidget* w) |
58 | { |
59 | const int screenNumber = w ? QGuiApplication::screens().indexOf(t: w->screen()) : 0; |
60 | QT_WARNING_PUSH // ### Qt 6: Find a replacement for QDesktopWidget::screen() |
61 | QT_WARNING_DISABLE_DEPRECATED |
62 | return QApplication::desktop()->screen(screen: screenNumber); |
63 | QT_WARNING_POP |
64 | } |
65 | |
66 | /* |
67 | Internal class QAlphaWidget. |
68 | |
69 | The QAlphaWidget is shown while the animation lasts |
70 | and displays the pixmap resulting from the alpha blending. |
71 | */ |
72 | |
73 | class QAlphaWidget: public QWidget, private QEffects |
74 | { |
75 | Q_OBJECT |
76 | public: |
77 | QAlphaWidget(QWidget* w, Qt::WindowFlags f = { }); |
78 | ~QAlphaWidget(); |
79 | |
80 | void run(int time); |
81 | |
82 | protected: |
83 | void paintEvent(QPaintEvent* e) override; |
84 | void closeEvent(QCloseEvent*) override; |
85 | void alphaBlend(); |
86 | bool eventFilter(QObject *, QEvent *) override; |
87 | |
88 | protected slots: |
89 | void render(); |
90 | |
91 | private: |
92 | QPixmap pm; |
93 | double alpha; |
94 | QImage backImage; |
95 | QImage frontImage; |
96 | QImage mixedImage; |
97 | QPointer<QWidget> widget; |
98 | int duration; |
99 | int elapsed; |
100 | bool showWidget; |
101 | QTimer anim; |
102 | QElapsedTimer checkTime; |
103 | }; |
104 | |
105 | static QAlphaWidget* q_blend = nullptr; |
106 | |
107 | /* |
108 | Constructs a QAlphaWidget. |
109 | */ |
110 | QAlphaWidget::QAlphaWidget(QWidget* w, Qt::WindowFlags f) |
111 | : QWidget(effectParent(w), f) |
112 | { |
113 | #ifndef Q_OS_WIN |
114 | setEnabled(false); |
115 | #endif |
116 | setAttribute(Qt::WA_NoSystemBackground, on: true); |
117 | widget = w; |
118 | alpha = 0; |
119 | } |
120 | |
121 | QAlphaWidget::~QAlphaWidget() |
122 | { |
123 | #if defined(Q_OS_WIN) |
124 | // Restore user-defined opacity value |
125 | if (widget) |
126 | widget->setWindowOpacity(1); |
127 | #endif |
128 | } |
129 | |
130 | /* |
131 | \reimp |
132 | */ |
133 | void QAlphaWidget::paintEvent(QPaintEvent*) |
134 | { |
135 | QPainter p(this); |
136 | p.drawPixmap(x: 0, y: 0, pm); |
137 | } |
138 | |
139 | /* |
140 | Starts the alphablending animation. |
141 | The animation will take about \a time ms |
142 | */ |
143 | void QAlphaWidget::run(int time) |
144 | { |
145 | duration = time; |
146 | |
147 | if (duration < 0) |
148 | duration = 150; |
149 | |
150 | if (!widget) |
151 | return; |
152 | |
153 | elapsed = 0; |
154 | checkTime.start(); |
155 | |
156 | showWidget = true; |
157 | #if defined(Q_OS_WIN) |
158 | qApp->installEventFilter(this); |
159 | widget->setWindowOpacity(0.0); |
160 | widget->show(); |
161 | connect(&anim, SIGNAL(timeout()), this, SLOT(render())); |
162 | anim.start(1); |
163 | #else |
164 | //This is roughly equivalent to calling setVisible(true) without actually showing the widget |
165 | widget->setAttribute(Qt::WA_WState_ExplicitShowHide, on: true); |
166 | widget->setAttribute(Qt::WA_WState_Hidden, on: false); |
167 | |
168 | qApp->installEventFilter(filterObj: this); |
169 | |
170 | move(ax: widget->geometry().x(),ay: widget->geometry().y()); |
171 | resize(w: widget->size().width(), h: widget->size().height()); |
172 | |
173 | frontImage = widget->grab().toImage(); |
174 | backImage = QGuiApplication::primaryScreen()->grabWindow(window: QApplication::desktop()->winId(), |
175 | x: widget->geometry().x(), y: widget->geometry().y(), |
176 | w: widget->geometry().width(), h: widget->geometry().height()).toImage(); |
177 | |
178 | if (!backImage.isNull() && checkTime.elapsed() < duration / 2) { |
179 | mixedImage = backImage.copy(); |
180 | pm = QPixmap::fromImage(image: mixedImage); |
181 | show(); |
182 | setEnabled(false); |
183 | |
184 | connect(sender: &anim, SIGNAL(timeout()), receiver: this, SLOT(render())); |
185 | anim.start(msec: 1); |
186 | } else { |
187 | duration = 0; |
188 | render(); |
189 | } |
190 | #endif |
191 | } |
192 | |
193 | /* |
194 | \reimp |
195 | */ |
196 | bool QAlphaWidget::eventFilter(QObject *o, QEvent *e) |
197 | { |
198 | switch (e->type()) { |
199 | case QEvent::Move: |
200 | if (o != widget) |
201 | break; |
202 | move(ax: widget->geometry().x(),ay: widget->geometry().y()); |
203 | update(); |
204 | break; |
205 | case QEvent::Hide: |
206 | case QEvent::Close: |
207 | if (o != widget) |
208 | break; |
209 | Q_FALLTHROUGH(); |
210 | case QEvent::MouseButtonPress: |
211 | case QEvent::MouseButtonDblClick: |
212 | showWidget = false; |
213 | render(); |
214 | break; |
215 | case QEvent::KeyPress: { |
216 | #ifndef QT_NO_SHORTCUT |
217 | QKeyEvent *ke = (QKeyEvent*)e; |
218 | if (ke->matches(key: QKeySequence::Cancel)) { |
219 | showWidget = false; |
220 | } else |
221 | #endif |
222 | { |
223 | duration = 0; |
224 | } |
225 | render(); |
226 | break; |
227 | } |
228 | default: |
229 | break; |
230 | } |
231 | return QWidget::eventFilter(watched: o, event: e); |
232 | } |
233 | |
234 | /* |
235 | \reimp |
236 | */ |
237 | void QAlphaWidget::closeEvent(QCloseEvent *e) |
238 | { |
239 | e->accept(); |
240 | if (!q_blend) |
241 | return; |
242 | |
243 | showWidget = false; |
244 | render(); |
245 | |
246 | QWidget::closeEvent(event: e); |
247 | } |
248 | |
249 | /* |
250 | Render alphablending for the time elapsed. |
251 | |
252 | Show the blended widget and free all allocated source |
253 | if the blending is finished. |
254 | */ |
255 | void QAlphaWidget::render() |
256 | { |
257 | int tempel = checkTime.elapsed(); |
258 | if (elapsed >= tempel) |
259 | elapsed++; |
260 | else |
261 | elapsed = tempel; |
262 | |
263 | if (duration != 0) |
264 | alpha = tempel / double(duration); |
265 | else |
266 | alpha = 1; |
267 | |
268 | #if defined(Q_OS_WIN) |
269 | if (alpha >= 1 || !showWidget) { |
270 | anim.stop(); |
271 | qApp->removeEventFilter(this); |
272 | widget->setWindowOpacity(1); |
273 | q_blend = 0; |
274 | deleteLater(); |
275 | } else { |
276 | widget->setWindowOpacity(alpha); |
277 | } |
278 | #else |
279 | if (alpha >= 1 || !showWidget) { |
280 | anim.stop(); |
281 | qApp->removeEventFilter(obj: this); |
282 | |
283 | if (widget) { |
284 | if (!showWidget) { |
285 | widget->hide(); |
286 | } else { |
287 | //Since we are faking the visibility of the widget |
288 | //we need to unset the hidden state on it before calling show |
289 | widget->setAttribute(Qt::WA_WState_Hidden, on: true); |
290 | widget->show(); |
291 | lower(); |
292 | } |
293 | } |
294 | q_blend = nullptr; |
295 | deleteLater(); |
296 | } else { |
297 | alphaBlend(); |
298 | pm = QPixmap::fromImage(image: mixedImage); |
299 | repaint(); |
300 | } |
301 | #endif // defined(Q_OS_WIN) |
302 | } |
303 | |
304 | /* |
305 | Calculate an alphablended image. |
306 | */ |
307 | void QAlphaWidget::alphaBlend() |
308 | { |
309 | const int a = qRound(d: alpha*256); |
310 | const int ia = 256 - a; |
311 | |
312 | const int sw = frontImage.width(); |
313 | const int sh = frontImage.height(); |
314 | const int bpl = frontImage.bytesPerLine(); |
315 | switch(frontImage.depth()) { |
316 | case 32: |
317 | { |
318 | uchar *mixed_data = mixedImage.bits(); |
319 | const uchar *back_data = backImage.bits(); |
320 | const uchar *front_data = frontImage.bits(); |
321 | |
322 | for (int sy = 0; sy < sh; sy++) { |
323 | quint32* mixed = (quint32*)mixed_data; |
324 | const quint32* back = (const quint32*)back_data; |
325 | const quint32* front = (const quint32*)front_data; |
326 | for (int sx = 0; sx < sw; sx++) { |
327 | quint32 bp = back[sx]; |
328 | quint32 fp = front[sx]; |
329 | |
330 | mixed[sx] = qRgb(r: (qRed(rgb: bp)*ia + qRed(rgb: fp)*a)>>8, |
331 | g: (qGreen(rgb: bp)*ia + qGreen(rgb: fp)*a)>>8, |
332 | b: (qBlue(rgb: bp)*ia + qBlue(rgb: fp)*a)>>8); |
333 | } |
334 | mixed_data += bpl; |
335 | back_data += bpl; |
336 | front_data += bpl; |
337 | } |
338 | } |
339 | default: |
340 | break; |
341 | } |
342 | } |
343 | |
344 | /* |
345 | Internal class QRollEffect |
346 | |
347 | The QRollEffect widget is shown while the animation lasts |
348 | and displays a scrolling pixmap. |
349 | */ |
350 | |
351 | class QRollEffect : public QWidget, private QEffects |
352 | { |
353 | Q_OBJECT |
354 | public: |
355 | QRollEffect(QWidget* w, Qt::WindowFlags f, DirFlags orient); |
356 | |
357 | void run(int time); |
358 | |
359 | protected: |
360 | void paintEvent(QPaintEvent*) override; |
361 | void closeEvent(QCloseEvent*) override; |
362 | |
363 | private slots: |
364 | void scroll(); |
365 | |
366 | private: |
367 | QPointer<QWidget> widget; |
368 | |
369 | int currentHeight; |
370 | int currentWidth; |
371 | int totalHeight; |
372 | int totalWidth; |
373 | |
374 | int duration; |
375 | int elapsed; |
376 | bool done; |
377 | bool showWidget; |
378 | int orientation; |
379 | |
380 | QTimer anim; |
381 | QElapsedTimer checkTime; |
382 | |
383 | QPixmap pm; |
384 | }; |
385 | |
386 | static QRollEffect* q_roll = nullptr; |
387 | |
388 | /* |
389 | Construct a QRollEffect widget. |
390 | */ |
391 | QRollEffect::QRollEffect(QWidget* w, Qt::WindowFlags f, DirFlags orient) |
392 | : QWidget(effectParent(w), f), orientation(orient) |
393 | { |
394 | #ifndef Q_OS_WIN |
395 | setEnabled(false); |
396 | #endif |
397 | |
398 | widget = w; |
399 | Q_ASSERT(widget); |
400 | |
401 | setAttribute(Qt::WA_NoSystemBackground, on: true); |
402 | |
403 | if (widget->testAttribute(attribute: Qt::WA_Resized)) { |
404 | totalWidth = widget->width(); |
405 | totalHeight = widget->height(); |
406 | } else { |
407 | totalWidth = widget->sizeHint().width(); |
408 | totalHeight = widget->sizeHint().height(); |
409 | } |
410 | |
411 | currentHeight = totalHeight; |
412 | currentWidth = totalWidth; |
413 | |
414 | if (orientation & (RightScroll|LeftScroll)) |
415 | currentWidth = 0; |
416 | if (orientation & (DownScroll|UpScroll)) |
417 | currentHeight = 0; |
418 | |
419 | pm = widget->grab(); |
420 | } |
421 | |
422 | /* |
423 | \reimp |
424 | */ |
425 | void QRollEffect::paintEvent(QPaintEvent*) |
426 | { |
427 | int x = orientation & RightScroll ? qMin(a: 0, b: currentWidth - totalWidth) : 0; |
428 | int y = orientation & DownScroll ? qMin(a: 0, b: currentHeight - totalHeight) : 0; |
429 | |
430 | QPainter p(this); |
431 | p.drawPixmap(x, y, pm); |
432 | } |
433 | |
434 | /* |
435 | \reimp |
436 | */ |
437 | void QRollEffect::closeEvent(QCloseEvent *e) |
438 | { |
439 | e->accept(); |
440 | if (done) |
441 | return; |
442 | |
443 | showWidget = false; |
444 | done = true; |
445 | scroll(); |
446 | |
447 | QWidget::closeEvent(event: e); |
448 | } |
449 | |
450 | /* |
451 | Start the animation. |
452 | |
453 | The animation will take about \a time ms, or is |
454 | calculated if \a time is negative |
455 | */ |
456 | void QRollEffect::run(int time) |
457 | { |
458 | if (!widget) |
459 | return; |
460 | |
461 | duration = time; |
462 | elapsed = 0; |
463 | |
464 | if (duration < 0) { |
465 | int dist = 0; |
466 | if (orientation & (RightScroll|LeftScroll)) |
467 | dist += totalWidth - currentWidth; |
468 | if (orientation & (DownScroll|UpScroll)) |
469 | dist += totalHeight - currentHeight; |
470 | duration = qMin(a: qMax(a: dist/3, b: 50), b: 120); |
471 | } |
472 | |
473 | connect(sender: &anim, SIGNAL(timeout()), receiver: this, SLOT(scroll())); |
474 | |
475 | move(ax: widget->geometry().x(),ay: widget->geometry().y()); |
476 | resize(w: qMin(a: currentWidth, b: totalWidth), h: qMin(a: currentHeight, b: totalHeight)); |
477 | |
478 | //This is roughly equivalent to calling setVisible(true) without actually showing the widget |
479 | widget->setAttribute(Qt::WA_WState_ExplicitShowHide, on: true); |
480 | widget->setAttribute(Qt::WA_WState_Hidden, on: false); |
481 | |
482 | show(); |
483 | setEnabled(false); |
484 | |
485 | showWidget = true; |
486 | done = false; |
487 | anim.start(msec: 1); |
488 | checkTime.start(); |
489 | } |
490 | |
491 | /* |
492 | Roll according to the time elapsed. |
493 | */ |
494 | void QRollEffect::scroll() |
495 | { |
496 | if (!done && widget) { |
497 | int tempel = checkTime.elapsed(); |
498 | if (elapsed >= tempel) |
499 | elapsed++; |
500 | else |
501 | elapsed = tempel; |
502 | |
503 | if (currentWidth != totalWidth) { |
504 | currentWidth = totalWidth * (elapsed/duration) |
505 | + (2 * totalWidth * (elapsed%duration) + duration) |
506 | / (2 * duration); |
507 | // equiv. to int((totalWidth*elapsed) / duration + 0.5) |
508 | done = (currentWidth >= totalWidth); |
509 | } |
510 | if (currentHeight != totalHeight) { |
511 | currentHeight = totalHeight * (elapsed/duration) |
512 | + (2 * totalHeight * (elapsed%duration) + duration) |
513 | / (2 * duration); |
514 | // equiv. to int((totalHeight*elapsed) / duration + 0.5) |
515 | done = (currentHeight >= totalHeight); |
516 | } |
517 | done = (currentHeight >= totalHeight) && |
518 | (currentWidth >= totalWidth); |
519 | |
520 | int w = totalWidth; |
521 | int h = totalHeight; |
522 | int x = widget->geometry().x(); |
523 | int y = widget->geometry().y(); |
524 | |
525 | if (orientation & RightScroll || orientation & LeftScroll) |
526 | w = qMin(a: currentWidth, b: totalWidth); |
527 | if (orientation & DownScroll || orientation & UpScroll) |
528 | h = qMin(a: currentHeight, b: totalHeight); |
529 | |
530 | setUpdatesEnabled(false); |
531 | if (orientation & UpScroll) |
532 | y = widget->geometry().y() + qMax(a: 0, b: totalHeight - currentHeight); |
533 | if (orientation & LeftScroll) |
534 | x = widget->geometry().x() + qMax(a: 0, b: totalWidth - currentWidth); |
535 | if (orientation & UpScroll || orientation & LeftScroll) |
536 | move(ax: x, ay: y); |
537 | |
538 | resize(w, h); |
539 | setUpdatesEnabled(true); |
540 | repaint(); |
541 | } |
542 | if (done || !widget) { |
543 | anim.stop(); |
544 | if (widget) { |
545 | if (!showWidget) { |
546 | #ifdef Q_OS_WIN |
547 | setEnabled(true); |
548 | setFocus(); |
549 | #endif |
550 | widget->hide(); |
551 | } else { |
552 | //Since we are faking the visibility of the widget |
553 | //we need to unset the hidden state on it before calling show |
554 | widget->setAttribute(Qt::WA_WState_Hidden, on: true); |
555 | widget->show(); |
556 | lower(); |
557 | } |
558 | } |
559 | q_roll = nullptr; |
560 | deleteLater(); |
561 | } |
562 | } |
563 | |
564 | /*! |
565 | Scroll widget \a w in \a time ms. \a orient may be 1 (vertical), 2 |
566 | (horizontal) or 3 (diagonal). |
567 | */ |
568 | void qScrollEffect(QWidget* w, QEffects::DirFlags orient, int time) |
569 | { |
570 | if (q_roll) { |
571 | q_roll->deleteLater(); |
572 | q_roll = nullptr; |
573 | } |
574 | |
575 | if (!w) |
576 | return; |
577 | |
578 | QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Move); |
579 | QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Resize); |
580 | Qt::WindowFlags flags = Qt::ToolTip; |
581 | |
582 | // those can be popups - they would steal the focus, but are disabled |
583 | q_roll = new QRollEffect(w, flags, orient); |
584 | q_roll->run(time); |
585 | } |
586 | |
587 | /*! |
588 | Fade in widget \a w in \a time ms. |
589 | */ |
590 | void qFadeEffect(QWidget* w, int time) |
591 | { |
592 | if (q_blend) { |
593 | q_blend->deleteLater(); |
594 | q_blend = nullptr; |
595 | } |
596 | |
597 | if (!w) |
598 | return; |
599 | |
600 | QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Move); |
601 | QCoreApplication::sendPostedEvents(receiver: w, event_type: QEvent::Resize); |
602 | |
603 | Qt::WindowFlags flags = Qt::ToolTip; |
604 | |
605 | // those can be popups - they would steal the focus, but are disabled |
606 | q_blend = new QAlphaWidget(w, flags); |
607 | |
608 | q_blend->run(time); |
609 | } |
610 | |
611 | QT_END_NAMESPACE |
612 | |
613 | /* |
614 | Delete this after timeout |
615 | */ |
616 | |
617 | #include "qeffects.moc" |
618 | |