From 55eed376980dd5f8e485887176f9e5911bab4abb Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 29 Aug 2017 20:47:09 +0200 Subject: [PATCH 01/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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/65] 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 6ba2a83bbe0c94aa5efad2f7b25c5b6118e81666 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sun, 29 Oct 2017 10:47:56 -0700 Subject: [PATCH 11/65] Send and receive Avatar MASTER Gain packets, signaled using null nodeID. Packet protocol change is not needed (existing audio-mixer will ignore). --- .../src/audio/AudioMixerClientData.cpp | 10 ++++++++-- libraries/networking/src/NodeList.cpp | 13 +++++++++---- .../script-engine/src/UsersScriptingInterface.h | 4 ++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 9bba9c7f30..59bc878cba 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -189,8 +189,14 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const uint8_t packedGain; message.readPrimitive(&packedGain); float gain = unpackFloatGainFromByte(packedGain); - hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain); - qDebug() << "Setting gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain; + + if (avatarUuid.isNull()) { + // FIXME: change master gain, and reset hrtf gains for all active streams + qDebug() << "Setting MASTER avatar gain for [" << uuid << "] to " << gain; + } else { + hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain); + qDebug() << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain; + } } void AudioMixerClientData::parseNodeIgnoreRequest(QSharedPointer message, const SharedNodePointer& node) { diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 71128c5ff0..04699d8ad1 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -979,8 +979,8 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) { } void NodeList::setAvatarGain(const QUuid& nodeID, float gain) { - // cannot set gain of yourself or nobody - if (!nodeID.isNull() && _sessionUUID != nodeID) { + // cannot set gain of yourself + if (_sessionUUID != nodeID) { auto audioMixer = soloNodeOfType(NodeType::AudioMixer); if (audioMixer) { // setup the packet @@ -988,10 +988,15 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) { // write the node ID to the packet setAvatarGainPacket->write(nodeID.toRfc4122()); + // We need to convert the gain in dB (from the script) to an amplitude before packing it. setAvatarGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.0206f))); - qCDebug(networking) << "Sending Set Avatar Gain packet UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain; + if (nodeID.isNull()) { + qCDebug(networking) << "Sending Set Avatar MASTER Gain packet with Gain:" << gain; + } else { + qCDebug(networking) << "Sending Set Avatar Gain packet with UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain; + } sendPacket(std::move(setAvatarGainPacket), *audioMixer); QWriteLocker{ &_avatarGainMapLock }; @@ -1001,7 +1006,7 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) { qWarning() << "Couldn't find audio mixer to send set gain request"; } } else { - qWarning() << "NodeList::setAvatarGain called with an invalid ID or an ID which matches the current session ID:" << nodeID; + qWarning() << "NodeList::setAvatarGain called with an ID which matches the current session ID:" << nodeID; } } diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index acaa92d9c8..2d33bbca14 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -65,7 +65,7 @@ public slots: * Sets an avatar's gain for you and you only. * Units are Decibels (dB) * @function Users.setAvatarGain - * @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify. + * @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify, or null to set the master gain. * @param {float} gain The gain of the avatar you'd like to set. Units are dB. */ void setAvatarGain(const QUuid& nodeID, float gain); @@ -73,7 +73,7 @@ public slots: /**jsdoc * Gets an avatar's gain for you and you only. * @function Users.getAvatarGain - * @param {nodeID} nodeID The node or session ID of the user whose gain you want to get. + * @param {nodeID} nodeID The node or session ID of the user whose gain you want to get, or null to get the master gain. * @return {float} gain (in dB) */ float getAvatarGain(const QUuid& nodeID); From a99b1ffa230ef426010dc5278c0db0cf2cdaebb8 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Sun, 29 Oct 2017 18:01:43 -0700 Subject: [PATCH 12/65] HRTF cleanup --- libraries/audio/src/AudioHRTF.cpp | 4 ++-- libraries/audio/src/avx512/AudioHRTF_avx512.cpp | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/libraries/audio/src/AudioHRTF.cpp b/libraries/audio/src/AudioHRTF.cpp index 1d5b074db7..f2f0235ccb 100644 --- a/libraries/audio/src/AudioHRTF.cpp +++ b/libraries/audio/src/AudioHRTF.cpp @@ -673,8 +673,8 @@ static void crossfade_4x2(float* src, float* dst, const float* win, int numFrame // linear interpolation with gain static void interpolate(float* dst, const float* src0, const float* src1, float frac, float gain) { - float f0 = HRTF_GAIN * gain * (1.0f - frac); - float f1 = HRTF_GAIN * gain * frac; + float f0 = gain * (1.0f - frac); + float f1 = gain * frac; for (int k = 0; k < HRTF_TAPS; k++) { dst[k] = f0 * src0[k] + f1 * src1[k]; diff --git a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp index 682f5f2f77..a8bb62be35 100644 --- a/libraries/audio/src/avx512/AudioHRTF_avx512.cpp +++ b/libraries/audio/src/avx512/AudioHRTF_avx512.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#if defined(__AVX512F__) +#ifdef __AVX512F__ #include #include @@ -87,15 +87,4 @@ void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* ds _mm256_zeroupper(); } -// FIXME: this fallback can be removed, once we require VS2017 -#elif defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) - -#include "../AudioHRTF.h" - -void FIR_1x4_AVX2(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames); - -void FIR_1x4_AVX512(float* src, float* dst0, float* dst1, float* dst2, float* dst3, float coef[4][HRTF_TAPS], int numFrames) { - FIR_1x4_AVX2(src, dst0, dst1, dst2, dst3, coef, numFrames); -} - #endif From 586dde7dd70e0846b2b6644a8abb0275f7503683 Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 18:15:42 +0100 Subject: [PATCH 13/65] 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 3a2780bdf689af2540fc01e0dc756512a967b0d6 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Mon, 30 Oct 2017 10:42:26 -0700 Subject: [PATCH 14/65] Enable gain slider in PAL menu to control Avatar MASTER Gain --- interface/resources/qml/hifi/NameCard.qml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index fcfff02b72..0adcde3ad9 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -441,7 +441,7 @@ Item { Rectangle { id: nameCardVUMeter // Size - width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width); + width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width); height: 8 // Anchors anchors.bottom: isMyCard ? avatarImage.bottom : parent.bottom; @@ -525,16 +525,14 @@ Item { anchors.verticalCenter: nameCardVUMeter.verticalCenter; anchors.left: nameCardVUMeter.left; // Properties - visible: !isMyCard && selected && pal.activeTab == "nearbyTab" && isPresent; + visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent; value: Users.getAvatarGain(uuid) minimumValue: -60.0 maximumValue: 20.0 stepSize: 5 updateValueWhileDragging: true onValueChanged: { - if (uuid !== "") { - updateGainFromQML(uuid, value, false); - } + updateGainFromQML(uuid, value, false); } onPressedChanged: { if (!pressed) { @@ -574,7 +572,19 @@ Item { implicitHeight: 16 } } - } + RalewayRegular { + // The slider for my card is special, it controls the master gain + id: gainSliderText; + visible: isMyCard; + text: "master gain"; + size: hifi.fontSizes.tabularData; + anchors.left: parent.right; + anchors.leftMargin: 8; + color: hifi.colors.baseGrayHighlight; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignTop; + } + } function updateGainFromQML(avatarUuid, sliderValue, isReleased) { Users.setAvatarGain(avatarUuid, sliderValue); From d9c6f17e42b3dc4d23e0bbf81594665f68b8a83d Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:06:17 +0100 Subject: [PATCH 15/65] 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 16/65] 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 17/65] 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 18/65] 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 From cfba6ae819cbcdb64dcbd5583dbe0334ed09e6c6 Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Tue, 31 Oct 2017 11:18:03 -0700 Subject: [PATCH 19/65] Implement master avatar gain in the audio-mixer --- .../src/audio/AudioMixerClientData.cpp | 6 ++++-- .../src/audio/AudioMixerClientData.h | 5 +++++ assignment-client/src/audio/AudioMixerSlave.cpp | 15 ++++++++++----- libraries/networking/src/NodeList.cpp | 4 ++-- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 59bc878cba..3b3d6549ee 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -191,9 +191,11 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const float gain = unpackFloatGainFromByte(packedGain); if (avatarUuid.isNull()) { - // FIXME: change master gain, and reset hrtf gains for all active streams - qDebug() << "Setting MASTER avatar gain for [" << uuid << "] to " << gain; + // set the MASTER avatar gain + setMasterAvatarGain(gain); + qDebug() << "Setting MASTER avatar gain for " << uuid << " to " << gain; } else { + // set the per-source avatar gain hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain); qDebug() << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain; } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 7a8690d8cc..c3a31715ea 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -83,6 +83,9 @@ public: // uses randomization to have the AudioMixer send a stats packet to this node around every second bool shouldSendStats(int frameNumber); + float getMasterAvatarGain() const { return _masterAvatarGain; } + void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; } + AudioLimiter audioLimiter; void setupCodec(CodecPluginPointer codec, const QString& codecName); @@ -175,6 +178,8 @@ private: int _frameToSendStats { 0 }; + float _masterAvatarGain { 1.0f }; // per-listener mixing gain, applied only to avatars + CodecPluginPointer _codec; QString _selectedCodecName; Encoder* _encoder{ nullptr }; // for outbound mixed stream diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index a131e266d2..86e6d21d66 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -48,8 +48,8 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData& // mix helpers inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition); -inline float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, - const glm::vec3& relativePosition, bool isEcho); +inline float computeGain(AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream, + const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho); inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition); @@ -266,7 +266,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition(); float distance = glm::max(glm::length(relativePosition), EPSILON); - float gain = computeGain(listeningNodeStream, streamToAdd, relativePosition, isEcho); + float gain = computeGain(listenerNodeData, listeningNodeStream, streamToAdd, relativePosition, isEcho); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); const int HRTF_DATASET_INDEX = 1; @@ -484,10 +484,12 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi // when throttling, as close streams are expected to be heard by a user float distance = glm::length(relativePosition); return gain / distance; + + // avatar: skip master gain - it is constant for all streams } -float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd, - const glm::vec3& relativePosition, bool isEcho) { +float computeGain(AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream, + const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho) { float gain = 1.0f; // injector: apply attenuation @@ -507,6 +509,9 @@ float computeGain(const AvatarAudioStream& listeningNodeStream, const Positional float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + (angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO)); gain *= offAxisCoefficient; + + // apply master gain, only to avatars + gain *= listenerNodeData.getMasterAvatarGain(); } auto& audioZones = AudioMixer::getAudioZones(); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 04699d8ad1..63ec460de8 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -990,10 +990,10 @@ void NodeList::setAvatarGain(const QUuid& nodeID, float gain) { setAvatarGainPacket->write(nodeID.toRfc4122()); // We need to convert the gain in dB (from the script) to an amplitude before packing it. - setAvatarGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.0206f))); + setAvatarGainPacket->writePrimitive(packFloatGainToByte(fastExp2f(gain / 6.02059991f))); if (nodeID.isNull()) { - qCDebug(networking) << "Sending Set Avatar MASTER Gain packet with Gain:" << gain; + qCDebug(networking) << "Sending Set MASTER Avatar Gain packet with Gain:" << gain; } else { qCDebug(networking) << "Sending Set Avatar Gain packet with UUID: " << uuidStringWithoutCurlyBraces(nodeID) << "Gain:" << gain; } From 68e9ec9b23862e00295c4dbe084a09cd1a44449d Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Thu, 2 Nov 2017 16:36:45 -0700 Subject: [PATCH 20/65] Use qCDebug instead of qDebug in audio-mixer --- assignment-client/src/audio/AudioMixer.cpp | 34 +++++++++---------- .../src/audio/AudioMixerClientData.cpp | 17 +++++----- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 9ed6c7fdbc..7f088d8183 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -29,6 +29,7 @@ #include #include +#include "AudioLogging.h" #include "AudioHelpers.h" #include "AudioRingBuffer.h" #include "AudioMixerClientData.h" @@ -130,7 +131,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer mess PacketType rewrittenType = PacketTypeEnum::getReplicatedPacketMapping().key(message->getType()); if (rewrittenType == PacketType::Unknown) { - qDebug() << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING"; + qCDebug(audio) << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING"; } auto replicatedMessage = QSharedPointer::create(audioData, rewrittenType, @@ -345,7 +346,7 @@ void AudioMixer::sendStatsPacket() { void AudioMixer::run() { - qDebug() << "Waiting for connection to domain to request settings from domain-server."; + qCDebug(audio) << "Waiting for connection to domain to request settings from domain-server."; // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -502,14 +503,14 @@ void AudioMixer::throttle(std::chrono::microseconds duration, int frame) { int proportionalTerm = 1 + (_trailingMixRatio - TARGET) / 0.1f; _throttlingRatio += THROTTLE_RATE * proportionalTerm; _throttlingRatio = std::min(_throttlingRatio, 1.0f); - qDebug("audio-mixer is struggling (%f mix/sleep) - throttling %f of streams", - (double)_trailingMixRatio, (double)_throttlingRatio); + qCDebug(audio) << "audio-mixer is struggling (" << _trailingMixRatio << "mix/sleep) - throttling" + << _throttlingRatio << "of streams"; } else if (_throttlingRatio > 0.0f && _trailingMixRatio <= BACKOFF_TARGET) { int proportionalTerm = 1 + (TARGET - _trailingMixRatio) / 0.2f; _throttlingRatio -= BACKOFF_RATE * proportionalTerm; _throttlingRatio = std::max(_throttlingRatio, 0.0f); - qDebug("audio-mixer is recovering (%f mix/sleep) - throttling %f of streams", - (double)_trailingMixRatio, (double)_throttlingRatio); + qCDebug(audio) << "audio-mixer is recovering (" << _trailingMixRatio << "mix/sleep) - throttling" + << _throttlingRatio << "of streams"; } } } @@ -534,7 +535,7 @@ void AudioMixer::clearDomainSettings() { } void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { - qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled"); + qCDebug(audio) << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled"); if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) { QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject(); @@ -557,7 +558,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer"; bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool(); if (enableDynamicJitterBuffer) { - qDebug() << "Enabling dynamic jitter buffers."; + qCDebug(audio) << "Enabling dynamic jitter buffers."; bool ok; const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames"; @@ -565,9 +566,9 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { if (!ok) { _numStaticJitterFrames = InboundAudioStream::DEFAULT_STATIC_JITTER_FRAMES; } - qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames; + qCDebug(audio) << "Static desired jitter buffer frames:" << _numStaticJitterFrames; } else { - qDebug() << "Disabling dynamic jitter buffers."; + qCDebug(audio) << "Disabling dynamic jitter buffers."; _numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES; } @@ -621,7 +622,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) { QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString(); _codecPreferenceOrder = codecPreferenceOrder.split(","); - qDebug() << "Codec preference order changed to" << _codecPreferenceOrder; + qCDebug(audio) << "Codec preference order changed to" << _codecPreferenceOrder; } const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance"; @@ -630,7 +631,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { float attenuation = audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].toString().toFloat(&ok); if (ok) { _attenuationPerDoublingInDistance = attenuation; - qDebug() << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance; + qCDebug(audio) << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance; } } @@ -640,7 +641,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { float noiseMutingThreshold = audioEnvGroupObject[NOISE_MUTING_THRESHOLD].toString().toFloat(&ok); if (ok) { _noiseMutingThreshold = noiseMutingThreshold; - qDebug() << "Noise muting threshold changed to" << _noiseMutingThreshold; + qCDebug(audio) << "Noise muting threshold changed to" << _noiseMutingThreshold; } } @@ -680,8 +681,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin); AABox zoneAABox(corner, dimensions); _audioZones.insert(zone, zoneAABox); - qDebug() << "Added zone:" << zone << "(corner:" << corner - << ", dimensions:" << dimensions << ")"; + qCDebug(audio) << "Added zone:" << zone << "(corner:" << corner << ", dimensions:" << dimensions << ")"; } } } @@ -712,7 +712,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { _audioZones.contains(settings.source) && _audioZones.contains(settings.listener)) { _zoneSettings.push_back(settings); - qDebug() << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient; + qCDebug(audio) << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient; } } } @@ -745,7 +745,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) { _zoneReverbSettings.push_back(settings); - qDebug() << "Added Reverb:" << zone << reverbTime << wetLevel; + qCDebug(audio) << "Added Reverb:" << zone << reverbTime << wetLevel; } } } diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 3b3d6549ee..49453c6fc6 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -19,6 +19,7 @@ #include "InjectedAudioStream.h" +#include "AudioLogging.h" #include "AudioHelpers.h" #include "AudioMixer.h" #include "AudioMixerClientData.h" @@ -132,7 +133,7 @@ void AudioMixerClientData::optionallyReplicatePacket(ReceivedMessage& message, c if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) { mirroredType = message.getType(); } else { - qDebug() << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning"; + qCDebug(audio) << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning"; return; } } @@ -193,11 +194,11 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const if (avatarUuid.isNull()) { // set the MASTER avatar gain setMasterAvatarGain(gain); - qDebug() << "Setting MASTER avatar gain for " << uuid << " to " << gain; + qCDebug(audio) << "Setting MASTER avatar gain for " << uuid << " to " << gain; } else { // set the per-source avatar gain hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain); - qDebug() << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain; + qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain; } } @@ -284,7 +285,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames()); avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO); - qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName; + qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName; connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::handleMismatchAudioFormat); @@ -323,7 +324,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { #if INJECTORS_SUPPORT_CODECS injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO); - qDebug() << "creating new injectorStream... codec:" << _selectedCodecName; + qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName; #endif auto emplaced = _audioStreams.emplace( @@ -347,8 +348,8 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) { auto parseResult = matchingStream->parseData(message); if (matchingStream->getOverflowCount() > overflowBefore) { - qDebug() << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr(); - qDebug() << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio"); + qCDebug(audio) << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr(); + qCDebug(audio) << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio"); } return parseResult; @@ -697,7 +698,7 @@ void AudioMixerClientData::setupCodecForReplicatedAgent(QSharedPointerreadString(); if (codecString != _selectedCodecName) { - qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID()) + qCDebug(audio) << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID()) << "-" << codecString; const std::pair codec = AudioMixer::negotiateCodec({ codecString }); From addcb51ed98e09693f79dd6c8bcf3f063b1f58df Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 8 Nov 2017 07:50:35 -0800 Subject: [PATCH 21/65] Change slider label from "master gain" to "master volume" --- interface/resources/qml/hifi/NameCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 0adcde3ad9..7f78a29b2a 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -576,7 +576,7 @@ Item { // The slider for my card is special, it controls the master gain id: gainSliderText; visible: isMyCard; - text: "master gain"; + text: "master volume"; size: hifi.fontSizes.tabularData; anchors.left: parent.right; anchors.leftMargin: 8; From 393b55424fd5c64a4ff53af70891be8216b7b554 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 8 Nov 2017 11:37:22 -0800 Subject: [PATCH 22/65] Fix Create-new-domain-id not saving in DS settings --- domain-server/resources/web/settings/js/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 7f99b367a3..d13dde18ca 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -533,7 +533,7 @@ function createNewDomainID(label, justConnected) { $.post("/api/domains", domainJSON, function(data){ // we successfully created a domain ID, set it on that field - var domainID = data.domain_id; + var domainID = data.domain.id; console.log("Setting domain id to ", data, domainID); $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); From 3a9c364837d9797ca146a61be95e397e3c6f0bd1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 8 Nov 2017 13:09:42 -0800 Subject: [PATCH 23/65] Fix domain server label not handling spaces correctly --- libraries/embedded-webserver/src/HTTPConnection.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 0a8448108e..a61bc95f8b 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -65,7 +65,9 @@ QHash HTTPConnection::parseUrlEncodedForm() { QUrlQuery form { _requestContent }; QHash pairs; for (auto pair : form.queryItems()) { - pairs[QUrl::fromPercentEncoding(pair.first.toLatin1())] = QUrl::fromPercentEncoding(pair.second.toLatin1()); + auto key = QUrl::fromPercentEncoding(pair.first.toLatin1().replace('+', ' ')); + auto value = QUrl::fromPercentEncoding(pair.second.toLatin1().replace('+', ' ')); + pairs[key] = value; } return pairs; From c77942c542e539a6bf9b5c746327362d4be1cd15 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 8 Nov 2017 13:36:03 -0800 Subject: [PATCH 24/65] Fix domain settings not showing label when first setting a placename This occurs because previously we only created the input elements if you have a domain id when the page loads. Because you can now have a domain id assigned after the page initially loads, we need to always create the input. --- domain-server/resources/web/settings/js/settings.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index d13dde18ca..a0bb407236 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -629,9 +629,7 @@ function showOrHideLabel() { } function setupDomainLabelSetting() { - if (!showOrHideLabel()) { - return; - } + showOrHideLabel(); var html = "
" html += " Edit"; From a55a30f87febdacf4e53693f47db33ac1c6bbfcd Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 8 Nov 2017 14:00:43 -0800 Subject: [PATCH 25/65] Fix 'Path' label when adding a place name --- domain-server/resources/web/js/shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 66159209ea..bff79ce863 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -236,7 +236,7 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd if (forcePathTo === undefined || forcePathTo === null) { var path = "
"; - path += ""; + path += ""; path += ""; path += "
"; modal_body.append($(path)); From 1db8e2a0ec2a4f8ba9abb76636a48bedfac64b84 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 8 Nov 2017 14:01:04 -0800 Subject: [PATCH 26/65] Update create domain id language --- domain-server/resources/web/settings/js/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index a0bb407236..419b55685f 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -503,7 +503,7 @@ function showDomainCreationAlert(justConnected) { swal({ title: 'Create new domain ID', type: 'input', - text: 'Enter a short description for this machine.

This will help you identify which domain ID belongs to which machine.

', + text: 'Enter a label this machine.

This will help you identify which domain ID belongs to which machine.

', showCancelButton: true, confirmButtonText: "Create", closeOnConfirm: false, From 028410a26a5c60ce7a00c79865c36ff7ebf37011 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 8 Nov 2017 14:05:51 -0800 Subject: [PATCH 27/65] Fix alignment of Disconnect btn on ds settings --- domain-server/resources/web/css/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 8b004687b9..fb1e1aa9f5 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -302,6 +302,7 @@ table .headers + .headers td { } .account-connected-header { + vertical-align: middle; color: #6FCF97; font-size: 30px; margin-right: 20px; From 3b702a7ae342098453ac5a58189979da18a5e6f9 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 9 Nov 2017 09:05:07 -0800 Subject: [PATCH 28/65] Make ESC close dialog windows on DS settings --- domain-server/resources/web/js/shared.js | 3 ++- domain-server/resources/web/settings/js/settings.js | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index bff79ce863..b21d1a2f79 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -363,7 +363,8 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd title: Strings.ADD_PLACE_TITLE, message: modal_body, closeButton: false, - buttons: modal_buttons + buttons: modal_buttons, + onEscape: true }); } else { bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 419b55685f..c24e2879ef 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -652,6 +652,7 @@ function setupDomainLabelSetting() { title: 'Edit Label', message: modal_body, closeButton: false, + onEscape: true, buttons: [ { label: 'Cancel', @@ -775,6 +776,7 @@ function setupDomainNetworkingSettings() { title: 'Edit Network', message: modal_body, closeButton: false, + onEscape: true, buttons: [ { label: 'Cancel', @@ -922,6 +924,7 @@ function placeTableRow(name, path, isTemporary, placeID) { var dialog = bootbox.dialog({ message: confirmString, closeButton: false, + onEscape: true, buttons: [ { label: Strings.REMOVE_PLACE_CANCEL_BUTTON, @@ -1096,6 +1099,7 @@ function editHighFidelityPlace(placeID, name, path) { dialog = bootbox.dialog({ title: Strings.EDIT_PLACE_TITLE, closeButton: false, + onEscape: true, message: modal_body, buttons: modal_buttons }) @@ -1178,6 +1182,7 @@ function chooseFromHighFidelityDomains(clickedButton) { bootbox.dialog({ title: "Choose matching domain", + onEscape: true, message: modal_body, buttons: modal_buttons }) From e274df065d006f8af7f1f6bb58a3f20efb2252f7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 9 Nov 2017 09:25:16 -0800 Subject: [PATCH 29/65] Fix label nav always being shown in ds settings nav --- domain-server/resources/web/settings/js/settings.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index c24e2879ef..19d711ebd8 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -620,12 +620,10 @@ function parseJSONResponse(xhr) { function showOrHideLabel() { var type = getCurrentDomainIDType(); - if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) { - $(".panel#label").hide(); - return false; - } - $(".panel#label").show(); - return true; + var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN); + $(".panel#label").toggle(shouldShow); + $("li a[href='#label']").parent().toggle(shouldShow); + return shouldShow; } function setupDomainLabelSetting() { From d62c98c6e2a99224461e97d76ed08cf677e549d1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 9 Nov 2017 10:03:41 -0800 Subject: [PATCH 30/65] Update network address/port label in DS settings --- domain-server/resources/web/settings/js/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 19d711ebd8..8f2e076f6c 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -739,7 +739,7 @@ function setupDomainNetworkingSettings() { var includeAddress = autoNetworkingSetting === 'disabled'; if (includeAddress) { - var label = "Network Address and Port"; + var label = "Network Address:Port"; } else { var label = "Network Port"; } From c6947dd165691887ac64e0fae778be96af0d6b4e Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 9 Nov 2017 18:20:06 -0800 Subject: [PATCH 31/65] some side by side plumbing for NLPackets and NLPacketLists --- libraries/networking/src/NodeList.h | 3 ++ libraries/networking/src/PacketSender.cpp | 38 ++++++++++++---- libraries/networking/src/PacketSender.h | 5 ++- .../octree/src/OctreeEditPacketSender.cpp | 44 +++++++++++++++++-- libraries/octree/src/OctreeEditPacketSender.h | 5 ++- 5 files changed, 80 insertions(+), 15 deletions(-) diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index b3a12153e5..0ebc1f0b22 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -40,6 +40,9 @@ const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; +using PacketOrPacketList = std::pair, std::unique_ptr>; +using NodePacketOrPacketListPair = std::pair; + using NodePacketPair = std::pair>; using NodeSharedPacketPair = std::pair>; using NodeSharedReceivedMessagePair = std::pair>; diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 0cfd67cc4e..01b78585c2 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -53,7 +53,19 @@ void PacketSender::queuePacketForSending(const SharedNodePointer& destinationNod _totalBytesQueued += packet->getDataSize(); lock(); - _packets.push_back({destinationNode, std::move(packet)}); + _packets.push_back({destinationNode, PacketOrPacketList { std::move(packet), nullptr} }); + unlock(); + + // Make sure to wake our actual processing thread because we now have packets for it to process. + _hasPackets.wakeAll(); +} + +void PacketSender::queuePacketListForSending(const SharedNodePointer& destinationNode, std::unique_ptr packetList) { + _totalPacketsQueued += packetList->getNumPackets(); + _totalBytesQueued += packetList->getMessageSize(); + + lock(); + _packets.push_back({ destinationNode, PacketOrPacketList { nullptr, std::move(packetList)} }); unlock(); // Make sure to wake our actual processing thread because we now have packets for it to process. @@ -178,7 +190,7 @@ bool PacketSender::nonThreadedProcess() { float averagePacketsPerCall = 0; // might be less than 1, if our caller calls us more frequently than the target PPS - int packetsSentThisCall = 0; + size_t packetsSentThisCall = 0; int packetsToSendThisCall = 0; // Since we're in non-threaded mode, we need to determine how many packets to send per call to process @@ -265,23 +277,31 @@ bool PacketSender::nonThreadedProcess() { while ((packetsSentThisCall < packetsToSendThisCall) && (packetsLeft > 0)) { lock(); - NodePacketPair packetPair = std::move(_packets.front()); + NodePacketOrPacketListPair packetPair = std::move(_packets.front()); _packets.pop_front(); packetsLeft = _packets.size(); unlock(); // send the packet through the NodeList... - DependencyManager::get()->sendUnreliablePacket(*packetPair.second, *packetPair.first); + //PacketOrPacketList packetOrList = packetPair.second; + bool sendAsPacket = packetPair.second.first.get(); + if (sendAsPacket) { + DependencyManager::get()->sendUnreliablePacket(*packetPair.second.first, *packetPair.first); + } else { + DependencyManager::get()->sendPacketList(*packetPair.second.second, *packetPair.first); + } - packetsSentThisCall++; - _packetsOverCheckInterval++; - _totalPacketsSent++; + size_t packetSize = sendAsPacket ? packetPair.second.first->getDataSize() : packetPair.second.second->getMessageSize(); + size_t packetCount = sendAsPacket ? 1 : packetPair.second.second->getNumPackets(); + + packetsSentThisCall += packetCount; + _packetsOverCheckInterval += packetCount; + _totalPacketsSent += packetCount; - int packetSize = packetPair.second->getDataSize(); _totalBytesSent += packetSize; - emit packetSent(packetSize); + emit packetSent(packetSize); // FIXME should include number of packets? _lastSendTime = now; } diff --git a/libraries/networking/src/PacketSender.h b/libraries/networking/src/PacketSender.h index 68faeaca47..fead49df72 100644 --- a/libraries/networking/src/PacketSender.h +++ b/libraries/networking/src/PacketSender.h @@ -39,6 +39,7 @@ public: /// Add packet to outbound queue. void queuePacketForSending(const SharedNodePointer& destinationNode, std::unique_ptr packet); + void queuePacketListForSending(const SharedNodePointer& destinationNode, std::unique_ptr packetList); void setPacketsPerSecond(int packetsPerSecond); int getPacketsPerSecond() const { return _packetsPerSecond; } @@ -99,14 +100,14 @@ protected: SimpleMovingAverage _averageProcessCallTime; private: - std::list _packets; + std::list _packets; quint64 _lastSendTime; bool threadedProcess(); bool nonThreadedProcess(); quint64 _lastPPSCheck; - int _packetsOverCheckInterval; + size_t _packetsOverCheckInterval; quint64 _started; quint64 _totalPacketsSent; diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index f3c9ece9fe..7b612a828c 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -115,6 +115,27 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::uniqu }); } +// This method is called when the edit packet layer has determined that it has a fully formed packet destined for +// a known nodeID. +void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList) { + + bool wantDebug = false; + DependencyManager::get()->eachNode([&](const SharedNodePointer& node) { + // only send to the NodeTypes that are getMyNodeType() + if (node->getType() == getMyNodeType() + && ((node->getUUID() == nodeUUID) || (nodeUUID.isNull())) + && node->getActiveSocket()) { + + // NOTE: unlike packets, the packet lists don't get rewritten sequence numbers. + + // add packet to history -- we don't keep track of sent PacketLists + //_sentPacketHistories[nodeUUID].packetSent(sequence, *packet); + + queuePacketListForSending(node, std::move(packetList)); + } + }); +} + void OctreeEditPacketSender::processPreServerExistsPackets() { assert(serversExist()); // we should only be here if we have jurisdictions @@ -247,7 +268,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& }); } if (isMyJurisdiction) { - std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID]; + std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID].first; //only a NLPacket for now if (!bufferedPacket) { bufferedPacket = initializePacket(type, node->getClockSkewUsec()); @@ -291,15 +312,24 @@ void OctreeEditPacketSender::releaseQueuedMessages() { } else { _packetsQueueLock.lock(); for (auto& i : _pendingEditPackets) { - if (i.second) { + if (i.second.first) { // construct a null unique_ptr to an NL packet std::unique_ptr releasedPacket; // swap the null ptr with the packet we want to release - i.second.swap(releasedPacket); + i.second.first.swap(releasedPacket); // move and release the queued packet releaseQueuedPacket(i.first, std::move(releasedPacket)); + } else if (i.second.second) { + // construct a null unique_ptr to an NLPacketList + std::unique_ptr releasedPacketList; + + // swap the null ptr with the NLPacketList we want to release + i.second.second.swap(releasedPacketList); + + // move and release the queued NLPacketList + releaseQueuedPacketList(i.first, std::move(releasedPacketList)); } } @@ -315,6 +345,14 @@ void OctreeEditPacketSender::releaseQueuedPacket(const QUuid& nodeID, std::uniqu _releaseQueuedPacketMutex.unlock(); } +void OctreeEditPacketSender::releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr packetList) { + _releaseQueuedPacketMutex.lock(); + if (packetList->getMessageSize() > 0 && packetList->getType() != PacketType::Unknown) { + queuePacketListToNode(nodeID, std::move(packetList)); + } + _releaseQueuedPacketMutex.unlock(); +} + std::unique_ptr OctreeEditPacketSender::initializePacket(PacketType type, qint64 nodeClockSkew) { auto newPacket = NLPacket::create(type); diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index fd8cc85f91..79c363bec5 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -87,15 +87,18 @@ protected: bool _shouldSend; void queuePacketToNode(const QUuid& nodeID, std::unique_ptr packet); + void queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList); + void queuePendingPacketToNodes(std::unique_ptr packet); void queuePacketToNodes(std::unique_ptr packet); std::unique_ptr initializePacket(PacketType type, qint64 nodeClockSkew); void releaseQueuedPacket(const QUuid& nodeUUID, std::unique_ptr packetBuffer); // releases specific queued packet + void releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr packetList); void processPreServerExistsPackets(); // These are packets which are destined from know servers but haven't been released because they're still too small - std::unordered_map> _pendingEditPackets; + std::unordered_map _pendingEditPackets; // These are packets that are waiting to be processed because we don't yet know if there are servers int _maxPendingMessages; From 908faa13345797dd37102cf324f3aade3ba9f459 Mon Sep 17 00:00:00 2001 From: beholder Date: Fri, 10 Nov 2017 19:38:07 +0300 Subject: [PATCH 32/65] refactoring: simplify code & fix the issue with menu-es sometimes losing checked state --- interface/resources/qml/desktop/Desktop.qml | 24 +++++++++++-------- .../qml/hifi/tablet/TabletMenuStack.qml | 23 ------------------ 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 847111b8a0..e7c68b2a47 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -51,19 +51,23 @@ FocusScope { // The VR version of the primary menu property var rootMenu: Menu { + id: rootMenuId objectName: "rootMenu" - // for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot - property var exclusionGroupsByMenuItem : ListModel {} + property var exclusionGroups: ({}); + property Component exclusiveGroupMaker: Component { + ExclusiveGroup { + } + } - function addExclusionGroup(menuItem, exclusionGroup) - { - exclusionGroupsByMenuItem.append( - { - 'menuItem' : menuItem.toString(), - 'exclusionGroup' : exclusionGroup.toString() - } - ); + function addExclusionGroup(qmlAction, exclusionGroup) { + + var exclusionGroupId = exclusionGroup.toString(); + if(!exclusionGroups[exclusionGroupId]) { + exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId); + } + + qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId] } } diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index ce4fac3bd5..8cd696a41b 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -66,7 +66,6 @@ Item { function toModel(items, newMenu) { var result = modelMaker.createObject(tabletMenu); - var exclusionGroups = {}; for (var i = 0; i < items.length; ++i) { var item = items[i]; @@ -78,28 +77,6 @@ Item { if (item.text !== "Users Online") { result.append({"name": item.text, "item": item}) } - - for(var j = 0; j < tabletMenu.rootMenu.exclusionGroupsByMenuItem.count; ++j) - { - var entry = tabletMenu.rootMenu.exclusionGroupsByMenuItem.get(j); - if(entry.menuItem == item.toString()) - { - var exclusionGroupId = entry.exclusionGroup; - console.debug('item exclusionGroupId: ', exclusionGroupId) - - if(!exclusionGroups[exclusionGroupId]) - { - exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(newMenu); - console.debug('new exclusion group created: ', exclusionGroups[exclusionGroupId]) - } - - var exclusionGroup = exclusionGroups[exclusionGroupId]; - - item.exclusiveGroup = exclusionGroup - console.debug('item.exclusiveGroup: ', item.exclusiveGroup) - } - } - break; case MenuItemType.Separator: result.append({"name": "", "item": item}) From 87519cd26f0174ce273fc63ee40076bb02132a22 Mon Sep 17 00:00:00 2001 From: beholder Date: Fri, 10 Nov 2017 19:39:51 +0300 Subject: [PATCH 33/65] use conditional bindings to avoid intermediate binding states --- .../qml/hifi/tablet/TabletMenuItem.qml | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 11d3cab35e..520841b33f 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -40,37 +40,29 @@ Item { CheckBox { id: checkbox - // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 visible: source !== null ? source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup : false - checked: setChecked() - function setChecked() { - if (!source || source.type !== 1 || !source.checkable) { - return false; - } - // FIXME this works for native QML menus but I don't think it will - // for proxied QML menus - return source.checked; + + Binding on checked { + value: source.checked; + when: source && source.type === 1 && source.checkable && !source.exclusiveGroup; } } RadioButton { id: radiobutton - // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 visible: source !== null ? source.visible && source.type === 1 && source.checkable && source.exclusiveGroup : false - checked: setChecked() - function setChecked() { - if (!source || source.type !== 1 || !source.checkable) { - return false; - } - // FIXME this works for native QML menus but I don't think it will - // for proxied QML menus - return source.checked; + + Binding on checked { + value: source.checked; + when: source && source.type === 1 && source.checkable && source.exclusiveGroup; } } } From da49ee8bee498b9d1d9280afbc6ccb325d566d40 Mon Sep 17 00:00:00 2001 From: beholder Date: Fri, 10 Nov 2017 19:42:10 +0300 Subject: [PATCH 34/65] 9032 Current display mode can be deselected in the toolbar/tablet menu --- interface/src/Application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b21588958e..e35407038b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7082,6 +7082,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const { return _displayPlugin; } +static const char* exclusionGroupKey = "exclusionGroup"; static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); @@ -7117,6 +7118,8 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti action->setCheckable(true); action->setChecked(active); displayPluginGroup->addAction(action); + + action->setProperty(exclusionGroupKey, QVariant::fromValue(displayPluginGroup)); Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name)); } From 03364c1893a7113574c532a8064460391d29462f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 10 Nov 2017 15:34:22 -0800 Subject: [PATCH 35/65] Cleanup DS settings for small screens --- domain-server/resources/web/css/style.css | 22 ++++++++++++++----- .../resources/web/settings/index.shtml | 5 ----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index fb1e1aa9f5..17dcc06ccb 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -80,11 +80,23 @@ span.port { display: none; } -#setup-sidebar.affix { - /* This overrides a case where going to the bottom of the page, - * then scrolling up, causes `position: relative` to be added to the style - */ - position: fixed !important; +@media (min-width: 768px) { + #setup-sidebar.affix { + /* This overrides a case where going to the bottom of the page, + * then scrolling up, causes `position: relative` to be added to the style + */ + position: fixed !important; + } +} + +@media (max-width: 767px) { + #setup-sidebar.affix { + position: static !important; + } + + #setup-sidebar { + margin-bottom: 20px; + } } #setup-sidebar button { diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index 5a8184db30..d36330375a 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -36,11 +36,6 @@
- - -