source: trunk/demos/embedded/anomaly/src/flickcharm.cpp@ 846

Last change on this file since 846 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 13.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the demos of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "flickcharm.h"
43
44#include <QAbstractScrollArea>
45#include <QApplication>
46#include <QBasicTimer>
47#include <QEvent>
48#include <QHash>
49#include <QList>
50#include <QMouseEvent>
51#include <QScrollBar>
52#include <QTime>
53#include <QWebFrame>
54#include <QWebView>
55
56#include <QDebug>
57
58const int fingerAccuracyThreshold = 3;
59
60struct FlickData {
61 typedef enum {
62 Steady, // Interaction without scrolling
63 ManualScroll, // Scrolling manually with the finger on the screen
64 AutoScroll, // Scrolling automatically
65 AutoScrollAcceleration // Scrolling automatically but a finger is on the screen
66 } State;
67 State state;
68 QWidget *widget;
69 QPoint pressPos;
70 QPoint lastPos;
71 QPoint speed;
72 QTime speedTimer;
73 QList<QEvent*> ignored;
74 QTime accelerationTimer;
75 bool lastPosValid:1;
76 bool waitingAcceleration:1;
77
78 FlickData()
79 : lastPosValid(false)
80 , waitingAcceleration(false)
81 {}
82
83 void resetSpeed()
84 {
85 speed = QPoint();
86 lastPosValid = false;
87 }
88 void updateSpeed(const QPoint &newPosition)
89 {
90 if (lastPosValid) {
91 const int timeElapsed = speedTimer.elapsed();
92 if (timeElapsed) {
93 const QPoint newPixelDiff = (newPosition - lastPos);
94 const QPoint pixelsPerSecond = newPixelDiff * (1000 / timeElapsed);
95 // fingers are inacurates, we ignore small changes to avoid stopping the autoscroll because
96 // of a small horizontal offset when scrolling vertically
97 const int newSpeedY = (qAbs(pixelsPerSecond.y()) > fingerAccuracyThreshold) ? pixelsPerSecond.y() : 0;
98 const int newSpeedX = (qAbs(pixelsPerSecond.x()) > fingerAccuracyThreshold) ? pixelsPerSecond.x() : 0;
99 if (state == AutoScrollAcceleration) {
100 const int max = 4000; // px by seconds
101 const int oldSpeedY = speed.y();
102 const int oldSpeedX = speed.x();
103 if ((oldSpeedY <= 0 && newSpeedY <= 0) || (oldSpeedY >= 0 && newSpeedY >= 0)
104 && (oldSpeedX <= 0 && newSpeedX <= 0) || (oldSpeedX >= 0 && newSpeedX >= 0)) {
105 speed.setY(qBound(-max, (oldSpeedY + (newSpeedY / 4)), max));
106 speed.setX(qBound(-max, (oldSpeedX + (newSpeedX / 4)), max));
107 } else {
108 speed = QPoint();
109 }
110 } else {
111 const int max = 2500; // px by seconds
112 // we average the speed to avoid strange effects with the last delta
113 if (!speed.isNull()) {
114 speed.setX(qBound(-max, (speed.x() / 4) + (newSpeedX * 3 / 4), max));
115 speed.setY(qBound(-max, (speed.y() / 4) + (newSpeedY * 3 / 4), max));
116 } else {
117 speed = QPoint(newSpeedX, newSpeedY);
118 }
119 }
120 }
121 } else {
122 lastPosValid = true;
123 }
124 speedTimer.start();
125 lastPos = newPosition;
126 }
127
128 // scroll by dx, dy
129 // return true if the widget was scrolled
130 bool scrollWidget(const int dx, const int dy)
131 {
132 QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
133 if (scrollArea) {
134 const int x = scrollArea->horizontalScrollBar()->value();
135 const int y = scrollArea->verticalScrollBar()->value();
136 scrollArea->horizontalScrollBar()->setValue(x - dx);
137 scrollArea->verticalScrollBar()->setValue(y - dy);
138 return (scrollArea->horizontalScrollBar()->value() != x
139 || scrollArea->verticalScrollBar()->value() != y);
140 }
141
142 QWebView *webView = qobject_cast<QWebView*>(widget);
143 if (webView) {
144 QWebFrame *frame = webView->page()->mainFrame();
145 const QPoint position = frame->scrollPosition();
146 frame->setScrollPosition(position - QPoint(dx, dy));
147 return frame->scrollPosition() != position;
148 }
149 return false;
150 }
151
152 bool scrollTo(const QPoint &newPosition)
153 {
154 const QPoint delta = newPosition - lastPos;
155 updateSpeed(newPosition);
156 return scrollWidget(delta.x(), delta.y());
157 }
158};
159
160class FlickCharmPrivate
161{
162public:
163 QHash<QWidget*, FlickData*> flickData;
164 QBasicTimer ticker;
165 QTime timeCounter;
166 void startTicker(QObject *object)
167 {
168 if (!ticker.isActive())
169 ticker.start(15, object);
170 timeCounter.start();
171 }
172};
173
174FlickCharm::FlickCharm(QObject *parent): QObject(parent)
175{
176 d = new FlickCharmPrivate;
177}
178
179FlickCharm::~FlickCharm()
180{
181 delete d;
182}
183
184void FlickCharm::activateOn(QWidget *widget)
185{
186 QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
187 if (scrollArea) {
188 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
189 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
190
191 QWidget *viewport = scrollArea->viewport();
192
193 viewport->installEventFilter(this);
194 scrollArea->installEventFilter(this);
195
196 d->flickData.remove(viewport);
197 d->flickData[viewport] = new FlickData;
198 d->flickData[viewport]->widget = widget;
199 d->flickData[viewport]->state = FlickData::Steady;
200
201 return;
202 }
203
204 QWebView *webView = qobject_cast<QWebView*>(widget);
205 if (webView) {
206 QWebFrame *frame = webView->page()->mainFrame();
207 frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
208 frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
209
210 webView->installEventFilter(this);
211
212 d->flickData.remove(webView);
213 d->flickData[webView] = new FlickData;
214 d->flickData[webView]->widget = webView;
215 d->flickData[webView]->state = FlickData::Steady;
216
217 return;
218 }
219
220 qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
221 qWarning() << "or QWebView (and derived classes)";
222}
223
224void FlickCharm::deactivateFrom(QWidget *widget)
225{
226 QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
227 if (scrollArea) {
228 QWidget *viewport = scrollArea->viewport();
229
230 viewport->removeEventFilter(this);
231 scrollArea->removeEventFilter(this);
232
233 delete d->flickData[viewport];
234 d->flickData.remove(viewport);
235
236 return;
237 }
238
239 QWebView *webView = qobject_cast<QWebView*>(widget);
240 if (webView) {
241 webView->removeEventFilter(this);
242
243 delete d->flickData[webView];
244 d->flickData.remove(webView);
245
246 return;
247 }
248}
249
250static QPoint deaccelerate(const QPoint &speed, const int deltatime)
251{
252 const int deltaSpeed = deltatime;
253
254 int x = speed.x();
255 int y = speed.y();
256 x = (x == 0) ? x : (x > 0) ? qMax(0, x - deltaSpeed) : qMin(0, x + deltaSpeed);
257 y = (y == 0) ? y : (y > 0) ? qMax(0, y - deltaSpeed) : qMin(0, y + deltaSpeed);
258 return QPoint(x, y);
259}
260
261bool FlickCharm::eventFilter(QObject *object, QEvent *event)
262{
263 if (!object->isWidgetType())
264 return false;
265
266 const QEvent::Type type = event->type();
267
268 switch (type) {
269 case QEvent::MouseButtonPress:
270 case QEvent::MouseMove:
271 case QEvent::MouseButtonRelease:
272 break;
273 case QEvent::MouseButtonDblClick: // skip double click
274 return true;
275 default:
276 return false;
277 }
278
279 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
280 if (type == QEvent::MouseMove && mouseEvent->buttons() != Qt::LeftButton)
281 return false;
282
283 if (mouseEvent->modifiers() != Qt::NoModifier)
284 return false;
285
286 QWidget *viewport = qobject_cast<QWidget*>(object);
287 FlickData *data = d->flickData.value(viewport);
288 if (!viewport || !data || data->ignored.removeAll(event))
289 return false;
290
291 const QPoint mousePos = mouseEvent->pos();
292 bool consumed = false;
293 switch (data->state) {
294
295 case FlickData::Steady:
296 if (type == QEvent::MouseButtonPress) {
297 consumed = true;
298 data->pressPos = mousePos;
299 } else if (type == QEvent::MouseButtonRelease) {
300 consumed = true;
301 QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
302 data->pressPos, Qt::LeftButton,
303 Qt::LeftButton, Qt::NoModifier);
304 QMouseEvent *event2 = new QMouseEvent(QEvent::MouseButtonRelease,
305 data->pressPos, Qt::LeftButton,
306 Qt::LeftButton, Qt::NoModifier);
307
308 data->ignored << event1;
309 data->ignored << event2;
310 QApplication::postEvent(object, event1);
311 QApplication::postEvent(object, event2);
312 } else if (type == QEvent::MouseMove) {
313 consumed = true;
314 data->scrollTo(mousePos);
315
316 const QPoint delta = mousePos - data->pressPos;
317 if (delta.x() > fingerAccuracyThreshold || delta.y() > fingerAccuracyThreshold)
318 data->state = FlickData::ManualScroll;
319 }
320 break;
321
322 case FlickData::ManualScroll:
323 if (type == QEvent::MouseMove) {
324 consumed = true;
325 data->scrollTo(mousePos);
326 } else if (type == QEvent::MouseButtonRelease) {
327 consumed = true;
328 data->state = FlickData::AutoScroll;
329 data->lastPosValid = false;
330 d->startTicker(this);
331 }
332 break;
333
334 case FlickData::AutoScroll:
335 if (type == QEvent::MouseButtonPress) {
336 consumed = true;
337 data->state = FlickData::AutoScrollAcceleration;
338 data->waitingAcceleration = true;
339 data->accelerationTimer.start();
340 data->updateSpeed(mousePos);
341 data->pressPos = mousePos;
342 } else if (type == QEvent::MouseButtonRelease) {
343 consumed = true;
344 data->state = FlickData::Steady;
345 data->resetSpeed();
346 }
347 break;
348
349 case FlickData::AutoScrollAcceleration:
350 if (type == QEvent::MouseMove) {
351 consumed = true;
352 data->updateSpeed(mousePos);
353 data->accelerationTimer.start();
354 if (data->speed.isNull())
355 data->state = FlickData::ManualScroll;
356 } else if (type == QEvent::MouseButtonRelease) {
357 consumed = true;
358 data->state = FlickData::AutoScroll;
359 data->waitingAcceleration = false;
360 data->lastPosValid = false;
361 }
362 break;
363 default:
364 break;
365 }
366 data->lastPos = mousePos;
367 return true;
368}
369
370void FlickCharm::timerEvent(QTimerEvent *event)
371{
372 int count = 0;
373 QHashIterator<QWidget*, FlickData*> item(d->flickData);
374 while (item.hasNext()) {
375 item.next();
376 FlickData *data = item.value();
377 if (data->state == FlickData::AutoScrollAcceleration
378 && data->waitingAcceleration
379 && data->accelerationTimer.elapsed() > 40) {
380 data->state = FlickData::ManualScroll;
381 data->resetSpeed();
382 }
383 if (data->state == FlickData::AutoScroll || data->state == FlickData::AutoScrollAcceleration) {
384 const int timeElapsed = d->timeCounter.elapsed();
385 const QPoint delta = (data->speed) * timeElapsed / 1000;
386 bool hasScrolled = data->scrollWidget(delta.x(), delta.y());
387
388 if (data->speed.isNull() || !hasScrolled)
389 data->state = FlickData::Steady;
390 else
391 count++;
392 data->speed = deaccelerate(data->speed, timeElapsed);
393 }
394 }
395
396 if (!count)
397 d->ticker.stop();
398 else
399 d->timeCounter.start();
400
401 QObject::timerEvent(event);
402}
Note: See TracBrowser for help on using the repository browser.