diff -r 000000000000 -r 4f2f89ce4247 WebKitTools/DumpRenderTree/qt/EventSenderQt.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKitTools/DumpRenderTree/qt/EventSenderQt.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "config.h" +#include "EventSenderQt.h" + +#include +#include + +#define KEYCODE_DEL 127 +#define KEYCODE_BACKSPACE 8 +#define KEYCODE_LEFTARROW 0xf702 +#define KEYCODE_RIGHTARROW 0xf703 +#define KEYCODE_UPARROW 0xf700 +#define KEYCODE_DOWNARROW 0xf701 + +// Ports like Gtk and Windows expose a different approach for their zooming +// API if compared to Qt: they have specific methods for zooming in and out, +// as well as a settable zoom factor, while Qt has only a 'setZoomValue' method. +// Hence Qt DRT adopts a fixed zoom-factor (1.2) for compatibility. +#define ZOOM_STEP 1.2 + +#define DRT_MESSAGE_DONE (QEvent::User + 1) + +struct DRTEventQueue { + QEvent* m_event; + int m_delay; +}; + +static DRTEventQueue eventQueue[1024]; +static unsigned endOfQueue; +static unsigned startOfQueue; + +EventSender::EventSender(QWebPage* parent) + : QObject(parent) +{ + m_page = parent; + m_mouseButtonPressed = false; + m_drag = false; + memset(eventQueue, 0, sizeof(eventQueue)); + endOfQueue = 0; + startOfQueue = 0; + m_eventLoop = 0; + m_currentButton = 0; + resetClickCount(); + m_page->view()->installEventFilter(this); + // So that we can match Scrollbar::pixelsPerLineStep() in WheelEventQt.cpp and + // pass fast/events/platform-wheelevent-in-scrolling-div.html + QApplication::setWheelScrollLines(2); +} + +void EventSender::mouseDown(int button) +{ + Qt::MouseButton mouseButton; + switch (button) { + case 0: + mouseButton = Qt::LeftButton; + break; + case 1: + mouseButton = Qt::MidButton; + break; + case 2: + mouseButton = Qt::RightButton; + break; + case 3: + // fast/events/mouse-click-events expects the 4th button to be treated as the middle button + mouseButton = Qt::MidButton; + break; + default: + mouseButton = Qt::LeftButton; + break; + } + + // only consider a click to count, an event originated by the + // same previous button and at the same position. + if (m_currentButton == button + && m_mousePos == m_clickPos + && m_clickTimer.isActive()) + m_clickCount++; + else + m_clickCount = 1; + + m_currentButton = button; + m_clickPos = m_mousePos; + m_mouseButtons |= mouseButton; + +// qDebug() << "EventSender::mouseDown" << frame; + QEvent* event; + if (isGraphicsBased()) { + event = createGraphicsSceneMouseEvent((m_clickCount == 2) ? + QEvent::GraphicsSceneMouseDoubleClick : QEvent::GraphicsSceneMousePress, + m_mousePos, m_mousePos, mouseButton, m_mouseButtons, Qt::NoModifier); + } else { + event = new QMouseEvent((m_clickCount == 2) ? QEvent::MouseButtonDblClick : + QEvent::MouseButtonPress, m_mousePos, m_mousePos, + mouseButton, m_mouseButtons, Qt::NoModifier); + } + + sendOrQueueEvent(event); + + m_clickTimer.start(QApplication::doubleClickInterval(), this); +} + +void EventSender::mouseUp(int button) +{ + Qt::MouseButton mouseButton; + switch (button) { + case 0: + mouseButton = Qt::LeftButton; + break; + case 1: + mouseButton = Qt::MidButton; + break; + case 2: + mouseButton = Qt::RightButton; + break; + case 3: + // fast/events/mouse-click-events expects the 4th button to be treated as the middle button + mouseButton = Qt::MidButton; + break; + default: + mouseButton = Qt::LeftButton; + break; + } + + m_mouseButtons &= ~mouseButton; + +// qDebug() << "EventSender::mouseUp" << frame; + QEvent* event; + if (isGraphicsBased()) { + event = createGraphicsSceneMouseEvent(QEvent::GraphicsSceneMouseRelease, + m_mousePos, m_mousePos, mouseButton, m_mouseButtons, Qt::NoModifier); + } else { + event = new QMouseEvent(QEvent::MouseButtonRelease, + m_mousePos, m_mousePos, mouseButton, m_mouseButtons, Qt::NoModifier); + } + + sendOrQueueEvent(event); +} + +void EventSender::mouseMoveTo(int x, int y) +{ +// qDebug() << "EventSender::mouseMoveTo" << x << y; + m_mousePos = QPoint(x, y); + + QEvent* event; + if (isGraphicsBased()) { + event = createGraphicsSceneMouseEvent(QEvent::GraphicsSceneMouseMove, + m_mousePos, m_mousePos, Qt::NoButton, m_mouseButtons, Qt::NoModifier); + } else { + event = new QMouseEvent(QEvent::MouseMove, + m_mousePos, m_mousePos, Qt::NoButton, m_mouseButtons, Qt::NoModifier); + } + + sendOrQueueEvent(event); +} + +#ifndef QT_NO_WHEELEVENT +void EventSender::mouseScrollBy(int x, int y) +{ + continuousMouseScrollBy((x*120), (y*120)); +} + +void EventSender::continuousMouseScrollBy(int x, int y) +{ + // continuousMouseScrollBy() mimics devices that send fine-grained scroll events where the 'delta' specified is not the usual + // multiple of 120. See http://doc.qt.nokia.com/4.6/qwheelevent.html#delta for a good explanation of this. + if (x) { + QEvent* event; + if (isGraphicsBased()) { + event = createGraphicsSceneWheelEvent(QEvent::GraphicsSceneWheel, + m_mousePos, m_mousePos, x, Qt::NoModifier, Qt::Horizontal); + } else + event = new QWheelEvent(m_mousePos, m_mousePos, x, m_mouseButtons, Qt::NoModifier, Qt::Horizontal); + + sendOrQueueEvent(event); + } + if (y) { + QEvent* event; + if (isGraphicsBased()) { + event = createGraphicsSceneWheelEvent(QEvent::GraphicsSceneWheel, + m_mousePos, m_mousePos, y, Qt::NoModifier, Qt::Vertical); + } else + event = new QWheelEvent(m_mousePos, m_mousePos, y, m_mouseButtons, Qt::NoModifier, Qt::Vertical); + + sendOrQueueEvent(event); + } +} +#endif + +void EventSender::leapForward(int ms) +{ + eventQueue[endOfQueue].m_delay = ms; + //qDebug() << "EventSender::leapForward" << ms; +} + +void EventSender::keyDown(const QString& string, const QStringList& modifiers, unsigned int location) +{ + QString s = string; + Qt::KeyboardModifiers modifs = 0; + for (int i = 0; i < modifiers.size(); ++i) { + const QString& m = modifiers.at(i); + if (m == "ctrlKey") + modifs |= Qt::ControlModifier; + else if (m == "shiftKey") + modifs |= Qt::ShiftModifier; + else if (m == "altKey") + modifs |= Qt::AltModifier; + else if (m == "metaKey") + modifs |= Qt::MetaModifier; + } + if (location == 3) + modifs |= Qt::KeypadModifier; + int code = 0; + if (string.length() == 1) { + code = string.unicode()->unicode(); + //qDebug() << ">>>>>>>>> keyDown" << code << (char)code; + // map special keycodes used by the tests to something that works for Qt/X11 + if (code == '\r') { + code = Qt::Key_Return; + } else if (code == '\t') { + code = Qt::Key_Tab; + if (modifs == Qt::ShiftModifier) + code = Qt::Key_Backtab; + s = QString(); + } else if (code == KEYCODE_DEL || code == KEYCODE_BACKSPACE) { + code = Qt::Key_Backspace; + if (modifs == Qt::AltModifier) + modifs = Qt::ControlModifier; + s = QString(); + } else if (code == 'o' && modifs == Qt::ControlModifier) { + s = QLatin1String("\n"); + code = '\n'; + modifs = 0; + } else if (code == 'y' && modifs == Qt::ControlModifier) { + s = QLatin1String("c"); + code = 'c'; + } else if (code == 'k' && modifs == Qt::ControlModifier) { + s = QLatin1String("x"); + code = 'x'; + } else if (code == 'a' && modifs == Qt::ControlModifier) { + s = QString(); + code = Qt::Key_Home; + modifs = 0; + } else if (code == KEYCODE_LEFTARROW) { + s = QString(); + code = Qt::Key_Left; + if (modifs & Qt::MetaModifier) { + code = Qt::Key_Home; + modifs &= ~Qt::MetaModifier; + } + } else if (code == KEYCODE_RIGHTARROW) { + s = QString(); + code = Qt::Key_Right; + if (modifs & Qt::MetaModifier) { + code = Qt::Key_End; + modifs &= ~Qt::MetaModifier; + } + } else if (code == KEYCODE_UPARROW) { + s = QString(); + code = Qt::Key_Up; + if (modifs & Qt::MetaModifier) { + code = Qt::Key_PageUp; + modifs &= ~Qt::MetaModifier; + } + } else if (code == KEYCODE_DOWNARROW) { + s = QString(); + code = Qt::Key_Down; + if (modifs & Qt::MetaModifier) { + code = Qt::Key_PageDown; + modifs &= ~Qt::MetaModifier; + } + } else if (code == 'a' && modifs == Qt::ControlModifier) { + s = QString(); + code = Qt::Key_Home; + modifs = 0; + } else + code = string.unicode()->toUpper().unicode(); + } else { + //qDebug() << ">>>>>>>>> keyDown" << string; + + if (string.startsWith(QLatin1Char('F')) && string.count() <= 3) { + s = s.mid(1); + int functionKey = s.toInt(); + Q_ASSERT(functionKey >= 1 && functionKey <= 35); + code = Qt::Key_F1 + (functionKey - 1); + // map special keycode strings used by the tests to something that works for Qt/X11 + } else if (string == QLatin1String("leftArrow")) { + s = QString(); + code = Qt::Key_Left; + } else if (string == QLatin1String("rightArrow")) { + s = QString(); + code = Qt::Key_Right; + } else if (string == QLatin1String("upArrow")) { + s = QString(); + code = Qt::Key_Up; + } else if (string == QLatin1String("downArrow")) { + s = QString(); + code = Qt::Key_Down; + } else if (string == QLatin1String("pageUp")) { + s = QString(); + code = Qt::Key_PageUp; + } else if (string == QLatin1String("pageDown")) { + s = QString(); + code = Qt::Key_PageDown; + } else if (string == QLatin1String("home")) { + s = QString(); + code = Qt::Key_Home; + } else if (string == QLatin1String("end")) { + s = QString(); + code = Qt::Key_End; + } else if (string == QLatin1String("delete")) { + s = QString(); + code = Qt::Key_Delete; + } + } + QKeyEvent event(QEvent::KeyPress, code, modifs, s); + sendEvent(m_page, &event); + QKeyEvent event2(QEvent::KeyRelease, code, modifs, s); + sendEvent(m_page, &event2); +} + +void EventSender::contextClick() +{ + QMouseEvent event(QEvent::MouseButtonPress, m_mousePos, Qt::RightButton, Qt::RightButton, Qt::NoModifier); + sendEvent(m_page, &event); + QMouseEvent event2(QEvent::MouseButtonRelease, m_mousePos, Qt::RightButton, Qt::RightButton, Qt::NoModifier); + sendEvent(m_page, &event2); + + if (isGraphicsBased()) { + QGraphicsSceneContextMenuEvent ctxEvent(QEvent::GraphicsSceneContextMenu); + ctxEvent.setReason(QGraphicsSceneContextMenuEvent::Mouse); + ctxEvent.setPos(m_mousePos); + WebCore::WebViewGraphicsBased* view = qobject_cast(m_page->view()); + if (view) + sendEvent(view->graphicsView(), &ctxEvent); + } else { + QContextMenuEvent ctxEvent(QContextMenuEvent::Mouse, m_mousePos); + sendEvent(m_page->view(), &ctxEvent); + } +} + +void EventSender::scheduleAsynchronousClick() +{ + QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonPress, m_mousePos, Qt::LeftButton, Qt::RightButton, Qt::NoModifier); + postEvent(m_page, event); + QMouseEvent* event2 = new QMouseEvent(QEvent::MouseButtonRelease, m_mousePos, Qt::LeftButton, Qt::RightButton, Qt::NoModifier); + postEvent(m_page, event2); +} + +void EventSender::addTouchPoint(int x, int y) +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + // Use index to refer to the position in the vector that this touch + // is stored. We then create a unique id for the touch that will be + // passed into WebCore. + int index = m_touchPoints.count(); + int id = m_touchPoints.isEmpty() ? 0 : m_touchPoints.last().id() + 1; + QTouchEvent::TouchPoint point(id); + m_touchPoints.append(point); + updateTouchPoint(index, x, y); + m_touchPoints[index].setState(Qt::TouchPointPressed); +#endif +} + +void EventSender::updateTouchPoint(int index, int x, int y) +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + if (index < 0 || index >= m_touchPoints.count()) + return; + + QTouchEvent::TouchPoint &p = m_touchPoints[index]; + p.setPos(QPointF(x, y)); + p.setState(Qt::TouchPointMoved); +#endif +} + +void EventSender::setTouchModifier(const QString &modifier, bool enable) +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + Qt::KeyboardModifier mod = Qt::NoModifier; + if (!modifier.compare(QLatin1String("shift"), Qt::CaseInsensitive)) + mod = Qt::ShiftModifier; + else if (!modifier.compare(QLatin1String("alt"), Qt::CaseInsensitive)) + mod = Qt::AltModifier; + else if (!modifier.compare(QLatin1String("meta"), Qt::CaseInsensitive)) + mod = Qt::MetaModifier; + else if (!modifier.compare(QLatin1String("ctrl"), Qt::CaseInsensitive)) + mod = Qt::ControlModifier; + + if (enable) + m_touchModifiers |= mod; + else + m_touchModifiers &= ~mod; +#endif +} + +void EventSender::touchStart() +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + if (!m_touchActive) { + sendTouchEvent(QEvent::TouchBegin); + m_touchActive = true; + } else + sendTouchEvent(QEvent::TouchUpdate); +#endif +} + +void EventSender::touchMove() +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + sendTouchEvent(QEvent::TouchUpdate); +#endif +} + +void EventSender::touchEnd() +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + for (int i = 0; i < m_touchPoints.count(); ++i) + if (m_touchPoints[i].state() != Qt::TouchPointReleased) { + sendTouchEvent(QEvent::TouchUpdate); + return; + } + sendTouchEvent(QEvent::TouchEnd); + m_touchActive = false; +#endif +} + +void EventSender::clearTouchPoints() +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + m_touchPoints.clear(); + m_touchModifiers = Qt::KeyboardModifiers(); + m_touchActive = false; +#endif +} + +void EventSender::releaseTouchPoint(int index) +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + if (index < 0 || index >= m_touchPoints.count()) + return; + + m_touchPoints[index].setState(Qt::TouchPointReleased); +#endif +} + +void EventSender::sendTouchEvent(QEvent::Type type) +{ +#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) + QTouchEvent event(type, QTouchEvent::TouchScreen, m_touchModifiers); + event.setTouchPoints(m_touchPoints); + sendEvent(m_page, &event); + QList::Iterator it = m_touchPoints.begin(); + while (it != m_touchPoints.end()) { + if (it->state() == Qt::TouchPointReleased) + it = m_touchPoints.erase(it); + else { + it->setState(Qt::TouchPointStationary); + ++it; + } + } +#endif +} + +void EventSender::zoomPageIn() +{ + if (QWebFrame* frame = m_page->mainFrame()) + frame->setZoomFactor(frame->zoomFactor() * ZOOM_STEP); +} + +void EventSender::zoomPageOut() +{ + if (QWebFrame* frame = m_page->mainFrame()) + frame->setZoomFactor(frame->zoomFactor() / ZOOM_STEP); +} + +void EventSender::textZoomIn() +{ + if (QWebFrame* frame = m_page->mainFrame()) + frame->setTextSizeMultiplier(frame->textSizeMultiplier() * ZOOM_STEP); +} + +void EventSender::textZoomOut() +{ + if (QWebFrame* frame = m_page->mainFrame()) + frame->setTextSizeMultiplier(frame->textSizeMultiplier() / ZOOM_STEP); +} + +QWebFrame* EventSender::frameUnderMouse() const +{ + QWebFrame* frame = m_page->mainFrame(); + +redo: + QList children = frame->childFrames(); + for (int i = 0; i < children.size(); ++i) { + if (children.at(i)->geometry().contains(m_mousePos)) { + frame = children.at(i); + goto redo; + } + } + if (frame->geometry().contains(m_mousePos)) + return frame; + return 0; +} + +void EventSender::sendOrQueueEvent(QEvent* event) +{ + // Mouse move events are queued if + // 1. A previous event was queued. + // 2. A delay was set-up by leapForward(). + // 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. + // To be safe and avoid a deadlock, this event is queued. + if (endOfQueue == startOfQueue && !eventQueue[endOfQueue].m_delay && (!(m_mouseButtonPressed && (m_eventLoop && event->type() == QEvent::MouseButtonRelease)))) { + sendEvent(m_page->view(), event); + delete event; + return; + } + eventQueue[endOfQueue++].m_event = event; + eventQueue[endOfQueue].m_delay = 0; + replaySavedEvents(event->type() != QEvent::MouseMove); +} + +void EventSender::replaySavedEvents(bool flush) +{ + if (startOfQueue < endOfQueue) { + // First send all the events that are ready to be sent + while (!eventQueue[startOfQueue].m_delay && startOfQueue < endOfQueue) { + QEvent* ev = eventQueue[startOfQueue++].m_event; + postEvent(m_page->view(), ev); + } + if (startOfQueue == endOfQueue) { + // Reset the queue + startOfQueue = 0; + endOfQueue = 0; + } else { + QTest::qWait(eventQueue[startOfQueue].m_delay); + eventQueue[startOfQueue].m_delay = 0; + } + } + if (!flush) + return; + + // Send a marker event, it will tell us when it is safe to exit the new event loop + QEvent* drtEvent = new QEvent((QEvent::Type)DRT_MESSAGE_DONE); + QApplication::postEvent(m_page->view(), drtEvent); + + // Start an event loop for async handling of Drag & Drop + m_eventLoop = new QEventLoop; + m_eventLoop->exec(); + delete m_eventLoop; + m_eventLoop = 0; +} + +bool EventSender::eventFilter(QObject* watched, QEvent* event) +{ + if (watched != m_page->view()) + return false; + switch (event->type()) { + case QEvent::Leave: + return true; + case QEvent::MouseButtonPress: + case QEvent::GraphicsSceneMousePress: + m_mouseButtonPressed = true; + break; + case QEvent::MouseMove: + case QEvent::GraphicsSceneMouseMove: + if (m_mouseButtonPressed) + m_drag = true; + break; + case QEvent::MouseButtonRelease: + case QEvent::GraphicsSceneMouseRelease: + m_mouseButtonPressed = false; + m_drag = false; + break; + case DRT_MESSAGE_DONE: + m_eventLoop->exit(); + return true; + } + return false; +} + +void EventSender::timerEvent(QTimerEvent* ev) +{ + m_clickTimer.stop(); +} + +QGraphicsSceneMouseEvent* EventSender::createGraphicsSceneMouseEvent(QEvent::Type type, const QPoint& pos, const QPoint& screenPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) +{ + QGraphicsSceneMouseEvent* event; + event = new QGraphicsSceneMouseEvent(type); + event->setPos(pos); + event->setScreenPos(screenPos); + event->setButton(button); + event->setButtons(buttons); + event->setModifiers(modifiers); + + return event; +} + +QGraphicsSceneWheelEvent* EventSender::createGraphicsSceneWheelEvent(QEvent::Type type, const QPoint& pos, const QPoint& screenPos, int delta, Qt::KeyboardModifiers modifiers, Qt::Orientation orientation) +{ + QGraphicsSceneWheelEvent* event; + event = new QGraphicsSceneWheelEvent(type); + event->setPos(pos); + event->setScreenPos(screenPos); + event->setDelta(delta); + event->setModifiers(modifiers); + event->setOrientation(orientation); + + return event; +} + +void EventSender::sendEvent(QObject* receiver, QEvent* event) +{ + if (WebCore::WebViewGraphicsBased* view = qobject_cast(receiver)) + view->scene()->sendEvent(view->graphicsView(), event); + else + QApplication::sendEvent(receiver, event); +} + +void EventSender::postEvent(QObject* receiver, QEvent* event) +{ + // QGraphicsScene does not have a postEvent method, so send the event in this case + // and delete it after that. + if (WebCore::WebViewGraphicsBased* view = qobject_cast(receiver)) { + view->scene()->sendEvent(view->graphicsView(), event); + delete event; + } else + QApplication::postEvent(receiver, event); // event deleted by the system +}