2 * Copyright (C) 2010, 2011 Nokia Corporation and/or its subsidiary(-ies)
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this program; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
22 #include "QtWebPageEventHandler.h"
24 #include "NativeWebKeyboardEvent.h"
25 #include "NativeWebMouseEvent.h"
26 #include "NativeWebWheelEvent.h"
27 #include "QtViewportInteractionEngine.h"
28 #include "qquickwebpage_p.h"
30 #include <QGraphicsSceneMouseEvent>
31 #include <QGuiApplication>
33 #include <QtQuick/QQuickCanvas>
34 #include <QStyleHints>
35 #include <QTextFormat>
36 #include <QTouchEvent>
37 #include <WebCore/DragData.h>
38 #include <WebCore/Editor.h>
40 using namespace WebKit;
41 using namespace WebCore;
43 static inline Qt::DropAction dragOperationToDropAction(unsigned dragOperation)
45 Qt::DropAction result = Qt::IgnoreAction;
46 if (dragOperation & DragOperationCopy)
47 result = Qt::CopyAction;
48 else if (dragOperation & DragOperationMove)
49 result = Qt::MoveAction;
50 else if (dragOperation & DragOperationGeneric)
51 result = Qt::MoveAction;
52 else if (dragOperation & DragOperationLink)
53 result = Qt::LinkAction;
57 static inline Qt::DropActions dragOperationToDropActions(unsigned dragOperations)
59 Qt::DropActions result = Qt::IgnoreAction;
60 if (dragOperations & DragOperationCopy)
61 result |= Qt::CopyAction;
62 if (dragOperations & DragOperationMove)
63 result |= Qt::MoveAction;
64 if (dragOperations & DragOperationGeneric)
65 result |= Qt::MoveAction;
66 if (dragOperations & DragOperationLink)
67 result |= Qt::LinkAction;
71 static inline WebCore::DragOperation dropActionToDragOperation(Qt::DropActions actions)
74 if (actions & Qt::CopyAction)
75 result |= DragOperationCopy;
76 if (actions & Qt::MoveAction)
77 result |= (DragOperationMove | DragOperationGeneric);
78 if (actions & Qt::LinkAction)
79 result |= DragOperationLink;
80 if (result == (DragOperationCopy | DragOperationMove | DragOperationGeneric | DragOperationLink))
81 result = DragOperationEvery;
82 return (DragOperation)result;
85 QtWebPageEventHandler::QtWebPageEventHandler(WKPageRef pageRef, QQuickWebPage* qmlWebPage)
86 : m_webPageProxy(toImpl(pageRef))
87 , m_panGestureRecognizer(this)
88 , m_pinchGestureRecognizer(this)
89 , m_tapGestureRecognizer(this)
90 , m_webPage(qmlWebPage)
91 , m_previousClickButton(Qt::NoButton)
96 QtWebPageEventHandler::~QtWebPageEventHandler()
100 bool QtWebPageEventHandler::handleEvent(QEvent* ev)
102 switch (ev->type()) {
103 case QEvent::MouseMove:
104 return handleMouseMoveEvent(static_cast<QMouseEvent*>(ev));
105 case QEvent::MouseButtonPress:
106 case QEvent::MouseButtonDblClick:
107 // If a MouseButtonDblClick was received then we got a MouseButtonPress before
108 // handleMousePressEvent will take care of double clicks.
109 return handleMousePressEvent(static_cast<QMouseEvent*>(ev));
110 case QEvent::MouseButtonRelease:
111 return handleMouseReleaseEvent(static_cast<QMouseEvent*>(ev));
113 return handleWheelEvent(static_cast<QWheelEvent*>(ev));
114 case QEvent::HoverLeave:
115 return handleHoverLeaveEvent(static_cast<QHoverEvent*>(ev));
116 case QEvent::HoverEnter: // Fall-through, for WebKit the distinction doesn't matter.
117 case QEvent::HoverMove:
118 return handleHoverMoveEvent(static_cast<QHoverEvent*>(ev));
119 case QEvent::DragEnter:
120 return handleDragEnterEvent(static_cast<QDragEnterEvent*>(ev));
121 case QEvent::DragLeave:
122 return handleDragLeaveEvent(static_cast<QDragLeaveEvent*>(ev));
123 case QEvent::DragMove:
124 return handleDragMoveEvent(static_cast<QDragMoveEvent*>(ev));
126 return handleDropEvent(static_cast<QDropEvent*>(ev));
127 case QEvent::KeyPress:
128 return handleKeyPressEvent(static_cast<QKeyEvent*>(ev));
129 case QEvent::KeyRelease:
130 return handleKeyReleaseEvent(static_cast<QKeyEvent*>(ev));
131 case QEvent::FocusIn:
132 return handleFocusInEvent(static_cast<QFocusEvent*>(ev));
133 case QEvent::FocusOut:
134 return handleFocusOutEvent(static_cast<QFocusEvent*>(ev));
135 case QEvent::TouchBegin:
136 case QEvent::TouchEnd:
137 case QEvent::TouchUpdate:
138 touchEvent(static_cast<QTouchEvent*>(ev));
140 case QEvent::InputMethod:
141 inputMethodEvent(static_cast<QInputMethodEvent*>(ev));
142 return false; // Look at comment in qquickwebpage.cpp
145 // FIXME: Move all common event handling here.
149 bool QtWebPageEventHandler::handleMouseMoveEvent(QMouseEvent* ev)
151 // For some reason mouse press results in mouse hover (which is
152 // converted to mouse move for WebKit). We ignore these hover
153 // events by comparing lastPos with newPos.
154 // NOTE: lastPos from the event always comes empty, so we work
156 static QPointF lastPos = QPointF();
157 if (lastPos == ev->pos())
158 return ev->isAccepted();
161 m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, /*eventClickCount*/ 0));
163 return ev->isAccepted();
166 bool QtWebPageEventHandler::handleMousePressEvent(QMouseEvent* ev)
168 if (m_clickTimer.isActive()
169 && m_previousClickButton == ev->button()
170 && (ev->pos() - m_lastClick).manhattanLength() < qApp->styleHints()->startDragDistance()) {
174 m_previousClickButton = ev->button();
177 m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, m_clickCount));
179 m_lastClick = ev->pos();
180 m_clickTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), this);
181 return ev->isAccepted();
184 bool QtWebPageEventHandler::handleMouseReleaseEvent(QMouseEvent* ev)
186 m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, /*eventClickCount*/ 0));
187 return ev->isAccepted();
190 bool QtWebPageEventHandler::handleWheelEvent(QWheelEvent* ev)
192 m_webPageProxy->handleWheelEvent(NativeWebWheelEvent(ev));
193 // FIXME: Handle whether the page used the wheel event or not.
194 if (m_interactionEngine)
195 m_interactionEngine->wheelEvent(ev);
196 return ev->isAccepted();
199 bool QtWebPageEventHandler::handleHoverLeaveEvent(QHoverEvent* ev)
201 // To get the correct behavior of mouseout, we need to turn the Leave event of our webview into a mouse move
202 // to a very far region.
203 QHoverEvent fakeEvent(QEvent::HoverMove, QPoint(INT_MIN, INT_MIN), ev->oldPos());
204 fakeEvent.setTimestamp(ev->timestamp());
205 return handleHoverMoveEvent(&fakeEvent);
208 bool QtWebPageEventHandler::handleHoverMoveEvent(QHoverEvent* ev)
210 QMouseEvent me(QEvent::MouseMove, ev->pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
211 me.setAccepted(ev->isAccepted());
212 me.setTimestamp(ev->timestamp());
214 return handleMouseMoveEvent(&me);
217 bool QtWebPageEventHandler::handleDragEnterEvent(QDragEnterEvent* ev)
219 m_webPageProxy->resetDragOperation();
220 // FIXME: Should not use QCursor::pos()
221 DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
222 m_webPageProxy->dragEntered(&dragData);
223 ev->acceptProposedAction();
227 bool QtWebPageEventHandler::handleDragLeaveEvent(QDragLeaveEvent* ev)
229 bool accepted = ev->isAccepted();
231 // FIXME: Should not use QCursor::pos()
232 DragData dragData(0, IntPoint(), QCursor::pos(), DragOperationNone);
233 m_webPageProxy->dragExited(&dragData);
234 m_webPageProxy->resetDragOperation();
236 ev->setAccepted(accepted);
240 bool QtWebPageEventHandler::handleDragMoveEvent(QDragMoveEvent* ev)
242 bool accepted = ev->isAccepted();
244 // FIXME: Should not use QCursor::pos()
245 DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
246 m_webPageProxy->dragUpdated(&dragData);
247 ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
248 if (m_webPageProxy->dragSession().operation != DragOperationNone)
251 ev->setAccepted(accepted);
255 bool QtWebPageEventHandler::handleDropEvent(QDropEvent* ev)
257 bool accepted = ev->isAccepted();
259 // FIXME: Should not use QCursor::pos()
260 DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
261 SandboxExtension::Handle handle;
262 m_webPageProxy->performDrag(&dragData, String(), handle);
263 ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
266 ev->setAccepted(accepted);
270 void QtWebPageEventHandler::handleSingleTapEvent(const QTouchEvent::TouchPoint& point)
272 WebGestureEvent gesture(WebEvent::GestureSingleTap, point.pos().toPoint(), point.screenPos().toPoint(), WebEvent::Modifiers(0), 0);
273 m_webPageProxy->handleGestureEvent(gesture);
276 void QtWebPageEventHandler::handleDoubleTapEvent(const QTouchEvent::TouchPoint& point)
278 m_webPageProxy->findZoomableAreaForPoint(point.pos().toPoint());
281 void QtWebPageEventHandler::timerEvent(QTimerEvent* ev)
283 int timerId = ev->timerId();
284 if (timerId == m_clickTimer.timerId())
287 QObject::timerEvent(ev);
290 bool QtWebPageEventHandler::handleKeyPressEvent(QKeyEvent* ev)
292 m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
296 bool QtWebPageEventHandler::handleKeyReleaseEvent(QKeyEvent* ev)
298 m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
302 bool QtWebPageEventHandler::handleFocusInEvent(QFocusEvent*)
304 m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
308 bool QtWebPageEventHandler::handleFocusOutEvent(QFocusEvent*)
310 m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
314 void QtWebPageEventHandler::setViewportInteractionEngine(QtViewportInteractionEngine* engine)
316 m_interactionEngine = engine;
319 void QtWebPageEventHandler::inputMethodEvent(QInputMethodEvent* ev)
321 QString commit = ev->commitString();
322 QString composition = ev->preeditString();
324 // NOTE: We might want to handle events of one char as special
325 // and resend them as key events to make web site completion work.
327 int cursorPositionWithinComposition = 0;
329 Vector<CompositionUnderline> underlines;
331 for (int i = 0; i < ev->attributes().size(); ++i) {
332 const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
334 case QInputMethodEvent::TextFormat: {
335 if (composition.isEmpty())
338 QTextCharFormat textCharFormat = attr.value.value<QTextFormat>().toCharFormat();
339 QColor qcolor = textCharFormat.underlineColor();
340 Color color = makeRGBA(qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha());
341 int start = qMin(attr.start, (attr.start + attr.length));
342 int end = qMax(attr.start, (attr.start + attr.length));
343 underlines.append(CompositionUnderline(start, end, color, false));
346 case QInputMethodEvent::Cursor:
348 cursorPositionWithinComposition = attr.start;
350 // Selection is handled further down.
355 if (composition.isEmpty()) {
356 int selectionStart = -1;
357 int selectionLength = 0;
358 for (int i = 0; i < ev->attributes().size(); ++i) {
359 const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
360 if (attr.type == QInputMethodEvent::Selection) {
361 selectionStart = attr.start;
362 selectionLength = attr.length;
364 ASSERT(selectionStart >= 0);
365 ASSERT(selectionLength >= 0);
370 // FIXME: Confirm the composition here.
372 ASSERT(cursorPositionWithinComposition >= 0);
373 ASSERT(replacementStart >= 0);
375 // FIXME: Set the composition here.
381 void QtWebPageEventHandler::touchEvent(QTouchEvent* event)
383 #if ENABLE(TOUCH_EVENTS)
384 m_webPageProxy->handleTouchEvent(NativeWebTouchEvent(event));
387 ASSERT_NOT_REACHED();
392 void QtWebPageEventHandler::resetGestureRecognizers()
394 m_panGestureRecognizer.reset();
395 m_pinchGestureRecognizer.reset();
396 m_tapGestureRecognizer.reset();
399 void QtWebPageEventHandler::doneWithTouchEvent(const NativeWebTouchEvent& event, bool wasEventHandled)
401 if (!m_interactionEngine)
404 if (wasEventHandled || event.type() == WebEvent::TouchCancel) {
405 resetGestureRecognizers();
409 const QTouchEvent* ev = event.nativeEvent();
411 switch (ev->type()) {
412 case QEvent::TouchBegin:
413 ASSERT(!m_interactionEngine->panGestureActive());
414 ASSERT(!m_interactionEngine->pinchGestureActive());
416 // The interaction engine might still be animating kinetic scrolling or a scale animation
417 // such as double-tap to zoom or the bounce back effect. A touch stops the kinetic scrolling
418 // where as it does not stop the scale animation.
419 if (m_interactionEngine->scrollAnimationActive())
420 m_interactionEngine->interruptScrollAnimation();
422 case QEvent::TouchUpdate:
423 // The scale animation can only be interrupted by a pinch gesture, which will then take over.
424 if (m_interactionEngine->scaleAnimationActive() && m_pinchGestureRecognizer.isRecognized())
425 m_interactionEngine->interruptScaleAnimation();
431 // If the scale animation is active we don't pass the event to the recognizers. In the future
432 // we would want to queue the event here and repost then when the animation ends.
433 if (m_interactionEngine->scaleAnimationActive())
436 // Convert the event timestamp from second to millisecond.
437 qint64 eventTimestampMillis = static_cast<qint64>(event.timestamp() * 1000);
438 m_panGestureRecognizer.recognize(ev, eventTimestampMillis);
439 m_pinchGestureRecognizer.recognize(ev);
441 if (m_panGestureRecognizer.isRecognized() || m_pinchGestureRecognizer.isRecognized())
442 m_tapGestureRecognizer.reset();
444 const QTouchEvent* ev = event.nativeEvent();
445 m_tapGestureRecognizer.recognize(ev, eventTimestampMillis);
449 void QtWebPageEventHandler::didFindZoomableArea(const IntPoint& target, const IntRect& area)
451 if (!m_interactionEngine)
454 // FIXME: As the find method might not respond immediately during load etc,
455 // we should ignore all but the latest request.
456 m_interactionEngine->zoomToAreaGestureEnded(QPointF(target), QRectF(area));
459 void QtWebPageEventHandler::focusEditableArea(const IntRect& caret, const IntRect& area)
461 if (!m_interactionEngine)
464 m_interactionEngine->focusEditableArea(QRectF(caret), QRectF(area));
467 void QtWebPageEventHandler::startDrag(const WebCore::DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
471 dragQImage = dragImage->createQImage();
472 else if (dragData.platformData() && dragData.platformData()->hasImage())
473 dragQImage = qvariant_cast<QImage>(dragData.platformData()->imageData());
475 DragOperation dragOperationMask = dragData.draggingSourceOperationMask();
476 QMimeData* mimeData = const_cast<QMimeData*>(dragData.platformData());
477 Qt::DropActions supportedDropActions = dragOperationToDropActions(dragOperationMask);
479 QPoint clientPosition;
480 QPoint globalPosition;
481 Qt::DropAction actualDropAction = Qt::IgnoreAction;
483 if (QWindow* window = m_webPage->canvas()) {
484 QDrag* drag = new QDrag(window);
485 drag->setPixmap(QPixmap::fromImage(dragQImage));
486 drag->setMimeData(mimeData);
487 actualDropAction = drag->exec(supportedDropActions);
488 globalPosition = QCursor::pos();
489 clientPosition = window->mapFromGlobal(globalPosition);
492 m_webPageProxy->dragEnded(clientPosition, globalPosition, dropActionToDragOperation(actualDropAction));
495 #include "moc_QtWebPageEventHandler.cpp"