[Qt] Implement textZoomIn() and textZoomOut() in DRT's EventSender, add results
[WebKit-https.git] / WebKitTools / DumpRenderTree / qt / EventSenderQt.cpp
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
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
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
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
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
139 void EventSender::leapForward(int ms)
140 {
141     eventQueue[endOfQueue].m_delay = ms;
142     //qDebug() << "EventSender::leapForward" << ms;
143 }
144
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
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     QContextMenuEvent event3(QContextMenuEvent::Mouse, m_mousePos);
278     QApplication::sendEvent(m_page->view(), &event3);
279 }
280
281 void EventSender::scheduleAsynchronousClick()
282 {
283     QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonPress, m_mousePos, Qt::LeftButton, Qt::RightButton, Qt::NoModifier);
284     QApplication::postEvent(m_page, event);
285     QMouseEvent* event2 = new QMouseEvent(QEvent::MouseButtonRelease, m_mousePos, Qt::LeftButton, Qt::RightButton, Qt::NoModifier);
286     QApplication::postEvent(m_page, event2);
287 }
288
289 void EventSender::addTouchPoint(int x, int y)
290 {
291 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
292     int id = m_touchPoints.count();
293     QTouchEvent::TouchPoint point(id);
294     m_touchPoints.append(point);
295     updateTouchPoint(id, x, y);
296     m_touchPoints[id].setState(Qt::TouchPointPressed);
297 #endif
298 }
299
300 void EventSender::updateTouchPoint(int index, int x, int y)
301 {
302 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
303     if (index < 0 || index >= m_touchPoints.count())
304         return;
305
306     QTouchEvent::TouchPoint &p = m_touchPoints[index];
307     p.setPos(QPointF(x, y));
308     p.setState(Qt::TouchPointMoved);
309 #endif
310 }
311
312 void EventSender::setTouchModifier(const QString &modifier, bool enable)
313 {
314 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
315     Qt::KeyboardModifier mod = Qt::NoModifier;
316     if (!modifier.compare(QLatin1String("shift"), Qt::CaseInsensitive))
317         mod = Qt::ShiftModifier;
318     else if (!modifier.compare(QLatin1String("alt"), Qt::CaseInsensitive))
319         mod = Qt::AltModifier;
320     else if (!modifier.compare(QLatin1String("meta"), Qt::CaseInsensitive))
321         mod = Qt::MetaModifier;
322     else if (!modifier.compare(QLatin1String("ctrl"), Qt::CaseInsensitive))
323         mod = Qt::ControlModifier;
324
325     if (enable)
326         m_touchModifiers |= mod;
327     else
328         m_touchModifiers &= ~mod;
329 #endif
330 }
331
332 void EventSender::touchStart()
333 {
334 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
335     if (!m_touchActive) {
336         sendTouchEvent(QEvent::TouchBegin);
337         m_touchActive = true;
338     } else
339         sendTouchEvent(QEvent::TouchUpdate);
340 #endif
341 }
342
343 void EventSender::touchMove()
344 {
345 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
346     sendTouchEvent(QEvent::TouchUpdate);
347 #endif
348 }
349
350 void EventSender::touchEnd()
351 {
352 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
353     for (int i = 0; i < m_touchPoints.count(); ++i)
354         if (m_touchPoints[i].state() != Qt::TouchPointReleased) {
355             sendTouchEvent(QEvent::TouchUpdate);
356             return;
357         }
358     sendTouchEvent(QEvent::TouchEnd);
359     m_touchActive = false;
360 #endif
361 }
362
363 void EventSender::clearTouchPoints()
364 {
365 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
366     m_touchPoints.clear();
367     m_touchModifiers = Qt::KeyboardModifiers();
368     m_touchActive = false;
369 #endif
370 }
371
372 void EventSender::releaseTouchPoint(int index)
373 {
374 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
375     if (index < 0 || index >= m_touchPoints.count())
376         return;
377
378     m_touchPoints[index].setState(Qt::TouchPointReleased);
379 #endif
380 }
381
382 void EventSender::sendTouchEvent(QEvent::Type type)
383 {
384 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
385     QTouchEvent event(type, QTouchEvent::TouchScreen, m_touchModifiers);
386     event.setTouchPoints(m_touchPoints);
387     QApplication::sendEvent(m_page, &event);
388     QList<QTouchEvent::TouchPoint>::Iterator it = m_touchPoints.begin();
389     while (it != m_touchPoints.end()) {
390         if (it->state() == Qt::TouchPointReleased)
391             it = m_touchPoints.erase(it);
392         else {
393             it->setState(Qt::TouchPointStationary);
394             ++it;
395         }
396     }
397 #endif
398 }
399
400 void EventSender::zoomPageIn()
401 {
402     if (QWebFrame* frame = m_page->mainFrame())
403         frame->setZoomFactor(frame->zoomFactor() * ZOOM_STEP);
404 }
405
406 void EventSender::zoomPageOut()
407 {
408     if (QWebFrame* frame = m_page->mainFrame())
409         frame->setZoomFactor(frame->zoomFactor() / ZOOM_STEP);
410 }
411
412 void EventSender::textZoomIn()
413 {
414     if (QWebFrame* frame = m_page->mainFrame())
415         frame->setTextSizeMultiplier(frame->textSizeMultiplier() * ZOOM_STEP);
416 }
417
418 void EventSender::textZoomOut()
419 {
420     if (QWebFrame* frame = m_page->mainFrame())
421         frame->setTextSizeMultiplier(frame->textSizeMultiplier() / ZOOM_STEP);
422 }
423
424 QWebFrame* EventSender::frameUnderMouse() const
425 {
426     QWebFrame* frame = m_page->mainFrame();
427
428 redo:
429     QList<QWebFrame*> children = frame->childFrames();
430     for (int i = 0; i < children.size(); ++i) {
431         if (children.at(i)->geometry().contains(m_mousePos)) {
432             frame = children.at(i);
433             goto redo;
434         }
435     }
436     if (frame->geometry().contains(m_mousePos))
437         return frame;
438     return 0;
439 }
440
441 void EventSender::sendOrQueueEvent(QEvent* event)
442 {
443     // Mouse move events are queued if 
444     // 1. A previous event was queued.
445     // 2. A delay was set-up by leapForward().
446     // 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. 
447     // To be safe and avoid a deadlock, this event is queued.
448     if (endOfQueue == startOfQueue && !eventQueue[endOfQueue].m_delay && (!(m_mouseButtonPressed && (m_eventLoop && event->type() == QEvent::MouseButtonRelease)))) {
449         QApplication::sendEvent(m_page->view(), event);
450         delete event;
451         return;
452     }
453     eventQueue[endOfQueue++].m_event = event;
454     eventQueue[endOfQueue].m_delay = 0;
455     replaySavedEvents(event->type() != QEvent::MouseMove);
456 }
457
458 void EventSender::replaySavedEvents(bool flush)
459 {
460     if (startOfQueue < endOfQueue) {
461         // First send all the events that are ready to be sent
462         while (!eventQueue[startOfQueue].m_delay && startOfQueue < endOfQueue) {
463             QEvent* ev = eventQueue[startOfQueue++].m_event;
464             QApplication::postEvent(m_page->view(), ev); // ev deleted by the system
465         }
466         if (startOfQueue == endOfQueue) {
467             // Reset the queue
468             startOfQueue = 0;
469             endOfQueue = 0;
470         } else {
471             QTest::qWait(eventQueue[startOfQueue].m_delay);
472             eventQueue[startOfQueue].m_delay = 0;
473         }
474     }
475     if (!flush)
476         return;
477
478     // Send a marker event, it will tell us when it is safe to exit the new event loop
479     QEvent* drtEvent = new QEvent((QEvent::Type)DRT_MESSAGE_DONE);
480     QApplication::postEvent(m_page->view(), drtEvent);
481
482     // Start an event loop for async handling of Drag & Drop
483     m_eventLoop = new QEventLoop;
484     m_eventLoop->exec();
485     delete m_eventLoop;
486     m_eventLoop = 0;
487 }
488
489 bool EventSender::eventFilter(QObject* watched, QEvent* event)
490 {
491     if (watched != m_page->view())
492         return false;
493     switch (event->type()) {
494     case QEvent::Leave:
495         return true;
496     case QEvent::MouseButtonPress:
497         m_mouseButtonPressed = true;
498         break;
499     case QEvent::MouseMove:
500         if (m_mouseButtonPressed)
501             m_drag = true;
502         break;
503     case QEvent::MouseButtonRelease:
504         m_mouseButtonPressed = false;
505         m_drag = false;
506         break;
507     case DRT_MESSAGE_DONE:
508         m_eventLoop->exit();
509         return true;
510     }
511     return false;
512 }