overte/interface/src/Application_UI.cpp
2025-04-05 22:09:13 +02:00

1403 lines
60 KiB
C++

//
// Application_UI.cpp
// interface/src
//
// Split from Application.cpp by HifiExperiments on 3/30/24
// Created by Andrzej Kapolka on 5/10/13.
// Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// Copyright 2022-2023 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// SPDX-License-Identifier: Apache-2.0
//
#include "Application.h"
#include <QtQml/QQmlContext>
#include <AddressManager.h>
#include <AnimationCacheScriptingInterface.h>
#include <audio/AudioScope.h>
#include <AudioScriptingInterface.h>
#include <AvatarBookmarks.h>
#include <display-plugins/CompositorHelper.h>
#include <FileDialogHelper.h>
#include <FSTReader.h>
#include <MainWindow.h>
#include <material-networking/TextureCacheScriptingInterface.h>
#include <Menu.h>
#include <MessagesClient.h>
#include <model-networking/ModelCacheScriptingInterface.h>
#include <OffscreenUi.h>
#include <plugins/InputConfiguration.h>
#include <plugins/PluginManager.h>
#include <plugins/PluginUtils.h>
#include <plugins/SteamClientPlugin.h>
#include <Preferences.h>
#include <procedural/MaterialCacheScriptingInterface.h>
#include <raypick/PointerScriptingInterface.h>
#include <recording/RecordingScriptingInterface.h>
#include <SandboxUtils.h>
#include <SceneScriptingInterface.h>
#include <ScriptEngines.h>
#include <scripting/AccountServicesScriptingInterface.h>
#include <scripting/AssetMappingsScriptingInterface.h>
#include <scripting/ControllerScriptingInterface.h>
#include <scripting/DesktopScriptingInterface.h>
#include <scripting/HMDScriptingInterface.h>
#include <scripting/KeyboardScriptingInterface.h>
#include <scripting/MenuScriptingInterface.h>
#include <scripting/PerformanceScriptingInterface.h>
#include <scripting/PlatformInfoScriptingInterface.h>
#include <scripting/RatesScriptingInterface.h>
#include <scripting/RenderScriptingInterface.h>
#include <scripting/SelectionScriptingInterface.h>
#include <scripting/SettingsScriptingInterface.h>
#include <scripting/WindowScriptingInterface.h>
#ifndef Q_OS_ANDROID
#include <shared/FileLogger.h>
#endif
#include <shared/GlobalAppProperties.h>
#include <shared/StringHelpers.h>
#include <SoundCacheScriptingInterface.h>
#include <ui/AnimStats.h>
#include <ui/AvatarInputs.h>
#include <ui/DialogsManager.h>
#include <ui/DomainConnectionModel.h>
#include <ui/EntityScriptServerLogDialog.h>
#include <ui/Keyboard.h>
#include <ui/LogDialog.h>
#include <ui/LoginDialog.h>
#include <ui/OctreeStatsDialog.h>
#include <ui/OctreeStatsProvider.h>
#include <ui/Snapshot.h>
#include <ui/Stats.h>
#include <ui/TabletScriptingInterface.h>
#include <ui/ToolbarScriptingInterface.h>
#include <UserActivityLogger.h>
#include <UserActivityLoggerScriptingInterface.h>
#include <UsersScriptingInterface.h>
#include "AboutUtil.h"
#include "ArchiveDownloadInterface.h"
#include "AudioClient.h"
#include "GLCanvas.h"
#include "LocationBookmarks.h"
#include "LODManager.h"
#include "ResourceRequestObserver.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
#endif
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
static const QString STANDARD_TO_ACTION_MAPPING_NAME = "Standard to Action";
static const QString NO_MOVEMENT_MAPPING_NAME = "Standard to Action (No Movement)";
static const QString NO_MOVEMENT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_nomovement.json";
const QString DEFAULT_CURSOR_NAME = "SYSTEM";
static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml");
static const QString INFO_HELP_PATH = "html/tabletHelp.html";
ui::Menu* Application::getPrimaryMenu() {
auto appMenu = _window->menuBar();
auto uiMenu = dynamic_cast<ui::Menu*>(appMenu);
return uiMenu;
}
void Application::showDisplayPluginsTools(bool show) {
DependencyManager::get<DialogsManager>()->hmdTools(show);
}
GLWidget* Application::getPrimaryWidget() {
return _glWidget;
}
MainWindow* Application::getPrimaryWindow() {
return getWindow();
}
QOpenGLContext* Application::getPrimaryContext() {
return _glWidget->qglContext();
}
bool Application::isForeground() const {
return _isForeground && !_window->isMinimized();
}
bool Application::hasFocus() const {
bool result = (QApplication::activeWindow() != nullptr);
#if defined(Q_OS_WIN)
// On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
// take user focus away from their current window. So also check whether the application is the user's current foreground
// window.
result = result && (HWND)QApplication::activeWindow()->winId() == GetForegroundWindow();
#endif
return result;
}
void Application::setFocus() {
// Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
// flashes the taskbar icon.
auto window = qApp->getWindow();
window->activateWindow();
}
void Application::raise() {
auto windowState = qApp->getWindow()->windowState();
if (windowState & Qt::WindowMinimized) {
if (windowState & Qt::WindowMaximized) {
qApp->getWindow()->showMaximized();
} else if (windowState & Qt::WindowFullScreen) {
qApp->getWindow()->showFullScreen();
} else {
qApp->getWindow()->showNormal();
}
}
qApp->getWindow()->raise();
}
void Application::showCursor(const Cursor::Icon& cursor) {
QMutexLocker locker(&_changeCursorLock);
auto managedCursor = Cursor::Manager::instance().getCursor();
auto curIcon = managedCursor->getIcon();
if (curIcon != cursor) {
managedCursor->setIcon(cursor);
curIcon = cursor;
}
_desiredCursor = cursor == Cursor::Icon::SYSTEM ? Qt::ArrowCursor : Qt::BlankCursor;
_cursorNeedsChanging = true;
}
CompositorHelper& Application::getApplicationCompositor() const {
return *DependencyManager::get<CompositorHelper>();
}
PickRay Application::computePickRay(float x, float y) const {
vec2 pickPoint { x, y };
PickRay result;
if (isHMDMode()) {
getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction);
} else {
pickPoint /= getCanvasSize();
if (_myCamera.getMode() == CameraMode::CAMERA_MODE_MIRROR) {
pickPoint.x = 1.0f - pickPoint.x;
}
QMutexLocker viewLocker(&_viewMutex);
_viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction);
}
return result;
}
void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties) {
surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
surfaceContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
surfaceContext->setContextProperty("Vec3", new Vec3());
surfaceContext->setContextProperty("Quat", new Quat());
surfaceContext->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
if (setAdditionalContextProperties) {
qDebug() << "setting additional context properties!";
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto flags = tabletScriptingInterface->getFlags();
surfaceContext->setContextProperty("offscreenFlags", flags);
surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
surfaceContext->setContextProperty("Settings", new QMLSettingsScriptingInterface(surfaceContext));
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("Performance", new PerformanceScriptingInterface());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
surfaceContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
surfaceContext->setContextProperty("fileDialogHelper", new FileDialogHelper());
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
surfaceContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
surfaceContext->setContextProperty("OctreeStats", DependencyManager::get<OctreeStatsProvider>().data());
surfaceContext->setContextProperty("DCModel", DependencyManager::get<DomainConnectionModel>().data());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
surfaceContext->setContextProperty("DialogsManager", DialogsManagerScriptingInterface::getInstance());
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
surfaceContext->setContextProperty("Render", RenderScriptingInterface::getInstance());
surfaceContext->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
surfaceContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
surfaceContext->setContextProperty("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
surfaceContext->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
surfaceContext->setContextProperty("About", AboutUtil::getInstance());
surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); // Deprecated.
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance());
surfaceContext->setContextProperty("ExternalResource", ExternalResource::getInstance());
// This `module` context property is blank for the QML scripting interface so that we don't get log errors when importing
// certain JS files from both scripts (in the JS context) and QML (in the QML context).
surfaceContext->setContextProperty("module", "");
}
}
void Application::setDesktopTabletBecomesToolbarSetting(bool value) {
_desktopTabletBecomesToolbarSetting.set(value);
updateSystemTabletMode();
}
void Application::setHmdTabletBecomesToolbarSetting(bool value) {
_hmdTabletBecomesToolbarSetting.set(value);
updateSystemTabletMode();
}
void Application::setMouseCaptureVR(bool value) {
_defaultMouseLock.set(value);
getApplicationCompositor().setMouseLockCompositVR(value);
}
bool Application::getMouseCaptureVR() {
return _defaultMouseLock.get();
}
void Application::setShowGraphicsIcon(bool value) {
_showGraphicsIconSetting.set(value);
DependencyManager::get<MessagesClient>()->sendLocalMessage("Overte-ShowGraphicsIconChanged", "");
}
void Application::setMiniTabletEnabled(bool enabled) {
_miniTabletEnabledSetting.set(enabled);
emit miniTabletEnabledChanged(enabled);
}
void Application::setSettingConstrainToolbarPosition(bool setting) {
_constrainToolbarPosition.set(setting);
getOffscreenUI()->setConstrainToolbarToCenterX(setting);
}
void Application::setAwayStateWhenFocusLostInVREnabled(bool enabled) {
_awayStateWhenFocusLostInVREnabled.set(enabled);
emit awayStateWhenFocusLostInVRChanged(enabled);
}
QUuid Application::getTabletScreenID() const {
auto HMD = DependencyManager::get<HMDScriptingInterface>();
return HMD->getCurrentTabletScreenID();
}
QUuid Application::getTabletHomeButtonID() const {
auto HMD = DependencyManager::get<HMDScriptingInterface>();
return HMD->getCurrentHomeButtonID();
}
QUuid Application::getTabletFrameID() const {
auto HMD = DependencyManager::get<HMDScriptingInterface>();
return HMD->getCurrentTabletFrameID();
}
QVector<QUuid> Application::getTabletIDs() const {
// Most important first.
QVector<QUuid> result;
auto HMD = DependencyManager::get<HMDScriptingInterface>();
result << HMD->getCurrentTabletScreenID();
result << HMD->getCurrentHomeButtonID();
result << HMD->getCurrentTabletFrameID();
return result;
}
void Application::confirmConnectWithoutAvatarEntities() {
if (_confirmConnectWithoutAvatarEntitiesDialog) {
// Dialog is already displayed.
return;
}
if (!getMyAvatar()->hasAvatarEntities()) {
// No avatar entities so continue with login.
DependencyManager::get<NodeList>()->getDomainHandler().setCanConnectWithoutAvatarEntities(true);
return;
}
QString continueMessage = "Your wearables will not display on this domain. Continue?";
_confirmConnectWithoutAvatarEntitiesDialog = OffscreenUi::asyncQuestion("Continue Without Wearables", continueMessage,
QMessageBox::Yes | QMessageBox::No);
if (_confirmConnectWithoutAvatarEntitiesDialog->getDialogItem()) {
QObject::connect(_confirmConnectWithoutAvatarEntitiesDialog, &ModalDialogListener::response, this, [=](QVariant answer) {
QObject::disconnect(_confirmConnectWithoutAvatarEntitiesDialog, &ModalDialogListener::response, this, nullptr);
_confirmConnectWithoutAvatarEntitiesDialog = nullptr;
bool shouldConnect = (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes);
DependencyManager::get<NodeList>()->getDomainHandler().setCanConnectWithoutAvatarEntities(shouldConnect);
});
}
}
void Application::createLoginDialog() {
const glm::vec3 LOGIN_DIMENSIONS { 0.89f, 0.5f, 0.01f };
const auto OFFSET = glm::vec2(0.7f, -0.1f);
auto cameraPosition = _myCamera.getPosition();
auto cameraOrientation = _myCamera.getOrientation();
auto upVec = getMyAvatar()->getWorldOrientation() * Vectors::UNIT_Y;
auto headLookVec = (cameraOrientation * Vectors::FRONT);
// DEFAULT_DPI / tablet scale percentage
const float DPI = 31.0f / (75.0f / 100.0f);
auto offset = headLookVec * OFFSET.x;
auto position = (cameraPosition + offset) + (upVec * OFFSET.y);
EntityItemProperties properties;
properties.setType(EntityTypes::Web);
properties.setName("LoginDialogEntity");
properties.setSourceUrl(LOGIN_DIALOG.toString());
properties.setPosition(position);
properties.setRotation(cameraOrientation);
properties.setDimensions(LOGIN_DIMENSIONS);
properties.setPrimitiveMode(PrimitiveMode::SOLID);
properties.getGrab().setGrabbable(false);
properties.setIgnorePickIntersection(false);
properties.setAlpha(1.0f);
properties.setDpi(DPI);
properties.setVisible(true);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
_loginDialogID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
auto keyboard = DependencyManager::get<Keyboard>().data();
if (!keyboard->getAnchorID().isNull() && !_loginDialogID.isNull()) {
auto keyboardLocalOffset = cameraOrientation * glm::vec3(-0.4f * getMyAvatar()->getSensorToWorldScale(), -0.3f, 0.2f);
EntityItemProperties properties;
properties.setPosition(position + keyboardLocalOffset);
properties.setRotation(cameraOrientation * Quaternions::Y_180);
entityScriptingInterface->editEntity(keyboard->getAnchorID(), properties);
keyboard->setResetKeyboardPositionOnRaise(false);
}
setKeyboardFocusEntity(_loginDialogID);
emit loginDialogFocusEnabled();
getApplicationCompositor().getReticleInterface()->setAllowMouseCapture(false);
getApplicationCompositor().getReticleInterface()->setVisible(false);
if (!_loginStateManager.isSetUp()) {
_loginStateManager.setUp();
}
}
void Application::updateLoginDialogPosition() {
const float LOOK_AWAY_THRESHOLD_ANGLE = 70.0f;
const auto OFFSET = glm::vec2(0.7f, -0.1f);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_POSITION;
auto properties = entityScriptingInterface->getEntityPropertiesInternal(_loginDialogID, desiredProperties, false);
auto positionVec = properties.getPosition();
auto cameraPositionVec = _myCamera.getPosition();
auto cameraOrientation = cancelOutRollAndPitch(_myCamera.getOrientation());
auto headLookVec = (cameraOrientation * Vectors::FRONT);
auto entityToHeadVec = positionVec - cameraPositionVec;
auto pointAngle = (glm::acos(glm::dot(glm::normalize(entityToHeadVec), glm::normalize(headLookVec))) * 180.0f / PI);
auto upVec = getMyAvatar()->getWorldOrientation() * Vectors::UNIT_Y;
auto offset = headLookVec * OFFSET.x;
auto newPositionVec = (cameraPositionVec + offset) + (upVec * OFFSET.y);
bool outOfBounds = glm::distance(positionVec, cameraPositionVec) > 1.0f;
if (pointAngle > LOOK_AWAY_THRESHOLD_ANGLE || outOfBounds) {
{
EntityItemProperties properties;
properties.setPosition(newPositionVec);
properties.setRotation(cameraOrientation);
entityScriptingInterface->editEntity(_loginDialogID, properties);
}
{
glm::vec3 keyboardLocalOffset = cameraOrientation * glm::vec3(-0.4f * getMyAvatar()->getSensorToWorldScale(), -0.3f, 0.2f);
glm::quat keyboardOrientation = cameraOrientation * glm::quat(glm::radians(glm::vec3(-30.0f, 180.0f, 0.0f)));
EntityItemProperties properties;
properties.setPosition(newPositionVec + keyboardLocalOffset);
properties.setRotation(keyboardOrientation);
entityScriptingInterface->editEntity(DependencyManager::get<Keyboard>()->getAnchorID(), properties);
}
}
}
void Application::createAvatarInputsBar() {
const glm::vec3 LOCAL_POSITION { 0.0, 0.0, -1.0 };
// DEFAULT_DPI / tablet scale percentage
const float DPI = 31.0f / (75.0f / 100.0f);
EntityItemProperties properties;
properties.setType(EntityTypes::Web);
properties.setName("AvatarInputsBarEntity");
properties.setSourceUrl(AVATAR_INPUTS_BAR_QML.toString());
properties.setParentID(getMyAvatar()->getSelfID());
properties.setParentJointIndex(getMyAvatar()->getJointIndex("_CAMERA_MATRIX"));
properties.setPosition(LOCAL_POSITION);
properties.setLocalRotation(Quaternions::IDENTITY);
//properties.setDimensions(LOGIN_DIMENSIONS);
properties.setPrimitiveMode(PrimitiveMode::SOLID);
properties.getGrab().setGrabbable(false);
properties.setIgnorePickIntersection(false);
properties.setAlpha(1.0f);
properties.setDpi(DPI);
properties.setVisible(true);
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
_avatarInputsBarID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
}
void Application::destroyAvatarInputsBar() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (!_avatarInputsBarID.isNull()) {
entityScriptingInterface->deleteEntity(_avatarInputsBarID);
}
}
void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const QString& name) const {
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet(SYSTEM_TABLET);
auto hmd = DependencyManager::get<HMDScriptingInterface>();
bool onTablet = false;
if (!tablet->getToolbarMode()) {
onTablet = tablet->pushOntoStack(tabletUrl);
if (onTablet) {
toggleTabletUI(true);
}
} else {
#if !defined(DISABLE_QML)
getOffscreenUI()->show(widgetUrl, name);
#endif
}
}
void Application::loadDialog() {
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_glWidget, tr("Open Script"),
getPreviousScriptLocation(),
tr("JavaScript Files (*.js)"));
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
const QString& response = answer.toString();
if (!response.isEmpty() && QFile(response).exists()) {
setPreviousScriptLocation(QFileInfo(response).absolutePath());
DependencyManager::get<ScriptEngines>()->loadScript(response, true, false, false, true); // Don't load from cache
}
});
}
void Application::loadScriptURLDialog() const {
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_NONE, "Open and Run Script", "Script URL");
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
const QString& newScript = response.toString();
if (QUrl(newScript).scheme() == "atp") {
OffscreenUi::asyncWarning("Error Loading Script", "Cannot load client script over ATP");
} else if (!newScript.isEmpty()) {
DependencyManager::get<ScriptEngines>()->loadScript(newScript.trimmed());
}
});
}
void Application::toggleLogDialog() {
#ifndef ANDROID_APP_QUEST_INTERFACE
if (getLoginDialogPoppedUp()) {
return;
}
if (!_logDialog) {
bool keepOnTop =_keepLogWindowOnTop.get();
#ifdef Q_OS_WIN
_logDialog = new LogDialog(keepOnTop ? qApp->getWindow() : nullptr, getLogger());
#elif !defined(Q_OS_ANDROID)
_logDialog = new LogDialog(nullptr, getLogger());
if (keepOnTop) {
Qt::WindowFlags flags = _logDialog->windowFlags() | Qt::Tool;
_logDialog->setWindowFlags(flags);
}
#else
Q_UNUSED(keepOnTop)
#endif
}
if (_logDialog->isVisible()) {
_logDialog->hide();
} else {
_logDialog->show();
}
#endif
}
void Application::recreateLogWindow(int keepOnTop) {
_keepLogWindowOnTop.set(keepOnTop != 0);
if (_logDialog) {
bool toggle = _logDialog->isVisible();
_logDialog->close();
_logDialog = nullptr;
if (toggle) {
toggleLogDialog();
}
}
}
void Application::toggleEntityScriptServerLogDialog() {
if (! _entityScriptServerLogDialog) {
_entityScriptServerLogDialog = new EntityScriptServerLogDialog(nullptr);
}
if (_entityScriptServerLogDialog->isVisible()) {
_entityScriptServerLogDialog->hide();
} else {
_entityScriptServerLogDialog->show();
}
}
void Application::showAssetServerWidget(QString filePath) {
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets() || getLoginDialogPoppedUp()) {
return;
}
static const QUrl url { "hifi/AssetServer.qml" };
auto startUpload = [=](QQmlContext* context, QObject* newObject){
if (!filePath.isEmpty()) {
emit uploadRequest(filePath);
}
};
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
auto hmd = DependencyManager::get<HMDScriptingInterface>();
if (tablet->getToolbarMode()) {
getOffscreenUI()->show(url, "AssetServer", startUpload);
} else {
if (!hmd->getShouldShowTablet() && !isHMDMode()) {
getOffscreenUI()->show(url, "AssetServer", startUpload);
} else {
static const QUrl url("qrc:///qml/hifi/dialogs/TabletAssetServer.qml");
if (!tablet->isPathLoaded(url)) {
tablet->pushOntoStack(url);
}
}
}
startUpload(nullptr, nullptr);
}
void Application::loadAddAvatarBookmarkDialog() const {
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
}
void Application::loadAvatarBrowser() const {
auto tablet = dynamic_cast<TabletProxy*>(DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"));
// construct the url to the marketplace item
QString url = MetaverseAPI::getCurrentMetaverseServerURL().toString() + "/marketplace?category=avatars";
QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js";
tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH);
DependencyManager::get<HMDScriptingInterface>()->openTablet();
}
void Application::showLoginScreen() {
#if !defined(DISABLE_QML)
auto accountManager = DependencyManager::get<AccountManager>();
auto dialogsManager = DependencyManager::get<DialogsManager>();
if (!accountManager->isLoggedIn()) {
if (!isHMDMode()) {
auto toolbar = DependencyManager::get<ToolbarScriptingInterface>()->getToolbar("com.highfidelity.interface.toolbar.system");
toolbar->writeProperty("visible", false);
}
_loginDialogPoppedUp = true;
dialogsManager->showLoginDialog();
emit loginDialogFocusEnabled();
QJsonObject loginData = {};
loginData["action"] = "login dialog popped up";
UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData);
_window->setWindowTitle("Overte");
} else {
resumeAfterLoginDialogActionTaken();
}
_loginDialogPoppedUp = !accountManager->isLoggedIn();
#else
resumeAfterLoginDialogActionTaken();
#endif
}
void Application::showHelp() {
static const QString HAND_CONTROLLER_NAME_VIVE = "vive";
static const QString HAND_CONTROLLER_NAME_OCULUS_TOUCH = "oculus";
static const QString HAND_CONTROLLER_NAME_WINDOWS_MR = "windowsMR";
static const QString VIVE_PLUGIN_NAME = "HTC Vive";
static const QString OCULUS_RIFT_PLUGIN_NAME = "Oculus Rift";
static const QString WINDOWS_MR_PLUGIN_NAME = "WindowsMR";
static const QString TAB_KEYBOARD_MOUSE = "kbm";
static const QString TAB_GAMEPAD = "gamepad";
static const QString TAB_HAND_CONTROLLERS = "handControllers";
QString handControllerName;
QString defaultTab = TAB_KEYBOARD_MOUSE;
if (PluginUtils::isHMDAvailable(WINDOWS_MR_PLUGIN_NAME)) {
defaultTab = TAB_HAND_CONTROLLERS;
handControllerName = HAND_CONTROLLER_NAME_WINDOWS_MR;
} else if (PluginUtils::isHMDAvailable(VIVE_PLUGIN_NAME)) {
defaultTab = TAB_HAND_CONTROLLERS;
handControllerName = HAND_CONTROLLER_NAME_VIVE;
} else if (PluginUtils::isHMDAvailable(OCULUS_RIFT_PLUGIN_NAME)) {
if (PluginUtils::isOculusTouchControllerAvailable()) {
defaultTab = TAB_HAND_CONTROLLERS;
handControllerName = HAND_CONTROLLER_NAME_OCULUS_TOUCH;
} else if (PluginUtils::isXboxControllerAvailable()) {
defaultTab = TAB_GAMEPAD;
} else {
defaultTab = TAB_KEYBOARD_MOUSE;
}
} else if (PluginUtils::isXboxControllerAvailable()) {
defaultTab = TAB_GAMEPAD;
} else {
defaultTab = TAB_KEYBOARD_MOUSE;
}
QUrlQuery queryString;
queryString.addQueryItem("handControllerName", handControllerName);
queryString.addQueryItem("defaultTab", defaultTab);
TabletProxy* tablet = dynamic_cast<TabletProxy*>(DependencyManager::get<TabletScriptingInterface>()->getTablet(SYSTEM_TABLET));
tablet->gotoWebScreen(PathUtils::resourcesUrl() + INFO_HELP_PATH + "?" + queryString.toString());
DependencyManager::get<HMDScriptingInterface>()->openTablet();
//InfoView::show(INFO_HELP_PATH, false, queryString.toString());
}
void Application::updateSystemTabletMode() {
if (_settingsLoaded && !getLoginDialogPoppedUp()) {
qApp->setProperty(hifi::properties::HMD, isHMDMode());
if (isHMDMode()) {
DependencyManager::get<TabletScriptingInterface>()->setToolbarMode(getHmdTabletBecomesToolbarSetting());
} else {
DependencyManager::get<TabletScriptingInterface>()->setToolbarMode(getDesktopTabletBecomesToolbarSetting());
}
}
}
void Application::captureMouseChanged(bool captureMouse) {
_captureMouse = captureMouse;
if (_captureMouse) {
_glWidget->setCursor(QCursor(Qt::BlankCursor));
} else {
_mouseCaptureTarget = QPointF(NAN, NAN);
_glWidget->unsetCursor();
}
}
void Application::toggleOverlays() {
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::Overlays, !menu->isOptionChecked(MenuOption::Overlays));
}
void Application::setOverlaysVisible(bool visible) {
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::Overlays, visible);
}
void Application::addAssetToWorldMessageClose() {
// Clear messages, e.g., if Interface is being closed or domain changes.
/*
Call if user manually closes message box.
Call if domain changes.
Call if application is shutting down.
Stop timers.
Close the message box if open.
Clear lists.
*/
_addAssetToWorldInfoTimer.stop();
_addAssetToWorldErrorTimer.stop();
if (_addAssetToWorldMessageBox) {
disconnect(_addAssetToWorldMessageBox);
_addAssetToWorldMessageBox->setVisible(false);
_addAssetToWorldMessageBox->deleteLater();
_addAssetToWorldMessageBox = nullptr;
}
_addAssetToWorldInfoKeys.clear();
_addAssetToWorldInfoMessages.clear();
}
void Application::loadLODToolsDialog() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !isHMDMode())) {
auto dialogsManager = DependencyManager::get<DialogsManager>();
dialogsManager->lodTools();
} else {
tablet->pushOntoStack("hifi/dialogs/TabletLODTools.qml");
}
}
void Application::loadEntityStatisticsDialog() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !isHMDMode())) {
auto dialogsManager = DependencyManager::get<DialogsManager>();
dialogsManager->octreeStatsDetails();
} else {
tablet->pushOntoStack("hifi/dialogs/TabletEntityStatistics.qml");
}
}
void Application::loadDomainConnectionDialog() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !isHMDMode())) {
auto dialogsManager = DependencyManager::get<DialogsManager>();
dialogsManager->showDomainConnectionDialog();
} else {
tablet->pushOntoStack("hifi/dialogs/TabletDCDialog.qml");
}
}
void Application::showScriptLogs() {
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/debugging/debugWindow.js");
DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString());
}
void Application::setPreferredCursor(const QString& cursorName) {
qCDebug(interfaceapp) << "setPreferredCursor" << cursorName;
if (_displayPlugin && _displayPlugin->isHmd()) {
_preferredCursor.set(cursorName.isEmpty() ? DEFAULT_CURSOR_NAME : cursorName);
} else {
_preferredCursor.set(cursorName.isEmpty() ? Cursor::Manager::getIconName(Cursor::Icon::SYSTEM) : cursorName);
}
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
}
void Application::showVRKeyboardForHudUI(bool show) {
if (show) {
DependencyManager::get<Keyboard>()->setRaised(true, true);
} else {
DependencyManager::get<Keyboard>()->setRaised(false);
}
}
void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
Stats::show();
AnimStats::show();
auto surfaceContext = getOffscreenUI()->getSurfaceContext();
surfaceContext->setContextProperty("Stats", Stats::getInstance());
surfaceContext->setContextProperty("AnimStats", AnimStats::getInstance());
#if !defined(Q_OS_ANDROID)
auto offscreenUi = getOffscreenUI();
auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
offscreenUi->show(qml, "AvatarInputsBar");
#endif
}
void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
auto engine = surfaceContext->engine();
// in Qt 5.10.0 there is already an "Audio" object in the QML context
// though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface"
surfaceContext->setContextProperty("AudioScriptingInterface", DependencyManager::get<AudioScriptingInterface>().data());
surfaceContext->setContextProperty("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
surfaceContext->setContextProperty("AudioScope", DependencyManager::get<AudioScope>().data());
surfaceContext->setContextProperty("Controller", DependencyManager::get<controller::ScriptingInterface>().data());
surfaceContext->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
surfaceContext->setContextProperty("Performance", new PerformanceScriptingInterface());
_fileDownload = new ArchiveDownloadInterface(engine);
surfaceContext->setContextProperty("File", _fileDownload);
connect(_fileDownload, &ArchiveDownloadInterface::unzipResult, this, &Application::handleUnzip);
surfaceContext->setContextProperty("MyAvatar", getMyAvatar().get());
surfaceContext->setContextProperty("Messages", DependencyManager::get<MessagesClient>().data());
surfaceContext->setContextProperty("Recording", DependencyManager::get<RecordingScriptingInterface>().data());
surfaceContext->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
surfaceContext->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
surfaceContext->setContextProperty("FrameTimings", &_graphicsEngine->_frameTimingsScriptingInterface);
surfaceContext->setContextProperty("Rates", new RatesScriptingInterface(this));
surfaceContext->setContextProperty("TREE_SCALE", TREE_SCALE);
// FIXME Quat and Vec3 won't work with QJSEngine used by QML
surfaceContext->setContextProperty("Quat", new Quat());
surfaceContext->setContextProperty("Vec3", new Vec3());
surfaceContext->setContextProperty("Uuid", new ScriptUUID());
surfaceContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
surfaceContext->setContextProperty("Keyboard", DependencyManager::get<KeyboardScriptingInterface>().data());
surfaceContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
surfaceContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
surfaceContext->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
surfaceContext->setContextProperty("Camera", &_myCamera);
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
surfaceContext->setContextProperty("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data());
#endif
surfaceContext->setContextProperty("Overlays", &_overlays);
surfaceContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
surfaceContext->setContextProperty("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("Settings", new QMLSettingsScriptingInterface(surfaceContext));
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
// Caches
surfaceContext->setContextProperty("AnimationCache", DependencyManager::get<AnimationCacheScriptingInterface>().data());
surfaceContext->setContextProperty("TextureCache", DependencyManager::get<TextureCacheScriptingInterface>().data());
surfaceContext->setContextProperty("MaterialCache", DependencyManager::get<MaterialCacheScriptingInterface>().data());
surfaceContext->setContextProperty("ModelCache", DependencyManager::get<ModelCacheScriptingInterface>().data());
surfaceContext->setContextProperty("SoundCache", DependencyManager::get<SoundCacheScriptingInterface>().data());
surfaceContext->setContextProperty("InputConfiguration", DependencyManager::get<InputConfiguration>().data());
surfaceContext->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
surfaceContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
surfaceContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
surfaceContext->setContextProperty("Render", RenderScriptingInterface::getInstance());
surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance());
surfaceContext->setContextProperty("Workload", _gameWorkload._engine->getConfiguration().get());
surfaceContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
surfaceContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
surfaceContext->setContextProperty("About", AboutUtil::getInstance());
surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); // Deprecated
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get<ResourceRequestObserver>().data());
surfaceContext->setContextProperty("ExternalResource", ExternalResource::getInstance());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
}
_window->setMenuBar(new Menu());
}
void Application::showDesktop() {}
void Application::notifyPacketVersionMismatch() {
if (!_notifiedPacketVersionMismatchThisDomain && !isInterstitialMode()) {
_notifiedPacketVersionMismatchThisDomain = true;
QString message = "The location you are visiting is running an incompatible server version.\n";
message += "Content may not display properly.";
OffscreenUi::asyncWarning("", message);
}
}
bool Application::askToSetAvatarUrl(const QString& url) {
QUrl realUrl(url);
if (realUrl.isLocalFile()) {
OffscreenUi::asyncWarning("", "You can not use local files for avatar components.");
return false;
}
// Download the FST file, to attempt to determine its model type
QVariantHash fstMapping = FSTReader::downloadMapping(url);
FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping);
QString modelName = fstMapping["name"].toString();
QString modelLicense = fstMapping["license"].toString();
bool agreeToLicense = true; // assume true
//create set avatar callback
auto setAvatar = [=] (QString url, QString modelName) {
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Set Avatar",
"Would you like to use '" + modelName + "' for your avatar?",
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
bool ok = (QMessageBox::Ok == static_cast<QMessageBox::StandardButton>(answer.toInt()));
if (ok) {
getMyAvatar()->useFullAvatarURL(url, modelName);
emit fullAvatarURLChanged(url, modelName);
} else {
qCDebug(interfaceapp) << "Declined to use the avatar";
}
});
};
if (!modelLicense.isEmpty()) {
// word wrap the license text to fit in a reasonable shaped message box.
const int MAX_CHARACTERS_PER_LINE = 90;
modelLicense = simpleWordWrap(modelLicense, MAX_CHARACTERS_PER_LINE);
ModalDialogListener* dlg = OffscreenUi::asyncQuestion("Avatar Usage License",
modelLicense + "\nDo you agree to these terms?",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
QObject::connect(dlg, &ModalDialogListener::response, this, [=, &agreeToLicense] (QVariant answer) {
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
agreeToLicense = (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes);
if (agreeToLicense) {
switch (modelType) {
case FSTReader::HEAD_AND_BODY_MODEL: {
setAvatar(url, modelName);
break;
}
default:
OffscreenUi::asyncWarning("", modelName + "Does not support a head and body as required.");
break;
}
} else {
qCDebug(interfaceapp) << "Declined to agree to avatar license";
}
//auto offscreenUi = getOffscreenUI();
});
} else {
setAvatar(url, modelName);
}
return true;
}
bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
QString shortName = scriptFilenameOrURL;
QUrl scriptURL { scriptFilenameOrURL };
if (scriptURL.host().endsWith(NetworkingConstants::HF_MARKETPLACE_CDN_HOSTNAME)) {
int startIndex = shortName.lastIndexOf('/') + 1;
int endIndex = shortName.lastIndexOf('?');
shortName = shortName.mid(startIndex, endIndex - startIndex);
}
#ifdef DISABLE_QML
DependencyManager::get<ScriptEngines>()->loadScript(scriptFilenameOrURL);
#else
QString message = "Would you like to run this script:\n" + shortName;
ModalDialogListener* dlg = OffscreenUi::asyncQuestion(getWindow(), "Run Script", message,
QMessageBox::Yes | QMessageBox::No);
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
const QString& fileName = scriptFilenameOrURL;
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
qCDebug(interfaceapp) << "Chose to run the script: " << fileName;
DependencyManager::get<ScriptEngines>()->loadScript(fileName);
} else {
qCDebug(interfaceapp) << "Declined to run the script";
}
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
});
#endif
return true;
}
bool Application::askToReplaceDomainContent(const QString& url) {
QString methodDetails;
const int MAX_CHARACTERS_PER_LINE = 90;
if (DependencyManager::get<NodeList>()->getThisNodeCanReplaceContent()) {
QUrl originURL { url };
if (originURL.host().endsWith(NetworkingConstants::HF_MARKETPLACE_CDN_HOSTNAME)) {
// Create a confirmation dialog when this call is made
static const QString infoText = simpleWordWrap("Your domain's content will be replaced with a new content set. "
"If you want to save what you have now, create a backup before proceeding. For more information about backing up "
"and restoring content, visit the documentation page at: ", MAX_CHARACTERS_PER_LINE) +
"\nhttps://docs.overte.org/host/maintain-domain/backup-domain.html";
ModalDialogListener* dig = OffscreenUi::asyncQuestion("Are you sure you want to replace this domain's content set?",
infoText, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
QObject::connect(dig, &ModalDialogListener::response, this, [=] (QVariant answer) {
QString details;
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
// Given confirmation, send request to domain server to replace content
replaceDomainContent(url, QString());
details = "SuccessfulRequestToReplaceContent";
} else {
details = "UserDeclinedToReplaceContent";
}
QJsonObject messageProperties = {
{ "status", details },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
QObject::disconnect(dig, &ModalDialogListener::response, this, nullptr);
});
} else {
methodDetails = "ContentSetDidNotOriginateFromMarketplace";
QJsonObject messageProperties = {
{ "status", methodDetails },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
}
} else {
methodDetails = "UserDoesNotHavePermissionToReplaceContent";
static const QString warningMessage = simpleWordWrap("The domain owner must enable 'Replace Content' "
"permissions for you in this domain's server settings before you can continue.", MAX_CHARACTERS_PER_LINE);
OffscreenUi::asyncWarning("You do not have permissions to replace domain content", warningMessage,
QMessageBox::Ok, QMessageBox::Ok);
QJsonObject messageProperties = {
{ "status", methodDetails },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
}
return true;
}
void Application::onDismissedLoginDialog() {
_loginDialogPoppedUp = false;
auto keyboard = DependencyManager::get<Keyboard>().data();
keyboard->setResetKeyboardPositionOnRaise(true);
if (!_loginDialogID.isNull()) {
DependencyManager::get<EntityScriptingInterface>()->deleteEntity(_loginDialogID);
_loginDialogID = QUuid();
_loginStateManager.tearDown();
}
resumeAfterLoginDialogActionTaken();
}
bool Application::initMenu() {
_isMenuInitialized = false;
qApp->getWindow()->menuBar();
return true;
}
void Application::pauseUntilLoginDetermined() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "pauseUntilLoginDetermined");
return;
}
auto myAvatar = getMyAvatar();
_previousAvatarTargetScale = myAvatar->getTargetScale();
_previousAvatarSkeletonModel = myAvatar->getSkeletonModelURL().toString();
myAvatar->setTargetScale(1.0f);
myAvatar->setSkeletonModelURLFromScript(myAvatar->defaultFullAvatarModelUrl().toString());
myAvatar->setEnableMeshVisible(false);
_controllerScriptingInterface->disableMapping(STANDARD_TO_ACTION_MAPPING_NAME);
{
auto userInputMapper = DependencyManager::get<UserInputMapper>();
if (userInputMapper->loadMapping(NO_MOVEMENT_MAPPING_JSON)) {
_controllerScriptingInterface->enableMapping(NO_MOVEMENT_MAPPING_NAME);
}
}
const auto& nodeList = DependencyManager::get<NodeList>();
// save interstitial mode setting until resuming.
_interstitialModeEnabled = nodeList->getDomainHandler().getInterstitialModeEnabled();
nodeList->getDomainHandler().setInterstitialModeEnabled(false);
auto menu = Menu::getInstance();
menu->getMenu("Edit")->setVisible(false);
menu->getMenu("View")->setVisible(false);
menu->getMenu("Navigate")->setVisible(false);
menu->getMenu("Settings")->setVisible(false);
_developerMenuVisible = menu->getMenu("Developer")->isVisible();
menu->setIsOptionChecked(MenuOption::Stats, false);
if (_developerMenuVisible) {
menu->getMenu("Developer")->setVisible(false);
}
_previousCameraMode = _myCamera.getMode();
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON_LOOK_AT);
cameraModeChanged();
// disconnect domain handler.
nodeList->getDomainHandler().disconnect("Pause until login determined");
// From now on, it's permissible to call resumeAfterLoginDialogActionTaken()
_resumeAfterLoginDialogActionTaken_SafeToRun = true;
if (_resumeAfterLoginDialogActionTaken_WasPostponed) {
// resumeAfterLoginDialogActionTaken() was already called, but it aborted. Now it's safe to call it again.
resumeAfterLoginDialogActionTaken();
}
}
void Application::resumeAfterLoginDialogActionTaken() {
if (QThread::currentThread() != qApp->thread()) {
QMetaObject::invokeMethod(this, "resumeAfterLoginDialogActionTaken");
return;
}
if (!_resumeAfterLoginDialogActionTaken_SafeToRun) {
_resumeAfterLoginDialogActionTaken_WasPostponed = true;
return;
}
#if !defined(DISABLE_QML)
if (!isHMDMode() && getDesktopTabletBecomesToolbarSetting()) {
auto toolbar = DependencyManager::get<ToolbarScriptingInterface>()->getToolbar("com.highfidelity.interface.toolbar.system");
toolbar->writeProperty("visible", true);
} else {
getApplicationCompositor().getReticleInterface()->setAllowMouseCapture(true);
getApplicationCompositor().getReticleInterface()->setVisible(true);
}
updateSystemTabletMode();
#endif
{
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->unloadMapping(NO_MOVEMENT_MAPPING_JSON);
_controllerScriptingInterface->disableMapping(NO_MOVEMENT_MAPPING_NAME);
}
auto myAvatar = getMyAvatar();
myAvatar->setTargetScale(_previousAvatarTargetScale);
myAvatar->setSkeletonModelURLFromScript(_previousAvatarSkeletonModel);
myAvatar->setEnableMeshVisible(true);
_controllerScriptingInterface->enableMapping(STANDARD_TO_ACTION_MAPPING_NAME);
const auto& nodeList = DependencyManager::get<NodeList>();
nodeList->getDomainHandler().setInterstitialModeEnabled(_interstitialModeEnabled);
{
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
// this will force the model the look at the correct directory (weird order of operations issue)
scriptEngines->reloadLocalFiles();
// if the --scripts command-line argument was used.
if (_overrideDefaultScriptsLocation && _defaultScriptsLocation.exists()) {
scriptEngines->loadDefaultScripts();
scriptEngines->defaultScriptsLocationOverridden(true);
} else {
scriptEngines->loadScripts();
}
}
auto accountManager = DependencyManager::get<AccountManager>();
auto addressManager = DependencyManager::get<AddressManager>();
// restart domain handler.
nodeList->getDomainHandler().resetting();
QVariant testProperty = property(hifi::properties::TEST);
if (testProperty.isValid()) {
const auto testScript = property(hifi::properties::TEST).toUrl();
// Set last parameter to exit interface when the test script finishes, if so requested
DependencyManager::get<ScriptEngines>()->loadScript(testScript, false, false, false, false, _quitWhenFinished);
// This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
if (!_urlParam.isEmpty()) {
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); });
}
} else {
auto reply = SandboxUtils::getStatus();
connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); });
}
auto menu = Menu::getInstance();
menu->getMenu("Edit")->setVisible(true);
menu->getMenu("View")->setVisible(true);
menu->getMenu("Navigate")->setVisible(true);
menu->getMenu("Settings")->setVisible(true);
menu->getMenu("Developer")->setVisible(_developerMenuVisible);
_myCamera.setMode(_previousCameraMode);
cameraModeChanged();
_startUpFinished = true;
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE);
}
QSharedPointer<OffscreenUi> Application::getOffscreenUI() {
#if !defined(DISABLE_QML)
return DependencyManager::get<OffscreenUi>();
#else
return nullptr;
#endif
}
void Application::updateDialogs(float deltaTime) const {
PerformanceTimer perfTimer("updateDialogs");
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateDialogs()");
auto dialogsManager = DependencyManager::get<DialogsManager>();
QPointer<OctreeStatsDialog> octreeStatsDialog = dialogsManager->getOctreeStatsDialog();
if (octreeStatsDialog) {
octreeStatsDialog->update();
}
}
void Application::maybeToggleMenuVisible(QMouseEvent* event) const {
#ifndef Q_OS_MAC
// If in full screen, and our main windows menu bar is hidden, and we're close to the top of the QMainWindow
// then show the menubar.
if (_window->isFullScreen()) {
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
static const int MENU_TOGGLE_AREA = 10;
if (!menuBar->isVisible()) {
if (event->pos().y() <= MENU_TOGGLE_AREA) {
menuBar->setVisible(true);
}
} else {
if (event->pos().y() > MENU_TOGGLE_AREA) {
menuBar->setVisible(false);
}
}
}
}
#endif
}
void Application::toggleTabletUI(bool shouldOpen) const {
auto hmd = DependencyManager::get<HMDScriptingInterface>();
if (!(shouldOpen && hmd->getShouldShowTablet())) {
auto HMD = DependencyManager::get<HMDScriptingInterface>();
HMD->toggleShouldShowTablet();
if (!HMD->getShouldShowTablet()) {
DependencyManager::get<Keyboard>()->setRaised(false);
_window->activateWindow();
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet(SYSTEM_TABLET);
tablet->unfocus();
}
}
}
bool Application::shouldCaptureMouse() const {
return _captureMouse && _glWidget->isActiveWindow() && !ui::Menu::isSomeSubmenuShown();
}
void Application::checkChangeCursor() {
QMutexLocker locker(&_changeCursorLock);
if (_cursorNeedsChanging) {
#ifdef Q_OS_MAC
auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
#else
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
// window menu, which is a pain, so only hide it for the GL surface
auto cursorTarget = _glWidget;
#endif
cursorTarget->setCursor(_desiredCursor);
_cursorNeedsChanging = false;
}
}
void Application::addAssetToWorldInfo(QString modelName, QString infoText) {
// Displays the most recent info message, subject to being overridden by error messages.
if (_aboutToQuit) {
return;
}
/*
Cancel info timer if running.
If list has an entry for modelName, delete it (just one).
Append modelName, infoText to list.
Display infoText in message box unless an error is being displayed (i.e., error timer is running).
Show message box if not already visible.
*/
_addAssetToWorldInfoTimer.stop();
addAssetToWorldInfoClear(modelName);
_addAssetToWorldInfoKeys.append(modelName);
_addAssetToWorldInfoMessages.append(infoText);
if (!_addAssetToWorldErrorTimer.isActive()) {
if (!_addAssetToWorldMessageBox) {
_addAssetToWorldMessageBox = getOffscreenUI()->createMessageBox(OffscreenUi::ICON_INFORMATION,
"Downloading Model", "", QMessageBox::NoButton, QMessageBox::NoButton);
connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed()));
}
_addAssetToWorldMessageBox->setProperty("text", "\n" + infoText);
_addAssetToWorldMessageBox->setVisible(true);
}
}
void Application::addAssetToWorldInfoClear(QString modelName) {
// Clears modelName entry from message list without affecting message currently displayed.
if (_aboutToQuit) {
return;
}
/*
Delete entry for modelName from list.
*/
auto index = _addAssetToWorldInfoKeys.indexOf(modelName);
if (index > -1) {
_addAssetToWorldInfoKeys.removeAt(index);
_addAssetToWorldInfoMessages.removeAt(index);
}
}
void Application::addAssetToWorldInfoDone(QString modelName) {
// Continues to display this message if the latest for a few seconds, then deletes it and displays the next latest.
if (_aboutToQuit) {
return;
}
/*
Delete entry for modelName from list.
(Re)start the info timer to update message box. ... onAddAssetToWorldInfoTimeout()
*/
addAssetToWorldInfoClear(modelName);
_addAssetToWorldInfoTimer.start();
}
void Application::addAssetToWorldError(QString modelName, QString errorText) {
// Displays the most recent error message for a few seconds.
if (_aboutToQuit) {
return;
}
/*
If list has an entry for modelName, delete it.
Display errorText in message box.
Show message box if not already visible.
(Re)start error timer. ... onAddAssetToWorldErrorTimeout()
*/
addAssetToWorldInfoClear(modelName);
if (!_addAssetToWorldMessageBox) {
_addAssetToWorldMessageBox = getOffscreenUI()->createMessageBox(OffscreenUi::ICON_INFORMATION,
"Downloading Model", "", QMessageBox::NoButton, QMessageBox::NoButton);
connect(_addAssetToWorldMessageBox, SIGNAL(destroyed()), this, SLOT(onAssetToWorldMessageBoxClosed()));
}
_addAssetToWorldMessageBox->setProperty("text", "\n" + errorText);
_addAssetToWorldMessageBox->setVisible(true);
_addAssetToWorldErrorTimer.start();
}