From 144e7412ea865170990695f9f83896b5d6502730 Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Mon, 19 Apr 2010 14:12:15 +0200 Subject: reorg: split the kinetics scroller and the test app --- kineticscroller.pro | 32 +- main.cpp | 33 - plotwidget.cpp | 161 ----- plotwidget.h | 81 --- qkineticscroller.cpp | 1237 -------------------------------- qkineticscroller.h | 165 ----- qkineticscroller_p.h | 156 ---- qscrollareakineticscroller.cpp | 356 --------- qscrollareakineticscroller.h | 92 --- scroller/qkineticscroller.cpp | 1237 ++++++++++++++++++++++++++++++++ scroller/qkineticscroller.h | 165 +++++ scroller/qkineticscroller_p.h | 156 ++++ scroller/scroller.pro | 12 + settingswidget.cpp | 362 ---------- settingswidget.h | 70 -- testapp/main.cpp | 33 + testapp/plotwidget.cpp | 161 +++++ testapp/plotwidget.h | 81 +++ testapp/qscrollareakineticscroller.cpp | 356 +++++++++ testapp/qscrollareakineticscroller.h | 92 +++ testapp/settingswidget.cpp | 362 ++++++++++ testapp/settingswidget.h | 70 ++ testapp/testapp.pro | 16 + 23 files changed, 2743 insertions(+), 2743 deletions(-) delete mode 100644 main.cpp delete mode 100644 plotwidget.cpp delete mode 100644 plotwidget.h delete mode 100644 qkineticscroller.cpp delete mode 100644 qkineticscroller.h delete mode 100644 qkineticscroller_p.h delete mode 100644 qscrollareakineticscroller.cpp delete mode 100644 qscrollareakineticscroller.h create mode 100644 scroller/qkineticscroller.cpp create mode 100644 scroller/qkineticscroller.h create mode 100644 scroller/qkineticscroller_p.h create mode 100644 scroller/scroller.pro delete mode 100644 settingswidget.cpp delete mode 100644 settingswidget.h create mode 100644 testapp/main.cpp create mode 100644 testapp/plotwidget.cpp create mode 100644 testapp/plotwidget.h create mode 100644 testapp/qscrollareakineticscroller.cpp create mode 100644 testapp/qscrollareakineticscroller.h create mode 100644 testapp/settingswidget.cpp create mode 100644 testapp/settingswidget.h create mode 100644 testapp/testapp.pro diff --git a/kineticscroller.pro b/kineticscroller.pro index 061318a..0d4e45f 100644 --- a/kineticscroller.pro +++ b/kineticscroller.pro @@ -1,30 +1,2 @@ -###################################################################### -# Automatically generated by qmake (2.01a) Wed Mar 31 20:01:48 2010 -###################################################################### - -TEMPLATE = app -TARGET = -DEPENDPATH += . -INCLUDEPATH += . - -# Input -HEADERS += \ - qkineticscroller.h \ - qkineticscroller_p.h \ - qscrollareakineticscroller.h - -SOURCES += \ - qkineticscroller.cpp \ - qscrollareakineticscroller.cpp - -# debug/development stuff - -HEADERS += \ - settingswidget.h \ - plotwidget.h - -SOURCES += \ - settingswidget.cpp \ - plotwidget.cpp \ - main.cpp - +TEMPLATE = subdirs +SUBDIRS = scroller testapp diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 698ec64..0000000 --- a/main.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include -#include "qscrollareakineticscroller.h" - -#include "settingswidget.h" -#include "plotwidget.h" - -int main(int argc, char **argv) -{ - QApplication a(argc, argv); - - QListWidget *lw = new QListWidget(); - - for (int i = 0; i < 1000; ++i) - new QListWidgetItem(QString("Oa dsjfhdk jhdsjk hfdskj k %1").arg(i), lw); - - QScrollAreaKineticScroller *s = new QScrollAreaKineticScroller(); - s->setWidget(lw); - - QWidget *settings = new SettingsWidget(s); - QWidget *plot = new PlotWidget(s); - - QSplitter *split = new QSplitter(); - split->addWidget(lw); - QTabWidget *tab = new QTabWidget(); - tab->addTab(settings, settings->windowTitle()); - tab->addTab(plot, plot->windowTitle()); - split->addWidget(tab); - - split->show(); - split->raise(); - - return a.exec(); -} diff --git a/plotwidget.cpp b/plotwidget.cpp deleted file mode 100644 index 0564df2..0000000 --- a/plotwidget.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "plotwidget.h" -#include "qkineticscroller.h" - -PlotWidget::PlotWidget(QKineticScroller *scroller) - : QWidget(), m_scroller(scroller) -{ - setWindowTitle(QLatin1String("Plot")); - m_scroller->registerDebugHook(debugHook, this); - - m_clear = new QPushButton(QLatin1String("Clear"), this); - connect(m_clear, SIGNAL(clicked()), this, SLOT(reset())); - m_legend = new QLabel(this); - QString legend; - QTextStream ts(&legend); - ts << ""; - ts << ""; - ts << ""; - ts << ""; - ts << ""; - ts << ""; - ts << ""; - ts << "
releaseVelocity X
releaseVelocity Y
Content Position X
Content Position Y
Overshoot Position X
Overshoot Position Y
"; - m_legend->setText(legend); -} - -void PlotWidget::debugHook(void *user, const QPointF &releaseVelocity, const QPointF &contentPosition, const QPointF &overshootPosition) -{ - PlotItem pi = { releaseVelocity, contentPosition, overshootPosition }; - static_cast(user)->addPlotItem(pi); -} - -static inline void doMaxMin(const QPointF &v, qreal &minmaxv) -{ - minmaxv = qMax(minmaxv, qMax(qAbs(v.x()), qAbs(v.y()))); -} - -void PlotWidget::addPlotItem(const PlotItem &pi) -{ - m_plotitems.append(pi); - minMaxVelocity = minMaxPosition = 0; - - while (m_plotitems.size() > 500) - m_plotitems.removeFirst(); - - foreach (const PlotItem &pi, m_plotitems) { - doMaxMin(pi.releaseVelocity, minMaxVelocity); - doMaxMin(pi.contentPosition, minMaxPosition); - doMaxMin(pi.overshootPosition, minMaxPosition); - } - update(); -} - -void PlotWidget::reset() -{ - m_plotitems.clear(); - minMaxVelocity = minMaxPosition = 0; - update(); -} - -void PlotWidget::resizeEvent(QResizeEvent *) -{ - QSize cs = m_clear->sizeHint(); - QSize ls = m_legend->sizeHint(); - m_clear->setGeometry(4, 4, cs.width(), cs.height()); - m_legend->setGeometry(4, height() - ls.height() - 4, ls.width(), ls.height()); -} - -void PlotWidget::paintEvent(QPaintEvent *) -{ -#define SCALE(v, mm) ((qreal(1) - (v / mm)) * qreal(0.5) * height()) - - QColor rvColor = Qt::red; - QColor cpColor = Qt::green; - QColor opColor = Qt::blue; - - - QPainter p(this); - p.setRenderHints(QPainter::Antialiasing); - p.fillRect(rect(), Qt::white); - - if (m_plotitems.isEmpty()) - return; - - int x = 2; - int offset = m_plotitems.size() - width() / 2; - QList::const_iterator it = m_plotitems.constBegin(); - if (offset > 0) - it += (offset - 1); - - const PlotItem *last = &(*it++); - - while (it != m_plotitems.constEnd()) { - p.setPen(rvColor.light()); - p.drawLine(qreal(x - 2), SCALE(last->releaseVelocity.x(), minMaxVelocity), - qreal(x), SCALE(it->releaseVelocity.x(), minMaxVelocity)); - p.setPen(rvColor.dark()); - p.drawLine(qreal(x - 2), SCALE(last->releaseVelocity.y(), minMaxVelocity), - qreal(x), SCALE(it->releaseVelocity.y(), minMaxVelocity)); - - p.setPen(cpColor.light()); - p.drawLine(qreal(x - 2), SCALE(last->contentPosition.x(), minMaxPosition), - qreal(x), SCALE(it->contentPosition.x(), minMaxPosition)); - p.setPen(cpColor.dark()); - p.drawLine(qreal(x - 2), SCALE(last->contentPosition.y(), minMaxPosition), - qreal(x), SCALE(it->contentPosition.y(), minMaxPosition)); - - p.setPen(opColor.light()); - p.drawLine(qreal(x - 2), SCALE(last->overshootPosition.x(), minMaxPosition), - qreal(x), SCALE(it->overshootPosition.x(), minMaxPosition)); - p.setPen(opColor.dark()); - p.drawLine(qreal(x - 2), SCALE(last->overshootPosition.y(), minMaxPosition), - qreal(x), SCALE(it->overshootPosition.y(), minMaxPosition)); - - last = &(*it++); - x += 2; - } -#undef SCALE -} diff --git a/plotwidget.h b/plotwidget.h deleted file mode 100644 index 80e76ad..0000000 --- a/plotwidget.h +++ /dev/null @@ -1,81 +0,0 @@ -/**************************************************************************** -** -** 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 PLOTWIDGET_H -#define PLOTWIDGET_H - -#include - -class QKineticScroller; - -class PlotWidget : public QWidget -{ - Q_OBJECT - -public: - PlotWidget(QKineticScroller *scroller); - -public slots: - void reset(); - -protected: - void resizeEvent(QResizeEvent *); - void paintEvent(QPaintEvent *); - -private: - static void debugHook(void *user, const QPointF &releaseVelocity, const QPointF &contentPosition, const QPointF &overshootPosition); - - struct PlotItem { - QPointF releaseVelocity; - QPointF contentPosition; - QPointF overshootPosition; - }; - - void addPlotItem(const PlotItem &pi); - - QKineticScroller *m_scroller; - QList m_plotitems; - qreal minMaxVelocity, minMaxPosition; - QPushButton *m_clear; - QLabel *m_legend; -}; - -#endif diff --git a/qkineticscroller.cpp b/qkineticscroller.cpp deleted file mode 100644 index b9c31a8..0000000 --- a/qkineticscroller.cpp +++ /dev/null @@ -1,1237 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include -#include -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -#define KINETIC_SCROLLER_DEBUG - -#ifdef KINETIC_SCROLLER_DEBUG -# define qKSDebug qDebug -#else -# define qKSDebug while (false) qDebug -#endif - - -inline bool operator<=(const QPointF &p, qreal f) -{ - return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f); -} -inline bool operator<(const QPointF &p, qreal f) -{ - return (qAbs(p.x()) < f) && (qAbs(p.y()) < f); -} - -inline bool operator>=(const QPointF &p, qreal f) -{ - return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f); -} -inline bool operator>(const QPointF &p, qreal f) -{ - return (qAbs(p.x()) > f) || (qAbs(p.y()) > f); -} - -inline QPointF qAbs(const QPointF &p) -{ - return QPointF(qAbs(p.x()), qAbs(p.y())); -} - -inline int qSign(qreal r) -{ - return (r < 0) ? -1 : ((r > 0) ? 1 : 0); -} - - -/*! - \class QKineticScroller - \brief The QKineticScroller class enables kinetic scrolling for any scrolling widget or graphics item. - \ingroup qtmaemo5 - \since 4.6 - \preliminary - - 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. - - To enable kinetic scrolling for a widget or graphics item, you need to - derive from this class and implement at least all the pure-virtual - functions. - - Qt for Maemo 5 already comes with two implementations for - QScrollArea and QWebView, and those kinetic scrollers are - automatically instantiated and attached to these widgets on creation. - In the QScrollArea case, the kinetic scroller is initially - disabled. However, for QItemView and QScrollArea derived classes - it is enabled by default. You can obtain these automatically created - objects via a dynamic property: - - \code - // disable the kinetic scroller on scrollArea - QKineticScroller *scroller = scrollArea->property("kineticScroller") - .value(); - if (scroller) - scroller->setEnabled(false); - \endcode - - In addition there is also an example on how you would add kinetic - scrolling to a QGraphicsView based application in \c maemobrowser - examples in the \c maemo5 examples directory. - - The kinetic scroller installs an event filter on the widget to handle mouse - presses and moves on the widget \mdash presses and moves on a device's touch screen - are also handled by this mechanism. These events will be interpreted as scroll actions - depending on the current state() of the scroller. - - Even though this kinetic scroller has a huge number of settings, we - recommend that you leave them all at their default values. In case you - really want to change them you can try out the \c kineticscroller - example in the \c maemo5 examples directory. - - \sa QWidget -*/ - - -/*! - Constructs a new kinetic scroller. -*/ -QKineticScroller::QKineticScroller() - : d_ptr(new QKineticScrollerPrivate()) -{ - Q_D(QKineticScroller); - d->q_ptr = this; - d->init(); -} - -/*! \internal -*/ -QKineticScroller::QKineticScroller(QKineticScrollerPrivate &dd) - : d_ptr(&dd) -{ - Q_D(QKineticScroller); - d->q_ptr = this; - d->init(); -} - -/*! - Destroys the scroller. -*/ -QKineticScroller::~QKineticScroller() -{ -} - -/*! - \enum QKineticScroller::State - - This enum describes the possible states the kinetic scroller can be in. - - \value Inactive The scroller is inactive. It may also have been disabled. - \value Pressed The user has pressed the mouse button (or pressed the - the touch screen). - \value Dragging The user is dragging the mouse cursor (or other input - point) over the scroll area. - \value Scrolling Scrolling is occurring without direct user input. -*/ - -QKineticScrollerPrivate::QKineticScrollerPrivate() - : enabled(true) - , state(QKineticScroller::StateInactive) - , hOvershootPolicy(QKineticScroller::OvershootWhenScrollable) - , vOvershootPolicy(QKineticScroller::OvershootWhenScrollable) - , pressTimestamp(0) - , lastTimestamp(0) - , scrollToX(false) - , scrollToY(false) - , overshootX(false) - , overshootY(false) - , debugHook(0) - , debugHookUser(0) -{ } - -QKineticScrollerPrivate::~QKineticScrollerPrivate() -{ } - -void QKineticScrollerPrivate::init() -{ - Q_Q(QKineticScroller); - q->setDpiFromWidget(0); - q->resetScrollMetrics(); -} - -void QKineticScroller::registerDebugHook(void (*callback)(void *user, const QPointF &releaseVelocity, const QPointF &position, const QPointF &overshootPosition), void *user) -{ - Q_D(QKineticScroller); - d->debugHook = callback; - d->debugHookUser = user; -} - -void QKineticScroller::resetScrollMetrics() -{ - static QMap metrics; - -#ifdef Q_WS_MAEMO_5 - metrics.insert(DragVelocitySmoothingFactor, qreal(0.15)); - metrics.insert(ExponentialDecelerationBase, qreal(0.38)); // 0.85^20 - metrics.insert(LinearDecelerationFactor, qreal(0)); - metrics.insert(OvershootSpringConstant, qreal(0.1)); - metrics.insert(OvershootDragResistanceFactor, qreal(1)); - metrics.insert(OvershootMaximumDistance, QPointF(qreal(150), qreal(150))); - metrics.insert(OvershootMaximumVelocity, qreal(2600)); - metrics.insert(DragStartDistance, qreal(25)); - metrics.insert(DragStartDirectionErrorMargin, qreal(10)); - metrics.insert(MaximumVelocity, qreal(70000)); - metrics.insert(MinimumVelocity, qreal(200)); - metrics.insert(MaximumNonAcceleratedVelocity, qreal(5600)); - metrics.insert(MaximumClickThroughVelocity, qreal(700)); - metrics.insert(AxisLockThreshold, qreal(0)); - metrics.insert(FastSwipeBaseVelocity, qreal(540)); - metrics.insert(FastSwipeMinimumVelocity, qreal(800)); - metrics.insert(FastSwipeMaximumTime, qreal(0.125)); - metrics.insert(FramesPerSecond, qreal(20)); -#else - metrics.insert(DragVelocitySmoothingFactor, qreal(0.02)); - metrics.insert(ExponentialDecelerationBase, qreal(1)); - metrics.insert(LinearDecelerationFactor, qreal(0.38)); - metrics.insert(OvershootSpringConstant, qreal(15.0)); - metrics.insert(OvershootDragResistanceFactor, qreal(0.5)); - metrics.insert(OvershootMaximumDistance, QPointF(0,0)); // QPointF(qreal(14.25 / 1000), qreal(14.25 / 1000))); - metrics.insert(DragStartDistance, qreal(2.5 / 1000)); - metrics.insert(DragStartDirectionErrorMargin, qreal(1.0 / 1000)); - metrics.insert(MaximumVelocity, qreal(6650.0 / 1000)); - metrics.insert(MaximumVelocity, qreal(6650.0 / 1000)); - metrics.insert(MinimumVelocity, qreal(30.0 / 1000)); - metrics.insert(MaximumNonAcceleratedVelocity, qreal(532.0 / 1000)); - metrics.insert(MaximumClickThroughVelocity, qreal(66.5 / 1000)); - metrics.insert(AxisLockThreshold, qreal(0)); - metrics.insert(FastSwipeBaseVelocity, qreal(51.3 / 1000)); - metrics.insert(FastSwipeMinimumVelocity, qreal(76.0 / 1000)); - metrics.insert(FastSwipeMaximumTime, qreal(0.125)); - metrics.insert(FramesPerSecond, qreal(60)); -#endif - - if (!metrics.isEmpty()) { - for (QMap::const_iterator it = metrics.constBegin(); it != metrics.constEnd(); ++it) - setScrollMetric(it.key(), it.value()); - if (metrics.count() != ScrollMetricCount) - qWarning("QKineticScroller::resetAllMetrics(): scroll metrics parameter set did not contain all metrics."); - } else { - qWarning("QKineticScroller::resetAllMetrics(): no platform default parameter set available."); - } -} - - -const char *QKineticScrollerPrivate::stateName(QKineticScroller::State state) -{ - switch (state) { - case QKineticScroller::StateInactive: return "inactive"; - case QKineticScroller::StatePressed: return "pressed"; - case QKineticScroller::StateDragging: return "dragging"; - case QKineticScroller::StateScrolling: return "scrolling"; - default: return "(invalid)"; - } -} - -const char *QKineticScrollerPrivate::inputName(QKineticScroller::Input input) -{ - switch (input) { - case QKineticScroller::InputPress: return "press"; - case QKineticScroller::InputMove: return "move"; - case QKineticScroller::InputRelease: return "release"; - default: return "(invalid)"; - } -} - - - - -void QKineticScrollerPrivate::timerEvent(QTimerEvent *e) -{ - if (e->timerId() != timerId) { - QObject::timerEvent(e); - return; - } - - struct timerevent { - QKineticScroller::State state; - typedef void (QKineticScrollerPrivate::*timerhandler_t)(); - timerhandler_t handler; - }; - - timerevent timerevents[] = { - { QKineticScroller::StateDragging, &QKineticScrollerPrivate::timerEventWhileDragging }, - { QKineticScroller::StateScrolling, &QKineticScrollerPrivate::timerEventWhileScrolling }, - }; - - for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) { - timerevent *te = timerevents + i; - - if (state == te->state) { - (this->*te->handler)(); - return; - } - } - - if (timerId) { - qWarning() << "Unhandled timer event, while in state " << stateName(state); - killTimer(timerId); - timerId = 0; - } - // otherwise this is a timer event that was already queued when the - // timer was killed, so just ignore it -} - -bool QKineticScroller::handleInput(Input input, const QPointF &position, qint64 timestamp) -{ - Q_D(QKineticScroller); - - - qKSDebug() << "QKS::handleInput(" << input << ", " << position << ", " << timestamp << ")"; - struct statechange { - State state; - Input input; - typedef bool (QKineticScrollerPrivate::*inputhandler_t)(Input input, const QPointF &position, qint64 timestamp); - inputhandler_t handler; - }; - - statechange statechanges[] = { - { StateInactive, InputPress, &QKineticScrollerPrivate::pressWhileInactive }, - { StatePressed, InputMove, &QKineticScrollerPrivate::moveWhilePressed }, - { StatePressed, InputRelease, &QKineticScrollerPrivate::releaseWhilePressed }, - { StateDragging, InputMove, &QKineticScrollerPrivate::moveWhileDragging }, - { StateDragging, InputRelease, &QKineticScrollerPrivate::releaseWhileDragging }, - { StateScrolling, InputPress, &QKineticScrollerPrivate::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)(input, position, timestamp); - } - - qWarning() << "Unhandled input: got input " << d->inputName(input) << " while in state " << d->stateName(d->state); - return false; -} - -bool QKineticScroller::isEnabled() const -{ - Q_D(const QKineticScroller); - return d->enabled; -} - -void QKineticScroller::setEnabled(bool b) -{ - Q_D(QKineticScroller); - d->enabled = b; -} - -QKineticScroller::State QKineticScroller::state() const -{ - Q_D(const QKineticScroller); - return d->state; -} - -/*! - Resets the internal state of the kinetic scroller. This function is not - needed for normal use. This function only needs to be called if the - kinetic scroller is being re-attached to a different widget. -*/ -void QKineticScroller::reset() -{ - Q_D(QKineticScroller); - - d->setState(StateInactive); -} - -QKineticScroller::OvershootPolicy QKineticScroller::horizontalOvershootPolicy() const -{ - Q_D(const QKineticScroller); - return d->hOvershootPolicy; -} - -void QKineticScroller::setHorizontalOvershootPolicy(QKineticScroller::OvershootPolicy policy) -{ - Q_D(QKineticScroller); - d->hOvershootPolicy = policy; -} - -QKineticScroller::OvershootPolicy QKineticScroller::verticalOvershootPolicy() const -{ - Q_D(const QKineticScroller); - return d->vOvershootPolicy; -} - -void QKineticScroller::setVerticalOvershootPolicy(QKineticScroller::OvershootPolicy policy) -{ - Q_D(QKineticScroller); - d->vOvershootPolicy = policy; -} - -QVariant QKineticScroller::scrollMetric(ScrollMetric metric) const -{ - Q_D(const QKineticScroller); - - switch (metric) { - case DragVelocitySmoothingFactor: return d->dragVelocitySmoothingFactor; - case LinearDecelerationFactor: return d->linearDecelerationFactor; - case ExponentialDecelerationBase: return d->exponentialDecelerationBase; - case OvershootSpringConstant: return d->overshootSpringConstantRoot * d->overshootSpringConstantRoot; - case OvershootDragResistanceFactor: return d->overshootDragResistanceFactor; - case OvershootMaximumDistance: return d->overshootMaximumDistance; - case DragStartDistance: return d->dragStartDistance; - case DragStartDirectionErrorMargin: return d->dragStartDirectionErrorMargin; - case MinimumVelocity: return d->minimumVelocity; - case MaximumVelocity: return d->maximumVelocity; - case MaximumNonAcceleratedVelocity: return d->maximumNonAcceleratedVelocity; - case MaximumClickThroughVelocity: return d->maximumClickThroughVelocity; - case AxisLockThreshold: return d->axisLockThreshold; - case FramesPerSecond: return d->framesPerSecond; - case FastSwipeMaximumTime: return d->fastSwipeMaximumTime; - case FastSwipeMinimumVelocity: return d->fastSwipeMinimumVelocity; - case FastSwipeBaseVelocity: return d->fastSwipeBaseVelocity; - case ScrollMetricCount: break; - } - return QVariant(); -} - - -void QKineticScroller::setScrollMetric(ScrollMetric metric, const QVariant &value) -{ - Q_D(QKineticScroller); - - switch (metric) { - case DragVelocitySmoothingFactor: d->dragVelocitySmoothingFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; - case LinearDecelerationFactor: d->linearDecelerationFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; - case ExponentialDecelerationBase: d->exponentialDecelerationBase = qBound(qreal(0), value.toReal(), qreal(1)); break; - case OvershootSpringConstant: d->overshootSpringConstantRoot = qSqrt(value.toReal()); break; - case OvershootDragResistanceFactor: d->overshootDragResistanceFactor = value.toReal(); break; - case OvershootMaximumDistance: d->overshootMaximumDistance = value.toPointF(); break; - case DragStartDistance: d->dragStartDistance = value.toReal(); break; - case DragStartDirectionErrorMargin: d->dragStartDirectionErrorMargin = value.toReal(); break; - case MinimumVelocity: d->minimumVelocity = value.toReal(); break; - case MaximumVelocity: d->maximumVelocity = value.toReal(); break; - case MaximumNonAcceleratedVelocity: d->maximumNonAcceleratedVelocity = value.toReal(); break; - case MaximumClickThroughVelocity: d->maximumClickThroughVelocity = value.toReal(); break; - case AxisLockThreshold: d->axisLockThreshold = qBound(qreal(0), value.toReal(), qreal(1)); break; - case FramesPerSecond: d->framesPerSecond = qBound(1, value.toInt(), 100); break; - case FastSwipeMaximumTime: d->fastSwipeMaximumTime = value.toReal(); break; - case FastSwipeMinimumVelocity: d->fastSwipeMinimumVelocity = value.toReal(); break; - case FastSwipeBaseVelocity: d->fastSwipeBaseVelocity = value.toReal(); break; - case ScrollMetricCount: break; - } -} - -qreal QKineticScroller::dpi() const -{ - Q_D(const QKineticScroller); - return d->pixelPerMeter / qreal(39.3700787); -} - - -void QKineticScroller::setDpi(qreal dpi) -{ - Q_D(QKineticScroller); - d->pixelPerMeter = dpi * qreal(39.3700787); -} - - -void QKineticScroller::setDpiFromWidget(QWidget *widget) -{ -#ifdef Q_WS_MAEMO_5 - // 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. - setDpi(qreal(260)); -#else - if (!widget) - widget = QApplication::desktop(); - setDpi(qreal(widget->physicalDpiX() + widget->physicalDpiY()) / qreal(2)); -#endif -} - -void QKineticScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime) -{ - qKSDebug() << "QKS::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])"; - - QPointF deltaPixel = deltaPixelRaw; - - // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms) - if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / pixelPerMeter * 1000) > qreal(2.5)) - deltaPixel = deltaPixelRaw * qreal(2.5) * pixelPerMeter / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength(); - - qreal inversSmoothingFactor = ((qreal(1) - dragVelocitySmoothingFactor) * qreal(deltaTime) / qreal(1000)); - QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / pixelPerMeter; - newv = newv * (qreal(1) - inversSmoothingFactor) + releaseVelocity * inversSmoothingFactor; - - // newv = newv * dragVelocitySmoothingFactor + velocity * (qreal(1) - dragVelocitySmoothingFactor); - - if (deltaPixel.x()) - releaseVelocity.setX(qBound(-maximumVelocity, newv.x(), maximumVelocity)); - if (deltaPixel.y()) - releaseVelocity.setY(qBound(-maximumVelocity, newv.y(), maximumVelocity)); - - qKSDebug() << " --> new velocity:" << releaseVelocity; -} - -qreal QKineticScrollerPrivate::decelerate(qreal v, qreal t) -{ - qreal result = v * qPow(exponentialDecelerationBase, t); - qreal linear = linearDecelerationFactor * t; - if (qAbs(result) > linear) - return result + (result < 0 ? linear : -linear); - else - return 0; -} - -/*! Calculates the current velocity during scrolling - */ -QPointF QKineticScrollerPrivate::calculateVelocity(qreal time) -{ - QPointF velocity; - - // -- x coordinate - if (overshootX) { - if (overshootSpringConstantRoot * (time-overshootStartTimeX) < M_PI) // prevent swinging around - velocity.setX(overshootVelocity.x() * qCos(overshootSpringConstantRoot * (time - overshootStartTimeX))); - else - velocity.setX(-overshootVelocity.x()); - - } else { - qreal newVelocity = decelerate(releaseVelocity.x(), time); - - if (scrollToX) { - if (qAbs(newVelocity) < qreal(30.0 / 1000) /* 30mm/s */) - newVelocity = qreal(30.0 / 1000) * qSign(releaseVelocity.x()); - - } else { - if (qAbs(newVelocity) < qreal(0.5) * qreal(framesPerSecond) / pixelPerMeter /* 0.5 [pix/frame] */) - newVelocity = 0; - } - - velocity.setX(newVelocity); - } - - // -- y coordinate - if (overshootY) { - if (overshootSpringConstantRoot * (time-overshootStartTimeY) < M_PI) // prevent swinging around - velocity.setY(overshootVelocity.y() * qCos(overshootSpringConstantRoot * (time - overshootStartTimeY))); - else - velocity.setY(-overshootVelocity.y()); - - } else { - qreal newVelocity = decelerate(releaseVelocity.y(), time); - - if (scrollToY) { - if (qAbs(newVelocity) < qreal(30.0 / 1000) /* 30mm/s */) - newVelocity = qreal(30.0 / 1000) * qSign(releaseVelocity.y()); - - } else { - if (qAbs(newVelocity) < qreal(0.5) * qreal(framesPerSecond) / pixelPerMeter /* 0.5 [pix/frame] */) - newVelocity = 0; - } - - velocity.setY(newVelocity); - } - - return velocity; -} - - -void QKineticScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp) -{ - Q_Q(QKineticScroller); - - QPointF deltaPixel = position - lastPosition; - qint64 deltaTime = timestamp - lastTimestamp; - - if (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); - //qKSDebug() << "QKS::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy; - if (alpha <= 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 - QPointF maxPos = q->maximumContentPosition(); - bool canScrollX = maxPos.x() || (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn); - bool canScrollY = maxPos.y() || (vOvershootPolicy == QKineticScroller::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; -// } - - if (canScrollX) - lastPosition.setX(position.x()); - if (canScrollY) - lastPosition.setY(position.y()); - lastTimestamp = timestamp; -} - - - -bool QKineticScrollerPrivate::pressWhileInactive(QKineticScroller::Input, const QPointF &position, qint64 timestamp) -{ - Q_Q(QKineticScroller); - - if ((q->maximumContentPosition() > qreal(0)) || - (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn) || - (vOvershootPolicy == QKineticScroller::OvershootAlwaysOn)) { - if (q->canStartScrollingAt(position)) { - lastPosition = pressPosition = position; - lastTimestamp = pressTimestamp = timestamp; - setState(QKineticScroller::StatePressed); - } - } - return false; -} - -bool QKineticScrollerPrivate::releaseWhilePressed(QKineticScroller::Input, const QPointF &, qint64) -{ - if (overshootX || overshootY) - setState(QKineticScroller::StateScrolling); - else - setState(QKineticScroller::StateInactive); - return false; -} - -bool QKineticScrollerPrivate::moveWhilePressed(QKineticScroller::Input, const QPointF &position, qint64 timestamp) -{ - Q_Q(QKineticScroller); - - QPointF deltaPixel = position - pressPosition; - - bool moveStarted = ((deltaPixel.manhattanLength() / pixelPerMeter) > dragStartDistance); - - if (moveStarted) { - qreal deltaXtoY = qAbs(pressPosition.x() - position.x()) - qAbs(pressPosition.y() - position.y()); - deltaXtoY /= pixelPerMeter; - - QPointF maxPos = q->maximumContentPosition(); - bool canScrollX = (maxPos.x() > 0); - bool canScrollY = (maxPos.y() > 0); - - if (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn) - canScrollX = true; - if (vOvershootPolicy == QKineticScroller::OvershootAlwaysOn) - canScrollY = true; - - if (deltaXtoY < 0) { - if (!canScrollY && (!canScrollX || (-deltaXtoY >= dragStartDirectionErrorMargin))) - moveStarted = false; - } else { - if (!canScrollX && (!canScrollY || (deltaXtoY >= dragStartDirectionErrorMargin))) - moveStarted = false; - } - } - - if (moveStarted) { - q->cancelPress(pressPosition); - setState(QKineticScroller::StateDragging); - - // subtract the dragStartDistance - deltaPixel = deltaPixel - deltaPixel * (dragStartDistance / deltaPixel.manhattanLength()); - - if (!deltaPixel.isNull()) { - // handleDrag updates lastPosition, lastTimestamp and velocity - handleDrag(pressPosition + deltaPixel, timestamp); - } - } - return moveStarted; -} - -bool QKineticScrollerPrivate::moveWhileDragging(QKineticScroller::Input, const QPointF &position, qint64 timestamp) -{ - // handleDrag updates lastPosition, lastTimestamp and velocity - handleDrag(position, timestamp); - return true; -} - -void QKineticScrollerPrivate::timerEventWhileDragging() -{ - if (!dragDistance.isNull()) { - qKSDebug() << "QKS::timerEventWhileDragging() -- dragDistance:" << dragDistance; - - setContentPositionHelper(-dragDistance); - dragDistance = QPointF(0, 0); - } -} - -bool QKineticScrollerPrivate::releaseWhileDragging(QKineticScroller::Input, const QPointF &, qint64 timestamp) -{ - Q_Q(QKineticScroller); - - // calculate the fastSwipe velocity - QPointF maxPos = q->maximumContentPosition(); - QPointF fastSwipeVelocity = QPoint(0, 0); - QSizeF size = q->viewportSize(); - if (size.width()) - fastSwipeVelocity.setX(qMin(maximumVelocity, maxPos.x() / size.width() * fastSwipeBaseVelocity)); - if (size.height()) - fastSwipeVelocity.setY(qMin(maximumVelocity, maxPos.y() / size.height() * fastSwipeBaseVelocity)); - - if (fastSwipeMaximumTime && - ((timestamp - pressTimestamp) < qint64(fastSwipeMaximumTime * 1000)) && - (oldVelocity > fastSwipeMinimumVelocity)) { - - // more than one fast swipe in a row: add fastSwipeVelocity - 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; - - releaseVelocity.setX(signX * (oldVelocity.x() + (oldVelocity.x() > 0 ? fastSwipeVelocity.x() : -fastSwipeVelocity.x()))); - releaseVelocity.setY(signY * (oldVelocity.y() + (oldVelocity.y() > 0 ? fastSwipeVelocity.y() : -fastSwipeVelocity.y()))); - - } else if (releaseVelocity >= minimumVelocity) { - - // if we have a fast swipe, accelerate it to the fastSwipe velocity - if ((qAbs(releaseVelocity.x()) > maximumNonAcceleratedVelocity) && - (fastSwipeVelocity.x() > maximumNonAcceleratedVelocity)) { - releaseVelocity.setX(releaseVelocity.x() > 0 ? fastSwipeVelocity.x() : -fastSwipeVelocity.x()); - } - if ((qAbs(releaseVelocity.y()) > maximumNonAcceleratedVelocity) && - (fastSwipeVelocity.y() > maximumNonAcceleratedVelocity)) { - releaseVelocity.setY(releaseVelocity.y() > 0 ? fastSwipeVelocity.y() : -fastSwipeVelocity.y()); - } - - } - - qKSDebug() << "QKS::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << minimumVelocity; - if (overshootX || overshootY) - setState(QKineticScroller::StateScrolling); - else if (releaseVelocity >= minimumVelocity) - setState(QKineticScroller::StateScrolling); - else - setState(QKineticScroller::StateInactive); - - return true; -} - -void QKineticScrollerPrivate::timerEventWhileScrolling() -{ - qreal deltaTime = qreal(scrollRelativeTimer.restart()) / 1000; - qreal time = qreal(scrollAbsoluteTimer.elapsed()) / 1000; - - // calculate the velocity for the passed interval deltatime. - // using the midpoint of the interval gives a better precision than using just time. - QPointF newVelocity = calculateVelocity(time - deltaTime / 2); - QPointF deltaPos = newVelocity * deltaTime * pixelPerMeter; - - // -- move (convert from [m/s] to [pix/frame] - if (!deltaPos.isNull()) - setContentPositionHelper(deltaPos); - - qKSDebug() << "QKS::timerEventWhileScrolling() -- DeltaPos:" << deltaPos << "- NewVel:" << newVelocity << "- Time:" << time; - - if (newVelocity.isNull() || - (releaseVelocity.isNull() && !scrollToX && !scrollToY && !overshootX && !overshootY)) - // if (newVelocity.isNull()) - setState(QKineticScroller::StateInactive); -} - -bool QKineticScrollerPrivate::pressWhileScrolling(QKineticScroller::Input, const QPointF &, qint64) -{ - setState(QKineticScroller::StatePressed); - return true; -} - -void QKineticScrollerPrivate::setState(QKineticScroller::State newstate) -{ - Q_Q(QKineticScroller); - - if (state == newstate) - return; - - qKSDebug() << "QKS::setState(" << stateName(newstate) << ")"; - - switch (newstate) { - case QKineticScroller::StateInactive: - if (state == QKineticScroller::StateScrolling) { - if (timerId) { - killTimer(timerId); - timerId = 0; - } else { - qKSDebug() << " --> state change from " << stateName(state) << " to " << stateName(newstate) << ", but timer is not active."; - } - } - releaseVelocity = QPointF(0, 0); - break; - - case QKineticScroller::StatePressed: - if (timerId) { - killTimer(timerId); - timerId = 0; - } - scrollToX = false; - scrollToY = false; - oldVelocity = releaseVelocity; - // releaseVelocity = QPointF(0, 0); - break; - - case QKineticScroller::StateDragging: - - dragDistance = QPointF(0, 0); - if (state == QKineticScroller::StatePressed) { - if (!timerId) { - timerId = startTimer(1000 / framesPerSecond); - } else { - qKSDebug() << " --> state change from " << stateName(state) << " to " << stateName(newstate) << ", but timer is already active."; - } - } - - break; - - case QKineticScroller::StateScrolling: - if (!timerId) { - timerId = startTimer(1000 / framesPerSecond); - } - scrollRelativeTimer.start(); - scrollAbsoluteTimer.start(); - - if (state == QKineticScroller::StateDragging) { - // TODO: better calculate StartTime using the current releaseVelocity - overshootStartTimeX = overshootStartTimeY = qreal(scrollAbsoluteTimer.elapsed()) / 1000 - M_PI / (overshootSpringConstantRoot * 2); - overshootVelocity = overshootPosition / pixelPerMeter * overshootSpringConstantRoot; - } - - break; - } - - qSwap(state, newstate); - q->stateChanged(newstate); -} - -/*! - Starts scrolling the widget so that the point \a pos is visible inside - the viewport. - - If the specified point cannot be reached, the contents are scrolled to the - nearest valid position (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 (1 second for Maemo 5). - The final speed at the end position is not guaranteed to be zero. - - \sa ensureVisible(), maximumContentPosition() -*/ -void QKineticScroller::scrollTo(const QPointF &pos, int scrollTime) -{ - Q_D(QKineticScroller); - - if (scrollTime <= 0) - scrollTime = 1; - - qKSDebug() << "QKS::scrollTo(" << pos << " [pix], " << scrollTime << " [ms])"; - - qreal time = qreal(scrollTime) / 1000; - - if ((pos == contentPosition()) || - (d->state == QKineticScroller::StatePressed) || - (d->state == QKineticScroller::StateDragging)) { - return; - } - - // estimate the minimal start velocity - // if the start velocity is below that then the scrolling would stop before scrollTime. - //qreal vMin = d->linearDecelerationFactor * time / qPow(d->exponentialDecelerationBase, time); - - /* - // estimate of the distance passed within the vMin time during scrollTime - qreal distMin = qreal(scrollTime) * vMin / 2.0; - - QPointF v = QPointF((pos.x()-contentPosition().x()) / distMin * vMin, - (pos.y()-contentPosition().y()) / distMin * vMin); - - */ - - // v(t) = vstart * exponentialDecelerationBase ^ t - linearDecelerationFactor * t - // pos(t) = integrate(v(t) * dt) - // pos(t) = vstart * (eDB ^ t / ln(eDB) + C) - lDF / 2 * t ^ 2 - // - // pos(time) = pos - contentsPos() - // vstart = ((lDF / 2) * time ^ 2 + (pos - contentPos())) / (eDB ^ time / ln(eDB) + C) - // (for C = -1/ln(eDB) ) - - QPointF scrollDir(qSign(pos.x() - contentPosition().x()), - qSign(pos.y() - contentPosition().y())); - - QPointF v = (scrollDir * (d->linearDecelerationFactor / qreal(2)) * qreal(time) * qreal(time) + (pos - contentPosition()) / d->pixelPerMeter); - if (d->exponentialDecelerationBase != qreal(1)) - v /= (qPow(d->exponentialDecelerationBase, time) - 1) / qLn(d->exponentialDecelerationBase); - else - v /= time; - - d->scrollToPosition = pos; - d->scrollToX = true; - d->scrollToY = true; - d->releaseVelocity = v; - d->scrollRelativeTimer.restart(); - d->scrollAbsoluteTimer.restart(); - d->setState(QKineticScroller::StateScrolling); -} - -/*! - Starts scrolling the widget so that the point \a pos is visible inside the - viewport with margins specified in pixels by \a xmargin and \a ymargin. - - If the specified point cannot be reached, the contents are scrolled to the - nearest valid position. The default value for both margins is 50 pixels. - - This function performs the actual scrolling by calling scrollTo(). - - \sa maximumContentPosition() -*/ -void QKineticScroller::ensureVisible(const QPointF &pos, int xmargin, int ymargin, int scrollTime) -{ - QSizeF visible = viewportSize(); - QPointF currentPos = contentPosition(); - - qKSDebug() << "QKS::ensureVisible(" << pos << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])"; - qKSDebug() << " --> content position:" << contentPosition(); - - QRectF posRect(pos.x() - xmargin, pos.y() - ymargin, 2 * xmargin, 2 * ymargin); - QRectF visibleRect(currentPos, visible); - - if (visibleRect.contains(posRect)) - return; - - QPointF newPos = currentPos; - if (posRect.top() < visibleRect.top()) - newPos.setY(posRect.top()); - else if (posRect.bottom() > visibleRect.bottom()) - newPos.setY(posRect.bottom() - visible.height()); - if (posRect.left() < visibleRect.left()) - newPos.setX(posRect.left()); - else if (posRect.right() > visibleRect.right()) - newPos.setY(posRect.right() - visible.width()); - - scrollTo(newPos, scrollTime); -} - - -/*! \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. - - \deltaPos is the amout of pixels the current content position should be moved -*/ -void QKineticScrollerPrivate::setContentPositionHelper(const QPointF &deltaPos) -{ - Q_Q(QKineticScroller); - - if (state == QKineticScroller::StateDragging && overshootDragResistanceFactor) - overshootPosition /= overshootDragResistanceFactor; - - QPointF oldPos = q->contentPosition() + overshootPosition; - QPointF newPos = oldPos + deltaPos; - QPointF maxPos = q->maximumContentPosition(); - - QPointF oldScrollToDist = scrollToPosition - oldPos; - QPointF newScrollToDist = scrollToPosition - newPos; - - qKSDebug() << "QKS::setContentPositionHelper(" << deltaPos << " [pix])"; - qKSDebug() << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos; - - QPointF oldClampedPos; - oldClampedPos.setX(qBound(qreal(0), oldPos.x(), maxPos.x())); - oldClampedPos.setY(qBound(qreal(0), oldPos.y(), maxPos.y())); - - QPointF newClampedPos; - newClampedPos.setX(qBound(qreal(0), newPos.x(), maxPos.x())); - newClampedPos.setY(qBound(qreal(0), newPos.y(), maxPos.y())); - - // --- handle overshooting and stop if the coordinate is going back inside the normal area - bool alwaysOvershootX = (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn); - bool alwaysOvershootY = (vOvershootPolicy == QKineticScroller::OvershootAlwaysOn); - bool noOvershootX = (hOvershootPolicy == QKineticScroller::OvershootAlwaysOff) || - ((state == QKineticScroller::StateDragging) && !overshootDragResistanceFactor); - bool noOvershootY = (vOvershootPolicy == QKineticScroller::OvershootAlwaysOff) || - ((state == QKineticScroller::StateDragging) && !overshootDragResistanceFactor); - bool canOvershootX = !noOvershootX && (alwaysOvershootX || maxPos.x()); - bool canOvershootY = !noOvershootY && (alwaysOvershootY || maxPos.y()); - - 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; - - if (state == QKineticScroller::StateDragging && overshootDragResistanceFactor) { - oldOvershootX *= overshootDragResistanceFactor; - oldOvershootY *= overshootDragResistanceFactor; - newOvershootX *= overshootDragResistanceFactor; - newOvershootY *= overshootDragResistanceFactor; - } - - // -- stop at the maximum overshoot distance (if set) - if (!overshootMaximumDistance.isNull()) { - newOvershootX = qBound(-overshootMaximumDistance.x() * pixelPerMeter, newOvershootX, overshootMaximumDistance.x() * pixelPerMeter); - - newOvershootY = qBound(-overshootMaximumDistance.y() * pixelPerMeter, newOvershootY, overshootMaximumDistance.y() * pixelPerMeter); - } - - // --- sanity check for scrollTo in case we can't even scroll that direction - if (!(maxPos.x() || alwaysOvershootX)) - scrollToX = false; - if (!(maxPos.y() || alwaysOvershootY)) - scrollToY = false; - - // --- handle crossing over borders (scrollTo and overshoot) - qKSDebug() << " --> old overshoot Y:" << oldOvershootY << "- new overshoot Y:" << newOvershootY; - // -- x axis - if (scrollToX && qSign(oldScrollToDist.x()) != qSign(newScrollToDist.x())) { - newClampedPos.setX(scrollToPosition.x()); - newOvershootX = 0; - releaseVelocity.setX(0); - scrollToX = false; - - } else if (oldOvershootX && (qSign(oldOvershootX) != qSign(newOvershootX))) { - newClampedPos.setX((oldOvershootX < 0) ? 0 : maxPos.x()); - newOvershootX = 0; - releaseVelocity.setX(0); - overshootVelocity.setX(0); - overshootX = false; - - } else if (!oldOvershootX && newOvershootX) { - overshootStartTimeX = qreal(scrollAbsoluteTimer.elapsed()) / 1000; - overshootVelocity.setX(calculateVelocity(overshootStartTimeX).x()); - - // restrict the overshoot to overshootMaximumDistance - qreal maxOvershootVelocity = overshootMaximumDistance.x() * overshootSpringConstantRoot; - if (overshootMaximumDistance.x() && qAbs(overshootVelocity.x()) > maxOvershootVelocity) - overshootVelocity.setX(maxOvershootVelocity * qSign(newOvershootX)); - - // -- prevent going into overshoot too far - if (state != QKineticScroller::StateDragging) - newOvershootX = qSign(newOvershootX) * qreal(0.0001); - - scrollToX = false; - overshootX = true; - } - - // -- y axis - if (scrollToY && qSign(oldScrollToDist.y()) != qSign(newScrollToDist.y())) { - newClampedPos.setY(scrollToPosition.y()); - newOvershootY = 0; - releaseVelocity.setY(0); - scrollToY = false; - - } else if (oldOvershootY && (qSign(oldOvershootY) != qSign(newOvershootY))) { - newClampedPos.setY((oldOvershootY < 0) ? 0 : maxPos.y()); - newOvershootY = 0; - releaseVelocity.setY(0); - overshootVelocity.setY(0); - overshootY = false; - - } else if (!oldOvershootY && newOvershootY) { - overshootStartTimeY = qreal(scrollAbsoluteTimer.elapsed()) / 1000; - overshootVelocity.setY(calculateVelocity(overshootStartTimeY).y()); - - // -- restrict the overshoot to overshootMaximumDistance - qreal maxOvershootVelocity = overshootMaximumDistance.y() * overshootSpringConstantRoot; - if (overshootMaximumDistance.y() && (qAbs(overshootVelocity.y()) > maxOvershootVelocity)) - overshootVelocity.setY(maxOvershootVelocity * qSign(newOvershootY)); - - // -- prevent going into overshoot too far - if (state != QKineticScroller::StateDragging) - newOvershootY = qSign(newOvershootY) * qreal(0.0001); - - scrollToY = false; - overshootY = true; - } - - overshootPosition.setX(newOvershootX); - overshootPosition.setY(newOvershootY); - - q->setContentPosition(newClampedPos, overshootPosition); - - if (debugHook) - debugHook(debugHookUser, calculateVelocity(qreal(scrollAbsoluteTimer.elapsed()) / 1000), newClampedPos, overshootPosition); - - qKSDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition << - "- overshoot x/y?:" << overshootX << "/" << overshootY << "- scrollto x/y?:" << scrollToX << "/" << scrollToY; -} - - -/*! - \enum QKineticScroller::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). -*/ - - -/*! - If kinetic scrolling can be started at the given content's \a position, - this function needs to return true; otherwise it needs to return false. - - The default value is true, regardless of \a position. -*/ -bool QKineticScroller::canStartScrollingAt(const QPointF &position) const -{ - Q_UNUSED(position); - return true; -} - -/*! - Since a mouse press is always delivered normally when the scroller is in - the StateInactive state, we may need to cancel it as soon as the user - has moved the mouse far enough to actually start a kinetic scroll - operation. - - The \a pressPosition parameter can be used to find out which widget (or - graphics item) received the mouse press in the first place. - - Subclasses may choose to simulate a fake mouse release event for that - widget (or graphics item), preferably \bold not within its boundaries. - The default implementation does nothing. -*/ -void QKineticScroller::cancelPress(const QPointF &pressPosition) -{ - Q_UNUSED(pressPosition); -} - - -/*! - This function get called whenever the state of the kinetic scroller changes. - The old state is supplied as \a oldState, while the new state is returned by - calling state(). - - The default implementation does nothing. - - \sa state() -*/ -void QKineticScroller::stateChanged(State oldState) -{ - Q_UNUSED(oldState); -} - -/*! - \fn QPointF QKineticScroller::maximumContentPosition() const - - Returns the maximum valid content position. The minimum is always \c - (0,0). - - \sa scrollTo() -*/ - -/*! - \fn QSizeF QKineticScroller::viewportSize() const - - Returns the size of the currently visible content positions. In the - case where an QAbstractScrollArea is used, this is equivalent to the - viewport() size. - - \sa scrollTo() -*/ - -/*! - \fn QPointF QKineticScroller::contentPosition() const - - \brief Returns the current position of the content. - - Note that overshooting is not considered to be "real" scrolling so the - position might be (0,0) even if the user is currently dragging the - widget outside the "normal" maximumContentPosition(). - - \sa maximumContentPosition() -*/ - - -/*! - \fn void QKineticScroller::setContentPosition(const QPointF &position, const QPointF &overshoot) - - Set the content's \a position. This parameter will always be in the - valid range QPointF(0, 0) and maximumContentPosition(). - - In the case where overshooting is required, the \a overshoot parameter - will give the direction and the absolute distance to overshoot. - - \sa maximumContentPosition() -*/ - -QT_END_NAMESPACE diff --git a/qkineticscroller.h b/qkineticscroller.h deleted file mode 100644 index e0378d4..0000000 --- a/qkineticscroller.h +++ /dev/null @@ -1,165 +0,0 @@ -/**************************************************************************** -** -** 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 QABSTRACTKINETICSCROLLER_H -#define QABSTRACTKINETICSCROLLER_H - -#include -#include -#include -#include - -QT_BEGIN_HEADER - -QT_BEGIN_NAMESPACE - -QT_MODULE(Gui) - -class QKineticScrollerPrivate; - -class Q_GUI_EXPORT QKineticScroller -{ -public: - virtual ~QKineticScroller(); - - bool isEnabled() const; - void setEnabled(bool b); - - enum State - { - StateInactive, - StatePressed, - StateDragging, - StateScrolling, - }; - - State state() const; - void reset(); - - enum OvershootPolicy - { - OvershootWhenScrollable, - OvershootAlwaysOff, - OvershootAlwaysOn, - }; - - OvershootPolicy horizontalOvershootPolicy() const; - void setHorizontalOvershootPolicy(OvershootPolicy policy); - OvershootPolicy verticalOvershootPolicy() const; - void setVerticalOvershootPolicy(OvershootPolicy policy); - - enum ScrollMetric - { - DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF) - - LinearDecelerationFactor, // qreal [m/s^2] - ExponentialDecelerationBase, // qreal - OvershootSpringConstant, // qreal [kg/s^2] - OvershootDragResistanceFactor, // qreal [0..1] - OvershootMaximumDistance, // QPointF([m], [m]) - - DragStartDistance, // qreal [m] - DragStartDirectionErrorMargin, // qreal [m] - - MinimumVelocity, // qreal [m/s] - MaximumVelocity, // qreal [m/s] - MaximumNonAcceleratedVelocity, // qreal [m/s] - - MaximumClickThroughVelocity, // qreal [m/s] - AxisLockThreshold, // qreal [0..1] atan(|min(dx,dy)|/|max(dx,dy)|) - - FramesPerSecond, // int [frames/s] - - FastSwipeMaximumTime, // qreal [s] - FastSwipeMinimumVelocity, // qreal [m/s] - FastSwipeBaseVelocity, // qreal [m/s] - - ScrollMetricCount - }; - - QVariant scrollMetric(ScrollMetric metric) const; - void setScrollMetric(ScrollMetric metric, const QVariant &value); - void resetScrollMetrics(); - - void scrollTo(const QPointF &pos, int scrollTime = 1000); - void ensureVisible(const QPointF &pos, int xmargin, int ymargin, int scrollTime = 1000); - - qreal dpi() const; - void setDpi(qreal dpi); - void setDpiFromWidget(QWidget *widget); - - void registerDebugHook(void (*callback)(void *user, const QPointF &releaseVelocity, const QPointF &position, const QPointF &overshootPosition), void *user); - -protected: - explicit QKineticScroller(); - - virtual QSizeF viewportSize() const = 0; - virtual QPointF maximumContentPosition() const = 0; - virtual QPointF contentPosition() const = 0; - virtual void setContentPosition(const QPointF &pos, const QPointF &overshootDelta) = 0; - - virtual void stateChanged(State oldState); - virtual bool canStartScrollingAt(const QPointF &pos) const; - virtual void cancelPress(const QPointF &pressPos); - - enum Input { - InputPress, - InputMove, - InputRelease - }; - - bool handleInput(Input input, const QPointF &position, qint64 timestamp); - - QKineticScroller(QKineticScrollerPrivate &dd); - QScopedPointer d_ptr; - -private: - Q_DISABLE_COPY(QKineticScroller) - Q_DECLARE_PRIVATE(QKineticScroller) -}; - -QT_END_NAMESPACE - -Q_DECLARE_METATYPE(QKineticScroller *) - -QT_END_HEADER - -#endif // QKINETICSCROLLER_H diff --git a/qkineticscroller_p.h b/qkineticscroller_p.h deleted file mode 100644 index 599bb68..0000000 --- a/qkineticscroller_p.h +++ /dev/null @@ -1,156 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// -// 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 -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QKineticScrollerPrivate : public QObject -{ - Q_OBJECT - Q_DECLARE_PUBLIC(QKineticScroller) - -public: - QKineticScrollerPrivate(); - virtual ~QKineticScrollerPrivate(); - void init(); - - void setState(QKineticScroller::State s); - - bool pressWhileInactive(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); - bool moveWhilePressed(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); - bool releaseWhilePressed(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); - bool moveWhileDragging(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); - bool releaseWhileDragging(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); - bool pressWhileScrolling(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); - - void timerEvent(QTimerEvent *); - void timerEventWhileDragging(); - void timerEventWhileScrolling(); - - void handleDrag(const QPointF &position, qint64 timestamp); - void updateVelocity(const QPointF &deltaPixel, qint64 deltaTime); - - qreal decelerate(qreal v, qreal t); - QPointF calculateVelocity(qreal time); - void setContentPositionHelper(const QPointF &deltaPos); - - - static const char *stateName(QKineticScroller::State state); - static const char *inputName(QKineticScroller::Input input); - - // metrics - - qreal dragVelocitySmoothingFactor; - qreal linearDecelerationFactor; - qreal exponentialDecelerationBase; - qreal overshootSpringConstantRoot; - qreal overshootDragResistanceFactor; - QPointF overshootMaximumDistance; - qreal overshootMaximumVelocity; - qreal dragStartDistance; - qreal dragStartDirectionErrorMargin; - qreal maximumVelocity; - qreal minimumVelocity; - qreal maximumNonAcceleratedVelocity; - qreal maximumClickThroughVelocity; - qreal axisLockThreshold; - qreal fastSwipeBaseVelocity; - qreal fastSwipeMinimumVelocity; - qreal fastSwipeMaximumTime; - int framesPerSecond; - - // state - - bool enabled; - QKineticScroller::State state; - QKineticScroller::OvershootPolicy hOvershootPolicy; - QKineticScroller::OvershootPolicy vOvershootPolicy; - QPointF oldVelocity; - - QPointF pressPosition; - QPointF lastPosition; - qint64 pressTimestamp; - qint64 lastTimestamp; - - QPointF dragDistance; // the distance we should move during the next drag timer event - - QPointF scrollToPosition; - bool scrollToX; - bool scrollToY; - - QPointF overshootPosition; // the number of pixels we are overshooting (before overshootDragResistanceFactor) - bool overshootX; - bool overshootY; - - qreal pixelPerMeter; - - QElapsedTimer scrollRelativeTimer; - QElapsedTimer scrollAbsoluteTimer; - QPointF releaseVelocity; // the starting velocity of the scrolling state - QPointF overshootVelocity; // the starting velocity when going into overshoot - qreal overshootStartTimeX; - qreal overshootStartTimeY; - int timerId; - - void (*debugHook)(void *user, const QPointF &releaseVelocity, const QPointF &position, const QPointF &overshootPosition); - void *debugHookUser; - - QKineticScroller *q_ptr; -}; - -QT_END_NAMESPACE diff --git a/qscrollareakineticscroller.cpp b/qscrollareakineticscroller.cpp deleted file mode 100644 index ecef0c6..0000000 --- a/qscrollareakineticscroller.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/**************************************************************************** -** -** 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qabstractitemview.h" -#include "qgraphicsview.h" -#include "qgraphicsitem.h" - - - -#include - -QT_BEGIN_NAMESPACE - - -bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event) -{ - return QCoreApplication::sendSpontaneousEvent(receiver, event); -} - - - -class Q_GUI_EXPORT QScrollAreaKineticScrollerPrivate -{ -public: - QScrollAreaKineticScroller *q_ptr; - - QAbstractScrollArea *area; - QItemSelection oldSelection; // the selection before the first mouse down - bool ignoreEvents; - QPoint lastOvershoot; // don't change the type to QPointF or we might never shoot completely back. - QPointer childWidget; // the widget where the mouse was pressed - QPointF fractionalPosition; - - QElapsedTimer timer; - - QScrollAreaKineticScrollerPrivate() - : q_ptr(0) - , area(0) - , ignoreEvents(false) - {} - - void init() - { - timer.start(); - } - - static QWidget *mouseTransparentChildAtGlobalPos(QWidget *parent, const QPoint &gp) - { - foreach (QWidget *w, parent->findChildren()) { - if (w && !w->isWindow() && w->isVisible() && (w->rect().contains(w->mapFromGlobal(gp)))) { - if (QWidget *t = mouseTransparentChildAtGlobalPos(w, gp)) - return t; - else - return w; - } - } - return 0; - } - - void sendEvent(QWidget *w, QEvent *e) - { - ignoreEvents = true; - qt_sendSpontaneousEvent(w, e); - ignoreEvents = false; - } -}; - -/*! - * The QScrollAreaKineticScroller class implements the QKineticScroller for the AbstractScrollArea - */ -QScrollAreaKineticScroller::QScrollAreaKineticScroller() - : QObject() - , QKineticScroller() - , d_ptr(new QScrollAreaKineticScrollerPrivate()) -{ - Q_D(QScrollAreaKineticScroller); - d->q_ptr = this; - d->init(); -} - -/*! - Destroys the scroller. -*/ -QScrollAreaKineticScroller::~QScrollAreaKineticScroller() -{} - -void QScrollAreaKineticScroller::setWidget(QAbstractScrollArea *widget) -{ - Q_D(QScrollAreaKineticScroller); - - if (d->area) { - d->area->viewport()->removeEventFilter(this); - d->area->viewport()->move(d->area->viewport()->pos() + d->lastOvershoot ); - } - - reset(); - d->area = widget; - d->lastOvershoot = QPoint(); - d->fractionalPosition = QPointF(); - - setParent(d->area); - if (d->area) { - d->area->viewport()->installEventFilter(this); - if (QAbstractItemView *view = qobject_cast(d->area)) { - view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - view->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - } - } -} - -bool QScrollAreaKineticScroller::eventFilter(QObject *o, QEvent *e) -{ - Q_D(QScrollAreaKineticScroller); - - qint64 timestamp = d->timer.elapsed(); - bool res = false; - bool isMouseEvent = false; - bool isMouseDown = false; - - if (d->area && (o == d->area->viewport()) && - !d->ignoreEvents && d->area->isEnabled() && isEnabled()) { - switch (e->type()) { - case QEvent::MouseButtonPress: { - // re-install the event filter so that we get the mouse release before all other filters. - // this is an evil hack, but hard to work around without prioritized event filters. - d->area->viewport()->removeEventFilter(this); - d->area->viewport()->installEventFilter(this); - // fall through - QMouseEvent *me = static_cast(e); - isMouseEvent = true; - res = handleInput(InputPress, me->posF() - d->lastOvershoot, timestamp); - break; - } - case QEvent::MouseButtonDblClick: { - isMouseDown = true; - // fall through - QMouseEvent *me = static_cast(e); - isMouseEvent = true; - res = handleInput(InputPress, me->posF() - d->lastOvershoot, timestamp); - break; - } - case QEvent::MouseMove: { - QMouseEvent *me = static_cast(e); - isMouseEvent = true; - res = handleInput(InputMove, me->posF() - d->lastOvershoot, timestamp); - break; - } - case QEvent::MouseButtonRelease: { - QMouseEvent *me = static_cast(e); - isMouseEvent = true; - res = handleInput(InputRelease, me->posF() - d->lastOvershoot, timestamp); - break; - } - - case QEvent::ChildAdded: - case QEvent::ChildRemoved: { - QChildEvent *ce = static_cast(e); - if (ce->child()->isWidgetType()) { - static_cast(ce->child())->setAttribute(Qt::WA_TransparentForMouseEvents, ce->added()); - if ((e->type() == QEvent::ChildRemoved) && (ce->child() == d->childWidget)) - d->childWidget = 0; - } - break; - } - default: - break; - } - } - - res |= QObject::eventFilter(o, e); - - // all child widgets get the WA_TransparentForMouseEvents attribute, so - // we have to find the "real" widget to forward this event to... - if (!res && isMouseEvent) { - QMouseEvent *me = static_cast(e); - - if (isMouseDown) - d->childWidget = d->mouseTransparentChildAtGlobalPos(d->area->viewport(), me->globalPos()); - - if (d->childWidget) { - QMouseEvent cme(me->type(), me->pos(), - me->globalPos(), me->button(), me->buttons(), me->modifiers()); - d->sendEvent(d->childWidget, &cme); - res = cme.isAccepted(); - } - } - return res; -} - -void QScrollAreaKineticScroller::stateChanged(State oldState) -{ - Q_D(QScrollAreaKineticScroller); - - // hack to remove the current selection as soon as we start to scroll - if (QAbstractItemView *view = qobject_cast(d->area)) { - if (oldState == StateInactive) - d->oldSelection = view->selectionModel()->selection(); // store the selection - } -} - -bool QScrollAreaKineticScroller::canStartScrollingAt(const QPointF &pos) const -{ - Q_D(const QScrollAreaKineticScroller); - - // don't start scrolling when a drag mode has been set. - // don't start scrolling on a movable item. - if (QGraphicsView *view = qobject_cast(d->area)) { - if (view->dragMode() != QGraphicsView::NoDrag) - return false; - - QGraphicsItem *childItem = view->itemAt(pos.toPoint()); - - if (childItem && (childItem->flags() & QGraphicsItem::ItemIsMovable)) - return false; - } - - // don't start scrolling on a QSlider - if (qobject_cast(d->mouseTransparentChildAtGlobalPos(d->area->viewport(), d->area->viewport()->mapToGlobal(pos.toPoint())))) { - return false; - } - - return true; -} - -void QScrollAreaKineticScroller::cancelPress(const QPointF &pressPos) -{ - Q_D(QScrollAreaKineticScroller); - - QPoint pos = pressPos.toPoint(); - - /* - if (QWidget *childWidget = d->mouseTransparentChildAtGlobalPos(d->area->viewport(), d->view->mapToGlobal(pos))) { - */ - if (d->childWidget) { - // simulate a mouse-move and mouse-release far, far away - QPoint faraway(-1000, -1000); - QMouseEvent cmem(QEvent::MouseMove, faraway, d->childWidget->mapToGlobal(faraway), - Qt::LeftButton, QApplication::mouseButtons() | Qt::LeftButton, - QApplication::keyboardModifiers()); - d->sendEvent(d->childWidget, &cmem); - - QMouseEvent cmer(QEvent::MouseButtonRelease, faraway, d->childWidget->mapToGlobal(faraway), - Qt::LeftButton, QApplication::mouseButtons() & ~Qt::LeftButton, - QApplication::keyboardModifiers()); - d->sendEvent(d->childWidget, &cmer); - } - - if (QAbstractItemView *view = qobject_cast(d->area)) - view->selectionModel()->select(d->oldSelection, QItemSelectionModel::ClearAndSelect); -} - -QSizeF QScrollAreaKineticScroller::viewportSize() const -{ - Q_D(const QScrollAreaKineticScroller); - - return d->area ? QSizeF(d->area->viewport()->size()) : QSizeF(); -} - -QPointF QScrollAreaKineticScroller::maximumContentPosition() const -{ - Q_D(const QScrollAreaKineticScroller); - - QPointF p; - if (d->area) { - if (QScrollBar *s = d->area->horizontalScrollBar()) - p.setX(s->maximum()); - if (QScrollBar *s = d->area->verticalScrollBar()) - p.setY(s->maximum()); - } - return p; -} - -QPointF QScrollAreaKineticScroller::contentPosition() const -{ - Q_D(const QScrollAreaKineticScroller); - - QPointF p; - if (d->area) { - if (QScrollBar *s = d->area->horizontalScrollBar()) - p.setX(s->value()); - if (QScrollBar *s = d->area->verticalScrollBar()) - p.setY(s->value()); - p += d->fractionalPosition; - } - return p; -} - -void QScrollAreaKineticScroller::setContentPosition(const QPointF &p, const QPointF &overshootDelta) -{ - Q_D(QScrollAreaKineticScroller); - - if (d->area) { - if (QScrollBar *s = d->area->horizontalScrollBar()) - s->setValue(p.x()); - if (QScrollBar *s = d->area->verticalScrollBar()) - s->setValue(p.y()); - - QPoint delta = d->lastOvershoot - overshootDelta.toPoint(); - if (!delta.isNull()) - d->area->viewport()->move(d->area->viewport()->pos() + delta); - d->lastOvershoot -= delta; - - d->fractionalPosition = QPointF(p.x() - int(p.x()), p.y() - int(p.y())); - } -} - -QT_END_NAMESPACE diff --git a/qscrollareakineticscroller.h b/qscrollareakineticscroller.h deleted file mode 100644 index 77b0caf..0000000 --- a/qscrollareakineticscroller.h +++ /dev/null @@ -1,92 +0,0 @@ -/**************************************************************************** -** -** 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 QSCROLLAREAKINETICSCROLLER_H -#define QSCROLLAREAKINETICSCROLLER_H - -#include -#include - -QT_BEGIN_HEADER - -QT_BEGIN_NAMESPACE - -QT_MODULE(Gui) - -class QScrollAreaKineticScrollerPrivate; - -class Q_GUI_EXPORT QScrollAreaKineticScroller : public QObject, public QKineticScroller -{ - Q_OBJECT -public: - QScrollAreaKineticScroller(); - // QScrollAreaKineticScroller(QScrollAreaKineticScrollerPrivate &dd); - ~QScrollAreaKineticScroller(); - - void setWidget(QAbstractScrollArea *widget); - - bool eventFilter(QObject *o, QEvent *e); - -protected: - - virtual QSizeF viewportSize() const; - virtual QPointF maximumContentPosition() const; - virtual QPointF contentPosition() const; - virtual void setContentPosition(const QPointF &pos, const QPointF &overshootDelta); - - virtual void stateChanged(State oldState); - virtual bool canStartScrollingAt(const QPointF &pos) const; - virtual void cancelPress(const QPointF &pressPos); - - QScopedPointer d_ptr; - -private: - Q_DISABLE_COPY(QScrollAreaKineticScroller) - Q_DECLARE_PRIVATE(QScrollAreaKineticScroller) -}; - -QT_END_NAMESPACE - -Q_DECLARE_METATYPE(QScrollAreaKineticScroller *) - -QT_END_HEADER - -#endif // QKINETICSCROLLER_H diff --git a/scroller/qkineticscroller.cpp b/scroller/qkineticscroller.cpp new file mode 100644 index 0000000..b9c31a8 --- /dev/null +++ b/scroller/qkineticscroller.cpp @@ -0,0 +1,1237 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +#define KINETIC_SCROLLER_DEBUG + +#ifdef KINETIC_SCROLLER_DEBUG +# define qKSDebug qDebug +#else +# define qKSDebug while (false) qDebug +#endif + + +inline bool operator<=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f); +} +inline bool operator<(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) < f) && (qAbs(p.y()) < f); +} + +inline bool operator>=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f); +} +inline bool operator>(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) > f) || (qAbs(p.y()) > f); +} + +inline QPointF qAbs(const QPointF &p) +{ + return QPointF(qAbs(p.x()), qAbs(p.y())); +} + +inline int qSign(qreal r) +{ + return (r < 0) ? -1 : ((r > 0) ? 1 : 0); +} + + +/*! + \class QKineticScroller + \brief The QKineticScroller class enables kinetic scrolling for any scrolling widget or graphics item. + \ingroup qtmaemo5 + \since 4.6 + \preliminary + + 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. + + To enable kinetic scrolling for a widget or graphics item, you need to + derive from this class and implement at least all the pure-virtual + functions. + + Qt for Maemo 5 already comes with two implementations for + QScrollArea and QWebView, and those kinetic scrollers are + automatically instantiated and attached to these widgets on creation. + In the QScrollArea case, the kinetic scroller is initially + disabled. However, for QItemView and QScrollArea derived classes + it is enabled by default. You can obtain these automatically created + objects via a dynamic property: + + \code + // disable the kinetic scroller on scrollArea + QKineticScroller *scroller = scrollArea->property("kineticScroller") + .value(); + if (scroller) + scroller->setEnabled(false); + \endcode + + In addition there is also an example on how you would add kinetic + scrolling to a QGraphicsView based application in \c maemobrowser + examples in the \c maemo5 examples directory. + + The kinetic scroller installs an event filter on the widget to handle mouse + presses and moves on the widget \mdash presses and moves on a device's touch screen + are also handled by this mechanism. These events will be interpreted as scroll actions + depending on the current state() of the scroller. + + Even though this kinetic scroller has a huge number of settings, we + recommend that you leave them all at their default values. In case you + really want to change them you can try out the \c kineticscroller + example in the \c maemo5 examples directory. + + \sa QWidget +*/ + + +/*! + Constructs a new kinetic scroller. +*/ +QKineticScroller::QKineticScroller() + : d_ptr(new QKineticScrollerPrivate()) +{ + Q_D(QKineticScroller); + d->q_ptr = this; + d->init(); +} + +/*! \internal +*/ +QKineticScroller::QKineticScroller(QKineticScrollerPrivate &dd) + : d_ptr(&dd) +{ + Q_D(QKineticScroller); + d->q_ptr = this; + d->init(); +} + +/*! + Destroys the scroller. +*/ +QKineticScroller::~QKineticScroller() +{ +} + +/*! + \enum QKineticScroller::State + + This enum describes the possible states the kinetic scroller can be in. + + \value Inactive The scroller is inactive. It may also have been disabled. + \value Pressed The user has pressed the mouse button (or pressed the + the touch screen). + \value Dragging The user is dragging the mouse cursor (or other input + point) over the scroll area. + \value Scrolling Scrolling is occurring without direct user input. +*/ + +QKineticScrollerPrivate::QKineticScrollerPrivate() + : enabled(true) + , state(QKineticScroller::StateInactive) + , hOvershootPolicy(QKineticScroller::OvershootWhenScrollable) + , vOvershootPolicy(QKineticScroller::OvershootWhenScrollable) + , pressTimestamp(0) + , lastTimestamp(0) + , scrollToX(false) + , scrollToY(false) + , overshootX(false) + , overshootY(false) + , debugHook(0) + , debugHookUser(0) +{ } + +QKineticScrollerPrivate::~QKineticScrollerPrivate() +{ } + +void QKineticScrollerPrivate::init() +{ + Q_Q(QKineticScroller); + q->setDpiFromWidget(0); + q->resetScrollMetrics(); +} + +void QKineticScroller::registerDebugHook(void (*callback)(void *user, const QPointF &releaseVelocity, const QPointF &position, const QPointF &overshootPosition), void *user) +{ + Q_D(QKineticScroller); + d->debugHook = callback; + d->debugHookUser = user; +} + +void QKineticScroller::resetScrollMetrics() +{ + static QMap metrics; + +#ifdef Q_WS_MAEMO_5 + metrics.insert(DragVelocitySmoothingFactor, qreal(0.15)); + metrics.insert(ExponentialDecelerationBase, qreal(0.38)); // 0.85^20 + metrics.insert(LinearDecelerationFactor, qreal(0)); + metrics.insert(OvershootSpringConstant, qreal(0.1)); + metrics.insert(OvershootDragResistanceFactor, qreal(1)); + metrics.insert(OvershootMaximumDistance, QPointF(qreal(150), qreal(150))); + metrics.insert(OvershootMaximumVelocity, qreal(2600)); + metrics.insert(DragStartDistance, qreal(25)); + metrics.insert(DragStartDirectionErrorMargin, qreal(10)); + metrics.insert(MaximumVelocity, qreal(70000)); + metrics.insert(MinimumVelocity, qreal(200)); + metrics.insert(MaximumNonAcceleratedVelocity, qreal(5600)); + metrics.insert(MaximumClickThroughVelocity, qreal(700)); + metrics.insert(AxisLockThreshold, qreal(0)); + metrics.insert(FastSwipeBaseVelocity, qreal(540)); + metrics.insert(FastSwipeMinimumVelocity, qreal(800)); + metrics.insert(FastSwipeMaximumTime, qreal(0.125)); + metrics.insert(FramesPerSecond, qreal(20)); +#else + metrics.insert(DragVelocitySmoothingFactor, qreal(0.02)); + metrics.insert(ExponentialDecelerationBase, qreal(1)); + metrics.insert(LinearDecelerationFactor, qreal(0.38)); + metrics.insert(OvershootSpringConstant, qreal(15.0)); + metrics.insert(OvershootDragResistanceFactor, qreal(0.5)); + metrics.insert(OvershootMaximumDistance, QPointF(0,0)); // QPointF(qreal(14.25 / 1000), qreal(14.25 / 1000))); + metrics.insert(DragStartDistance, qreal(2.5 / 1000)); + metrics.insert(DragStartDirectionErrorMargin, qreal(1.0 / 1000)); + metrics.insert(MaximumVelocity, qreal(6650.0 / 1000)); + metrics.insert(MaximumVelocity, qreal(6650.0 / 1000)); + metrics.insert(MinimumVelocity, qreal(30.0 / 1000)); + metrics.insert(MaximumNonAcceleratedVelocity, qreal(532.0 / 1000)); + metrics.insert(MaximumClickThroughVelocity, qreal(66.5 / 1000)); + metrics.insert(AxisLockThreshold, qreal(0)); + metrics.insert(FastSwipeBaseVelocity, qreal(51.3 / 1000)); + metrics.insert(FastSwipeMinimumVelocity, qreal(76.0 / 1000)); + metrics.insert(FastSwipeMaximumTime, qreal(0.125)); + metrics.insert(FramesPerSecond, qreal(60)); +#endif + + if (!metrics.isEmpty()) { + for (QMap::const_iterator it = metrics.constBegin(); it != metrics.constEnd(); ++it) + setScrollMetric(it.key(), it.value()); + if (metrics.count() != ScrollMetricCount) + qWarning("QKineticScroller::resetAllMetrics(): scroll metrics parameter set did not contain all metrics."); + } else { + qWarning("QKineticScroller::resetAllMetrics(): no platform default parameter set available."); + } +} + + +const char *QKineticScrollerPrivate::stateName(QKineticScroller::State state) +{ + switch (state) { + case QKineticScroller::StateInactive: return "inactive"; + case QKineticScroller::StatePressed: return "pressed"; + case QKineticScroller::StateDragging: return "dragging"; + case QKineticScroller::StateScrolling: return "scrolling"; + default: return "(invalid)"; + } +} + +const char *QKineticScrollerPrivate::inputName(QKineticScroller::Input input) +{ + switch (input) { + case QKineticScroller::InputPress: return "press"; + case QKineticScroller::InputMove: return "move"; + case QKineticScroller::InputRelease: return "release"; + default: return "(invalid)"; + } +} + + + + +void QKineticScrollerPrivate::timerEvent(QTimerEvent *e) +{ + if (e->timerId() != timerId) { + QObject::timerEvent(e); + return; + } + + struct timerevent { + QKineticScroller::State state; + typedef void (QKineticScrollerPrivate::*timerhandler_t)(); + timerhandler_t handler; + }; + + timerevent timerevents[] = { + { QKineticScroller::StateDragging, &QKineticScrollerPrivate::timerEventWhileDragging }, + { QKineticScroller::StateScrolling, &QKineticScrollerPrivate::timerEventWhileScrolling }, + }; + + for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) { + timerevent *te = timerevents + i; + + if (state == te->state) { + (this->*te->handler)(); + return; + } + } + + if (timerId) { + qWarning() << "Unhandled timer event, while in state " << stateName(state); + killTimer(timerId); + timerId = 0; + } + // otherwise this is a timer event that was already queued when the + // timer was killed, so just ignore it +} + +bool QKineticScroller::handleInput(Input input, const QPointF &position, qint64 timestamp) +{ + Q_D(QKineticScroller); + + + qKSDebug() << "QKS::handleInput(" << input << ", " << position << ", " << timestamp << ")"; + struct statechange { + State state; + Input input; + typedef bool (QKineticScrollerPrivate::*inputhandler_t)(Input input, const QPointF &position, qint64 timestamp); + inputhandler_t handler; + }; + + statechange statechanges[] = { + { StateInactive, InputPress, &QKineticScrollerPrivate::pressWhileInactive }, + { StatePressed, InputMove, &QKineticScrollerPrivate::moveWhilePressed }, + { StatePressed, InputRelease, &QKineticScrollerPrivate::releaseWhilePressed }, + { StateDragging, InputMove, &QKineticScrollerPrivate::moveWhileDragging }, + { StateDragging, InputRelease, &QKineticScrollerPrivate::releaseWhileDragging }, + { StateScrolling, InputPress, &QKineticScrollerPrivate::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)(input, position, timestamp); + } + + qWarning() << "Unhandled input: got input " << d->inputName(input) << " while in state " << d->stateName(d->state); + return false; +} + +bool QKineticScroller::isEnabled() const +{ + Q_D(const QKineticScroller); + return d->enabled; +} + +void QKineticScroller::setEnabled(bool b) +{ + Q_D(QKineticScroller); + d->enabled = b; +} + +QKineticScroller::State QKineticScroller::state() const +{ + Q_D(const QKineticScroller); + return d->state; +} + +/*! + Resets the internal state of the kinetic scroller. This function is not + needed for normal use. This function only needs to be called if the + kinetic scroller is being re-attached to a different widget. +*/ +void QKineticScroller::reset() +{ + Q_D(QKineticScroller); + + d->setState(StateInactive); +} + +QKineticScroller::OvershootPolicy QKineticScroller::horizontalOvershootPolicy() const +{ + Q_D(const QKineticScroller); + return d->hOvershootPolicy; +} + +void QKineticScroller::setHorizontalOvershootPolicy(QKineticScroller::OvershootPolicy policy) +{ + Q_D(QKineticScroller); + d->hOvershootPolicy = policy; +} + +QKineticScroller::OvershootPolicy QKineticScroller::verticalOvershootPolicy() const +{ + Q_D(const QKineticScroller); + return d->vOvershootPolicy; +} + +void QKineticScroller::setVerticalOvershootPolicy(QKineticScroller::OvershootPolicy policy) +{ + Q_D(QKineticScroller); + d->vOvershootPolicy = policy; +} + +QVariant QKineticScroller::scrollMetric(ScrollMetric metric) const +{ + Q_D(const QKineticScroller); + + switch (metric) { + case DragVelocitySmoothingFactor: return d->dragVelocitySmoothingFactor; + case LinearDecelerationFactor: return d->linearDecelerationFactor; + case ExponentialDecelerationBase: return d->exponentialDecelerationBase; + case OvershootSpringConstant: return d->overshootSpringConstantRoot * d->overshootSpringConstantRoot; + case OvershootDragResistanceFactor: return d->overshootDragResistanceFactor; + case OvershootMaximumDistance: return d->overshootMaximumDistance; + case DragStartDistance: return d->dragStartDistance; + case DragStartDirectionErrorMargin: return d->dragStartDirectionErrorMargin; + case MinimumVelocity: return d->minimumVelocity; + case MaximumVelocity: return d->maximumVelocity; + case MaximumNonAcceleratedVelocity: return d->maximumNonAcceleratedVelocity; + case MaximumClickThroughVelocity: return d->maximumClickThroughVelocity; + case AxisLockThreshold: return d->axisLockThreshold; + case FramesPerSecond: return d->framesPerSecond; + case FastSwipeMaximumTime: return d->fastSwipeMaximumTime; + case FastSwipeMinimumVelocity: return d->fastSwipeMinimumVelocity; + case FastSwipeBaseVelocity: return d->fastSwipeBaseVelocity; + case ScrollMetricCount: break; + } + return QVariant(); +} + + +void QKineticScroller::setScrollMetric(ScrollMetric metric, const QVariant &value) +{ + Q_D(QKineticScroller); + + switch (metric) { + case DragVelocitySmoothingFactor: d->dragVelocitySmoothingFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case LinearDecelerationFactor: d->linearDecelerationFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case ExponentialDecelerationBase: d->exponentialDecelerationBase = qBound(qreal(0), value.toReal(), qreal(1)); break; + case OvershootSpringConstant: d->overshootSpringConstantRoot = qSqrt(value.toReal()); break; + case OvershootDragResistanceFactor: d->overshootDragResistanceFactor = value.toReal(); break; + case OvershootMaximumDistance: d->overshootMaximumDistance = value.toPointF(); break; + case DragStartDistance: d->dragStartDistance = value.toReal(); break; + case DragStartDirectionErrorMargin: d->dragStartDirectionErrorMargin = value.toReal(); break; + case MinimumVelocity: d->minimumVelocity = value.toReal(); break; + case MaximumVelocity: d->maximumVelocity = value.toReal(); break; + case MaximumNonAcceleratedVelocity: d->maximumNonAcceleratedVelocity = value.toReal(); break; + case MaximumClickThroughVelocity: d->maximumClickThroughVelocity = value.toReal(); break; + case AxisLockThreshold: d->axisLockThreshold = qBound(qreal(0), value.toReal(), qreal(1)); break; + case FramesPerSecond: d->framesPerSecond = qBound(1, value.toInt(), 100); break; + case FastSwipeMaximumTime: d->fastSwipeMaximumTime = value.toReal(); break; + case FastSwipeMinimumVelocity: d->fastSwipeMinimumVelocity = value.toReal(); break; + case FastSwipeBaseVelocity: d->fastSwipeBaseVelocity = value.toReal(); break; + case ScrollMetricCount: break; + } +} + +qreal QKineticScroller::dpi() const +{ + Q_D(const QKineticScroller); + return d->pixelPerMeter / qreal(39.3700787); +} + + +void QKineticScroller::setDpi(qreal dpi) +{ + Q_D(QKineticScroller); + d->pixelPerMeter = dpi * qreal(39.3700787); +} + + +void QKineticScroller::setDpiFromWidget(QWidget *widget) +{ +#ifdef Q_WS_MAEMO_5 + // 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. + setDpi(qreal(260)); +#else + if (!widget) + widget = QApplication::desktop(); + setDpi(qreal(widget->physicalDpiX() + widget->physicalDpiY()) / qreal(2)); +#endif +} + +void QKineticScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime) +{ + qKSDebug() << "QKS::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])"; + + QPointF deltaPixel = deltaPixelRaw; + + // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms) + if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / pixelPerMeter * 1000) > qreal(2.5)) + deltaPixel = deltaPixelRaw * qreal(2.5) * pixelPerMeter / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength(); + + qreal inversSmoothingFactor = ((qreal(1) - dragVelocitySmoothingFactor) * qreal(deltaTime) / qreal(1000)); + QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / pixelPerMeter; + newv = newv * (qreal(1) - inversSmoothingFactor) + releaseVelocity * inversSmoothingFactor; + + // newv = newv * dragVelocitySmoothingFactor + velocity * (qreal(1) - dragVelocitySmoothingFactor); + + if (deltaPixel.x()) + releaseVelocity.setX(qBound(-maximumVelocity, newv.x(), maximumVelocity)); + if (deltaPixel.y()) + releaseVelocity.setY(qBound(-maximumVelocity, newv.y(), maximumVelocity)); + + qKSDebug() << " --> new velocity:" << releaseVelocity; +} + +qreal QKineticScrollerPrivate::decelerate(qreal v, qreal t) +{ + qreal result = v * qPow(exponentialDecelerationBase, t); + qreal linear = linearDecelerationFactor * t; + if (qAbs(result) > linear) + return result + (result < 0 ? linear : -linear); + else + return 0; +} + +/*! Calculates the current velocity during scrolling + */ +QPointF QKineticScrollerPrivate::calculateVelocity(qreal time) +{ + QPointF velocity; + + // -- x coordinate + if (overshootX) { + if (overshootSpringConstantRoot * (time-overshootStartTimeX) < M_PI) // prevent swinging around + velocity.setX(overshootVelocity.x() * qCos(overshootSpringConstantRoot * (time - overshootStartTimeX))); + else + velocity.setX(-overshootVelocity.x()); + + } else { + qreal newVelocity = decelerate(releaseVelocity.x(), time); + + if (scrollToX) { + if (qAbs(newVelocity) < qreal(30.0 / 1000) /* 30mm/s */) + newVelocity = qreal(30.0 / 1000) * qSign(releaseVelocity.x()); + + } else { + if (qAbs(newVelocity) < qreal(0.5) * qreal(framesPerSecond) / pixelPerMeter /* 0.5 [pix/frame] */) + newVelocity = 0; + } + + velocity.setX(newVelocity); + } + + // -- y coordinate + if (overshootY) { + if (overshootSpringConstantRoot * (time-overshootStartTimeY) < M_PI) // prevent swinging around + velocity.setY(overshootVelocity.y() * qCos(overshootSpringConstantRoot * (time - overshootStartTimeY))); + else + velocity.setY(-overshootVelocity.y()); + + } else { + qreal newVelocity = decelerate(releaseVelocity.y(), time); + + if (scrollToY) { + if (qAbs(newVelocity) < qreal(30.0 / 1000) /* 30mm/s */) + newVelocity = qreal(30.0 / 1000) * qSign(releaseVelocity.y()); + + } else { + if (qAbs(newVelocity) < qreal(0.5) * qreal(framesPerSecond) / pixelPerMeter /* 0.5 [pix/frame] */) + newVelocity = 0; + } + + velocity.setY(newVelocity); + } + + return velocity; +} + + +void QKineticScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + QPointF deltaPixel = position - lastPosition; + qint64 deltaTime = timestamp - lastTimestamp; + + if (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); + //qKSDebug() << "QKS::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy; + if (alpha <= 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 + QPointF maxPos = q->maximumContentPosition(); + bool canScrollX = maxPos.x() || (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn); + bool canScrollY = maxPos.y() || (vOvershootPolicy == QKineticScroller::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; +// } + + if (canScrollX) + lastPosition.setX(position.x()); + if (canScrollY) + lastPosition.setY(position.y()); + lastTimestamp = timestamp; +} + + + +bool QKineticScrollerPrivate::pressWhileInactive(QKineticScroller::Input, const QPointF &position, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + if ((q->maximumContentPosition() > qreal(0)) || + (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn) || + (vOvershootPolicy == QKineticScroller::OvershootAlwaysOn)) { + if (q->canStartScrollingAt(position)) { + lastPosition = pressPosition = position; + lastTimestamp = pressTimestamp = timestamp; + setState(QKineticScroller::StatePressed); + } + } + return false; +} + +bool QKineticScrollerPrivate::releaseWhilePressed(QKineticScroller::Input, const QPointF &, qint64) +{ + if (overshootX || overshootY) + setState(QKineticScroller::StateScrolling); + else + setState(QKineticScroller::StateInactive); + return false; +} + +bool QKineticScrollerPrivate::moveWhilePressed(QKineticScroller::Input, const QPointF &position, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + QPointF deltaPixel = position - pressPosition; + + bool moveStarted = ((deltaPixel.manhattanLength() / pixelPerMeter) > dragStartDistance); + + if (moveStarted) { + qreal deltaXtoY = qAbs(pressPosition.x() - position.x()) - qAbs(pressPosition.y() - position.y()); + deltaXtoY /= pixelPerMeter; + + QPointF maxPos = q->maximumContentPosition(); + bool canScrollX = (maxPos.x() > 0); + bool canScrollY = (maxPos.y() > 0); + + if (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn) + canScrollX = true; + if (vOvershootPolicy == QKineticScroller::OvershootAlwaysOn) + canScrollY = true; + + if (deltaXtoY < 0) { + if (!canScrollY && (!canScrollX || (-deltaXtoY >= dragStartDirectionErrorMargin))) + moveStarted = false; + } else { + if (!canScrollX && (!canScrollY || (deltaXtoY >= dragStartDirectionErrorMargin))) + moveStarted = false; + } + } + + if (moveStarted) { + q->cancelPress(pressPosition); + setState(QKineticScroller::StateDragging); + + // subtract the dragStartDistance + deltaPixel = deltaPixel - deltaPixel * (dragStartDistance / deltaPixel.manhattanLength()); + + if (!deltaPixel.isNull()) { + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(pressPosition + deltaPixel, timestamp); + } + } + return moveStarted; +} + +bool QKineticScrollerPrivate::moveWhileDragging(QKineticScroller::Input, const QPointF &position, qint64 timestamp) +{ + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(position, timestamp); + return true; +} + +void QKineticScrollerPrivate::timerEventWhileDragging() +{ + if (!dragDistance.isNull()) { + qKSDebug() << "QKS::timerEventWhileDragging() -- dragDistance:" << dragDistance; + + setContentPositionHelper(-dragDistance); + dragDistance = QPointF(0, 0); + } +} + +bool QKineticScrollerPrivate::releaseWhileDragging(QKineticScroller::Input, const QPointF &, qint64 timestamp) +{ + Q_Q(QKineticScroller); + + // calculate the fastSwipe velocity + QPointF maxPos = q->maximumContentPosition(); + QPointF fastSwipeVelocity = QPoint(0, 0); + QSizeF size = q->viewportSize(); + if (size.width()) + fastSwipeVelocity.setX(qMin(maximumVelocity, maxPos.x() / size.width() * fastSwipeBaseVelocity)); + if (size.height()) + fastSwipeVelocity.setY(qMin(maximumVelocity, maxPos.y() / size.height() * fastSwipeBaseVelocity)); + + if (fastSwipeMaximumTime && + ((timestamp - pressTimestamp) < qint64(fastSwipeMaximumTime * 1000)) && + (oldVelocity > fastSwipeMinimumVelocity)) { + + // more than one fast swipe in a row: add fastSwipeVelocity + 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; + + releaseVelocity.setX(signX * (oldVelocity.x() + (oldVelocity.x() > 0 ? fastSwipeVelocity.x() : -fastSwipeVelocity.x()))); + releaseVelocity.setY(signY * (oldVelocity.y() + (oldVelocity.y() > 0 ? fastSwipeVelocity.y() : -fastSwipeVelocity.y()))); + + } else if (releaseVelocity >= minimumVelocity) { + + // if we have a fast swipe, accelerate it to the fastSwipe velocity + if ((qAbs(releaseVelocity.x()) > maximumNonAcceleratedVelocity) && + (fastSwipeVelocity.x() > maximumNonAcceleratedVelocity)) { + releaseVelocity.setX(releaseVelocity.x() > 0 ? fastSwipeVelocity.x() : -fastSwipeVelocity.x()); + } + if ((qAbs(releaseVelocity.y()) > maximumNonAcceleratedVelocity) && + (fastSwipeVelocity.y() > maximumNonAcceleratedVelocity)) { + releaseVelocity.setY(releaseVelocity.y() > 0 ? fastSwipeVelocity.y() : -fastSwipeVelocity.y()); + } + + } + + qKSDebug() << "QKS::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << minimumVelocity; + if (overshootX || overshootY) + setState(QKineticScroller::StateScrolling); + else if (releaseVelocity >= minimumVelocity) + setState(QKineticScroller::StateScrolling); + else + setState(QKineticScroller::StateInactive); + + return true; +} + +void QKineticScrollerPrivate::timerEventWhileScrolling() +{ + qreal deltaTime = qreal(scrollRelativeTimer.restart()) / 1000; + qreal time = qreal(scrollAbsoluteTimer.elapsed()) / 1000; + + // calculate the velocity for the passed interval deltatime. + // using the midpoint of the interval gives a better precision than using just time. + QPointF newVelocity = calculateVelocity(time - deltaTime / 2); + QPointF deltaPos = newVelocity * deltaTime * pixelPerMeter; + + // -- move (convert from [m/s] to [pix/frame] + if (!deltaPos.isNull()) + setContentPositionHelper(deltaPos); + + qKSDebug() << "QKS::timerEventWhileScrolling() -- DeltaPos:" << deltaPos << "- NewVel:" << newVelocity << "- Time:" << time; + + if (newVelocity.isNull() || + (releaseVelocity.isNull() && !scrollToX && !scrollToY && !overshootX && !overshootY)) + // if (newVelocity.isNull()) + setState(QKineticScroller::StateInactive); +} + +bool QKineticScrollerPrivate::pressWhileScrolling(QKineticScroller::Input, const QPointF &, qint64) +{ + setState(QKineticScroller::StatePressed); + return true; +} + +void QKineticScrollerPrivate::setState(QKineticScroller::State newstate) +{ + Q_Q(QKineticScroller); + + if (state == newstate) + return; + + qKSDebug() << "QKS::setState(" << stateName(newstate) << ")"; + + switch (newstate) { + case QKineticScroller::StateInactive: + if (state == QKineticScroller::StateScrolling) { + if (timerId) { + killTimer(timerId); + timerId = 0; + } else { + qKSDebug() << " --> state change from " << stateName(state) << " to " << stateName(newstate) << ", but timer is not active."; + } + } + releaseVelocity = QPointF(0, 0); + break; + + case QKineticScroller::StatePressed: + if (timerId) { + killTimer(timerId); + timerId = 0; + } + scrollToX = false; + scrollToY = false; + oldVelocity = releaseVelocity; + // releaseVelocity = QPointF(0, 0); + break; + + case QKineticScroller::StateDragging: + + dragDistance = QPointF(0, 0); + if (state == QKineticScroller::StatePressed) { + if (!timerId) { + timerId = startTimer(1000 / framesPerSecond); + } else { + qKSDebug() << " --> state change from " << stateName(state) << " to " << stateName(newstate) << ", but timer is already active."; + } + } + + break; + + case QKineticScroller::StateScrolling: + if (!timerId) { + timerId = startTimer(1000 / framesPerSecond); + } + scrollRelativeTimer.start(); + scrollAbsoluteTimer.start(); + + if (state == QKineticScroller::StateDragging) { + // TODO: better calculate StartTime using the current releaseVelocity + overshootStartTimeX = overshootStartTimeY = qreal(scrollAbsoluteTimer.elapsed()) / 1000 - M_PI / (overshootSpringConstantRoot * 2); + overshootVelocity = overshootPosition / pixelPerMeter * overshootSpringConstantRoot; + } + + break; + } + + qSwap(state, newstate); + q->stateChanged(newstate); +} + +/*! + Starts scrolling the widget so that the point \a pos is visible inside + the viewport. + + If the specified point cannot be reached, the contents are scrolled to the + nearest valid position (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 (1 second for Maemo 5). + The final speed at the end position is not guaranteed to be zero. + + \sa ensureVisible(), maximumContentPosition() +*/ +void QKineticScroller::scrollTo(const QPointF &pos, int scrollTime) +{ + Q_D(QKineticScroller); + + if (scrollTime <= 0) + scrollTime = 1; + + qKSDebug() << "QKS::scrollTo(" << pos << " [pix], " << scrollTime << " [ms])"; + + qreal time = qreal(scrollTime) / 1000; + + if ((pos == contentPosition()) || + (d->state == QKineticScroller::StatePressed) || + (d->state == QKineticScroller::StateDragging)) { + return; + } + + // estimate the minimal start velocity + // if the start velocity is below that then the scrolling would stop before scrollTime. + //qreal vMin = d->linearDecelerationFactor * time / qPow(d->exponentialDecelerationBase, time); + + /* + // estimate of the distance passed within the vMin time during scrollTime + qreal distMin = qreal(scrollTime) * vMin / 2.0; + + QPointF v = QPointF((pos.x()-contentPosition().x()) / distMin * vMin, + (pos.y()-contentPosition().y()) / distMin * vMin); + + */ + + // v(t) = vstart * exponentialDecelerationBase ^ t - linearDecelerationFactor * t + // pos(t) = integrate(v(t) * dt) + // pos(t) = vstart * (eDB ^ t / ln(eDB) + C) - lDF / 2 * t ^ 2 + // + // pos(time) = pos - contentsPos() + // vstart = ((lDF / 2) * time ^ 2 + (pos - contentPos())) / (eDB ^ time / ln(eDB) + C) + // (for C = -1/ln(eDB) ) + + QPointF scrollDir(qSign(pos.x() - contentPosition().x()), + qSign(pos.y() - contentPosition().y())); + + QPointF v = (scrollDir * (d->linearDecelerationFactor / qreal(2)) * qreal(time) * qreal(time) + (pos - contentPosition()) / d->pixelPerMeter); + if (d->exponentialDecelerationBase != qreal(1)) + v /= (qPow(d->exponentialDecelerationBase, time) - 1) / qLn(d->exponentialDecelerationBase); + else + v /= time; + + d->scrollToPosition = pos; + d->scrollToX = true; + d->scrollToY = true; + d->releaseVelocity = v; + d->scrollRelativeTimer.restart(); + d->scrollAbsoluteTimer.restart(); + d->setState(QKineticScroller::StateScrolling); +} + +/*! + Starts scrolling the widget so that the point \a pos is visible inside the + viewport with margins specified in pixels by \a xmargin and \a ymargin. + + If the specified point cannot be reached, the contents are scrolled to the + nearest valid position. The default value for both margins is 50 pixels. + + This function performs the actual scrolling by calling scrollTo(). + + \sa maximumContentPosition() +*/ +void QKineticScroller::ensureVisible(const QPointF &pos, int xmargin, int ymargin, int scrollTime) +{ + QSizeF visible = viewportSize(); + QPointF currentPos = contentPosition(); + + qKSDebug() << "QKS::ensureVisible(" << pos << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])"; + qKSDebug() << " --> content position:" << contentPosition(); + + QRectF posRect(pos.x() - xmargin, pos.y() - ymargin, 2 * xmargin, 2 * ymargin); + QRectF visibleRect(currentPos, visible); + + if (visibleRect.contains(posRect)) + return; + + QPointF newPos = currentPos; + if (posRect.top() < visibleRect.top()) + newPos.setY(posRect.top()); + else if (posRect.bottom() > visibleRect.bottom()) + newPos.setY(posRect.bottom() - visible.height()); + if (posRect.left() < visibleRect.left()) + newPos.setX(posRect.left()); + else if (posRect.right() > visibleRect.right()) + newPos.setY(posRect.right() - visible.width()); + + scrollTo(newPos, scrollTime); +} + + +/*! \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. + + \deltaPos is the amout of pixels the current content position should be moved +*/ +void QKineticScrollerPrivate::setContentPositionHelper(const QPointF &deltaPos) +{ + Q_Q(QKineticScroller); + + if (state == QKineticScroller::StateDragging && overshootDragResistanceFactor) + overshootPosition /= overshootDragResistanceFactor; + + QPointF oldPos = q->contentPosition() + overshootPosition; + QPointF newPos = oldPos + deltaPos; + QPointF maxPos = q->maximumContentPosition(); + + QPointF oldScrollToDist = scrollToPosition - oldPos; + QPointF newScrollToDist = scrollToPosition - newPos; + + qKSDebug() << "QKS::setContentPositionHelper(" << deltaPos << " [pix])"; + qKSDebug() << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos; + + QPointF oldClampedPos; + oldClampedPos.setX(qBound(qreal(0), oldPos.x(), maxPos.x())); + oldClampedPos.setY(qBound(qreal(0), oldPos.y(), maxPos.y())); + + QPointF newClampedPos; + newClampedPos.setX(qBound(qreal(0), newPos.x(), maxPos.x())); + newClampedPos.setY(qBound(qreal(0), newPos.y(), maxPos.y())); + + // --- handle overshooting and stop if the coordinate is going back inside the normal area + bool alwaysOvershootX = (hOvershootPolicy == QKineticScroller::OvershootAlwaysOn); + bool alwaysOvershootY = (vOvershootPolicy == QKineticScroller::OvershootAlwaysOn); + bool noOvershootX = (hOvershootPolicy == QKineticScroller::OvershootAlwaysOff) || + ((state == QKineticScroller::StateDragging) && !overshootDragResistanceFactor); + bool noOvershootY = (vOvershootPolicy == QKineticScroller::OvershootAlwaysOff) || + ((state == QKineticScroller::StateDragging) && !overshootDragResistanceFactor); + bool canOvershootX = !noOvershootX && (alwaysOvershootX || maxPos.x()); + bool canOvershootY = !noOvershootY && (alwaysOvershootY || maxPos.y()); + + 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; + + if (state == QKineticScroller::StateDragging && overshootDragResistanceFactor) { + oldOvershootX *= overshootDragResistanceFactor; + oldOvershootY *= overshootDragResistanceFactor; + newOvershootX *= overshootDragResistanceFactor; + newOvershootY *= overshootDragResistanceFactor; + } + + // -- stop at the maximum overshoot distance (if set) + if (!overshootMaximumDistance.isNull()) { + newOvershootX = qBound(-overshootMaximumDistance.x() * pixelPerMeter, newOvershootX, overshootMaximumDistance.x() * pixelPerMeter); + + newOvershootY = qBound(-overshootMaximumDistance.y() * pixelPerMeter, newOvershootY, overshootMaximumDistance.y() * pixelPerMeter); + } + + // --- sanity check for scrollTo in case we can't even scroll that direction + if (!(maxPos.x() || alwaysOvershootX)) + scrollToX = false; + if (!(maxPos.y() || alwaysOvershootY)) + scrollToY = false; + + // --- handle crossing over borders (scrollTo and overshoot) + qKSDebug() << " --> old overshoot Y:" << oldOvershootY << "- new overshoot Y:" << newOvershootY; + // -- x axis + if (scrollToX && qSign(oldScrollToDist.x()) != qSign(newScrollToDist.x())) { + newClampedPos.setX(scrollToPosition.x()); + newOvershootX = 0; + releaseVelocity.setX(0); + scrollToX = false; + + } else if (oldOvershootX && (qSign(oldOvershootX) != qSign(newOvershootX))) { + newClampedPos.setX((oldOvershootX < 0) ? 0 : maxPos.x()); + newOvershootX = 0; + releaseVelocity.setX(0); + overshootVelocity.setX(0); + overshootX = false; + + } else if (!oldOvershootX && newOvershootX) { + overshootStartTimeX = qreal(scrollAbsoluteTimer.elapsed()) / 1000; + overshootVelocity.setX(calculateVelocity(overshootStartTimeX).x()); + + // restrict the overshoot to overshootMaximumDistance + qreal maxOvershootVelocity = overshootMaximumDistance.x() * overshootSpringConstantRoot; + if (overshootMaximumDistance.x() && qAbs(overshootVelocity.x()) > maxOvershootVelocity) + overshootVelocity.setX(maxOvershootVelocity * qSign(newOvershootX)); + + // -- prevent going into overshoot too far + if (state != QKineticScroller::StateDragging) + newOvershootX = qSign(newOvershootX) * qreal(0.0001); + + scrollToX = false; + overshootX = true; + } + + // -- y axis + if (scrollToY && qSign(oldScrollToDist.y()) != qSign(newScrollToDist.y())) { + newClampedPos.setY(scrollToPosition.y()); + newOvershootY = 0; + releaseVelocity.setY(0); + scrollToY = false; + + } else if (oldOvershootY && (qSign(oldOvershootY) != qSign(newOvershootY))) { + newClampedPos.setY((oldOvershootY < 0) ? 0 : maxPos.y()); + newOvershootY = 0; + releaseVelocity.setY(0); + overshootVelocity.setY(0); + overshootY = false; + + } else if (!oldOvershootY && newOvershootY) { + overshootStartTimeY = qreal(scrollAbsoluteTimer.elapsed()) / 1000; + overshootVelocity.setY(calculateVelocity(overshootStartTimeY).y()); + + // -- restrict the overshoot to overshootMaximumDistance + qreal maxOvershootVelocity = overshootMaximumDistance.y() * overshootSpringConstantRoot; + if (overshootMaximumDistance.y() && (qAbs(overshootVelocity.y()) > maxOvershootVelocity)) + overshootVelocity.setY(maxOvershootVelocity * qSign(newOvershootY)); + + // -- prevent going into overshoot too far + if (state != QKineticScroller::StateDragging) + newOvershootY = qSign(newOvershootY) * qreal(0.0001); + + scrollToY = false; + overshootY = true; + } + + overshootPosition.setX(newOvershootX); + overshootPosition.setY(newOvershootY); + + q->setContentPosition(newClampedPos, overshootPosition); + + if (debugHook) + debugHook(debugHookUser, calculateVelocity(qreal(scrollAbsoluteTimer.elapsed()) / 1000), newClampedPos, overshootPosition); + + qKSDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition << + "- overshoot x/y?:" << overshootX << "/" << overshootY << "- scrollto x/y?:" << scrollToX << "/" << scrollToY; +} + + +/*! + \enum QKineticScroller::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). +*/ + + +/*! + If kinetic scrolling can be started at the given content's \a position, + this function needs to return true; otherwise it needs to return false. + + The default value is true, regardless of \a position. +*/ +bool QKineticScroller::canStartScrollingAt(const QPointF &position) const +{ + Q_UNUSED(position); + return true; +} + +/*! + Since a mouse press is always delivered normally when the scroller is in + the StateInactive state, we may need to cancel it as soon as the user + has moved the mouse far enough to actually start a kinetic scroll + operation. + + The \a pressPosition parameter can be used to find out which widget (or + graphics item) received the mouse press in the first place. + + Subclasses may choose to simulate a fake mouse release event for that + widget (or graphics item), preferably \bold not within its boundaries. + The default implementation does nothing. +*/ +void QKineticScroller::cancelPress(const QPointF &pressPosition) +{ + Q_UNUSED(pressPosition); +} + + +/*! + This function get called whenever the state of the kinetic scroller changes. + The old state is supplied as \a oldState, while the new state is returned by + calling state(). + + The default implementation does nothing. + + \sa state() +*/ +void QKineticScroller::stateChanged(State oldState) +{ + Q_UNUSED(oldState); +} + +/*! + \fn QPointF QKineticScroller::maximumContentPosition() const + + Returns the maximum valid content position. The minimum is always \c + (0,0). + + \sa scrollTo() +*/ + +/*! + \fn QSizeF QKineticScroller::viewportSize() const + + Returns the size of the currently visible content positions. In the + case where an QAbstractScrollArea is used, this is equivalent to the + viewport() size. + + \sa scrollTo() +*/ + +/*! + \fn QPointF QKineticScroller::contentPosition() const + + \brief Returns the current position of the content. + + Note that overshooting is not considered to be "real" scrolling so the + position might be (0,0) even if the user is currently dragging the + widget outside the "normal" maximumContentPosition(). + + \sa maximumContentPosition() +*/ + + +/*! + \fn void QKineticScroller::setContentPosition(const QPointF &position, const QPointF &overshoot) + + Set the content's \a position. This parameter will always be in the + valid range QPointF(0, 0) and maximumContentPosition(). + + In the case where overshooting is required, the \a overshoot parameter + will give the direction and the absolute distance to overshoot. + + \sa maximumContentPosition() +*/ + +QT_END_NAMESPACE diff --git a/scroller/qkineticscroller.h b/scroller/qkineticscroller.h new file mode 100644 index 0000000..e0378d4 --- /dev/null +++ b/scroller/qkineticscroller.h @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** 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 QABSTRACTKINETICSCROLLER_H +#define QABSTRACTKINETICSCROLLER_H + +#include +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QKineticScrollerPrivate; + +class Q_GUI_EXPORT QKineticScroller +{ +public: + virtual ~QKineticScroller(); + + bool isEnabled() const; + void setEnabled(bool b); + + enum State + { + StateInactive, + StatePressed, + StateDragging, + StateScrolling, + }; + + State state() const; + void reset(); + + enum OvershootPolicy + { + OvershootWhenScrollable, + OvershootAlwaysOff, + OvershootAlwaysOn, + }; + + OvershootPolicy horizontalOvershootPolicy() const; + void setHorizontalOvershootPolicy(OvershootPolicy policy); + OvershootPolicy verticalOvershootPolicy() const; + void setVerticalOvershootPolicy(OvershootPolicy policy); + + enum ScrollMetric + { + DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF) + + LinearDecelerationFactor, // qreal [m/s^2] + ExponentialDecelerationBase, // qreal + OvershootSpringConstant, // qreal [kg/s^2] + OvershootDragResistanceFactor, // qreal [0..1] + OvershootMaximumDistance, // QPointF([m], [m]) + + DragStartDistance, // qreal [m] + DragStartDirectionErrorMargin, // qreal [m] + + MinimumVelocity, // qreal [m/s] + MaximumVelocity, // qreal [m/s] + MaximumNonAcceleratedVelocity, // qreal [m/s] + + MaximumClickThroughVelocity, // qreal [m/s] + AxisLockThreshold, // qreal [0..1] atan(|min(dx,dy)|/|max(dx,dy)|) + + FramesPerSecond, // int [frames/s] + + FastSwipeMaximumTime, // qreal [s] + FastSwipeMinimumVelocity, // qreal [m/s] + FastSwipeBaseVelocity, // qreal [m/s] + + ScrollMetricCount + }; + + QVariant scrollMetric(ScrollMetric metric) const; + void setScrollMetric(ScrollMetric metric, const QVariant &value); + void resetScrollMetrics(); + + void scrollTo(const QPointF &pos, int scrollTime = 1000); + void ensureVisible(const QPointF &pos, int xmargin, int ymargin, int scrollTime = 1000); + + qreal dpi() const; + void setDpi(qreal dpi); + void setDpiFromWidget(QWidget *widget); + + void registerDebugHook(void (*callback)(void *user, const QPointF &releaseVelocity, const QPointF &position, const QPointF &overshootPosition), void *user); + +protected: + explicit QKineticScroller(); + + virtual QSizeF viewportSize() const = 0; + virtual QPointF maximumContentPosition() const = 0; + virtual QPointF contentPosition() const = 0; + virtual void setContentPosition(const QPointF &pos, const QPointF &overshootDelta) = 0; + + virtual void stateChanged(State oldState); + virtual bool canStartScrollingAt(const QPointF &pos) const; + virtual void cancelPress(const QPointF &pressPos); + + enum Input { + InputPress, + InputMove, + InputRelease + }; + + bool handleInput(Input input, const QPointF &position, qint64 timestamp); + + QKineticScroller(QKineticScrollerPrivate &dd); + QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(QKineticScroller) + Q_DECLARE_PRIVATE(QKineticScroller) +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QKineticScroller *) + +QT_END_HEADER + +#endif // QKINETICSCROLLER_H diff --git a/scroller/qkineticscroller_p.h b/scroller/qkineticscroller_p.h new file mode 100644 index 0000000..599bb68 --- /dev/null +++ b/scroller/qkineticscroller_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// +// 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 +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QKineticScrollerPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(QKineticScroller) + +public: + QKineticScrollerPrivate(); + virtual ~QKineticScrollerPrivate(); + void init(); + + void setState(QKineticScroller::State s); + + bool pressWhileInactive(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool moveWhilePressed(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool releaseWhilePressed(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool moveWhileDragging(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool releaseWhileDragging(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + bool pressWhileScrolling(QKineticScroller::Input input, const QPointF &position, qint64 timestamp); + + void timerEvent(QTimerEvent *); + void timerEventWhileDragging(); + void timerEventWhileScrolling(); + + void handleDrag(const QPointF &position, qint64 timestamp); + void updateVelocity(const QPointF &deltaPixel, qint64 deltaTime); + + qreal decelerate(qreal v, qreal t); + QPointF calculateVelocity(qreal time); + void setContentPositionHelper(const QPointF &deltaPos); + + + static const char *stateName(QKineticScroller::State state); + static const char *inputName(QKineticScroller::Input input); + + // metrics + + qreal dragVelocitySmoothingFactor; + qreal linearDecelerationFactor; + qreal exponentialDecelerationBase; + qreal overshootSpringConstantRoot; + qreal overshootDragResistanceFactor; + QPointF overshootMaximumDistance; + qreal overshootMaximumVelocity; + qreal dragStartDistance; + qreal dragStartDirectionErrorMargin; + qreal maximumVelocity; + qreal minimumVelocity; + qreal maximumNonAcceleratedVelocity; + qreal maximumClickThroughVelocity; + qreal axisLockThreshold; + qreal fastSwipeBaseVelocity; + qreal fastSwipeMinimumVelocity; + qreal fastSwipeMaximumTime; + int framesPerSecond; + + // state + + bool enabled; + QKineticScroller::State state; + QKineticScroller::OvershootPolicy hOvershootPolicy; + QKineticScroller::OvershootPolicy vOvershootPolicy; + QPointF oldVelocity; + + QPointF pressPosition; + QPointF lastPosition; + qint64 pressTimestamp; + qint64 lastTimestamp; + + QPointF dragDistance; // the distance we should move during the next drag timer event + + QPointF scrollToPosition; + bool scrollToX; + bool scrollToY; + + QPointF overshootPosition; // the number of pixels we are overshooting (before overshootDragResistanceFactor) + bool overshootX; + bool overshootY; + + qreal pixelPerMeter; + + QElapsedTimer scrollRelativeTimer; + QElapsedTimer scrollAbsoluteTimer; + QPointF releaseVelocity; // the starting velocity of the scrolling state + QPointF overshootVelocity; // the starting velocity when going into overshoot + qreal overshootStartTimeX; + qreal overshootStartTimeY; + int timerId; + + void (*debugHook)(void *user, const QPointF &releaseVelocity, const QPointF &position, const QPointF &overshootPosition); + void *debugHookUser; + + QKineticScroller *q_ptr; +}; + +QT_END_NAMESPACE diff --git a/scroller/scroller.pro b/scroller/scroller.pro new file mode 100644 index 0000000..b2f738d --- /dev/null +++ b/scroller/scroller.pro @@ -0,0 +1,12 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Mon Apr 19 14:02:12 2010 +###################################################################### + +TEMPLATE = lib +TARGET = +DEPENDPATH += . +INCLUDEPATH += . + +# Input +HEADERS += qkineticscroller.h qkineticscroller_p.h +SOURCES += qkineticscroller.cpp diff --git a/settingswidget.cpp b/settingswidget.cpp deleted file mode 100644 index 7f41a7b..0000000 --- a/settingswidget.cpp +++ /dev/null @@ -1,362 +0,0 @@ -/**************************************************************************** -** -** 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 - -#include "settingswidget.h" -#include "qkineticscroller.h" - -struct MetricItem -{ - QKineticScroller::ScrollMetric metric; - const char *name; - int scaling; - const char *unit; - QVariant min, max; - QVariant step; -}; - -class MetricItemUpdater : public QObject -{ - Q_OBJECT -public: - MetricItemUpdater(MetricItem *item, QKineticScroller *scroller) - : m_item(item), m_scroller(scroller) - { - if (m_item->min.type() == QVariant::PointF) { - m_slider = new QSlider(Qt::Horizontal); - m_slider->setSingleStep(1); - m_slider->setMinimum(-1); - m_slider->setMaximum(int((m_item->max.toPointF().x() - m_item->min.toPointF().x()) / m_item->step.toReal())); - m_slider->setValue(-1); - m_slider2 = new QSlider(Qt::Horizontal); - m_slider2->setSingleStep(1); - m_slider2->setMinimum(-1); - m_slider2->setMaximum(int((m_item->max.toPointF().y() - m_item->min.toPointF().y()) / m_item->step.toReal())); - m_slider2->setValue(-1); - m_control = new QWidget(); - QHBoxLayout *lay = new QHBoxLayout(m_control); - lay->setContentsMargins(0, 0, 0, 0); - lay->addWidget(m_slider); - lay->addWidget(m_slider2); - } else { - m_slider = new QSlider(Qt::Horizontal); - m_slider->setSingleStep(1); - m_slider->setMinimum(-1); - m_slider->setMaximum(int((m_item->max.toReal() - m_item->min.toReal()) / m_item->step.toReal())); - m_slider->setValue(-1); - m_slider2 = 0; - m_control = m_slider; - } - m_unitLabel = new QLabel(QLatin1String(m_item->unit)); - m_valueLabel = new QLabel(); - m_nameLabel = new QLabel(QLatin1String(m_item->name)); - m_resetButton = new QToolButton(); - m_resetButton->setText(QLatin1String("Reset")); - m_resetButton->setEnabled(false); - - connect(m_resetButton, SIGNAL(clicked()), this, SLOT(reset())); - connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int))); - if (m_slider2) - connect(m_slider2, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int))); - - m_default_value = m_scroller->scrollMetric(m_item->metric); - valueChanged(m_default_value); - - m_slider->setMinimum(0); - if (m_slider2) - m_slider2->setMinimum(0); - } - - QWidget *nameLabel() { return m_nameLabel; } - QWidget *unitLabel() { return m_unitLabel; } - QWidget *valueLabel() { return m_valueLabel; } - QWidget *valueControl() { return m_control; } - QWidget *resetButton() { return m_resetButton; } - -private slots: - void valueChanged(const QVariant v) - { - m_value = v; - switch (m_item->min.type()) { - case QVariant::Double: { - m_slider->setValue(int((m_value.toReal() * m_item->scaling - m_item->min.toReal()) / m_item->step.toReal())); - break; - } - case QVariant::Int: { - m_slider->setValue(int((m_value.toInt() * m_item->scaling - m_item->min.toInt()) / m_item->step.toInt())); - break; - } - case QVariant::PointF: { - m_slider->setValue(int((m_value.toPointF().x() * m_item->scaling - m_item->min.toPointF().x()) / m_item->step.toReal())); - m_slider2->setValue(int((m_value.toPointF().y() * m_item->scaling - m_item->min.toPointF().y()) / m_item->step.toReal())); - break; - } - default: break; - } - } - - void sliderChanged(int value) - { - bool second = (m_slider2 && (sender() == m_slider2)); - QString text; - - switch (m_item->min.type()) { - case QVariant::Double: { - qreal d = m_item->min.toReal() + qreal(value) * m_item->step.toReal(); - text = QString::number(d); - m_value = d / qreal(m_item->scaling); - break; - } - case QVariant::Int: { - int i = m_item->min.toInt() + int(qreal(value) * m_item->step.toReal()); - text = QString::number(i); - m_value = i / m_item->scaling; - break; - } - case QVariant::PointF: { - qreal p = (second ? m_item->min.toPointF().y() : m_item->min.toPointF().x()) + qreal(value) * m_item->step.toReal(); - text = QString("%1, %2").arg(second ? m_value.toPointF().x() * m_item->scaling : p).arg(second ? p : m_value.toPointF().y() * m_item->scaling); - m_value = QPointF(second ? m_value.toPointF().x() : p / m_item->scaling, second ? p / m_item->scaling : m_value.toPointF().y()); - break; - } - default: break; - } - m_valueLabel->setText(text); - m_scroller->setScrollMetric(m_item->metric, m_value); - - m_resetButton->setEnabled(m_value != m_default_value); - } - - void reset() - { - m_scroller->setScrollMetric(m_item->metric, m_value); - valueChanged(m_default_value); - } - -private: - MetricItem *m_item; - QKineticScroller *m_scroller; - - QSlider *m_slider, *m_slider2; - QLabel *m_unitLabel, *m_nameLabel, *m_valueLabel; - QToolButton *m_resetButton; - QWidget *m_control; - - QVariant m_value, m_default_value; -}; - -#define METRIC(x) QKineticScroller::x, #x - -MetricItem items[QKineticScroller::ScrollMetricCount] = { - { METRIC(DragVelocitySmoothingFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, - - { METRIC(LinearDecelerationFactor), 1, "m/s^2", qreal(0), qreal(3), qreal(0.01) }, - { METRIC(ExponentialDecelerationBase), 1, "", qreal(0), qreal(1), qreal(0.01) }, - { METRIC(OvershootSpringConstant), 1, "kg/s^2", qreal(1), qreal(500), qreal(1) }, - { METRIC(OvershootDragResistanceFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, - { METRIC(OvershootMaximumDistance), 1000, "(mm, mm)", QPointF(0, 0), QPointF(500, 500), qreal(1) }, - - { METRIC(DragStartDistance), 1000, "mm", qreal(1), qreal(20), qreal(0.1) }, - { METRIC(DragStartDirectionErrorMargin), 1000, "mm", qreal(1), qreal(20), qreal(0.1) }, - - { METRIC(MinimumVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, - { METRIC(MaximumVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, - { METRIC(MaximumNonAcceleratedVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, - - { METRIC(MaximumClickThroughVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, - { METRIC(AxisLockThreshold), 1, "", qreal(0), qreal(1), qreal(0.01) }, - - { METRIC(FramesPerSecond), 1, "frames/s", int(10), int(100), int(1) }, - - { METRIC(FastSwipeMaximumTime), 1000, "ms", qreal(10), qreal(1000), qreal(1) }, - { METRIC(FastSwipeMinimumVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, - { METRIC(FastSwipeBaseVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) } -}; - -#undef METRIC - -QGridLayout *SettingsWidget::createMetricsItemGrid() -{ - QGridLayout *grid = new QGridLayout(); - grid->setVerticalSpacing(2); - int row = 0; - - for (int i = 0; i < QKineticScroller::ScrollMetricCount; i++) { - MetricItemUpdater *u = new MetricItemUpdater(items + i, m_scroller); - u->setParent(this); - - grid->addWidget(u->nameLabel(), row, 0); - grid->addWidget(u->valueControl(), row, 1); - grid->addWidget(u->valueLabel(), row, 2); - grid->addWidget(u->unitLabel(), row, 3); - grid->addWidget(u->resetButton(), row, 4); - - row++; - } - return grid; -} - -class HackScroller : public QKineticScroller { -public: - using QKineticScroller::viewportSize; - using QKineticScroller::maximumContentPosition; -}; - -class HackSpinBox : public QSpinBox { -public: - using QAbstractSpinBox::lineEdit; -}; - -SettingsWidget::SettingsWidget(QKineticScroller *scroller) - : QWidget(), m_scroller(scroller) -{ - setWindowTitle(QLatin1String("Settings")); - QVBoxLayout *layout = new QVBoxLayout(this); - QGroupBox *grp; - QGridLayout *grid; - - grp = new QGroupBox(QLatin1String("General")); - grid = new QGridLayout(); - grid->setVerticalSpacing(2); - QCheckBox *onoff = new QCheckBox(QLatin1String("Enabled")); - onoff->setChecked(m_scroller->isEnabled()); - connect(onoff, SIGNAL(toggled(bool)), this, SLOT(enabledChanged(bool))); - grid->addWidget(onoff, 0, 0, 1, 2); - grid->addWidget(new QLabel("DPI"), 1, 0); - QSpinBox *dpi = new QSpinBox(); - dpi->setRange(10, 1000); - dpi->setSuffix(QLatin1String(" dpi")); - dpi->setValue(m_scroller->dpi()); - connect(dpi, SIGNAL(valueChanged(int)), this, SLOT(dpiChanged(int))); - grid->addWidget(dpi, 1, 1); - - grid->addWidget(new QLabel("Horizontal Overshoot Policy"), 2, 0); - m_hospolicy = new QComboBox(); - m_hospolicy->addItem("When Scrollable", QKineticScroller::OvershootWhenScrollable); - m_hospolicy->addItem("Always On", QKineticScroller::OvershootAlwaysOn); - m_hospolicy->addItem("Always Off", QKineticScroller::OvershootAlwaysOff); - m_hospolicy->setCurrentIndex(m_hospolicy->findData(m_scroller->horizontalOvershootPolicy())); - connect(m_hospolicy, SIGNAL(currentIndexChanged(int)), this, SLOT(overshootPolicyChanged(int))); - grid->addWidget(m_hospolicy, 2, 1); - - grid->addWidget(new QLabel("Vertical Overshoot Policy"), 3, 0); - m_vospolicy = new QComboBox(); - m_vospolicy->addItem("When Scrollable", QKineticScroller::OvershootWhenScrollable); - m_vospolicy->addItem("Always On", QKineticScroller::OvershootAlwaysOn); - m_vospolicy->addItem("Always Off", QKineticScroller::OvershootAlwaysOff); - m_vospolicy->setCurrentIndex(m_vospolicy->findData(m_scroller->verticalOvershootPolicy())); - connect(m_vospolicy, SIGNAL(currentIndexChanged(int)), this, SLOT(overshootPolicyChanged(int))); - grid->addWidget(m_vospolicy, 3, 1); - grp->setLayout(grid); - layout->addWidget(grp); - - grp = new QGroupBox(QLatin1String("Scroll Metrics")); - grid = createMetricsItemGrid(); - grp->setLayout(grid); - layout->addWidget(grp); - - grp = new QGroupBox(QLatin1String("Scroll To")); - grid = new QGridLayout(); - - QSizeF vp = static_cast(m_scroller)->viewportSize(); - QPointF maxc = static_cast(m_scroller)->maximumContentPosition(); - - m_scrollx = new QSpinBox(); - m_scrollx->setRange(int(-vp.width()), int(maxc.x() + vp.width())); - m_scrolly = new QSpinBox(); - m_scrolly->setRange(int(-vp.height()), int(maxc.y() + vp.height())); - m_scrolltime = new QSpinBox(); - m_scrolltime->setRange(0, 10000); - m_scrolltime->setValue(1000); - m_scrolltime->setSuffix(QLatin1String(" ms")); - QPushButton *go = new QPushButton(QLatin1String("Go")); - connect(go, SIGNAL(clicked()), this, SLOT(scrollTo())); - connect(m_scrollx, SIGNAL(editingFinished()), this, SLOT(scrollTo())); - connect(m_scrolly, SIGNAL(editingFinished()), this, SLOT(scrollTo())); - connect(m_scrolltime, SIGNAL(editingFinished()), this, SLOT(scrollTo())); - grid->addWidget(new QLabel(QLatin1String("X:")), 0, 0); - grid->addWidget(m_scrollx, 0, 1); - grid->addWidget(new QLabel(QLatin1String("Y:")), 0, 2); - grid->addWidget(m_scrolly, 0, 3); - grid->addWidget(new QLabel(QLatin1String("in")), 0, 4); - grid->addWidget(m_scrolltime, 0, 5); - grid->addWidget(go, 0, 6); - grid->setColumnStretch(1, 1); - grid->setColumnStretch(3, 1); - grid->setColumnStretch(5, 1); - grid->setColumnStretch(6, 1); - grp->setLayout(grid); - layout->addWidget(grp); - layout->addStretch(100); -} - -void SettingsWidget::enabledChanged(bool on) -{ - m_scroller->setEnabled(on); -} - -void SettingsWidget::dpiChanged(int dpi) -{ - m_scroller->setDpi(dpi); -} - -void SettingsWidget::overshootPolicyChanged(int index) -{ - if (sender() == m_hospolicy) - m_scroller->setHorizontalOvershootPolicy(static_cast(m_hospolicy->itemData(index).toInt())); - else if (sender() == m_vospolicy) - m_scroller->setVerticalOvershootPolicy(static_cast(m_hospolicy->itemData(index).toInt())); -} - -void SettingsWidget::scrollTo() -{ - if ((sender() == m_scrollx) && !m_scrollx->hasFocus()) - return; - if ((sender() == m_scrolly) && !m_scrolly->hasFocus()) - return; - if ((sender() == m_scrolltime) && !m_scrolltime->hasFocus()) - return; - - m_scroller->scrollTo(QPointF(m_scrollx->value(), m_scrolly->value()), m_scrolltime->value()); -} - -#include "settingswidget.moc" diff --git a/settingswidget.h b/settingswidget.h deleted file mode 100644 index 22ec6aa..0000000 --- a/settingswidget.h +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************************** -** -** 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 SETTINGSWIDGET_H -#define SETTINGSWIDGET_H - -#include - -class QKineticScroller; - -class SettingsWidget : public QWidget -{ - Q_OBJECT - -public: - SettingsWidget(QKineticScroller *scroller); - -private slots: - void enabledChanged(bool on); - void dpiChanged(int dpi); - void overshootPolicyChanged(int index); - void scrollTo(); - -private: - QGridLayout *createMetricsItemGrid(); - - QKineticScroller *m_scroller; - QSpinBox *m_scrollx, *m_scrolly, *m_scrolltime; - QComboBox *m_hospolicy, *m_vospolicy; -}; - -#endif diff --git a/testapp/main.cpp b/testapp/main.cpp new file mode 100644 index 0000000..698ec64 --- /dev/null +++ b/testapp/main.cpp @@ -0,0 +1,33 @@ +#include +#include "qscrollareakineticscroller.h" + +#include "settingswidget.h" +#include "plotwidget.h" + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + QListWidget *lw = new QListWidget(); + + for (int i = 0; i < 1000; ++i) + new QListWidgetItem(QString("Oa dsjfhdk jhdsjk hfdskj k %1").arg(i), lw); + + QScrollAreaKineticScroller *s = new QScrollAreaKineticScroller(); + s->setWidget(lw); + + QWidget *settings = new SettingsWidget(s); + QWidget *plot = new PlotWidget(s); + + QSplitter *split = new QSplitter(); + split->addWidget(lw); + QTabWidget *tab = new QTabWidget(); + tab->addTab(settings, settings->windowTitle()); + tab->addTab(plot, plot->windowTitle()); + split->addWidget(tab); + + split->show(); + split->raise(); + + return a.exec(); +} diff --git a/testapp/plotwidget.cpp b/testapp/plotwidget.cpp new file mode 100644 index 0000000..0564df2 --- /dev/null +++ b/testapp/plotwidget.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** 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 + +#include "plotwidget.h" +#include "qkineticscroller.h" + +PlotWidget::PlotWidget(QKineticScroller *scroller) + : QWidget(), m_scroller(scroller) +{ + setWindowTitle(QLatin1String("Plot")); + m_scroller->registerDebugHook(debugHook, this); + + m_clear = new QPushButton(QLatin1String("Clear"), this); + connect(m_clear, SIGNAL(clicked()), this, SLOT(reset())); + m_legend = new QLabel(this); + QString legend; + QTextStream ts(&legend); + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << "
releaseVelocity X
releaseVelocity Y
Content Position X
Content Position Y
Overshoot Position X
Overshoot Position Y
"; + m_legend->setText(legend); +} + +void PlotWidget::debugHook(void *user, const QPointF &releaseVelocity, const QPointF &contentPosition, const QPointF &overshootPosition) +{ + PlotItem pi = { releaseVelocity, contentPosition, overshootPosition }; + static_cast(user)->addPlotItem(pi); +} + +static inline void doMaxMin(const QPointF &v, qreal &minmaxv) +{ + minmaxv = qMax(minmaxv, qMax(qAbs(v.x()), qAbs(v.y()))); +} + +void PlotWidget::addPlotItem(const PlotItem &pi) +{ + m_plotitems.append(pi); + minMaxVelocity = minMaxPosition = 0; + + while (m_plotitems.size() > 500) + m_plotitems.removeFirst(); + + foreach (const PlotItem &pi, m_plotitems) { + doMaxMin(pi.releaseVelocity, minMaxVelocity); + doMaxMin(pi.contentPosition, minMaxPosition); + doMaxMin(pi.overshootPosition, minMaxPosition); + } + update(); +} + +void PlotWidget::reset() +{ + m_plotitems.clear(); + minMaxVelocity = minMaxPosition = 0; + update(); +} + +void PlotWidget::resizeEvent(QResizeEvent *) +{ + QSize cs = m_clear->sizeHint(); + QSize ls = m_legend->sizeHint(); + m_clear->setGeometry(4, 4, cs.width(), cs.height()); + m_legend->setGeometry(4, height() - ls.height() - 4, ls.width(), ls.height()); +} + +void PlotWidget::paintEvent(QPaintEvent *) +{ +#define SCALE(v, mm) ((qreal(1) - (v / mm)) * qreal(0.5) * height()) + + QColor rvColor = Qt::red; + QColor cpColor = Qt::green; + QColor opColor = Qt::blue; + + + QPainter p(this); + p.setRenderHints(QPainter::Antialiasing); + p.fillRect(rect(), Qt::white); + + if (m_plotitems.isEmpty()) + return; + + int x = 2; + int offset = m_plotitems.size() - width() / 2; + QList::const_iterator it = m_plotitems.constBegin(); + if (offset > 0) + it += (offset - 1); + + const PlotItem *last = &(*it++); + + while (it != m_plotitems.constEnd()) { + p.setPen(rvColor.light()); + p.drawLine(qreal(x - 2), SCALE(last->releaseVelocity.x(), minMaxVelocity), + qreal(x), SCALE(it->releaseVelocity.x(), minMaxVelocity)); + p.setPen(rvColor.dark()); + p.drawLine(qreal(x - 2), SCALE(last->releaseVelocity.y(), minMaxVelocity), + qreal(x), SCALE(it->releaseVelocity.y(), minMaxVelocity)); + + p.setPen(cpColor.light()); + p.drawLine(qreal(x - 2), SCALE(last->contentPosition.x(), minMaxPosition), + qreal(x), SCALE(it->contentPosition.x(), minMaxPosition)); + p.setPen(cpColor.dark()); + p.drawLine(qreal(x - 2), SCALE(last->contentPosition.y(), minMaxPosition), + qreal(x), SCALE(it->contentPosition.y(), minMaxPosition)); + + p.setPen(opColor.light()); + p.drawLine(qreal(x - 2), SCALE(last->overshootPosition.x(), minMaxPosition), + qreal(x), SCALE(it->overshootPosition.x(), minMaxPosition)); + p.setPen(opColor.dark()); + p.drawLine(qreal(x - 2), SCALE(last->overshootPosition.y(), minMaxPosition), + qreal(x), SCALE(it->overshootPosition.y(), minMaxPosition)); + + last = &(*it++); + x += 2; + } +#undef SCALE +} diff --git a/testapp/plotwidget.h b/testapp/plotwidget.h new file mode 100644 index 0000000..80e76ad --- /dev/null +++ b/testapp/plotwidget.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 PLOTWIDGET_H +#define PLOTWIDGET_H + +#include + +class QKineticScroller; + +class PlotWidget : public QWidget +{ + Q_OBJECT + +public: + PlotWidget(QKineticScroller *scroller); + +public slots: + void reset(); + +protected: + void resizeEvent(QResizeEvent *); + void paintEvent(QPaintEvent *); + +private: + static void debugHook(void *user, const QPointF &releaseVelocity, const QPointF &contentPosition, const QPointF &overshootPosition); + + struct PlotItem { + QPointF releaseVelocity; + QPointF contentPosition; + QPointF overshootPosition; + }; + + void addPlotItem(const PlotItem &pi); + + QKineticScroller *m_scroller; + QList m_plotitems; + qreal minMaxVelocity, minMaxPosition; + QPushButton *m_clear; + QLabel *m_legend; +}; + +#endif diff --git a/testapp/qscrollareakineticscroller.cpp b/testapp/qscrollareakineticscroller.cpp new file mode 100644 index 0000000..ecef0c6 --- /dev/null +++ b/testapp/qscrollareakineticscroller.cpp @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qabstractitemview.h" +#include "qgraphicsview.h" +#include "qgraphicsitem.h" + + + +#include + +QT_BEGIN_NAMESPACE + + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event) +{ + return QCoreApplication::sendSpontaneousEvent(receiver, event); +} + + + +class Q_GUI_EXPORT QScrollAreaKineticScrollerPrivate +{ +public: + QScrollAreaKineticScroller *q_ptr; + + QAbstractScrollArea *area; + QItemSelection oldSelection; // the selection before the first mouse down + bool ignoreEvents; + QPoint lastOvershoot; // don't change the type to QPointF or we might never shoot completely back. + QPointer childWidget; // the widget where the mouse was pressed + QPointF fractionalPosition; + + QElapsedTimer timer; + + QScrollAreaKineticScrollerPrivate() + : q_ptr(0) + , area(0) + , ignoreEvents(false) + {} + + void init() + { + timer.start(); + } + + static QWidget *mouseTransparentChildAtGlobalPos(QWidget *parent, const QPoint &gp) + { + foreach (QWidget *w, parent->findChildren()) { + if (w && !w->isWindow() && w->isVisible() && (w->rect().contains(w->mapFromGlobal(gp)))) { + if (QWidget *t = mouseTransparentChildAtGlobalPos(w, gp)) + return t; + else + return w; + } + } + return 0; + } + + void sendEvent(QWidget *w, QEvent *e) + { + ignoreEvents = true; + qt_sendSpontaneousEvent(w, e); + ignoreEvents = false; + } +}; + +/*! + * The QScrollAreaKineticScroller class implements the QKineticScroller for the AbstractScrollArea + */ +QScrollAreaKineticScroller::QScrollAreaKineticScroller() + : QObject() + , QKineticScroller() + , d_ptr(new QScrollAreaKineticScrollerPrivate()) +{ + Q_D(QScrollAreaKineticScroller); + d->q_ptr = this; + d->init(); +} + +/*! + Destroys the scroller. +*/ +QScrollAreaKineticScroller::~QScrollAreaKineticScroller() +{} + +void QScrollAreaKineticScroller::setWidget(QAbstractScrollArea *widget) +{ + Q_D(QScrollAreaKineticScroller); + + if (d->area) { + d->area->viewport()->removeEventFilter(this); + d->area->viewport()->move(d->area->viewport()->pos() + d->lastOvershoot ); + } + + reset(); + d->area = widget; + d->lastOvershoot = QPoint(); + d->fractionalPosition = QPointF(); + + setParent(d->area); + if (d->area) { + d->area->viewport()->installEventFilter(this); + if (QAbstractItemView *view = qobject_cast(d->area)) { + view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + view->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + } + } +} + +bool QScrollAreaKineticScroller::eventFilter(QObject *o, QEvent *e) +{ + Q_D(QScrollAreaKineticScroller); + + qint64 timestamp = d->timer.elapsed(); + bool res = false; + bool isMouseEvent = false; + bool isMouseDown = false; + + if (d->area && (o == d->area->viewport()) && + !d->ignoreEvents && d->area->isEnabled() && isEnabled()) { + switch (e->type()) { + case QEvent::MouseButtonPress: { + // re-install the event filter so that we get the mouse release before all other filters. + // this is an evil hack, but hard to work around without prioritized event filters. + d->area->viewport()->removeEventFilter(this); + d->area->viewport()->installEventFilter(this); + // fall through + QMouseEvent *me = static_cast(e); + isMouseEvent = true; + res = handleInput(InputPress, me->posF() - d->lastOvershoot, timestamp); + break; + } + case QEvent::MouseButtonDblClick: { + isMouseDown = true; + // fall through + QMouseEvent *me = static_cast(e); + isMouseEvent = true; + res = handleInput(InputPress, me->posF() - d->lastOvershoot, timestamp); + break; + } + case QEvent::MouseMove: { + QMouseEvent *me = static_cast(e); + isMouseEvent = true; + res = handleInput(InputMove, me->posF() - d->lastOvershoot, timestamp); + break; + } + case QEvent::MouseButtonRelease: { + QMouseEvent *me = static_cast(e); + isMouseEvent = true; + res = handleInput(InputRelease, me->posF() - d->lastOvershoot, timestamp); + break; + } + + case QEvent::ChildAdded: + case QEvent::ChildRemoved: { + QChildEvent *ce = static_cast(e); + if (ce->child()->isWidgetType()) { + static_cast(ce->child())->setAttribute(Qt::WA_TransparentForMouseEvents, ce->added()); + if ((e->type() == QEvent::ChildRemoved) && (ce->child() == d->childWidget)) + d->childWidget = 0; + } + break; + } + default: + break; + } + } + + res |= QObject::eventFilter(o, e); + + // all child widgets get the WA_TransparentForMouseEvents attribute, so + // we have to find the "real" widget to forward this event to... + if (!res && isMouseEvent) { + QMouseEvent *me = static_cast(e); + + if (isMouseDown) + d->childWidget = d->mouseTransparentChildAtGlobalPos(d->area->viewport(), me->globalPos()); + + if (d->childWidget) { + QMouseEvent cme(me->type(), me->pos(), + me->globalPos(), me->button(), me->buttons(), me->modifiers()); + d->sendEvent(d->childWidget, &cme); + res = cme.isAccepted(); + } + } + return res; +} + +void QScrollAreaKineticScroller::stateChanged(State oldState) +{ + Q_D(QScrollAreaKineticScroller); + + // hack to remove the current selection as soon as we start to scroll + if (QAbstractItemView *view = qobject_cast(d->area)) { + if (oldState == StateInactive) + d->oldSelection = view->selectionModel()->selection(); // store the selection + } +} + +bool QScrollAreaKineticScroller::canStartScrollingAt(const QPointF &pos) const +{ + Q_D(const QScrollAreaKineticScroller); + + // don't start scrolling when a drag mode has been set. + // don't start scrolling on a movable item. + if (QGraphicsView *view = qobject_cast(d->area)) { + if (view->dragMode() != QGraphicsView::NoDrag) + return false; + + QGraphicsItem *childItem = view->itemAt(pos.toPoint()); + + if (childItem && (childItem->flags() & QGraphicsItem::ItemIsMovable)) + return false; + } + + // don't start scrolling on a QSlider + if (qobject_cast(d->mouseTransparentChildAtGlobalPos(d->area->viewport(), d->area->viewport()->mapToGlobal(pos.toPoint())))) { + return false; + } + + return true; +} + +void QScrollAreaKineticScroller::cancelPress(const QPointF &pressPos) +{ + Q_D(QScrollAreaKineticScroller); + + QPoint pos = pressPos.toPoint(); + + /* + if (QWidget *childWidget = d->mouseTransparentChildAtGlobalPos(d->area->viewport(), d->view->mapToGlobal(pos))) { + */ + if (d->childWidget) { + // simulate a mouse-move and mouse-release far, far away + QPoint faraway(-1000, -1000); + QMouseEvent cmem(QEvent::MouseMove, faraway, d->childWidget->mapToGlobal(faraway), + Qt::LeftButton, QApplication::mouseButtons() | Qt::LeftButton, + QApplication::keyboardModifiers()); + d->sendEvent(d->childWidget, &cmem); + + QMouseEvent cmer(QEvent::MouseButtonRelease, faraway, d->childWidget->mapToGlobal(faraway), + Qt::LeftButton, QApplication::mouseButtons() & ~Qt::LeftButton, + QApplication::keyboardModifiers()); + d->sendEvent(d->childWidget, &cmer); + } + + if (QAbstractItemView *view = qobject_cast(d->area)) + view->selectionModel()->select(d->oldSelection, QItemSelectionModel::ClearAndSelect); +} + +QSizeF QScrollAreaKineticScroller::viewportSize() const +{ + Q_D(const QScrollAreaKineticScroller); + + return d->area ? QSizeF(d->area->viewport()->size()) : QSizeF(); +} + +QPointF QScrollAreaKineticScroller::maximumContentPosition() const +{ + Q_D(const QScrollAreaKineticScroller); + + QPointF p; + if (d->area) { + if (QScrollBar *s = d->area->horizontalScrollBar()) + p.setX(s->maximum()); + if (QScrollBar *s = d->area->verticalScrollBar()) + p.setY(s->maximum()); + } + return p; +} + +QPointF QScrollAreaKineticScroller::contentPosition() const +{ + Q_D(const QScrollAreaKineticScroller); + + QPointF p; + if (d->area) { + if (QScrollBar *s = d->area->horizontalScrollBar()) + p.setX(s->value()); + if (QScrollBar *s = d->area->verticalScrollBar()) + p.setY(s->value()); + p += d->fractionalPosition; + } + return p; +} + +void QScrollAreaKineticScroller::setContentPosition(const QPointF &p, const QPointF &overshootDelta) +{ + Q_D(QScrollAreaKineticScroller); + + if (d->area) { + if (QScrollBar *s = d->area->horizontalScrollBar()) + s->setValue(p.x()); + if (QScrollBar *s = d->area->verticalScrollBar()) + s->setValue(p.y()); + + QPoint delta = d->lastOvershoot - overshootDelta.toPoint(); + if (!delta.isNull()) + d->area->viewport()->move(d->area->viewport()->pos() + delta); + d->lastOvershoot -= delta; + + d->fractionalPosition = QPointF(p.x() - int(p.x()), p.y() - int(p.y())); + } +} + +QT_END_NAMESPACE diff --git a/testapp/qscrollareakineticscroller.h b/testapp/qscrollareakineticscroller.h new file mode 100644 index 0000000..77b0caf --- /dev/null +++ b/testapp/qscrollareakineticscroller.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 QSCROLLAREAKINETICSCROLLER_H +#define QSCROLLAREAKINETICSCROLLER_H + +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QScrollAreaKineticScrollerPrivate; + +class Q_GUI_EXPORT QScrollAreaKineticScroller : public QObject, public QKineticScroller +{ + Q_OBJECT +public: + QScrollAreaKineticScroller(); + // QScrollAreaKineticScroller(QScrollAreaKineticScrollerPrivate &dd); + ~QScrollAreaKineticScroller(); + + void setWidget(QAbstractScrollArea *widget); + + bool eventFilter(QObject *o, QEvent *e); + +protected: + + virtual QSizeF viewportSize() const; + virtual QPointF maximumContentPosition() const; + virtual QPointF contentPosition() const; + virtual void setContentPosition(const QPointF &pos, const QPointF &overshootDelta); + + virtual void stateChanged(State oldState); + virtual bool canStartScrollingAt(const QPointF &pos) const; + virtual void cancelPress(const QPointF &pressPos); + + QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(QScrollAreaKineticScroller) + Q_DECLARE_PRIVATE(QScrollAreaKineticScroller) +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QScrollAreaKineticScroller *) + +QT_END_HEADER + +#endif // QKINETICSCROLLER_H diff --git a/testapp/settingswidget.cpp b/testapp/settingswidget.cpp new file mode 100644 index 0000000..7f41a7b --- /dev/null +++ b/testapp/settingswidget.cpp @@ -0,0 +1,362 @@ +/**************************************************************************** +** +** 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 + +#include "settingswidget.h" +#include "qkineticscroller.h" + +struct MetricItem +{ + QKineticScroller::ScrollMetric metric; + const char *name; + int scaling; + const char *unit; + QVariant min, max; + QVariant step; +}; + +class MetricItemUpdater : public QObject +{ + Q_OBJECT +public: + MetricItemUpdater(MetricItem *item, QKineticScroller *scroller) + : m_item(item), m_scroller(scroller) + { + if (m_item->min.type() == QVariant::PointF) { + m_slider = new QSlider(Qt::Horizontal); + m_slider->setSingleStep(1); + m_slider->setMinimum(-1); + m_slider->setMaximum(int((m_item->max.toPointF().x() - m_item->min.toPointF().x()) / m_item->step.toReal())); + m_slider->setValue(-1); + m_slider2 = new QSlider(Qt::Horizontal); + m_slider2->setSingleStep(1); + m_slider2->setMinimum(-1); + m_slider2->setMaximum(int((m_item->max.toPointF().y() - m_item->min.toPointF().y()) / m_item->step.toReal())); + m_slider2->setValue(-1); + m_control = new QWidget(); + QHBoxLayout *lay = new QHBoxLayout(m_control); + lay->setContentsMargins(0, 0, 0, 0); + lay->addWidget(m_slider); + lay->addWidget(m_slider2); + } else { + m_slider = new QSlider(Qt::Horizontal); + m_slider->setSingleStep(1); + m_slider->setMinimum(-1); + m_slider->setMaximum(int((m_item->max.toReal() - m_item->min.toReal()) / m_item->step.toReal())); + m_slider->setValue(-1); + m_slider2 = 0; + m_control = m_slider; + } + m_unitLabel = new QLabel(QLatin1String(m_item->unit)); + m_valueLabel = new QLabel(); + m_nameLabel = new QLabel(QLatin1String(m_item->name)); + m_resetButton = new QToolButton(); + m_resetButton->setText(QLatin1String("Reset")); + m_resetButton->setEnabled(false); + + connect(m_resetButton, SIGNAL(clicked()), this, SLOT(reset())); + connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int))); + if (m_slider2) + connect(m_slider2, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int))); + + m_default_value = m_scroller->scrollMetric(m_item->metric); + valueChanged(m_default_value); + + m_slider->setMinimum(0); + if (m_slider2) + m_slider2->setMinimum(0); + } + + QWidget *nameLabel() { return m_nameLabel; } + QWidget *unitLabel() { return m_unitLabel; } + QWidget *valueLabel() { return m_valueLabel; } + QWidget *valueControl() { return m_control; } + QWidget *resetButton() { return m_resetButton; } + +private slots: + void valueChanged(const QVariant v) + { + m_value = v; + switch (m_item->min.type()) { + case QVariant::Double: { + m_slider->setValue(int((m_value.toReal() * m_item->scaling - m_item->min.toReal()) / m_item->step.toReal())); + break; + } + case QVariant::Int: { + m_slider->setValue(int((m_value.toInt() * m_item->scaling - m_item->min.toInt()) / m_item->step.toInt())); + break; + } + case QVariant::PointF: { + m_slider->setValue(int((m_value.toPointF().x() * m_item->scaling - m_item->min.toPointF().x()) / m_item->step.toReal())); + m_slider2->setValue(int((m_value.toPointF().y() * m_item->scaling - m_item->min.toPointF().y()) / m_item->step.toReal())); + break; + } + default: break; + } + } + + void sliderChanged(int value) + { + bool second = (m_slider2 && (sender() == m_slider2)); + QString text; + + switch (m_item->min.type()) { + case QVariant::Double: { + qreal d = m_item->min.toReal() + qreal(value) * m_item->step.toReal(); + text = QString::number(d); + m_value = d / qreal(m_item->scaling); + break; + } + case QVariant::Int: { + int i = m_item->min.toInt() + int(qreal(value) * m_item->step.toReal()); + text = QString::number(i); + m_value = i / m_item->scaling; + break; + } + case QVariant::PointF: { + qreal p = (second ? m_item->min.toPointF().y() : m_item->min.toPointF().x()) + qreal(value) * m_item->step.toReal(); + text = QString("%1, %2").arg(second ? m_value.toPointF().x() * m_item->scaling : p).arg(second ? p : m_value.toPointF().y() * m_item->scaling); + m_value = QPointF(second ? m_value.toPointF().x() : p / m_item->scaling, second ? p / m_item->scaling : m_value.toPointF().y()); + break; + } + default: break; + } + m_valueLabel->setText(text); + m_scroller->setScrollMetric(m_item->metric, m_value); + + m_resetButton->setEnabled(m_value != m_default_value); + } + + void reset() + { + m_scroller->setScrollMetric(m_item->metric, m_value); + valueChanged(m_default_value); + } + +private: + MetricItem *m_item; + QKineticScroller *m_scroller; + + QSlider *m_slider, *m_slider2; + QLabel *m_unitLabel, *m_nameLabel, *m_valueLabel; + QToolButton *m_resetButton; + QWidget *m_control; + + QVariant m_value, m_default_value; +}; + +#define METRIC(x) QKineticScroller::x, #x + +MetricItem items[QKineticScroller::ScrollMetricCount] = { + { METRIC(DragVelocitySmoothingFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, + + { METRIC(LinearDecelerationFactor), 1, "m/s^2", qreal(0), qreal(3), qreal(0.01) }, + { METRIC(ExponentialDecelerationBase), 1, "", qreal(0), qreal(1), qreal(0.01) }, + { METRIC(OvershootSpringConstant), 1, "kg/s^2", qreal(1), qreal(500), qreal(1) }, + { METRIC(OvershootDragResistanceFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, + { METRIC(OvershootMaximumDistance), 1000, "(mm, mm)", QPointF(0, 0), QPointF(500, 500), qreal(1) }, + + { METRIC(DragStartDistance), 1000, "mm", qreal(1), qreal(20), qreal(0.1) }, + { METRIC(DragStartDirectionErrorMargin), 1000, "mm", qreal(1), qreal(20), qreal(0.1) }, + + { METRIC(MinimumVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, + { METRIC(MaximumVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, + { METRIC(MaximumNonAcceleratedVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, + + { METRIC(MaximumClickThroughVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, + { METRIC(AxisLockThreshold), 1, "", qreal(0), qreal(1), qreal(0.01) }, + + { METRIC(FramesPerSecond), 1, "frames/s", int(10), int(100), int(1) }, + + { METRIC(FastSwipeMaximumTime), 1000, "ms", qreal(10), qreal(1000), qreal(1) }, + { METRIC(FastSwipeMinimumVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, + { METRIC(FastSwipeBaseVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) } +}; + +#undef METRIC + +QGridLayout *SettingsWidget::createMetricsItemGrid() +{ + QGridLayout *grid = new QGridLayout(); + grid->setVerticalSpacing(2); + int row = 0; + + for (int i = 0; i < QKineticScroller::ScrollMetricCount; i++) { + MetricItemUpdater *u = new MetricItemUpdater(items + i, m_scroller); + u->setParent(this); + + grid->addWidget(u->nameLabel(), row, 0); + grid->addWidget(u->valueControl(), row, 1); + grid->addWidget(u->valueLabel(), row, 2); + grid->addWidget(u->unitLabel(), row, 3); + grid->addWidget(u->resetButton(), row, 4); + + row++; + } + return grid; +} + +class HackScroller : public QKineticScroller { +public: + using QKineticScroller::viewportSize; + using QKineticScroller::maximumContentPosition; +}; + +class HackSpinBox : public QSpinBox { +public: + using QAbstractSpinBox::lineEdit; +}; + +SettingsWidget::SettingsWidget(QKineticScroller *scroller) + : QWidget(), m_scroller(scroller) +{ + setWindowTitle(QLatin1String("Settings")); + QVBoxLayout *layout = new QVBoxLayout(this); + QGroupBox *grp; + QGridLayout *grid; + + grp = new QGroupBox(QLatin1String("General")); + grid = new QGridLayout(); + grid->setVerticalSpacing(2); + QCheckBox *onoff = new QCheckBox(QLatin1String("Enabled")); + onoff->setChecked(m_scroller->isEnabled()); + connect(onoff, SIGNAL(toggled(bool)), this, SLOT(enabledChanged(bool))); + grid->addWidget(onoff, 0, 0, 1, 2); + grid->addWidget(new QLabel("DPI"), 1, 0); + QSpinBox *dpi = new QSpinBox(); + dpi->setRange(10, 1000); + dpi->setSuffix(QLatin1String(" dpi")); + dpi->setValue(m_scroller->dpi()); + connect(dpi, SIGNAL(valueChanged(int)), this, SLOT(dpiChanged(int))); + grid->addWidget(dpi, 1, 1); + + grid->addWidget(new QLabel("Horizontal Overshoot Policy"), 2, 0); + m_hospolicy = new QComboBox(); + m_hospolicy->addItem("When Scrollable", QKineticScroller::OvershootWhenScrollable); + m_hospolicy->addItem("Always On", QKineticScroller::OvershootAlwaysOn); + m_hospolicy->addItem("Always Off", QKineticScroller::OvershootAlwaysOff); + m_hospolicy->setCurrentIndex(m_hospolicy->findData(m_scroller->horizontalOvershootPolicy())); + connect(m_hospolicy, SIGNAL(currentIndexChanged(int)), this, SLOT(overshootPolicyChanged(int))); + grid->addWidget(m_hospolicy, 2, 1); + + grid->addWidget(new QLabel("Vertical Overshoot Policy"), 3, 0); + m_vospolicy = new QComboBox(); + m_vospolicy->addItem("When Scrollable", QKineticScroller::OvershootWhenScrollable); + m_vospolicy->addItem("Always On", QKineticScroller::OvershootAlwaysOn); + m_vospolicy->addItem("Always Off", QKineticScroller::OvershootAlwaysOff); + m_vospolicy->setCurrentIndex(m_vospolicy->findData(m_scroller->verticalOvershootPolicy())); + connect(m_vospolicy, SIGNAL(currentIndexChanged(int)), this, SLOT(overshootPolicyChanged(int))); + grid->addWidget(m_vospolicy, 3, 1); + grp->setLayout(grid); + layout->addWidget(grp); + + grp = new QGroupBox(QLatin1String("Scroll Metrics")); + grid = createMetricsItemGrid(); + grp->setLayout(grid); + layout->addWidget(grp); + + grp = new QGroupBox(QLatin1String("Scroll To")); + grid = new QGridLayout(); + + QSizeF vp = static_cast(m_scroller)->viewportSize(); + QPointF maxc = static_cast(m_scroller)->maximumContentPosition(); + + m_scrollx = new QSpinBox(); + m_scrollx->setRange(int(-vp.width()), int(maxc.x() + vp.width())); + m_scrolly = new QSpinBox(); + m_scrolly->setRange(int(-vp.height()), int(maxc.y() + vp.height())); + m_scrolltime = new QSpinBox(); + m_scrolltime->setRange(0, 10000); + m_scrolltime->setValue(1000); + m_scrolltime->setSuffix(QLatin1String(" ms")); + QPushButton *go = new QPushButton(QLatin1String("Go")); + connect(go, SIGNAL(clicked()), this, SLOT(scrollTo())); + connect(m_scrollx, SIGNAL(editingFinished()), this, SLOT(scrollTo())); + connect(m_scrolly, SIGNAL(editingFinished()), this, SLOT(scrollTo())); + connect(m_scrolltime, SIGNAL(editingFinished()), this, SLOT(scrollTo())); + grid->addWidget(new QLabel(QLatin1String("X:")), 0, 0); + grid->addWidget(m_scrollx, 0, 1); + grid->addWidget(new QLabel(QLatin1String("Y:")), 0, 2); + grid->addWidget(m_scrolly, 0, 3); + grid->addWidget(new QLabel(QLatin1String("in")), 0, 4); + grid->addWidget(m_scrolltime, 0, 5); + grid->addWidget(go, 0, 6); + grid->setColumnStretch(1, 1); + grid->setColumnStretch(3, 1); + grid->setColumnStretch(5, 1); + grid->setColumnStretch(6, 1); + grp->setLayout(grid); + layout->addWidget(grp); + layout->addStretch(100); +} + +void SettingsWidget::enabledChanged(bool on) +{ + m_scroller->setEnabled(on); +} + +void SettingsWidget::dpiChanged(int dpi) +{ + m_scroller->setDpi(dpi); +} + +void SettingsWidget::overshootPolicyChanged(int index) +{ + if (sender() == m_hospolicy) + m_scroller->setHorizontalOvershootPolicy(static_cast(m_hospolicy->itemData(index).toInt())); + else if (sender() == m_vospolicy) + m_scroller->setVerticalOvershootPolicy(static_cast(m_hospolicy->itemData(index).toInt())); +} + +void SettingsWidget::scrollTo() +{ + if ((sender() == m_scrollx) && !m_scrollx->hasFocus()) + return; + if ((sender() == m_scrolly) && !m_scrolly->hasFocus()) + return; + if ((sender() == m_scrolltime) && !m_scrolltime->hasFocus()) + return; + + m_scroller->scrollTo(QPointF(m_scrollx->value(), m_scrolly->value()), m_scrolltime->value()); +} + +#include "settingswidget.moc" diff --git a/testapp/settingswidget.h b/testapp/settingswidget.h new file mode 100644 index 0000000..22ec6aa --- /dev/null +++ b/testapp/settingswidget.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 SETTINGSWIDGET_H +#define SETTINGSWIDGET_H + +#include + +class QKineticScroller; + +class SettingsWidget : public QWidget +{ + Q_OBJECT + +public: + SettingsWidget(QKineticScroller *scroller); + +private slots: + void enabledChanged(bool on); + void dpiChanged(int dpi); + void overshootPolicyChanged(int index); + void scrollTo(); + +private: + QGridLayout *createMetricsItemGrid(); + + QKineticScroller *m_scroller; + QSpinBox *m_scrollx, *m_scrolly, *m_scrolltime; + QComboBox *m_hospolicy, *m_vospolicy; +}; + +#endif diff --git a/testapp/testapp.pro b/testapp/testapp.pro new file mode 100644 index 0000000..03da8ec --- /dev/null +++ b/testapp/testapp.pro @@ -0,0 +1,16 @@ +###################################################################### +# Automatically generated by qmake (2.01a) Mon Apr 19 14:02:09 2010 +###################################################################### + +TEMPLATE = app +TARGET = +DEPENDPATH += . ../scroller +INCLUDEPATH += . ../scroller +LIBS += -L../scroller -lscroller + +# Input +HEADERS += plotwidget.h qscrollareakineticscroller.h settingswidget.h +SOURCES += main.cpp \ + plotwidget.cpp \ + qscrollareakineticscroller.cpp \ + settingswidget.cpp -- cgit v1.2.3