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 } 278 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 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 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 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 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 341 void EventSender::touchMove() 342 { 343 #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) 344 sendTouchEvent(QEvent::TouchUpdate); 345 #endif 346 } 347 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 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 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 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 398 void EventSender::zoomPageIn() 399 { 400 QWebFrame* frame = m_page->mainFrame(); 401 if (frame) 402 frame->setZoomFactor(frame->zoomFactor() * ZOOM_STEP); 403 } 404 405 void EventSender::zoomPageOut() 406 { 407 QWebFrame* frame = m_page->mainFrame(); 408 if (frame) 409 frame->setZoomFactor(frame->zoomFactor() / ZOOM_STEP); 410 } 411 412 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 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 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 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