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 
effectParent(const QWidget * w)57 static QWidget *effectParent(const QWidget* w)
58 {
59     const int screenNumber = w ? QGuiApplication::screens().indexOf(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(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 */
QAlphaWidget(QWidget * w,Qt::WindowFlags f)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, true);
117     widget = w;
118     alpha = 0;
119 }
120 
~QAlphaWidget()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 */
paintEvent(QPaintEvent *)133 void QAlphaWidget::paintEvent(QPaintEvent*)
134 {
135     QPainter p(this);
136     p.drawPixmap(0, 0, pm);
137 }
138 
139 /*
140   Starts the alphablending animation.
141   The animation will take about \a time ms
142 */
run(int time)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, true);
166     widget->setAttribute(Qt::WA_WState_Hidden, false);
167 
168     qApp->installEventFilter(this);
169 
170     move(widget->geometry().x(),widget->geometry().y());
171     resize(widget->size().width(), widget->size().height());
172 
173     frontImage = widget->grab().toImage();
174     backImage = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId(),
175                                 widget->geometry().x(), widget->geometry().y(),
176                                 widget->geometry().width(), widget->geometry().height()).toImage();
177 
178     if (!backImage.isNull() && checkTime.elapsed() < duration / 2) {
179         mixedImage = backImage.copy();
180         pm = QPixmap::fromImage(mixedImage);
181         show();
182         setEnabled(false);
183 
184         connect(&anim, SIGNAL(timeout()), this, SLOT(render()));
185         anim.start(1);
186     } else {
187        duration = 0;
188        render();
189     }
190 #endif
191 }
192 
193 /*
194   \reimp
195 */
eventFilter(QObject * o,QEvent * e)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(widget->geometry().x(),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(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(o, e);
232 }
233 
234 /*
235   \reimp
236 */
closeEvent(QCloseEvent * e)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(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 */
render()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(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, true);
290                 widget->show();
291                 lower();
292             }
293         }
294         q_blend = nullptr;
295         deleteLater();
296     } else {
297         alphaBlend();
298         pm = QPixmap::fromImage(mixedImage);
299         repaint();
300     }
301 #endif // defined(Q_OS_WIN)
302 }
303 
304 /*
305   Calculate an alphablended image.
306 */
alphaBlend()307 void QAlphaWidget::alphaBlend()
308 {
309     const int a = qRound(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((qRed(bp)*ia + qRed(fp)*a)>>8,
331                                       (qGreen(bp)*ia + qGreen(fp)*a)>>8,
332                                       (qBlue(bp)*ia + qBlue(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 */
QRollEffect(QWidget * w,Qt::WindowFlags f,DirFlags orient)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, true);
402 
403     if (widget->testAttribute(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 */
paintEvent(QPaintEvent *)425 void QRollEffect::paintEvent(QPaintEvent*)
426 {
427     int x = orientation & RightScroll ? qMin(0, currentWidth - totalWidth) : 0;
428     int y = orientation & DownScroll ? qMin(0, currentHeight - totalHeight) : 0;
429 
430     QPainter p(this);
431     p.drawPixmap(x, y, pm);
432 }
433 
434 /*
435   \reimp
436 */
closeEvent(QCloseEvent * e)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(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 */
run(int time)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(qMax(dist/3, 50), 120);
471     }
472 
473     connect(&anim, SIGNAL(timeout()), this, SLOT(scroll()));
474 
475     move(widget->geometry().x(),widget->geometry().y());
476     resize(qMin(currentWidth, totalWidth), qMin(currentHeight, totalHeight));
477 
478     //This is roughly equivalent to calling setVisible(true) without actually showing the widget
479     widget->setAttribute(Qt::WA_WState_ExplicitShowHide, true);
480     widget->setAttribute(Qt::WA_WState_Hidden, false);
481 
482     show();
483     setEnabled(false);
484 
485     showWidget = true;
486     done = false;
487     anim.start(1);
488     checkTime.start();
489 }
490 
491 /*
492   Roll according to the time elapsed.
493 */
scroll()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(currentWidth, totalWidth);
527         if (orientation & DownScroll || orientation & UpScroll)
528             h = qMin(currentHeight, totalHeight);
529 
530         setUpdatesEnabled(false);
531         if (orientation & UpScroll)
532             y = widget->geometry().y() + qMax(0, totalHeight - currentHeight);
533         if (orientation & LeftScroll)
534             x = widget->geometry().x() + qMax(0, totalWidth - currentWidth);
535         if (orientation & UpScroll || orientation & LeftScroll)
536             move(x, 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, 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 */
qScrollEffect(QWidget * w,QEffects::DirFlags orient,int time)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(w, QEvent::Move);
579     QCoreApplication::sendPostedEvents(w, 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 */
qFadeEffect(QWidget * w,int time)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(w, QEvent::Move);
601     QCoreApplication::sendPostedEvents(w, 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