From 55eed376980dd5f8e485887176f9e5911bab4abb Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 29 Aug 2017 20:47:09 +0200 Subject: [PATCH 01/15] Start working on autocompletion --- interface/resources/qml/Browser.qml | 2 +- .../qml/controls-uit/BaseWebView.qml | 4 +- interface/resources/qml/hifi/Desktop.qml | 2 +- interface/resources/qml/hifi/WebBrowser.qml | 72 ++++++++++++------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 55927fda24..2809f91923 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 import QtWebChannel 1.0 -import QtWebEngine 1.2 +import QtWebEngine 1.5 import "controls-uit" import "styles" as HifiStyles diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml index 3ca57f03bf..fdd9c12220 100644 --- a/interface/resources/qml/controls-uit/BaseWebView.qml +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtWebEngine 1.2 +import QtQuick 2.7 +import QtWebEngine 1.5 WebEngineView { id: root diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index ea9ec2f6c9..96bb359aea 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import QtWebEngine 1.1; +import QtWebEngine 1.5; import Qt.labs.settings 1.0 import "../desktop" as OriginalDesktop diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index af54c86bf4..972c03355c 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -9,12 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.5 as QQControls +import QtQuick 2.7 +import QtQuick.Controls 2.2 as QQControls import QtQuick.Layouts 1.3 -import QtQuick.Controls.Styles 1.4 -import QtWebEngine 1.2 +import QtWebEngine 1.5 import QtWebChannel 1.0 import "../styles-uit" @@ -66,9 +65,27 @@ Rectangle { } } - QQControls.TextField { + QQControls.ComboBox { id: addressBar + //selectByMouse: true + focus: true + //placeholderText: "Enter URL" + editable: true + flat: true + indicator: Item {} + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") + + Keys.onPressed: { + if (event.key === Qt.Key_Return) { + if (editText.indexOf("http") != 0) { + editText = "http://" + editText; + } + webEngineView.url = editText + event.accepted = true; + } + } + Image { anchors.verticalCenter: addressBar.verticalCenter; x: 5 @@ -94,21 +111,18 @@ Rectangle { } } - style: TextFieldStyle { - padding { - left: 26; - right: 26 - } - } - focus: true + leftPadding: 26 + rightPadding: 26 + Layout.fillWidth: true - text: webEngineView.url - onAccepted: webEngineView.url = text + editText: webEngineView.url + onAccepted: webEngineView.url = editText } + HifiControls.WebGlyphButton { checkable: true //only QtWebEngine 1.3 - //checked: webEngineView.audioMuted + checked: webEngineView.audioMuted glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight @@ -120,18 +134,24 @@ Rectangle { QQControls.ProgressBar { id: loadProgressBar - style: ProgressBarStyle { - background: Rectangle { - color: "#6A6A6A" - } - progress: Rectangle{ + background: Rectangle { + implicitHeight: 2 + color: "#6A6A6A" + } + + contentItem: Item { + implicitHeight: 2 + + Rectangle { + width: loadProgressBar.visualPosition * parent.width + height: parent.height color: "#00B4EF" } } width: parent.width; - minimumValue: 0 - maximumValue: 100 + from: 0 + to: 100 value: webEngineView.loadProgress height: 2 } @@ -183,8 +203,8 @@ Rectangle { settings.pluginsEnabled: true settings.fullScreenSupportEnabled: false //from WebEngine 1.3 - // settings.autoLoadIconsForPage: false - // settings.touchIconsEnabled: false + settings.autoLoadIconsForPage: false + settings.touchIconsEnabled: false onCertificateError: { error.defer(); @@ -201,9 +221,7 @@ Rectangle { } onNewViewRequested: { - if (!request.userInitiated) { - print("Warning: Blocked a popup window."); - } + request.openIn(webEngineView); } onRenderProcessTerminated: { From 748ef8bb87663201a260bb71139512f44fe81677 Mon Sep 17 00:00:00 2001 From: vladest Date: Sun, 3 Sep 2017 18:10:53 +0200 Subject: [PATCH 02/15] Enable edit in combobox --- interface/resources/qml/hifi/WebBrowser.qml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 972c03355c..98ed0f10be 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -72,7 +72,7 @@ Rectangle { focus: true //placeholderText: "Enter URL" editable: true - flat: true + //flat: true indicator: Item {} Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") @@ -158,14 +158,15 @@ Rectangle { HifiControls.BaseWebView { id: webEngineView + width: parent.width; + property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 + height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight + + focus: true objectName: "tabletWebEngineView" url: "http://www.highfidelity.com" - property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 - - width: parent.width; - height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight profile: HFWebEngineProfile; @@ -258,6 +259,7 @@ Rectangle { } } + HifiControls.Keyboard { id: keyboard raised: parent.keyboardEnabled && parent.keyboardRaised From 6de00c796989ba26a6bdf48a89185e2e9d1a1b48 Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 5 Sep 2017 13:11:52 +0200 Subject: [PATCH 03/15] Implemented suggestions popup. Cleanup --- interface/resources/qml/hifi/WebBrowser.qml | 143 ++-- interface/src/Application.cpp | 3 + interface/src/opensearch/opensearchengine.cpp | 650 ++++++++++++++++++ interface/src/opensearch/opensearchengine.h | 128 ++++ .../opensearch/opensearchenginedelegate.cpp | 43 ++ .../src/opensearch/opensearchenginedelegate.h | 18 + 6 files changed, 946 insertions(+), 39 deletions(-) create mode 100644 interface/src/opensearch/opensearchengine.cpp create mode 100644 interface/src/opensearch/opensearchengine.h create mode 100644 interface/src/opensearch/opensearchenginedelegate.cpp create mode 100644 interface/src/opensearch/opensearchenginedelegate.h diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 98ed0f10be..582e26bbeb 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -21,6 +21,8 @@ import "../controls-uit" as HifiControls import "../windows" import "../controls" +import HifiWeb 1.0 + Rectangle { id: root; @@ -31,17 +33,61 @@ Rectangle { property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false property bool keyboardRaised: false property bool punctuationMode: false + property var suggestionsList: [] + OpenSearchEngine { + id: searchEngine + name: "Google"; + //icon: ":icons/sites/google.png" + searchUrlTemplate: "https://www.google.com/search?client=qupzilla&q=%s"; + suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; + suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; + + onSuggestions: { + if (suggestions.length > 0) { + console.log("suggestions:", suggestions) + suggestionsList = [] + suggestionsList.push(addressBar.editText) //do not overwrite edit text + for(var i = 0; i < suggestions.length; i++) { + suggestionsList.push(suggestions[i]) + } + addressBar.model = suggestionsList + if (!addressBar.popup.visible) { + addressBar.popup.open() + } + } + } + } + + Timer { + id: suggestionRequestTimer + interval: 200 + repeat: false + onTriggered: { + if (addressBar.editText !== "") { + searchEngine.requestSuggestions(addressBar.editText) + } + } + } color: hifi.colors.baseGray; - // only show the title if loaded through a "loader" + function goTo(url) { + if (url.indexOf("http") !== 0) { + url = "http://" + url; + } + webEngineView.url = url + event.accepted = true; + suggestionRequestTimer.stop() + addressBar.popup.close() + } Column { spacing: 2 width: parent.width; RowLayout { + id: addressBarRow width: parent.width; height: 48 @@ -70,50 +116,65 @@ Rectangle { //selectByMouse: true focus: true - //placeholderText: "Enter URL" + editable: true //flat: true indicator: Item {} + background: Item {} + onActivated: { + goTo(textAt(index)) + } + + contentItem: QQControls.TextField { + id: addressBarInput + leftPadding: 26 + rightPadding: hifi.dimensions.controlLineHeight + text: addressBar.editText + placeholderText: qsTr("Enter URL") + font: addressBar.font + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + Image { + anchors.verticalCenter: parent.verticalCenter; + x: 5 + z: 2 + id: faviconImage + width: 16; height: 16 + sourceSize: Qt.size(width, height) + source: webEngineView.icon + onSourceChanged: console.log("web icon", source) + } + + HifiControls.WebGlyphButton { + glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; + anchors.verticalCenter: parent.verticalCenter; + width: hifi.dimensions.controlLineHeight + z: 2 + x: addressBarInput.width - implicitWidth + onClicked: { + if (webEngineView.loading) { + webEngineView.stop() + } else { + reloadTimer.start() + } + } + } + } + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") Keys.onPressed: { if (event.key === Qt.Key_Return) { - if (editText.indexOf("http") != 0) { - editText = "http://" + editText; - } - webEngineView.url = editText - event.accepted = true; + goTo(editText) } } - Image { - anchors.verticalCenter: addressBar.verticalCenter; - x: 5 - z: 2 - id: faviconImage - width: 16; height: 16 - sourceSize: Qt.size(width, height) - source: webEngineView.icon + onEditTextChanged: { + console.log("edit text", addressBar.editText) + suggestionRequestTimer.restart() } - HifiControls.WebGlyphButton { - glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; - anchors.verticalCenter: parent.verticalCenter; - width: hifi.dimensions.controlLineHeight - z: 2 - x: addressBar.width - 28 - onClicked: { - if (webEngineView.loading) { - webEngineView.stop() - } else { - reloadTimer.start() - } - } - } - - leftPadding: 26 - rightPadding: 26 - Layout.fillWidth: true editText: webEngineView.url onAccepted: webEngineView.url = editText @@ -121,7 +182,6 @@ Rectangle { HifiControls.WebGlyphButton { checkable: true - //only QtWebEngine 1.3 checked: webEngineView.audioMuted glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted anchors.verticalCenter: parent.verticalCenter; @@ -162,7 +222,6 @@ Rectangle { property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - focus: true objectName: "tabletWebEngineView" @@ -172,6 +231,13 @@ Rectangle { property string userScriptUrl: "" + onLoadingChanged: { + if (!loading) { + suggestionRequestTimer.stop() + addressBar.popup.close() + } + } + // creates a global EventBridge object. WebEngineScript { id: createGlobalEventBridge @@ -202,10 +268,9 @@ Rectangle { settings.javascriptEnabled: true settings.errorPageEnabled: true settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: false - //from WebEngine 1.3 - settings.autoLoadIconsForPage: false - settings.touchIconsEnabled: false + settings.fullScreenSupportEnabled: true + settings.autoLoadIconsForPage: true + settings.touchIconsEnabled: true onCertificateError: { error.defer(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fe6ea2a0b3..726e5ba6e4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -201,6 +201,8 @@ #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" +#include "opensearch/opensearchengine.h" + // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. #if defined(Q_OS_WIN) @@ -2224,6 +2226,7 @@ void Application::initializeUi() { QmlCommerce::registerType(); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); + qmlRegisterType("HifiWeb", 1, 0, "OpenSearchEngine"); auto offscreenUi = DependencyManager::get(); offscreenUi->create(); diff --git a/interface/src/opensearch/opensearchengine.cpp b/interface/src/opensearch/opensearchengine.cpp new file mode 100644 index 0000000000..28cacab256 --- /dev/null +++ b/interface/src/opensearch/opensearchengine.cpp @@ -0,0 +1,650 @@ +#include "opensearchengine.h" +#include "qregexp.h" +#include "opensearchenginedelegate.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +/*! + \class OpenSearchEngine + \brief A class representing a single search engine described in OpenSearch format + + OpenSearchEngine is a class that represents a single search engine based on + the OpenSearch format. + For more information about the format, see http://www.opensearch.org/. + + Instances of the class hold all the data associated with the corresponding search + engines, such as name(), description() and also URL templates that are used + to construct URLs, which can be used later to perform search queries. Search engine + can also have an image, even an external one, in this case it will be downloaded + automatically from the network. + + OpenSearchEngine instances can be constructed from scratch but also read from + external sources and written back to them. OpenSearchReader and OpenSearchWriter + are the classes provided for reading and writing OpenSearch descriptions. + + Default constructed engines need to be filled with the necessary information before + they can be used to peform search requests. First of all, a search engine should have + the metadata including the name and the description. + However, the most important are URL templates, which are the construction of URLs + but can also contain template parameters, that are replaced with corresponding values + at the time of constructing URLs. + + There are two types of URL templates: search URL template and suggestions URL template. + Search URL template is needed for constructing search URLs, which point directly to + search results. Suggestions URL template is necessary to construct suggestion queries + URLs, which are then used for requesting contextual suggestions, a popular service + offered along with search results that provides search terms related to what has been + supplied by the user. + + Both types of URLs are constructed by the class, by searchUrl() and suggestionsUrl() + functions respectively. However, search requests are supposed to be performed outside + the class, while suggestion queries can be executed using the requestSuggestions() + method. The class will take care of peforming the network request and parsing the + JSON response. + + Both the image request and suggestion queries need network access. The class can + perform network requests on its own, though the client application needs to provide + a network access manager, which then will to be used for network operations. + Without that, both images delivered from remote locations and contextual suggestions + will be disabled. + + \sa OpenSearchReader, OpenSearchWriter +*/ + +/*! + Constructs an engine with a given \a parent. +*/ +OpenSearchEngine::OpenSearchEngine(QObject* parent) + : QObject(parent) + , m_searchMethod(QLatin1String("get")) + , m_suggestionsMethod(QLatin1String("get")) + , m_suggestionsReply(0) + , m_delegate(0) +{ + m_requestMethods.insert(QLatin1String("get"), QNetworkAccessManager::GetOperation); + m_requestMethods.insert(QLatin1String("post"), QNetworkAccessManager::PostOperation); +} + +/*! + A destructor. +*/ +OpenSearchEngine::~OpenSearchEngine() +{ +} + +QString OpenSearchEngine::parseTemplate(const QString &searchTerm, const QString &searchTemplate) +{ + QString language = QLocale().name(); + // Simple conversion to RFC 3066. + language = language.replace(QLatin1Char('_'), QLatin1Char('-')); + + QString result = searchTemplate; + result.replace(QLatin1String("{count}"), QLatin1String("20")); + result.replace(QLatin1String("{startIndex}"), QLatin1String("0")); + result.replace(QLatin1String("{startPage}"), QLatin1String("0")); + result.replace(QLatin1String("{language}"), language); + result.replace(QLatin1String("{inputEncoding}"), QLatin1String("UTF-8")); + result.replace(QLatin1String("{outputEncoding}"), QLatin1String("UTF-8")); + result.replace(QRegExp(QLatin1String("\\{([^\\}]*:|)source\\??\\}")), QCoreApplication::applicationName()); + result.replace(QLatin1String("{searchTerms}"), QLatin1String(QUrl::toPercentEncoding(searchTerm))); + + return result; +} + +/*! + \property OpenSearchEngine::name + \brief the name of the engine + + \sa description() +*/ +QString OpenSearchEngine::name() const +{ + return m_name; +} + +void OpenSearchEngine::setName(const QString &name) +{ + m_name = name; +} + +/*! + \property OpenSearchEngine::description + \brief the description of the engine + + \sa name() +*/ +QString OpenSearchEngine::description() const +{ + return m_description; +} + +void OpenSearchEngine::setDescription(const QString &description) +{ + m_description = description; +} + +/*! + \property OpenSearchEngine::searchUrlTemplate + \brief the template of the search URL + + \sa searchUrl(), searchParameters(), suggestionsUrlTemplate() +*/ +QString OpenSearchEngine::searchUrlTemplate() const +{ + return m_searchUrlTemplate; +} + +void OpenSearchEngine::setSearchUrlTemplate(const QString &searchUrlTemplate) +{ + if (!searchUrlTemplate.startsWith(QLatin1String("http://")) && !searchUrlTemplate.startsWith(QLatin1String("https://"))) { + return; + } + + m_searchUrlTemplate = searchUrlTemplate; +} + +/*! + Constructs and returns a search URL with a given \a searchTerm. + + The URL template is processed according to the specification: + http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax + + A list of template parameters currently supported and what they are replaced with: + \table + \header \o parameter + \o value + \row \o "{count}" + \o "20" + \row \o "{startIndex}" + \o "0" + \row \o "{startPage}" + \o "0" + \row \o "{language}" + \o "the default language code (RFC 3066)" + \row \o "{inputEncoding}" + \o "UTF-8" + \row \o "{outputEncoding}" + \o "UTF-8" + \row \o "{*:source}" + \o "application name, QCoreApplication::applicationName()" + \row \o "{searchTerms}" + \o "the string supplied by the user" + \endtable + + \sa searchUrlTemplate(), searchParameters(), suggestionsUrl() +*/ +QUrl OpenSearchEngine::searchUrl(const QString &searchTerm) const +{ + if (m_searchUrlTemplate.isEmpty()) { + return QUrl(); + } + + QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_searchUrlTemplate).toUtf8()); + + QUrlQuery query(retVal); + if (m_searchMethod != QLatin1String("post")) { + Parameters::const_iterator end = m_searchParameters.constEnd(); + Parameters::const_iterator i = m_searchParameters.constBegin(); + for (; i != end; ++i) { + query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); + } + retVal.setQuery(query); + } + + return retVal; +} + +QByteArray OpenSearchEngine::getPostData(const QString &searchTerm) const +{ + if (m_searchMethod != QLatin1String("post")) { + return QByteArray(); + } + + QUrl retVal = QUrl("http://foo.bar"); + + QUrlQuery query(retVal); + Parameters::const_iterator end = m_searchParameters.constEnd(); + Parameters::const_iterator i = m_searchParameters.constBegin(); + for (; i != end; ++i) { + query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); + } + retVal.setQuery(query); + + QByteArray data = retVal.toEncoded(QUrl::RemoveScheme); + return data.contains('?') ? data.mid(data.lastIndexOf('?') + 1) : QByteArray(); +} + +/*! + \property providesSuggestions + \brief indicates whether the engine supports contextual suggestions +*/ +bool OpenSearchEngine::providesSuggestions() const +{ + return (!m_suggestionsUrlTemplate.isEmpty() || !m_preparedSuggestionsUrl.isEmpty()); +} + +/*! + \property OpenSearchEngine::suggestionsUrlTemplate + \brief the template of the suggestions URL + + \sa suggestionsUrl(), suggestionsParameters(), searchUrlTemplate() +*/ +QString OpenSearchEngine::suggestionsUrlTemplate() const +{ + return m_suggestionsUrlTemplate; +} + +void OpenSearchEngine::setSuggestionsUrlTemplate(const QString &suggestionsUrlTemplate) +{ + if (!suggestionsUrlTemplate.startsWith(QLatin1String("http://")) && !suggestionsUrlTemplate.startsWith(QLatin1String("https://"))) { + return; + } + + m_suggestionsUrlTemplate = suggestionsUrlTemplate; +} + +/*! + Constructs a suggestions URL with a given \a searchTerm. + + The URL template is processed according to the specification: + http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax + + See searchUrl() for more information about processing template parameters. + + \sa suggestionsUrlTemplate(), suggestionsParameters(), searchUrl() +*/ +QUrl OpenSearchEngine::suggestionsUrl(const QString &searchTerm) const +{ + if (!m_preparedSuggestionsUrl.isEmpty()) { + QString s = m_preparedSuggestionsUrl; + s.replace(QLatin1String("%s"), searchTerm); + return QUrl(s); + } + + if (m_suggestionsUrlTemplate.isEmpty()) { + return QUrl(); + } + + QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_suggestionsUrlTemplate).toUtf8()); + + QUrlQuery query(retVal); + if (m_suggestionsMethod != QLatin1String("post")) { + Parameters::const_iterator end = m_suggestionsParameters.constEnd(); + Parameters::const_iterator i = m_suggestionsParameters.constBegin(); + for (; i != end; ++i) { + query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); + } + retVal.setQuery(query); + } + + return retVal; +} + +/*! + \property searchParameters + \brief additional parameters that will be included in the search URL + + For more information see: + http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 +*/ +OpenSearchEngine::Parameters OpenSearchEngine::searchParameters() const +{ + return m_searchParameters; +} + +void OpenSearchEngine::setSearchParameters(const Parameters &searchParameters) +{ + m_searchParameters = searchParameters; +} + +/*! + \property suggestionsParameters + \brief additional parameters that will be included in the suggestions URL + + For more information see: + http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 +*/ +OpenSearchEngine::Parameters OpenSearchEngine::suggestionsParameters() const +{ + return m_suggestionsParameters; +} + +void OpenSearchEngine::setSuggestionsParameters(const Parameters &suggestionsParameters) +{ + m_suggestionsParameters = suggestionsParameters; +} + +/*! + \property searchMethod + \brief HTTP request method that will be used to perform search requests +*/ +QString OpenSearchEngine::searchMethod() const +{ + return m_searchMethod; +} + +void OpenSearchEngine::setSearchMethod(const QString &method) +{ + QString requestMethod = method.toLower(); + if (!m_requestMethods.contains(requestMethod)) { + return; + } + + m_searchMethod = requestMethod; +} + +/*! + \property suggestionsMethod + \brief HTTP request method that will be used to perform suggestions requests +*/ +QString OpenSearchEngine::suggestionsMethod() const +{ + return m_suggestionsMethod; +} + +void OpenSearchEngine::setSuggestionsMethod(const QString &method) +{ + QString requestMethod = method.toLower(); + if (!m_requestMethods.contains(requestMethod)) { + return; + } + + m_suggestionsMethod = requestMethod; +} + +/*! + \property imageUrl + \brief the image URL of the engine + + When setting a new image URL, it won't be loaded immediately. The first request will be + deferred until image() is called for the first time. + + \note To be able to request external images, you need to provide a network access manager, + which will be used for network operations. + + \sa image(), networkAccessManager() +*/ +QString OpenSearchEngine::imageUrl() const +{ + return m_imageUrl; +} + +void OpenSearchEngine::setImageUrl(const QString &imageUrl) +{ + m_imageUrl = imageUrl; +} + +void OpenSearchEngine::loadImage() const +{ + if (m_imageUrl.isEmpty()) { + return; + } + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(QUrl::fromEncoded(m_imageUrl.toUtf8()))); + connect(reply, SIGNAL(finished()), this, SLOT(imageObtained())); +} + +void OpenSearchEngine::imageObtained() +{ + QNetworkReply* reply = qobject_cast(sender()); + + if (!reply) { + return; + } + + QByteArray response = reply->readAll(); + + reply->close(); + reply->deleteLater(); + + if (response.isEmpty()) { + return; + } + + m_image.loadFromData(response); + emit imageChanged(); +} + +/*! + \property image + \brief the image of the engine + + When no image URL has been set and an image will be set explicitly, a new data URL + will be constructed, holding the image data encoded with Base64. + + \sa imageUrl() +*/ +QImage OpenSearchEngine::image() const +{ + if (m_image.isNull()) { + loadImage(); + } + return m_image; +} + +void OpenSearchEngine::setImage(const QImage &image) +{ + if (m_imageUrl.isEmpty()) { + QBuffer imageBuffer; + imageBuffer.open(QBuffer::ReadWrite); + if (image.save(&imageBuffer, "PNG")) { + m_imageUrl = QString(QLatin1String("data:image/png;base64,%1")) + .arg(QLatin1String(imageBuffer.buffer().toBase64())); + } + } + + m_image = image; + emit imageChanged(); +} + +/*! + \property valid + \brief indicates whether the engine is valid i.e. the description was properly formed and included all necessary information +*/ +bool OpenSearchEngine::isValid() const +{ + return (!m_name.isEmpty() && !m_searchUrlTemplate.isEmpty()); +} + +bool OpenSearchEngine::operator==(const OpenSearchEngine &other) const +{ + return (m_name == other.m_name + && m_description == other.m_description + && m_imageUrl == other.m_imageUrl + && m_searchUrlTemplate == other.m_searchUrlTemplate + && m_suggestionsUrlTemplate == other.m_suggestionsUrlTemplate + && m_searchParameters == other.m_searchParameters + && m_suggestionsParameters == other.m_suggestionsParameters); +} + +bool OpenSearchEngine::operator<(const OpenSearchEngine &other) const +{ + return (m_name < other.m_name); +} + +/*! + Requests contextual suggestions on the search engine, for a given \a searchTerm. + + If succeeded, suggestions() signal will be emitted once the suggestions are received. + + \note To be able to request suggestions, you need to provide a network access manager, + which will be used for network operations. + + \sa requestSearchResults() +*/ + +void OpenSearchEngine::setSuggestionsParameters(const QByteArray ¶meters) +{ + m_preparedSuggestionsParameters = parameters; +} + +void OpenSearchEngine::setSuggestionsUrl(const QString &string) +{ + m_preparedSuggestionsUrl = string; +} + +QString OpenSearchEngine::getSuggestionsUrl() +{ + return suggestionsUrl("searchstring").toString().replace(QLatin1String("searchstring"), QLatin1String("%s")); +} + +QByteArray OpenSearchEngine::getSuggestionsParameters() +{ + QStringList parameters; + Parameters::const_iterator end = m_suggestionsParameters.constEnd(); + Parameters::const_iterator i = m_suggestionsParameters.constBegin(); + for (; i != end; ++i) { + parameters.append(i->first + QLatin1String("=") + i->second); + } + + QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); + + return data; +} + +void OpenSearchEngine::requestSuggestions(const QString &searchTerm) +{ + if (searchTerm.isEmpty() || !providesSuggestions()) { + return; + } + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + if (m_suggestionsReply) { + m_suggestionsReply->disconnect(this); + m_suggestionsReply->abort(); + m_suggestionsReply->deleteLater(); + m_suggestionsReply = 0; + } + + Q_ASSERT(m_requestMethods.contains(m_suggestionsMethod)); + if (m_suggestionsMethod == QLatin1String("get")) { + m_suggestionsReply = networkAccessManager.get(QNetworkRequest(suggestionsUrl(searchTerm))); + } + else { + QStringList parameters; + Parameters::const_iterator end = m_suggestionsParameters.constEnd(); + Parameters::const_iterator i = m_suggestionsParameters.constBegin(); + for (; i != end; ++i) { + parameters.append(i->first + QLatin1String("=") + i->second); + } + + QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); + m_suggestionsReply = networkAccessManager.post(QNetworkRequest(suggestionsUrl(searchTerm)), data); + } + + connect(m_suggestionsReply, SIGNAL(finished()), this, SLOT(suggestionsObtained())); +} + +/*! + Requests search results on the search engine, for a given \a searchTerm. + + The default implementation does nothing, to supply your own you need to create your own + OpenSearchEngineDelegate subclass and supply it to the engine. Then the function will call + the performSearchRequest() method of the delegate, which can then handle the request + in a custom way. + + \sa requestSuggestions(), delegate() +*/ +void OpenSearchEngine::requestSearchResults(const QString &searchTerm) +{ + if (!m_delegate || searchTerm.isEmpty()) { + return; + } + + Q_ASSERT(m_requestMethods.contains(m_searchMethod)); + + QNetworkRequest request(QUrl(searchUrl(searchTerm))); + QByteArray data; + QNetworkAccessManager::Operation operation = m_requestMethods.value(m_searchMethod); + + if (operation == QNetworkAccessManager::PostOperation) { + QStringList parameters; + Parameters::const_iterator end = m_searchParameters.constEnd(); + Parameters::const_iterator i = m_searchParameters.constBegin(); + for (; i != end; ++i) { + parameters.append(i->first + QLatin1String("=") + i->second); + } + + data = parameters.join(QLatin1String("&")).toUtf8(); + } + + m_delegate->performSearchRequest(request, operation, data); +} + +void OpenSearchEngine::suggestionsObtained() +{ + const QByteArray response = m_suggestionsReply->readAll(); + + m_suggestionsReply->close(); + m_suggestionsReply->deleteLater(); + m_suggestionsReply = 0; + + QJsonParseError err; + QJsonDocument json = QJsonDocument::fromJson(response, &err); + const QVariant res = json.toVariant(); + + if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) + return; + + const QVariantList list = res.toList(); + + if (list.size() < 2) + return; + + QStringList out; + + foreach (const QVariant &v, list.at(1).toList()) + out.append(v.toString()); + + emit suggestions(out); +} + + +/*! + \property delegate + \brief the delegate that is used to perform specific tasks. + + It can be currently supplied to provide a custom behaviour ofthe requetSearchResults() method. + The default implementation does nothing. +*/ +OpenSearchEngineDelegate* OpenSearchEngine::delegate() const +{ + return m_delegate; +} + +void OpenSearchEngine::setDelegate(OpenSearchEngineDelegate* delegate) +{ + m_delegate = delegate; +} + +/*! + \fn void OpenSearchEngine::imageChanged() + + This signal is emitted whenever the image of the engine changes. + + \sa image(), imageUrl() +*/ + +/*! + \fn void OpenSearchEngine::suggestions(const QStringList &suggestions) + + This signal is emitted whenever new contextual suggestions have been provided + by the search engine. To request suggestions, use requestSuggestions(). + The suggestion set is specified by \a suggestions. + + \sa requestSuggestions() +*/ diff --git a/interface/src/opensearch/opensearchengine.h b/interface/src/opensearch/opensearchengine.h new file mode 100644 index 0000000000..2b717fab7e --- /dev/null +++ b/interface/src/opensearch/opensearchengine.h @@ -0,0 +1,128 @@ +#ifndef OPENSEARCHENGINE_H +#define OPENSEARCHENGINE_H + +#include +#include +#include +#include +#include +#include + +class QNetworkReply; + +class OpenSearchEngineDelegate; +class OpenSearchEngine : public QObject +{ + Q_OBJECT + +signals: + void imageChanged(); + void suggestions(const QStringList &suggestions); + +public: + typedef QPair Parameter; + typedef QList Parameters; + + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(QString description READ description WRITE setDescription) + Q_PROPERTY(QString searchUrlTemplate READ searchUrlTemplate WRITE setSearchUrlTemplate) + Q_PROPERTY(Parameters searchParameters READ searchParameters WRITE setSearchParameters) + Q_PROPERTY(QString searchMethod READ searchMethod WRITE setSearchMethod) + Q_PROPERTY(QString suggestionsUrlTemplate READ suggestionsUrlTemplate WRITE setSuggestionsUrlTemplate) + Q_PROPERTY(QString suggestionsUrl READ getSuggestionsUrl WRITE setSuggestionsUrl) + Q_PROPERTY(Parameters suggestionsParameters READ suggestionsParameters WRITE setSuggestionsParameters) + Q_PROPERTY(QString suggestionsMethod READ suggestionsMethod WRITE setSuggestionsMethod) + Q_PROPERTY(bool providesSuggestions READ providesSuggestions) + Q_PROPERTY(QString imageUrl READ imageUrl WRITE setImageUrl) + Q_PROPERTY(bool valid READ isValid) + + OpenSearchEngine(QObject* parent = 0); + ~OpenSearchEngine(); + + QString name() const; + void setName(const QString &name); + + QString description() const; + void setDescription(const QString &description); + + QString searchUrlTemplate() const; + void setSearchUrlTemplate(const QString &searchUrl); + QUrl searchUrl(const QString &searchTerm) const; + + QByteArray getPostData(const QString &searchTerm) const; + + bool providesSuggestions() const; + + QString suggestionsUrlTemplate() const; + void setSuggestionsUrlTemplate(const QString &suggestionsUrl); + QUrl suggestionsUrl(const QString &searchTerm) const; + + Parameters searchParameters() const; + void setSearchParameters(const Parameters &searchParameters); + + Parameters suggestionsParameters() const; + void setSuggestionsParameters(const Parameters &suggestionsParameters); + + QString searchMethod() const; + void setSearchMethod(const QString &method); + + QString suggestionsMethod() const; + void setSuggestionsMethod(const QString &method); + + QString imageUrl() const; + void setImageUrl(const QString &url); + + QImage image() const; + void setImage(const QImage &image); + + bool isValid() const; + + void setSuggestionsUrl(const QString &string); + void setSuggestionsParameters(const QByteArray ¶meters); + QString getSuggestionsUrl(); + QByteArray getSuggestionsParameters(); + + OpenSearchEngineDelegate* delegate() const; + void setDelegate(OpenSearchEngineDelegate* delegate); + + bool operator==(const OpenSearchEngine &other) const; + bool operator<(const OpenSearchEngine &other) const; + +public slots: + void requestSuggestions(const QString &searchTerm); + void requestSearchResults(const QString &searchTerm); + +protected: + static QString parseTemplate(const QString &searchTerm, const QString &searchTemplate); + void loadImage() const; + +private slots: + void imageObtained(); + void suggestionsObtained(); + +private: + QString m_name; + QString m_description; + + QString m_imageUrl; + QImage m_image; + + QString m_searchUrlTemplate; + QString m_suggestionsUrlTemplate; + Parameters m_searchParameters; + Parameters m_suggestionsParameters; + QString m_searchMethod; + QString m_suggestionsMethod; + + QByteArray m_preparedSuggestionsParameters; + QString m_preparedSuggestionsUrl; + + QMap m_requestMethods; + + QNetworkReply* m_suggestionsReply; + + OpenSearchEngineDelegate* m_delegate; +}; + +#endif // OPENSEARCHENGINE_H + diff --git a/interface/src/opensearch/opensearchenginedelegate.cpp b/interface/src/opensearch/opensearchenginedelegate.cpp new file mode 100644 index 0000000000..2c9c18ece0 --- /dev/null +++ b/interface/src/opensearch/opensearchenginedelegate.cpp @@ -0,0 +1,43 @@ +#include "opensearchenginedelegate.h" + +/*! + \class OpenSearchEngineDelegate + \brief An abstract class providing custom processing of specific activities. + + OpenSearchEngineDelegate is an abstract class that can be subclassed and set on + an OpenSearchEngine. It allows to customize some parts of the default implementation + or even extend it with missing bits. + + Currently subclasses can only provide a custom way of handling search requests by + reimplementing the performSearchRequest() method. + + \sa OpenSearchEngine +*/ + +/*! + Constructs the delegate. +*/ +OpenSearchEngineDelegate::OpenSearchEngineDelegate() +{ +} + +/*! + Destructs the delegate. +*/ +OpenSearchEngineDelegate::~OpenSearchEngineDelegate() +{ +} + +/*! + \fn void performSearchRequest(const QNetworkRequest &request, + QNetworkAccessManager::Operation operation, const QByteArray &data) = 0 + + This method will be used after OpenSearchEngine::requestResults() is called. + + For example, a console application that uses the OpenSearchEngine class to generate + a search URL for a search term supplied by the user would reimplement the function + and forward the request to e.g. a web browser. + + Likewise, a web browser that uses the OpenSearchEngine class to support multiple search + engines e.g. in a toolbar would perform the request and navigate to the search results site. +*/ diff --git a/interface/src/opensearch/opensearchenginedelegate.h b/interface/src/opensearch/opensearchenginedelegate.h new file mode 100644 index 0000000000..ae7b5ef876 --- /dev/null +++ b/interface/src/opensearch/opensearchenginedelegate.h @@ -0,0 +1,18 @@ +#ifndef OPENSEARCHENGINEDELEGATE_H +#define OPENSEARCHENGINEDELEGATE_H + +#include +#include + +class OpenSearchEngineDelegate +{ +public: + OpenSearchEngineDelegate(); + virtual ~OpenSearchEngineDelegate(); + + virtual void performSearchRequest(const QNetworkRequest &request, + QNetworkAccessManager::Operation operation, + const QByteArray &data) = 0; +}; + +#endif // OPENSEARCHENGINEDELEGATE_H From 33767862888359beeb85fa2a41e7e24c3bbff933 Mon Sep 17 00:00:00 2001 From: vladest Date: Wed, 20 Sep 2017 17:02:54 +0200 Subject: [PATCH 04/15] Added text selection on focus. Added basic check for text within url input field to decide is this something for search or for goto. Limited popup list height to do not cover virtual keyboard --- interface/resources/qml/hifi/WebBrowser.qml | 37 ++++++++++++++++++--- interface/src/opensearch/opensearchengine.h | 2 +- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 582e26bbeb..f1c6efde9a 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -39,7 +39,7 @@ Rectangle { id: searchEngine name: "Google"; //icon: ":icons/sites/google.png" - searchUrlTemplate: "https://www.google.com/search?client=qupzilla&q=%s"; + searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q={searchTerms}"; suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; @@ -73,11 +73,14 @@ Rectangle { color: hifi.colors.baseGray; function goTo(url) { - if (url.indexOf("http") !== 0) { + //must be valid attempt to open an site with dot + if (url.indexOf("http") <= 0 && url.indexOf(".") > 0) { url = "http://" + url; + } else { + url = searchEngine.searchUrl(url) } + webEngineView.url = url - event.accepted = true; suggestionRequestTimer.stop() addressBar.popup.close() } @@ -125,6 +128,14 @@ Rectangle { goTo(textAt(index)) } + popup.bottomPadding: keyboard.height + + onFocusChanged: { + if (focus) { + addressBarInput.selectAll() + } + } + contentItem: QQControls.TextField { id: addressBarInput leftPadding: 26 @@ -132,8 +143,25 @@ Rectangle { text: addressBar.editText placeholderText: qsTr("Enter URL") font: addressBar.font + selectByMouse: true horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter + onFocusChanged: { + if (focus) { + selectAll() + } + } + + Keys.onDeletePressed: { + editText = "" + } + + Keys.onPressed: { + if (event.key === Qt.Key_Return) { + goTo(editText) + event.accepted = true; + } + } Image { anchors.verticalCenter: parent.verticalCenter; @@ -167,6 +195,7 @@ Rectangle { Keys.onPressed: { if (event.key === Qt.Key_Return) { goTo(editText) + event.accepted = true; } } @@ -177,7 +206,7 @@ Rectangle { Layout.fillWidth: true editText: webEngineView.url - onAccepted: webEngineView.url = editText + onAccepted: goTo(editText) } HifiControls.WebGlyphButton { diff --git a/interface/src/opensearch/opensearchengine.h b/interface/src/opensearch/opensearchengine.h index 2b717fab7e..fa714b85d3 100644 --- a/interface/src/opensearch/opensearchengine.h +++ b/interface/src/opensearch/opensearchengine.h @@ -47,7 +47,6 @@ public: QString searchUrlTemplate() const; void setSearchUrlTemplate(const QString &searchUrl); - QUrl searchUrl(const QString &searchTerm) const; QByteArray getPostData(const QString &searchTerm) const; @@ -89,6 +88,7 @@ public: bool operator<(const OpenSearchEngine &other) const; public slots: + QUrl searchUrl(const QString &searchTerm) const; void requestSuggestions(const QString &searchTerm); void requestSearchResults(const QString &searchTerm); From 45f1f074dff350da0762b180884383e7ae8f8851 Mon Sep 17 00:00:00 2001 From: vladest Date: Thu, 12 Oct 2017 20:52:40 +0200 Subject: [PATCH 05/15] Reduce combobox dropdown height taking keyboard in account --- interface/resources/qml/hifi/WebBrowser.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index f1c6efde9a..1b49701bdf 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -38,14 +38,12 @@ Rectangle { OpenSearchEngine { id: searchEngine name: "Google"; - //icon: ":icons/sites/google.png" searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q={searchTerms}"; suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; onSuggestions: { if (suggestions.length > 0) { - console.log("suggestions:", suggestions) suggestionsList = [] suggestionsList.push(addressBar.editText) //do not overwrite edit text for(var i = 0; i < suggestions.length; i++) { @@ -128,7 +126,7 @@ Rectangle { goTo(textAt(index)) } - popup.bottomPadding: keyboard.height + popup.height: webEngineView.height onFocusChanged: { if (focus) { From 83c14bba5ca412c88468a5bcf8ba3fdcd04f17a3 Mon Sep 17 00:00:00 2001 From: vladest Date: Fri, 20 Oct 2017 11:55:41 +0200 Subject: [PATCH 06/15] Fix popups misbehavior, wrong links with double http etc --- interface/resources/qml/hifi/WebBrowser.qml | 35 +++++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 1b49701bdf..6a1d61de11 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -1,3 +1,4 @@ + // // WebBrowser.qml // @@ -45,7 +46,7 @@ Rectangle { onSuggestions: { if (suggestions.length > 0) { suggestionsList = [] - suggestionsList.push(addressBar.editText) //do not overwrite edit text + suggestionsList.push(addressBarInput.text) //do not overwrite edit text for(var i = 0; i < suggestions.length; i++) { suggestionsList.push(suggestions[i]) } @@ -63,7 +64,7 @@ Rectangle { repeat: false onTriggered: { if (addressBar.editText !== "") { - searchEngine.requestSuggestions(addressBar.editText) + searchEngine.requestSuggestions(addressBarInput.text) } } } @@ -72,12 +73,16 @@ Rectangle { function goTo(url) { //must be valid attempt to open an site with dot - if (url.indexOf("http") <= 0 && url.indexOf(".") > 0) { - url = "http://" + url; + + if (url.indexOf(".") > 0) { + if (url.indexOf("http") < 0) { + url = "http://" + url; + } } else { url = searchEngine.searchUrl(url) } + addressBar.model = [] webEngineView.url = url suggestionRequestTimer.stop() addressBar.popup.close() @@ -151,12 +156,12 @@ Rectangle { } Keys.onDeletePressed: { - editText = "" + addressBarInput.text = "" } Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(editText) + goTo(addressBarInput.text) event.accepted = true; } } @@ -192,19 +197,24 @@ Rectangle { Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(editText) + goTo(addressBarInput.text) event.accepted = true; } } onEditTextChanged: { - console.log("edit text", addressBar.editText) - suggestionRequestTimer.restart() + if (addressBar.editText !== "" && addressBar.editText !== webEngineView.url.toString()) { + suggestionRequestTimer.restart(); + } else { + addressBar.model = [] + addressBar.popup.close() + } + } Layout.fillWidth: true editText: webEngineView.url - onAccepted: goTo(editText) + onAccepted: goTo(addressBarInput.text) } HifiControls.WebGlyphButton { @@ -265,6 +275,11 @@ Rectangle { } } + onLinkHovered: { + //TODO: change cursor shape? + console.error("hoveredUrl:", hoveredUrl) + } + // creates a global EventBridge object. WebEngineScript { id: createGlobalEventBridge From fd41299efc5e6ed42d2ec7f6336fc57dac2f86da Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 23 Oct 2017 22:34:22 +0200 Subject: [PATCH 07/15] Disable profile and custom user agent which broke nav bars --- interface/resources/qml/hifi/WebBrowser.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 6a1d61de11..edd0523aa6 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -264,7 +264,7 @@ Rectangle { url: "http://www.highfidelity.com" - profile: HFWebEngineProfile; + //profile: HFWebEngineProfile; property string userScriptUrl: "" @@ -321,7 +321,7 @@ Rectangle { Component.onCompleted: { webChannel.registerObject("eventBridge", eventBridge); webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; } onFeaturePermissionRequested: { From 874b9149e7a204eff07d5c48c637d6793e27f812 Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 24 Oct 2017 22:04:25 +0200 Subject: [PATCH 08/15] Popupd support reworked --- interface/resources/qml/hifi/WebBrowser.qml | 255 ++++++++++++-------- 1 file changed, 159 insertions(+), 96 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index edd0523aa6..c96d05d6a7 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -253,115 +253,178 @@ Rectangle { height: 2 } - HifiControls.BaseWebView { - id: webEngineView + Component { + id: webDialogComponent + Rectangle { + property alias webDialogView: webDialogView + color: "white" + HifiControls.BaseWebView { + id: webDialogView + anchors.fill: parent + + settings.autoLoadImages: true + settings.javascriptEnabled: true + settings.errorPageEnabled: true + settings.pluginsEnabled: true + settings.fullScreenSupportEnabled: true + settings.autoLoadIconsForPage: true + settings.touchIconsEnabled: true + + onWindowCloseRequested: { + webDialog.active = false + webDialog.request = null + } + } + } + } + + Item { width: parent.width; property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - focus: true - objectName: "tabletWebEngineView" + HifiControls.BaseWebView { + id: webEngineView + anchors.fill: parent - url: "http://www.highfidelity.com" + focus: true + objectName: "tabletWebEngineView" - //profile: HFWebEngineProfile; + url: "https://www.highfidelity.com" - property string userScriptUrl: "" + //profile: HFWebEngineProfile; - onLoadingChanged: { - if (!loading) { - suggestionRequestTimer.stop() - addressBar.popup.close() + property string userScriptUrl: "" + + onLoadingChanged: { + if (!loading) { + suggestionRequestTimer.stop() + addressBar.popup.close() + } + } + + onLinkHovered: { + //TODO: change cursor shape? + console.error("hoveredUrl:", hoveredUrl) + } + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webEngineView.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + settings.autoLoadImages: true + settings.javascriptEnabled: true + settings.errorPageEnabled: true + settings.pluginsEnabled: true + settings.fullScreenSupportEnabled: true + settings.autoLoadIconsForPage: true + settings.touchIconsEnabled: true + + onCertificateError: { + error.defer(); + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onNewViewRequested: { + console.error("new view requested:", request.destination) + if (request.destination == WebEngineView.NewViewInDialog) { + webDialog.request = request + webDialog.active = true + } else { + request.openIn(webEngineView); + } + } + + onRenderProcessTerminated: { + var status = ""; + switch (terminationStatus) { + case WebEngineView.NormalTerminationStatus: + status = "(normal exit)"; + break; + case WebEngineView.AbnormalTerminationStatus: + status = "(abnormal exit)"; + break; + case WebEngineView.CrashedTerminationStatus: + status = "(crashed)"; + break; + case WebEngineView.KilledTerminationStatus: + status = "(killed)"; + break; + } + + console.error("Render process exited with code " + exitCode + " " + status); + reloadTimer.running = true; + } + + onFullScreenRequested: { + console.error("FS requested:", request.destination) + if (request.toggleOn) { + webEngineView.state = "FullScreen"; + } else { + webEngineView.state = ""; + } + request.accept(); + } + + onWindowCloseRequested: { + console.error("window close requested:", request.destination) + } + + Timer { + id: reloadTimer + interval: 0 + running: false + repeat: false + onTriggered: webEngineView.reload() } } - onLinkHovered: { - //TODO: change cursor shape? - console.error("hoveredUrl:", hoveredUrl) - } + Loader { + id: webDialog + property WebEngineNewViewRequest request: null + anchors.fill: parent + anchors.margins: 10 - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webEngineView.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - settings.autoLoadImages: true - settings.javascriptEnabled: true - settings.errorPageEnabled: true - settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: true - settings.autoLoadIconsForPage: true - settings.touchIconsEnabled: true - - onCertificateError: { - error.defer(); - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - onNewViewRequested: { - request.openIn(webEngineView); - } - - onRenderProcessTerminated: { - var status = ""; - switch (terminationStatus) { - case WebEngineView.NormalTerminationStatus: - status = "(normal exit)"; - break; - case WebEngineView.AbnormalTerminationStatus: - status = "(abnormal exit)"; - break; - case WebEngineView.CrashedTerminationStatus: - status = "(crashed)"; - break; - case WebEngineView.KilledTerminationStatus: - status = "(killed)"; - break; + active: false + sourceComponent: webDialogComponent + onStatusChanged: { + if (Loader.Ready === status) { + focus = true + item.webDialogView.profile = webEngineView.profile + request.openIn(item.webDialogView) + } } - - print("Render process exited with code " + exitCode + " " + status); - reloadTimer.running = true; - } - - onWindowCloseRequested: { - } - - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() } } } From 320ab2e68211fc5753de3e1e3b9b4d74cc502db4 Mon Sep 17 00:00:00 2001 From: vladest Date: Thu, 26 Oct 2017 21:14:44 +0200 Subject: [PATCH 09/15] Rework using StackView. Now backward takes in account popups as well --- interface/resources/qml/hifi/WebBrowser.qml | 356 ++++++++++---------- 1 file changed, 174 insertions(+), 182 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index c96d05d6a7..79932dbcdd 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -13,6 +13,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 as QQControls import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 import QtWebEngine 1.5 import QtWebChannel 1.0 @@ -46,13 +47,13 @@ Rectangle { onSuggestions: { if (suggestions.length > 0) { suggestionsList = [] - suggestionsList.push(addressBarInput.text) //do not overwrite edit text + suggestionsList.push(addressBarInput.text); //do not overwrite edit text for(var i = 0; i < suggestions.length; i++) { - suggestionsList.push(suggestions[i]) + suggestionsList.push(suggestions[i]); } addressBar.model = suggestionsList if (!addressBar.popup.visible) { - addressBar.popup.open() + addressBar.popup.open(); } } } @@ -64,7 +65,7 @@ Rectangle { repeat: false onTriggered: { if (addressBar.editText !== "") { - searchEngine.requestSuggestions(addressBarInput.text) + searchEngine.requestSuggestions(addressBarInput.text); } } } @@ -83,9 +84,9 @@ Rectangle { } addressBar.model = [] - webEngineView.url = url - suggestionRequestTimer.stop() - addressBar.popup.close() + webStack.currentItem.webEngineView.url = url + suggestionRequestTimer.stop(); + addressBar.popup.close(); } Column { @@ -98,22 +99,26 @@ Rectangle { height: 48 HifiControls.WebGlyphButton { - enabled: webEngineView.canGoBack + enabled: webStack.currentItem.webEngineView.canGoBack || webStack.depth > 1 glyph: hifi.glyphs.backward; anchors.verticalCenter: parent.verticalCenter; size: 38; onClicked: { - webEngineView.goBack() + if (webStack.currentItem.webEngineView.canGoBack) { + webStack.currentItem.webEngineView.goBack(); + } else if (webStack.depth > 1) { + webStack.pop(); + } } } HifiControls.WebGlyphButton { - enabled: webEngineView.canGoForward + enabled: webStack.currentItem.webEngineView.canGoForward glyph: hifi.glyphs.forward; anchors.verticalCenter: parent.verticalCenter; size: 38; onClicked: { - webEngineView.goForward() + webStack.currentItem.webEngineView.goForward(); } } @@ -128,21 +133,21 @@ Rectangle { indicator: Item {} background: Item {} onActivated: { - goTo(textAt(index)) + goTo(textAt(index)); } - popup.height: webEngineView.height + popup.height: webStack.height onFocusChanged: { if (focus) { - addressBarInput.selectAll() + addressBarInput.selectAll(); } } contentItem: QQControls.TextField { id: addressBarInput leftPadding: 26 - rightPadding: hifi.dimensions.controlLineHeight + rightPadding: hifi.dimensions.controlLineHeight + 5 text: addressBar.editText placeholderText: qsTr("Enter URL") font: addressBar.font @@ -151,7 +156,7 @@ Rectangle { verticalAlignment: Text.AlignVCenter onFocusChanged: { if (focus) { - selectAll() + selectAll(); } } @@ -161,7 +166,7 @@ Rectangle { Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text) + goTo(addressBarInput.text); event.accepted = true; } } @@ -173,58 +178,57 @@ Rectangle { id: faviconImage width: 16; height: 16 sourceSize: Qt.size(width, height) - source: webEngineView.icon - onSourceChanged: console.log("web icon", source) + source: webStack.currentItem.webEngineView.icon } HifiControls.WebGlyphButton { - glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; + glyph: webStack.currentItem.webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight z: 2 x: addressBarInput.width - implicitWidth onClicked: { - if (webEngineView.loading) { - webEngineView.stop() + if (webStack.currentItem.webEngineView.loading) { + webStack.currentItem.webEngineView.stop(); } else { - reloadTimer.start() + webStack.currentItem.webEngineView.reloadTimer.start(); } } } } - Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i"); Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text) + goTo(addressBarInput.text); event.accepted = true; } } onEditTextChanged: { - if (addressBar.editText !== "" && addressBar.editText !== webEngineView.url.toString()) { + if (addressBar.editText !== "" && addressBar.editText !== webStack.currentItem.webEngineView.url.toString()) { suggestionRequestTimer.restart(); } else { addressBar.model = [] - addressBar.popup.close() + addressBar.popup.close(); } } Layout.fillWidth: true - editText: webEngineView.url - onAccepted: goTo(addressBarInput.text) + editText: webStack.currentItem.webEngineView.url + onAccepted: goTo(addressBarInput.text); } HifiControls.WebGlyphButton { checkable: true - checked: webEngineView.audioMuted + checked: webStack.currentItem.webEngineView.audioMuted glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight onClicked: { - webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute) + webStack.currentItem.webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute); } } } @@ -249,18 +253,88 @@ Rectangle { width: parent.width; from: 0 to: 100 - value: webEngineView.loadProgress + value: webStack.currentItem.webEngineView.loadProgress height: 2 } Component { - id: webDialogComponent + id: webViewComponent Rectangle { - property alias webDialogView: webDialogView - color: "white" + property alias webEngineView: webEngineView + property WebEngineNewViewRequest request: null + + property bool isDialog: QQControls.StackView.index > 0 + property real margins: isDialog ? 10 : 0 + + color: "#d1d1d1" + + QQControls.StackView.onActivated: { + addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); + } + + onRequestChanged: { + if (isDialog && request !== null && request !== undefined) {//is Dialog ? + request.openIn(webEngineView); + } + } + HifiControls.BaseWebView { - id: webDialogView + id: webEngineView anchors.fill: parent + anchors.margins: parent.margins + + layer.enabled: parent.isDialog + layer.effect: DropShadow { + verticalOffset: 8 + horizontalOffset: 8 + color: "#330066ff" + samples: 10 + spread: 0.5 + } + + focus: true + objectName: "tabletWebEngineView" + + //profile: HFWebEngineProfile; + + property string userScriptUrl: "" + + onLoadingChanged: { + if (!loading) { + suggestionRequestTimer.stop(); + addressBar.popup.close(); + } + } + + onLinkHovered: { + //TODO: change cursor shape? + } + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webEngineView.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] settings.autoLoadImages: true settings.javascriptEnabled: true @@ -270,162 +344,80 @@ Rectangle { settings.autoLoadIconsForPage: true settings.touchIconsEnabled: true + onCertificateError: { + error.defer(); + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onNewViewRequested: { + if (request.destination == WebEngineView.NewViewInDialog) { + webStack.push(webViewComponent, {"request": request}); + } else { + request.openIn(webEngineView); + } + } + + onRenderProcessTerminated: { + var status = ""; + switch (terminationStatus) { + case WebEngineView.NormalTerminationStatus: + status = "(normal exit)"; + break; + case WebEngineView.AbnormalTerminationStatus: + status = "(abnormal exit)"; + break; + case WebEngineView.CrashedTerminationStatus: + status = "(crashed)"; + break; + case WebEngineView.KilledTerminationStatus: + status = "(killed)"; + break; + } + + console.error("Render process exited with code " + exitCode + " " + status); + reloadTimer.running = true; + } + + onFullScreenRequested: { + if (request.toggleOn) { + webEngineView.state = "FullScreen"; + } else { + webEngineView.state = ""; + } + request.accept(); + } + onWindowCloseRequested: { - webDialog.active = false - webDialog.request = null + webStack.pop(); + } + + Timer { + id: reloadTimer + interval: 0 + running: false + repeat: false + onTriggered: webEngineView.reload() } } } } - Item { + QQControls.StackView { + id: webStack width: parent.width; property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - HifiControls.BaseWebView { - id: webEngineView - anchors.fill: parent - - focus: true - objectName: "tabletWebEngineView" - - url: "https://www.highfidelity.com" - - //profile: HFWebEngineProfile; - - property string userScriptUrl: "" - - onLoadingChanged: { - if (!loading) { - suggestionRequestTimer.stop() - addressBar.popup.close() - } - } - - onLinkHovered: { - //TODO: change cursor shape? - console.error("hoveredUrl:", hoveredUrl) - } - - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webEngineView.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - settings.autoLoadImages: true - settings.javascriptEnabled: true - settings.errorPageEnabled: true - settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: true - settings.autoLoadIconsForPage: true - settings.touchIconsEnabled: true - - onCertificateError: { - error.defer(); - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - onNewViewRequested: { - console.error("new view requested:", request.destination) - if (request.destination == WebEngineView.NewViewInDialog) { - webDialog.request = request - webDialog.active = true - } else { - request.openIn(webEngineView); - } - } - - onRenderProcessTerminated: { - var status = ""; - switch (terminationStatus) { - case WebEngineView.NormalTerminationStatus: - status = "(normal exit)"; - break; - case WebEngineView.AbnormalTerminationStatus: - status = "(abnormal exit)"; - break; - case WebEngineView.CrashedTerminationStatus: - status = "(crashed)"; - break; - case WebEngineView.KilledTerminationStatus: - status = "(killed)"; - break; - } - - console.error("Render process exited with code " + exitCode + " " + status); - reloadTimer.running = true; - } - - onFullScreenRequested: { - console.error("FS requested:", request.destination) - if (request.toggleOn) { - webEngineView.state = "FullScreen"; - } else { - webEngineView.state = ""; - } - request.accept(); - } - - onWindowCloseRequested: { - console.error("window close requested:", request.destination) - } - - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() - } - } - - Loader { - id: webDialog - property WebEngineNewViewRequest request: null - anchors.fill: parent - anchors.margins: 10 - - active: false - sourceComponent: webDialogComponent - onStatusChanged: { - if (Loader.Ready === status) { - focus = true - item.webDialogView.profile = webEngineView.profile - request.openIn(item.webDialogView) - } - } - } + Component.onCompleted: webStack.push(webViewComponent, {"webEngineView.url": "https://www.highfidelity.com"}); } } From 950ac3540ab0af9a10b6188e190f8ec9cd16fb1b Mon Sep 17 00:00:00 2001 From: vladest Date: Sun, 29 Oct 2017 14:05:47 +0100 Subject: [PATCH 10/15] Fix mute unmute. Scrolling thru suggestions popup now set popup's contains into url. Fixed reload timer --- interface/resources/qml/hifi/WebBrowser.qml | 30 +++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 79932dbcdd..ec5594e74d 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -85,6 +85,7 @@ Rectangle { addressBar.model = [] webStack.currentItem.webEngineView.url = url + addressBar.editText = webStack.currentItem.webEngineView.url suggestionRequestTimer.stop(); addressBar.popup.close(); } @@ -136,6 +137,12 @@ Rectangle { goTo(textAt(index)); } + onHighlightedIndexChanged: { + if (highlightedIndex >= 0) { + addressBar.editText = textAt(highlightedIndex) + } + } + popup.height: webStack.height onFocusChanged: { @@ -191,7 +198,7 @@ Rectangle { if (webStack.currentItem.webEngineView.loading) { webStack.currentItem.webEngineView.stop(); } else { - webStack.currentItem.webEngineView.reloadTimer.start(); + webStack.currentItem.reloadTimer.start(); } } } @@ -228,7 +235,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight onClicked: { - webStack.currentItem.webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute); + webStack.currentItem.webEngineView.audioMuted = !webStack.currentItem.webEngineView.audioMuted } } } @@ -261,6 +268,8 @@ Rectangle { id: webViewComponent Rectangle { property alias webEngineView: webEngineView + property alias reloadTimer: reloadTimer + property WebEngineNewViewRequest request: null property bool isDialog: QQControls.StackView.index > 0 @@ -314,7 +323,7 @@ Rectangle { WebEngineScript { id: createGlobalEventBridge sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation + injectionPoint: WebEngineScript.Deferred worldId: WebEngineScript.MainWorld } @@ -399,14 +408,13 @@ Rectangle { onWindowCloseRequested: { webStack.pop(); } - - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() - } + } + Timer { + id: reloadTimer + interval: 0 + running: false + repeat: false + onTriggered: webEngineView.reload() } } } From 586dde7dd70e0846b2b6644a8abb0275f7503683 Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 18:15:42 +0100 Subject: [PATCH 11/15] Removed GPL code. Added own implementation of suggestions engine. Fixed Google maps different issues --- interface/resources/qml/hifi/WebBrowser.qml | 24 +- interface/src/Application.cpp | 4 +- interface/src/opensearch/opensearchengine.cpp | 650 ------------------ interface/src/opensearch/opensearchengine.h | 128 ---- .../opensearch/opensearchenginedelegate.cpp | 43 -- .../src/opensearch/opensearchenginedelegate.h | 18 - .../webbrowsersuggestionsengine.cpp | 87 +++ .../webbrowser/webbrowsersuggestionsengine.h | 46 ++ 8 files changed, 147 insertions(+), 853 deletions(-) delete mode 100644 interface/src/opensearch/opensearchengine.cpp delete mode 100644 interface/src/opensearch/opensearchengine.h delete mode 100644 interface/src/opensearch/opensearchenginedelegate.cpp delete mode 100644 interface/src/opensearch/opensearchenginedelegate.h create mode 100644 interface/src/webbrowser/webbrowsersuggestionsengine.cpp create mode 100644 interface/src/webbrowser/webbrowsersuggestionsengine.h diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index ec5594e74d..ab93752d92 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -36,13 +36,11 @@ Rectangle { property bool keyboardRaised: false property bool punctuationMode: false property var suggestionsList: [] + readonly property string searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q="; - OpenSearchEngine { + + WebBrowserSuggestionsEngine { id: searchEngine - name: "Google"; - searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q={searchTerms}"; - suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; - suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; onSuggestions: { if (suggestions.length > 0) { @@ -65,7 +63,7 @@ Rectangle { repeat: false onTriggered: { if (addressBar.editText !== "") { - searchEngine.requestSuggestions(addressBarInput.text); + searchEngine.querySuggestions(addressBarInput.text); } } } @@ -74,18 +72,19 @@ Rectangle { function goTo(url) { //must be valid attempt to open an site with dot - + var urlNew = url if (url.indexOf(".") > 0) { if (url.indexOf("http") < 0) { - url = "http://" + url; + urlNew = "http://" + url; } } else { - url = searchEngine.searchUrl(url) + urlNew = searchUrlTemplate + url } addressBar.model = [] - webStack.currentItem.webEngineView.url = url - addressBar.editText = webStack.currentItem.webEngineView.url + //need to rebind if binfing was broken by selecting from suggestions + addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); + webStack.currentItem.webEngineView.url = urlNew suggestionRequestTimer.stop(); addressBar.popup.close(); } @@ -305,11 +304,13 @@ Rectangle { objectName: "tabletWebEngineView" //profile: HFWebEngineProfile; + profile.httpUserAgent: "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0" property string userScriptUrl: "" onLoadingChanged: { if (!loading) { + addressBarInput.cursorPosition = 0 //set input field cursot to beginning suggestionRequestTimer.stop(); addressBar.popup.close(); } @@ -360,7 +361,6 @@ Rectangle { Component.onCompleted: { webChannel.registerObject("eventBridge", eventBridge); webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; } onFeaturePermissionRequested: { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0b9e8833e4..bf2754917b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -203,7 +203,7 @@ #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" -#include "opensearch/opensearchengine.h" +#include "webbrowser/webbrowsersuggestionsengine.h" // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -2225,7 +2225,7 @@ void Application::initializeUi() { QmlCommerce::registerType(); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); - qmlRegisterType("HifiWeb", 1, 0, "OpenSearchEngine"); + qmlRegisterType("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine"); auto offscreenUi = DependencyManager::get(); offscreenUi->create(); diff --git a/interface/src/opensearch/opensearchengine.cpp b/interface/src/opensearch/opensearchengine.cpp deleted file mode 100644 index 28cacab256..0000000000 --- a/interface/src/opensearch/opensearchengine.cpp +++ /dev/null @@ -1,650 +0,0 @@ -#include "opensearchengine.h" -#include "qregexp.h" -#include "opensearchenginedelegate.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - - -/*! - \class OpenSearchEngine - \brief A class representing a single search engine described in OpenSearch format - - OpenSearchEngine is a class that represents a single search engine based on - the OpenSearch format. - For more information about the format, see http://www.opensearch.org/. - - Instances of the class hold all the data associated with the corresponding search - engines, such as name(), description() and also URL templates that are used - to construct URLs, which can be used later to perform search queries. Search engine - can also have an image, even an external one, in this case it will be downloaded - automatically from the network. - - OpenSearchEngine instances can be constructed from scratch but also read from - external sources and written back to them. OpenSearchReader and OpenSearchWriter - are the classes provided for reading and writing OpenSearch descriptions. - - Default constructed engines need to be filled with the necessary information before - they can be used to peform search requests. First of all, a search engine should have - the metadata including the name and the description. - However, the most important are URL templates, which are the construction of URLs - but can also contain template parameters, that are replaced with corresponding values - at the time of constructing URLs. - - There are two types of URL templates: search URL template and suggestions URL template. - Search URL template is needed for constructing search URLs, which point directly to - search results. Suggestions URL template is necessary to construct suggestion queries - URLs, which are then used for requesting contextual suggestions, a popular service - offered along with search results that provides search terms related to what has been - supplied by the user. - - Both types of URLs are constructed by the class, by searchUrl() and suggestionsUrl() - functions respectively. However, search requests are supposed to be performed outside - the class, while suggestion queries can be executed using the requestSuggestions() - method. The class will take care of peforming the network request and parsing the - JSON response. - - Both the image request and suggestion queries need network access. The class can - perform network requests on its own, though the client application needs to provide - a network access manager, which then will to be used for network operations. - Without that, both images delivered from remote locations and contextual suggestions - will be disabled. - - \sa OpenSearchReader, OpenSearchWriter -*/ - -/*! - Constructs an engine with a given \a parent. -*/ -OpenSearchEngine::OpenSearchEngine(QObject* parent) - : QObject(parent) - , m_searchMethod(QLatin1String("get")) - , m_suggestionsMethod(QLatin1String("get")) - , m_suggestionsReply(0) - , m_delegate(0) -{ - m_requestMethods.insert(QLatin1String("get"), QNetworkAccessManager::GetOperation); - m_requestMethods.insert(QLatin1String("post"), QNetworkAccessManager::PostOperation); -} - -/*! - A destructor. -*/ -OpenSearchEngine::~OpenSearchEngine() -{ -} - -QString OpenSearchEngine::parseTemplate(const QString &searchTerm, const QString &searchTemplate) -{ - QString language = QLocale().name(); - // Simple conversion to RFC 3066. - language = language.replace(QLatin1Char('_'), QLatin1Char('-')); - - QString result = searchTemplate; - result.replace(QLatin1String("{count}"), QLatin1String("20")); - result.replace(QLatin1String("{startIndex}"), QLatin1String("0")); - result.replace(QLatin1String("{startPage}"), QLatin1String("0")); - result.replace(QLatin1String("{language}"), language); - result.replace(QLatin1String("{inputEncoding}"), QLatin1String("UTF-8")); - result.replace(QLatin1String("{outputEncoding}"), QLatin1String("UTF-8")); - result.replace(QRegExp(QLatin1String("\\{([^\\}]*:|)source\\??\\}")), QCoreApplication::applicationName()); - result.replace(QLatin1String("{searchTerms}"), QLatin1String(QUrl::toPercentEncoding(searchTerm))); - - return result; -} - -/*! - \property OpenSearchEngine::name - \brief the name of the engine - - \sa description() -*/ -QString OpenSearchEngine::name() const -{ - return m_name; -} - -void OpenSearchEngine::setName(const QString &name) -{ - m_name = name; -} - -/*! - \property OpenSearchEngine::description - \brief the description of the engine - - \sa name() -*/ -QString OpenSearchEngine::description() const -{ - return m_description; -} - -void OpenSearchEngine::setDescription(const QString &description) -{ - m_description = description; -} - -/*! - \property OpenSearchEngine::searchUrlTemplate - \brief the template of the search URL - - \sa searchUrl(), searchParameters(), suggestionsUrlTemplate() -*/ -QString OpenSearchEngine::searchUrlTemplate() const -{ - return m_searchUrlTemplate; -} - -void OpenSearchEngine::setSearchUrlTemplate(const QString &searchUrlTemplate) -{ - if (!searchUrlTemplate.startsWith(QLatin1String("http://")) && !searchUrlTemplate.startsWith(QLatin1String("https://"))) { - return; - } - - m_searchUrlTemplate = searchUrlTemplate; -} - -/*! - Constructs and returns a search URL with a given \a searchTerm. - - The URL template is processed according to the specification: - http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax - - A list of template parameters currently supported and what they are replaced with: - \table - \header \o parameter - \o value - \row \o "{count}" - \o "20" - \row \o "{startIndex}" - \o "0" - \row \o "{startPage}" - \o "0" - \row \o "{language}" - \o "the default language code (RFC 3066)" - \row \o "{inputEncoding}" - \o "UTF-8" - \row \o "{outputEncoding}" - \o "UTF-8" - \row \o "{*:source}" - \o "application name, QCoreApplication::applicationName()" - \row \o "{searchTerms}" - \o "the string supplied by the user" - \endtable - - \sa searchUrlTemplate(), searchParameters(), suggestionsUrl() -*/ -QUrl OpenSearchEngine::searchUrl(const QString &searchTerm) const -{ - if (m_searchUrlTemplate.isEmpty()) { - return QUrl(); - } - - QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_searchUrlTemplate).toUtf8()); - - QUrlQuery query(retVal); - if (m_searchMethod != QLatin1String("post")) { - Parameters::const_iterator end = m_searchParameters.constEnd(); - Parameters::const_iterator i = m_searchParameters.constBegin(); - for (; i != end; ++i) { - query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); - } - retVal.setQuery(query); - } - - return retVal; -} - -QByteArray OpenSearchEngine::getPostData(const QString &searchTerm) const -{ - if (m_searchMethod != QLatin1String("post")) { - return QByteArray(); - } - - QUrl retVal = QUrl("http://foo.bar"); - - QUrlQuery query(retVal); - Parameters::const_iterator end = m_searchParameters.constEnd(); - Parameters::const_iterator i = m_searchParameters.constBegin(); - for (; i != end; ++i) { - query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); - } - retVal.setQuery(query); - - QByteArray data = retVal.toEncoded(QUrl::RemoveScheme); - return data.contains('?') ? data.mid(data.lastIndexOf('?') + 1) : QByteArray(); -} - -/*! - \property providesSuggestions - \brief indicates whether the engine supports contextual suggestions -*/ -bool OpenSearchEngine::providesSuggestions() const -{ - return (!m_suggestionsUrlTemplate.isEmpty() || !m_preparedSuggestionsUrl.isEmpty()); -} - -/*! - \property OpenSearchEngine::suggestionsUrlTemplate - \brief the template of the suggestions URL - - \sa suggestionsUrl(), suggestionsParameters(), searchUrlTemplate() -*/ -QString OpenSearchEngine::suggestionsUrlTemplate() const -{ - return m_suggestionsUrlTemplate; -} - -void OpenSearchEngine::setSuggestionsUrlTemplate(const QString &suggestionsUrlTemplate) -{ - if (!suggestionsUrlTemplate.startsWith(QLatin1String("http://")) && !suggestionsUrlTemplate.startsWith(QLatin1String("https://"))) { - return; - } - - m_suggestionsUrlTemplate = suggestionsUrlTemplate; -} - -/*! - Constructs a suggestions URL with a given \a searchTerm. - - The URL template is processed according to the specification: - http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax - - See searchUrl() for more information about processing template parameters. - - \sa suggestionsUrlTemplate(), suggestionsParameters(), searchUrl() -*/ -QUrl OpenSearchEngine::suggestionsUrl(const QString &searchTerm) const -{ - if (!m_preparedSuggestionsUrl.isEmpty()) { - QString s = m_preparedSuggestionsUrl; - s.replace(QLatin1String("%s"), searchTerm); - return QUrl(s); - } - - if (m_suggestionsUrlTemplate.isEmpty()) { - return QUrl(); - } - - QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_suggestionsUrlTemplate).toUtf8()); - - QUrlQuery query(retVal); - if (m_suggestionsMethod != QLatin1String("post")) { - Parameters::const_iterator end = m_suggestionsParameters.constEnd(); - Parameters::const_iterator i = m_suggestionsParameters.constBegin(); - for (; i != end; ++i) { - query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); - } - retVal.setQuery(query); - } - - return retVal; -} - -/*! - \property searchParameters - \brief additional parameters that will be included in the search URL - - For more information see: - http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 -*/ -OpenSearchEngine::Parameters OpenSearchEngine::searchParameters() const -{ - return m_searchParameters; -} - -void OpenSearchEngine::setSearchParameters(const Parameters &searchParameters) -{ - m_searchParameters = searchParameters; -} - -/*! - \property suggestionsParameters - \brief additional parameters that will be included in the suggestions URL - - For more information see: - http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 -*/ -OpenSearchEngine::Parameters OpenSearchEngine::suggestionsParameters() const -{ - return m_suggestionsParameters; -} - -void OpenSearchEngine::setSuggestionsParameters(const Parameters &suggestionsParameters) -{ - m_suggestionsParameters = suggestionsParameters; -} - -/*! - \property searchMethod - \brief HTTP request method that will be used to perform search requests -*/ -QString OpenSearchEngine::searchMethod() const -{ - return m_searchMethod; -} - -void OpenSearchEngine::setSearchMethod(const QString &method) -{ - QString requestMethod = method.toLower(); - if (!m_requestMethods.contains(requestMethod)) { - return; - } - - m_searchMethod = requestMethod; -} - -/*! - \property suggestionsMethod - \brief HTTP request method that will be used to perform suggestions requests -*/ -QString OpenSearchEngine::suggestionsMethod() const -{ - return m_suggestionsMethod; -} - -void OpenSearchEngine::setSuggestionsMethod(const QString &method) -{ - QString requestMethod = method.toLower(); - if (!m_requestMethods.contains(requestMethod)) { - return; - } - - m_suggestionsMethod = requestMethod; -} - -/*! - \property imageUrl - \brief the image URL of the engine - - When setting a new image URL, it won't be loaded immediately. The first request will be - deferred until image() is called for the first time. - - \note To be able to request external images, you need to provide a network access manager, - which will be used for network operations. - - \sa image(), networkAccessManager() -*/ -QString OpenSearchEngine::imageUrl() const -{ - return m_imageUrl; -} - -void OpenSearchEngine::setImageUrl(const QString &imageUrl) -{ - m_imageUrl = imageUrl; -} - -void OpenSearchEngine::loadImage() const -{ - if (m_imageUrl.isEmpty()) { - return; - } - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - - QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(QUrl::fromEncoded(m_imageUrl.toUtf8()))); - connect(reply, SIGNAL(finished()), this, SLOT(imageObtained())); -} - -void OpenSearchEngine::imageObtained() -{ - QNetworkReply* reply = qobject_cast(sender()); - - if (!reply) { - return; - } - - QByteArray response = reply->readAll(); - - reply->close(); - reply->deleteLater(); - - if (response.isEmpty()) { - return; - } - - m_image.loadFromData(response); - emit imageChanged(); -} - -/*! - \property image - \brief the image of the engine - - When no image URL has been set and an image will be set explicitly, a new data URL - will be constructed, holding the image data encoded with Base64. - - \sa imageUrl() -*/ -QImage OpenSearchEngine::image() const -{ - if (m_image.isNull()) { - loadImage(); - } - return m_image; -} - -void OpenSearchEngine::setImage(const QImage &image) -{ - if (m_imageUrl.isEmpty()) { - QBuffer imageBuffer; - imageBuffer.open(QBuffer::ReadWrite); - if (image.save(&imageBuffer, "PNG")) { - m_imageUrl = QString(QLatin1String("data:image/png;base64,%1")) - .arg(QLatin1String(imageBuffer.buffer().toBase64())); - } - } - - m_image = image; - emit imageChanged(); -} - -/*! - \property valid - \brief indicates whether the engine is valid i.e. the description was properly formed and included all necessary information -*/ -bool OpenSearchEngine::isValid() const -{ - return (!m_name.isEmpty() && !m_searchUrlTemplate.isEmpty()); -} - -bool OpenSearchEngine::operator==(const OpenSearchEngine &other) const -{ - return (m_name == other.m_name - && m_description == other.m_description - && m_imageUrl == other.m_imageUrl - && m_searchUrlTemplate == other.m_searchUrlTemplate - && m_suggestionsUrlTemplate == other.m_suggestionsUrlTemplate - && m_searchParameters == other.m_searchParameters - && m_suggestionsParameters == other.m_suggestionsParameters); -} - -bool OpenSearchEngine::operator<(const OpenSearchEngine &other) const -{ - return (m_name < other.m_name); -} - -/*! - Requests contextual suggestions on the search engine, for a given \a searchTerm. - - If succeeded, suggestions() signal will be emitted once the suggestions are received. - - \note To be able to request suggestions, you need to provide a network access manager, - which will be used for network operations. - - \sa requestSearchResults() -*/ - -void OpenSearchEngine::setSuggestionsParameters(const QByteArray ¶meters) -{ - m_preparedSuggestionsParameters = parameters; -} - -void OpenSearchEngine::setSuggestionsUrl(const QString &string) -{ - m_preparedSuggestionsUrl = string; -} - -QString OpenSearchEngine::getSuggestionsUrl() -{ - return suggestionsUrl("searchstring").toString().replace(QLatin1String("searchstring"), QLatin1String("%s")); -} - -QByteArray OpenSearchEngine::getSuggestionsParameters() -{ - QStringList parameters; - Parameters::const_iterator end = m_suggestionsParameters.constEnd(); - Parameters::const_iterator i = m_suggestionsParameters.constBegin(); - for (; i != end; ++i) { - parameters.append(i->first + QLatin1String("=") + i->second); - } - - QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); - - return data; -} - -void OpenSearchEngine::requestSuggestions(const QString &searchTerm) -{ - if (searchTerm.isEmpty() || !providesSuggestions()) { - return; - } - - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - - if (m_suggestionsReply) { - m_suggestionsReply->disconnect(this); - m_suggestionsReply->abort(); - m_suggestionsReply->deleteLater(); - m_suggestionsReply = 0; - } - - Q_ASSERT(m_requestMethods.contains(m_suggestionsMethod)); - if (m_suggestionsMethod == QLatin1String("get")) { - m_suggestionsReply = networkAccessManager.get(QNetworkRequest(suggestionsUrl(searchTerm))); - } - else { - QStringList parameters; - Parameters::const_iterator end = m_suggestionsParameters.constEnd(); - Parameters::const_iterator i = m_suggestionsParameters.constBegin(); - for (; i != end; ++i) { - parameters.append(i->first + QLatin1String("=") + i->second); - } - - QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); - m_suggestionsReply = networkAccessManager.post(QNetworkRequest(suggestionsUrl(searchTerm)), data); - } - - connect(m_suggestionsReply, SIGNAL(finished()), this, SLOT(suggestionsObtained())); -} - -/*! - Requests search results on the search engine, for a given \a searchTerm. - - The default implementation does nothing, to supply your own you need to create your own - OpenSearchEngineDelegate subclass and supply it to the engine. Then the function will call - the performSearchRequest() method of the delegate, which can then handle the request - in a custom way. - - \sa requestSuggestions(), delegate() -*/ -void OpenSearchEngine::requestSearchResults(const QString &searchTerm) -{ - if (!m_delegate || searchTerm.isEmpty()) { - return; - } - - Q_ASSERT(m_requestMethods.contains(m_searchMethod)); - - QNetworkRequest request(QUrl(searchUrl(searchTerm))); - QByteArray data; - QNetworkAccessManager::Operation operation = m_requestMethods.value(m_searchMethod); - - if (operation == QNetworkAccessManager::PostOperation) { - QStringList parameters; - Parameters::const_iterator end = m_searchParameters.constEnd(); - Parameters::const_iterator i = m_searchParameters.constBegin(); - for (; i != end; ++i) { - parameters.append(i->first + QLatin1String("=") + i->second); - } - - data = parameters.join(QLatin1String("&")).toUtf8(); - } - - m_delegate->performSearchRequest(request, operation, data); -} - -void OpenSearchEngine::suggestionsObtained() -{ - const QByteArray response = m_suggestionsReply->readAll(); - - m_suggestionsReply->close(); - m_suggestionsReply->deleteLater(); - m_suggestionsReply = 0; - - QJsonParseError err; - QJsonDocument json = QJsonDocument::fromJson(response, &err); - const QVariant res = json.toVariant(); - - if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) - return; - - const QVariantList list = res.toList(); - - if (list.size() < 2) - return; - - QStringList out; - - foreach (const QVariant &v, list.at(1).toList()) - out.append(v.toString()); - - emit suggestions(out); -} - - -/*! - \property delegate - \brief the delegate that is used to perform specific tasks. - - It can be currently supplied to provide a custom behaviour ofthe requetSearchResults() method. - The default implementation does nothing. -*/ -OpenSearchEngineDelegate* OpenSearchEngine::delegate() const -{ - return m_delegate; -} - -void OpenSearchEngine::setDelegate(OpenSearchEngineDelegate* delegate) -{ - m_delegate = delegate; -} - -/*! - \fn void OpenSearchEngine::imageChanged() - - This signal is emitted whenever the image of the engine changes. - - \sa image(), imageUrl() -*/ - -/*! - \fn void OpenSearchEngine::suggestions(const QStringList &suggestions) - - This signal is emitted whenever new contextual suggestions have been provided - by the search engine. To request suggestions, use requestSuggestions(). - The suggestion set is specified by \a suggestions. - - \sa requestSuggestions() -*/ diff --git a/interface/src/opensearch/opensearchengine.h b/interface/src/opensearch/opensearchengine.h deleted file mode 100644 index fa714b85d3..0000000000 --- a/interface/src/opensearch/opensearchengine.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef OPENSEARCHENGINE_H -#define OPENSEARCHENGINE_H - -#include -#include -#include -#include -#include -#include - -class QNetworkReply; - -class OpenSearchEngineDelegate; -class OpenSearchEngine : public QObject -{ - Q_OBJECT - -signals: - void imageChanged(); - void suggestions(const QStringList &suggestions); - -public: - typedef QPair Parameter; - typedef QList Parameters; - - Q_PROPERTY(QString name READ name WRITE setName) - Q_PROPERTY(QString description READ description WRITE setDescription) - Q_PROPERTY(QString searchUrlTemplate READ searchUrlTemplate WRITE setSearchUrlTemplate) - Q_PROPERTY(Parameters searchParameters READ searchParameters WRITE setSearchParameters) - Q_PROPERTY(QString searchMethod READ searchMethod WRITE setSearchMethod) - Q_PROPERTY(QString suggestionsUrlTemplate READ suggestionsUrlTemplate WRITE setSuggestionsUrlTemplate) - Q_PROPERTY(QString suggestionsUrl READ getSuggestionsUrl WRITE setSuggestionsUrl) - Q_PROPERTY(Parameters suggestionsParameters READ suggestionsParameters WRITE setSuggestionsParameters) - Q_PROPERTY(QString suggestionsMethod READ suggestionsMethod WRITE setSuggestionsMethod) - Q_PROPERTY(bool providesSuggestions READ providesSuggestions) - Q_PROPERTY(QString imageUrl READ imageUrl WRITE setImageUrl) - Q_PROPERTY(bool valid READ isValid) - - OpenSearchEngine(QObject* parent = 0); - ~OpenSearchEngine(); - - QString name() const; - void setName(const QString &name); - - QString description() const; - void setDescription(const QString &description); - - QString searchUrlTemplate() const; - void setSearchUrlTemplate(const QString &searchUrl); - - QByteArray getPostData(const QString &searchTerm) const; - - bool providesSuggestions() const; - - QString suggestionsUrlTemplate() const; - void setSuggestionsUrlTemplate(const QString &suggestionsUrl); - QUrl suggestionsUrl(const QString &searchTerm) const; - - Parameters searchParameters() const; - void setSearchParameters(const Parameters &searchParameters); - - Parameters suggestionsParameters() const; - void setSuggestionsParameters(const Parameters &suggestionsParameters); - - QString searchMethod() const; - void setSearchMethod(const QString &method); - - QString suggestionsMethod() const; - void setSuggestionsMethod(const QString &method); - - QString imageUrl() const; - void setImageUrl(const QString &url); - - QImage image() const; - void setImage(const QImage &image); - - bool isValid() const; - - void setSuggestionsUrl(const QString &string); - void setSuggestionsParameters(const QByteArray ¶meters); - QString getSuggestionsUrl(); - QByteArray getSuggestionsParameters(); - - OpenSearchEngineDelegate* delegate() const; - void setDelegate(OpenSearchEngineDelegate* delegate); - - bool operator==(const OpenSearchEngine &other) const; - bool operator<(const OpenSearchEngine &other) const; - -public slots: - QUrl searchUrl(const QString &searchTerm) const; - void requestSuggestions(const QString &searchTerm); - void requestSearchResults(const QString &searchTerm); - -protected: - static QString parseTemplate(const QString &searchTerm, const QString &searchTemplate); - void loadImage() const; - -private slots: - void imageObtained(); - void suggestionsObtained(); - -private: - QString m_name; - QString m_description; - - QString m_imageUrl; - QImage m_image; - - QString m_searchUrlTemplate; - QString m_suggestionsUrlTemplate; - Parameters m_searchParameters; - Parameters m_suggestionsParameters; - QString m_searchMethod; - QString m_suggestionsMethod; - - QByteArray m_preparedSuggestionsParameters; - QString m_preparedSuggestionsUrl; - - QMap m_requestMethods; - - QNetworkReply* m_suggestionsReply; - - OpenSearchEngineDelegate* m_delegate; -}; - -#endif // OPENSEARCHENGINE_H - diff --git a/interface/src/opensearch/opensearchenginedelegate.cpp b/interface/src/opensearch/opensearchenginedelegate.cpp deleted file mode 100644 index 2c9c18ece0..0000000000 --- a/interface/src/opensearch/opensearchenginedelegate.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "opensearchenginedelegate.h" - -/*! - \class OpenSearchEngineDelegate - \brief An abstract class providing custom processing of specific activities. - - OpenSearchEngineDelegate is an abstract class that can be subclassed and set on - an OpenSearchEngine. It allows to customize some parts of the default implementation - or even extend it with missing bits. - - Currently subclasses can only provide a custom way of handling search requests by - reimplementing the performSearchRequest() method. - - \sa OpenSearchEngine -*/ - -/*! - Constructs the delegate. -*/ -OpenSearchEngineDelegate::OpenSearchEngineDelegate() -{ -} - -/*! - Destructs the delegate. -*/ -OpenSearchEngineDelegate::~OpenSearchEngineDelegate() -{ -} - -/*! - \fn void performSearchRequest(const QNetworkRequest &request, - QNetworkAccessManager::Operation operation, const QByteArray &data) = 0 - - This method will be used after OpenSearchEngine::requestResults() is called. - - For example, a console application that uses the OpenSearchEngine class to generate - a search URL for a search term supplied by the user would reimplement the function - and forward the request to e.g. a web browser. - - Likewise, a web browser that uses the OpenSearchEngine class to support multiple search - engines e.g. in a toolbar would perform the request and navigate to the search results site. -*/ diff --git a/interface/src/opensearch/opensearchenginedelegate.h b/interface/src/opensearch/opensearchenginedelegate.h deleted file mode 100644 index ae7b5ef876..0000000000 --- a/interface/src/opensearch/opensearchenginedelegate.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef OPENSEARCHENGINEDELEGATE_H -#define OPENSEARCHENGINEDELEGATE_H - -#include -#include - -class OpenSearchEngineDelegate -{ -public: - OpenSearchEngineDelegate(); - virtual ~OpenSearchEngineDelegate(); - - virtual void performSearchRequest(const QNetworkRequest &request, - QNetworkAccessManager::Operation operation, - const QByteArray &data) = 0; -}; - -#endif // OPENSEARCHENGINEDELEGATE_H diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.cpp b/interface/src/webbrowser/webbrowsersuggestionsengine.cpp new file mode 100644 index 0000000000..546d9c68b0 --- /dev/null +++ b/interface/src/webbrowser/webbrowsersuggestionsengine.cpp @@ -0,0 +1,87 @@ +// +// webbrowsersuggestionsengine.cpp +// interface/src/webbrowser +// +// Created by Vlad Stelmahovsky Kapolka on 30/10/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "webbrowsersuggestionsengine.h" +#include "qregexp.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; + +WebBrowserSuggestionsEngine::WebBrowserSuggestionsEngine(QObject* parent) + : QObject(parent) + , _suggestionsReply(0) { + _currentNAM = &NetworkAccessManager::getInstance(); + connect(_currentNAM, &QNetworkAccessManager::finished, this, &WebBrowserSuggestionsEngine::suggestionsFinished); +} + + +WebBrowserSuggestionsEngine::~WebBrowserSuggestionsEngine() { + disconnect(_currentNAM, &QNetworkAccessManager::finished, this, &WebBrowserSuggestionsEngine::suggestionsFinished); +} + +void WebBrowserSuggestionsEngine::querySuggestions(const QString &searchString) { + if (_suggestionsReply) { + _suggestionsReply->disconnect(this); + _suggestionsReply->abort(); + _suggestionsReply->deleteLater(); + _suggestionsReply = 0; + } + QString url = QString(GoogleSuggestionsUrl).arg(searchString); + _suggestionsReply = _currentNAM->get(QNetworkRequest(url)); +} + +void WebBrowserSuggestionsEngine::suggestionsFinished(QNetworkReply *reply) { + + if (reply != _suggestionsReply) { + return; //invalid reply. ignore + } + + const QByteArray response = _suggestionsReply->readAll(); + + _suggestionsReply->close(); + _suggestionsReply->deleteLater(); + _suggestionsReply = 0; + + QJsonParseError err; + QJsonDocument json = QJsonDocument::fromJson(response, &err); + const QVariant res = json.toVariant(); + + if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) { + return; + } + + const QVariantList list = res.toList(); + + if (list.size() < 2) { + return; + } + + QStringList out; + const QVariantList& suggList = list.at(1).toList(); + + foreach (const QVariant &v, suggList) { + out.append(v.toString()); + } + + emit suggestions(out); +} diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.h b/interface/src/webbrowser/webbrowsersuggestionsengine.h new file mode 100644 index 0000000000..fa0f7f8e2e --- /dev/null +++ b/interface/src/webbrowser/webbrowsersuggestionsengine.h @@ -0,0 +1,46 @@ +#ifndef WEBBROWSERSUGGESTIONSENGINE_H +#define WEBBROWSERSUGGESTIONSENGINE_H + +#include +#include +#include +// +// webbrowsersuggestionsengine.h +// interface/src/webbrowser +// +// Created by Vlad Stelmahovsky Kapolka on 30/10/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +class QNetworkReply; + +class WebBrowserSuggestionsEngine : public QObject +{ + Q_OBJECT + +public: + WebBrowserSuggestionsEngine(QObject* parent = 0); + virtual ~WebBrowserSuggestionsEngine(); + +public slots: + void querySuggestions(const QString& searchString); + +signals: + void suggestions(const QStringList& suggestions); + +private slots: + void suggestionsFinished(QNetworkReply *reply); +private: + QNetworkReply* _suggestionsReply; + QNetworkAccessManager* _currentNAM; +}; + +#endif // WEBBROWSERSUGGESTIONSENGINE_H + From d9c6f17e42b3dc4d23e0bbf81594665f68b8a83d Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:06:17 +0100 Subject: [PATCH 12/15] Coding standards --- interface/src/Application.cpp | 2 +- ...ggestionsengine.cpp => WebBrowserSuggestionsEngine.cpp} | 7 ++++--- ...ersuggestionsengine.h => WebBrowserSuggestionsEngine.h} | 0 3 files changed, 5 insertions(+), 4 deletions(-) rename interface/src/webbrowser/{webbrowsersuggestionsengine.cpp => WebBrowserSuggestionsEngine.cpp} (92%) rename interface/src/webbrowser/{webbrowsersuggestionsengine.h => WebBrowserSuggestionsEngine.h} (100%) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bf2754917b..ab1884f67a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -203,7 +203,7 @@ #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" -#include "webbrowser/webbrowsersuggestionsengine.h" +#include "webbrowser/WebBrowserSuggestionsEngine.h" // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.cpp b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp similarity index 92% rename from interface/src/webbrowser/webbrowsersuggestionsengine.cpp rename to interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp index 546d9c68b0..40af21753b 100644 --- a/interface/src/webbrowser/webbrowsersuggestionsengine.cpp +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "webbrowsersuggestionsengine.h" +#include "WebBrowserSuggestionsEngine.h" #include "qregexp.h" #include @@ -26,6 +26,7 @@ #include const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; +const int SUGGESTIONS_LIST_INDEX = 2; WebBrowserSuggestionsEngine::WebBrowserSuggestionsEngine(QObject* parent) : QObject(parent) @@ -72,12 +73,12 @@ void WebBrowserSuggestionsEngine::suggestionsFinished(QNetworkReply *reply) { const QVariantList list = res.toList(); - if (list.size() < 2) { + if (list.size() <= SUGGESTIONS_LIST_INDEX) { return; } QStringList out; - const QVariantList& suggList = list.at(1).toList(); + const QVariantList& suggList = list.at(SUGGESTIONS_LIST_INDEX).toList(); foreach (const QVariant &v, suggList) { out.append(v.toString()); diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.h b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h similarity index 100% rename from interface/src/webbrowser/webbrowsersuggestionsengine.h rename to interface/src/webbrowser/WebBrowserSuggestionsEngine.h From 7b57fe1a5da214b6b621b17fc5b49d3128f96b04 Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:21:34 +0100 Subject: [PATCH 13/15] type fixed --- interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp index 40af21753b..a21badb0a8 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp @@ -25,8 +25,8 @@ #include -const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; -const int SUGGESTIONS_LIST_INDEX = 2; +static const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; +static const int SUGGESTIONS_LIST_INDEX = 1; WebBrowserSuggestionsEngine::WebBrowserSuggestionsEngine(QObject* parent) : QObject(parent) From ea55b5407ea66541f435e193d7a8c4d4ec524b7b Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:22:47 +0100 Subject: [PATCH 14/15] bracket fix --- interface/src/webbrowser/WebBrowserSuggestionsEngine.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h index fa0f7f8e2e..0a94ea75c4 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h @@ -21,8 +21,7 @@ class QNetworkReply; -class WebBrowserSuggestionsEngine : public QObject -{ +class WebBrowserSuggestionsEngine : public QObject { Q_OBJECT public: From aa9ae6c8381829f598d8b8231d286fa756022564 Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:29:44 +0100 Subject: [PATCH 15/15] License header fix --- .../WebBrowserSuggestionsEngine.cpp | 4 ++-- .../webbrowser/WebBrowserSuggestionsEngine.h | 21 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp index a21badb0a8..4e7b135cdf 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp @@ -1,8 +1,8 @@ // -// webbrowsersuggestionsengine.cpp +// WebBrowserSuggestionsEngine.cpp // interface/src/webbrowser // -// Created by Vlad Stelmahovsky Kapolka on 30/10/17. +// Created by Vlad Stelmahovsky on 30/10/17. // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h index 0a94ea75c4..976631f109 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h @@ -1,19 +1,20 @@ +// +// WebBrowserSuggestionsEngine.h +// interface/src/webbrowser +// +// Created by Vlad Stelmahovsky on 30/10/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + #ifndef WEBBROWSERSUGGESTIONSENGINE_H #define WEBBROWSERSUGGESTIONSENGINE_H #include #include #include -// -// webbrowsersuggestionsengine.h -// interface/src/webbrowser -// -// Created by Vlad Stelmahovsky Kapolka on 30/10/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// #include #include