1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** Contact: Qt Software Information (qt-info@nokia.com) |
|
5 ** |
|
6 ** This file is part of the Graphics Dojo project on Qt Labs. |
|
7 ** |
|
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. |
|
15 ** |
|
16 ** If you are unsure which license is appropriate for your use, please |
|
17 ** contact the sales department at qt-sales@nokia.com. |
|
18 ** |
|
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. |
|
21 ** |
|
22 ****************************************************************************/ |
|
23 |
|
24 #include "FlickCharm.h" |
|
25 |
|
26 #include <QAbstractScrollArea> |
|
27 #include <QApplication> |
|
28 #include <QBasicTimer> |
|
29 #include <QEvent> |
|
30 #include <QHash> |
|
31 #include <QList> |
|
32 #include <QMouseEvent> |
|
33 #include <QScrollBar> |
|
34 #include <QWebFrame> |
|
35 #include <QWebView> |
|
36 #include <QGraphicsWebView> |
|
37 #include <QGraphicsSceneMouseEvent> |
|
38 |
|
39 #include <QDebug> |
|
40 |
|
41 struct FlickData { |
|
42 typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State; |
|
43 State state; |
|
44 QObject *widget; |
|
45 QPoint pressPos; |
|
46 QPoint offset; |
|
47 QPoint dragPos; |
|
48 QPoint speed; |
|
49 QList<QEvent*> ignored; |
|
50 }; |
|
51 |
|
52 class FlickCharmPrivate |
|
53 { |
|
54 public: |
|
55 QHash<QObject*, FlickData*> flickData; |
|
56 QBasicTimer ticker; |
|
57 }; |
|
58 |
|
59 FlickCharm::FlickCharm(QObject *parent): QObject(parent) |
|
60 { |
|
61 d = new FlickCharmPrivate; |
|
62 } |
|
63 |
|
64 FlickCharm::~FlickCharm() |
|
65 { |
|
66 delete d; |
|
67 } |
|
68 |
|
69 void FlickCharm::activateOn(QWidget *widget) |
|
70 { |
|
71 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget); |
|
72 if (scrollArea) { |
|
73 scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
74 scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
|
75 |
|
76 QWidget *viewport = scrollArea->viewport(); |
|
77 |
|
78 viewport->installEventFilter(this); |
|
79 scrollArea->installEventFilter(this); |
|
80 |
|
81 d->flickData.remove(viewport); |
|
82 d->flickData[viewport] = new FlickData; |
|
83 d->flickData[viewport]->widget = widget; |
|
84 d->flickData[viewport]->state = FlickData::Steady; |
|
85 |
|
86 return; |
|
87 } |
|
88 } |
|
89 |
|
90 void FlickCharm::activateOn(QWebView *webView) |
|
91 { |
|
92 if (webView) { |
|
93 QWebFrame *frame = webView->page()->mainFrame(); |
|
94 frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); |
|
95 frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); |
|
96 |
|
97 webView->installEventFilter(this); |
|
98 |
|
99 d->flickData.remove(webView); |
|
100 d->flickData[webView] = new FlickData; |
|
101 d->flickData[webView]->widget = webView; |
|
102 d->flickData[webView]->state = FlickData::Steady; |
|
103 |
|
104 return; |
|
105 } |
|
106 } |
|
107 |
|
108 void FlickCharm::activateOn(QGraphicsWebView *webView) |
|
109 { |
|
110 qDebug() << "FlickCharm::activateOn"; |
|
111 if (webView) { |
|
112 QWebFrame *frame = webView->page()->mainFrame(); |
|
113 frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); |
|
114 frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); |
|
115 |
|
116 webView->installEventFilter(this); |
|
117 |
|
118 d->flickData.remove(webView); |
|
119 d->flickData[webView] = new FlickData; |
|
120 d->flickData[webView]->widget = webView; |
|
121 d->flickData[webView]->state = FlickData::Steady; |
|
122 |
|
123 return; |
|
124 } |
|
125 } |
|
126 |
|
127 void FlickCharm::deactivateFrom(QWidget *widget) |
|
128 { |
|
129 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget); |
|
130 if (scrollArea) { |
|
131 QWidget *viewport = scrollArea->viewport(); |
|
132 |
|
133 viewport->removeEventFilter(this); |
|
134 scrollArea->removeEventFilter(this); |
|
135 |
|
136 delete d->flickData[viewport]; |
|
137 d->flickData.remove(viewport); |
|
138 |
|
139 return; |
|
140 } |
|
141 |
|
142 QWebView *webView = dynamic_cast<QWebView*>(widget); |
|
143 if (webView) { |
|
144 webView->removeEventFilter(this); |
|
145 |
|
146 delete d->flickData[webView]; |
|
147 d->flickData.remove(webView); |
|
148 |
|
149 return; |
|
150 } |
|
151 } |
|
152 |
|
153 static QPoint scrollOffset(QObject *widget) |
|
154 { |
|
155 int x = 0, y = 0; |
|
156 |
|
157 QGraphicsWebView *gWebView = dynamic_cast<QGraphicsWebView*>(widget); |
|
158 if (gWebView) { |
|
159 QWebFrame *frame = gWebView->page()->mainFrame(); |
|
160 x = frame->evaluateJavaScript("window.scrollX").toInt(); |
|
161 y = frame->evaluateJavaScript("window.scrollY").toInt(); |
|
162 } |
|
163 |
|
164 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget); |
|
165 if (scrollArea) { |
|
166 x = scrollArea->horizontalScrollBar()->value(); |
|
167 y = scrollArea->verticalScrollBar()->value(); |
|
168 } |
|
169 |
|
170 QWebView *webView = dynamic_cast<QWebView*>(widget); |
|
171 if (webView) { |
|
172 QWebFrame *frame = webView->page()->mainFrame(); |
|
173 x = frame->evaluateJavaScript("window.scrollX").toInt(); |
|
174 y = frame->evaluateJavaScript("window.scrollY").toInt(); |
|
175 } |
|
176 |
|
177 return QPoint(x, y); |
|
178 } |
|
179 |
|
180 static void setScrollOffset(QObject *widget, const QPoint &p) |
|
181 { |
|
182 QGraphicsWebView *gWebView = dynamic_cast<QGraphicsWebView*>(widget); |
|
183 if (gWebView) { |
|
184 QWebFrame *frame = gWebView->page()->mainFrame(); |
|
185 if (frame) |
|
186 frame->evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y())); |
|
187 return; |
|
188 } |
|
189 |
|
190 QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget); |
|
191 if (scrollArea) { |
|
192 scrollArea->horizontalScrollBar()->setValue(p.x()); |
|
193 scrollArea->verticalScrollBar()->setValue(p.y()); |
|
194 return; |
|
195 } |
|
196 |
|
197 QWebView *webView = dynamic_cast<QWebView*>(widget); |
|
198 QWebFrame *frame = webView ? webView->page()->mainFrame() : 0; |
|
199 if (frame) |
|
200 frame->evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y())); |
|
201 } |
|
202 |
|
203 static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64) |
|
204 { |
|
205 int x = qBound(-max, speed.x(), max); |
|
206 int y = qBound(-max, speed.y(), max); |
|
207 x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a); |
|
208 y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a); |
|
209 return QPoint(x, y); |
|
210 } |
|
211 |
|
212 bool FlickCharm::eventFilter(QObject *object, QEvent *event) |
|
213 { |
|
214 // qDebug() << "FlickCharm::eventFilter: " << object << event; |
|
215 // if (!object->isWidgetType()) |
|
216 // return false; |
|
217 |
|
218 QEvent::Type type = event->type(); |
|
219 switch (type) { |
|
220 case QEvent::MouseButtonPress: |
|
221 case QEvent::MouseButtonRelease: |
|
222 case QEvent::MouseMove: |
|
223 case QEvent::GraphicsSceneMousePress: |
|
224 case QEvent::GraphicsSceneMouseRelease: |
|
225 case QEvent::GraphicsSceneMouseMove: |
|
226 break; |
|
227 default: |
|
228 return false; |
|
229 } |
|
230 |
|
231 QPoint eventPos; |
|
232 QEvent::Type eventType; |
|
233 Qt::MouseButtons eventButtons; |
|
234 QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event); |
|
235 if (mouseEvent) { |
|
236 if (mouseEvent->modifiers() != Qt::NoModifier) |
|
237 return false; |
|
238 eventPos = mouseEvent->pos(); |
|
239 eventType = mouseEvent->type(); |
|
240 eventButtons = mouseEvent->buttons(); |
|
241 } |
|
242 else { |
|
243 QGraphicsSceneMouseEvent *mouseEvent = dynamic_cast<QGraphicsSceneMouseEvent*>(event); |
|
244 if (mouseEvent && mouseEvent->modifiers() == Qt::NoModifier) { |
|
245 eventPos = mouseEvent->pos().toPoint(); |
|
246 eventType = mouseEvent->type(); |
|
247 eventButtons = mouseEvent->buttons(); |
|
248 } |
|
249 else |
|
250 return false; |
|
251 } |
|
252 |
|
253 // QWidget *viewport = dynamic_cast<QWidget*>(object); |
|
254 FlickData *data = d->flickData.value(object); |
|
255 if (!data || data->ignored.removeAll(event)) { |
|
256 return false; |
|
257 } |
|
258 |
|
259 bool consumed = false; |
|
260 switch (data->state) { |
|
261 |
|
262 case FlickData::Steady: |
|
263 if (eventType == QEvent::MouseButtonPress || |
|
264 eventType == QEvent::GraphicsSceneMousePress) |
|
265 if (eventButtons == Qt::LeftButton) { |
|
266 consumed = true; |
|
267 data->state = FlickData::Pressed; |
|
268 data->pressPos = eventPos; |
|
269 data->offset = scrollOffset(data->widget); |
|
270 } |
|
271 break; |
|
272 |
|
273 case FlickData::Pressed: |
|
274 if (eventType == QEvent::MouseButtonRelease) { |
|
275 consumed = true; |
|
276 data->state = FlickData::Steady; |
|
277 |
|
278 QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress, |
|
279 data->pressPos, Qt::LeftButton, |
|
280 Qt::LeftButton, Qt::NoModifier); |
|
281 QMouseEvent *event2 = new QMouseEvent(*mouseEvent); |
|
282 |
|
283 data->ignored << event1; |
|
284 data->ignored << event2; |
|
285 QApplication::postEvent(object, event1); |
|
286 QApplication::postEvent(object, event2); |
|
287 } |
|
288 else if (eventType == QEvent::GraphicsSceneMouseRelease) { |
|
289 consumed = true; |
|
290 data->state = FlickData::Steady; |
|
291 |
|
292 QGraphicsSceneMouseEvent *origMouseEvent = static_cast<QGraphicsSceneMouseEvent*>(event); |
|
293 QGraphicsSceneMouseEvent *event1 = |
|
294 new QGraphicsSceneMouseEvent(QEvent::GraphicsSceneMousePress); |
|
295 event1->setPos(origMouseEvent->pos()); |
|
296 event1->setScenePos(origMouseEvent->scenePos()); |
|
297 event1->setLastPos(origMouseEvent->lastPos()); |
|
298 event1->setLastScenePos(origMouseEvent->lastScenePos()); |
|
299 event1->setButtons(origMouseEvent->buttons()); |
|
300 event1->setButton(origMouseEvent->button()); |
|
301 event1->setModifiers(origMouseEvent->modifiers()); |
|
302 |
|
303 QGraphicsSceneMouseEvent *event2 = new QGraphicsSceneMouseEvent(origMouseEvent->type()); |
|
304 event2->setPos(origMouseEvent->pos()); |
|
305 event2->setScenePos(origMouseEvent->scenePos()); |
|
306 event2->setLastPos(origMouseEvent->lastPos()); |
|
307 event2->setLastScenePos(origMouseEvent->lastScenePos()); |
|
308 event2->setButtons(origMouseEvent->buttons()); |
|
309 event2->setButton(origMouseEvent->button()); |
|
310 event2->setModifiers(origMouseEvent->modifiers()); |
|
311 |
|
312 data->ignored << event1; |
|
313 data->ignored << event2; |
|
314 QApplication::postEvent(object, event1); |
|
315 QApplication::postEvent(object, event2); |
|
316 } |
|
317 if (eventType == QEvent::MouseMove || |
|
318 eventType == QEvent::GraphicsSceneMouseMove) { |
|
319 consumed = true; |
|
320 data->state = FlickData::ManualScroll; |
|
321 data->dragPos = QCursor::pos(); |
|
322 if (!d->ticker.isActive()) |
|
323 d->ticker.start(20, this); |
|
324 } |
|
325 break; |
|
326 |
|
327 case FlickData::ManualScroll: |
|
328 if (eventType == QEvent::MouseMove || |
|
329 eventType == QEvent::GraphicsSceneMouseMove) { |
|
330 consumed = true; |
|
331 QPoint delta = eventPos - data->pressPos; |
|
332 setScrollOffset(data->widget, data->offset - delta); |
|
333 } |
|
334 if (eventType == QEvent::MouseButtonRelease || |
|
335 eventType == QEvent::GraphicsSceneMouseRelease) { |
|
336 consumed = true; |
|
337 data->state = FlickData::AutoScroll; |
|
338 } |
|
339 break; |
|
340 |
|
341 case FlickData::AutoScroll: |
|
342 if (eventType == QEvent::MouseButtonPress || |
|
343 eventType == QEvent::GraphicsSceneMousePress) { |
|
344 consumed = true; |
|
345 data->state = FlickData::Stop; |
|
346 data->speed = QPoint(0, 0); |
|
347 data->pressPos = eventPos; |
|
348 data->offset = scrollOffset(data->widget); |
|
349 } |
|
350 if (eventType == QEvent::MouseButtonRelease || |
|
351 eventType == QEvent::GraphicsSceneMouseRelease) { |
|
352 consumed = true; |
|
353 data->state = FlickData::Steady; |
|
354 data->speed = QPoint(0, 0); |
|
355 } |
|
356 break; |
|
357 |
|
358 case FlickData::Stop: |
|
359 if (eventType == QEvent::MouseButtonRelease || |
|
360 eventType == QEvent::GraphicsSceneMouseRelease) { |
|
361 consumed = true; |
|
362 data->state = FlickData::Steady; |
|
363 } |
|
364 if (eventType == QEvent::MouseMove || |
|
365 eventType == QEvent::GraphicsSceneMouseMove) { |
|
366 consumed = true; |
|
367 data->state = FlickData::ManualScroll; |
|
368 data->dragPos = QCursor::pos(); |
|
369 if (!d->ticker.isActive()) |
|
370 d->ticker.start(20, this); |
|
371 } |
|
372 break; |
|
373 |
|
374 default: |
|
375 break; |
|
376 } |
|
377 |
|
378 return consumed; |
|
379 } |
|
380 |
|
381 void FlickCharm::timerEvent(QTimerEvent *event) |
|
382 { |
|
383 int count = 0; |
|
384 QHashIterator<QObject*, FlickData*> item(d->flickData); |
|
385 while (item.hasNext()) { |
|
386 item.next(); |
|
387 FlickData *data = item.value(); |
|
388 |
|
389 if (data->state == FlickData::ManualScroll) { |
|
390 count++; |
|
391 data->speed = QCursor::pos() - data->dragPos; |
|
392 data->dragPos = QCursor::pos(); |
|
393 } |
|
394 |
|
395 if (data->state == FlickData::AutoScroll) { |
|
396 count++; |
|
397 data->speed = deaccelerate(data->speed); |
|
398 QPoint p = scrollOffset(data->widget); |
|
399 setScrollOffset(data->widget, p - data->speed); |
|
400 if (data->speed == QPoint(0, 0)) |
|
401 data->state = FlickData::Steady; |
|
402 } |
|
403 } |
|
404 |
|
405 if (!count) |
|
406 d->ticker.stop(); |
|
407 |
|
408 QObject::timerEvent(event); |
|
409 } |
|