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