diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 61bed8d9b1..b6462b7fc3 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -19,3 +19,4 @@ Script.load("users.js"); Script.load("grab.js"); Script.load("pointer.js"); Script.load("directory.js"); +Script.load("dialTone.js"); diff --git a/examples/dialTone.js b/examples/dialTone.js index b55e654f25..eb1fc5ec1c 100644 --- a/examples/dialTone.js +++ b/examples/dialTone.js @@ -11,19 +11,26 @@ // // setup the local sound we're going to use -var connectSound = SoundCache.getSound("file://" + Paths.resources + "sounds/hello.wav"); -var disconnectSound = SoundCache.getSound("file://" + Paths.resources + "sounds/goodbye.wav"); +var connectSound = SoundCache.getSound("file:///" + Paths.resources + "sounds/hello.wav"); +var disconnectSound = SoundCache.getSound("file:///" + Paths.resources + "sounds/goodbye.wav"); +var micMutedSound = SoundCache.getSound("file:///" + Paths.resources + "sounds/goodbye.wav"); // setup the options needed for that sound -var connectSoundOptions = { +var soundOptions = { localOnly: true }; // play the sound locally once we get the first audio packet from a mixer Audio.receivedFirstPacket.connect(function(){ - Audio.playSound(connectSound, connectSoundOptions); + Audio.playSound(connectSound, soundOptions); }); Audio.disconnected.connect(function(){ - Audio.playSound(disconnectSound, connectSoundOptions); + Audio.playSound(disconnectSound, soundOptions); +}); + +AudioDevice.muteToggled.connect(function () { + if (AudioDevice.getMuted()) { + Audio.playSound(micMutedSound, soundOptions); + } }); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 30dab3644a..00f6d4c3b2 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -140,7 +140,7 @@ target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) # link required hifi libraries link_hifi_libraries(shared octree environment gpu model render fbx networking entities avatars audio audio-client animation script-engine physics - render-utils entities-renderer ui) + render-utils entities-renderer ui auto-updater) add_dependency_external_projects(sdl2) diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml new file mode 100644 index 0000000000..e5216ff619 --- /dev/null +++ b/interface/resources/qml/UpdateDialog.qml @@ -0,0 +1,172 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls.Styles 1.3 +import QtGraphicalEffects 1.0 +import "controls" +import "styles" + +DialogContainer { + HifiConstants { id: hifi } + id: root + objectName: "UpdateDialog" + implicitWidth: updateDialog.width + implicitHeight: updateDialog.height + x: parent ? parent.width / 2 - width / 2 : 0 + y: parent ? parent.height / 2 - height / 2 : 0 + + UpdateDialog { + id: updateDialog + + implicitWidth: backgroundRectangle.width + implicitHeight: backgroundRectangle.height + + readonly property int inputWidth: 500 + readonly property int inputHeight: 60 + readonly property int borderWidth: 30 + readonly property int closeMargin: 16 + readonly property int inputSpacing: 16 + readonly property int buttonWidth: 150 + readonly property int buttonHeight: 50 + readonly property int buttonRadius: 15 + + signal triggerBuildDownload + signal closeUpdateDialog + + Column { + id: mainContent + width: updateDialog.inputWidth + spacing: updateDialog.inputSpacing + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + + Rectangle { + id: backgroundRectangle + color: "#2c86b1" + opacity: 0.85 + radius: updateDialog.closeMargin * 2 + + width: updateDialog.inputWidth + updateDialog.borderWidth * 2 + height: updateDialog.inputHeight * 6 + updateDialog.closeMargin * 2 + + Rectangle { + id: dialogTitle + width: updateDialog.inputWidth + height: updateDialog.inputHeight + radius: height / 2 + color: "#ebebeb" + + anchors { + top: parent.top + topMargin: updateDialog.inputSpacing + horizontalCenter: parent.horizontalCenter + } + + Text { + id: updateAvailableText + text: "Update Available" + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: updateDialog.inputSpacing + } + } + + Text { + text: updateDialog.updateAvailableDetails + font.pixelSize: 14 + color: hifi.colors.text + anchors { + verticalCenter: parent.verticalCenter + left: updateAvailableText.right + leftMargin: 13 + } + } + } + + Flickable { + id: scrollArea + anchors { + top: dialogTitle.bottom + } + contentWidth: updateDialog.inputWidth + contentHeight: backgroundRectangle.height - (dialogTitle.height * 2.5) + width: updateDialog.inputWidth + height: backgroundRectangle.height - (dialogTitle.height * 2.5) + flickableDirection: Flickable.VerticalFlick + clip: true + + TextEdit { + id: releaseNotes + wrapMode: TextEdit.Wrap + width: parent.width + readOnly: true + text: updateDialog.releaseNotes + font.pixelSize: 14 + color: hifi.colors.text + anchors { + left: parent.left + leftMargin: updateDialog.borderWidth + } + } + } + + Rectangle { + id: downloadButton + width: updateDialog.buttonWidth + height: updateDialog.buttonHeight + radius: updateDialog.buttonRadius + color: "green" + anchors { + top: scrollArea.bottom + topMargin: 10 + right: backgroundRectangle.right + rightMargin: 15 + } + Text { + text: "Upgrade" + anchors { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + } + MouseArea { + id: downloadButtonAction + anchors.fill: parent + onClicked: updateDialog.triggerUpgrade() + cursorShape: "PointingHandCursor" + } + } + + Rectangle { + id: cancelButton + width: updateDialog.buttonWidth + height: updateDialog.buttonHeight + radius: updateDialog.buttonRadius + color: "red" + anchors { + top: scrollArea.bottom + topMargin: 10 + right: downloadButton.left + rightMargin: 15 + } + + Text { + text: "Cancel" + anchors { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + } + MouseArea { + id: cancelButtonAction + anchors.fill: parent + onClicked: updateDialog.closeDialog() + cursorShape: "PointingHandCursor" + } + } + } + } + } +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2aa718a27c..52aa9e97b3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -148,6 +149,7 @@ #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" #include "ui/AddressBarDialog.h" +#include "ui/UpdateDialog.h" // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU @@ -297,6 +299,7 @@ bool setupEssentials(int& argc, char** argv) { auto discoverabilityManager = DependencyManager::set(); auto sceneScriptingInterface = DependencyManager::set(); auto offscreenUi = DependencyManager::set(); + auto autoUpdater = DependencyManager::set(); auto pathUtils = DependencyManager::set(); auto actionFactory = DependencyManager::set(); @@ -558,8 +561,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // allow you to move an entity around in your hand _entityEditSender.setPacketsPerSecond(3000); // super high!! - checkVersion(); - _overlays.init(); // do this before scripts load _runningScriptsWidget->setRunningScripts(getRunningScripts()); @@ -631,9 +632,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : ddeTracker->init(); connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #endif + + auto applicationUpdater = DependencyManager::get(); + connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); + applicationUpdater->checkForUpdate(); } - void Application::aboutToQuit() { emit beforeAboutToQuit(); @@ -824,6 +828,7 @@ void Application::initializeUi() { MessageDialog::registerType(); VrMenu::registerType(); Tooltip::registerType(); + UpdateDialog::registerType(); auto offscreenUi = DependencyManager::get(); offscreenUi->create(_glWidget->context()->contextHandle()); @@ -4511,72 +4516,6 @@ void Application::toggleLogDialog() { } } -void Application::checkVersion() { - QNetworkRequest latestVersionRequest((QUrl(CHECK_VERSION_URL))); - latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - latestVersionRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - QNetworkReply* reply = NetworkAccessManager::getInstance().get(latestVersionRequest); - connect(reply, SIGNAL(finished()), SLOT(parseVersionXml())); -} - -void Application::parseVersionXml() { - - #ifdef Q_OS_WIN32 - QString operatingSystem("win"); - #endif - - #ifdef Q_OS_MAC - QString operatingSystem("mac"); - #endif - - #ifdef Q_OS_LINUX - QString operatingSystem("ubuntu"); - #endif - - QString latestVersion; - QUrl downloadUrl; - QString releaseNotes("Unavailable"); - QNetworkReply* sender = qobject_cast(QObject::sender()); - - QXmlStreamReader xml(sender); - - while (!xml.atEnd() && !xml.hasError()) { - if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == operatingSystem) { - while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == operatingSystem)) { - if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "version") { - xml.readNext(); - latestVersion = xml.text().toString(); - } - if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name().toString() == "url") { - xml.readNext(); - downloadUrl = QUrl(xml.text().toString()); - } - xml.readNext(); - } - } - xml.readNext(); - } - - if (!shouldSkipVersion(latestVersion) && applicationVersion() != latestVersion) { - new UpdateDialog(_glWidget, releaseNotes, latestVersion, downloadUrl); - } - sender->deleteLater(); -} - -bool Application::shouldSkipVersion(QString latestVersion) { - QFile skipFile(SKIP_FILENAME); - skipFile.open(QIODevice::ReadWrite); - QString skipVersion(skipFile.readAll()); - return (skipVersion == latestVersion || applicationVersion() == "dev"); -} - -void Application::skipVersion(QString latestVersion) { - QFile skipFile(SKIP_FILENAME); - skipFile.open(QIODevice::WriteOnly | QIODevice::Truncate); - skipFile.seek(0); - skipFile.write(latestVersion.toStdString().c_str()); -} - void Application::takeSnapshot() { QMediaPlayer* player = new QMediaPlayer(); QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav"); diff --git a/interface/src/Application.h b/interface/src/Application.h index 6f30bbf560..1873ee4b80 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -66,7 +66,6 @@ #include "ui/SnapshotShareDialog.h" #include "ui/LodToolsDialog.h" #include "ui/LogDialog.h" -#include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" #include "ui/ApplicationOverlay.h" #include "ui/ApplicationCompositor.h" @@ -312,8 +311,6 @@ public: NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } - void skipVersion(QString latestVersion); - QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; } @@ -463,8 +460,6 @@ private slots: void restoreMirrorView(); void shrinkMirrorView(); - void parseVersionXml(); - void manageRunningScriptsWidgetVisibility(bool shown); void runTests(); @@ -619,9 +614,6 @@ private: FileLogger* _logger; - void checkVersion(); - void displayUpdateDialog(); - bool shouldSkipVersion(QString latestVersion); void takeSnapshot(); TouchEvent _lastTouchEvent; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6242318170..216f733709 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -90,6 +90,30 @@ Menu::Menu() { addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, qApp, SLOT(toggleRunningScriptsWidget())); + auto addressManager = DependencyManager::get(); + + addDisabledActionAndSeparator(fileMenu, "History"); + + QAction* backAction = addActionToQMenuAndActionHash(fileMenu, + MenuOption::Back, + 0, + addressManager.data(), + SLOT(goBack())); + + QAction* forwardAction = addActionToQMenuAndActionHash(fileMenu, + MenuOption::Forward, + 0, + addressManager.data(), + SLOT(goForward())); + + // connect to the AddressManager signal to enable and disable the back and forward menu items + connect(addressManager.data(), &AddressManager::goBackPossible, backAction, &QAction::setEnabled); + connect(addressManager.data(), &AddressManager::goForwardPossible, forwardAction, &QAction::setEnabled); + + // set the two actions to start disabled since the stacks are clear on startup + backAction->setDisabled(true); + forwardAction->setDisabled(true); + addDisabledActionAndSeparator(fileMenu, "Location"); qApp->getBookmarks()->setupMenus(this, fileMenu); @@ -98,7 +122,6 @@ Menu::Menu() { Qt::CTRL | Qt::Key_L, dialogsManager.data(), SLOT(toggleAddressBar())); - auto addressManager = DependencyManager::get(); addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyAddress, 0, addressManager.data(), SLOT(copyAddress())); addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyPath, 0, @@ -407,7 +430,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, Qt::CTRL | Qt::SHIFT | Qt::Key_F, true, // DDE face tracking is on by default qApp, SLOT(toggleFaceTrackerMute())); - addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, true); + addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, false); #endif auto avatarManager = DependencyManager::get(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 6107744abc..6b892ebc3c 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -149,6 +149,7 @@ namespace MenuOption { const QString AudioStatsShowInjectedStreams = "Audio Stats Show Injected Streams"; const QString AutoMuteAudio = "Auto Mute Microphone"; const QString AvatarReceiveStats = "Show Receive Stats"; + const QString Back = "Back"; const QString BandwidthDetails = "Bandwidth Details"; const QString BinaryEyelidControl = "Binary Eyelid Control"; const QString BlueSpeechSphere = "Blue Sphere While Speaking"; @@ -194,12 +195,12 @@ namespace MenuOption { const QString Faceshift = "Faceshift"; const QString FilterSixense = "Smooth Sixense Movement"; const QString FirstPerson = "First Person"; + const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; const QString Fullscreen = "Fullscreen"; const QString FullscreenMirror = "Fullscreen Mirror"; const QString GlowWhenSpeaking = "Glow When Speaking"; const QString NamesAboveHeads = "Names Above Heads"; - const QString GoToUser = "Go To User"; const QString HMDTools = "HMD Tools"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString KeyboardMotorControl = "Enable Keyboard Motor Control"; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 1170e3c3a6..81c7cd2770 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -31,6 +31,7 @@ #include "OctreeStatsDialog.h" #include "PreferencesDialog.h" #include "ScriptEditorWindow.h" +#include "UpdateDialog.h" void DialogsManager::toggleAddressBar() { @@ -50,6 +51,10 @@ void DialogsManager::showLoginDialog() { LoginDialog::show(); } +void DialogsManager::showUpdateDialog() { + UpdateDialog::show(); +} + void DialogsManager::octreeStatsDetails() { if (!_octreeStatsDialog) { _octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats()); diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index fc2dad072b..7eb716b73c 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -35,6 +35,7 @@ class ScriptEditorWindow; class QMessageBox; class AvatarAppearanceDialog; class DomainConnectionDialog; +class UpdateDialog; class DialogsManager : public QObject, public Dependency { Q_OBJECT @@ -64,6 +65,9 @@ public slots: void showIRCLink(); void changeAvatarAppearance(); void showDomainConnectionDialog(); + + // Application Update + void showUpdateDialog(); private slots: void toggleToolWindow(); @@ -101,6 +105,7 @@ private: QPointer _scriptEditor; QPointer _avatarAppearanceDialog; QPointer _domainConnectionDialog; + QPointer _updateDialog; }; #endif // hifi_DialogsManager_h diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 40b32d66e3..69dfc343d9 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -2,54 +2,50 @@ // UpdateDialog.cpp // interface/src/ui // -// Copyright 2014 High Fidelity, Inc. +// Created by Leonardo Murillo on 6/3/15. +// Copyright 2015 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 "ui_updateDialog.h" - -#include "Application.h" #include "UpdateDialog.h" +#include -UpdateDialog::UpdateDialog(QWidget *parent, const QString& releaseNotes, const QString& latestVersion, const QUrl& downloadURL) : - QDialog(parent), - _latestVersion(latestVersion), - _downloadUrl(downloadURL) +#include "DependencyManager.h" + +HIFI_QML_DEF(UpdateDialog) + +UpdateDialog::UpdateDialog(QQuickItem* parent) : + OffscreenQmlDialog(parent) { - Ui::Dialog dialogUI; - dialogUI.setupUi(this); - - QString updateRequired = QString("You are currently running build %1, the latest build released is %2." - "\n\nPlease download and install the most recent release to access the latest features and bug fixes.") - .arg(Application::getInstance()->applicationVersion(), latestVersion); - - setAttribute(Qt::WA_DeleteOnClose); - - QPushButton* downloadButton = findChild("downloadButton"); - QPushButton* skipButton = findChild("skipButton"); - QPushButton* closeButton = findChild("closeButton"); - QLabel* updateContent = findChild("updateContent"); - - updateContent->setText(updateRequired); - - connect(downloadButton, SIGNAL(released()), this, SLOT(handleDownload())); - connect(skipButton, SIGNAL(released()), this, SLOT(handleSkip())); - connect(closeButton, SIGNAL(released()), this, SLOT(close())); - - QMetaObject::invokeMethod(this, "show", Qt::QueuedConnection); + auto applicationUpdater = DependencyManager::get(); + int currentVersion = QCoreApplication::applicationVersion().toInt(); + int latestVersion = applicationUpdater.data()->getBuildData().lastKey(); + int versionsBehind = latestVersion - currentVersion; + _updateAvailableDetails = "v" + QString::number(latestVersion) + " released on " + applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"]; + _updateAvailableDetails += "\nYou are " + QString::number(versionsBehind) + " versions behind"; + _releaseNotes = applicationUpdater.data()->getBuildData()[latestVersion]["releaseNotes"]; } -void UpdateDialog::handleDownload() { - QDesktopServices::openUrl(_downloadUrl); - Application::getInstance()->quit(); +const QString& UpdateDialog::updateAvailableDetails() const { + return _updateAvailableDetails; } -void UpdateDialog::handleSkip() { - Application::getInstance()->skipVersion(_latestVersion); - this->close(); +const QString& UpdateDialog::releaseNotes() const { + return _releaseNotes; } + +void UpdateDialog::closeDialog() { + hide(); +} + +void UpdateDialog::hide() { + ((QQuickItem*)parent())->setEnabled(false); +} + +void UpdateDialog::triggerUpgrade() { + auto applicationUpdater = DependencyManager::get(); + applicationUpdater.data()->performAutoUpdate(applicationUpdater.data()->getBuildData().lastKey()); +} \ No newline at end of file diff --git a/interface/src/ui/UpdateDialog.h b/interface/src/ui/UpdateDialog.h index 15a97bf024..84d390c942 100644 --- a/interface/src/ui/UpdateDialog.h +++ b/interface/src/ui/UpdateDialog.h @@ -2,30 +2,42 @@ // UpdateDialog.h // interface/src/ui // -// Copyright 2014 High Fidelity, Inc. +// Created by Leonardo Murillo on 6/3/15. +// Copyright 2015 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 // +#pragma once #ifndef hifi_UpdateDialog_h #define hifi_UpdateDialog_h -#include +#include -class UpdateDialog : public QDialog { +#include + +class UpdateDialog : public OffscreenQmlDialog { Q_OBJECT + HIFI_QML_DECL + + Q_PROPERTY(QString updateAvailableDetails READ updateAvailableDetails) + Q_PROPERTY(QString releaseNotes READ releaseNotes) public: - UpdateDialog(QWidget* parent, const QString& releaseNotes, const QString& latestVersion, const QUrl& downloadURL); + UpdateDialog(QQuickItem* parent = nullptr); + const QString& updateAvailableDetails() const; + const QString& releaseNotes() const; private: - QString _latestVersion; - QUrl _downloadUrl; - -private slots: - void handleDownload(); - void handleSkip(); + QString _updateAvailableDetails; + QString _releaseNotes; + +protected: + void hide(); + Q_INVOKABLE void triggerUpgrade(); + Q_INVOKABLE void closeDialog(); + }; #endif // hifi_UpdateDialog_h diff --git a/interface/ui/updateDialog.ui b/interface/ui/updateDialog.ui deleted file mode 100644 index fc375e17c2..0000000000 --- a/interface/ui/updateDialog.ui +++ /dev/null @@ -1,132 +0,0 @@ - - - Dialog - - - Qt::NonModal - - - - 0 - 0 - 750 - 213 - - - - PointingHandCursor - - - Update Required - - - background-color: rgb(255, 255, 255); - - - - - 50 - 20 - 641 - 111 - - - - - Arial - -1 - - - - font-family: Arial; -font-size: 20px; - - - - - - true - - - - - - 360 - 160 - 374 - 42 - - - - - - - PointingHandCursor - - - background-color: #333333; -border-width: 0; -border-radius: 9px; -border-radius: 9px; -font-family: Arial; -font-size: 18px; -font-weight: 100; -color: #b7b7b7; -width: 120px; -height: 40px; - - - Download - - - - - - - PointingHandCursor - - - background-color: #333333; -border-width: 0; -border-radius: 9px; -border-radius: 9px; -font-family: Arial; -font-size: 18px; -font-weight: 100; -color: #b7b7b7; -width: 120px; -height: 40px; - - - Skip Version - - - - - - - PointingHandCursor - - - background-color: #333333; -border-width: 0; -border-radius: 9px; -border-radius: 9px; -font-family: Arial; -font-size: 18px; -font-weight: 100; -color: #b7b7b7; -width: 120px; -height: 40px; - - - Close - - - - - - - - - diff --git a/libraries/auto-updater/CMakeLists.txt b/libraries/auto-updater/CMakeLists.txt new file mode 100644 index 0000000000..b3665af2cb --- /dev/null +++ b/libraries/auto-updater/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME auto-updater) +setup_hifi_library(Network) +link_hifi_libraries(shared networking) diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp new file mode 100644 index 0000000000..8c6aa5605d --- /dev/null +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -0,0 +1,144 @@ +// +// AutoUpdater.cpp +// libraries/auto-update/src +// +// Created by Leonardo Murillo on 6/1/2015. +// Copyright 2015 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 "AutoUpdater.h" + +#include +#include + +AutoUpdater::AutoUpdater() { +#if defined Q_OS_WIN32 + _operatingSystem = "windows"; +#elif defined Q_OS_MAC + _operatingSystem = "mac"; +#elif defined Q_OS_LINUX + _operatingSystem = "ubuntu"; +#endif + + connect(this, SIGNAL(latestVersionDataParsed()), this, SLOT(checkVersionAndNotify())); +} + +void AutoUpdater::checkForUpdate() { + this->getLatestVersionData(); +} + +void AutoUpdater::getLatestVersionData() { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest latestVersionRequest(BUILDS_XML_URL); + latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(latestVersionRequest); + connect(reply, &QNetworkReply::finished, this, &AutoUpdater::parseLatestVersionData); +} + +void AutoUpdater::parseLatestVersionData() { + QNetworkReply* sender = qobject_cast(QObject::sender()); + + QXmlStreamReader xml(sender); + + int version; + QString downloadUrl; + QString releaseTime; + QString releaseNotes; + QString commitSha; + QString pullRequestNumber; + + while (!xml.atEnd() && !xml.hasError()) { + if (xml.name().toString() == "project" && + xml.attributes().hasAttribute("name") && + xml.attributes().value("name").toString() == "interface") { + xml.readNext(); + + while (!xml.atEnd() && !xml.hasError() && xml.name().toString() != "project") { + if (xml.name().toString() == "platform" && + xml.attributes().hasAttribute("name") && + xml.attributes().value("name").toString() == _operatingSystem) { + xml.readNext(); + while (!xml.atEnd() && !xml.hasError() && + xml.name().toString() != "platform") { + + if (xml.name().toString() == "build" && xml.tokenType() != QXmlStreamReader::EndElement) { + xml.readNext(); + version = xml.readElementText().toInt(); + xml.readNext(); + downloadUrl = xml.readElementText(); + xml.readNext(); + releaseTime = xml.readElementText(); + xml.readNext(); + if (xml.name().toString() == "notes" && xml.tokenType() != QXmlStreamReader::EndElement) { + xml.readNext(); + while (!xml.atEnd() && !xml.hasError() && xml.name().toString() != "notes") { + if (xml.name().toString() == "note" && xml.tokenType() != QXmlStreamReader::EndElement) { + releaseNotes = releaseNotes + "\n" + xml.readElementText(); + } + xml.readNext(); + } + } + xml.readNext(); + commitSha = xml.readElementText(); + xml.readNext(); + pullRequestNumber = xml.readElementText(); + appendBuildData(version, downloadUrl, releaseTime, releaseNotes, pullRequestNumber); + releaseNotes = ""; + } + + xml.readNext(); + } + } + xml.readNext(); + } + + } else { + xml.readNext(); + } + } + sender->deleteLater(); + emit latestVersionDataParsed(); +} + +void AutoUpdater::checkVersionAndNotify() { + if (QCoreApplication::applicationVersion() == "dev" || _builds.empty()) { + // No version checking is required in dev builds or when no build + // data was found for the platform + return; + } + int latestVersionAvailable = _builds.lastKey(); + if (QCoreApplication::applicationVersion().toInt() < latestVersionAvailable) { + emit newVersionIsAvailable(); + } +} + +void AutoUpdater::performAutoUpdate(int version) { + // NOTE: This is not yet auto updating - however this is a checkpoint towards that end + // Next PR will handle the automatic download, upgrading and application restart + const QMap& chosenVersion = _builds.value(version); + const QUrl& downloadUrl = chosenVersion.value("downloadUrl"); + QDesktopServices::openUrl(downloadUrl); + QCoreApplication::quit(); +} + +void AutoUpdater::downloadUpdateVersion(int version) { + emit newVersionIsDownloaded(); +} + +void AutoUpdater::appendBuildData(int versionNumber, + const QString& downloadURL, + const QString& releaseTime, + const QString& releaseNotes, + const QString& pullRequestNumber) { + + QMap thisBuildDetails; + thisBuildDetails.insert("downloadUrl", downloadURL); + thisBuildDetails.insert("releaseTime", releaseTime); + thisBuildDetails.insert("releaseNotes", releaseNotes); + thisBuildDetails.insert("pullRequestNumber", pullRequestNumber); + _builds.insert(versionNumber, thisBuildDetails); + +} \ No newline at end of file diff --git a/libraries/auto-updater/src/AutoUpdater.h b/libraries/auto-updater/src/AutoUpdater.h new file mode 100644 index 0000000000..70867e5a44 --- /dev/null +++ b/libraries/auto-updater/src/AutoUpdater.h @@ -0,0 +1,68 @@ +// +// AutoUpdater.h +// libraries/auto-update/src +// +// Created by Leonardo Murillo on 6/1/2015. +// Copyright 2015 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 hifi_AutoUpdater_h +#define hifi_AutoUpdater_h + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); + +class AutoUpdater : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + AutoUpdater(); + + void checkForUpdate(); + const QMap>& getBuildData() { return _builds; } + void performAutoUpdate(int version); + +signals: + void latestVersionDataParsed(); + void newVersionIsAvailable(); + void newVersionIsDownloaded(); + +private: + QMap> _builds; + QString _operatingSystem; + + void getLatestVersionData(); + void downloadUpdateVersion(int version); + void appendBuildData(int versionNumber, + const QString& downloadURL, + const QString& releaseTime, + const QString& releaseNotes, + const QString& pullRequestNumber); + +private slots: + void parseLatestVersionData(); + void checkVersionAndNotify(); +}; + +#endif // _hifi_AutoUpdater_h diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index ac1d4b494f..4842063af3 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -41,7 +41,11 @@ public: ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData); - virtual void somethingChangedNotification() { _needsInitialSimulation = true; } + virtual void somethingChangedNotification() { + // FIX ME: this is overly aggressive. We only really need to simulate() if something about + // the world space transform has changed and/or if some animation is occurring. + _needsInitialSimulation = true; + } virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr); virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 91c89bb183..d00728a9eb 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -53,7 +53,7 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) { batch.setUniformTexture(0, _texture->getGPUTexture()); } batch.setModelTransform(getTransformToCenter()); - DependencyManager::get()->bindSimpleProgram(batch); + DependencyManager::get()->bindSimpleProgram(batch, textured); DependencyManager::get()->renderVertices(batch, gpu::QUADS, _cacheID); }; @@ -75,31 +75,34 @@ void RenderableParticleEffectEntityItem::updateQuads(RenderArgs* args, bool text vertices.reserve(getLivingParticleCount() * VERTS_PER_PARTICLE); if (textured) { - positions.reserve(getLivingParticleCount()); textureCoords.reserve(getLivingParticleCount() * VERTS_PER_PARTICLE); - - for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { - positions.append(_particlePositions[i]); - + } + positions.reserve(getLivingParticleCount()); + + + for (quint32 i = _particleHeadIndex; i != _particleTailIndex; i = (i + 1) % _maxParticles) { + positions.append(_particlePositions[i]); + if (textured) { textureCoords.append(glm::vec2(0, 1)); textureCoords.append(glm::vec2(1, 1)); textureCoords.append(glm::vec2(1, 0)); textureCoords.append(glm::vec2(0, 0)); } - - // sort particles back to front - ::zSortAxis = args->_viewFrustum->getDirection(); - qSort(positions.begin(), positions.end(), zSort); } + + // sort particles back to front + ::zSortAxis = args->_viewFrustum->getDirection(); + qSort(positions.begin(), positions.end(), zSort); for (int i = 0; i < positions.size(); i++) { glm::vec3 pos = (textured) ? positions[i] : _particlePositions[i]; // generate corners of quad aligned to face the camera. - vertices.append(pos - rightOffset + upOffset); vertices.append(pos + rightOffset + upOffset); - vertices.append(pos + rightOffset - upOffset); + vertices.append(pos - rightOffset + upOffset); vertices.append(pos - rightOffset - upOffset); + vertices.append(pos + rightOffset - upOffset); + } if (textured) { diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 51575bfde5..6941d7335d 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -54,9 +54,33 @@ const QUrl AddressManager::currentAddress() const { void AddressManager::loadSettings(const QString& lookupString) { if (lookupString.isEmpty()) { - handleLookupString(currentAddressHandle.get().toString()); + handleUrl(currentAddressHandle.get().toString(), LookupTrigger::StartupFromSettings); } else { - handleLookupString(lookupString); + handleUrl(lookupString, LookupTrigger::StartupFromSettings); + } +} + +void AddressManager::goBack() { + if (_backStack.size() > 0) { + // go to that address + handleUrl(_backStack.pop(), LookupTrigger::Back); + + if (_backStack.size() == 0) { + // the back stack is now empty so it is no longer possible to go back - emit that signal + emit goBackPossible(false); + } + } +} + +void AddressManager::goForward() { + if (_forwardStack.size() > 0) { + // pop a URL from the forwardStack and go to that address + handleUrl(_forwardStack.pop(), LookupTrigger::Forward); + + if (_forwardStack.size() == 0) { + // the forward stack is empty so it is no longer possible to go forwards - emit that signal + emit goForwardPossible(false); + } } } @@ -102,7 +126,7 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() { return callbackParams; } -bool AddressManager::handleUrl(const QUrl& lookupUrl) { +bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { if (lookupUrl.scheme() == HIFI_URL_SCHEME) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); @@ -121,17 +145,17 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { // we're assuming this is either a network address or global place name // check if it is a network address first if (handleNetworkAddress(lookupUrl.host() - + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())))) { + + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger)) { // we may have a path that defines a relative viewpoint - if so we should jump to that now - handlePath(lookupUrl.path()); + handlePath(lookupUrl.path(), trigger); } else if (handleDomainID(lookupUrl.host())){ // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API - attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path()); + attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after - attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path()); + attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path(), trigger); } } @@ -140,8 +164,10 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // if this is a relative path then handle it as a relative viewpoint - handlePath(lookupUrl.path()); + handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); + + return true; } return false; @@ -183,6 +209,7 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { } const char OVERRIDE_PATH_KEY[] = "override_path"; +const char LOOKUP_TRIGGER_KEY[] = "lookup_trigger"; void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply& reply) { @@ -243,6 +270,8 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); } + LookupTrigger trigger = (LookupTrigger) reply.property(LOOKUP_TRIGGER_KEY).toInt(); + // set our current root place id to the ID that came back const QString PLACE_ID_KEY = "id"; _rootPlaceID = rootMap[PLACE_ID_KEY].toUuid(); @@ -251,16 +280,16 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString PLACE_NAME_KEY = "name"; QString placeName = rootMap[PLACE_NAME_KEY].toString(); if (!placeName.isEmpty()) { - setHost(placeName); + setHost(placeName, trigger); } else { - setHost(domainIDString); + setHost(domainIDString, trigger); } // check if we had a path to override the path returned QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); if (!overridePath.isEmpty()) { - handlePath(overridePath); + handlePath(overridePath, trigger); } else { // take the path that came back const QString PLACE_PATH_KEY = "path"; @@ -269,10 +298,14 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY); if (!returnedPath.isEmpty()) { - // try to parse this returned path as a viewpoint, that's the only thing it could be for now - if (!handleViewpoint(returnedPath, shouldFaceViewpoint)) { - qCDebug(networking) << "Received a location path that was could not be handled as a viewpoint -" - << returnedPath; + if (shouldFaceViewpoint) { + // try to parse this returned path as a viewpoint, that's the only thing it could be for now + if (!handleViewpoint(returnedPath, shouldFaceViewpoint)) { + qCDebug(networking) << "Received a location path that was could not be handled as a viewpoint -" + << returnedPath; + } + } else { + handlePath(overridePath, trigger); } } else { // we didn't override the path or get one back - ask the DS for the viewpoint of its index path @@ -306,15 +339,20 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) { const QString GET_PLACE = "/api/v1/places/%1"; -void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath) { +void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath, LookupTrigger trigger) { // assume this is a place name and see if we can get any info on it QString placeName = QUrl::toPercentEncoding(lookupString); QVariantMap requestParams; + + // if the user asked for a specific path with this lookup then keep it with the request so we can use it later if (!overridePath.isEmpty()) { requestParams.insert(OVERRIDE_PATH_KEY, overridePath); } + // remember how this lookup was triggered for history storage handling later + requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(trigger)); + AccountManager::getInstance().sendRequest(GET_PLACE.arg(placeName), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, @@ -324,15 +362,20 @@ void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const Q const QString GET_DOMAIN_ID = "/api/v1/domains/%1"; -void AddressManager::attemptDomainIDLookup(const QString& lookupString, const QString& overridePath) { +void AddressManager::attemptDomainIDLookup(const QString& lookupString, const QString& overridePath, LookupTrigger trigger) { // assume this is a domain ID and see if we can get any info on it QString domainID = QUrl::toPercentEncoding(lookupString); QVariantMap requestParams; + + // if the user asked for a specific path with this lookup then keep it with the request so we can use it later if (!overridePath.isEmpty()) { requestParams.insert(OVERRIDE_PATH_KEY, overridePath); } + // remember how this lookup was triggered for history storage handling later + requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(trigger)); + AccountManager::getInstance().sendRequest(GET_DOMAIN_ID.arg(domainID), AccountManagerAuth::None, QNetworkAccessManager::GetOperation, @@ -340,7 +383,7 @@ void AddressManager::attemptDomainIDLookup(const QString& lookupString, const QS QByteArray(), NULL, requestParams); } -bool AddressManager::handleNetworkAddress(const QString& lookupString) { +bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTrigger trigger) { const QString IP_ADDRESS_REGEX_STRING = "^((?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(?::(\\d{1,5}))?$"; @@ -358,7 +401,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) { } emit lookupResultsFinished(); - setDomainInfo(domainIPString, domainPort); + setDomainInfo(domainIPString, domainPort, trigger); return true; } @@ -375,7 +418,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) { } emit lookupResultsFinished(); - setDomainInfo(domainHostname, domainPort); + setDomainInfo(domainHostname, domainPort, trigger); return true; } @@ -391,15 +434,26 @@ bool AddressManager::handleDomainID(const QString& host) { return (domainIDRegex.indexIn(host) != -1); } -void AddressManager::handlePath(const QString& path) { - if (!handleViewpoint(path)) { +void AddressManager::handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly) { + if (!handleViewpoint(path, false, wasPathOnly)) { qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path << "- wll attempt to ask domain-server to resolve."; + + if (!wasPathOnly) { + // if we received a path with a host then we need to remember what it was here so we can not + // double set add to the history stack once handle viewpoint is called with the result + _newHostLookupPath = path; + } else { + // clear the _newHostLookupPath so it doesn't match when this return comes in + _newHostLookupPath = QString(); + } + emit pathChangeRequired(path); } } -bool AddressManager::handleViewpoint(const QString& viewpointString, bool shouldFace) { +bool AddressManager::handleViewpoint(const QString& viewpointString, bool shouldFace, + bool definitelyPathOnly, const QString& pathString) { const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*"; const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + @@ -416,8 +470,18 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should positionRegex.cap(2).toFloat(), positionRegex.cap(3).toFloat()); - // we're about to jump positions - store the current address in our history - addCurrentAddressToHistory(); + // We need to use definitelyPathOnly, pathString and _newHostLookupPath to determine if the current address + // should be stored in the history before we ask for a position/orientation change. A relative path that was + // not associated with a host lookup should always trigger a history change (definitelyPathOnly) and a viewpointString + // with a non empty pathString (suggesting this is the result of a lookup with the domain-server) that does not match + // _newHostLookupPath should always trigger a history change. + // + // We use _newHostLookupPath to determine if the client has already stored its last address + // before moving to a new host thanks to the information in the same lookup URL. + + if (definitelyPathOnly || (!pathString.isEmpty() && pathString != _newHostLookupPath)) { + addCurrentAddressToHistory(LookupTrigger::UserInput); + } if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) { glm::quat newOrientation; @@ -469,11 +533,11 @@ bool AddressManager::handleUsername(const QString& lookupString) { return false; } -void AddressManager::setHost(const QString& host) { +void AddressManager::setHost(const QString& host, LookupTrigger trigger) { if (host != _host) { // if the host is being changed we should store current address in the history - addCurrentAddressToHistory(); + addCurrentAddressToHistory(trigger); _host = host; emit hostChanged(_host); @@ -481,8 +545,8 @@ void AddressManager::setHost(const QString& host) { } -void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { - setHost(hostname); +void AddressManager::setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger) { + setHost(hostname, trigger); _rootPlaceID = QUuid(); @@ -495,11 +559,17 @@ void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { void AddressManager::goToUser(const QString& username) { QString formattedUsername = QUrl::toPercentEncoding(username); + + // for history storage handling we remember how this lookup was trigged - for a username it's always user input + QVariantMap requestParams; + requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(LookupTrigger::UserInput)); + // this is a username - pull the captured name and lookup that user's location AccountManager::getInstance().sendRequest(GET_USER_LOCATION.arg(formattedUsername), AccountManagerAuth::Optional, QNetworkAccessManager::GetOperation, - apiCallbackParameters()); + apiCallbackParameters(), + QByteArray(), nullptr, requestParams); } void AddressManager::copyAddress() { @@ -510,21 +580,36 @@ void AddressManager::copyPath() { QApplication::clipboard()->setText(currentPath()); } -void AddressManager::addCurrentAddressToHistory() { - if (_lastHistoryAppend == 0) { - // we don't store the first address on application load - // just update the last append time so the next is stored - _lastHistoryAppend = usecTimestampNow(); - } else { - const quint64 DOUBLE_STORE_THRESHOLD_USECS = 500000; +void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { - // avoid double storing when the host changes and the viewpoint changes immediately after - if (usecTimestampNow() - _lastHistoryAppend > DOUBLE_STORE_THRESHOLD_USECS) { - // add the current address to the history - _history.append(currentAddress()); + // if we're cold starting and this is called for the first address (from settings) we don't do anything + if (trigger != LookupTrigger::StartupFromSettings) { + if (trigger == LookupTrigger::UserInput) { + // anyime the user has manually looked up an address we know we should clear the forward stack + _forwardStack.clear(); - // change our last history append to now - _lastHistoryAppend = usecTimestampNow(); + emit goForwardPossible(false); + } + + if (trigger == LookupTrigger::Back) { + // we're about to push to the forward stack + // if it's currently empty emit our signal to say that going forward is now possible + if (_forwardStack.size() == 0) { + emit goForwardPossible(true); + } + + // when the user is going back, we move the current address to the forward stack + // and do not but it into the back stack + _forwardStack.push(currentAddress()); + } else { + // we're about to push to the back stack + // if it's currently empty emit our signal to say that going forward is now possible + if (_forwardStack.size() == 0) { + emit goBackPossible(true); + } + + // unless this was triggered from the result of a named path lookup, add the current address to the history + _backStack.push(currentAddress()); } } } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index d950ae0275..a0fc7eb0be 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -12,8 +12,8 @@ #ifndef hifi_AddressManager_h #define hifi_AddressManager_h -#include -#include +#include +#include #include #include @@ -38,6 +38,14 @@ class AddressManager : public QObject, public Dependency { Q_PROPERTY(QString hostname READ getHost) Q_PROPERTY(QString pathname READ currentPath) public: + + enum LookupTrigger { + UserInput, + Back, + Forward, + StartupFromSettings + }; + bool isConnected(); const QString& getProtocol() { return HIFI_URL_SCHEME; }; @@ -47,10 +55,7 @@ public: const QUuid& getRootPlaceID() const { return _rootPlaceID; } const QString& getHost() const { return _host; } - void setHost(const QString& host); - void attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath = QString()); - void attemptDomainIDLookup(const QString& lookupString, const QString& overridePath = QString()); void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; } void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; } @@ -60,9 +65,12 @@ public: public slots: void handleLookupString(const QString& lookupString); - void goToUser(const QString& username); - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); - bool goToViewpoint(const QString& viewpointString) { return handleViewpoint(viewpointString); } + // we currently expect this to be called from NodeList once handleLookupString has been called with a path + bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) + { return handleViewpoint(viewpointString, false, false, pathString); } + + void goBack(); + void goForward(); void storeCurrentAddress(); @@ -73,40 +81,57 @@ signals: void lookupResultsFinished(); void lookupResultIsOffline(); void lookupResultIsNotFound(); + void possibleDomainChangeRequired(const QString& newHostname, quint16 newPort); void possibleDomainChangeRequiredViaICEForID(const QString& iceServerHostname, const QUuid& domainID); + void locationChangeRequired(const glm::vec3& newPosition, bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); void pathChangeRequired(const QString& newPath); void hostChanged(const QString& newHost); + + void goBackPossible(bool isPossible); + void goForwardPossible(bool isPossible); + protected: AddressManager(); private slots: void handleAPIResponse(QNetworkReply& requestReply); void handleAPIError(QNetworkReply& errorReply); + + void goToUser(const QString& username); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); private: - void setDomainInfo(const QString& hostname, quint16 port); + void setHost(const QString& host, LookupTrigger trigger); + void setDomainInfo(const QString& hostname, quint16 port, LookupTrigger trigger); const JSONCallbackParameters& apiCallbackParameters(); - bool handleUrl(const QUrl& lookupUrl); + bool handleUrl(const QUrl& lookupUrl, LookupTrigger trigger = UserInput); - bool handleNetworkAddress(const QString& lookupString); - void handlePath(const QString& path); - bool handleViewpoint(const QString& viewpointString, bool shouldFace = false); + bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger); + void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); + bool handleViewpoint(const QString& viewpointString, bool shouldFace = false, + bool definitelyPathOnly = false, const QString& pathString = QString()); bool handleUsername(const QString& lookupString); bool handleDomainID(const QString& host); - void addCurrentAddressToHistory(); + void attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath, LookupTrigger trigger); + void attemptDomainIDLookup(const QString& lookupString, const QString& overridePath, LookupTrigger trigger); + + void addCurrentAddressToHistory(LookupTrigger trigger); QString _host; QUuid _rootPlaceID; PositionGetter _positionGetter; OrientationGetter _orientationGetter; - QList _history; - quint64 _lastHistoryAppend = 0; + QStack _backStack; + QStack _forwardStack; + quint64 _lastBackPush = 0; + + QString _newHostLookupPath; }; #endif // hifi_AddressManager_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 44aa5bc644..e7bb4fbb6f 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -467,7 +467,7 @@ void NodeList::handleDSPathQueryResponse(const QByteArray& packet) { QString viewpoint = QString::fromUtf8(currentPosition, numViewpointBytes); // Hand it off to the AddressManager so it can handle it as a relative viewpoint - if (DependencyManager::get()->goToViewpoint(viewpoint)) { + if (DependencyManager::get()->goToViewpointForPath(viewpoint, pathQuery)) { qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery; } else { qCDebug(networking) << "Could not go to viewpoint" << viewpoint diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 71178070c6..803673a06e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -82,6 +82,7 @@ Model::Model(QObject* parent) : _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), + _calculatedMeshPartOffsetValid(false), _calculatedMeshPartBoxesValid(false), _calculatedMeshBoxesValid(false), _calculatedMeshTrianglesValid(false), @@ -544,6 +545,10 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g // we can use the AABox's ray intersection by mapping our origin and direction into the model frame // and testing intersection there. if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face)) { + + if (!_calculatedMeshBoxesValid) { + recalculateMeshBoxes(pickAgainstTriangles); + } float bestDistance = std::numeric_limits::max(); @@ -660,6 +665,25 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } +void Model::recalculateMeshPartOffsets() { + if (!_calculatedMeshPartOffsetValid) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + int numberOfMeshes = geometry.meshes.size(); + _calculatedMeshPartOffset.clear(); + for (int i = 0; i < numberOfMeshes; i++) { + const FBXMesh& mesh = geometry.meshes.at(i); + qint64 partOffset = 0; + for (int j = 0; j < mesh.parts.size(); j++) { + const FBXMeshPart& part = mesh.parts.at(j); + _calculatedMeshPartOffset[QPair(i, j)] = partOffset; + partOffset += part.quadIndices.size() * sizeof(int); + partOffset += part.triangleIndices.size() * sizeof(int); + + } + } + _calculatedMeshPartOffsetValid = true; + } +} // TODO: we seem to call this too often when things haven't actually changed... look into optimizing this // Any script might trigger findRayIntersectionAgainstSubMeshes (and maybe convexHullContains), so these // can occur multiple times. In addition, rendering does it's own ray picking in order to decide which @@ -675,7 +699,8 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { _calculatedMeshTriangles.clear(); _calculatedMeshTriangles.resize(numberOfMeshes); _calculatedMeshPartBoxes.clear(); - _calculatedMeshPartOffet.clear(); + _calculatedMeshPartOffset.clear(); + _calculatedMeshPartOffsetValid = false; for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents); @@ -770,7 +795,7 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { } } _calculatedMeshPartBoxes[QPair(i, j)] = thisPartBounds; - _calculatedMeshPartOffet[QPair(i, j)] = partOffset; + _calculatedMeshPartOffset[QPair(i, j)] = partOffset; partOffset += part.quadIndices.size() * sizeof(int); partOffset += part.triangleIndices.size() * sizeof(int); @@ -778,6 +803,7 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { } _calculatedMeshTriangles[i] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; + _calculatedMeshPartOffsetValid = true; } } _calculatedMeshBoxesValid = true; @@ -786,16 +812,6 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { } void Model::renderSetup(RenderArgs* args) { - // if we don't have valid mesh boxes, calculate them now, this only matters in cases - // where our caller has passed RenderArgs which will include a view frustum we can cull - // against. We cache the results of these calculations so long as the model hasn't been - // simulated and the mesh hasn't changed. - if (args && !_calculatedMeshBoxesValid) { - _mutex.lock(); - recalculateMeshBoxes(); - _mutex.unlock(); - } - // set up dilated textures on first render after load/simulate const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (_dilatedTextures.isEmpty()) { @@ -1342,18 +1358,15 @@ void Model::snapToRegistrationPoint() { } void Model::simulate(float deltaTime, bool fullUpdate) { - /* - qDebug() << "Model::simulate()"; - qDebug() << " _translation:" << _translation; - qDebug() << " _rotation:" << _rotation; - */ - fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) || (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint); if (isActive() && fullUpdate) { - // NOTE: this seems problematic... need to review - _calculatedMeshBoxesValid = false; // if we have to simulate, we need to assume our mesh boxes are all invalid + // NOTE: This is overly aggressive and we are invalidating the MeshBoxes when in fact they may not be invalid + // they really only become invalid if something about the transform to world space has changed. This is + // not too bad at this point, because it doesn't impact rendering. However it does slow down ray picking + // because ray picking needs valid boxes to work + _calculatedMeshBoxesValid = false; _calculatedMeshTrianglesValid = false; // check for scale to fit @@ -1761,10 +1774,6 @@ void Model::setupBatchTransform(gpu::Batch& batch, RenderArgs* args) { } AABox Model::getPartBounds(int meshIndex, int partIndex) { - if (!_calculatedMeshPartBoxesValid || !_calculatedMeshBoxesValid) { - recalculateMeshBoxes(true); - } - if (meshIndex < _meshStates.size()) { const MeshState& state = _meshStates.at(meshIndex); bool isSkinned = state.clusterMatrices.size() > 1; @@ -1774,7 +1783,7 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { } } - if (_calculatedMeshPartBoxesValid && _calculatedMeshPartBoxes.contains(QPair(meshIndex, partIndex))) { + if (_geometry->getFBXGeometry().meshes.size() > meshIndex) { // FIX ME! - This is currently a hack because for some mesh parts our efforts to calculate the bounding // box of the mesh part fails. It seems to create boxes that are not consistent with where the @@ -1795,15 +1804,13 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { } void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) { - if (!_readyWhenAdded) { return; // bail asap } - // we always need these properly calculated before we can render, this will likely already have been done - // since the engine will call our getPartBounds() before rendering us. - if (!_calculatedMeshPartBoxesValid || !_calculatedMeshBoxesValid) { - recalculateMeshBoxes(true); + // We need to make sure we have valid offsets calculated before we can render + if (!_calculatedMeshPartOffsetValid) { + recalculateMeshPartOffsets(); } auto textureCache = DependencyManager::get(); @@ -2011,7 +2018,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran } } - qint64 offset = _calculatedMeshPartOffet[QPair(meshIndex, partIndex)]; + qint64 offset = _calculatedMeshPartOffset[QPair(meshIndex, partIndex)]; if (part.quadIndices.size() > 0) { batch.drawIndexed(gpu::QUADS, part.quadIndices.size(), offset); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 7c1572418e..6dfe223581 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -369,7 +369,8 @@ private: }; QHash, AABox> _calculatedMeshPartBoxes; // world coordinate AABoxes for all sub mesh part boxes - QHash, qint64> _calculatedMeshPartOffet; + QHash, qint64> _calculatedMeshPartOffset; + bool _calculatedMeshPartOffsetValid; bool _calculatedMeshPartBoxesValid; @@ -381,6 +382,7 @@ private: QMutex _mutex; void recalculateMeshBoxes(bool pickAgainstTriangles = false); + void recalculateMeshPartOffsets(); void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes