Further improve window resizing.
[dorian] / widgets / flickable.cpp
1 /****************************************************************************\r
2 **\r
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).\r
4 ** All rights reserved.\r
5 ** Contact: Nokia Corporation (qt-info@nokia.com)\r
6 **\r
7 ** This file is part of the demonstration applications of the Qt Toolkit.\r
8 **\r
9 ** $QT_BEGIN_LICENSE:LGPL$\r
10 ** Commercial Usage\r
11 ** Licensees holding valid Qt Commercial licenses may use this file in\r
12 ** accordance with the Qt Commercial License Agreement provided with the\r
13 ** Software or, alternatively, in accordance with the terms contained in\r
14 ** a written agreement between you and Nokia.\r
15 **\r
16 ** GNU Lesser General Public License Usage\r
17 ** Alternatively, this file may be used under the terms of the GNU Lesser\r
18 ** General Public License version 2.1 as published by the Free Software\r
19 ** Foundation and appearing in the file LICENSE.LGPL included in the\r
20 ** packaging of this file.  Please review the following information to\r
21 ** ensure the GNU Lesser General Public License version 2.1 requirements\r
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.\r
23 **\r
24 ** In addition, as a special exception, Nokia gives you certain additional\r
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception\r
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.\r
27 **\r
28 ** GNU General Public License Usage\r
29 ** Alternatively, this file may be used under the terms of the GNU\r
30 ** General Public License version 3.0 as published by the Free Software\r
31 ** Foundation and appearing in the file LICENSE.GPL included in the\r
32 ** packaging of this file.  Please review the following information to\r
33 ** ensure the GNU General Public License version 3.0 requirements will be\r
34 ** met: http://www.gnu.org/copyleft/gpl.html.\r
35 **\r
36 ** If you have questions regarding the use of this file, please contact\r
37 ** Nokia at qt-info@nokia.com.\r
38 ** $QT_END_LICENSE$\r
39 **\r
40 ****************************************************************************/\r
41 \r
42 #include "flickable.h"\r
43 \r
44 #include <QtCore>\r
45 #include <QtGui>\r
46 \r
47 class FlickableTicker: QObject\r
48 {\r
49 public:\r
50     FlickableTicker(Flickable *scroller) {\r
51         m_scroller = scroller;\r
52     }\r
53 \r
54     void start(int interval) {\r
55         if (!m_timer.isActive())\r
56             m_timer.start(interval, this);\r
57     }\r
58 \r
59     void stop() {\r
60         m_timer.stop();\r
61     }\r
62 \r
63 protected:\r
64     void timerEvent(QTimerEvent *event) {\r
65         Q_UNUSED(event);\r
66         m_scroller->tick();\r
67     }\r
68 \r
69 private:\r
70     Flickable *m_scroller;\r
71     QBasicTimer m_timer;\r
72 };\r
73 \r
74 class FlickablePrivate\r
75 {\r
76 public:\r
77     typedef enum {\r
78         Steady,\r
79         Pressed,\r
80         ManualScroll,\r
81         AutoScroll,\r
82         Stop\r
83     } State;\r
84 \r
85     State state;\r
86     int threshold;\r
87     QPoint pressPos;\r
88     QPoint offset;\r
89     QPoint delta;\r
90     QPoint speed;\r
91     FlickableTicker *ticker;\r
92     QTime timeStamp;\r
93     QWidget *target;\r
94     QList<QEvent*> ignoreList;\r
95 };\r
96 \r
97 Flickable::Flickable()\r
98 {\r
99     d = new FlickablePrivate;\r
100     d->state = FlickablePrivate::Steady;\r
101     d->threshold = 10;\r
102     d->ticker = new FlickableTicker(this);\r
103     d->timeStamp = QTime::currentTime();\r
104     d->target = 0;\r
105 }\r
106 \r
107 Flickable::~Flickable()\r
108 {\r
109     delete d;\r
110 }\r
111 \r
112 void Flickable::setThreshold(int th)\r
113 {\r
114     if (th >= 0)\r
115         d->threshold = th;\r
116 }\r
117 \r
118 int Flickable::threshold() const\r
119 {\r
120     return d->threshold;\r
121 }\r
122 \r
123 void Flickable::setAcceptMouseClick(QWidget *target)\r
124 {\r
125     d->target = target;\r
126 }\r
127 \r
128 static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)\r
129 {\r
130     int x = qBound(-max, speed.x(), max);\r
131     int y = qBound(-max, speed.y(), max);\r
132     x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a);\r
133     y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a);\r
134     return QPoint(x, y);\r
135 }\r
136 \r
137 void Flickable::handleMousePress(QMouseEvent *event)\r
138 {\r
139     event->ignore();\r
140 \r
141     if (event->button() != Qt::LeftButton)\r
142         return;\r
143 \r
144     if (d->ignoreList.removeAll(event))\r
145         return;\r
146 \r
147     switch (d->state) {\r
148 \r
149     case FlickablePrivate::Steady:\r
150         event->accept();\r
151         d->state = FlickablePrivate::Pressed;\r
152         d->pressPos = event->pos();\r
153         break;\r
154 \r
155     case FlickablePrivate::AutoScroll:\r
156         event->accept();\r
157         d->state = FlickablePrivate::Stop;\r
158         d->speed = QPoint(0, 0);\r
159         d->pressPos = event->pos();\r
160         d->offset = scrollOffset();\r
161         d->ticker->stop();\r
162         break;\r
163 \r
164     default:\r
165         break;\r
166     }\r
167 }\r
168 \r
169 void Flickable::handleMouseRelease(QMouseEvent *event)\r
170 {\r
171     event->ignore();\r
172 \r
173     if (event->button() != Qt::LeftButton)\r
174         return;\r
175 \r
176     if (d->ignoreList.removeAll(event))\r
177         return;\r
178 \r
179     QPoint delta;\r
180 \r
181     switch (d->state) {\r
182 \r
183     case FlickablePrivate::Pressed:\r
184         event->accept();\r
185         d->state = FlickablePrivate::Steady;\r
186         if (d->target) {\r
187             QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,\r
188                                                   d->pressPos, Qt::LeftButton,\r
189                                                   Qt::LeftButton, Qt::NoModifier);\r
190             QMouseEvent *event2 = new QMouseEvent(*event);\r
191             d->ignoreList << event1;\r
192             d->ignoreList << event2;\r
193             QApplication::postEvent(d->target, event1);\r
194             QApplication::postEvent(d->target, event2);\r
195         }\r
196         break;\r
197 \r
198     case FlickablePrivate::ManualScroll:\r
199         event->accept();\r
200         delta = event->pos() - d->pressPos;\r
201         if (d->timeStamp.elapsed() > 100) {\r
202             d->timeStamp = QTime::currentTime();\r
203             d->speed = delta - d->delta;\r
204             d->delta = delta;\r
205         }\r
206         d->offset = scrollOffset();\r
207         d->pressPos = event->pos();\r
208         if (d->speed == QPoint(0, 0)) {\r
209             d->state = FlickablePrivate::Steady;\r
210         } else {\r
211             d->speed /= 4;\r
212             d->state = FlickablePrivate::AutoScroll;\r
213             d->ticker->start(20);\r
214         }\r
215         break;\r
216 \r
217     case FlickablePrivate::Stop:\r
218         event->accept();\r
219         d->state = FlickablePrivate::Steady;\r
220         d->offset = scrollOffset();\r
221         break;\r
222 \r
223     default:\r
224         break;\r
225     }\r
226 }\r
227 \r
228 void Flickable::handleMouseMove(QMouseEvent *event)\r
229 {\r
230     event->ignore();\r
231 \r
232     if (!(event->buttons() & Qt::LeftButton))\r
233         return;\r
234 \r
235     if (d->ignoreList.removeAll(event))\r
236         return;\r
237 \r
238     QPoint delta;\r
239 \r
240     switch (d->state) {\r
241 \r
242     case FlickablePrivate::Pressed:\r
243     case FlickablePrivate::Stop:\r
244         delta = event->pos() - d->pressPos;\r
245         if (delta.x() > d->threshold || delta.x() < -d->threshold ||\r
246                 delta.y() > d->threshold || delta.y() < -d->threshold) {\r
247             d->timeStamp = QTime::currentTime();\r
248             d->state = FlickablePrivate::ManualScroll;\r
249             d->delta = QPoint(0, 0);\r
250             d->pressPos = event->pos();\r
251             event->accept();\r
252         }\r
253         break;\r
254 \r
255     case FlickablePrivate::ManualScroll:\r
256         event->accept();\r
257         delta = event->pos() - d->pressPos;\r
258         setScrollOffset(d->offset - delta);\r
259         if (d->timeStamp.elapsed() > 100) {\r
260             d->timeStamp = QTime::currentTime();\r
261             d->speed = delta - d->delta;\r
262             d->delta = delta;\r
263         }\r
264         break;\r
265 \r
266     default:\r
267         break;\r
268     }\r
269 }\r
270 \r
271 void Flickable::tick()\r
272 {\r
273     if (d->state == FlickablePrivate:: AutoScroll) {\r
274         d->speed = deaccelerate(d->speed);\r
275         setScrollOffset(d->offset - d->speed);\r
276         d->offset = scrollOffset();\r
277         if (d->speed == QPoint(0, 0)) {\r
278             d->state = FlickablePrivate::Steady;\r
279             d->ticker->stop();\r
280         }\r
281     } else {\r
282         d->ticker->stop();\r
283     }\r
284 }\r
285 \r