- error messages

- style changes
This commit is contained in:
Thijs Wenker 2019-01-03 22:00:22 +01:00
parent 37d00d9519
commit ecc578c2dd
12 changed files with 242 additions and 83 deletions

View file

@ -1,15 +1,7 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQml.Models 2.1
import QtGraphicalEffects 1.0
import "../controlsUit" 1.0 as HifiControls
import "../stylesUit" 1.0
import "../windows" as Windows
import "../controls" 1.0
import "../dialogs"
import "avatarPackager" 1.0
import "avatarapp" 1.0 as AvatarApp
Windows.ScrollingWindow {
id: root

View file

@ -3,9 +3,9 @@ import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQml.Models 2.1
import QtGraphicalEffects 1.0
import Hifi.AvatarPackager.AvatarProjectStatus 1.0
import "../../controlsUit" 1.0 as HifiControls
import "../../stylesUit" 1.0
import "../../windows" as Windows
import "../../controls" 1.0
import "../../dialogs"
import "../avatarapp" 1.0 as AvatarApp
@ -16,9 +16,6 @@ Item {
property alias desktopObject: avatarPackager.desktopObject
id: windowContent
// height: pane ? pane.height : parent.width
// width: pane ? pane.width : parent.width
MouseArea {
anchors.fill: parent
@ -75,6 +72,37 @@ Item {
}
}
InfoBox {
id: errorPopup
property string errorMessage;
boxWidth: 380
boxHeight: 293
content: RalewayRegular {
id: bodyMessage
anchors.fill: parent
anchors.bottomMargin: 10
anchors.leftMargin: 29
anchors.rightMargin: 29
size: 20
color: "white"
text: errorPopup.errorMessage
width: parent.width
wrapMode: Text.WordWrap
}
function show(title, message) {
errorPopup.title = title;
errorMessage = message;
errorPopup.open();
}
}
Rectangle {
id: modalOverlay
anchors.fill: parent
@ -85,8 +113,8 @@ Item {
// This mouse area captures the cursor events while the modalOverlay is active
MouseArea {
anchors.fill: parent
propagateComposedEvents: false;
hoverEnabled: true;
propagateComposedEvents: false
hoverEnabled: true
}
}
@ -133,12 +161,58 @@ Item {
property var desktopObject: desktop
function openProject(path) {
let project = AvatarPackagerCore.openAvatarProject(path);
if (project) {
avatarProject.reset();
avatarPackager.state = AvatarPackagerState.project;
let status = AvatarPackagerCore.openAvatarProject(path);
if (status !== AvatarProjectStatus.SUCCESS) {
displayErrorMessage(status);
return status;
}
return project;
avatarProject.reset();
avatarPackager.state = AvatarPackagerState.project;
return status;
}
function displayErrorMessage(status) {
if (status === AvatarProjectStatus.SUCCESS) {
return;
}
switch (status) {
case AvatarProjectStatus.ERROR_CREATE_PROJECT_NAME:
errorPopup.show("Project Folder Already Exists", "A folder with that name already exists at that location. Please choose a different project name or location.");
break;
case AvatarProjectStatus.ERROR_CREATE_CREATING_DIRECTORIES:
errorPopup.show("Project Folders Creation Error", "There was a problem during the creation of the Avatar Project directories. Please select a project location with write permissions.");
break;
case AvatarProjectStatus.ERROR_CREATE_FIND_MODEL:
errorPopup.show("Cannot Find Model File", "There was a problem while trying to find the specified model file. Please verify if it exist at the specified location.");
break;
case AvatarProjectStatus.ERROR_CREATE_OPEN_MODEL:
errorPopup.show("Cannot Open Model File", "There was a problem while trying to open the specified model file. Please verify if you have read permissions at the specified location.");
break;
case AvatarProjectStatus.ERROR_CREATE_READ_MODEL:
errorPopup.show("Error Read Model File", "There was a problem while trying to read the specified model file. Please verify if the model file is supported by High Fidelity.");
break;
case AvatarProjectStatus.ERROR_CREATE_WRITE_FST:
errorPopup.show("Error Writing Project File", "There was a problem while trying to write the FST file.");
break;
case AvatarProjectStatus.ERROR_OPEN_INVALID_FILE_TYPE:
errorPopup.show("Invalid Project Path", "The avatar packager can only open FST files.");
break;
case AvatarProjectStatus.ERROR_OPEN_PROJECT_FOLDER:
errorPopup.show("Project Missing", "Project folder cannot be found. Please locate the folder and copy/move it to its original location.");
break;
case AvatarProjectStatus.ERROR_OPEN_FIND_FST:
errorPopup.show("File Missing", "We cannot find the project file (avatar.fst) in the folder. Please locate it and move to the project folder.");
break;
case AvatarProjectStatus.ERROR_OPEN_OPEN_FST:
errorPopup.show("File Read Error", "We cannot read the project file (avatar.fst). Please make sure that it is not in use by another program.");
break;
case AvatarProjectStatus.ERROR_OPEN_FIND_MODEL:
errorPopup.show("File Missing", "We cannot find the avatar model file (.fbx) in the folder. Please locate it and move to the project folder.");
break;
default:
errorPopup.show("Error Message Missing", "Error message missing for status " + status);
}
}
function openDocs() {
@ -233,10 +307,8 @@ Item {
browser.selectedFile.connect(function(fileUrl) {
let fstFilePath = fileDialogHelper.urlToPath(fileUrl);
let currentAvatarProject = avatarPackager.openProject(fstFilePath);
if (currentAvatarProject) {
avatarPackager.showModalOverlay = false;
}
avatarPackager.showModalOverlay = false;
avatarPackager.openProject(fstFilePath);
});
}
}
@ -253,12 +325,20 @@ Item {
RalewayRegular {
size: 20
color: "white"
text: qsTr("Use a custom avatar to express your identity")
text: "Use a custom avatar of your choice."
width: parent.width
wrapMode: Text.WordWrap
}
RalewayRegular {
size: 20
color: "white"
text: qsTr("To learn more about using this tool, visit our docs")
text: "<a href='javascript:void'>Visit our docs</a> to learn more about using the packager."
linkColor: "#00B4EF"
width: parent.width
wrapMode: Text.WordWrap
onLinkActivated: {
avatarPackager.openDocs();
}
}
}

View file

@ -38,5 +38,4 @@ Rectangle {
width: parent.width
}
}
}
}

View file

@ -93,7 +93,6 @@ ShadowRectangle {
z: 200
onFocusChanged: {
if (titleArea.state === "renaming" && !titleArea.focus) {
//titleArea.state = "";
accepted();
}
}
@ -104,11 +103,7 @@ ShadowRectangle {
}
onAccepted: {
if (acceptableInput) {
//AvatarPackagerCore.renameProject(text);
console.warn(text);
AvatarPackagerCore.currentAvatarProject.name = text;
console.warn(AvatarPackagerCore.currentAvatarProject.name);
}
titleArea.state = "";
}

View file

@ -52,7 +52,7 @@ Item {
colorScheme: root.colorScheme
width: 133
height: 40
onClicked: function() {
onClicked: {
uploadNew();
}
}
@ -70,7 +70,7 @@ Item {
colorScheme: root.colorScheme
width: 134
height: 40
onClicked: function() {
onClicked: {
showConfirmUploadPopup(uploadNew, uploadUpdate);
}
}
@ -90,7 +90,7 @@ Item {
colorScheme: root.colorScheme
width: 134
height: 40
onClicked: function() {
onClicked: {
showConfirmUploadPopup(uploadNew, uploadUpdate);
}
}

View file

@ -29,10 +29,11 @@ Item {
function stateChangedCallback(newState) {
if (newState >= 4) {
root.uploader.stateChanged.disconnect(stateChangedCallback)
root.uploader.stateChanged.disconnect(stateChangedCallback);
backToProjectTimer.start();
}
}
onVisibleChanged: {
if (visible) {
root.uploader.stateChanged.connect(stateChangedCallback);
@ -120,6 +121,7 @@ Item {
source: "../../../icons/checkmark-stroke.svg"
}
}
Item {
id: statusRows
@ -169,6 +171,7 @@ Item {
color: "white"
text: "We couldn't upload your avatar at this time. Please try again later."
}
AvatarPackagerFooter {
id: errorFooter

View file

@ -2,6 +2,8 @@ import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Hifi.AvatarPackager.AvatarProjectStatus 1.0
import "../../controlsUit" 1.0 as HifiControls
import "../../stylesUit" 1.0
@ -24,8 +26,9 @@ Item {
text: qsTr("Create")
enabled: false
onClicked: {
if (!AvatarPackagerCore.createAvatarProject(projectLocation.text, name.text, avatarModel.text, textureFolder.text)) {
Window.alert('Failed to create project');
let status = AvatarPackagerCore.createAvatarProject(projectLocation.text, name.text, avatarModel.text, textureFolder.text);
if (status !== AvatarProjectStatus.SUCCESS) {
avatarPackager.displayErrorMessage(status);
return;
}
avatarPackager.state = AvatarPackagerState.project;

View file

@ -5,9 +5,9 @@ import controlsUit 1.0 as HifiControlsUit
import "../../controls" as HifiControls
Rectangle {
id: root;
visible: false;
color: Qt.rgba(.34, .34, .34, 0.6);
id: root
visible: false
color: Qt.rgba(.34, .34, .34, 0.6)
z: 999;
anchors.fill: parent
@ -17,6 +17,9 @@ Rectangle {
property bool closeOnClickOutside: false;
property alias boxWidth: mainContainer.width
property alias boxHeight: mainContainer.height
function open() {
visible = true;
}
@ -44,15 +47,15 @@ Rectangle {
}
Rectangle {
id: mainContainer;
id: mainContainer
width: Math.max(parent.width * 0.8, 400)
height: parent.height * 0.6
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
hoverEnabled: true;
anchors.fill: parent
propagateComposedEvents: false
hoverEnabled: true
onClicked: function(ev) {
ev.accepted = true;
}
@ -107,6 +110,5 @@ Rectangle {
colorScheme: hifi.colorSchemes.dark;
}
}
}
}

View file

@ -22,7 +22,6 @@
#include <avatar/MarketplaceItemUploader.h>
#include <mutex>
#include "scripting/HMDScriptingInterface.h"
#include "ui/TabletScriptingInterface.h"
std::once_flag setupQMLTypesFlag;
@ -32,6 +31,14 @@ AvatarPackager::AvatarPackager() {
qmlRegisterType<MarketplaceItemUploader>();
qRegisterMetaType<AvatarPackager*>();
qRegisterMetaType<AvatarProject*>();
qRegisterMetaType<AvatarProjectStatus::AvatarProjectStatus>();
qmlRegisterUncreatableMetaObject(
AvatarProjectStatus::staticMetaObject,
"Hifi.AvatarPackager.AvatarProjectStatus",
1, 0,
"AvatarProjectStatus",
"Error: only enums"
);
});
recentProjectsFromVariantList(_recentProjectsSetting.get());
@ -41,39 +48,39 @@ AvatarPackager::AvatarPackager() {
}
bool AvatarPackager::open() {
static const QUrl url{ "hifi/AvatarPackagerWindow.qml" };
const auto packageModelDialogCreated = [=](QQmlContext* context, QObject* newObject) {
context->setContextProperty("AvatarPackagerCore", this);
};
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet(SYSTEM_TABLET));
auto hmd = DependencyManager::get<HMDScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(DependencyManager::get<TabletScriptingInterface>()->getTablet(SYSTEM_TABLET));
if (tablet->getToolbarMode()) {
static const QUrl url{ "hifi/AvatarPackagerWindow.qml" };
DependencyManager::get<OffscreenUi>()->show(url, "AvatarPackager", packageModelDialogCreated);
} else {
static const QUrl url("hifi/tablet/AvatarPackager.qml");
if (!tablet->isPathLoaded(url)) {
tablet->getTabletSurface()->getSurfaceContext()->setContextProperty("AvatarPackagerCore", this);
tablet->pushOntoStack(url);
}
return true;
}
return true;
static const QUrl url{ "hifi/tablet/AvatarPackager.qml" };
if (!tablet->isPathLoaded(url)) {
tablet->getTabletSurface()->getSurfaceContext()->setContextProperty("AvatarPackagerCore", this);
tablet->pushOntoStack(url);
return true;
}
return false;
}
void AvatarPackager::addCurrentProjectToRecentProjects() {
const int MAX_RECENT_PROJECTS = 5;
const QString& fstPath = _currentAvatarProject->getFSTPath();
auto removeProjects = QVector<RecentAvatarProject>();
for (auto project : _recentProjects) {
for (const auto& project : _recentProjects) {
if (project.getProjectFSTPath() == fstPath) {
removeProjects.append(project);
}
}
for (const auto removeProject : removeProjects) {
for (const auto& removeProject : removeProjects) {
_recentProjects.removeOne(removeProject);
}
@ -110,14 +117,19 @@ void AvatarPackager::recentProjectsFromVariantList(QVariantList projectsVariant)
}
}
AvatarProject* AvatarPackager::openAvatarProject(const QString& avatarProjectFSTPath) {
setAvatarProject(AvatarProject::openAvatarProject(avatarProjectFSTPath));
return _currentAvatarProject;
AvatarProjectStatus::AvatarProjectStatus AvatarPackager::openAvatarProject(const QString& avatarProjectFSTPath) {
AvatarProjectStatus::AvatarProjectStatus status;
setAvatarProject(AvatarProject::openAvatarProject(avatarProjectFSTPath, status));
return status;
}
AvatarProject* AvatarPackager::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder) {
setAvatarProject(AvatarProject::createAvatarProject(projectsFolder, avatarProjectName, avatarModelPath, textureFolder));
return _currentAvatarProject;
AvatarProjectStatus::AvatarProjectStatus AvatarPackager::createAvatarProject(const QString& projectsFolder,
const QString& avatarProjectName,
const QString& avatarModelPath,
const QString& textureFolder) {
AvatarProjectStatus::AvatarProjectStatus status;
setAvatarProject(AvatarProject::createAvatarProject(projectsFolder, avatarProjectName, avatarModelPath, textureFolder, status));
return status;
}
void AvatarPackager::setAvatarProject(AvatarProject* avatarProject) {

View file

@ -63,8 +63,12 @@ public:
AvatarPackager();
bool open();
Q_INVOKABLE AvatarProject* createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder);
Q_INVOKABLE AvatarProject* openAvatarProject(const QString& avatarProjectFSTPath);
Q_INVOKABLE AvatarProjectStatus::AvatarProjectStatus createAvatarProject(const QString& projectsFolder,
const QString& avatarProjectName,
const QString& avatarModelPath,
const QString& textureFolder);
Q_INVOKABLE AvatarProjectStatus::AvatarProjectStatus openAvatarProject(const QString& avatarProjectFSTPath);
Q_INVOKABLE bool isValidNewProjectName(const QString& projectPath, const QString& projectName) { return AvatarProject::isValidNewProjectName(projectPath, projectName); }
signals:

View file

@ -22,55 +22,98 @@
#include <ui/TabletScriptingInterface.h>
#include "scripting/HMDScriptingInterface.h"
AvatarProject* AvatarProject::openAvatarProject(const QString& path) {
AvatarProject* AvatarProject::openAvatarProject(const QString& path, AvatarProjectStatus::AvatarProjectStatus& status) {
status = AvatarProjectStatus::NONE;
if (!path.toLower().endsWith(".fst")) {
status = AvatarProjectStatus::ERROR_OPEN_INVALID_FILE_TYPE;
return nullptr;
}
QFile file{ path };
QFileInfo fstFileInfo{ path };
if (!fstFileInfo.absoluteDir().exists()) {
status = AvatarProjectStatus::ERROR_OPEN_PROJECT_FOLDER;
return nullptr;
}
if (!fstFileInfo.exists()) {
status = AvatarProjectStatus::ERROR_OPEN_FIND_FST;
return nullptr;
}
QFile file{ fstFileInfo.filePath() };
if (!file.open(QIODevice::ReadOnly)) {
status = AvatarProjectStatus::ERROR_OPEN_OPEN_FST;
return nullptr;
}
const auto project = new AvatarProject(path, file.readAll());
QFileInfo fbxFileInfo{ project->getFBXPath() };
if (!fbxFileInfo.exists()) {
project->deleteLater();
status = AvatarProjectStatus::ERROR_OPEN_FIND_MODEL;
return nullptr;
}
QQmlEngine::setObjectOwnership(project, QQmlEngine::CppOwnership);
status = AvatarProjectStatus::SUCCESS;
return project;
}
AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder) {
AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName,
const QString& avatarModelPath, const QString& textureFolder,
AvatarProjectStatus::AvatarProjectStatus& status) {
status = AvatarProjectStatus::NONE;
if (!isValidNewProjectName(projectsFolder, avatarProjectName)) {
status = AvatarProjectStatus::ERROR_CREATE_PROJECT_NAME;
return nullptr;
}
QDir projectDir(projectsFolder + "/" + avatarProjectName);
if (!projectDir.mkpath(".")) {
status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES;
return nullptr;
}
QDir projectTexturesDir(projectDir.path() + "/textures");
if (!projectTexturesDir.mkpath(".")) {
status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES;
return nullptr;
}
QDir projectScriptsDir(projectDir.path() + "/scripts");
if (!projectScriptsDir.mkpath(".")) {
status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES;
return nullptr;
}
const auto fileName = QFileInfo(avatarModelPath).fileName();
const auto newModelPath = projectDir.absoluteFilePath(fileName);
const auto newFSTPath = projectDir.absoluteFilePath("avatar.fst");
QFile::copy(avatarModelPath, newModelPath);
QFileInfo fbxInfo(newModelPath);
QFile fbx(fbxInfo.filePath());
if (!fbxInfo.exists() || !fbxInfo.isFile() || !fbx.open(QIODevice::ReadOnly)) {
// TODO: Can't open model FBX (throw error here)
QFileInfo fbxInfo{ newModelPath };
if (!fbxInfo.exists() || !fbxInfo.isFile()) {
status = AvatarProjectStatus::ERROR_CREATE_FIND_MODEL;
return nullptr;
}
QFile fbx{ fbxInfo.filePath() };
if (!fbx.open(QIODevice::ReadOnly)) {
status = AvatarProjectStatus::ERROR_CREATE_OPEN_MODEL;
return nullptr;
}
std::shared_ptr<hfm::Model> hfmModel;
try {
qDebug() << "Reading FBX file : " << fbxInfo.filePath();
const QByteArray fbxContents = fbx.readAll();
hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), fbxInfo.filePath());
} catch (const QString& error) {
qDebug() << "Error reading: " << error;
Q_UNUSED(error)
status = AvatarProjectStatus::ERROR_CREATE_READ_MODEL;
return nullptr;
}
QStringList textures{};
@ -111,9 +154,11 @@ AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder,
fst->setName(avatarProjectName);
if (!fst->write()) {
status = AvatarProjectStatus::ERROR_CREATE_WRITE_FST;
return nullptr;
}
status = AvatarProjectStatus::SUCCESS;
return new AvatarProject(fst);
}
@ -157,7 +202,7 @@ AvatarProject::AvatarProject(FST* fst) {
_projectPath = fileInfo.absoluteDir().absolutePath();
}
void AvatarProject::appendDirectory(QString prefix, QDir dir) {
void AvatarProject::appendDirectory(const QString& prefix, const QDir& dir) {
constexpr auto flags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden;
for (auto& entry : dir.entryInfoList({}, flags)) {
if (entry.isFile()) {

View file

@ -22,6 +22,27 @@
#include <QVariantHash>
#include <QStandardPaths>
namespace AvatarProjectStatus {
Q_NAMESPACE
enum AvatarProjectStatus {
NONE,
SUCCESS,
ERROR_CREATE_PROJECT_NAME,
ERROR_CREATE_CREATING_DIRECTORIES,
ERROR_CREATE_FIND_MODEL,
ERROR_CREATE_OPEN_MODEL,
ERROR_CREATE_READ_MODEL,
ERROR_CREATE_WRITE_FST,
ERROR_OPEN_INVALID_FILE_TYPE,
ERROR_OPEN_PROJECT_FOLDER,
ERROR_OPEN_FIND_FST,
ERROR_OPEN_OPEN_FST,
ERROR_OPEN_FIND_MODEL
};
Q_ENUM_NS(AvatarProjectStatus)
}
class AvatarProject : public QObject {
Q_OBJECT
Q_PROPERTY(FST* fst READ getFST CONSTANT)
@ -48,16 +69,19 @@ public:
}
Q_INVOKABLE QString getProjectPath() const { return _projectPath; }
Q_INVOKABLE QString getFSTPath() const { return _fst->getPath(); }
Q_INVOKABLE QString getFBXPath() const { return _fst->getModelPath(); }
Q_INVOKABLE QString getFBXPath() const {
return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath()));
}
/**
* returns the AvatarProject or a nullptr on failure.
*/
static AvatarProject* openAvatarProject(const QString& path);
static AvatarProject* openAvatarProject(const QString& path, AvatarProjectStatus::AvatarProjectStatus& status);
static AvatarProject* createAvatarProject(const QString& projectsFolder,
const QString& avatarProjectName,
const QString& avatarModelPath,
const QString& textureFolder);
const QString& textureFolder,
AvatarProjectStatus::AvatarProjectStatus& status);
static bool isValidNewProjectName(const QString& projectPath, const QString& projectName);
@ -78,7 +102,7 @@ private:
FST* getFST() { return _fst; }
void refreshProjectFiles();
void appendDirectory(QString prefix, QDir dir);
void appendDirectory(const QString& prefix, const QDir& dir);
QStringList getScriptPaths(const QDir& scriptsDir);
FST* _fst;