50
50
#include <QMouseEvent>
51
51
#include <QScrollBar>
52
53
#include <QWebFrame>
53
54
#include <QWebView>
58
const int fingerAccuracyThreshold = 3;
58
typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State;
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
65
73
QList<QEvent*> ignored;
74
QTime accelerationTimer;
76
bool waitingAcceleration:1;
80
, waitingAcceleration(false)
88
void updateSpeed(const QPoint &newPosition)
91
const int timeElapsed = speedTimer.elapsed();
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));
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));
117
speed = QPoint(newSpeedX, newSpeedY);
125
lastPos = newPosition;
129
// return true if the widget was scrolled
130
bool scrollWidget(const int dx, const int dy)
132
QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
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);
142
QWebView *webView = qobject_cast<QWebView*>(widget);
144
QWebFrame *frame = webView->page()->mainFrame();
145
const QPoint position = frame->scrollPosition();
146
frame->setScrollPosition(position - QPoint(dx, dy));
147
return frame->scrollPosition() != position;
152
bool scrollTo(const QPoint &newPosition)
154
const QPoint delta = newPosition - lastPos;
155
updateSpeed(newPosition);
156
return scrollWidget(delta.x(), delta.y());
68
160
class FlickCharmPrivate
151
static QPoint scrollOffset(QWidget *widget)
155
QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
157
x = scrollArea->horizontalScrollBar()->value();
158
y = scrollArea->verticalScrollBar()->value();
161
QWebView *webView = qobject_cast<QWebView*>(widget);
163
QWebFrame *frame = webView->page()->mainFrame();
164
x = frame->evaluateJavaScript("window.scrollX").toInt();
165
y = frame->evaluateJavaScript("window.scrollY").toInt();
171
static void setScrollOffset(QWidget *widget, const QPoint &p)
173
QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
175
scrollArea->horizontalScrollBar()->setValue(p.x());
176
scrollArea->verticalScrollBar()->setValue(p.y());
179
QWebView *webView = qobject_cast<QWebView*>(widget);
180
QWebFrame *frame = webView ? webView->page()->mainFrame() : 0;
182
frame->evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
185
static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)
187
int x = qBound(-max, speed.x(), max);
188
int y = qBound(-max, speed.y(), max);
189
x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a);
190
y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a);
250
static QPoint deaccelerate(const QPoint &speed, const int deltatime)
252
const int deltaSpeed = deltatime;
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);
191
258
return QPoint(x, y);
196
263
if (!object->isWidgetType())
199
QEvent::Type type = event->type();
200
if (type != QEvent::MouseButtonPress &&
201
type != QEvent::MouseButtonRelease &&
202
type != QEvent::MouseMove)
266
const QEvent::Type type = event->type();
269
case QEvent::MouseButtonPress:
270
case QEvent::MouseMove:
271
case QEvent::MouseButtonRelease:
273
case QEvent::MouseButtonDblClick: // skip double click
205
QMouseEvent *mouseEvent = 0;
206
switch (event->type()) {
207
case QEvent::MouseButtonPress:
208
case QEvent::MouseButtonRelease:
209
case QEvent::MouseMove:
210
mouseEvent = static_cast<QMouseEvent*>(event);
214
if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier)
279
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
280
if (type == QEvent::MouseMove && mouseEvent->buttons() != Qt::LeftButton)
283
if (mouseEvent->modifiers() != Qt::NoModifier)
217
286
QWidget *viewport = qobject_cast<QWidget*>(object);
219
288
if (!viewport || !data || data->ignored.removeAll(event))
291
const QPoint mousePos = mouseEvent->pos();
222
292
bool consumed = false;
223
293
switch (data->state) {
225
295
case FlickData::Steady:
226
if (mouseEvent->type() == QEvent::MouseButtonPress)
227
if (mouseEvent->buttons() == Qt::LeftButton) {
229
data->state = FlickData::Pressed;
230
data->pressPos = mouseEvent->pos();
231
data->offset = scrollOffset(data->widget);
235
case FlickData::Pressed:
236
if (mouseEvent->type() == QEvent::MouseButtonRelease) {
238
data->state = FlickData::Steady;
296
if (type == QEvent::MouseButtonPress) {
298
data->pressPos = mousePos;
299
} else if (type == QEvent::MouseButtonRelease) {
240
301
QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
241
302
data->pressPos, Qt::LeftButton,
242
303
Qt::LeftButton, Qt::NoModifier);
243
QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
304
QMouseEvent *event2 = new QMouseEvent(QEvent::MouseButtonRelease,
305
data->pressPos, Qt::LeftButton,
306
Qt::LeftButton, Qt::NoModifier);
245
308
data->ignored << event1;
246
309
data->ignored << event2;
247
310
QApplication::postEvent(object, event1);
248
311
QApplication::postEvent(object, event2);
250
if (mouseEvent->type() == QEvent::MouseMove) {
312
} else if (type == QEvent::MouseMove) {
252
data->state = FlickData::ManualScroll;
253
data->dragPos = QCursor::pos();
254
if (!d->ticker.isActive())
255
d->ticker.start(20, this);
314
data->scrollTo(mousePos);
316
const QPoint delta = mousePos - data->pressPos;
317
if (delta.x() > fingerAccuracyThreshold || delta.y() > fingerAccuracyThreshold)
318
data->state = FlickData::ManualScroll;
259
322
case FlickData::ManualScroll:
260
if (mouseEvent->type() == QEvent::MouseMove) {
323
if (type == QEvent::MouseMove) {
262
QPoint delta = mouseEvent->pos() - data->pressPos;
263
setScrollOffset(data->widget, data->offset - delta);
265
if (mouseEvent->type() == QEvent::MouseButtonRelease) {
325
data->scrollTo(mousePos);
326
} else if (type == QEvent::MouseButtonRelease) {
267
328
data->state = FlickData::AutoScroll;
329
data->lastPosValid = false;
330
d->startTicker(this);
271
334
case FlickData::AutoScroll:
272
if (mouseEvent->type() == QEvent::MouseButtonPress) {
274
data->state = FlickData::Stop;
275
data->speed = QPoint(0, 0);
276
data->pressPos = mouseEvent->pos();
277
data->offset = scrollOffset(data->widget);
279
if (mouseEvent->type() == QEvent::MouseButtonRelease) {
281
data->state = FlickData::Steady;
282
data->speed = QPoint(0, 0);
286
case FlickData::Stop:
287
if (mouseEvent->type() == QEvent::MouseButtonRelease) {
289
data->state = FlickData::Steady;
291
if (mouseEvent->type() == QEvent::MouseMove) {
293
data->state = FlickData::ManualScroll;
294
data->dragPos = QCursor::pos();
295
if (!d->ticker.isActive())
296
d->ticker.start(20, this);
335
if (type == QEvent::MouseButtonPress) {
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) {
344
data->state = FlickData::Steady;
349
case FlickData::AutoScrollAcceleration:
350
if (type == QEvent::MouseMove) {
352
data->updateSpeed(mousePos);
353
data->accelerationTimer.start();
354
if (data->speed.isNull())
355
data->state = FlickData::ManualScroll;
356
} else if (type == QEvent::MouseButtonRelease) {
358
data->state = FlickData::AutoScroll;
359
data->waitingAcceleration = false;
360
data->lastPosValid = false;
366
data->lastPos = mousePos;
307
370
void FlickCharm::timerEvent(QTimerEvent *event)
311
374
while (item.hasNext()) {
313
376
FlickData *data = item.value();
315
if (data->state == FlickData::ManualScroll) {
317
data->speed = QCursor::pos() - data->dragPos;
318
data->dragPos = QCursor::pos();
377
if (data->state == FlickData::AutoScrollAcceleration
378
&& data->waitingAcceleration
379
&& data->accelerationTimer.elapsed() > 40) {
380
data->state = FlickData::ManualScroll;
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());
321
if (data->state == FlickData::AutoScroll) {
323
data->speed = deaccelerate(data->speed);
324
QPoint p = scrollOffset(data->widget);
325
setScrollOffset(data->widget, p - data->speed);
326
if (data->speed == QPoint(0, 0))
388
if (data->speed.isNull() || !hasScrolled)
327
389
data->state = FlickData::Steady;
392
data->speed = deaccelerate(data->speed, timeElapsed);
332
397
d->ticker.stop();
399
d->timeCounter.start();
334
401
QObject::timerEvent(event);