Merge pull request #11159 from vladest/modal_dialogs_async

Modal dialogs async
This commit is contained in:
Dante Ruiz 2017-09-26 11:07:42 -07:00 committed by GitHub
commit e397d49564
18 changed files with 965 additions and 372 deletions

View file

@ -980,7 +980,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(scriptEngines, &ScriptEngines::scriptLoadError,
scriptEngines, [](const QString& filename, const QString& error){
OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load.");
OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load.");
}, Qt::QueuedConnection);
#ifdef _WIN32
@ -1845,7 +1845,7 @@ void Application::domainConnectionRefused(const QString& reasonMessage, int reas
case DomainHandler::ConnectionRefusedReason::Unknown: {
QString message = "Unable to connect to the location you are visiting.\n";
message += reasonMessage;
OffscreenUi::warning("", message);
OffscreenUi::asyncWarning("", message);
break;
}
default:
@ -3751,7 +3751,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
DependencyManager::get<AddressManager>()->handleLookupString(snapshotData->getURL().toString());
}
} else {
OffscreenUi::warning("", "No location details were found in the file\n" +
OffscreenUi::asyncWarning("", "No location details were found in the file\n" +
snapshotPath + "\nTry dragging in an authentic Hifi snapshot.");
}
return true;
@ -6151,7 +6151,7 @@ void Application::setSessionUUID(const QUuid& sessionUUID) const {
bool Application::askToSetAvatarUrl(const QString& url) {
QUrl realUrl(url);
if (realUrl.isLocalFile()) {
OffscreenUi::warning("", "You can not use local files for avatar components.");
OffscreenUi::asyncWarning("", "You can not use local files for avatar components.");
return false;
}
@ -6163,41 +6163,55 @@ bool Application::askToSetAvatarUrl(const QString& url) {
QString modelName = fstMapping["name"].toString();
QString modelLicense = fstMapping["license"].toString();
bool agreeToLicence = true; // assume true
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: " << url;
}
});
};
if (!modelLicense.isEmpty()) {
// word wrap the licence text to fit in a reasonable shaped message box.
// 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);
agreeToLicence = QMessageBox::Yes == OffscreenUi::question("Avatar Usage License",
modelLicense + "\nDo you agree to these terms?",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
}
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);
bool ok = false;
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: " << url;
}
if (!agreeToLicence) {
qCDebug(interfaceapp) << "Declined to agree to avatar license: " << url;
//auto offscreenUi = DependencyManager::get<OffscreenUi>();
});
} 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;
setAvatar(url, modelName);
}
return true;
@ -6205,8 +6219,6 @@ bool Application::askToSetAvatarUrl(const QString& url) {
bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
QMessageBox::StandardButton reply;
QString shortName = scriptFilenameOrURL;
QUrl scriptURL { scriptFilenameOrURL };
@ -6218,15 +6230,20 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
}
QString message = "Would you like to run this script:\n" + shortName;
ModalDialogListener* dlg = OffscreenUi::asyncQuestion(getWindow(), "Run Script", message,
QMessageBox::Yes | QMessageBox::No);
reply = OffscreenUi::question(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: " << scriptFilenameOrURL;
}
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
});
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;
}
@ -6263,22 +6280,26 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
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;
}
auto avatarAttachmentConfirmationTitle = tr("Avatar Attachment Confirmation");
auto avatarAttachmentConfirmationMessage = tr("Would you like to wear '%1' on your avatar?").arg(name);
ModalDialogListener* dlg = OffscreenUi::asyncQuestion(avatarAttachmentConfirmationTitle,
avatarAttachmentConfirmationMessage,
QMessageBox::Ok | QMessageBox::Cancel);
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
// 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\"");
@ -6351,20 +6372,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
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;
}
OffscreenUi::asyncWarning(avatarAttachmentWarningTitle, message);
}
void Application::showDialog(const QUrl& widgetUrl, const QUrl& tabletUrl, const QString& name) const {
@ -6945,12 +6953,17 @@ void Application::openUrl(const QUrl& url) const {
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
}
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
}
});
}
QString Application::getPreviousScriptLocation() {
@ -6963,12 +6976,16 @@ void Application::setPreviousScriptLocation(const QString& location) {
}
void Application::loadScriptURLDialog() const {
QString newScript = OffscreenUi::getText(OffscreenUi::ICON_NONE, "Open and Run Script", "Script URL");
if (QUrl(newScript).scheme() == "atp") {
OffscreenUi::warning("Error Loading Script", "Cannot load client script over ATP");
} else if (!newScript.isEmpty()) {
DependencyManager::get<ScriptEngines>()->loadScript(newScript.trimmed());
}
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::loadLODToolsDialog() {
@ -7085,7 +7102,7 @@ void Application::notifyPacketVersionMismatch() {
QString message = "The location you are visiting is running an incompatible server version.\n";
message += "Content may not display properly.";
OffscreenUi::warning("", message);
OffscreenUi::asyncWarning("", message);
}
}
@ -7094,7 +7111,7 @@ void Application::checkSkeleton() const {
qCDebug(interfaceapp) << "MyAvatar model has no skeleton";
QString message = "Your selected avatar body has no skeleton.\n\nThe default body will be loaded...";
OffscreenUi::warning("", message);
OffscreenUi::asyncWarning("", message);
getMyAvatar()->useFullAvatarURL(AvatarData::defaultFullAvatarModelUrl(), DEFAULT_FULL_AVATAR_MODEL_NAME);
} else {

View file

@ -429,7 +429,6 @@ private slots:
bool askToWearAvatarAttachmentUrl(const QString& url);
void displayAvatarAttachmentWarning(const QString& message) const;
bool displayAvatarAttachmentConfirmationDialog(const QString& name) const;
bool askToReplaceDomainContent(const QString& url);

View file

@ -106,30 +106,30 @@ void AvatarBookmarks::changeToBookmarkedAvatar() {
}
void AvatarBookmarks::addBookmark() {
bool ok = false;
auto bookmarkName = OffscreenUi::getText(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString(), &ok);
if (!ok) {
return;
}
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString());
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
auto bookmarkName = response.toString();
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
const QVariant& avatarScale = myAvatar->getAvatarScale();
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
const QVariant& avatarScale = myAvatar->getAvatarScale();
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
QVariantMap *bookmark = new QVariantMap;
bookmark->insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
bookmark->insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark->insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
Bookmarks::addBookmarkToFile(bookmarkName, *bookmark);
});
// If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION
QVariantMap *bookmark = new QVariantMap;
bookmark->insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
bookmark->insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark->insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
Bookmarks::addBookmarkToFile(bookmarkName, *bookmark);
}
void AvatarBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {

View file

@ -58,20 +58,25 @@ void Bookmarks::addBookmarkToFile(const QString& bookmarkName, const QVariant& b
Menu* menubar = Menu::getInstance();
if (contains(bookmarkName)) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto duplicateBookmarkMessage = offscreenUi->createMessageBox(OffscreenUi::ICON_WARNING, "Duplicate Bookmark",
"The bookmark name you entered already exists in your list.",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
duplicateBookmarkMessage->setProperty("informativeText", "Would you like to overwrite it?");
auto result = offscreenUi->waitForMessageBoxResult(duplicateBookmarkMessage);
if (result != QMessageBox::Yes) {
return;
}
removeBookmarkFromMenu(menubar, bookmarkName);
}
ModalDialogListener* dlg = OffscreenUi::asyncWarning("Duplicate Bookmark",
"The bookmark name you entered already exists in your list.",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
dlg->setProperty("informativeText", "Would you like to overwrite it?");
QObject::connect(dlg, &ModalDialogListener::response, this, [=] (QVariant answer) {
QObject::disconnect(dlg, &ModalDialogListener::response, this, nullptr);
addBookmarkToMenu(menubar, bookmarkName, bookmark);
insert(bookmarkName, bookmark); // Overwrites any item with the same bookmarkName.
enableMenuItems(true);
if (QMessageBox::Yes == static_cast<QMessageBox::StandardButton>(answer.toInt())) {
removeBookmarkFromMenu(menubar, bookmarkName);
addBookmarkToMenu(menubar, bookmarkName, bookmark);
insert(bookmarkName, bookmark); // Overwrites any item with the same bookmarkName.
enableMenuItems(true);
}
});
} else {
addBookmarkToMenu(menubar, bookmarkName, bookmark);
insert(bookmarkName, bookmark); // Overwrites any item with the same bookmarkName.
enableMenuItems(true);
}
}
void Bookmarks::insert(const QString& name, const QVariant& bookmark) {

View file

@ -74,20 +74,21 @@ void LocationBookmarks::teleportToBookmark() {
}
void LocationBookmarks::addBookmark() {
bool ok = false;
auto bookmarkName = OffscreenUi::getText(OffscreenUi::ICON_PLACEMARK, "Bookmark Location", "Name", QString(), &ok);
if (!ok) {
return;
}
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Location", "Name", QString());
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
auto bookmarkName = response.toString();
auto addressManager = DependencyManager::get<AddressManager>();
QString bookmarkAddress = addressManager->currentAddress().toString();
Bookmarks::addBookmarkToFile(bookmarkName, bookmarkAddress);
bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
auto addressManager = DependencyManager::get<AddressManager>();
QString bookmarkAddress = addressManager->currentAddress().toString();
Bookmarks::addBookmarkToFile(bookmarkName, bookmarkAddress);
});
}
void LocationBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& address) {
@ -101,4 +102,4 @@ void LocationBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, co
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, teleportAction, name, 0, QAction::NoRole);
Bookmarks::sortActions(menubar, _bookmarksMenu);
}
}
}

View file

@ -79,7 +79,7 @@ bool ModelPackager::loadModel() {
if (_modelFile.completeSuffix().contains("fst")) {
QFile fst(_modelFile.filePath());
if (!fst.open(QFile::ReadOnly | QFile::Text)) {
OffscreenUi::warning(NULL,
OffscreenUi::asyncWarning(NULL,
QString("ModelPackager::loadModel()"),
QString("Could not open FST file %1").arg(_modelFile.filePath()),
QMessageBox::Ok);
@ -98,7 +98,7 @@ bool ModelPackager::loadModel() {
// open the fbx file
QFile fbx(_fbxInfo.filePath());
if (!_fbxInfo.exists() || !_fbxInfo.isFile() || !fbx.open(QIODevice::ReadOnly)) {
OffscreenUi::warning(NULL,
OffscreenUi::asyncWarning(NULL,
QString("ModelPackager::loadModel()"),
QString("Could not open FBX file %1").arg(_fbxInfo.filePath()),
QMessageBox::Ok);
@ -408,7 +408,7 @@ bool ModelPackager::copyTextures(const QString& oldDir, const QDir& newDir) {
}
if (!errors.isEmpty()) {
OffscreenUi::warning(nullptr, "ModelPackager::copyTextures()",
OffscreenUi::asyncWarning(nullptr, "ModelPackager::copyTextures()",
"Missing textures:" + errors);
qCDebug(interfaceapp) << "ModelPackager::copyTextures():" << errors;
return false;

View file

@ -201,7 +201,7 @@ void ModelPropertiesDialog::chooseTextureDirectory() {
return;
}
if (!directory.startsWith(_basePath)) {
OffscreenUi::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path.");
OffscreenUi::asyncWarning(NULL, "Invalid texture directory", "Texture directory must be child of base path.");
return;
}
_textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1));

View file

@ -42,117 +42,161 @@ static const QString COMPOUND_SHAPE_URL_KEY = "compoundShapeURL";
static const QString MESSAGE_BOX_TITLE = "ATP Asset Migration";
void ATPAssetMigrator::loadEntityServerFile() {
auto filename = OffscreenUi::getOpenFileName(_dialogParent, tr("Select an entity-server content file to migrate"), QString(), tr("Entity-Server Content (*.gz)"));
if (!filename.isEmpty()) {
qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename;
static const QString MIGRATION_CONFIRMATION_TEXT {
"The ATP Asset Migration process will scan the selected entity-server file,\nupload discovered resources to the"\
" current asset-server\nand then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\
" continue?\n\nMake sure you are connected to the right domain."
};
auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT,
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (button == QMessageBox::No) {
return;
}
// try to open the file at the given filename
QFile modelsFile { filename };
if (modelsFile.open(QIODevice::ReadOnly)) {
QByteArray compressedJsonData = modelsFile.readAll();
QByteArray jsonData;
if (!gunzip(compressedJsonData, jsonData)) {
OffscreenUi::warning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format.");
}
QJsonDocument modelsJSON = QJsonDocument::fromJson(jsonData);
_entitiesArray = modelsJSON.object()["Entities"].toArray();
for (auto jsonValue : _entitiesArray) {
QJsonObject entityObject = jsonValue.toObject();
QString modelURLString = entityObject.value(MODEL_URL_KEY).toString();
QString compoundURLString = entityObject.value(COMPOUND_SHAPE_URL_KEY).toString();
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_dialogParent, tr("Select an entity-server content file to migrate"),
QString(), tr("Entity-Server Content (*.gz)"));
for (int i = 0; i < 2; ++i) {
bool isModelURL = (i == 0);
quint8 replacementType = i;
auto migrationURLString = (isModelURL) ? modelURLString : compoundURLString;
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
const QString& filename = response.toString();
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
if (!filename.isEmpty()) {
qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename;
if (!migrationURLString.isEmpty()) {
QUrl migrationURL = QUrl(migrationURLString);
auto migrateResources = [=](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) {
auto request =
DependencyManager::get<ResourceManager>()->createResourceRequest(this, migrationURL);
if (!_ignoredUrls.contains(migrationURL)
&& (migrationURL.scheme() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS
|| migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == URL_SCHEME_FTP)) {
if (request) {
qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";
if (_pendingReplacements.contains(migrationURL)) {
// we already have a request out for this asset, just store the QJsonValueRef
// so we can do the hash replacement when the request comes back
_pendingReplacements.insert(migrationURL, { jsonValue, replacementType });
} else if (_uploadedAssets.contains(migrationURL)) {
// we already have a hash for this asset
// so just do the replacement immediately
if (isModelURL) {
entityObject[MODEL_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
} else {
entityObject[COMPOUND_SHAPE_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
}
// add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL
// to an ATP one once ready
_pendingReplacements.insert(migrationURL, { jsonValue, (isModelURL ? 0 : 1)});
jsonValue = entityObject;
} else if (wantsToMigrateResource(migrationURL)) {
auto request =
DependencyManager::get<ResourceManager>()->createResourceRequest(this, migrationURL);
connect(request, &ResourceRequest::finished, this, [=]() {
if (request->getResult() == ResourceRequest::Success) {
migrateResource(request);
} else {
++_errorCount;
_pendingReplacements.remove(migrationURL);
qWarning() << "Could not retrieve asset at" << migrationURL.toString();
if (request) {
qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";
checkIfFinished();
}
request->deleteLater();
});
// add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL
// to an ATP one once ready
_pendingReplacements.insert(migrationURL, { jsonValue, (isModelURL ? 0 : 1)});
connect(request, &ResourceRequest::finished, this, [=]() {
if (request->getResult() == ResourceRequest::Success) {
migrateResource(request);
} else {
++_errorCount;
_pendingReplacements.remove(migrationURL);
qWarning() << "Could not retrieve asset at" << migrationURL.toString();
checkIfFinished();
}
request->deleteLater();
});
request->send();
} else {
++_errorCount;
qWarning() << "Count not create request for asset at" << migrationURL.toString();
}
} else {
_ignoredUrls.insert(migrationURL);
}
}
}
request->send();
} else {
++_errorCount;
qWarning() << "Count not create request for asset at" << migrationURL.toString();
}
}
_doneReading = true;
};
static const QString MIGRATION_CONFIRMATION_TEXT {
"The ATP Asset Migration process will scan the selected entity-server file,\nupload discovered resources to the"\
" current asset-server\nand then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\
" continue?\n\nMake sure you are connected to the right domain."
};
ModalDialogListener* migrationConfirmDialog = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT,
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
QObject::connect(migrationConfirmDialog, &ModalDialogListener::response, this, [=] (QVariant answer) {
QObject::disconnect(migrationConfirmDialog, &ModalDialogListener::response, this, nullptr);
checkIfFinished();
} else {
OffscreenUi::warning(_dialogParent, "Error",
"There was a problem loading that entity-server file for ATP asset migration. Please try again");
if (QMessageBox::Yes == static_cast<QMessageBox::StandardButton>(answer.toInt())) {
// try to open the file at the given filename
QFile modelsFile { filename };
if (modelsFile.open(QIODevice::ReadOnly)) {
QByteArray compressedJsonData = modelsFile.readAll();
QByteArray jsonData;
if (!gunzip(compressedJsonData, jsonData)) {
OffscreenUi::asyncWarning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format.");
}
QJsonDocument modelsJSON = QJsonDocument::fromJson(jsonData);
_entitiesArray = modelsJSON.object()["Entities"].toArray();
for (auto jsonValue : _entitiesArray) {
QJsonObject entityObject = jsonValue.toObject();
QString modelURLString = entityObject.value(MODEL_URL_KEY).toString();
QString compoundURLString = entityObject.value(COMPOUND_SHAPE_URL_KEY).toString();
for (int i = 0; i < 2; ++i) {
bool isModelURL = (i == 0);
quint8 replacementType = i;
auto migrationURLString = (isModelURL) ? modelURLString : compoundURLString;
if (!migrationURLString.isEmpty()) {
QUrl migrationURL = QUrl(migrationURLString);
if (!_ignoredUrls.contains(migrationURL)
&& (migrationURL.scheme() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS
|| migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == URL_SCHEME_FTP)) {
if (_pendingReplacements.contains(migrationURL)) {
// we already have a request out for this asset, just store the QJsonValueRef
// so we can do the hash replacement when the request comes back
_pendingReplacements.insert(migrationURL, { jsonValue, replacementType });
} else if (_uploadedAssets.contains(migrationURL)) {
// we already have a hash for this asset
// so just do the replacement immediately
if (isModelURL) {
entityObject[MODEL_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
} else {
entityObject[COMPOUND_SHAPE_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
}
jsonValue = entityObject;
} else {
static bool hasAskedForCompleteMigration { false };
static bool wantsCompleteMigration { false };
if (!hasAskedForCompleteMigration) {
// this is the first resource migration - ask the user if they just want to migrate everything
static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\
"Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\
"Select \"No\" to be prompted for each discovered asset."
};
ModalDialogListener* migrationConfirmDialog1 = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE,
"Would you like to migrate the following resource?\n" + migrationURL.toString(),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
QObject::connect(migrationConfirmDialog1, &ModalDialogListener::response, this, [=] (QVariant answer) {
QObject::disconnect(migrationConfirmDialog1, &ModalDialogListener::response, this, nullptr);
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) ==
QMessageBox::Yes) {
wantsCompleteMigration = true;
migrateResources(migrationURL, jsonValue, isModelURL);
} else {
ModalDialogListener* migrationConfirmDialog2 = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT,
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
QObject::connect(migrationConfirmDialog2, &ModalDialogListener::response, this, [=] (QVariant answer) {
QObject::disconnect(migrationConfirmDialog2, &ModalDialogListener::response, this, nullptr);
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) ==
QMessageBox::Yes) {
migrateResources(migrationURL, jsonValue, isModelURL);
} else {
_ignoredUrls.insert(migrationURL);
}
});
}
});
hasAskedForCompleteMigration = true;
}
if (wantsCompleteMigration) {
migrateResources(migrationURL, jsonValue, isModelURL);
}
}
}
}
}
}
_doneReading = true;
checkIfFinished();
} else {
OffscreenUi::asyncWarning(_dialogParent, "Error",
"There was a problem loading that entity-server file for ATP asset migration. Please try again");
}
}
});
}
}
});
}
void ATPAssetMigrator::migrateResource(ResourceRequest* request) {
@ -248,9 +292,6 @@ void ATPAssetMigrator::checkIfFinished() {
// are we out of pending replacements? if so it is time to save the entity-server file
if (_doneReading && _pendingReplacements.empty()) {
saveEntityServerFile();
// reset after the attempted save, success or fail
reset();
}
}
@ -288,39 +329,43 @@ bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) {
void ATPAssetMigrator::saveEntityServerFile() {
// show a dialog to ask the user where they want to save the file
QString saveName = OffscreenUi::getSaveFileName(_dialogParent, "Save Migrated Entities File");
QFile saveFile { saveName };
if (saveFile.open(QIODevice::WriteOnly)) {
QJsonObject rootObject;
rootObject[ENTITIES_OBJECT_KEY] = _entitiesArray;
QJsonDocument newDocument { rootObject };
QByteArray jsonDataForFile;
if (gzip(newDocument.toJson(), jsonDataForFile, -1)) {
saveFile.write(jsonDataForFile);
saveFile.close();
ModalDialogListener* dlg = OffscreenUi::getSaveFileNameAsync(_dialogParent, "Save Migrated Entities File");
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
const QString& saveName = response.toString();
QFile saveFile { saveName };
QString infoMessage = QString("Your new entities file has been saved at\n%1.").arg(saveName);
if (saveFile.open(QIODevice::WriteOnly)) {
QJsonObject rootObject;
rootObject[ENTITIES_OBJECT_KEY] = _entitiesArray;
if (_errorCount > 0) {
infoMessage += QString("\nThere were %1 models that could not be migrated.\n").arg(_errorCount);
infoMessage += "Check the warnings in your log for details.\n";
infoMessage += "You can re-attempt migration on those models\nby restarting this process with the newly saved file.";
QJsonDocument newDocument { rootObject };
QByteArray jsonDataForFile;
if (gzip(newDocument.toJson(), jsonDataForFile, -1)) {
saveFile.write(jsonDataForFile);
saveFile.close();
QString infoMessage = QString("Your new entities file has been saved at\n%1.").arg(saveName);
if (_errorCount > 0) {
infoMessage += QString("\nThere were %1 models that could not be migrated.\n").arg(_errorCount);
infoMessage += "Check the warnings in your log for details.\n";
infoMessage += "You can re-attempt migration on those models\nby restarting this process with the newly saved file.";
}
OffscreenUi::asyncInformation(_dialogParent, "Success", infoMessage);
} else {
OffscreenUi::asyncWarning(_dialogParent, "Error", "Could not gzip JSON data for new entities file.");
}
OffscreenUi::information(_dialogParent, "Success", infoMessage);
} else {
OffscreenUi::warning(_dialogParent, "Error", "Could not gzip JSON data for new entities file.");
OffscreenUi::asyncWarning(_dialogParent, "Error",
QString("Could not open file at %1 to write new entities file to.").arg(saveName));
}
} else {
OffscreenUi::warning(_dialogParent, "Error",
QString("Could not open file at %1 to write new entities file to.").arg(saveName));
}
// reset after the attempted save, success or fail
reset();
});
}
void ATPAssetMigrator::reset() {

View file

@ -59,7 +59,7 @@ WindowScriptingInterface::WindowScriptingInterface() {
QUrl url(urlString);
emit svoImportRequested(url.url());
} else {
OffscreenUi::warning("Import SVO Error", "You need to be running edit.js to import entities.");
OffscreenUi::asyncWarning("Import SVO Error", "You need to be running edit.js to import entities.");
}
});
@ -103,7 +103,7 @@ void WindowScriptingInterface::raiseMainWindow() {
/// \param const QString& message message to display
/// \return QScriptValue::UndefinedValue
void WindowScriptingInterface::alert(const QString& message) {
OffscreenUi::warning("", message);
OffscreenUi::asyncWarning("", message);
}
/// Display a confirmation box with the options 'Yes' and 'No'
@ -123,6 +123,17 @@ QScriptValue WindowScriptingInterface::prompt(const QString& message, const QStr
return ok ? QScriptValue(result) : QScriptValue::NullValue;
}
/// Display a prompt with a text box
/// \param const QString& message message to display
/// \param const QString& defaultText default text in the text box
void WindowScriptingInterface::promptAsync(const QString& message, const QString& defaultText) {
ModalDialogListener* dlg = OffscreenUi::getTextAsync(nullptr, "", message, QLineEdit::Normal, defaultText);
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant result) {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
emit promptTextChanged(result.toString());
});
}
CustomPromptResult WindowScriptingInterface::customPrompt(const QVariant& config) {
CustomPromptResult result;
bool ok = false;
@ -191,8 +202,31 @@ QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QSt
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
}
/// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
void WindowScriptingInterface::browseDirAsync(const QString& title, const QString& directory) {
ensureReticleVisible();
QString path = directory;
if (path.isEmpty()) {
path = getPreviousBrowseLocation();
}
#ifndef Q_OS_WIN
path = fixupPathForMac(directory);
#endif
ModalDialogListener* dlg = OffscreenUi::getExistingDirectoryAsync(nullptr, title, path);
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
const QString& result = response.toString();
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
if (!result.isEmpty()) {
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
emit browseDirChanged(result);
});
}
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
@ -213,6 +247,31 @@ QScriptValue WindowScriptingInterface::browse(const QString& title, const QStrin
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
}
/// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
void WindowScriptingInterface::browseAsync(const QString& title, const QString& directory, const QString& nameFilter) {
ensureReticleVisible();
QString path = directory;
if (path.isEmpty()) {
path = getPreviousBrowseLocation();
}
#ifndef Q_OS_WIN
path = fixupPathForMac(directory);
#endif
ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(nullptr, title, path, nameFilter);
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
const QString& result = response.toString();
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
if (!result.isEmpty()) {
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
emit openFileChanged(result);
});
}
/// Display a save file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
@ -235,7 +294,32 @@ QScriptValue WindowScriptingInterface::save(const QString& title, const QString&
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
}
/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid
/// Display a save file dialog. If `directory` is an invalid file or directory the browser will start at the current
/// working directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the file browser at
/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
void WindowScriptingInterface::saveAsync(const QString& title, const QString& directory, const QString& nameFilter) {
ensureReticleVisible();
QString path = directory;
if (path.isEmpty()) {
path = getPreviousBrowseLocation();
}
#ifndef Q_OS_WIN
path = fixupPathForMac(directory);
#endif
ModalDialogListener* dlg = OffscreenUi::getSaveFileNameAsync(nullptr, title, path, nameFilter);
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
const QString& result = response.toString();
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
if (!result.isEmpty()) {
setPreviousBrowseLocation(QFileInfo(result).absolutePath());
}
emit saveFileChanged(result);
});
}
/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid
/// directory the browser will start at the root directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the asset browser at
@ -260,6 +344,35 @@ QScriptValue WindowScriptingInterface::browseAssets(const QString& title, const
return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
}
/// Display a select asset dialog that lets the user select an asset from the Asset Server. If `directory` is an invalid
/// directory the browser will start at the root directory.
/// \param const QString& title title of the window
/// \param const QString& directory directory to start the asset browser at
/// \param const QString& nameFilter filter to filter asset names by - see `QFileDialog`
void WindowScriptingInterface::browseAssetsAsync(const QString& title, const QString& directory, const QString& nameFilter) {
ensureReticleVisible();
QString path = directory;
if (path.isEmpty()) {
path = getPreviousBrowseAssetLocation();
}
if (path.left(1) != "/") {
path = "/" + path;
}
if (path.right(1) != "/") {
path = path + "/";
}
ModalDialogListener* dlg = OffscreenUi::getOpenAssetNameAsync(nullptr, title, path, nameFilter);
connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) {
const QString& result = response.toString();
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
if (!result.isEmpty()) {
setPreviousBrowseAssetLocation(QFileInfo(result).absolutePath());
}
emit assetsDirChanged(result);
});
}
void WindowScriptingInterface::showAssetServer(const QString& upload) {
QMetaObject::invokeMethod(qApp, "showAssetServerWidget", Qt::QueuedConnection, Q_ARG(QString, upload));
}

View file

@ -51,12 +51,17 @@ public slots:
void raiseMainWindow();
void alert(const QString& message = "");
QScriptValue confirm(const QString& message = "");
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
QScriptValue prompt(const QString& message, const QString& defaultText);
void promptAsync(const QString& message = "", const QString& defaultText = "");
CustomPromptResult customPrompt(const QVariant& config);
QScriptValue browseDir(const QString& title = "", const QString& directory = "");
void browseDirAsync(const QString& title = "", const QString& directory = "");
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void browseAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void saveAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void browseAssetsAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
void showAssetServer(const QString& upload = "");
QString checkVersion();
void copyToClipboard(const QString& text);
@ -89,6 +94,11 @@ signals:
void announcement(const QString& message);
void messageBoxClosed(int id, int button);
void browseDirChanged(QString browseDir);
void assetsDirChanged(QString assetsDir);
void saveFileChanged(QString filename);
void openFileChanged(QString filename);
void promptTextChanged(QString text);
// triggered when window size or position changes
void geometryChanged(QRect geometry);

View file

@ -73,11 +73,11 @@ void AddressBarDialog::loadForward() {
}
void AddressBarDialog::displayAddressOfflineMessage() {
OffscreenUi::critical("", "That user or place is currently offline");
OffscreenUi::asyncCritical("", "That user or place is currently offline");
}
void AddressBarDialog::displayAddressNotFoundMessage() {
OffscreenUi::critical("", "There is no address information for that user or place");
OffscreenUi::asyncCritical("", "There is no address information for that user or place");
}
void AddressBarDialog::observeShownChanged(bool visible) {

View file

@ -136,7 +136,7 @@ void EyeTracker::onStreamStarted() {
qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result);
// Display error dialog unless SMI SDK has already displayed an error message.
if (result != SMI_ERROR_HMD_NOT_SUPPORTED) {
OffscreenUi::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
}
} else {
qCDebug(interfaceapp) << "Eye Tracker: Started streaming";
@ -149,7 +149,7 @@ void EyeTracker::onStreamStarted() {
result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION);
if (result != SMI_RET_SUCCESS) {
qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result);
OffscreenUi::warning(nullptr, "Eye Tracker Error", "Error loading calibration"
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", "Error loading calibration"
+ smiReturnValueToString(result));
} else {
qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration";
@ -165,7 +165,7 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) {
int result = smi_setCallback(eyeTrackerCallback);
if (result != SMI_RET_SUCCESS) {
qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << smiReturnValueToString(result);
OffscreenUi::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", smiReturnValueToString(result));
} else {
_isInitialized = true;
}
@ -270,7 +270,7 @@ void EyeTracker::calibrate(int points) {
}
if (result != SMI_RET_SUCCESS) {
OffscreenUi::warning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result));
OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result));
}
}
#endif

View file

@ -91,6 +91,13 @@ QObject* OffscreenUi::getFlags() {
return offscreenFlags;
}
void OffscreenUi::removeModalDialog(QObject* modal) {
if (modal) {
_modalDialogListeners.removeOne(modal);
modal->deleteLater();
}
}
void OffscreenUi::create() {
OffscreenQmlSurface::create();
auto myContext = getSurfaceContext();
@ -145,45 +152,6 @@ bool OffscreenUi::isVisible(const QString& name) {
}
}
class ModalDialogListener : public QObject {
Q_OBJECT
friend class OffscreenUi;
protected:
ModalDialogListener(QQuickItem* dialog) : _dialog(dialog) {
if (!dialog) {
_finished = true;
return;
}
connect(_dialog, SIGNAL(destroyed()), this, SLOT(onDestroyed()));
}
~ModalDialogListener() {
if (_dialog) {
disconnect(_dialog);
}
}
virtual QVariant waitForResult() {
while (!_finished) {
QCoreApplication::processEvents();
}
return _result;
}
protected slots:
void onDestroyed() {
_finished = true;
disconnect(_dialog);
_dialog = nullptr;
}
protected:
QQuickItem* _dialog;
bool _finished { false };
QVariant _result;
};
class MessageBoxListener : public ModalDialogListener {
Q_OBJECT
@ -204,6 +172,9 @@ private slots:
void onSelected(int button) {
_result = button;
_finished = true;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
emit response(_result);
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
disconnect(_dialog);
}
};
@ -263,6 +234,25 @@ QMessageBox::StandardButton OffscreenUi::messageBox(Icon icon, const QString& ti
return static_cast<QMessageBox::StandardButton>(waitForMessageBoxResult(createMessageBox(icon, title, text, buttons, defaultButton)));
}
ModalDialogListener* OffscreenUi::asyncMessageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
if (QThread::currentThread() != thread()) {
ModalDialogListener* ret;
BLOCKING_INVOKE_METHOD(this, "asyncMessageBox",
Q_RETURN_ARG(ModalDialogListener*, ret),
Q_ARG(Icon, icon),
Q_ARG(QString, title),
Q_ARG(QString, text),
Q_ARG(QMessageBox::StandardButtons, buttons),
Q_ARG(QMessageBox::StandardButton, defaultButton));
return ret;
}
MessageBoxListener* messageBoxListener = new MessageBoxListener(createMessageBox(icon, title, text, buttons, defaultButton));
QObject* modalDialog = qobject_cast<QObject*>(messageBoxListener);
_modalDialogListeners.push_back(modalDialog);
return messageBoxListener;
}
QMessageBox::StandardButton OffscreenUi::critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_CRITICAL, title, text, buttons, defaultButton);
@ -271,15 +261,36 @@ QMessageBox::StandardButton OffscreenUi::information(const QString& title, const
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_INFORMATION, title, text, buttons, defaultButton);
}
ModalDialogListener* OffscreenUi::asyncCritical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_CRITICAL, title, text, buttons, defaultButton);
}
ModalDialogListener* OffscreenUi::asyncInformation(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_INFORMATION, title, text, buttons, defaultButton);
}
QMessageBox::StandardButton OffscreenUi::question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_QUESTION, title, text, buttons, defaultButton);
}
ModalDialogListener *OffscreenUi::asyncQuestion(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_QUESTION, title, text, buttons, defaultButton);
}
QMessageBox::StandardButton OffscreenUi::warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->messageBox(OffscreenUi::Icon::ICON_WARNING, title, text, buttons, defaultButton);
}
ModalDialogListener* OffscreenUi::asyncWarning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) {
return DependencyManager::get<OffscreenUi>()->asyncMessageBox(OffscreenUi::Icon::ICON_WARNING, title, text, buttons, defaultButton);
}
class InputDialogListener : public ModalDialogListener {
@ -296,6 +307,9 @@ class InputDialogListener : public ModalDialogListener {
private slots:
void onSelected(const QVariant& result) {
_result = result;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
emit response(_result);
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
_finished = true;
disconnect(_dialog);
}
@ -349,6 +363,31 @@ QVariant OffscreenUi::getCustomInfo(const Icon icon, const QString& title, const
return result;
}
ModalDialogListener* OffscreenUi::getTextAsync(const Icon icon, const QString& title, const QString& label, const QString& text) {
return DependencyManager::get<OffscreenUi>()->inputDialogAsync(icon, title, label, text);
}
ModalDialogListener* OffscreenUi::getItemAsync(const Icon icon, const QString& title, const QString& label, const QStringList& items,
int current, bool editable) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto inputDialog = offscreenUi->createInputDialog(icon, title, label, current);
if (!inputDialog) {
return nullptr;
}
inputDialog->setProperty("items", items);
inputDialog->setProperty("editable", editable);
InputDialogListener* inputDialogListener = new InputDialogListener(inputDialog);
offscreenUi->getModalDialogListeners().push_back(qobject_cast<QObject*>(inputDialogListener));
return inputDialogListener;
}
ModalDialogListener* OffscreenUi::getCustomInfoAsync(const Icon icon, const QString& title, const QVariantMap& config) {
return DependencyManager::get<OffscreenUi>()->customInputDialogAsync(icon, title, config);
}
QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const QString& label, const QVariant& current) {
if (QThread::currentThread() != thread()) {
QVariant result;
@ -364,6 +403,24 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q
return waitForInputDialogResult(createInputDialog(icon, title, label, current));
}
ModalDialogListener* OffscreenUi::inputDialogAsync(const Icon icon, const QString& title, const QString& label, const QVariant& current) {
if (QThread::currentThread() != thread()) {
ModalDialogListener* ret;
BLOCKING_INVOKE_METHOD(this, "inputDialogAsync",
Q_RETURN_ARG(ModalDialogListener*, ret),
Q_ARG(Icon, icon),
Q_ARG(QString, title),
Q_ARG(QString, label),
Q_ARG(QVariant, current));
return ret;
}
InputDialogListener* inputDialogListener = new InputDialogListener(createInputDialog(icon, title, label, current));
QObject* inputDialog = qobject_cast<QObject*>(inputDialogListener);
_modalDialogListeners.push_back(inputDialog);
return inputDialogListener;
}
QVariant OffscreenUi::customInputDialog(const Icon icon, const QString& title, const QVariantMap& config) {
if (QThread::currentThread() != thread()) {
QVariant result;
@ -384,6 +441,23 @@ QVariant OffscreenUi::customInputDialog(const Icon icon, const QString& title, c
return result;
}
ModalDialogListener* OffscreenUi::customInputDialogAsync(const Icon icon, const QString& title, const QVariantMap& config) {
if (QThread::currentThread() != thread()) {
ModalDialogListener* ret;
BLOCKING_INVOKE_METHOD(this, "customInputDialogAsync",
Q_RETURN_ARG(ModalDialogListener*, ret),
Q_ARG(Icon, icon),
Q_ARG(QString, title),
Q_ARG(QVariantMap, config));
return ret;
}
InputDialogListener* inputDialogListener = new InputDialogListener(createCustomInputDialog(icon, title, config));
QObject* inputDialog = qobject_cast<QObject*>(inputDialogListener);
_modalDialogListeners.push_back(inputDialog);
return inputDialogListener;
}
void OffscreenUi::togglePinned() {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned");
if (!invokeResult) {
@ -555,6 +629,7 @@ void OffscreenUi::createDesktop(const QUrl& url) {
#endif
load(url, [=](QQmlContext* context, QObject* newObject) {
Q_UNUSED(context)
_desktop = static_cast<QQuickItem*>(newObject);
getSurfaceContext()->setContextProperty("desktop", _desktop);
_toolWindow = _desktop->findChild<QQuickItem*>("ToolWindow");
@ -600,8 +675,11 @@ class FileDialogListener : public ModalDialogListener {
private slots:
void onSelectedFile(QVariant file) {
_result = file;
_result = file.toUrl().toLocalFile();
_finished = true;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
emit response(_result);
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
disconnect(_dialog);
}
};
@ -637,6 +715,35 @@ QString OffscreenUi::fileDialog(const QVariantMap& properties) {
return result.toUrl().toLocalFile();
}
ModalDialogListener* OffscreenUi::fileDialogAsync(const QVariantMap& properties) {
QVariant buildDialogResult;
bool invokeResult;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) {
invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties)));
} else {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties)));
emit tabletScriptingInterface->tabletNotification();
}
if (!invokeResult) {
qWarning() << "Failed to create file open dialog";
return nullptr;
}
FileDialogListener* fileDialogListener = new FileDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult));
QObject* fileModalDialog = qobject_cast<QObject*>(fileDialogListener);
_modalDialogListeners.push_back(fileModalDialog);
return fileDialogListener;
}
QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
QString result;
@ -659,6 +766,28 @@ QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir,
return fileDialog(map);
}
ModalDialogListener* OffscreenUi::fileOpenDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
ModalDialogListener* ret;
BLOCKING_INVOKE_METHOD(this, "fileOpenDialogAsync",
Q_RETURN_ARG(ModalDialogListener*, ret),
Q_ARG(QString, caption),
Q_ARG(QString, dir),
Q_ARG(QString, filter),
Q_ARG(QString*, selectedFilter),
Q_ARG(QFileDialog::Options, options));
return ret;
}
// FIXME support returning the selected filter... somehow?
QVariantMap map;
map.insert("caption", caption);
map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
return fileDialogAsync(map);
}
QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
QString result;
@ -683,6 +812,30 @@ QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir,
return fileDialog(map);
}
ModalDialogListener* OffscreenUi::fileSaveDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
ModalDialogListener* ret;
BLOCKING_INVOKE_METHOD(this, "fileSaveDialogAsync",
Q_RETURN_ARG(ModalDialogListener*, ret),
Q_ARG(QString, caption),
Q_ARG(QString, dir),
Q_ARG(QString, filter),
Q_ARG(QString*, selectedFilter),
Q_ARG(QFileDialog::Options, options));
return ret;
}
// FIXME support returning the selected filter... somehow?
QVariantMap map;
map.insert("caption", caption);
map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
map.insert("saveDialog", true);
return fileDialogAsync(map);
}
QString OffscreenUi::existingDirectoryDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
QString result;
@ -705,18 +858,58 @@ QString OffscreenUi::existingDirectoryDialog(const QString& caption, const QStri
return fileDialog(map);
}
ModalDialogListener* OffscreenUi::existingDirectoryDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
if (QThread::currentThread() != thread()) {
ModalDialogListener* ret;
BLOCKING_INVOKE_METHOD(this, "existingDirectoryDialogAsync",
Q_RETURN_ARG(ModalDialogListener*, ret),
Q_ARG(QString, caption),
Q_ARG(QString, dir),
Q_ARG(QString, filter),
Q_ARG(QString*, selectedFilter),
Q_ARG(QFileDialog::Options, options));
return ret;
}
QVariantMap map;
map.insert("caption", caption);
map.insert("dir", QUrl::fromLocalFile(dir));
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
map.insert("selectDirectory", true);
return fileDialogAsync(map);
}
QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->fileOpenDialog(caption, dir, filter, selectedFilter, options);
}
ModalDialogListener* OffscreenUi::getOpenFileNameAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->fileOpenDialogAsync(caption, dir, filter, selectedFilter, options);
}
QString OffscreenUi::getSaveFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->fileSaveDialog(caption, dir, filter, selectedFilter, options);
}
ModalDialogListener* OffscreenUi::getSaveFileNameAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->fileSaveDialogAsync(caption, dir, filter, selectedFilter, options);
}
QString OffscreenUi::getExistingDirectory(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->existingDirectoryDialog(caption, dir, filter, selectedFilter, options);
}
ModalDialogListener* OffscreenUi::getExistingDirectoryAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->existingDirectoryDialogAsync(caption, dir, filter, selectedFilter, options);
}
class AssetDialogListener : public ModalDialogListener {
// ATP equivalent of FileDialogListener.
Q_OBJECT
@ -732,6 +925,9 @@ class AssetDialogListener : public ModalDialogListener {
private slots:
void onSelectedAsset(QVariant asset) {
_result = asset;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
emit response(_result);
offscreenUi->removeModalDialog(qobject_cast<QObject*>(this));
_finished = true;
disconnect(_dialog);
}
@ -769,6 +965,39 @@ QString OffscreenUi::assetDialog(const QVariantMap& properties) {
return result.toUrl().toString();
}
ModalDialogListener *OffscreenUi::assetDialogAsync(const QVariantMap& properties) {
// ATP equivalent of fileDialog().
QVariant buildDialogResult;
bool invokeResult;
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) {
invokeResult = QMetaObject::invokeMethod(_desktop, "assetDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties)));
} else {
QQuickItem* tabletRoot = tablet->getTabletRoot();
invokeResult = QMetaObject::invokeMethod(tabletRoot, "assetDialog",
Q_RETURN_ARG(QVariant, buildDialogResult),
Q_ARG(QVariant, QVariant::fromValue(properties)));
emit tabletScriptingInterface->tabletNotification();
}
if (!invokeResult) {
qWarning() << "Failed to create asset open dialog";
return nullptr;
}
AssetDialogListener* assetDialogListener = new AssetDialogListener(qvariant_cast<QQuickItem*>(buildDialogResult));
QObject* assetModalDialog = qobject_cast<QObject*>(assetDialogListener);
_modalDialogListeners.push_back(assetModalDialog);
return assetDialogListener;
}
QList<QObject *> &OffscreenUi::getModalDialogListeners() {
return _modalDialogListeners;
}
QString OffscreenUi::assetOpenDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
// ATP equivalent of fileOpenDialog().
if (QThread::currentThread() != thread()) {
@ -792,11 +1021,41 @@ QString OffscreenUi::assetOpenDialog(const QString& caption, const QString& dir,
return assetDialog(map);
}
ModalDialogListener* OffscreenUi::assetOpenDialogAsync(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) {
// ATP equivalent of fileOpenDialog().
if (QThread::currentThread() != thread()) {
ModalDialogListener* ret;
BLOCKING_INVOKE_METHOD(this, "assetOpenDialogAsync",
Q_RETURN_ARG(ModalDialogListener*, ret),
Q_ARG(QString, caption),
Q_ARG(QString, dir),
Q_ARG(QString, filter),
Q_ARG(QString*, selectedFilter),
Q_ARG(QFileDialog::Options, options));
return ret;
}
// FIXME support returning the selected filter... somehow?
QVariantMap map;
map.insert("caption", caption);
map.insert("dir", dir);
map.insert("filter", filter);
map.insert("options", static_cast<int>(options));
return assetDialogAsync(map);
}
QString OffscreenUi::getOpenAssetName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
// ATP equivalent of getOpenFileName().
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->assetOpenDialog(caption, dir, filter, selectedFilter, options);
}
ModalDialogListener* OffscreenUi::getOpenAssetNameAsync(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) {
// ATP equivalent of getOpenFileName().
Q_UNUSED(ignored)
return DependencyManager::get<OffscreenUi>()->assetOpenDialogAsync(caption, dir, filter, selectedFilter, options);
}
bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) {
if (!filterEnabled(originalDestination, event)) {
return false;
@ -836,5 +1095,31 @@ unsigned int OffscreenUi::getMenuUserDataId() const {
return _vrMenu->_userDataId;
}
#include "OffscreenUi.moc"
ModalDialogListener::ModalDialogListener(QQuickItem *dialog) : _dialog(dialog) {
if (!dialog) {
_finished = true;
return;
}
connect(_dialog, SIGNAL(destroyed()), this, SLOT(onDestroyed()));
}
ModalDialogListener::~ModalDialogListener() {
if (_dialog) {
disconnect(_dialog);
}
}
QVariant ModalDialogListener::waitForResult() {
while (!_finished) {
QCoreApplication::processEvents();
}
return _result;
}
void ModalDialogListener::onDestroyed() {
_finished = true;
disconnect(_dialog);
_dialog = nullptr;
}
#include "OffscreenUi.moc"

View file

@ -30,6 +30,27 @@ class VrMenu;
#define OFFSCREEN_VISIBILITY_PROPERTY "shown"
class ModalDialogListener : public QObject {
Q_OBJECT
friend class OffscreenUi;
protected:
ModalDialogListener(QQuickItem* dialog);
virtual ~ModalDialogListener();
virtual QVariant waitForResult();
signals:
void response(const QVariant& value);
protected slots:
void onDestroyed();
protected:
QQuickItem* _dialog;
bool _finished { false };
QVariant _result;
};
class OffscreenUi : public OffscreenQmlSurface, public Dependency {
Q_OBJECT
@ -71,6 +92,7 @@ public:
// Message box compatibility
Q_INVOKABLE QMessageBox::StandardButton messageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
Q_INVOKABLE ModalDialogListener* asyncMessageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
// Must be called from the main thread
QQuickItem* createMessageBox(Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton);
// Must be called from the main thread
@ -88,18 +110,39 @@ public:
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
return information(title, text, buttons, defaultButton);
}
static ModalDialogListener* asyncCritical(void* ignored, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
return asyncCritical(title, text, buttons, defaultButton);
}
static ModalDialogListener* asyncInformation(void* ignored, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
return asyncInformation(title, text, buttons, defaultButton);
}
/// Same design as QMessageBox::question(), will block, returns result
static QMessageBox::StandardButton question(void* ignored, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
return question(title, text, buttons, defaultButton);
}
static ModalDialogListener* asyncQuestion(void* ignored, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
return asyncQuestion(title, text, buttons, defaultButton);
}
/// Same design as QMessageBox::warning(), will block, returns result
static QMessageBox::StandardButton warning(void* ignored, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
return warning(title, text, buttons, defaultButton);
}
static ModalDialogListener* asyncWarning(void* ignored, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) {
return asyncWarning(title, text, buttons, defaultButton);
}
static QMessageBox::StandardButton critical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
@ -107,30 +150,55 @@ public:
static QMessageBox::StandardButton information(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static ModalDialogListener* asyncCritical(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static ModalDialogListener *asyncInformation(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton question(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static ModalDialogListener* asyncQuestion (const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static QMessageBox::StandardButton warning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
static ModalDialogListener *asyncWarning(const QString& title, const QString& text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* fileOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* fileSaveDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* existingDirectoryDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QString assetOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE ModalDialogListener* assetOpenDialogAsync(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// Compatibility with QFileDialog::getOpenFileName
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getOpenFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// Compatibility with QFileDialog::getSaveFileName
static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getSaveFileNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
// Compatibility with QFileDialog::getExistingDirectory
static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getExistingDirectoryAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static QString getOpenAssetName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
static ModalDialogListener* getOpenAssetNameAsync(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0);
Q_INVOKABLE QVariant inputDialog(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
Q_INVOKABLE ModalDialogListener* inputDialogAsync(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant());
Q_INVOKABLE QVariant customInputDialog(const Icon icon, const QString& title, const QVariantMap& config);
Q_INVOKABLE ModalDialogListener* customInputDialogAsync(const Icon icon, const QString& title, const QVariantMap& config);
QQuickItem* createInputDialog(const Icon icon, const QString& title, const QString& label, const QVariant& current);
QQuickItem* createCustomInputDialog(const Icon icon, const QString& title, const QVariantMap& config);
QVariant waitForInputDialogResult(QQuickItem* inputDialog);
@ -148,21 +216,47 @@ public:
return getItem(OffscreenUi::ICON_NONE, title, label, items, current, editable, ok);
}
// Compatibility with QInputDialog::getText
static ModalDialogListener* getTextAsync(void* ignored, const QString & title, const QString & label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString & text = QString(), bool * ok = 0,
Qt::WindowFlags flags = 0, Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getTextAsync(OffscreenUi::ICON_NONE, title, label, text);
}
// Compatibility with QInputDialog::getItem
static ModalDialogListener* getItemAsync(void *ignored, const QString & title, const QString & label, const QStringList & items,
int current = 0, bool editable = true, bool * ok = 0, Qt::WindowFlags flags = 0,
Qt::InputMethodHints inputMethodHints = Qt::ImhNone) {
return getItemAsync(OffscreenUi::ICON_NONE, title, label, items, current, editable);
}
static QString getText(const Icon icon, const QString & title, const QString & label, const QString & text = QString(), bool * ok = 0);
static QString getItem(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true, bool * ok = 0);
static QVariant getCustomInfo(const Icon icon, const QString& title, const QVariantMap& config, bool* ok = 0);
static ModalDialogListener* getTextAsync(const Icon icon, const QString & title, const QString & label, const QString & text = QString());
static ModalDialogListener* getItemAsync(const Icon icon, const QString & title, const QString & label, const QStringList & items, int current = 0, bool editable = true);
static ModalDialogListener* getCustomInfoAsync(const Icon icon, const QString& title, const QVariantMap& config);
unsigned int getMenuUserDataId() const;
QList<QObject *> &getModalDialogListeners();
signals:
void showDesktop();
// void response(QMessageBox::StandardButton response);
// void fileDialogResponse(QString response);
// void assetDialogResponse(QString response);
// void inputDialogResponse(QVariant response);
public slots:
void removeModalDialog(QObject* modal);
private:
QString fileDialog(const QVariantMap& properties);
ModalDialogListener *fileDialogAsync(const QVariantMap &properties);
QString assetDialog(const QVariantMap& properties);
ModalDialogListener* assetDialogAsync(const QVariantMap& properties);
QQuickItem* _desktop { nullptr };
QQuickItem* _toolWindow { nullptr };
QList<QObject*> _modalDialogListeners;
std::unordered_map<int, bool> _pressedKeys;
VrMenu* _vrMenu { nullptr };
QQueue<std::function<void(VrMenu*)>> _queuedMenuInitializers;

View file

@ -433,17 +433,8 @@ var toolBar = (function () {
});
addButton("importEntitiesButton", "assets-01.svg", function() {
var importURL = null;
var fullPath = Window.browse("Select Model to Import", "", "*.json");
if (fullPath) {
importURL = "file:///" + fullPath;
}
if (importURL) {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
toolBar.toggle();
}
importSVO(importURL);
}
Window.openFileChanged.connect(onFileOpenChanged);
Window.browseAsync("Select Model to Import", "", "*.json");
});
addButton("openAssetBrowserButton", "assets-01.svg", function() {
@ -1461,6 +1452,39 @@ function toggleSelectedEntitiesVisible() {
}
}
function onFileSaveChanged(filename) {
Window.saveFileChanged.disconnect(onFileSaveChanged);
if (filename !== "") {
var success = Clipboard.exportEntities(filename, selectionManager.selections);
if (!success) {
Window.notifyEditError("Export failed.");
}
}
}
function onFileOpenChanged(filename) {
var importURL = null;
if (filename !== "") {
importURL = "file:///" + filename;
}
if (importURL) {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
toolBar.toggle();
}
importSVO(importURL);
}
}
function onPromptTextChanged(prompt) {
Window.promptTextChanged.disconnect(onPromptTextChanged);
if (prompt !== "") {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
toolBar.toggle();
}
importSVO(prompt);
}
}
function handeMenuEvent(menuItem) {
if (menuItem === "Allow Selecting of Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
@ -1478,30 +1502,16 @@ function handeMenuEvent(menuItem) {
if (!selectionManager.hasSelection()) {
Window.notifyEditError("No entities have been selected.");
} else {
var filename = Window.save("Select Where to Save", "", "*.json");
if (filename) {
var success = Clipboard.exportEntities(filename, selectionManager.selections);
if (!success) {
Window.notifyEditError("Export failed.");
}
}
Window.saveFileChanged.connect(onFileSaveChanged);
Window.saveAsync("Select Where to Save", "", "*.json");
}
} else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") {
var importURL = null;
if (menuItem === "Import Entities") {
var fullPath = Window.browse("Select Model to Import", "", "*.json");
if (fullPath) {
importURL = "file:///" + fullPath;
}
Window.openFileChanged.connect(onFileOpenChanged);
Window.browseAsync("Select Model to Import", "", "*.json");
} else {
importURL = Window.prompt("URL of SVO to import", "");
}
if (importURL) {
if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
toolBar.toggle();
}
importSVO(importURL);
Window.promptTextChanged.connect(onFileOpenChanged);
Window.promptAsync("URL of SVO to import", "");
}
} else if (menuItem === "Entity List...") {
entityListTool.toggleVisible();

View file

@ -108,6 +108,16 @@ EntityListTool = function(opts) {
webView.emitScriptEvent(JSON.stringify(data));
};
function onFileSaveChanged(filename) {
Window.saveFileChanged.disconnect(onFileSaveChanged);
if (filename !== "") {
var success = Clipboard.exportEntities(filename, selectionManager.selections);
if (!success) {
Window.notifyEditError("Export failed.");
}
}
}
webView.webEventReceived.connect(function(data) {
try {
data = JSON.parse(data);
@ -139,13 +149,8 @@ EntityListTool = function(opts) {
if (!selectionManager.hasSelection()) {
Window.notifyEditError("No entities have been selected.");
} else {
var filename = Window.save("Select Where to Save", "", "*.json");
if (filename) {
var success = Clipboard.exportEntities(filename, selectionManager.selections);
if (!success) {
Window.notifyEditError("Export failed.");
}
}
Window.saveFileChanged.connect(onFileSaveChanged);
Window.saveAsync("Select Where to Save", "", "*.json");
}
} else if (data.type == "pal") {
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.

View file

@ -113,15 +113,8 @@ function onMessage(message) {
openLoginWindow();
break;
case 'chooseSnapshotLocation':
var snapshotPath = Window.browseDir("Choose Snapshots Directory", "", "");
if (snapshotPath) { // not cancelled
Snapshot.setSnapshotsLocation(snapshotPath);
tablet.emitScriptEvent(JSON.stringify({
type: "snapshot",
action: "snapshotLocationChosen"
}));
}
Window.browseDirChanged.connect(snapshotDirChanged);
Window.browseDirAsync("Choose Snapshots Directory", "", "");
break;
case 'openSettings':
if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false))
@ -572,6 +565,17 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
});
}
function snapshotDirChanged(snapshotPath) {
Window.browseDirChanged.disconnect(snapshotDirChanged);
if (snapshotPath !== "") { // not cancelled
Snapshot.setSnapshotsLocation(snapshotPath);
tablet.emitScriptEvent(JSON.stringify({
type: "snapshot",
action: "snapshotLocationChosen"
}));
}
}
function processingGifStarted(pathStillSnapshot) {
Window.processingGifStarted.disconnect(processingGifStarted);
Window.processingGifCompleted.connect(processingGifCompleted);

View file

@ -486,6 +486,15 @@
return isFinishOnOpen;
}
function onAssetsDirChanged(recording) {
Window.assetsDirChanged.disconnect(onAssetsDirChanged);
if (recording !== "") {
log("Load recording " + recording);
UserActivityLogger.logAction("record_load_recording", logDetails());
Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation);
}
}
function onWebEventReceived(data) {
var message,
recording;
@ -520,12 +529,8 @@
break;
case LOAD_RECORDING_ACTION:
// User wants to select an ATP recording to play.
recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hfr");
if (recording) {
log("Load recording " + recording);
UserActivityLogger.logAction("record_load_recording", logDetails());
Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation);
}
Window.assetsDirChanged.connect(onAssetsDirChanged);
Window.browseAssetsAsync("Select Recording to Play", "recordings", "*.hfr");
break;
case START_RECORDING_ACTION:
// Start making a recording.