From b127a26eab70c143b6cc6b6bd65cf881aa527cb0 Mon Sep 17 00:00:00 2001 From: Flavio Tordini Date: Wed, 11 Nov 2009 21:25:23 +0100 Subject: [PATCH] Flickable playlist with Flick Charm --- minitube.pro | 10 +- src/flickcharm.cpp | 327 +++++++++++++++++++++++++++++++++++++++++++++ src/flickcharm.h | 52 +++++++ 3 files changed, 384 insertions(+), 5 deletions(-) create mode 100644 src/flickcharm.cpp create mode 100644 src/flickcharm.h diff --git a/minitube.pro b/minitube.pro index c5150fd..a5800b7 100755 --- a/minitube.pro +++ b/minitube.pro @@ -42,7 +42,8 @@ HEADERS += src/MainWindow.h \ src/loadingwidget.h \ src/videoareawidget.h \ src/googlesuggest.h \ - src/videowidget.h + src/videowidget.h \ + src/flickcharm.h SOURCES += src/main.cpp \ src/MainWindow.cpp \ src/SearchView.cpp \ @@ -68,7 +69,8 @@ SOURCES += src/main.cpp \ src/loadingwidget.cpp \ src/videoareawidget.cpp \ src/googlesuggest.cpp \ - src/videowidget.cpp + src/videowidget.cpp \ + src/flickcharm.cpp RESOURCES += resources.qrc DESTDIR = build/target/ OBJECTS_DIR = build/obj/ @@ -120,6 +122,4 @@ unix { icon128.path = $$DATADIR/icons/hicolor/128x128/apps icon128.files += data/128x128/minitube.png } -win32 { - RC_FILE = minitube.rc -} +win32:RC_FILE = minitube.rc diff --git a/src/flickcharm.cpp b/src/flickcharm.cpp new file mode 100644 index 0000000..1f5175b --- /dev/null +++ b/src/flickcharm.cpp @@ -0,0 +1,327 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Graphics Dojo project on Qt Labs. +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 or 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#include "flickcharm.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct FlickData { + typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State; + State state; + QWidget *widget; + QPoint pressPos; + QPoint offset; + QPoint dragPos; + QPoint speed; + QList ignored; +}; + +class FlickCharmPrivate +{ +public: + QHash flickData; + QBasicTimer ticker; +}; + +FlickCharm::FlickCharm(QObject *parent): QObject(parent) +{ + d = new FlickCharmPrivate; +} + +FlickCharm::~FlickCharm() +{ + delete d; +} + +void FlickCharm::activateOn(QWidget *widget) +{ + QAbstractScrollArea *scrollArea = dynamic_cast(widget); + if (scrollArea) { + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + QWidget *viewport = scrollArea->viewport(); + + viewport->installEventFilter(this); + scrollArea->installEventFilter(this); + + d->flickData.remove(viewport); + d->flickData[viewport] = new FlickData; + d->flickData[viewport]->widget = widget; + d->flickData[viewport]->state = FlickData::Steady; + + return; + } + + qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)"; +} + +void FlickCharm::deactivateFrom(QWidget *widget) +{ + QAbstractScrollArea *scrollArea = dynamic_cast(widget); + if (scrollArea) { + QWidget *viewport = scrollArea->viewport(); + + viewport->removeEventFilter(this); + scrollArea->removeEventFilter(this); + + delete d->flickData[viewport]; + d->flickData.remove(viewport); + + return; + } +} + +static QPoint scrollOffset(QWidget *widget) +{ + int x = 0, y = 0; + + QAbstractScrollArea *scrollArea = dynamic_cast(widget); + if (scrollArea) { + x = scrollArea->horizontalScrollBar()->value(); + y = scrollArea->verticalScrollBar()->value(); + } + + return QPoint(x, y); +} + +static void setScrollOffset(QWidget *widget, const QPoint &p) +{ + QAbstractScrollArea *scrollArea = dynamic_cast(widget); + if (scrollArea) { + scrollArea->horizontalScrollBar()->setValue(p.x()); + scrollArea->verticalScrollBar()->setValue(p.y()); + } +} + +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); +} + +bool FlickCharm::eventFilter(QObject *object, QEvent *event) +{ + + if (!object->isWidgetType()) + return false; + + QEvent::Type type = event->type(); + if (type != QEvent::MouseButtonPress && + type != QEvent::MouseButtonRelease && + type != QEvent::MouseMove) + return false; + + QMouseEvent *mouseEvent = dynamic_cast(event); + if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier) + return false; + + QWidget *viewport = dynamic_cast(object); + FlickData *data = d->flickData.value(viewport); + if (!viewport || !data || data->ignored.removeAll(event)) + return false; + + QWidget *scrollArea = dynamic_cast(object); + + bool consumed = false; + switch (data->state) { + + case FlickData::Steady: + if (mouseEvent->type() == QEvent::MouseButtonPress) + if (mouseEvent->buttons() == Qt::LeftButton) { + consumed = true; + data->state = FlickData::Pressed; + data->pressPos = mouseEvent->pos(); + data->offset = scrollOffset(data->widget); + } + break; + + case FlickData::Pressed: + if (mouseEvent->type() == QEvent::MouseButtonRelease) { + consumed = true; + data->state = FlickData::Steady; + + QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress, + data->pressPos, Qt::LeftButton, + Qt::LeftButton, Qt::NoModifier); + QMouseEvent *event2 = new QMouseEvent(*mouseEvent); + + data->ignored << event1; + data->ignored << event2; + QApplication::postEvent(object, event1); + QApplication::postEvent(object, event2); + } + if (mouseEvent->type() == QEvent::MouseMove) { + + consumed = true; + data->state = FlickData::ManualScroll; + data->dragPos = QCursor::pos(); + if (!d->ticker.isActive()) + d->ticker.start(20, this); + + } + break; + + case FlickData::ManualScroll: + if (mouseEvent->type() == QEvent::MouseMove) { + QPoint pos = scrollArea->mapFromGlobal(QCursor::pos()); + if (pos.x() > scrollArea->width() || pos.x() < 0) { + pos.setX(1); + QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress, + pos, Qt::LeftButton, + Qt::LeftButton, Qt::NoModifier); + QMouseEvent *event2 = new QMouseEvent(QEvent::MouseMove, + pos, Qt::LeftButton, + Qt::LeftButton, Qt::NoModifier); + + data->ignored << event1; + data->ignored << event2; + QApplication::postEvent(object, event1); + QApplication::postEvent(object, event2); + data->state = FlickData::Steady; + consumed = true; + } else { + consumed = true; + QPoint delta = mouseEvent->pos() - data->pressPos; + setScrollOffset(data->widget, data->offset - delta); + } + } + if (mouseEvent->type() == QEvent::MouseButtonRelease) { + consumed = true; + data->state = FlickData::AutoScroll; + } + break; + + case FlickData::AutoScroll: + if (mouseEvent->type() == QEvent::MouseButtonPress) { + consumed = true; + data->state = FlickData::Stop; + data->speed = QPoint(0, 0); + data->pressPos = mouseEvent->pos(); + data->offset = scrollOffset(data->widget); + } + if (mouseEvent->type() == QEvent::MouseButtonRelease) { + consumed = true; + data->state = FlickData::Steady; + data->speed = QPoint(0, 0); + } + break; + + case FlickData::Stop: + if (mouseEvent->type() == QEvent::MouseButtonRelease) { + consumed = true; + data->state = FlickData::Steady; + } + if (mouseEvent->type() == QEvent::MouseMove) { + consumed = true; + data->state = FlickData::ManualScroll; + data->dragPos = QCursor::pos(); + if (!d->ticker.isActive()) + d->ticker.start(20, this); + } + break; + + default: + break; + } + + return consumed; +} + +void FlickCharm::timerEvent(QTimerEvent *event) +{ + int count = 0; + QHashIterator item(d->flickData); + while (item.hasNext()) { + item.next(); + FlickData *data = item.value(); + + const bool scrolling = data->state == FlickData::ManualScroll || data->state == FlickData::AutoScroll; + scrollBarShow(data->widget, scrolling); + // data->widget->setUpdatesEnabled(!scrolling); + + if (data->state == FlickData::ManualScroll) { + count++; + data->speed = QCursor::pos() - data->dragPos; + data->dragPos = QCursor::pos(); + } + + if (data->state == FlickData::AutoScroll) { + count++; + data->speed = deaccelerate(data->speed); + QPoint p = scrollOffset(data->widget); + setScrollOffset(data->widget, p - data->speed); + if (data->speed == QPoint(0, 0)) + data->state = FlickData::Steady; + } + } + + if (!count) + d->ticker.stop(); + + QObject::timerEvent(event); +} + +void FlickCharm::showScrollBars(QWidget *widget) { + QAbstractScrollArea *scrollArea = dynamic_cast(widget); + if (scrollArea) { + // scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } +} + +void FlickCharm::hideScrollBars(QWidget *widget) { + QAbstractScrollArea *scrollArea = dynamic_cast(widget); + if (scrollArea) { + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } +} + +void FlickCharm::scrollBarShow(QWidget *widget, bool show) { + static bool shown = false; + + if (show) { + if (!shown) { + showScrollBars(widget); + shown = true; + } + } else { + if (shown) { + hideScrollBars(widget); + shown = false; + } + } +} diff --git a/src/flickcharm.h b/src/flickcharm.h new file mode 100644 index 0000000..2a1bfea --- /dev/null +++ b/src/flickcharm.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the Graphics Dojo project on Qt Labs. +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 or 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#ifndef FLICKCHARM_H +#define FLICKCHARM_H + +#include + +class FlickCharmPrivate; +class QWidget; + +class FlickCharm: public QObject +{ + Q_OBJECT +public: + FlickCharm(QObject *parent = 0); + ~FlickCharm(); + void activateOn(QWidget *widget); + void deactivateFrom(QWidget *widget); + bool eventFilter(QObject *object, QEvent *event); + +protected: + void timerEvent(QTimerEvent *event); + +private: + void scrollBarShow(QWidget *widget, bool show); + void hideScrollBars(QWidget *widget); + void showScrollBars(QWidget *widget); + FlickCharmPrivate *d; +}; + +#endif // FLICKCHARM_H -- 2.39.5