From 0274e68767cce6440515a68d6af868725d5577a4 Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Tue, 30 Nov 2010 22:44:52 +0100 Subject: QScroller merge, part 1 This merge consists of the actual kinetic scroller implementation, its autotests plus a few examples. QAbstractScrollArea and QAbstractItemView have been extended to support the new scroll events. The complete history is in http://scm.dev.nokia.troll.no/projects/qt/repos/rgriebls-qt-flickgesture/logs/4.7-flickgesture (part 2 is the QML Flickable replacement / part 3 is QWebView support) Task-number: QTBUG-9054 Reviewed-by: Ralf Engels --- doc/src/examples/wheel.qdoc | 109 ++ examples/examples.pro | 1 + examples/scroller/graphicsview/graphicsview.pro | 8 + examples/scroller/graphicsview/main.cpp | 292 ++++ examples/scroller/plot/main.cpp | 183 +++ examples/scroller/plot/plot.pro | 18 + examples/scroller/plot/plotwidget.cpp | 205 +++ examples/scroller/plot/plotwidget.h | 88 + examples/scroller/plot/settingswidget.cpp | 690 ++++++++ examples/scroller/plot/settingswidget.h | 107 ++ examples/scroller/scroller.pro | 11 + examples/scroller/wheel/main.cpp | 119 ++ examples/scroller/wheel/wheel.pro | 16 + examples/scroller/wheel/wheelwidget.cpp | 276 ++++ examples/scroller/wheel/wheelwidget.h | 102 ++ src/corelib/kernel/qcoreevent.cpp | 2 + src/corelib/kernel/qcoreevent.h | 3 + src/gui/itemviews/qabstractitemview.cpp | 39 + src/gui/itemviews/qabstractitemview.h | 1 + src/gui/itemviews/qabstractitemview_p.h | 7 + src/gui/kernel/qevent.cpp | 219 +++ src/gui/kernel/qevent.h | 46 + src/gui/kernel/qevent_p.h | 28 + src/gui/util/qflickgesture.cpp | 677 ++++++++ src/gui/util/qflickgesture_p.h | 113 ++ src/gui/util/qscroller.cpp | 1984 +++++++++++++++++++++++ src/gui/util/qscroller.h | 149 ++ src/gui/util/qscroller_mac.mm | 69 + src/gui/util/qscroller_p.h | 205 +++ src/gui/util/qscrollerproperties.cpp | 418 +++++ src/gui/util/qscrollerproperties.h | 140 ++ src/gui/util/qscrollerproperties_p.h | 94 ++ src/gui/util/util.pri | 12 + src/gui/widgets/qabstractscrollarea.cpp | 105 +- src/gui/widgets/qabstractscrollarea_p.h | 2 + tests/auto/gui.pro | 1 + tests/auto/qscroller/qscroller.pro | 3 + tests/auto/qscroller/tst_qscroller.cpp | 529 ++++++ tools/qml/texteditautoresizer_maemo5.h | 12 +- 39 files changed, 7076 insertions(+), 7 deletions(-) create mode 100644 doc/src/examples/wheel.qdoc create mode 100644 examples/scroller/graphicsview/graphicsview.pro create mode 100644 examples/scroller/graphicsview/main.cpp create mode 100644 examples/scroller/plot/main.cpp create mode 100644 examples/scroller/plot/plot.pro create mode 100644 examples/scroller/plot/plotwidget.cpp create mode 100644 examples/scroller/plot/plotwidget.h create mode 100644 examples/scroller/plot/settingswidget.cpp create mode 100644 examples/scroller/plot/settingswidget.h create mode 100644 examples/scroller/scroller.pro create mode 100644 examples/scroller/wheel/main.cpp create mode 100644 examples/scroller/wheel/wheel.pro create mode 100644 examples/scroller/wheel/wheelwidget.cpp create mode 100644 examples/scroller/wheel/wheelwidget.h create mode 100644 src/gui/util/qflickgesture.cpp create mode 100644 src/gui/util/qflickgesture_p.h create mode 100644 src/gui/util/qscroller.cpp create mode 100644 src/gui/util/qscroller.h create mode 100644 src/gui/util/qscroller_mac.mm create mode 100644 src/gui/util/qscroller_p.h create mode 100644 src/gui/util/qscrollerproperties.cpp create mode 100644 src/gui/util/qscrollerproperties.h create mode 100644 src/gui/util/qscrollerproperties_p.h create mode 100644 tests/auto/qscroller/qscroller.pro create mode 100644 tests/auto/qscroller/tst_qscroller.cpp diff --git a/doc/src/examples/wheel.qdoc b/doc/src/examples/wheel.qdoc new file mode 100644 index 0000000..1ea85fc --- /dev/null +++ b/doc/src/examples/wheel.qdoc @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial Usage +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in a +** written agreement between you and Nokia. +** +** GNU Free Documentation License +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of this +** file. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example scroller/wheel + \title Wheel Scroller Example + + The Wheel Scroller Example shows how to use QScroller, QScrollEvent + and QScrollPrepareEvent to implement smooth scrolling for a + custom Widget. + + \section1 Basics + + The QScroller class is the main part of the smooth scrolling + mechanism in Qt. It keeps track of the current scroll position and + speed and updates the object through events. + QScroller will get touch events via the QFlickGesture. + It will query the target object through a QScrollPrepareEvent for + the scroll area and other information. + QScroller will send QScrollEvents to inform the target object about + the current scroll position. + The target object (usually a QWidget or a QGraphicsObject) will + then need to update it's graphical representation to reflect the + new scroll position. + + \section1 The Wheel Widget class + + To demonstrate how to use the QScroller we implement a QWidget that + looks and works like the wheel of a slot machine. + The wheel can be started via touch events and will continue getting + slower. + Additionally the wheel should appear as if no border exists (which + would seem unnatural) and the scrolling should snap to center one + item. + + In the widget we need to grab the QFlickGesture. The gesture itself + will setAcceptTouchEvents for us, so we don't need to do that here. + + \snippet examples/scroller/wheel/wheelwidget.cpp 0 + + The widget will get gesture events but in addition we also will + get the events from QScroller. + We will need to accept the QScrollPrepareEvent to indicate that + a scrolling should really be started from the given position. + + \snippet examples/scroller/wheel/wheelwidget.cpp 1 + + We should call all three set functions form QScrollPrepareEvent. + + \list + \o \c setViewportSize to indicate our viewport size. Actually the + given code could be improved by giving our size minus the borders. + \o \c setMaxContentPos to indicate the maximum values for the scroll + position. The minimum values are implicitely set to 0. + In our example we give a very high number here and hope that the user + is not patient enough to scroll until the very end. + \o \c setContentPos to indicate the current scroll position. + We give a position in the middle of the huge scroll area. + Actually we give this position every time a new scroll is started so + the user will only reach the end if he continuously scrolls in one + direction which is not very likely. + \endlist + + The handling of the QScrollEvent is a lengthly code not fully shown here. + \snippet examples/scroller/wheel/wheelwidget.cpp 2 + + In principle it does three steps. + \list + \o It calculates and updates the current scroll position as given by + QScroller. + \o It repaints the widget so that the new position is shown. + \o It centers the item as soon as the scrolling stopps. + \endlist + + The following code does the centering. + \snippet examples/scroller/wheel/wheelwidget.cpp 3 + + We check if the scrolling is finished which is indicated in the + QScrollEvent by the \c isLast flag. + We then check if the item is not already centered and if not start a new + scroll by calling QScroller::scrollTo. + + As you can see the QScroller can be used for other things besides simple + scroll areas. +*/ diff --git a/examples/examples.pro b/examples/examples.pro index f233aba..968740d 100644 --- a/examples/examples.pro +++ b/examples/examples.pro @@ -20,6 +20,7 @@ SUBDIRS = \ mainwindows \ painting \ richtext \ + scroller \ sql \ tools \ tutorials \ diff --git a/examples/scroller/graphicsview/graphicsview.pro b/examples/scroller/graphicsview/graphicsview.pro new file mode 100644 index 0000000..dcebe62 --- /dev/null +++ b/examples/scroller/graphicsview/graphicsview.pro @@ -0,0 +1,8 @@ +TEMPLATE = app +SOURCES = main.cpp + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/scroller/graphicsview +sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS graphicsview.pro +sources.path = $$[QT_INSTALL_EXAMPLES]/scroller/graphicsview +INSTALLS += target sources diff --git a/examples/scroller/graphicsview/main.cpp b/examples/scroller/graphicsview/main.cpp new file mode 100644 index 0000000..e28978f --- /dev/null +++ b/examples/scroller/graphicsview/main.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** 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 examples 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 + +#define NUM_ITEMS 100 +#define NUM_LISTS 10 + +/*! + \class RectObject + Note that it needs to be a QGraphicsObject or else the gestures will not work correctly. +*/ +class RectObject : public QGraphicsObject +{ + Q_OBJECT + +public: + + RectObject(const QString &text, qreal x, qreal y, qreal width, qreal height, QBrush brush, QGraphicsItem *parent = 0) + : QGraphicsObject(parent) + , m_text(text) + , m_rect(x, y, width, height) + , m_pen(brush.color().lighter(), 3.0) + , m_brush(brush) + { + setFlag(QGraphicsItem::ItemClipsToShape, true); + } + + QRectF boundingRect() const + { + // here we only want the size of the children and not the size of the children of the children... + qreal halfpw = m_pen.widthF() / 2; + QRectF rect = m_rect; + if (halfpw > 0.0) + rect.adjust(-halfpw, -halfpw, halfpw, halfpw); + + return rect; + } + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + Q_UNUSED(option); + Q_UNUSED(widget); + painter->setPen(m_pen); + painter->setBrush(m_brush); + painter->drawRect(m_rect); + + painter->setPen(Qt::black); + QFont f; + f.setPixelSize(m_rect.height()); + painter->setFont(f); + painter->drawText(m_rect, Qt::AlignCenter, m_text); + } + + QString m_text; + QRectF m_rect; + QPen m_pen; + QBrush m_brush; +}; + +class ViewObject : public QGraphicsObject +{ + Q_OBJECT +public: + ViewObject(QGraphicsObject *parent) + : QGraphicsObject(parent) + { } + + QRectF boundingRect() const + { + QRectF rect; + foreach (QGraphicsItem *item, childItems()) + rect |= item->boundingRect().translated(item->pos()); + return rect; + } + + void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) + { } +}; + +class ListObject : public QGraphicsObject +{ + Q_OBJECT + +public: + ListObject(const QSizeF &size, bool useTouch) + { + m_size = size; + setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); + // grab gesture via Touch or Mouse events + QScroller::grabGesture(this, useTouch ? QScroller::TouchGesture : QScroller::LeftMouseButtonGesture); + + // this needs to be QGraphicsOBJECT - otherwise gesture recognition + // will not work for the parent of the viewport (in this case the + // list) + m_viewport = new ViewObject(this); + + } + + QGraphicsObject *viewport() const + { + return m_viewport; + } + + bool event(QEvent *e) + { + switch (e->type()) { +// ![2] + case QEvent::ScrollPrepare: { + QScrollPrepareEvent *se = static_cast(e); + se->setViewportSize(m_size); + QRectF br = m_viewport->boundingRect(); + se->setContentPosRange(QRectF(0, 0, + qMax(qreal(0), br.width() - m_size.width()), + qMax(qreal(0), br.height() - m_size.height()))); + se->setContentPos(-m_viewport->pos()); + se->accept(); + return true; + } +// ![1] +// ![2] + case QEvent::Scroll: { + QScrollEvent *se = static_cast(e); + m_viewport->setPos(-se->contentPos() - se->overshootDistance()); + return true; + } +// ![2] + default: + break; + } + return QGraphicsObject::event(e); + } + + bool sceneEvent(QEvent *e) + { + switch (e->type()) { + case QEvent::TouchBegin: { + // We need to return true for the TouchBegin here in the + // top-most graphics object - otherwise gestures in our parent + // objects will NOT work at all (the accept() flag is already + // set due to our setAcceptTouchEvents(true) call in the c'tor + return true; + + } + case QEvent::GraphicsSceneMousePress: { + // We need to return true for the MousePress here in the + // top-most graphics object - otherwise gestures in our parent + // objects will NOT work at all (the accept() flag is already + // set to true by Qt) + return true; + + } + default: + break; + } + return QGraphicsObject::sceneEvent(e); + } + + QRectF boundingRect() const + { + return QRectF(0, 0, m_size.width() + 3, m_size.height()); + } + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) + { + Q_UNUSED(option); + Q_UNUSED(widget); + painter->setPen(QPen(QColor(100, 100, 100), 3.0)); + painter->drawRect(QRect(1.5, 1.5, m_size.width() - 3, m_size.height() - 3)); + } + + QSizeF m_size; + ViewObject *m_viewport; +}; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(bool useTouch) + { + m_scene = new QGraphicsScene(); + + // -- make the main list + ListObject *mainList = new ListObject(QSizeF(780, 400), useTouch); + mainList->setObjectName(QLatin1String("MainList")); + m_scene->addItem(mainList); +// ![3] + for (int i=0; im_size.width()/3, mainList->m_size.height()), useTouch); + childList->setObjectName(QString("ChildList %1").arg(i)); + fillList(childList); + childList->setParentItem(mainList->viewport()); + childList->setPos(i*mainList->m_size.width()/3, 0); + } + mainList->viewport()->setPos(0, 0); + + + /* + list1->setTransformOriginPoint(200, 200); + list1->setRotation(135); + list1->setPos(20 + 200 * .41, 20 + 200 * .41); + */ +// ![3] + + m_view = new QGraphicsView(m_scene); + setCentralWidget(m_view); + setWindowTitle(tr("Gesture example")); + m_scene->setSceneRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height()); + } + + /** + * Fills the list object \a list with RectObjects. + */ + void fillList(ListObject *list) + { + qreal h = list->m_size.height() / 10; + for (int i=0; im_size.width() - 6, h - 3, QBrush(color), list->viewport()); + rect->setPos(3, h*i+3); + } + list->viewport()->setPos(0, 0); + } + + +protected: + void resizeEvent(QResizeEvent *e) + { + // resize the scene according to our own size to prevent scrolling + m_scene->setSceneRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height()); + QMainWindow::resizeEvent(e); + } + + QGraphicsScene *m_scene; + QGraphicsView *m_view; +}; + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + bool useTouch = (app.arguments().contains(QLatin1String("--touch"))); + MainWindow mw(useTouch); + mw.show(); +#ifdef Q_WS_MAC + mw.raise(); +#endif + return app.exec(); +} + +#include "main.moc" diff --git a/examples/scroller/plot/main.cpp b/examples/scroller/plot/main.cpp new file mode 100644 index 0000000..a4e2add --- /dev/null +++ b/examples/scroller/plot/main.cpp @@ -0,0 +1,183 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "settingswidget.h" +#include "plotwidget.h" + + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + MainWindow(bool smallscreen, bool touch) + : QMainWindow(), m_touch(touch) + { + m_list = new QListWidget(); + m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_list_scroller = installKineticScroller(m_list); + + for (int i = 0; i < 1000; ++i) + new QListWidgetItem(QString("This is a test text %1 %2").arg(i).arg(QString("--------").left(i % 8)), m_list); + + connect(m_list, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(listItemActivated(QListWidgetItem*))); + connect(m_list, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(listItemClicked(QListWidgetItem*))); + connect(m_list, SIGNAL(itemPressed(QListWidgetItem*)), this, SLOT(listItemPressed(QListWidgetItem*))); + connect(m_list, SIGNAL(itemSelectionChanged()), this, SLOT(listItemSelectionChanged())); + connect(m_list, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(listItemCurrentChanged(QListWidgetItem*))); + + m_web = new QWebView(); + m_web_scroller = installKineticScroller(m_web); + + QTimer::singleShot(1000, this, SLOT(loadUrl())); + + m_settings = new SettingsWidget(smallscreen); + installKineticScroller(m_settings); + m_plot = new PlotWidget(smallscreen); + + QStackedWidget *stack = new QStackedWidget(); + stack->addWidget(m_list); + stack->addWidget(m_web); + + QActionGroup *pages = new QActionGroup(this); + pages->setExclusive(true); + QSignalMapper *mapper = new QSignalMapper(this); + connect(mapper, SIGNAL(mapped(int)), stack, SLOT(setCurrentIndex(int))); + + createAction("List", pages, mapper, 0, true); + createAction("Web", pages, mapper, 1); + + if (smallscreen) { + stack->addWidget(m_settings); + stack->addWidget(m_plot); + + createAction("Settings", pages, mapper, 2); + createAction("Plot", pages, mapper, 3); + + setCentralWidget(stack); + } else { + QSplitter *split = new QSplitter(); + m_settings->setMinimumWidth(m_settings->sizeHint().width()); + split->addWidget(stack); + split->addWidget(m_settings); + split->addWidget(m_plot); + setCentralWidget(split); + } + menuBar()->addMenu(QLatin1String("Pages"))->addActions(pages->actions()); + connect(stack, SIGNAL(currentChanged(int)), this, SLOT(pageChanged(int))); + pageChanged(0); + } + +private slots: + void pageChanged(int page) + { + if (page < 0 || page > 1) + return; + switch (page) { + case 0: + m_settings->setScroller(m_list); + m_plot->setScroller(m_list); + break; + case 1: + m_settings->setScroller(m_web); + m_plot->setScroller(m_web); + break; + default: + break; + } + } + + void loadUrl() + { + m_web->load(QUrl("http://www.google.com")); + } + + void listItemActivated(QListWidgetItem *lwi) { qWarning() << "Item ACTIVATED: " << lwi->text(); } + void listItemClicked(QListWidgetItem *lwi) { qWarning() << "Item CLICKED: " << lwi->text(); } + void listItemPressed(QListWidgetItem *lwi) { qWarning() << "Item PRESSED: " << lwi->text(); } + void listItemCurrentChanged(QListWidgetItem *lwi) { qWarning() << "Item CURRENT: " << (lwi ? lwi->text() : QString("(none)")); } + void listItemSelectionChanged() + { + int n = m_list->selectedItems().count(); + qWarning("Item%s SELECTED: %d", n == 1 ? "" : "s", n); + foreach (QListWidgetItem *lwi, m_list->selectedItems()) + qWarning() << " " << lwi->text(); + } + +private: + QAction *createAction(const char *text, QActionGroup *group, QSignalMapper *mapper, int mapping, bool checked = false) + { + QAction *a = new QAction(QLatin1String(text), group); + a->setCheckable(true); + a->setChecked(checked); +#if defined(Q_WS_MAC) + a->setMenuRole(QAction::NoRole); +#endif + mapper->setMapping(a, mapping); + connect(a, SIGNAL(toggled(bool)), mapper, SLOT(map())); + return a; + } + + QScroller *installKineticScroller(QWidget *w) + { + if (QAbstractScrollArea *area = qobject_cast(w)) { + QScroller::grabGesture(area->viewport(), m_touch ? QScroller::TouchGesture : QScroller::LeftMouseButtonGesture); + return QScroller::scroller(area->viewport()); + } else if (QWebView *web = qobject_cast(w)) { + QScroller::grabGesture(web, m_touch ? QScroller::TouchGesture : QScroller::LeftMouseButtonGesture); + } + return QScroller::scroller(w); + } + +private: + QListWidget *m_list; + QWebView *m_web; + QScroller *m_list_scroller, *m_web_scroller; + SettingsWidget *m_settings; + PlotWidget *m_plot; + bool m_touch; +}; + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_S60) || defined(Q_WS_WINCE) + bool smallscreen = true; +#else + bool smallscreen = false; +#endif + bool touch = false; + + if (a.arguments().contains(QLatin1String("--small"))) + smallscreen = true; + if (a.arguments().contains(QLatin1String("--touch"))) + touch = true; + + MainWindow *mw = new MainWindow(smallscreen, touch); + if (smallscreen) + mw->showMaximized(); + else + mw->show(); +#if defined(Q_WS_MAC) + mw->raise(); +#endif + + return a.exec(); +} + +#include "main.moc" diff --git a/examples/scroller/plot/plot.pro b/examples/scroller/plot/plot.pro new file mode 100644 index 0000000..8c37b04 --- /dev/null +++ b/examples/scroller/plot/plot.pro @@ -0,0 +1,18 @@ +HEADERS = settingswidget.h \ + plotwidget.h +SOURCES = settingswidget.cpp \ + plotwidget.cpp \ + main.cpp + +QT += webkit + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/scroller/plot +sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS plot.pro +sources.path = $$[QT_INSTALL_EXAMPLES]/scroller/plot +INSTALLS += target sources + +symbian { + TARGET.UID3 = 0xA000CF66 + include($$QT_SOURCE_TREE/examples/symbianpkgrules.pri) +} diff --git a/examples/scroller/plot/plotwidget.cpp b/examples/scroller/plot/plotwidget.cpp new file mode 100644 index 0000000..5f0df67 --- /dev/null +++ b/examples/scroller/plot/plotwidget.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plotwidget.h" +#include "qscroller.h" + +PlotWidget::PlotWidget(bool /*smallscreen*/) + : QWidget(), m_widget(0) +{ + setWindowTitle(QLatin1String("Plot")); + m_clear = new QPushButton(QLatin1String("Clear"), this); +#if defined(Q_WS_MAEMO_5) + m_clear->setStyle(new QPlastiqueStyle()); + m_clear->setFixedHeight(55); +#endif + connect(m_clear, SIGNAL(clicked()), this, SLOT(reset())); + m_legend = new QLabel(this); + QString legend; + QTextStream ts(&legend); + // ok. this wouldn't pass the w3c html verification... + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << ""; + ts << "
Velocity X
Velocity Y
Content Position X
Content Position Y
Overshoot Position X
Overshoot Position Y
"; + m_legend->setText(legend); +} + +void PlotWidget::setScroller(QWidget *widget) +{ + if (QAbstractScrollArea *area = qobject_cast(widget)) + widget = area->viewport(); + + if (m_widget) + m_widget->removeEventFilter(this); + m_widget = widget; + reset(); + if (m_widget) + m_widget->installEventFilter(this); +} + +bool PlotWidget::eventFilter(QObject *obj, QEvent *ev) +{ + if (ev->type() == QEvent::Scroll) { + QScrollEvent *se = static_cast(ev); + QScroller *scroller = QScroller::scroller(m_widget); + + QPointF v = scroller->velocity(); + //v.rx() *= scroller->pixelPerMeter().x(); + //v.ry() *= scroller->pixelPerMeter().y(); + + PlotItem pi = { v, se->contentPos(), se->overshootDistance() }; + addPlotItem(pi); + } + + return QWidget::eventFilter(obj, ev); +} + +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.velocity, 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); //too slow for 60fps + p.fillRect(rect(), Qt::white); + + p.setPen(Qt::black); + p.drawLine(0, SCALE(0, 1), width(), SCALE(0, 1)); + + 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->velocity.x(), minMaxVelocity), + qreal(x), SCALE(it->velocity.x(), minMaxVelocity)); + p.setPen(rvColor.dark()); + p.drawLine(qreal(x - 2), SCALE(last->velocity.y(), minMaxVelocity), + qreal(x), SCALE(it->velocity.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; + } + + QString toptext = QString("%1 [m/s] / %2 [pix]").arg(minMaxVelocity, 0, 'f', 2).arg(minMaxPosition, 0, 'f', 2); + QString bottomtext = QString("-%1 [m/s] / -%2 [pix]").arg(minMaxVelocity, 0, 'f', 2).arg(minMaxPosition, 0, 'f', 2); + + p.setPen(Qt::black); + p.drawText(rect(), Qt::AlignTop | Qt::AlignHCenter, toptext); + p.drawText(rect(), Qt::AlignBottom | Qt::AlignHCenter, bottomtext); +#undef SCALE +} diff --git a/examples/scroller/plot/plotwidget.h b/examples/scroller/plot/plotwidget.h new file mode 100644 index 0000000..3b4d92d --- /dev/null +++ b/examples/scroller/plot/plotwidget.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 +#include + +class QPushButton; +class QLabel; + +class QScroller; + +class PlotWidget : public QWidget +{ + Q_OBJECT + +public: + PlotWidget(bool smallscreen = false); + + void setScroller(QWidget *widget); + +public slots: + void reset(); + +protected: + void resizeEvent(QResizeEvent *); + void paintEvent(QPaintEvent *); + + bool eventFilter(QObject *obj, QEvent *ev); + +private: + + struct PlotItem { + QPointF velocity; + QPointF contentPosition; + QPointF overshootPosition; + }; + + void addPlotItem(const PlotItem &pi); + + QWidget *m_widget; + QList m_plotitems; + qreal minMaxVelocity, minMaxPosition; + QPushButton *m_clear; + QLabel *m_legend; +}; + +#endif diff --git a/examples/scroller/plot/settingswidget.cpp b/examples/scroller/plot/settingswidget.cpp new file mode 100644 index 0000000..af1e621 --- /dev/null +++ b/examples/scroller/plot/settingswidget.cpp @@ -0,0 +1,690 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "math.h" + +#include "settingswidget.h" +#include "qscroller.h" +#include "qscrollerproperties.h" + +class SnapOverlay : public QWidget +{ + Q_OBJECT +public: + SnapOverlay(QWidget *w) + : QWidget(w) + { + setAttribute(Qt::WA_TransparentForMouseEvents); + + if (QAbstractScrollArea *area = qobject_cast(w)) { + connect(area->horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(update())); + connect(area->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(update())); + area->viewport()->installEventFilter(this); + } + } + void clear(Qt::Orientation o) + { + m_snap[o].clear(); + update(); + } + + void set(Qt::Orientation o, qreal first, qreal step) + { + m_snap[o] = QList() << -Q_INFINITY << first << step; + update(); + } + + void set(Qt::Orientation o, const QList &list) + { + m_snap[o] = list; + update(); + } + +protected: + bool eventFilter(QObject *o, QEvent *e) + { + if (QAbstractScrollArea *area = qobject_cast(parentWidget())) { + if (area->viewport() == o) { + if (e->type() == QEvent::Move || e->type() == QEvent::Resize) { + setGeometry(area->viewport()->rect()); + } + } + } + return false; + } + + void paintEvent(QPaintEvent *e) + { + if (QAbstractScrollArea *area = qobject_cast(parentWidget())) { + int dx = area->horizontalScrollBar()->value(); + int dy = area->verticalScrollBar()->value(); + + QPainter paint(this); + paint.fillRect(e->rect(), Qt::transparent); + paint.setPen(QPen(Qt::red, 9)); + + if (m_snap[Qt::Horizontal].isEmpty()) { + } else if (m_snap[Qt::Horizontal][0] == -Q_INFINITY) { + int start = int(m_snap[Qt::Horizontal][1]); + int step = int(m_snap[Qt::Horizontal][2]); + if (step > 0) { + for (int i = start; i < area->horizontalScrollBar()->maximum(); i += step) + paint.drawPoint(i - dx, 5); + } + } else { + foreach (qreal r, m_snap[Qt::Horizontal]) + paint.drawPoint(int(r) - dx, 5); + } + paint.setPen(QPen(Qt::green, 9)); + if (m_snap[Qt::Vertical].isEmpty()) { + } else if (m_snap[Qt::Vertical][0] == -Q_INFINITY) { + int start = int(m_snap[Qt::Vertical][1]); + int step = int(m_snap[Qt::Vertical][2]); + if (step > 0) { + for (int i = start; i < area->verticalScrollBar()->maximum(); i += step) + paint.drawPoint(5, i - dy); + } + } else { + foreach (qreal r, m_snap[Qt::Vertical]) + paint.drawPoint(5, int(r) - dy); + } + } + } + +private: + QMap > m_snap; +}; + +struct MetricItem +{ + QScrollerProperties::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) + : m_item(item) + , m_widget(0) + , m_slider(0) + , m_combo(0) + , m_valueLabel(0) + { + m_frameRateType = QVariant::fromValue(QScrollerProperties::Standard).userType(); + m_overshootPolicyType = QVariant::fromValue(QScrollerProperties::OvershootWhenScrollable).userType(); + + if (m_item->min.type() == QVariant::EasingCurve) { + m_combo = new QComboBox(); + m_combo->addItem("OutQuad", QEasingCurve::OutQuad); + m_combo->addItem("OutCubic", QEasingCurve::OutCubic); + m_combo->addItem("OutQuart", QEasingCurve::OutQuart); + m_combo->addItem("OutQuint", QEasingCurve::OutQuint); + m_combo->addItem("OutExpo", QEasingCurve::OutExpo); + m_combo->addItem("OutSine", QEasingCurve::OutSine); + m_combo->addItem("OutCirc", QEasingCurve::OutCirc); + } else if (m_item->min.userType() == m_frameRateType) { + m_combo = new QComboBox(); + m_combo->addItem("Standard", QScrollerProperties::Standard); + m_combo->addItem("60 FPS", QScrollerProperties::Fps60); + m_combo->addItem("30 FPS", QScrollerProperties::Fps30); + m_combo->addItem("20 FPS", QScrollerProperties::Fps20); + } else if (m_item->min.userType() == m_overshootPolicyType) { + m_combo = new QComboBox(); + m_combo->addItem("When Scrollable", QScrollerProperties::OvershootWhenScrollable); + m_combo->addItem("Always On", QScrollerProperties::OvershootAlwaysOn); + m_combo->addItem("Always Off", QScrollerProperties::OvershootAlwaysOff); + } else { + m_slider = new QSlider(Qt::Horizontal); + m_slider->setSingleStep(1); + m_slider->setMinimum(-1); + m_slider->setMaximum(qRound((m_item->max.toReal() - m_item->min.toReal()) / m_item->step.toReal())); + m_slider->setValue(-1); + m_valueLabel = new QLabel(); + } + m_nameLabel = new QLabel(QLatin1String(m_item->name)); + if (m_item->unit && m_item->unit[0]) + m_nameLabel->setText(m_nameLabel->text() + QLatin1String(" [") + QLatin1String(m_item->unit) + QLatin1String("]")); + m_resetButton = new QToolButton(); + m_resetButton->setText(QLatin1String("Reset")); + m_resetButton->setEnabled(false); + + connect(m_resetButton, SIGNAL(clicked()), this, SLOT(reset())); + if (m_slider) { + connect(m_slider, SIGNAL(valueChanged(int)), this, SLOT(controlChanged(int))); + m_slider->setMinimum(0); + } else if (m_combo) { + connect(m_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(controlChanged(int))); + } + } + + void setScroller(QWidget *widget) + { + m_widget = widget; + QScroller *scroller = QScroller::scroller(widget); + QScrollerProperties properties = QScroller::scroller(widget)->scrollerProperties(); + + if (m_slider) + m_slider->setEnabled(scroller); + if (m_combo) + m_combo->setEnabled(scroller); + m_nameLabel->setEnabled(scroller); + if (m_valueLabel) + m_valueLabel->setEnabled(scroller); + m_resetButton->setEnabled(scroller); + + if (!scroller) + return; + + m_default_value = properties.scrollMetric(m_item->metric); + valueChanged(m_default_value); + } + + QWidget *nameLabel() { return m_nameLabel; } + QWidget *valueLabel() { return m_valueLabel; } + QWidget *valueControl() { if (m_combo) return m_combo; else return m_slider; } + QWidget *resetButton() { return m_resetButton; } + +private slots: + void valueChanged(const QVariant &v) + { + m_value = v; + if (m_slider) { + switch (m_item->min.type()) { + case QMetaType::Float: + case QVariant::Double: { + m_slider->setValue(qRound((m_value.toReal() * m_item->scaling - m_item->min.toReal()) / m_item->step.toReal())); + break; + } + case QVariant::Int: { + m_slider->setValue(qRound((m_value.toInt() * m_item->scaling - m_item->min.toInt()) / m_item->step.toInt())); + break; + } + default: break; + } + } else if (m_combo) { + if (m_item->min.type() == QVariant::EasingCurve) { + m_combo->setCurrentIndex(m_combo->findData(v.toEasingCurve().type())); + } else if (m_item->min.userType() == m_overshootPolicyType) { + m_combo->setCurrentIndex(m_combo->findData(v.value())); + } else if (m_item->min.userType() == m_frameRateType) { + m_combo->setCurrentIndex(m_combo->findData(v.value())); + } + } + } + + void controlChanged(int value) + { + bool combo = (m_combo && (sender() == m_combo)); + QString text; + + if (m_slider && !combo) { + switch (m_item->min.type()) { + case QMetaType::Float: + 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() + qRound(qreal(value) * m_item->step.toReal()); + text = QString::number(i); + m_value = i / m_item->scaling; + break; + } + default: break; + } + } else if (m_combo && combo) { + if (m_item->min.type() == QVariant::EasingCurve) { + m_value = QVariant(QEasingCurve(static_cast(m_combo->itemData(value).toInt()))); + } else if (m_item->min.userType() == m_overshootPolicyType) { + m_value = QVariant::fromValue(static_cast(m_combo->itemData(value).toInt())); + } else if (m_item->min.userType() == m_frameRateType) { + m_value = QVariant::fromValue(static_cast(m_combo->itemData(value).toInt())); + } + } + if (m_valueLabel) + m_valueLabel->setText(text); + if (m_widget && QScroller::scroller(m_widget)) { + QScrollerProperties properties = QScroller::scroller(m_widget)->scrollerProperties(); + properties.setScrollMetric(m_item->metric, m_value); + QScroller::scroller(m_widget)->setScrollerProperties(properties); + } + + m_resetButton->setEnabled(m_value != m_default_value); + } + + void reset() + { + QScrollerProperties properties = QScroller::scroller(m_widget)->scrollerProperties(); + properties.setScrollMetric(m_item->metric, m_value); + QScroller::scroller(m_widget)->setScrollerProperties(properties); + valueChanged(m_default_value); + } + +private: + MetricItem *m_item; + int m_frameRateType; + int m_overshootPolicyType; + + QWidget *m_widget; + QSlider *m_slider; + QComboBox *m_combo; + QLabel *m_nameLabel, *m_valueLabel; + QToolButton *m_resetButton; + + QVariant m_value, m_default_value; +}; + +#define METRIC(x) QScrollerProperties::x, #x + +MetricItem items[] = { + { METRIC(MousePressEventDelay), 1000, "ms", qreal(0), qreal(2000), qreal(10) }, + { METRIC(DragStartDistance), 1000, "mm", qreal(1), qreal(20), qreal(0.1) }, + { METRIC(DragVelocitySmoothingFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, + { METRIC(AxisLockThreshold), 1, "", qreal(0), qreal(1), qreal(0.01) }, + + { METRIC(ScrollingCurve), 1, "", QEasingCurve(), 0, 0 }, + { METRIC(DecelerationFactor), 1, "", qreal(0), qreal(3), qreal(0.01) }, + + { 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(MaximumClickThroughVelocity), 1, "m/s", qreal(0), qreal(7), qreal(0.01) }, + + { METRIC(AcceleratingFlickMaximumTime), 1000, "ms", qreal(100), qreal(5000), qreal(100) }, + { METRIC(AcceleratingFlickSpeedupFactor), 1, "", qreal(1), qreal(7), qreal(0.1) }, + + { METRIC(SnapPositionRatio), 1, "", qreal(0.1), qreal(0.9), qreal(0.1) }, + { METRIC(SnapTime), 1000, "ms", qreal(0), qreal(2000), qreal(10) }, + + { METRIC(OvershootDragResistanceFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, + { METRIC(OvershootDragDistanceFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, + { METRIC(OvershootScrollDistanceFactor), 1, "", qreal(0), qreal(1), qreal(0.01) }, + { METRIC(OvershootScrollTime), 1000, "ms", qreal(0), qreal(2000), qreal(10) }, + + { METRIC(HorizontalOvershootPolicy), 1, "", QVariant::fromValue(QScrollerProperties::OvershootWhenScrollable), 0, 0 }, + { METRIC(VerticalOvershootPolicy), 1, "", QVariant::fromValue(QScrollerProperties::OvershootWhenScrollable), 0, 0 }, + { METRIC(FrameRate), 1, "", QVariant::fromValue(QScrollerProperties::Standard), 0, 0 }, +}; + +#undef METRIC + +void SettingsWidget::addToGrid(QGridLayout *grid, QWidget *label, int widgetCount, ...) +{ + va_list args; + va_start(args, widgetCount); + + int rows = grid->rowCount(); + int cols = grid->columnCount(); + + if (label) { + if (m_smallscreen) + grid->addWidget(label, rows++, 0, 1, qMax(cols, widgetCount)); + else + grid->addWidget(label, rows, 0); + } + for (int i = 0; i < widgetCount; i++) { + if (QWidget *w = va_arg(args, QWidget *)) + grid->addWidget(w, rows, m_smallscreen ? i : i + 1); + } + va_end(args); +} + +SettingsWidget::SettingsWidget(bool smallscreen) + : QScrollArea() + , m_widget(0) + , m_snapoverlay(0) + , m_smallscreen(smallscreen) +{ + setWindowTitle(QLatin1String("Settings")); + QWidget *view = new QWidget(); + QVBoxLayout *layout = new QVBoxLayout(view); + QGroupBox *grp; + QGridLayout *grid; + + // GROUP: SCROLL METRICS + + grp = new QGroupBox(QLatin1String("Scroll Metrics")); + grid = new QGridLayout(); + grid->setVerticalSpacing(m_smallscreen ? 4 : 2); + + for (int i = 0; i < int(sizeof(items) / sizeof(items[0])); i++) { + MetricItemUpdater *u = new MetricItemUpdater(items + i); + u->setParent(this); + addToGrid(grid, u->nameLabel(), 3, u->valueControl(), u->valueLabel(), u->resetButton()); + m_metrics.append(u); + } + grp->setLayout(grid); + layout->addWidget(grp); + + // GROUP: SCROLL TO + + grp = new QGroupBox(QLatin1String("Scroll To")); + grid = new QGridLayout(); + grid->setVerticalSpacing(m_smallscreen ? 4 : 2); + + m_scrollx = new QSpinBox(); + m_scrolly = new QSpinBox(); + 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); + int row = smallscreen ? 1 : 0; + int col = smallscreen ? 0 : 4; + grid->addWidget(new QLabel(QLatin1String("in")), row, col++); + grid->addWidget(m_scrolltime, row, col++); + if (smallscreen) { + grid->addWidget(go, row, col + 1); + } else { + grid->addWidget(go, row, col); + grid->setColumnStretch(5, 1); + grid->setColumnStretch(6, 1); + } + grid->setColumnStretch(1, 1); + grid->setColumnStretch(3, 1); + grp->setLayout(grid); + layout->addWidget(grp); + + QLayout *snapbox = new QHBoxLayout(); + + // GROUP: SNAP POINTS X + + grp = new QGroupBox(QLatin1String("Snap Positions X")); + QBoxLayout *vbox = new QVBoxLayout(); + vbox->setSpacing(m_smallscreen ? 4 : 2); + m_snapx = new QComboBox(); + m_snapx->addItem(QLatin1String("No Snapping"), NoSnap); + m_snapx->addItem(QLatin1String("Snap to Interval"), SnapToInterval); + m_snapx->addItem(QLatin1String("Snap to List"), SnapToList); + connect(m_snapx, SIGNAL(currentIndexChanged(int)), this, SLOT(snapModeChanged(int))); + vbox->addWidget(m_snapx); + + m_snapxinterval = new QWidget(); + grid = new QGridLayout(); + grid->setVerticalSpacing(m_smallscreen ? 4 : 2); + m_snapxfirst = new QSpinBox(); + connect(m_snapxfirst, SIGNAL(valueChanged(int)), this, SLOT(snapPositionsChanged())); + grid->addWidget(new QLabel("First:"), 0, 0); + grid->addWidget(m_snapxfirst, 0, 1); + m_snapxstep = new QSpinBox(); + connect(m_snapxstep, SIGNAL(valueChanged(int)), this, SLOT(snapPositionsChanged())); + grid->addWidget(new QLabel("Interval:"), 0, 2); + grid->addWidget(m_snapxstep, 0, 3); + m_snapxinterval->setLayout(grid); + vbox->addWidget(m_snapxinterval); + m_snapxinterval->hide(); + + m_snapxlist = new QPlainTextEdit(); + m_snapxlist->setToolTip(QLatin1String("One snap position per line. Empty lines are ignored.")); + m_snapxlist->installEventFilter(this); + connect(m_snapxlist, SIGNAL(textChanged()), this, SLOT(snapPositionsChanged())); + vbox->addWidget(m_snapxlist); + m_snapxlist->hide(); + + vbox->addStretch(100); + grp->setLayout(vbox); + snapbox->addWidget(grp); + + // GROUP: SNAP POINTS Y + + grp = new QGroupBox(QLatin1String("Snap Positions Y")); + vbox = new QVBoxLayout(); + vbox->setSpacing(m_smallscreen ? 4 : 2); + m_snapy = new QComboBox(); + m_snapy->addItem(QLatin1String("No Snapping"), NoSnap); + m_snapy->addItem(QLatin1String("Snap to Interval"), SnapToInterval); + m_snapy->addItem(QLatin1String("Snap to List"), SnapToList); + connect(m_snapy, SIGNAL(currentIndexChanged(int)), this, SLOT(snapModeChanged(int))); + vbox->addWidget(m_snapy); + + m_snapyinterval = new QWidget(); + grid = new QGridLayout(); + grid->setVerticalSpacing(m_smallscreen ? 4 : 2); + m_snapyfirst = new QSpinBox(); + connect(m_snapyfirst, SIGNAL(valueChanged(int)), this, SLOT(snapPositionsChanged())); + grid->addWidget(new QLabel("First:"), 0, 0); + grid->addWidget(m_snapyfirst, 0, 1); + m_snapystep = new QSpinBox(); + connect(m_snapystep, SIGNAL(valueChanged(int)), this, SLOT(snapPositionsChanged())); + grid->addWidget(new QLabel("Interval:"), 0, 2); + grid->addWidget(m_snapystep, 0, 3); + m_snapyinterval->setLayout(grid); + vbox->addWidget(m_snapyinterval); + m_snapyinterval->hide(); + + m_snapylist = new QPlainTextEdit(); + m_snapylist->setToolTip(QLatin1String("One snap position per line. Empty lines are ignored.")); + m_snapylist->installEventFilter(this); + connect(m_snapylist, SIGNAL(textChanged()), this, SLOT(snapPositionsChanged())); + vbox->addWidget(m_snapylist); + m_snapylist->hide(); + + vbox->addStretch(100); + grp->setLayout(vbox); + snapbox->addWidget(grp); + + layout->addLayout(snapbox); + + layout->addStretch(100); + setWidget(view); + setWidgetResizable(true); +} + +void SettingsWidget::setScroller(QWidget *widget) +{ + delete m_snapoverlay; + if (m_widget) + m_widget->removeEventFilter(this); + QAbstractScrollArea *area = qobject_cast(widget); + if (area) + widget = area->viewport(); + m_widget = widget; + m_widget->installEventFilter(this); + m_snapoverlay = new SnapOverlay(area); + QScrollerProperties properties = QScroller::scroller(widget)->scrollerProperties(); + + QMutableListIterator it(m_metrics); + while (it.hasNext()) + it.next()->setScroller(widget); + + if (!widget) + return; + + updateScrollRanges(); +} + +bool SettingsWidget::eventFilter(QObject *o, QEvent *e) +{ + if (o == m_widget && e->type() == QEvent::Resize) + updateScrollRanges(); + return false; +} + +void SettingsWidget::updateScrollRanges() +{ + QScrollPrepareEvent spe(QPoint(0, 0)); + QApplication::sendEvent(m_widget, &spe); + + QSizeF vp = spe.viewportSize(); + QRectF maxc = spe.contentPosRange(); + + m_scrollx->setRange(qRound(-vp.width()), qRound(maxc.width() + vp.width())); + m_scrolly->setRange(qRound(-vp.height()), qRound(maxc.height() + vp.height())); + + m_snapxfirst->setRange(maxc.left(), maxc.right()); + m_snapxstep->setRange(0, maxc.width()); + m_snapyfirst->setRange(maxc.top(), maxc.bottom()); + m_snapystep->setRange(0, maxc.height()); +} + +void SettingsWidget::scrollTo() +{ + if (QApplication::activePopupWidget()) + return; + if ((sender() == m_scrollx) && !m_scrollx->hasFocus()) + return; + if ((sender() == m_scrolly) && !m_scrolly->hasFocus()) + return; + if ((sender() == m_scrolltime) && !m_scrolltime->hasFocus()) + return; + + if (QScroller *scroller = QScroller::scroller(m_widget)) + scroller->scrollTo(QPointF(m_scrollx->value(), m_scrolly->value()), m_scrolltime->value()); +} + +void SettingsWidget::snapModeChanged(int mode) +{ + if (sender() == m_snapx) { + m_snapxmode = static_cast(mode); + m_snapxinterval->setVisible(mode == SnapToInterval); + m_snapxlist->setVisible(mode == SnapToList); + snapPositionsChanged(); + } else if (sender() == m_snapy) { + m_snapymode = static_cast(mode); + m_snapyinterval->setVisible(mode == SnapToInterval); + m_snapylist->setVisible(mode == SnapToList); + snapPositionsChanged(); + } +} + +void SettingsWidget::snapPositionsChanged() +{ + QScroller *s = QScroller::scroller(m_widget); + if (!s) + return; + + switch (m_snapxmode) { + case NoSnap: + s->setSnapPositionsX(QList()); + m_snapoverlay->clear(Qt::Horizontal); + break; + case SnapToInterval: + s->setSnapPositionsX(m_snapxfirst->value(), m_snapxstep->value()); + m_snapoverlay->set(Qt::Horizontal, m_snapxfirst->value(), m_snapxstep->value()); + break; + case SnapToList: + s->setSnapPositionsX(toPositionList(m_snapxlist, m_snapxfirst->minimum(), m_snapxfirst->maximum())); + m_snapoverlay->set(Qt::Horizontal, toPositionList(m_snapxlist, m_snapxfirst->minimum(), m_snapxfirst->maximum())); + break; + } + switch (m_snapymode) { + case NoSnap: + s->setSnapPositionsY(QList()); + m_snapoverlay->clear(Qt::Vertical); + break; + case SnapToInterval: + s->setSnapPositionsY(m_snapyfirst->value(), m_snapystep->value()); + m_snapoverlay->set(Qt::Vertical, m_snapyfirst->value(), m_snapystep->value()); + break; + case SnapToList: + s->setSnapPositionsY(toPositionList(m_snapylist, m_snapyfirst->minimum(), m_snapyfirst->maximum())); + m_snapoverlay->set(Qt::Vertical, toPositionList(m_snapylist, m_snapyfirst->minimum(), m_snapyfirst->maximum())); + break; + } +} + +QList SettingsWidget::toPositionList(QPlainTextEdit *list, int vmin, int vmax) +{ + QList snaps; + QList extrasel; + QTextEdit::ExtraSelection uline; + uline.format.setUnderlineColor(Qt::red); + uline.format.setUnderlineStyle(QTextCharFormat::WaveUnderline); + int line = 0; + + foreach (const QString &str, list->toPlainText().split(QLatin1Char('\n'))) { + ++line; + if (str.isEmpty()) + continue; + bool ok = false; + double d = str.toDouble(&ok); + if (ok && d >= vmin && d <= vmax) { + snaps << d; + } else { + QTextEdit::ExtraSelection esel = uline; + esel.cursor = QTextCursor(list->document()->findBlockByLineNumber(line - 1)); + esel.cursor.select(QTextCursor::LineUnderCursor); + extrasel << esel; + } + } + list->setExtraSelections(extrasel); + return snaps; +} + +#include "settingswidget.moc" diff --git a/examples/scroller/plot/settingswidget.h b/examples/scroller/plot/settingswidget.h new file mode 100644 index 0000000..2fb268c --- /dev/null +++ b/examples/scroller/plot/settingswidget.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** 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 QScroller; +class QGridLayout; +class QSpinBox; +class QComboBox; +class QCheckBox; +class QPlainTextEdit; + +class MetricItemUpdater; +class SnapOverlay; + +class SettingsWidget : public QScrollArea +{ + Q_OBJECT + +public: + SettingsWidget(bool smallscreen = false); + + void setScroller(QWidget *widget); + +protected: + bool eventFilter(QObject *, QEvent *); + +private slots: + void scrollTo(); + void snapModeChanged(int); + void snapPositionsChanged(); + +private: + enum SnapMode { + NoSnap, + SnapToInterval, + SnapToList + }; + + void addToGrid(QGridLayout *grid, QWidget *label, int widgetCount, ...); + QList toPositionList(QPlainTextEdit *list, int vmin, int vmax); + void updateScrollRanges(); + + QWidget *m_widget; + QSpinBox *m_scrollx, *m_scrolly, *m_scrolltime; + QList m_metrics; + + SnapMode m_snapxmode; + QComboBox *m_snapx; + QWidget *m_snapxinterval; + QPlainTextEdit *m_snapxlist; + QSpinBox *m_snapxfirst; + QSpinBox *m_snapxstep; + + SnapMode m_snapymode; + QComboBox *m_snapy; + QWidget *m_snapyinterval; + QPlainTextEdit *m_snapylist; + QSpinBox *m_snapyfirst; + QSpinBox *m_snapystep; + SnapOverlay *m_snapoverlay; + + bool m_smallscreen; +}; + +#endif diff --git a/examples/scroller/scroller.pro b/examples/scroller/scroller.pro new file mode 100644 index 0000000..e830745 --- /dev/null +++ b/examples/scroller/scroller.pro @@ -0,0 +1,11 @@ +TEMPLATE = subdirs +SUBDIRS = graphicsview \ + plot \ + wheel + +# install +sources.files = *.pro +sources.path = $$[QT_INSTALL_EXAMPLES]/scroller +INSTALLS += sources + +symbian: include($$QT_SOURCE_TREE/examples/symbianpkgrules.pri) diff --git a/examples/scroller/wheel/main.cpp b/examples/scroller/wheel/main.cpp new file mode 100644 index 0000000..4264377 --- /dev/null +++ b/examples/scroller/wheel/main.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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 "wheelwidget.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + MainWindow(bool touch) + : QMainWindow() + { + makeSlotMachine(touch); + setCentralWidget(m_slotMachine); + } + + void makeSlotMachine(bool touch) + { + if (QApplication::desktop()->width() > 1000) { + QFont f = font(); + f.setPointSize(f.pointSize() * 2); + setFont(f); + } + + m_slotMachine = new QWidget(this); + QGridLayout *grid = new QGridLayout(m_slotMachine); + grid->setSpacing(20); + + QStringList colors; + colors << "Red" << "Magenta" << "Peach" << "Orange" << "Yellow" << "Citro" << "Green" << "Cyan" << "Blue" << "Violet"; + + m_wheel1 = new StringWheelWidget(touch); + m_wheel1->setItems( colors ); + grid->addWidget( m_wheel1, 0, 0 ); + + m_wheel2 = new StringWheelWidget(touch); + m_wheel2->setItems( colors ); + grid->addWidget( m_wheel2, 0, 1 ); + + m_wheel3 = new StringWheelWidget(touch); + m_wheel3->setItems( colors ); + grid->addWidget( m_wheel3, 0, 2 ); + + QPushButton *shakeButton = new QPushButton(tr("Shake")); + connect(shakeButton, SIGNAL(clicked()), this, SLOT(rotateRandom())); + + grid->addWidget( shakeButton, 1, 0, 1, 3 ); + } + +private slots: + void rotateRandom() + { + m_wheel1->scrollTo(m_wheel1->currentIndex() + (qrand() % 200)); + m_wheel2->scrollTo(m_wheel2->currentIndex() + (qrand() % 200)); + m_wheel3->scrollTo(m_wheel3->currentIndex() + (qrand() % 200)); + } + +private: + QWidget *m_slotMachine; + + StringWheelWidget *m_wheel1; + StringWheelWidget *m_wheel2; + StringWheelWidget *m_wheel3; +}; + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + bool touch = a.arguments().contains(QLatin1String("--touch")); + + MainWindow *mw = new MainWindow(touch); + mw->show(); + + return a.exec(); +} + +#include "main.moc" diff --git a/examples/scroller/wheel/wheel.pro b/examples/scroller/wheel/wheel.pro new file mode 100644 index 0000000..1f9b789 --- /dev/null +++ b/examples/scroller/wheel/wheel.pro @@ -0,0 +1,16 @@ +HEADERS = wheelwidget.h +SOURCES = wheelwidget.cpp \ + main.cpp + +QT += webkit + +# install +target.path = $$[QT_INSTALL_EXAMPLES]/scroller/wheel +sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS wheel.pro +sources.path = $$[QT_INSTALL_EXAMPLES]/scroller/wheel +INSTALLS += target sources + +symbian { + TARGET.UID3 = 0xA000CF66 + include($$QT_SOURCE_TREE/examples/symbianpkgrules.pri) +} diff --git a/examples/scroller/wheel/wheelwidget.cpp b/examples/scroller/wheel/wheelwidget.cpp new file mode 100644 index 0000000..64a459b --- /dev/null +++ b/examples/scroller/wheel/wheelwidget.cpp @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** 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 "wheelwidget.h" + +#define WHEEL_SCROLL_OFFSET 50000.0 + +AbstractWheelWidget::AbstractWheelWidget(bool touch, QWidget *parent) + : QWidget(parent) + , m_currentItem(0) + , m_itemOffset(0) +{ +// ![0] + QScroller::grabGesture(this, touch ? QScroller::TouchGesture : QScroller::LeftMouseButtonGesture); +// ![0] +} + +AbstractWheelWidget::~AbstractWheelWidget() +{ } + +int AbstractWheelWidget::currentIndex() const +{ + return m_currentItem; +} + +void AbstractWheelWidget::setCurrentIndex(int index) +{ + if (index >= 0 && index < itemCount()) { + m_currentItem = index; + m_itemOffset = 0; + update(); + } +} + +bool AbstractWheelWidget::event(QEvent *e) +{ + switch (e->type()) { +// ![1] + case QEvent::ScrollPrepare: + { + // We set the snap positions as late as possible so that we are sure + // we get the correct itemHeight + QScroller *scroller = QScroller::scroller(this); + scroller->setSnapPositionsY( WHEEL_SCROLL_OFFSET, itemHeight() ); + + QScrollPrepareEvent *se = static_cast(e); + se->setViewportSize(QSizeF(size())); + // we claim a huge scrolling area and a huge content position and + // hope that the user doesn't notice that the scroll area is restricted + se->setContentPosRange(QRectF(0.0, 0.0, 0.0, WHEEL_SCROLL_OFFSET * 2)); + se->setContentPos(QPointF(0.0, WHEEL_SCROLL_OFFSET + m_currentItem * itemHeight() + m_itemOffset)); + se->accept(); + return true; + } +// ![1] +// ![2] + case QEvent::Scroll: + { + QScrollEvent *se = static_cast(e); + + qreal y = se->contentPos().y(); + int iy = y - WHEEL_SCROLL_OFFSET; + int ih = itemHeight(); + +// ![2] + + // -- calculate the current item position and offset and redraw the widget + int ic = itemCount(); + if (ic>0) { + m_currentItem = iy / ih % ic; + m_itemOffset = iy % ih; + + // take care when scrolling backwards. Modulo returns negative numbers + if (m_itemOffset < 0) { + m_itemOffset += ih; + m_currentItem--; + } + + if (m_currentItem < 0) + m_currentItem += ic; + } + // -- repaint + update(); + + se->accept(); + return true; + } + default: + return QWidget::event(e); + } + return true; +} + +void AbstractWheelWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED( event ); + + // -- first calculate size and position. + int w = width(); + int h = height(); + + QPainter painter(this); + QPalette palette = QApplication::palette(); + QPalette::ColorGroup colorGroup = isEnabled() ? QPalette::Active : QPalette::Disabled; + + // linear gradient brush + QLinearGradient grad(0.5, 0, 0.5, 1.0); + grad.setColorAt(0, palette.color(colorGroup, QPalette::ButtonText)); + grad.setColorAt(0.2, palette.color(colorGroup, QPalette::Button)); + grad.setColorAt(0.8, palette.color(colorGroup, QPalette::Button)); + grad.setColorAt(1.0, palette.color(colorGroup, QPalette::ButtonText)); + grad.setCoordinateMode( QGradient::ObjectBoundingMode ); + QBrush gBrush( grad ); + + // paint a border and background + painter.setPen(palette.color(colorGroup, QPalette::ButtonText)); + painter.setBrush(gBrush); + // painter.setBrushOrigin( QPointF( 0.0, 0.0 ) ); + painter.drawRect( 0, 0, w-1, h-1 ); + + // paint inner border + painter.setPen(palette.color(colorGroup, QPalette::Button)); + painter.setBrush(Qt::NoBrush); + painter.drawRect( 1, 1, w-3, h-3 ); + + // paint the items + painter.setClipRect( QRect( 3, 3, w-6, h-6 ) ); + painter.setPen(palette.color(colorGroup, QPalette::ButtonText)); + + int iH = itemHeight(); + int iC = itemCount(); + if (iC > 0) { + + m_itemOffset = m_itemOffset % iH; + + for (int i=-h/2/iH; i<=h/2/iH+1; i++) { + + int itemNum = m_currentItem + i; + while (itemNum < 0) + itemNum += iC; + while (itemNum >= iC) + itemNum -= iC; + + paintItem(&painter, itemNum, QRect(6, h/2 +i*iH - m_itemOffset - iH/2, w-6, iH )); + } + } + + // draw a transparent bar over the center + QColor highlight = palette.color(colorGroup, QPalette::Highlight); + highlight.setAlpha(150); + + QLinearGradient grad2(0.5, 0, 0.5, 1.0); + grad2.setColorAt(0, highlight); + grad2.setColorAt(1.0, highlight.lighter()); + grad2.setCoordinateMode( QGradient::ObjectBoundingMode ); + QBrush gBrush2( grad2 ); + + QLinearGradient grad3(0.5, 0, 0.5, 1.0); + grad3.setColorAt(0, highlight); + grad3.setColorAt(1.0, highlight.darker()); + grad3.setCoordinateMode( QGradient::ObjectBoundingMode ); + QBrush gBrush3( grad3 ); + + painter.fillRect( QRect( 0, h/2 - iH/2, w, iH/2 ), gBrush2 ); + painter.fillRect( QRect( 0, h/2, w, iH/2 ), gBrush3 ); +} + +/*! + Rotates the wheel widget to a given index. + You can also give an index greater than itemCount or less than zero in which + case the wheel widget will scroll in the given direction and end up with + (index % itemCount) +*/ +void AbstractWheelWidget::scrollTo(int index) +{ + QScroller *scroller = QScroller::scroller(this); + + scroller->scrollTo(QPointF(0, WHEEL_SCROLL_OFFSET + index * itemHeight()), 5000); +} + +/*! + \class StringWheelWidget + \brief The StringWheelWidget class is an implementation of the AbstractWheelWidget class that draws QStrings as items. + \sa AbstractWheelWidget +*/ + +StringWheelWidget::StringWheelWidget(bool touch) + : AbstractWheelWidget(touch) +{ } + +QStringList StringWheelWidget::items() const +{ + return m_items; +} + +void StringWheelWidget::setItems( const QStringList &items ) +{ + m_items = items; + if (m_currentItem >= items.count()) + m_currentItem = items.count()-1; + update(); +} + + +QSize StringWheelWidget::sizeHint() const +{ + // determine font size + QFontMetrics fm(font()); + + return QSize( fm.width("m") * 10 + 6, fm.height() * 7 + 6 ); +} + +QSize StringWheelWidget::minimumSizeHint() const +{ + QFontMetrics fm(font()); + + return QSize( fm.width("m") * 5 + 6, fm.height() * 3 + 6 ); +} + +void StringWheelWidget::paintItem(QPainter* painter, int index, const QRect &rect) +{ + painter->drawText(rect, Qt::AlignCenter, m_items.at(index)); +} + +int StringWheelWidget::itemHeight() const +{ + QFontMetrics fm(font()); + return fm.height(); +} + +int StringWheelWidget::itemCount() const +{ + return m_items.count(); +} + + diff --git a/examples/scroller/wheel/wheelwidget.h b/examples/scroller/wheel/wheelwidget.h new file mode 100644 index 0000000..818b6ab --- /dev/null +++ b/examples/scroller/wheel/wheelwidget.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 WHEELWIDGET_H +#define WHEELWIDGET_H + +#include +#include + +class QPainter; +class QRect; + +class AbstractWheelWidget : public QWidget { + Q_OBJECT + +public: + AbstractWheelWidget(bool touch, QWidget *parent = 0); + virtual ~AbstractWheelWidget(); + + int currentIndex() const; + void setCurrentIndex(int index); + + bool event(QEvent*); + void paintEvent(QPaintEvent *e); + virtual void paintItem(QPainter* painter, int index, const QRect &rect) = 0; + + virtual int itemHeight() const = 0; + virtual int itemCount() const = 0; + +public slots: + void scrollTo(int index); + +signals: + void stopped(int index); + +protected: + int m_currentItem; + int m_itemOffset; // 0-itemHeight() + qreal m_lastY; +}; + + +class StringWheelWidget : public AbstractWheelWidget { + Q_OBJECT + +public: + StringWheelWidget(bool touch); + + QStringList items() const; + void setItems( const QStringList &items ); + + QSize sizeHint() const; + QSize minimumSizeHint() const; + + void paintItem(QPainter* painter, int index, const QRect &rect); + + int itemHeight() const; + int itemCount() const; + +private: + QStringList m_items; +}; + +#endif // WHEELWIDGET_H diff --git a/src/corelib/kernel/qcoreevent.cpp b/src/corelib/kernel/qcoreevent.cpp index d23ea4c..4da5fa9 100644 --- a/src/corelib/kernel/qcoreevent.cpp +++ b/src/corelib/kernel/qcoreevent.cpp @@ -231,6 +231,8 @@ QT_BEGIN_NAMESPACE \value WinIdChange The window system identifer for this native widget has changed \value Gesture A gesture was triggered (QGestureEvent) \value GestureOverride A gesture override was triggered (QGestureEvent) + \value ScrollPrepare The object needs to fill in its geometry information (QScrollPrepareEvent) + \value Scroll The object needs to scroll to the supplied position (QScrollEvent) User events should have values between \c User and \c{MaxUser}: diff --git a/src/corelib/kernel/qcoreevent.h b/src/corelib/kernel/qcoreevent.h index 4c91aaf..08b751c 100644 --- a/src/corelib/kernel/qcoreevent.h +++ b/src/corelib/kernel/qcoreevent.h @@ -288,6 +288,9 @@ public: Gesture = 198, GestureOverride = 202, #endif + ScrollPrepare = 204, + Scroll = 205, + // 512 reserved for Qt Jambi's MetaCall event // 513 reserved for Qt Jambi's DeleteOnMainThread event diff --git a/src/gui/itemviews/qabstractitemview.cpp b/src/gui/itemviews/qabstractitemview.cpp index 8af6013..f2d8303 100644 --- a/src/gui/itemviews/qabstractitemview.cpp +++ b/src/gui/itemviews/qabstractitemview.cpp @@ -62,6 +62,9 @@ #include #endif #include +#ifndef QT_NO_GESTURE +# include +#endif QT_BEGIN_NAMESPACE @@ -191,6 +194,37 @@ void QAbstractItemViewPrivate::checkMouseMove(const QPersistentModelIndex &index } } +// stores and restores the selection and current item when flicking +void QAbstractItemViewPrivate::_q_scrollerStateChanged() +{ + Q_Q(QAbstractItemView); + + if (QScroller *scroller = QScroller::scroller(viewport)) { + switch (scroller->state()) { + case QScroller::Pressed: + // store the current selection in case we start scrolling + if (q->selectionModel()) { + oldSelection = q->selectionModel()->selection(); + oldCurrent = q->selectionModel()->currentIndex(); + } + break; + + case QScroller::Dragging: + // restore the old selection if we really start scrolling + if (q->selectionModel()) { + q->selectionModel()->select(oldSelection, QItemSelectionModel::ClearAndSelect); + q->selectionModel()->setCurrentIndex(oldCurrent, QItemSelectionModel::NoUpdate); + } + // fall through + + default: + oldSelection = QItemSelection(); + oldCurrent = QModelIndex(); + break; + } + } +} + /*! \class QAbstractItemView @@ -1616,6 +1650,11 @@ bool QAbstractItemView::viewportEvent(QEvent *event) case QEvent::WindowDeactivate: d->viewport->update(); break; + case QEvent::ScrollPrepare: + executeDelayedItemsLayout(); + connect(QScroller::scroller(d->viewport), SIGNAL(stateChanged(QScroller::State)), this, SLOT(_q_scrollerStateChanged()), Qt::UniqueConnection); + break; + default: break; } diff --git a/src/gui/itemviews/qabstractitemview.h b/src/gui/itemviews/qabstractitemview.h index 0f86f62..6f7db09 100644 --- a/src/gui/itemviews/qabstractitemview.h +++ b/src/gui/itemviews/qabstractitemview.h @@ -359,6 +359,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_modelDestroyed()) Q_PRIVATE_SLOT(d_func(), void _q_layoutChanged()) Q_PRIVATE_SLOT(d_func(), void _q_headerDataChanged()) + Q_PRIVATE_SLOT(d_func(), void _q_scrollerStateChanged()) friend class QTreeViewPrivate; // needed to compile with MSVC friend class QAccessibleItemRow; diff --git a/src/gui/itemviews/qabstractitemview_p.h b/src/gui/itemviews/qabstractitemview_p.h index 03b413a..be20dce 100644 --- a/src/gui/itemviews/qabstractitemview_p.h +++ b/src/gui/itemviews/qabstractitemview_p.h @@ -114,6 +114,7 @@ public: virtual void _q_modelDestroyed(); virtual void _q_layoutChanged(); void _q_headerDataChanged() { doDelayedItemsLayout(); } + void _q_scrollerStateChanged(); void fetchMore(); @@ -414,6 +415,12 @@ public: QAbstractItemView::ScrollMode verticalScrollMode; QAbstractItemView::ScrollMode horizontalScrollMode; +#ifndef QT_NO_GESTURES + // the selection before the last mouse down. In case we have to restore it for scrolling + QItemSelection oldSelection; + QModelIndex oldCurrent; +#endif + bool currentIndexSet; bool wrapItemText; diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp index 5633cb8..422aeeb 100644 --- a/src/gui/kernel/qevent.cpp +++ b/src/gui/kernel/qevent.cpp @@ -4568,4 +4568,223 @@ const QGestureEventPrivate *QGestureEvent::d_func() const #endif // QT_NO_GESTURES +/*! + \class QScrollPrepareEvent + \since 4.8 + \ingroup events + + \brief The QScrollPrepareEvent class is send in preparation of a scrolling. + + The scroll prepare event is send before scrolling (usually by QScroller) is started. + The object receiving this event should set viewportSize, maxContentPos and contentPos. + It also should accept this event to indicate that scrolling should be started. + + It is not guaranteed that a QScrollEvent will be send after an acceepted + QScrollPrepareEvent, e.g. in a case where the maximum content position is (0,0). + + \sa QScrollEvent, QScroller +*/ + +/*! + Creates new QScrollPrepareEvent + The \a startPos is the position of a touch or mouse event that started the scrolling. +*/ +QScrollPrepareEvent::QScrollPrepareEvent(const QPointF &startPos) + : QEvent(QEvent::ScrollPrepare) +{ + d = reinterpret_cast(new QScrollPrepareEventPrivate()); + d_func()->startPos = startPos; +} + +/*! + Destroys QScrollEvent. +*/ +QScrollPrepareEvent::~QScrollPrepareEvent() +{ + delete reinterpret_cast(d); +} + +/*! + Returns the position of the touch or mouse event that started the scrolling. +*/ +QPointF QScrollPrepareEvent::startPos() const +{ + return d_func()->startPos; +} + +/*! + Returns size of the area that is to be scrolled as set by setViewportSize + + \sa setViewportSize() +*/ +QSizeF QScrollPrepareEvent::viewportSize() const +{ + return d_func()->viewportSize; +} + +/*! + Returns the range of coordinates for the content as set by setContentPosRange(). +*/ +QRectF QScrollPrepareEvent::contentPosRange() const +{ + return d_func()->contentPosRange; +} + +/*! + Returns the current position of the content as set by setContentPos. +*/ +QPointF QScrollPrepareEvent::contentPos() const +{ + return d_func()->contentPos; +} + + +/*! + Sets the size of the area that is to be scrolled to \a size. + + \sa viewportSize() +*/ +void QScrollPrepareEvent::setViewportSize(const QSizeF &size) +{ + d_func()->viewportSize = size; +} + +/*! + Sets the range of content coordinates to \a rect. + + \sa contentPosRange() +*/ +void QScrollPrepareEvent::setContentPosRange(const QRectF &rect) +{ + d_func()->contentPosRange = rect; +} + +/*! + Sets the current content position to \a pos. + + \sa contentPos() +*/ +void QScrollPrepareEvent::setContentPos(const QPointF &pos) +{ + d_func()->contentPos = pos; +} + + +/*! + \internal +*/ +QScrollPrepareEventPrivate *QScrollPrepareEvent::d_func() +{ + return reinterpret_cast(d); +} + +/*! + \internal +*/ +const QScrollPrepareEventPrivate *QScrollPrepareEvent::d_func() const +{ + return reinterpret_cast(d); +} + +/*! + \class QScrollEvent + \since 4.8 + \ingroup events + + \brief The QScrollEvent class is send when scrolling. + + The scroll event is send to indicate that the receiver should be scrolled. + Usually the receiver should be something visual like QWidget or QGraphicsObject. + + Some care should be taken that no conflicting QScrollEvents are sent from two + sources. Using QScroller::scrollTo is save however. + + \sa QScrollPrepareEvent, QScroller +*/ + +/*! + \enum QScrollEvent::ScrollState + + This enum describes the states a scroll event can have. + + \value ScrollStarted Set for the first scroll event of a scroll activity. + + \value ScrollUpdated Set for all but the first and the last scroll event of a scroll activity. + + \value ScrollFinished Set for the last scroll event of a scroll activity. + + \sa QScrollEvent::scrollState() +*/ + +/*! + Creates a new QScrollEvent + \a contentPos is the new content position, \a overshootDistance is the + new overshoot distance while \a scrollState indicates if this scroll + event is the first one, the last one or some event in between. +*/ +QScrollEvent::QScrollEvent(const QPointF &contentPos, const QPointF &overshootDistance, ScrollState scrollState) + : QEvent(QEvent::Scroll) +{ + d = reinterpret_cast(new QScrollEventPrivate()); + d_func()->contentPos = contentPos; + d_func()->overshoot= overshootDistance; + d_func()->state = scrollState; +} + +/*! + Destroys QScrollEvent. +*/ +QScrollEvent::~QScrollEvent() +{ + delete reinterpret_cast(d); +} + +/*! + Returns the new scroll position. +*/ +QPointF QScrollEvent::contentPos() const +{ + return d_func()->contentPos; +} + +/*! + Returns the new overshoot distance. + See QScroller for an explanation of the term overshoot. + + \sa QScroller +*/ +QPointF QScrollEvent::overshootDistance() const +{ + return d_func()->overshoot; +} + +/*! + Returns the current scroll state as a combination of ScrollStateFlag values. + ScrollStarted (or ScrollFinished) will be set, if this scroll event is the first (or last) event in a scrolling activity. + Please note that both values can be set at the same time, if the activity consists of a single QScrollEvent. + All other scroll events in between will have their state set to ScrollUpdated. + + A widget could for example revert selections when scrolling is started and stopped. +*/ +QScrollEvent::ScrollState QScrollEvent::scrollState() const +{ + return d_func()->state; +} + +/*! + \internal +*/ +QScrollEventPrivate *QScrollEvent::d_func() +{ + return reinterpret_cast(d); +} + +/*! + \internal +*/ +const QScrollEventPrivate *QScrollEvent::d_func() const +{ + return reinterpret_cast(d); +} + QT_END_NAMESPACE diff --git a/src/gui/kernel/qevent.h b/src/gui/kernel/qevent.h index 9c70c02..6c0076d 100644 --- a/src/gui/kernel/qevent.h +++ b/src/gui/kernel/qevent.h @@ -880,6 +880,52 @@ private: }; #endif // QT_NO_GESTURES +class QScrollPrepareEventPrivate; +class Q_GUI_EXPORT QScrollPrepareEvent : public QEvent +{ +public: + QScrollPrepareEvent(const QPointF &startPos); + ~QScrollPrepareEvent(); + + QPointF startPos() const; + + QSizeF viewportSize() const; + QRectF contentPosRange() const; + QPointF contentPos() const; + + void setViewportSize(const QSizeF &size); + void setContentPosRange(const QRectF &rect); + void setContentPos(const QPointF &pos); + +private: + QScrollPrepareEventPrivate *d_func(); + const QScrollPrepareEventPrivate *d_func() const; +}; + + +class QScrollEventPrivate; +class Q_GUI_EXPORT QScrollEvent : public QEvent +{ +public: + enum ScrollState + { + ScrollStarted, + ScrollUpdated, + ScrollFinished + }; + + QScrollEvent(const QPointF &contentPos, const QPointF &overshoot, ScrollState scrollState); + ~QScrollEvent(); + + QPointF contentPos() const; + QPointF overshootDistance() const; + ScrollState scrollState() const; + +private: + QScrollEventPrivate *d_func(); + const QScrollEventPrivate *d_func() const; +}; + QT_END_NAMESPACE QT_END_HEADER diff --git a/src/gui/kernel/qevent_p.h b/src/gui/kernel/qevent_p.h index e323aa9..02f41e7 100644 --- a/src/gui/kernel/qevent_p.h +++ b/src/gui/kernel/qevent_p.h @@ -178,6 +178,34 @@ public: QUrl url; }; + +class QScrollPrepareEventPrivate +{ +public: + inline QScrollPrepareEventPrivate() + : target(0) + { + } + + QObject* target; + QPointF startPos; + QSizeF viewportSize; + QRectF contentPosRange; + QPointF contentPos; +}; + +class QScrollEventPrivate +{ +public: + inline QScrollEventPrivate() + { + } + + QPointF contentPos; + QPointF overshoot; + QScrollEvent::ScrollState state; +}; + QT_END_NAMESPACE #endif // QEVENT_P_H diff --git a/src/gui/util/qflickgesture.cpp b/src/gui/util/qflickgesture.cpp new file mode 100644 index 0000000..fa04754 --- /dev/null +++ b/src/gui/util/qflickgesture.cpp @@ -0,0 +1,677 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgesture.h" +#include "qcoreapplication.h" +#include "qevent.h" +#include "qwidget.h" +#include "qgraphicsitem.h" +#include "qgraphicsscene.h" +#include "qgraphicssceneevent.h" +#include "qgraphicsview.h" +#include "qscroller.h" +#include "private/qevent_p.h" +#include "private/qflickgesture_p.h" +#include "qdebug.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +//#define QFLICKGESTURE_DEBUG + +#ifdef QFLICKGESTURE_DEBUG +# define qFGDebug qDebug +#else +# define qFGDebug while (false) qDebug +#endif + +extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +static QMouseEvent *copyMouseEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: { + QMouseEvent *me = static_cast(e); + return new QMouseEvent(me->type(), QPoint(0, 0), me->globalPos(), me->button(), me->buttons(), me->modifiers()); + } + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseMove: { + QGraphicsSceneMouseEvent *me = static_cast(e); +#if 1 + QEvent::Type met = me->type() == QEvent::GraphicsSceneMousePress ? QEvent::MouseButtonPress : + (me->type() == QEvent::GraphicsSceneMouseRelease ? QEvent::MouseButtonRelease : QEvent::MouseMove); + return new QMouseEvent(met, QPoint(0, 0), me->screenPos(), me->button(), me->buttons(), me->modifiers()); +#else + QGraphicsSceneMouseEvent *copy = new QGraphicsSceneMouseEvent(me->type()); + copy->setPos(me->pos()); + copy->setScenePos(me->scenePos()); + copy->setScreenPos(me->screenPos()); + for (int i = 0x1; i <= 0x10; i <<= 1) { + Qt::MouseButton button = Qt::MouseButton(i); + copy->setButtonDownPos(button, me->buttonDownPos(button)); + copy->setButtonDownScenePos(button, me->buttonDownScenePos(button)); + copy->setButtonDownScreenPos(button, me->buttonDownScreenPos(button)); + } + copy->setLastPos(me->lastPos()); + copy->setLastScenePos(me->lastScenePos()); + copy->setLastScreenPos(me->lastScreenPos()); + copy->setButtons(me->buttons()); + copy->setButton(me->button()); + copy->setModifiers(me->modifiers()); + return copy; +#endif + } + default: + return 0; + } +} + +class PressDelayHandler : public QObject +{ +private: + PressDelayHandler(QObject *parent = 0) + : QObject(parent) + , pressDelayTimer(0) + , sendingEvent(false) + , mouseButton(Qt::NoButton) + , mouseTarget(0) + { } + + static PressDelayHandler *inst; + +public: + enum { + UngrabMouseBefore = 1, + RegrabMouseAfterwards = 2 + }; + + static PressDelayHandler *instance() + { + static PressDelayHandler *inst = 0; + if (!inst) + inst = new PressDelayHandler(QCoreApplication::instance()); + return inst; + } + + bool shouldEventBeIgnored(QEvent *) const + { + return sendingEvent; + } + + bool isDelaying() const + { + return !pressDelayEvent.isNull(); + } + + void pressed(QEvent *e, int delay) + { + if (!pressDelayEvent) { + pressDelayEvent.reset(copyMouseEvent(e)); + pressDelayTimer = startTimer(delay); + mouseTarget = QApplication::widgetAt(pressDelayEvent->globalPos()); + mouseButton = pressDelayEvent->button(); + qFGDebug() << "QFG: consuming/delaying mouse press"; + } else { + qFGDebug() << "QFG: NOT consuming/delaying mouse press"; + } + e->setAccepted(true); + } + + bool released(QEvent *e, bool scrollerWasActive, bool scrollerIsActive) + { + // consume this event if the scroller was or is active + bool result = scrollerWasActive || scrollerIsActive; + + // stop the timer + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + // we still haven't even sent the press, so do it now + if (pressDelayEvent && mouseTarget && !scrollerIsActive) { + QScopedPointer releaseEvent(copyMouseEvent(e)); + + qFGDebug() << "QFG: re-sending mouse press (due to release) for " << mouseTarget; + sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore); + + qFGDebug() << "QFG: faking mouse release (due to release) for " << mouseTarget; + sendMouseEvent(releaseEvent.data()); + + result = true; // consume this event + } else if (mouseTarget && scrollerIsActive) { + // we grabbed the mouse expicitly when the scroller became active, so undo that now + sendMouseEvent(0, UngrabMouseBefore); + } + pressDelayEvent.reset(0); + mouseTarget = 0; + return result; + } + + void scrollerWasIntercepted() + { + qFGDebug() << "QFG: deleting delayed mouse press, since scroller was only intercepted"; + if (pressDelayEvent) { + // we still haven't even sent the press, so just throw it away now + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + pressDelayEvent.reset(0); + } + mouseTarget = 0; + } + + void scrollerBecameActive() + { + if (pressDelayEvent) { + // we still haven't even sent the press, so just throw it away now + qFGDebug() << "QFG: deleting delayed mouse press, since scroller is active now"; + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + pressDelayEvent.reset(0); + mouseTarget = 0; + } else if (mouseTarget) { + // we did send a press, so we need to fake a release now + Qt::MouseButtons mouseButtons = QApplication::mouseButtons(); + + // release all pressed mouse buttons + /*for (int i = 0; i < 32; ++i) { + if (mouseButtons & (1 << i)) { + Qt::MouseButton b = static_cast(1 << i); + mouseButtons &= ~b; + QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); + + qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; + QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, + b, mouseButtons, QApplication::keyboardModifiers()); + sendMouseEvent(&re); + } + }*/ + + QPoint farFarAway(-QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); + + qFGDebug() << "QFG: sending a fake mouse release at far-far-away to " << mouseTarget; + QMouseEvent re(QEvent::MouseButtonRelease, QPoint(), farFarAway, + mouseButton, QApplication::mouseButtons() & ~mouseButton, + QApplication::keyboardModifiers()); + sendMouseEvent(&re, RegrabMouseAfterwards); + // don't clear the mouseTarget just yet, since we need to explicitly ungrab the mouse on release! + } + } + +protected: + void timerEvent(QTimerEvent *e) + { + if (e->timerId() == pressDelayTimer) { + if (pressDelayEvent && mouseTarget) { + qFGDebug() << "QFG: timer event: re-sending mouse press to " << mouseTarget; + sendMouseEvent(pressDelayEvent.data(), UngrabMouseBefore); + } + pressDelayEvent.reset(0); + + if (pressDelayTimer) { + killTimer(pressDelayTimer); + pressDelayTimer = 0; + } + } + } + + void sendMouseEvent(QMouseEvent *me, int flags = 0) + { + if (mouseTarget) { + sendingEvent = true; + + QGraphicsItem *grabber = 0; + if (mouseTarget->parentWidget()) { + if (QGraphicsView *gv = qobject_cast(mouseTarget->parentWidget())) { + if (gv->scene()) + grabber = gv->scene()->mouseGrabberItem(); + } + } + + if (grabber && (flags & UngrabMouseBefore)) { + // GraphicsView Mouse Handling Workaround #1: + // we need to ungrab the mouse before re-sending the press, + // since the scene had already set the mouse grabber to the + // original (and consumed) event's receiver + qFGDebug() << "QFG: ungrabbing" << grabber; + grabber->ungrabMouse(); + } + + if (me) { + QMouseEvent copy(me->type(), mouseTarget->mapFromGlobal(me->globalPos()), me->globalPos(), me->button(), me->buttons(), me->modifiers()); + qt_sendSpontaneousEvent(mouseTarget, ©); + } + + if (grabber && (flags & RegrabMouseAfterwards)) { + // GraphicsView Mouse Handling Workaround #2: + // we need to re-grab the mouse after sending a faked mouse + // release, since we still need the mouse moves for the gesture + // (the scene will clear the item's mouse grabber status on + // release). + qFGDebug() << "QFG: re-grabbing" << grabber; + grabber->grabMouse(); + } + sendingEvent = false; + } + } + + +private: + int pressDelayTimer; + QScopedPointer pressDelayEvent; + bool sendingEvent; + Qt::MouseButton mouseButton; + QPointer mouseTarget; +}; + + +/*! + \internal + \class QFlickGesture + \since 4.8 + \brief The QFlickGesture class describes a flicking gesture made by the user. + \ingroup gestures + The QFlickGesture is more complex than the QPanGesture that uses QScroller and QScrollerProperties + to decide if it is triggered. + This gesture is reacting on touch event as compared to the QMouseFlickGesture. + + \sa {Gestures Programming}, QScroller, QScrollerProperties, QMouseFlickGesture +*/ + +/*! + \internal +*/ +QFlickGesture::QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent) + : QGesture(*new QFlickGesturePrivate, parent) +{ + d_func()->q_ptr = this; + d_func()->receiver = receiver; + d_func()->receiverScroller = (receiver && QScroller::hasScroller(receiver)) ? QScroller::scroller(receiver) : 0; + d_func()->button = button; +} + +QFlickGesture::~QFlickGesture() +{ } + +QFlickGesturePrivate::QFlickGesturePrivate() + : receiverScroller(0), button(Qt::NoButton), macIgnoreWheel(false) +{ } + + +// +// QFlickGestureRecognizer +// + + +QFlickGestureRecognizer::QFlickGestureRecognizer(Qt::MouseButton button) +{ + this->button = button; +} + +/*! \reimp + */ +QGesture *QFlickGestureRecognizer::create(QObject *target) +{ + QGraphicsObject *go = qobject_cast(target); + if (go && button == Qt::NoButton) { + go->setAcceptTouchEvents(true); + } + return new QFlickGesture(target, button); +} + +/*! \internal + The recognize function detects a touch event suitable to start the attached QScroller. + The QFlickGesture will be triggered as soon as the scroller is no longer in the state + QScroller::Inactive or QScroller::Pressed. It will be finished or canceled + at the next QEvent::TouchEnd. + Note that the QScroller might continue scrolling (kinetically) at this point. + */ +QGestureRecognizer::Result QFlickGestureRecognizer::recognize(QGesture *state, + QObject *watched, + QEvent *event) +{ + Q_UNUSED(watched); + + static QElapsedTimer monotonicTimer; + if (!monotonicTimer.isValid()) + monotonicTimer.start(); + + QFlickGesture *q = static_cast(state); + QFlickGesturePrivate *d = q->d_func(); + + QScroller *scroller = d->receiverScroller; + if (!scroller) + return Ignore; // nothing to do without a scroller? + + QWidget *receiverWidget = qobject_cast(d->receiver); + QGraphicsObject *receiverGraphicsObject = qobject_cast(d->receiver); + + // this is only set for events that we inject into the event loop via sendEvent() + if (PressDelayHandler::instance()->shouldEventBeIgnored(event)) { + //qFGDebug() << state << "QFG: ignored event: " << event->type(); + return Ignore; + } + + const QMouseEvent *me = 0; + const QGraphicsSceneMouseEvent *gsme = 0; + const QTouchEvent *te = 0; + QPoint globalPos; + + // qFGDebug() << "FlickGesture "<receiver<<"event"<type()<<"button"<type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + if (!receiverWidget) + return Ignore; + if (button != Qt::NoButton) { + me = static_cast(event); + globalPos = me->globalPos(); + } + break; + case QEvent::GraphicsSceneMousePress: + case QEvent::GraphicsSceneMouseRelease: + case QEvent::GraphicsSceneMouseMove: + if (!receiverGraphicsObject) + return Ignore; + if (button != Qt::NoButton) { + gsme = static_cast(event); + globalPos = gsme->screenPos(); + } + break; + case QEvent::TouchBegin: + case QEvent::TouchEnd: + case QEvent::TouchUpdate: + if (button == Qt::NoButton) { + te = static_cast(event); + if (!te->touchPoints().isEmpty()) + globalPos = te->touchPoints().at(0).screenPos().toPoint(); + } + break; + +#if defined(Q_WS_MAC) + // the only way to distinguish between real mouse wheels and wheel + // events generated by the native 2 finger swipe gesture is to listen + // for these events (according to Apple's Cocoa Event-Handling Guide) + + case QEvent::NativeGesture: { + QNativeGestureEvent *nge = static_cast(event); + if (nge->gestureType == QNativeGestureEvent::GestureBegin) + d->macIgnoreWheel = true; + else if (nge->gestureType == QNativeGestureEvent::GestureEnd) + d->macIgnoreWheel = false; + break; + } +#endif + + // consume all wheel events if the scroller is active + case QEvent::Wheel: + if (d->macIgnoreWheel || (scroller->state() != QScroller::Inactive)) + return Ignore | ConsumeEventHint; + break; + + // consume all dbl click events if the scroller is active + case QEvent::MouseButtonDblClick: + if (scroller->state() != QScroller::Inactive) + return Ignore | ConsumeEventHint; + break; + + default: + break; + } + + if (!me && !gsme && !te) // Neither mouse nor touch + return Ignore; + + // get the current pointer position in local coordinates. + QPointF point; + QScroller::Input inputType = (QScroller::Input) 0; + + switch (event->type()) { + case QEvent::MouseButtonPress: + if (me && me->button() == button && me->buttons() == button) { + point = me->globalPos(); + inputType = QScroller::InputPress; + } else if (me) { + scroller->stop(); + return CancelGesture; + } + break; + case QEvent::MouseButtonRelease: + if (me && me->button() == button) { + point = me->globalPos(); + inputType = QScroller::InputRelease; + } + break; + case QEvent::MouseMove: +#ifdef Q_OS_SYMBIAN + // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix + // relies on the windowing system to report the current buttons state. + if (me && (me->buttons() == button || !me->buttons())) { +#else + if (me && me->buttons() == button) { +#endif + point = me->globalPos(); + inputType = QScroller::InputMove; + } + break; + + case QEvent::GraphicsSceneMousePress: + if (gsme && gsme->button() == button && gsme->buttons() == button) { + point = gsme->scenePos(); + inputType = QScroller::InputPress; + } else if (gsme) { + scroller->stop(); + return CancelGesture; + } + break; + case QEvent::GraphicsSceneMouseRelease: + if (gsme && gsme->button() == button) { + point = gsme->scenePos(); + inputType = QScroller::InputRelease; + } + break; + case QEvent::GraphicsSceneMouseMove: +#ifdef Q_OS_SYMBIAN + // Qt on Symbian tracks the button state internally, while Qt on Win/Mac/Unix + // relies on the windowing system to report the current buttons state. + if (gsme && (gsme->buttons() == button || !me->buttons())) { +#else + if (gsme && gsme->buttons() == button) { +#endif + point = gsme->scenePos(); + inputType = QScroller::InputMove; + } + break; + + case QEvent::TouchBegin: + inputType = QScroller::InputPress; + // fall through + case QEvent::TouchEnd: + if (!inputType) + inputType = QScroller::InputRelease; + // fallthrough + case QEvent::TouchUpdate: + if (!inputType) + inputType = QScroller::InputMove; + + if (te->deviceType() == QTouchEvent::TouchPad) { + if (te->touchPoints().count() != 2) // 2 fingers on pad + return Ignore; + + point = te->touchPoints().at(0).startScenePos() + + ((te->touchPoints().at(0).scenePos() - te->touchPoints().at(0).startScenePos()) + + (te->touchPoints().at(1).scenePos() - te->touchPoints().at(1).startScenePos())) / 2; + } else { // TouchScreen + if (te->touchPoints().count() != 1) // 1 finger on screen + return Ignore; + + point = te->touchPoints().at(0).scenePos(); + } + break; + + default: + break; + } + + // Check for an active scroller at globalPos + if (inputType == QScroller::InputPress) { + foreach (QScroller *as, QScroller::activeScrollers()) { + if (as != scroller) { + QRegion scrollerRegion; + + if (QWidget *w = qobject_cast(as->target())) { + scrollerRegion = QRect(w->mapToGlobal(QPoint(0, 0)), w->size()); + } else if (QGraphicsObject *go = qobject_cast(as->target())) { + if (go->scene() && !go->scene()->views().isEmpty()) { + foreach (QGraphicsView *gv, go->scene()->views()) + scrollerRegion |= gv->mapFromScene(go->mapToScene(go->boundingRect())) + .translated(gv->mapToGlobal(QPoint(0, 0))); + } + } + // active scrollers always have priority + if (scrollerRegion.contains(globalPos)) + return Ignore; + } + } + } + + bool scrollerWasDragging = (scroller->state() == QScroller::Dragging); + bool scrollerWasScrolling = (scroller->state() == QScroller::Scrolling); + + if (inputType) { + if (QWidget *w = qobject_cast(d->receiver)) + point = w->mapFromGlobal(point.toPoint()); + else if (QGraphicsObject *go = qobject_cast(d->receiver)) + point = go->mapFromScene(point); + + // inform the scroller about the new event + scroller->handleInput(inputType, point, monotonicTimer.elapsed()); + } + + // depending on the scroller state return the gesture state + Result result(0); + bool scrollerIsActive = (scroller->state() == QScroller::Dragging || + scroller->state() == QScroller::Scrolling); + + // Consume all mouse events while dragging or scrolling to avoid nasty + // side effects with Qt's standard widgets. + if ((me || gsme) && scrollerIsActive) + result |= ConsumeEventHint; + + // The only problem with this approach is that we consume the + // MouseRelease when we start the scrolling with a flick gesture, so we + // have to fake a MouseRelease "somewhere" to not mess with the internal + // states of Qt's widgets (a QPushButton would stay in 'pressed' state + // forever, if it doesn't receive a MouseRelease). + if (me || gsme) { + if (!scrollerWasDragging && !scrollerWasScrolling && scrollerIsActive) + PressDelayHandler::instance()->scrollerBecameActive(); + else if (scrollerWasScrolling && (scroller->state() == QScroller::Dragging || scroller->state() == QScroller::Inactive)) + PressDelayHandler::instance()->scrollerWasIntercepted(); + } + + if (!inputType) { + result |= Ignore; + } else { + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::GraphicsSceneMousePress: + if (scroller->state() == QScroller::Pressed) { + int pressDelay = int(1000 * scroller->scrollerProperties().scrollMetric(QScrollerProperties::MousePressEventDelay).toReal()); + if (pressDelay > 0) { + result |= ConsumeEventHint; + + PressDelayHandler::instance()->pressed(event, pressDelay); + event->accept(); + } + } + // fall through + case QEvent::TouchBegin: + q->setHotSpot(globalPos); + result |= scrollerIsActive ? TriggerGesture : MayBeGesture; + break; + + case QEvent::MouseMove: + case QEvent::GraphicsSceneMouseMove: + if (PressDelayHandler::instance()->isDelaying()) + result |= ConsumeEventHint; + // fall through + case QEvent::TouchUpdate: + result |= scrollerIsActive ? TriggerGesture : Ignore; + break; + + case QEvent::GraphicsSceneMouseRelease: + case QEvent::MouseButtonRelease: + if (PressDelayHandler::instance()->released(event, scrollerWasDragging || scrollerWasScrolling, scrollerIsActive)) + result |= ConsumeEventHint; + // fall through + case QEvent::TouchEnd: + result |= scrollerIsActive ? FinishGesture : CancelGesture; + break; + + default: + result |= Ignore; + break; + } + } + return result; +} + + +/*! \reimp + */ +void QFlickGestureRecognizer::reset(QGesture *state) +{ + QGestureRecognizer::reset(state); +} + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES diff --git a/src/gui/util/qflickgesture_p.h b/src/gui/util/qflickgesture_p.h new file mode 100644 index 0000000..c3c263b --- /dev/null +++ b/src/gui/util/qflickgesture_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QFLICKGESTURE_P_H +#define QFLICKGESTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qevent.h" +#include "qgesturerecognizer.h" +#include "private/qgesture_p.h" +#include "qscroller.h" +#include "qscopedpointer.h" + +#ifndef QT_NO_GESTURES + +QT_BEGIN_NAMESPACE + +class QFlickGesturePrivate; +class QGraphicsItem; + +class Q_GUI_EXPORT QFlickGesture : public QGesture +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QFlickGesture) + +public: + QFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent = 0); + ~QFlickGesture(); + + friend class QFlickGestureRecognizer; +}; + +class PressDelayHandler; + +class QFlickGesturePrivate : public QGesturePrivate +{ + Q_DECLARE_PUBLIC(QFlickGesture) +public: + QFlickGesturePrivate(); + + QPointer receiver; + QScroller *receiverScroller; + Qt::MouseButton button; // NoButton == Touch + bool macIgnoreWheel; + static PressDelayHandler *pressDelayHandler; +}; + +class QFlickGestureRecognizer : public QGestureRecognizer +{ +public: + QFlickGestureRecognizer(Qt::MouseButton button); + + QGesture *create(QObject *target); + QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event); + void reset(QGesture *state); + +private: + Qt::MouseButton button; // NoButton == Touch +}; + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES + +#endif // QFLICKGESTURE_P_H diff --git a/src/gui/util/qscroller.cpp b/src/gui/util/qscroller.cpp new file mode 100644 index 0000000..d6b7aaf --- /dev/null +++ b/src/gui/util/qscroller.cpp @@ -0,0 +1,1984 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qevent.h" +#include "qwidget.h" +#include "qscroller.h" +#include "private/qflickgesture_p.h" +#include "private/qscroller_p.h" +#include "qscrollerproperties.h" +#include "private/qscrollerproperties_p.h" +#include "qnumeric.h" +#include "math.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +QT_BEGIN_NAMESPACE + +bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); + +//#define QSCROLLER_DEBUG + +#ifdef QSCROLLER_DEBUG +# define qScrollerDebug qDebug +#else +# define qScrollerDebug while (false) qDebug +#endif + +QDebug &operator<<(QDebug &dbg, const QScrollerPrivate::ScrollSegment &s) +{ + dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime; + dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos; + dbg << "\n Curve: type:" << s.curve.type() << " max progress:" << s.maxProgress << "\n"; + return dbg; +} + + +// a few helper operators to make the code below a lot more readable: +// otherwise a lot of ifs would have to be multi-line to check both the x +// and y coordinate separately. + +// returns true only if the abs. value of BOTH x and y are <= f +inline bool operator<=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f); +} + +// returns true only if the abs. value of BOTH x and y are < f +inline bool operator<(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) < f) && (qAbs(p.y()) < f); +} + +// returns true if the abs. value of EITHER x or y are >= f +inline bool operator>=(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f); +} + +// returns true if the abs. value of EITHER x or y are > f +inline bool operator>(const QPointF &p, qreal f) +{ + return (qAbs(p.x()) > f) || (qAbs(p.y()) > f); +} + +// returns a new point with both coordinates having the abs. value of the original one +inline QPointF qAbs(const QPointF &p) +{ + return QPointF(qAbs(p.x()), qAbs(p.y())); +} + +// returns a new point with all components of p1 multiplied by the corresponding components of p2 +inline QPointF operator*(const QPointF &p1, const QPointF &p2) +{ + return QPointF(p1.x() * p2.x(), p1.y() * p2.y()); +} + +// returns a new point with all components of p1 divided by the corresponding components of p2 +inline QPointF operator/(const QPointF &p1, const QPointF &p2) +{ + return QPointF(p1.x() / p2.x(), p1.y() / p2.y()); +} + +inline QPointF clampToRect(const QPointF &p, const QRectF &rect) +{ + qreal x = qBound(rect.left(), p.x(), rect.right()); + qreal y = qBound(rect.top(), p.y(), rect.bottom()); + return QPointF(x, y); +} + +// returns -1, 0 or +1 according to r being <0, ==0 or >0 +inline int qSign(qreal r) +{ + return (r < 0) ? -1 : ((r > 0) ? 1 : 0); +} + +// this version is not mathematically exact, but it just works for every +// easing curve type (even custom ones) + +static qreal differentialForProgress(const QEasingCurve &curve, qreal pos) +{ + const qreal dx = 0.01; + qreal left = (pos < qreal(0.5)) ? pos : pos - qreal(dx); + qreal right = (pos >= qreal(0.5)) ? pos : pos + qreal(dx); + qreal d = (curve.valueForProgress(right) - curve.valueForProgress(left)) / qreal(dx); + + //qScrollerDebug() << "differentialForProgress(type: " << curve.type() << ", pos: " << pos << ") = " << d; + + return d; +} + +// this version is not mathematically exact, but it just works for every +// easing curve type (even custom ones) + +static qreal progressForValue(const QEasingCurve &curve, qreal value) +{ + if (curve.type() >= QEasingCurve::InElastic && + curve.type() < QEasingCurve::Custom) { + qWarning("progressForValue(): QEasingCurves of type %d do not have an inverse, since they are not injective.", curve.type()); + return value; + } + if (value < qreal(0) || value > qreal(1)) + return value; + + qreal progress = value, left(0), right(1); + for (int iterations = 6; iterations; --iterations) { + qreal v = curve.valueForProgress(progress); + if (v < value) + left = progress; + else if (v > value) + right = progress; + else + break; + progress = (left + right) / qreal(2); + } + return progress; +} + + +class QScrollTimer : public QAbstractAnimation +{ +public: + QScrollTimer(QScrollerPrivate *_d) + : d(_d), ignoreUpdate(false), skip(0) + { } + + int duration() const + { + return -1; + } + + void start() + { + // QAbstractAnimation::start() will immediately call + // updateCurrentTime(), but our state is not set correctly yet + ignoreUpdate = true; + QAbstractAnimation::start(); + ignoreUpdate = false; + skip = 0; + } + +protected: + void updateCurrentTime(int /*currentTime*/) + { + if (!ignoreUpdate) { + if (++skip >= d->frameRateSkip()) { + skip = 0; + d->timerTick(); + } + } + } + +private: + QScrollerPrivate *d; + bool ignoreUpdate; + int skip; +}; + +/*! + \class QScroller + \brief The QScroller class enables kinetic scrolling for any scrolling widget or graphics item. + \since 4.8 + + With kinetic scrolling, the user can push the widget in a given + direction and it will continue to scroll in this direction until it is + stopped either by the user or by friction. Aspects of inertia, friction + and other physical concepts can be changed in order to fine-tune an + intuitive user experience. + + The QScroller object is the object that stores the current position and + speed of the scrolling and takes care of updates. + QScroller can be triggered by a flick gesture + + \code + QWidget *w = ...; + QScroller::grabGesture(w, QScroller::LeftMouseButtonGesture); + \endcode + + or directly like this: + + \code + QWidget *w = ...; + QScroller *scroller = QScroller::scroller(w); + scroller->scrollTo(QPointF(100, 100)); + \endcode + + The scrolled QObjects will be receive a QScrollPrepareEvent whenever the scroller needs to + update its geometry information and a QScrollEvent whenever the content of the object should + actually be scrolled. + + The scroller uses the global QAbstractAnimation timer to generate its QScrollEvents, but this + can be slowed down with QScrollerProperties::FrameRate on a per-QScroller basis. + + Several examples in the \c scroller examples directory show how QScroller, + QScrollEvent and the scroller gesture can be used. + + Even though this kinetic scroller has a huge number of settings available via + QScrollerProperties, we recommend that you leave them all at their default, platform optimized + values. In case you really want to change them you can experiment with the \c plot example in + the \c scroller examples directory first. + + \sa QScrollEvent, QScrollPrepareEvent, QScrollerProperties +*/ + + +QMap QScrollerPrivate::allScrollers; +QSet QScrollerPrivate::activeScrollers; + +/*! + Returns \c true if a QScroller object was already created for \a target; \c false otherwise. + + \sa scroller() +*/ +bool QScroller::hasScroller(QObject *target) +{ + return (QScrollerPrivate::allScrollers.value(target)); +} + +/*! + Returns the scroller for the given \a target. + As long as the object exist this function will always return the same QScroller. + If a QScroller does not exist yet for the \a target, it will implicitly be created. + At no point will two QScrollers be active on one object. + + \sa hasScroller(), target() +*/ +QScroller *QScroller::scroller(QObject *target) +{ + if (!target) { + qWarning("QScroller::scroller() was called with a null target."); + return 0; + } + + if (QScrollerPrivate::allScrollers.contains(target)) + return QScrollerPrivate::allScrollers.value(target); + + QScroller *s = new QScroller(target); + QScrollerPrivate::allScrollers.insert(target, s); + return s; +} + +/*! + \overload + This is the const version of scroller(). +*/ +const QScroller *QScroller::scroller(const QObject *target) +{ + return scroller(const_cast(target)); +} + +/*! + Returns an application wide list of currently active, i.e. state() != + QScroller::Inactive, QScroller objects. + This routine is mostly useful when writing your own gesture recognizer. +*/ +QList QScroller::activeScrollers() +{ + return QScrollerPrivate::activeScrollers.toList(); +} + +/*! + Returns the target object of this scroller. + \sa hasScroller(), scroller() + */ +QObject *QScroller::target() const +{ + Q_D(const QScroller); + return d->target; +} + +/*! + \fn QScroller::scrollerPropertiesChanged(const QScrollerProperties &newProperties); + + QScroller emits this signal whenever its scroller properties have been + changed. \a newProperties are the new scroller properties. + + \sa scrollerProperties +*/ + + +/*! \property QScroller::scrollerProperties + \brief The scroller properties of this scroller. + The properties will be used by the QScroller to determine its scrolling behaviour. +*/ +QScrollerProperties QScroller::scrollerProperties() const +{ + Q_D(const QScroller); + return d->properties; +} + +void QScroller::setScrollerProperties(const QScrollerProperties &sp) +{ + Q_D(QScroller); + if (d->properties != sp) { + d->properties = sp; + emit scrollerPropertiesChanged(sp); + + // we need to force the recalculation here, since the overshootPolicy may have changed and + // exisiting segments may include an overshoot animation. + d->recalcScrollingSegments(true); + } +} + + +/*! + Registers a custom scroll gesture recognizer and grabs it for the \a + target and returns the resulting gesture type. If \a scrollGestureType is + set to TouchGesture the gesture will trigger on touch events - if set to + one of LeftMouseButtonGesture, RightMouseButtonGesture or + MiddleMouseButtonGesture it will trigger on mouse events of the + corresponding button. + + Only one scroll gesture can be active on a single object at the same + time, so if you call this function twice on the same object, it will + ungrab the existing gesture before grabbing the new one. + + Please note: To avoid nasty side-effects, all mouse events will be + consumed while the gesture is triggered. Since the mouse press event is + not consumed, the gesture needs to also send a fake mouse release event + at the global position \c{(INT_MIN, INT_MIN)} to make sure that it + doesn't mess with the internal states of the widget that received the + mouse press in the first place (which could e.g. be a QPushButton + inside a QScrollArea). +*/ +Qt::GestureType QScroller::grabGesture(QObject *target, ScrollerGestureType scrollGestureType) +{ + // ensure that a scroller for target is created + QScroller *s = scroller(target); + if (!s) + return Qt::GestureType(0); + + QScrollerPrivate *sp = s->d_ptr; + if (sp->recognizer) + ungrabGesture(target); // ungrab the old gesture + + Qt::MouseButton button; + switch (scrollGestureType) { + case LeftMouseButtonGesture : button = Qt::LeftButton; break; + case RightMouseButtonGesture : button = Qt::RightButton; break; + case MiddleMouseButtonGesture: button = Qt::MiddleButton; break; + default : + case TouchGesture : button = Qt::NoButton; break; // NoButton == Touch + } + + sp->recognizer = new QFlickGestureRecognizer(button); + sp->recognizerType = QGestureRecognizer::registerRecognizer(sp->recognizer); + + if (target->isWidgetType()) { + QWidget *widget = static_cast(target); + widget->grabGesture(sp->recognizerType); + if (scrollGestureType == TouchGesture) + widget->setAttribute(Qt::WA_AcceptTouchEvents); + + } else if(QGraphicsObject *go = qobject_cast(target)) { + if (scrollGestureType == TouchGesture) + go->setAcceptTouchEvents(true); + go->grabGesture(sp->recognizerType); + } + return sp->recognizerType; +} + +/*! + Returns the gesture type currently grabbed for the \a target or 0 if no + gesture is grabbed. +*/ +Qt::GestureType QScroller::grabbedGesture(QObject *target) +{ + QScroller *s = scroller(target); + if (s && s->d_ptr) + return s->d_ptr->recognizerType; + else + return Qt::GestureType(0); +} + +/*! + Ungrabs the gesture for the \a target. +*/ +void QScroller::ungrabGesture(QObject *target) +{ + QScroller *s = scroller(target); + if (!s) + return; + + QScrollerPrivate *sp = s->d_ptr; + if (!sp->recognizer) + return; // nothing to do + + if (target->isWidgetType()) { + QWidget *widget = static_cast(target); + widget->ungrabGesture(sp->recognizerType); + + } else if(QGraphicsObject *go = qobject_cast(target)) { + go->ungrabGesture(sp->recognizerType); + } + + QGestureRecognizer::unregisterRecognizer(sp->recognizerType); + // do not delete the recognizer. The QGestureManager is doing this. + sp->recognizer = 0; +} + +/*! + \internal +*/ +QScroller::QScroller(QObject *target) + : d_ptr(new QScrollerPrivate(this, target)) +{ + Q_ASSERT(target); // you can't create a scroller without a target in any normal way + Q_D(QScroller); + d->init(); +} + +/*! + \internal +*/ +QScroller::~QScroller() +{ + Q_D(QScroller); + QGestureRecognizer::unregisterRecognizer(d->recognizerType); + // do not delete the recognizer. The QGestureManager is doing this. + d->recognizer = 0; + QScrollerPrivate::allScrollers.remove(d->target); + QScrollerPrivate::activeScrollers.remove(this); + + delete d_ptr; +} + + +/*! + \fn QScroller::stateChanged(QScroller::State newState); + + QScroller emits this signal whenever the state changes. \a newState is the new State. + + \sa state +*/ + +/*! + \property QScroller::state + \brief the state of the scroller + + \sa QScroller::State +*/ +QScroller::State QScroller::state() const +{ + Q_D(const QScroller); + return d->state; +} + +/*! + Stops the scroller and resets the state back to Inactive. +*/ +void QScroller::stop() +{ + Q_D(QScroller); + if (d->state != Inactive) { + QPointF here = clampToRect(d->contentPosition, d->contentPosRange); + qreal snapX = d->nextSnapPos(here.x(), 0, Qt::Horizontal); + qreal snapY = d->nextSnapPos(here.y(), 0, Qt::Vertical); + QPointF snap = here; + if (!qIsNaN(snapX)) + snap.setX(snapX); + if (!qIsNaN(snapY)) + snap.setY(snapY); + d->contentPosition = snap; + d->overshootPosition = QPointF(0, 0); + + d->setState(Inactive); + } +} + +/*! + \brief Returns the pixel per meter metric for the scrolled widget. + + The value is reported for both the x and y axis separately by using a QPointF. + + \note Please note that this value should be physically correct, while the actual DPI settings + that Qt returns for the display may be reported wrongly (on purpose) by the underlying + windowing system (e.g. Mac OS X or Maemo 5). +*/ +QPointF QScroller::pixelPerMeter() const +{ + Q_D(const QScroller); + QPointF ppm = d->pixelPerMeter; + + if (QGraphicsObject *go = qobject_cast(d->target)) { + QTransform viewtr; + //TODO: the first view isn't really correct - maybe use an additional field in the prepare event? + if (go->scene() && !go->scene()->views().isEmpty()) + viewtr = go->scene()->views().first()->viewportTransform(); + QTransform tr = go->deviceTransform(viewtr); + if (tr.isScaling()) { + QPointF p0 = tr.map(QPointF(0, 0)); + QPointF px = tr.map(QPointF(1, 0)); + QPointF py = tr.map(QPointF(0, 1)); + ppm.rx() /= QLineF(p0, px).length(); + ppm.ry() /= QLineF(p0, py).length(); + } + } + return ppm; +} + +/*! + \brief Returns the current velocity of the scroller. + + Returns the current scrolling velocity in meter per second when in the state Scrolling. + Returns a null velocity otherwise. + + The velocity is reported for both the x and y axis separately by using a QPointF. + + \sa pixelPerMeter() +*/ +QPointF QScroller::velocity() const +{ + Q_D(const QScroller); + const QScrollerPropertiesPrivate *sp = d->properties.d.data(); + + switch (state()) { + case Dragging: + return d->releaseVelocity; + case Scrolling: { + QPointF vel; + qint64 now = d->monotonicTimer.elapsed(); + + if (!d->xSegments.isEmpty()) { + const QScrollerPrivate::ScrollSegment &s = d->xSegments.head(); + qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); + qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / s.maxProgress / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); + vel.setX(v); + } + + if (!d->ySegments.isEmpty()) { + const QScrollerPrivate::ScrollSegment &s = d->ySegments.head(); + qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); + qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / s.maxProgress / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress); + vel.setY(v); + } + //qScrollerDebug() << "Velocity: " << vel; + return vel; + } + default: + return QPointF(0, 0); + } +} + +/*! + \brief Returns the target position for the scroll movement. + + Returns the planned final position for the current scroll movement or the current + position if the scroller is not in the scrolling state. + The result is undefined when the scroller is in the inactive state. + + The target position is in pixel. + + \sa pixelPerMeter(), scrollTo() +*/ +QPointF QScroller::finalPosition() const +{ + Q_D(const QScroller); + return QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal), + d->scrollingSegmentsEndPos(Qt::Vertical)); +} + +/*! + Starts scrolling the widget so that point \a pos is at the top-left position in + the viewport. + + The behaviour when scrolling outside the valid scroll area is undefined. + In this case the scroller might or might not overshoot. + + The scrolling speed will be calculated so that the given position will + be reached after a platform-defined time span (e.g. 1 second for Maemo 5). + + \a pos is given in viewport coordinates. + + \sa ensureVisible() +*/ +void QScroller::scrollTo(const QPointF &pos) +{ + // we could make this adjustable via QScrollerProperties + scrollTo(pos, 300); +} + +/*! \overload + + This version will reach its destination position in \a scrollTime milli seconds. +*/ +void QScroller::scrollTo(const QPointF &pos, int scrollTime) +{ + Q_D(QScroller); + + if (d->state == Pressed || d->state == Dragging ) + return; + + // no need to resend a prepare event if we are already scrolling + if (d->state == Inactive && !d->prepareScrolling(QPointF())) + return; + + QPointF newpos = clampToRect(pos, d->contentPosRange); + qreal snapX = d->nextSnapPos(newpos.x(), 0, Qt::Horizontal); + qreal snapY = d->nextSnapPos(newpos.y(), 0, Qt::Vertical); + if (!qIsNaN(snapX)) + newpos.setX(snapX); + if (!qIsNaN(snapY)) + newpos.setY(snapY); + + qScrollerDebug() << "QScroller::scrollTo(req:" << pos << " [pix] / snap:" << newpos << ", " << scrollTime << " [ms])"; + + if (newpos == d->contentPosition + d->overshootPosition) + return; + + QPointF vel = velocity(); + + if (scrollTime < 0) + scrollTime = 0; + qreal time = qreal(scrollTime) / 1000; + + d->createScrollToSegments(vel.x(), time, newpos.x(), Qt::Horizontal, QScrollerPrivate::ScrollTypeScrollTo); + d->createScrollToSegments(vel.y(), time, newpos.y(), Qt::Vertical, QScrollerPrivate::ScrollTypeScrollTo); + + if (!scrollTime) + d->setContentPositionHelperScrolling(); + d->setState(scrollTime ? Scrolling : Inactive); +} + +/*! + Starts scrolling so that the rectangle \a rect is visible inside the + viewport with additional margins specified in pixels by \a xmargin and \a ymargin around + the rect. + + In cases where it is not possible to fit the rect plus margins inside the viewport the contents + are scrolled so that as much as possible is visible from \a rect. + + The scrolling speed will be calculated so that the given position will + be reached after a platform-defined time span (e.g. 1 second for Maemo 5). + + This function performs the actual scrolling by calling scrollTo(). +*/ +void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin) +{ + // we could make this adjustable via QScrollerProperties + ensureVisible(rect, xmargin, ymargin, 1000); +} + +/*! \overload + + This version will reach its destination position in \a scrollTime milli seconds. +*/ +void QScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime) +{ + Q_D(QScroller); + + if (d->state == Pressed || d->state == Dragging ) + return; + + if (d->state == Inactive && !d->prepareScrolling(QPointF())) + return; + + // -- calculate the current pos (or the position after the current scroll) + QPointF startPos = d->contentPosition + d->overshootPosition; + startPos = QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal), + d->scrollingSegmentsEndPos(Qt::Vertical)); + + QRectF marginRect(rect.x() - xmargin, rect.y() - ymargin, + rect.width() + 2 * xmargin, rect.height() + 2 * ymargin); + + QSizeF visible = d->viewportSize; + QRectF visibleRect(startPos, visible); + + qScrollerDebug() << "QScroller::ensureVisible(" << rect << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])"; + qScrollerDebug() << " --> content position:" << d->contentPosition; + + if (visibleRect.contains(marginRect)) + return; + + QPointF newPos = startPos; + + if (visibleRect.width() < rect.width()) { + // at least try to move the rect into view + if (rect.left() > visibleRect.left()) + newPos.setX(rect.left()); + else if (rect.right() < visibleRect.right()) + newPos.setX(rect.right() - visible.width()); + + } else if (visibleRect.width() < marginRect.width()) { + newPos.setX(rect.center().x() - visibleRect.width() / 2); + } else if (marginRect.left() > visibleRect.left()) { + newPos.setX(marginRect.left()); + } else if (marginRect.right() < visibleRect.right()) { + newPos.setX(marginRect.right() - visible.width()); + } + + if (visibleRect.height() < rect.height()) { + // at least try to move the rect into view + if (rect.top() > visibleRect.top()) + newPos.setX(rect.top()); + else if (rect.bottom() < visibleRect.bottom()) + newPos.setX(rect.bottom() - visible.height()); + + } else if (visibleRect.height() < marginRect.height()) { + newPos.setY(rect.center().y() - visibleRect.height() / 2); + } else if (marginRect.top() > visibleRect.top()) { + newPos.setY(marginRect.top()); + } else if (marginRect.bottom() < visibleRect.bottom()) { + newPos.setY(marginRect.bottom() - visible.height()); + } + + // clamp to maximum content position + newPos = clampToRect(newPos, d->contentPosRange); + if (newPos == startPos) + return; + + scrollTo(newPos, scrollTime); +} + +/*! This function resends the QScrollPrepareEvent. + * Calling resendPrepareEvent triggers a QScrollPrepareEvent from the scroller. + * This will allow the receiver to re-set content position and content size while + * scrolling. + * Calling this function while in the Inactive state is useless as the prepare event + * is send again right before scrolling starts. + */ +void QScroller::resendPrepareEvent() +{ + Q_D(QScroller); + d->prepareScrolling(d->pressPosition); +} + +/*! Set the snap positions for the horizontal axis. + * Set the snap positions to a list of \a positions. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an empty list of positions. + */ +void QScroller::setSnapPositionsX(const QList &positions) +{ + Q_D(QScroller); + d->snapPositionsX = positions; + d->snapIntervalX = 0.0; + + d->recalcScrollingSegments(); +} + +/*! Set the snap positions for the horizontal axis. + * Set the snap positions to regular spaced intervals. + * The first snap position will be at \a first from the beginning of the list. The next at \a first + \a interval and so on. + * This can be used to implement a list header. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an interval of 0.0 + */ +void QScroller::setSnapPositionsX(qreal first, qreal interval) +{ + Q_D(QScroller); + d->snapFirstX = first; + d->snapIntervalX = interval; + d->snapPositionsX.clear(); + + d->recalcScrollingSegments(); +} + +/*! Set the snap positions for the vertical axis. + * Set the snap positions to a list of \a positions. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an empty list of positions. + */ +void QScroller::setSnapPositionsY(const QList &positions) +{ + Q_D(QScroller); + d->snapPositionsY = positions; + d->snapIntervalY = 0.0; + + d->recalcScrollingSegments(); +} + +/*! Set the snap positions for the vertical axis. + * Set the snap positions to regular spaced intervals. + * The first snap position will be at \a first. The next at \a first + \a interval and so on. + * This will overwrite all previously set snap positions and also a previously + * set snapping interval. + * Snapping can be deactivated by setting an interval of 0.0 + */ +void QScroller::setSnapPositionsY(qreal first, qreal interval) +{ + Q_D(QScroller); + d->snapFirstY = first; + d->snapIntervalY = interval; + d->snapPositionsY.clear(); + + d->recalcScrollingSegments(); +} + + + +// -------------- private ------------ + +QScrollerPrivate::QScrollerPrivate(QScroller *q, QObject *_target) + : target(_target) + , recognizer(0) + , recognizerType(Qt::CustomGesture) + , state(QScroller::Inactive) + , firstScroll(true) + , pressTimestamp(0) + , lastTimestamp(0) + , snapFirstX(-1.0) + , snapIntervalX(0.0) + , snapFirstY(-1.0) + , snapIntervalY(0.0) + , scrollTimer(new QScrollTimer(this)) + , q_ptr(q) +{ + connect(target, SIGNAL(destroyed(QObject*)), this, SLOT(targetDestroyed())); +} + +void QScrollerPrivate::init() +{ + setDpiFromWidget(0); + monotonicTimer.start(); +} + +void QScrollerPrivate::sendEvent(QObject *o, QEvent *e) +{ + qt_sendSpontaneousEvent(o, e); +} + +const char *QScrollerPrivate::stateName(QScroller::State state) +{ + switch (state) { + case QScroller::Inactive: return "inactive"; + case QScroller::Pressed: return "pressed"; + case QScroller::Dragging: return "dragging"; + case QScroller::Scrolling: return "scrolling"; + default: return "(invalid)"; + } +} + +const char *QScrollerPrivate::inputName(QScroller::Input input) +{ + switch (input) { + case QScroller::InputPress: return "press"; + case QScroller::InputMove: return "move"; + case QScroller::InputRelease: return "release"; + default: return "(invalid)"; + } +} + +void QScrollerPrivate::targetDestroyed() +{ + scrollTimer->stop(); + delete q_ptr; +} + +void QScrollerPrivate::timerTick() +{ + struct timerevent { + QScroller::State state; + typedef void (QScrollerPrivate::*timerhandler_t)(); + timerhandler_t handler; + }; + + timerevent timerevents[] = { + { QScroller::Dragging, &QScrollerPrivate::timerEventWhileDragging }, + { QScroller::Scrolling, &QScrollerPrivate::timerEventWhileScrolling }, + }; + + for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) { + timerevent *te = timerevents + i; + + if (state == te->state) { + (this->*te->handler)(); + return; + } + } + + scrollTimer->stop(); +} + +/*! + This function is used by gesture recognizers to inform the scroller about a new input event. + The scroller will change its internal state() according to the input event and its attached + scroller properties. Since the scroller doesn't care about the actual kind of input device the + event came from, you need to decompose the event into the \a input type, a \a position and a + milli-second \a timestamp. The \a position needs to be in the target's coordinate system. + The return value is \c true if the event should be consumed by the calling filter or \c false + if the event should be forwarded to the control. + + \note Using grabGesture() should be sufficient for most use cases though. +*/ +bool QScroller::handleInput(Input input, const QPointF &position, qint64 timestamp) +{ + Q_D(QScroller); + + qScrollerDebug() << "QScroller::handleInput(" << input << ", " << d->stateName(d->state) << ", " << position << ", " << timestamp << ")"; + struct statechange { + State state; + Input input; + typedef bool (QScrollerPrivate::*inputhandler_t)(const QPointF &position, qint64 timestamp); + inputhandler_t handler; + }; + + statechange statechanges[] = { + { QScroller::Inactive, InputPress, &QScrollerPrivate::pressWhileInactive }, + { QScroller::Pressed, InputMove, &QScrollerPrivate::moveWhilePressed }, + { QScroller::Pressed, InputRelease, &QScrollerPrivate::releaseWhilePressed }, + { QScroller::Dragging, InputMove, &QScrollerPrivate::moveWhileDragging }, + { QScroller::Dragging, InputRelease, &QScrollerPrivate::releaseWhileDragging }, + { QScroller::Scrolling, InputPress, &QScrollerPrivate::pressWhileScrolling } + }; + + for (int i = 0; i < int(sizeof(statechanges) / sizeof(*statechanges)); ++i) { + statechange *sc = statechanges + i; + + if (d->state == sc->state && input == sc->input) + return (d->*sc->handler)(position - d->overshootPosition, timestamp); + } + return false; +} + +#ifdef Q_WS_MAEMO_5 + +QPointF QScrollerPrivate::realDpi(int screen) +{ + Q_UNUSED(screen); + // The DPI value is hardcoded to 96 on Maemo5: + // https://projects.maemo.org/bugzilla/show_bug.cgi?id=152525 + // This value (260) is only correct for the N900 though, but + // there's no way to get the real DPI at run time. + return QPointF(260, 260); +} + +#elif defined(Q_WS_MAC) + +// implemented in qscroller_mac.mm + +#else + +QPointF QScrollerPrivate::realDpi(int screen) +{ + QWidget *w = QApplication::desktop()->screen(screen); + return QPointF(w->physicalDpiX(), w->physicalDpiY()); +} + +#endif + +/*! \internal + Returns the resolution of the used screen. +*/ +QPointF QScrollerPrivate::dpi() const +{ + return pixelPerMeter / qreal(39.3700787); +} + +/*! \internal + Sets the resolution used for scrolling. + This resolution is only used by the kinetic scroller. If you change this + then the scroller will behave quite different as a lot of the values are + given in physical distances (millimeter). +*/ +void QScrollerPrivate::setDpi(const QPointF &dpi) +{ + pixelPerMeter = dpi * qreal(39.3700787); +} + +/*! \internal + Sets the dpi used for scrolling to the value of the widget. +*/ +void QScrollerPrivate::setDpiFromWidget(QWidget *widget) +{ + QDesktopWidget *dw = QApplication::desktop(); + setDpi(realDpi(widget ? dw->screenNumber(widget) : dw->primaryScreen())); +} + +/*! \internal + Updates the velocity during dragging. + Sets releaseVelocity. +*/ +void QScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime) +{ + Q_Q(QScroller); + QPointF ppm = q->pixelPerMeter(); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + QPointF deltaPixel = deltaPixelRaw; + + qScrollerDebug() << "QScroller::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])"; + + // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms) + if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / ((ppm.x() + ppm.y()) / 2) * 1000) > qreal(2.5)) + deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength(); + + qreal inversSmoothingFactor = ((qreal(1) - sp->dragVelocitySmoothingFactor) * qreal(deltaTime) / qreal(1000)); + QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm; + newv = newv * (qreal(1) - inversSmoothingFactor) + releaseVelocity * inversSmoothingFactor; + + if (deltaPixel.x()) + releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity)); + if (deltaPixel.y()) + releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity)); + + qScrollerDebug() << " --> new velocity:" << releaseVelocity; +} + +void QScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal startPos, qreal endPos, QEasingCurve::Type curve, Qt::Orientation orientation, qreal maxProgress) +{ + if (startPos == endPos) + return; + + ScrollSegment s; + if (orientation == Qt::Horizontal && !xSegments.isEmpty()) + s.startTime = xSegments.last().startTime + xSegments.last().deltaTime; + else if (orientation == Qt::Vertical && !ySegments.isEmpty()) + s.startTime = ySegments.last().startTime + ySegments.last().deltaTime; + else + s.startTime = monotonicTimer.elapsed(); + + s.startPos = startPos; + s.deltaPos = endPos - startPos; + s.deltaTime = deltaTime * 1000; + s.maxProgress = maxProgress; + s.curve.setType(curve); + s.type = type; + + if (orientation == Qt::Horizontal) + xSegments.enqueue(s); + else + ySegments.enqueue(s); + + qScrollerDebug() << "+++ Added a new ScrollSegment: " << s; +} + + +/*! \internal + Clears the old segments and recalculates them if the current segments are not longer valid +*/ +void QScrollerPrivate::recalcScrollingSegments(bool forceRecalc) +{ + Q_Q(QScroller); + QPointF ppm = q->pixelPerMeter(); + + releaseVelocity = q->velocity(); + + if (forceRecalc || !scrollingSegmentsValid(Qt::Horizontal)) + createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal); + + if (forceRecalc || !scrollingSegmentsValid(Qt::Vertical)) + createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical); +} + +/*! \internal + Returns the end position after the current scroll has finished. +*/ +qreal QScrollerPrivate::scrollingSegmentsEndPos(Qt::Orientation orientation) const +{ + const QQueue *segments; + qreal endPos; + + if (orientation == Qt::Horizontal) { + segments = &xSegments; + endPos = contentPosition.x() + overshootPosition.x(); + } else { + segments = &ySegments; + endPos = contentPosition.y() + overshootPosition.y(); + } + + if (!segments->isEmpty()) { + const ScrollSegment &last = segments->last(); + endPos = last.startPos + last.deltaPos; + } + + return endPos; +} + +/*! \internal + Checks if the scroller segment end in a valid position. +*/ +bool QScrollerPrivate::scrollingSegmentsValid(Qt::Orientation orientation) +{ + QQueue *segments; + qreal minPos; + qreal maxPos; + + if (orientation == Qt::Horizontal) { + segments = &xSegments; + minPos = contentPosRange.left(); + maxPos = contentPosRange.right(); + } else { + segments = &ySegments; + minPos = contentPosRange.top(); + maxPos = contentPosRange.bottom(); + } + + if (segments->isEmpty()) + return true; + + const ScrollSegment &last = segments->last(); + qreal endPos = last.startPos + last.deltaPos; + + if (last.type == ScrollTypeScrollTo) + return true; // scrollTo is always valid + + if (last.type == ScrollTypeOvershoot && + endPos != minPos && endPos != maxPos) + return false; + + if (endPos < minPos || endPos > maxPos) + return false; + + if (endPos == minPos || endPos == maxPos) // the begin and the end of the list are always ok + return true; + + qreal nextSnap = nextSnapPos(endPos, 0, orientation); + if (!qIsNaN(nextSnap) && endPos != nextSnap) + return false; + + return true; +} + +/*! \internal + Creates the sections needed to scroll to the specific \a endPos to the segments queue. +*/ +void QScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type) +{ + Q_UNUSED(v); + + if (orientation == Qt::Horizontal) { + xSegments.clear(); + } else { + ySegments.clear(); + } + + qScrollerDebug() << "+++ createScrollToSegments: t:" << deltaTime << "ep:" << endPos << "o:" << int(orientation); + + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x() + : contentPosition.y() + overshootPosition.y(); + qreal deltaPos = endPos - startPos; + + pushSegment(type, deltaTime * 0.3, startPos, startPos + deltaPos * 0.5, QEasingCurve::InQuad, orientation); + pushSegment(type, deltaTime * 0.7, startPos + deltaPos * 0.5, endPos, sp->scrollingCurve.type(), orientation); +} + +/*! \internal +*/ +void QScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation) +{ + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + QScrollerProperties::OvershootPolicy policy; + qreal minPos; + qreal maxPos; + qreal viewSize; + + if (orientation == Qt::Horizontal) { + xSegments.clear(); + policy = sp->hOvershootPolicy; + minPos = contentPosRange.left(); + maxPos = contentPosRange.right(); + viewSize = viewportSize.width(); + } else { + ySegments.clear(); + policy = sp->vOvershootPolicy; + minPos = contentPosRange.top(); + maxPos = contentPosRange.bottom(); + viewSize = viewportSize.height(); + } + + bool alwaysOvershoot = (policy == QScrollerProperties::OvershootAlwaysOn); + bool noOvershoot = (policy == QScrollerProperties::OvershootAlwaysOff) || !sp->overshootScrollDistanceFactor; + bool canOvershoot = !noOvershoot && (alwaysOvershoot || maxPos); + + qScrollerDebug() << "+++ createScrollingSegments: s:" << startPos << "maxPos:" << maxPos << "o:" << int(orientation); + + // -- check if we are in overshoot + if (startPos < minPos) { + createScrollToSegments(v, sp->overshootScrollTime * 0.5, minPos, orientation, ScrollTypeOvershoot); + return; + } + + if (startPos > maxPos) { + createScrollToSegments(v, sp->overshootScrollTime * 0.5, maxPos, orientation, ScrollTypeOvershoot); + return; + } + + qScrollerDebug() << "v = " << v << ", decelerationFactor = " << sp->decelerationFactor << ", curveType = " << sp->scrollingCurve.type(); + + // This is only correct for QEasingCurve::OutQuad (linear velocity, + // constant deceleration), but the results look and feel ok for OutExpo + // and OutSine as well + + // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime) + // v(0) = vrelease + // v(deltaTime) = 0 + // deltaTime = (2 * vrelease) / (a * differntial(0)) + + // pos(t) = integrate(v(t)dt) + // pos(t) = vrelease * t - 0.5 * a * t * t + // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime + // deltaPos = pos(deltaTime) + + qreal deltaTime = (qreal(2) * qAbs(v)) / (sp->decelerationFactor * differentialForProgress(sp->scrollingCurve, 0)); + qreal deltaPos = qSign(v) * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor * ppm; + qreal endPos = startPos + deltaPos; + + qScrollerDebug() << " Real Delta:" << deltaPos; + + // -- determine snap points + qreal nextSnap = nextSnapPos(endPos, 0, orientation); + qreal lowerSnapPos = nextSnapPos(startPos, -1, orientation); + qreal higherSnapPos = nextSnapPos(startPos, 1, orientation); + + qScrollerDebug() << " Real Delta:" << lowerSnapPos <<"-"< higherSnapPos || qIsNaN(higherSnapPos)) + higherSnapPos = nextSnap; + if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos)) + lowerSnapPos = nextSnap; + + if (qAbs(v) < sp->minimumVelocity) { + + qScrollerDebug() << "### below minimum Vel" << orientation; + + // - no snap points or already at one + if (qIsNaN(nextSnap) || nextSnap == startPos) + return; // nothing to do, no scrolling needed. + + // - decide which point to use + + qreal snapDistance = higherSnapPos - lowerSnapPos; + + qreal pressDistance = (orientation == Qt::Horizontal) ? + lastPosition.x() - pressPosition.x() : + lastPosition.y() - pressPosition.y(); + + // if not dragged far enough, pick the next snap point. + if (sp->snapPositionRatio == 0.0 || qAbs(pressDistance / sp->snapPositionRatio) > snapDistance) + endPos = nextSnap; + else if (pressDistance < 0.0) + endPos = lowerSnapPos; + else + endPos = higherSnapPos; + + deltaPos = endPos - startPos; + pushSegment(ScrollTypeFlick, sp->snapTime * 0.3, startPos, startPos + deltaPos * 0.3, QEasingCurve::InQuad, orientation); + pushSegment(ScrollTypeFlick, sp->snapTime * 0.7, startPos + deltaPos * 0.3, endPos, sp->scrollingCurve.type(), orientation); + return; + } + + // - go to the next snappoint if there is one + if (v > 0 && !qIsNaN(higherSnapPos)) { + // change the time in relation to the changed end position + if (endPos - startPos) + deltaTime *= qAbs((higherSnapPos - startPos) / (endPos - startPos)); + if (deltaTime > sp->snapTime) + deltaTime = sp->snapTime; + endPos = higherSnapPos; + + } else if (v < 0 && !qIsNaN(lowerSnapPos)) { + // change the time in relation to the changed end position + if (endPos - startPos) + deltaTime *= qAbs((lowerSnapPos - startPos) / (endPos - startPos)); + if (deltaTime > sp->snapTime) + deltaTime = sp->snapTime; + endPos = lowerSnapPos; + + // -- check if we are overshooting + } else if (endPos < minPos || endPos > maxPos) { + qreal stopPos = endPos < minPos ? minPos : maxPos; + + qScrollerDebug() << "Overshoot: delta:" << (stopPos - startPos); + + qreal maxProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos)); + qScrollerDebug() << "Overshoot maxp:" << maxProgress; + + pushSegment(ScrollTypeFlick, deltaTime * maxProgress, startPos, stopPos, sp->scrollingCurve.type(), orientation, maxProgress); + + if (canOvershoot) { + qreal endV = qSign(v) * deltaTime * sp->decelerationFactor * qreal(0.5) * differentialForProgress(sp->scrollingCurve, maxProgress); + qScrollerDebug() << "Overshoot: velocity" << endV; + qScrollerDebug() << "Overshoot: maxVelocity" << sp->maximumVelocity; + qScrollerDebug() << "Overshoot: viewsize" << viewSize; + qScrollerDebug() << "Overshoot: factor" << sp->overshootScrollDistanceFactor; + + qreal oDistance = viewSize * sp->overshootScrollDistanceFactor * endV / sp->maximumVelocity; + qreal oDeltaTime = sp->overshootScrollTime; + + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.5, stopPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation); + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.3, stopPos + oDistance, stopPos + oDistance * 0.3, QEasingCurve::InQuad, orientation); + pushSegment(ScrollTypeOvershoot, oDeltaTime * 0.2, stopPos + oDistance * 0.3, stopPos, QEasingCurve::OutQuad, orientation); + } + return; + } + + pushSegment(ScrollTypeFlick, deltaTime, startPos, endPos, sp->scrollingCurve.type(), orientation); +} + + +/*! Prepares scrolling by sending a QScrollPrepareEvent to the receiver widget. + Returns true if the scrolling was accepted and a target was returned. +*/ +bool QScrollerPrivate::prepareScrolling(const QPointF &position) +{ + QScrollPrepareEvent spe(position); + spe.ignore(); + sendEvent(target, &spe); + + qScrollerDebug() << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted() << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos(); + if (spe.isAccepted()) { + QPointF oldContentPos = contentPosition + overshootPosition; + QPointF contentDelta = spe.contentPos() - oldContentPos; + + viewportSize = spe.viewportSize(); + contentPosRange = spe.contentPosRange(); + if (contentPosRange.width() < 0) + contentPosRange.setWidth(0); + if (contentPosRange.height() < 0) + contentPosRange.setHeight(0); + contentPosition = clampToRect(spe.contentPos(), contentPosRange); + overshootPosition = spe.contentPos() - contentPosition; + + // - check if the content position was moved + if (contentDelta != QPointF(0, 0)) { + // need to correct all segments + for (int i = 0; i < xSegments.count(); i++) + xSegments[i].startPos -= contentDelta.x(); + + for (int i = 0; i < ySegments.count(); i++) + ySegments[i].startPos -= contentDelta.y(); + } + + if (QWidget *w = qobject_cast(target)) + setDpiFromWidget(w); + if (QGraphicsObject *go = qobject_cast(target)) { + //TODO: the first view isn't really correct - maybe use an additional field in the prepare event? + if (go->scene() && !go->scene()->views().isEmpty()) + setDpiFromWidget(go->scene()->views().first()); + } + + if (state == QScroller::Scrolling) { + recalcScrollingSegments(); + } + return true; + } + + return false; +} + +void QScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp) +{ + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + QPointF deltaPixel = position - lastPosition; + qint64 deltaTime = timestamp - lastTimestamp; + + if (sp->axisLockThreshold) { + int dx = qAbs(deltaPixel.x()); + int dy = qAbs(deltaPixel.y()); + if (dx || dy) { + bool vertical = (dy > dx); + qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx); + //qScrollerDebug() << "QScroller::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy; + if (alpha <= sp->axisLockThreshold) { + if (vertical) + deltaPixel.setX(0); + else + deltaPixel.setY(0); + } + } + } + + // calculate velocity (if the user would release the mouse NOW) + updateVelocity(deltaPixel, deltaTime); + + // restrict velocity, if content is not scrollable + QRectF max = contentPosRange; + bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + + if (!canScrollX) { + deltaPixel.setX(0); + releaseVelocity.setX(0); + } + if (!canScrollY) { + deltaPixel.setY(0); + releaseVelocity.setY(0); + } + +// if (firstDrag) { +// // Do not delay the first drag +// setContentPositionHelper(q->contentPosition() - overshootDistance - deltaPixel); +// dragDistance = QPointF(0, 0); +// } else { + dragDistance += deltaPixel; +// } +//qScrollerDebug() << "######################" << deltaPixel << position.y() << lastPosition.y(); + if (canScrollX) + lastPosition.setX(position.x()); + if (canScrollY) + lastPosition.setY(position.y()); + lastTimestamp = timestamp; +} + +bool QScrollerPrivate::pressWhileInactive(const QPointF &position, qint64 timestamp) +{ + if (prepareScrolling(position)) { + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + if (!contentPosRange.isNull() || + (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) || + (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn)) { + + lastPosition = pressPosition = position; + lastTimestamp = pressTimestamp = timestamp; + setState(QScroller::Pressed); + } + } + return false; +} + +bool QScrollerPrivate::releaseWhilePressed(const QPointF &, qint64) +{ + if (overshootPosition != QPointF(0.0, 0.0)) { + setState(QScroller::Scrolling); + return true; + } else { + setState(QScroller::Inactive); + return false; + } +} + +bool QScrollerPrivate::moveWhilePressed(const QPointF &position, qint64 timestamp) +{ + Q_Q(QScroller); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + QPointF ppm = q->pixelPerMeter(); + + QPointF deltaPixel = position - pressPosition; + + bool moveAborted = false; + bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance); + + // check the direction of the mouse drag and abort if it's too much in the wrong direction. + if (moveStarted) { + QRectF max = contentPosRange; + bool canScrollX = (max.width() > 0); + bool canScrollY = (max.height() > 0); + + if (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) + canScrollX = true; + if (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn) + canScrollY = true; + + if (qAbs(deltaPixel.x() / ppm.x()) < qAbs(deltaPixel.y() / ppm.y())) { + if (!canScrollY) + moveAborted = true; + } else { + if (!canScrollX) + moveAborted = true; + } + } + + if (moveAborted) { + setState(QScroller::Inactive); + moveStarted = false; + + } else if (moveStarted) { + setState(QScroller::Dragging); + + // subtract the dragStartDistance + deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength()); + + if (deltaPixel != QPointF(0, 0)) { + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(pressPosition + deltaPixel, timestamp); + } + } + return moveStarted; +} + +bool QScrollerPrivate::moveWhileDragging(const QPointF &position, qint64 timestamp) +{ + // handleDrag updates lastPosition, lastTimestamp and velocity + handleDrag(position, timestamp); + return true; +} + +void QScrollerPrivate::timerEventWhileDragging() +{ + if (dragDistance != QPointF(0, 0)) { + qScrollerDebug() << "QScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance; + + setContentPositionHelperDragging(-dragDistance); + dragDistance = QPointF(0, 0); + } +} + +bool QScrollerPrivate::releaseWhileDragging(const QPointF &position, qint64 timestamp) +{ + Q_Q(QScroller); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + + // check if we moved at all - this can happen if you stop a running + // scroller with a press and release shortly afterwards + QPointF deltaPixel = position - pressPosition; + if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) { + + // handle accelerating flicks + if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime && + ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) { + + // - determine if the direction was changed + int signX = 0, signY = 0; + if (releaseVelocity.x()) + signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1; + if (releaseVelocity.y()) + signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1; + + if (signX > 0) + releaseVelocity.setX(qBound(-sp->maximumVelocity, + oldVelocity.x() * sp->acceleratingFlickSpeedupFactor, + sp->maximumVelocity)); + if (signY > 0) + releaseVelocity.setY(qBound(-sp->maximumVelocity, + oldVelocity.y() * sp->acceleratingFlickSpeedupFactor, + sp->maximumVelocity)); + } + } + + QPointF ppm = q->pixelPerMeter(); + createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal); + createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical); + + qScrollerDebug() << "QScroller::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition; + + if (xSegments.isEmpty() && ySegments.isEmpty()) + setState(QScroller::Inactive); + else + setState(QScroller::Scrolling); + + return true; +} + +void QScrollerPrivate::timerEventWhileScrolling() +{ + qScrollerDebug() << "QScroller::timerEventWhileScrolling()"; + + setContentPositionHelperScrolling(); + if (xSegments.isEmpty() && ySegments.isEmpty()) + setState(QScroller::Inactive); +} + +bool QScrollerPrivate::pressWhileScrolling(const QPointF &position, qint64 timestamp) +{ + Q_Q(QScroller); + + if ((q->velocity() <= properties.d->maximumClickThroughVelocity) && + (overshootPosition == QPointF(0.0, 0.0))) { + setState(QScroller::Inactive); + return false; + } else { + lastPosition = pressPosition = position; + lastTimestamp = pressTimestamp = timestamp; + setState(QScroller::Pressed); + setState(QScroller::Dragging); + return true; + } +} + +/*! \internal + This function handles all state changes of the scroller. +*/ +void QScrollerPrivate::setState(QScroller::State newstate) +{ + Q_Q(QScroller); + bool sendLastScroll = false; + + if (state == newstate) + return; + + qScrollerDebug() << q << "QScroller::setState(" << stateName(newstate) << ")"; + + switch (newstate) { + case QScroller::Inactive: + scrollTimer->stop(); + + // send the last scroll event (but only after the current state change was finished) + if (!firstScroll) + sendLastScroll = true; + + releaseVelocity = QPointF(0, 0); + break; + + case QScroller::Pressed: + scrollTimer->stop(); + + oldVelocity = releaseVelocity; + releaseVelocity = QPointF(0, 0); + break; + + case QScroller::Dragging: + dragDistance = QPointF(0, 0); + if (state == QScroller::Pressed) + scrollTimer->start(); + break; + + case QScroller::Scrolling: + scrollTimer->start(); + break; + } + + qSwap(state, newstate); + + if (sendLastScroll) { + QScrollEvent se(contentPosition, overshootPosition, QScrollEvent::ScrollFinished); + sendEvent(target, &se); + firstScroll = true; + } + if (state == QScroller::Dragging || state == QScroller::Scrolling) + activeScrollers.insert(q); + else + activeScrollers.remove(q); + emit q->stateChanged(state); +} + + +/*! \internal + Helps when setting the content position. + It will try to move the content by the requested delta but stop in case + when we are coming back from an overshoot or a scrollTo. + It will also indicate a new overshooting condition by the overshootX and oversthootY flags. + + In this cases it will reset the velocity variables and other flags. + + Also keeps track of the current over-shooting value in overshootPosition. + + \a deltaPos is the amount of pixels the current content position should be moved +*/ +void QScrollerPrivate::setContentPositionHelperDragging(const QPointF &deltaPos) +{ + Q_Q(QScroller); + QPointF ppm = q->pixelPerMeter(); + const QScrollerPropertiesPrivate *sp = properties.d.data(); + QPointF v = q->velocity(); + + if (sp->overshootDragResistanceFactor) + overshootPosition /= sp->overshootDragResistanceFactor; + + QPointF oldPos = contentPosition + overshootPosition; + QPointF newPos = oldPos + deltaPos; + + qScrollerDebug() << "QScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])"; + qScrollerDebug() << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos; + + QPointF oldClampedPos = clampToRect(oldPos, contentPosRange); + QPointF newClampedPos = clampToRect(newPos, contentPosRange); + + // --- handle overshooting and stop if the coordinate is going back inside the normal area + bool alwaysOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + bool alwaysOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOn); + bool noOvershootX = (sp->hOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) || + ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) || + !sp->overshootDragDistanceFactor; + bool noOvershootY = (sp->vOvershootPolicy == QScrollerProperties::OvershootAlwaysOff) || + ((state == QScroller::Dragging) && !sp->overshootDragResistanceFactor) || + !sp->overshootDragDistanceFactor; + bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width()); + bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height()); + + qreal oldOvershootX = (canOvershootX) ? oldPos.x() - oldClampedPos.x() : 0; + qreal oldOvershootY = (canOvershootY) ? oldPos.y() - oldClampedPos.y() : 0; + + qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0; + qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0; + + qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor; + qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor; + + qScrollerDebug() << " --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<hOvershootPolicy; + qScrollerDebug() << " --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX; + + if (sp->overshootDragResistanceFactor) { + oldOvershootX *= sp->overshootDragResistanceFactor; + oldOvershootY *= sp->overshootDragResistanceFactor; + newOvershootX *= sp->overshootDragResistanceFactor; + newOvershootY *= sp->overshootDragResistanceFactor; + } + + // -- stop at the maximum overshoot distance + + newOvershootX = qBound(-maxOvershootX, newOvershootX, maxOvershootX); + newOvershootY = qBound(-maxOvershootY, newOvershootY, maxOvershootY); + + overshootPosition.setX(newOvershootX); + overshootPosition.setY(newOvershootY); + contentPosition = newClampedPos; + + QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated); + sendEvent(target, &se); + firstScroll = false; + + qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition << + "- overshoot x/y?:" << overshootPosition; +} + + +qreal QScrollerPrivate::nextSegmentPosition(QQueue &segments, qint64 now, qreal oldPos) +{ + qreal pos = oldPos; + + // check the X segments for new positions + while (!segments.isEmpty()) { + const ScrollSegment s = segments.head(); + + if ((s.startTime + s.deltaTime) <= now) { + segments.dequeue(); + pos = s.startPos + s.deltaPos; + } else if (s.startTime <= now) { + qreal progress = qreal(now - s.startTime) / (qreal(s.deltaTime) / s.maxProgress); + pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress) / s.curve.valueForProgress(s.maxProgress); + break; + } else { + break; + } + } + return pos; +} + +void QScrollerPrivate::setContentPositionHelperScrolling() +{ + qint64 now = monotonicTimer.elapsed(); + QPointF newPos = contentPosition + overshootPosition; + + newPos.setX(nextSegmentPosition(xSegments, now, newPos.x())); + newPos.setY(nextSegmentPosition(ySegments, now, newPos.y())); + + // -- set the position and handle overshoot + qScrollerDebug() << "QScroller::setContentPositionHelperScrolling()"; + qScrollerDebug() << " --> overshoot:" << overshootPosition << "- new pos:" << newPos; + + QPointF newClampedPos = clampToRect(newPos, contentPosRange); + + overshootPosition = newPos - newClampedPos; + contentPosition = newClampedPos; + + QScrollEvent se(contentPosition, overshootPosition, firstScroll ? QScrollEvent::ScrollStarted : QScrollEvent::ScrollUpdated); + sendEvent(target, &se); + firstScroll = false; + + qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition; +} + +/*! \internal + * Returns the next snap point in direction. + * If \a direction >0 it will return the next snap point that is larger than the current position. + * If \a direction <0 it will return the next snap point that is smaller than the current position. + * If \a direction ==0 it will return the nearest snap point (or the current position if we are already + * on a snap point. + * Returns the nearest snap position or NaN if no such point could be found. + */ +qreal QScrollerPrivate::nextSnapPos(qreal p, int dir, Qt::Orientation orientation) +{ + qreal bestSnapPos = Q_QNAN; + qreal bestSnapPosDist = Q_INFINITY; + + qreal minPos; + qreal maxPos; + + if (orientation == Qt::Horizontal) { + minPos = contentPosRange.left(); + maxPos = contentPosRange.right(); + } else { + minPos = contentPosRange.top(); + maxPos = contentPosRange.bottom(); + } + + if (orientation == Qt::Horizontal) { + // the snap points in the list + foreach (qreal snapPos, snapPositionsX) { + qreal snapPosDist = snapPos - p; + if ((dir > 0 && snapPosDist < 0) || + (dir < 0 && snapPosDist > 0)) + continue; // wrong direction + if (snapPos < minPos || snapPos > maxPos ) + continue; // invalid + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist ) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + + // the snap point interval + if (snapIntervalX > 0.0) { + qreal first = minPos + snapFirstX; + qreal snapPos; + if (dir > 0) + snapPos = qCeil((p - first) / snapIntervalX) * snapIntervalX + first; + else if (dir < 0) + snapPos = qFloor((p - first) / snapIntervalX) * snapIntervalX + first; + else if (p <= first) + snapPos = first; + else + { + qreal last = qFloor((maxPos - first) / snapIntervalX) * snapIntervalX + first; + if (p >= last) + snapPos = last; + else + snapPos = qRound((p - first) / snapIntervalX) * snapIntervalX + first; + } + + if (snapPos >= first && snapPos <= maxPos ) { + qreal snapPosDist = snapPos - p; + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist ) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + } + + } else { // (orientation == Qt::Vertical) + // the snap points in the list + foreach (qreal snapPos, snapPositionsY) { + qreal snapPosDist = snapPos - p; + if ((dir > 0 && snapPosDist < 0) || + (dir < 0 && snapPosDist > 0)) + continue; // wrong direction + if (snapPos < minPos || snapPos > maxPos ) + continue; // invalid + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + + // the snap point interval + if (snapIntervalY > 0.0) { + qreal first = minPos + snapFirstY; + qreal snapPos; + if (dir > 0) + snapPos = qCeil((p - first) / snapIntervalY) * snapIntervalY + first; + else if (dir < 0) + snapPos = qFloor((p - first) / snapIntervalY) * snapIntervalY + first; + else if (p <= first) + snapPos = first; + else + { + qreal last = qFloor((maxPos - first) / snapIntervalY) * snapIntervalY + first; + if (p >= last) + snapPos = last; + else + snapPos = qRound((p - first) / snapIntervalY) * snapIntervalY + first; + } + + if (snapPos >= first && snapPos <= maxPos ) { + qreal snapPosDist = snapPos - p; + + if (qIsNaN(bestSnapPos) || + qAbs(snapPosDist) < bestSnapPosDist) { + bestSnapPos = snapPos; + bestSnapPosDist = qAbs(snapPosDist); + } + } + } + } + + return bestSnapPos; +} + +/*! + \enum QScroller::State + + This enum contains the different QScroller states. + + \value Inactive The scroller is not scrolling and nothing is pressed. + \value Pressed A touch event was received or the mouse button pressed but the scroll area is currently not dragged. + \value Dragging The scroll area is currently following the touch point or mouse. + \value Scrolling The scroll area is moving on it's own. +*/ + +/*! + \enum QScroller::ScrollerGestureType + + This enum contains the different gesture types that are supported by the QScroller gesture recognizer. + + \value TouchGesture The gesture recognizer will only trigger on touch + events. Specifically it will react on single touch points when using a + touch screen and dual touch points when using a touchpad. + \value LeftMouseButtonGesture The gesture recognizer will only trigger on left mouse button events. + \value MiddleMouseButtonGesture The gesture recognizer will only trigger on middle mouse button events. + \value RightMouseButtonGesture The gesture recognizer will only trigger on right mouse button events. +*/ + +/*! + \enum QScroller::Input + + This enum contains an input device agnostic view of input events that are relevant for QScroller. + + \value InputPress The user pressed the input device (e.g. QEvent::MouseButtonPress, + QEvent::GraphicsSceneMousePress, QEvent::TouchBegin) + + \value InputMove The user moved the input device (e.g. QEvent::MouseMove, + QEvent::GraphicsSceneMouseMove, QEvent::TouchUpdate) + + \value InputRelease The user released the input device (e.g. QEvent::MouseButtonRelease, + QEvent::GraphicsSceneMouseRelease, QEvent::TouchEnd) + +*/ + +QT_END_NAMESPACE diff --git a/src/gui/util/qscroller.h b/src/gui/util/qscroller.h new file mode 100644 index 0000000..34e1125 --- /dev/null +++ b/src/gui/util/qscroller.h @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCROLLER_H +#define QSCROLLER_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QWidget; +class QScrollerPrivate; +class QScrollerProperties; +class QFlickGestureRecognizer; +class QMouseFlickGestureRecognizer; + +class Q_GUI_EXPORT QScroller : public QObject +{ + Q_OBJECT + Q_PROPERTY(State state READ state NOTIFY stateChanged) + Q_PROPERTY(QScrollerProperties scrollerProperties READ scrollerProperties WRITE setScrollerProperties NOTIFY scrollerPropertiesChanged) + Q_ENUMS(State) + +public: + enum State + { + Inactive, + Pressed, + Dragging, + Scrolling, + }; + + enum ScrollerGestureType + { + TouchGesture, + LeftMouseButtonGesture, + RightMouseButtonGesture, + MiddleMouseButtonGesture + }; + + enum Input + { + InputPress = 1, + InputMove, + InputRelease + }; + + static bool hasScroller(QObject *target); + + static QScroller *scroller(QObject *target); + static const QScroller *scroller(const QObject *target); + + static Qt::GestureType grabGesture(QObject *target, ScrollerGestureType gestureType = TouchGesture); + static Qt::GestureType grabbedGesture(QObject *target); + static void ungrabGesture(QObject *target); + + static QList activeScrollers(); + + QObject *target() const; + + State state() const; + + bool handleInput(Input input, const QPointF &position, qint64 timestamp = 0); + + void stop(); + QPointF velocity() const; + QPointF finalPosition() const; + QPointF pixelPerMeter() const; + + QScrollerProperties scrollerProperties() const; + + void setSnapPositionsX( const QList &positions ); + void setSnapPositionsX( qreal first, qreal interval ); + void setSnapPositionsY( const QList &positions ); + void setSnapPositionsY( qreal first, qreal interval ); + +public Q_SLOTS: + void setScrollerProperties(const QScrollerProperties &prop); + void scrollTo(const QPointF &pos); + void scrollTo(const QPointF &pos, int scrollTime); + void ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin); + void ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime); + void resendPrepareEvent(); + +Q_SIGNALS: + void stateChanged(QScroller::State newstate); + void scrollerPropertiesChanged(const QScrollerProperties &); + +private: + QScrollerPrivate *d_ptr; + + QScroller(QObject *target); + virtual ~QScroller(); + + Q_DISABLE_COPY(QScroller) + Q_DECLARE_PRIVATE(QScroller) + + friend class QFlickGestureRecognizer; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSCROLLER_H diff --git a/src/gui/util/qscroller_mac.mm b/src/gui/util/qscroller_mac.mm new file mode 100644 index 0000000..3203036 --- /dev/null +++ b/src/gui/util/qscroller_mac.mm @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#import + +#include "qscroller_p.h" + +#ifdef Q_WS_MAC + +QPointF QScrollerPrivate::realDpi(int screen) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSArray *nsscreens = [NSScreen screens]; + + if (screen < 0 || screen >= int([nsscreens count])) + screen = 0; + + NSScreen *nsscreen = [nsscreens objectAtIndex:screen]; + CGDirectDisplayID display = [[[nsscreen deviceDescription] objectForKey:@"NSScreenNumber"] intValue]; + + CGSize mmsize = CGDisplayScreenSize(display); + if (mmsize.width > 0 && mmsize.height > 0) { + return QPointF(CGDisplayPixelsWide(display) / mmsize.width, + CGDisplayPixelsHigh(display) / mmsize.height) * qreal(25.4); + } else { + return QPointF(); + } + [pool release]; +} + +#endif diff --git a/src/gui/util/qscroller_p.h b/src/gui/util/qscroller_p.h new file mode 100644 index 0000000..98f34f7 --- /dev/null +++ b/src/gui/util/qscroller_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCROLLER_P_H +#define QSCROLLER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QFlickGestureRecognizer; + +class QScrollTimer; + +class QScrollerPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(QScroller) + +public: + QScrollerPrivate(QScroller *q, QObject *target); + void init(); + + void sendEvent(QObject *o, QEvent *e); + + void setState(QScroller::State s); + + enum ScrollType { + ScrollTypeFlick = 0, + ScrollTypeScrollTo, + ScrollTypeOvershoot + }; + + struct ScrollSegment { + qint64 startTime; + qint64 deltaTime; + qreal startPos; + qreal deltaPos; + QEasingCurve curve; + qreal maxProgress; + ScrollType type; + }; + + bool pressWhileInactive(const QPointF &position, qint64 timestamp); + bool moveWhilePressed(const QPointF &position, qint64 timestamp); + bool releaseWhilePressed(const QPointF &position, qint64 timestamp); + bool moveWhileDragging(const QPointF &position, qint64 timestamp); + bool releaseWhileDragging(const QPointF &position, qint64 timestamp); + bool pressWhileScrolling(const QPointF &position, qint64 timestamp); + + void timerTick(); + void timerEventWhileDragging(); + void timerEventWhileScrolling(); + + bool prepareScrolling(const QPointF &position); + void handleDrag(const QPointF &position, qint64 timestamp); + + QPointF realDpi(int screen); + QPointF dpi() const; + void setDpi(const QPointF &dpi); + void setDpiFromWidget(QWidget *widget); + + void updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime); + void pushSegment(ScrollType type, qreal deltaTime, qreal startPos, qreal endPos, QEasingCurve::Type curve, Qt::Orientation orientation, qreal maxProgress = 1.0); + void recalcScrollingSegments(bool forceRecalc = false); + qreal scrollingSegmentsEndPos(Qt::Orientation orientation) const; + bool scrollingSegmentsValid(Qt::Orientation orientation); + void createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type); + void createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation); + + void setContentPositionHelperDragging(const QPointF &deltaPos); + void setContentPositionHelperScrolling(); + + qreal nextSnapPos(qreal p, int dir, Qt::Orientation orientation); + static qreal nextSegmentPosition(QQueue &segments, qint64 now, qreal oldPos); + + inline int frameRateSkip() const { return properties.d.data()->frameRate; } + + static const char *stateName(QScroller::State state); + static const char *inputName(QScroller::Input input); + +public slots: + void targetDestroyed(); + +public: + // static + static QMap allScrollers; + static QSet activeScrollers; + + // non static + QObject *target; + QScrollerProperties properties; + QFlickGestureRecognizer *recognizer; + Qt::GestureType recognizerType; + + // scroller state: + + // QPointer scrollTarget; + QSizeF viewportSize; + QRectF contentPosRange; + QPointF contentPosition; + QPointF overshootPosition; // the number of pixels we are overshooting (before overshootDragResistanceFactor) + + // state + + bool enabled; + QScroller::State state; + bool firstScroll; // true if we haven't already send a scroll event + + QPointF oldVelocity; // the release velocity of the last drag + + QPointF pressPosition; + QPointF lastPosition; + qint64 pressTimestamp; + qint64 lastTimestamp; + + QPointF dragDistance; // the distance we should move during the next drag timer event + + QQueue xSegments; + QQueue ySegments; + + // snap positions + QList snapPositionsX; + qreal snapFirstX; + qreal snapIntervalX; + QList snapPositionsY; + qreal snapFirstY; + qreal snapIntervalY; + + QPointF pixelPerMeter; + + QElapsedTimer monotonicTimer; + + QPointF releaseVelocity; // the starting velocity of the scrolling state + QScrollTimer *scrollTimer; + + QScroller *q_ptr; +}; + + +QT_END_NAMESPACE + +#endif // QSCROLLER_P_H + diff --git a/src/gui/util/qscrollerproperties.cpp b/src/gui/util/qscrollerproperties.cpp new file mode 100644 index 0000000..4fba489 --- /dev/null +++ b/src/gui/util/qscrollerproperties.cpp @@ -0,0 +1,418 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#ifdef Q_WS_WIN +# include +#endif + +#include "qscrollerproperties.h" +#include "private/qscrollerproperties_p.h" + +QT_BEGIN_NAMESPACE + +static QScrollerPropertiesPrivate *userDefaults = 0; +static QScrollerPropertiesPrivate *systemDefaults = 0; + +QScrollerPropertiesPrivate *QScrollerPropertiesPrivate::defaults() +{ + if (!systemDefaults) { + QScrollerPropertiesPrivate spp; +#ifdef Q_WS_MAEMO_5 + spp.mousePressEventDelay = qreal(0); + spp.dragStartDistance = qreal(2.5 / 1000); + spp.dragVelocitySmoothingFactor = qreal(0.15); + spp.axisLockThreshold = qreal(0); + spp.scrollingCurve.setType(QEasingCurve::OutQuad); + spp.decelerationFactor = 1.0; + spp.minimumVelocity = qreal(0.0195); + spp.maximumVelocity = qreal(6.84); + spp.maximumClickThroughVelocity = qreal(0.0684); + spp.acceleratingFlickMaximumTime = qreal(0.125); + spp.acceleratingFlickSpeedupFactor = qreal(3.0); + spp.snapPositionRatio = qreal(0.25); + spp.snapTime = qreal(1); + spp.overshootDragResistanceFactor = qreal(1); + spp.overshootDragDistanceFactor = qreal(0.3); + spp.overshootScrollDistanceFactor = qreal(0.3); + spp.overshootScrollTime = qreal(0.5); + spp.hOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.vOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.frameRate = QScrollerProperties::Fps30; +#else + spp.mousePressEventDelay = qreal(0.25); + spp.dragStartDistance = qreal(5.0 / 1000); + spp.dragVelocitySmoothingFactor = qreal(0.02); + spp.axisLockThreshold = qreal(0); + spp.scrollingCurve.setType(QEasingCurve::OutQuad); + spp.decelerationFactor = qreal(0.125); + spp.minimumVelocity = qreal(50.0 / 1000); + spp.maximumVelocity = qreal(500.0 / 1000); + spp.maximumClickThroughVelocity = qreal(66.5 / 1000); + spp.acceleratingFlickMaximumTime = qreal(1.25); + spp.acceleratingFlickSpeedupFactor = qreal(3.0); + spp.snapPositionRatio = qreal(0.5); + spp.snapTime = qreal(0.3); + spp.overshootDragResistanceFactor = qreal(0.5); + spp.overshootDragDistanceFactor = qreal(1); + spp.overshootScrollDistanceFactor = qreal(0.5); + spp.overshootScrollTime = qreal(0.7); +# ifdef Q_WS_WIN + if (QLibrary::resolve(QLatin1String("UxTheme"), "BeginPanningFeedback")) + spp.overshootScrollTime = qreal(0.35); +# endif + spp.hOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.vOvershootPolicy = QScrollerProperties::OvershootWhenScrollable; + spp.frameRate = QScrollerProperties::Standard; +#endif + systemDefaults = new QScrollerPropertiesPrivate(spp); + } + return new QScrollerPropertiesPrivate(userDefaults ? *userDefaults : *systemDefaults); +} + +/*! + \class QScrollerProperties + \brief The QScrollerProperties class stores the settings for a QScroller. + \since 4.8 + + The QScrollerProperties class stores the parameters used by QScroller. + + The default settings are platform dependant and Qt will emulate the + platform behaviour for kinetic scrolling. + + As a convention the QScrollerProperties are in physical units (meter, + seconds) and will be converted by QScroller using the current DPI. + + \sa QScroller +*/ + +/*! + Constructs new scroller properties. +*/ +QScrollerProperties::QScrollerProperties() + : d(QScrollerPropertiesPrivate::defaults()) +{ +} + +/*! + Constructs a copy of \a sp. +*/ +QScrollerProperties::QScrollerProperties(const QScrollerProperties &sp) + : d(new QScrollerPropertiesPrivate(*sp.d)) +{ +} + +/*! + Assigns \a sp to these scroller properties and returns a reference to these scroller properties. +*/ +QScrollerProperties &QScrollerProperties::operator=(const QScrollerProperties &sp) +{ + *d.data() = *sp.d.data(); + return *this; +} + +/*! + Destroys the scroller properties. +*/ +QScrollerProperties::~QScrollerProperties() +{ +} + +/*! + Returns true if these scroller properties are equal to \a sp; otherwise returns false. +*/ +bool QScrollerProperties::operator==(const QScrollerProperties &sp) const +{ + return *d.data() == *sp.d.data(); +} + +/*! + Returns true if these scroller properties are different from \a sp; otherwise returns false. +*/ +bool QScrollerProperties::operator!=(const QScrollerProperties &sp) const +{ + return !(*d.data() == *sp.d.data()); +} + +bool QScrollerPropertiesPrivate::operator==(const QScrollerPropertiesPrivate &p) const +{ + bool same = true; + same &= (mousePressEventDelay == p.mousePressEventDelay); + same &= (dragStartDistance == p.dragStartDistance); + same &= (dragVelocitySmoothingFactor == p.dragVelocitySmoothingFactor); + same &= (axisLockThreshold == p.axisLockThreshold); + same &= (scrollingCurve == p.scrollingCurve); + same &= (decelerationFactor == p.decelerationFactor); + same &= (minimumVelocity == p.minimumVelocity); + same &= (maximumVelocity == p.maximumVelocity); + same &= (maximumClickThroughVelocity == p.maximumClickThroughVelocity); + same &= (acceleratingFlickMaximumTime == p.acceleratingFlickMaximumTime); + same &= (acceleratingFlickSpeedupFactor == p.acceleratingFlickSpeedupFactor); + same &= (snapPositionRatio == p.snapPositionRatio); + same &= (snapTime == p.snapTime); + same &= (overshootDragResistanceFactor == p.overshootDragResistanceFactor); + same &= (overshootDragDistanceFactor == p.overshootDragDistanceFactor); + same &= (overshootScrollDistanceFactor == p.overshootScrollDistanceFactor); + same &= (overshootScrollTime == p.overshootScrollTime); + same &= (hOvershootPolicy == p.hOvershootPolicy); + same &= (vOvershootPolicy == p.vOvershootPolicy); + same &= (frameRate == p.frameRate); + return same; +} + +/*! + Sets the scroller properties returned by the default constructor to \a sp. + + Use this function to override the platform default properties returned by the default + constructor. If you only want to change the scroller properties of a single scroller, then use + QScroller::setScrollerProperties() + + \note Calling this function will not change the content of already existing + QScrollerProperties objects. + + \sa unsetDefaultScrollerProperties() +*/ +void QScrollerProperties::setDefaultScrollerProperties(const QScrollerProperties &sp) +{ + if (!userDefaults) + userDefaults = new QScrollerPropertiesPrivate(*sp.d); + else + *userDefaults = *sp.d; +} + +/*! + Sets the scroller properties returned by the default constructor back to the platform default + properties. + + \sa setDefaultScrollerProperties() +*/ +void QScrollerProperties::unsetDefaultScrollerProperties() +{ + delete userDefaults; + userDefaults = 0; +} + +/*! + Query the \a metric value of the scroller properties. + + \sa setScrollMetric(), ScrollMetric +*/ +QVariant QScrollerProperties::scrollMetric(ScrollMetric metric) const +{ + switch (metric) { + case MousePressEventDelay: return d->mousePressEventDelay; + case DragStartDistance: return d->dragStartDistance; + case DragVelocitySmoothingFactor: return d->dragVelocitySmoothingFactor; + case AxisLockThreshold: return d->axisLockThreshold; + case ScrollingCurve: return d->scrollingCurve; + case DecelerationFactor: return d->decelerationFactor; + case MinimumVelocity: return d->minimumVelocity; + case MaximumVelocity: return d->maximumVelocity; + case MaximumClickThroughVelocity: return d->maximumClickThroughVelocity; + case AcceleratingFlickMaximumTime: return d->acceleratingFlickMaximumTime; + case AcceleratingFlickSpeedupFactor:return d->acceleratingFlickSpeedupFactor; + case SnapPositionRatio: return d->snapPositionRatio; + case SnapTime: return d->snapTime; + case OvershootDragResistanceFactor: return d->overshootDragResistanceFactor; + case OvershootDragDistanceFactor: return d->overshootDragDistanceFactor; + case OvershootScrollDistanceFactor: return d->overshootScrollDistanceFactor; + case OvershootScrollTime: return d->overshootScrollTime; + case HorizontalOvershootPolicy: return QVariant::fromValue(d->hOvershootPolicy); + case VerticalOvershootPolicy: return QVariant::fromValue(d->vOvershootPolicy); + case FrameRate: return QVariant::fromValue(d->frameRate); + case ScrollMetricCount: break; + } + return QVariant(); +} + +/*! + Set a specific value of the \a metric ScrollerMetric to \a value. + + \sa scrollMetric(), ScrollMetric +*/ +void QScrollerProperties::setScrollMetric(ScrollMetric metric, const QVariant &value) +{ + switch (metric) { + case MousePressEventDelay: d->mousePressEventDelay = value.toReal(); break; + case DragStartDistance: d->dragStartDistance = value.toReal(); break; + case DragVelocitySmoothingFactor: d->dragVelocitySmoothingFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case AxisLockThreshold: d->axisLockThreshold = qBound(qreal(0), value.toReal(), qreal(1)); break; + case ScrollingCurve: d->scrollingCurve = value.toEasingCurve(); break; + case DecelerationFactor: d->decelerationFactor = value.toReal(); break; + case MinimumVelocity: d->minimumVelocity = value.toReal(); break; + case MaximumVelocity: d->maximumVelocity = value.toReal(); break; + case MaximumClickThroughVelocity: d->maximumClickThroughVelocity = value.toReal(); break; + case AcceleratingFlickMaximumTime: d->acceleratingFlickMaximumTime = value.toReal(); break; + case AcceleratingFlickSpeedupFactor:d->acceleratingFlickSpeedupFactor = value.toReal(); break; + case SnapPositionRatio: d->snapPositionRatio = qBound(qreal(0), value.toReal(), qreal(1)); break; + case SnapTime: d->snapTime = value.toReal(); break; + case OvershootDragResistanceFactor: d->overshootDragResistanceFactor = value.toReal(); break; + case OvershootDragDistanceFactor: d->overshootDragDistanceFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case OvershootScrollDistanceFactor: d->overshootScrollDistanceFactor = qBound(qreal(0), value.toReal(), qreal(1)); break; + case OvershootScrollTime: d->overshootScrollTime = value.toReal(); break; + case HorizontalOvershootPolicy: d->hOvershootPolicy = value.value(); break; + case VerticalOvershootPolicy: d->vOvershootPolicy = value.value(); break; + case FrameRate: d->frameRate = value.value(); break; + case ScrollMetricCount: break; + } +} + +/*! + \enum QScrollerProperties::FrameRates + + This enum describes the available frame rates used while dragging or scrolling. + + \value Fps60 60 frames per second + \value Fps30 30 frames per second + \value Fps20 20 frames per second + \value Standard the default value is 60 frames per second (which corresponds to QAbstractAnimation). +*/ + +/*! + \enum QScrollerProperties::OvershootPolicy + + This enum describes the various modes of overshooting. + + \value OvershootWhenScrollable Overshooting is when the content is scrollable. This is the + default. + + \value OvershootAlwaysOff Overshooting is never enabled (even when the content is scrollable). + + \value OvershootAlwaysOn Overshooting is always enabled (even when the content is not + scrollable). +*/ + +/*! + \enum QScrollerProperties::ScrollMetric + + This enum contains the different scroll metric types. When not indicated otherwise the + setScrollMetric function expects a QVariant of a real value. + + See the QScroller documentation for a further explanation of the concepts behind the different + values. + + \value MousePressEventDelay This is the time a mouse press event will be delayed when starting + a flick gesture in \c{[s]}. If the gesture is triggered within that time, no mouse press or + release will be sent to the scrolled object. If it triggers after that delay the (delayed) + mouse press plus a faked release event (at global postion \c{QPoint(-QWIDGETSIZE_MAX, + -QWIDGETSIZE_MAX)} will be sent. If the gesture is canceled, then both the (delayed) mouse + press plus the real release event will be delivered. + + \value DragStartDistance This is the minimum distance the touch or mouse point needs to be + moved before the flick gesture is triggered in \c m. + + \value DragVelocitySmoothingFactor A value that describes how much new drag velocities are + included in the final scrolling velocity. This value should be in the range between \c 0 and \c + 1. Low values meaning that the last dragging velocity is not very important. + + \value AxisLockThreshold If greater than zero a scroll movement will be restricted to one axis + only if the movement is inside an angle about the axis. The threshold must be in the range \c 0 + to \c 1. + + \value ScrollingCurve The QEasingCurve used when decelerating the scrolling velocity after an + user initiated flick. Please note that this is the easing curve for the positions, \bold{not} + the velocity: the default is QEasingCurve::OutQuad, which results is a linear decrease in + velocity (1st derivative) and a constant deceleration (2nd derivative). + + \value DecelerationFactor This factor influences how long it takes the scroller to decelerate + to 0 velocity. The actual value heavily depends on the chosen ScrollingCurve, but for most + types the value should be in the range from \c 0.1 to \c 2.0 + + \value MinimumVelocity The minimum velocity that is needed after ending the touch or releasing + the mouse to start scrolling in \c{m/s}. + + \value MaximumVelocity This is the maximum velocity that can be reached in \c{m/s}. + + \value MaximumClickThroughVelocity This is the maximum allowed scroll speed for a click-through + in \c{m/s}. This means that a click on a currently (slowly) scrolling object will not only stop + the scrolling but the click event will also be delivered to the UI control - this is very + useful when using exponential-type scrolling curves. + + \value AcceleratingFlickMaximumTime This is the maximum time in \c seconds that a flick gesture + can take to be recognized as an accelerating flick. If set to zero no such gesture will be + detected. An "accelerating flick" is a flick gesture executed on an already scrolling object. + In such cases the scrolling speed is multiplied by AcceleratingFlickSpeedupFactor in order to + accelerate it. + + \value AcceleratingFlickSpeedupFactor The current speed will be multiplied by this number if an + accelerating flick is detected. Should be \c{> 1}. + + \value SnapPositionRatio This is the distance that the user must drag the area beween two snap + points in order to snap it to the next position. e.g. \c{0.33} means that the scroll must only + reach one third of the distance between two snap points to snap to the next one. The ratio must + be in the range \c 0 to \c 1. + + \value SnapTime This is the time factor for the scrolling curve. A lower value means that the + scrolling will take longer. The scrolling distance is independet of this value. + + \value OvershootDragResistanceFactor This value is the factor between the mouse dragging and + the actual scroll area movement (during overshoot). The factor must be in the range \c 0 to \c + 1. + + \value OvershootDragDistanceFactor This is the maximum distance for overshoot movements while + dragging. The actual overshoot distance will be calculated by multiplying this value with the + viewport size of the scrolled object. The factor must be in the range \c 0 to \c 1. + + \value OvershootScrollDistanceFactor This is the maximum distance for overshoot movements while + scrolling. The actual overshoot distance will be calculated by multiplying this value with the + viewport size of the scrolled object. The factor must be in the range \c 0 to \c 1. + + \value OvershootScrollTime This is the time in \c seconds that will be used to play the + complete overshoot animation. + + \value HorizontalOvershootPolicy This is the horizontal overshooting policy (see OvershootPolicy). + + \value VerticalOvershootPolicy This is the horizontal overshooting policy (see OvershootPolicy). + + \value FrameRate This is the frame rate which should be used while dragging or scrolling. + QScroller uses a QAbstractAnimation timer internally to sync all scrolling operations to other + animations that might be active at the same time. If the Standard value of 60 frames per + second is too fast for your use case, you can lower the frames per second with this setting + (while still being in-sync with QAbstractAnimation). Please note that only the values of the + FrameRates enum are allowed here. + + \value ScrollMetricCount This is just used when enumerating the metrics. It is always the last + entry. +*/ + +QT_END_NAMESPACE diff --git a/src/gui/util/qscrollerproperties.h b/src/gui/util/qscrollerproperties.h new file mode 100644 index 0000000..5284876 --- /dev/null +++ b/src/gui/util/qscrollerproperties.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCROLLERPROPERTIES_H +#define QSCROLLERPROPERTIES_H + +#include +#include +#include + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Gui) + +class QScroller; +class QScrollerPrivate; +class QScrollerPropertiesPrivate; + +class Q_GUI_EXPORT QScrollerProperties +{ +public: + QScrollerProperties(); + QScrollerProperties(const QScrollerProperties &sp); + QScrollerProperties &operator=(const QScrollerProperties &sp); + virtual ~QScrollerProperties(); + + bool operator==(const QScrollerProperties &sp) const; + bool operator!=(const QScrollerProperties &sp) const; + + static void setDefaultScrollerProperties(const QScrollerProperties &sp); + static void unsetDefaultScrollerProperties(); + + enum OvershootPolicy + { + OvershootWhenScrollable, + OvershootAlwaysOff, + OvershootAlwaysOn + }; + + enum FrameRates { + Standard, + Fps60, + Fps30, + Fps20 + }; + + enum ScrollMetric + { + MousePressEventDelay, // qreal [s] + DragStartDistance, // qreal [m] + DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF) + AxisLockThreshold, // qreal [0..1] atan(|min(dx,dy)|/|max(dx,dy)|) + + ScrollingCurve, // QEasingCurve + DecelerationFactor, // slope of the curve + + MinimumVelocity, // qreal [m/s] + MaximumVelocity, // qreal [m/s] + MaximumClickThroughVelocity, // qreal [m/s] + + AcceleratingFlickMaximumTime, // qreal [s] + AcceleratingFlickSpeedupFactor, // qreal [1..] + + SnapPositionRatio, // qreal [0..1] + SnapTime, // qreal [s] + + OvershootDragResistanceFactor, // qreal [0..1] + OvershootDragDistanceFactor, // qreal [0..1] + OvershootScrollDistanceFactor, // qreal [0..1] + OvershootScrollTime, // qreal [s] + + HorizontalOvershootPolicy, // enum OvershootPolicy + VerticalOvershootPolicy, // enum OvershootPolicy + FrameRate, // enum FrameRates + + ScrollMetricCount + }; + + QVariant scrollMetric(ScrollMetric metric) const; + void setScrollMetric(ScrollMetric metric, const QVariant &value); + +protected: + QScopedPointer d; + +private: + QScrollerProperties(QScrollerPropertiesPrivate &dd); + + friend class QScrollerPropertiesPrivate; + friend class QScroller; + friend class QScrollerPrivate; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QScrollerProperties::OvershootPolicy); +Q_DECLARE_METATYPE(QScrollerProperties::FrameRates); + +QT_END_HEADER + +#endif // QSCROLLERPROPERTIES_H diff --git a/src/gui/util/qscrollerproperties_p.h b/src/gui/util/qscrollerproperties_p.h new file mode 100644 index 0000000..093f615 --- /dev/null +++ b/src/gui/util/qscrollerproperties_p.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSCROLLERPROPERTIES_P_H +#define QSCROLLERPROPERTIES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QScrollerPropertiesPrivate +{ +public: + static QScrollerPropertiesPrivate *defaults(); + + bool operator==(const QScrollerPropertiesPrivate &) const; + + qreal mousePressEventDelay; + qreal dragStartDistance; + qreal dragVelocitySmoothingFactor; + qreal axisLockThreshold; + QEasingCurve scrollingCurve; + qreal decelerationFactor; + qreal minimumVelocity; + qreal maximumVelocity; + qreal maximumClickThroughVelocity; + qreal acceleratingFlickMaximumTime; + qreal acceleratingFlickSpeedupFactor; + qreal snapPositionRatio; + qreal snapTime; + qreal overshootDragResistanceFactor; + qreal overshootDragDistanceFactor; + qreal overshootScrollDistanceFactor; + qreal overshootScrollTime; + QScrollerProperties::OvershootPolicy hOvershootPolicy; + QScrollerProperties::OvershootPolicy vOvershootPolicy; + QScrollerProperties::FrameRates frameRate; +}; + +QT_END_NAMESPACE + +#endif // QSCROLLERPROPERTIES_P_H + diff --git a/src/gui/util/util.pri b/src/gui/util/util.pri index f125f82..2814a2d 100644 --- a/src/gui/util/util.pri +++ b/src/gui/util/util.pri @@ -6,6 +6,11 @@ HEADERS += \ util/qcompleter_p.h \ util/qdesktopservices.h \ util/qsystemtrayicon_p.h \ + util/qscroller.h \ + util/qscroller_p.h \ + util/qscrollerproperties.h \ + util/qscrollerproperties_p.h \ + util/qflickgesture_p.h \ util/qundogroup.h \ util/qundostack.h \ util/qundostack_p.h \ @@ -15,6 +20,9 @@ SOURCES += \ util/qsystemtrayicon.cpp \ util/qcompleter.cpp \ util/qdesktopservices.cpp \ + util/qscroller.cpp \ + util/qscrollerproperties.cpp \ + util/qflickgesture.cpp \ util/qundogroup.cpp \ util/qundostack.cpp \ util/qundoview.cpp @@ -57,3 +65,7 @@ symbian { DEFINES += USE_SCHEMEHANDLER } } + +macx { + OBJECTIVE_SOURCES += util/qscroller_mac.mm +} diff --git a/src/gui/widgets/qabstractscrollarea.cpp b/src/gui/widgets/qabstractscrollarea.cpp index 30ce23b..13942ea 100644 --- a/src/gui/widgets/qabstractscrollarea.cpp +++ b/src/gui/widgets/qabstractscrollarea.cpp @@ -53,6 +53,8 @@ #include "qpainter.h" #include "qmargins.h" +#include + #include "qabstractscrollarea_p.h" #include @@ -62,6 +64,10 @@ #include #include #endif +#ifdef Q_WS_WIN +# include +# include +#endif QT_BEGIN_NAMESPACE @@ -295,9 +301,14 @@ void QAbstractScrollAreaPrivate::init() q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layoutChildren(); #ifndef Q_WS_MAC -#ifndef QT_NO_GESTURES +# ifndef QT_NO_GESTURES viewport->grabGesture(Qt::PanGesture); +# endif #endif +#ifdef Q_WS_MAEMO_5 +# ifndef QT_NO_GESTURES + // viewport->grabGesture(Qt::TouchFlickGesture); +# endif #endif } @@ -552,6 +563,11 @@ void QAbstractScrollArea::setViewport(QWidget *widget) d->viewport->grabGesture(Qt::PanGesture); #endif #endif +#ifdef Q_WS_MAEMO_5 +#ifndef QT_NO_GESTURES +// d->viewport->grabGesture(Qt::TouchFlickGesture); +#endif +#endif d->layoutChildren(); if (isVisible()) d->viewport->show(); @@ -986,6 +1002,66 @@ bool QAbstractScrollArea::event(QEvent *e) return false; } #endif // QT_NO_GESTURES + case QEvent::ScrollPrepare: + { + QScrollPrepareEvent *se = static_cast(e); + if( d->canStartScrollingAt(se->startPos().toPoint()) ) { + QScrollBar *hBar = horizontalScrollBar(); + QScrollBar *vBar = verticalScrollBar(); + + se->setViewportSize( QSizeF(viewport()->size()) ); + se->setContentPosRange( QRectF(0, 0, hBar->maximum(), vBar->maximum()) ); + se->setContentPos( QPointF(hBar->value(), vBar->value()) ); + se->accept(); + return true; + } + return false; + } + case QEvent::Scroll: + { + QScrollEvent *se = static_cast(e); + + QScrollBar *hBar = horizontalScrollBar(); + QScrollBar *vBar = verticalScrollBar(); + hBar->setValue(se->contentPos().x()); + vBar->setValue(se->contentPos().y()); + +#ifdef Q_WS_WIN + typedef BOOL (*PtrBeginPanningFeedback)(HWND); + typedef BOOL (*PtrUpdatePanningFeedback)(HWND, LONG, LONG, BOOL); + typedef BOOL (*PtrEndPanningFeedback)(HWND, BOOL); + + static PtrBeginPanningFeedback ptrBeginPanningFeedback = 0; + static PtrUpdatePanningFeedback ptrUpdatePanningFeedback = 0; + static PtrEndPanningFeedback ptrEndPanningFeedback = 0; + + if (!ptrBeginPanningFeedback) + ptrBeginPanningFeedback = (PtrBeginPanningFeedback) QLibrary::resolve(QLatin1String("UxTheme"), "BeginPanningFeedback"); + if (!ptrUpdatePanningFeedback) + ptrUpdatePanningFeedback = (PtrUpdatePanningFeedback) QLibrary::resolve(QLatin1String("UxTheme"), "UpdatePanningFeedback"); + if (!ptrEndPanningFeedback) + ptrEndPanningFeedback = (PtrEndPanningFeedback) QLibrary::resolve(QLatin1String("UxTheme"), "EndPanningFeedback"); + + if (ptrBeginPanningFeedback && ptrUpdatePanningFeedback && ptrEndPanningFeedback) { + WId wid = window()->winId(); + + if (!se->overshootDistance().isNull() && d->overshoot.isNull()) + ptrBeginPanningFeedback(wid); + if (!se->overshootDistance().isNull()) + ptrUpdatePanningFeedback(wid, -se->overshootDistance().x(), -se->overshootDistance().y(), false); + if (se->overshootDistance().isNull() && !d->overshoot.isNull()) + ptrEndPanningFeedback(wid, true); + } else +#endif + { + QPoint delta = d->overshoot - se->overshootDistance().toPoint(); + if (!delta.isNull()) + viewport()->move(viewport()->pos() + delta); + } + d->overshoot = se->overshootDistance().toPoint(); + + return true; + } case QEvent::StyleChange: case QEvent::LayoutDirectionChange: case QEvent::ApplicationLayoutDirectionChange: @@ -1047,6 +1123,9 @@ bool QAbstractScrollArea::viewportEvent(QEvent *e) case QEvent::GestureOverride: return event(e); #endif + case QEvent::ScrollPrepare: + case QEvent::Scroll: + return event(e); default: break; } @@ -1303,6 +1382,30 @@ void QAbstractScrollArea::scrollContentsBy(int, int) viewport()->update(); } +bool QAbstractScrollAreaPrivate::canStartScrollingAt( const QPoint &startPos ) +{ + Q_Q(QAbstractScrollArea); + + // don't start scrolling when a drag mode has been set. + // don't start scrolling on a movable item. + if (QGraphicsView *view = qobject_cast(q)) { + if (view->dragMode() != QGraphicsView::NoDrag) + return false; + + QGraphicsItem *childItem = view->itemAt(startPos); + + if (childItem && (childItem->flags() & QGraphicsItem::ItemIsMovable)) + return false; + } + + // don't start scrolling on a QAbstractSlider + if (qobject_cast(q->viewport()->childAt(startPos))) { + return false; + } + + return true; +} + void QAbstractScrollAreaPrivate::_q_hslide(int x) { Q_Q(QAbstractScrollArea); diff --git a/src/gui/widgets/qabstractscrollarea_p.h b/src/gui/widgets/qabstractscrollarea_p.h index 9a0d66f..8dbb3e4 100644 --- a/src/gui/widgets/qabstractscrollarea_p.h +++ b/src/gui/widgets/qabstractscrollarea_p.h @@ -84,11 +84,13 @@ public: int left, top, right, bottom; // viewport margin int xoffset, yoffset; + QPoint overshoot; void init(); void layoutChildren(); // ### Fix for 4.4, talk to Bjoern E or Girish. virtual void scrollBarPolicyChanged(Qt::Orientation, Qt::ScrollBarPolicy) {} + bool canStartScrollingAt( const QPoint &startPos ); void _q_hslide(int); void _q_vslide(int); diff --git a/tests/auto/gui.pro b/tests/auto/gui.pro index c94272c..48b8cec 100644 --- a/tests/auto/gui.pro +++ b/tests/auto/gui.pro @@ -146,6 +146,7 @@ SUBDIRS=\ qregion \ qscrollarea \ qscrollbar \ + qscroller \ qsharedpointer_and_qwidget \ qshortcut \ qsidebar \ diff --git a/tests/auto/qscroller/qscroller.pro b/tests/auto/qscroller/qscroller.pro new file mode 100644 index 0000000..845dcb9 --- /dev/null +++ b/tests/auto/qscroller/qscroller.pro @@ -0,0 +1,3 @@ +load(qttest_p4) + +SOURCES += tst_qscroller.cpp diff --git a/tests/auto/qscroller/tst_qscroller.cpp b/tests/auto/qscroller/tst_qscroller.cpp new file mode 100644 index 0000000..61d3980 --- /dev/null +++ b/tests/auto/qscroller/tst_qscroller.cpp @@ -0,0 +1,529 @@ +/**************************************************************************** +** +** 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 $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 + +class tst_QScrollerWidget : public QWidget +{ +public: + tst_QScrollerWidget() + : QWidget() + { + reset(); + } + + void reset() + { + receivedPrepare = false; + receivedScroll = false; + receivedFirst = false; + receivedLast = false; + receivedOvershoot = false; + } + + bool event(QEvent *e) + { + switch (e->type()) { + case QEvent::Gesture: + e->setAccepted(false); // better reject the event or QGestureManager will make trouble + return false; + + case QEvent::ScrollPrepare: + { + receivedPrepare = true; + QScrollPrepareEvent *se = static_cast(e); + se->setViewportSize(QSizeF(100,100)); + se->setContentPosRange(scrollArea); + se->setContentPos(scrollPosition); + se->accept(); + return true; + } + + case QEvent::Scroll: + { + receivedScroll = true; + QScrollEvent *se = static_cast(e); + // qDebug() << "Scroll for"<scrollPos()<<"ov"<overshoot()<<"first"<isFirst()<<"last"<isLast(); + + if (se->scrollState() == QScrollEvent::ScrollStarted) + receivedFirst = true; + if (se->scrollState() == QScrollEvent::ScrollFinished) + receivedLast = true; + + currentPos = se->contentPos(); + overshoot = se->overshootDistance(); + if (!qFuzzyCompare( overshoot.x() + 1.0, 1.0 ) || + !qFuzzyCompare( overshoot.y() + 1.0, 1.0 )) + receivedOvershoot = true; + return true; + } + + default: + return QObject::event(e); + } + } + + + QRectF scrollArea; + QPointF scrollPosition; + + bool receivedPrepare; + bool receivedScroll; + bool receivedFirst; + bool receivedLast; + bool receivedOvershoot; + + QPointF currentPos; + QPointF overshoot; +}; + + +class tst_QScroller : public QObject +{ + Q_OBJECT +public: + tst_QScroller() { } + ~tst_QScroller() { } + +private: + void kineticScroll( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd); + void kineticScrollNoTest( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd); + +private slots: + void staticScrollers(); + void scrollerProperties(); + void scrollTo(); + void scroll(); + void overshoot(); +}; + +/*! \internal + Generates touchBegin, touchUpdate and touchEnd events to trigger scrolling. + Tests some in between states but does not wait until scrolling is finished. +*/ +void tst_QScroller::kineticScroll( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd) +{ + sw->scrollPosition = from; + sw->currentPos= from; + + QScroller *s1 = QScroller::scroller(sw); + QCOMPARE( s1->state(), QScroller::Inactive ); + + QScrollerProperties sp1 = QScroller::scroller(sw)->scrollerProperties(); + int fps = 60; + + QTouchEvent::TouchPoint rawTouchPoint; + rawTouchPoint.setId(0); + + // send the touch begin event + QTouchEvent::TouchPoint touchPoint(0); + touchPoint.setState(Qt::TouchPointPressed); + touchPoint.setPos(touchStart); + touchPoint.setScenePos(touchStart); + touchPoint.setScreenPos(touchStart); + QTouchEvent touchEvent1(QEvent::TouchBegin, + QTouchEvent::TouchScreen, + Qt::NoModifier, + Qt::TouchPointPressed, + (QList() << touchPoint)); + QApplication::sendEvent(sw, &touchEvent1); + + QCOMPARE( s1->state(), QScroller::Pressed ); + + // send the touch update far enough to trigger a scroll + QTest::qWait(200); // we need to wait a little or else the speed would be infinite. now we have around 500 pixel per second. + touchPoint.setPos(touchUpdate); + touchPoint.setScenePos(touchUpdate); + touchPoint.setScreenPos(touchUpdate); + QTouchEvent touchEvent2(QEvent::TouchUpdate, + QTouchEvent::TouchScreen, + Qt::NoModifier, + Qt::TouchPointMoved, + (QList() << touchPoint)); + QApplication::sendEvent(sw, &touchEvent2); + + QCOMPARE( s1->state(), QScroller::Dragging ); + QCOMPARE( sw->receivedPrepare, true ); + + + QTest::qWait(1000 / fps * 2); // wait until the first scroll move + QCOMPARE( sw->receivedFirst, true ); + QCOMPARE( sw->receivedScroll, true ); + QCOMPARE( sw->receivedOvershoot, false ); + + // note that the scrolling goes in a different direction than the mouse move + QPoint calculatedPos = from.toPoint() - touchUpdate - touchStart; + QVERIFY(qAbs(sw->currentPos.x() - calculatedPos.x()) < 1.0); + QVERIFY(qAbs(sw->currentPos.y() - calculatedPos.y()) < 1.0); + + // send the touch end + touchPoint.setPos(touchEnd); + touchPoint.setScenePos(touchEnd); + touchPoint.setScreenPos(touchEnd); + QTouchEvent touchEvent5(QEvent::TouchEnd, + QTouchEvent::TouchScreen, + Qt::NoModifier, + Qt::TouchPointReleased, + (QList() << touchPoint)); + QApplication::sendEvent(sw, &touchEvent5); +} + +/*! \internal + Generates touchBegin, touchUpdate and touchEnd events to trigger scrolling. + This function does not have any in between tests, it does not expect the scroller to actually scroll. +*/ +void tst_QScroller::kineticScrollNoTest( tst_QScrollerWidget *sw, QPointF from, QPoint touchStart, QPoint touchUpdate, QPoint touchEnd) +{ + sw->scrollPosition = from; + sw->currentPos = from; + + QScroller *s1 = QScroller::scroller(sw); + QCOMPARE( s1->state(), QScroller::Inactive ); + + QScrollerProperties sp1 = s1->scrollerProperties(); + int fps = 60; + + QTouchEvent::TouchPoint rawTouchPoint; + rawTouchPoint.setId(0); + + // send the touch begin event + QTouchEvent::TouchPoint touchPoint(0); + touchPoint.setState(Qt::TouchPointPressed); + touchPoint.setPos(touchStart); + touchPoint.setScenePos(touchStart); + touchPoint.setScreenPos(touchStart); + QTouchEvent touchEvent1(QEvent::TouchBegin, + QTouchEvent::TouchScreen, + Qt::NoModifier, + Qt::TouchPointPressed, + (QList() << touchPoint)); + QApplication::sendEvent(sw, &touchEvent1); + + // send the touch update far enough to trigger a scroll + QTest::qWait(200); // we need to wait a little or else the speed would be infinite. now we have around 500 pixel per second. + touchPoint.setPos(touchUpdate); + touchPoint.setScenePos(touchUpdate); + touchPoint.setScreenPos(touchUpdate); + QTouchEvent touchEvent2(QEvent::TouchUpdate, + QTouchEvent::TouchScreen, + Qt::NoModifier, + Qt::TouchPointMoved, + (QList() << touchPoint)); + QApplication::sendEvent(sw, &touchEvent2); + + QTest::qWait(1000 / fps * 2); // wait until the first scroll move + + // send the touch end + touchPoint.setPos(touchEnd); + touchPoint.setScenePos(touchEnd); + touchPoint.setScreenPos(touchEnd); + QTouchEvent touchEvent5(QEvent::TouchEnd, + QTouchEvent::TouchScreen, + Qt::NoModifier, + Qt::TouchPointReleased, + (QList() << touchPoint)); + QApplication::sendEvent(sw, &touchEvent5); +} + + +void tst_QScroller::staticScrollers() +{ + // scrollers + { + QObject *o1 = new QObject(this); + QObject *o2 = new QObject(this); + + // get scroller for object + QScroller *s1 = QScroller::scroller(o1); + QScroller *s2 = QScroller::scroller(o2); + + QVERIFY(s1); + QVERIFY(s2); + QVERIFY(s1 != s2); + + QVERIFY(!QScroller::scroller(static_cast(0))); + QCOMPARE(QScroller::scroller(o1), s1); + + delete o1; + delete o2; + } + + // the same for properties + { + QObject *o1 = new QObject(this); + QObject *o2 = new QObject(this); + + // get scroller for object + QScrollerProperties sp1 = QScroller::scroller(o1)->scrollerProperties(); + QScrollerProperties sp2 = QScroller::scroller(o2)->scrollerProperties(); + + // default properties should be the same + QVERIFY(sp1 == sp2); + + QCOMPARE(QScroller::scroller(o1)->scrollerProperties(), sp1); + + delete o1; + delete o2; + } +} + +void tst_QScroller::scrollerProperties() +{ + QObject *o1 = new QObject(this); + QScrollerProperties sp1 = QScroller::scroller(o1)->scrollerProperties(); + + QScrollerProperties::ScrollMetric metrics[] = + { + QScrollerProperties::MousePressEventDelay, // qreal [s] + QScrollerProperties::DragStartDistance, // qreal [m] + QScrollerProperties::DragVelocitySmoothingFactor, // qreal [0..1/s] (complex calculation involving time) v = v_new* DASF + v_old * (1-DASF) + QScrollerProperties::AxisLockThreshold, // qreal [0..1] atan(|min(dx,dy)|/|max(dx,dy)|) + + QScrollerProperties::DecelerationFactor, // slope of the curve + + QScrollerProperties::MinimumVelocity, // qreal [m/s] + QScrollerProperties::MaximumVelocity, // qreal [m/s] + QScrollerProperties::MaximumClickThroughVelocity, // qreal [m/s] + + QScrollerProperties::AcceleratingFlickMaximumTime, // qreal [s] + QScrollerProperties::AcceleratingFlickSpeedupFactor, // qreal [1..] + + QScrollerProperties::SnapPositionRatio, // qreal [0..1] + QScrollerProperties::SnapTime, // qreal [s] + + QScrollerProperties::OvershootDragResistanceFactor, // qreal [0..1] + QScrollerProperties::OvershootDragDistanceFactor, // qreal [0..1] + QScrollerProperties::OvershootScrollDistanceFactor, // qreal [0..1] + QScrollerProperties::OvershootScrollTime, // qreal [s] + }; + + for (unsigned int i = 0; i < sizeof(metrics) / sizeof(metrics[0]); i++) { + sp1.setScrollMetric(metrics[i], 0.9); + QCOMPARE(sp1.scrollMetric(metrics[i]).toDouble(), 0.9); + } + sp1.setScrollMetric(QScrollerProperties::ScrollingCurve, QEasingCurve(QEasingCurve::OutQuart)); + QCOMPARE(sp1.scrollMetric(QScrollerProperties::ScrollingCurve).toEasingCurve().type(), QEasingCurve::OutQuart); + + sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); + QCOMPARE(sp1.scrollMetric(QScrollerProperties::HorizontalOvershootPolicy).value(), QScrollerProperties::OvershootAlwaysOff); + + sp1.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOn)); + QCOMPARE(sp1.scrollMetric(QScrollerProperties::VerticalOvershootPolicy).value(), QScrollerProperties::OvershootAlwaysOn); + + sp1.setScrollMetric(QScrollerProperties::FrameRate, QVariant::fromValue(QScrollerProperties::Fps20)); + QCOMPARE(sp1.scrollMetric(QScrollerProperties::FrameRate).value(), QScrollerProperties::Fps20); +} + +void tst_QScroller::scrollTo() +{ + { + tst_QScrollerWidget *sw = new tst_QScrollerWidget(); + sw->scrollArea = QRectF( 0, 0, 1000, 1000 ); + sw->scrollPosition = QPointF( 500, 500 ); + + QScroller *s1 = QScroller::scroller(sw); + QCOMPARE( s1->state(), QScroller::Inactive ); + + // a normal scroll + s1->scrollTo(QPointF(100,100), 100); + QTest::qWait(200); + + QCOMPARE( sw->receivedPrepare, true ); + QCOMPARE( sw->receivedScroll, true ); + QCOMPARE( sw->receivedFirst, true ); + QCOMPARE( sw->receivedLast, true ); + QCOMPARE( sw->receivedOvershoot, false ); + QVERIFY(qFuzzyCompare( sw->currentPos.x(), 100 )); + QVERIFY(qFuzzyCompare( sw->currentPos.y(), 100 )); + + delete sw; + } +} + +void tst_QScroller::scroll() +{ +#ifndef QT_NO_GESTURES + + // -- good case. normal scroll + + tst_QScrollerWidget *sw = new tst_QScrollerWidget(); + sw->scrollArea = QRectF(0, 0, 1000, 1000); + QScroller::grabGesture(sw, QScroller::TouchGesture); + sw->setGeometry(100, 100, 400, 300); + + QScroller *s1 = QScroller::scroller(sw); + kineticScroll(sw, QPointF(500, 500), QPoint(0, 0), QPoint(100, 100), QPoint(200, 200)); + // now we should be scrolling + QCOMPARE( s1->state(), QScroller::Scrolling ); + + // wait until finished, check that no further first scroll is send + sw->receivedFirst = false; + sw->receivedScroll = false; + while (s1->state() == QScroller::Scrolling) + QTest::qWait(100); + + QCOMPARE( sw->receivedFirst, false ); + QCOMPARE( sw->receivedScroll, true ); + QCOMPARE( sw->receivedLast, true ); + QVERIFY(sw->currentPos.x() < 400); + QVERIFY(sw->currentPos.y() < 400); + + // -- try to scroll when nothing to scroll + + sw->reset(); + sw->scrollArea = QRectF(0, 0, 0, 1000); + kineticScrollNoTest(sw, QPointF(0, 500), QPoint(0, 0), QPoint(100, 0), QPoint(200, 0)); + + while (s1->state() != QScroller::Inactive) + QTest::qWait(20); + + QCOMPARE(sw->currentPos.x(), 0.0); + QCOMPARE(sw->currentPos.y(), 500.0); + + delete sw; +#endif +} + +void tst_QScroller::overshoot() +{ +#ifndef QT_NO_GESTURES + tst_QScrollerWidget *sw = new tst_QScrollerWidget(); + sw->scrollArea = QRectF(0, 0, 1000, 1000); + QScroller::grabGesture(sw, QScroller::TouchGesture); + sw->setGeometry(100, 100, 400, 300); + + QScroller *s1 = QScroller::scroller(sw); + QScrollerProperties sp1 = s1->scrollerProperties(); + + sp1.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, 0.5); + sp1.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, 0.2); + sp1.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.2); + + // -- try to scroll with overshoot (when scrollable good case) + + sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootWhenScrollable)); + s1->setScrollerProperties(sp1); + kineticScrollNoTest(sw, QPointF(500, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); + + while (s1->state() != QScroller::Inactive) + QTest::qWait(20); + + //qDebug() << "Overshoot fuzzy: "<currentPos; + QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); + QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); + QCOMPARE( sw->receivedOvershoot, true ); + + // -- try to scroll with overshoot (when scrollable bad case) + sw->reset(); + sw->scrollArea = QRectF(0, 0, 0, 1000); + + sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootWhenScrollable)); + s1->setScrollerProperties(sp1); + kineticScrollNoTest(sw, QPointF(0, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); + + while (s1->state() != QScroller::Inactive) + QTest::qWait(20); + + //qDebug() << "Overshoot fuzzy: "<currentPos; + QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); + QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); + QCOMPARE( sw->receivedOvershoot, false ); + + // -- try to scroll with overshoot (always on) + sw->reset(); + sw->scrollArea = QRectF(0, 0, 0, 1000); + + sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOn)); + s1->setScrollerProperties(sp1); + kineticScrollNoTest(sw, QPointF(0, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); + + while (s1->state() != QScroller::Inactive) + QTest::qWait(20); + + //qDebug() << "Overshoot fuzzy: "<currentPos; + + QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); + QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); + QCOMPARE( sw->receivedOvershoot, true ); + + // -- try to scroll with overshoot (always off) + sw->reset(); + sw->scrollArea = QRectF(0, 0, 1000, 1000); + + sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOff)); + s1->setScrollerProperties(sp1); + kineticScrollNoTest(sw, QPointF(500, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); + + while (s1->state() != QScroller::Inactive) + QTest::qWait(20); + + QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); + QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); + QCOMPARE( sw->receivedOvershoot, false ); + + // -- try to scroll with overshoot (always on but max overshoot = 0) + sp1.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor, 0.0); + sp1.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.0); + sw->reset(); + sw->scrollArea = QRectF(0, 0, 1000, 1000); + + sp1.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QVariant::fromValue(QScrollerProperties::OvershootAlwaysOn)); + s1->setScrollerProperties(sp1); + kineticScrollNoTest(sw, QPointF(500, 500), QPoint(0, 0), QPoint(400, 0), QPoint(490, 0)); + + while (s1->state() != QScroller::Inactive) + QTest::qWait(20); + + QVERIFY(qFuzzyCompare( sw->currentPos.x(), 0 )); + QVERIFY(qFuzzyCompare( sw->currentPos.y(), 500 )); + QCOMPARE( sw->receivedOvershoot, false ); + + + delete sw; +#endif +} + + +QTEST_MAIN(tst_QScroller) + +#include "tst_qscroller.moc" diff --git a/tools/qml/texteditautoresizer_maemo5.h b/tools/qml/texteditautoresizer_maemo5.h index bb5567a..fd35ca5 100644 --- a/tools/qml/texteditautoresizer_maemo5.h +++ b/tools/qml/texteditautoresizer_maemo5.h @@ -41,7 +41,7 @@ #include #include -#include +#include #include #include @@ -102,11 +102,11 @@ void TextEditAutoResizer::textEditChanged() QPoint scrollto = area->widget()->mapFrom(edit, cursor.center()); QPoint margin(10 + cursor.width(), 2 * cursor.height()); - if (QAbstractKineticScroller *scroller = area->property("kineticScroller").value()) { - scroller->ensureVisible(scrollto, margin.x(), margin.y()); - } else { - area->ensureVisible(scrollto.x(), scrollto.y(), margin.x(), margin.y()); - } +#ifdef Q_WS_MAEMO_5 + QScroller::scroller(area)->ensureVisible(scrollto, margin.x(), margin.y()); +#else + area->ensureVisible(scrollto.x(), scrollto.y(), margin.x(), margin.y()); +#endif } } -- cgit v0.12 From 2770b1277744bb676e96e4ae8c89acd645ec895d Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Thu, 2 Dec 2010 01:12:33 +0100 Subject: Fix code style issues in QScroller --- src/gui/util/qscroller.cpp | 4 ++-- src/gui/util/qscrollerproperties.cpp | 4 ++-- src/gui/widgets/qabstractscrollarea.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/util/qscroller.cpp b/src/gui/util/qscroller.cpp index d6b7aaf..5aa2b71 100644 --- a/src/gui/util/qscroller.cpp +++ b/src/gui/util/qscroller.cpp @@ -422,7 +422,7 @@ Qt::GestureType QScroller::grabGesture(QObject *target, ScrollerGestureType scro if (scrollGestureType == TouchGesture) widget->setAttribute(Qt::WA_AcceptTouchEvents); - } else if(QGraphicsObject *go = qobject_cast(target)) { + } else if (QGraphicsObject *go = qobject_cast(target)) { if (scrollGestureType == TouchGesture) go->setAcceptTouchEvents(true); go->grabGesture(sp->recognizerType); @@ -460,7 +460,7 @@ void QScroller::ungrabGesture(QObject *target) QWidget *widget = static_cast(target); widget->ungrabGesture(sp->recognizerType); - } else if(QGraphicsObject *go = qobject_cast(target)) { + } else if (QGraphicsObject *go = qobject_cast(target)) { go->ungrabGesture(sp->recognizerType); } diff --git a/src/gui/util/qscrollerproperties.cpp b/src/gui/util/qscrollerproperties.cpp index 4fba489..d21f131 100644 --- a/src/gui/util/qscrollerproperties.cpp +++ b/src/gui/util/qscrollerproperties.cpp @@ -262,7 +262,7 @@ QVariant QScrollerProperties::scrollMetric(ScrollMetric metric) const case FrameRate: return QVariant::fromValue(d->frameRate); case ScrollMetricCount: break; } - return QVariant(); + return QVariant(); } /*! @@ -404,7 +404,7 @@ void QScrollerProperties::setScrollMetric(ScrollMetric metric, const QVariant &v \value VerticalOvershootPolicy This is the horizontal overshooting policy (see OvershootPolicy). - \value FrameRate This is the frame rate which should be used while dragging or scrolling. + \value FrameRate This is the frame rate which should be used while dragging or scrolling. QScroller uses a QAbstractAnimation timer internally to sync all scrolling operations to other animations that might be active at the same time. If the Standard value of 60 frames per second is too fast for your use case, you can lower the frames per second with this setting diff --git a/src/gui/widgets/qabstractscrollarea.cpp b/src/gui/widgets/qabstractscrollarea.cpp index 13942ea..030f544 100644 --- a/src/gui/widgets/qabstractscrollarea.cpp +++ b/src/gui/widgets/qabstractscrollarea.cpp @@ -1005,13 +1005,13 @@ bool QAbstractScrollArea::event(QEvent *e) case QEvent::ScrollPrepare: { QScrollPrepareEvent *se = static_cast(e); - if( d->canStartScrollingAt(se->startPos().toPoint()) ) { + if (d->canStartScrollingAt(se->startPos().toPoint())) { QScrollBar *hBar = horizontalScrollBar(); QScrollBar *vBar = verticalScrollBar(); - se->setViewportSize( QSizeF(viewport()->size()) ); - se->setContentPosRange( QRectF(0, 0, hBar->maximum(), vBar->maximum()) ); - se->setContentPos( QPointF(hBar->value(), vBar->value()) ); + se->setViewportSize(QSizeF(viewport()->size())); + se->setContentPosRange(QRectF(0, 0, hBar->maximum(), vBar->maximum())); + se->setContentPos(QPointF(hBar->value(), vBar->value())); se->accept(); return true; } -- cgit v0.12 From 02e1f4e83dc8e3c4ab957095167b3d34c51ba3c1 Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Thu, 2 Dec 2010 10:01:36 +0100 Subject: Fix compile on QWS --- src/gui/util/qflickgesture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/util/qflickgesture.cpp b/src/gui/util/qflickgesture.cpp index fa04754..eb0cc8d 100644 --- a/src/gui/util/qflickgesture.cpp +++ b/src/gui/util/qflickgesture.cpp @@ -40,7 +40,7 @@ ****************************************************************************/ #include "qgesture.h" -#include "qcoreapplication.h" +#include "qapplication.h" #include "qevent.h" #include "qwidget.h" #include "qgraphicsitem.h" -- cgit v0.12 From fdf3be5b6b5db75833e0a7e9a90445ddd794fe4d Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Thu, 2 Dec 2010 15:39:29 +0100 Subject: Simple fixes for CI gate --- examples/scroller/plot/main.cpp | 40 ++++++++++++++++++++++++++++++++++++++ src/gui/util/qscroller.cpp | 2 +- src/gui/util/qscroller.h | 2 +- src/gui/util/qscrollerproperties.h | 4 ++-- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/examples/scroller/plot/main.cpp b/examples/scroller/plot/main.cpp index a4e2add..acf83ee 100644 --- a/examples/scroller/plot/main.cpp +++ b/examples/scroller/plot/main.cpp @@ -1,3 +1,43 @@ +/**************************************************************************** +** +** 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 diff --git a/src/gui/util/qscroller.cpp b/src/gui/util/qscroller.cpp index 5aa2b71..b7b5903 100644 --- a/src/gui/util/qscroller.cpp +++ b/src/gui/util/qscroller.cpp @@ -367,7 +367,7 @@ void QScroller::setScrollerProperties(const QScrollerProperties &sp) emit scrollerPropertiesChanged(sp); // we need to force the recalculation here, since the overshootPolicy may have changed and - // exisiting segments may include an overshoot animation. + // existing segments may include an overshoot animation. d->recalcScrollingSegments(true); } } diff --git a/src/gui/util/qscroller.h b/src/gui/util/qscroller.h index 34e1125..7d7e1ca 100644 --- a/src/gui/util/qscroller.h +++ b/src/gui/util/qscroller.h @@ -71,7 +71,7 @@ public: Inactive, Pressed, Dragging, - Scrolling, + Scrolling }; enum ScrollerGestureType diff --git a/src/gui/util/qscrollerproperties.h b/src/gui/util/qscrollerproperties.h index 5284876..e292d8d 100644 --- a/src/gui/util/qscrollerproperties.h +++ b/src/gui/util/qscrollerproperties.h @@ -132,8 +132,8 @@ private: QT_END_NAMESPACE -Q_DECLARE_METATYPE(QScrollerProperties::OvershootPolicy); -Q_DECLARE_METATYPE(QScrollerProperties::FrameRates); +Q_DECLARE_METATYPE(QScrollerProperties::OvershootPolicy) +Q_DECLARE_METATYPE(QScrollerProperties::FrameRates) QT_END_HEADER -- cgit v0.12 From d1f9a534da288884f443a975f428b0cfe0a7b29b Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Fri, 3 Dec 2010 14:48:53 +0100 Subject: Touch events do not work on Mac OS 10.5 --- tests/auto/qscroller/tst_qscroller.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/auto/qscroller/tst_qscroller.cpp b/tests/auto/qscroller/tst_qscroller.cpp index 61d3980..4179fee 100644 --- a/tests/auto/qscroller/tst_qscroller.cpp +++ b/tests/auto/qscroller/tst_qscroller.cpp @@ -382,10 +382,13 @@ void tst_QScroller::scrollTo() void tst_QScroller::scroll() { -#ifndef QT_NO_GESTURES +#if defined(Q_OS_MACX) && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6) + QSKIP("Mac OS X < 10.6 does not support QTouchEvents"); + return; +#endif +#ifndef QT_NO_GESTURES // -- good case. normal scroll - tst_QScrollerWidget *sw = new tst_QScrollerWidget(); sw->scrollArea = QRectF(0, 0, 1000, 1000); QScroller::grabGesture(sw, QScroller::TouchGesture); @@ -426,6 +429,11 @@ void tst_QScroller::scroll() void tst_QScroller::overshoot() { +#if defined(Q_OS_MACX) && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6) + QSKIP("Mac OS X < 10.6 does not support QTouchEvents"); + return; +#endif + #ifndef QT_NO_GESTURES tst_QScrollerWidget *sw = new tst_QScrollerWidget(); sw->scrollArea = QRectF(0, 0, 1000, 1000); -- cgit v0.12 From 205d607c3387d074fb87f8deb77a8f515ae2e189 Mon Sep 17 00:00:00 2001 From: Robert Griebl Date: Sat, 4 Dec 2010 00:13:34 +0100 Subject: Make it compile on Mac OS X 10.5 --- tests/auto/qscroller/tst_qscroller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/auto/qscroller/tst_qscroller.cpp b/tests/auto/qscroller/tst_qscroller.cpp index 4179fee..b4e4ddf 100644 --- a/tests/auto/qscroller/tst_qscroller.cpp +++ b/tests/auto/qscroller/tst_qscroller.cpp @@ -383,7 +383,7 @@ void tst_QScroller::scrollTo() void tst_QScroller::scroll() { #if defined(Q_OS_MACX) && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6) - QSKIP("Mac OS X < 10.6 does not support QTouchEvents"); + QSKIP("Mac OS X < 10.6 does not support QTouchEvents", SkipAll); return; #endif @@ -430,7 +430,7 @@ void tst_QScroller::scroll() void tst_QScroller::overshoot() { #if defined(Q_OS_MACX) && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6) - QSKIP("Mac OS X < 10.6 does not support QTouchEvents"); + QSKIP("Mac OS X < 10.6 does not support QTouchEvents", SkipAll); return; #endif -- cgit v0.12