[Qt][WK2] Move non-api classes to WebKit namespace at WebKit2/UiProcess/qt
[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 "WebPageProxy.h"
29 #include "qquickwebpage_p.h"
30 #include "qquickwebview_p.h"
31 #include <QCursor>
32 #include <QDrag>
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 WebCore;
45
46 namespace WebKit {
47
48 static inline Qt::DropAction dragOperationToDropAction(unsigned dragOperation)
49 {
50     Qt::DropAction result = Qt::IgnoreAction;
51     if (dragOperation & DragOperationCopy)
52         result = Qt::CopyAction;
53     else if (dragOperation & DragOperationMove)
54         result = Qt::MoveAction;
55     else if (dragOperation & DragOperationGeneric)
56         result = Qt::MoveAction;
57     else if (dragOperation & DragOperationLink)
58         result = Qt::LinkAction;
59     return result;
60 }
61
62 static inline Qt::DropActions dragOperationToDropActions(unsigned dragOperations)
63 {
64     Qt::DropActions result = Qt::IgnoreAction;
65     if (dragOperations & DragOperationCopy)
66         result |= Qt::CopyAction;
67     if (dragOperations & DragOperationMove)
68         result |= Qt::MoveAction;
69     if (dragOperations & DragOperationGeneric)
70         result |= Qt::MoveAction;
71     if (dragOperations & DragOperationLink)
72         result |= Qt::LinkAction;
73     return result;
74 }
75
76 static inline WebCore::DragOperation dropActionToDragOperation(Qt::DropActions actions)
77 {
78     unsigned result = 0;
79     if (actions & Qt::CopyAction)
80         result |= DragOperationCopy;
81     if (actions & Qt::MoveAction)
82         result |= (DragOperationMove | DragOperationGeneric);
83     if (actions & Qt::LinkAction)
84         result |= DragOperationLink;
85     if (result == (DragOperationCopy | DragOperationMove | DragOperationGeneric | DragOperationLink))
86         result = DragOperationEvery;
87     return (DragOperation)result;
88 }
89
90 QtWebPageEventHandler::QtWebPageEventHandler(WKPageRef pageRef, QQuickWebPage* qmlWebPage, QQuickWebView* qmlWebView)
91     : m_webPageProxy(toImpl(pageRef))
92     , m_interactionEngine(0)
93     , m_panGestureRecognizer(this)
94     , m_pinchGestureRecognizer(this)
95     , m_tapGestureRecognizer(this)
96     , m_webPage(qmlWebPage)
97     , m_webView(qmlWebView)
98     , m_previousClickButton(Qt::NoButton)
99     , m_clickCount(0)
100     , m_postponeTextInputStateChanged(false)
101 {
102     connect(qApp->inputPanel(), SIGNAL(visibleChanged()), this, SLOT(inputPanelVisibleChanged()));
103 }
104
105 QtWebPageEventHandler::~QtWebPageEventHandler()
106 {
107     disconnect(qApp->inputPanel(), SIGNAL(visibleChanged()), this, SLOT(inputPanelVisibleChanged()));
108 }
109
110 void QtWebPageEventHandler::handleMouseMoveEvent(QMouseEvent* ev)
111 {
112     // For some reason mouse press results in mouse hover (which is
113     // converted to mouse move for WebKit). We ignore these hover
114     // events by comparing lastPos with newPos.
115     // NOTE: lastPos from the event always comes empty, so we work
116     // around that here.
117     static QPointF lastPos = QPointF();
118     QTransform fromItemTransform = m_webPage->transformFromItem();
119     QPointF webPagePoint = fromItemTransform.map(ev->localPos());
120     if (lastPos == webPagePoint)
121         return;
122     lastPos = webPagePoint;
123
124     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, /*eventClickCount*/ 0));
125 }
126
127 void QtWebPageEventHandler::handleMousePressEvent(QMouseEvent* ev)
128 {
129     QTransform fromItemTransform = m_webPage->transformFromItem();
130     QPointF webPagePoint = fromItemTransform.map(ev->localPos());
131
132     if (m_clickTimer.isActive()
133         && m_previousClickButton == ev->button()
134         && (webPagePoint - m_lastClick).manhattanLength() < qApp->styleHints()->startDragDistance()) {
135         m_clickCount++;
136     } else {
137         m_clickCount = 1;
138         m_previousClickButton = ev->button();
139     }
140
141     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, m_clickCount));
142
143     m_lastClick = webPagePoint;
144     m_clickTimer.start(qApp->styleHints()->mouseDoubleClickInterval(), this);
145 }
146
147 void QtWebPageEventHandler::handleMouseReleaseEvent(QMouseEvent* ev)
148 {
149     QTransform fromItemTransform = m_webPage->transformFromItem();
150     m_webPageProxy->handleMouseEvent(NativeWebMouseEvent(ev, fromItemTransform, /*eventClickCount*/ 0));
151 }
152
153 void QtWebPageEventHandler::handleWheelEvent(QWheelEvent* ev)
154 {
155     QTransform fromItemTransform = m_webPage->transformFromItem();
156     m_webPageProxy->handleWheelEvent(NativeWebWheelEvent(ev, fromItemTransform));
157     // FIXME: Handle whether the page used the wheel event or not.
158     if (m_interactionEngine)
159         m_interactionEngine->wheelEvent(ev);
160 }
161
162 void QtWebPageEventHandler::handleHoverLeaveEvent(QHoverEvent* ev)
163 {
164     // To get the correct behavior of mouseout, we need to turn the Leave event of our webview into a mouse move
165     // to a very far region.
166     QTransform fromItemTransform = m_webPage->transformFromItem();
167     QHoverEvent fakeEvent(QEvent::HoverMove, QPoint(INT_MIN, INT_MIN), fromItemTransform.map(ev->oldPosF()));
168     fakeEvent.setTimestamp(ev->timestamp());
169     handleHoverMoveEvent(&fakeEvent);
170 }
171
172 void QtWebPageEventHandler::handleHoverMoveEvent(QHoverEvent* ev)
173 {
174     QTransform fromItemTransform = m_webPage->transformFromItem();
175     QMouseEvent me(QEvent::MouseMove, fromItemTransform.map(ev->posF()), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
176     me.setAccepted(ev->isAccepted());
177     me.setTimestamp(ev->timestamp());
178     handleMouseMoveEvent(&me);
179 }
180
181 void QtWebPageEventHandler::handleDragEnterEvent(QDragEnterEvent* ev)
182 {
183     m_webPageProxy->resetDragOperation();
184     QTransform fromItemTransform = m_webPage->transformFromItem();
185     // FIXME: Should not use QCursor::pos()
186     DragData dragData(ev->mimeData(), fromItemTransform.map(ev->pos()), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
187     m_webPageProxy->dragEntered(&dragData);
188     ev->acceptProposedAction();
189 }
190
191 void QtWebPageEventHandler::handleDragLeaveEvent(QDragLeaveEvent* ev)
192 {
193     bool accepted = ev->isAccepted();
194
195     // FIXME: Should not use QCursor::pos()
196     DragData dragData(0, IntPoint(), QCursor::pos(), DragOperationNone);
197     m_webPageProxy->dragExited(&dragData);
198     m_webPageProxy->resetDragOperation();
199
200     ev->setAccepted(accepted);
201 }
202
203 void QtWebPageEventHandler::handleDragMoveEvent(QDragMoveEvent* ev)
204 {
205     bool accepted = ev->isAccepted();
206
207     QTransform fromItemTransform = m_webPage->transformFromItem();
208     // FIXME: Should not use QCursor::pos()
209     DragData dragData(ev->mimeData(), fromItemTransform.map(ev->pos()), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
210     m_webPageProxy->dragUpdated(&dragData);
211     ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
212     if (m_webPageProxy->dragSession().operation != DragOperationNone)
213         ev->accept();
214
215     ev->setAccepted(accepted);
216 }
217
218 void QtWebPageEventHandler::handleDropEvent(QDropEvent* ev)
219 {
220     bool accepted = ev->isAccepted();
221     QTransform fromItemTransform = m_webPage->transformFromItem();
222     // FIXME: Should not use QCursor::pos()
223     DragData dragData(ev->mimeData(), fromItemTransform.map(ev->pos()), QCursor::pos(), dropActionToDragOperation(ev->possibleActions()));
224     SandboxExtension::Handle handle;
225     SandboxExtension::HandleArray sandboxExtensionForUpload;
226     m_webPageProxy->performDrag(&dragData, String(), handle, sandboxExtensionForUpload);
227     ev->setDropAction(dragOperationToDropAction(m_webPageProxy->dragSession().operation));
228     ev->accept();
229
230     ev->setAccepted(accepted);
231 }
232
233 void QtWebPageEventHandler::handlePotentialSingleTapEvent(const QTouchEvent::TouchPoint& point)
234 {
235 #if ENABLE(TOUCH_EVENTS)
236     if (point.pos() == QPointF()) {
237         // An empty point deactivates the highlighting.
238         m_webPageProxy->handlePotentialActivation(IntPoint(), IntSize());
239     } else {
240         QTransform fromItemTransform = m_webPage->transformFromItem();
241         m_webPageProxy->handlePotentialActivation(IntPoint(fromItemTransform.map(point.pos()).toPoint()), IntSize(point.rect().size().toSize()));
242     }
243 #else
244     Q_UNUSED(point);
245 #endif
246 }
247
248 void QtWebPageEventHandler::handleSingleTapEvent(const QTouchEvent::TouchPoint& point)
249 {
250     m_postponeTextInputStateChanged = true;
251
252     QTransform fromItemTransform = m_webPage->transformFromItem();
253     WebGestureEvent gesture(WebEvent::GestureSingleTap, fromItemTransform.map(point.pos()).toPoint(), point.screenPos().toPoint(), WebEvent::Modifiers(0), 0, IntSize(point.rect().size().toSize()), FloatPoint(0, 0));
254     m_webPageProxy->handleGestureEvent(gesture);
255 }
256
257 void QtWebPageEventHandler::handleDoubleTapEvent(const QTouchEvent::TouchPoint& point)
258 {
259     QTransform fromItemTransform = m_webPage->transformFromItem();
260     m_webPageProxy->findZoomableAreaForPoint(fromItemTransform.map(point.pos()).toPoint(), IntSize(point.rect().size().toSize()));
261 }
262
263 void QtWebPageEventHandler::timerEvent(QTimerEvent* ev)
264 {
265     int timerId = ev->timerId();
266     if (timerId == m_clickTimer.timerId())
267         m_clickTimer.stop();
268     else
269         QObject::timerEvent(ev);
270 }
271
272 void QtWebPageEventHandler::handleKeyPressEvent(QKeyEvent* ev)
273 {
274     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
275 }
276
277 void QtWebPageEventHandler::handleKeyReleaseEvent(QKeyEvent* ev)
278 {
279     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
280 }
281
282 void QtWebPageEventHandler::handleFocusInEvent(QFocusEvent*)
283 {
284     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
285 }
286
287 void QtWebPageEventHandler::handleFocusOutEvent(QFocusEvent*)
288 {
289     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
290 }
291
292 void QtWebPageEventHandler::setViewportInteractionEngine(QtViewportInteractionEngine* engine)
293 {
294     m_interactionEngine = engine;
295 }
296
297 void QtWebPageEventHandler::handleInputMethodEvent(QInputMethodEvent* ev)
298 {
299     QString commit = ev->commitString();
300     QString composition = ev->preeditString();
301
302     int replacementStart = ev->replacementStart();
303     int replacementLength = ev->replacementLength();
304
305     // NOTE: We might want to handle events of one char as special
306     // and resend them as key events to make web site completion work.
307
308     int cursorPositionWithinComposition = 0;
309
310     Vector<CompositionUnderline> underlines;
311
312     for (int i = 0; i < ev->attributes().size(); ++i) {
313         const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
314         switch (attr.type) {
315         case QInputMethodEvent::TextFormat: {
316             if (composition.isEmpty())
317                 break;
318
319             QTextCharFormat textCharFormat = attr.value.value<QTextFormat>().toCharFormat();
320             QColor qcolor = textCharFormat.underlineColor();
321             Color color = makeRGBA(qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha());
322             int start = qMin(attr.start, (attr.start + attr.length));
323             int end = qMax(attr.start, (attr.start + attr.length));
324             underlines.append(CompositionUnderline(start, end, color, false));
325             break;
326         }
327         case QInputMethodEvent::Cursor:
328             if (attr.length)
329                 cursorPositionWithinComposition = attr.start;
330             break;
331         // Selection is handled further down.
332         default: break;
333         }
334     }
335
336     if (composition.isEmpty()) {
337         int selectionStart = -1;
338         int selectionLength = 0;
339         for (int i = 0; i < ev->attributes().size(); ++i) {
340             const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
341             if (attr.type == QInputMethodEvent::Selection) {
342                 selectionStart = attr.start;
343                 selectionLength = attr.length;
344
345                 ASSERT(selectionStart >= 0);
346                 ASSERT(selectionLength >= 0);
347                 break;
348             }
349         }
350
351         m_webPageProxy->confirmComposition(commit, selectionStart, selectionLength);
352     } else {
353         ASSERT(cursorPositionWithinComposition >= 0);
354         ASSERT(replacementStart >= 0);
355
356         m_webPageProxy->setComposition(composition, underlines,
357             cursorPositionWithinComposition, cursorPositionWithinComposition,
358             replacementStart, replacementLength);
359     }
360
361     ev->accept();
362 }
363
364 void QtWebPageEventHandler::handleTouchEvent(QTouchEvent* event)
365 {
366 #if ENABLE(TOUCH_EVENTS)
367     QTransform fromItemTransform = m_webPage->transformFromItem();
368     m_webPageProxy->handleTouchEvent(NativeWebTouchEvent(event, fromItemTransform));
369     event->accept();
370 #else
371     ASSERT_NOT_REACHED();
372     event->ignore();
373 #endif
374 }
375
376 void QtWebPageEventHandler::resetGestureRecognizers()
377 {
378     m_panGestureRecognizer.cancel();
379     m_pinchGestureRecognizer.cancel();
380     m_tapGestureRecognizer.cancel();
381 }
382
383 static void setInputPanelVisible(bool visible)
384 {
385     if (qApp->inputPanel()->visible() == visible)
386         return;
387
388     qApp->inputPanel()->setVisible(visible);
389 }
390
391 void QtWebPageEventHandler::inputPanelVisibleChanged()
392 {
393     if (!m_interactionEngine)
394         return;
395
396     // We only respond to the input panel becoming visible.
397     if (!m_webView->hasFocus() || !qApp->inputPanel()->visible())
398         return;
399
400     const EditorState& editor = m_webPageProxy->editorState();
401     if (editor.isContentEditable)
402         m_interactionEngine->focusEditableArea(QRectF(editor.cursorRect), QRectF(editor.editorRect));
403 }
404
405 void QtWebPageEventHandler::updateTextInputState()
406 {
407     if (m_postponeTextInputStateChanged)
408         return;
409
410     const EditorState& editor = m_webPageProxy->editorState();
411
412     if (!m_webView->hasFocus())
413         return;
414
415     // Ignore input method requests not due to a tap gesture.
416     if (!editor.isContentEditable)
417         setInputPanelVisible(false);
418 }
419
420 void QtWebPageEventHandler::doneWithGestureEvent(const WebGestureEvent& event, bool wasEventHandled)
421 {
422     if (event.type() != WebEvent::GestureSingleTap)
423         return;
424
425     m_postponeTextInputStateChanged = false;
426
427     if (!wasEventHandled || !m_webView->hasFocus())
428         return;
429
430     const EditorState& editor = m_webPageProxy->editorState();
431     bool newVisible = editor.isContentEditable;
432
433     setInputPanelVisible(newVisible);
434 }
435
436 #if ENABLE(TOUCH_EVENTS)
437 void QtWebPageEventHandler::doneWithTouchEvent(const NativeWebTouchEvent& event, bool wasEventHandled)
438 {
439     if (!m_interactionEngine)
440         return;
441
442     if (wasEventHandled || event.type() == WebEvent::TouchCancel) {
443         resetGestureRecognizers();
444         return;
445     }
446
447     const QTouchEvent* ev = event.nativeEvent();
448
449     switch (ev->type()) {
450     case QEvent::TouchBegin:
451         ASSERT(!m_interactionEngine->panGestureActive());
452         ASSERT(!m_interactionEngine->pinchGestureActive());
453         m_interactionEngine->touchBegin();
454
455         // The interaction engine might still be animating kinetic scrolling or a scale animation
456         // such as double-tap to zoom or the bounce back effect. A touch stops the kinetic scrolling
457         // where as it does not stop the scale animation.
458         // The gesture recognizer stops the kinetic scrolling animation if needed.
459         break;
460     case QEvent::TouchUpdate:
461         // The scale animation can only be interrupted by a pinch gesture, which will then take over.
462         if (m_interactionEngine->scaleAnimationActive() && m_pinchGestureRecognizer.isRecognized())
463             m_interactionEngine->interruptScaleAnimation();
464         break;
465     case QEvent::TouchEnd:
466         m_interactionEngine->touchEnd();
467         break;
468     default:
469         break;
470     }
471
472     // If the scale animation is active we don't pass the event to the recognizers. In the future
473     // we would want to queue the event here and repost then when the animation ends.
474     if (m_interactionEngine->scaleAnimationActive())
475         return;
476
477     const QList<QTouchEvent::TouchPoint>& touchPoints = ev->touchPoints();
478     const int touchPointCount = touchPoints.size();
479     qint64 eventTimestampMillis = ev->timestamp();
480     QList<QTouchEvent::TouchPoint> activeTouchPoints;
481     activeTouchPoints.reserve(touchPointCount);
482
483     for (int i = 0; i < touchPointCount; ++i) {
484         if (touchPoints[i].state() != Qt::TouchPointReleased)
485             activeTouchPoints << touchPoints[i];
486     }
487
488     const int activeTouchPointCount = activeTouchPoints.size();
489
490     if (!activeTouchPointCount) {
491         if (touchPointCount == 1)
492            // No active touch points, one finger released.
493            m_panGestureRecognizer.finish(touchPoints.first(), eventTimestampMillis);
494         else
495            m_pinchGestureRecognizer.finish();
496     } else if (activeTouchPointCount == 1) {
497         // If the pinch gesture recognizer was previously in active state the content might
498         // be out of valid zoom boundaries, thus we need to finish the pinch gesture here.
499         // This will resume the content to valid zoom levels before the pan gesture is started.
500         m_pinchGestureRecognizer.finish();
501         m_panGestureRecognizer.update(activeTouchPoints.first(), eventTimestampMillis);
502     } else if (activeTouchPointCount == 2) {
503         m_panGestureRecognizer.cancel();
504         m_pinchGestureRecognizer.update(activeTouchPoints.first(), activeTouchPoints.last());
505     }
506
507     if (m_panGestureRecognizer.isRecognized() || m_pinchGestureRecognizer.isRecognized())
508         m_tapGestureRecognizer.cancel();
509     else if (touchPointCount == 1)
510         m_tapGestureRecognizer.update(ev->type(), touchPoints.first());
511 }
512 #endif
513
514 void QtWebPageEventHandler::didFindZoomableArea(const IntPoint& target, const IntRect& area)
515 {
516     if (!m_interactionEngine)
517         return;
518
519     // FIXME: As the find method might not respond immediately during load etc,
520     // we should ignore all but the latest request.
521     m_interactionEngine->zoomToAreaGestureEnded(QPointF(target), QRectF(area));
522 }
523
524 void QtWebPageEventHandler::startDrag(const WebCore::DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
525 {
526     QImage dragQImage;
527     if (dragImage)
528         dragQImage = dragImage->createQImage();
529     else if (dragData.platformData() && dragData.platformData()->hasImage())
530         dragQImage = qvariant_cast<QImage>(dragData.platformData()->imageData());
531
532     DragOperation dragOperationMask = dragData.draggingSourceOperationMask();
533     QMimeData* mimeData = const_cast<QMimeData*>(dragData.platformData());
534     Qt::DropActions supportedDropActions = dragOperationToDropActions(dragOperationMask);
535
536     QPoint clientPosition;
537     QPoint globalPosition;
538     Qt::DropAction actualDropAction = Qt::IgnoreAction;
539
540     if (QWindow* window = m_webPage->canvas()) {
541         QDrag* drag = new QDrag(window);
542         drag->setPixmap(QPixmap::fromImage(dragQImage));
543         drag->setMimeData(mimeData);
544         actualDropAction = drag->exec(supportedDropActions);
545         globalPosition = QCursor::pos();
546         clientPosition = window->mapFromGlobal(globalPosition);
547     }
548
549     m_webPageProxy->dragEnded(clientPosition, globalPosition, dropActionToDragOperation(actualDropAction));
550 }
551
552 } // namespace WebKit
553
554 #include "moc_QtWebPageEventHandler.cpp"
555