QsKineticScroller.cpp

Go to the documentation of this file.
00001 // Copyright (c) 2010, Razvan Petru
00002 // All rights reserved.
00003 
00004 // Redistribution and use in source and binary forms, with or without modification,
00005 // are permitted provided that the following conditions are met:
00006 
00007 // * Redistributions of source code must retain the above copyright notice, this
00008 //   list of conditions and the following disclaimer.
00009 // * Redistributions in binary form must reproduce the above copyright notice, this
00010 //   list of conditions and the following disclaimer in the documentation and/or other
00011 //   materials provided with the distribution.
00012 // * The name of the contributors may not be used to endorse or promote products
00013 //   derived from this software without specific prior written permission.
00014 
00015 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
00016 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00017 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
00018 // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
00019 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00020 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00021 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00022 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
00023 // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
00024 // OF THE POSSIBILITY OF SUCH DAMAGE.
00025 
00026 // References:
00027 // http://blog.codeimproved.net/2010/12/kinetic-scrolling-with-qt-the-what-and-the-how/
00028 
00029 #include "QsKineticScroller.hpp"
00030 #include <QApplication>
00031 #include <QScrollBar>
00032 #include <QAbstractScrollArea>
00033 #include <QMouseEvent>
00034 #include <QEvent>
00035 #include <QTimer>
00036 #include <cstddef> // for NULL
00037 
00038 // A number of mouse moves are ignored after a press to differentiate
00039 // it from a press & drag.
00040 static const int gMaxIgnoredMouseMoves = 4;
00041 // The timer measures the drag speed & handles kinetic scrolling. Adjusting
00042 // the timer interval will change the scrolling speed and smoothness.
00043 static const int gTimerInterval = 30;
00044 // The speed measurement is imprecise, limit it so that the scrolling is not
00045 // too fast.
00046 static const int gMaxDecelerationSpeed = 30;
00047 // influences how fast the scroller decelerates
00048 static const int gFriction = 1;
00049 
00050 class QsKineticScrollerImpl
00051 {
00052 public:
00053    QsKineticScrollerImpl()
00054       : scrollArea(NULL)
00055       , isPressed(false)
00056       , isMoving(false)
00057       , lastMouseYPos(0)
00058       , lastScrollBarPosition(0)
00059       , velocity(0)
00060       , ignoredMouseMoves(0)
00061       , ignoredMouseActions(0) {}
00062 
00063    void stopMotion()
00064    {
00065       isMoving = false;
00066       velocity = 0;
00067       kineticTimer.stop();
00068    }
00069 
00070    QAbstractScrollArea* scrollArea;
00071    bool isPressed;
00072    bool isMoving;
00073    QPoint lastPressPoint;
00074    int lastMouseYPos;
00075    int lastScrollBarPosition;
00076    int velocity;
00077    int ignoredMouseMoves;
00078    int ignoredMouseActions;
00079    QTimer kineticTimer;
00080 };
00081 
00082 QsKineticScroller::QsKineticScroller(QObject *parent)
00083    : QObject(parent)
00084    , d(new QsKineticScrollerImpl)
00085 {
00086    connect(&d->kineticTimer, SIGNAL(timeout()), SLOT(onKineticTimerElapsed()));
00087 }
00088 
00089 // needed by smart pointer
00090 QsKineticScroller::~QsKineticScroller()
00091 {
00092 }
00093 
00094 void QsKineticScroller::enableKineticScrollFor(QAbstractScrollArea* scrollArea)
00095 {
00096    if( !scrollArea )
00097    {
00098       Q_ASSERT_X(0, "kinetic scroller", "missing scroll area");
00099       return;
00100    }
00101 
00102    // remove existing association
00103    if( d->scrollArea )
00104    {
00105       d->scrollArea->viewport()->removeEventFilter(this);
00106       d->scrollArea->removeEventFilter(this);
00107       d->scrollArea = NULL;
00108    }
00109 
00110    // associate
00111    scrollArea->installEventFilter(this);
00112    scrollArea->viewport()->installEventFilter(this);
00113    d->scrollArea = scrollArea;
00114 }
00115 
00116 //! intercepts mouse events to make the scrolling work
00117 bool QsKineticScroller::eventFilter(QObject* object, QEvent* event)
00118 {
00119    const QEvent::Type eventType = event->type();
00120    const bool isMouseAction = QEvent::MouseButtonPress == eventType
00121       || QEvent::MouseButtonRelease == eventType;
00122    const bool isMouseEvent = isMouseAction || QEvent::MouseMove == eventType;
00123    if( !isMouseEvent || !d->scrollArea )
00124      return false;
00125    if( isMouseAction && d->ignoredMouseActions-- > 0 ) // don't filter simulated click
00126      return false;
00127 
00128    QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
00129    switch( eventType )
00130    {
00131    case QEvent::MouseButtonPress:
00132       {
00133          d->isPressed = true;
00134          d->lastPressPoint = mouseEvent->pos();
00135          d->lastScrollBarPosition = d->scrollArea->verticalScrollBar()->value();
00136          if( d->isMoving ) // press while kinetic scrolling, so stop
00137             d->stopMotion();
00138       }
00139       break;
00140    case QEvent::MouseMove:
00141       {
00142          if( !d->isMoving )
00143          {
00144             // A few move events are ignored as "click jitter", but after that we
00145             // assume that the user is doing a click & drag
00146             if( d->ignoredMouseMoves < gMaxIgnoredMouseMoves )
00147                ++d->ignoredMouseMoves;
00148             else
00149             {
00150                d->ignoredMouseMoves = 0;
00151                d->isMoving = true;
00152                d->lastMouseYPos = mouseEvent->pos().y();
00153                if( !d->kineticTimer.isActive() )
00154                   d->kineticTimer.start(gTimerInterval);
00155             }
00156          }
00157          else
00158          {
00159             // manual scroll
00160             const int dragDistance = mouseEvent->pos().y() - d->lastPressPoint.y();
00161             d->scrollArea->verticalScrollBar()->setValue(
00162                d->lastScrollBarPosition - dragDistance);
00163          }
00164       }
00165       break;
00166    case QEvent::MouseButtonRelease:
00167       {
00168          d->isPressed = false;
00169          d->ignoredMouseMoves = 0;
00170          // Looks like the user wanted a single click. Simulate the click,
00171          // as the events were already consumed
00172          if( !d->isMoving )
00173          {
00174             QMouseEvent* mousePress = new QMouseEvent(QEvent::MouseButtonPress,
00175                d->lastPressPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
00176             QMouseEvent* mouseRelease = new QMouseEvent(QEvent::MouseButtonRelease,
00177                d->lastPressPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
00178 
00179             d->ignoredMouseActions = 2;
00180             QApplication::postEvent(object, mousePress);
00181             QApplication::postEvent(object, mouseRelease);
00182          }
00183       }
00184       break;
00185    default:
00186       break;
00187    }
00188 
00189    return true; // filter event
00190 }
00191 
00192 void QsKineticScroller::onKineticTimerElapsed()
00193 {
00194    if( d->isPressed && d->isMoving )
00195    {
00196       // the speed is measured between two timer ticks
00197       const int cursorYPos = d->scrollArea->mapFromGlobal(QCursor::pos()).y();
00198       d->velocity = cursorYPos - d->lastMouseYPos;
00199       d->lastMouseYPos = cursorYPos;
00200    }
00201    else if( !d->isPressed && d->isMoving )
00202    {
00203       // use the previously recorded speed and gradually decelerate
00204       d->velocity = qBound(-gMaxDecelerationSpeed, d->velocity, gMaxDecelerationSpeed);
00205       if( d->velocity > 0 )
00206          d->velocity -= gFriction;
00207       else if( d->velocity < 0 )
00208          d->velocity += gFriction;
00209       if( qAbs(d->velocity) < qAbs(gFriction) )
00210          d->stopMotion();
00211 
00212       const int scrollBarYPos = d->scrollArea->verticalScrollBar()->value();
00213       d->scrollArea->verticalScrollBar()->setValue(scrollBarYPos - d->velocity);
00214    }
00215    else
00216       d->stopMotion();
00217 }

ContextLogger2—ContextLogger2 Logger Daemon Internals—Generated on Mon May 2 13:49:56 2011 by Doxygen 1.6.1