[Qt][WK2] Build failure when using --no-touch-events
[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 #if ENABLE(TOUCH_EVENTS)
234     QTransform fromItemTransform = m_webPage->transformFromItem();
235     m_webPageProxy->handlePotentialActivation(fromItemTransform.map(point.pos()).toPoint());
236 #else
237     Q_UNUSED(point);
238 #endif
239 }
240
241 void QtWebPageEventHandler::handleSingleTapEvent(const QTouchEvent::TouchPoint& point)
242 {
243     m_postponeTextInputStateChanged = true;
244
245     QTransform fromItemTransform = m_webPage->transformFromItem();
246     WebGestureEvent gesture(WebEvent::GestureSingleTap, fromItemTransform.map(point.pos()).toPoint(), point.screenPos().toPoint(), WebEvent::Modifiers(0), 0, IntSize(point.rect().size().toSize()), FloatPoint(0, 0));
247     m_webPageProxy->handleGestureEvent(gesture);
248 }
249
250 void QtWebPageEventHandler::handleDoubleTapEvent(const QTouchEvent::TouchPoint& point)
251 {
252     QTransform fromItemTransform = m_webPage->transformFromItem();
253     m_webPageProxy->findZoomableAreaForPoint(fromItemTransform.map(point.pos()).toPoint());
254 }
255
256 void QtWebPageEventHandler::timerEvent(QTimerEvent* ev)
257 {
258     int timerId = ev->timerId();
259     if (timerId == m_clickTimer.timerId())
260         m_clickTimer.stop();
261     else
262         QObject::timerEvent(ev);
263 }
264
265 void QtWebPageEventHandler::handleKeyPressEvent(QKeyEvent* ev)
266 {
267     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
268 }
269
270 void QtWebPageEventHandler::handleKeyReleaseEvent(QKeyEvent* ev)
271 {
272     m_webPageProxy->handleKeyboardEvent(NativeWebKeyboardEvent(ev));
273 }
274
275 void QtWebPageEventHandler::handleFocusInEvent(QFocusEvent*)
276 {
277     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
278 }
279
280 void QtWebPageEventHandler::handleFocusOutEvent(QFocusEvent*)
281 {
282     m_webPageProxy->viewStateDidChange(WebPageProxy::ViewIsFocused | WebPageProxy::ViewWindowIsActive);
283 }
284
285 void QtWebPageEventHandler::setViewportInteractionEngine(QtViewportInteractionEngine* engine)
286 {
287     m_interactionEngine = engine;
288 }
289
290 void QtWebPageEventHandler::handleInputMethodEvent(QInputMethodEvent* ev)
291 {
292     QString commit = ev->commitString();
293     QString composition = ev->preeditString();
294
295     int replacementStart = ev->replacementStart();
296     int replacementLength = ev->replacementLength();
297
298     // NOTE: We might want to handle events of one char as special
299     // and resend them as key events to make web site completion work.
300
301     int cursorPositionWithinComposition = 0;
302
303     Vector<CompositionUnderline> underlines;
304
305     for (int i = 0; i < ev->attributes().size(); ++i) {
306         const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
307         switch (attr.type) {
308         case QInputMethodEvent::TextFormat: {
309             if (composition.isEmpty())
310                 break;
311
312             QTextCharFormat textCharFormat = attr.value.value<QTextFormat>().toCharFormat();
313             QColor qcolor = textCharFormat.underlineColor();
314             Color color = makeRGBA(qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha());
315             int start = qMin(attr.start, (attr.start + attr.length));
316             int end = qMax(attr.start, (attr.start + attr.length));
317             underlines.append(CompositionUnderline(start, end, color, false));
318             break;
319         }
320         case QInputMethodEvent::Cursor:
321             if (attr.length)
322                 cursorPositionWithinComposition = attr.start;
323             break;
324         // Selection is handled further down.
325         default: break;
326         }
327     }
328
329     if (composition.isEmpty()) {
330         int selectionStart = -1;
331         int selectionLength = 0;
332         for (int i = 0; i < ev->attributes().size(); ++i) {
333             const QInputMethodEvent::Attribute& attr = ev->attributes().at(i);
334             if (attr.type == QInputMethodEvent::Selection) {
335                 selectionStart = attr.start;
336                 selectionLength = attr.length;
337
338                 ASSERT(selectionStart >= 0);
339                 ASSERT(selectionLength >= 0);
340                 break;
341             }
342         }
343
344         m_webPageProxy->confirmComposition(commit, selectionStart, selectionLength);
345     } else {
346         ASSERT(cursorPositionWithinComposition >= 0);
347         ASSERT(replacementStart >= 0);
348
349         m_webPageProxy->setComposition(composition, underlines,
350             cursorPositionWithinComposition, cursorPositionWithinComposition,
351             replacementStart, replacementLength);
352     }
353
354     ev->accept();
355 }
356
357 void QtWebPageEventHandler::handleTouchEvent(QTouchEvent* event)
358 {
359 #if ENABLE(TOUCH_EVENTS)
360     QTransform fromItemTransform = m_webPage->transformFromItem();
361     m_webPageProxy->handleTouchEvent(NativeWebTouchEvent(event, fromItemTransform));
362     event->accept();
363 #else
364     ASSERT_NOT_REACHED();
365     event->ignore();
366 #endif
367 }
368
369 void QtWebPageEventHandler::resetGestureRecognizers()
370 {
371     m_panGestureRecognizer.reset();
372     m_pinchGestureRecognizer.reset();
373     m_tapGestureRecognizer.reset();
374 }
375
376 static void setInputPanelVisible(bool visible)
377 {
378     if (qApp->inputPanel()->visible() == visible)
379         return;
380
381     qApp->inputPanel()->setVisible(visible);
382 }
383
384 void QtWebPageEventHandler::inputPanelVisibleChanged()
385 {
386     if (!m_interactionEngine)
387         return;
388
389     // We only respond to the input panel becoming visible.
390     if (!m_webView->hasFocus() || !qApp->inputPanel()->visible())
391         return;
392
393     const EditorState& editor = m_webPageProxy->editorState();
394     if (editor.isContentEditable)
395         m_interactionEngine->focusEditableArea(QRectF(editor.cursorRect), QRectF(editor.editorRect));
396 }
397
398 void QtWebPageEventHandler::updateTextInputState()
399 {
400     if (m_postponeTextInputStateChanged)
401         return;
402
403     const EditorState& editor = m_webPageProxy->editorState();
404
405     if (!m_webView->hasFocus())
406         return;
407
408     // Ignore input method requests not due to a tap gesture.
409     if (!editor.isContentEditable)
410         setInputPanelVisible(false);
411 }
412
413 void QtWebPageEventHandler::doneWithGestureEvent(const WebGestureEvent& event, bool wasEventHandled)
414 {
415     if (event.type() != WebEvent::GestureSingleTap)
416         return;
417
418     m_postponeTextInputStateChanged = false;
419
420     if (!wasEventHandled || !m_webView->hasFocus())
421         return;
422
423     const EditorState& editor = m_webPageProxy->editorState();
424     bool newVisible = editor.isContentEditable;
425
426     setInputPanelVisible(newVisible);
427 }
428
429 #if ENABLE(TOUCH_EVENTS)
430 void QtWebPageEventHandler::doneWithTouchEvent(const NativeWebTouchEvent& event, bool wasEventHandled)
431 {
432     if (!m_interactionEngine)
433         return;
434
435     if (wasEventHandled || event.type() == WebEvent::TouchCancel) {
436         resetGestureRecognizers();
437         return;
438     }
439
440     const QTouchEvent* ev = event.nativeEvent();
441
442     switch (ev->type()) {
443     case QEvent::TouchBegin:
444         ASSERT(!m_interactionEngine->panGestureActive());
445         ASSERT(!m_interactionEngine->pinchGestureActive());
446
447         // The interaction engine might still be animating kinetic scrolling or a scale animation
448         // such as double-tap to zoom or the bounce back effect. A touch stops the kinetic scrolling
449         // where as it does not stop the scale animation.
450         // Sending the event to the flickProvider will stop the kinetic scrolling animation.
451         break;
452     case QEvent::TouchUpdate:
453         // The scale animation can only be interrupted by a pinch gesture, which will then take over.
454         if (m_interactionEngine->scaleAnimationActive() && m_pinchGestureRecognizer.isRecognized())
455             m_interactionEngine->interruptScaleAnimation();
456         break;
457     default:
458         break;
459     }
460
461     // If the scale animation is active we don't pass the event to the recognizers. In the future
462     // we would want to queue the event here and repost then when the animation ends.
463     if (m_interactionEngine->scaleAnimationActive())
464         return;
465
466     m_panGestureRecognizer.recognize(ev);
467     m_pinchGestureRecognizer.recognize(ev);
468
469     if (m_panGestureRecognizer.isRecognized() || m_pinchGestureRecognizer.isRecognized())
470         m_tapGestureRecognizer.reset();
471     else {
472         // Convert the event timestamp from second to millisecond.
473         qint64 eventTimestampMillis = static_cast<qint64>(event.timestamp() * 1000);
474         m_tapGestureRecognizer.recognize(ev, eventTimestampMillis);
475     }
476 }
477 #endif
478
479 void QtWebPageEventHandler::didFindZoomableArea(const IntPoint& target, const IntRect& area)
480 {
481     if (!m_interactionEngine)
482         return;
483
484     // FIXME: As the find method might not respond immediately during load etc,
485     // we should ignore all but the latest request.
486     m_interactionEngine->zoomToAreaGestureEnded(QPointF(target), QRectF(area));
487 }
488
489 void QtWebPageEventHandler::startDrag(const WebCore::DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
490 {
491     QImage dragQImage;
492     if (dragImage)
493         dragQImage = dragImage->createQImage();
494     else if (dragData.platformData() && dragData.platformData()->hasImage())
495         dragQImage = qvariant_cast<QImage>(dragData.platformData()->imageData());
496
497     DragOperation dragOperationMask = dragData.draggingSourceOperationMask();
498     QMimeData* mimeData = const_cast<QMimeData*>(dragData.platformData());
499     Qt::DropActions supportedDropActions = dragOperationToDropActions(dragOperationMask);
500
501     QPoint clientPosition;
502     QPoint globalPosition;
503     Qt::DropAction actualDropAction = Qt::IgnoreAction;
504
505     if (QWindow* window = m_webPage->canvas()) {
506         QDrag* drag = new QDrag(window);
507         drag->setPixmap(QPixmap::fromImage(dragQImage));
508         drag->setMimeData(mimeData);
509         actualDropAction = drag->exec(supportedDropActions);
510         globalPosition = QCursor::pos();
511         clientPosition = window->mapFromGlobal(globalPosition);
512     }
513
514     m_webPageProxy->dragEnded(clientPosition, globalPosition, dropActionToDragOperation(actualDropAction));
515 }
516
517 #include "moc_QtWebPageEventHandler.cpp"