}} bool Application::nearbyEntitiesAreReadyForPhysics() { // this is used to avoid the following scenario: // A table has some items sitting on top of it. The items are at rest, meaning they aren't active in bullet. // Someone logs in close to the table. They receive information about the items on the table before they // receive information about the table. The items are very close to the avatar's capsule, so they become // activated in bullet. This causes them to fall to the floor, because the table's shape isn't yet in bullet. EntityTreePointer entityTree = getEntities()->getTree(); if (!entityTree) { return false; } QVector<EntityItemPointer> entities; entityTree->withReadLock([&] { AABox box(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); entityTree->findEntities(box, entities); }); // For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply // a heuristic to try to decide when we actually know about all of the nearby entities. uint32_t nearbyCount = entities.size(); if (nearbyCount == _nearbyEntitiesCountAtLastPhysicsCheck) { _nearbyEntitiesStabilityCount++; } else { _nearbyEntitiesStabilityCount = 0; } _nearbyEntitiesCountAtLastPhysicsCheck = nearbyCount; const uint32_t MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT = 3; if (_nearbyEntitiesStabilityCount >= MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT) { // We've seen the same number of nearby entities for several stats packets in a row. assume we've got all // the local entities. foreach (EntityItemPointer entity, entities) { if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); return false; } } return true; } return false;} int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) { // But, also identify the sender, and keep track of the contained jurisdiction root for this server // parse the incoming stats datas stick it in a temporary object for now, while we // determine which server it belongs to int statsMessageLength = 0; const QUuid& nodeUUID = sendingNode->getUUID(); // now that we know the node ID, let's add these stats to the stats for that node... _octreeServerSceneStats.withWriteLock([&] { OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID]; statsMessageLength = octreeStats.unpackFromPacket(message); if (octreeStats.isFullScene()) { _fullSceneReceivedCounter++; } // see if this is the first we've heard of this node... NodeToJurisdictionMap* jurisdiction = nullptr; QString serverType; if (sendingNode->getType() == NodeType::EntityServer) { jurisdiction = &_entityServerJurisdictions; serverType = "Entity"; } bool found = false; jurisdiction->withReadLock([&] { if (jurisdiction->find(nodeUUID) != jurisdiction->end()) { found = true; return; } VoxelPositionSize rootDetails; voxelDetailsForCode(octreeStats.getJurisdictionRoot().get(), rootDetails); qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]", qPrintable(serverType), (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); }); if (!found) { // store jurisdiction details for later use // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the // details from the OctreeSceneStats to construct the JurisdictionMap JurisdictionMap jurisdictionMap; jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); jurisdiction->withWriteLock([&] { (*jurisdiction)[nodeUUID] = jurisdictionMap; }); } }); return statsMessageLength;} void Application::packetSent(quint64 length) {} void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) { // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); entityScriptingInterface->setPacketSender(&_entityEditSender); entityScriptingInterface->setEntityTree(getEntities()->getTree()); // AvatarManager has some custom types AvatarManager::registerMetaTypes(scriptEngine); scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this)); // hook our avatar and avatar hash map object into this script engine scriptEngine->registerGlobalObject("MyAvatar", getMyAvatar()); qScriptRegisterMetaType(scriptEngine, audioListenModeToScriptValue, audioListenModeFromScriptValue); scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarManager>().data()); scriptEngine->registerGlobalObject("Camera", &_myCamera); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) scriptEngine->registerGlobalObject("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data());#endif ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface(); scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); connect(scriptEngine, &ScriptEngine::finished, clipboardScriptable, &ClipboardScriptingInterface::deleteLater); scriptEngine->registerGlobalObject("Overlays", &_overlays); qScriptRegisterMetaType(scriptEngine, OverlayPropertyResultToScriptValue, OverlayPropertyResultFromScriptValue); qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data()); scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data()); scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter, "Window"); // register `location` on the global object. scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); // Caches scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data()); scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCache>().data()); scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCache>().data()); scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data()); scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface); scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance()); qScriptRegisterMetaType(scriptEngine, DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue); scriptEngine->registerGlobalObject("FaceTracker", DependencyManager::get<DdeFaceTracker>().data()); scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data()); scriptEngine->registerGlobalObject("UndoStack", &_undoStackScriptingInterface); scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data()); scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data()); scriptEngine->registerGlobalObject("HMD", DependencyManager::get<HMDScriptingInterface>().data()); scriptEngine->registerFunction("HMD", "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0); scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0); scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());} bool Application::canAcceptURL(const QString& urlString) const { QUrl url(urlString); if (urlString.startsWith(HIFI_URL_SCHEME)) { return true; } QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions); QString lowerPath = url.path().toLower(); while (i.hasNext()) { i.next(); if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { return true; } } return false;} bool Application::acceptURL(const QString& urlString, bool defaultUpload) { if (urlString.startsWith(HIFI_URL_SCHEME)) { // this is a hifi URL - have the AddressManager handle it QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString", Qt::AutoConnection, Q_ARG(const QString&, urlString)); return true; } QUrl url(urlString); QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions); QString lowerPath = url.path().toLower(); while (i.hasNext()) { i.next(); if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { AcceptURLMethod method = i.value(); return (this->*method)(urlString); } } if (defaultUpload) { toggleAssetServerWidget(urlString); } return defaultUpload;} void Application::setSessionUUID(const QUuid& sessionUUID) const { Physics::setSessionUUID(sessionUUID);} bool Application::askToSetAvatarUrl(const QString& url) { QUrl realUrl(url); if (realUrl.isLocalFile()) { OffscreenUi::warning("", "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 agreeToLicence = true; // assume true if (!modelLicense.isEmpty()) { // word wrap the licence text to fit in a reasonable shaped message box. const int MAX_CHARACTERS_PER_LINE = 90; modelLicense = simpleWordWrap(modelLicense, MAX_CHARACTERS_PER_LINE); agreeToLicence = QMessageBox::Yes == OffscreenUi::question("Avatar Usage License", modelLicense + "\nDo you argee to these terms?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); } bool ok = false; if (!agreeToLicence) { qCDebug(interfaceapp) << "Declined to agree to avatar license: " << url; } else { switch (modelType) { case FSTReader::HEAD_AND_BODY_MODEL: ok = QMessageBox::Ok == OffscreenUi::question("Set Avatar", "Would you like to use '" + modelName + "' for your avatar?", QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok); break; default: OffscreenUi::warning("", modelName + "Does not support a head and body as required."); break; } } if (ok) { getMyAvatar()->useFullAvatarURL(url, modelName); emit fullAvatarURLChanged(url, modelName); } else { qCDebug(interfaceapp) << "Declined to use the avatar: " << url; } return true;} bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { QMessageBox::StandardButton reply; QString message = "Would you like to run this script:\n" + scriptFilenameOrURL; reply = OffscreenUi::question(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { qCDebug(interfaceapp) << "Chose to run the script: " << scriptFilenameOrURL; DependencyManager::get<ScriptEngines>()->loadScript(scriptFilenameOrURL); } else { qCDebug(interfaceapp) << "Declined to run the script: " << scriptFilenameOrURL; } return true;} bool Application::askToWearAvatarAttachmentUrl(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); int requestNumber = ++_avatarAttachmentRequest; connect(reply, &QNetworkReply::finished, [this, reply, url, requestNumber]() { if (requestNumber != _avatarAttachmentRequest) { // this request has been superseded by another more recent request reply->deleteLater(); return; } QNetworkReply::NetworkError networkError = reply->error(); if (networkError == QNetworkReply::NoError) { // download success QByteArray contents = reply->readAll(); QJsonParseError jsonError; auto doc = QJsonDocument::fromJson(contents, &jsonError); if (jsonError.error == QJsonParseError::NoError) { auto jsonObject = doc.object(); // retrieve optional name field from JSON QString name = tr("Unnamed Attachment"); auto nameValue = jsonObject.value("name"); if (nameValue.isString()) { name = nameValue.toString(); } // display confirmation dialog if (displayAvatarAttachmentConfirmationDialog(name)) { // add attachment to avatar auto myAvatar = getMyAvatar(); assert(myAvatar); auto attachmentDataVec = myAvatar->getAttachmentData(); AttachmentData attachmentData; attachmentData.fromJson(jsonObject); attachmentDataVec.push_back(attachmentData); myAvatar->setAttachmentData(attachmentDataVec); } else { qCDebug(interfaceapp) << "User declined to wear the avatar attachment: " << url; } } else { // json parse error auto avatarAttachmentParseErrorString = tr("Error parsing attachment JSON from url: \"%1\""); displayAvatarAttachmentWarning(avatarAttachmentParseErrorString.arg(url)); } } else { // download failure auto avatarAttachmentDownloadErrorString = tr("Error downloading attachment JSON from url: \"%1\""); displayAvatarAttachmentWarning(avatarAttachmentDownloadErrorString.arg(url)); } reply->deleteLater(); }); return true;} void Application::displayAvatarAttachmentWarning(const QString& message) const { auto avatarAttachmentWarningTitle = tr("Avatar Attachment Failure"); OffscreenUi::warning(avatarAttachmentWarningTitle, message);} bool Application::displayAvatarAttachmentConfirmationDialog(const QString& name) const { auto avatarAttachmentConfirmationTitle = tr("Avatar Attachment Confirmation"); auto avatarAttachmentConfirmationMessage = tr("Would you like to wear '%1' on your avatar?").arg(name); auto reply = OffscreenUi::question(avatarAttachmentConfirmationTitle, avatarAttachmentConfirmationMessage, QMessageBox::Ok | QMessageBox::Cancel); if (QMessageBox::Ok == reply) { return true; } else { return false; }} void Application::toggleRunningScriptsWidget() const { static const QUrl url("hifi/dialogs/RunningScripts.qml"); DependencyManager::get<OffscreenUi>()->show(url, "RunningScripts"); //if (_runningScriptsWidget->isVisible()) { // if (_runningScriptsWidget->hasFocus()) { // _runningScriptsWidget->hide(); // } else { // _runningScriptsWidget->raise(); // setActiveWindow(_runningScriptsWidget); // _runningScriptsWidget->setFocus(); // } //} else { // _runningScriptsWidget->show(); // _runningScriptsWidget->setFocus(); //}} void Application::toggleAssetServerWidget(QString filePath) { if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) { return; } static const QUrl url { "AssetServer.qml" }; auto startUpload = [=](QQmlContext* context, QObject* newObject){ if (!filePath.isEmpty()) { emit uploadRequest(filePath); } }; DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload); startUpload(nullptr, nullptr);} void Application::packageModel() { ModelPackager::package();} void Application::openUrl(const QUrl& url) const { if (!url.isEmpty()) { if (url.scheme() == HIFI_URL_SCHEME) { DependencyManager::get<AddressManager>()->handleLookupString(url.toString()); } else { // address manager did not handle - ask QDesktopServices to handle QDesktopServices::openUrl(url); } }} void Application::loadDialog() { auto scriptEngines = DependencyManager::get<ScriptEngines>(); QString fileNameString = OffscreenUi::getOpenFileName( _glWidget, tr("Open Script"), getPreviousScriptLocation(), tr("JavaScript Files (*.js)")); if (!fileNameString.isEmpty() && QFile(fileNameString).exists()) { setPreviousScriptLocation(QFileInfo(fileNameString).absolutePath()); DependencyManager::get<ScriptEngines>()->loadScript(fileNameString, true, false, false, true); // Don't load from cache }} QString Application::getPreviousScriptLocation() { QString result = _previousScriptLocation.get(); return result;} void Application::setPreviousScriptLocation(const QString& location) { _previousScriptLocation.set(location);} void Application::loadScriptURLDialog() const { auto newScript = OffscreenUi::getText(nullptr, "Open and Run Script", "Script URL"); if (!newScript.isEmpty()) { DependencyManager::get<ScriptEngines>()->loadScript(newScript); }} void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(_glWidget, getLogger()); } if (_logDialog->isVisible()) { _logDialog->hide(); } else { _logDialog->show(); }} void Application::takeSnapshot() { QMediaPlayer* player = new QMediaPlayer(); QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav"); player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); player->play(); QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); auto accountManager = DependencyManager::get<AccountManager>(); if (!accountManager->isLoggedIn()) { return; } DependencyManager::get<OffscreenUi>()->load("hifi/dialogs/SnapshotShareDialog.qml", [=](QQmlContext*, QObject* dialog) { dialog->setProperty("source", QUrl::fromLocalFile(fileName)); connect(dialog, SIGNAL(uploadSnapshot(const QString& snapshot)), this, SLOT(uploadSnapshot(const QString& snapshot))); });} float Application::getRenderResolutionScale() const { if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) { return 1.0f;Lines 4501–5000 of 5476. Previous lines Load more
} } bool Application::nearbyEntitiesAreReadyForPhysics() { // this is used to avoid the following scenario: // A table has some items sitting on top of it. The items are at rest, meaning they aren't active in bullet. // Someone logs in close to the table. They receive information about the items on the table before they // receive information about the table. The items are very close to the avatar's capsule, so they become // activated in bullet. This causes them to fall to the floor, because the table's shape isn't yet in bullet. EntityTreePointer entityTree = getEntities()->getTree(); if (!entityTree) { return false; } QVector<EntityItemPointer> entities; entityTree->withReadLock([&] { AABox box(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); entityTree->findEntities(box, entities); }); // For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply // a heuristic to try to decide when we actually know about all of the nearby entities. uint32_t nearbyCount = entities.size(); if (nearbyCount == _nearbyEntitiesCountAtLastPhysicsCheck) { _nearbyEntitiesStabilityCount++; } else { _nearbyEntitiesStabilityCount = 0; } _nearbyEntitiesCountAtLastPhysicsCheck = nearbyCount; const uint32_t MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT = 3; if (_nearbyEntitiesStabilityCount >= MINIMUM_NEARBY_ENTITIES_STABILITY_COUNT) { // We've seen the same number of nearby entities for several stats packets in a row. assume we've got all // the local entities. foreach (EntityItemPointer entity, entities) { if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); return false; } } return true; } return false; } int Application::processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode) { // But, also identify the sender, and keep track of the contained jurisdiction root for this server // parse the incoming stats datas stick it in a temporary object for now, while we // determine which server it belongs to int statsMessageLength = 0; const QUuid& nodeUUID = sendingNode->getUUID(); // now that we know the node ID, let's add these stats to the stats for that node... _octreeServerSceneStats.withWriteLock([&] { OctreeSceneStats& octreeStats = _octreeServerSceneStats[nodeUUID]; statsMessageLength = octreeStats.unpackFromPacket(message); if (octreeStats.isFullScene()) { _fullSceneReceivedCounter++; } // see if this is the first we've heard of this node... NodeToJurisdictionMap* jurisdiction = nullptr; QString serverType; if (sendingNode->getType() == NodeType::EntityServer) { jurisdiction = &_entityServerJurisdictions; serverType = "Entity"; } bool found = false; jurisdiction->withReadLock([&] { if (jurisdiction->find(nodeUUID) != jurisdiction->end()) { found = true; return; } VoxelPositionSize rootDetails; voxelDetailsForCode(octreeStats.getJurisdictionRoot().get(), rootDetails); qCDebug(interfaceapp, "stats from new %s server... [%f, %f, %f, %f]", qPrintable(serverType), (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); }); if (!found) { // store jurisdiction details for later use // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it // but OctreeSceneStats thinks it's just returning a reference to its contents. So we need to make a copy of the // details from the OctreeSceneStats to construct the JurisdictionMap JurisdictionMap jurisdictionMap; jurisdictionMap.copyContents(octreeStats.getJurisdictionRoot(), octreeStats.getJurisdictionEndNodes()); jurisdiction->withWriteLock([&] { (*jurisdiction)[nodeUUID] = jurisdictionMap; }); } }); return statsMessageLength; } void Application::packetSent(quint64 length) { } void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) { // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); entityScriptingInterface->setPacketSender(&_entityEditSender); entityScriptingInterface->setEntityTree(getEntities()->getTree()); // AvatarManager has some custom types AvatarManager::registerMetaTypes(scriptEngine); scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this)); // hook our avatar and avatar hash map object into this script engine scriptEngine->registerGlobalObject("MyAvatar", getMyAvatar()); qScriptRegisterMetaType(scriptEngine, audioListenModeToScriptValue, audioListenModeFromScriptValue); scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarManager>().data()); scriptEngine->registerGlobalObject("Camera", &_myCamera); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) scriptEngine->registerGlobalObject("SpeechRecognizer", DependencyManager::get<SpeechRecognizer>().data()); #endif ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface(); scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); connect(scriptEngine, &ScriptEngine::finished, clipboardScriptable, &ClipboardScriptingInterface::deleteLater); scriptEngine->registerGlobalObject("Overlays", &_overlays); qScriptRegisterMetaType(scriptEngine, OverlayPropertyResultToScriptValue, OverlayPropertyResultFromScriptValue); qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data()); scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data()); scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter, "Window"); // register `location` on the global object. scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); // Caches scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data()); scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get<TextureCache>().data()); scriptEngine->registerGlobalObject("ModelCache", DependencyManager::get<ModelCache>().data()); scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data()); scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DialogsManager", _dialogsManagerScriptingInterface); scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance()); qScriptRegisterMetaType(scriptEngine, DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue); scriptEngine->registerGlobalObject("FaceTracker", DependencyManager::get<DdeFaceTracker>().data()); scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data()); scriptEngine->registerGlobalObject("UndoStack", &_undoStackScriptingInterface); scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data()); scriptEngine->registerGlobalObject("Paths", DependencyManager::get<PathUtils>().data()); scriptEngine->registerGlobalObject("HMD", DependencyManager::get<HMDScriptingInterface>().data()); scriptEngine->registerFunction("HMD", "getHUDLookAtPosition2D", HMDScriptingInterface::getHUDLookAtPosition2D, 0); scriptEngine->registerFunction("HMD", "getHUDLookAtPosition3D", HMDScriptingInterface::getHUDLookAtPosition3D, 0); scriptEngine->registerGlobalObject("Scene", DependencyManager::get<SceneScriptingInterface>().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data()); scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data()); } bool Application::canAcceptURL(const QString& urlString) const { QUrl url(urlString); if (urlString.startsWith(HIFI_URL_SCHEME)) { return true; } QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions); QString lowerPath = url.path().toLower(); while (i.hasNext()) { i.next(); if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { return true; } } return false; } bool Application::acceptURL(const QString& urlString, bool defaultUpload) { if (urlString.startsWith(HIFI_URL_SCHEME)) { // this is a hifi URL - have the AddressManager handle it QMetaObject::invokeMethod(DependencyManager::get<AddressManager>().data(), "handleLookupString", Qt::AutoConnection, Q_ARG(const QString&, urlString)); return true; } QUrl url(urlString); QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions); QString lowerPath = url.path().toLower(); while (i.hasNext()) { i.next(); if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) { AcceptURLMethod method = i.value(); return (this->*method)(urlString); } } if (defaultUpload) { toggleAssetServerWidget(urlString); } return defaultUpload; } void Application::setSessionUUID(const QUuid& sessionUUID) const { Physics::setSessionUUID(sessionUUID); } bool Application::askToSetAvatarUrl(const QString& url) { QUrl realUrl(url); if (realUrl.isLocalFile()) { OffscreenUi::warning("", "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 agreeToLicence = true; // assume true if (!modelLicense.isEmpty()) { // word wrap the licence text to fit in a reasonable shaped message box. const int MAX_CHARACTERS_PER_LINE = 90; modelLicense = simpleWordWrap(modelLicense, MAX_CHARACTERS_PER_LINE); agreeToLicence = QMessageBox::Yes == OffscreenUi::question("Avatar Usage License", modelLicense + "\nDo you argee to these terms?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); } bool ok = false; if (!agreeToLicence) { qCDebug(interfaceapp) << "Declined to agree to avatar license: " << url; } else { switch (modelType) { case FSTReader::HEAD_AND_BODY_MODEL: ok = QMessageBox::Ok == OffscreenUi::question("Set Avatar", "Would you like to use '" + modelName + "' for your avatar?", QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok); break; default: OffscreenUi::warning("", modelName + "Does not support a head and body as required."); break; } } if (ok) { getMyAvatar()->useFullAvatarURL(url, modelName); emit fullAvatarURLChanged(url, modelName); } else { qCDebug(interfaceapp) << "Declined to use the avatar: " << url; } return true; } bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { QMessageBox::StandardButton reply; QString message = "Would you like to run this script:\n" + scriptFilenameOrURL; reply = OffscreenUi::question(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { qCDebug(interfaceapp) << "Chose to run the script: " << scriptFilenameOrURL; DependencyManager::get<ScriptEngines>()->loadScript(scriptFilenameOrURL); } else { qCDebug(interfaceapp) << "Declined to run the script: " << scriptFilenameOrURL; } return true; } bool Application::askToWearAvatarAttachmentUrl(const QString& url) { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); int requestNumber = ++_avatarAttachmentRequest; connect(reply, &QNetworkReply::finished, [this, reply, url, requestNumber]() { if (requestNumber != _avatarAttachmentRequest) { // this request has been superseded by another more recent request reply->deleteLater(); return; } QNetworkReply::NetworkError networkError = reply->error(); if (networkError == QNetworkReply::NoError) { // download success QByteArray contents = reply->readAll(); QJsonParseError jsonError; auto doc = QJsonDocument::fromJson(contents, &jsonError); if (jsonError.error == QJsonParseError::NoError) { auto jsonObject = doc.object(); // retrieve optional name field from JSON QString name = tr("Unnamed Attachment"); auto nameValue = jsonObject.value("name"); if (nameValue.isString()) { name = nameValue.toString(); } // display confirmation dialog if (displayAvatarAttachmentConfirmationDialog(name)) { // add attachment to avatar auto myAvatar = getMyAvatar(); assert(myAvatar); auto attachmentDataVec = myAvatar->getAttachmentData(); AttachmentData attachmentData; attachmentData.fromJson(jsonObject); attachmentDataVec.push_back(attachmentData); myAvatar->setAttachmentData(attachmentDataVec); } else { qCDebug(interfaceapp) << "User declined to wear the avatar attachment: " << url; } } else { // json parse error auto avatarAttachmentParseErrorString = tr("Error parsing attachment JSON from url: \"%1\""); displayAvatarAttachmentWarning(avatarAttachmentParseErrorString.arg(url)); } } else { // download failure auto avatarAttachmentDownloadErrorString = tr("Error downloading attachment JSON from url: \"%1\""); displayAvatarAttachmentWarning(avatarAttachmentDownloadErrorString.arg(url)); } reply->deleteLater(); }); return true; } void Application::displayAvatarAttachmentWarning(const QString& message) const { auto avatarAttachmentWarningTitle = tr("Avatar Attachment Failure"); OffscreenUi::warning(avatarAttachmentWarningTitle, message); } bool Application::displayAvatarAttachmentConfirmationDialog(const QString& name) const { auto avatarAttachmentConfirmationTitle = tr("Avatar Attachment Confirmation"); auto avatarAttachmentConfirmationMessage = tr("Would you like to wear '%1' on your avatar?").arg(name); auto reply = OffscreenUi::question(avatarAttachmentConfirmationTitle, avatarAttachmentConfirmationMessage, QMessageBox::Ok | QMessageBox::Cancel); if (QMessageBox::Ok == reply) { return true; } else { return false; } } void Application::toggleRunningScriptsWidget() const { static const QUrl url("hifi/dialogs/RunningScripts.qml"); DependencyManager::get<OffscreenUi>()->show(url, "RunningScripts"); //if (_runningScriptsWidget->isVisible()) { // if (_runningScriptsWidget->hasFocus()) { // _runningScriptsWidget->hide(); // } else { // _runningScriptsWidget->raise(); // setActiveWindow(_runningScriptsWidget); // _runningScriptsWidget->setFocus(); // } //} else { // _runningScriptsWidget->show(); // _runningScriptsWidget->setFocus(); //} } void Application::toggleAssetServerWidget(QString filePath) { if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) { return; } static const QUrl url { "AssetServer.qml" }; auto startUpload = [=](QQmlContext* context, QObject* newObject){ if (!filePath.isEmpty()) { emit uploadRequest(filePath); } }; DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload); startUpload(nullptr, nullptr); } void Application::packageModel() { ModelPackager::package(); } void Application::openUrl(const QUrl& url) const { if (!url.isEmpty()) { if (url.scheme() == HIFI_URL_SCHEME) { DependencyManager::get<AddressManager>()->handleLookupString(url.toString()); } else { // address manager did not handle - ask QDesktopServices to handle QDesktopServices::openUrl(url); } } } void Application::loadDialog() { auto scriptEngines = DependencyManager::get<ScriptEngines>(); QString fileNameString = OffscreenUi::getOpenFileName( _glWidget, tr("Open Script"), getPreviousScriptLocation(), tr("JavaScript Files (*.js)")); if (!fileNameString.isEmpty() && QFile(fileNameString).exists()) { setPreviousScriptLocation(QFileInfo(fileNameString).absolutePath()); DependencyManager::get<ScriptEngines>()->loadScript(fileNameString, true, false, false, true); // Don't load from cache } } QString Application::getPreviousScriptLocation() { QString result = _previousScriptLocation.get(); return result; } void Application::setPreviousScriptLocation(const QString& location) { _previousScriptLocation.set(location); } void Application::loadScriptURLDialog() const { auto newScript = OffscreenUi::getText(nullptr, "Open and Run Script", "Script URL"); if (!newScript.isEmpty()) { DependencyManager::get<ScriptEngines>()->loadScript(newScript); } } void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(_glWidget, getLogger()); } if (_logDialog->isVisible()) { _logDialog->hide(); } else { _logDialog->show(); } } void Application::takeSnapshot() { QMediaPlayer* player = new QMediaPlayer(); QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav"); player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); player->play(); QString fileName = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot()); auto accountManager = DependencyManager::get<AccountManager>(); if (!accountManager->isLoggedIn()) { return; } DependencyManager::get<OffscreenUi>()->load("hifi/dialogs/SnapshotShareDialog.qml", [=](QQmlContext*, QObject* dialog) { dialog->setProperty("source", QUrl::fromLocalFile(fileName)); connect(dialog, SIGNAL(uploadSnapshot(const QString& snapshot)), this, SLOT(uploadSnapshot(const QString& snapshot))); }); } float Application::getRenderResolutionScale() const { if (Menu::getInstance()->isOptionChecked(MenuOption::RenderResolutionOne)) { return 1.0f;