/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the demos of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.1, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "flickcharm.h" #include #include #include #include #include #include #include #include #include #include #include struct FlickData { typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State; State state; QWidget *widget; QPoint pressPos; QPoint offset; QPoint dragPos; QPoint speed; QList ignored; }; class FlickCharmPrivate { public: QHash flickData; QBasicTimer ticker; }; FlickCharm::FlickCharm(QObject *parent): QObject(parent) { d = new FlickCharmPrivate; } FlickCharm::~FlickCharm() { delete d; } void FlickCharm::activateOn(QWidget *widget) { QAbstractScrollArea *scrollArea = dynamic_cast(widget); if (scrollArea) { scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); QWidget *viewport = scrollArea->viewport(); viewport->installEventFilter(this); scrollArea->installEventFilter(this); d->flickData.remove(viewport); d->flickData[viewport] = new FlickData; d->flickData[viewport]->widget = widget; d->flickData[viewport]->state = FlickData::Steady; return; } QWebView *webView = dynamic_cast(widget); if (webView) { QWebFrame *frame = webView->page()->mainFrame(); frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); webView->installEventFilter(this); d->flickData.remove(webView); d->flickData[webView] = new FlickData; d->flickData[webView]->widget = webView; d->flickData[webView]->state = FlickData::Steady; return; } qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)"; qWarning() << "or QWebView (and derived classes)"; } void FlickCharm::deactivateFrom(QWidget *widget) { QAbstractScrollArea *scrollArea = dynamic_cast(widget); if (scrollArea) { QWidget *viewport = scrollArea->viewport(); viewport->removeEventFilter(this); scrollArea->removeEventFilter(this); delete d->flickData[viewport]; d->flickData.remove(viewport); return; } QWebView *webView = dynamic_cast(widget); if (webView) { webView->removeEventFilter(this); delete d->flickData[webView]; d->flickData.remove(webView); return; } } static QPoint scrollOffset(QWidget *widget) { int x = 0, y = 0; QAbstractScrollArea *scrollArea = dynamic_cast(widget); if (scrollArea) { x = scrollArea->horizontalScrollBar()->value(); y = scrollArea->verticalScrollBar()->value(); } QWebView *webView = dynamic_cast(widget); if (webView) { QWebFrame *frame = webView->page()->mainFrame(); x = frame->evaluateJavaScript("window.scrollX").toInt(); y = frame->evaluateJavaScript("window.scrollY").toInt(); } return QPoint(x, y); } static void setScrollOffset(QWidget *widget, const QPoint &p) { QAbstractScrollArea *scrollArea = dynamic_cast(widget); if (scrollArea) { scrollArea->horizontalScrollBar()->setValue(p.x()); scrollArea->verticalScrollBar()->setValue(p.y()); } QWebView *webView = dynamic_cast(widget); QWebFrame *frame = webView ? webView->page()->mainFrame() : 0; if (frame) frame->evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y())); } static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64) { int x = qBound(-max, speed.x(), max); int y = qBound(-max, speed.y(), max); x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a); y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a); return QPoint(x, y); } bool FlickCharm::eventFilter(QObject *object, QEvent *event) { if (!object->isWidgetType()) return false; QEvent::Type type = event->type(); if (type != QEvent::MouseButtonPress && type != QEvent::MouseButtonRelease && type != QEvent::MouseMove) return false; QMouseEvent *mouseEvent = dynamic_cast(event); if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier) return false; QWidget *viewport = dynamic_cast(object); FlickData *data = d->flickData.value(viewport); if (!viewport || !data || data->ignored.removeAll(event)) return false; bool consumed = false; switch (data->state) { case FlickData::Steady: if (mouseEvent->type() == QEvent::MouseButtonPress) if (mouseEvent->buttons() == Qt::LeftButton) { consumed = true; data->state = FlickData::Pressed; data->pressPos = mouseEvent->pos(); data->offset = scrollOffset(data->widget); } break; case FlickData::Pressed: if (mouseEvent->type() == QEvent::MouseButtonRelease) { consumed = true; data->state = FlickData::Steady; QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress, data->pressPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent *event2 = new QMouseEvent(*mouseEvent); data->ignored << event1; data->ignored << event2; QApplication::postEvent(object, event1); QApplication::postEvent(object, event2); } if (mouseEvent->type() == QEvent::MouseMove) { consumed = true; data->state = FlickData::ManualScroll; data->dragPos = QCursor::pos(); if (!d->ticker.isActive()) d->ticker.start(20, this); } break; case FlickData::ManualScroll: if (mouseEvent->type() == QEvent::MouseMove) { consumed = true; QPoint delta = mouseEvent->pos() - data->pressPos; setScrollOffset(data->widget, data->offset - delta); } if (mouseEvent->type() == QEvent::MouseButtonRelease) { consumed = true; data->state = FlickData::AutoScroll; } break; case FlickData::AutoScroll: if (mouseEvent->type() == QEvent::MouseButtonPress) { consumed = true; data->state = FlickData::Stop; data->speed = QPoint(0, 0); data->pressPos = mouseEvent->pos(); data->offset = scrollOffset(data->widget); } if (mouseEvent->type() == QEvent::MouseButtonRelease) { consumed = true; data->state = FlickData::Steady; data->speed = QPoint(0, 0); } break; case FlickData::Stop: if (mouseEvent->type() == QEvent::MouseButtonRelease) { consumed = true; data->state = FlickData::Steady; } if (mouseEvent->type() == QEvent::MouseMove) { consumed = true; data->state = FlickData::ManualScroll; data->dragPos = QCursor::pos(); if (!d->ticker.isActive()) d->ticker.start(20, this); } break; default: break; } return consumed; } void FlickCharm::timerEvent(QTimerEvent *event) { int count = 0; QHashIterator item(d->flickData); while (item.hasNext()) { item.next(); FlickData *data = item.value(); if (data->state == FlickData::ManualScroll) { count++; data->speed = QCursor::pos() - data->dragPos; data->dragPos = QCursor::pos(); } if (data->state == FlickData::AutoScroll) { count++; data->speed = deaccelerate(data->speed); QPoint p = scrollOffset(data->widget); setScrollOffset(data->widget, p - data->speed); if (data->speed == QPoint(0, 0)) data->state = FlickData::Steady; } } if (!count) d->ticker.stop(); QObject::timerEvent(event); }