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

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