First stab at upstreaming our virtual keyboard code
[WebKit-https.git] / Source / WebKit2 / UIProcess / qt / QtWebPageEventHandler.cpp
1 /*
2  * Copyright (C) 2010, 2011 Nokia Corporation and/or its subsidiary(-ies)
3  *
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.
8  *
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.
13  *
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.
18  *
19  */
20
21 #include "config.h"
22 #include "QtWebPageEventHandler.h"
23
24 #include "NativeWebKeyboardEvent.h"
25 #include "NativeWebMouseEvent.h"
26 #include "NativeWebWheelEvent.h"
27 #include "QtViewportInteractionEngine.h"
28 #include "qquickwebpage_p.h"
29 #include <QDrag>
30 #include <QGraphicsSceneMouseEvent>
31 #include <QGuiApplication>
32 #include <QMimeData>
33 #include <QtQuick/QQuickCanvas>
34 #include <QStyleHints>
35 #include <QTextFormat>
36 #include <QTouchEvent>
37 #include <WebCore/DragData.h>
38 #include <WebCore/Editor.h>
39
40 using namespace WebKit;
41 using namespace WebCore;
42
43 static inline Qt::DropAction dragOperationToDropAction(unsigned dragOperation)
44 {
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;
54     return result;
55 }
56
57 static inline Qt::DropActions dragOperationToDropActions(unsigned dragOperations)
58 {
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;
68     return result;
69 }
70
71 static inline WebCore::DragOperation dropActionToDragOperation(Qt::DropActions actions)
72 {
73     unsigned result = 0;
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;
83 }
84
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)
92     , m_clickCount(0)
93 {
94 }
95
96 QtWebPageEventHandler::~QtWebPageEventHandler()
97 {
98 }
99
100 bool QtWebPageEventHandler::handleEvent(QEvent* ev)
101 {
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));
112     case QEvent::Wheel:
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));
125     case QEvent::Drop:
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));
139         return true;
140     case QEvent::InputMethod:
141         inputMethodEvent(static_cast<QInputMethodEvent*>(ev));
142         return false; // Look at comment in qquickwebpage.cpp
143     }
144
145     // FIXME: Move all common event handling here.
146     return false;
147 }
148
149 bool QtWebPageEventHandler::handleMouseMoveEvent(QMouseEvent* ev)
150 {
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
155     // around that here.
156     static QPointF lastPos = QPointF();
157     if (lastPos == ev->pos())
158         return ev->isAccepted();
159     lastPos = ev->pos();
160
161     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, /*eventClickCount*/ 0));
162
163     return ev->isAccepted();
164 }
165
166 bool QtWebPageEventHandler::handleMousePressEvent(QMouseEvent* ev)
167 {
168     if (m_clickTimer.isActive()
169         && m_previousClickButton == ev->button()
170         && (ev->pos() - m_lastClick).manhattanLength() < qApp->styleHints()->startDragDistance()) {
171         m_clickCount++;
172     } else {
173         m_clickCount = 1;
174         m_previousClickButton = ev->button();
175     }
176
177     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, m_clickCount));
178
179     m_lastClick = ev->pos();
180     m_clickTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), this);
181     return ev->isAccepted();
182 }
183
184 bool QtWebPageEventHandler::handleMouseReleaseEvent(QMouseEvent* ev)
185 {
186     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, /*eventClickCount*/ 0));
187     return ev->isAccepted();
188 }
189
190 bool QtWebPageEventHandler::handleWheelEvent(QWheelEvent* ev)
191 {
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();
197 }
198
199 bool QtWebPageEventHandler::handleHoverLeaveEvent(QHoverEvent* ev)
200 {
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);
206 }
207
208 bool QtWebPageEventHandler::handleHoverMoveEvent(QHoverEvent* ev)
209 {
210     QMouseEvent me(QEvent::MouseMove, ev->pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
211     me.setAccepted(ev->isAccepted());
212     me.setTimestamp(ev->timestamp());
213
214     return handleMouseMoveEvent(&me);
215 }
216
217 bool QtWebPageEventHandler::handleDragEnterEvent(QDragEnterEvent* ev)
218 {
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();
224     return true;
225 }
226
227 bool QtWebPageEventHandler::handleDragLeaveEvent(QDragLeaveEvent* ev)
228 {
229     bool accepted = ev->isAccepted();
230
231     // FIXME: Should not use QCursor::pos()
232     DragData dragData(0, IntPoint(), QCursor::pos(), DragOperationNone);
233     m_webPageProxy->dragExited(&dragData);
234     m_webPageProxy->resetDragOperation();
235
236     ev->setAccepted(accepted);
237     return accepted;
238 }
239
240 bool QtWebPageEventHandler::handleDragMoveEvent(QDragMoveEvent* ev)
241 {
242     bool accepted = ev->isAccepted();
243
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)
249         ev->accept();
250
251     ev->setAccepted(accepted);
252     return accepted;
253 }
254
255 bool QtWebPageEventHandler::handleDropEvent(QDropEvent* ev)
256 {
257     bool accepted = ev->isAccepted();
258
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));
264     ev->accept();
265
266     ev->setAccepted(accepted);
267     return accepted;
268 }
269
270 void QtWebPageEventHandler::handleSingleTapEvent(const QTouchEvent::TouchPoint& point)
271 {
272     WebGestureEvent gesture(WebEvent::GestureSingleTap, point.pos().toPoint(), point.screenPos().toPoint(), WebEvent::Modifiers(0), 0);
273     m_webPageProxy->handleGestureEvent(gesture);
274 }
275
276 void QtWebPageEventHandler::handleDoubleTapEvent(const QTouchEvent::TouchPoint& point)
277 {
278     m_webPageProxy->findZoomableAreaForPoint(point.pos().toPoint());
279 }
280
281 void QtWebPageEventHandler::timerEvent(QTimerEvent* ev)
282 {
283     int timerId = ev->timerId();
284     if (timerId == m_clickTimer.timerId())
285         m_clickTimer.stop();
286     else
287         QObject::timerEvent(ev);
288 }
289
290 bool QtWebPageEventHandler::handleKeyPressEvent(QKeyEvent* ev)
291 {
292     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
293     return true;
294 }
295
296 bool QtWebPageEventHandler::handleKeyReleaseEvent(QKeyEvent* ev)
297 {
298     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
299     return true;
300 }
301
302 bool QtWebPageEventHandler::handleFocusInEvent(QFocusEvent*)
303 {
304     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
305     return true;
306 }
307
308 bool QtWebPageEventHandler::handleFocusOutEvent(QFocusEvent*)
309 {
310     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
311     return true;
312 }
313
314 void QtWebPageEventHandler::setViewportInteractionEngine(QtViewportInteractionEngine* engine)
315 {
316     m_interactionEngine = engine;
317 }
318
319 void QtWebPageEventHandler::inputMethodEvent(QInputMethodEvent* ev)
320 {
321     QString commit = ev->commitString();
322     QString composition = ev->preeditString();
323
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.
326
327     int cursorPositionWithinComposition = 0;
328
329     Vector<CompositionUnderline> underlines;
330
331     for (int i = 0; i < ev->attributes().size(); ++i) {
332         const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
333         switch (attr.type) {
334         case QInputMethodEvent::TextFormat: {
335             if (composition.isEmpty())
336                 break;
337
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));
344             break;
345         }
346         case QInputMethodEvent::Cursor:
347             if (attr.length)
348                 cursorPositionWithinComposition = attr.start;
349             break;
350         // Selection is handled further down.
351         default: break;
352         }
353     }
354
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;
363
364                 ASSERT(selectionStart >= 0);
365                 ASSERT(selectionLength >= 0);
366                 break;
367             }
368         }
369
370         // FIXME: Confirm the composition here.
371     } else {
372         ASSERT(cursorPositionWithinComposition >= 0);
373         ASSERT(replacementStart >= 0);
374
375         // FIXME: Set the composition here.
376     }
377
378     ev->accept();
379 }
380
381 void QtWebPageEventHandler::touchEvent(QTouchEvent* event)
382 {
383 #if ENABLE(TOUCH_EVENTS)
384     m_webPageProxy->handleTouchEvent(NativeWebTouchEvent(event));
385     event->accept();
386 #else
387     ASSERT_NOT_REACHED();
388     ev->ignore();
389 #endif
390 }
391
392 void QtWebPageEventHandler::resetGestureRecognizers()
393 {
394     m_panGestureRecognizer.reset();
395     m_pinchGestureRecognizer.reset();
396     m_tapGestureRecognizer.reset();
397 }
398
399 void QtWebPageEventHandler::doneWithTouchEvent(const NativeWebTouchEvent& event, bool wasEventHandled)
400 {
401     if (!m_interactionEngine)
402         return;
403
404     if (wasEventHandled || event.type() == WebEvent::TouchCancel) {
405         resetGestureRecognizers();
406         return;
407     }
408
409     const QTouchEvent* ev = event.nativeEvent();
410
411     switch (ev->type()) {
412     case QEvent::TouchBegin:
413         ASSERT(!m_interactionEngine->panGestureActive());
414         ASSERT(!m_interactionEngine->pinchGestureActive());
415
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();
421         break;
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();
426         break;
427     default:
428         break;
429     }
430
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())
434         return;
435
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);
440
441     if (m_panGestureRecognizer.isRecognized() || m_pinchGestureRecognizer.isRecognized())
442         m_tapGestureRecognizer.reset();
443     else {
444         const QTouchEvent* ev = event.nativeEvent();
445         m_tapGestureRecognizer.recognize(ev, eventTimestampMillis);
446     }
447 }
448
449 void QtWebPageEventHandler::didFindZoomableArea(const IntPoint& target, const IntRect& area)
450 {
451     if (!m_interactionEngine)
452         return;
453
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));
457 }
458
459 void QtWebPageEventHandler::focusEditableArea(const IntRect& caret, const IntRect& area)
460 {
461     if (!m_interactionEngine)
462         return;
463
464     m_interactionEngine->focusEditableArea(QRectF(caret), QRectF(area));
465 }
466
467 void QtWebPageEventHandler::startDrag(const WebCore::DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
468 {
469     QImage dragQImage;
470     if (dragImage)
471         dragQImage = dragImage->createQImage();
472     else if (dragData.platformData() && dragData.platformData()->hasImage())
473         dragQImage = qvariant_cast<QImage>(dragData.platformData()->imageData());
474
475     DragOperation dragOperationMask = dragData.draggingSourceOperationMask();
476     QMimeData* mimeData = const_cast<QMimeData*>(dragData.platformData());
477     Qt::DropActions supportedDropActions = dragOperationToDropActions(dragOperationMask);
478
479     QPoint clientPosition;
480     QPoint globalPosition;
481     Qt::DropAction actualDropAction = Qt::IgnoreAction;
482
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);
490     }
491
492     m_webPageProxy->dragEnded(clientPosition, globalPosition, dropActionToDragOperation(actualDropAction));
493 }
494
495 #include "moc_QtWebPageEventHandler.cpp"