1 /*
2 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
3 * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 * its contributors may be used to endorse or promote products derived
16 * from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 #include "config.h"
30 #include "EventSenderQt.h"
31
32 //#include <QtDebug>
33
34 #include <QtTest/QtTest>
35
36 #define KEYCODE_DEL 127
37 #define KEYCODE_BACKSPACE 8
38 #define KEYCODE_LEFTARROW 0xf702
39 #define KEYCODE_RIGHTARROW 0xf703
40 #define KEYCODE_UPARROW 0xf700
41 #define KEYCODE_DOWNARROW 0xf701
42
43 // Ports like Gtk and Windows expose a different approach for their zooming
44 // API if compared to Qt: they have specific methods for zooming in and out,
45 // as well as a settable zoom factor, while Qt has only a 'setZoomValue' method.
46 // Hence Qt DRT adopts a fixed zoom-factor (1.2) for compatibility.
47 #define ZOOM_STEP 1.2
48
49 #define DRT_MESSAGE_DONE (QEvent::User + 1)
50
51 struct DRTEventQueue {
52 QEvent* m_event;
53 int m_delay;
54 };
55
56 static DRTEventQueue eventQueue[1024];
57 static unsigned endOfQueue;
58 static unsigned startOfQueue;
59
EventSender(QWebPage * parent)60 EventSender::EventSender(QWebPage* parent)
61 : QObject(parent)
62 {
63 m_page = parent;
64 m_mouseButtonPressed = false;
65 m_drag = false;
66 memset(eventQueue, 0, sizeof(eventQueue));
67 endOfQueue = 0;
68 startOfQueue = 0;
69 m_eventLoop = 0;
70 m_page->view()->installEventFilter(this);
71 }
72
mouseDown(int button)73 void EventSender::mouseDown(int button)
74 {
75 Qt::MouseButton mouseButton;
76 switch (button) {
77 case 0:
78 mouseButton = Qt::LeftButton;
79 break;
80 case 1:
81 mouseButton = Qt::MidButton;
82 break;
83 case 2:
84 mouseButton = Qt::RightButton;
85 break;
86 case 3:
87 // fast/events/mouse-click-events expects the 4th button to be treated as the middle button
88 mouseButton = Qt::MidButton;
89 break;
90 default:
91 mouseButton = Qt::LeftButton;
92 break;
93 }
94
95 m_mouseButtons |= mouseButton;
96
97 // qDebug() << "EventSender::mouseDown" << frame;
98 QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonPress, m_mousePos, m_mousePos, mouseButton, m_mouseButtons, Qt::NoModifier);
99 sendOrQueueEvent(event);
100 }
101
mouseUp(int button)102 void EventSender::mouseUp(int button)
103 {
104 Qt::MouseButton mouseButton;
105 switch (button) {
106 case 0:
107 mouseButton = Qt::LeftButton;
108 break;
109 case 1:
110 mouseButton = Qt::MidButton;
111 break;
112 case 2:
113 mouseButton = Qt::RightButton;
114 break;
115 case 3:
116 // fast/events/mouse-click-events expects the 4th button to be treated as the middle button
117 mouseButton = Qt::MidButton;
118 break;
119 default:
120 mouseButton = Qt::LeftButton;
121 break;
122 }
123
124 m_mouseButtons &= ~mouseButton;
125
126 // qDebug() << "EventSender::mouseUp" << frame;
127 QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonRelease, m_mousePos, m_mousePos, mouseButton, m_mouseButtons, Qt::NoModifier);
128 sendOrQueueEvent(event);
129 }
130
mouseMoveTo(int x,int y)131 void EventSender::mouseMoveTo(int x, int y)
132 {
133 // qDebug() << "EventSender::mouseMoveTo" << x << y;
134 m_mousePos = QPoint(x, y);
135 QMouseEvent* event = new QMouseEvent(QEvent::MouseMove, m_mousePos, m_mousePos, Qt::NoButton, m_mouseButtons, Qt::NoModifier);
136 sendOrQueueEvent(event);
137 }
138
leapForward(int ms)139 void EventSender::leapForward(int ms)
140 {
141 eventQueue[endOfQueue].m_delay = ms;
142 //qDebug() << "EventSender::leapForward" << ms;
143 }
144
keyDown(const QString & string,const QStringList & modifiers,unsigned int location)145 void EventSender::keyDown(const QString& string, const QStringList& modifiers, unsigned int location)
146 {
147 QString s = string;
148 Qt::KeyboardModifiers modifs = 0;
149 for (int i = 0; i < modifiers.size(); ++i) {
150 const QString& m = modifiers.at(i);
151 if (m == "ctrlKey")
152 modifs |= Qt::ControlModifier;
153 else if (m == "shiftKey")
154 modifs |= Qt::ShiftModifier;
155 else if (m == "altKey")
156 modifs |= Qt::AltModifier;
157 else if (m == "metaKey")
158 modifs |= Qt::MetaModifier;
159 }
160 if (location == 3)
161 modifs |= Qt::KeypadModifier;
162 int code = 0;
163 if (string.length() == 1) {
164 code = string.unicode()->unicode();
165 //qDebug() << ">>>>>>>>> keyDown" << code << (char)code;
166 // map special keycodes used by the tests to something that works for Qt/X11
167 if (code == '\r') {
168 code = Qt::Key_Return;
169 } else if (code == '\t') {
170 code = Qt::Key_Tab;
171 if (modifs == Qt::ShiftModifier)
172 code = Qt::Key_Backtab;
173 s = QString();
174 } else if (code == KEYCODE_DEL || code == KEYCODE_BACKSPACE) {
175 code = Qt::Key_Backspace;
176 if (modifs == Qt::AltModifier)
177 modifs = Qt::ControlModifier;
178 s = QString();
179 } else if (code == 'o' && modifs == Qt::ControlModifier) {
180 s = QLatin1String("\n");
181 code = '\n';
182 modifs = 0;
183 } else if (code == 'y' && modifs == Qt::ControlModifier) {
184 s = QLatin1String("c");
185 code = 'c';
186 } else if (code == 'k' && modifs == Qt::ControlModifier) {
187 s = QLatin1String("x");
188 code = 'x';
189 } else if (code == 'a' && modifs == Qt::ControlModifier) {
190 s = QString();
191 code = Qt::Key_Home;
192 modifs = 0;
193 } else if (code == KEYCODE_LEFTARROW) {
194 s = QString();
195 code = Qt::Key_Left;
196 if (modifs & Qt::MetaModifier) {
197 code = Qt::Key_Home;
198 modifs &= ~Qt::MetaModifier;
199 }
200 } else if (code == KEYCODE_RIGHTARROW) {
201 s = QString();
202 code = Qt::Key_Right;
203 if (modifs & Qt::MetaModifier) {
204 code = Qt::Key_End;
205 modifs &= ~Qt::MetaModifier;
206 }
207 } else if (code == KEYCODE_UPARROW) {
208 s = QString();
209 code = Qt::Key_Up;
210 if (modifs & Qt::MetaModifier) {
211 code = Qt::Key_PageUp;
212 modifs &= ~Qt::MetaModifier;
213 }
214 } else if (code == KEYCODE_DOWNARROW) {
215 s = QString();
216 code = Qt::Key_Down;
217 if (modifs & Qt::MetaModifier) {
218 code = Qt::Key_PageDown;
219 modifs &= ~Qt::MetaModifier;
220 }
221 } else if (code == 'a' && modifs == Qt::ControlModifier) {
222 s = QString();
223 code = Qt::Key_Home;
224 modifs = 0;
225 } else
226 code = string.unicode()->toUpper().unicode();
227 } else {
228 //qDebug() << ">>>>>>>>> keyDown" << string;
229
230 if (string.startsWith(QLatin1Char('F')) && string.count() <= 3) {
231 s = s.mid(1);
232 int functionKey = s.toInt();
233 Q_ASSERT(functionKey >= 1 && functionKey <= 35);
234 code = Qt::Key_F1 + (functionKey - 1);
235 // map special keycode strings used by the tests to something that works for Qt/X11
236 } else if (string == QLatin1String("leftArrow")) {
237 s = QString();
238 code = Qt::Key_Left;
239 } else if (string == QLatin1String("rightArrow")) {
240 s = QString();
241 code = Qt::Key_Right;
242 } else if (string == QLatin1String("upArrow")) {
243 s = QString();
244 code = Qt::Key_Up;
245 } else if (string == QLatin1String("downArrow")) {
246 s = QString();
247 code = Qt::Key_Down;
248 } else if (string == QLatin1String("pageUp")) {
249 s = QString();
250 code = Qt::Key_PageUp;
251 } else if (string == QLatin1String("pageDown")) {
252 s = QString();
253 code = Qt::Key_PageDown;
254 } else if (string == QLatin1String("home")) {
255 s = QString();
256 code = Qt::Key_Home;
257 } else if (string == QLatin1String("end")) {
258 s = QString();
259 code = Qt::Key_End;
260 } else if (string == QLatin1String("delete")) {
261 s = QString();
262 code = Qt::Key_Delete;
263 }
264 }
265 QKeyEvent event(QEvent::KeyPress, code, modifs, s);
266 QApplication::sendEvent(m_page, &event);
267 QKeyEvent event2(QEvent::KeyRelease, code, modifs, s);
268 QApplication::sendEvent(m_page, &event2);
269 }
270
contextClick()271 void EventSender::contextClick()
272 {
273 QMouseEvent event(QEvent::MouseButtonPress, m_mousePos, Qt::RightButton, Qt::RightButton, Qt::NoModifier);
274 QApplication::sendEvent(m_page, &event);
275 QMouseEvent event2(QEvent::MouseButtonRelease, m_mousePos, Qt::RightButton, Qt::RightButton, Qt::NoModifier);
276 QApplication::sendEvent(m_page, &event2);
277 }
278
scheduleAsynchronousClick()279 void EventSender::scheduleAsynchronousClick()
280 {
281 QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonPress, m_mousePos, Qt::LeftButton, Qt::RightButton, Qt::NoModifier);
282 QApplication::postEvent(m_page, event);
283 QMouseEvent* event2 = new QMouseEvent(QEvent::MouseButtonRelease, m_mousePos, Qt::LeftButton, Qt::RightButton, Qt::NoModifier);
284 QApplication::postEvent(m_page, event2);
285 }
286
addTouchPoint(int x,int y)287 void EventSender::addTouchPoint(int x, int y)
288 {
289 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
290 int id = m_touchPoints.count();
291 QTouchEvent::TouchPoint point(id);
292 m_touchPoints.append(point);
293 updateTouchPoint(id, x, y);
294 m_touchPoints[id].setState(Qt::TouchPointPressed);
295 #endif
296 }
297
updateTouchPoint(int index,int x,int y)298 void EventSender::updateTouchPoint(int index, int x, int y)
299 {
300 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
301 if (index < 0 || index >= m_touchPoints.count())
302 return;
303
304 QTouchEvent::TouchPoint &p = m_touchPoints[index];
305 p.setPos(QPointF(x, y));
306 p.setState(Qt::TouchPointMoved);
307 #endif
308 }
309
setTouchModifier(const QString & modifier,bool enable)310 void EventSender::setTouchModifier(const QString &modifier, bool enable)
311 {
312 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
313 Qt::KeyboardModifier mod = Qt::NoModifier;
314 if (!modifier.compare(QLatin1String("shift"), Qt::CaseInsensitive))
315 mod = Qt::ShiftModifier;
316 else if (!modifier.compare(QLatin1String("alt"), Qt::CaseInsensitive))
317 mod = Qt::AltModifier;
318 else if (!modifier.compare(QLatin1String("meta"), Qt::CaseInsensitive))
319 mod = Qt::MetaModifier;
320 else if (!modifier.compare(QLatin1String("ctrl"), Qt::CaseInsensitive))
321 mod = Qt::ControlModifier;
322
323 if (enable)
324 m_touchModifiers |= mod;
325 else
326 m_touchModifiers &= ~mod;
327 #endif
328 }
329
touchStart()330 void EventSender::touchStart()
331 {
332 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
333 if (!m_touchActive) {
334 sendTouchEvent(QEvent::TouchBegin);
335 m_touchActive = true;
336 } else
337 sendTouchEvent(QEvent::TouchUpdate);
338 #endif
339 }
340
touchMove()341 void EventSender::touchMove()
342 {
343 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
344 sendTouchEvent(QEvent::TouchUpdate);
345 #endif
346 }
347
touchEnd()348 void EventSender::touchEnd()
349 {
350 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
351 for (int i = 0; i < m_touchPoints.count(); ++i)
352 if (m_touchPoints[i].state() != Qt::TouchPointReleased) {
353 sendTouchEvent(QEvent::TouchUpdate);
354 return;
355 }
356 sendTouchEvent(QEvent::TouchEnd);
357 m_touchActive = false;
358 #endif
359 }
360
clearTouchPoints()361 void EventSender::clearTouchPoints()
362 {
363 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
364 m_touchPoints.clear();
365 m_touchModifiers = Qt::KeyboardModifiers();
366 m_touchActive = false;
367 #endif
368 }
369
releaseTouchPoint(int index)370 void EventSender::releaseTouchPoint(int index)
371 {
372 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
373 if (index < 0 || index >= m_touchPoints.count())
374 return;
375
376 m_touchPoints[index].setState(Qt::TouchPointReleased);
377 #endif
378 }
379
sendTouchEvent(QEvent::Type type)380 void EventSender::sendTouchEvent(QEvent::Type type)
381 {
382 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
383 QTouchEvent event(type, QTouchEvent::TouchScreen, m_touchModifiers);
384 event.setTouchPoints(m_touchPoints);
385 QApplication::sendEvent(m_page, &event);
386 QList<QTouchEvent::TouchPoint>::Iterator it = m_touchPoints.begin();
387 while (it != m_touchPoints.end()) {
388 if (it->state() == Qt::TouchPointReleased)
389 it = m_touchPoints.erase(it);
390 else {
391 it->setState(Qt::TouchPointStationary);
392 ++it;
393 }
394 }
395 #endif
396 }
397
zoomPageIn()398 void EventSender::zoomPageIn()
399 {
400 QWebFrame* frame = m_page->mainFrame();
401 if (frame)
402 frame->setZoomFactor(frame->zoomFactor() * ZOOM_STEP);
403 }
404
zoomPageOut()405 void EventSender::zoomPageOut()
406 {
407 QWebFrame* frame = m_page->mainFrame();
408 if (frame)
409 frame->setZoomFactor(frame->zoomFactor() / ZOOM_STEP);
410 }
411
frameUnderMouse() const412 QWebFrame* EventSender::frameUnderMouse() const
413 {
414 QWebFrame* frame = m_page->mainFrame();
415
416 redo:
417 QList<QWebFrame*> children = frame->childFrames();
418 for (int i = 0; i < children.size(); ++i) {
419 if (children.at(i)->geometry().contains(m_mousePos)) {
420 frame = children.at(i);
421 goto redo;
422 }
423 }
424 if (frame->geometry().contains(m_mousePos))
425 return frame;
426 return 0;
427 }
428
sendOrQueueEvent(QEvent * event)429 void EventSender::sendOrQueueEvent(QEvent* event)
430 {
431 // Mouse move events are queued if
432 // 1. A previous event was queued.
433 // 2. A delay was set-up by leapForward().
434 // 3. A call to mouseMoveTo while the mouse button is pressed could initiate a drag operation, and that does not return until mouseUp is processed.
435 // To be safe and avoid a deadlock, this event is queued.
436 if (endOfQueue == startOfQueue && !eventQueue[endOfQueue].m_delay && (!(m_mouseButtonPressed && (m_eventLoop && event->type() == QEvent::MouseButtonRelease)))) {
437 QApplication::sendEvent(m_page->view(), event);
438 delete event;
439 return;
440 }
441 eventQueue[endOfQueue++].m_event = event;
442 eventQueue[endOfQueue].m_delay = 0;
443 replaySavedEvents(event->type() != QEvent::MouseMove);
444 }
445
replaySavedEvents(bool flush)446 void EventSender::replaySavedEvents(bool flush)
447 {
448 if (startOfQueue < endOfQueue) {
449 // First send all the events that are ready to be sent
450 while (!eventQueue[startOfQueue].m_delay && startOfQueue < endOfQueue) {
451 QEvent* ev = eventQueue[startOfQueue++].m_event;
452 QApplication::postEvent(m_page->view(), ev); // ev deleted by the system
453 }
454 if (startOfQueue == endOfQueue) {
455 // Reset the queue
456 startOfQueue = 0;
457 endOfQueue = 0;
458 } else {
459 QTest::qWait(eventQueue[startOfQueue].m_delay);
460 eventQueue[startOfQueue].m_delay = 0;
461 }
462 }
463 if (!flush)
464 return;
465
466 // Send a marker event, it will tell us when it is safe to exit the new event loop
467 QEvent* drtEvent = new QEvent((QEvent::Type)DRT_MESSAGE_DONE);
468 QApplication::postEvent(m_page->view(), drtEvent);
469
470 // Start an event loop for async handling of Drag & Drop
471 m_eventLoop = new QEventLoop;
472 m_eventLoop->exec();
473 delete m_eventLoop;
474 m_eventLoop = 0;
475 }
476
eventFilter(QObject * watched,QEvent * event)477 bool EventSender::eventFilter(QObject* watched, QEvent* event)
478 {
479 if (watched != m_page->view())
480 return false;
481 switch (event->type()) {
482 case QEvent::Leave:
483 return true;
484 case QEvent::MouseButtonPress:
485 m_mouseButtonPressed = true;
486 break;
487 case QEvent::MouseMove:
488 if (m_mouseButtonPressed)
489 m_drag = true;
490 break;
491 case QEvent::MouseButtonRelease:
492 m_mouseButtonPressed = false;
493 m_drag = false;
494 break;
495 case DRT_MESSAGE_DONE:
496 m_eventLoop->exit();
497 return true;
498 }
499 return false;
500 }
501