1477 lines · 62.8 KB · C++
RawDownload
////  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-2025 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 <QStyle>#include <QStyleFactory>#if defined(Q_OS_WIN)#include <windows.h>#endif #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);} #ifdef USE_GLGLWidget* Application::getPrimaryWidget() {    return _primaryWidget;}#elseVKWidget* Application::getPrimaryWidget() {    return _primaryWidget;}#endif MainWindow* Application::getPrimaryWindow() {    return getWindow();} QOpenGLContext* Application::getPrimaryContext() {    return _primaryWidget->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.#ifdef USE_GL    auto window = qApp->getWindow();    window->activateWindow(); //VKTODO#endif} 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) {    _defaultMouseCaptureVR.set(value);    getApplicationCompositor().setEnableMouseCaptureVR(value);}bool Application::getMouseCaptureVR() {    return _defaultMouseCaptureVR.get();} 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, [=, 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(_primaryWidget, tr("Open Script"),                                                                 getPreviousScriptLocation(),                                                                 tr("JavaScript Files (*.js)"));

Lines 1–500 of 1477.