ginebra/flickcharm.cpp
changeset 0 1450b09d0cfd
equal deleted inserted replaced
-1:000000000000 0:1450b09d0cfd
       
     1 /*
       
     2 * Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description: 
       
    15 *
       
    16 */
       
    17 
       
    18 
       
    19 #include "flickcharm.h"
       
    20 
       
    21 #include <QAbstractScrollArea>
       
    22 #include <QApplication>
       
    23 #include <QBasicTimer>
       
    24 #include <QEvent>
       
    25 #include <QHash>
       
    26 #include <QList>
       
    27 #include <QMouseEvent>
       
    28 #include <QScrollBar>
       
    29 #include "qwebframe.h"
       
    30 #include "qwebview.h"
       
    31 
       
    32 #include <QDebug>
       
    33 
       
    34 struct FlickData {
       
    35     typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State;
       
    36     State state;
       
    37     QWidget *widget;
       
    38     QPoint pressPos;
       
    39     QPoint offset;
       
    40     QPoint dragPos;
       
    41     QPoint speed;
       
    42     QList<QEvent*> ignored;
       
    43 };
       
    44 
       
    45 class FlickCharmPrivate
       
    46 {
       
    47 public:
       
    48     QHash<QWidget*, FlickData*> flickData;
       
    49     QBasicTimer ticker;
       
    50 };
       
    51 
       
    52 FlickCharm::FlickCharm(QObject *parent): QObject(parent)
       
    53 {
       
    54     d = new FlickCharmPrivate;
       
    55 }
       
    56 
       
    57 FlickCharm::~FlickCharm()
       
    58 {
       
    59     delete d;
       
    60 }
       
    61 
       
    62 void FlickCharm::activateOn(QWidget *widget)
       
    63 {
       
    64     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
       
    65     if (scrollArea) {
       
    66         scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
       
    67         scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
       
    68 
       
    69         QWidget *viewport = scrollArea->viewport();
       
    70 
       
    71         viewport->installEventFilter(this);
       
    72         scrollArea->installEventFilter(this);
       
    73 
       
    74         d->flickData.remove(viewport);
       
    75         d->flickData[viewport] = new FlickData;
       
    76         d->flickData[viewport]->widget = widget;
       
    77         d->flickData[viewport]->state = FlickData::Steady;
       
    78 
       
    79         return;
       
    80     }
       
    81 
       
    82     QWebView *webView = dynamic_cast<QWebView*>(widget);
       
    83     if (webView) {
       
    84         QWebFrame *frame = webView->page()->mainFrame();
       
    85         frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
       
    86         frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
       
    87 
       
    88         webView->installEventFilter(this);
       
    89 
       
    90         d->flickData.remove(webView);
       
    91         d->flickData[webView] = new FlickData;
       
    92         d->flickData[webView]->widget = webView;
       
    93         d->flickData[webView]->state = FlickData::Steady;
       
    94 
       
    95         return;
       
    96     }
       
    97 
       
    98     qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
       
    99     qWarning() << "or QWebView (and derived classes)";
       
   100 }
       
   101 
       
   102 void FlickCharm::deactivateFrom(QWidget *widget)
       
   103 {
       
   104     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
       
   105     if (scrollArea) {
       
   106         QWidget *viewport = scrollArea->viewport();
       
   107 
       
   108         viewport->removeEventFilter(this);
       
   109         scrollArea->removeEventFilter(this);
       
   110 
       
   111         delete d->flickData[viewport];
       
   112         d->flickData.remove(viewport);
       
   113 
       
   114         return;
       
   115     }
       
   116 
       
   117     QWebView *webView = dynamic_cast<QWebView*>(widget);
       
   118     if (webView) {
       
   119         webView->removeEventFilter(this);
       
   120 
       
   121         delete d->flickData[webView];
       
   122         d->flickData.remove(webView);
       
   123 
       
   124         return;
       
   125     }
       
   126 }
       
   127 
       
   128 static QPoint scrollOffset(QWidget *widget)
       
   129 {
       
   130     int x = 0, y = 0;
       
   131 
       
   132     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
       
   133     if (scrollArea) {
       
   134         x = scrollArea->horizontalScrollBar()->value();
       
   135         y = scrollArea->verticalScrollBar()->value();
       
   136     }
       
   137 
       
   138     QWebView *webView = dynamic_cast<QWebView*>(widget);
       
   139     if (webView) {
       
   140         QWebFrame *frame = webView->page()->mainFrame();
       
   141         x = frame->evaluateJavaScript("window.scrollX").toInt();
       
   142         y = frame->evaluateJavaScript("window.scrollY").toInt();
       
   143     }
       
   144 
       
   145     return QPoint(x, y);
       
   146 }
       
   147 
       
   148 static void setScrollOffset(QWidget *widget, const QPoint &p)
       
   149 {
       
   150     QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
       
   151     if (scrollArea) {
       
   152         scrollArea->horizontalScrollBar()->setValue(p.x());
       
   153         scrollArea->verticalScrollBar()->setValue(p.y());
       
   154     }
       
   155 
       
   156     widget->update();  // hab - added to force repainting of overlapping widgets.
       
   157 
       
   158     QWebView *webView = dynamic_cast<QWebView*>(widget);
       
   159     QWebFrame *frame = webView ? webView->page()->mainFrame() : 0;
       
   160     if (frame)
       
   161         frame->evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
       
   162 }
       
   163 
       
   164 static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)
       
   165 {
       
   166     int x = qBound(-max, speed.x(), max);
       
   167     int y = qBound(-max, speed.y(), max);
       
   168     x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a);
       
   169     y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a);
       
   170     return QPoint(x, y);
       
   171 }
       
   172 
       
   173 bool FlickCharm::eventFilter(QObject *object, QEvent *event)
       
   174 {
       
   175 
       
   176     if (!object->isWidgetType())
       
   177         return false;
       
   178 
       
   179     QEvent::Type type = event->type();
       
   180     if (type != QEvent::MouseButtonPress &&
       
   181             type != QEvent::MouseButtonRelease &&
       
   182             type != QEvent::MouseMove)
       
   183         return false;
       
   184 
       
   185     QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
       
   186     if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier)
       
   187         return false;
       
   188 
       
   189     QWidget *viewport = dynamic_cast<QWidget*>(object);
       
   190     FlickData *data = d->flickData.value(viewport);
       
   191     if (!viewport || !data || data->ignored.removeAll(event))
       
   192         return false;
       
   193 
       
   194     if (type == QEvent::MouseMove){
       
   195       //Throw away spurious mouse moves on mac
       
   196       if(data->pressPos == mouseEvent->pos()){
       
   197 	 return false;
       
   198       }
       
   199     }
       
   200     bool consumed = false;
       
   201     switch (data->state) {
       
   202 
       
   203     case FlickData::Steady:
       
   204  
       
   205         if (mouseEvent->type() == QEvent::MouseButtonPress)
       
   206             if (mouseEvent->buttons() == Qt::LeftButton) {
       
   207                 consumed = true;
       
   208                 data->state = FlickData::Pressed;
       
   209                 data->pressPos = mouseEvent->pos();
       
   210                 data->offset = scrollOffset(data->widget);
       
   211             }
       
   212         break;
       
   213 
       
   214     case FlickData::Pressed:
       
   215  
       
   216         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
       
   217             consumed = true;
       
   218             data->state = FlickData::Steady;
       
   219 
       
   220             QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
       
   221                                                   data->pressPos, Qt::LeftButton,
       
   222                                                   Qt::LeftButton, Qt::NoModifier);
       
   223             QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
       
   224 
       
   225             data->ignored << event1;
       
   226             data->ignored << event2;
       
   227             QApplication::postEvent(object, event1);
       
   228             QApplication::postEvent(object, event2);
       
   229         }
       
   230         if (mouseEvent->type() == QEvent::MouseMove) {
       
   231             consumed = true;
       
   232             data->state = FlickData::ManualScroll;
       
   233             data->dragPos = QCursor::pos();
       
   234             if (!d->ticker.isActive())
       
   235                 d->ticker.start(20, this);
       
   236         }
       
   237         break;
       
   238 
       
   239     case FlickData::ManualScroll:
       
   240         if (mouseEvent->type() == QEvent::MouseMove) {
       
   241             consumed = true;
       
   242             QPoint delta = mouseEvent->pos() - data->pressPos;
       
   243             setScrollOffset(data->widget, data->offset - delta);
       
   244         }
       
   245         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
       
   246             consumed = true;
       
   247             data->state = FlickData::AutoScroll;
       
   248         }
       
   249         break;
       
   250 
       
   251     case FlickData::AutoScroll:
       
   252         if (mouseEvent->type() == QEvent::MouseButtonPress) {
       
   253             consumed = true;
       
   254             data->state = FlickData::Stop;
       
   255             data->speed = QPoint(0, 0);
       
   256         }
       
   257         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
       
   258             consumed = true;
       
   259             data->state = FlickData::Steady;
       
   260             data->speed = QPoint(0, 0);
       
   261         }
       
   262         break;
       
   263 
       
   264     case FlickData::Stop:
       
   265         if (mouseEvent->type() == QEvent::MouseButtonRelease) {
       
   266             consumed = true;
       
   267             data->state = FlickData::Steady;
       
   268         }
       
   269         if (mouseEvent->type() == QEvent::MouseMove) {
       
   270             consumed = true;
       
   271             data->state = FlickData::ManualScroll;
       
   272             data->dragPos = QCursor::pos();
       
   273             if (!d->ticker.isActive())
       
   274                 d->ticker.start(20, this);
       
   275         }
       
   276         break;
       
   277 
       
   278     default:
       
   279         break;
       
   280     }
       
   281 
       
   282     return consumed;
       
   283 }
       
   284 
       
   285 void FlickCharm::timerEvent(QTimerEvent *event)
       
   286 {
       
   287     int count = 0;
       
   288     QHashIterator<QWidget*, FlickData*> item(d->flickData);
       
   289     while (item.hasNext()) {
       
   290         item.next();
       
   291         FlickData *data = item.value();
       
   292 
       
   293         if (data->state == FlickData::ManualScroll) {
       
   294             count++;
       
   295             data->speed = QCursor::pos() - data->dragPos;
       
   296             data->dragPos = QCursor::pos();
       
   297         }
       
   298 
       
   299         if (data->state == FlickData::AutoScroll) {
       
   300             count++;
       
   301             data->speed = deaccelerate(data->speed);
       
   302             QPoint p = scrollOffset(data->widget);
       
   303             setScrollOffset(data->widget, p - data->speed);
       
   304             if (data->speed == QPoint(0, 0))
       
   305                 data->state = FlickData::Steady;
       
   306         }
       
   307     }
       
   308 
       
   309     if (!count)
       
   310         d->ticker.stop();
       
   311 
       
   312     QObject::timerEvent(event);
       
   313 }