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"
29 #include "qquickwebview_p.h"
32 #include <QGraphicsSceneMouseEvent>
33 #include <QGuiApplication>
34 #include <QInputPanel>
36 #include <QtQuick/QQuickCanvas>
37 #include <QStyleHints>
38 #include <QTextFormat>
39 #include <QTouchEvent>
41 #include <WebCore/DragData.h>
42 #include <WebCore/Editor.h>
44 using namespace WebKit;
45 using namespace WebCore;
47 static inline Qt::DropAction dragOperationToDropAction(unsigned dragOperation)
49 Qt::DropAction result = Qt::IgnoreAction;
50 if (dragOperation & DragOperationCopy)
51 result = Qt::CopyAction;
52 else if (dragOperation & DragOperationMove)
53 result = Qt::MoveAction;
54 else if (dragOperation & DragOperationGeneric)
55 result = Qt::MoveAction;
56 else if (dragOperation & DragOperationLink)
57 result = Qt::LinkAction;
61 static inline Qt::DropActions dragOperationToDropActions(unsigned dragOperations)
63 Qt::DropActions result = Qt::IgnoreAction;
64 if (dragOperations & DragOperationCopy)
65 result |= Qt::CopyAction;
66 if (dragOperations & DragOperationMove)
67 result |= Qt::MoveAction;
68 if (dragOperations & DragOperationGeneric)
69 result |= Qt::MoveAction;
70 if (dragOperations & DragOperationLink)
71 result |= Qt::LinkAction;
75 static inline WebCore::DragOperation dropActionToDragOperation(Qt::DropActions actions)
78 if (actions & Qt::CopyAction)
79 result |= DragOperationCopy;
80 if (actions & Qt::MoveAction)
81 result |= (DragOperationMove | DragOperationGeneric);
82 if (actions & Qt::LinkAction)
83 result |= DragOperationLink;
84 if (result == (DragOperationCopy | DragOperationMove | DragOperationGeneric | DragOperationLink))
85 result = DragOperationEvery;
86 return (DragOperation)result;
89 QtWebPageEventHandler::QtWebPageEventHandler(WKPageRef pageRef, QQuickWebPage* qmlWebPage, QQuickWebView* qmlWebView)
90 : m_webPageProxy(toImpl(pageRef))
91 , m_interactionEngine(0)
92 , m_panGestureRecognizer(this)
93 , m_pinchGestureRecognizer(this)
94 , m_tapGestureRecognizer(this)
95 , m_webPage(qmlWebPage)
96 , m_webView(qmlWebView)
97 , m_previousClickButton(Qt::NoButton)
99 , m_postponeTextInputStateChanged(false)
101 connect(qApp->inputPanel(), SIGNAL(visibleChanged()), this, SLOT(inputPanelVisibleChanged()));
104 QtWebPageEventHandler::~QtWebPageEventHandler()
106 disconnect(qApp->inputPanel(), SIGNAL(visibleChanged()), this, SLOT(inputPanelVisibleChanged()));
109 void QtWebPageEventHandler::handleMouseMoveEvent(QMouseEvent* ev)
111 // For some reason mouse press results in mouse hover (which is
112 // converted to mouse move for WebKit). We ignore these hover
113 // events by comparing lastPos with newPos.
114 // NOTE: lastPos from the event always comes empty, so we work
116 static QPointF lastPos = QPointF();
117 QTransform fromItemTransform = m_webPage->transformFromItem();
118 QPointF webPagePoint = fromItemTransform.map(ev->localPos());
119 if (lastPos == webPagePoint)
121 lastPos = webPagePoint;
123 m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, /*eventClickCount*/ 0));
126 void QtWebPageEventHandler::handleMousePressEvent(QMouseEvent* ev)
128 QTransform fromItemTransform = m_webPage->transformFromItem();
129 QPointF webPagePoint = fromItemTransform.map(ev->localPos());
131 if (m_clickTimer.isActive()
132 && m_previousClickButton == ev->button()
133 && (webPagePoint - m_lastClick).manhattanLength() < qApp->styleHints()->startDragDistance()) {
137 m_previousClickButton = ev->button();
140 m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, m_clickCount));
142 m_lastClick = webPagePoint;
143 m_clickTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), this);
146 void QtWebPageEventHandler::handleMouseReleaseEvent(QMouseEvent* ev)
148 QTransform fromItemTransform = m_webPage->transformFromItem();
149 m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, /*eventClickCount*/ 0));
152 void QtWebPageEventHandler::handleWheelEvent(QWheelEvent* ev)
154 QTransform fromItemTransform = m_webPage->transformFromItem();
155 m_webPageProxy->handleWheelEvent(NativeWebWheelEvent(ev, fromItemTransform));
156 // FIXME: Handle whether the page used the wheel event or not.
157 if (m_interactionEngine)
158 m_interactionEngine->wheelEvent(ev);
161 void QtWebPageEventHandler::handleHoverLeaveEvent(QHoverEvent* ev)
163 // To get the correct behavior of mouseout, we need to turn the Leave event of our webview into a mouse move
164 // to a very far region.
165 QTransform fromItemTransform = m_webPage->transformFromItem();
166 QHoverEvent fakeEvent(QEvent::HoverMove, QPoint(INT_MIN, INT_MIN), fromItemTransform.map(ev->oldPosF()));
167 fakeEvent.setTimestamp(ev->timestamp());
168 handleHoverMoveEvent(&fakeEvent);
171 void QtWebPageEventHandler::handleHoverMoveEvent(QHoverEvent* ev)
173 QTransform fromItemTransform = m_webPage->transformFromItem();
174 QMouseEvent me(QEvent::MouseMove, fromItemTransform.map(ev->posF()), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
175 me.setAccepted(ev->isAccepted());
176 me.setTimestamp(ev->timestamp());
177 handleMouseMoveEvent(&me);
180 void QtWebPageEventHandler::handleDragEnterEvent(QDragEnterEvent* ev)
182 m_webPageProxy->resetDragOperation();
183 QTransform fromItemTransform = m_webPage->transformFromItem();
184 // FIXME: Should not use QCursor::pos()
185 DragData dragData(ev->mimeData(), fromItemTransform.map(ev->pos()), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
186 m_webPageProxy->dragEntered(&dragData);
187 ev->acceptProposedAction();
190 void QtWebPageEventHandler::handleDragLeaveEvent(QDragLeaveEvent* ev)
192 bool accepted = ev->isAccepted();
194 // FIXME: Should not use QCursor::pos()
195 DragData dragData(0, IntPoint(), QCursor::pos(), DragOperationNone);
196 m_webPageProxy->dragExited(&dragData);
197 m_webPageProxy->resetDragOperation();
199 ev->setAccepted(accepted);
202 void QtWebPageEventHandler::handleDragMoveEvent(QDragMoveEvent* ev)
204 bool accepted = ev->isAccepted();
206 QTransform fromItemTransform = m_webPage->transformFromItem();
207 // FIXME: Should not use QCursor::pos()
208 DragData dragData(ev->mimeData(), fromItemTransform.map(ev->pos()), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
209 m_webPageProxy->dragUpdated(&dragData);
210 ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
211 if (m_webPageProxy->dragSession().operation != DragOperationNone)
214 ev->setAccepted(accepted);
217 void QtWebPageEventHandler::handleDropEvent(QDropEvent* ev)
219 bool accepted = ev->isAccepted();
220 QTransform fromItemTransform = m_webPage->transformFromItem();
221 // FIXME: Should not use QCursor::pos()
222 DragData dragData(ev->mimeData(), fromItemTransform.map(ev->pos()), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
223 SandboxExtension::Handle handle;
224 m_webPageProxy->performDrag(&dragData, String(), handle);
225 ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
228 ev->setAccepted(accepted);
231 void QtWebPageEventHandler::handlePotentialSingleTapEvent(const QTouchEvent::TouchPoint& point)
233 QTransform fromItemTransform = m_webPage->transformFromItem();
234 m_webPageProxy->handlePotentialActivation(fromItemTransform.map(point.pos()).toPoint());
237 void QtWebPageEventHandler::handleSingleTapEvent(const QTouchEvent::TouchPoint& point)
239 m_postponeTextInputStateChanged = true;
241 QTransform fromItemTransform = m_webPage->transformFromItem();
242 WebGestureEvent gesture(WebEvent::GestureSingleTap, fromItemTransform.map(point.pos()).toPoint(), point.screenPos().toPoint(), WebEvent::Modifiers(0), 0, IntSize(point.rect().size().toSize()), FloatPoint(0, 0));
243 m_webPageProxy->handleGestureEvent(gesture);
246 void QtWebPageEventHandler::handleDoubleTapEvent(const QTouchEvent::TouchPoint& point)
248 QTransform fromItemTransform = m_webPage->transformFromItem();
249 m_webPageProxy->findZoomableAreaForPoint(fromItemTransform.map(point.pos()).toPoint());
252 void QtWebPageEventHandler::timerEvent(QTimerEvent* ev)
254 int timerId = ev->timerId();
255 if (timerId == m_clickTimer.timerId())
258 QObject::timerEvent(ev);
261 void QtWebPageEventHandler::handleKeyPressEvent(QKeyEvent* ev)
263 m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
266 void QtWebPageEventHandler::handleKeyReleaseEvent(QKeyEvent* ev)
268 m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
271 void QtWebPageEventHandler::handleFocusInEvent(QFocusEvent*)
273 m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
276 void QtWebPageEventHandler::handleFocusOutEvent(QFocusEvent*)
278 m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
281 void QtWebPageEventHandler::setViewportInteractionEngine(QtViewportInteractionEngine* engine)
283 m_interactionEngine = engine;
286 void QtWebPageEventHandler::handleInputMethodEvent(QInputMethodEvent* ev)
288 QString commit = ev->commitString();
289 QString composition = ev->preeditString();
291 int replacementStart = ev->replacementStart();
292 int replacementLength = ev->replacementLength();
294 // NOTE: We might want to handle events of one char as special
295 // and resend them as key events to make web site completion work.
297 int cursorPositionWithinComposition = 0;
299 Vector<CompositionUnderline> underlines;
301 for (int i = 0; i < ev->attributes().size(); ++i) {
302 const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
304 case QInputMethodEvent::TextFormat: {
305 if (composition.isEmpty())
308 QTextCharFormat textCharFormat = attr.value.value<QTextFormat>().toCharFormat();
309 QColor qcolor = textCharFormat.underlineColor();
310 Color color = makeRGBA(qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha());
311 int start = qMin(attr.start, (attr.start + attr.length));
312 int end = qMax(attr.start, (attr.start + attr.length));
313 underlines.append(CompositionUnderline(start, end, color, false));
316 case QInputMethodEvent::Cursor:
318 cursorPositionWithinComposition = attr.start;
320 // Selection is handled further down.
325 if (composition.isEmpty()) {
326 int selectionStart = -1;
327 int selectionLength = 0;
328 for (int i = 0; i < ev->attributes().size(); ++i) {
329 const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
330 if (attr.type == QInputMethodEvent::Selection) {
331 selectionStart = attr.start;
332 selectionLength = attr.length;
334 ASSERT(selectionStart >= 0);
335 ASSERT(selectionLength >= 0);
340 m_webPageProxy->confirmComposition(commit, selectionStart, selectionLength);
342 ASSERT(cursorPositionWithinComposition >= 0);
343 ASSERT(replacementStart >= 0);
345 m_webPageProxy->setComposition(composition, underlines,
346 cursorPositionWithinComposition, cursorPositionWithinComposition,
347 replacementStart, replacementLength);
353 void QtWebPageEventHandler::handleTouchEvent(QTouchEvent* event)
355 #if ENABLE(TOUCH_EVENTS)
356 QTransform fromItemTransform = m_webPage->transformFromItem();
357 m_webPageProxy->handleTouchEvent(NativeWebTouchEvent(event, fromItemTransform));
360 ASSERT_NOT_REACHED();
365 void QtWebPageEventHandler::resetGestureRecognizers()
367 m_panGestureRecognizer.reset();
368 m_pinchGestureRecognizer.reset();
369 m_tapGestureRecognizer.reset();
372 static void setInputPanelVisible(bool visible)
374 if (qApp->inputPanel()->visible() == visible)
377 qApp->inputPanel()->setVisible(visible);
380 void QtWebPageEventHandler::inputPanelVisibleChanged()
382 if (!m_interactionEngine)
385 // We only respond to the input panel becoming visible.
386 if (!m_webView->hasFocus() || !qApp->inputPanel()->visible())
389 const EditorState& editor = m_webPageProxy->editorState();
390 if (editor.isContentEditable)
391 m_interactionEngine->focusEditableArea(QRectF(editor.cursorRect), QRectF(editor.editorRect));
394 void QtWebPageEventHandler::updateTextInputState()
396 if (m_postponeTextInputStateChanged)
399 const EditorState& editor = m_webPageProxy->editorState();
401 if (!m_webView->hasFocus())
404 // Ignore input method requests not due to a tap gesture.
405 if (!editor.isContentEditable)
406 setInputPanelVisible(false);
409 void QtWebPageEventHandler::doneWithGestureEvent(const WebGestureEvent& event, bool wasEventHandled)
411 if (event.type() != WebEvent::GestureSingleTap)
414 m_postponeTextInputStateChanged = false;
416 if (!wasEventHandled || !m_webView->hasFocus())
419 const EditorState& editor = m_webPageProxy->editorState();
420 bool newVisible = editor.isContentEditable;
422 setInputPanelVisible(newVisible);
425 void QtWebPageEventHandler::doneWithTouchEvent(const NativeWebTouchEvent& event, bool wasEventHandled)
427 if (!m_interactionEngine)
430 if (wasEventHandled || event.type() == WebEvent::TouchCancel) {
431 resetGestureRecognizers();
435 const QTouchEvent* ev = event.nativeEvent();
437 switch (ev->type()) {
438 case QEvent::TouchBegin:
439 ASSERT(!m_interactionEngine->panGestureActive());
440 ASSERT(!m_interactionEngine->pinchGestureActive());
442 // The interaction engine might still be animating kinetic scrolling or a scale animation
443 // such as double-tap to zoom or the bounce back effect. A touch stops the kinetic scrolling
444 // where as it does not stop the scale animation.
445 // Sending the event to the flickProvider will stop the kinetic scrolling animation.
447 case QEvent::TouchUpdate:
448 // The scale animation can only be interrupted by a pinch gesture, which will then take over.
449 if (m_interactionEngine->scaleAnimationActive() && m_pinchGestureRecognizer.isRecognized())
450 m_interactionEngine->interruptScaleAnimation();
456 // If the scale animation is active we don't pass the event to the recognizers. In the future
457 // we would want to queue the event here and repost then when the animation ends.
458 if (m_interactionEngine->scaleAnimationActive())
461 m_panGestureRecognizer.recognize(ev);
462 m_pinchGestureRecognizer.recognize(ev);
464 if (m_panGestureRecognizer.isRecognized() || m_pinchGestureRecognizer.isRecognized())
465 m_tapGestureRecognizer.reset();
467 // Convert the event timestamp from second to millisecond.
468 qint64 eventTimestampMillis = static_cast<qint64>(event.timestamp() * 1000);
469 m_tapGestureRecognizer.recognize(ev, eventTimestampMillis);
473 void QtWebPageEventHandler::didFindZoomableArea(const IntPoint& target, const IntRect& area)
475 if (!m_interactionEngine)
478 // FIXME: As the find method might not respond immediately during load etc,
479 // we should ignore all but the latest request.
480 m_interactionEngine->zoomToAreaGestureEnded(QPointF(target), QRectF(area));
483 void QtWebPageEventHandler::startDrag(const WebCore::DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
487 dragQImage = dragImage->createQImage();
488 else if (dragData.platformData() && dragData.platformData()->hasImage())
489 dragQImage = qvariant_cast<QImage>(dragData.platformData()->imageData());
491 DragOperation dragOperationMask = dragData.draggingSourceOperationMask();
492 QMimeData* mimeData = const_cast<QMimeData*>(dragData.platformData());
493 Qt::DropActions supportedDropActions = dragOperationToDropActions(dragOperationMask);
495 QPoint clientPosition;
496 QPoint globalPosition;
497 Qt::DropAction actualDropAction = Qt::IgnoreAction;
499 if (QWindow* window = m_webPage->canvas()) {
500 QDrag* drag = new QDrag(window);
501 drag->setPixmap(QPixmap::fromImage(dragQImage));
502 drag->setMimeData(mimeData);
503 actualDropAction = drag->exec(supportedDropActions);
504 globalPosition = QCursor::pos();
505 clientPosition = window->mapFromGlobal(globalPosition);
508 m_webPageProxy->dragEnded(clientPosition, globalPosition, dropActionToDragOperation(actualDropAction));
511 #include "moc_QtWebPageEventHandler.cpp"