/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the demonstration applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "flickable.h" #include #include class FlickableTicker: QObject { public: FlickableTicker(Flickable *scroller) { m_scroller = scroller; } void start(int interval) { if (!m_timer.isActive()) m_timer.start(interval, this); } void stop() { m_timer.stop(); } protected: void timerEvent(QTimerEvent *event) { Q_UNUSED(event); m_scroller->tick(); } private: Flickable *m_scroller; QBasicTimer m_timer; }; class FlickablePrivate { public: typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State; State state; int threshold; QPoint pressPos; QPoint offset; QPoint delta; QPoint speed; FlickableTicker *ticker; QTime timeStamp; QWidget *target; QList ignoreList; }; Flickable::Flickable() { d = new FlickablePrivate; d->state = FlickablePrivate::Steady; d->threshold = 10; d->ticker = new FlickableTicker(this); d->timeStamp = QTime::currentTime(); d->target = 0; } Flickable::~Flickable() { delete d; } void Flickable::setThreshold(int th) { if (th >= 0) d->threshold = th; } int Flickable::threshold() const { return d->threshold; } void Flickable::setAcceptMouseClick(QWidget *target) { d->target = target; } static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64) { int x = qBound(-max, speed.x(), max); int y = qBound(-max, speed.y(), max); x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a); y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a); return QPoint(x, y); } void Flickable::handleMousePress(QMouseEvent *event) { event->ignore(); if (event->button() != Qt::LeftButton) return; if (d->ignoreList.removeAll(event)) return; switch (d->state) { case FlickablePrivate::Steady: event->accept(); d->state = FlickablePrivate::Pressed; d->pressPos = event->pos(); break; case FlickablePrivate::AutoScroll: event->accept(); d->state = FlickablePrivate::Stop; d->speed = QPoint(0, 0); d->pressPos = event->pos(); d->offset = scrollOffset(); d->ticker->stop(); break; default: break; } } void Flickable::handleMouseRelease(QMouseEvent *event) { event->ignore(); if (event->button() != Qt::LeftButton) return; if (d->ignoreList.removeAll(event)) return; QPoint delta; switch (d->state) { case FlickablePrivate::Pressed: event->accept(); d->state = FlickablePrivate::Steady; if (d->target) { QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress, d->pressPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent *event2 = new QMouseEvent(*event); d->ignoreList << event1; d->ignoreList << event2; QApplication::postEvent(d->target, event1); QApplication::postEvent(d->target, event2); } break; case FlickablePrivate::ManualScroll: event->accept(); delta = event->pos() - d->pressPos; if (d->timeStamp.elapsed() > 100) { d->timeStamp = QTime::currentTime(); d->speed = delta - d->delta; d->delta = delta; } d->offset = scrollOffset(); d->pressPos = event->pos(); if (d->speed == QPoint(0, 0)) { d->state = FlickablePrivate::Steady; } else { d->speed /= 4; d->state = FlickablePrivate::AutoScroll; d->ticker->start(20); } break; case FlickablePrivate::Stop: event->accept(); d->state = FlickablePrivate::Steady; d->offset = scrollOffset(); break; default: break; } } void Flickable::handleMouseMove(QMouseEvent *event) { event->ignore(); if (!(event->buttons() & Qt::LeftButton)) return; if (d->ignoreList.removeAll(event)) return; QPoint delta; switch (d->state) { case FlickablePrivate::Pressed: case FlickablePrivate::Stop: delta = event->pos() - d->pressPos; if (delta.x() > d->threshold || delta.x() < -d->threshold || delta.y() > d->threshold || delta.y() < -d->threshold) { d->timeStamp = QTime::currentTime(); d->state = FlickablePrivate::ManualScroll; d->delta = QPoint(0, 0); d->pressPos = event->pos(); event->accept(); } break; case FlickablePrivate::ManualScroll: event->accept(); delta = event->pos() - d->pressPos; setScrollOffset(d->offset - delta); if (d->timeStamp.elapsed() > 100) { d->timeStamp = QTime::currentTime(); d->speed = delta - d->delta; d->delta = delta; } break; default: break; } } void Flickable::tick() { if (d->state == FlickablePrivate:: AutoScroll) { d->speed = deaccelerate(d->speed); setScrollOffset(d->offset - d->speed); d->offset = scrollOffset(); if (d->speed == QPoint(0, 0)) { d->state = FlickablePrivate::Steady; d->ticker->stop(); } } else { d->ticker->stop(); } }