1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: Qt Software Information (qt-info@nokia.com)
6 ** This file is part of the Graphics Dojo project on Qt Labs.
8 ** This file may be used under the terms of the GNU General Public
9 ** License version 2.0 or 3.0 as published by the Free Software Foundation
10 ** and appearing in the file LICENSE.GPL included in the packaging of
11 ** this file. Please review the following information to ensure GNU
12 ** General Public Licensing requirements will be met:
13 ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
14 ** http://www.gnu.org/copyleft/gpl.html.
16 ** If you are unsure which license is appropriate for your use, please
17 ** contact the sales department at qt-sales@nokia.com.
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22 ****************************************************************************/
24 #include "flickcharm.h"
26 #include <QAbstractScrollArea>
27 #include <QApplication>
28 #include <QBasicTimer>
32 #include <QMouseEvent>
38 typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State;
45 QList<QEvent*> ignored;
48 class FlickCharmPrivate
51 QHash<QWidget*, FlickData*> flickData;
55 FlickCharm::FlickCharm(QObject *parent): QObject(parent)
57 d = new FlickCharmPrivate;
60 FlickCharm::~FlickCharm()
65 void FlickCharm::activateOn(QWidget *widget)
67 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
69 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
70 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
72 QWidget *viewport = scrollArea->viewport();
74 viewport->installEventFilter(this);
75 scrollArea->installEventFilter(this);
77 d->flickData.remove(viewport);
78 d->flickData[viewport] = new FlickData;
79 d->flickData[viewport]->widget = widget;
80 d->flickData[viewport]->state = FlickData::Steady;
85 qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
88 void FlickCharm::deactivateFrom(QWidget *widget)
90 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
92 QWidget *viewport = scrollArea->viewport();
94 viewport->removeEventFilter(this);
95 scrollArea->removeEventFilter(this);
97 delete d->flickData[viewport];
98 d->flickData.remove(viewport);
104 static QPoint scrollOffset(QWidget *widget)
108 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
110 x = scrollArea->horizontalScrollBar()->value();
111 y = scrollArea->verticalScrollBar()->value();
117 static void setScrollOffset(QWidget *widget, const QPoint &p)
119 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
121 scrollArea->horizontalScrollBar()->setValue(p.x());
122 scrollArea->verticalScrollBar()->setValue(p.y());
126 static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)
128 int x = qBound(-max, speed.x(), max);
129 int y = qBound(-max, speed.y(), max);
130 x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a);
131 y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a);
135 bool FlickCharm::eventFilter(QObject *object, QEvent *event)
138 if (!object->isWidgetType())
141 QEvent::Type type = event->type();
142 if (type != QEvent::MouseButtonPress &&
143 type != QEvent::MouseButtonRelease &&
144 type != QEvent::MouseMove)
147 QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
148 if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier)
151 QWidget *viewport = dynamic_cast<QWidget*>(object);
152 FlickData *data = d->flickData.value(viewport);
153 if (!viewport || !data || data->ignored.removeAll(event))
156 QWidget *scrollArea = dynamic_cast<QWidget*>(object);
158 bool consumed = false;
159 switch (data->state) {
161 case FlickData::Steady:
162 if (mouseEvent->type() == QEvent::MouseButtonPress)
163 if (mouseEvent->buttons() == Qt::LeftButton) {
165 data->state = FlickData::Pressed;
166 data->pressPos = mouseEvent->pos();
167 data->offset = scrollOffset(data->widget);
171 case FlickData::Pressed:
172 if (mouseEvent->type() == QEvent::MouseButtonRelease) {
174 data->state = FlickData::Steady;
176 QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
177 data->pressPos, Qt::LeftButton,
178 Qt::LeftButton, Qt::NoModifier);
179 QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
181 data->ignored << event1;
182 data->ignored << event2;
183 QApplication::postEvent(object, event1);
184 QApplication::postEvent(object, event2);
186 if (mouseEvent->type() == QEvent::MouseMove) {
189 data->state = FlickData::ManualScroll;
190 data->dragPos = QCursor::pos();
191 if (!d->ticker.isActive())
192 d->ticker.start(20, this);
197 case FlickData::ManualScroll:
198 if (mouseEvent->type() == QEvent::MouseMove) {
199 QPoint pos = scrollArea->mapFromGlobal(QCursor::pos());
200 if (pos.x() > scrollArea->width() || pos.x() < 0) {
202 QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
204 Qt::LeftButton, Qt::NoModifier);
205 QMouseEvent *event2 = new QMouseEvent(QEvent::MouseMove,
207 Qt::LeftButton, Qt::NoModifier);
209 data->ignored << event1;
210 data->ignored << event2;
211 QApplication::postEvent(object, event1);
212 QApplication::postEvent(object, event2);
213 data->state = FlickData::Steady;
217 QPoint delta = mouseEvent->pos() - data->pressPos;
218 setScrollOffset(data->widget, data->offset - delta);
221 if (mouseEvent->type() == QEvent::MouseButtonRelease) {
223 data->state = FlickData::AutoScroll;
227 case FlickData::AutoScroll:
228 if (mouseEvent->type() == QEvent::MouseButtonPress) {
230 data->state = FlickData::Stop;
231 data->speed = QPoint(0, 0);
232 data->pressPos = mouseEvent->pos();
233 data->offset = scrollOffset(data->widget);
235 if (mouseEvent->type() == QEvent::MouseButtonRelease) {
237 data->state = FlickData::Steady;
238 data->speed = QPoint(0, 0);
242 case FlickData::Stop:
243 if (mouseEvent->type() == QEvent::MouseButtonRelease) {
245 data->state = FlickData::Steady;
247 if (mouseEvent->type() == QEvent::MouseMove) {
249 data->state = FlickData::ManualScroll;
250 data->dragPos = QCursor::pos();
251 if (!d->ticker.isActive())
252 d->ticker.start(20, this);
263 void FlickCharm::timerEvent(QTimerEvent *event)
266 QHashIterator<QWidget*, FlickData*> item(d->flickData);
267 while (item.hasNext()) {
269 FlickData *data = item.value();
271 const bool scrolling = data->state == FlickData::ManualScroll || data->state == FlickData::AutoScroll;
272 scrollBarShow(data->widget, scrolling);
273 // data->widget->setUpdatesEnabled(!scrolling);
275 if (data->state == FlickData::ManualScroll) {
277 data->speed = QCursor::pos() - data->dragPos;
278 data->dragPos = QCursor::pos();
281 if (data->state == FlickData::AutoScroll) {
283 data->speed = deaccelerate(data->speed);
284 QPoint p = scrollOffset(data->widget);
285 setScrollOffset(data->widget, p - data->speed);
286 if (data->speed == QPoint(0, 0))
287 data->state = FlickData::Steady;
294 QObject::timerEvent(event);
297 void FlickCharm::showScrollBars(QWidget *widget) {
298 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
300 // scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
301 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
305 void FlickCharm::hideScrollBars(QWidget *widget) {
306 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
308 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
309 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
313 void FlickCharm::scrollBarShow(QWidget *widget, bool show) {
314 static bool shown = false;
318 showScrollBars(widget);
323 hideScrollBars(widget);