diff options
author | Andras Becsi <andras.becsi@theqtcompany.com> | 2015-06-22 21:38:53 +0100 |
---|---|---|
committer | Andras Becsi <andras.becsi@theqtcompany.com> | 2015-08-12 17:21:42 +0200 |
commit | 13730318d4261fbc9f3225a27e38578e61b75583 (patch) | |
tree | 4c2b47c703d4297a01f043bed46b31649c5fdf4a |
Initial code drop
24 files changed, 1817 insertions, 0 deletions
diff --git a/qtbrowser.pro b/qtbrowser.pro new file mode 100644 index 0000000..069da53 --- /dev/null +++ b/qtbrowser.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS += src diff --git a/src/browserwindow.cpp b/src/browserwindow.cpp new file mode 100644 index 0000000..5823014 --- /dev/null +++ b/src/browserwindow.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "browserwindow.h" +#include "utils.h" + +#include <QList> +#include <QQmlContext> +#include <QQmlEngine> +#include <QQuickItem> +#include <QRectF> +#include <QUrl> +#include <QVariant> + +namespace { + +static QUrl startupUrl() +{ + QUrl ret; + QStringList args(qApp->arguments()); + args.takeFirst(); + Q_FOREACH (const QString& arg, args) { + if (arg.startsWith(QLatin1Char('-'))) + continue; + ret = Utils::fromUserInput(arg); + if (ret.isValid()) + return ret; + } + return QUrl(QStringLiteral("http://qt.io/")); +} + +} + +void BrowserWindow::ensureProfileInstance() +{ + if (m_lazyProfileInstance) + return; + QQmlComponent *component = new QQmlComponent(engine(), this); + + component->setData( + QByteArrayLiteral("import QtQuick 2.0\n" + "import QtWebEngine 1.1\n" + "WebEngineProfile {\n" + " storageName: \"YABProfile\"\n" + "}") + , QUrl()); + m_lazyProfileInstance = component->create(engine()->rootContext()); + Q_ASSERT(m_lazyProfileInstance); + QQmlEngine::setObjectOwnership(m_lazyProfileInstance, QQmlEngine::JavaScriptOwnership); +} + +QObject *BrowserWindow::defaultProfile() +{ + ensureProfileInstance(); + return m_lazyProfileInstance; +} + +BrowserWindow::BrowserWindow(QWindow *) + : m_lazyProfileInstance(0) +{ + setTitle("Yet Another Browser"); + setFlags(Qt::Window | Qt::WindowTitleHint); + setResizeMode(QQuickView::SizeRootObjectToView); + setColor(Qt::black); + + engine()->rootContext()->setContextProperty("engine", new Utils(this)); + setSource(QUrl("qrc:///qml/BrowserWindow.qml")); +} + +BrowserWindow::~BrowserWindow() +{ +} + +void BrowserWindow::updateVisualMockTouchPoints(const QList<QTouchEvent::TouchPoint>& touchPoints) +{ + if (touchPoints.isEmpty()) { + // Hide all touch indicator items. + foreach (QQuickItem* item, m_activeMockComponents.values()) + item->setProperty("pressed", false); + + return; + } + + foreach (const QTouchEvent::TouchPoint& touchPoint, touchPoints) { + QQuickItem* mockTouchPointItem = m_activeMockComponents.value(touchPoint.id()); + + if (!mockTouchPointItem) { + QQmlComponent touchMockPointComponent(engine(), QUrl("qrc:///qml/MockTouchPoint.qml")); + mockTouchPointItem = qobject_cast<QQuickItem*>(touchMockPointComponent.create()); + Q_ASSERT(mockTouchPointItem); + m_activeMockComponents.insert(touchPoint.id(), mockTouchPointItem); + mockTouchPointItem->setProperty("pointId", QVariant(touchPoint.id())); + mockTouchPointItem->setParent(rootObject()); + mockTouchPointItem->setParentItem(rootObject()); + } + + QRectF touchRect = touchPoint.rect(); + mockTouchPointItem->setX(touchRect.center().x()); + mockTouchPointItem->setY(touchRect.center().y()); + mockTouchPointItem->setWidth(touchRect.width()); + mockTouchPointItem->setHeight(touchRect.height()); + mockTouchPointItem->setProperty("pressed", QVariant(touchPoint.state() != Qt::TouchPointReleased)); + } +} diff --git a/src/browserwindow.h b/src/browserwindow.h new file mode 100644 index 0000000..1d055bf --- /dev/null +++ b/src/browserwindow.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef BROWSERWINDOW_H +#define BROWSERWINDOW_H + +#include <QHash> +#include <QTouchEvent> +#include <QQuickView> +#include <QQmlComponent> + +class BrowserWindow : public QQuickView +{ + Q_OBJECT + + QHash<int, QQuickItem*> m_activeMockComponents; + QObject *m_lazyProfileInstance; + + void ensureProfileInstance(); +public: + BrowserWindow(QWindow *parent = 0); + ~BrowserWindow(); + + void updateVisualMockTouchPoints(const QList<QTouchEvent::TouchPoint>& touchPoints); + +public Q_SLOTS: + QObject *defaultProfile(); +}; + +#endif // BROWSERWINDOW_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..339847b --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QQmlContext> +#include <QQmlEngine> +#include <QQuickView> +#include <QtWebEngine/qtwebengineglobal.h> + +#include "touchmockingapplication.h" +#include "utils.h" + +int main(int argc, char **argv) +{ + qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")); +#if defined(HOST_BUILD) + // We use touch mocking on desktop and apply all the mobile switches. + QByteArrayList args = QByteArrayList() + << QByteArrayLiteral("--enable-embedded-switches"); + const int count = args.size() + argc; + QVector<char*> qargv(count); + + qargv[0] = argv[0]; + for (int i = 0; i < args.size(); ++i) + qargv[i + 1] = args[i].data(); + for (int i = args.size() + 1; i < count; ++i) + qargv[i] = argv[i - args.size()]; + + int qAppArgCount = qargv.size(); + TouchMockingApplication app(qAppArgCount, qargv.data()); +#else + QGuiApplication app(argc, argv); +#endif + QtWebEngine::initialize(); + + BrowserWindow window; + QObject::connect(window.rootContext()->engine(), SIGNAL(quit()), &app, SLOT(quit())); + +#if defined(HOST_BUILD) + window.show(); + if (window.size().isEmpty()) + window.setGeometry(0, 0, 800, 600); +#else + window.showFullScreen(); +#endif + + app.exec(); +} diff --git a/src/qml/BrowserDialog.qml b/src/qml/BrowserDialog.qml new file mode 100644 index 0000000..d463be6 --- /dev/null +++ b/src/qml/BrowserDialog.qml @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Window 2.2 +import QtWebEngine 1.1 + +Window { + property alias defaultProfile: webView.profile + property alias currentWebView: webView + flags: Qt.Dialog + width: 800 + height: 600 + visible: true + onClosing: destroy() + WebEngineView { + id: webView + anchors.fill: parent + } +} diff --git a/src/qml/BrowserWindow.qml b/src/qml/BrowserWindow.qml new file mode 100644 index 0000000..642c16c --- /dev/null +++ b/src/qml/BrowserWindow.qml @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.1 +import QtWebEngine 1.1 +import QtWebEngine.experimental 1.0 + +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Window 2.1 +import QtQuick.Controls.Private 1.0 +import Qt.labs.settings 1.0 +import QtQuick.Dialogs 1.2 + +import "assets" + +Item { + id: browserWindow + + property alias currentWebView: tabs.currentWebView + property int visibility: Window.Windowed + property int previousVisibility: Window.Windowed + + property string uiColor: "#46a1da" + property string uiSelectionColor: "#fad84a" + property string uiBorderColor: "#377fac" + + property QtObject otrProfile: WebEngineProfile { + offTheRecord: true + } + + property bool isFullScreen: visibility == Window.FullScreen + onIsFullScreenChanged: { + // This is for the case where the system forces us to leave fullscreen. + if (currentWebView && !isFullScreen) { + currentWebView.state = "" + if (currentWebView.isFullScreen) + currentWebView.fullScreenCancelled() + } + } + + height: 600 + width: 800 + visible: true + + Settings { + id : appSettings + property alias autoLoadImages: loadImages.checked; + property alias javaScriptEnabled: javaScriptEnabled.checked; + property alias errorPageEnabled: errorPageEnabled.checked; + property alias pluginsEnabled: pluginsEnabled.checked; + } + + Action { + shortcut: "Ctrl+D" + onTriggered: { + downloadView.visible = !downloadView.visible + } + } + + Action { + id: focus + shortcut: "Ctrl+L" + onTriggered: { + addressBar.forceActiveFocus(); + addressBar.selectAll(); + } + } + Action { + shortcut: "Ctrl+R" + onTriggered: { + if (currentWebView) + currentWebView.reload() + } + } + Action { + shortcut: "Ctrl+T" + onTriggered: { + tabs.createEmptyTab() + tabs.currentIndex = tabs.count - 1 + addressBar.forceActiveFocus(); + addressBar.selectAll(); + } + } + Action { + shortcut: "Ctrl+W" + onTriggered: { + if (tabs.count == 1) + browserWindow.close() + else + tabs.remove(tabs.currentIndex) + } + } + + Action { + shortcut: "Escape" + onTriggered: { + if (browserWindow.isFullScreen) + browserWindow.visibility = browserWindow.previousVisibility + } + } + + ToolBar { + id: navigationBar + + style: ToolBarStyle { + padding { + left: 8 + right: 8 + top: 3 + bottom: 3 + } + background: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + color: "#46a1da" + } + } + + RowLayout { + anchors.fill: parent; + + UIButton { + id: backButton + source: "qrc:///previous.png" + color: uiColor + onClicked: tabs.currentWebView.goBack() + enabled: tabs.currentWebView && tabs.currentWebView.canGoBack + } + + UIButton { + id: forwardButton + source: "qrc:///next.png" + color: uiColor + onClicked: tabs.currentWebView.goForward() + enabled: tabs.currentWebView && tabs.currentWebView.canGoForward + } + + + TextField { + id: addressBar + UIButton { + id: reloadButton + source: currentWebView && currentWebView.loading ? "qrc:///stop.png" : "qrc:///refresh.png" + anchors { + rightMargin: 10 + right: parent.right + verticalCenter: addressBar.verticalCenter; + } + onClicked: currentWebView.loading ? currentWebView.stop() : currentWebView.reload() + } + style: TextFieldStyle { + textColor: "black" + font.family: "Helvetica" + font.pointSize: 18 + selectionColor: uiSelectionColor + background: Rectangle { + implicitWidth: 200 + implicitHeight: 40 + border.color: uiBorderColor + border.width: 1 + } + padding { + right: 10 + left: 10 + } + } + focus: true + Layout.fillWidth: true + text: tabs.currentWebView ? tabs.currentWebView.url : "about:blank" + onAccepted: { + console.log("WEBVIEW "+ tabs.get(tabs.currentIndex).item.webView.url) + tabs.get(tabs.currentIndex).item.webView.url = engine.fromUserInput(text) + tabs.viewState = "page" + } + } + + UIButton { + id: pageViewButton + source: "qrc:///tabs.png" + color: uiColor + onClicked: { + if (tabs.viewState == "list") { + tabs.viewState = "page" + } else { + tabs.viewState = "list" + } + console.log("BUTTON " + tabs.viewState) + } + } + + ToolButton { + id: settingsMenuButton + menu: Menu { + MenuItem { + id: loadImages + text: "Autoload images" + checkable: true + checked: true + } + MenuItem { + id: javaScriptEnabled + text: "JavaScript On" + checkable: true + checked: true + } + MenuItem { + id: errorPageEnabled + text: "ErrorPage On" + checkable: true + checked: true + } + MenuItem { + id: pluginsEnabled + text: "Plugins On" + checkable: true + checked: true + } + MenuItem { + id: offTheRecordEnabled + text: "Off The Record" + checkable: true + checked: currentWebView ? currentWebView.profile.offTheRecord : true + onToggled: currentWebView.profile = (checked ? otrProfile : engine.rootWindow.defaultProfile()); + } + MenuItem { + id: httpDiskCacheEnabled + text: "HTTP Disk Cache" + checkable: currentWebView && !currentWebView.profile.offTheRecord + checked: currentWebView && (currentWebView.profile.httpCacheType == WebEngineProfile.DiskHttpCache) + onToggled: currentWebView.profile.httpCacheType = checked ? WebEngineProfile.DiskHttpCache : WebEngineProfile.MemoryHttpCache; + } + } + } + } + ProgressBar { + id: progressBar + height: 3 + anchors { + left: parent.left + top: parent.bottom + right: parent.right + leftMargin: -parent.leftMargin + rightMargin: -parent.rightMargin + } + style: ProgressBarStyle { + background: Item {} + } + z: -2; + minimumValue: 0 + maximumValue: 100 + value: (currentWebView && currentWebView.loadProgress < 100) ? currentWebView.loadProgress : 0 + } + } + + PageView { + id: tabs + + itemWidth: browserWindow.width / 2 + itemHeight: browserWindow.height / 2 + + anchors { + top: navigationBar.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + + Component.onCompleted: { + var tab = createEmptyTab() + tab.webView.url = "about:blank" + } + } + + QtObject{ + id:acceptedCertificates + + property var acceptedUrls : [] + + function shouldAutoAccept(certificateError){ + var domain = engine.domainFromString(certificateError.url) + return acceptedUrls.indexOf(domain) >= 0 + } + } + + MessageDialog { + id: sslDialog + + property var certErrors: [] + icon: StandardIcon.Warning + standardButtons: StandardButton.No | StandardButton.Yes + title: "Server's certificate not trusted" + text: "Do you wish to continue?" + detailedText: "If you wish so, you may continue with an unverified certificate. " + + "Accepting an unverified certificate means " + + "you may not be connected with the host you tried to connect to.\n" + + "Do you wish to override the security check and continue?" + onYes: { + var cert = certErrors.shift() + var domain = engine.domainFromString(cert.url) + acceptedCertificates.acceptedUrls.push(domain) + cert.ignoreCertificateError() + presentError() + } + onNo: reject() + onRejected: reject() + + function reject(){ + certErrors.shift().rejectCertificate() + presentError() + } + function enqueue(error){ + certErrors.push(error) + presentError() + } + function presentError(){ + visible = certErrors.length > 0 + } + } +} diff --git a/src/qml/FeaturePermissionBar.qml b/src/qml/FeaturePermissionBar.qml new file mode 100644 index 0000000..908c85b --- /dev/null +++ b/src/qml/FeaturePermissionBar.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtWebEngine 1.1 +import QtQuick.Layouts 1.0 + +Rectangle { + property var requestedFeature; + property url securityOrigin; + property WebEngineView view; + + id: permissionBar + visible: false + height: acceptButton.height + 4 + + onRequestedFeatureChanged: { + message.text = securityOrigin + " wants to access " + message.textForFeature(requestedFeature); + } + + + RowLayout { + anchors { + fill: permissionBar + leftMargin: 5 + rightMargin: 5 + } + Label { + id: message + Layout.fillWidth: true + + function textForFeature(feature) { + if (feature === WebEngineView.MediaAudioCapture) + return "your microphone" + if (feature === WebEngineView.MediaVideoCapture) + return "your camera" + if (feature === WebEngineView.MediaAudioVideoCapture) + return "your camera and microphone" + if (feature === WebEngineView.Geolocation) + return "your position" + } + } + + Button { + id: acceptButton + text: "Accept" + Layout.alignment: Qt.AlignRight + onClicked: { + view.grantFeaturePermission(securityOrigin, requestedFeature, true); + permissionBar.visible = false; + } + } + + Button { + text: "Deny" + Layout.alignment: Qt.AlignRight + onClicked: { + view.grantFeaturePermission(securityOrigin, requestedFeature, false); + permissionBar.visible = false + } + } + } +} diff --git a/src/qml/LoadIndicator.qml b/src/qml/LoadIndicator.qml new file mode 100644 index 0000000..43af34c --- /dev/null +++ b/src/qml/LoadIndicator.qml @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + id: container + + property bool running: false + property string imageSource: "busy.png" + + visible: running + + Image { + id: image + anchors.centerIn: parent + source: container.imageSource + ParallelAnimation { + running: container.running + NumberAnimation { target: image; property: "rotation"; from: 0; to: 360; loops: Animation.Infinite; duration: 1200 } + } + } +} diff --git a/src/qml/MockTouchPoint.qml b/src/qml/MockTouchPoint.qml new file mode 100644 index 0000000..4784ff7 --- /dev/null +++ b/src/qml/MockTouchPoint.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + id: mockTouchPoint + + property bool pressed: false + property int pointId: 0 + + Image { + source: "qrc:///touchpoint.png" + x: -(width / 2) + y: -(height / 2) + height: parent.height + width: parent.width + opacity: parent.pressed ? 0.6 : 0.0 + + Behavior on opacity { + NumberAnimation { duration: 200 } + } + + Text { + text: mockTouchPoint.pointId + anchors.centerIn: parent + } + } +} diff --git a/src/qml/PageView.qml b/src/qml/PageView.qml new file mode 100644 index 0000000..ec1b205 --- /dev/null +++ b/src/qml/PageView.qml @@ -0,0 +1,395 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtWebEngine 1.2 +import QtWebEngine.experimental 1.0 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 + +Rectangle { + id: root + property int animationDuration: 200 + property int tabDisplacement: 100 + property int itemWidth: root.width / 2 + property int itemHeight: root.height / 2 + + property alias currentIndex: pathView.currentIndex + property alias count: pathView.count + property Item currentWebView: { + console.log(count) + return get(currentIndex) ? get(currentIndex).item.webView : null + } + + + property string viewState: "page" + + Component { + id: tabComponent + Item { + id: tabItem + property alias webView: webEngineView + property alias title: webEngineView.title + + property var image: QtObject { + property string imageUrl: "qrc:///busy.png" + property string url: "about:blank" + } + + function grabImage() { + if (image.url == webEngineView.url) + return + + image.url = webEngineView.url + webEngineView.grabToImage(function(result) { + image.imageUrl = result.url; + console.log("grabImage("+result.url+")") + }); + } + + visible: opacity != 0.0 + + Behavior on opacity { + NumberAnimation { duration: animationDuration / 4; easing.type: Easing.InQuad} + } + + anchors.fill: parent + + Action { + shortcut: "Ctrl+F" + onTriggered: { + findBar.visible = !findBar.visible + if (findBar.visible) { + findTextField.forceActiveFocus() + } + } + } + FeaturePermissionBar { + id: permBar + view: webEngineView + anchors { + left: parent.left + right: parent.right + top: parent.top + } + z: 3 + } + + WebEngineView { + id: webEngineView + + //visible: parent.visible + + anchors { + fill: parent + top: permBar.bottom + } + +// settings.autoLoadImages: appSettings.autoLoadImages +// settings.javascriptEnabled: appSettings.javaScriptEnabled +// settings.errorPageEnabled: appSettings.errorPageEnabled +// settings.pluginsEnabled: appSettings.pluginsEnabled + + onCertificateError: { + if (!acceptedCertificates.shouldAutoAccept(error)){ + error.defer() + sslDialog.enqueue(error) + } else{ + error.ignoreCertificateError() + } + } + + onNewViewRequested: { + if (!request.userInitiated) + print("Warning: Blocked a popup window.") + else if (request.destination == WebEngineView.NewViewInTab) { + var tab = tabs.createEmptyTab() + tabs.currentIndex = tabs.count - 1 + console.log("newWindow inTab") + request.openIn(tab.webView) + } else if (request.destination == WebEngineView.NewViewInBackgroundTab) { + var tab = tabs.createEmptyTab() + console.log("newWindow inBackTab") + request.openIn(tab.webView) + } else if (request.destination == WebEngineView.NewViewInDialog) { + var dialog = engine.rootWindow.newDialog() + request.openIn(dialog.currentWebView) + } else { + console.log("newWindow BLA") + var window = engine.rootWindow.newWindow() + request.openIn(window.currentWebView) + } + } + + onFullScreenRequested: { + if (request.toggleOn) { + webEngineView.state = "FullScreen" + browserWindow.previousVisibility = browserWindow.visibility + browserWindow.showFullScreen() + } else { + webEngineView.state = "" + browserWindow.visibility = browserWindow.previousVisibility + } + request.accept() + } + + onFeaturePermissionRequested: { + permBar.securityOrigin = securityOrigin; + permBar.requestedFeature = feature; + permBar.visible = true; + } + + onLoadingChanged: { + if (!loading && visible) + grabImage() + } + } + + Rectangle { + id: findBar + anchors.top: webEngineView.top + anchors.right: webEngineView.right + width: 240 + height: 35 + border.color: "lightgray" + border.width: 1 + radius: 5 + visible: false + color: "gray" + + RowLayout { + anchors.centerIn: findBar + TextField { + id: findTextField + onAccepted: { + webEngineView.findText(text) + } + } + ToolButton { + id: findBackwardButton + iconSource: "qrc:///previous.png" + onClicked: webEngineView.findText(findTextField.text, WebEngineView.FindBackward) + } + ToolButton { + id: findForwardButton + iconSource: "qrc:///next.png" + onClicked: webEngineView.findText(findTextField.text) + } + ToolButton { + id: findCancelButton + iconSource: "qrc:///stop.png" + onClicked: findBar.visible = false + } + } + } + } + } + + ListModel { + id: listModel + } + + function createEmptyTab() { + var tab = add(tabComponent) + //timer.running = true + return tab + } + + function add(component) { + var element = {"item": null } + element.item = component.createObject(root, { "anchors.top": root.top, "anchors.left": root.left, "width": root.width, "height": root.height }) + + if (element.item == null) { + // Error Handling + console.log("Error creating object"); + return + } + + element.item.webView.profile = engine.rootWindow.defaultProfile() + element.item.webView.url = "about:blank" + element.index = listModel.count + listModel.append(element) + + console.log("index " + element.index) + return element.item + } + + function remove(index) { + listModel.remove(index) + } + + function get(index) { + return listModel.get(index) + } + + Component { + id: delegate + + Rectangle { + id: wrapper + + state: index == pathView.currentIndex ? root.viewState : "list" + + property real visibility: 1.0 + + visible: visibility != 0.0 + + onStateChanged: { + console.log("WRAPPER " + state) + } + + states: [ + State { + name: "page" + PropertyChanges { target: wrapper; x: 0; y: 0; width: root.width; height: root.height; color: "grey"; visibility: 0.0 } + PropertyChanges { target: pathView; interactive: false } + PropertyChanges { target: item; opacity: 1.0; visible: visibility < 0.1; z: 5 } + }, + State { + name: "list" + PropertyChanges { target: wrapper; width: itemWidth; height: itemHeight; color: "white"; visibility: 1.0 } + PropertyChanges { target: pathView; interactive: true } + PropertyChanges { target: item; opacity: 0.0; visible: opacity != 0.0 } + } + ] + + transitions: Transition { + SequentialAnimation { + ParallelAnimation { + ColorAnimation { property: "color"; duration: animationDuration } + PropertyAnimation { properties: "x,y"; duration: animationDuration } + PropertyAnimation { properties: "width,height"; duration: animationDuration; easing.type: Easing.OutQuad } + PropertyAnimation { properties: "visibility"; duration: animationDuration; easing.type: Easing.OutQuad } + } + } + } + + width: itemWidth; height: itemHeight + scale: PathView.isCurrentItem ? 1 : 0.5 + z: PathView.isCurrentItem ? 1 : 0 + + Column { + anchors.fill: wrapper + Rectangle { + color: "#83bfe5" + + + Image { + smooth: false + source: item.image.imageUrl + anchors.fill: parent + } + + anchors.horizontalCenter: parent.horizontalCenter + width: wrapper.width + height: wrapper.height + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: item.title + font.pointSize: 12 + color: wrapper.PathView.isCurrentItem ? uiBorderColor : uiColor + } + } + MouseArea { + anchors.fill: wrapper + onClicked: { + var pos = pathView.mapFromItem(wrapper, mouse.x, mouse.y) + var index = pathView.indexAt(pos.x, pos.y) + + if (index < 0) + return + + if (index === pathView.currentIndex) { + if (root.viewState == "list") + root.viewState = "page" + return + } + + if (pathView.currentIndex == 0 && index === pathView.count - 1) { + pathView.decrementCurrentIndex() + return + } + + if (pathView.currentIndex == pathView.count - 1 && index == 0) { + pathView.incrementCurrentIndex() + return + } + + if (pathView.currentIndex > index) { + pathView.decrementCurrentIndex() + return + } + + if (pathView.currentIndex < index) { + pathView.incrementCurrentIndex() + return + } + } + } + Behavior on scale { + NumberAnimation { duration: animationDuration } + } + } + } + + PathView { + id: pathView + pathItemCount: 3 + anchors.fill: parent + model: listModel + delegate: delegate + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + + path: Path { + id: path + startX: 0 - itemWidth / 4; startY: root.height / 2 - tabDisplacement + //PathLine { x: root.width; y: root.height / 2 } + PathCurve { x: root.width / 4; y: root.height / 2 - tabDisplacement / 2 } + PathCurve { x: root.width / 2; y: root.height / 2 } + PathCurve { x: 3/4 * root.width; y: root.height / 2 - tabDisplacement / 2 } + PathCurve { x: root.width + itemWidth / 4; y: root.height / 2 - tabDisplacement } + } + + focus: true + Keys.onLeftPressed: decrementCurrentIndex() + Keys.onRightPressed: incrementCurrentIndex() + } +} diff --git a/src/qml/assets/UIButton.qml b/src/qml/assets/UIButton.qml new file mode 100644 index 0000000..a415f3f --- /dev/null +++ b/src/qml/assets/UIButton.qml @@ -0,0 +1,25 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.5 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 + + +ToolButton { + id: root + + property string source; + property string color: "white"; + style: ButtonStyle { + background: Rectangle { + implicitWidth: 30 + implicitHeight: 30 + radius: 4 + color: root.color + Image { + source: root.source + anchors.fill: parent + } + } + } +} + diff --git a/src/qml/assets/icons/busy.png b/src/qml/assets/icons/busy.png Binary files differnew file mode 100644 index 0000000..3b60d26 --- /dev/null +++ b/src/qml/assets/icons/busy.png diff --git a/src/qml/assets/icons/home.png b/src/qml/assets/icons/home.png Binary files differnew file mode 100644 index 0000000..92d01d1 --- /dev/null +++ b/src/qml/assets/icons/home.png diff --git a/src/qml/assets/icons/next.png b/src/qml/assets/icons/next.png Binary files differnew file mode 100644 index 0000000..757ca9e --- /dev/null +++ b/src/qml/assets/icons/next.png diff --git a/src/qml/assets/icons/previous.png b/src/qml/assets/icons/previous.png Binary files differnew file mode 100644 index 0000000..4eadb93 --- /dev/null +++ b/src/qml/assets/icons/previous.png diff --git a/src/qml/assets/icons/refresh.png b/src/qml/assets/icons/refresh.png Binary files differnew file mode 100644 index 0000000..585152f --- /dev/null +++ b/src/qml/assets/icons/refresh.png diff --git a/src/qml/assets/icons/stop.png b/src/qml/assets/icons/stop.png Binary files differnew file mode 100644 index 0000000..26d585b --- /dev/null +++ b/src/qml/assets/icons/stop.png diff --git a/src/qml/assets/icons/tabs.png b/src/qml/assets/icons/tabs.png Binary files differnew file mode 100644 index 0000000..492d83d --- /dev/null +++ b/src/qml/assets/icons/tabs.png diff --git a/src/qml/assets/icons/touchpoint.png b/src/qml/assets/icons/touchpoint.png Binary files differnew file mode 100644 index 0000000..7649ee9 --- /dev/null +++ b/src/qml/assets/icons/touchpoint.png diff --git a/src/resources.qrc b/src/resources.qrc new file mode 100644 index 0000000..bb43044 --- /dev/null +++ b/src/resources.qrc @@ -0,0 +1,21 @@ +<RCC> + <qresource prefix="/"> + <file>qml/BrowserDialog.qml</file> + <file>qml/BrowserWindow.qml</file> + <file>qml/FeaturePermissionBar.qml</file> + <file>qml/LoadIndicator.qml</file> + <file>qml/MockTouchPoint.qml</file> + <file>qml/PageView.qml</file> + + <file>qml/assets/UIButton.qml</file> + + <file alias="busy.png">qml/assets/icons/busy.png</file> + <file alias="home.png">qml/assets/icons/home.png</file> + <file alias="tabs.png">qml/assets/icons/tabs.png</file> + <file alias="next.png">qml/assets/icons/next.png</file> + <file alias="previous.png">qml/assets/icons/previous.png</file> + <file alias="refresh.png">qml/assets/icons/refresh.png</file> + <file alias="stop.png">qml/assets/icons/stop.png</file> + <file alias="touchpoint.png">qml/assets/icons/touchpoint.png</file> + </qresource> +</RCC> diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 0000000..b9596b0 --- /dev/null +++ b/src/src.pro @@ -0,0 +1,30 @@ +TARGET = qtbrowser + +DESTDIR = ../ +CONFIG += c++11 +CONFIG -= app_bundle + +SOURCES = main.cpp \ + touchmockingapplication.cpp \ + browserwindow.cpp + +HEADERS = utils.h \ + touchmockingapplication.h \ + browserwindow.h + +OTHER_FILES = \ + qml/assets/UIButton.qml \ + qml/ApplicationRoot.qml \ + qml/BrowserDialog.qml \ + qml/BrowserWindow.qml \ + qml/FeaturePermissionBar.qml \ + qml/LoadIndicator.qml \ + qml/MockTouchPoint.qml \ + qml/PageView.qml \ + +QT += qml quick webengine +QT_PRIVATE += quick-private gui-private core-private + +RESOURCES += resources.qrc + +!cross_compile: DEFINES += HOST_BUILD diff --git a/src/touchmockingapplication.cpp b/src/touchmockingapplication.cpp new file mode 100644 index 0000000..2685152 --- /dev/null +++ b/src/touchmockingapplication.cpp @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "touchmockingapplication.h" + +#include <qpa/qwindowsysteminterface.h> + +#include <QRegExp> +#include <QEvent> +#include <QMouseEvent> +#include <QTouchEvent> + +static inline QRectF touchRectForPosition(QPointF centerPoint) +{ + QRectF touchRect(0, 0, 40, 40); + touchRect.moveCenter(centerPoint); + return touchRect; +} + +static inline bool isTouchEvent(const QEvent* event) +{ + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + return true; + default: + return false; + } +} + +static inline bool isMouseEvent(const QEvent* event) +{ + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + return true; + default: + return false; + } +} + +TouchMockingApplication::TouchMockingApplication(int& argc, char** argv) + : QGuiApplication(argc, argv) + , m_realTouchEventReceived(false) + , m_pendingFakeTouchEventCount(0) + , m_holdingControl(false) +{ +} + +bool TouchMockingApplication::notify(QObject* target, QEvent* event) +{ + // We try to be smart, if we received real touch event, we are probably on a device + // with touch screen, and we should not have touch mocking. + + if (!event->spontaneous() || m_realTouchEventReceived) + return QGuiApplication::notify(target, event); + + if (isTouchEvent(event)) { + if (m_pendingFakeTouchEventCount) + --m_pendingFakeTouchEventCount; + else + m_realTouchEventReceived = true; + return QGuiApplication::notify(target, event); + } + + BrowserWindow* window = qobject_cast<BrowserWindow*>(target); + if (!window) + return QGuiApplication::notify(target, event); + + m_holdingControl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + + if (event->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(event)->key() == Qt::Key_Control) { + foreach (int id, m_heldTouchPoints) + if (m_touchPoints.contains(id) && !QGuiApplication::mouseButtons().testFlag(Qt::MouseButton(id))) { + m_touchPoints[id].setState(Qt::TouchPointReleased); + m_heldTouchPoints.remove(id); + } else + m_touchPoints[id].setState(Qt::TouchPointStationary); + + sendTouchEvent(window, m_heldTouchPoints.isEmpty() ? QEvent::TouchEnd : QEvent::TouchUpdate, static_cast<QKeyEvent*>(event)->timestamp()); + } + + if (event->type() == QEvent::Wheel) { + // Eat wheel events. + event->accept(); + return true; + } + + if (isMouseEvent(event)) { + const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event); + + QTouchEvent::TouchPoint touchPoint; + touchPoint.setPressure(1); + + QEvent::Type touchType = QEvent::None; + + switch (mouseEvent->type()) { + case QEvent::MouseButtonPress: + touchPoint.setId(mouseEvent->button()); + if (m_touchPoints.contains(touchPoint.id())) { + touchPoint.setState(Qt::TouchPointMoved); + touchType = QEvent::TouchUpdate; + } else { + touchPoint.setState(Qt::TouchPointPressed); + // Check if more buttons are held down than just the event triggering one. + if (mouseEvent->buttons() > mouseEvent->button()) + touchType = QEvent::TouchUpdate; + else + touchType = QEvent::TouchBegin; + } + break; + case QEvent::MouseMove: + if (!mouseEvent->buttons()) { + // We have to swallow the event instead of propagating it, + // since we avoid sending the mouse release events and if the + // Flickable is the mouse grabber it would receive the event + // and would move the content. + event->accept(); + return true; + } + touchType = QEvent::TouchUpdate; + touchPoint.setId(mouseEvent->buttons()); + touchPoint.setState(Qt::TouchPointMoved); + break; + case QEvent::MouseButtonRelease: + // Check if any buttons are still held down after this event. + if (mouseEvent->buttons()) + touchType = QEvent::TouchUpdate; + else + touchType = QEvent::TouchEnd; + touchPoint.setId(mouseEvent->button()); + touchPoint.setState(Qt::TouchPointReleased); + break; + case QEvent::MouseButtonDblClick: + // Eat double-clicks, their accompanying press event is all we need. + event->accept(); + return true; + default: + Q_ASSERT_X(false, "multi-touch mocking", "unhandled event type"); + } + + // A move can have resulted in multiple buttons, so we need check them individually. + if (touchPoint.id() & Qt::LeftButton) + updateTouchPoint(mouseEvent, touchPoint, Qt::LeftButton); + if (touchPoint.id() & Qt::MidButton) + updateTouchPoint(mouseEvent, touchPoint, Qt::MidButton); + if (touchPoint.id() & Qt::RightButton) + updateTouchPoint(mouseEvent, touchPoint, Qt::RightButton); + + if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) { + // We avoid sending the release event because the Flickable is + // listening to mouse events and would start a bounce-back + // animation if it received a mouse release. + event->accept(); + return true; + } + + // Update states for all other touch-points + for (QHash<int, QTouchEvent::TouchPoint>::iterator it = m_touchPoints.begin(), end = m_touchPoints.end(); it != end; ++it) { + if (!(it.value().id() & touchPoint.id())) + it.value().setState(Qt::TouchPointStationary); + } + + Q_ASSERT(touchType != QEvent::None); + + if (!sendTouchEvent(window, touchType, mouseEvent->timestamp())) + return QGuiApplication::notify(target, event); + + event->accept(); + return true; + } + + return QGuiApplication::notify(target, event); +} + +void TouchMockingApplication::updateTouchPoint(const QMouseEvent* mouseEvent, QTouchEvent::TouchPoint touchPoint, Qt::MouseButton mouseButton) +{ + // Ignore inserting additional touch points if Ctrl isn't held because it produces + // inconsistent touch events and results in assers in the gesture recognizers. + if (!m_holdingControl && m_touchPoints.size() && !m_touchPoints.contains(mouseButton)) + return; + + if (m_holdingControl && touchPoint.state() == Qt::TouchPointReleased) { + m_heldTouchPoints.insert(mouseButton); + return; + } + + // Gesture recognition uses the screen position for the initial threshold + // but since the canvas translates touch events we actually need to pass + // the screen position as the scene position to deliver the appropriate + // coordinates to the target. + touchPoint.setRect(touchRectForPosition(mouseEvent->localPos())); + touchPoint.setSceneRect(touchRectForPosition(mouseEvent->screenPos())); + + if (touchPoint.state() == Qt::TouchPointPressed) + touchPoint.setStartScenePos(mouseEvent->screenPos()); + else { + const QTouchEvent::TouchPoint& oldTouchPoint = m_touchPoints[mouseButton]; + touchPoint.setStartScenePos(oldTouchPoint.startScenePos()); + touchPoint.setLastPos(oldTouchPoint.pos()); + touchPoint.setLastScenePos(oldTouchPoint.scenePos()); + } + + // Update current touch-point. + touchPoint.setId(mouseButton); + m_touchPoints.insert(mouseButton, touchPoint); +} + +bool TouchMockingApplication::sendTouchEvent(BrowserWindow* window, QEvent::Type type, ulong timestamp) +{ + static QTouchDevice* device = 0; + if (!device) { + device = new QTouchDevice; + device->setType(QTouchDevice::TouchScreen); + QWindowSystemInterface::registerTouchDevice(device); + } + + m_pendingFakeTouchEventCount++; + + const QList<QTouchEvent::TouchPoint>& currentTouchPoints = m_touchPoints.values(); + Qt::TouchPointStates touchPointStates = 0; + foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints) + touchPointStates |= touchPoint.state(); + + QTouchEvent event(type, device, Qt::NoModifier, touchPointStates, currentTouchPoints); + event.setTimestamp(timestamp); + event.setAccepted(false); + + QGuiApplication::notify(window, &event); + + window->updateVisualMockTouchPoints(m_holdingControl ? currentTouchPoints : QList<QTouchEvent::TouchPoint>()); + + // Get rid of touch-points that are no longer valid + foreach (const QTouchEvent::TouchPoint& touchPoint, currentTouchPoints) { + if (touchPoint.state() == Qt::TouchPointReleased) + m_touchPoints.remove(touchPoint.id()); + } + + return event.isAccepted(); +} + diff --git a/src/touchmockingapplication.h b/src/touchmockingapplication.h new file mode 100644 index 0000000..6350436 --- /dev/null +++ b/src/touchmockingapplication.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef TOUCHMOCKINGAPPLICATION_H +#define TOUCHMOCKINGAPPLICATION_H + +#include "browserwindow.h" + +#include <QHash> +#include <QGuiApplication> +#include <QTouchEvent> +#include <QUrl> + +class TouchMockingApplication : public QGuiApplication +{ + Q_OBJECT + +public: + TouchMockingApplication(int &argc, char** argv); + + virtual bool notify(QObject*, QEvent*) override; + +private: + void updateTouchPoint(const QMouseEvent*, QTouchEvent::TouchPoint, Qt::MouseButton); + bool sendTouchEvent(BrowserWindow *, QEvent::Type, ulong timestamp); + +private: + bool m_realTouchEventReceived; + int m_pendingFakeTouchEventCount; + + QPointF m_lastPos; + QPointF m_lastScreenPos; + QPointF m_startScreenPos; + + QHash<int, QTouchEvent::TouchPoint> m_touchPoints; + QSet<int> m_heldTouchPoints; + + bool m_holdingControl; +}; + +#endif // TOUCHMOCKINGAPPLICATION_H diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..8ea1aea --- /dev/null +++ b/src/utils.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBrowser project. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPLv2 included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef UTILS_H +#define UTILS_H + +#include <QtCore/QFileInfo> +#include <QtCore/QUrl> + +class Utils : public QObject { + Q_OBJECT + + Q_PROPERTY(QObject * rootWindow READ rootWindow FINAL CONSTANT) +public: + Utils(QObject *parent) + : QObject(parent) + { + } + QObject *rootWindow() + { + return parent(); + } + + Q_INVOKABLE static QUrl fromUserInput(const QString& userInput); + Q_INVOKABLE static QString domainFromString(const QString& urlString); +}; + +inline QUrl Utils::fromUserInput(const QString& userInput) +{ + QFileInfo fileInfo(userInput); + if (fileInfo.exists()) + return QUrl::fromLocalFile(fileInfo.absoluteFilePath()); + return QUrl::fromUserInput(userInput); +} + +inline QString Utils::domainFromString(const QString& urlString) +{ + return QUrl::fromUserInput(urlString).host(); +} + +#endif // UTILS_H |