[Qt][WK2] Move startDrag implementation to QtWebPageEventHandler
[WebKit.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 "qquickwebview_p.h"
25 #include "NativeWebKeyboardEvent.h"
26 #include "NativeWebMouseEvent.h"
27 #include "NativeWebWheelEvent.h"
28 #include "QtViewportInteractionEngine.h"
29 #include <QDrag>
30 #include <QGraphicsSceneMouseEvent>
31 #include <QGuiApplication>
32 #include <QMimeData>
33 #include <QtQuick/QQuickCanvas>
34 #include <QStyleHints>
35 #include <QTouchEvent>
36 #include <WebCore/DragData.h>
37
38 using namespace WebKit;
39 using namespace WebCore;
40
41 static inline Qt::DropAction dragOperationToDropAction(unsigned dragOperation)
42 {
43     Qt::DropAction result = Qt::IgnoreAction;
44     if (dragOperation & DragOperationCopy)
45         result = Qt::CopyAction;
46     else if (dragOperation & DragOperationMove)
47         result = Qt::MoveAction;
48     else if (dragOperation & DragOperationGeneric)
49         result = Qt::MoveAction;
50     else if (dragOperation & DragOperationLink)
51         result = Qt::LinkAction;
52     return result;
53 }
54
55 static inline Qt::DropActions dragOperationToDropActions(unsigned dragOperations)
56 {
57     Qt::DropActions result = Qt::IgnoreAction;
58     if (dragOperations & DragOperationCopy)
59         result |= Qt::CopyAction;
60     if (dragOperations & DragOperationMove)
61         result |= Qt::MoveAction;
62     if (dragOperations & DragOperationGeneric)
63         result |= Qt::MoveAction;
64     if (dragOperations & DragOperationLink)
65         result |= Qt::LinkAction;
66     return result;
67 }
68
69 static inline WebCore::DragOperation dropActionToDragOperation(Qt::DropActions actions)
70 {
71     unsigned result = 0;
72     if (actions & Qt::CopyAction)
73         result |= DragOperationCopy;
74     if (actions & Qt::MoveAction)
75         result |= (DragOperationMove | DragOperationGeneric);
76     if (actions & Qt::LinkAction)
77         result |= DragOperationLink;
78     if (result == (DragOperationCopy | DragOperationMove | DragOperationGeneric | DragOperationLink))
79         result = DragOperationEvery;
80     return (DragOperation)result;
81 }
82
83 QtWebPageEventHandler::QtWebPageEventHandler(WKPageRef pageRef, QQuickWebView* qmlWebView, WebKit::QtViewportInteractionEngine* viewportInteractionEngine)
84     : m_webPageProxy(toImpl(pageRef))
85     , m_interactionEngine(viewportInteractionEngine)
86     , m_panGestureRecognizer(this)
87     , m_pinchGestureRecognizer(this)
88     , m_tapGestureRecognizer(this)
89     , m_webView(qmlWebView)
90     , m_previousClickButton(Qt::NoButton)
91     , m_clickCount(0)
92 {
93 }
94
95 QtWebPageEventHandler::~QtWebPageEventHandler()
96 {
97 }
98
99 bool QtWebPageEventHandler::handleEvent(QEvent* ev)
100 {
101     switch (ev->type()) {
102     case QEvent::MouseMove:
103         return handleMouseMoveEvent(static_cast<QMouseEvent*>(ev));
104     case QEvent::MouseButtonPress:
105     case QEvent::MouseButtonDblClick:
106         // If a MouseButtonDblClick was received then we got a MouseButtonPress before
107         // handleMousePressEvent will take care of double clicks.
108         return handleMousePressEvent(static_cast<QMouseEvent*>(ev));
109     case QEvent::MouseButtonRelease:
110         return handleMouseReleaseEvent(static_cast<QMouseEvent*>(ev));
111     case QEvent::Wheel:
112         return handleWheelEvent(static_cast<QWheelEvent*>(ev));
113     case QEvent::HoverLeave:
114         return handleHoverLeaveEvent(static_cast<QHoverEvent*>(ev));
115     case QEvent::HoverEnter: // Fall-through, for WebKit the distinction doesn't matter.
116     case QEvent::HoverMove:
117         return handleHoverMoveEvent(static_cast<QHoverEvent*>(ev));
118     case QEvent::DragEnter:
119         return handleDragEnterEvent(static_cast<QDragEnterEvent*>(ev));
120     case QEvent::DragLeave:
121         return handleDragLeaveEvent(static_cast<QDragLeaveEvent*>(ev));
122     case QEvent::DragMove:
123         return handleDragMoveEvent(static_cast<QDragMoveEvent*>(ev));
124     case QEvent::Drop:
125         return handleDropEvent(static_cast<QDropEvent*>(ev));
126     case QEvent::KeyPress:
127         return handleKeyPressEvent(static_cast<QKeyEvent*>(ev));
128     case QEvent::KeyRelease:
129         return handleKeyReleaseEvent(static_cast<QKeyEvent*>(ev));
130     case QEvent::FocusIn:
131         return handleFocusInEvent(static_cast<QFocusEvent*>(ev));
132     case QEvent::FocusOut:
133         return handleFocusOutEvent(static_cast<QFocusEvent*>(ev));
134     case QEvent::TouchBegin:
135     case QEvent::TouchEnd:
136     case QEvent::TouchUpdate:
137         touchEvent(static_cast<QTouchEvent*>(ev));
138         return true;
139     }
140
141     // FIXME: Move all common event handling here.
142     return false;
143 }
144
145 bool QtWebPageEventHandler::handleMouseMoveEvent(QMouseEvent* ev)
146 {
147     // For some reason mouse press results in mouse hover (which is
148     // converted to mouse move for WebKit). We ignore these hover
149     // events by comparing lastPos with newPos.
150     // NOTE: lastPos from the event always comes empty, so we work
151     // around that here.
152     static QPointF lastPos = QPointF();
153     if (lastPos == ev->pos())
154         return ev->isAccepted();
155     lastPos = ev->pos();
156
157     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, /*eventClickCount*/ 0));
158
159     return ev->isAccepted();
160 }
161
162 bool QtWebPageEventHandler::handleMousePressEvent(QMouseEvent* ev)
163 {
164     if (m_clickTimer.isActive()
165         && m_previousClickButton == ev->button()
166         && (ev->pos() - m_lastClick).manhattanLength() < qApp->styleHints()->startDragDistance()) {
167         m_clickCount++;
168     } else {
169         m_clickCount = 1;
170         m_previousClickButton = ev->button();
171     }
172
173     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, m_clickCount));
174
175     m_lastClick = ev->pos();
176     m_clickTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), this);
177     return ev->isAccepted();
178 }
179
180 bool QtWebPageEventHandler::handleMouseReleaseEvent(QMouseEvent* ev)
181 {
182     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, /*eventClickCount*/ 0));
183     return ev->isAccepted();
184 }
185
186 bool QtWebPageEventHandler::handleWheelEvent(QWheelEvent* ev)
187 {
188     m_webPageProxy->handleWheelEvent(NativeWebWheelEvent(ev));
189     // FIXME: Handle whether the page used the wheel event or not.
190     if (m_interactionEngine)
191         m_interactionEngine->wheelEvent(ev);
192     return ev->isAccepted();
193 }
194
195 bool QtWebPageEventHandler::handleHoverLeaveEvent(QHoverEvent* ev)
196 {
197     // To get the correct behavior of mouseout, we need to turn the Leave event of our webview into a mouse move
198     // to a very far region.
199     QHoverEvent fakeEvent(QEvent::HoverMove, QPoint(INT_MIN, INT_MIN), ev->oldPos());
200     fakeEvent.setTimestamp(ev->timestamp());
201     return handleHoverMoveEvent(&fakeEvent);
202 }
203
204 bool QtWebPageEventHandler::handleHoverMoveEvent(QHoverEvent* ev)
205 {
206     QMouseEvent me(QEvent::MouseMove, ev->pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
207     me.setAccepted(ev->isAccepted());
208     me.setTimestamp(ev->timestamp());
209
210     return handleMouseMoveEvent(&me);
211 }
212
213 bool QtWebPageEventHandler::handleDragEnterEvent(QDragEnterEvent* ev)
214 {
215     m_webPageProxy->resetDragOperation();
216     // FIXME: Should not use QCursor::pos()
217     DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
218     m_webPageProxy->dragEntered(&dragData);
219     ev->acceptProposedAction();
220     return true;
221 }
222
223 bool QtWebPageEventHandler::handleDragLeaveEvent(QDragLeaveEvent* ev)
224 {
225     bool accepted = ev->isAccepted();
226
227     // FIXME: Should not use QCursor::pos()
228     DragData dragData(0, IntPoint(), QCursor::pos(), DragOperationNone);
229     m_webPageProxy->dragExited(&dragData);
230     m_webPageProxy->resetDragOperation();
231
232     ev->setAccepted(accepted);
233     return accepted;
234 }
235
236 bool QtWebPageEventHandler::handleDragMoveEvent(QDragMoveEvent* ev)
237 {
238     bool accepted = ev->isAccepted();
239
240     // FIXME: Should not use QCursor::pos()
241     DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
242     m_webPageProxy->dragUpdated(&dragData);
243     ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
244     if (m_webPageProxy->dragSession().operation != DragOperationNone)
245         ev->accept();
246
247     ev->setAccepted(accepted);
248     return accepted;
249 }
250
251 bool QtWebPageEventHandler::handleDropEvent(QDropEvent* ev)
252 {
253     bool accepted = ev->isAccepted();
254
255     // FIXME: Should not use QCursor::pos()
256     DragData dragData(ev->mimeData(), ev->pos(), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
257     SandboxExtension::Handle handle;
258     m_webPageProxy->performDrag(&dragData, String(), handle);
259     ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
260     ev->accept();
261
262     ev->setAccepted(accepted);
263     return accepted;
264 }
265
266 void QtWebPageEventHandler::handleSingleTapEvent(const QTouchEvent::TouchPoint& point)
267 {
268     WebGestureEvent gesture(WebEvent::GestureSingleTap, point.pos().toPoint(), point.screenPos().toPoint(), WebEvent::Modifiers(0), 0);
269     m_webPageProxy->handleGestureEvent(gesture);
270 }
271
272 void QtWebPageEventHandler::handleDoubleTapEvent(const QTouchEvent::TouchPoint& point)
273 {
274     m_webPageProxy->findZoomableAreaForPoint(point.pos().toPoint());
275 }
276
277 void QtWebPageEventHandler::timerEvent(QTimerEvent* ev)
278 {
279     int timerId = ev->timerId();
280     if (timerId == m_clickTimer.timerId())
281         m_clickTimer.stop();
282     else
283         QObject::timerEvent(ev);
284 }
285
286 bool QtWebPageEventHandler::handleKeyPressEvent(QKeyEvent* ev)
287 {
288     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
289     return true;
290 }
291
292 bool QtWebPageEventHandler::handleKeyReleaseEvent(QKeyEvent* ev)
293 {
294     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
295     return true;
296 }
297
298 bool QtWebPageEventHandler::handleFocusInEvent(QFocusEvent*)
299 {
300     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
301     return true;
302 }
303
304 bool QtWebPageEventHandler::handleFocusOutEvent(QFocusEvent*)
305 {
306     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
307     return true;
308 }
309
310 void QtWebPageEventHandler::setViewportInteractionEngine(QtViewportInteractionEngine* engine)
311 {
312     m_interactionEngine = engine;
313 }
314
315 void QtWebPageEventHandler::touchEvent(QTouchEvent* event)
316 {
317 #if ENABLE(TOUCH_EVENTS)
318     m_webPageProxy->handleTouchEvent(NativeWebTouchEvent(event));
319     event->accept();
320 #else
321     ASSERT_NOT_REACHED();
322     ev->ignore();
323 #endif
324 }
325
326 void QtWebPageEventHandler::resetGestureRecognizers()
327 {
328     m_panGestureRecognizer.reset();
329     m_pinchGestureRecognizer.reset();
330     m_tapGestureRecognizer.reset();
331 }
332
333 void QtWebPageEventHandler::doneWithTouchEvent(const NativeWebTouchEvent& event, bool wasEventHandled)
334 {
335     if (!m_interactionEngine)
336         return;
337
338     if (wasEventHandled || event.type() == WebEvent::TouchCancel) {
339         resetGestureRecognizers();
340         return;
341     }
342
343     const QTouchEvent* ev = event.nativeEvent();
344
345     switch (ev->type()) {
346     case QEvent::TouchBegin:
347         ASSERT(!m_interactionEngine->panGestureActive());
348         ASSERT(!m_interactionEngine->pinchGestureActive());
349
350         // The interaction engine might still be animating kinetic scrolling or a scale animation
351         // such as double-tap to zoom or the bounce back effect. A touch stops the kinetic scrolling
352         // where as it does not stop the scale animation.
353         if (m_interactionEngine->scrollAnimationActive())
354             m_interactionEngine->interruptScrollAnimation();
355         break;
356     case QEvent::TouchUpdate:
357         // The scale animation can only be interrupted by a pinch gesture, which will then take over.
358         if (m_interactionEngine->scaleAnimationActive() && m_pinchGestureRecognizer.isRecognized())
359             m_interactionEngine->interruptScaleAnimation();
360         break;
361     default:
362         break;
363     }
364
365     // If the scale animation is active we don't pass the event to the recognizers. In the future
366     // we would want to queue the event here and repost then when the animation ends.
367     if (m_interactionEngine->scaleAnimationActive())
368         return;
369
370     // Convert the event timestamp from second to millisecond.
371     qint64 eventTimestampMillis = static_cast<qint64>(event.timestamp() * 1000);
372     m_panGestureRecognizer.recognize(ev, eventTimestampMillis);
373     m_pinchGestureRecognizer.recognize(ev);
374
375     if (m_panGestureRecognizer.isRecognized() || m_pinchGestureRecognizer.isRecognized())
376         m_tapGestureRecognizer.reset();
377     else {
378         const QTouchEvent* ev = event.nativeEvent();
379         m_tapGestureRecognizer.recognize(ev, eventTimestampMillis);
380     }
381 }
382
383 void QtWebPageEventHandler::didFindZoomableArea(const IntPoint& target, const IntRect& area)
384 {
385     if (!m_interactionEngine)
386         return;
387
388     // FIXME: As the find method might not respond immediately during load etc,
389     // we should ignore all but the latest request.
390     m_interactionEngine->zoomToAreaGestureEnded(QPointF(target), QRectF(area));
391 }
392
393 void QtWebPageEventHandler::focusEditableArea(const IntRect& caret, const IntRect& area)
394 {
395     if (!m_interactionEngine)
396         return;
397
398     m_interactionEngine->focusEditableArea(QRectF(caret), QRectF(area));
399 }
400
401 void QtWebPageEventHandler::startDrag(const WebCore::DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
402 {
403     QImage dragQImage;
404     if (dragImage)
405         dragQImage = dragImage->createQImage();
406     else if (dragData.platformData() && dragData.platformData()->hasImage())
407         dragQImage = qvariant_cast<QImage>(dragData.platformData()->imageData());
408
409     DragOperation dragOperationMask = dragData.draggingSourceOperationMask();
410     QMimeData* mimeData = const_cast<QMimeData*>(dragData.platformData());
411     Qt::DropActions supportedDropActions = dragOperationToDropActions(dragOperationMask);
412
413     QPoint clientPosition;
414     QPoint globalPosition;
415     Qt::DropAction actualDropAction = Qt::IgnoreAction;
416
417     if (QWindow* window = m_webView->canvas()) {
418         QDrag* drag = new QDrag(window);
419         drag->setPixmap(QPixmap::fromImage(dragQImage));
420         drag->setMimeData(mimeData);
421         actualDropAction = drag->exec(supportedDropActions);
422         globalPosition = QCursor::pos();
423         clientPosition = window->mapFromGlobal(globalPosition);
424     }
425
426     m_webPageProxy->dragEnded(clientPosition, globalPosition, dropActionToDragOperation(actualDropAction));
427 }
428
429 #include "moc_QtWebPageEventHandler.cpp"