diff options
Diffstat (limited to 'src/gui/util')
-rw-r--r-- | src/gui/util/qflickgesture.cpp | 677 | ||||
-rw-r--r-- | src/gui/util/qflickgesture_p.h | 113 | ||||
-rw-r--r-- | src/gui/util/qscroller.cpp | 1984 | ||||
-rw-r--r-- | src/gui/util/qscroller.h | 149 | ||||
-rw-r--r-- | src/gui/util/qscroller_mac.mm | 69 | ||||
-rw-r--r-- | src/gui/util/qscroller_p.h | 205 | ||||
-rw-r--r-- | src/gui/util/qscrollerproperties.cpp | 418 | ||||
-rw-r--r-- | src/gui/util/qscrollerproperties.h | 140 | ||||
-rw-r--r-- | src/gui/util/qscrollerproperties_p.h | 94 | ||||
-rw-r--r-- | src/gui/util/util.pri | 12 |
10 files changed, 3861 insertions, 0 deletions
diff --git a/src/gui/util/qflickgesture.cpp b/src/gui/util/qflickgesture.cpp new file mode 100644 index 0000000..eb0cc8d --- /dev/null +++ b/src/gui/util/qflickgesture.cpp @@ -0,0 +1,677 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 "qgesture.h" +#include "qapplication.h" +#include "qevent.h" +#include "qwidget.h" +#include "qgraphicsitem.h" +#include "qgraphicsscene.h" +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qscroller.h" +#include "private/qevent_p.h" +#include "private/qflickgesture_p.h" +#include "qdebug.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +//#define QFLICKGESTURE_DEBUG + +#ifdef QFLICKGESTURE_DEBUG +# define qFGDebug qDebug +#else +# define qFGDebug while (false) qDebug +#endif + +extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +static QMouseEvent *copyMouseEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: { + QMouseEvent *me = static_cast<QMouseEvent *>(e); + return new QMouseEvent(me->type(), QPoint(0, 0), me->globalPos(), me->button(), me->buttons(), me->modifiers()); + } + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseMove: { + QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(e); +#if 1 + QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress : + (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove); + return new QMouseEvent(met, QPoint(0, 0), me->screenPos(), me->button(), me->buttons(), me->modifiers()); +#else + QGraphicsSceneMouseEvent *copy = new QGraphicsSceneMouseEvent(me->type()); + copy->setPos(me->pos()); + copy->setScenePos(me->scenePos()); + copy->setScreenPos(me->screenPos()); + for (int i = 0x1; i <= 0x10; i <<= 1) { + Qt::MouseButton button = Qt::MouseButton(i); + copy->setButtonDownPos(button, me->buttonDownPos(button)); + copy->setButtonDownScenePos(button, me->buttonDownScenePos(button)); + copy->setButtonDownScreenPos(button, me->buttonDownScreenPos(button)); + } + copy->setLastPos(me->lastPos()); + copy->setLastScenePos(me->lastScenePos()); + copy->setLastScreenPos(me->lastScreenPos()); + copy->setButtons(me->buttons()); + copy->setButton(me->button()); + copy->setModifiers(me->modifiers()); + return copy; +#endif + } + default: + return 0; + } +} + +class PressDelayHandler : public QObject +{ +private: + PressDelayHandler(QObject *parent = 0) + : QObject(parent) + , pressDelayTimer(0) + , sendingEvent(false) + , mouseButton(Qt::NoButton) + , mouseTarget(0) + { } + + static PressDelayHandler *inst; + +public: + enum { + UngrabMouseBefore = 1, + RegrabMouseAfterwards = 2 + }; + + static PressDelayHandler *instance() + { + static PressDelayHandler *inst = 0; + if (!inst) + inst = new PressDelayHandler(QCoreApplication::instance()); + return inst; + } + + bool shouldEventBeIgnored(QEvent *) const + { + return sendingEvent; + } + + bool isDelaying() const + { + return !pressDelayEvent.isNull(); + } + + void pressed(QEvent *e, int delay) + { + if (!pressDelayEvent) { + pressDelayEvent.reset(copyMouseEvent(e)); + pressDelayTimer = startTimer(delay); + mouseTarget = QApplication::widgetAt(pressDelayEvent->globalPos()); + mouseButton = pressDelayEvent->button(); + qFGDebug() << "QFG: consuming/delaying mouse press"; + } else { + qFGDebug() << "QFG: NOT consuming/delaying mouse press"; + } + e->setAccepted(true); + } + + bool released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive) + { + // consume this event if the scroller was or is active + bool result = scrollerWasActive || scrollerIsActive; + + // stop the timer + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + // we still haven't even sent the press, so do it now + if (pressDelayEvent && mouseTarget && !scrollerIsActive) { + QScopedPointer<QMouseEvent> releaseEvent(copyMouseEvent(e)); + + qFGDebug() << "QFG: re-sending mouse press (due to release) for " << mouseTarget; + sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore); + + qFGDebug() << "QFG: faking mouse release (due to release) for " << mouseTarget; + sendMouseEvent(releaseEvent.data()); + + result = true; // consume this event + } else if (mouseTarget && scrollerIsActive) { + // we grabbed the mouse expicitly when the scroller became active, so undo that now + sendMouseEvent(0, UngrabMouseBefore); + } + pressDelayEvent.reset(0); + mouseTarget = 0; + return result; + } + + void scrollerWasIntercepted() + { + qFGDebug() << "QFG: deleting delayed mouse press, since scroller was only intercepted"; + if (pressDelayEvent) { + // we still haven't even sent the press, so just throw it away now + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + pressDelayEvent.reset(0); + } + mouseTarget = 0; + } + + void scrollerBecameActive() + { + if (pressDelayEvent) { + // we still haven't even sent the press, so just throw it away now + qFGDebug() << "QFG: deleting delayed mouse press, since scroller is active now"; + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + pressDelayEvent.reset(0); + mouseTarget = 0; + } else if (mouseTarget) { + // we did send a press, so we need to fake a release now + Qt::MouseButtons mouseButtons = QApplication::mouseButtons(); + + // release all pressed mouse buttons + /*for (int i = 0; i < 32; ++i) { + if (mouseButtons & (1 << i)) { + Qt::MouseButton b = static_cast<Qt::MouseButton>(1 << i); + mouseButtons &= ~b; + QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); + + qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; + QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, + b, mouseButtons, QApplication::keyboardModifiers()); + sendMouseEvent(&re); + } + }*/ + + QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); + + qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; + QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, + mouseButton, QApplication::mouseButtons() & ~mouseButton, + QApplication::keyboardModifiers()); + sendMouseEvent(&re, RegrabMouseAfterwards); + // don't clear the mouseTarget just yet, since we need to explicitly ungrab the mouse on release! + } + } + +protected: + void timerEvent(QTimerEvent *e) + { + if (e->timerId() == pressDelayTimer) { + if (pressDelayEvent && mouseTarget) { + qFGDebug() << "QFG: timer event: re-sending mouse press to " << mouseTarget; + sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore); + } + pressDelayEvent.reset(0); + + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + } + } + + void sendMouseEvent(QMouseEvent *me, int flags = 0) + { + if (mouseTarget) { + sendingEvent = true; + + QGraphicsItem *grabber = 0; + if (mouseTarget->parentWidget()) { + if (QGraphicsView *gv = qobject_cast<QGraphicsView *>(mouseTarget->parentWidget())) { + if (gv->scene()) + grabber = gv->scene()->mouseGrabberItem(); + } + } + + if (grabber && (flags & UngrabMouseBefore)) { + // GraphicsView Mouse Handling Workaround #1: + // we need to ungrab the mouse before re-sending the press, + // since the scene had already set the mouse grabber to the + // original (and consumed) event's receiver + qFGDebug() << "QFG: ungrabbing" << grabber; + grabber->ungrabMouse(); + } + + if (me) { + QMouseEvent copy(me->type(), mouseTarget->mapFromGlobal(me->globalPos()), me->globalPos(), me->button(), me->buttons(), me->modifiers()); + qt_sendSpontaneousEvent(mouseTarget, ©); + } + + if (grabber && (flags & RegrabMouseAfterwards)) { + // GraphicsView Mouse Handling Workaround #2: + // we need to re-grab the mouse after sending a faked mouse + // release, since we still need the mouse moves for the gesture + // (the scene will clear the item's mouse grabber status on + // release). + qFGDebug() << "QFG: re-grabbing" << grabber; + grabber->grabMouse(); + } + sendingEvent = false; + } + } + + +private: + int pressDelayTimer; + QScopedPointer<QMouseEvent> pressDelayEvent; + bool sendingEvent; + Qt::MouseButton mouseButton; + QPointer<QWidget> mouseTarget; +}; + + +/*! + \internal + \class QFlickGesture + \since 4.8 + \brief The QFlickGesture class describes a flicking gesture made by the user. + \ingroup gestures + The QFlickGesture is more complex than the QPanGesture that uses QScroller and QScrollerProperties + to decide if it is triggered. + This gesture is reacting on touch event as compared to the QMouseFlickGesture. + + \sa {Gestures Programming}, QScroller, QScrollerProperties, QMouseFlickGesture +*/ + +/*! + \internal +*/ +QFlickGesture::QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent) + : QGesture(*new QFlickGesturePrivate, parent) +{ + d_func()->q_ptr = this; + d_func()->receiver = receiver; + d_func()->receiverScroller = (receiver && QScroller::hasScroller(receiver)) ? QScroller::scroller(receiver) : 0; + d_func()->button = button; +} + +QFlickGesture::~QFlickGesture() +{ } + +QFlickGesturePrivate::QFlickGesturePrivate() + : receiverScroller(0), button(Qt::NoButton), macIgnoreWheel(false) +{ } + + +// +// QFlickGestureRecognizer +// + + +QFlickGestureRecognizer::QFlickGestureRecognizer(Qt::MouseButton button) +{ + this->button = button; +} + +/*! \reimp + */ +QGesture *QFlickGestureRecognizer::create(QObject *target) +{ + QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target); + if (go && button == Qt::NoButton) { + go->setAcceptTouchEvents(true); + } + return new QFlickGesture(target, button); +} + +/*! \internal + The recognize function detects a touch event suitable to start the attached QScroller. + The QFlickGesture will be triggered as soon as the scroller is no longer in the state + QScroller::Inactive or QScroller::Pressed. It will be finished or canceled + at the next QEvent::TouchEnd. + Note that the QScroller might continue scrolling (kinetically) at this point. + */ +QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state, + QObject *watched, + QEvent *event) +{ + Q_UNUSED(watched); + + static QElapsedTimer monotonicTimer; + if (!monotonicTimer.isValid()) + monotonicTimer.start(); + + QFlickGesture *q = static_cast<QFlickGesture *>(state); + QFlickGesturePrivate *d = q->d_func(); + + QScroller *scroller = d->receiverScroller; + if (!scroller) + return Ignore; // nothing to do without a scroller? + + QWidget *receiverWidget = qobject_cast<QWidget *>(d->receiver); + QGraphicsObject *receiverGraphicsObject = qobject_cast<QGraphicsObject *>(d->receiver); + + // this is only set for events that we inject into the event loop via sendEvent() + if (PressDelayHandler::instance()->shouldEventBeIgnored(event)) { + //qFGDebug() << state << "QFG: ignored event: " << event->type(); + return Ignore; + } + + const QMouseEvent *me = 0; + const QGraphicsSceneMouseEvent *gsme = 0; + const QTouchEvent *te = 0; + QPoint globalPos; + + // qFGDebug() << "FlickGesture "<<state<<"watched:"<<watched<<"receiver"<<d->receiver<<"event"<<event->type()<<"button"<<button; + + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + if (!receiverWidget) + return Ignore; + if (button != Qt::NoButton) { + me = static_cast<const QMouseEvent *>(event); + globalPos = me->globalPos(); + } + break; + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseMove: + if (!receiverGraphicsObject) + return Ignore; + if (button != Qt::NoButton) { + gsme = static_cast<const QGraphicsSceneMouseEvent *>(event); + globalPos = gsme->screenPos(); + } + break; + case QEvent::TouchBegin: + case QEvent::TouchEnd: + case QEvent::TouchUpdate: + if (button == Qt::NoButton) { + te = static_cast<const QTouchEvent *>(event); + if (!te->touchPoints().isEmpty()) + globalPos = te->touchPoints().at(0).screenPos().toPoint(); + } + break; + +#if defined(Q_WS_MAC) + // the only way to distinguish between real mouse wheels and wheel + // events generated by the native 2 finger swipe gesture is to listen + // for these events (according to Apple's Cocoa Event-Handling Guide) + + case QEvent::NativeGesture: { + QNativeGestureEvent *nge = static_cast<QNativeGestureEvent *>(event); + if (nge->gestureType == QNativeGestureEvent::GestureBegin) + d->macIgnoreWheel = true; + else if (nge->gestureType == QNativeGestureEvent::GestureEnd) + d->macIgnoreWheel = false; + break; + } +#endif + + // consume all wheel events if the scroller is active + case QEvent::Wheel: + if (d->macIgnoreWheel || (scroller->state() != QScroller::Inactive)) + return Ignore | ConsumeEventHint; + break; + + // consume all dbl click events if the scroller is active + case QEvent::MouseButtonDblClick: + if (scroller->state() != QScroller::Inactive) + return Ignore | ConsumeEventHint; + break; + + default: + break; + } + + if (!me && !gsme && !te) // Neither mouse nor touch + return Ignore; + + // get the current pointer position in local coordinates. + QPointF point; + QScroller::Input inputType = (QScroller::Input) 0; + + switch (event->type()) { + case QEvent::MouseButtonPress: + if (me && me->button() == button && me->buttons() == button) { + point = me->globalPos(); + inputType = QScroller::InputPress; + } else if (me) { + scroller->stop(); + return CancelGesture; + } + break; + case QEvent::MouseButtonRelease: + if (me && me->button() == button) { + point = me->globalPos(); + inputType = QScroller::InputRelease; + } + break; + case QEvent::MouseMove: +#ifdef Q_OS_SYMBIAN + // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix + // relies on the windowing system to report the current buttons state. + if (me && (me->buttons() == button || !me->buttons())) { +#else + if (me && me->buttons() == button) { +#endif + point = me->globalPos(); + inputType = QScroller::InputMove; + } + break; + + case QEvent::GraphicsSceneMousePress: + if (gsme && gsme->button() == button && gsme->buttons() == button) { + point = gsme->scenePos(); + inputType = QScroller::InputPress; + } else if (gsme) { + scroller->stop(); + return CancelGesture; + } + break; + case QEvent::GraphicsSceneMouseRelease: + if (gsme && gsme->button() == button) { + point = gsme->scenePos(); + inputType = QScroller::InputRelease; + } + break; + case QEvent::GraphicsSceneMouseMove: +#ifdef Q_OS_SYMBIAN + // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix + // relies on the windowing system to report the current buttons state. + if (gsme && (gsme->buttons() == button || !me->buttons())) { +#else + if (gsme && gsme->buttons() == button) { +#endif + point = gsme->scenePos(); + inputType = QScroller::InputMove; + } + break; + + case QEvent::TouchBegin: + inputType = QScroller::InputPress; + // fall through + case QEvent::TouchEnd: + if (!inputType) + inputType = QScroller::InputRelease; + // fallthrough + case QEvent::TouchUpdate: + if (!inputType) + inputType = QScroller::InputMove; + + if (te->deviceType() == QTouchEvent::TouchPad) { + if (te->touchPoints().count() != 2) // 2 fingers on pad + return Ignore; + + point = te->touchPoints().at(0).startScenePos() + + ((te->touchPoints().at(0).scenePos() - te->touchPoints().at(0).startScenePos()) + + (te->touchPoints().at(1).scenePos() - te->touchPoints().at(1).startScenePos())) / 2; + } else { // TouchScreen + if (te->touchPoints().count() != 1) // 1 finger on screen + return Ignore; + + point = te->touchPoints().at(0).scenePos(); + } + break; + + default: + break; + } + + // Check for an active scroller at globalPos + if (inputType == QScroller::InputPress) { + foreach (QScroller *as, QScroller::activeScrollers()) { + if (as != scroller) { + QRegion scrollerRegion; + + if (QWidget *w = qobject_cast<QWidget *>(as->target())) { + scrollerRegion = QRect(w->mapToGlobal(QPoint(0, 0)), w->size()); + } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(as->target())) { + if (go->scene() && !go->scene()->views().isEmpty()) { + foreach (QGraphicsView *gv, go->scene()->views()) + scrollerRegion |= gv->mapFromScene(go->mapToScene(go->boundingRect())) + .translated(gv->mapToGlobal(QPoint(0, 0))); + } + } + // active scrollers always have priority + if (scrollerRegion.contains(globalPos)) + return Ignore; + } + } + } + + bool scrollerWasDragging = (scroller->state() == QScroller::Dragging); + bool scrollerWasScrolling = (scroller->state() == QScroller::Scrolling); + + if (inputType) { + if (QWidget *w = qobject_cast<QWidget *>(d->receiver)) + point = w->mapFromGlobal(point.toPoint()); + else if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->receiver)) + point = go->mapFromScene(point); + + // inform the scroller about the new event + scroller->handleInput(inputType, point, monotonicTimer.elapsed()); + } + + // depending on the scroller state return the gesture state + Result result(0); + bool scrollerIsActive = (scroller->state() == QScroller::Dragging || + scroller->state() == QScroller::Scrolling); + + // Consume all mouse events while dragging or scrolling to avoid nasty + // side effects with Qt's standard widgets. + if ((me || gsme) && scrollerIsActive) + result |= ConsumeEventHint; + + // The only problem with this approach is that we consume the + // MouseRelease when we start the scrolling with a flick gesture, so we + // have to fake a MouseRelease "somewhere" to not mess with the internal + // states of Qt's widgets (a QPushButton would stay in 'pressed' state + // forever, if it doesn't receive a MouseRelease). + if (me || gsme) { + if (!scrollerWasDragging && !scrollerWasScrolling && scrollerIsActive) + PressDelayHandler::instance()->scrollerBecameActive(); + else if (scrollerWasScrolling && (scroller->state() == QScroller::Dragging || scroller->state() == QScroller::Inactive)) + PressDelayHandler::instance()->scrollerWasIntercepted(); + } + + if (!inputType) { + result |= Ignore; + } else { + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::GraphicsSceneMousePress: + if (scroller->state() == QScroller::Pressed) { + int pressDelay = int(1000 * scroller->scrollerProperties().scrollMetric(QScrollerProperties::MousePressEventDelay).toReal()); + if (pressDelay > 0) { + result |= ConsumeEventHint; + + PressDelayHandler::instance()->pressed(event, pressDelay); + event->accept(); + } + } + // fall through + case QEvent::TouchBegin: + q->setHotSpot(globalPos); + result |= scrollerIsActive ? TriggerGesture : MayBeGesture; + break; + + case QEvent::MouseMove: + case QEvent::GraphicsSceneMouseMove: + if (PressDelayHandler::instance()->isDelaying()) + result |= ConsumeEventHint; + // fall through + case QEvent::TouchUpdate: + result |= scrollerIsActive ? TriggerGesture : Ignore; + break; + + case QEvent::GraphicsSceneMouseRelease: + case QEvent::MouseButtonRelease: + if (PressDelayHandler::instance()->released(event, scrollerWasDragging || scrollerWasScrolling, scrollerIsActive)) + result |= ConsumeEventHint; + // fall through + case QEvent::TouchEnd: + result |= scrollerIsActive ? FinishGesture : CancelGesture; + break; + + default: + result |= Ignore; + break; + } + } + return result; +} + + +/*! \reimp + */ +void QFlickGestureRecognizer::reset(QGesture *state) +{ + QGestureRecognizer::reset(state); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES diff --git a/src/gui/util/qflickgesture_p.h b/src/gui/util/qflickgesture_p.h new file mode 100644 index 0000000..c3c263b --- /dev/null +++ b/src/gui/util/qflickgesture_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QFLICKGESTURE_P_H +#define QFLICKGESTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qevent.h" +#include "qgesturerecognizer.h" +#include "private/qgesture_p.h" +#include "qscroller.h" +#include "qscopedpointer.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +class QFlickGesturePrivate; +class QGraphicsItem; + +class Q_GUI_EXPORT QFlickGesture : public QGesture +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QFlickGesture) + +public: + QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent = 0); + ~QFlickGesture(); + + friend class QFlickGestureRecognizer; +}; + +class PressDelayHandler; + +class QFlickGesturePrivate : public QGesturePrivate +{ + Q_DECLARE_PUBLIC(QFlickGesture) +public: + QFlickGesturePrivate(); + + QPointer<QObject> receiver; + QScroller *receiverScroller; + Qt::MouseButton button; // NoButton == Touch + bool macIgnoreWheel; + static PressDelayHandler *pressDelayHandler; +}; + +class QFlickGestureRecognizer : public QGestureRecognizer +{ +public: + QFlickGestureRecognizer(Qt::MouseButton button); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); + +private: + Qt::MouseButton button; // NoButton == Touch +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#endif // QFLICKGESTURE_P_H diff --git a/src/gui/util/qscroller.cpp b/src/gui/util/qscroller.cpp new file mode 100644 index 0000000..b7b5903 --- /dev/null +++ b/src/gui/util/qscroller.cpp @@ -0,0 +1,1984 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 "qevent.h" +#include "qwidget.h" +#include "qscroller.h" +#include "private/qflickgesture_p.h" +#include "private/qscroller_p.h" +#include "qscrollerproperties.h" +#include "private/qscrollerproperties_p.h" +#include "qnumeric.h" +#include "math.h" + +#include <QTime> +#include <QElapsedTimer> +#include <QMap> +#include <QApplication> +#include <QAbstractScrollArea> +#include <QGraphicsObject> +#include <QGraphicsScene> +#include <QGraphicsView> +#include <QDesktopWidget> +#include <QtCore/qmath.h> +#include <QtGui/qevent.h> +#include <qnumeric.h> + +#include <QtDebug> + + +QT_BEGIN_NAMESPACE + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +//#define QSCROLLER_DEBUG + +#ifdef QSCROLLER_DEBUG +# define qScrollerDebug qDebug +#else +# define qScrollerDebug while (false) qDebug +#endif + +QDebug &operator<<(QDebug &dbg, const QScrollerPrivate::ScrollSegment &s) +{ + dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime; + dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos; + dbg << "\n Curve: type:" << s.curve.type() << " max progress:" << s.maxProgress << "\n"; + return dbg; +} + + +// a few helper operators to make the code below a lot more readable: +// otherwise a lot of ifs would have to be multi-line to check both the x +// and y coordinate separately. + +// returns true only if the abs. value of BOTH x and y are <= f +inline bool operator<=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f); +} + +// returns true only if the abs. value of BOTH x and y are < f +inline bool operator<(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) < f) && (qAbs(p.y()) < f); +} + +// returns true if the abs. value of EITHER x or y are >= f +inline bool operator>=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f); +} + +// returns true if the abs. value of EITHER x or y are > f +inline bool operator>(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) > f) || (qAbs(p.y()) > f); +} + +// returns a new point with both coordinates having the abs. value of the original one +inline QPointF qAbs(const QPointF &p) +{ + return QPointF(qAbs(p.x()), qAbs(p.y())); +} + +// returns a new point with all components of p1 multiplied by the corresponding components of p2 +inline QPointF operator*(const QPointF &p1, const QPointF &p2) +{ + return QPointF(p1.x() * p2.x(), p1.y() * p2.y()); +} + +// returns a new point with all components of p1 divided by the corresponding components of p2 +inline QPointF operator/(const QPointF &p1, const QPointF &p2) +{ + return QPointF(p1.x() / p2.x(), p1.y() / p2.y()); +} + +inline QPointF clampToRect(const QPointF &p, const QRectF &rect) +{ + qreal x = qBound(rect.left(), p.x(), rect.right()); + qreal y = qBound(rect.top(), p.y(), rect.bottom()); + return QPointF(x, y); +} + +// returns -1, 0 or +1 according to r being <0, ==0 or >0 +inline int qSign(qreal r) +{ + return (r < 0) ? -1 : ((r > 0) ? 1 : 0); +} + +// this version is not mathematically exact, but it just works for every +// easing curve type (even custom ones) + +static qreal differentialForProgress(const QEasingCurve &curve, qreal pos) +{ + const qreal dx = 0.01; + qreal left = (pos < qreal(0.5)) ? pos : pos - qreal(dx); + qreal right = (pos >= qreal(0.5)) ? pos : pos + qreal(dx); + qreal d = (curve.valueForProgress(right) - curve.valueForProgress(left)) / qreal(dx); + + //qScrollerDebug() << "differentialForProgress(type: " << curve.type() << ", pos: " << pos << ") = " << d; + + return d; +} + +// this version is not mathematically exact, but it just works for every +// easing curve type (even custom ones) + +static qreal progressForValue(const QEasingCurve &curve, qreal value) +{ + if (curve.type() >= QEasingCurve::InElastic && + curve.type() < QEasingCurve::Custom) { + qWarning("progressForValue(): QEasingCurves of type %d do not have an inverse, since they are not injective.", curve.type()); + return value; + } + if (value < qreal(0) || value > qreal(1)) + return value; + + qreal progress = value, left(0), right(1); + for (int iterations = 6; iterations; --iterations) { + qreal v = curve.valueForProgress(progress); + if (v < value) + left = progress; + else if (v > value) + right = progress; + else + break; + progress = (left + right) / qreal(2); + } + return progress; +} + + +class QScrollTimer : public QAbstractAnimation +{ +public: + QScrollTimer(QScrollerPrivate *_d) + : d(_d), ignoreUpdate(false), skip(0) + { } + + int duration() const + { + return -1; + } + + void start() + { + // QAbstractAnimation::start() will immediately call + // updateCurrentTime(), but our state is not set correctly yet + ignoreUpdate = true; + QAbstractAnimation::start(); + ignoreUpdate = false; + skip = 0; + } + +protected: + void updateCurrentTime(int /*currentTime*/) + { + if (!ignoreUpdate) { + if (++skip >= d->frameRateSkip()) { + skip = 0; + d->timerTick(); + } + } + } + +private: + QScrollerPrivate *d; + bool ignoreUpdate; + int skip; +}; + +/*! + \class QScroller + \brief The QScroller class enables kinetic scrolling for any scrolling widget or graphics item. + \since 4.8 + + With kinetic scrolling, the user can push the widget in a given + direction and it will continue to scroll in this direction until it is + stopped either by the user or by friction. Aspects of inertia, friction + and other physical concepts can be changed in order to fine-tune an + intuitive user experience. + + The QScroller object is the object that stores the current position and + speed of the scrolling and takes care of updates. + QScroller can be triggered by a flick gesture + + \code + QWidget *w = ...; + QScroller::grabGesture(w, QScroller::LeftMouseButtonGesture); + \endcode + + or directly like this: + + \code + QWidget *w = ...; + QScroller *scroller = QScroller::scroller(w); + scroller->scrollTo(QPointF(100, 100)); + \endcode + + The scrolled QObjects will be receive a QScrollPrepareEvent whenever the scroller needs to + update its geometry information and a QScrollEvent whenever the content of the object should + actually be scrolled. + + The scroller uses the global QAbstractAnimation timer to generate its QScrollEvents, but this + can be slowed down with QScrollerProperties::FrameRate on a per-QScroller basis. + + Several examples in the \c scroller examples directory show how QScroller, + QScrollEvent and the scroller gesture can be used. + + Even though this kinetic scroller has a huge number of settings available via + QScrollerProperties, we recommend that you leave them all at their default, platform optimized + values. In case you really want to change them you can experiment with the \c plot example in + the \c scroller examples directory first. + + \sa QScrollEvent, QScrollPrepareEvent, QScrollerProperties +*/ + + +QMap<QObject *, QScroller *> QScrollerPrivate::allScrollers; +QSet<QScroller *> QScrollerPrivate::activeScrollers; + +/*! + Returns \c true if a QScroller object was already created for \a target; \c false otherwise. + + \sa scroller() +*/ +bool QScroller::hasScroller(QObject *target) +{ + return (QScrollerPrivate::allScrollers.value(target)); +} + +/*! + Returns the scroller for the given \a target. + As long as the object exist this function will always return the same QScroller. + If a QScroller does not exist yet for the \a target, it will implicitly be created. + At no point will two QScrollers be active on one object. + + \sa hasScroller(), target() +*/ +QScroller *QScroller::scroller(QObject *target) +{ + if (!target) { + qWarning("QScroller::scroller() was called with a null target."); + return 0; + } + + if (QScrollerPrivate::allScrollers.contains(target)) + return QScrollerPrivate::allScrollers.value(target); + + QScroller *s = new QScroller(target); + QScrollerPrivate::allScrollers.insert(target, s); + return s; +} + +/*! + \overload + This is the const version of scroller(). +*/ +const QScroller *QScroller::scroller(const QObject *target) +{ + return scroller(const_cast<QObject*>(target)); +} + +/*! + Returns an application wide list of currently active, i.e. state() != + QScroller::Inactive, QScroller objects. + This routine is mostly useful when writing your own gesture recognizer. +*/ +QList<QScroller *> QScroller::activeScrollers() +{ + return QScrollerPrivate::activeScrollers.toList(); +} + +/*! + Returns the target object of this scroller. + \sa hasScroller(), scroller() + */ +QObject *QScroller::target() const +{ + Q_D(const QScroller); + return d->target; +} + +/*! + \fn QScroller::scrollerPropertiesChanged(const QScrollerProperties &newProperties); + + QScroller emits this signal whenever its scroller properties have been + changed. \a newProperties are the new scroller properties. + + \sa scrollerProperties +*/ + + +/*! \property QScroller::scrollerProperties + \brief The scroller properties of this scroller. + The properties will be used by the QScroller to determine its scrolling behaviour. +*/ +QScrollerProperties QScroller::scrollerProperties() const +{ + Q_D(const QScroller); + return d->properties; +} + +void QScroller::setScrollerProperties(const QScrollerProperties &sp) +{ + Q_D(QScroller); + if (d->properties != sp) { + d->properties = sp; + emit scrollerPropertiesChanged(sp); + + // we need to force the recalculation here, since the overshootPolicy may have changed and + // existing segments may include an overshoot animation. + d->recalcScrollingSegments(true); + } +} + + +/*! + Registers a custom scroll gesture recognizer and grabs it for the \a + target and returns the resulting gesture type. If \a scrollGestureType is + set to TouchGesture the gesture will trigger on touch events - if set to + one of LeftMouseButtonGesture, RightMouseButtonGesture or + MiddleMouseButtonGesture it will trigger on mouse events of the + corresponding button. + + Only one scroll gesture can be active on a single object at the same + time, so if you call this function twice on the same object, it will + ungrab the existing gesture before grabbing the new one. + + Please note: To avoid nasty side-effects, all mouse events will be + consumed while the gesture is triggered. Since the mouse press event is + not consumed, the gesture needs to also send a fake mouse release event + at the global position \c{(INT_MIN, INT_MIN)} to make sure that it + doesn't mess with the internal states of the widget that received the + mouse press in the first place (which could e.g. be a QPushButton + inside a QScrollArea). +*/ +Qt::GestureType QScroller::grabGesture(QObject *target, ScrollerGestureType scrollGestureType) +{ + // ensure that a scroller for target is created + QScroller *s = scroller(target); + if (!s) + return Qt::GestureType(0); + + QScrollerPrivate *sp = s->d_ptr; + if (sp->recognizer) + ungrabGesture(target); // ungrab the old gesture + + Qt::MouseButton button; + switch (scrollGestureType) { + case LeftMouseButtonGesture : button = Qt::LeftButton; break; + case RightMouseButtonGesture : button = Qt::RightButton; break; + case MiddleMouseButtonGesture: button = Qt::MiddleButton; break; + default : + case TouchGesture : button = Qt::NoButton; break; // NoButton == Touch + } + + sp->recognizer = new QFlickGestureRecognizer(button); + sp->recognizerType = QGestureRecognizer::registerRecognizer(sp->recognizer); + + if (target->isWidgetType()) { + QWidget *widget = static_cast<QWidget *>(target); + widget->grabGesture(sp->recognizerType); + if (scrollGestureType == TouchGesture) + widget->setAttribute(Qt::WA_AcceptTouchEvents); + + } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) { + if (scrollGestureType == TouchGesture) + go->setAcceptTouchEvents(true); + go->grabGesture(sp->recognizerType); + } + return sp->recognizerType; +} + +/*! + Returns the gesture type currently grabbed for the \a target or 0 if no + gesture is grabbed. +*/ +Qt::GestureType QScroller::grabbedGesture(QObject *target) +{ + QScroller *s = scroller(target); + if (s && s->d_ptr) + return s->d_ptr->recognizerType; + else + return Qt::GestureType(0); +} + +/*! + Ungrabs the gesture for the \a target. +*/ +void QScroller::ungrabGesture(QObject *target) +{ + QScroller *s = scroller(target); + if (!s) + return; + + QScrollerPrivate *sp = s->d_ptr; + if (!sp->recognizer) + return; // nothing to do + + if (target->isWidgetType()) { + QWidget *widget = static_cast<QWidget *>(target); + widget->ungrabGesture(sp->recognizerType); + + } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) { + go->ungrabGesture(sp->recognizerType); + } + + QGestureRecognizer::unregisterRecognizer(sp->recognizerType); + // do not delete the recognizer. The QGestureManager is doing this. + sp->recognizer = 0; +} + +/*! + \internal +*/ +QScroller::QScroller(QObject *target) + : d_ptr(new QScrollerPrivate(this, target)) +{ + Q_ASSERT(target); // you can't create a scroller without a target in any normal way + Q_D(QScroller); + d->init(); +} + +/*! + \internal +*/ +QScroller::~QScroller() +{ + Q_D(QScroller); + QGestureRecognizer::unregisterRecognizer(d->recognizerType); + // do not delete the recognizer. The QGestureManager is doing this. + d->recognizer = 0; + QScrollerPrivate::allScrollers.remove(d->target); + QScrollerPrivate::activeScrollers.remove(this); + + delete d_ptr; +} + + +/*! + \fn QScroller::stateChanged(QScroller::State newState); + + QScroller emits this signal whenever the state changes. \a newState is the new State. + + \sa state +*/ + +/*! + \property QScroller::state + \brief the state of the scroller + + \sa QScroller::State +*/ +QScroller::State QScroller::state() const +{ + Q_D(const QScroller); + return d->state; +} + +/*! + Stops the scroller and resets the state back to Inactive. +*/ +void QScroller::stop() +{ + Q_D(QScroller); + if (d->state != Inactive) { + QPointF here = clampToRect(d->contentPosition, d->contentPosRange); + qreal snapX = d->nextSnapPos(here.x(), 0, Qt::Horizontal); + qreal snapY = d->nextSnapPos(here.y(), 0, Qt::Vertical); + QPointF snap = here; + if (!qIsNaN(snapX)) + snap.setX(snapX); + if (!qIsNaN(snapY)) + snap.setY(snapY); + d->contentPosition = snap; + d->overshootPosition = QPointF(0, 0); + + d->setState(Inactive); + } +} + +/*! + \brief Returns the pixel per meter metric for the scrolled widget. + + The value is reported for both the x and y axis separately by using a QPointF. + + \note Please note that this value should be physically correct, while the actual DPI settings + that Qt returns for the display may be reported wrongly (on purpose) by the underlying + windowing system (e.g. Mac OS X or Maemo 5). +*/ +QPointF QScroller::pixelPerMeter() const +{ + Q_D(const QScroller); + QPointF ppm = d->pixelPerMeter; + + if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->target)) { + QTransform viewtr; + //TODO: the first view isn't really correct - maybe use an additional field in the prepare event? + if (go->scene() && !go->scene()->views().isEmpty()) + viewtr = go->scene()->views().first()->viewportTransform(); + QTransform tr = go->deviceTransform(viewtr); + if (tr.isScaling()) { + QPointF p0 = tr.map(QPointF(0, 0)); + QPointF px = tr.map(QPointF(1, 0)); + QPointF py = tr.map(QPointF(0, 1)); + ppm.rx() /= QLineF(p0, px).length(); + ppm.ry() /= QLineF(p0, py).length(); + } + } + return ppm; +} + +/*! + \brief Returns the current velocity of the scroller. + + Returns the current scrolling velocity in meter per second when in the state Scrolling. + Returns a null velocity otherwise. + + The velocity is reported for both the x and y axis separately by using a QPointF. + + \sa pixelPerMeter() +*/ +QPointF QScroller::velocity() const +{ + Q_D(const QScroller); + const QScrollerPropertiesPrivate *sp = d->properties.d.data(); + + switch (state()) { + case Dragging: + return d->releaseVelocity; + case Scrolling: { + QPointF vel; + qint64 now = d->monotonicTimer.elapsed(); + + if (!d->xSegments.isEmpty()) { + const QScrollerPrivate::ScrollSegment &s = d->xSegments.head(); + qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); + qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / s.maxProgress / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); + vel.setX(v); + } + + if (!d->ySegments.isEmpty()) { + const QScrollerPrivate::ScrollSegment &s = d->ySegments.head(); + qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); + qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / s.maxProgress / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); + vel.setY(v); + } + //qScrollerDebug() << "Velocity: " << vel; + return vel; + } + default: + return QPointF(0, 0); + } +} + +/*! + \brief Returns the target position for the scroll movement. + + Returns the planned final position for the current scroll movement or the current + position if the scroller is not in the scrolling state. + The result is undefined when the scroller is in the inactive state. + + The target position is in pixel. + + \sa pixelPerMeter(), scrollTo() +*/ +QPointF QScroller::finalPosition() const +{ + Q_D(const QScroller); + return QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal), + d->scrollingSegmentsEndPos(Qt::Vertical)); +} + +/*! + Starts scrolling the widget so that point \a pos is at the top-left position in + the viewport. + + The behaviour when scrolling outside the valid scroll area is undefined. + In this case the scroller might or might not overshoot. + + The scrolling speed will be calculated so that the given position will + be reached after a platform-defined time span (e.g. 1 second for Maemo 5). + + \a pos is given in viewport coordinates. + + \sa ensureVisible() +*/ +void QScroller::scrollTo(const QPointF &pos) +{ + // we could make this adjustable via QScrollerProperties + scrollTo(pos, 300); +} + +/*! \overload + + This version will reach its destination position in \a scrollTime milli seconds. +*/ +void QScroller::scrollTo(const QPointF &pos, int scrollTime) +{ + Q_D(QScroller); + + if (d->state == Pressed || d->state == Dragging ) + return; + + // no need to resend a prepare event if we are already scrolling + if (d->state == Inactive && !d->prepareScrolling(QPointF())) + return; + + QPointF newpos = clampToRect(pos, d->contentPosRange); + qreal snapX = d->nextSnapPos(newpos.x(), 0, Qt::Horizontal); + qreal snapY = d->nextSnapPos(newpos.y(), 0, Qt::Vertical); + if (!qIsNaN(snapX)) + newpos.setX(snapX); + if (!qIsNaN(snapY)) + newpos.setY(snapY); + + qScrollerDebug() << "QScroller::scrollTo(req:" << pos << " [pix] / snap:" << newpos << ", " << scrollTime << " [ms])"; + + if (newpos == d->contentPosition + d->overshootPosition) + return; + + QPointF vel = velocity(); + + if (scrollTime < 0) + scrollTime = 0; + qreal time = qreal(scrollTime) / 1000; + + d->createScrollToSegments(vel.x(), time, newpos.x(), Qt::Horizontal, QScrollerPrivate::ScrollTypeScrollTo); + d->createScrollToSegments(vel.y(), time, newpos.y(), Qt::Vertical, QScrollerPrivate::ScrollTypeScrollTo); + + if (!scrollTime) + d->setContentPositionHelperScrolling(); + d->setState(scrollTime ? Scrolling : Inactive); +} + +/*! + Starts scrolling so that the rectangle \a rect is visible inside the + viewport with additional margins specified in pixels by \a xmargin and \a ymargin around + the rect. + + In cases where it is not possible to fit the rect plus margins inside the viewport the contents + are scrolled so that as much as possible is visible from \a rect. + + The scrolling speed will be calculated so that the given position will + be reached after a platform-defined time span (e.g. 1 second for Maemo 5). + + This function performs the actual scrolling by calling scrollTo(). +*/ +void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin) +{ + // we could make this adjustable via QScrollerProperties + ensureVisible(rect, xmargin, ymargin, 1000); +} + +/*! \overload + + This version will reach its destination position in \a scrollTime milli seconds. +*/ +void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime) +{ + Q_D(QScroller); + + if (d->state == Pressed || d->state == Dragging ) + return; + + if (d->state == Inactive && !d->prepareScrolling(QPointF())) + return; + + // -- calculate the current pos (or the position after the current scroll) + QPointF startPos = d->contentPosition + d->overshootPosition; + startPos = QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal), + d->scrollingSegmentsEndPos(Qt::Vertical)); + + QRectF marginRect(rect.x() - xmargin, rect.y() - ymargin, + rect.width() + 2 * xmargin, rect.height() + 2 * ymargin); + + QSizeF visible = d->viewportSize; + QRectF visibleRect(startPos, visible); + + qScrollerDebug() << "QScroller::ensureVisible(" << rect << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])"; + qScrollerDebug() << " --> content position:" << d->contentPosition; + + if (visibleRect.contains(marginRect)) + return; + + QPointF newPos = startPos; + + if (visibleRect.width() < rect.width()) { + // at least try to move the rect into view + if (rect.left() > visibleRect.left()) + newPos.setX(rect.left()); + else if (rect.right() < visibleRect.right()) + newPos.setX(rect.right() - visible.width()); + + } else if (visibleRect.width() < marginRect.width()) { + newPos.setX(rect.center().x() - visibleRect.width() / 2); + } else if (marginRect.left() > visibleRect.left()) { + newPos.setX(marginRect.left()); + } else if (marginRect.right() < visibleRect.right()) { + newPos.setX(marginRect.right() - visible.width()); + } + + if (visibleRect.height() < rect.height()) { + // at least try to move the rect into view + if (rect.top() > visibleRect.top()) + newPos.setX(rect.top()); + else if (rect.bottom() < visibleRect.bottom()) + newPos.setX(rect.bottom() - visible.height()); + + } else if (visibleRect.height() < marginRect.height()) { + newPos.setY(rect.center().y() - visibleRect.height() / 2); + } else if (marginRect.top() > visibleRect.top()) { + newPos.setY(marginRect.top()); + } else if (marginRect.bottom() < visibleRect.bottom()) { + newPos.setY(marginRect.bottom() - visible.height()); + } + + // clamp to maximum content position + newPos = clampToRect(newPos, d->contentPosRange); + if (newPos == startPos) + return; + + scrollTo(newPos, scrollTime); +} + +/*! This function resends the QScrollPrepareEvent. + * Calling resendPrepareEvent triggers a QScrollPrepareEvent from the scroller. + * This will allow the receiver to re-set content position and content size while + * scrolling. + * Calling this function while in the Inactive state is useless as the prepare event + * is send again right before scrolling starts. + */ +void QScroller::resendPrepareEvent() +{ + Q_D(QScroller); + d->prepareScrolling(d->pressPosition); +} + +/*! Set the snap positions for the horizontal axis. + * Set the snap positions to a list of \a positions. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an empty list of positions. + */ +void QScroller::setSnapPositionsX(const QList<qreal> &positions) +{ + Q_D(QScroller); + d->snapPositionsX = positions; + d->snapIntervalX = 0.0; + + d->recalcScrollingSegments(); +} + +/*! Set the snap positions for the horizontal axis. + * Set the snap positions to regular spaced intervals. + * The first snap position will be at \a first from the beginning of the list. The next at \a first + \a interval and so on. + * This can be used to implement a list header. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an interval of 0.0 + */ +void QScroller::setSnapPositionsX(qreal first, qreal interval) +{ + Q_D(QScroller); + d->snapFirstX = first; + d->snapIntervalX = interval; + d->snapPositionsX.clear(); + + d->recalcScrollingSegments(); +} + +/*! Set the snap positions for the vertical axis. + * Set the snap positions to a list of \a positions. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an empty list of positions. + */ +void QScroller::setSnapPositionsY(const QList<qreal> &positions) +{ + Q_D(QScroller); + d->snapPositionsY = positions; + d->snapIntervalY = 0.0; + + d->recalcScrollingSegments(); +} + +/*! Set the snap positions for the vertical axis. + * Set the snap positions to regular spaced intervals. + * The first snap position will be at \a first. The next at \a first + \a interval and so on. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an interval of 0.0 + */ +void QScroller::setSnapPositionsY(qreal first, qreal interval) +{ + Q_D(QScroller); + d->snapFirstY = first; + d->snapIntervalY = interval; + d->snapPositionsY.clear(); + + d->recalcScrollingSegments(); +} + + + +// -------------- private ------------ + +QScrollerPrivate::QScrollerPrivate(QScroller *q, QObject *_target) + : target(_target) + , recognizer(0) + , recognizerType(Qt::CustomGesture) + , state(QScroller::Inactive) + , firstScroll(true) + , pressTimestamp(0) + , lastTimestamp(0) + , snapFirstX(-1.0) + , snapIntervalX(0.0) + , snapFirstY(-1.0) + , snapIntervalY(0.0) + , scrollTimer(new QScrollTimer(this)) + , q_ptr(q) +{ + connect(target, SIGNAL(destroyed(QObject*)), this, SLOT(targetDestroyed())); +} + +void QScrollerPrivate::init() +{ + setDpiFromWidget(0); + monotonicTimer.start(); +} + +void QScrollerPrivate::sendEvent(QObject *o, QEvent *e) +{ + qt_sendSpontaneousEvent(o, e); +} + +const char *QScrollerPrivate::stateName(QScroller::State state) +{ + switch (state) { + case QScroller::Inactive: return "inactive"; + case QScroller::Pressed: return "pressed"; + case QScroller::Dragging: return "dragging"; + case QScroller::Scrolling: return "scrolling"; + default: return "(invalid)"; + } +} + +const char *QScrollerPrivate::inputName(QScroller::Input input) +{ + switch (input) { + case QScroller::InputPress: return "press"; + case QScroller::InputMove: return "move"; + case QScroller::InputRelease: return "release"; + default: return "(invalid)"; + } +} + +void QScrollerPrivate::targetDestroyed() +{ + scrollTimer->stop(); + delete q_ptr; +} + +void QScrollerPrivate::timerTick() +{ + struct timerevent { + QScroller::State state; + typedef void (QScrollerPrivate::*timerhandler_t)(); + timerhandler_t handler; + }; + + timerevent timerevents[] = { + { QScroller::Dragging, &QScrollerPrivate::timerEventWhileDragging }, + { QScroller::Scrolling, &QScrollerPrivate::timerEventWhileScrolling }, + }; + + for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) { + timerevent *te = timerevents + i; + + if (state == te->state) { + (this->*te->handler)(); + return; + } + } + + scrollTimer->stop(); +} + +/*! + This function is used by gesture recognizers to inform the scroller about a new input event. + The scroller will change its internal state() according to the input event and its attached + scroller properties. Since the scroller doesn't care about the actual kind of input device the + event came from, you need to decompose the event into the \a input type, a \a position and a + milli-second \a timestamp. The \a position needs to be in the target's coordinate system. + The return value is \c true if the event should be consumed by the calling filter or \c false + if the event should be forwarded to the control. + + \note Using grabGesture() should be sufficient for most use cases though. +*/ +bool QScroller::handleInput(Input input, const QPointF &position, qint64 timestamp) +{ + Q_D(QScroller); + + qScrollerDebug() << "QScroller::handleInput(" << input << ", " << d->stateName(d->state) << ", " << position << ", " << timestamp << ")"; + struct statechange { + State state; + Input input; + typedef bool (QScrollerPrivate::*inputhandler_t)(const QPointF &position, qint64 timestamp); + inputhandler_t handler; + }; + + statechange statechanges[] = { + { QScroller::Inactive, InputPress, &QScrollerPrivate::pressWhileInactive }, + { QScroller::Pressed, InputMove, &QScrollerPrivate::moveWhilePressed }, + { QScroller::Pressed, InputRelease, &QScrollerPrivate::releaseWhilePressed }, + { QScroller::Dragging, InputMove, &QScrollerPrivate::moveWhileDragging }, + { QScroller::Dragging, InputRelease, &QScrollerPrivate::releaseWhileDragging }, + { QScroller::Scrolling, InputPress, &QScrollerPrivate::pressWhileScrolling } + }; + + for (int i = 0; i < int(sizeof(statechanges) / sizeof(*statechanges)); ++i) { + statechange *sc = statechanges + i; + + if (d->state == sc->state && input == sc->input) + return (d->*sc->handler)(position - d->overshootPosition, timestamp); + } + return false; +} + +#ifdef Q_WS_MAEMO_5 + +QPointF QScrollerPrivate::realDpi(int screen) +{ + Q_UNUSED(screen); + // The DPI value is hardcoded to 96 on Maemo5: + // https://projects.maemo.org/bugzilla/show_bug.cgi?id=152525 + // This value (260) is only correct for the N900 though, but + // there's no way to get the real DPI at run time. + return QPointF(260, 260); +} + +#elif defined(Q_WS_MAC) + +// implemented in qscroller_mac.mm + +#else + +QPointF QScrollerPrivate::realDpi(int screen) +{ + QWidget *w = QApplication::desktop()->screen(screen); + return QPointF(w->physicalDpiX(), w->physicalDpiY()); +} + +#endif + +/*! \internal + Returns the resolution of the used screen. +*/ +QPointF QScrollerPrivate::dpi() const +{ + return pixelPerMeter / qreal(39.3700787); +} + +/*! \internal + Sets the resolution used for scrolling. + This resolution is only used by the kinetic scroller. If you change this + then the scroller will behave quite different as a lot of the values are + given in physical distances (millimeter). +*/ +void QScrollerPrivate::setDpi(const QPointF &dpi) +{ + pixelPerMeter = dpi * qreal(39.3700787); +} + +/*! \internal + Sets the dpi used for scrolling to the value of the widget. +*/ +void QScrollerPrivate::setDpiFromWidget(QWidget *widget) +{ + QDesktopWidget *dw = QApplication::desktop(); + setDpi(realDpi(widget ? dw->screenNumber(widget) : dw->primaryScreen())); +} + +/*! \internal + Updates the velocity during dragging. + Sets releaseVelocity. +*/ +void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime) +{ + Q_Q(QScroller); + QPointF ppm = q->pixelPerMeter(); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + QPointF deltaPixel = deltaPixelRaw; + + qScrollerDebug() << "QScroller::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])"; + + // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms) + if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / ((ppm.x() + ppm.y()) / 2) * 1000) > qreal(2.5)) + deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength(); + + qreal inversSmoothingFactor = ((qreal(1) - sp->dragVelocitySmoothingFactor) * qreal(deltaTime) / qreal(1000)); + QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm; + newv = newv * (qreal(1) - inversSmoothingFactor) + releaseVelocity * inversSmoothingFactor; + + if (deltaPixel.x()) + releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity)); + if (deltaPixel.y()) + releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity)); + + qScrollerDebug() << " --> new velocity:" << releaseVelocity; +} + +void QScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal startPos, qreal endPos, QEasingCurve::Type curve, Qt::Orientation orientation, qreal maxProgress) +{ + if (startPos == endPos) + return; + + ScrollSegment s; + if (orientation == Qt::Horizontal && !xSegments.isEmpty()) + s.startTime = xSegments.last().startTime + xSegments.last().deltaTime; + else if (orientation == Qt::Vertical && !ySegments.isEmpty()) + s.startTime = ySegments.last().startTime + ySegments.last().deltaTime; + else + s.startTime = monotonicTimer.elapsed(); + + s.startPos = startPos; + s.deltaPos = endPos - startPos; + s.deltaTime = deltaTime * 1000; + s.maxProgress = maxProgress; + s.curve.setType(curve); + s.type = type; + + if (orientation == Qt::Horizontal) + xSegments.enqueue(s); + else + ySegments.enqueue(s); + + qScrollerDebug() << "+++ Added a new ScrollSegment: " << s; +} + + +/*! \internal + Clears the old segments and recalculates them if the current segments are not longer valid +*/ +void QScrollerPrivate::recalcScrollingSegments(bool forceRecalc) +{ + Q_Q(QScroller); + QPointF ppm = q->pixelPerMeter(); + + releaseVelocity = q->velocity(); + + if (forceRecalc || !scrollingSegmentsValid(Qt::Horizontal)) + createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal); + + if (forceRecalc || !scrollingSegmentsValid(Qt::Vertical)) + createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical); +} + +/*! \internal + Returns the end position after the current scroll has finished. +*/ +qreal QScrollerPrivate::scrollingSegmentsEndPos(Qt::Orientation orientation) const +{ + const QQueue<ScrollSegment> *segments; + qreal endPos; + + if (orientation == Qt::Horizontal) { + segments = &xSegments; + endPos = contentPosition.x() + overshootPosition.x(); + } else { + segments = &ySegments; + endPos = contentPosition.y() + overshootPosition.y(); + } + + if (!segments->isEmpty()) { + const ScrollSegment &last = segments->last(); + endPos = last.startPos + last.deltaPos; + } + + return endPos; +} + +/*! \internal + Checks if the scroller segment end in a valid position. +*/ +bool QScrollerPrivate::scrollingSegmentsValid(Qt::Orientation orientation) +{ + QQueue<ScrollSegment> *segments; + qreal minPos; + qreal maxPos; + + if (orientation == Qt::Horizontal) { + segments = &xSegments; + minPos = contentPosRange.left(); + maxPos = contentPosRange.right(); + } else { + segments = &ySegments; + minPos = contentPosRange.top(); + maxPos = contentPosRange.bottom(); + } + + if (segments->isEmpty()) + return true; + + const ScrollSegment &last = segments->last(); + qreal endPos = last.startPos + last.deltaPos; + + if (last.type == ScrollTypeScrollTo) + return true; // scrollTo is always valid + + if (last.type == ScrollTypeOvershoot && + endPos != minPos && endPos != maxPos) + return false; + + if (endPos < minPos || endPos > maxPos) + return false; + + if (endPos == minPos || endPos == maxPos) // the begin and the end of the list are always ok + return true; + + qreal nextSnap = nextSnapPos(endPos, 0, orientation); + if (!qIsNaN(nextSnap) && endPos != nextSnap) + return false; + + return true; +} + +/*! \internal + Creates the sections needed to scroll to the specific \a endPos to the segments queue. +*/ +void QScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type) +{ + Q_UNUSED(v); + + if (orientation == Qt::Horizontal) { + xSegments.clear(); + } else { + ySegments.clear(); + } + + qScrollerDebug() << "+++ createScrollToSegments: t:" << deltaTime << "ep:" << endPos << "o:" << int(orientation); + + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x() + : contentPosition.y() + overshootPosition.y(); + qreal deltaPos = endPos - startPos; + + pushSegment(type, deltaTime * 0.3, startPos, startPos + deltaPos * 0.5, QEasingCurve::InQuad, orientation); + pushSegment(type, deltaTime * 0.7, startPos + deltaPos * 0.5, endPos, sp->scrollingCurve.type(), orientation); +} + +/*! \internal +*/ +void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation) +{ + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + QScrollerProperties::OvershootPolicy policy; + qreal minPos; + qreal maxPos; + qreal viewSize; + + if (orientation == Qt::Horizontal) { + xSegments.clear(); + policy = sp->hOvershootPolicy; + minPos = contentPosRange.left(); + maxPos = contentPosRange.right(); + viewSize = viewportSize.width(); + } else { + ySegments.clear(); + policy = sp->vOvershootPolicy; + minPos = contentPosRange.top(); + maxPos = contentPosRange.bottom(); + viewSize = viewportSize.height(); + } + + bool alwaysOvershoot = (policy == QScrollerProperties::OvershootAlwaysOn); + bool noOvershoot = (policy == QScrollerProperties::OvershootAlwaysOff) || !sp->overshootScrollDistanceFactor; + bool canOvershoot = !noOvershoot && (alwaysOvershoot || maxPos); + + qScrollerDebug() << "+++ createScrollingSegments: s:" << startPos << "maxPos:" << maxPos << "o:" << int(orientation); + + // -- check if we are in overshoot + if (startPos < minPos) { + createScrollToSegments(v, sp->overshootScrollTime * 0.5, minPos, orientation, ScrollTypeOvershoot); + return; + } + + if (startPos > maxPos) { + createScrollToSegments(v, sp->overshootScrollTime * 0.5, maxPos, orientation, ScrollTypeOvershoot); + return; + } + + qScrollerDebug() << "v = " << v << ", decelerationFactor = " << sp->decelerationFactor << ", curveType = " << sp->scrollingCurve.type(); + + // This is only correct for QEasingCurve::OutQuad (linear velocity, + // constant deceleration), but the results look and feel ok for OutExpo + // and OutSine as well + + // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime) + // v(0) = vrelease + // v(deltaTime) = 0 + // deltaTime = (2 * vrelease) / (a * differntial(0)) + + // pos(t) = integrate(v(t)dt) + // pos(t) = vrelease * t - 0.5 * a * t * t + // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime + // deltaPos = pos(deltaTime) + + qreal deltaTime = (qreal(2) * qAbs(v)) / (sp->decelerationFactor * differentialForProgress(sp->scrollingCurve, 0)); + qreal deltaPos = qSign(v) * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor * ppm; + qreal endPos = startPos + deltaPos; + + qScrollerDebug() << " Real Delta:" << deltaPos; + + // -- determine snap points + qreal nextSnap = nextSnapPos(endPos, 0, orientation); + qreal lowerSnapPos = nextSnapPos(startPos, -1, orientation); + qreal higherSnapPos = nextSnapPos(startPos, 1, orientation); + + qScrollerDebug() << " Real Delta:" << lowerSnapPos <<"-"<<nextSnap <<"-"<<higherSnapPos; + + // - check if we can reach another snap point + if (nextSnap > higherSnapPos || qIsNaN(higherSnapPos)) + higherSnapPos = nextSnap; + if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos)) + lowerSnapPos = nextSnap; + + if (qAbs(v) < sp->minimumVelocity) { + + qScrollerDebug() << "### below minimum Vel" << orientation; + + // - no snap points or already at one + if (qIsNaN(nextSnap) || nextSnap == startPos) + return; // nothing to do, no scrolling needed. + + // - decide which point to use + + qreal snapDistance = higherSnapPos - lowerSnapPos; + + qreal pressDistance = (orientation == Qt::Horizontal) ? + lastPosition.x() - pressPosition.x() : + lastPosition.y() - pressPosition.y(); + + // if not dragged far enough, pick the next snap point. + if (sp->snapPositionRatio == 0.0 || qAbs(pressDistance / sp->snapPositionRatio) > snapDistance) + endPos = nextSnap; + else if (pressDistance < 0.0) + endPos = lowerSnapPos; + else + endPos = higherSnapPos; + + deltaPos = endPos - startPos; + pushSegment(ScrollTypeFlick, sp->snapTime * 0.3, startPos, startPos + deltaPos * 0.3, QEasingCurve::InQuad, orientation); + pushSegment(ScrollTypeFlick, sp->snapTime * 0.7, startPos + deltaPos * 0.3, endPos, sp->scrollingCurve.type(), orientation); + return; + } + + // - go to the next snappoint if there is one + if (v > 0 && !qIsNaN(higherSnapPos)) { + // change the time in relation to the changed end position + if (endPos - startPos) + deltaTime *= qAbs((higherSnapPos - startPos) / (endPos - startPos)); + if (deltaTime > sp->snapTime) + deltaTime = sp->snapTime; + endPos = higherSnapPos; + + } else if (v < 0 && !qIsNaN(lowerSnapPos)) { + // change the time in relation to the changed end position + if (endPos - startPos) + deltaTime *= qAbs((lowerSnapPos - startPos) / (endPos - startPos)); + if (deltaTime > sp->snapTime) + deltaTime = sp->snapTime; + endPos = lowerSnapPos; + + // -- check if we are overshooting + } else if (endPos < minPos || endPos > maxPos) { + qreal stopPos = endPos < minPos ? minPos : maxPos; + + qScrollerDebug() << "Overshoot: delta:" << (stopPos - startPos); + + qreal maxProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos)); + qScrollerDebug() << "Overshoot maxp:" << maxProgress; + + pushSegment(ScrollTypeFlick, deltaTime * maxProgress, startPos, stopPos, sp->scrollingCurve.type(), orientation, maxProgress); + + if (canOvershoot) { + qreal endV = qSign(v) * deltaTime * sp->decelerationFactor * qreal(0.5) * differentialForProgress(sp->scrollingCurve, maxProgress); + qScrollerDebug() << "Overshoot: velocity" << endV; + qScrollerDebug() << "Overshoot: maxVelocity" << sp->maximumVelocity; + qScrollerDebug() << "Overshoot: viewsize" << viewSize; + qScrollerDebug() << "Overshoot: factor" << sp->overshootScrollDistanceFactor; + + qreal oDistance = viewSize * sp->overshootScrollDistanceFactor * endV / sp->maximumVelocity; + qreal oDeltaTime = sp->overshootScrollTime; + + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.5, stopPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation); + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.3, stopPos + oDistance, stopPos + oDistance * 0.3, QEasingCurve::InQuad, orientation); + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.2, stopPos + oDistance * 0.3, stopPos, QEasingCurve::OutQuad, orientation); + } + return; + } + + pushSegment(ScrollTypeFlick, deltaTime, startPos, endPos, sp->scrollingCurve.type(), orientation); +} + + +/*! Prepares scrolling by sending a QScrollPrepareEvent to the receiver widget. + Returns true if the scrolling was accepted and a target was returned. +*/ +bool QScrollerPrivate::prepareScrolling(const QPointF &position) +{ + QScrollPrepareEvent spe(position); + spe.ignore(); + sendEvent(target, &spe); + + qScrollerDebug() << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted() << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos(); + if (spe.isAccepted()) { + QPointF oldContentPos = contentPosition + overshootPosition; + QPointF contentDelta = spe.contentPos() - oldContentPos; + + viewportSize = spe.viewportSize(); + contentPosRange = spe.contentPosRange(); + if (contentPosRange.width() < 0) + contentPosRange.setWidth(0); + if (contentPosRange.height() < 0) + contentPosRange.setHeight(0); + contentPosition = clampToRect(spe.contentPos(), contentPosRange); + overshootPosition = spe.contentPos() - contentPosition; + + // - check if the content position was moved + if (contentDelta != QPointF(0, 0)) { + // need to correct all segments + for (int i = 0; i < xSegments.count(); i++) + xSegments[i].startPos -= contentDelta.x(); + + for (int i = 0; i < ySegments.count(); i++) + ySegments[i].startPos -= contentDelta.y(); + } + + if (QWidget *w = qobject_cast<QWidget *>(target)) + setDpiFromWidget(w); + if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(target)) { + //TODO: the first view isn't really correct - maybe use an additional field in the prepare event? + if (go->scene() && !go->scene()->views().isEmpty()) + setDpiFromWidget(go->scene()->views().first()); + } + + if (state == QScroller::Scrolling) { + recalcScrollingSegments(); + } + return true; + } + + return false; +} + +void QScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp) +{ + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + QPointF deltaPixel = position - lastPosition; + qint64 deltaTime = timestamp - lastTimestamp; + + if (sp->axisLockThreshold) { + int dx = qAbs(deltaPixel.x()); + int dy = qAbs(deltaPixel.y()); + if (dx || dy) { + bool vertical = (dy > dx); + qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx); + //qScrollerDebug() << "QScroller::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy; + if (alpha <= sp->axisLockThreshold) { + if (vertical) + deltaPixel.setX(0); + else + deltaPixel.setY(0); + } + } + } + + // calculate velocity (if the user would release the mouse NOW) + updateVelocity(deltaPixel, deltaTime); + + // restrict velocity, if content is not scrollable + QRectF max = contentPosRange; + bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + + if (!canScrollX) { + deltaPixel.setX(0); + releaseVelocity.setX(0); + } + if (!canScrollY) { + deltaPixel.setY(0); + releaseVelocity.setY(0); + } + +// if (firstDrag) { +// // Do not delay the first drag +// setContentPositionHelper(q->contentPosition() - overshootDistance - deltaPixel); +// dragDistance = QPointF(0, 0); +// } else { + dragDistance += deltaPixel; +// } +//qScrollerDebug() << "######################" << deltaPixel << position.y() << lastPosition.y(); + if (canScrollX) + lastPosition.setX(position.x()); + if (canScrollY) + lastPosition.setY(position.y()); + lastTimestamp = timestamp; +} + +bool QScrollerPrivate::pressWhileInactive(const QPointF &position, qint64 timestamp) +{ + if (prepareScrolling(position)) { + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + if (!contentPosRange.isNull() || + (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) || + (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)) { + + lastPosition = pressPosition = position; + lastTimestamp = pressTimestamp = timestamp; + setState(QScroller::Pressed); + } + } + return false; +} + +bool QScrollerPrivate::releaseWhilePressed(const QPointF &, qint64) +{ + if (overshootPosition != QPointF(0.0, 0.0)) { + setState(QScroller::Scrolling); + return true; + } else { + setState(QScroller::Inactive); + return false; + } +} + +bool QScrollerPrivate::moveWhilePressed(const QPointF &position, qint64 timestamp) +{ + Q_Q(QScroller); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + QPointF ppm = q->pixelPerMeter(); + + QPointF deltaPixel = position - pressPosition; + + bool moveAborted = false; + bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance); + + // check the direction of the mouse drag and abort if it's too much in the wrong direction. + if (moveStarted) { + QRectF max = contentPosRange; + bool canScrollX = (max.width() > 0); + bool canScrollY = (max.height() > 0); + + if (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) + canScrollX = true; + if (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) + canScrollY = true; + + if (qAbs(deltaPixel.x() / ppm.x()) < qAbs(deltaPixel.y() / ppm.y())) { + if (!canScrollY) + moveAborted = true; + } else { + if (!canScrollX) + moveAborted = true; + } + } + + if (moveAborted) { + setState(QScroller::Inactive); + moveStarted = false; + + } else if (moveStarted) { + setState(QScroller::Dragging); + + // subtract the dragStartDistance + deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength()); + + if (deltaPixel != QPointF(0, 0)) { + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(pressPosition + deltaPixel, timestamp); + } + } + return moveStarted; +} + +bool QScrollerPrivate::moveWhileDragging(const QPointF &position, qint64 timestamp) +{ + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(position, timestamp); + return true; +} + +void QScrollerPrivate::timerEventWhileDragging() +{ + if (dragDistance != QPointF(0, 0)) { + qScrollerDebug() << "QScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance; + + setContentPositionHelperDragging(-dragDistance); + dragDistance = QPointF(0, 0); + } +} + +bool QScrollerPrivate::releaseWhileDragging(const QPointF &position, qint64 timestamp) +{ + Q_Q(QScroller); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + // check if we moved at all - this can happen if you stop a running + // scroller with a press and release shortly afterwards + QPointF deltaPixel = position - pressPosition; + if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) { + + // handle accelerating flicks + if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime && + ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) { + + // - determine if the direction was changed + int signX = 0, signY = 0; + if (releaseVelocity.x()) + signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1; + if (releaseVelocity.y()) + signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1; + + if (signX > 0) + releaseVelocity.setX(qBound(-sp->maximumVelocity, + oldVelocity.x() * sp->acceleratingFlickSpeedupFactor, + sp->maximumVelocity)); + if (signY > 0) + releaseVelocity.setY(qBound(-sp->maximumVelocity, + oldVelocity.y() * sp->acceleratingFlickSpeedupFactor, + sp->maximumVelocity)); + } + } + + QPointF ppm = q->pixelPerMeter(); + createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal); + createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical); + + qScrollerDebug() << "QScroller::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition; + + if (xSegments.isEmpty() && ySegments.isEmpty()) + setState(QScroller::Inactive); + else + setState(QScroller::Scrolling); + + return true; +} + +void QScrollerPrivate::timerEventWhileScrolling() +{ + qScrollerDebug() << "QScroller::timerEventWhileScrolling()"; + + setContentPositionHelperScrolling(); + if (xSegments.isEmpty() && ySegments.isEmpty()) + setState(QScroller::Inactive); +} + +bool QScrollerPrivate::pressWhileScrolling(const QPointF &position, qint64 timestamp) +{ + Q_Q(QScroller); + + if ((q->velocity() <= properties.d->maximumClickThroughVelocity) && + (overshootPosition == QPointF(0.0, 0.0))) { + setState(QScroller::Inactive); + return false; + } else { + lastPosition = pressPosition = position; + lastTimestamp = pressTimestamp = timestamp; + setState(QScroller::Pressed); + setState(QScroller::Dragging); + return true; + } +} + +/*! \internal + This function handles all state changes of the scroller. +*/ +void QScrollerPrivate::setState(QScroller::State newstate) +{ + Q_Q(QScroller); + bool sendLastScroll = false; + + if (state == newstate) + return; + + qScrollerDebug() << q << "QScroller::setState(" << stateName(newstate) << ")"; + + switch (newstate) { + case QScroller::Inactive: + scrollTimer->stop(); + + // send the last scroll event (but only after the current state change was finished) + if (!firstScroll) + sendLastScroll = true; + + releaseVelocity = QPointF(0, 0); + break; + + case QScroller::Pressed: + scrollTimer->stop(); + + oldVelocity = releaseVelocity; + releaseVelocity = QPointF(0, 0); + break; + + case QScroller::Dragging: + dragDistance = QPointF(0, 0); + if (state == QScroller::Pressed) + scrollTimer->start(); + break; + + case QScroller::Scrolling: + scrollTimer->start(); + break; + } + + qSwap(state, newstate); + + if (sendLastScroll) { + QScrollEvent se(contentPosition, overshootPosition, QScrollEvent::ScrollFinished); + sendEvent(target, &se); + firstScroll = true; + } + if (state == QScroller::Dragging || state == QScroller::Scrolling) + activeScrollers.insert(q); + else + activeScrollers.remove(q); + emit q->stateChanged(state); +} + + +/*! \internal + Helps when setting the content position. + It will try to move the content by the requested delta but stop in case + when we are coming back from an overshoot or a scrollTo. + It will also indicate a new overshooting condition by the overshootX and oversthootY flags. + + In this cases it will reset the velocity variables and other flags. + + Also keeps track of the current over-shooting value in overshootPosition. + + \a deltaPos is the amount of pixels the current content position should be moved +*/ +void QScrollerPrivate::setContentPositionHelperDragging(const QPointF &deltaPos) +{ + Q_Q(QScroller); + QPointF ppm = q->pixelPerMeter(); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + QPointF v = q->velocity(); + + if (sp->overshootDragResistanceFactor) + overshootPosition /= sp->overshootDragResistanceFactor; + + QPointF oldPos = contentPosition + overshootPosition; + QPointF newPos = oldPos + deltaPos; + + qScrollerDebug() << "QScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])"; + qScrollerDebug() << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos; + + QPointF oldClampedPos = clampToRect(oldPos, contentPosRange); + QPointF newClampedPos = clampToRect(newPos, contentPosRange); + + // --- handle overshooting and stop if the coordinate is going back inside the normal area + bool alwaysOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + bool alwaysOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + bool noOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) || + ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) || + !sp->overshootDragDistanceFactor; + bool noOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) || + ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) || + !sp->overshootDragDistanceFactor; + bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width()); + bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height()); + + qreal oldOvershootX = (canOvershootX) ? oldPos.x() - oldClampedPos.x() : 0; + qreal oldOvershootY = (canOvershootY) ? oldPos.y() - oldClampedPos.y() : 0; + + qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0; + qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0; + + qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor; + qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor; + + qScrollerDebug() << " --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<<sp->hOvershootPolicy; + qScrollerDebug() << " --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX; + + if (sp->overshootDragResistanceFactor) { + oldOvershootX *= sp->overshootDragResistanceFactor; + oldOvershootY *= sp->overshootDragResistanceFactor; + newOvershootX *= sp->overshootDragResistanceFactor; + newOvershootY *= sp->overshootDragResistanceFactor; + } + + // -- stop at the maximum overshoot distance + + newOvershootX = qBound(-maxOvershootX, newOvershootX, maxOvershootX); + newOvershootY = qBound(-maxOvershootY, newOvershootY, maxOvershootY); + + overshootPosition.setX(newOvershootX); + overshootPosition.setY(newOvershootY); + contentPosition = newClampedPos; + + QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated); + sendEvent(target, &se); + firstScroll = false; + + qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition << + "- overshoot x/y?:" << overshootPosition; +} + + +qreal QScrollerPrivate::nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos) +{ + qreal pos = oldPos; + + // check the X segments for new positions + while (!segments.isEmpty()) { + const ScrollSegment s = segments.head(); + + if ((s.startTime + s.deltaTime) <= now) { + segments.dequeue(); + pos = s.startPos + s.deltaPos; + } else if (s.startTime <= now) { + qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); + pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress) / s.curve.valueForProgress(s.maxProgress); + break; + } else { + break; + } + } + return pos; +} + +void QScrollerPrivate::setContentPositionHelperScrolling() +{ + qint64 now = monotonicTimer.elapsed(); + QPointF newPos = contentPosition + overshootPosition; + + newPos.setX(nextSegmentPosition(xSegments, now, newPos.x())); + newPos.setY(nextSegmentPosition(ySegments, now, newPos.y())); + + // -- set the position and handle overshoot + qScrollerDebug() << "QScroller::setContentPositionHelperScrolling()"; + qScrollerDebug() << " --> overshoot:" << overshootPosition << "- new pos:" << newPos; + + QPointF newClampedPos = clampToRect(newPos, contentPosRange); + + overshootPosition = newPos - newClampedPos; + contentPosition = newClampedPos; + + QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated); + sendEvent(target, &se); + firstScroll = false; + + qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition; +} + +/*! \internal + * Returns the next snap point in direction. + * If \a direction >0 it will return the next snap point that is larger than the current position. + * If \a direction <0 it will return the next snap point that is smaller than the current position. + * If \a direction ==0 it will return the nearest snap point (or the current position if we are already + * on a snap point. + * Returns the nearest snap position or NaN if no such point could be found. + */ +qreal QScrollerPrivate::nextSnapPos(qreal p, int dir, Qt::Orientation orientation) +{ + qreal bestSnapPos = Q_QNAN; + qreal bestSnapPosDist = Q_INFINITY; + + qreal minPos; + qreal maxPos; + + if (orientation == Qt::Horizontal) { + minPos = contentPosRange.left(); + maxPos = contentPosRange.right(); + } else { + minPos = contentPosRange.top(); + maxPos = contentPosRange.bottom(); + } + + if (orientation == Qt::Horizontal) { + // the snap points in the list + foreach (qreal snapPos, snapPositionsX) { + qreal snapPosDist = snapPos - p; + if ((dir > 0 && snapPosDist < 0) || + (dir < 0 && snapPosDist > 0)) + continue; // wrong direction + if (snapPos < minPos || snapPos > maxPos ) + continue; // invalid + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist ) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + + // the snap point interval + if (snapIntervalX > 0.0) { + qreal first = minPos + snapFirstX; + qreal snapPos; + if (dir > 0) + snapPos = qCeil((p - first) / snapIntervalX) * snapIntervalX + first; + else if (dir < 0) + snapPos = qFloor((p - first) / snapIntervalX) * snapIntervalX + first; + else if (p <= first) + snapPos = first; + else + { + qreal last = qFloor((maxPos - first) / snapIntervalX) * snapIntervalX + first; + if (p >= last) + snapPos = last; + else + snapPos = qRound((p - first) / snapIntervalX) * snapIntervalX + first; + } + + if (snapPos >= first && snapPos <= maxPos ) { + qreal snapPosDist = snapPos - p; + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist ) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + } + + } else { // (orientation == Qt::Vertical) + // the snap points in the list + foreach (qreal snapPos, snapPositionsY) { + qreal snapPosDist = snapPos - p; + if ((dir > 0 && snapPosDist < 0) || + (dir < 0 && snapPosDist > 0)) + continue; // wrong direction + if (snapPos < minPos || snapPos > maxPos ) + continue; // invalid + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + + // the snap point interval + if (snapIntervalY > 0.0) { + qreal first = minPos + snapFirstY; + qreal snapPos; + if (dir > 0) + snapPos = qCeil((p - first) / snapIntervalY) * snapIntervalY + first; + else if (dir < 0) + snapPos = qFloor((p - first) / snapIntervalY) * snapIntervalY + first; + else if (p <= first) + snapPos = first; + else + { + qreal last = qFloor((maxPos - first) / snapIntervalY) * snapIntervalY + first; + if (p >= last) + snapPos = last; + else + snapPos = qRound((p - first) / snapIntervalY) * snapIntervalY + first; + } + + if (snapPos >= first && snapPos <= maxPos ) { + qreal snapPosDist = snapPos - p; + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + } + } + + return bestSnapPos; +} + +/*! + \enum QScroller::State + + This enum contains the different QScroller states. + + \value Inactive The scroller is not scrolling and nothing is pressed. + \value Pressed A touch event was received or the mouse button pressed but the scroll area is currently not dragged. + \value Dragging The scroll area is currently following the touch point or mouse. + \value Scrolling The scroll area is moving on it's own. +*/ + +/*! + \enum QScroller::ScrollerGestureType + + This enum contains the different gesture types that are supported by the QScroller gesture recognizer. + + \value TouchGesture The gesture recognizer will only trigger on touch + events. Specifically it will react on single touch points when using a + touch screen and dual touch points when using a touchpad. + \value LeftMouseButtonGesture The gesture recognizer will only trigger on left mouse button events. + \value MiddleMouseButtonGesture The gesture recognizer will only trigger on middle mouse button events. + \value RightMouseButtonGesture The gesture recognizer will only trigger on right mouse button events. +*/ + +/*! + \enum QScroller::Input + + This enum contains an input device agnostic view of input events that are relevant for QScroller. + + \value InputPress The user pressed the input device (e.g. QEvent::MouseButtonPress, + QEvent::GraphicsSceneMousePress, QEvent::TouchBegin) + + \value InputMove The user moved the input device (e.g. QEvent::MouseMove, + QEvent::GraphicsSceneMouseMove, QEvent::TouchUpdate) + + \value InputRelease The user released the input device (e.g. QEvent::MouseButtonRelease, + QEvent::GraphicsSceneMouseRelease, QEvent::TouchEnd) + +*/ + +QT_END_NAMESPACE diff --git a/src/gui/util/qscroller.h b/src/gui/util/qscroller.h new file mode 100644 index 0000000..7d7e1ca --- /dev/null +++ b/src/gui/util/qscroller.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QSCROLLER_H +#define QSCROLLER_H + +#include <QtCore/QObject> +#include <QtCore/QPointF> +#include <QtGui/QScrollerProperties> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWidget; +class QScrollerPrivate; +class QScrollerProperties; +class QFlickGestureRecognizer; +class QMouseFlickGestureRecognizer; + +class Q_GUI_EXPORT QScroller : public QObject +{ + Q_OBJECT + Q_PROPERTY(State state READ state NOTIFY stateChanged) + Q_PROPERTY(QScrollerProperties scrollerProperties READ scrollerProperties WRITE setScrollerProperties NOTIFY scrollerPropertiesChanged) + Q_ENUMS(State) + +public: + enum State + { + Inactive, + Pressed, + Dragging, + Scrolling + }; + + enum ScrollerGestureType + { + TouchGesture, + LeftMouseButtonGesture, + RightMouseButtonGesture, + MiddleMouseButtonGesture + }; + + enum Input + { + InputPress = 1, + InputMove, + InputRelease + }; + + static bool hasScroller(QObject *target); + + static QScroller *scroller(QObject *target); + static const QScroller *scroller(const QObject *target); + + static Qt::GestureType grabGesture(QObject *target, ScrollerGestureType gestureType = TouchGesture); + static Qt::GestureType grabbedGesture(QObject *target); + static void ungrabGesture(QObject *target); + + static QList<QScroller *> activeScrollers(); + + QObject *target() const; + + State state() const; + + bool handleInput(Input input, const QPointF &position, qint64 timestamp = 0); + + void stop(); + QPointF velocity() const; + QPointF finalPosition() const; + QPointF pixelPerMeter() const; + + QScrollerProperties scrollerProperties() const; + + void setSnapPositionsX( const QList<qreal> &positions ); + void setSnapPositionsX( qreal first, qreal interval ); + void setSnapPositionsY( const QList<qreal> &positions ); + void setSnapPositionsY( qreal first, qreal interval ); + +public Q_SLOTS: + void setScrollerProperties(const QScrollerProperties &prop); + void scrollTo(const QPointF &pos); + void scrollTo(const QPointF &pos, int scrollTime); + void ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin); + void ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime); + void resendPrepareEvent(); + +Q_SIGNALS: + void stateChanged(QScroller::State newstate); + void scrollerPropertiesChanged(const QScrollerProperties &); + +private: + QScrollerPrivate *d_ptr; + + QScroller(QObject *target); + virtual ~QScroller(); + + Q_DISABLE_COPY(QScroller) + Q_DECLARE_PRIVATE(QScroller) + + friend class QFlickGestureRecognizer; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCROLLER_H diff --git a/src/gui/util/qscroller_mac.mm b/src/gui/util/qscroller_mac.mm new file mode 100644 index 0000000..3203036 --- /dev/null +++ b/src/gui/util/qscroller_mac.mm @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#import <Cocoa/Cocoa.h> + +#include "qscroller_p.h" + +#ifdef Q_WS_MAC + +QPointF QScrollerPrivate::realDpi(int screen) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSArray *nsscreens = [NSScreen screens]; + + if (screen < 0 || screen >= int([nsscreens count])) + screen = 0; + + NSScreen *nsscreen = [nsscreens objectAtIndex:screen]; + CGDirectDisplayID display = [[[nsscreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue]; + + CGSize mmsize = CGDisplayScreenSize(display); + if (mmsize.width > 0 && mmsize.height > 0) { + return QPointF(CGDisplayPixelsWide(display) / mmsize.width, + CGDisplayPixelsHigh(display) / mmsize.height) * qreal(25.4); + } else { + return QPointF(); + } + [pool release]; +} + +#endif diff --git a/src/gui/util/qscroller_p.h b/src/gui/util/qscroller_p.h new file mode 100644 index 0000000..98f34f7 --- /dev/null +++ b/src/gui/util/qscroller_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QSCROLLER_P_H +#define QSCROLLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QObject> +#include <QPointer> +#include <QQueue> +#include <QSet> +#include <QEasingCurve> +#include <QElapsedTimer> +#include <QSizeF> +#include <QPointF> +#include <QRectF> +#include <qscroller.h> +#include <qscrollerproperties.h> +#include <private/qscrollerproperties_p.h> +#include <QAbstractAnimation> + +QT_BEGIN_NAMESPACE + +class QFlickGestureRecognizer; + +class QScrollTimer; + +class QScrollerPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(QScroller) + +public: + QScrollerPrivate(QScroller *q, QObject *target); + void init(); + + void sendEvent(QObject *o, QEvent *e); + + void setState(QScroller::State s); + + enum ScrollType { + ScrollTypeFlick = 0, + ScrollTypeScrollTo, + ScrollTypeOvershoot + }; + + struct ScrollSegment { + qint64 startTime; + qint64 deltaTime; + qreal startPos; + qreal deltaPos; + QEasingCurve curve; + qreal maxProgress; + ScrollType type; + }; + + bool pressWhileInactive(const QPointF &position, qint64 timestamp); + bool moveWhilePressed(const QPointF &position, qint64 timestamp); + bool releaseWhilePressed(const QPointF &position, qint64 timestamp); + bool moveWhileDragging(const QPointF &position, qint64 timestamp); + bool releaseWhileDragging(const QPointF &position, qint64 timestamp); + bool pressWhileScrolling(const QPointF &position, qint64 timestamp); + + void timerTick(); + void timerEventWhileDragging(); + void timerEventWhileScrolling(); + + bool prepareScrolling(const QPointF &position); + void handleDrag(const QPointF &position, qint64 timestamp); + + QPointF realDpi(int screen); + QPointF dpi() const; + void setDpi(const QPointF &dpi); + void setDpiFromWidget(QWidget *widget); + + void updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime); + void pushSegment(ScrollType type, qreal deltaTime, qreal startPos, qreal endPos, QEasingCurve::Type curve, Qt::Orientation orientation, qreal maxProgress = 1.0); + void recalcScrollingSegments(bool forceRecalc = false); + qreal scrollingSegmentsEndPos(Qt::Orientation orientation) const; + bool scrollingSegmentsValid(Qt::Orientation orientation); + void createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type); + void createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation); + + void setContentPositionHelperDragging(const QPointF &deltaPos); + void setContentPositionHelperScrolling(); + + qreal nextSnapPos(qreal p, int dir, Qt::Orientation orientation); + static qreal nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos); + + inline int frameRateSkip() const { return properties.d.data()->frameRate; } + + static const char *stateName(QScroller::State state); + static const char *inputName(QScroller::Input input); + +public slots: + void targetDestroyed(); + +public: + // static + static QMap<QObject *, QScroller *> allScrollers; + static QSet<QScroller *> activeScrollers; + + // non static + QObject *target; + QScrollerProperties properties; + QFlickGestureRecognizer *recognizer; + Qt::GestureType recognizerType; + + // scroller state: + + // QPointer<QObject> scrollTarget; + QSizeF viewportSize; + QRectF contentPosRange; + QPointF contentPosition; + QPointF overshootPosition; // the number of pixels we are overshooting (before overshootDragResistanceFactor) + + // state + + bool enabled; + QScroller::State state; + bool firstScroll; // true if we haven't already send a scroll event + + QPointF oldVelocity; // the release velocity of the last drag + + QPointF pressPosition; + QPointF lastPosition; + qint64 pressTimestamp; + qint64 lastTimestamp; + + QPointF dragDistance; // the distance we should move during the next drag timer event + + QQueue<ScrollSegment> xSegments; + QQueue<ScrollSegment> ySegments; + + // snap positions + QList<qreal> snapPositionsX; + qreal snapFirstX; + qreal snapIntervalX; + QList<qreal> snapPositionsY; + qreal snapFirstY; + qreal snapIntervalY; + + QPointF pixelPerMeter; + + QElapsedTimer monotonicTimer; + + QPointF releaseVelocity; // the starting velocity of the scrolling state + QScrollTimer *scrollTimer; + + QScroller *q_ptr; +}; + + +QT_END_NAMESPACE + +#endif // QSCROLLER_P_H + diff --git a/src/gui/util/qscrollerproperties.cpp b/src/gui/util/qscrollerproperties.cpp new file mode 100644 index 0000000..d21f131 --- /dev/null +++ b/src/gui/util/qscrollerproperties.cpp @@ -0,0 +1,418 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 <QPointer> +#include <QObject> +#include <QtCore/qmath.h> +#ifdef Q_WS_WIN +# include <QLibrary> +#endif + +#include "qscrollerproperties.h" +#include "private/qscrollerproperties_p.h" + +QT_BEGIN_NAMESPACE + +static QScrollerPropertiesPrivate *userDefaults = 0; +static QScrollerPropertiesPrivate *systemDefaults = 0; + +QScrollerPropertiesPrivate *QScrollerPropertiesPrivate::defaults() +{ + if (!systemDefaults) { + QScrollerPropertiesPrivate spp; +#ifdef Q_WS_MAEMO_5 + spp.mousePressEventDelay = qreal(0); + spp.dragStartDistance = qreal(2.5 / 1000); + spp.dragVelocitySmoothingFactor = qreal(0.15); + spp.axisLockThreshold = qreal(0); + spp.scrollingCurve.setType(QEasingCurve::OutQuad); + spp.decelerationFactor = 1.0; + spp.minimumVelocity = qreal(0.0195); + spp.maximumVelocity = qreal(6.84); + spp.maximumClickThroughVelocity = qreal(0.0684); + spp.acceleratingFlickMaximumTime = qreal(0.125); + spp.acceleratingFlickSpeedupFactor = qreal(3.0); + spp.snapPositionRatio = qreal(0.25); + spp.snapTime = qreal(1); + spp.overshootDragResistanceFactor = qreal(1); + spp.overshootDragDistanceFactor = qreal(0.3); + spp.overshootScrollDistanceFactor = qreal(0.3); + spp.overshootScrollTime = qreal(0.5); + spp.hOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.vOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.frameRate = QScrollerProperties::Fps30; +#else + spp.mousePressEventDelay = qreal(0.25); + spp.dragStartDistance = qreal(5.0 / 1000); + spp.dragVelocitySmoothingFactor = qreal(0.02); + spp.axisLockThreshold = qreal(0); + spp.scrollingCurve.setType(QEasingCurve::OutQuad); + spp.decelerationFactor = qreal(0.125); + spp.minimumVelocity = qreal(50.0 / 1000); + spp.maximumVelocity = qreal(500.0 / 1000); + spp.maximumClickThroughVelocity = qreal(66.5 / 1000); + spp.acceleratingFlickMaximumTime = qreal(1.25); + spp.acceleratingFlickSpeedupFactor = qreal(3.0); + spp.snapPositionRatio = qreal(0.5); + spp.snapTime = qreal(0.3); + spp.overshootDragResistanceFactor = qreal(0.5); + spp.overshootDragDistanceFactor = qreal(1); + spp.overshootScrollDistanceFactor = qreal(0.5); + spp.overshootScrollTime = qreal(0.7); +# ifdef Q_WS_WIN + if (QLibrary::resolve(QLatin1String("UxTheme"), "BeginPanningFeedback")) + spp.overshootScrollTime = qreal(0.35); +# endif + spp.hOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.vOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.frameRate = QScrollerProperties::Standard; +#endif + systemDefaults = new QScrollerPropertiesPrivate(spp); + } + return new QScrollerPropertiesPrivate(userDefaults ? *userDefaults : *systemDefaults); +} + +/*! + \class QScrollerProperties + \brief The QScrollerProperties class stores the settings for a QScroller. + \since 4.8 + + The QScrollerProperties class stores the parameters used by QScroller. + + The default settings are platform dependant and Qt will emulate the + platform behaviour for kinetic scrolling. + + As a convention the QScrollerProperties are in physical units (meter, + seconds) and will be converted by QScroller using the current DPI. + + \sa QScroller +*/ + +/*! + Constructs new scroller properties. +*/ +QScrollerProperties::QScrollerProperties() + : d(QScrollerPropertiesPrivate::defaults()) +{ +} + +/*! + Constructs a copy of \a sp. +*/ +QScrollerProperties::QScrollerProperties(const QScrollerProperties &sp) + : d(new QScrollerPropertiesPrivate(*sp.d)) +{ +} + +/*! + Assigns \a sp to these scroller properties and returns a reference to these scroller properties. +*/ +QScrollerProperties &QScrollerProperties::operator=(const QScrollerProperties &sp) +{ + *d.data() = *sp.d.data(); + return *this; +} + +/*! + Destroys the scroller properties. +*/ +QScrollerProperties::~QScrollerProperties() +{ +} + +/*! + Returns true if these scroller properties are equal to \a sp; otherwise returns false. +*/ +bool QScrollerProperties::operator==(const QScrollerProperties &sp) const +{ + return *d.data() == *sp.d.data(); +} + +/*! + Returns true if these scroller properties are different from \a sp; otherwise returns false. +*/ +bool QScrollerProperties::operator!=(const QScrollerProperties &sp) const +{ + return !(*d.data() == *sp.d.data()); +} + +bool QScrollerPropertiesPrivate::operator==(const QScrollerPropertiesPrivate &p) const +{ + bool same = true; + same &= (mousePressEventDelay == p.mousePressEventDelay); + same &= (dragStartDistance == p.dragStartDistance); + same &= (dragVelocitySmoothingFactor == p.dragVelocitySmoothingFactor); + same &= (axisLockThreshold == p.axisLockThreshold); + same &= (scrollingCurve == p.scrollingCurve); + same &= (decelerationFactor == p.decelerationFactor); + same &= (minimumVelocity == p.minimumVelocity); + same &= (maximumVelocity == p.maximumVelocity); + same &= (maximumClickThroughVelocity == p.maximumClickThroughVelocity); + same &= (acceleratingFlickMaximumTime == p.acceleratingFlickMaximumTime); + same &= (acceleratingFlickSpeedupFactor == p.acceleratingFlickSpeedupFactor); + same &= (snapPositionRatio == p.snapPositionRatio); + same &= (snapTime == p.snapTime); + same &= (overshootDragResistanceFactor == p.overshootDragResistanceFactor); + same &= (overshootDragDistanceFactor == p.overshootDragDistanceFactor); + same &= (overshootScrollDistanceFactor == p.overshootScrollDistanceFactor); + same &= (overshootScrollTime == p.overshootScrollTime); + same &= (hOvershootPolicy == p.hOvershootPolicy); + same &= (vOvershootPolicy == p.vOvershootPolicy); + same &= (frameRate == p.frameRate); + return same; +} + +/*! + Sets the scroller properties returned by the default constructor to \a sp. + + Use this function to override the platform default properties returned by the default + constructor. If you only want to change the scroller properties of a single scroller, then use + QScroller::setScrollerProperties() + + \note Calling this function will not change the content of already existing + QScrollerProperties objects. + + \sa unsetDefaultScrollerProperties() +*/ +void QScrollerProperties::setDefaultScrollerProperties(const QScrollerProperties &sp) +{ + if (!userDefaults) + userDefaults = new QScrollerPropertiesPrivate(*sp.d); + else + *userDefaults = *sp.d; +} + +/*! + Sets the scroller properties returned by the default constructor back to the platform default + properties. + + \sa setDefaultScrollerProperties() +*/ +void QScrollerProperties::unsetDefaultScrollerProperties() +{ + delete userDefaults; + userDefaults = 0; +} + +/*! + Query the \a metric value of the scroller properties. + + \sa setScrollMetric(), ScrollMetric +*/ +QVariant QScrollerProperties::scrollMetric(ScrollMetric metric) const +{ + switch (metric) { + case MousePressEventDelay: return d->mousePressEventDelay; + case DragStartDistance: return d->dragStartDistance; + case DragVelocitySmoothingFactor: return d->dragVelocitySmoothingFactor; + case AxisLockThreshold: return d->axisLockThreshold; + case ScrollingCurve: return d->scrollingCurve; + case DecelerationFactor: return d->decelerationFactor; + case MinimumVelocity: return d->minimumVelocity; + case MaximumVelocity: return d->maximumVelocity; + case MaximumClickThroughVelocity: return d->maximumClickThroughVelocity; + case AcceleratingFlickMaximumTime: return d->acceleratingFlickMaximumTime; + case AcceleratingFlickSpeedupFactor:return d->acceleratingFlickSpeedupFactor; + case SnapPositionRatio: return d->snapPositionRatio; + case SnapTime: return d->snapTime; + case OvershootDragResistanceFactor: return d->overshootDragResistanceFactor; + case OvershootDragDistanceFactor: return d->overshootDragDistanceFactor; + case OvershootScrollDistanceFactor: return d->overshootScrollDistanceFactor; + case OvershootScrollTime: return d->overshootScrollTime; + case HorizontalOvershootPolicy: return QVariant::fromValue(d->hOvershootPolicy); + case VerticalOvershootPolicy: return QVariant::fromValue(d->vOvershootPolicy); + case FrameRate: return QVariant::fromValue(d->frameRate); + case ScrollMetricCount: break; + } + return QVariant(); +} + +/*! + Set a specific value of the \a metric ScrollerMetric to \a value. + + \sa scrollMetric(), ScrollMetric +*/ +void QScrollerProperties::setScrollMetric(ScrollMetric metric, const QVariant &value) +{ + switch (metric) { + case MousePressEventDelay: d->mousePressEventDelay = value.toReal(); break; + case DragStartDistance: d->dragStartDistance = value.toReal(); break; + case DragVelocitySmoothingFactor: d->dragVelocitySmoothingFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case AxisLockThreshold: d->axisLockThreshold = qBound(qreal(0), value.toReal(), qreal(1)); break; + case ScrollingCurve: d->scrollingCurve = value.toEasingCurve(); break; + case DecelerationFactor: d->decelerationFactor = value.toReal(); break; + case MinimumVelocity: d->minimumVelocity = value.toReal(); break; + case MaximumVelocity: d->maximumVelocity = value.toReal(); break; + case MaximumClickThroughVelocity: d->maximumClickThroughVelocity = value.toReal(); break; + case AcceleratingFlickMaximumTime: d->acceleratingFlickMaximumTime = value.toReal(); break; + case AcceleratingFlickSpeedupFactor:d->acceleratingFlickSpeedupFactor = value.toReal(); break; + case SnapPositionRatio: d->snapPositionRatio = qBound(qreal(0), value.toReal(), qreal(1)); break; + case SnapTime: d->snapTime = value.toReal(); break; + case OvershootDragResistanceFactor: d->overshootDragResistanceFactor = value.toReal(); break; + case OvershootDragDistanceFactor: d->overshootDragDistanceFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case OvershootScrollDistanceFactor: d->overshootScrollDistanceFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case OvershootScrollTime: d->overshootScrollTime = value.toReal(); break; + case HorizontalOvershootPolicy: d->hOvershootPolicy = value.value<QScrollerProperties::OvershootPolicy>(); break; + case VerticalOvershootPolicy: d->vOvershootPolicy = value.value<QScrollerProperties::OvershootPolicy>(); break; + case FrameRate: d->frameRate = value.value<QScrollerProperties::FrameRates>(); break; + case ScrollMetricCount: break; + } +} + +/*! + \enum QScrollerProperties::FrameRates + + This enum describes the available frame rates used while dragging or scrolling. + + \value Fps60 60 frames per second + \value Fps30 30 frames per second + \value Fps20 20 frames per second + \value Standard the default value is 60 frames per second (which corresponds to QAbstractAnimation). +*/ + +/*! + \enum QScrollerProperties::OvershootPolicy + + This enum describes the various modes of overshooting. + + \value OvershootWhenScrollable Overshooting is when the content is scrollable. This is the + default. + + \value OvershootAlwaysOff Overshooting is never enabled (even when the content is scrollable). + + \value OvershootAlwaysOn Overshooting is always enabled (even when the content is not + scrollable). +*/ + +/*! + \enum QScrollerProperties::ScrollMetric + + This enum contains the different scroll metric types. When not indicated otherwise the + setScrollMetric function expects a QVariant of a real value. + + See the QScroller documentation for a further explanation of the concepts behind the different + values. + + \value MousePressEventDelay This is the time a mouse press event will be delayed when starting + a flick gesture in \c{[s]}. If the gesture is triggered within that time, no mouse press or + release will be sent to the scrolled object. If it triggers after that delay the (delayed) + mouse press plus a faked release event (at global postion \c{QPoint(-QWIDGETSIZE_MAX, + -QWIDGETSIZE_MAX)} will be sent. If the gesture is canceled, then both the (delayed) mouse + press plus the real release event will be delivered. + + \value DragStartDistance This is the minimum distance the touch or mouse point needs to be + moved before the flick gesture is triggered in \c m. + + \value DragVelocitySmoothingFactor A value that describes how much new drag velocities are + included in the final scrolling velocity. This value should be in the range between \c 0 and \c + 1. Low values meaning that the last dragging velocity is not very important. + + \value AxisLockThreshold If greater than zero a scroll movement will be restricted to one axis + only if the movement is inside an angle about the axis. The threshold must be in the range \c 0 + to \c 1. + + \value ScrollingCurve The QEasingCurve used when decelerating the scrolling velocity after an + user initiated flick. Please note that this is the easing curve for the positions, \bold{not} + the velocity: the default is QEasingCurve::OutQuad, which results is a linear decrease in + velocity (1st derivative) and a constant deceleration (2nd derivative). + + \value DecelerationFactor This factor influences how long it takes the scroller to decelerate + to 0 velocity. The actual value heavily depends on the chosen ScrollingCurve, but for most + types the value should be in the range from \c 0.1 to \c 2.0 + + \value MinimumVelocity The minimum velocity that is needed after ending the touch or releasing + the mouse to start scrolling in \c{m/s}. + + \value MaximumVelocity This is the maximum velocity that can be reached in \c{m/s}. + + \value MaximumClickThroughVelocity This is the maximum allowed scroll speed for a click-through + in \c{m/s}. This means that a click on a currently (slowly) scrolling object will not only stop + the scrolling but the click event will also be delivered to the UI control - this is very + useful when using exponential-type scrolling curves. + + \value AcceleratingFlickMaximumTime This is the maximum time in \c seconds that a flick gesture + can take to be recognized as an accelerating flick. If set to zero no such gesture will be + detected. An "accelerating flick" is a flick gesture executed on an already scrolling object. + In such cases the scrolling speed is multiplied by AcceleratingFlickSpeedupFactor in order to + accelerate it. + + \value AcceleratingFlickSpeedupFactor The current speed will be multiplied by this number if an + accelerating flick is detected. Should be \c{> 1}. + + \value SnapPositionRatio This is the distance that the user must drag the area beween two snap + points in order to snap it to the next position. e.g. \c{0.33} means that the scroll must only + reach one third of the distance between two snap points to snap to the next one. The ratio must + be in the range \c 0 to \c 1. + + \value SnapTime This is the time factor for the scrolling curve. A lower value means that the + scrolling will take longer. The scrolling distance is independet of this value. + + \value OvershootDragResistanceFactor This value is the factor between the mouse dragging and + the actual scroll area movement (during overshoot). The factor must be in the range \c 0 to \c + 1. + + \value OvershootDragDistanceFactor This is the maximum distance for overshoot movements while + dragging. The actual overshoot distance will be calculated by multiplying this value with the + viewport size of the scrolled object. The factor must be in the range \c 0 to \c 1. + + \value OvershootScrollDistanceFactor This is the maximum distance for overshoot movements while + scrolling. The actual overshoot distance will be calculated by multiplying this value with the + viewport size of the scrolled object. The factor must be in the range \c 0 to \c 1. + + \value OvershootScrollTime This is the time in \c seconds that will be used to play the + complete overshoot animation. + + \value HorizontalOvershootPolicy This is the horizontal overshooting policy (see OvershootPolicy). + + \value VerticalOvershootPolicy This is the horizontal overshooting policy (see OvershootPolicy). + + \value FrameRate This is the frame rate which should be used while dragging or scrolling. + QScroller uses a QAbstractAnimation timer internally to sync all scrolling operations to other + animations that might be active at the same time. If the Standard value of 60 frames per + second is too fast for your use case, you can lower the frames per second with this setting + (while still being in-sync with QAbstractAnimation). Please note that only the values of the + FrameRates enum are allowed here. + + \value ScrollMetricCount This is just used when enumerating the metrics. It is always the last + entry. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/util/qscrollerproperties.h b/src/gui/util/qscrollerproperties.h new file mode 100644 index 0000000..e292d8d --- /dev/null +++ b/src/gui/util/qscrollerproperties.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QSCROLLERPROPERTIES_H +#define QSCROLLERPROPERTIES_H + +#include <QtCore/QScopedPointer> +#include <QtCore/QMetaType> +#include <QtCore/QVariant> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QScroller; +class QScrollerPrivate; +class QScrollerPropertiesPrivate; + +class Q_GUI_EXPORT QScrollerProperties +{ +public: + QScrollerProperties(); + QScrollerProperties(const QScrollerProperties &sp); + QScrollerProperties &operator=(const QScrollerProperties &sp); + virtual ~QScrollerProperties(); + + bool operator==(const QScrollerProperties &sp) const; + bool operator!=(const QScrollerProperties &sp) const; + + static void setDefaultScrollerProperties(const QScrollerProperties &sp); + static void unsetDefaultScrollerProperties(); + + enum OvershootPolicy + { + OvershootWhenScrollable, + OvershootAlwaysOff, + OvershootAlwaysOn + }; + + enum FrameRates { + Standard, + Fps60, + Fps30, + Fps20 + }; + + enum ScrollMetric + { + MousePressEventDelay, // qreal [s] + DragStartDistance, // qreal [m] + DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF) + AxisLockThreshold, // qreal [0..1] atan(|min(dx,dy)|/|max(dx,dy)|) + + ScrollingCurve, // QEasingCurve + DecelerationFactor, // slope of the curve + + MinimumVelocity, // qreal [m/s] + MaximumVelocity, // qreal [m/s] + MaximumClickThroughVelocity, // qreal [m/s] + + AcceleratingFlickMaximumTime, // qreal [s] + AcceleratingFlickSpeedupFactor, // qreal [1..] + + SnapPositionRatio, // qreal [0..1] + SnapTime, // qreal [s] + + OvershootDragResistanceFactor, // qreal [0..1] + OvershootDragDistanceFactor, // qreal [0..1] + OvershootScrollDistanceFactor, // qreal [0..1] + OvershootScrollTime, // qreal [s] + + HorizontalOvershootPolicy, // enum OvershootPolicy + VerticalOvershootPolicy, // enum OvershootPolicy + FrameRate, // enum FrameRates + + ScrollMetricCount + }; + + QVariant scrollMetric(ScrollMetric metric) const; + void setScrollMetric(ScrollMetric metric, const QVariant &value); + +protected: + QScopedPointer<QScrollerPropertiesPrivate> d; + +private: + QScrollerProperties(QScrollerPropertiesPrivate &dd); + + friend class QScrollerPropertiesPrivate; + friend class QScroller; + friend class QScrollerPrivate; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QScrollerProperties::OvershootPolicy) +Q_DECLARE_METATYPE(QScrollerProperties::FrameRates) + +QT_END_HEADER + +#endif // QSCROLLERPROPERTIES_H diff --git a/src/gui/util/qscrollerproperties_p.h b/src/gui/util/qscrollerproperties_p.h new file mode 100644 index 0000000..093f615 --- /dev/null +++ b/src/gui/util/qscrollerproperties_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QSCROLLERPROPERTIES_P_H +#define QSCROLLERPROPERTIES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QPointF> +#include <QEasingCurve> +#include <qscrollerproperties.h> + +QT_BEGIN_NAMESPACE + +class QScrollerPropertiesPrivate +{ +public: + static QScrollerPropertiesPrivate *defaults(); + + bool operator==(const QScrollerPropertiesPrivate &) const; + + qreal mousePressEventDelay; + qreal dragStartDistance; + qreal dragVelocitySmoothingFactor; + qreal axisLockThreshold; + QEasingCurve scrollingCurve; + qreal decelerationFactor; + qreal minimumVelocity; + qreal maximumVelocity; + qreal maximumClickThroughVelocity; + qreal acceleratingFlickMaximumTime; + qreal acceleratingFlickSpeedupFactor; + qreal snapPositionRatio; + qreal snapTime; + qreal overshootDragResistanceFactor; + qreal overshootDragDistanceFactor; + qreal overshootScrollDistanceFactor; + qreal overshootScrollTime; + QScrollerProperties::OvershootPolicy hOvershootPolicy; + QScrollerProperties::OvershootPolicy vOvershootPolicy; + QScrollerProperties::FrameRates frameRate; +}; + +QT_END_NAMESPACE + +#endif // QSCROLLERPROPERTIES_P_H + diff --git a/src/gui/util/util.pri b/src/gui/util/util.pri index f125f82..2814a2d 100644 --- a/src/gui/util/util.pri +++ b/src/gui/util/util.pri @@ -6,6 +6,11 @@ HEADERS += \ util/qcompleter_p.h \ util/qdesktopservices.h \ util/qsystemtrayicon_p.h \ + util/qscroller.h \ + util/qscroller_p.h \ + util/qscrollerproperties.h \ + util/qscrollerproperties_p.h \ + util/qflickgesture_p.h \ util/qundogroup.h \ util/qundostack.h \ util/qundostack_p.h \ @@ -15,6 +20,9 @@ SOURCES += \ util/qsystemtrayicon.cpp \ util/qcompleter.cpp \ util/qdesktopservices.cpp \ + util/qscroller.cpp \ + util/qscrollerproperties.cpp \ + util/qflickgesture.cpp \ util/qundogroup.cpp \ util/qundostack.cpp \ util/qundoview.cpp @@ -57,3 +65,7 @@ symbian { DEFINES += USE_SCHEMEHANDLER } } + +macx { + OBJECTIVE_SOURCES += util/qscroller_mac.mm +} |