[Release] Webkit2-efl-123997_0.11.78
[framework/web/webkit-efl.git] / Tools / MiniBrowser / qt / MiniBrowserApplication.cpp
1 /*
2  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
3  * Copyright (C) 2010 University of Szeged
4  *
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "MiniBrowserApplication.h"
30
31 #include "BrowserWindow.h"
32 #include "QtInitializeTestFonts.h"
33 #include "private/qquickwebview_p.h"
34 #include "utils.h"
35 #include <QRegExp>
36 #include <QEvent>
37 #include <QMouseEvent>
38 #include <QTouchEvent>
39
40 static inline QRectF touchRectForPosition(QPointF centerPoint)
41 {
42     QRectF touchRect(0, 0, 40, 40);
43     touchRect.moveCenter(centerPoint);
44     return touchRect;
45 }
46
47 static inline bool isTouchEvent(const QEvent* event)
48 {
49     switch (event->type()) {
50     case QEvent::TouchBegin:
51     case QEvent::TouchUpdate:
52     case QEvent::TouchEnd:
53         return true;
54     default:
55         return false;
56     }
57 }
58
59 static inline bool isMouseEvent(const QEvent* event)
60 {
61     switch (event->type()) {
62     case QEvent::MouseButtonPress:
63     case QEvent::MouseMove:
64     case QEvent::MouseButtonRelease:
65     case QEvent::MouseButtonDblClick:
66         return true;
67     default:
68         return false;
69     }
70 }
71
72 MiniBrowserApplication::MiniBrowserApplication(int& argc, char** argv)
73     : QGuiApplication(argc, argv)
74     , m_realTouchEventReceived(false)
75     , m_pendingFakeTouchEventCount(0)
76     , m_isRobotized(false)
77     , m_robotTimeoutSeconds(0)
78     , m_robotExtraTimeSeconds(0)
79     , m_windowOptions(this)
80     , m_holdingControl(false)
81 {
82     setOrganizationName("Nokia");
83     setApplicationName("QtMiniBrowser");
84     setApplicationVersion("0.1");
85
86     handleUserOptions();
87 }
88
89 bool MiniBrowserApplication::notify(QObject* target, QEvent* event)
90 {
91     // We try to be smart, if we received real touch event, we are probably on a device
92     // with touch screen, and we should not have touch mocking.
93
94     if (!event->spontaneous() || m_realTouchEventReceived || !m_windowOptions.touchMockingEnabled())
95         return QGuiApplication::notify(target, event);
96
97     if (isTouchEvent(event) && static_cast<QTouchEvent*>(event)->deviceType() == QTouchEvent::TouchScreen) {
98         if (m_pendingFakeTouchEventCount)
99             --m_pendingFakeTouchEventCount;
100         else
101             m_realTouchEventReceived = true;
102         return QGuiApplication::notify(target, event);
103     }
104
105     BrowserWindow* browserWindow = qobject_cast<BrowserWindow*>(target);
106     if (!browserWindow)
107         return QGuiApplication::notify(target, event);
108
109     // In QML events are propagated through parents. But since the WebView
110     // may consume key events, a shortcut might never reach the top QQuickItem.
111     // Therefore we are checking here for shortcuts.
112     if (event->type() == QEvent::KeyPress) {
113         QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
114         if ((keyEvent->key() == Qt::Key_R && keyEvent->modifiers() == Qt::ControlModifier) || keyEvent->key() == Qt::Key_F5) {
115             browserWindow->reload();
116             return true;
117         }
118         if ((keyEvent->key() == Qt::Key_L && keyEvent->modifiers() == Qt::ControlModifier) || keyEvent->key() == Qt::Key_F6) {
119             browserWindow->focusAddressBar();
120             return true;
121         }
122     }
123
124     if (event->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(event)->key() == Qt::Key_Control) {
125         foreach (int id, m_heldTouchPoints)
126             if (m_touchPoints.contains(id))
127                 m_touchPoints[id].setState(Qt::TouchPointReleased);
128         m_heldTouchPoints.clear();
129         sendTouchEvent(browserWindow, QEvent::TouchEnd, static_cast<QKeyEvent*>(event)->timestamp());
130     }
131
132     if (isMouseEvent(event)) {
133         const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
134         m_holdingControl = mouseEvent->modifiers().testFlag(Qt::ControlModifier);
135
136         QTouchEvent::TouchPoint touchPoint;
137         touchPoint.setPressure(1);
138
139         QEvent::Type touchType = QEvent::None;
140
141         switch (mouseEvent->type()) {
142         case QEvent::MouseButtonPress:
143         case QEvent::MouseButtonDblClick:
144             touchPoint.setId(mouseEvent->button());
145             if (m_touchPoints.contains(touchPoint.id())) {
146                 touchPoint.setState(Qt::TouchPointMoved);
147                 touchType = QEvent::TouchUpdate;
148             } else {
149                 touchPoint.setState(Qt::TouchPointPressed);
150                 // Check if more buttons are held down than just the event triggering one.
151                 if (mouseEvent->buttons() > mouseEvent->button())
152                     touchType = QEvent::TouchUpdate;
153                 else
154                     touchType = QEvent::TouchBegin;
155             }
156             break;
157         case QEvent::MouseMove:
158             if (!mouseEvent->buttons()) {
159                 // We have to swallow the event instead of propagating it,
160                 // since we avoid sending the mouse release events and if the
161                 // Flickable is the mouse grabber it would receive the event
162                 // and would move the content.
163                 event->accept();
164                 return true;
165             }
166             touchType = QEvent::TouchUpdate;
167             touchPoint.setId(mouseEvent->buttons());
168             touchPoint.setState(Qt::TouchPointMoved);
169             break;
170         case QEvent::MouseButtonRelease:
171             // Check if any buttons are still held down after this event.
172             if (mouseEvent->buttons())
173                 touchType = QEvent::TouchUpdate;
174             else
175                 touchType = QEvent::TouchEnd;
176             touchPoint.setId(mouseEvent->button());
177             touchPoint.setState(Qt::TouchPointReleased);
178             break;
179         default:
180             Q_ASSERT_X(false, "multi-touch mocking", "unhandled event type");
181         }
182
183         // A move can have resulted in multiple buttons, so we need check them individually.
184         if (touchPoint.id() & Qt::LeftButton)
185             updateTouchPoint(mouseEvent, touchPoint, Qt::LeftButton);
186         if (touchPoint.id() & Qt::MidButton)
187             updateTouchPoint(mouseEvent, touchPoint, Qt::MidButton);
188         if (touchPoint.id() & Qt::RightButton)
189             updateTouchPoint(mouseEvent, touchPoint, Qt::RightButton);
190
191         if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
192             // We avoid sending the release event because the Flickable is
193             // listening to mouse events and would start a bounce-back
194             // animation if it received a mouse release.
195             event->accept();
196             return true;
197         }
198
199         // Update states for all other touch-points
200         for (QHash<int, QTouchEvent::TouchPoint>::iterator it = m_touchPoints.begin(), end = m_touchPoints.end(); it != end; ++it) {
201             if (!(it.value().id() & touchPoint.id()))
202                 it.value().setState(Qt::TouchPointStationary);
203         }
204
205         Q_ASSERT(touchType != QEvent::None);
206
207         if (!sendTouchEvent(browserWindow, touchType, mouseEvent->timestamp()))
208             return QGuiApplication::notify(target, event);
209
210         event->accept();
211         return true;
212     }
213
214     return QGuiApplication::notify(target, event);
215 }
216
217 void MiniBrowserApplication::updateTouchPoint(const QMouseEvent* mouseEvent, QTouchEvent::TouchPoint touchPoint, Qt::MouseButton mouseButton)
218 {
219     if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
220         m_heldTouchPoints.insert(mouseButton);
221         return;
222     }
223     // Gesture recognition uses the screen position for the initial threshold
224     // but since the canvas translates touch events we actually need to pass
225     // the screen position as the scene position to deliver the appropriate
226     // coordinates to the target.
227     touchPoint.setRect(touchRectForPosition(mouseEvent->localPos()));
228     touchPoint.setSceneRect(touchRectForPosition(mouseEvent->screenPos()));
229
230     if (touchPoint.state() == Qt::TouchPointPressed)
231         touchPoint.setStartScenePos(mouseEvent->screenPos());
232     else {
233         const QTouchEvent::TouchPoint& oldTouchPoint = m_touchPoints[mouseButton];
234         touchPoint.setStartScenePos(oldTouchPoint.startScenePos());
235         touchPoint.setLastPos(oldTouchPoint.pos());
236         touchPoint.setLastScenePos(oldTouchPoint.scenePos());
237     }
238
239     // Update current touch-point.
240     touchPoint.setId(mouseButton);
241     m_touchPoints.insert(mouseButton, touchPoint);
242 }
243
244
245 bool MiniBrowserApplication::sendTouchEvent(BrowserWindow* browserWindow, QEvent::Type type, ulong timestamp)
246 {
247     static QTouchDevice* device = 0;
248     if (!device) {
249         device = new QTouchDevice;
250         device->setType(QTouchDevice::TouchScreen);
251         QWindowSystemInterface::registerTouchDevice(device);
252     }
253
254     m_pendingFakeTouchEventCount++;
255
256     const QList<QTouchEvent::TouchPoint>& currentTouchPoints = m_touchPoints.values();
257     Qt::TouchPointStates touchPointStates = 0;
258     foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints)
259         touchPointStates |= touchPoint.state();
260
261     QTouchEvent event(type, device, Qt::NoModifier, touchPointStates, currentTouchPoints);
262     event.setTimestamp(timestamp);
263     event.setAccepted(false);
264
265     QGuiApplication::notify(browserWindow, &event);
266
267     if (QQuickWebViewExperimental::flickableViewportEnabled())
268         browserWindow->updateVisualMockTouchPoints(m_holdingControl ? currentTouchPoints : QList<QTouchEvent::TouchPoint>());
269
270     // Get rid of touch-points that are no longer valid
271     foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints) {
272         if (touchPoint.state() ==  Qt::TouchPointReleased)
273             m_touchPoints.remove(touchPoint.id());
274     }
275
276     return event.isAccepted();
277 }
278
279 static void printHelp(const QString& programName)
280 {
281     qDebug() << "Usage:" << programName.toLatin1().data()
282          << "[--desktop]"
283          << "[-r list]"
284          << "[--robot-timeout seconds]"
285          << "[--robot-extra-time seconds]"
286          << "[--window-size (width)x(height)]"
287          << "[--maximize]"
288          << "[-f]                                    Full screen mode."
289          << "[--user-agent string]"
290          << "[-v]"
291          << "URL";
292 }
293
294 void MiniBrowserApplication::handleUserOptions()
295 {
296     QStringList args = arguments();
297     QFileInfo program(args.takeAt(0));
298     QString programName("MiniBrowser");
299     if (program.exists())
300         programName = program.baseName();
301
302     if (takeOptionFlag(&args, "--help")) {
303         printHelp(programName);
304         appQuit(0);
305     }
306
307     const bool useDesktopBehavior = takeOptionFlag(&args, "--desktop");
308     if (useDesktopBehavior)
309         windowOptions()->setTouchMockingEnabled(false);
310
311     QQuickWebViewExperimental::setFlickableViewportEnabled(!useDesktopBehavior);
312     if (!useDesktopBehavior)
313         qputenv("QT_WEBKIT_USE_MOBILE_THEME", QByteArray("1"));
314     m_windowOptions.setPrintLoadedUrls(takeOptionFlag(&args, "-v"));
315     m_windowOptions.setStartMaximized(takeOptionFlag(&args, "--maximize"));
316     m_windowOptions.setStartFullScreen(takeOptionFlag(&args, "-f"));
317
318     if (args.contains("--user-agent"))
319         m_windowOptions.setUserAgent(takeOptionValue(&args, "--user-agent"));
320
321     if (args.contains("--window-size")) {
322         QString value = takeOptionValue(&args, "--window-size");
323         QStringList list = value.split(QRegExp("\\D+"), QString::SkipEmptyParts);
324         if (list.length() == 2)
325             m_windowOptions.setRequestedWindowSize(QSize(list.at(0).toInt(), list.at(1).toInt()));
326     }
327
328     if (takeOptionFlag(&args, QStringLiteral("--use-test-fonts")))
329         WebKit::initializeTestFonts();
330
331     if (args.contains("-r")) {
332         QString listFile = takeOptionValue(&args, "-r");
333         if (listFile.isEmpty())
334             appQuit(1, "-r needs a list file to start in robotized mode");
335         if (!QFile::exists(listFile))
336             appQuit(1, "The list file supplied to -r does not exist.");
337
338         m_isRobotized = true;
339         m_urls = QStringList(listFile);
340
341         // toInt() returns 0 if it fails parsing.
342         m_robotTimeoutSeconds = takeOptionValue(&args, "--robot-timeout").toInt();
343         m_robotExtraTimeSeconds = takeOptionValue(&args, "--robot-extra-time").toInt();
344     } else {
345         int urlArg;
346
347         while ((urlArg = args.indexOf(QRegExp("^[^-].*"))) != -1)
348             m_urls += args.takeAt(urlArg);
349     }
350
351     if (!args.isEmpty()) {
352         printHelp(programName);
353         appQuit(1, QString("Unknown argument(s): %1").arg(args.join(",")));
354     }
355 }