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
51struct DRTEventQueue {
52    QEvent* m_event;
53    int m_delay;
54};
55
56static DRTEventQueue eventQueue[1024];
57static unsigned endOfQueue;
58static unsigned startOfQueue;
59
60EventSender::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
73void 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
102void 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
131void 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
139void EventSender::leapForward(int ms)
140{
141    eventQueue[endOfQueue].m_delay = ms;
142    //qDebug() << "EventSender::leapForward" << ms;
143}
144
145void 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
271void 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
279void 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
287void 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
298void 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
310void 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
330void 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
341void EventSender::touchMove()
342{
343#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
344    sendTouchEvent(QEvent::TouchUpdate);
345#endif
346}
347
348void 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
361void 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
370void 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
380void 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
398void EventSender::zoomPageIn()
399{
400    QWebFrame* frame = m_page->mainFrame();
401    if (frame)
402        frame->setZoomFactor(frame->zoomFactor() * ZOOM_STEP);
403}
404
405void EventSender::zoomPageOut()
406{
407    QWebFrame* frame = m_page->mainFrame();
408    if (frame)
409        frame->setZoomFactor(frame->zoomFactor() / ZOOM_STEP);
410}
411
412QWebFrame* EventSender::frameUnderMouse() const
413{
414    QWebFrame* frame = m_page->mainFrame();
415
416redo:
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
429void 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
446void 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
477bool 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