95ed431916d40fd7581d7e37116c1e846aae553b
[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 "qquickwebview_p.h"
30 #include <QCursor>
31 #include <QDrag>
32 #include <QGraphicsSceneMouseEvent>
33 #include <QGuiApplication>
34 #include <QInputPanel>
35 #include <QMimeData>
36 #include <QtQuick/QQuickCanvas>
37 #include <QStyleHints>
38 #include <QTextFormat>
39 #include <QTouchEvent>
40 #include <QTransform>
41 #include <WebCore/DragData.h>
42 #include <WebCore/Editor.h>
43
44 using namespace WebKit;
45 using namespace WebCore;
46
47 static inline Qt::DropAction dragOperationToDropAction(unsigned dragOperation)
48 {
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;
58     return result;
59 }
60
61 static inline Qt::DropActions dragOperationToDropActions(unsigned dragOperations)
62 {
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;
72     return result;
73 }
74
75 static inline WebCore::DragOperation dropActionToDragOperation(Qt::DropActions actions)
76 {
77     unsigned result = 0;
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;
87 }
88
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)
98     , m_clickCount(0)
99     , m_postponeTextInputStateChanged(false)
100 {
101     connect(qApp->inputPanel(), SIGNAL(visibleChanged()), this, SLOT(inputPanelVisibleChanged()));
102 }
103
104 QtWebPageEventHandler::~QtWebPageEventHandler()
105 {
106     disconnect(qApp->inputPanel(), SIGNAL(visibleChanged()), this, SLOT(inputPanelVisibleChanged()));
107 }
108
109 void QtWebPageEventHandler::handleMouseMoveEvent(QMouseEvent* ev)
110 {
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
115     // around that here.
116     static QPointF lastPos = QPointF();
117     QTransform fromItemTransform = m_webPage->transformFromItem();
118     QPointF webPagePoint = fromItemTransform.map(ev->localPos());
119     if (lastPos == webPagePoint)
120         return;
121     lastPos = webPagePoint;
122
123     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, /*eventClickCount*/ 0));
124 }
125
126 void QtWebPageEventHandler::handleMousePressEvent(QMouseEvent* ev)
127 {
128     QTransform fromItemTransform = m_webPage->transformFromItem();
129     QPointF webPagePoint = fromItemTransform.map(ev->localPos());
130
131     if (m_clickTimer.isActive()
132         && m_previousClickButton == ev->button()
133         && (webPagePoint - m_lastClick).manhattanLength() < qApp->styleHints()->startDragDistance()) {
134         m_clickCount++;
135     } else {
136         m_clickCount = 1;
137         m_previousClickButton = ev->button();
138     }
139
140     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, m_clickCount));
141
142     m_lastClick = webPagePoint;
143     m_clickTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), this);
144 }
145
146 void QtWebPageEventHandler::handleMouseReleaseEvent(QMouseEvent* ev)
147 {
148     QTransform fromItemTransform = m_webPage->transformFromItem();
149     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, /*eventClickCount*/ 0));
150 }
151
152 void QtWebPageEventHandler::handleWheelEvent(QWheelEvent* ev)
153 {
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);
159 }
160
161 void QtWebPageEventHandler::handleHoverLeaveEvent(QHoverEvent* ev)
162 {
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);
169 }
170
171 void QtWebPageEventHandler::handleHoverMoveEvent(QHoverEvent* ev)
172 {
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);
178 }
179
180 void QtWebPageEventHandler::handleDragEnterEvent(QDragEnterEvent* ev)
181 {
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();
188 }
189
190 void QtWebPageEventHandler::handleDragLeaveEvent(QDragLeaveEvent* ev)
191 {
192     bool accepted = ev->isAccepted();
193
194     // FIXME: Should not use QCursor::pos()
195     DragData dragData(0, IntPoint(), QCursor::pos(), DragOperationNone);
196     m_webPageProxy->dragExited(&dragData);
197     m_webPageProxy->resetDragOperation();
198
199     ev->setAccepted(accepted);
200 }
201
202 void QtWebPageEventHandler::handleDragMoveEvent(QDragMoveEvent* ev)
203 {
204     bool accepted = ev->isAccepted();
205
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)
212         ev->accept();
213
214     ev->setAccepted(accepted);
215 }
216
217 void QtWebPageEventHandler::handleDropEvent(QDropEvent* ev)
218 {
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));
226     ev->accept();
227
228     ev->setAccepted(accepted);
229 }
230
231 void QtWebPageEventHandler::handlePotentialSingleTapEvent(const QTouchEvent::TouchPoint& point)
232 {
233     QTransform fromItemTransform = m_webPage->transformFromItem();
234     m_webPageProxy->handlePotentialActivation(fromItemTransform.map(point.pos()).toPoint());
235 }
236
237 void QtWebPageEventHandler::handleSingleTapEvent(const QTouchEvent::TouchPoint& point)
238 {
239     m_postponeTextInputStateChanged = true;
240
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);
244 }
245
246 void QtWebPageEventHandler::handleDoubleTapEvent(const QTouchEvent::TouchPoint& point)
247 {
248     QTransform fromItemTransform = m_webPage->transformFromItem();
249     m_webPageProxy->findZoomableAreaForPoint(fromItemTransform.map(point.pos()).toPoint());
250 }
251
252 void QtWebPageEventHandler::timerEvent(QTimerEvent* ev)
253 {
254     int timerId = ev->timerId();
255     if (timerId == m_clickTimer.timerId())
256         m_clickTimer.stop();
257     else
258         QObject::timerEvent(ev);
259 }
260
261 void QtWebPageEventHandler::handleKeyPressEvent(QKeyEvent* ev)
262 {
263     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
264 }
265
266 void QtWebPageEventHandler::handleKeyReleaseEvent(QKeyEvent* ev)
267 {
268     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
269 }
270
271 void QtWebPageEventHandler::handleFocusInEvent(QFocusEvent*)
272 {
273     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
274 }
275
276 void QtWebPageEventHandler::handleFocusOutEvent(QFocusEvent*)
277 {
278     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
279 }
280
281 void QtWebPageEventHandler::setViewportInteractionEngine(QtViewportInteractionEngine* engine)
282 {
283     m_interactionEngine = engine;
284 }
285
286 void QtWebPageEventHandler::handleInputMethodEvent(QInputMethodEvent* ev)
287 {
288     QString commit = ev->commitString();
289     QString composition = ev->preeditString();
290
291     int replacementStart = ev->replacementStart();
292     int replacementLength = ev->replacementLength();
293
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.
296
297     int cursorPositionWithinComposition = 0;
298
299     Vector<CompositionUnderline> underlines;
300
301     for (int i = 0; i < ev->attributes().size(); ++i) {
302         const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
303         switch (attr.type) {
304         case QInputMethodEvent::TextFormat: {
305             if (composition.isEmpty())
306                 break;
307
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));
314             break;
315         }
316         case QInputMethodEvent::Cursor:
317             if (attr.length)
318                 cursorPositionWithinComposition = attr.start;
319             break;
320         // Selection is handled further down.
321         default: break;
322         }
323     }
324
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;
333
334                 ASSERT(selectionStart >= 0);
335                 ASSERT(selectionLength >= 0);
336                 break;
337             }
338         }
339
340         m_webPageProxy->confirmComposition(commit, selectionStart, selectionLength);
341     } else {
342         ASSERT(cursorPositionWithinComposition >= 0);
343         ASSERT(replacementStart >= 0);
344
345         m_webPageProxy->setComposition(composition, underlines,
346             cursorPositionWithinComposition, cursorPositionWithinComposition,
347             replacementStart, replacementLength);
348     }
349
350     ev->accept();
351 }
352
353 void QtWebPageEventHandler::handleTouchEvent(QTouchEvent* event)
354 {
355 #if ENABLE(TOUCH_EVENTS)
356     QTransform fromItemTransform = m_webPage->transformFromItem();
357     m_webPageProxy->handleTouchEvent(NativeWebTouchEvent(event, fromItemTransform));
358     event->accept();
359 #else
360     ASSERT_NOT_REACHED();
361     event->ignore();
362 #endif
363 }
364
365 void QtWebPageEventHandler::resetGestureRecognizers()
366 {
367     m_panGestureRecognizer.reset();
368     m_pinchGestureRecognizer.reset();
369     m_tapGestureRecognizer.reset();
370 }
371
372 static void setInputPanelVisible(bool visible)
373 {
374     if (qApp->inputPanel()->visible() == visible)
375         return;
376
377     qApp->inputPanel()->setVisible(visible);
378 }
379
380 void QtWebPageEventHandler::inputPanelVisibleChanged()
381 {
382     if (!m_interactionEngine)
383         return;
384
385     // We only respond to the input panel becoming visible.
386     if (!m_webView->hasFocus() || !qApp->inputPanel()->visible())
387         return;
388
389     const EditorState& editor = m_webPageProxy->editorState();
390     if (editor.isContentEditable)
391         m_interactionEngine->focusEditableArea(QRectF(editor.cursorRect), QRectF(editor.editorRect));
392 }
393
394 void QtWebPageEventHandler::updateTextInputState()
395 {
396     if (m_postponeTextInputStateChanged)
397         return;
398
399     const EditorState& editor = m_webPageProxy->editorState();
400
401     if (!m_webView->hasFocus())
402         return;
403
404     // Ignore input method requests not due to a tap gesture.
405     if (!editor.isContentEditable)
406         setInputPanelVisible(false);
407 }
408
409 void QtWebPageEventHandler::doneWithGestureEvent(const WebGestureEvent& event, bool wasEventHandled)
410 {
411     if (event.type() != WebEvent::GestureSingleTap)
412         return;
413
414     m_postponeTextInputStateChanged = false;
415
416     if (!wasEventHandled || !m_webView->hasFocus())
417         return;
418
419     const EditorState& editor = m_webPageProxy->editorState();
420     bool newVisible = editor.isContentEditable;
421
422     setInputPanelVisible(newVisible);
423 }
424
425 void QtWebPageEventHandler::doneWithTouchEvent(const NativeWebTouchEvent& event, bool wasEventHandled)
426 {
427     if (!m_interactionEngine)
428         return;
429
430     if (wasEventHandled || event.type() == WebEvent::TouchCancel) {
431         resetGestureRecognizers();
432         return;
433     }
434
435     const QTouchEvent* ev = event.nativeEvent();
436
437     switch (ev->type()) {
438     case QEvent::TouchBegin:
439         ASSERT(!m_interactionEngine->panGestureActive());
440         ASSERT(!m_interactionEngine->pinchGestureActive());
441
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.
446         break;
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();
451         break;
452     default:
453         break;
454     }
455
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())
459         return;
460
461     m_panGestureRecognizer.recognize(ev);
462     m_pinchGestureRecognizer.recognize(ev);
463
464     if (m_panGestureRecognizer.isRecognized() || m_pinchGestureRecognizer.isRecognized())
465         m_tapGestureRecognizer.reset();
466     else {
467         // Convert the event timestamp from second to millisecond.
468         qint64 eventTimestampMillis = static_cast<qint64>(event.timestamp() * 1000);
469         m_tapGestureRecognizer.recognize(ev, eventTimestampMillis);
470     }
471 }
472
473 void QtWebPageEventHandler::didFindZoomableArea(const IntPoint& target, const IntRect& area)
474 {
475     if (!m_interactionEngine)
476         return;
477
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));
481 }
482
483 void QtWebPageEventHandler::startDrag(const WebCore::DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
484 {
485     QImage dragQImage;
486     if (dragImage)
487         dragQImage = dragImage->createQImage();
488     else if (dragData.platformData() && dragData.platformData()->hasImage())
489         dragQImage = qvariant_cast<QImage>(dragData.platformData()->imageData());
490
491     DragOperation dragOperationMask = dragData.draggingSourceOperationMask();
492     QMimeData* mimeData = const_cast<QMimeData*>(dragData.platformData());
493     Qt::DropActions supportedDropActions = dragOperationToDropActions(dragOperationMask);
494
495     QPoint clientPosition;
496     QPoint globalPosition;
497     Qt::DropAction actualDropAction = Qt::IgnoreAction;
498
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);
506     }
507
508     m_webPageProxy->dragEnded(clientPosition, globalPosition, dropActionToDragOperation(actualDropAction));
509 }
510
511 #include "moc_QtWebPageEventHandler.cpp"