]> git.sur5r.net Git - minitube/blob - src/flickcharm.cpp
Imported Upstream version 1.0
[minitube] / src / flickcharm.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: Qt Software Information (qt-info@nokia.com)
5 **
6 ** This file is part of the Graphics Dojo project on Qt Labs.
7 **
8 ** This file may be used under the terms of the GNU General Public
9 ** License version 2.0 or 3.0 as published by the Free Software Foundation
10 ** and appearing in the file LICENSE.GPL included in the packaging of
11 ** this file.  Please review the following information to ensure GNU
12 ** General Public Licensing requirements will be met:
13 ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
14 ** http://www.gnu.org/copyleft/gpl.html.
15 **
16 ** If you are unsure which license is appropriate for your use, please
17 ** contact the sales department at qt-sales@nokia.com.
18 **
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 **
22 ****************************************************************************/
23
24 #include "flickcharm.h"
25
26 #include <QAbstractScrollArea>
27 #include <QApplication>
28 #include <QBasicTimer>
29 #include <QEvent>
30 #include <QHash>
31 #include <QList>
32 #include <QMouseEvent>
33 #include <QScrollBar>
34
35 #include <QDebug>
36
37 struct FlickData {
38     typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State;
39     State state;
40     QWidget *widget;
41     QPoint pressPos;
42     QPoint offset;
43     QPoint dragPos;
44     QPoint speed;
45     QList<QEvent*> ignored;
46 };
47
48 class FlickCharmPrivate
49 {
50 public:
51     QHash<QWidget*, FlickData*> flickData;
52     QBasicTimer ticker;
53 };
54
55 FlickCharm::FlickCharm(QObject *parent): QObject(parent)
56 {
57     d = new FlickCharmPrivate;
58 }
59
60 FlickCharm::~FlickCharm()
61 {
62     delete d;
63 }
64
65 void FlickCharm::activateOn(QWidget *widget)
66 {
67     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
68     if (scrollArea) {
69         scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
70         scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
71
72         QWidget *viewport = scrollArea->viewport();
73
74         viewport->installEventFilter(this);
75         scrollArea->installEventFilter(this);
76
77         d->flickData.remove(viewport);
78         d->flickData[viewport] = new FlickData;
79         d->flickData[viewport]->widget = widget;
80         d->flickData[viewport]->state = FlickData::Steady;
81
82         return;
83     }
84
85     qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
86 }
87
88 void FlickCharm::deactivateFrom(QWidget *widget)
89 {
90     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
91     if (scrollArea) {
92         QWidget *viewport = scrollArea->viewport();
93
94         viewport->removeEventFilter(this);
95         scrollArea->removeEventFilter(this);
96
97         delete d->flickData[viewport];
98         d->flickData.remove(viewport);
99
100         return;
101     }
102 }
103
104 static QPoint scrollOffset(QWidget *widget)
105 {
106     int x = 0, y = 0;
107
108     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
109     if (scrollArea) {
110         x = scrollArea->horizontalScrollBar()->value();
111         y = scrollArea->verticalScrollBar()->value();
112     }
113
114     return QPoint(x, y);
115 }
116
117 static void setScrollOffset(QWidget *widget, const QPoint &p)
118 {
119     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
120     if (scrollArea) {
121         scrollArea->horizontalScrollBar()->setValue(p.x());
122         scrollArea->verticalScrollBar()->setValue(p.y());
123     }
124 }
125
126 static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)
127 {
128     int x = qBound(-max, speed.x(), max);
129     int y = qBound(-max, speed.y(), max);
130     x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a);
131     y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a);
132     return QPoint(x, y);
133 }
134
135 bool FlickCharm::eventFilter(QObject *object, QEvent *event)
136 {
137
138     if (!object->isWidgetType())
139         return false;
140
141     QEvent::Type type = event->type();
142     if (type != QEvent::MouseButtonPress &&
143         type != QEvent::MouseButtonRelease &&
144         type != QEvent::MouseMove)
145         return false;
146
147     QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
148     if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier)
149         return false;
150
151     QWidget *viewport = dynamic_cast<QWidget*>(object);
152     FlickData *data = d->flickData.value(viewport);
153     if (!viewport || !data || data->ignored.removeAll(event))
154         return false;
155
156     QWidget *scrollArea = dynamic_cast<QWidget*>(object);
157
158     bool consumed = false;
159     switch (data->state) {
160
161     case FlickData::Steady:
162         if (mouseEvent->type() == QEvent::MouseButtonPress)
163             if (mouseEvent->buttons() == Qt::LeftButton) {
164             consumed = true;
165             data->state = FlickData::Pressed;
166             data->pressPos = mouseEvent->pos();
167             data->offset = scrollOffset(data->widget);
168         }
169         break;
170
171     case FlickData::Pressed:
172         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
173             consumed = true;
174             data->state = FlickData::Steady;
175
176             QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
177                                                   data->pressPos, Qt::LeftButton,
178                                                   Qt::LeftButton, Qt::NoModifier);
179             QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
180
181             data->ignored << event1;
182             data->ignored << event2;
183             QApplication::postEvent(object, event1);
184             QApplication::postEvent(object, event2);
185         }
186         if (mouseEvent->type() == QEvent::MouseMove) {
187
188             consumed = true;
189             data->state = FlickData::ManualScroll;
190             data->dragPos = QCursor::pos();
191             if (!d->ticker.isActive())
192                 d->ticker.start(20, this);
193
194         }
195         break;
196
197     case FlickData::ManualScroll:
198         if (mouseEvent->type() == QEvent::MouseMove) {
199             QPoint pos = scrollArea->mapFromGlobal(QCursor::pos());
200             if (pos.x() > scrollArea->width() || pos.x() < 0) {
201                 pos.setX(1);
202                 QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
203                                                       pos, Qt::LeftButton,
204                                                       Qt::LeftButton, Qt::NoModifier);
205                 QMouseEvent *event2 = new QMouseEvent(QEvent::MouseMove,
206                                                       pos, Qt::LeftButton,
207                                                       Qt::LeftButton, Qt::NoModifier);
208
209                 data->ignored << event1;
210                 data->ignored << event2;
211                 QApplication::postEvent(object, event1);
212                 QApplication::postEvent(object, event2);
213                 data->state = FlickData::Steady;
214                 consumed = true;
215             } else {
216                 consumed = true;
217                 QPoint delta = mouseEvent->pos() - data->pressPos;
218                 setScrollOffset(data->widget, data->offset - delta);
219             }
220         }
221         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
222             consumed = true;
223             data->state = FlickData::AutoScroll;
224         }
225         break;
226
227     case FlickData::AutoScroll:
228         if (mouseEvent->type() == QEvent::MouseButtonPress) {
229             consumed = true;
230             data->state = FlickData::Stop;
231             data->speed = QPoint(0, 0);
232             data->pressPos = mouseEvent->pos();
233             data->offset = scrollOffset(data->widget);
234         }
235         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
236             consumed = true;
237             data->state = FlickData::Steady;
238             data->speed = QPoint(0, 0);
239         }
240         break;
241
242     case FlickData::Stop:
243         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
244             consumed = true;
245             data->state = FlickData::Steady;
246         }
247         if (mouseEvent->type() == QEvent::MouseMove) {
248             consumed = true;
249             data->state = FlickData::ManualScroll;
250             data->dragPos = QCursor::pos();
251             if (!d->ticker.isActive())
252                 d->ticker.start(20, this);
253         }
254         break;
255
256     default:
257         break;
258     }
259
260     return consumed;
261 }
262
263 void FlickCharm::timerEvent(QTimerEvent *event)
264 {
265     int count = 0;
266     QHashIterator<QWidget*, FlickData*> item(d->flickData);
267     while (item.hasNext()) {
268         item.next();
269         FlickData *data = item.value();
270
271         const bool scrolling = data->state == FlickData::ManualScroll || data->state == FlickData::AutoScroll;
272         scrollBarShow(data->widget, scrolling);
273         // data->widget->setUpdatesEnabled(!scrolling);
274
275         if (data->state == FlickData::ManualScroll) {
276             count++;
277             data->speed = QCursor::pos() - data->dragPos;
278             data->dragPos = QCursor::pos();
279         }
280
281         if (data->state == FlickData::AutoScroll) {
282             count++;
283             data->speed = deaccelerate(data->speed);
284             QPoint p = scrollOffset(data->widget);
285             setScrollOffset(data->widget, p - data->speed);
286             if (data->speed == QPoint(0, 0))
287                 data->state = FlickData::Steady;
288         }
289     }
290
291     if (!count)
292         d->ticker.stop();
293
294     QObject::timerEvent(event);
295 }
296
297 void FlickCharm::showScrollBars(QWidget *widget) {
298     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
299     if (scrollArea) {
300         // scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
301         scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
302     }
303 }
304
305 void FlickCharm::hideScrollBars(QWidget *widget) {
306     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
307     if (scrollArea) {
308         scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
309         scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
310     }
311 }
312
313 void FlickCharm::scrollBarShow(QWidget *widget, bool show) {
314     static bool shown = false;
315
316     if (show) {
317         if (!shown) {
318             showScrollBars(widget);
319             shown = true;
320         }
321     } else {
322         if (shown) {
323             hideScrollBars(widget);
324             shown = false;
325         }
326     }
327 }