demos/embedded/anomaly/src/flickcharm.cpp
changeset 18 2f34d5167611
parent 0 1918ee327afb
equal deleted inserted replaced
3:41300fa6a67c 18:2f34d5167611
     1 /****************************************************************************
     1 /****************************************************************************
     2 **
     2 **
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
     4 ** All rights reserved.
     4 ** All rights reserved.
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
     6 **
     6 **
     7 ** This file is part of the demos of the Qt Toolkit.
     7 ** This file is part of the demos of the Qt Toolkit.
     8 **
     8 **
    47 #include <QEvent>
    47 #include <QEvent>
    48 #include <QHash>
    48 #include <QHash>
    49 #include <QList>
    49 #include <QList>
    50 #include <QMouseEvent>
    50 #include <QMouseEvent>
    51 #include <QScrollBar>
    51 #include <QScrollBar>
       
    52 #include <QTime>
    52 #include <QWebFrame>
    53 #include <QWebFrame>
    53 #include <QWebView>
    54 #include <QWebView>
    54 
    55 
    55 #include <QDebug>
    56 #include <QDebug>
    56 
    57 
       
    58 const int fingerAccuracyThreshold = 3;
       
    59 
    57 struct FlickData {
    60 struct FlickData {
    58     typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State;
    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;
    59     State state;
    67     State state;
    60     QWidget *widget;
    68     QWidget *widget;
    61     QPoint pressPos;
    69     QPoint pressPos;
    62     QPoint offset;
    70     QPoint lastPos;
    63     QPoint dragPos;
       
    64     QPoint speed;
    71     QPoint speed;
       
    72     QTime speedTimer;
    65     QList<QEvent*> ignored;
    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     }
    66 };
   158 };
    67 
   159 
    68 class FlickCharmPrivate
   160 class FlickCharmPrivate
    69 {
   161 {
    70 public:
   162 public:
    71     QHash<QWidget*, FlickData*> flickData;
   163     QHash<QWidget*, FlickData*> flickData;
    72     QBasicTimer ticker;
   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     }
    73 };
   172 };
    74 
   173 
    75 FlickCharm::FlickCharm(QObject *parent): QObject(parent)
   174 FlickCharm::FlickCharm(QObject *parent): QObject(parent)
    76 {
   175 {
    77     d = new FlickCharmPrivate;
   176     d = new FlickCharmPrivate;
   146 
   245 
   147         return;
   246         return;
   148     }
   247     }
   149 }
   248 }
   150 
   249 
   151 static QPoint scrollOffset(QWidget *widget)
   250 static QPoint deaccelerate(const QPoint &speed, const int deltatime)
   152 {
   251 {
   153     int x = 0, y = 0;
   252     const int deltaSpeed = deltatime;
   154 
   253 
   155     QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
   254     int x = speed.x();
   156     if (scrollArea) {
   255     int y = speed.y();
   157         x = scrollArea->horizontalScrollBar()->value();
   256     x = (x == 0) ? x : (x > 0) ? qMax(0, x - deltaSpeed) : qMin(0, x + deltaSpeed);
   158         y = scrollArea->verticalScrollBar()->value();
   257     y = (y == 0) ? y : (y > 0) ? qMax(0, y - deltaSpeed) : qMin(0, y + deltaSpeed);
   159     }
       
   160 
       
   161     QWebView *webView = qobject_cast<QWebView*>(widget);
       
   162     if (webView) {
       
   163         QWebFrame *frame = webView->page()->mainFrame();
       
   164         x = frame->evaluateJavaScript("window.scrollX").toInt();
       
   165         y = frame->evaluateJavaScript("window.scrollY").toInt();
       
   166     }
       
   167 
       
   168     return QPoint(x, y);
   258     return QPoint(x, y);
   169 }
   259 }
   170 
   260 
   171 static void setScrollOffset(QWidget *widget, const QPoint &p)
       
   172 {
       
   173     QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
       
   174     if (scrollArea) {
       
   175         scrollArea->horizontalScrollBar()->setValue(p.x());
       
   176         scrollArea->verticalScrollBar()->setValue(p.y());
       
   177     }
       
   178 
       
   179     QWebView *webView = qobject_cast<QWebView*>(widget);
       
   180     QWebFrame *frame = webView ? webView->page()->mainFrame() : 0;
       
   181     if (frame)
       
   182         frame->evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
       
   183 }
       
   184 
       
   185 static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)
       
   186 {
       
   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);
       
   191     return QPoint(x, y);
       
   192 }
       
   193 
       
   194 bool FlickCharm::eventFilter(QObject *object, QEvent *event)
   261 bool FlickCharm::eventFilter(QObject *object, QEvent *event)
   195 {
   262 {
   196     if (!object->isWidgetType())
   263     if (!object->isWidgetType())
   197         return false;
   264         return false;
   198 
   265 
   199     QEvent::Type type = event->type();
   266     const QEvent::Type type = event->type();
   200     if (type != QEvent::MouseButtonPress &&
   267 
   201             type != QEvent::MouseButtonRelease &&
   268     switch (type) {
   202             type != QEvent::MouseMove)
   269     case QEvent::MouseButtonPress:
   203         return false;
   270     case QEvent::MouseMove:
   204 
   271     case QEvent::MouseButtonRelease:
   205     QMouseEvent *mouseEvent = 0;
   272         break;
   206     switch (event->type()) {
   273     case QEvent::MouseButtonDblClick: // skip double click
   207         case QEvent::MouseButtonPress:
   274         return true;
   208         case QEvent::MouseButtonRelease:
   275     default:
   209         case QEvent::MouseMove:
   276         return false;
   210             mouseEvent = static_cast<QMouseEvent*>(event);
   277     }
   211             break;
   278 
   212     }
   279     QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
   213 
   280     if (type == QEvent::MouseMove && mouseEvent->buttons() != Qt::LeftButton)
   214     if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier)
   281         return false;
       
   282 
       
   283     if (mouseEvent->modifiers() != Qt::NoModifier)
   215         return false;
   284         return false;
   216 
   285 
   217     QWidget *viewport = qobject_cast<QWidget*>(object);
   286     QWidget *viewport = qobject_cast<QWidget*>(object);
   218     FlickData *data = d->flickData.value(viewport);
   287     FlickData *data = d->flickData.value(viewport);
   219     if (!viewport || !data || data->ignored.removeAll(event))
   288     if (!viewport || !data || data->ignored.removeAll(event))
   220         return false;
   289         return false;
   221 
   290 
       
   291     const QPoint mousePos = mouseEvent->pos();
   222     bool consumed = false;
   292     bool consumed = false;
   223     switch (data->state) {
   293     switch (data->state) {
   224 
   294 
   225     case FlickData::Steady:
   295     case FlickData::Steady:
   226         if (mouseEvent->type() == QEvent::MouseButtonPress)
   296         if (type == QEvent::MouseButtonPress) {
   227             if (mouseEvent->buttons() == Qt::LeftButton) {
   297             consumed = true;
   228                 consumed = true;
   298             data->pressPos = mousePos;
   229                 data->state = FlickData::Pressed;
   299         } else if (type == QEvent::MouseButtonRelease) {
   230                 data->pressPos = mouseEvent->pos();
   300             consumed = true;
   231                 data->offset = scrollOffset(data->widget);
       
   232             }
       
   233         break;
       
   234 
       
   235     case FlickData::Pressed:
       
   236         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
       
   237             consumed = true;
       
   238             data->state = FlickData::Steady;
       
   239 
       
   240             QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
   301             QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
   241                                                   data->pressPos, Qt::LeftButton,
   302                                                   data->pressPos, Qt::LeftButton,
   242                                                   Qt::LeftButton, Qt::NoModifier);
   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);
   244 
   307 
   245             data->ignored << event1;
   308             data->ignored << event1;
   246             data->ignored << event2;
   309             data->ignored << event2;
   247             QApplication::postEvent(object, event1);
   310             QApplication::postEvent(object, event1);
   248             QApplication::postEvent(object, event2);
   311             QApplication::postEvent(object, event2);
   249         }
   312         } else if (type == QEvent::MouseMove) {
   250         if (mouseEvent->type() == QEvent::MouseMove) {
   313             consumed = true;
   251             consumed = true;
   314             data->scrollTo(mousePos);
   252             data->state = FlickData::ManualScroll;
   315 
   253             data->dragPos = QCursor::pos();
   316             const QPoint delta = mousePos - data->pressPos;
   254             if (!d->ticker.isActive())
   317             if (delta.x() > fingerAccuracyThreshold || delta.y() > fingerAccuracyThreshold)
   255                 d->ticker.start(20, this);
   318                 data->state = FlickData::ManualScroll;
   256         }
   319         }
   257         break;
   320         break;
   258 
   321 
   259     case FlickData::ManualScroll:
   322     case FlickData::ManualScroll:
   260         if (mouseEvent->type() == QEvent::MouseMove) {
   323         if (type == QEvent::MouseMove) {
   261             consumed = true;
   324             consumed = true;
   262             QPoint delta = mouseEvent->pos() - data->pressPos;
   325             data->scrollTo(mousePos);
   263             setScrollOffset(data->widget, data->offset - delta);
   326         } else if (type == QEvent::MouseButtonRelease) {
   264         }
       
   265         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
       
   266             consumed = true;
   327             consumed = true;
   267             data->state = FlickData::AutoScroll;
   328             data->state = FlickData::AutoScroll;
       
   329             data->lastPosValid = false;
       
   330             d->startTicker(this);
   268         }
   331         }
   269         break;
   332         break;
   270 
   333 
   271     case FlickData::AutoScroll:
   334     case FlickData::AutoScroll:
   272         if (mouseEvent->type() == QEvent::MouseButtonPress) {
   335         if (type == QEvent::MouseButtonPress) {
   273             consumed = true;
   336             consumed = true;
   274             data->state = FlickData::Stop;
   337             data->state = FlickData::AutoScrollAcceleration;
   275             data->speed = QPoint(0, 0);
   338             data->waitingAcceleration = true;
   276             data->pressPos = mouseEvent->pos();
   339             data->accelerationTimer.start();
   277             data->offset = scrollOffset(data->widget);
   340             data->updateSpeed(mousePos);
   278         }
   341             data->pressPos = mousePos;
   279         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
   342         } else if (type == QEvent::MouseButtonRelease) {
   280             consumed = true;
   343             consumed = true;
   281             data->state = FlickData::Steady;
   344             data->state = FlickData::Steady;
   282             data->speed = QPoint(0, 0);
   345             data->resetSpeed();
   283         }
   346         }
   284         break;
   347         break;
   285 
   348 
   286     case FlickData::Stop:
   349     case FlickData::AutoScrollAcceleration:
   287         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
   350         if (type == QEvent::MouseMove) {
   288             consumed = true;
   351             consumed = true;
   289             data->state = FlickData::Steady;
   352             data->updateSpeed(mousePos);
   290         }
   353             data->accelerationTimer.start();
   291         if (mouseEvent->type() == QEvent::MouseMove) {
   354             if (data->speed.isNull())
   292             consumed = true;
   355                 data->state = FlickData::ManualScroll;
   293             data->state = FlickData::ManualScroll;
   356         } else if (type == QEvent::MouseButtonRelease) {
   294             data->dragPos = QCursor::pos();
   357             consumed = true;
   295             if (!d->ticker.isActive())
   358             data->state = FlickData::AutoScroll;
   296                 d->ticker.start(20, this);
   359             data->waitingAcceleration = false;
   297         }
   360             data->lastPosValid = false;
   298         break;
   361         }
   299 
   362         break;
   300     default:
   363     default:
   301         break;
   364         break;
   302     }
   365     }
   303 
   366     data->lastPos = mousePos;
   304     return consumed;
   367     return true;
   305 }
   368 }
   306 
   369 
   307 void FlickCharm::timerEvent(QTimerEvent *event)
   370 void FlickCharm::timerEvent(QTimerEvent *event)
   308 {
   371 {
   309     int count = 0;
   372     int count = 0;
   310     QHashIterator<QWidget*, FlickData*> item(d->flickData);
   373     QHashIterator<QWidget*, FlickData*> item(d->flickData);
   311     while (item.hasNext()) {
   374     while (item.hasNext()) {
   312         item.next();
   375         item.next();
   313         FlickData *data = item.value();
   376         FlickData *data = item.value();
   314 
   377         if (data->state == FlickData::AutoScrollAcceleration
   315         if (data->state == FlickData::ManualScroll) {
   378             && data->waitingAcceleration
   316             count++;
   379             && data->accelerationTimer.elapsed() > 40) {
   317             data->speed = QCursor::pos() - data->dragPos;
   380             data->state = FlickData::ManualScroll;
   318             data->dragPos = QCursor::pos();
   381             data->resetSpeed();
   319         }
   382         }
   320 
   383         if (data->state == FlickData::AutoScroll || data->state == FlickData::AutoScrollAcceleration) {
   321         if (data->state == FlickData::AutoScroll) {
   384             const int timeElapsed = d->timeCounter.elapsed();
   322             count++;
   385             const QPoint delta = (data->speed) * timeElapsed / 1000;
   323             data->speed = deaccelerate(data->speed);
   386             bool hasScrolled = data->scrollWidget(delta.x(), delta.y());
   324             QPoint p = scrollOffset(data->widget);
   387 
   325             setScrollOffset(data->widget, p - data->speed);
   388             if (data->speed.isNull() || !hasScrolled)
   326             if (data->speed == QPoint(0, 0))
       
   327                 data->state = FlickData::Steady;
   389                 data->state = FlickData::Steady;
       
   390             else
       
   391                 count++;
       
   392             data->speed = deaccelerate(data->speed, timeElapsed);
   328         }
   393         }
   329     }
   394     }
   330 
   395 
   331     if (!count)
   396     if (!count)
   332         d->ticker.stop();
   397         d->ticker.stop();
       
   398     else
       
   399         d->timeCounter.start();
   333 
   400 
   334     QObject::timerEvent(event);
   401     QObject::timerEvent(event);
   335 }
   402 }