2 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
3 * Copyright (C) 2010 University of Szeged
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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.
29 #include "MiniBrowserApplication.h"
31 #include "BrowserWindow.h"
32 #include "QtInitializeTestFonts.h"
33 #include "private/qquickwebview_p.h"
37 #include <QMouseEvent>
38 #include <QTouchEvent>
40 static inline QRectF touchRectForPosition(QPointF centerPoint)
42 QRectF touchRect(0, 0, 40, 40);
43 touchRect.moveCenter(centerPoint);
47 static inline bool isTouchEvent(const QEvent* event)
49 switch (event->type()) {
50 case QEvent::TouchBegin:
51 case QEvent::TouchUpdate:
52 case QEvent::TouchEnd:
59 static inline bool isMouseEvent(const QEvent* event)
61 switch (event->type()) {
62 case QEvent::MouseButtonPress:
63 case QEvent::MouseMove:
64 case QEvent::MouseButtonRelease:
65 case QEvent::MouseButtonDblClick:
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)
82 setOrganizationName("Nokia");
83 setApplicationName("QtMiniBrowser");
84 setApplicationVersion("0.1");
89 bool MiniBrowserApplication::notify(QObject* target, QEvent* event)
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.
94 if (!event->spontaneous() || m_realTouchEventReceived || !m_windowOptions.touchMockingEnabled())
95 return QGuiApplication::notify(target, event);
97 if (isTouchEvent(event) && static_cast<QTouchEvent*>(event)->deviceType() == QTouchEvent::TouchScreen) {
98 if (m_pendingFakeTouchEventCount)
99 --m_pendingFakeTouchEventCount;
101 m_realTouchEventReceived = true;
102 return QGuiApplication::notify(target, event);
105 BrowserWindow* browserWindow = qobject_cast<BrowserWindow*>(target);
107 return QGuiApplication::notify(target, event);
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();
118 if ((keyEvent->key() == Qt::Key_L && keyEvent->modifiers() == Qt::ControlModifier) || keyEvent->key() == Qt::Key_F6) {
119 browserWindow->focusAddressBar();
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());
132 if (isMouseEvent(event)) {
133 const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
134 m_holdingControl = mouseEvent->modifiers().testFlag(Qt::ControlModifier);
136 QTouchEvent::TouchPoint touchPoint;
137 touchPoint.setPressure(1);
139 QEvent::Type touchType = QEvent::None;
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;
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;
154 touchType = QEvent::TouchBegin;
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.
166 touchType = QEvent::TouchUpdate;
167 touchPoint.setId(mouseEvent->buttons());
168 touchPoint.setState(Qt::TouchPointMoved);
170 case QEvent::MouseButtonRelease:
171 // Check if any buttons are still held down after this event.
172 if (mouseEvent->buttons())
173 touchType = QEvent::TouchUpdate;
175 touchType = QEvent::TouchEnd;
176 touchPoint.setId(mouseEvent->button());
177 touchPoint.setState(Qt::TouchPointReleased);
180 Q_ASSERT_X(false, "multi-touch mocking", "unhandled event type");
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);
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.
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);
205 Q_ASSERT(touchType != QEvent::None);
207 if (!sendTouchEvent(browserWindow, touchType, mouseEvent->timestamp()))
208 return QGuiApplication::notify(target, event);
214 return QGuiApplication::notify(target, event);
217 void MiniBrowserApplication::updateTouchPoint(const QMouseEvent* mouseEvent, QTouchEvent::TouchPoint touchPoint, Qt::MouseButton mouseButton)
219 if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) {
220 m_heldTouchPoints.insert(mouseButton);
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()));
230 if (touchPoint.state() == Qt::TouchPointPressed)
231 touchPoint.setStartScenePos(mouseEvent->screenPos());
233 const QTouchEvent::TouchPoint& oldTouchPoint = m_touchPoints[mouseButton];
234 touchPoint.setStartScenePos(oldTouchPoint.startScenePos());
235 touchPoint.setLastPos(oldTouchPoint.pos());
236 touchPoint.setLastScenePos(oldTouchPoint.scenePos());
239 // Update current touch-point.
240 touchPoint.setId(mouseButton);
241 m_touchPoints.insert(mouseButton, touchPoint);
245 bool MiniBrowserApplication::sendTouchEvent(BrowserWindow* browserWindow, QEvent::Type type, ulong timestamp)
247 static QTouchDevice* device = 0;
249 device = new QTouchDevice;
250 device->setType(QTouchDevice::TouchScreen);
251 QWindowSystemInterface::registerTouchDevice(device);
254 m_pendingFakeTouchEventCount++;
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();
261 QTouchEvent event(type, device, Qt::NoModifier, touchPointStates, currentTouchPoints);
262 event.setTimestamp(timestamp);
263 event.setAccepted(false);
265 QGuiApplication::notify(browserWindow, &event);
267 if (QQuickWebViewExperimental::flickableViewportEnabled())
268 browserWindow->updateVisualMockTouchPoints(m_holdingControl ? currentTouchPoints : QList<QTouchEvent::TouchPoint>());
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());
276 return event.isAccepted();
279 static void printHelp(const QString& programName)
281 qDebug() << "Usage:" << programName.toLatin1().data()
284 << "[--robot-timeout seconds]"
285 << "[--robot-extra-time seconds]"
286 << "[--window-size (width)x(height)]"
288 << "[-f] Full screen mode."
289 << "[--user-agent string]"
294 void MiniBrowserApplication::handleUserOptions()
296 QStringList args = arguments();
297 QFileInfo program(args.takeAt(0));
298 QString programName("MiniBrowser");
299 if (program.exists())
300 programName = program.baseName();
302 if (takeOptionFlag(&args, "--help")) {
303 printHelp(programName);
307 const bool useDesktopBehavior = takeOptionFlag(&args, "--desktop");
308 if (useDesktopBehavior)
309 windowOptions()->setTouchMockingEnabled(false);
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"));
318 if (args.contains("--user-agent"))
319 m_windowOptions.setUserAgent(takeOptionValue(&args, "--user-agent"));
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()));
328 if (takeOptionFlag(&args, QStringLiteral("--use-test-fonts")))
329 WebKit::initializeTestFonts();
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.");
338 m_isRobotized = true;
339 m_urls = QStringList(listFile);
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();
347 while ((urlArg = args.indexOf(QRegExp("^[^-].*"))) != -1)
348 m_urls += args.takeAt(urlArg);
351 if (!args.isEmpty()) {
352 printHelp(programName);
353 appQuit(1, QString("Unknown argument(s): %1").arg(args.join(",")));