Merge remote-tracking branch 'upstream/stable' into android_stable
5
.gitignore
vendored
|
@ -85,4 +85,7 @@ npm-debug.log
|
|||
android/app/src/main/assets
|
||||
|
||||
# Resource binary file
|
||||
interface/compiledResources
|
||||
interface/compiledResources
|
||||
|
||||
# GPUCache
|
||||
interface/resources/GPUCache/*
|
|
@ -1,6 +1,6 @@
|
|||
set(TARGET_NAME assignment-client)
|
||||
|
||||
setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets)
|
||||
setup_hifi_project(Core Gui Network Script Quick WebSockets)
|
||||
|
||||
# Fix up the rpath so macdeployqt works
|
||||
if (APPLE)
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
#include "SendAssetTask.h"
|
||||
#include "UploadAssetTask.h"
|
||||
|
||||
|
||||
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
|
||||
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
|
||||
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
|
||||
|
@ -56,7 +55,7 @@ static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
|||
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
||||
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
||||
|
||||
void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) {
|
||||
void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) {
|
||||
qDebug() << "Starting bake for: " << assetPath << assetHash;
|
||||
auto it = _pendingBakes.find(assetHash);
|
||||
if (it == _pendingBakes.end()) {
|
||||
|
@ -74,23 +73,23 @@ void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPa
|
|||
}
|
||||
}
|
||||
|
||||
QString AssetServer::getPathToAssetHash(const AssetHash& assetHash) {
|
||||
QString AssetServer::getPathToAssetHash(const AssetUtils::AssetHash& assetHash) {
|
||||
return _filesDirectory.absoluteFilePath(assetHash);
|
||||
}
|
||||
|
||||
std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& path, const AssetHash& hash) {
|
||||
std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) {
|
||||
auto it = _pendingBakes.find(hash);
|
||||
if (it != _pendingBakes.end()) {
|
||||
return { (*it)->isBaking() ? Baking : Pending, "" };
|
||||
return { (*it)->isBaking() ? AssetUtils::Baking : AssetUtils::Pending, "" };
|
||||
}
|
||||
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return { Baked, "" };
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return { AssetUtils::Baked, "" };
|
||||
}
|
||||
|
||||
auto dotIndex = path.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
return { Irrelevant, "" };
|
||||
return { AssetUtils::Irrelevant, "" };
|
||||
}
|
||||
|
||||
auto extension = path.mid(dotIndex + 1);
|
||||
|
@ -104,16 +103,16 @@ std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& pa
|
|||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
|
||||
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
|
||||
} else {
|
||||
return { Irrelevant, "" };
|
||||
return { AssetUtils::Irrelevant, "" };
|
||||
}
|
||||
|
||||
auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
|
||||
auto jt = _fileMappings.find(bakedPath);
|
||||
if (jt != _fileMappings.end()) {
|
||||
if (jt->second == hash) {
|
||||
return { NotBaked, "" };
|
||||
return { AssetUtils::NotBaked, "" };
|
||||
} else {
|
||||
return { Baked, "" };
|
||||
return { AssetUtils::Baked, "" };
|
||||
}
|
||||
} else {
|
||||
bool loaded;
|
||||
|
@ -121,11 +120,11 @@ std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& pa
|
|||
|
||||
std::tie(loaded, meta) = readMetaFile(hash);
|
||||
if (loaded && meta.failedLastBake) {
|
||||
return { Error, meta.lastBakeErrors };
|
||||
return { AssetUtils::Error, meta.lastBakeErrors };
|
||||
}
|
||||
}
|
||||
|
||||
return { Pending, "" };
|
||||
return { AssetUtils::Pending, "" };
|
||||
}
|
||||
|
||||
void AssetServer::bakeAssets() {
|
||||
|
@ -137,14 +136,14 @@ void AssetServer::bakeAssets() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssetServer::maybeBake(const AssetPath& path, const AssetHash& hash) {
|
||||
void AssetServer::maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) {
|
||||
if (needsToBeBaked(path, hash)) {
|
||||
qDebug() << "Queuing bake of: " << path;
|
||||
bakeAsset(hash, path, getPathToAssetHash(hash));
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::createEmptyMetaFile(const AssetHash& hash) {
|
||||
void AssetServer::createEmptyMetaFile(const AssetUtils::AssetHash& hash) {
|
||||
QString metaFilePath = "atp:/" + hash + "/meta.json";
|
||||
QFile metaFile { metaFilePath };
|
||||
|
||||
|
@ -157,14 +156,14 @@ void AssetServer::createEmptyMetaFile(const AssetHash& hash) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::hasMetaFile(const AssetHash& hash) {
|
||||
QString metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json";
|
||||
bool AssetServer::hasMetaFile(const AssetUtils::AssetHash& hash) {
|
||||
QString metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json";
|
||||
|
||||
return _fileMappings.find(metaFilePath) != _fileMappings.end();
|
||||
}
|
||||
|
||||
bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHash) {
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash) {
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -196,7 +195,7 @@ bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHa
|
|||
return false;
|
||||
}
|
||||
|
||||
auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
return _fileMappings.find(bakedPath) == _fileMappings.end();
|
||||
}
|
||||
|
||||
|
@ -235,7 +234,7 @@ AssetServer::AssetServer(ReceivedMessage& message) :
|
|||
ThreadedAssignment(message),
|
||||
_transferTaskPool(this),
|
||||
_bakingTaskPool(this),
|
||||
_filesizeLimit(MAX_UPLOAD_SIZE)
|
||||
_filesizeLimit(AssetUtils::MAX_UPLOAD_SIZE)
|
||||
{
|
||||
// store the current state of image compression so we can reset it when this assignment is complete
|
||||
_wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
|
||||
|
@ -390,7 +389,7 @@ void AssetServer::completeSetup() {
|
|||
// Check the asset directory to output some information about what we have
|
||||
auto files = _filesDirectory.entryList(QDir::Files);
|
||||
|
||||
QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING };
|
||||
QRegExp hashFileRegex { AssetUtils::ASSET_HASH_REGEX_STRING };
|
||||
auto hashedFiles = files.filter(hashFileRegex);
|
||||
|
||||
qCInfo(asset_server) << "There are" << hashedFiles.size() << "asset files in the asset directory.";
|
||||
|
@ -410,9 +409,9 @@ void AssetServer::completeSetup() {
|
|||
// get file size limit for an asset
|
||||
static const QString ASSETS_FILESIZE_LIMIT_OPTION = "assets_filesize_limit";
|
||||
auto assetsFilesizeLimitJSONValue = assetServerObject[ASSETS_FILESIZE_LIMIT_OPTION];
|
||||
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(MAX_UPLOAD_SIZE);
|
||||
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(AssetUtils::MAX_UPLOAD_SIZE);
|
||||
|
||||
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) {
|
||||
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < AssetUtils::MAX_UPLOAD_SIZE) {
|
||||
_filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS;
|
||||
}
|
||||
|
||||
|
@ -421,7 +420,7 @@ void AssetServer::completeSetup() {
|
|||
}
|
||||
|
||||
void AssetServer::cleanupUnmappedFiles() {
|
||||
QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}" };
|
||||
QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(AssetUtils::SHA256_HASH_HEX_LENGTH) + "}" };
|
||||
|
||||
auto files = _filesDirectory.entryInfoList(QDir::Files);
|
||||
|
||||
|
@ -454,6 +453,8 @@ void AssetServer::cleanupUnmappedFiles() {
|
|||
}
|
||||
|
||||
void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
using AssetMappingOperationType = AssetUtils::AssetMappingOperationType;
|
||||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
|
@ -519,7 +520,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
|
||||
if (!bakedRootFile.isEmpty()) {
|
||||
// we ran into an asset for which we could have a baked version, let's check if it's ready
|
||||
bakedAssetPath = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
||||
bakedAssetPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
||||
auto bakedIt = _fileMappings.find(bakedAssetPath);
|
||||
|
||||
if (bakedIt != _fileMappings.end()) {
|
||||
|
@ -537,7 +538,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
}
|
||||
}
|
||||
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
|
||||
if (wasRedirected) {
|
||||
qDebug() << "Writing re-directed hash for" << originalAssetHash << "to" << redirectedAssetHash;
|
||||
|
@ -563,12 +564,12 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
}
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
|
||||
uint32_t count = (uint32_t)_fileMappings.size();
|
||||
|
||||
|
@ -580,11 +581,11 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN
|
|||
replyPacket.writeString(mapping);
|
||||
replyPacket.write(QByteArray::fromHex(hash.toUtf8()));
|
||||
|
||||
BakingStatus status;
|
||||
AssetUtils::BakingStatus status;
|
||||
QString lastBakeErrors;
|
||||
std::tie(status, lastBakeErrors) = getAssetStatus(mapping, hash);
|
||||
replyPacket.writePrimitive(status);
|
||||
if (status == Error) {
|
||||
if (status == AssetUtils::Error) {
|
||||
replyPacket.writeString(lastBakeErrors);
|
||||
}
|
||||
}
|
||||
|
@ -594,22 +595,22 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
if (senderNode->getCanWriteToAssetServer()) {
|
||||
QString assetPath = message.readString();
|
||||
|
||||
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
||||
auto assetHash = message.read(AssetUtils::SHA256_HASH_LENGTH).toHex();
|
||||
|
||||
// don't process a set mapping operation that is inside the hidden baked folder
|
||||
if (assetPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
if (assetPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
} else {
|
||||
if (setMapping(assetPath, assetHash)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,21 +624,21 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared
|
|||
for (int i = 0; i < numberOfDeletedMappings; ++i) {
|
||||
auto mapping = message.readString();
|
||||
|
||||
if (!mapping.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (!mapping.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
mappingsToDelete << mapping;
|
||||
} else {
|
||||
qCDebug(asset_server) << "Refusing to delete mapping" << mapping
|
||||
<< "that is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
<< "that is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteMappings(mappingsToDelete)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -646,20 +647,20 @@ void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedN
|
|||
QString oldPath = message.readString();
|
||||
QString newPath = message.readString();
|
||||
|
||||
if (oldPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (oldPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Cannot rename" << oldPath << "to" << newPath
|
||||
<< "since one of the paths is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
<< "since one of the paths is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
} else {
|
||||
if (renameMapping(oldPath, newPath)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,12 +679,12 @@ void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, Shar
|
|||
}
|
||||
|
||||
if (setBakingEnabled(mappings, enabled)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -691,15 +692,15 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
QByteArray assetHash;
|
||||
MessageID messageID;
|
||||
|
||||
if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) {
|
||||
if (message->getSize() < qint64(AssetUtils::SHA256_HASH_LENGTH + sizeof(messageID))) {
|
||||
qCDebug(asset_server) << "ERROR bad file request";
|
||||
return;
|
||||
}
|
||||
|
||||
message->readPrimitive(&messageID);
|
||||
assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH);
|
||||
assetHash = message->readWithoutCopy(AssetUtils::SHA256_HASH_LENGTH);
|
||||
|
||||
auto size = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(AssetServerError) + sizeof(qint64));
|
||||
auto size = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::AssetServerError) + sizeof(qint64));
|
||||
auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply, size, true);
|
||||
|
||||
QByteArray hexHash = assetHash.toHex();
|
||||
|
@ -712,11 +713,11 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
|
||||
if (fileInfo.exists() && fileInfo.isReadable()) {
|
||||
qCDebug(asset_server) << "Opening file: " << fileInfo.filePath();
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(fileInfo.size());
|
||||
} else {
|
||||
qCDebug(asset_server) << "Asset not found: " << QString(hexHash);
|
||||
replyPacket->writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -725,7 +726,7 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
|
||||
void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
||||
auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset));
|
||||
auto minSize = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::DataOffset) + sizeof(AssetUtils::DataOffset));
|
||||
|
||||
if (message->getSize() < minSize) {
|
||||
qCDebug(asset_server) << "ERROR bad file request";
|
||||
|
@ -749,14 +750,14 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
|
|||
// for now this also means it isn't allowed to add assets
|
||||
// so return a packet with error that indicates that
|
||||
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError), true);
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetUtils::AssetServerError), true);
|
||||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
// write the message ID and a permission denied error
|
||||
permissionErrorPacket->writePrimitive(messageID);
|
||||
permissionErrorPacket->writePrimitive(AssetServerError::PermissionDenied);
|
||||
permissionErrorPacket->writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
|
||||
// send off the packet
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -863,12 +864,12 @@ bool AssetServer::loadMappingsFromFile() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!isValidFilePath(key)) {
|
||||
if (!AssetUtils::isValidFilePath(key)) {
|
||||
qCWarning(asset_server) << "Will not keep mapping for" << key << "since it is not a valid path.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isValidHash(value.toString())) {
|
||||
if (!AssetUtils::isValidHash(value.toString())) {
|
||||
qCWarning(asset_server) << "Will not keep mapping for" << key << "since it does not have a valid hash.";
|
||||
continue;
|
||||
}
|
||||
|
@ -918,15 +919,15 @@ bool AssetServer::writeMappingsToFile() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
||||
bool AssetServer::setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash) {
|
||||
path = path.trimmed();
|
||||
|
||||
if (!isValidFilePath(path)) {
|
||||
if (!AssetUtils::isValidFilePath(path)) {
|
||||
qCWarning(asset_server) << "Cannot set a mapping for invalid path:" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidHash(hash)) {
|
||||
if (!AssetUtils::isValidHash(hash)) {
|
||||
qCWarning(asset_server) << "Cannot set a mapping for invalid hash" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
@ -958,23 +959,23 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
|||
}
|
||||
}
|
||||
|
||||
bool pathIsFolder(const AssetPath& path) {
|
||||
bool pathIsFolder(const AssetUtils::AssetPath& path) {
|
||||
return path.endsWith('/');
|
||||
}
|
||||
|
||||
void AssetServer::removeBakedPathsForDeletedAsset(AssetHash hash) {
|
||||
void AssetServer::removeBakedPathsForDeletedAsset(AssetUtils::AssetHash hash) {
|
||||
// we deleted the file with this hash
|
||||
|
||||
// check if we had baked content for that file that should also now be removed
|
||||
// by calling deleteMappings for the hidden baked content folder for this hash
|
||||
AssetPathList hiddenBakedFolder { HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" };
|
||||
AssetUtils::AssetPathList hiddenBakedFolder { AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" };
|
||||
|
||||
qCDebug(asset_server) << "Deleting baked content below" << hiddenBakedFolder << "since" << hash << "was deleted";
|
||||
|
||||
deleteMappings(hiddenBakedFolder);
|
||||
}
|
||||
|
||||
bool AssetServer::deleteMappings(const AssetPathList& paths) {
|
||||
bool AssetServer::deleteMappings(const AssetUtils::AssetPathList& paths) {
|
||||
// take a copy of the current mappings in case persistence of these deletes fails
|
||||
auto oldMappings = _fileMappings;
|
||||
|
||||
|
@ -1060,11 +1061,11 @@ bool AssetServer::deleteMappings(const AssetPathList& paths) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
|
||||
bool AssetServer::renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath) {
|
||||
oldPath = oldPath.trimmed();
|
||||
newPath = newPath.trimmed();
|
||||
|
||||
if (!isValidFilePath(oldPath) || !isValidFilePath(newPath)) {
|
||||
if (!AssetUtils::isValidFilePath(oldPath) || !AssetUtils::isValidFilePath(newPath)) {
|
||||
qCWarning(asset_server) << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:"
|
||||
<< oldPath << "=>" << newPath;
|
||||
|
||||
|
@ -1164,8 +1165,8 @@ static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx";
|
|||
static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx";
|
||||
static const QString BAKED_ASSET_SIMPLE_JS_NAME = "asset.js";
|
||||
|
||||
QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) {
|
||||
return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
||||
QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativeFilePath) {
|
||||
return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
||||
}
|
||||
|
||||
void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
|
||||
|
@ -1197,7 +1198,7 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
|
|||
|
||||
qDebug() << "File path: " << filePath;
|
||||
|
||||
AssetHash bakedFileHash;
|
||||
AssetUtils::AssetHash bakedFileHash;
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha256);
|
||||
|
@ -1290,8 +1291,8 @@ static const QString BAKE_VERSION_KEY = "bake_version";
|
|||
static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake";
|
||||
static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors";
|
||||
|
||||
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetHash hash) {
|
||||
auto metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
||||
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash) {
|
||||
auto metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
||||
|
||||
auto it = _fileMappings.find(metaFilePath);
|
||||
if (it == _fileMappings.end()) {
|
||||
|
@ -1335,7 +1336,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetHash hash) {
|
|||
return { false, {} };
|
||||
}
|
||||
|
||||
bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta) {
|
||||
bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta) {
|
||||
// construct the JSON that will be in the meta file
|
||||
QJsonObject metaFileObject;
|
||||
|
||||
|
@ -1349,7 +1350,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
auto metaFileJSON = metaFileDoc.toJson();
|
||||
|
||||
// get a hash for the contents of the meta-file
|
||||
AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex();
|
||||
AssetUtils::AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex();
|
||||
|
||||
// create the meta file in our files folder, named by the hash of its contents
|
||||
QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash));
|
||||
|
@ -1359,7 +1360,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
metaFile.close();
|
||||
|
||||
// add a mapping to the meta file so it doesn't get deleted because it is unmapped
|
||||
auto metaFileMapping = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json";
|
||||
auto metaFileMapping = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json";
|
||||
|
||||
return setMapping(metaFileMapping, metaFileHash);
|
||||
} else {
|
||||
|
@ -1367,7 +1368,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) {
|
||||
bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled) {
|
||||
for (const auto& path : paths) {
|
||||
auto it = _fileMappings.find(path);
|
||||
if (it != _fileMappings.end()) {
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include "AssetUtils.h"
|
||||
#include "ReceivedMessage.h"
|
||||
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<QString> {
|
||||
|
@ -75,29 +74,29 @@ private:
|
|||
bool writeMappingsToFile();
|
||||
|
||||
/// Set the mapping for path to hash
|
||||
bool setMapping(AssetPath path, AssetHash hash);
|
||||
bool setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash);
|
||||
|
||||
/// Delete mapping `path`. Returns `true` if deletion of mappings succeeds, else `false`.
|
||||
bool deleteMappings(const AssetPathList& paths);
|
||||
bool deleteMappings(const AssetUtils::AssetPathList& paths);
|
||||
|
||||
/// Rename mapping from `oldPath` to `newPath`. Returns true if successful
|
||||
bool renameMapping(AssetPath oldPath, AssetPath newPath);
|
||||
bool renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath);
|
||||
|
||||
bool setBakingEnabled(const AssetPathList& paths, bool enabled);
|
||||
bool setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled);
|
||||
|
||||
/// Delete any unmapped files from the local asset directory
|
||||
void cleanupUnmappedFiles();
|
||||
|
||||
QString getPathToAssetHash(const AssetHash& assetHash);
|
||||
QString getPathToAssetHash(const AssetUtils::AssetHash& assetHash);
|
||||
|
||||
std::pair<BakingStatus, QString> getAssetStatus(const AssetPath& path, const AssetHash& hash);
|
||||
std::pair<AssetUtils::BakingStatus, QString> getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
|
||||
void bakeAssets();
|
||||
void maybeBake(const AssetPath& path, const AssetHash& hash);
|
||||
void createEmptyMetaFile(const AssetHash& hash);
|
||||
bool hasMetaFile(const AssetHash& hash);
|
||||
bool needsToBeBaked(const AssetPath& path, const AssetHash& assetHash);
|
||||
void bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
|
||||
void maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
void createEmptyMetaFile(const AssetUtils::AssetHash& hash);
|
||||
bool hasMetaFile(const AssetUtils::AssetHash& hash);
|
||||
bool needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash);
|
||||
void bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
||||
|
||||
/// Move baked content for asset to baked directory and update baked status
|
||||
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
|
||||
|
@ -106,11 +105,11 @@ private:
|
|||
void handleAbortedBake(QString originalAssetHash, QString assetPath);
|
||||
|
||||
/// Create meta file to describe baked content for original asset
|
||||
std::pair<bool, AssetMeta> readMetaFile(AssetHash hash);
|
||||
bool writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta());
|
||||
std::pair<bool, AssetMeta> readMetaFile(AssetUtils::AssetHash hash);
|
||||
bool writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta());
|
||||
|
||||
/// Remove baked paths when the original asset is deleteds
|
||||
void removeBakedPathsForDeletedAsset(AssetHash originalAssetHash);
|
||||
void removeBakedPathsForDeletedAsset(AssetUtils::AssetHash originalAssetHash);
|
||||
|
||||
Mappings _fileMappings;
|
||||
|
||||
|
@ -120,7 +119,7 @@ private:
|
|||
/// Task pool for handling uploads and downloads of assets
|
||||
QThreadPool _transferTaskPool;
|
||||
|
||||
QHash<AssetHash, std::shared_ptr<BakeAssetTask>> _pendingBakes;
|
||||
QHash<AssetUtils::AssetHash, std::shared_ptr<BakeAssetTask>> _pendingBakes;
|
||||
QThreadPool _bakingTaskPool;
|
||||
|
||||
bool _wasColorTextureCompressionEnabled { false };
|
||||
|
|
|
@ -24,7 +24,7 @@ static const int OVEN_STATUS_CODE_ABORT { 2 };
|
|||
|
||||
std::once_flag registerMetaTypesFlag;
|
||||
|
||||
BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) :
|
||||
BakeAssetTask::BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) :
|
||||
_assetHash(assetHash),
|
||||
_assetPath(assetPath),
|
||||
_filePath(filePath)
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
class BakeAssetTask : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
|
||||
BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
||||
|
||||
bool isBaking() { return _isBaking.load(); }
|
||||
|
||||
|
@ -41,8 +41,8 @@ signals:
|
|||
|
||||
private:
|
||||
std::atomic<bool> _isBaking { false };
|
||||
AssetHash _assetHash;
|
||||
AssetPath _assetPath;
|
||||
AssetUtils::AssetHash _assetHash;
|
||||
AssetUtils::AssetPath _assetPath;
|
||||
QString _filePath;
|
||||
std::unique_ptr<QProcess> _ovenProcess { nullptr };
|
||||
std::atomic<bool> _wasAborted { false };
|
||||
|
|
|
@ -40,7 +40,7 @@ void SendAssetTask::run() {
|
|||
ByteRange byteRange;
|
||||
|
||||
_message->readPrimitive(&messageID);
|
||||
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
||||
QByteArray assetHash = _message->read(AssetUtils::SHA256_HASH_LENGTH);
|
||||
|
||||
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
||||
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
||||
|
@ -61,7 +61,7 @@ void SendAssetTask::run() {
|
|||
replyPacketList->writePrimitive(messageID);
|
||||
|
||||
if (!byteRange.isValid()) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange);
|
||||
} else {
|
||||
QString filePath = _resourcesDir.filePath(QString(hexHash));
|
||||
|
||||
|
@ -75,7 +75,7 @@ void SendAssetTask::run() {
|
|||
// check if we're being asked to read data that we just don't have
|
||||
// because of the file size
|
||||
if (file.size() < byteRange.fromInclusive || file.size() < byteRange.toExclusive) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange);
|
||||
qCDebug(networking) << "Bad byte range: " << hexHash << " "
|
||||
<< byteRange.fromInclusive << ":" << byteRange.toExclusive;
|
||||
} else {
|
||||
|
@ -86,7 +86,7 @@ void SendAssetTask::run() {
|
|||
|
||||
// this range is positive, meaning we just need to seek into the file and then read from there
|
||||
file.seek(byteRange.fromInclusive);
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
replyPacketList->write(file.read(size));
|
||||
} else {
|
||||
|
@ -95,7 +95,7 @@ void SendAssetTask::run() {
|
|||
// seek to the part of the file where the negative range begins
|
||||
file.seek(file.size() + byteRange.fromInclusive);
|
||||
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
|
||||
// first write everything from the negative range to the end of the file
|
||||
|
@ -107,7 +107,7 @@ void SendAssetTask::run() {
|
|||
file.close();
|
||||
} else {
|
||||
qCDebug(networking) << "Asset not found: " << filePath << "(" << hexHash << ")";
|
||||
replyPacketList->writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
#include "ClientServerUtils.h"
|
||||
|
||||
|
||||
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
|
||||
const QDir& resourcesDir, uint64_t filesizeLimit) :
|
||||
_receivedMessage(receivedMessage),
|
||||
|
@ -50,11 +49,11 @@ void UploadAssetTask::run() {
|
|||
replyPacket->writePrimitive(messageID);
|
||||
|
||||
if (fileSize > _filesizeLimit) {
|
||||
replyPacket->writePrimitive(AssetServerError::AssetTooLarge);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetTooLarge);
|
||||
} else {
|
||||
QByteArray fileData = buffer.read(fileSize);
|
||||
|
||||
auto hash = hashData(fileData);
|
||||
auto hash = AssetUtils::hashData(fileData);
|
||||
auto hexHash = hash.toHex();
|
||||
|
||||
qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID())
|
||||
|
@ -66,12 +65,12 @@ void UploadAssetTask::run() {
|
|||
|
||||
if (file.exists()) {
|
||||
// check if the local file has the correct contents, otherwise we overwrite
|
||||
if (file.open(QIODevice::ReadOnly) && hashData(file.readAll()) == hash) {
|
||||
if (file.open(QIODevice::ReadOnly) && AssetUtils::hashData(file.readAll()) == hash) {
|
||||
qDebug() << "Not overwriting existing verified file: " << hexHash;
|
||||
|
||||
existingCorrectFile = true;
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qDebug() << "Overwriting an existing file whose contents did not match the expected hash: " << hexHash;
|
||||
|
@ -84,7 +83,7 @@ void UploadAssetTask::run() {
|
|||
qDebug() << "Wrote file" << hexHash << "to disk. Upload complete";
|
||||
file.close();
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qWarning() << "Failed to upload or write to file" << hexHash << " - upload failed.";
|
||||
|
@ -96,7 +95,7 @@ void UploadAssetTask::run() {
|
|||
qWarning() << "Removal of failed upload file" << hexHash << "failed.";
|
||||
}
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::FileOperationFailed);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::FileOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,12 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
AvatarAudioStream* listenerAudioStream = static_cast<AudioMixerClientData*>(listener->getLinkedData())->getAvatarAudioStream();
|
||||
AudioMixerClientData* listenerData = static_cast<AudioMixerClientData*>(listener->getLinkedData());
|
||||
|
||||
// if we received an invalid position from this listener, then refuse to make them a mix
|
||||
// because we don't know how to do it properly
|
||||
if (!listenerAudioStream->hasValidPosition()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// zero out the mix for this listener
|
||||
memset(_mixSamples, 0, sizeof(_mixSamples));
|
||||
|
||||
|
@ -244,12 +250,18 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
|
||||
void AudioMixerSlave::throttleStream(AudioMixerClientData& listenerNodeData, const QUuid& sourceNodeID,
|
||||
const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, true);
|
||||
// only throttle this stream to the mix if it has a valid position, we won't know how to mix it otherwise
|
||||
if (streamToAdd.hasValidPosition()) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, true);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixerSlave::mixStream(AudioMixerClientData& listenerNodeData, const QUuid& sourceNodeID,
|
||||
const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, false);
|
||||
// only add the stream to the mix if it has a valid position, we won't know how to mix it otherwise
|
||||
if (streamToAdd.hasValidPosition()) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, false);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QUuid& sourceNodeID,
|
||||
|
|
|
@ -453,7 +453,7 @@ void EntityServer::domainSettingsRequestFailed() {
|
|||
void EntityServer::startDynamicDomainVerification() {
|
||||
qCDebug(entities) << "Starting Dynamic Domain Verification...";
|
||||
|
||||
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainId().remove(QRegExp("\\{|\\}"));
|
||||
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}"));
|
||||
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
|
||||
|
|
35
cmake/externals/crashpad/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
include(ExternalProject)
|
||||
set(EXTERNAL_NAME crashpad)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://backtrace.io/download/crashpad_062317.zip
|
||||
URL_MD5 65817e564b3628492abfc1dbd2a1e98b
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "List of Crashpad include directories")
|
||||
|
||||
set(LIB_EXT "lib")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util release library")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util debug library")
|
||||
|
||||
set(CRASHPAD_HANDLER_EXE_PATH ${SOURCE_DIR}/out/Release_x64/crashpad_handler.exe CACHE FILEPATH "Path to the Crashpad handler executable")
|
||||
endif ()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|
@ -1,34 +0,0 @@
|
|||
#
|
||||
# AddBugSplat.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Ryan Huffman on 02/09/16.
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(add_bugsplat)
|
||||
get_property(BUGSPLAT_CHECKED GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE)
|
||||
|
||||
if (NOT BUGSPLAT_CHECKED)
|
||||
find_package(BugSplat)
|
||||
set_property(GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE TRUE)
|
||||
endif ()
|
||||
|
||||
if (BUGSPLAT_FOUND)
|
||||
add_definitions(-DHAS_BUGSPLAT)
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${BUGSPLAT_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${BUGSPLAT_LIBRARIES})
|
||||
add_paths_to_fixup_libs(${BUGSPLAT_DLL_PATH})
|
||||
|
||||
add_custom_command(TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_RC_DLL_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
|
||||
add_custom_command(TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
|
||||
endif ()
|
||||
endmacro()
|
58
cmake/macros/AddCrashpad.cmake
Normal file
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# AddCrashpad.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Clement Brisset on 01/19/18.
|
||||
# Copyright 2018 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(add_crashpad)
|
||||
set (USE_CRASHPAD TRUE)
|
||||
if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "")
|
||||
set(USE_CRASHPAD FALSE)
|
||||
else()
|
||||
set(CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL})
|
||||
endif()
|
||||
|
||||
if ("$ENV{CMAKE_BACKTRACE_TOKEN}" STREQUAL "")
|
||||
set(USE_CRASHPAD FALSE)
|
||||
else()
|
||||
set(CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN})
|
||||
endif()
|
||||
|
||||
if (WIN32 AND USE_CRASHPAD)
|
||||
get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE)
|
||||
if (NOT CRASHPAD_CHECKED)
|
||||
|
||||
add_dependency_external_projects(crashpad)
|
||||
find_package(crashpad REQUIRED)
|
||||
|
||||
set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE)
|
||||
endif()
|
||||
|
||||
add_definitions(-DHAS_CRASHPAD)
|
||||
add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\")
|
||||
add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\")
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY})
|
||||
|
||||
if (WIN32)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
|
||||
)
|
||||
install(
|
||||
PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH}
|
||||
DESTINATION ${INTERFACE_INSTALL_DIR}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
endif ()
|
||||
endmacro()
|
7
cmake/macros/AddCustomQrcPath.cmake
Normal file
|
@ -0,0 +1,7 @@
|
|||
# adds a custom path and local path to the inserted CUSTOM_PATHS_VAR list which
|
||||
# can be given to the GENERATE_QRC command
|
||||
|
||||
function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH)
|
||||
list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}")
|
||||
set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE)
|
||||
endfunction()
|
14
cmake/macros/FindNPM.cmake
Normal file
|
@ -0,0 +1,14 @@
|
|||
#
|
||||
# FindNPM.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Thijs Wenker on 01/23/18.
|
||||
# Copyright 2018 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(find_npm)
|
||||
find_program(NPM_EXECUTABLE "npm")
|
||||
endmacro()
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
function(GENERATE_QRC)
|
||||
set(oneValueArgs OUTPUT PREFIX PATH)
|
||||
set(multiValueArgs GLOBS)
|
||||
set(multiValueArgs CUSTOM_PATHS GLOBS)
|
||||
cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
|
||||
if ("${GENERATE_QRC_PREFIX}" STREQUAL "")
|
||||
set(QRC_PREFIX_PATH /)
|
||||
|
@ -20,6 +20,13 @@ function(GENERATE_QRC)
|
|||
endforeach()
|
||||
endforeach()
|
||||
|
||||
foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS})
|
||||
string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH})
|
||||
list(GET CUSTOM_PATH 0 IMPORT_PATH)
|
||||
list(GET CUSTOM_PATH 1 LOCAL_PATH)
|
||||
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${LOCAL_PATH}\">${IMPORT_PATH}</file>\n")
|
||||
endforeach()
|
||||
|
||||
set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE)
|
||||
configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
|
||||
endfunction()
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
#
|
||||
# FindBugSplat.cmake
|
||||
# cmake/modules
|
||||
#
|
||||
# Created by Ryan Huffman on 02/09/16.
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (WIN32)
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("BugSplat")
|
||||
|
||||
find_path(BUGSPLAT_INCLUDE_DIRS NAMES BugSplat.h PATH_SUFFIXES inc HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
|
||||
find_library(BUGSPLAT_LIBRARY_RELEASE "BugSplat64.lib" PATH_SUFFIXES "lib64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_path(BUGSPLAT_DLL_PATH NAMES "BugSplat64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_file(BUGSPLAT_RC_DLL_PATH NAMES "BugSplatRc64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_file(BUGSPLAT_EXE_PATH NAMES "BsSndRpt64.exe" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(BUGSPLAT)
|
||||
|
||||
set(BUGSPLAT_LIBRARIES ${BUGSPLAT_LIBRARY_RELEASE})
|
||||
endif ()
|
||||
|
||||
set(BUGSPLAT_REQUIREMENTS BUGSPLAT_INCLUDE_DIRS BUGSPLAT_LIBRARIES BUGSPLAT_DLL_PATH BUGSPLAT_RC_DLL_PATH BUGSPLAT_EXE_PATH)
|
||||
find_package_handle_standard_args(BugSplat DEFAULT_MSG ${BUGSPLAT_REQUIREMENTS})
|
41
cmake/modules/FindCrashpad.cmake
Normal file
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# FindCrashpad.cmake
|
||||
#
|
||||
# Try to find Crashpad libraries and include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# CRASHPAD_FOUND
|
||||
# CRASHPAD_INCLUDE_DIRS
|
||||
# CRASHPAD_LIBRARY
|
||||
# CRASHPAD_BASE_LIBRARY
|
||||
# CRASHPAD_UTIL_LIBRARY
|
||||
#
|
||||
# Created on 01/19/2018 by Clement Brisset
|
||||
# Copyright 2018 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("crashpad")
|
||||
|
||||
find_path(CRASHPAD_INCLUDE_DIRS base/macros.h PATH_SUFFIXES include HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_library(CRASHPAD_LIBRARY_RELEASE crashpad PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_BASE_LIBRARY_RELEASE base PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_UTIL_LIBRARY_RELEASE util PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_library(CRASHPAD_LIBRARY_DEBUG crashpad PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_BASE_LIBRARY_DEBUG base PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_UTIL_LIBRARY_DEBUG util PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_file(CRASHPAD_HANDLER_EXE_PATH NAME "crashpad_handler.exe" PATH_SUFFIXES "Release_x64" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(CRASHPAD)
|
||||
select_library_configurations(CRASHPAD_BASE)
|
||||
select_library_configurations(CRASHPAD_UTIL)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(CRASHPAD DEFAULT_MSG CRASHPAD_INCLUDE_DIRS CRASHPAD_LIBRARY CRASHPAD_BASE_LIBRARY CRASHPAD_UTIL_LIBRARY)
|
|
@ -823,6 +823,7 @@ Section "-Core installation"
|
|||
Delete "$INSTDIR\server-console.exe"
|
||||
RMDir /r "$INSTDIR\locales"
|
||||
RMDir /r "$INSTDIR\resources\app"
|
||||
RMDir /r "$INSTDIR\client"
|
||||
Delete "$INSTDIR\resources\atom.asar"
|
||||
Delete "$INSTDIR\build-info.json"
|
||||
Delete "$INSTDIR\content_resources_200_percent.pak"
|
||||
|
|
|
@ -161,7 +161,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
|
|||
} else if (_server->_settingsManager.hasPermissionsForMachineFingerprint(machineFingerprint)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForMachineFingerprint(machineFingerprint);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug(() << "| user-permissions: specific Machine Fingerprint matches, so: " << userPerms;
|
||||
qDebug() << "| user-permissions: specific Machine Fingerprint matches, so: " << userPerms;
|
||||
#endif
|
||||
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
|
@ -187,7 +187,7 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
|
|||
} else if (_server->_settingsManager.hasPermissionsForMachineFingerprint(machineFingerprint)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForMachineFingerprint(machineFingerprint);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug(() << "| user-permissions: specific Machine Fingerprint matches, so: " << userPerms;
|
||||
qDebug() << "| user-permissions: specific Machine Fingerprint matches, so: " << userPerms;
|
||||
#endif
|
||||
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
|
@ -393,9 +393,12 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
|
||||
QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt
|
||||
if (!username.isEmpty()) {
|
||||
if (usernameSignature.isEmpty()) {
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||
|
||||
if (usernameSignature.isEmpty() || connectionToken.isNull()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username, true);
|
||||
getGroupMemberships(username); // optimistically get started on group memberships
|
||||
|
|
|
@ -157,7 +157,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
DependencyManager::set<StatTracker>();
|
||||
|
||||
LogUtils::init();
|
||||
Setting::init();
|
||||
|
||||
qDebug() << "Setting up domain-server";
|
||||
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
|
||||
|
|
|
@ -29,6 +29,8 @@ int main(int argc, char* argv[]) {
|
|||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Setting::init();
|
||||
|
||||
#ifndef WIN32
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
#endif
|
||||
|
|
|
@ -17,9 +17,9 @@ if (ANDROID)
|
|||
set(BUILD_SHARED_LIBS ON)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}")
|
||||
|
||||
setup_hifi_library(Gui Widgets AndroidExtras)
|
||||
setup_hifi_library(Gui AndroidExtras)
|
||||
else ()
|
||||
setup_hifi_project(Gui Widgets)
|
||||
setup_hifi_project(Gui)
|
||||
endif ()
|
||||
|
||||
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
|
|
@ -11,9 +11,17 @@ function(JOIN VALUES GLUE OUTPUT)
|
|||
set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
set(CUSTOM_INTERFACE_QRC_PATHS "")
|
||||
|
||||
find_npm()
|
||||
|
||||
if (BUILD_TOOLS AND NPM_EXECUTABLE)
|
||||
add_custom_qrc_path(CUSTOM_INTERFACE_QRC_PATHS "${CMAKE_SOURCE_DIR}/tools/jsdoc/out/hifiJSDoc.json" "auto-complete/hifiJSDoc.json")
|
||||
endif ()
|
||||
|
||||
set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
|
||||
set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc)
|
||||
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *)
|
||||
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_INTERFACE_QRC_PATHS} GLOBS *)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
|
@ -70,7 +78,7 @@ endif ()
|
|||
|
||||
find_package(
|
||||
Qt5 COMPONENTS
|
||||
Gui Multimedia Network OpenGL Qml Quick Script Svg
|
||||
Gui Widgets Multimedia Network Qml Quick Script Svg
|
||||
${PLATFORM_QT_COMPONENTS}
|
||||
WebChannel WebSockets
|
||||
)
|
||||
|
@ -163,6 +171,11 @@ else ()
|
|||
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
|
||||
endif ()
|
||||
|
||||
if (BUILD_TOOLS AND NPM_EXECUTABLE)
|
||||
# require JSDoc to be build before interface is deployed (Console Auto-complete)
|
||||
add_dependencies(resources jsdoc)
|
||||
endif()
|
||||
|
||||
add_dependencies(${TARGET_NAME} resources)
|
||||
|
||||
if (WIN32)
|
||||
|
@ -206,6 +219,7 @@ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries
|
|||
|
||||
target_bullet()
|
||||
target_opengl()
|
||||
add_crashpad()
|
||||
|
||||
# perform standard include and linking for found externals
|
||||
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
||||
|
@ -255,7 +269,7 @@ endif ()
|
|||
|
||||
target_link_libraries(
|
||||
${TARGET_NAME}
|
||||
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
|
||||
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::Widgets
|
||||
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
|
||||
Qt5::WebChannel
|
||||
${PLATFORM_QT_LIBRARIES}
|
||||
|
@ -289,6 +303,7 @@ if (APPLE)
|
|||
|
||||
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_APP_PATH}/Contents/Resources")
|
||||
|
||||
|
||||
# copy script files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
|
@ -350,8 +365,6 @@ if (SCRIPTS_INSTALL_DIR)
|
|||
)
|
||||
endif()
|
||||
|
||||
add_bugsplat()
|
||||
|
||||
if (WIN32)
|
||||
set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"")
|
||||
|
||||
|
|
|
@ -1,96 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 50"
|
||||
style="enable-background:new 0 0 50 50;"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="bubble-a.svg"><metadata
|
||||
id="metadata36"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs34" /><sodipodi:namedview
|
||||
pagecolor="#ff4900"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1149"
|
||||
inkscape:window-height="801"
|
||||
id="namedview32"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.72"
|
||||
inkscape:cx="25"
|
||||
inkscape:cy="25"
|
||||
inkscape:window-x="485"
|
||||
inkscape:window-y="514"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><style
|
||||
type="text/css"
|
||||
id="style4">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style><g
|
||||
id="Layer_2" /><g
|
||||
id="Layer_1"
|
||||
style="fill:#000000;fill-opacity:1"><g
|
||||
id="g8"
|
||||
style="fill:#000000;fill-opacity:1"><path
|
||||
class="st0"
|
||||
d="M23.2,24.1c-0.8,0.9-1.5,1.8-2.2,2.6c-0.1,0.2-0.1,0.5-0.1,0.7c0.1,1.7,0.2,3.4,0.2,5.1 c0,0.8-0.4,1.2-1.1,1.3c-0.7,0.1-1.3-0.4-1.4-1.1c-0.2-2.2-0.3-4.3-0.5-6.5c0-0.3,0.1-0.7,0.4-1c1.1-1.5,2.3-3,3.4-4.5 c0.6-0.7,1.6-1.6,2.6-1.6c0.3,0,1.1,0,1.4,0c0.8-0.1,1.3,0.1,1.9,0.9c1,1.2,1.5,2.3,2.4,3.6c0.7,1.1,1.4,1.6,2.9,1.9 c1.1,0.2,2.2,0.5,3.3,0.8c0.3,0.1,0.6,0.2,0.8,0.3c0.5,0.3,0.7,0.8,0.6,1.3c-0.1,0.5-0.5,0.7-1,0.8c-0.4,0-0.9,0-1.3-0.1 c-1.4-0.3-2.7-0.6-4.1-0.9c-0.8-0.2-1.5-0.6-2.1-1.1c-0.3-0.3-0.6-0.5-0.9-0.8c0,0.3,0,0.5,0,0.7c0,1.2,0,2.4,0,3.6 c0,0.4-0.3,12.6-0.1,16.8c0,0.5-0.1,1-0.2,1.5c-0.2,0.7-0.6,1-1.4,1.1c-0.8,0-1.4-0.3-1.7-1c-0.2-0.5-0.3-1.1-0.4-1.6 c-0.4-4.6-0.9-12.9-1.1-13.8c-0.1-0.8-0.2-1.1-0.3-2.1c-0.1-0.5-0.1-0.9-0.1-1.3C23.3,27.9,23.2,26.1,23.2,24.1z"
|
||||
id="path10"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M28.2,14.6c0,1.4-1.1,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.1-2.6-2.6v-1.6c0-1.4,1.1-2.6,2.6-2.6l0,0 c1.4,0,2.6,1.1,2.6,2.6V14.6z"
|
||||
id="path12"
|
||||
style="fill:#000000;fill-opacity:1" /></g><path
|
||||
class="st0"
|
||||
d="M8.4,38.9c2.8,3.2,6.4,5.5,10.5,6.7c0.6,0.2,1.3,0.1,1.7-0.3c0.4-0.3,0.6-0.6,0.7-1c0.2-0.5,0.1-1.1-0.2-1.5 c-0.3-0.5-0.7-0.8-1.2-1c-1.6-0.5-3.2-1.2-4.6-2.1c-1.5-0.9-2.8-2.1-4-3.4c-0.4-0.4-0.9-0.7-1.5-0.7c-0.5,0-1,0.2-1.3,0.5 c-0.4,0.4-0.6,0.8-0.7,1.4C7.8,38,8,38.5,8.4,38.9z"
|
||||
id="path14"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M43.7,36.8c0.3-0.4,0.4-1,0.3-1.5c-0.1-0.5-0.4-1-0.8-1.3c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9 c-1.1,1.6-2.4,3-4,4.2c-1.2,0.9-2.5,1.7-3.9,2.3c-0.5,0.2-0.9,0.6-1.1,1.1c-0.2,0.5-0.2,1,0,1.5c0.4,1,1.6,1.5,2.6,1 c1.7-0.7,3.3-1.7,4.8-2.8C40.7,40.4,42.4,38.7,43.7,36.8z"
|
||||
id="path16"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M5.1,33.2c0.5,0.4,1.2,0.4,1.8,0.2c0.5-0.2,0.9-0.6,1.1-1.1c0.2-0.5,0.2-1,0-1.5c-0.1-0.4-0.4-0.7-0.7-0.9 c-0.3-0.2-0.7-0.3-1.1-0.3c-0.2,0-0.5,0-0.7,0.1c-1,0.4-1.5,1.6-1.1,2.6C4.5,32.7,4.7,33,5.1,33.2z"
|
||||
id="path18"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M45.4,27.3c-0.2,0-0.3-0.1-0.5-0.1c-0.9,0-1.7,0.6-1.9,1.5c-0.1,0.5-0.1,1.1,0.2,1.5c0.3,0.5,0.7,0.8,1.2,0.9 c0.2,0,0.3,0.1,0.5,0.1c0.9,0,1.7-0.6,1.9-1.5c0.1-0.5,0.1-1.1-0.2-1.5C46.4,27.8,45.9,27.5,45.4,27.3z"
|
||||
id="path20"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M8.6,12c-0.3-0.2-0.7-0.3-1-0.3c-0.3,0-0.7,0.1-1,0.3c-0.3,0.2-0.6,0.4-0.7,0.7c-2,3.5-3.1,7.4-3.1,11.4 c0,0.2,0,0.4,0,0.6c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6v0.4l0.1-0.4c0.5,0,1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.5-1.4 c0-0.2,0-0.4,0-0.5c0-3.3,0.9-6.6,2.6-9.4C9.9,13.8,9.6,12.6,8.6,12z"
|
||||
id="path22"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M39.3,11.4c-0.1,0.5,0.1,1.1,0.4,1.5c1.1,1.4,2,3,2.6,4.6c0.6,1.6,1,3.2,1.2,4.9c0,0.5,0.3,1,0.6,1.3 c0.4,0.4,1,0.6,1.5,0.5c0.5,0,1-0.3,1.4-0.7c0.3-0.4,0.5-0.9,0.5-1.5c-0.4-4.2-2-8.2-4.6-11.6c-0.4-0.5-1-0.8-1.6-0.8 c-0.4,0-0.9,0.1-1.2,0.4C39.7,10.4,39.4,10.8,39.3,11.4z"
|
||||
id="path24"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M12.2,6.3c-0.5,0-0.9,0.2-1.3,0.5c-0.4,0.3-0.7,0.8-0.7,1.4c-0.1,0.5,0.1,1.1,0.4,1.5c0.7,0.8,2,1,2.8,0.3 c0.4-0.3,0.7-0.8,0.7-1.3c0.1-0.5-0.1-1.1-0.4-1.5C13.4,6.6,12.8,6.3,12.2,6.3z"
|
||||
id="path26"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M37.2,5.2c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9c-0.3,0.4-0.4,1-0.3,1.5c0.1,0.5,0.4,1,0.9,1.3 C36,9.2,37.3,8.9,37.9,8C38.4,7.1,38.2,5.8,37.2,5.2z"
|
||||
id="path28"
|
||||
style="fill:#000000;fill-opacity:1" /><path
|
||||
class="st0"
|
||||
d="M16.5,4c-0.2,0.5-0.3,1-0.1,1.5c0.4,1,1.5,1.6,2.6,1.2c3.3-1.2,6.8-1.4,10.2-0.6c0.6,0.1,1.2,0,1.7-0.4 c0.4-0.3,0.6-0.7,0.7-1.1c0.1-0.5,0-1.1-0.3-1.5c-0.3-0.5-0.7-0.8-1.3-0.9c-1.6-0.4-3.3-0.5-4.9-0.5c-2.6,0-5.1,0.4-7.5,1.3 C17.1,3.2,16.7,3.6,16.5,4z"
|
||||
id="path30"
|
||||
style="fill:#000000;fill-opacity:1" /></g></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<g>
|
||||
<ellipse cx="26.1" cy="18.8" rx="4.6" ry="4.5"/>
|
||||
<path d="M29.6,25.8h-6.7c-2.8,0-5,2.3-5,5v2c2,2.9,7.4,6.8,8.5,6.8c1.3,0,6.3-4,8.1-6.9v-1.9C34.5,28,32.4,25.8,29.6,25.8z"/>
|
||||
<path d="M25.8,5.4C32.5,8,35.9,8.6,38.9,9.3C40,9.5,41,9.7,42.1,9.9c-0.3,4.9-0.3,9.8-0.3,12.5c0,8.7-3.6,15.3-11.3,20.4
|
||||
c-1.2,0.8-2.7,1.5-4.2,2.3c-0.1,0.1-0.2,0.1-0.3,0.2c-3.7-1.7-6.8-3.8-9.2-6.3c-2.7-2.8-4.6-6.1-5.7-10.1c-0.5-1.7-0.7-3.6-0.8-6.3
|
||||
c-0.1-3.6-0.3-7.8-0.7-12.9C11,9.5,12.2,9.3,13.5,9C16.6,8.3,20.1,7.6,25.8,5.4 M25.8,1.5c-0.2,0-0.4,0-0.6,0.1
|
||||
C15.7,5.4,12.6,4.9,6.6,6.8C6,7,5.8,7.3,5.8,7.9c0.4,5,0.7,10.2,0.8,14.9c0.1,2.4,0.3,4.9,0.9,7.2c2.6,9.4,9,15.4,17.9,19.2
|
||||
c0.2,0.1,0.4,0.1,0.6,0.1c0.2,0,0.5,0,0.7-0.1c2-1,4-1.9,5.8-3.1c8.4-5.7,13-13.4,12.9-23.6c0-3.8,0.1-9.8,0.5-15.2
|
||||
c-0.2-0.1-0.3-0.2-0.5-0.2c-6.2-2-8.1-1.1-19.3-5.5C26.1,1.5,26,1.5,25.8,1.5L25.8,1.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -1,46 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path class="st0" d="M23.2,24.1c-0.8,0.9-1.5,1.8-2.2,2.6c-0.1,0.2-0.1,0.5-0.1,0.7c0.1,1.7,0.2,3.4,0.2,5.1
|
||||
c0,0.8-0.4,1.2-1.1,1.3c-0.7,0.1-1.3-0.4-1.4-1.1c-0.2-2.2-0.3-4.3-0.5-6.5c0-0.3,0.1-0.7,0.4-1c1.1-1.5,2.3-3,3.4-4.5
|
||||
c0.6-0.7,1.6-1.6,2.6-1.6c0.3,0,1.1,0,1.4,0c0.8-0.1,1.3,0.1,1.9,0.9c1,1.2,1.5,2.3,2.4,3.6c0.7,1.1,1.4,1.6,2.9,1.9
|
||||
c1.1,0.2,2.2,0.5,3.3,0.8c0.3,0.1,0.6,0.2,0.8,0.3c0.5,0.3,0.7,0.8,0.6,1.3c-0.1,0.5-0.5,0.7-1,0.8c-0.4,0-0.9,0-1.3-0.1
|
||||
c-1.4-0.3-2.7-0.6-4.1-0.9c-0.8-0.2-1.5-0.6-2.1-1.1c-0.3-0.3-0.6-0.5-0.9-0.8c0,0.3,0,0.5,0,0.7c0,1.2,0,2.4,0,3.6
|
||||
c0,0.4-0.3,12.6-0.1,16.8c0,0.5-0.1,1-0.2,1.5c-0.2,0.7-0.6,1-1.4,1.1c-0.8,0-1.4-0.3-1.7-1c-0.2-0.5-0.3-1.1-0.4-1.6
|
||||
c-0.4-4.6-0.9-12.9-1.1-13.8c-0.1-0.8-0.2-1.1-0.3-2.1c-0.1-0.5-0.1-0.9-0.1-1.3C23.3,27.9,23.2,26.1,23.2,24.1z"/>
|
||||
<path class="st0" d="M28.2,14.6c0,1.4-1.1,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.1-2.6-2.6v-1.6c0-1.4,1.1-2.6,2.6-2.6l0,0
|
||||
c1.4,0,2.6,1.1,2.6,2.6V14.6z"/>
|
||||
</g>
|
||||
<path class="st0" d="M8.4,38.9c2.8,3.2,6.4,5.5,10.5,6.7c0.6,0.2,1.3,0.1,1.7-0.3c0.4-0.3,0.6-0.6,0.7-1c0.2-0.5,0.1-1.1-0.2-1.5
|
||||
c-0.3-0.5-0.7-0.8-1.2-1c-1.6-0.5-3.2-1.2-4.6-2.1c-1.5-0.9-2.8-2.1-4-3.4c-0.4-0.4-0.9-0.7-1.5-0.7c-0.5,0-1,0.2-1.3,0.5
|
||||
c-0.4,0.4-0.6,0.8-0.7,1.4C7.8,38,8,38.5,8.4,38.9z"/>
|
||||
<path class="st0" d="M43.7,36.8c0.3-0.4,0.4-1,0.3-1.5c-0.1-0.5-0.4-1-0.8-1.3c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9
|
||||
c-1.1,1.6-2.4,3-4,4.2c-1.2,0.9-2.5,1.7-3.9,2.3c-0.5,0.2-0.9,0.6-1.1,1.1c-0.2,0.5-0.2,1,0,1.5c0.4,1,1.6,1.5,2.6,1
|
||||
c1.7-0.7,3.3-1.7,4.8-2.8C40.7,40.4,42.4,38.7,43.7,36.8z"/>
|
||||
<path class="st0" d="M5.1,33.2c0.5,0.4,1.2,0.4,1.8,0.2c0.5-0.2,0.9-0.6,1.1-1.1c0.2-0.5,0.2-1,0-1.5c-0.1-0.4-0.4-0.7-0.7-0.9
|
||||
c-0.3-0.2-0.7-0.3-1.1-0.3c-0.2,0-0.5,0-0.7,0.1c-1,0.4-1.5,1.6-1.1,2.6C4.5,32.7,4.7,33,5.1,33.2z"/>
|
||||
<path class="st0" d="M45.4,27.3c-0.2,0-0.3-0.1-0.5-0.1c-0.9,0-1.7,0.6-1.9,1.5c-0.1,0.5-0.1,1.1,0.2,1.5c0.3,0.5,0.7,0.8,1.2,0.9
|
||||
c0.2,0,0.3,0.1,0.5,0.1c0.9,0,1.7-0.6,1.9-1.5c0.1-0.5,0.1-1.1-0.2-1.5C46.4,27.8,45.9,27.5,45.4,27.3z"/>
|
||||
<path class="st0" d="M8.6,12c-0.3-0.2-0.7-0.3-1-0.3c-0.3,0-0.7,0.1-1,0.3c-0.3,0.2-0.6,0.4-0.7,0.7c-2,3.5-3.1,7.4-3.1,11.4
|
||||
c0,0.2,0,0.4,0,0.6c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6v0.4l0.1-0.4c0.5,0,1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.5-1.4
|
||||
c0-0.2,0-0.4,0-0.5c0-3.3,0.9-6.6,2.6-9.4C9.9,13.8,9.6,12.6,8.6,12z"/>
|
||||
<path class="st0" d="M39.3,11.4c-0.1,0.5,0.1,1.1,0.4,1.5c1.1,1.4,2,3,2.6,4.6c0.6,1.6,1,3.2,1.2,4.9c0,0.5,0.3,1,0.6,1.3
|
||||
c0.4,0.4,1,0.6,1.5,0.5c0.5,0,1-0.3,1.4-0.7c0.3-0.4,0.5-0.9,0.5-1.5c-0.4-4.2-2-8.2-4.6-11.6c-0.4-0.5-1-0.8-1.6-0.8
|
||||
c-0.4,0-0.9,0.1-1.2,0.4C39.7,10.4,39.4,10.8,39.3,11.4z"/>
|
||||
<path class="st0" d="M12.2,6.3c-0.5,0-0.9,0.2-1.3,0.5c-0.4,0.3-0.7,0.8-0.7,1.4c-0.1,0.5,0.1,1.1,0.4,1.5c0.7,0.8,2,1,2.8,0.3
|
||||
c0.4-0.3,0.7-0.8,0.7-1.3c0.1-0.5-0.1-1.1-0.4-1.5C13.4,6.6,12.8,6.3,12.2,6.3z"/>
|
||||
<path class="st0" d="M37.2,5.2c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9c-0.3,0.4-0.4,1-0.3,1.5c0.1,0.5,0.4,1,0.9,1.3
|
||||
C36,9.2,37.3,8.9,37.9,8C38.4,7.1,38.2,5.8,37.2,5.2z"/>
|
||||
<path class="st0" d="M16.5,4c-0.2,0.5-0.3,1-0.1,1.5c0.4,1,1.5,1.6,2.6,1.2c3.3-1.2,6.8-1.4,10.2-0.6c0.6,0.1,1.2,0,1.7-0.4
|
||||
c0.4-0.3,0.6-0.7,0.7-1.1c0.1-0.5,0-1.1-0.3-1.5c-0.3-0.5-0.7-0.8-1.3-0.9c-1.6-0.4-3.3-0.5-4.9-0.5c-2.6,0-5.1,0.4-7.5,1.3
|
||||
C17.1,3.2,16.7,3.6,16.5,4z"/>
|
||||
<g>
|
||||
<ellipse class="st0" cx="26.1" cy="18.8" rx="4.6" ry="4.5"/>
|
||||
<path class="st0" d="M29.6,25.8h-6.7c-2.8,0-5,2.3-5,5v2c2,2.9,7.4,6.8,8.5,6.8c1.3,0,6.3-4,8.1-6.9v-1.9
|
||||
C34.5,28,32.4,25.8,29.6,25.8z"/>
|
||||
<path class="st0" d="M25.8,5.4C32.5,8,35.9,8.6,38.9,9.3C40,9.5,41,9.7,42.1,9.9c-0.3,4.9-0.3,9.8-0.3,12.5
|
||||
c0,8.7-3.6,15.3-11.3,20.4c-1.2,0.8-2.7,1.5-4.2,2.3c-0.1,0.1-0.2,0.1-0.3,0.2c-3.7-1.7-6.8-3.8-9.2-6.3c-2.7-2.8-4.6-6.1-5.7-10.1
|
||||
c-0.5-1.7-0.7-3.6-0.8-6.3c-0.1-3.6-0.3-7.8-0.7-12.9C11,9.5,12.2,9.3,13.5,9C16.6,8.3,20.1,7.6,25.8,5.4 M25.8,1.5
|
||||
c-0.2,0-0.4,0-0.6,0.1C15.7,5.4,12.6,4.9,6.6,6.8C6,7,5.8,7.3,5.8,7.9c0.4,5,0.7,10.2,0.8,14.9c0.1,2.4,0.3,4.9,0.9,7.2
|
||||
c2.6,9.4,9,15.4,17.9,19.2c0.2,0.1,0.4,0.1,0.6,0.1c0.2,0,0.5,0,0.7-0.1c2-1,4-1.9,5.8-3.1c8.4-5.7,13-13.4,12.9-23.6
|
||||
c0-3.8,0.1-9.8,0.5-15.2c-0.2-0.1-0.3-0.2-0.5-0.2c-6.2-2-8.1-1.1-19.3-5.5C26.1,1.5,26,1.5,25.8,1.5L25.8,1.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -32,6 +32,12 @@ Original.Button {
|
|||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
}
|
||||
}
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
|
@ -59,6 +65,8 @@ Original.Button {
|
|||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else if (!control.hovered && control.focus) {
|
||||
hifi.buttons.focusedColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorStart[control.color]
|
||||
}
|
||||
|
@ -73,6 +81,8 @@ Original.Button {
|
|||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else if (!control.hovered && control.focus) {
|
||||
hifi.buttons.focusedColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorFinish[control.color]
|
||||
}
|
||||
|
|
|
@ -31,6 +31,12 @@ Original.Button {
|
|||
}
|
||||
}
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
}
|
||||
|
@ -50,6 +56,8 @@ Original.Button {
|
|||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else if (!control.hovered && control.focus) {
|
||||
hifi.buttons.focusedColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorStart[control.color]
|
||||
}
|
||||
|
@ -64,6 +72,8 @@ Original.Button {
|
|||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else if (!control.hovered && control.focus) {
|
||||
hifi.buttons.focusedColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorFinish[control.color]
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ TableView {
|
|||
size: hifi.fontSizes.tableHeadingIcon
|
||||
anchors {
|
||||
left: titleText.right
|
||||
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 5 : 0)
|
||||
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 15 : 10)
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: titleText.verticalCenter
|
||||
|
|
|
@ -110,7 +110,17 @@ ModalWindow {
|
|||
}
|
||||
});
|
||||
|
||||
fileTableView.forceActiveFocus();
|
||||
focusTimer.start();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: focusTimer
|
||||
interval: 10
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
fileTableView.contentItem.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -130,7 +140,9 @@ ModalWindow {
|
|||
drag.target: root
|
||||
onClicked: {
|
||||
d.clearSelection();
|
||||
frame.forceActiveFocus(); // Defocus text field so that the keyboard gets hidden.
|
||||
// Defocus text field so that the keyboard gets hidden.
|
||||
// Clicking also breaks keyboard navigation apart from backtabbing to cancel
|
||||
frame.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,6 +162,11 @@ ModalWindow {
|
|||
size: 30
|
||||
enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
|
||||
onClicked: d.navigateUp();
|
||||
Keys.onReturnPressed: { d.navigateUp(); }
|
||||
KeyNavigation.tab: homeButton
|
||||
KeyNavigation.backtab: upButton
|
||||
KeyNavigation.left: upButton
|
||||
KeyNavigation.right: homeButton
|
||||
}
|
||||
|
||||
GlyphButton {
|
||||
|
@ -160,6 +177,10 @@ ModalWindow {
|
|||
width: height
|
||||
enabled: d.homeDestination ? true : false
|
||||
onClicked: d.navigateHome();
|
||||
Keys.onReturnPressed: { d.navigateHome(); }
|
||||
KeyNavigation.tab: fileTableView.contentItem
|
||||
KeyNavigation.backtab: upButton
|
||||
KeyNavigation.left: upButton
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,9 +249,15 @@ ModalWindow {
|
|||
d.currentSelectionUrl = helper.pathToUrl(currentText);
|
||||
}
|
||||
fileTableModel.folder = folder;
|
||||
fileTableView.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.up: fileTableView.contentItem
|
||||
KeyNavigation.down: fileTableView.contentItem
|
||||
KeyNavigation.tab: fileTableView.contentItem
|
||||
KeyNavigation.backtab: fileTableView.contentItem
|
||||
KeyNavigation.left: fileTableView.contentItem
|
||||
KeyNavigation.right: fileTableView.contentItem
|
||||
}
|
||||
|
||||
QtObject {
|
||||
|
@ -483,7 +510,6 @@ ModalWindow {
|
|||
}
|
||||
headerVisible: !selectDirectory
|
||||
onDoubleClicked: navigateToRow(row);
|
||||
focus: true
|
||||
Keys.onReturnPressed: navigateToCurrentRow();
|
||||
Keys.onEnterPressed: navigateToCurrentRow();
|
||||
|
||||
|
@ -560,7 +586,7 @@ ModalWindow {
|
|||
resizable: true
|
||||
}
|
||||
TableViewColumn {
|
||||
id: fileMofifiedColumn
|
||||
id: fileModifiedColumn
|
||||
role: "fileModified"
|
||||
title: "Date"
|
||||
width: 0.3 * fileTableView.width
|
||||
|
@ -571,7 +597,7 @@ ModalWindow {
|
|||
TableViewColumn {
|
||||
role: "fileSize"
|
||||
title: "Size"
|
||||
width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
|
||||
width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width
|
||||
movable: false
|
||||
resizable: true
|
||||
visible: !selectDirectory
|
||||
|
@ -649,6 +675,8 @@ ModalWindow {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.tab: root.saveDialog ? currentSelection : openButton
|
||||
}
|
||||
|
||||
TextField {
|
||||
|
@ -665,6 +693,10 @@ ModalWindow {
|
|||
activeFocusOnTab: !readOnly
|
||||
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
||||
onAccepted: okAction.trigger();
|
||||
KeyNavigation.up: fileTableView.contentItem
|
||||
KeyNavigation.down: openButton
|
||||
KeyNavigation.tab: openButton
|
||||
KeyNavigation.backtab: fileTableView.contentItem
|
||||
}
|
||||
|
||||
FileTypeSelection {
|
||||
|
@ -675,8 +707,6 @@ ModalWindow {
|
|||
right: parent.right
|
||||
}
|
||||
visible: !selectDirectory && filtersCount > 1
|
||||
KeyNavigation.left: fileTableView
|
||||
KeyNavigation.right: openButton
|
||||
}
|
||||
|
||||
Keyboard {
|
||||
|
@ -704,18 +734,18 @@ ModalWindow {
|
|||
color: hifi.buttons.blue
|
||||
action: okAction
|
||||
Keys.onReturnPressed: okAction.trigger()
|
||||
KeyNavigation.up: selectionType
|
||||
KeyNavigation.left: selectionType
|
||||
KeyNavigation.right: cancelButton
|
||||
KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
|
||||
KeyNavigation.tab: cancelButton
|
||||
}
|
||||
|
||||
Button {
|
||||
id: cancelButton
|
||||
action: cancelAction
|
||||
KeyNavigation.up: selectionType
|
||||
Keys.onReturnPressed: { cancelAction.trigger() }
|
||||
KeyNavigation.left: openButton
|
||||
KeyNavigation.right: fileTableView.contentItem
|
||||
Keys.onReturnPressed: { canceled(); root.enabled = false }
|
||||
KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
|
||||
KeyNavigation.backtab: openButton
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,16 @@ ModalWindow {
|
|||
return OffscreenUi.waitForMessageBoxResult(root);
|
||||
}
|
||||
|
||||
Keys.onRightPressed: if (defaultButton === OriginalDialogs.StandardButton.Yes) {
|
||||
yesButton.forceActiveFocus()
|
||||
} else if (defaultButton === OriginalDialogs.StandardButton.Ok) {
|
||||
okButton.forceActiveFocus()
|
||||
}
|
||||
Keys.onTabPressed: if (defaultButton === OriginalDialogs.StandardButton.Yes) {
|
||||
yesButton.forceActiveFocus()
|
||||
} else if (defaultButton === OriginalDialogs.StandardButton.Ok) {
|
||||
okButton.forceActiveFocus()
|
||||
}
|
||||
property alias detailedText: detailedText.text
|
||||
property alias text: mainTextContainer.text
|
||||
property alias informativeText: informativeTextContainer.text
|
||||
|
@ -47,7 +57,6 @@ ModalWindow {
|
|||
onIconChanged: updateIcon();
|
||||
property int defaultButton: OriginalDialogs.StandardButton.NoButton;
|
||||
property int clickedButton: OriginalDialogs.StandardButton.NoButton;
|
||||
focus: defaultButton === OriginalDialogs.StandardButton.NoButton
|
||||
|
||||
property int titleWidth: 0
|
||||
onTitleWidthChanged: d.resize();
|
||||
|
@ -134,16 +143,35 @@ ModalWindow {
|
|||
MessageDialogButton { dialog: root; text: qsTr("Reset"); button: OriginalDialogs.StandardButton.Reset; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("Discard"); button: OriginalDialogs.StandardButton.Discard; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("No to All"); button: OriginalDialogs.StandardButton.NoToAll; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("No"); button: OriginalDialogs.StandardButton.No; }
|
||||
MessageDialogButton {
|
||||
id: noButton
|
||||
dialog: root
|
||||
text: qsTr("No")
|
||||
button: OriginalDialogs.StandardButton.No
|
||||
KeyNavigation.left: yesButton
|
||||
KeyNavigation.backtab: yesButton
|
||||
}
|
||||
MessageDialogButton { dialog: root; text: qsTr("Yes to All"); button: OriginalDialogs.StandardButton.YesToAll; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("Yes"); button: OriginalDialogs.StandardButton.Yes; }
|
||||
MessageDialogButton {
|
||||
id: yesButton
|
||||
dialog: root
|
||||
text: qsTr("Yes")
|
||||
button: OriginalDialogs.StandardButton.Yes
|
||||
KeyNavigation.right: noButton
|
||||
KeyNavigation.tab: noButton
|
||||
}
|
||||
MessageDialogButton { dialog: root; text: qsTr("Apply"); button: OriginalDialogs.StandardButton.Apply; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("Ignore"); button: OriginalDialogs.StandardButton.Ignore; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("Retry"); button: OriginalDialogs.StandardButton.Retry; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("Save All"); button: OriginalDialogs.StandardButton.SaveAll; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("Save"); button: OriginalDialogs.StandardButton.Save; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("Open"); button: OriginalDialogs.StandardButton.Open; }
|
||||
MessageDialogButton { dialog: root; text: qsTr("OK"); button: OriginalDialogs.StandardButton.Ok; }
|
||||
MessageDialogButton {
|
||||
id: okButton
|
||||
dialog: root
|
||||
text: qsTr("OK")
|
||||
button: OriginalDialogs.StandardButton.Ok
|
||||
}
|
||||
|
||||
Button {
|
||||
id: moreButton
|
||||
|
@ -230,12 +258,6 @@ ModalWindow {
|
|||
event.accepted = true
|
||||
root.click(OriginalDialogs.StandardButton.Cancel)
|
||||
break
|
||||
|
||||
case Qt.Key_Enter:
|
||||
case Qt.Key_Return:
|
||||
event.accepted = true
|
||||
root.click(root.defaultButton)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,19 +95,19 @@ ModalWindow {
|
|||
TextField {
|
||||
id: textResult
|
||||
label: root.label
|
||||
focus: items ? false : true
|
||||
visible: items ? false : true
|
||||
anchors {
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
bottom: parent.bottom
|
||||
}
|
||||
KeyNavigation.down: acceptButton
|
||||
KeyNavigation.tab: acceptButton
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBox
|
||||
label: root.label
|
||||
focus: true
|
||||
visible: items ? true : false
|
||||
anchors {
|
||||
left: parent.left
|
||||
|
@ -115,6 +115,8 @@ ModalWindow {
|
|||
bottom: parent.bottom
|
||||
}
|
||||
model: items ? items : []
|
||||
KeyNavigation.down: acceptButton
|
||||
KeyNavigation.tab: acceptButton
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,7 +137,6 @@ ModalWindow {
|
|||
|
||||
Flow {
|
||||
id: buttons
|
||||
focus: true
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
onHeightChanged: d.resize(); onWidthChanged: d.resize();
|
||||
layoutDirection: Qt.RightToLeft
|
||||
|
@ -145,8 +146,21 @@ ModalWindow {
|
|||
margins: 0
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
Button { action: cancelAction }
|
||||
Button { action: acceptAction }
|
||||
Button {
|
||||
id: cancelButton
|
||||
action: cancelAction
|
||||
KeyNavigation.left: acceptButton
|
||||
KeyNavigation.up: items ? comboBox : textResult
|
||||
KeyNavigation.backtab: acceptButton
|
||||
}
|
||||
Button {
|
||||
id: acceptButton
|
||||
action: acceptAction
|
||||
KeyNavigation.right: cancelButton
|
||||
KeyNavigation.up: items ? comboBox : textResult
|
||||
KeyNavigation.tab: cancelButton
|
||||
KeyNavigation.backtab: items ? comboBox : textResult
|
||||
}
|
||||
}
|
||||
|
||||
Action {
|
||||
|
@ -184,7 +198,13 @@ ModalWindow {
|
|||
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
acceptAction.trigger()
|
||||
if (acceptButton.focus) {
|
||||
acceptAction.trigger()
|
||||
} else if (cancelButton.focus) {
|
||||
cancelAction.trigger()
|
||||
} else if (comboBox.focus || comboBox.popup.focus) {
|
||||
comboBox.showList()
|
||||
}
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
|
@ -194,6 +214,10 @@ ModalWindow {
|
|||
keyboardEnabled = HMD.active;
|
||||
updateIcon();
|
||||
d.resize();
|
||||
textResult.forceActiveFocus();
|
||||
if (items) {
|
||||
comboBox.forceActiveFocus()
|
||||
} else {
|
||||
textResult.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,21 @@ import "../../controls-uit"
|
|||
|
||||
Button {
|
||||
property var dialog;
|
||||
property int button: StandardButton.NoButton;
|
||||
property int button: StandardButton.Ok;
|
||||
|
||||
color: dialog.defaultButton === button ? hifi.buttons.blue : hifi.buttons.white
|
||||
focus: dialog.defaultButton === button
|
||||
color: focus ? hifi.buttons.blue : hifi.buttons.white
|
||||
onClicked: dialog.click(button)
|
||||
visible: dialog.buttons & button
|
||||
Keys.onPressed: {
|
||||
if (!focus) {
|
||||
return
|
||||
}
|
||||
switch (event.key) {
|
||||
case Qt.Key_Enter:
|
||||
case Qt.Key_Return:
|
||||
event.accepted = true
|
||||
dialog.click(button)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ Column {
|
|||
property int labelSize: 20;
|
||||
|
||||
property string metaverseServerUrl: '';
|
||||
property string protocol: '';
|
||||
property string actions: 'snapshot';
|
||||
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
|
||||
property string labelText: actions;
|
||||
|
@ -102,7 +103,7 @@ Column {
|
|||
'include_actions=' + actions,
|
||||
'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
|
||||
'require_online=true',
|
||||
'protocol=' + encodeURIComponent(Window.protocolSignature()),
|
||||
'protocol=' + protocol,
|
||||
'page=' + pageNumber
|
||||
];
|
||||
var url = metaverseBase + 'user_stories?' + options.join('&');
|
||||
|
|
|
@ -571,6 +571,7 @@ Rectangle {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
RalewayBold {
|
||||
|
|
|
@ -45,6 +45,7 @@ Rectangle {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
|
|
@ -44,6 +44,7 @@ Rectangle {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Item {
|
||||
|
|
|
@ -22,7 +22,7 @@ ListModel {
|
|||
function swap(a, b) {
|
||||
if (a < b) {
|
||||
move(a, b, 1);
|
||||
move (b - 1, a, 1);
|
||||
move(b - 1, a, 1);
|
||||
} else if (a > b) {
|
||||
move(b, a, 1);
|
||||
move(a - 1, b, 1);
|
||||
|
@ -34,16 +34,17 @@ ListModel {
|
|||
var piv = get(pivot)[sortColumnName];
|
||||
swap(pivot, end - 1);
|
||||
var store = begin;
|
||||
var i;
|
||||
|
||||
for (var i = begin; i < end - 1; ++i) {
|
||||
for (i = begin; i < end - 1; ++i) {
|
||||
var currentElement = get(i)[sortColumnName];
|
||||
if (isSortingDescending) {
|
||||
if (currentElement < piv) {
|
||||
if (currentElement > piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
} else {
|
||||
if (currentElement > piv) {
|
||||
if (currentElement < piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
|
@ -56,16 +57,17 @@ ListModel {
|
|||
var piv = get(pivot)[sortColumnName].toLowerCase();
|
||||
swap(pivot, end - 1);
|
||||
var store = begin;
|
||||
var i;
|
||||
|
||||
for (var i = begin; i < end - 1; ++i) {
|
||||
for (i = begin; i < end - 1; ++i) {
|
||||
var currentElement = get(i)[sortColumnName].toLowerCase();
|
||||
if (isSortingDescending) {
|
||||
if (currentElement < piv) {
|
||||
if (currentElement > piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
} else {
|
||||
if (currentElement > piv) {
|
||||
if (currentElement < piv) {
|
||||
swap(store, i);
|
||||
++store;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
@ -19,21 +19,31 @@ import "../../../controls-uit" as HifiControlsUit
|
|||
import "../../../controls" as HifiControls
|
||||
import "../wallet" as HifiWallet
|
||||
|
||||
// references XXX from root context
|
||||
|
||||
Rectangle {
|
||||
HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
property string marketplaceUrl;
|
||||
property string certificateId;
|
||||
property string marketplaceUrl: "";
|
||||
property string entityId: "";
|
||||
property string certificateId: "";
|
||||
property string itemName: "--";
|
||||
property string itemOwner: "--";
|
||||
property string itemEdition: "--";
|
||||
property string dateOfPurchase: "--";
|
||||
property string itemCost: "--";
|
||||
property string certTitleTextColor: hifi.colors.darkGray;
|
||||
property string certTextColor: hifi.colors.white;
|
||||
property string infoTextColor: hifi.colors.blueAccent;
|
||||
// 0 means replace none
|
||||
// 4 means replace all but "Item Edition"
|
||||
// 5 means replace all 5 replaceable fields
|
||||
property int certInfoReplaceMode: 5;
|
||||
property bool isLightbox: false;
|
||||
property bool isMyCert: false;
|
||||
property bool isCertificateInvalid: false;
|
||||
property bool useGoldCert: true;
|
||||
property bool certificateInfoPending: true;
|
||||
property int certificateStatus: 0;
|
||||
property bool certificateStatusPending: true;
|
||||
// Style
|
||||
color: hifi.colors.faintGray;
|
||||
Connections {
|
||||
|
@ -45,72 +55,135 @@ Rectangle {
|
|||
} else {
|
||||
root.marketplaceUrl = result.data.marketplace_item_url;
|
||||
root.isMyCert = result.isMyCert ? result.isMyCert : false;
|
||||
root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username :
|
||||
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022");
|
||||
root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" :
|
||||
(result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run));
|
||||
root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000);
|
||||
root.itemName = result.data.marketplace_item_name;
|
||||
|
||||
if (root.certInfoReplaceMode > 3) {
|
||||
root.itemName = result.data.marketplace_item_name;
|
||||
// "\u2022" is the Unicode character 'BULLET' - it's what's used in password fields on the web, etc
|
||||
root.itemOwner = root.isMyCert ? Account.username :
|
||||
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
||||
root.dateOfPurchase = root.isMyCert ? getFormattedDate(result.data.transfer_created_at * 1000) : "Undisclosed";
|
||||
root.itemCost = (root.isMyCert && result.data.cost !== undefined) ? result.data.cost : "Undisclosed";
|
||||
}
|
||||
if (root.certInfoReplaceMode > 4) {
|
||||
root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run);
|
||||
}
|
||||
|
||||
if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED
|
||||
if (root.isMyCert) {
|
||||
errorText.text = "This item is an uncertified copy of an item you purchased.";
|
||||
} else {
|
||||
errorText.text = "The person who placed this item doesn't own it.";
|
||||
}
|
||||
}
|
||||
|
||||
if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") {
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
titleBarText.color = hifi.colors.redHighlight;
|
||||
root.useGoldCert = false;
|
||||
root.certTitleTextColor = hifi.colors.redHighlight;
|
||||
root.certTextColor = hifi.colors.redHighlight;
|
||||
root.infoTextColor = hifi.colors.redHighlight;
|
||||
titleBarText.text = "Certificate\nNo Longer Valid";
|
||||
popText.text = "";
|
||||
showInMarketplaceButton.visible = false;
|
||||
// "Edition" text previously set above in this function
|
||||
// "Owner" text previously set above in this function
|
||||
// "Purchase Date" text previously set above in this function
|
||||
// "Purchase Price" text previously set above in this function
|
||||
if (result.data.invalid_reason) {
|
||||
errorText.text = result.data.invalid_reason;
|
||||
}
|
||||
} else if (result.data.transfer_status[0] === "pending") {
|
||||
root.useGoldCert = false;
|
||||
root.certTitleTextColor = hifi.colors.redHighlight;
|
||||
root.certTextColor = hifi.colors.redHighlight;
|
||||
root.infoTextColor = hifi.colors.redHighlight;
|
||||
titleBarText.text = "Certificate Pending";
|
||||
popText.text = "";
|
||||
showInMarketplaceButton.visible = true;
|
||||
// "Edition" text previously set above in this function
|
||||
// "Owner" text previously set above in this function
|
||||
// "Purchase Date" text previously set above in this function
|
||||
// "Purchase Price" text previously set above in this function
|
||||
errorText.text = "The status of this item is still pending confirmation. If the purchase is not confirmed, " +
|
||||
"this entity will be cleaned up by the domain.";
|
||||
errorText.color = hifi.colors.baseGray;
|
||||
}
|
||||
}
|
||||
root.certificateInfoPending = false;
|
||||
}
|
||||
|
||||
onUpdateCertificateStatus: {
|
||||
if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS
|
||||
// NOP
|
||||
} else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT
|
||||
root.isCertificateInvalid = true;
|
||||
errorText.text = "Verification of this certificate timed out.";
|
||||
errorText.color = hifi.colors.redHighlight;
|
||||
} else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED
|
||||
root.isCertificateInvalid = true;
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
titleBarText.color = hifi.colors.redHighlight;
|
||||
|
||||
popText.text = "";
|
||||
root.itemOwner = "";
|
||||
dateOfPurchaseHeader.text = "";
|
||||
root.dateOfPurchase = "";
|
||||
root.itemEdition = "Uncertified Copy";
|
||||
|
||||
errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item.";
|
||||
errorText.color = hifi.colors.baseGray;
|
||||
} else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED
|
||||
root.isCertificateInvalid = true;
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
titleBarText.color = hifi.colors.redHighlight;
|
||||
|
||||
popText.text = "";
|
||||
root.itemOwner = "";
|
||||
dateOfPurchaseHeader.text = "";
|
||||
root.dateOfPurchase = "";
|
||||
root.itemEdition = "Uncertified Copy";
|
||||
|
||||
errorText.text = "The avatar who rezzed this item doesn't own it.";
|
||||
errorText.color = hifi.colors.baseGray;
|
||||
} else {
|
||||
console.log("Unknown certificate status received from ledger signal!");
|
||||
}
|
||||
updateCertificateStatus(certStatus);
|
||||
}
|
||||
}
|
||||
|
||||
onCertificateIdChanged: {
|
||||
if (certificateId !== "") {
|
||||
Commerce.certificateInfo(certificateId);
|
||||
function updateCertificateStatus(status) {
|
||||
root.certificateStatus = status;
|
||||
if (root.certificateStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS
|
||||
root.useGoldCert = true;
|
||||
root.certTitleTextColor = hifi.colors.darkGray;
|
||||
root.certTextColor = hifi.colors.white;
|
||||
root.infoTextColor = hifi.colors.blueAccent;
|
||||
titleBarText.text = "Certificate";
|
||||
popText.text = "PROOF OF PROVENANCE";
|
||||
showInMarketplaceButton.visible = true;
|
||||
root.certInfoReplaceMode = 5;
|
||||
// "Item Name" text will be set in "onCertificateInfoResult()"
|
||||
// "Edition" text will be set in "onCertificateInfoResult()"
|
||||
// "Owner" text will be set in "onCertificateInfoResult()"
|
||||
// "Purchase Date" text will be set in "onCertificateInfoResult()"
|
||||
// "Purchase Price" text will be set in "onCertificateInfoResult()"
|
||||
errorText.text = "";
|
||||
} else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT
|
||||
root.useGoldCert = false;
|
||||
root.certTitleTextColor = hifi.colors.redHighlight;
|
||||
root.certTextColor = hifi.colors.redHighlight;
|
||||
root.infoTextColor = hifi.colors.redHighlight;
|
||||
titleBarText.text = "Request Timed Out";
|
||||
popText.text = "";
|
||||
showInMarketplaceButton.visible = false;
|
||||
root.certInfoReplaceMode = 0;
|
||||
root.itemName = "";
|
||||
root.itemEdition = "";
|
||||
root.itemOwner = "";
|
||||
root.dateOfPurchase = "";
|
||||
root.itemCost = "";
|
||||
errorText.text = "Your request to inspect this item timed out. Please try again later.";
|
||||
} else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED
|
||||
root.useGoldCert = false;
|
||||
root.certTitleTextColor = hifi.colors.redHighlight;
|
||||
root.certTextColor = hifi.colors.redHighlight;
|
||||
root.infoTextColor = hifi.colors.redHighlight;
|
||||
titleBarText.text = "Certificate\nNo Longer Valid";
|
||||
popText.text = "";
|
||||
showInMarketplaceButton.visible = true;
|
||||
root.certInfoReplaceMode = 5;
|
||||
// "Item Name" text will be set in "onCertificateInfoResult()"
|
||||
// "Edition" text will be set in "onCertificateInfoResult()"
|
||||
// "Owner" text will be set in "onCertificateInfoResult()"
|
||||
// "Purchase Date" text will be set in "onCertificateInfoResult()"
|
||||
// "Purchase Price" text will be set in "onCertificateInfoResult()"
|
||||
errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item.";
|
||||
} else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED
|
||||
root.useGoldCert = false;
|
||||
root.certTitleTextColor = hifi.colors.redHighlight;
|
||||
root.certTextColor = hifi.colors.redHighlight;
|
||||
root.infoTextColor = hifi.colors.redHighlight;
|
||||
titleBarText.text = "Invalid Certificate";
|
||||
popText.text = "";
|
||||
showInMarketplaceButton.visible = true;
|
||||
root.certInfoReplaceMode = 4;
|
||||
// "Item Name" text will be set in "onCertificateInfoResult()"
|
||||
root.itemEdition = "Uncertified Copy"
|
||||
// "Owner" text will be set in "onCertificateInfoResult()"
|
||||
// "Purchase Date" text will be set in "onCertificateInfoResult()"
|
||||
// "Purchase Price" text will be set in "onCertificateInfoResult()"
|
||||
// "Error Text" text will be set in "onCertificateInfoResult()"
|
||||
} else {
|
||||
console.log("Unknown certificate status received from ledger signal!");
|
||||
}
|
||||
|
||||
root.certificateStatusPending = false;
|
||||
// We've gotten cert status - we are GO on getting the cert info
|
||||
Commerce.certificateInfo(root.certificateId);
|
||||
}
|
||||
|
||||
// This object is always used in a popup.
|
||||
|
@ -119,11 +192,38 @@ Rectangle {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: loadingOverlay;
|
||||
z: 998;
|
||||
|
||||
visible: root.certificateInfoPending || root.certificateStatusPending;
|
||||
anchors.fill: parent;
|
||||
color: Qt.rgba(0.0, 0.0, 0.0, 0.7);
|
||||
|
||||
// This object is always used in a popup or full-screen Wallet section.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
// able to click on a button/mouseArea underneath the popup/section.
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
source: "../common/images/loader.gif"
|
||||
width: 96;
|
||||
height: width;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: backgroundImage;
|
||||
anchors.fill: parent;
|
||||
source: "images/cert-bg.jpg";
|
||||
source: root.useGoldCert ? "images/cert-bg-gold-split.png" : "images/nocert-bg-split.png";
|
||||
}
|
||||
|
||||
// Title text
|
||||
|
@ -136,16 +236,17 @@ Rectangle {
|
|||
anchors.top: parent.top;
|
||||
anchors.topMargin: 40;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 45;
|
||||
anchors.leftMargin: 36;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 8;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
color: root.certTitleTextColor;
|
||||
wrapMode: Text.WordWrap;
|
||||
}
|
||||
// Title text
|
||||
RalewayRegular {
|
||||
id: popText;
|
||||
text: "Proof of Provenance";
|
||||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
|
@ -153,9 +254,39 @@ Rectangle {
|
|||
anchors.topMargin: 4;
|
||||
anchors.left: titleBarText.left;
|
||||
anchors.right: titleBarText.right;
|
||||
height: paintedHeight;
|
||||
height: text === "" ? 0 : paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
color: root.certTitleTextColor;
|
||||
}
|
||||
|
||||
// "Close" button
|
||||
HiFiGlyphs {
|
||||
z: 999;
|
||||
id: closeGlyphButton;
|
||||
text: hifi.glyphs.close;
|
||||
color: hifi.colors.white;
|
||||
size: 26;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 10;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 10;
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
onEntered: {
|
||||
parent.text = hifi.glyphs.closeInverted;
|
||||
}
|
||||
onExited: {
|
||||
parent.text = hifi.glyphs.close;
|
||||
}
|
||||
onClicked: {
|
||||
if (root.isLightbox) {
|
||||
root.visible = false;
|
||||
} else {
|
||||
sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -163,11 +294,13 @@ Rectangle {
|
|||
//
|
||||
Item {
|
||||
id: certificateContainer;
|
||||
anchors.top: popText.bottom;
|
||||
anchors.topMargin: 30;
|
||||
anchors.bottom: buttonsContainer.top;
|
||||
anchors.top: titleBarText.top;
|
||||
anchors.topMargin: 110;
|
||||
anchors.bottom: infoContainer.top;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: titleBarText.anchors.leftMargin;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 24;
|
||||
|
||||
RalewayRegular {
|
||||
id: itemNameHeader;
|
||||
|
@ -177,9 +310,7 @@ Rectangle {
|
|||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 45;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
|
@ -196,79 +327,30 @@ Rectangle {
|
|||
anchors.right: itemNameHeader.right;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
color: root.certTextColor;
|
||||
elide: Text.ElideRight;
|
||||
MouseArea {
|
||||
enabled: showInMarketplaceButton.visible;
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: enabled;
|
||||
onClicked: {
|
||||
sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl});
|
||||
}
|
||||
onEntered: itemName.color = hifi.colors.blueHighlight;
|
||||
onExited: itemName.color = hifi.colors.white;
|
||||
onExited: itemName.color = root.certTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: ownedByHeader;
|
||||
text: "OWNER";
|
||||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: itemName.bottom;
|
||||
anchors.topMargin: 28;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 45;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
}
|
||||
RalewayRegular {
|
||||
id: ownedBy;
|
||||
text: root.itemOwner;
|
||||
// Text size
|
||||
size: 22;
|
||||
// Anchors
|
||||
anchors.top: ownedByHeader.bottom;
|
||||
anchors.topMargin: 8;
|
||||
anchors.left: ownedByHeader.left;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
elide: Text.ElideRight;
|
||||
}
|
||||
AnonymousProRegular {
|
||||
id: isMyCertText;
|
||||
visible: root.isMyCert && !root.isCertificateInvalid;
|
||||
text: "(Private)";
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: ownedBy.top;
|
||||
anchors.topMargin: 4;
|
||||
anchors.bottom: ownedBy.bottom;
|
||||
anchors.left: ownedBy.right;
|
||||
anchors.leftMargin: 6;
|
||||
anchors.right: ownedByHeader.right;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
elide: Text.ElideRight;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: editionHeader;
|
||||
text: "EDITION";
|
||||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: ownedBy.bottom;
|
||||
anchors.top: itemName.bottom;
|
||||
anchors.topMargin: 28;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 45;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
|
@ -285,21 +367,117 @@ Rectangle {
|
|||
anchors.right: editionHeader.right;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
color: root.certTextColor;
|
||||
}
|
||||
|
||||
// "Show In Marketplace" button
|
||||
HifiControlsUit.Button {
|
||||
id: showInMarketplaceButton;
|
||||
enabled: root.marketplaceUrl;
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 48;
|
||||
anchors.right: parent.right;
|
||||
width: 200;
|
||||
height: 40;
|
||||
text: "View In Market"
|
||||
onClicked: {
|
||||
sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl});
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// "CERTIFICATE" END
|
||||
//
|
||||
|
||||
//
|
||||
// "INFO CONTAINER" START
|
||||
//
|
||||
Item {
|
||||
id: infoContainer;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: titleBarText.anchors.leftMargin;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 24;
|
||||
height: root.useGoldCert ? 220 : 372;
|
||||
|
||||
RalewayRegular {
|
||||
id: errorText;
|
||||
visible: !root.useGoldCert;
|
||||
// Text size
|
||||
size: 20;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 36;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: 116;
|
||||
// Style
|
||||
wrapMode: Text.WordWrap;
|
||||
color: hifi.colors.baseGray;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: ownedByHeader;
|
||||
text: "OWNER";
|
||||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: errorText.visible ? errorText.bottom : parent.top;
|
||||
anchors.topMargin: 28;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: ownedBy;
|
||||
text: root.itemOwner;
|
||||
// Text size
|
||||
size: 22;
|
||||
// Anchors
|
||||
anchors.top: ownedByHeader.bottom;
|
||||
anchors.topMargin: 8;
|
||||
anchors.left: ownedByHeader.left;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: root.infoTextColor;
|
||||
elide: Text.ElideRight;
|
||||
}
|
||||
AnonymousProRegular {
|
||||
id: isMyCertText;
|
||||
visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== "";
|
||||
text: "(Private)";
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: ownedBy.top;
|
||||
anchors.topMargin: 4;
|
||||
anchors.bottom: ownedBy.bottom;
|
||||
anchors.left: ownedBy.right;
|
||||
anchors.leftMargin: 6;
|
||||
anchors.right: ownedByHeader.right;
|
||||
// Style
|
||||
color: root.infoTextColor;
|
||||
elide: Text.ElideRight;
|
||||
verticalAlignment: Text.AlignVCenter;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: dateOfPurchaseHeader;
|
||||
text: "DATE OF PURCHASE";
|
||||
text: "PURCHASE DATE";
|
||||
// Text size
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: edition.bottom;
|
||||
anchors.top: ownedBy.bottom;
|
||||
anchors.topMargin: 28;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 45;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
anchors.right: parent.horizontalCenter;
|
||||
anchors.rightMargin: 8;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
|
@ -316,73 +494,58 @@ Rectangle {
|
|||
anchors.right: dateOfPurchaseHeader.right;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.white;
|
||||
color: root.infoTextColor;
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: errorText;
|
||||
id: priceHeader;
|
||||
text: "PURCHASE PRICE";
|
||||
// Text size
|
||||
size: 20;
|
||||
size: 16;
|
||||
// Anchors
|
||||
anchors.top: dateOfPurchase.bottom;
|
||||
anchors.topMargin: 36;
|
||||
anchors.left: dateOfPurchase.left;
|
||||
anchors.right: dateOfPurchase.right;
|
||||
anchors.bottom: parent.bottom;
|
||||
// Style
|
||||
wrapMode: Text.WordWrap;
|
||||
color: hifi.colors.redHighlight;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
}
|
||||
}
|
||||
//
|
||||
// "CERTIFICATE" END
|
||||
//
|
||||
|
||||
Item {
|
||||
id: buttonsContainer;
|
||||
anchors.bottom: parent.bottom;
|
||||
anchors.bottomMargin: 30;
|
||||
anchors.left: parent.left;
|
||||
anchors.right: parent.right;
|
||||
height: 50;
|
||||
|
||||
// "Cancel" button
|
||||
HifiControlsUit.Button {
|
||||
color: hifi.buttons.noneBorderlessWhite;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: parent.top;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 30;
|
||||
width: parent.width/2 - 50;
|
||||
height: 50;
|
||||
text: "close";
|
||||
onClicked: {
|
||||
if (root.isLightbox) {
|
||||
root.visible = false;
|
||||
} else {
|
||||
sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "Show In Marketplace" button
|
||||
HifiControlsUit.Button {
|
||||
id: showInMarketplaceButton;
|
||||
enabled: root.marketplaceUrl;
|
||||
color: hifi.buttons.blue;
|
||||
colorScheme: hifi.colorSchemes.light;
|
||||
anchors.top: parent.top;
|
||||
anchors.top: ownedBy.bottom;
|
||||
anchors.topMargin: 28;
|
||||
anchors.left: parent.horizontalCenter;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 30;
|
||||
width: parent.width/2 - 50;
|
||||
height: 50;
|
||||
text: "View In Market"
|
||||
onClicked: {
|
||||
sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl});
|
||||
}
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: hifi.colors.darkGray;
|
||||
}
|
||||
HiFiGlyphs {
|
||||
id: hfcGlyph;
|
||||
visible: priceText.text !== "Undisclosed" && priceText.text !== "";
|
||||
text: hifi.glyphs.hfc;
|
||||
// Size
|
||||
size: 24;
|
||||
// Anchors
|
||||
anchors.top: priceHeader.bottom;
|
||||
anchors.topMargin: 8;
|
||||
anchors.left: priceHeader.left;
|
||||
width: visible ? paintedWidth + 6 : 0;
|
||||
height: 40;
|
||||
// Style
|
||||
color: root.infoTextColor;
|
||||
verticalAlignment: Text.AlignTop;
|
||||
horizontalAlignment: Text.AlignLeft;
|
||||
}
|
||||
AnonymousProRegular {
|
||||
id: priceText;
|
||||
text: root.itemCost;
|
||||
// Text size
|
||||
size: 18;
|
||||
// Anchors
|
||||
anchors.top: priceHeader.bottom;
|
||||
anchors.topMargin: 8;
|
||||
anchors.left: hfcGlyph.right;
|
||||
anchors.right: priceHeader.right;
|
||||
height: paintedHeight;
|
||||
// Style
|
||||
color: root.infoTextColor;
|
||||
}
|
||||
}
|
||||
//
|
||||
// "INFO CONTAINER" END
|
||||
//
|
||||
|
||||
//
|
||||
// FUNCTION DEFINITIONS START
|
||||
|
@ -403,19 +566,17 @@ Rectangle {
|
|||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case 'inspectionCertificate_setCertificateId':
|
||||
resetCert(false);
|
||||
root.certificateId = message.certificateId;
|
||||
if (message.entityId === "") {
|
||||
updateCertificateStatus(1); // CERTIFICATE_STATUS_VERIFICATION_SUCCESS
|
||||
} else {
|
||||
root.entityId = message.entityId;
|
||||
sendToScript({method: 'inspectionCertificate_requestOwnershipVerification', entity: root.entityId});
|
||||
}
|
||||
break;
|
||||
case 'inspectionCertificate_resetCert':
|
||||
titleBarText.text = "Certificate";
|
||||
popText.text = "PROOF OF PURCHASE";
|
||||
root.certificateId = "";
|
||||
root.itemName = "--";
|
||||
root.itemOwner = "--";
|
||||
root.itemEdition = "--";
|
||||
root.dateOfPurchase = "--";
|
||||
root.marketplaceUrl = "";
|
||||
root.isMyCert = false;
|
||||
errorText.text = "";
|
||||
resetCert(true);
|
||||
break;
|
||||
default:
|
||||
console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
|
||||
|
@ -423,7 +584,34 @@ Rectangle {
|
|||
}
|
||||
signal sendToScript(var message);
|
||||
|
||||
function resetCert(alsoResetCertID) {
|
||||
if (alsoResetCertID) {
|
||||
root.entityId = "";
|
||||
root.certificateId = "";
|
||||
}
|
||||
root.certInfoReplaceMode = 5;
|
||||
root.certificateInfoPending = true;
|
||||
root.certificateStatusPending = true;
|
||||
root.useGoldCert = true;
|
||||
root.certTitleTextColor = hifi.colors.darkGray;
|
||||
root.certTextColor = hifi.colors.white;
|
||||
root.infoTextColor = hifi.colors.blueAccent;
|
||||
titleBarText.text = "Certificate";
|
||||
popText.text = "";
|
||||
root.itemName = "--";
|
||||
root.itemOwner = "--";
|
||||
root.itemEdition = "--";
|
||||
root.dateOfPurchase = "--";
|
||||
root.marketplaceUrl = "";
|
||||
root.itemCost = "--";
|
||||
root.isMyCert = false;
|
||||
errorText.text = "";
|
||||
}
|
||||
|
||||
function getFormattedDate(timestamp) {
|
||||
if (timestamp === "--") {
|
||||
return "--";
|
||||
}
|
||||
function addLeadingZero(n) {
|
||||
return n < 10 ? '0' + n : '' + n;
|
||||
}
|
||||
|
@ -448,7 +636,7 @@ Rectangle {
|
|||
|
||||
var min = addLeadingZero(a.getMinutes());
|
||||
var sec = addLeadingZero(a.getSeconds());
|
||||
return year + '-' + month + '-' + day + '<br>' + drawnHour + ':' + min + amOrPm;
|
||||
return year + '-' + month + '-' + day + ' ' + drawnHour + ':' + min + amOrPm;
|
||||
}
|
||||
//
|
||||
// FUNCTION DEFINITIONS END
|
||||
|
|
After Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 17 KiB |
|
@ -316,6 +316,7 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
RalewayBold {
|
||||
|
|
|
@ -317,6 +317,7 @@ Rectangle {
|
|||
|
||||
HifiControlsUit.TextField {
|
||||
id: filterBar;
|
||||
property string previousText: "";
|
||||
colorScheme: hifi.colorSchemes.faintGray;
|
||||
hasClearButton: true;
|
||||
hasRoundedBorder: true;
|
||||
|
@ -329,6 +330,8 @@ Rectangle {
|
|||
|
||||
onTextChanged: {
|
||||
buildFilteredPurchasesModel();
|
||||
purchasesContentsList.positionViewAtIndex(0, ListView.Beginning)
|
||||
filterBar.previousText = filterBar.text;
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
|
@ -647,7 +650,8 @@ Rectangle {
|
|||
|
||||
function sortByDate() {
|
||||
filteredPurchasesModel.sortColumnName = "purchase_date";
|
||||
filteredPurchasesModel.isSortingDescending = false;
|
||||
filteredPurchasesModel.isSortingDescending = true;
|
||||
filteredPurchasesModel.valuesAreNumerical = true;
|
||||
filteredPurchasesModel.quickSort();
|
||||
}
|
||||
|
||||
|
@ -677,7 +681,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== "") {
|
||||
if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) {
|
||||
filteredPurchasesModel.clear();
|
||||
for (var i = 0; i < tempPurchasesModel.count; i++) {
|
||||
filteredPurchasesModel.append(tempPurchasesModel.get(i));
|
||||
|
|
|
@ -66,6 +66,7 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
// This will cause a bug -- if you bring up passphrase selection in HUD mode while
|
||||
|
|
|
@ -34,6 +34,7 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
|
|
@ -306,6 +306,7 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
RalewayBold {
|
||||
|
|
|
@ -394,6 +394,7 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Image {
|
||||
|
@ -440,7 +441,7 @@ Item {
|
|||
}
|
||||
Item {
|
||||
id: choosePassphraseContainer;
|
||||
visible: root.activeView === "step_3";
|
||||
visible: root.hasShownSecurityImageTip && root.activeView === "step_3";
|
||||
// Anchors
|
||||
anchors.top: titleBarContainer.bottom;
|
||||
anchors.topMargin: 30;
|
||||
|
@ -450,7 +451,10 @@ Item {
|
|||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
sendSignalToWallet({method: 'disableHmdPreview'});
|
||||
Commerce.getWalletAuthenticatedStatus();
|
||||
} else {
|
||||
sendSignalToWallet({method: 'maybeEnableHmdPreview'});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ Item {
|
|||
width: parent.width;
|
||||
height: (root.shouldShowTopAndBottomOfWallet || root.shouldShowTopOfWallet) ? parent.height : parent.height - root.parentAppTitleBarHeight - root.parentAppNavBarHeight;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
@ -57,6 +58,9 @@ Item {
|
|||
|
||||
if (result.status === 'success') {
|
||||
root.nextActiveView = 'paymentSuccess';
|
||||
if (sendPubliclyCheckbox.checked && sendMoneyStep.referrer === "nearby") {
|
||||
sendSignalToWallet({method: 'sendMoney_sendPublicly', recipient: sendMoneyStep.selectedRecipientNodeID, amount: parseInt(amountTextField.text)});
|
||||
}
|
||||
} else {
|
||||
root.nextActiveView = 'paymentFailure';
|
||||
}
|
||||
|
@ -103,6 +107,12 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
HifiCommerceCommon.CommerceLightbox {
|
||||
id: lightboxPopup;
|
||||
visible: false;
|
||||
anchors.fill: parent;
|
||||
}
|
||||
|
||||
// Send Money Home BEGIN
|
||||
Item {
|
||||
id: sendMoneyHome;
|
||||
|
@ -326,6 +336,7 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
ListModel {
|
||||
|
@ -477,6 +488,7 @@ Item {
|
|||
enabled: connectionsList.currentIndex !== index;
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
onClicked: {
|
||||
connectionsList.currentIndex = index;
|
||||
}
|
||||
|
@ -505,6 +517,7 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -917,7 +930,7 @@ Item {
|
|||
id: optionalMessage;
|
||||
property int maximumLength: 72;
|
||||
property string previousText: text;
|
||||
placeholderText: "<i>Optional Message (" + maximumLength + " character limit)</i>";
|
||||
placeholderText: "<i>Optional Public Message (" + maximumLength + " character limit)</i>";
|
||||
font.family: firaSansSemiBold.name;
|
||||
font.pixelSize: 20;
|
||||
// Anchors
|
||||
|
@ -967,16 +980,54 @@ Item {
|
|||
|
||||
HifiControlsUit.CheckBox {
|
||||
id: sendPubliclyCheckbox;
|
||||
visible: false; // FIXME ONCE PARTICLE EFFECTS ARE IN
|
||||
text: "Send Publicly"
|
||||
visible: sendMoneyStep.referrer === "nearby";
|
||||
checked: Settings.getValue("sendMoneyNearbyPublicly", true);
|
||||
text: "Show Effect"
|
||||
// Anchors
|
||||
anchors.top: messageContainer.bottom;
|
||||
anchors.topMargin: 16;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 20;
|
||||
anchors.right: parent.right;
|
||||
anchors.rightMargin: 16;
|
||||
boxSize: 24;
|
||||
width: 110;
|
||||
boxSize: 28;
|
||||
onCheckedChanged: {
|
||||
Settings.setValue("sendMoneyNearbyPublicly", checked);
|
||||
}
|
||||
}
|
||||
RalewaySemiBold {
|
||||
id: sendPubliclyCheckboxHelp;
|
||||
visible: sendPubliclyCheckbox.visible;
|
||||
text: "[?]";
|
||||
// Anchors
|
||||
anchors.left: sendPubliclyCheckbox.right;
|
||||
anchors.leftMargin: 8;
|
||||
anchors.verticalCenter: sendPubliclyCheckbox.verticalCenter;
|
||||
height: 30;
|
||||
width: paintedWidth;
|
||||
// Text size
|
||||
size: 18;
|
||||
// Style
|
||||
color: hifi.colors.blueAccent;
|
||||
MouseArea {
|
||||
enabled: sendPubliclyCheckboxHelp.visible;
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true;
|
||||
onEntered: {
|
||||
parent.color = hifi.colors.blueHighlight;
|
||||
}
|
||||
onExited: {
|
||||
parent.color = hifi.colors.blueAccent;
|
||||
}
|
||||
onClicked: {
|
||||
lightboxPopup.titleText = "Send Money Effect";
|
||||
lightboxPopup.bodyImageSource = "../wallet/sendMoney/images/send-money-effect-sm.jpg"; // Path relative to CommerceLightbox.qml
|
||||
lightboxPopup.bodyText = "Enabling this option will create a particle effect between you and " +
|
||||
"your recipient that is visible to everyone nearby.";
|
||||
lightboxPopup.button1text = "CLOSE";
|
||||
lightboxPopup.button1method = "root.visible = false;"
|
||||
lightboxPopup.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -1059,11 +1110,12 @@ Item {
|
|||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: sendingMoneyImage;
|
||||
source: "./images/loader.gif"
|
||||
source: "../../common/images/loader.gif"
|
||||
width: 96;
|
||||
height: width;
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
|
@ -1533,6 +1585,8 @@ Item {
|
|||
sendMoneyStep.selectedRecipientProfilePic = "";
|
||||
amountTextField.text = "";
|
||||
optionalMessage.text = "";
|
||||
sendPubliclyCheckbox.checked = Settings.getValue("sendMoneyNearbyPublicly", true);
|
||||
sendMoneyStep.referrer = "";
|
||||
}
|
||||
|
||||
//
|
||||
|
|
After Width: | Height: | Size: 22 KiB |
|
@ -12,7 +12,7 @@ StackView {
|
|||
HifiConstants { id: hifi }
|
||||
|
||||
function pushSource(path) {
|
||||
editRoot.push(Qt.resolvedUrl(path));
|
||||
editRoot.push(Qt.resolvedUrl("../../" + path));
|
||||
editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ StackView {
|
|||
console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds);
|
||||
|
||||
feeds.forEach(function(feed) {
|
||||
feed.protocol = encodeURIComponent(message.protocolSignature);
|
||||
Qt.callLater(feed.fillDestinations);
|
||||
});
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ Item {
|
|||
|
||||
GridView {
|
||||
id: gridView
|
||||
|
||||
flickableDirection: Flickable.AutoFlickIfNeeded
|
||||
keyNavigationEnabled: false
|
||||
highlightFollowsCurrentItem: false
|
||||
|
||||
|
@ -144,23 +144,20 @@ Item {
|
|||
bottomMargin: 0
|
||||
}
|
||||
|
||||
function setButtonState(buttonIndex, buttonstate) {
|
||||
if (buttonIndex < 0 || gridView.contentItem === undefined
|
||||
|| gridView.contentItem.children.length - 1 < buttonIndex) {
|
||||
return;
|
||||
}
|
||||
var itemat = gridView.contentItem.children[buttonIndex].children[0];
|
||||
if (itemat.isActive) {
|
||||
itemat.state = "active state";
|
||||
} else {
|
||||
itemat.state = buttonstate;
|
||||
}
|
||||
onCurrentIndexChanged: {
|
||||
previousGridIndex = currentIndex
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
setButtonState(previousGridIndex, "base state");
|
||||
setButtonState(currentIndex, "hover state");
|
||||
previousGridIndex = currentIndex
|
||||
onMovementStarted: {
|
||||
if (currentIndex < 0 || gridView.currentItem === undefined || gridView.contentItem.children.length - 1 < currentIndex) {
|
||||
return;
|
||||
}
|
||||
var button = gridView.contentItem.children[currentIndex].children[0];
|
||||
if (button.isActive) {
|
||||
button.state = "active state";
|
||||
} else {
|
||||
button.state = "base state";
|
||||
}
|
||||
}
|
||||
|
||||
cellWidth: width/3
|
||||
|
|
|
@ -219,6 +219,7 @@ Item {
|
|||
readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ]
|
||||
readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
|
||||
readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
|
||||
readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
|
||||
readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight]
|
||||
readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow]
|
||||
readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow]
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
#include <Midi.h>
|
||||
#include <AudioInjectorManager.h>
|
||||
#include <AvatarBookmarks.h>
|
||||
#include <AvatarEntitiesBookmarks.h>
|
||||
#include <CursorManager.h>
|
||||
#include <VirtualPadManager.h>
|
||||
#include <DebugDraw.h>
|
||||
|
@ -150,6 +151,7 @@
|
|||
#include "avatar/AvatarManager.h"
|
||||
#include "avatar/MyHead.h"
|
||||
#include "CrashHandler.h"
|
||||
#include "Crashpad.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "DiscoverabilityManager.h"
|
||||
#include "GLCanvas.h"
|
||||
|
@ -318,7 +320,7 @@ static QTimer pingTimer;
|
|||
static bool DISABLE_WATCHDOG = true;
|
||||
#else
|
||||
static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" };
|
||||
static bool DISABLE_WATCHDOG = QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG);
|
||||
static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG);
|
||||
#endif
|
||||
|
||||
#if defined(USE_GLES)
|
||||
|
@ -397,6 +399,7 @@ public:
|
|||
setObjectName("Deadlock Watchdog");
|
||||
// Give the heartbeat an initial value
|
||||
_heartbeat = usecTimestampNow();
|
||||
_paused = false;
|
||||
connect(qApp, &QCoreApplication::aboutToQuit, [this] {
|
||||
_quit = true;
|
||||
});
|
||||
|
@ -414,11 +417,26 @@ public:
|
|||
*crashTrigger = 0xDEAD10CC;
|
||||
}
|
||||
|
||||
static void withPause(const std::function<void()>& lambda) {
|
||||
pause();
|
||||
lambda();
|
||||
resume();
|
||||
}
|
||||
static void pause() {
|
||||
_paused = true;
|
||||
}
|
||||
|
||||
static void resume() {
|
||||
// Update the heartbeat BEFORE resuming the checks
|
||||
updateHeartbeat();
|
||||
_paused = false;
|
||||
}
|
||||
|
||||
void run() override {
|
||||
while (!_quit) {
|
||||
QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS);
|
||||
// Don't do heartbeat detection under nsight
|
||||
if (nsightActive()) {
|
||||
if (_paused) {
|
||||
continue;
|
||||
}
|
||||
uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us
|
||||
|
@ -474,6 +492,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
static std::atomic<bool> _paused;
|
||||
static std::atomic<uint64_t> _heartbeat;
|
||||
static std::atomic<uint64_t> _maxElapsed;
|
||||
static std::atomic<int> _maxElapsedAverage;
|
||||
|
@ -482,6 +501,7 @@ public:
|
|||
bool _quit { false };
|
||||
};
|
||||
|
||||
std::atomic<bool> DeadlockWatchdogThread::_paused;
|
||||
std::atomic<uint64_t> DeadlockWatchdogThread::_heartbeat;
|
||||
std::atomic<uint64_t> DeadlockWatchdogThread::_maxElapsed;
|
||||
std::atomic<int> DeadlockWatchdogThread::_maxElapsedAverage;
|
||||
|
@ -657,8 +677,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
}
|
||||
#endif
|
||||
|
||||
Setting::init();
|
||||
|
||||
// Tell the plugin manager about our statically linked plugins
|
||||
auto pluginManager = PluginManager::getInstance();
|
||||
pluginManager->setInputPluginProvider([] { return getInputPlugins(); });
|
||||
|
@ -766,6 +784,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<GooglePolyScriptingInterface>();
|
||||
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
|
||||
DependencyManager::set<AvatarBookmarks>();
|
||||
DependencyManager::set<AvatarEntitiesBookmarks>();
|
||||
DependencyManager::set<LocationBookmarks>();
|
||||
DependencyManager::set<Snapshot>();
|
||||
DependencyManager::set<CloseEventSender>();
|
||||
|
@ -910,6 +929,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
_logger->setSessionID(accountManager->getSessionID());
|
||||
|
||||
setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString());
|
||||
|
||||
if (steamClient) {
|
||||
qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID();
|
||||
}
|
||||
|
@ -1034,7 +1055,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
|
||||
|
||||
// you might think we could just do this in NodeList but we only want this connection for Interface
|
||||
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
|
||||
connect(&nodeList->getDomainHandler(), SIGNAL(limitOfSilentDomainCheckInsReached()),
|
||||
nodeList.data(), SLOT(reset()));
|
||||
|
||||
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
||||
connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
|
||||
|
@ -1912,7 +1934,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
entityResult.distance = pickResult->distance;
|
||||
entityResult.surfaceNormal = pickResult->surfaceNormal;
|
||||
entityResult.entityID = pickResult->objectID;
|
||||
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
|
||||
entityResult.extraInfo = pickResult->extraInfo;
|
||||
}
|
||||
}
|
||||
return entityResult;
|
||||
|
@ -2273,19 +2295,22 @@ void Application::initializeGL() {
|
|||
initDisplay();
|
||||
qCDebug(interfaceapp, "Initialized Display.");
|
||||
|
||||
// Set up the render engine
|
||||
render::CullFunctor cullFunctor = LODManager::shouldRender;
|
||||
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
|
||||
_renderEngine->addJob<UpdateSceneTask>("UpdateScene");
|
||||
// FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread.
|
||||
DeadlockWatchdogThread::withPause([&] {
|
||||
// Set up the render engine
|
||||
render::CullFunctor cullFunctor = LODManager::shouldRender;
|
||||
static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD";
|
||||
_renderEngine->addJob<UpdateSceneTask>("UpdateScene");
|
||||
#ifndef Q_OS_ANDROID
|
||||
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED);
|
||||
_renderEngine->addJob<SecondaryCameraRenderTask>("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED);
|
||||
#endif
|
||||
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, !DISABLE_DEFERRED);
|
||||
_renderEngine->load();
|
||||
_renderEngine->registerScene(_main3DScene);
|
||||
_renderEngine->addJob<RenderViewTask>("RenderMainView", cullFunctor, !DISABLE_DEFERRED, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0);
|
||||
_renderEngine->load();
|
||||
_renderEngine->registerScene(_main3DScene);
|
||||
|
||||
// Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.
|
||||
DependencyManager::get<GeometryCache>()->initializeShapePipelines();
|
||||
// Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.
|
||||
DependencyManager::get<GeometryCache>()->initializeShapePipelines();
|
||||
});
|
||||
|
||||
_offscreenContext = new OffscreenGLCanvas();
|
||||
_offscreenContext->setObjectName("MainThreadContext");
|
||||
|
@ -2384,7 +2409,9 @@ void Application::initializeUi() {
|
|||
tabletScriptingInterface->getTablet(SYSTEM_TABLET);
|
||||
}
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
DeadlockWatchdogThread::pause();
|
||||
offscreenUi->create();
|
||||
DeadlockWatchdogThread::resume();
|
||||
|
||||
auto surfaceContext = offscreenUi->getSurfaceContext();
|
||||
|
||||
|
@ -2449,6 +2476,7 @@ void Application::initializeUi() {
|
|||
surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
surfaceContext->setContextProperty("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
// Caches
|
||||
|
@ -5876,6 +5904,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
|
||||
scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get<AudioScope>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get<AvatarBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("AvatarEntitiesBookmarks", DependencyManager::get<AvatarEntitiesBookmarks>().data());
|
||||
scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get<LocationBookmarks>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("RayPick", DependencyManager::get<RayPickScriptingInterface>().data());
|
||||
|
@ -6293,7 +6322,7 @@ void Application::showAssetServerWidget(QString filePath) {
|
|||
if (!hmd->getShouldShowTablet() && !isHMDMode()) {
|
||||
DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
|
||||
} else {
|
||||
static const QUrl url("../dialogs/TabletAssetServer.qml");
|
||||
static const QUrl url("hifi/dialogs/TabletAssetServer.qml");
|
||||
tablet->pushOntoStack(url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ void Application::paintGL() {
|
|||
// If a display plugin loses it's underlying support, it
|
||||
// needs to be able to signal us to not use it
|
||||
if (!displayPlugin->beginFrameRender(_renderFrameCount)) {
|
||||
updateDisplayMode();
|
||||
QMetaObject::invokeMethod(this, "updateDisplayMode");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ public:
|
|||
render::ItemID WorldBoxRenderData::_item{ render::Item::INVALID_ITEM_ID };
|
||||
|
||||
namespace render {
|
||||
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); }
|
||||
template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1); }
|
||||
template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); }
|
||||
template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) {
|
||||
|
|
168
interface/src/AvatarEntitiesBookmarks.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QAction>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardPaths>
|
||||
#include <QQmlContext>
|
||||
#include <QList>
|
||||
|
||||
#include <Application.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <EntityItemProperties.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <EntityItemID.h>
|
||||
#include <EntityTree.h>
|
||||
#include <PhysicalEntitySimulation.h>
|
||||
#include <EntityEditPacketSender.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
void addAvatarEntities(const QVariantList& avatarEntities) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||
EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
|
||||
if (!entityTree) {
|
||||
return;
|
||||
}
|
||||
EntitySimulationPointer entitySimulation = entityTree->getSimulation();
|
||||
PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation);
|
||||
EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender();
|
||||
QScriptEngine scriptEngine;
|
||||
for (int index = 0; index < avatarEntities.count(); index++) {
|
||||
const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap();
|
||||
QVariant variantProperties = avatarEntityProperties["properties"];
|
||||
QVariantMap asMap = variantProperties.toMap();
|
||||
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
|
||||
EntityItemProperties entityProperties;
|
||||
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
|
||||
|
||||
entityProperties.setParentID(myNodeID);
|
||||
entityProperties.setClientOnly(true);
|
||||
entityProperties.setOwningAvatarID(myNodeID);
|
||||
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
|
||||
entityProperties.markAllChanged();
|
||||
|
||||
EntityItemID id = EntityItemID(QUuid::createUuid());
|
||||
bool success = true;
|
||||
entityTree->withWriteLock([&] {
|
||||
EntityItemPointer entity = entityTree->addEntity(id, entityProperties);
|
||||
if (entity) {
|
||||
if (entityProperties.queryAACubeRelatedPropertyChanged()) {
|
||||
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
|
||||
bool success;
|
||||
AACube queryAACube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
entityProperties.setQueryAACube(queryAACube);
|
||||
}
|
||||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
// since we're creating this object we will immediately volunteer to own its simulation
|
||||
entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY);
|
||||
entityProperties.setLastEdited(entity->getLastEdited());
|
||||
} else {
|
||||
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
}
|
||||
});
|
||||
|
||||
if (success) {
|
||||
entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() {
|
||||
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME;
|
||||
Bookmarks::readFromFile();
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
|
||||
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities);
|
||||
QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection);
|
||||
_bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks);
|
||||
_deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarEntitiesBookmark);
|
||||
QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection);
|
||||
|
||||
for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) {
|
||||
addBookmarkToMenu(menubar, it.key(), it.value());
|
||||
}
|
||||
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() {
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QMap<QString, QVariant> bookmark = action->data().toMap();
|
||||
|
||||
if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) {
|
||||
myAvatar->removeAvatarEntities();
|
||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||
myAvatar->useFullAvatarURL(avatarUrl);
|
||||
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||
addAvatarEntities(avatarEntities);
|
||||
const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||
myAvatar->setAvatarScale(avatarScale);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarEntitiesBookmark";
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmark() {
|
||||
ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar Entities", "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;
|
||||
}
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString();
|
||||
const QVariant& avatarScale = myAvatar->getAvatarScale();
|
||||
|
||||
QVariantMap *bookmark = new QVariantMap;
|
||||
bookmark->insert(ENTRY_VERSION, AVATAR_ENTITIES_BOOKMARK_VERSION);
|
||||
bookmark->insert(ENTRY_AVATAR_URL, avatarUrl);
|
||||
bookmark->insert(ENTRY_AVATAR_SCALE, avatarScale);
|
||||
bookmark->insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant());
|
||||
|
||||
Bookmarks::addBookmarkToFile(bookmarkName, *bookmark);
|
||||
});
|
||||
}
|
||||
|
||||
void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) {
|
||||
QAction* changeAction = _bookmarksMenu->newAction();
|
||||
changeAction->setData(bookmark);
|
||||
connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookmarkedAvatarEntities()));
|
||||
if (!_isMenuSorted) {
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
} else {
|
||||
// TODO: this is aggressive but other alternatives have proved less fruitful so far.
|
||||
menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole);
|
||||
Bookmarks::sortActions(menubar, _bookmarksMenu);
|
||||
}
|
||||
}
|
45
interface/src/AvatarEntitiesBookmarks.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// AvatarEntitiesBookmarks.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Dante Ruiz on 15/01/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AvatarEntitiesBookmarks_h
|
||||
#define hifi_AvatarEntitiesBookmarks_h
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include "Bookmarks.h"
|
||||
|
||||
class AvatarEntitiesBookmarks: public Bookmarks, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
AvatarEntitiesBookmarks();
|
||||
void setupMenus(Menu* menubar, MenuWrapper* menu) override;
|
||||
|
||||
public slots:
|
||||
void addBookmark();
|
||||
|
||||
protected:
|
||||
void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override;
|
||||
|
||||
private:
|
||||
const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json";
|
||||
const QString ENTRY_AVATAR_URL = "AvatarUrl";
|
||||
const QString ENTRY_AVATAR_SCALE = "AvatarScale";
|
||||
const QString ENTRY_AVATAR_ENTITIES = "AvatarEntities";
|
||||
const QString ENTRY_VERSION = "version";
|
||||
|
||||
const int AVATAR_ENTITIES_BOOKMARK_VERSION = 1;
|
||||
|
||||
private slots:
|
||||
void applyBookmarkedAvatarEntities();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,166 +0,0 @@
|
|||
//
|
||||
// CrashReporter.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 11 April 2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "Application.h"
|
||||
#include "CrashReporter.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <new.h>
|
||||
#include <Windows.h>
|
||||
#include <DbgHelp.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
#pragma comment(lib, "Dbghelp.lib")
|
||||
|
||||
// SetUnhandledExceptionFilter can be overridden by the CRT at the point that an error occurs. More information
|
||||
// can be found here: http://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li
|
||||
// A fairly common approach is to patch the SetUnhandledExceptionFilter so that it cannot be overridden and so
|
||||
// that the applicaiton can handle it itself.
|
||||
// The CAPIHook class referenced in the above article is not openly available, but a similar implementation
|
||||
// can be found here: http://blog.kalmbach-software.de/2008/04/02/unhandled-exceptions-in-vc8-and-above-for-x86-and-x64/
|
||||
// The below has been adapted to work with different library and functions.
|
||||
BOOL redirectLibraryFunctionToFunction(char* library, char* function, void* fn)
|
||||
{
|
||||
HMODULE lib = LoadLibrary(library);
|
||||
if (lib == NULL) return FALSE;
|
||||
void *pOrgEntry = GetProcAddress(lib, function);
|
||||
if (pOrgEntry == NULL) return FALSE;
|
||||
|
||||
DWORD dwOldProtect = 0;
|
||||
SIZE_T jmpSize = 5;
|
||||
#ifdef _M_X64
|
||||
jmpSize = 13;
|
||||
#endif
|
||||
BOOL bProt = VirtualProtect(pOrgEntry, jmpSize,
|
||||
PAGE_EXECUTE_READWRITE, &dwOldProtect);
|
||||
BYTE newJump[20];
|
||||
void *pNewFunc = fn;
|
||||
#ifdef _M_IX86
|
||||
DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
|
||||
dwOrgEntryAddr += jmpSize; // add 5 for 5 op-codes for jmp rel32
|
||||
DWORD dwNewEntryAddr = (DWORD)pNewFunc;
|
||||
DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
|
||||
// JMP rel32: Jump near, relative, displacement relative to next instruction.
|
||||
newJump[0] = 0xE9; // JMP rel32
|
||||
memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
|
||||
#elif _M_X64
|
||||
// We must use R10 or R11, because these are "scratch" registers
|
||||
// which need not to be preserved accross function calls
|
||||
// For more info see: Register Usage for x64 64-Bit
|
||||
// http://msdn.microsoft.com/en-us/library/ms794547.aspx
|
||||
// Thanks to Matthew Smith!!!
|
||||
newJump[0] = 0x49; // MOV R11, ...
|
||||
newJump[1] = 0xBB; // ...
|
||||
memcpy(&newJump[2], &pNewFunc, sizeof(pNewFunc));
|
||||
//pCur += sizeof (ULONG_PTR);
|
||||
newJump[10] = 0x41; // JMP R11, ...
|
||||
newJump[11] = 0xFF; // ...
|
||||
newJump[12] = 0xE3; // ...
|
||||
#endif
|
||||
SIZE_T bytesWritten;
|
||||
BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
|
||||
pOrgEntry, newJump, jmpSize, &bytesWritten);
|
||||
|
||||
if (bProt != FALSE)
|
||||
{
|
||||
DWORD dwBuf;
|
||||
VirtualProtect(pOrgEntry, jmpSize, dwOldProtect, &dwBuf);
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
|
||||
void printStackTrace(ULONG framesToSkip = 1) {
|
||||
HANDLE process = GetCurrentProcess();
|
||||
SymInitialize(process, NULL, TRUE);
|
||||
void* stack[100];
|
||||
uint16_t frames = CaptureStackBackTrace(framesToSkip, 100, stack, NULL);
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
|
||||
symbol->MaxNameLen = 255;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
|
||||
for (uint16_t i = 0; i < frames; ++i) {
|
||||
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
|
||||
qWarning() << QString("%1: %2 - 0x%0X").arg(QString::number(frames - i - 1), QString(symbol->Name), QString::number(symbol->Address, 16));
|
||||
}
|
||||
|
||||
free(symbol);
|
||||
|
||||
// Try to force the log to sync to the filesystem
|
||||
auto app = qApp;
|
||||
if (app && app->getLogger()) {
|
||||
app->getLogger()->sync();
|
||||
}
|
||||
}
|
||||
|
||||
void handleSignal(int signal) {
|
||||
// Throw so BugSplat can handle
|
||||
throw(signal);
|
||||
}
|
||||
|
||||
void __cdecl handlePureVirtualCall() {
|
||||
qWarning() << "Pure virtual function call detected";
|
||||
printStackTrace(2);
|
||||
// Throw so BugSplat can handle
|
||||
throw("ERROR: Pure virtual call");
|
||||
}
|
||||
|
||||
void handleInvalidParameter(const wchar_t * expression, const wchar_t * function, const wchar_t * file,
|
||||
unsigned int line, uintptr_t pReserved ) {
|
||||
// Throw so BugSplat can handle
|
||||
throw("ERROR: Invalid parameter");
|
||||
}
|
||||
|
||||
int handleNewError(size_t size) {
|
||||
// Throw so BugSplat can handle
|
||||
throw("ERROR: Errors calling new");
|
||||
}
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI noop_SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_purecall_handler __cdecl noop_set_purecall_handler(_purecall_handler pNew) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
|
||||
static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;
|
||||
|
||||
CrashReporter::CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version)
|
||||
: mpSender(qPrintable(bugSplatDatabase), qPrintable(bugSplatApplicationName), qPrintable(version), nullptr, BUG_SPLAT_FLAGS)
|
||||
{
|
||||
signal(SIGSEGV, handleSignal);
|
||||
signal(SIGABRT, handleSignal);
|
||||
_set_purecall_handler(handlePureVirtualCall);
|
||||
_set_invalid_parameter_handler(handleInvalidParameter);
|
||||
_set_new_mode(1);
|
||||
_set_new_handler(handleNewError);
|
||||
|
||||
// Disable WER popup
|
||||
//SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
|
||||
//_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||
|
||||
// QtWebEngineCore internally sets its own purecall handler, overriding our own error handling. This disables that.
|
||||
if (!redirectLibraryFunctionToFunction("msvcr120.dll", "_set_purecall_handler", &noop_set_purecall_handler)) {
|
||||
qWarning() << "Failed to patch _set_purecall_handler";
|
||||
}
|
||||
// Patch SetUnhandledExceptionFilter to keep the CRT from overriding our own error handling.
|
||||
if (!redirectLibraryFunctionToFunction("kernel32.dll", "SetUnhandledExceptionFilter", &noop_SetUnhandledExceptionFilter)) {
|
||||
qWarning() << "Failed to patch setUnhandledExceptionFilter";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// CrashReporter.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Ryan Huffman on 11 April 2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_CrashReporter_h
|
||||
#define hifi_CrashReporter_h
|
||||
|
||||
#include <QString>
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
|
||||
#include <BugSplat.h>
|
||||
|
||||
class CrashReporter {
|
||||
public:
|
||||
CrashReporter(QString bugSplatDatabase, QString bugSplatApplicationName, QString version);
|
||||
|
||||
MiniDmpSender mpSender;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // hifi_CrashReporter_h
|
101
interface/src/Crashpad.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// Crashpad.cpp
|
||||
// interface/src
|
||||
//
|
||||
// Created by Clement Brisset on 01/19/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Crashpad.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#if HAS_CRASHPAD
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
|
||||
#include <client/crashpad_client.h>
|
||||
#include <client/crash_report_database.h>
|
||||
#include <client/settings.h>
|
||||
// #include <client/annotation_list.h>
|
||||
// #include <client/crashpad_info.h>
|
||||
|
||||
using namespace crashpad;
|
||||
|
||||
static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL };
|
||||
static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN };
|
||||
|
||||
extern QString qAppFileName();
|
||||
|
||||
// crashpad::AnnotationList* crashpadAnnotations { nullptr };
|
||||
|
||||
bool startCrashHandler() {
|
||||
if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CrashpadClient client;
|
||||
std::vector<std::string> arguments;
|
||||
|
||||
std::map<std::string, std::string> annotations;
|
||||
annotations["token"] = BACKTRACE_TOKEN;
|
||||
annotations["format"] = "minidump";
|
||||
annotations["version"] = BuildInfo::VERSION.toStdString();
|
||||
|
||||
arguments.push_back("--no-rate-limit");
|
||||
|
||||
// Setup Crashpad DB directory
|
||||
const auto crashpadDbName = "crashpad-db";
|
||||
const auto crashpadDbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists
|
||||
const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName;
|
||||
|
||||
// Locate Crashpad handler
|
||||
const std::string CRASHPAD_HANDLER_PATH = QFileInfo(qAppFileName()).absolutePath().toStdString() + "/crashpad_handler.exe";
|
||||
|
||||
// Setup different file paths
|
||||
base::FilePath::StringType dbPath;
|
||||
base::FilePath::StringType handlerPath;
|
||||
dbPath.assign(crashpadDbPath.cbegin(), crashpadDbPath.cend());
|
||||
handlerPath.assign(CRASHPAD_HANDLER_PATH.cbegin(), CRASHPAD_HANDLER_PATH.cend());
|
||||
|
||||
base::FilePath db(dbPath);
|
||||
base::FilePath handler(handlerPath);
|
||||
|
||||
auto database = crashpad::CrashReportDatabase::Initialize(db);
|
||||
if (database == nullptr || database->GetSettings() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable automated uploads.
|
||||
database->GetSettings()->SetUploadsEnabled(true);
|
||||
|
||||
return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true);
|
||||
}
|
||||
|
||||
void setCrashAnnotation(std::string name, std::string value) {
|
||||
// if (!crashpadAnnotations) {
|
||||
// crashpadAnnotations = new crashpad::AnnotationList(); // don't free this, let it leak
|
||||
// crashpad::CrashpadInfo* crashpad_info = crashpad::GetCrashpadInfo();
|
||||
// crashpad_info->set_simple_annotations(crashpadAnnotations);
|
||||
// }
|
||||
// crashpadAnnotations->SetKeyValue(name, value);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool startCrashHandler() {
|
||||
qDebug() << "No crash handler available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
void setCrashAnnotation(std::string name, std::string value) {
|
||||
}
|
||||
|
||||
#endif
|
20
interface/src/Crashpad.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Crashpad.h
|
||||
// interface/src
|
||||
//
|
||||
// Created by Clement Brisset on 01/19/18.
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Crashpad_h
|
||||
#define hifi_Crashpad_h
|
||||
|
||||
#include <string>
|
||||
|
||||
bool startCrashHandler();
|
||||
void setCrashAnnotation(std::string name, std::string value);
|
||||
|
||||
#endif // hifi_Crashpad_h
|
|
@ -168,7 +168,7 @@ float LODManager::getDesktopLODDecreaseFPS() const {
|
|||
}
|
||||
|
||||
float LODManager::getDesktopLODIncreaseFPS() const {
|
||||
return glm::max(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS);
|
||||
return glm::min(((float)MSECS_PER_SECOND / _desktopMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_DESKTOP_FPS);
|
||||
}
|
||||
|
||||
void LODManager::setHMDLODDecreaseFPS(float fps) {
|
||||
|
@ -184,7 +184,7 @@ float LODManager::getHMDLODDecreaseFPS() const {
|
|||
}
|
||||
|
||||
float LODManager::getHMDLODIncreaseFPS() const {
|
||||
return glm::max(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS);
|
||||
return glm::min(((float)MSECS_PER_SECOND / _hmdMaxRenderTime) + INCREASE_LOD_GAP_FPS, MAX_LIKELY_HMD_FPS);
|
||||
}
|
||||
|
||||
QString LODManager::getLODFeedbackText() {
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
#include <render/Args.h>
|
||||
|
||||
const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 30.0f;
|
||||
const float DEFAULT_HMD_LOD_DOWN_FPS = 45.0f;
|
||||
const float DEFAULT_HMD_LOD_DOWN_FPS = 34.0f;
|
||||
const float DEFAULT_DESKTOP_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_DESKTOP_LOD_DOWN_FPS; // msec
|
||||
const float DEFAULT_HMD_MAX_RENDER_TIME = (float)MSECS_PER_SECOND / DEFAULT_HMD_LOD_DOWN_FPS; // msec
|
||||
const float MAX_LIKELY_DESKTOP_FPS = 59.0f; // this is essentially, V-synch - 1 fps
|
||||
const float MAX_LIKELY_HMD_FPS = 74.0f; // this is essentially, V-synch - 1 fps
|
||||
const float INCREASE_LOD_GAP_FPS = 15.0f; // fps
|
||||
const float INCREASE_LOD_GAP_FPS = 10.0f; // fps
|
||||
|
||||
// The default value DEFAULT_OCTREE_SIZE_SCALE means you can be 400 meters away from a 1 meter object in order to see it (which is ~20:20 vision).
|
||||
const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "audio/AudioScope.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "AvatarBookmarks.h"
|
||||
#include "AvatarEntitiesBookmarks.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "MainWindow.h"
|
||||
#include "render/DrawStatus.h"
|
||||
|
@ -206,6 +207,9 @@ Menu::Menu() {
|
|||
auto avatarBookmarks = DependencyManager::get<AvatarBookmarks>();
|
||||
avatarBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
auto avatarEntitiesBookmarks = DependencyManager::get<AvatarEntitiesBookmarks>();
|
||||
avatarEntitiesBookmarks->setupMenus(this, avatarMenu);
|
||||
|
||||
// Display menu ----------------------------------
|
||||
// FIXME - this is not yet matching Alan's spec because it doesn't have
|
||||
// menus for "2D"/"3D" - we need to add support for detecting the appropriate
|
||||
|
@ -278,7 +282,7 @@ Menu::Menu() {
|
|||
|
||||
// Navigate > Show Address Bar
|
||||
addActionToQMenuAndActionHash(navigateMenu, MenuOption::AddressBar, Qt::CTRL | Qt::Key_L,
|
||||
dialogsManager.data(), SLOT(showAddressBar()));
|
||||
dialogsManager.data(), SLOT(toggleAddressBar()));
|
||||
|
||||
// Navigate > LocationBookmarks related menus -- Note: the LocationBookmarks class adds its own submenus here.
|
||||
auto locationBookmarks = DependencyManager::get<LocationBookmarks>();
|
||||
|
|
|
@ -46,9 +46,11 @@ namespace MenuOption {
|
|||
const QString AutoMuteAudio = "Auto Mute Microphone";
|
||||
const QString AvatarReceiveStats = "Show Receive Stats";
|
||||
const QString AvatarBookmarks = "Avatar Bookmarks";
|
||||
const QString AvatarEntitiesBookmarks = "Avatar Entities Bookmarks";
|
||||
const QString Back = "Back";
|
||||
const QString BinaryEyelidControl = "Binary Eyelid Control";
|
||||
const QString BookmarkAvatar = "Bookmark Avatar";
|
||||
const QString BookmarkAvatarEntities = "Bookmark Avatar Entities";
|
||||
const QString BookmarkLocation = "Bookmark Location";
|
||||
const QString CalibrateCamera = "Calibrate Camera";
|
||||
const QString CameraEntityMode = "Entity Mode";
|
||||
|
@ -78,6 +80,7 @@ namespace MenuOption {
|
|||
const QString DecreaseAvatarSize = "Decrease Avatar Size";
|
||||
const QString DefaultSkybox = "Default Skybox";
|
||||
const QString DeleteAvatarBookmark = "Delete Avatar Bookmark...";
|
||||
const QString DeleteAvatarEntitiesBookmark = "Delete Avatar Entities Bookmark";
|
||||
const QString DeleteBookmark = "Delete Bookmark...";
|
||||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
|
||||
|
|
|
@ -19,8 +19,9 @@
|
|||
using RenderArgsPointer = std::shared_ptr<RenderArgs>;
|
||||
|
||||
void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
|
||||
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor);
|
||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
|
||||
|
||||
task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
|
||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
|
||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||
if (!isDeferred) {
|
||||
task.addJob<RenderForwardTask>("Forward", items);
|
||||
|
@ -205,7 +206,7 @@ public:
|
|||
|
||||
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
|
||||
const auto cachedArg = task.addJob<SecondaryCameraJob>("SecondaryCamera");
|
||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor);
|
||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
|
||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||
if (!isDeferred) {
|
||||
task.addJob<RenderForwardTask>("Forward", items);
|
||||
|
|
|
@ -31,6 +31,9 @@ AvatarActionHold::AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntit
|
|||
myAvatar->addHoldAction(this);
|
||||
}
|
||||
|
||||
_positionalTargetSet = true;
|
||||
_rotationalTargetSet = true;
|
||||
|
||||
#if WANT_DEBUG
|
||||
qDebug() << "AvatarActionHold::AvatarActionHold" << (void*)this;
|
||||
#endif
|
||||
|
|
|
@ -546,7 +546,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
continue;
|
||||
}
|
||||
|
||||
QString extraInfo;
|
||||
QVariantMap extraInfo;
|
||||
intersects = avatarModel->findRayIntersectionAgainstSubMeshes(ray.origin, normDirection,
|
||||
distance, face, surfaceNormal, extraInfo, true);
|
||||
|
||||
|
@ -554,6 +554,7 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
|
|||
result.intersects = true;
|
||||
result.avatarID = avatar->getID();
|
||||
result.distance = distance;
|
||||
result.extraInfo = extraInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ModelEntityItem.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <TextRenderer3D.h>
|
||||
#include <UserActivityLogger.h>
|
||||
#include <AnimDebugDraw.h>
|
||||
|
@ -46,6 +48,7 @@
|
|||
#include <recording/Frame.h>
|
||||
#include <RecordingScriptingInterface.h>
|
||||
#include <trackers/FaceTracker.h>
|
||||
#include <RenderableModelEntityItem.h>
|
||||
|
||||
#include "MyHead.h"
|
||||
#include "MySkeletonModel.h"
|
||||
|
@ -501,11 +504,42 @@ void MyAvatar::updateEyeContactTarget(float deltaTime) {
|
|||
extern QByteArray avatarStateToFrame(const AvatarData* _avatar);
|
||||
extern void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
||||
|
||||
void MyAvatar::beParentOfChild(SpatiallyNestablePointer newChild) const {
|
||||
_cauterizationNeedsUpdate = true;
|
||||
SpatiallyNestable::beParentOfChild(newChild);
|
||||
}
|
||||
|
||||
void MyAvatar::forgetChild(SpatiallyNestablePointer newChild) const {
|
||||
_cauterizationNeedsUpdate = true;
|
||||
SpatiallyNestable::forgetChild(newChild);
|
||||
}
|
||||
|
||||
void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object) {
|
||||
if (object->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||
entity->setCauterized(!_prevShouldDrawHead);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
|
||||
animateScaleChanges(deltaTime);
|
||||
|
||||
if (_cauterizationNeedsUpdate) {
|
||||
const std::unordered_set<int>& headBoneSet = _skeletonModel->getCauterizeBoneSet();
|
||||
forEachChild([&](SpatiallyNestablePointer object) {
|
||||
bool isChildOfHead = headBoneSet.find(object->getParentJointIndex()) != headBoneSet.end();
|
||||
if (isChildOfHead) {
|
||||
updateChildCauterization(object);
|
||||
object->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
updateChildCauterization(descendant);
|
||||
});
|
||||
}
|
||||
});
|
||||
_cauterizationNeedsUpdate = false;
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("transform");
|
||||
bool stepAction = false;
|
||||
|
@ -561,6 +595,12 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
if (!_skeletonModel->getHeadPosition(headPosition)) {
|
||||
headPosition = getWorldPosition();
|
||||
}
|
||||
|
||||
if (isNaN(headPosition)) {
|
||||
qCDebug(interfaceapp) << "MyAvatar::simulate headPosition is NaN";
|
||||
headPosition = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
head->setPosition(headPosition);
|
||||
head->setScale(getModelScale());
|
||||
head->simulate(deltaTime);
|
||||
|
@ -1059,7 +1099,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) {
|
|||
}
|
||||
|
||||
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
||||
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene());
|
||||
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
|
||||
|
@ -1409,12 +1449,44 @@ void MyAvatar::clearJointsData() {
|
|||
|
||||
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||
Avatar::setSkeletonModelURL(skeletonModelURL);
|
||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene());
|
||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE);
|
||||
_headBoneSet.clear();
|
||||
_cauterizationNeedsUpdate = true;
|
||||
emit skeletonChanged();
|
||||
|
||||
}
|
||||
|
||||
void MyAvatar::removeAvatarEntities() {
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
if (entityTree) {
|
||||
entityTree->withWriteLock([&] {
|
||||
AvatarEntityMap avatarEntities = getAvatarEntityData();
|
||||
for (auto entityID : avatarEntities.keys()) {
|
||||
entityTree->deleteEntity(entityID, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList MyAvatar::getAvatarEntitiesVariant() {
|
||||
QVariantList avatarEntitiesData;
|
||||
QScriptEngine scriptEngine;
|
||||
forEachChild([&](SpatiallyNestablePointer child) {
|
||||
if (child->getNestableType() == NestableType::Entity) {
|
||||
auto modelEntity = std::dynamic_pointer_cast<ModelEntityItem>(child);
|
||||
if (modelEntity) {
|
||||
QVariantMap avatarEntityData;
|
||||
EntityItemProperties entityProperties = modelEntity->getProperties();
|
||||
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
|
||||
avatarEntityData["properties"] = scriptProperties.toVariant();
|
||||
avatarEntitiesData.append(QVariant(avatarEntityData));
|
||||
}
|
||||
}
|
||||
});
|
||||
return avatarEntitiesData;
|
||||
}
|
||||
|
||||
|
||||
void MyAvatar::resetFullAvatarURL() {
|
||||
auto lastAvatarURL = getFullAvatarURLFromPreferences();
|
||||
|
@ -1723,7 +1795,7 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName,
|
|||
|
||||
void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) {
|
||||
if (model->isActive() && model->isRenderable()) {
|
||||
model->setVisibleInScene(visible, scene);
|
||||
model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1751,6 +1823,8 @@ void MyAvatar::initHeadBones() {
|
|||
}
|
||||
q.pop();
|
||||
}
|
||||
|
||||
_cauterizationNeedsUpdate = true;
|
||||
}
|
||||
|
||||
QUrl MyAvatar::getAnimGraphOverrideUrl() const {
|
||||
|
@ -1821,6 +1895,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
|
|||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
_isAnimatingScale = true;
|
||||
_cauterizationNeedsUpdate = true;
|
||||
}
|
||||
|
||||
if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) {
|
||||
|
@ -1909,6 +1984,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
|
|||
// toggle using the cauterizedBones depending on where the camera is and the rendering pass type.
|
||||
const bool shouldDrawHead = shouldRenderHead(renderArgs);
|
||||
if (shouldDrawHead != _prevShouldDrawHead) {
|
||||
_cauterizationNeedsUpdate = true;
|
||||
_skeletonModel->setEnableCauterization(!shouldDrawHead);
|
||||
|
||||
for (int i = 0; i < _attachmentData.size(); i++) {
|
||||
|
@ -1918,7 +1994,8 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
|
|||
_attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 ||
|
||||
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
|
||||
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
|
||||
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene());
|
||||
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(),
|
||||
render::ItemKey::TAG_BITS_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2414,7 +2491,6 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
};
|
||||
auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) {
|
||||
OctreeElementPointer element;
|
||||
EntityItemPointer intersectedEntity = NULL;
|
||||
float distance;
|
||||
BoxFace face;
|
||||
const bool visibleOnly = false;
|
||||
|
@ -2426,13 +2502,14 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
const auto lockType = Octree::Lock; // Should we refactor to take a lock just once?
|
||||
bool* accurateResult = NULL;
|
||||
|
||||
bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult);
|
||||
if (!intersects || !intersectedEntity) {
|
||||
QVariantMap extraInfo;
|
||||
EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, extraInfo, lockType, accurateResult);
|
||||
if (entityID.isNull()) {
|
||||
return false;
|
||||
}
|
||||
intersectionOut = startPointIn + (directionIn * distance);
|
||||
entityIdOut = intersectedEntity->getEntityItemID();
|
||||
entityIdOut = entityID;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -2700,27 +2777,48 @@ void MyAvatar::setWalkSpeed(float value) {
|
|||
}
|
||||
|
||||
glm::vec3 MyAvatar::getPositionForAudio() {
|
||||
glm::vec3 result;
|
||||
switch (_audioListenerMode) {
|
||||
case AudioListenerMode::FROM_HEAD:
|
||||
return getHead()->getPosition();
|
||||
result = getHead()->getPosition();
|
||||
break;
|
||||
case AudioListenerMode::FROM_CAMERA:
|
||||
return qApp->getCamera().getPosition();
|
||||
result = qApp->getCamera().getPosition();
|
||||
break;
|
||||
case AudioListenerMode::CUSTOM:
|
||||
return _customListenPosition;
|
||||
result = _customListenPosition;
|
||||
break;
|
||||
}
|
||||
return vec3();
|
||||
|
||||
if (isNaN(result)) {
|
||||
qCDebug(interfaceapp) << "MyAvatar::getPositionForAudio produced NaN" << _audioListenerMode;
|
||||
result = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::quat MyAvatar::getOrientationForAudio() {
|
||||
glm::quat result;
|
||||
|
||||
switch (_audioListenerMode) {
|
||||
case AudioListenerMode::FROM_HEAD:
|
||||
return getHead()->getFinalOrientationInWorldFrame();
|
||||
result = getHead()->getFinalOrientationInWorldFrame();
|
||||
break;
|
||||
case AudioListenerMode::FROM_CAMERA:
|
||||
return qApp->getCamera().getOrientation();
|
||||
result = qApp->getCamera().getOrientation();
|
||||
break;
|
||||
case AudioListenerMode::CUSTOM:
|
||||
return _customListenOrientation;
|
||||
result = _customListenOrientation;
|
||||
break;
|
||||
}
|
||||
return quat();
|
||||
|
||||
if (isNaN(result)) {
|
||||
qCDebug(interfaceapp) << "MyAvatar::getOrientationForAudio produced NaN" << _audioListenerMode;
|
||||
result = glm::quat();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void MyAvatar::setAudioListenerMode(AudioListenerMode audioListenerMode) {
|
||||
|
@ -3178,8 +3276,6 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o
|
|||
slamPosition(position);
|
||||
setWorldOrientation(orientation);
|
||||
|
||||
_skeletonModel->getRig().setMaxHipsOffsetLength(0.05f);
|
||||
|
||||
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||
if (it == _pinnedJoints.end()) {
|
||||
_pinnedJoints.push_back(index);
|
||||
|
@ -3199,12 +3295,6 @@ bool MyAvatar::clearPinOnJoint(int index) {
|
|||
auto it = std::find(_pinnedJoints.begin(), _pinnedJoints.end(), index);
|
||||
if (it != _pinnedJoints.end()) {
|
||||
_pinnedJoints.erase(it);
|
||||
|
||||
auto hipsIndex = getJointIndex("Hips");
|
||||
if (index == hipsIndex) {
|
||||
_skeletonModel->getRig().setMaxHipsOffsetLength(FLT_MAX);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -81,7 +81,7 @@ class MyAvatar : public Avatar {
|
|||
* and MyAvatar.customListenOrientation properties.
|
||||
* @property customListenPosition {Vec3} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the position
|
||||
* of audio spatialization listener.
|
||||
* @property customListenOreintation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation
|
||||
* @property customListenOrientation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation
|
||||
* of the audio spatialization listener.
|
||||
* @property audioListenerModeHead {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener
|
||||
* around the avatar's head.
|
||||
|
@ -512,6 +512,9 @@ public:
|
|||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
QVariantList getAvatarEntitiesVariant();
|
||||
void removeAvatarEntities();
|
||||
|
||||
Q_INVOKABLE bool isFlying();
|
||||
Q_INVOKABLE bool isInAir();
|
||||
Q_INVOKABLE void setFlyingEnabled(bool enabled);
|
||||
|
@ -630,6 +633,11 @@ signals:
|
|||
private slots:
|
||||
void leaveDomain();
|
||||
|
||||
|
||||
protected:
|
||||
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
|
||||
virtual void forgetChild(SpatiallyNestablePointer newChild) const override;
|
||||
|
||||
private:
|
||||
|
||||
bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut);
|
||||
|
@ -809,6 +817,8 @@ private:
|
|||
bool _enableDebugDrawIKChains { false };
|
||||
bool _enableDebugDrawDetailedCollision { false };
|
||||
|
||||
mutable bool _cauterizationNeedsUpdate; // do we need to scan children and update their "cauterized" state?
|
||||
|
||||
AudioListenerMode _audioListenerMode;
|
||||
glm::vec3 _customListenPosition;
|
||||
glm::quat _customListenOrientation;
|
||||
|
@ -846,6 +856,8 @@ private:
|
|||
// height of user in sensor space, when standing erect.
|
||||
ThreadSafeValueCache<float> _userHeight { DEFAULT_AVATAR_HEIGHT };
|
||||
|
||||
void updateChildCauterization(SpatiallyNestablePointer object);
|
||||
|
||||
// max unscaled forward movement speed
|
||||
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||
};
|
||||
|
|
|
@ -43,8 +43,6 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
AnimPose result = AnimPose(worldToSensorMat * avatarTransform.getMatrix() * Matrices::Y_180);
|
||||
result.scale() = glm::vec3(1.0f, 1.0f, 1.0f);
|
||||
return result;
|
||||
} else {
|
||||
DebugDraw::getInstance().removeMarker("pinnedHips");
|
||||
}
|
||||
|
||||
glm::mat4 hipsMat = myAvatar->deriveBodyFromHMDSensor();
|
||||
|
|
|
@ -26,15 +26,11 @@
|
|||
|
||||
#include "AddressManager.h"
|
||||
#include "Application.h"
|
||||
#include "Crashpad.h"
|
||||
#include "InterfaceLogging.h"
|
||||
#include "UserActivityLogger.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
#include <BugSplat.h>
|
||||
#include <CrashReporter.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
extern "C" {
|
||||
typedef int(__stdcall * CHECKMINSPECPROC) ();
|
||||
|
@ -42,11 +38,6 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
#if HAS_BUGSPLAT
|
||||
static QString BUG_SPLAT_DATABASE = "interface_alpha";
|
||||
static QString BUG_SPLAT_APPLICATION_NAME = "Interface";
|
||||
CrashReporter crashReporter { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION };
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
|
||||
|
@ -71,6 +62,17 @@ int main(int argc, const char* argv[]) {
|
|||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Setting::init();
|
||||
|
||||
// Instance UserActivityLogger now that the settings are loaded
|
||||
auto& ual = UserActivityLogger::getInstance();
|
||||
qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled();
|
||||
|
||||
if (ual.isEnabled()) {
|
||||
auto crashHandlerStarted = startCrashHandler();
|
||||
qDebug() << "Crash handler started:" << crashHandlerStarted;
|
||||
}
|
||||
|
||||
QStringList arguments;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
arguments << argv[i];
|
||||
|
@ -259,7 +261,6 @@ int main(int argc, const char* argv[]) {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Setup local server
|
||||
QLocalServer server { &app };
|
||||
|
@ -268,29 +269,8 @@ int main(int argc, const char* argv[]) {
|
|||
server.removeServer(applicationName);
|
||||
server.listen(applicationName);
|
||||
|
||||
QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection, Qt::DirectConnection);
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
crashReporter.mpSender.setDefaultUserName(qPrintable(accountManager->getAccountInfo().getUsername()));
|
||||
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, &app, [&crashReporter](const QString& newUsername) {
|
||||
crashReporter.mpSender.setDefaultUserName(qPrintable(newUsername));
|
||||
});
|
||||
|
||||
// BugSplat WILL NOT work with file paths that do not use OS native separators.
|
||||
auto logger = app.getLogger();
|
||||
auto logPath = QDir::toNativeSeparators(logger->getFilename());
|
||||
crashReporter.mpSender.sendAdditionalFile(qPrintable(logPath));
|
||||
|
||||
QMetaObject::Connection connection;
|
||||
connection = QObject::connect(logger, &FileLogger::rollingLogFile, &app, [&crashReporter, &connection](QString newFilename) {
|
||||
// We only want to add the first rolled log file (the "beginning" of the log) to BugSplat to ensure we don't exceed the 2MB
|
||||
// zipped limit, so we disconnect here.
|
||||
QObject::disconnect(connection);
|
||||
auto rolledLogPath = QDir::toNativeSeparators(newFilename);
|
||||
crashReporter.mpSender.sendAdditionalFile(qPrintable(rolledLogPath));
|
||||
});
|
||||
#endif
|
||||
QObject::connect(&server, &QLocalServer::newConnection,
|
||||
&app, &Application::handleLocalServerConnection, Qt::DirectConnection);
|
||||
|
||||
printSystemInformation();
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ public:
|
|||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {PickRay} searchRay The PickRay that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
|
@ -127,6 +128,7 @@ public:
|
|||
* @property {float} distance The distance to the intersection point from the origin of the ray.
|
||||
* @property {Vec3} intersection The intersection point in world-space.
|
||||
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
|
||||
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
|
||||
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
|
||||
*/
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
|||
DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (entityRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
|||
qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
|
||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
if (overlayRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
|||
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
|
||||
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
|
||||
if (avatarRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo);
|
||||
} else {
|
||||
return std::make_shared<RayPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ class RayPickResult : public PickResult {
|
|||
public:
|
||||
RayPickResult() {}
|
||||
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) {
|
||||
}
|
||||
|
||||
RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) {
|
||||
|
@ -29,6 +29,7 @@ public:
|
|||
distance = rayPickResult.distance;
|
||||
intersection = rayPickResult.intersection;
|
||||
surfaceNormal = rayPickResult.surfaceNormal;
|
||||
extraInfo = rayPickResult.extraInfo;
|
||||
}
|
||||
|
||||
IntersectionType type { NONE };
|
||||
|
@ -37,6 +38,7 @@ public:
|
|||
float distance { FLT_MAX };
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
QVariantMap extraInfo;
|
||||
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
|
@ -47,6 +49,7 @@ public:
|
|||
toReturn["intersection"] = vec3toVariant(intersection);
|
||||
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
|
||||
toReturn["searchRay"] = PickResult::toVariantMap();
|
||||
toReturn["extraInfo"] = extraInfo;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
|
|
|
@ -260,7 +260,7 @@ void AssetMappingModel::refresh() {
|
|||
for (auto& mapping : mappings) {
|
||||
auto& path = mapping.first;
|
||||
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
// Hide baked mappings
|
||||
continue;
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ void AssetMappingModel::refresh() {
|
|||
auto statusString = isFolder ? "--" : bakingStatusToString(mapping.second.status);
|
||||
lastItem->setData(statusString, Qt::UserRole + 5);
|
||||
lastItem->setData(mapping.second.bakingErrors, Qt::UserRole + 6);
|
||||
if (mapping.second.status == Pending) {
|
||||
if (mapping.second.status == AssetUtils::Pending) {
|
||||
++numPendingBakes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q
|
|||
}
|
||||
|
||||
WalletScriptingInterface::WalletScriptingInterface() {
|
||||
|
||||
}
|
||||
|
||||
void WalletScriptingInterface::refreshWalletStatus() {
|
||||
|
@ -26,4 +27,19 @@ void WalletScriptingInterface::refreshWalletStatus() {
|
|||
void WalletScriptingInterface::setWalletStatus(const uint& status) {
|
||||
_walletStatus = status;
|
||||
emit DependencyManager::get<Wallet>()->walletStatusResult(status);
|
||||
}
|
||||
|
||||
void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUuid& entityID) {
|
||||
QSharedPointer<ContextOverlayInterface> contextOverlayInterface = DependencyManager::get<ContextOverlayInterface>();
|
||||
EntityItemProperties entityProperties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID,
|
||||
contextOverlayInterface->getEntityPropertyFlags());
|
||||
if (entityProperties.getClientOnly()) {
|
||||
if (!entityID.isNull() && entityProperties.getCertificateID().length() > 0) {
|
||||
contextOverlayInterface->requestOwnershipVerification(entityID);
|
||||
} else {
|
||||
qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is null or not a certified item";
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "Failed to prove ownership of:" << entityID << "is not an avatar entity";
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
#include <OffscreenUi.h>
|
||||
#include "Application.h"
|
||||
#include "commerce/Wallet.h"
|
||||
#include "ui/overlays/ContextOverlayInterface.h"
|
||||
|
||||
class CheckoutProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
|
@ -39,6 +40,7 @@ public:
|
|||
|
||||
Q_INVOKABLE void refreshWalletStatus();
|
||||
Q_INVOKABLE uint getWalletStatus() { return _walletStatus; }
|
||||
Q_INVOKABLE void proveAvatarEntityOwnershipVerification(const QUuid& entityID);
|
||||
// setWalletStatus() should never be made Q_INVOKABLE. If it were,
|
||||
// scripts could cause the Wallet to incorrectly report its status.
|
||||
void setWalletStatus(const uint& status);
|
||||
|
@ -46,6 +48,8 @@ public:
|
|||
signals:
|
||||
void walletStatusChanged();
|
||||
void walletNotSetup();
|
||||
void ownershipVerificationSuccess(const QUuid& entityID);
|
||||
void ownershipVerificationFailed(const QUuid& entityID);
|
||||
|
||||
private:
|
||||
uint _walletStatus;
|
||||
|
|
|
@ -105,14 +105,14 @@ void WindowScriptingInterface::raiseMainWindow() {
|
|||
/// \param const QString& message message to display
|
||||
/// \return QScriptValue::UndefinedValue
|
||||
void WindowScriptingInterface::alert(const QString& message) {
|
||||
OffscreenUi::asyncWarning("", message);
|
||||
OffscreenUi::asyncWarning("", message, QMessageBox::Ok, QMessageBox::Ok);
|
||||
}
|
||||
|
||||
/// Display a confirmation box with the options 'Yes' and 'No'
|
||||
/// \param const QString& message message to display
|
||||
/// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise
|
||||
QScriptValue WindowScriptingInterface::confirm(const QString& message) {
|
||||
return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No)));
|
||||
return QScriptValue((QMessageBox::Yes == OffscreenUi::question("", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)));
|
||||
}
|
||||
|
||||
/// Display a prompt with a text box
|
||||
|
@ -354,7 +354,7 @@ 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
|
||||
/// 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
|
||||
|
@ -396,11 +396,11 @@ QString WindowScriptingInterface::protocolSignature() {
|
|||
}
|
||||
|
||||
int WindowScriptingInterface::getInnerWidth() {
|
||||
return qApp->getDeviceSize().x;
|
||||
return qApp->getWindow()->geometry().width();
|
||||
}
|
||||
|
||||
int WindowScriptingInterface::getInnerHeight() {
|
||||
return qApp->getDeviceSize().y;
|
||||
return qApp->getWindow()->geometry().height() - qApp->getPrimaryMenu()->geometry().height();
|
||||
}
|
||||
|
||||
glm::vec2 WindowScriptingInterface::getDeviceSize() const {
|
||||
|
|
|
@ -22,7 +22,7 @@ class AddressBarDialog : public OffscreenQmlDialog {
|
|||
Q_PROPERTY(bool backEnabled READ backEnabled NOTIFY backEnabledChanged)
|
||||
Q_PROPERTY(bool forwardEnabled READ forwardEnabled NOTIFY forwardEnabledChanged)
|
||||
Q_PROPERTY(bool useFeed READ useFeed WRITE setUseFeed NOTIFY useFeedChanged)
|
||||
Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl)
|
||||
Q_PROPERTY(QString metaverseServerUrl READ metaverseServerUrl CONSTANT)
|
||||
|
||||
public:
|
||||
AddressBarDialog(QQuickItem* parent = nullptr);
|
||||
|
|
|
@ -58,7 +58,7 @@ void DialogsManager::showAddressBar() {
|
|||
hmd->openTablet();
|
||||
}
|
||||
qApp->setKeyboardFocusOverlay(hmd->getCurrentTabletScreenID());
|
||||
emit addressBarShown(true);
|
||||
setAddressBarVisible(true);
|
||||
}
|
||||
|
||||
void DialogsManager::hideAddressBar() {
|
||||
|
@ -71,7 +71,7 @@ void DialogsManager::hideAddressBar() {
|
|||
hmd->closeTablet();
|
||||
}
|
||||
qApp->setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
|
||||
emit addressBarShown(false);
|
||||
setAddressBarVisible(false);
|
||||
}
|
||||
|
||||
void DialogsManager::showFeed() {
|
||||
|
@ -157,6 +157,24 @@ void DialogsManager::hmdToolsClosed() {
|
|||
}
|
||||
}
|
||||
|
||||
void DialogsManager::toggleAddressBar() {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
|
||||
const bool addressBarLoaded = tablet->isPathLoaded(TABLET_ADDRESS_DIALOG);
|
||||
|
||||
if (_addressBarVisible || addressBarLoaded) {
|
||||
hideAddressBar();
|
||||
} else {
|
||||
showAddressBar();
|
||||
}
|
||||
}
|
||||
|
||||
void DialogsManager::setAddressBarVisible(bool addressBarVisible) {
|
||||
_addressBarVisible = addressBarVisible;
|
||||
emit addressBarShown(_addressBarVisible);
|
||||
}
|
||||
|
||||
void DialogsManager::showTestingResults() {
|
||||
if (!_testingDialog) {
|
||||
_testingDialog = new TestingDialog(qApp->getWindow());
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
QPointer<OctreeStatsDialog> getOctreeStatsDialog() const { return _octreeStatsDialog; }
|
||||
QPointer<TestingDialog> getTestingDialog() const { return _testingDialog; }
|
||||
void emitAddressBarShown(bool visible) { emit addressBarShown(visible); }
|
||||
void setAddressBarVisible(bool addressBarVisible);
|
||||
|
||||
public slots:
|
||||
void showAddressBar();
|
||||
|
@ -52,6 +53,7 @@ public slots:
|
|||
void hmdTools(bool showTools);
|
||||
void showDomainConnectionDialog();
|
||||
void showTestingResults();
|
||||
void toggleAddressBar();
|
||||
|
||||
// Application Update
|
||||
void showUpdateDialog();
|
||||
|
@ -78,7 +80,7 @@ private:
|
|||
QPointer<OctreeStatsDialog> _octreeStatsDialog;
|
||||
QPointer<TestingDialog> _testingDialog;
|
||||
QPointer<DomainConnectionDialog> _domainConnectionDialog;
|
||||
bool _closeAddressBar { false };
|
||||
bool _addressBarVisible { false };
|
||||
};
|
||||
|
||||
#endif // hifi_DialogsManager_h
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
#include "JSConsole.h"
|
||||
|
||||
#include <QFuture>
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QScrollBar>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QStringListModel>
|
||||
#include <QListView>
|
||||
#include <QToolTip>
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
#include <ScriptEngines.h>
|
||||
|
@ -38,6 +40,21 @@ const QString RESULT_ERROR_STYLE = "color: #d13b22;";
|
|||
const QString GUTTER_PREVIOUS_COMMAND = "<span style=\"color: #57b8bb;\"><</span>";
|
||||
const QString GUTTER_ERROR = "<span style=\"color: #d13b22;\">X</span>";
|
||||
|
||||
const QString JSDOC_LINE_SEPARATOR = "\r";
|
||||
|
||||
const QString JSDOC_STYLE =
|
||||
"<style type=\"text/css\"> \
|
||||
.code { \
|
||||
font-family: Consolas, Monaco, 'Andale Mono', monospace \
|
||||
} \
|
||||
pre, code { \
|
||||
display: inline; \
|
||||
} \
|
||||
.no-wrap { \
|
||||
white-space: nowrap; \
|
||||
} \
|
||||
</style>";
|
||||
|
||||
const QString JSConsole::_consoleFileName { "about:console" };
|
||||
|
||||
const QString JSON_KEY = "entries";
|
||||
|
@ -49,7 +66,7 @@ QList<QString> _readLines(const QString& filename) {
|
|||
// TODO: check root["version"]
|
||||
return root[JSON_KEY].toVariant().toStringList();
|
||||
}
|
||||
|
||||
|
||||
void _writeLines(const QString& filename, const QList<QString>& lines) {
|
||||
QFile file(filename);
|
||||
file.open(QFile::WriteOnly);
|
||||
|
@ -61,12 +78,71 @@ void _writeLines(const QString& filename, const QList<QString>& lines) {
|
|||
QTextStream(&file) << json;
|
||||
}
|
||||
|
||||
QString _jsdocTypeToString(QJsonValue jsdocType) {
|
||||
return jsdocType.toObject().value("names").toVariant().toStringList().join("/");
|
||||
}
|
||||
|
||||
void JSConsole::readAPI() {
|
||||
QFile file(PathUtils::resourcesPath() + "auto-complete/hifiJSDoc.json");
|
||||
file.open(QFile::ReadOnly);
|
||||
auto json = QTextStream(&file).readAll().toUtf8();
|
||||
_apiDocs = QJsonDocument::fromJson(json).array();
|
||||
}
|
||||
|
||||
QStandardItem* getAutoCompleteItem(QJsonValue propertyObject) {
|
||||
auto propertyItem = new QStandardItem(propertyObject.toObject().value("name").toString());
|
||||
propertyItem->setData(propertyObject.toVariant());
|
||||
return propertyItem;
|
||||
}
|
||||
|
||||
QStandardItemModel* JSConsole::getAutoCompleteModel(const QString& memberOf) {
|
||||
QString memberOfProperty = nullptr;
|
||||
|
||||
auto model = new QStandardItemModel(this);
|
||||
|
||||
if (memberOf != nullptr) {
|
||||
foreach(auto doc, _apiDocs) {
|
||||
auto object = doc.toObject();
|
||||
if (object.value("name").toString() == memberOf && object.value("scope").toString() == "global" &&
|
||||
object.value("kind").toString() == "namespace") {
|
||||
|
||||
memberOfProperty = object.value("longname").toString();
|
||||
|
||||
auto properties = doc.toObject().value("properties").toArray();
|
||||
foreach(auto propertyObject, properties) {
|
||||
model->appendRow(getAutoCompleteItem(propertyObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (memberOfProperty == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
foreach(auto doc, _apiDocs) {
|
||||
auto object = doc.toObject();
|
||||
auto scope = object.value("scope");
|
||||
if ((memberOfProperty == nullptr && scope.toString() == "global" && object.value("kind").toString() == "namespace") ||
|
||||
(memberOfProperty != nullptr && object.value("memberof").toString() == memberOfProperty &&
|
||||
object.value("kind").toString() != "typedef")) {
|
||||
|
||||
model->appendRow(getAutoCompleteItem(doc));
|
||||
}
|
||||
}
|
||||
model->sort(0);
|
||||
return model;
|
||||
}
|
||||
|
||||
JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) :
|
||||
QWidget(parent),
|
||||
_ui(new Ui::Console),
|
||||
_currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND),
|
||||
_savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME),
|
||||
_commandHistory(_readLines(_savedHistoryFilename)) {
|
||||
_commandHistory(_readLines(_savedHistoryFilename)),
|
||||
_completer(new QCompleter(this)) {
|
||||
|
||||
readAPI();
|
||||
|
||||
_ui->setupUi(this);
|
||||
_ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap);
|
||||
_ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap);
|
||||
|
@ -78,38 +154,174 @@ JSConsole::JSConsole(QWidget* parent, const ScriptEnginePointer& scriptEngine) :
|
|||
setStyleSheet(styleSheet.readAll());
|
||||
}
|
||||
|
||||
connect(_ui->scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(scrollToBottom()));
|
||||
connect(_ui->promptTextEdit, SIGNAL(textChanged()), this, SLOT(resizeTextInput()));
|
||||
connect(_ui->scrollArea->verticalScrollBar(), &QScrollBar::rangeChanged, this, &JSConsole::scrollToBottom);
|
||||
connect(_ui->promptTextEdit, &QTextEdit::textChanged, this, &JSConsole::resizeTextInput);
|
||||
|
||||
_completer->setWidget(_ui->promptTextEdit);
|
||||
_completer->setModel(getAutoCompleteModel(nullptr));
|
||||
_completer->setModelSorting(QCompleter::CaseSensitivelySortedModel);
|
||||
_completer->setMaxVisibleItems(12);
|
||||
_completer->setFilterMode(Qt::MatchStartsWith);
|
||||
_completer->setWrapAround(false);
|
||||
_completer->setCompletionMode(QCompleter::PopupCompletion);
|
||||
_completer->setCaseSensitivity(Qt::CaseSensitive);
|
||||
|
||||
QListView *listView = new QListView();
|
||||
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
listView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
listView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
listView->setModelColumn(_completer->completionColumn());
|
||||
|
||||
_completer->setPopup(listView);
|
||||
_completer->popup()->installEventFilter(this);
|
||||
QObject::connect(_completer, static_cast<void(QCompleter::*)(const QModelIndex&)>(&QCompleter::activated), this,
|
||||
&JSConsole::insertCompletion);
|
||||
|
||||
QObject::connect(_completer, static_cast<void(QCompleter::*)(const QModelIndex&)>(&QCompleter::highlighted), this,
|
||||
&JSConsole::highlightedCompletion);
|
||||
|
||||
setScriptEngine(scriptEngine);
|
||||
|
||||
resizeTextInput();
|
||||
|
||||
connect(&_executeWatcher, SIGNAL(finished()), this, SLOT(commandFinished()));
|
||||
connect(&_executeWatcher, &QFutureWatcher<QScriptValue>::finished, this, &JSConsole::commandFinished);
|
||||
}
|
||||
|
||||
void JSConsole::insertCompletion(const QModelIndex& completion) {
|
||||
auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject();
|
||||
auto kind = jsdocObject.value("kind").toString();
|
||||
auto completionString = completion.data().toString();
|
||||
if (kind == "function") {
|
||||
auto params = jsdocObject.value("params").toArray();
|
||||
// automatically add the parenthesis/parentheses for the functions
|
||||
completionString += params.isEmpty() ? "()" : "(";
|
||||
}
|
||||
QTextCursor textCursor = _ui->promptTextEdit->textCursor();
|
||||
int extra = completionString.length() - _completer->completionPrefix().length();
|
||||
textCursor.movePosition(QTextCursor::Left);
|
||||
textCursor.movePosition(QTextCursor::EndOfWord);
|
||||
textCursor.insertText(completionString.right(extra));
|
||||
_ui->promptTextEdit->setTextCursor(textCursor);
|
||||
}
|
||||
|
||||
void JSConsole::highlightedCompletion(const QModelIndex& completion) {
|
||||
auto jsdocObject = QJsonValue::fromVariant(completion.data(Qt::UserRole + 1)).toObject();
|
||||
QString memberOf = "";
|
||||
if (!_completerModule.isEmpty()) {
|
||||
memberOf = _completerModule + ".";
|
||||
}
|
||||
auto name = memberOf + "<b>" + jsdocObject.value("name").toString() + "</b>";
|
||||
auto description = jsdocObject.value("description").toString();
|
||||
auto examples = jsdocObject.value("examples").toArray();
|
||||
auto kind = jsdocObject.value("kind").toString();
|
||||
QString returnTypeText = "";
|
||||
|
||||
QString paramsTable = "";
|
||||
if (kind == "function") {
|
||||
auto params = jsdocObject.value("params").toArray();
|
||||
auto returns = jsdocObject.value("returns");
|
||||
if (!returns.isUndefined()) {
|
||||
returnTypeText = _jsdocTypeToString(jsdocObject.value("returns").toArray().at(0).toObject().value("type")) + " ";
|
||||
}
|
||||
name += "(";
|
||||
if (!params.isEmpty()) {
|
||||
bool hasDefaultParam = false;
|
||||
bool hasOptionalParam = false;
|
||||
bool firstItem = true;
|
||||
foreach(auto param, params) {
|
||||
auto paramObject = param.toObject();
|
||||
if (!hasOptionalParam && paramObject.value("optional").toBool(false)) {
|
||||
hasOptionalParam = true;
|
||||
name += "<i>[";
|
||||
}
|
||||
if (!firstItem) {
|
||||
name += ", ";
|
||||
} else {
|
||||
firstItem = false;
|
||||
}
|
||||
name += paramObject.value("name").toString();
|
||||
if (!hasDefaultParam && !paramObject.value("defaultvalue").isUndefined()) {
|
||||
hasDefaultParam = true;
|
||||
}
|
||||
}
|
||||
if (hasOptionalParam) {
|
||||
name += "]</i>";
|
||||
}
|
||||
|
||||
paramsTable += "<table border=\"1\" cellpadding=\"10\"><thead><tr><th>Name</th><th>Type</th>";
|
||||
if (hasDefaultParam) {
|
||||
paramsTable += "<th>Default</th>";
|
||||
}
|
||||
paramsTable += "<th>Description</th></tr></thead><tbody>";
|
||||
foreach(auto param, params) {
|
||||
auto paramObject = param.toObject();
|
||||
paramsTable += "<tr><td>" + paramObject.value("name").toString() + "</td><td>" +
|
||||
_jsdocTypeToString(paramObject.value("type")) + "</td><td>";
|
||||
if (hasDefaultParam) {
|
||||
paramsTable += paramObject.value("defaultvalue").toVariant().toString() + "</td><td>";
|
||||
}
|
||||
paramsTable += paramObject.value("description").toString() + "</td></tr>";
|
||||
}
|
||||
|
||||
paramsTable += "</tbody></table>";
|
||||
}
|
||||
name += ")";
|
||||
} else if (!jsdocObject.value("type").isUndefined()){
|
||||
returnTypeText = _jsdocTypeToString(jsdocObject.value("type")) + " ";
|
||||
}
|
||||
auto popupText = JSDOC_STYLE + "<span class=\"no-wrap\">" + returnTypeText + name + "</span>";
|
||||
auto descriptionText = "<p>" + description.replace(JSDOC_LINE_SEPARATOR, "<br>") + "</p>";
|
||||
|
||||
popupText += descriptionText;
|
||||
popupText += paramsTable;
|
||||
auto returns = jsdocObject.value("returns");
|
||||
if (!returns.isUndefined()) {
|
||||
foreach(auto returnEntry, returns.toArray()) {
|
||||
auto returnsObject = returnEntry.toObject();
|
||||
auto returnsDescription = returnsObject.value("description").toString().replace(JSDOC_LINE_SEPARATOR, "<br>");
|
||||
popupText += "<h4>Returns</h4><p>" + returnsDescription + "</p><h5>Type</h5><pre><code>" +
|
||||
_jsdocTypeToString(returnsObject.value("type")) + "</code></pre>";
|
||||
}
|
||||
}
|
||||
|
||||
if (!examples.isEmpty()) {
|
||||
popupText += "<h4>Examples</h4>";
|
||||
foreach(auto example, examples) {
|
||||
auto exampleText = example.toString();
|
||||
auto exampleLines = exampleText.split(JSDOC_LINE_SEPARATOR);
|
||||
foreach(auto exampleLine, exampleLines) {
|
||||
if (exampleLine.contains("<caption>")) {
|
||||
popupText += exampleLine.replace("caption>", "h5>");
|
||||
} else {
|
||||
popupText += "<pre><code>" + exampleLine + "\n</code></pre>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QToolTip::showText(QPoint(_completer->popup()->pos().x() + _completer->popup()->width(), _completer->popup()->pos().y()),
|
||||
popupText, _completer->popup());
|
||||
}
|
||||
|
||||
JSConsole::~JSConsole() {
|
||||
if (_scriptEngine) {
|
||||
disconnect(_scriptEngine.data(), SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
|
||||
disconnect(_scriptEngine.data(), SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
|
||||
disconnect(_scriptEngine.data(), nullptr, this, nullptr);
|
||||
_scriptEngine.reset();
|
||||
}
|
||||
delete _ui;
|
||||
}
|
||||
|
||||
void JSConsole::setScriptEngine(const ScriptEnginePointer& scriptEngine) {
|
||||
if (_scriptEngine == scriptEngine && scriptEngine != NULL) {
|
||||
if (_scriptEngine == scriptEngine && scriptEngine != nullptr) {
|
||||
return;
|
||||
}
|
||||
if (_scriptEngine != NULL) {
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
|
||||
disconnect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError);
|
||||
if (_scriptEngine != nullptr) {
|
||||
disconnect(_scriptEngine.data(), nullptr, this, nullptr);
|
||||
_scriptEngine.reset();
|
||||
}
|
||||
|
||||
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
|
||||
// if scriptEngine is nullptr then create one and keep track of it using _ownScriptEngine
|
||||
if (scriptEngine.isNull()) {
|
||||
_scriptEngine = DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false);
|
||||
} else {
|
||||
|
@ -199,45 +411,121 @@ void JSConsole::showEvent(QShowEvent* event) {
|
|||
}
|
||||
|
||||
bool JSConsole::eventFilter(QObject* sender, QEvent* event) {
|
||||
if (sender == _ui->promptTextEdit) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
|
||||
if ((key == Qt::Key_Return || key == Qt::Key_Enter)) {
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
// If the shift key is being used then treat it as a regular return/enter. If this isn't done,
|
||||
// a new QTextBlock isn't created.
|
||||
keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier);
|
||||
} else {
|
||||
QString command = _ui->promptTextEdit->toPlainText().replace("\r\n","\n").trimmed();
|
||||
|
||||
if (!command.isEmpty()) {
|
||||
QTextCursor cursor = _ui->promptTextEdit->textCursor();
|
||||
_ui->promptTextEdit->clear();
|
||||
|
||||
executeCommand(command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Down) {
|
||||
// Go to the next command in history if the cursor is at the last line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
int blockCount = _ui->promptTextEdit->document()->blockCount();
|
||||
if (blockNumber == blockCount - 1) {
|
||||
setToNextCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Up) {
|
||||
// Go to the previous command in history if the cursor is at the first line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
if (blockNumber == 0) {
|
||||
setToPreviousCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
if (_completer->popup()->isVisible()) {
|
||||
// The following keys are forwarded by the completer to the widget
|
||||
switch (key) {
|
||||
case Qt::Key_Space:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
insertCompletion(_completer->popup()->currentIndex());
|
||||
_completer->popup()->hide();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == Qt::Key_Return || key == Qt::Key_Enter) {
|
||||
if (keyEvent->modifiers() & Qt::ShiftModifier) {
|
||||
// If the shift key is being used then treat it as a regular return/enter. If this isn't done,
|
||||
// a new QTextBlock isn't created.
|
||||
keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier);
|
||||
} else {
|
||||
QString command = _ui->promptTextEdit->toPlainText().replace("\r\n", "\n").trimmed();
|
||||
|
||||
if (!command.isEmpty()) {
|
||||
QTextCursor cursor = _ui->promptTextEdit->textCursor();
|
||||
_ui->promptTextEdit->clear();
|
||||
|
||||
executeCommand(command);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Down) {
|
||||
// Go to the next command in history if the cursor is at the last line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
int blockCount = _ui->promptTextEdit->document()->blockCount();
|
||||
if (blockNumber == blockCount - 1) {
|
||||
setToNextCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
} else if (key == Qt::Key_Up) {
|
||||
// Go to the previous command in history if the cursor is at the first line of the current command.
|
||||
int blockNumber = _ui->promptTextEdit->textCursor().blockNumber();
|
||||
if (blockNumber == 0) {
|
||||
setToPreviousCommandInHistory();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if ((sender == _ui->promptTextEdit || sender == _completer->popup()) && event->type() == QEvent::KeyRelease) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
int key = keyEvent->key();
|
||||
|
||||
// completer shortcut (CTRL + SPACE)
|
||||
bool isCompleterShortcut = ((keyEvent->modifiers() & Qt::ControlModifier) && key == Qt::Key_Space) ||
|
||||
key == Qt::Key_Period;
|
||||
if (_completer->popup()->isVisible() || isCompleterShortcut) {
|
||||
|
||||
const bool ctrlOrShift = keyEvent->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
|
||||
if (ctrlOrShift && keyEvent->text().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word
|
||||
|
||||
if (!isCompleterShortcut && (!keyEvent->text().isEmpty() && eow.contains(keyEvent->text().right(1)))) {
|
||||
_completer->popup()->hide();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto textCursor = _ui->promptTextEdit->textCursor();
|
||||
|
||||
textCursor.select(QTextCursor::WordUnderCursor);
|
||||
|
||||
QString completionPrefix = textCursor.selectedText();
|
||||
|
||||
auto leftOfCursor = _ui->promptTextEdit->toPlainText().left(textCursor.position());
|
||||
|
||||
// RegEx [3] [4]
|
||||
// (Module.subModule).(property/subModule)
|
||||
|
||||
const int MODULE_INDEX = 3;
|
||||
const int PROPERTY_INDEX = 4;
|
||||
// TODO: disallow invalid characters on left of property
|
||||
QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$");
|
||||
regExp.indexIn(leftOfCursor);
|
||||
auto rexExpCapturedTexts = regExp.capturedTexts();
|
||||
auto memberOf = rexExpCapturedTexts[MODULE_INDEX];
|
||||
completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX];
|
||||
bool switchedModule = false;
|
||||
if (memberOf != _completerModule) {
|
||||
_completerModule = memberOf;
|
||||
auto autoCompleteModel = getAutoCompleteModel(memberOf);
|
||||
if (autoCompleteModel == nullptr) {
|
||||
_completer->popup()->hide();
|
||||
return false;
|
||||
}
|
||||
_completer->setModel(autoCompleteModel);
|
||||
_completer->popup()->installEventFilter(this);
|
||||
switchedModule = true;
|
||||
}
|
||||
|
||||
if (switchedModule || completionPrefix != _completer->completionPrefix()) {
|
||||
_completer->setCompletionPrefix(completionPrefix);
|
||||
_completer->popup()->setCurrentIndex(_completer->completionModel()->index(0, 0));
|
||||
}
|
||||
auto cursorRect = _ui->promptTextEdit->cursorRect();
|
||||
cursorRect.setWidth(_completer->popup()->sizeHintForColumn(0) +
|
||||
_completer->popup()->verticalScrollBar()->sizeHint().width());
|
||||
_completer->complete(cursorRect);
|
||||
highlightedCompletion(_completer->popup()->currentIndex());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -321,7 +609,7 @@ void JSConsole::appendMessage(const QString& gutter, const QString& message) {
|
|||
|
||||
void JSConsole::clear() {
|
||||
QLayoutItem* item;
|
||||
while ((item = _ui->logArea->layout()->takeAt(0)) != NULL) {
|
||||
while ((item = _ui->logArea->layout()->takeAt(0)) != nullptr) {
|
||||
delete item->widget();
|
||||
delete item;
|
||||
}
|
||||
|
|
|
@ -12,12 +12,11 @@
|
|||
#ifndef hifi_JSConsole_h
|
||||
#define hifi_JSConsole_h
|
||||
|
||||
#include <QDialog>
|
||||
#include <QEvent>
|
||||
#include <QFutureWatcher>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QSharedPointer>
|
||||
#include <QCompleter>
|
||||
#include <QtCore/QJsonArray>
|
||||
|
||||
#include "ui_console.h"
|
||||
#include "ScriptEngine.h"
|
||||
|
@ -54,12 +53,20 @@ protected slots:
|
|||
void handleError(const QString& message, const QString& scriptName);
|
||||
void commandFinished();
|
||||
|
||||
private slots:
|
||||
void insertCompletion(const QModelIndex& completion);
|
||||
void highlightedCompletion(const QModelIndex& completion);
|
||||
|
||||
private:
|
||||
void appendMessage(const QString& gutter, const QString& message);
|
||||
void setToNextCommandInHistory();
|
||||
void setToPreviousCommandInHistory();
|
||||
void resetCurrentCommandHistory();
|
||||
|
||||
void readAPI();
|
||||
|
||||
QStandardItemModel* getAutoCompleteModel(const QString& memberOf = nullptr);
|
||||
|
||||
QFutureWatcher<QScriptValue> _executeWatcher;
|
||||
Ui::Console* _ui;
|
||||
int _currentCommandInHistory;
|
||||
|
@ -68,6 +75,9 @@ private:
|
|||
QString _rootCommand;
|
||||
ScriptEnginePointer _scriptEngine;
|
||||
static const QString _consoleFileName;
|
||||
QJsonArray _apiDocs;
|
||||
QCompleter* _completer;
|
||||
QString _completerModule {""};
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ const QString FATAL_TEXT = "[FATAL]";
|
|||
const QString SUPPRESS_TEXT = "[SUPPRESS]";
|
||||
const QString UNKNOWN_TEXT = "[UNKNOWN]";
|
||||
|
||||
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLogDialog(parent) {
|
||||
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLogDialog(parent), _windowGeometry("logDialogGeometry", QRect()) {
|
||||
_logger = logger;
|
||||
setWindowTitle("Log");
|
||||
|
||||
|
@ -155,6 +155,11 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog
|
|||
_clearFilterButton->show();
|
||||
connect(_clearFilterButton, &QPushButton::clicked, this, &LogDialog::handleClearFilterButton);
|
||||
handleClearFilterButton();
|
||||
|
||||
auto windowGeometry = _windowGeometry.get();
|
||||
if (windowGeometry.isValid()) {
|
||||
setGeometry(windowGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
void LogDialog::resizeEvent(QResizeEvent* event) {
|
||||
|
@ -173,6 +178,11 @@ void LogDialog::resizeEvent(QResizeEvent* event) {
|
|||
ELEMENT_HEIGHT);
|
||||
}
|
||||
|
||||
void LogDialog::closeEvent(QCloseEvent* event) {
|
||||
BaseLogDialog::closeEvent(event);
|
||||
_windowGeometry.set(geometry());
|
||||
}
|
||||
|
||||
void LogDialog::handleRevealButton() {
|
||||
_logger->locateLog();
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_LogDialog_h
|
||||
|
||||
#include "BaseLogDialog.h"
|
||||
#include <SettingHandle.h>
|
||||
|
||||
class QCheckBox;
|
||||
class QPushButton;
|
||||
|
@ -44,6 +45,8 @@ private slots:
|
|||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
QString getCurrentLog() override;
|
||||
void printLogFile();
|
||||
|
||||
|
@ -62,6 +65,7 @@ private:
|
|||
QString _filterSelection;
|
||||
|
||||
AbstractLoggerInterface* _logger;
|
||||
Setting::Handle<QRect> _windowGeometry;
|
||||
};
|
||||
|
||||
#endif // hifi_LogDialog_h
|
||||
|
|
|
@ -181,6 +181,8 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
|
||||
if (properties["parentID"].isValid()) {
|
||||
setParentID(QUuid(properties["parentID"].toString()));
|
||||
bool success;
|
||||
getParentPointer(success); // call this to hook-up the parent's back-pointers to its child overlays
|
||||
needRenderItemUpdate = true;
|
||||
}
|
||||
if (properties["parentJointIndex"].isValid()) {
|
||||
|
|
|
@ -68,7 +68,7 @@ public:
|
|||
BoxFace& face, glm::vec3& surfaceNormal);
|
||||
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
return findRayIntersection(origin, direction, distance, face, surfaceNormal);
|
||||
}
|
||||
|
||||
|
|
|
@ -266,22 +266,16 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemI
|
|||
}
|
||||
}
|
||||
|
||||
static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
|
||||
void ContextOverlayInterface::openInspectionCertificate() {
|
||||
// lets open the tablet to the inspection certificate QML
|
||||
if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {
|
||||
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
|
||||
_hmdScriptingInterface->openTablet();
|
||||
void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID) {
|
||||
|
||||
setLastInspectedEntity(_currentEntityWithContextOverlay);
|
||||
setLastInspectedEntity(entityID);
|
||||
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (entityProperties.verifyStaticCertificateProperties()) {
|
||||
if (entityProperties.getClientOnly()) {
|
||||
if (entityProperties.verifyStaticCertificateProperties()) {
|
||||
SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer);
|
||||
|
||||
if (entityServer) {
|
||||
|
@ -349,13 +343,30 @@ void ContextOverlayInterface::openInspectionCertificate() {
|
|||
} else {
|
||||
qCWarning(context_overlay) << "Couldn't get Entity Server!";
|
||||
}
|
||||
} else {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
|
||||
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
|
||||
}
|
||||
} else {
|
||||
// We don't currently verify ownership of entities that aren't Avatar Entities,
|
||||
// so they always pass Ownership Verification. It's necessary to emit this signal
|
||||
// so that the Inspection Certificate can continue its information-grabbing process.
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
}
|
||||
} else {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
_challengeOwnershipTimeoutTimer.stop();
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
|
||||
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
|
||||
}
|
||||
}
|
||||
|
||||
static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
|
||||
void ContextOverlayInterface::openInspectionCertificate() {
|
||||
// lets open the tablet to the inspection certificate QML
|
||||
if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {
|
||||
setLastInspectedEntity(_currentEntityWithContextOverlay);
|
||||
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
|
||||
_hmdScriptingInterface->openTablet();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,6 +408,7 @@ void ContextOverlayInterface::startChallengeOwnershipTimer() {
|
|||
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
|
||||
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
|
||||
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
|
||||
});
|
||||
|
||||
_challengeOwnershipTimeoutTimer.start(5000);
|
||||
|
@ -421,7 +433,9 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer
|
|||
|
||||
if (verificationSuccess) {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationSuccess(_lastInspectedEntity);
|
||||
} else {
|
||||
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
|
||||
emit DependencyManager::get<WalletScriptingInterface>()->ownershipVerificationFailed(_lastInspectedEntity);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "ui/overlays/Overlays.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
#include "scripting/SelectionScriptingInterface.h"
|
||||
#include "scripting/WalletScriptingInterface.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
#include "ContextOverlayLogging.h"
|
||||
|
@ -33,12 +34,13 @@
|
|||
/**jsdoc
|
||||
* @namespace ContextOverlay
|
||||
*/
|
||||
class ContextOverlayInterface : public QObject, public Dependency {
|
||||
class ContextOverlayInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QUuid entityWithContextOverlay READ getCurrentEntityWithContextOverlay WRITE setCurrentEntityWithContextOverlay)
|
||||
Q_PROPERTY(bool enabled READ getEnabled WRITE setEnabled)
|
||||
Q_PROPERTY(bool isInMarketplaceInspectionMode READ getIsInMarketplaceInspectionMode WRITE setIsInMarketplaceInspectionMode)
|
||||
|
||||
QSharedPointer<EntityScriptingInterface> _entityScriptingInterface;
|
||||
EntityPropertyFlags _entityPropertyFlags;
|
||||
QSharedPointer<HMDScriptingInterface> _hmdScriptingInterface;
|
||||
|
@ -47,9 +49,7 @@ class ContextOverlayInterface : public QObject, public Dependency {
|
|||
OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID };
|
||||
std::shared_ptr<Image3DOverlay> _contextOverlay { nullptr };
|
||||
public:
|
||||
|
||||
ContextOverlayInterface();
|
||||
|
||||
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
|
||||
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
|
||||
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
|
||||
|
@ -57,6 +57,8 @@ public:
|
|||
bool getEnabled() { return _enabled; }
|
||||
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
|
||||
void setIsInMarketplaceInspectionMode(bool mode) { _isInMarketplaceInspectionMode = mode; }
|
||||
Q_INVOKABLE void requestOwnershipVerification(const QUuid& entityID);
|
||||
EntityPropertyFlags getEntityPropertyFlags() { return _entityPropertyFlags; }
|
||||
|
||||
signals:
|
||||
void contextOverlayClicked(const QUuid& currentEntityWithContextOverlay);
|
||||
|
@ -80,8 +82,7 @@ private:
|
|||
enum {
|
||||
MAX_SELECTION_COUNT = 16
|
||||
};
|
||||
|
||||
bool _verboseLogging { true };
|
||||
bool _verboseLogging{ true };
|
||||
bool _enabled { true };
|
||||
EntityItemID _currentEntityWithContextOverlay{};
|
||||
EntityItemID _lastInspectedEntity{};
|
||||
|
|
|
@ -63,7 +63,7 @@ glm::vec3 Line3DOverlay::getEnd() const {
|
|||
localEnd = getLocalEnd();
|
||||
worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), getScalesWithParent(), success);
|
||||
if (!success) {
|
||||
qDebug() << "Line3DOverlay::getEnd failed";
|
||||
qDebug() << "Line3DOverlay::getEnd failed, parentID = " << getParentID();
|
||||
}
|
||||
return worldEnd;
|
||||
}
|
||||
|
|
|
@ -86,7 +86,8 @@ void ModelOverlay::update(float deltatime) {
|
|||
}
|
||||
if (_visibleDirty) {
|
||||
_visibleDirty = false;
|
||||
_model->setVisibleInScene(getVisible(), scene);
|
||||
// don't show overlays in mirrors
|
||||
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0);
|
||||
}
|
||||
if (_drawInFrontDirty) {
|
||||
_drawInFrontDirty = false;
|
||||
|
@ -120,8 +121,10 @@ void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::Scene
|
|||
}
|
||||
|
||||
void ModelOverlay::setVisible(bool visible) {
|
||||
Overlay::setVisible(visible);
|
||||
_visibleDirty = true;
|
||||
if (visible != getVisible()) {
|
||||
Overlay::setVisible(visible);
|
||||
_visibleDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ModelOverlay::setDrawInFront(bool drawInFront) {
|
||||
|
@ -446,12 +449,12 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
|
||||
QString subMeshNameTemp;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, subMeshNameTemp);
|
||||
QVariantMap extraInfo;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
}
|
||||
|
||||
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) {
|
||||
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QString& extraInfo) override;
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) override;
|
||||
|
||||
virtual ModelOverlay* createClone() const override;
|
||||
|
||||
|
@ -106,7 +106,7 @@ private:
|
|||
bool _jointMappingCompleted { false };
|
||||
QVector<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints
|
||||
|
||||
bool _visibleDirty { false };
|
||||
bool _visibleDirty { true };
|
||||
bool _drawInFrontDirty { false };
|
||||
bool _drawInHUDDirty { false };
|
||||
|
||||
|
|
|
@ -520,7 +520,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
|
|||
float thisDistance;
|
||||
BoxFace thisFace;
|
||||
glm::vec3 thisSurfaceNormal;
|
||||
QString thisExtraInfo;
|
||||
QVariantMap thisExtraInfo;
|
||||
if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance,
|
||||
thisFace, thisSurfaceNormal, thisExtraInfo)) {
|
||||
bool isDrawInFront = thisOverlay->getDrawInFront();
|
||||
|
@ -578,7 +578,7 @@ QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine,
|
|||
obj.setProperty("face", faceName);
|
||||
auto intersection = vec3toScriptValue(engine, value.intersection);
|
||||
obj.setProperty("intersection", intersection);
|
||||
obj.setProperty("extraInfo", value.extraInfo);
|
||||
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -612,7 +612,7 @@ void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar
|
|||
value.intersection = newIntersection;
|
||||
}
|
||||
}
|
||||
value.extraInfo = object["extraInfo"].toString();
|
||||
value.extraInfo = object["extraInfo"].toMap();
|
||||
}
|
||||
|
||||
bool Overlays::isLoaded(OverlayID id) {
|
||||
|
|
|
@ -51,6 +51,7 @@ const OverlayID UNKNOWN_OVERLAY_ID = OverlayID();
|
|||
* @property {number} distance - The distance from the {@link PickRay} origin to the intersection point.
|
||||
* @property {Vec3} surfaceNormal - The normal of the overlay surface at the intersection point.
|
||||
* @property {Vec3} intersection - The position of the intersection point.
|
||||
* @property {Object} extraInfo Additional intersection details, if available.
|
||||
*/
|
||||
class RayToOverlayIntersectionResult {
|
||||
public:
|
||||
|
@ -60,7 +61,7 @@ public:
|
|||
BoxFace face;
|
||||
glm::vec3 surfaceNormal;
|
||||
glm::vec3 intersection;
|
||||
QString extraInfo;
|
||||
QVariantMap extraInfo;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -44,6 +44,13 @@ namespace render {
|
|||
} else {
|
||||
builder.withViewSpace();
|
||||
}
|
||||
|
||||
if (!overlay->getVisible()) {
|
||||
builder.withInvisible();
|
||||
}
|
||||
|
||||
builder.withTagBits(render::ItemKey::TAG_BITS_0); // Only draw overlays in main view
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay) {
|
||||
|
|
|
@ -72,6 +72,14 @@ Web3DOverlay::Web3DOverlay() {
|
|||
connect(this, &Web3DOverlay::requestWebSurface, this, &Web3DOverlay::buildWebSurface);
|
||||
connect(this, &Web3DOverlay::releaseWebSurface, this, &Web3DOverlay::destroyWebSurface);
|
||||
connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface);
|
||||
|
||||
//need to be intialized before Tablet 1st open
|
||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(_url);
|
||||
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
_webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
_webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get<AddressManager>().data());
|
||||
}
|
||||
|
||||
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
||||
|
|
|
@ -880,25 +880,6 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
|
|||
return _relativePoses;
|
||||
}
|
||||
|
||||
AnimPose AnimInverseKinematics::applyHipsOffset() const {
|
||||
glm::vec3 hipsOffset = _hipsOffset;
|
||||
AnimPose relHipsPose = _relativePoses[_hipsIndex];
|
||||
float offsetLength = glm::length(hipsOffset);
|
||||
const float MIN_HIPS_OFFSET_LENGTH = 0.03f;
|
||||
if (offsetLength > MIN_HIPS_OFFSET_LENGTH) {
|
||||
float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength);
|
||||
glm::vec3 scaledHipsOffset = scaleFactor * hipsOffset;
|
||||
if (_hipsParentIndex == -1) {
|
||||
relHipsPose.trans() = _relativePoses[_hipsIndex].trans() + scaledHipsOffset;
|
||||
} else {
|
||||
AnimPose absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses);
|
||||
absHipsPose.trans() += scaledHipsOffset;
|
||||
relHipsPose = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose;
|
||||
}
|
||||
}
|
||||
return relHipsPose;
|
||||
}
|
||||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
// allows solutionSource to be overridden by an animVar
|
||||
|
@ -996,27 +977,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
|
||||
_relativePoses[_hipsIndex] = parentAbsPose.inverse() * absPose;
|
||||
_relativePoses[_hipsIndex].scale() = glm::vec3(1.0f);
|
||||
_hipsOffset = Vectors::ZERO;
|
||||
|
||||
} else if (_hipsIndex >= 0) {
|
||||
|
||||
// if there is no hips target, shift hips according to the _hipsOffset from the previous frame
|
||||
AnimPose relHipsPose = applyHipsOffset();
|
||||
|
||||
// determine if we should begin interpolating the hips.
|
||||
for (size_t i = 0; i < targets.size(); i++) {
|
||||
if (_prevJointChainInfoVec[i].target.getIndex() == _hipsIndex) {
|
||||
if (_prevJointChainInfoVec[i].timer > 0.0f) {
|
||||
// smoothly lerp in hipsOffset
|
||||
float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[i].timer) / JOINT_CHAIN_INTERP_TIME;
|
||||
AnimPose prevRelHipsPose(_prevJointChainInfoVec[i].jointInfoVec[0].rot, _prevJointChainInfoVec[i].jointInfoVec[0].trans);
|
||||
::blend(1, &prevRelHipsPose, &relHipsPose, alpha, &relHipsPose);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_relativePoses[_hipsIndex] = relHipsPose;
|
||||
}
|
||||
|
||||
// if there is an active jointChainInfo for the hips store the post shifted hips into it.
|
||||
|
@ -1084,11 +1044,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
|
||||
solve(context, targets, dt, jointChainInfoVec);
|
||||
}
|
||||
|
||||
if (_hipsTargetIndex < 0) {
|
||||
PROFILE_RANGE_EX(simulation_animation, "ik/measureHipsOffset", 0xffff00ff, 0);
|
||||
_hipsOffset = computeHipsOffset(targets, underPoses, dt, _hipsOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.getEnableDebugDrawIKConstraints()) {
|
||||
|
@ -1099,69 +1054,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
return _relativePoses;
|
||||
}
|
||||
|
||||
glm::vec3 AnimInverseKinematics::computeHipsOffset(const std::vector<IKTarget>& targets, const AnimPoseVec& underPoses, float dt, glm::vec3 prevHipsOffset) const {
|
||||
|
||||
// measure new _hipsOffset for next frame
|
||||
// by looking for discrepancies between where a targeted endEffector is
|
||||
// and where it wants to be (after IK solutions are done)
|
||||
glm::vec3 hipsOffset = prevHipsOffset;
|
||||
glm::vec3 newHipsOffset = Vectors::ZERO;
|
||||
for (auto& target: targets) {
|
||||
int targetIndex = target.getIndex();
|
||||
if (targetIndex == _headIndex && _headIndex != -1) {
|
||||
// special handling for headTarget
|
||||
if (target.getType() == IKTarget::Type::RotationOnly) {
|
||||
// we want to shift the hips to bring the underPose closer
|
||||
// to where the head happens to be (overpose)
|
||||
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans();
|
||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
||||
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
|
||||
} else if (target.getType() == IKTarget::Type::HmdHead) {
|
||||
// we want to shift the hips to bring the head to its designated position
|
||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans();
|
||||
hipsOffset += target.getTranslation() - actual;
|
||||
// and ignore all other targets
|
||||
newHipsOffset = hipsOffset;
|
||||
break;
|
||||
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
||||
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
|
||||
glm::vec3 targetPosition = target.getTranslation();
|
||||
newHipsOffset += targetPosition - actualPosition;
|
||||
|
||||
// Add downward pressure on the hips
|
||||
const float PRESSURE_SCALE_FACTOR = 0.95f;
|
||||
const float PRESSURE_TRANSLATION_OFFSET = 1.0f;
|
||||
newHipsOffset *= PRESSURE_SCALE_FACTOR;
|
||||
newHipsOffset -= PRESSURE_TRANSLATION_OFFSET;
|
||||
}
|
||||
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
||||
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
|
||||
glm::vec3 targetPosition = target.getTranslation();
|
||||
newHipsOffset += targetPosition - actualPosition;
|
||||
}
|
||||
}
|
||||
|
||||
// smooth transitions by relaxing hipsOffset toward the new value
|
||||
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f;
|
||||
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
|
||||
hipsOffset += (newHipsOffset - hipsOffset) * tau;
|
||||
|
||||
// clamp the hips offset
|
||||
float hipsOffsetLength = glm::length(hipsOffset);
|
||||
if (hipsOffsetLength > _maxHipsOffsetLength) {
|
||||
hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength;
|
||||
}
|
||||
|
||||
return hipsOffset;
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) {
|
||||
// manually adjust scale here
|
||||
const float METERS_TO_CENTIMETERS = 100.0f;
|
||||
_maxHipsOffsetLength = METERS_TO_CENTIMETERS * maxLength;
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::clearIKJointLimitHistory() {
|
||||
for (auto& pair : _constraints) {
|
||||
pair.second->clearHistory();
|
||||
|
|