QmlDesigner: Update selected imported node

Add context menu item for updating selected 3D node. If selected node
is a component created by import, that import is updated. Otherwise
if the open document itself is an imported component, update that
import. In the latter case, preselect the source file relevant for
the selected node.

Fixes: QDS-3738
Change-Id: Id678288893f1700648d084ba92df40844d2af0b5
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2021-02-23 13:02:31 +02:00
parent f51387fb8e
commit 69087a2c4b
13 changed files with 262 additions and 34 deletions

View File

@@ -90,6 +90,7 @@ const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen";
const char editAnnotationCommandId[] = "EditAnnotation";
const char openSignalDialogCommandId[] = "OpenSignalDialog";
const char update3DAssetCommandId[] = "Update3DAsset";
const char selectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Selection");
const char flowConnectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Connect");
@@ -131,6 +132,7 @@ const char moveToComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextM
const char editAnnotationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotation");
const char openSignalDialogDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Open Signal Dialog");
const char update3DAssetDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Update 3D Asset");
const char setIdDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Set Id");

View File

@@ -1412,6 +1412,17 @@ void DesignerActionManager::createDefaultDesignerActions()
66,
&openSignalDialog,
&singleSelectionAndHasSlotTrigger));
addDesignerAction(new ModelNodeContextMenuAction(
update3DAssetCommandId,
update3DAssetDisplayName,
{},
rootCategory,
QKeySequence(),
priorityGenericToolBar,
&updateImported3DAsset,
&selectionIsImported3DAsset,
&selectionIsImported3DAsset));
}
void DesignerActionManager::createDefaultAddResourceHandler()

View File

@@ -31,6 +31,7 @@
#include <bindingproperty.h>
#include <nodeproperty.h>
#include <qmldesignerplugin.h>
#include <qmldesignerconstants.h>
namespace QmlDesigner {
@@ -99,6 +100,21 @@ bool selectionIsComponent(const SelectionContext &selectionState)
&& selectionState.currentSingleSelectedNode().isComponent();
}
bool selectionIsImported3DAsset(const SelectionContext &selectionState)
{
ModelNode node = selectionState.currentSingleSelectedNode();
if (selectionState.view() && node.isValid() && node.hasMetaInfo()) {
QString fileName = node.metaInfo().componentFileName(); // absolute path
if (fileName.isEmpty()) {
// Node is not a file component, so we have to check if the current doc itself is
fileName = node.model()->fileUrl().toLocalFile();
}
if (fileName.contains(Constants::QUICK_3D_ASSETS_FOLDER))
return true;
}
return false;
}
} //SelectionStateFunctors
} //QmlDesigner

View File

@@ -109,6 +109,7 @@ bool selectionHasSameParent(const SelectionContext &selectionState);
bool selectionIsComponent(const SelectionContext &selectionState);
bool singleSelectionItemIsAnchored(const SelectionContext &selectionState);
bool singleSelectionItemIsNotAnchored(const SelectionContext &selectionState);
bool selectionIsImported3DAsset(const SelectionContext &selectionState);
} // namespace SelectionStateFunctors

View File

@@ -1581,6 +1581,15 @@ void openSignalDialog(const SelectionContext &selectionContext)
SignalList::showWidget(selectionContext.currentSingleSelectedNode());
}
void updateImported3DAsset(const SelectionContext &selectionContext)
{
if (selectionContext.view()) {
selectionContext.view()->emitCustomNotification(
"UpdateImported3DAsset", {selectionContext.currentSingleSelectedNode()});
}
}
} // namespace ModelNodeOperations
} //QmlDesigner

View File

@@ -89,6 +89,7 @@ void removeGroup(const SelectionContext &selectionContext);
void editAnnotation(const SelectionContext &selectionContext);
void openSignalDialog(const SelectionContext &selectionContext);
void updateImported3DAsset(const SelectionContext &selectionContext);
// ModelNodePreviewImageOperations
QVariant previewImageDataForGenericNode(const ModelNode &modelNode);

View File

@@ -28,26 +28,33 @@
#include "qmldesignerplugin.h"
#include "qmldesignerconstants.h"
#include "model.h"
#include "nodemetainfo.h"
#include "variantproperty.h"
#include "utils/outputformatter.h"
#include "theme.h"
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <coreplugin/icore.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qtimer.h>
#include <QtCore/qjsonarray.h>
#include <QtWidgets/qpushbutton.h>
#include <QtWidgets/qgridlayout.h>
#include <QtWidgets/qlabel.h>
#include <QtWidgets/qcheckbox.h>
#include <QtWidgets/qspinbox.h>
#include <QtWidgets/qscrollbar.h>
#include <QtWidgets/qtabbar.h>
#include <QtWidgets/qscrollarea.h>
#include <QFileInfo>
#include <QDir>
#include <QLoggingCategory>
#include <QTimer>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QPushButton>
#include <QGridLayout>
#include <QLabel>
#include <QCheckBox>
#include <QSpinBox>
#include <QScrollBar>
#include <QTabBar>
#include <QScrollArea>
#include <QMessageBox>
#include <QFileDialog>
namespace QmlDesigner {
@@ -70,14 +77,15 @@ static const int rowHeight = 26;
}
ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &importFiles,
const QString &defaulTargetDirectory,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts,
QWidget *parent) :
QDialog(parent)
ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(
const QStringList &importFiles, const QString &defaulTargetDirectory,
const QVariantMap &supportedExts, const QVariantMap &supportedOpts,
const QJsonObject &defaultOpts, const QSet<QString> &preselectedFilesForOverwrite,
QWidget *parent)
: QDialog(parent)
, ui(new Ui::ItemLibraryAssetImportDialog)
, m_importer(this)
, m_preselectedFilesForOverwrite(preselectedFilesForOverwrite)
{
setModal(true);
ui->setupUi(this);
@@ -172,6 +180,16 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog(const QStringList &im
while (optIt != supportedOpts.constEnd()) {
QJsonObject options = QJsonObject::fromVariantMap(qvariant_cast<QVariantMap>(optIt.value()));
m_importOptions << options.value("options").toObject();
auto it = defaultOpts.constBegin();
while (it != defaultOpts.constEnd()) {
if (m_importOptions.last().contains(it.key())) {
QJsonObject optObj = m_importOptions.last()[it.key()].toObject();
QJsonValue value(it.value()["value"]);
optObj.insert("value", value);
m_importOptions.last().insert(it.key(), optObj);
}
++it;
}
groups << options.value("groups").toObject();
const auto &exts = optIt.key().split(':');
for (const auto &ext : exts)
@@ -252,6 +270,125 @@ ItemLibraryAssetImportDialog::~ItemLibraryAssetImportDialog()
delete ui;
}
void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts)
{
QString errorMsg;
const ModelNode &node = updateNode;
if (node.isValid() && node.hasMetaInfo()) {
QString compFileName = node.metaInfo().componentFileName(); // absolute path
bool preselectNodeSource = false;
if (compFileName.isEmpty()) {
// Node is not a file component, so we have to check if the current doc itself is
compFileName = node.model()->fileUrl().toLocalFile();
preselectNodeSource = true;
}
QFileInfo compFileInfo{compFileName};
// Find to top asset folder
const QString assetFolder = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER).mid(1);
const QStringList parts = compFileName.split('/');
int i = parts.size() - 1;
int previousSize = 0;
for (; i >= 0; --i) {
if (parts[i] == assetFolder)
break;
previousSize = parts[i].size();
}
if (i >= 0) {
const QString assetPath = compFileName.left(compFileName.lastIndexOf(assetFolder)
+ assetFolder.size() + previousSize + 1);
const QDir assetDir(assetPath);
// Find import options and the original source scene
const QString jsonFileName = assetDir.absoluteFilePath(
Constants::QUICK_3D_ASSET_IMPORT_DATA_NAME);
QFile jsonFile{jsonFileName};
if (jsonFile.open(QIODevice::ReadOnly)) {
QJsonParseError jsonError;
const QByteArray fileData = jsonFile.readAll();
auto jsonDocument = QJsonDocument::fromJson(fileData, &jsonError);
jsonFile.close();
if (jsonError.error == QJsonParseError::NoError) {
QJsonObject jsonObj = jsonDocument.object();
const QJsonObject options = jsonObj.value(
Constants::QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY).toObject();
QString sourcePath = jsonObj.value(
Constants::QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY).toString();
if (options.isEmpty() || sourcePath.isEmpty()) {
errorMsg = QCoreApplication::translate(
"ModelNodeOperations",
"Asset import data file '%1' is invalid.").arg(jsonFileName);
} else {
QFileInfo sourceInfo{sourcePath};
if (!sourceInfo.exists()) {
// Unable to find original scene source, launch file dialog to locate it
QString initialPath;
ProjectExplorer::Project *currentProject
= ProjectExplorer::SessionManager::projectForFile(
Utils::FilePath::fromString(compFileName));
if (currentProject)
initialPath = currentProject->projectDirectory().toString();
else
initialPath = compFileInfo.absolutePath();
QStringList selectedFiles = QFileDialog::getOpenFileNames(
Core::ICore::dialogParent(),
tr("Locate 3D Asset '%1'").arg(sourceInfo.fileName()),
initialPath, sourceInfo.fileName());
if (!selectedFiles.isEmpty()
&& QFileInfo{selectedFiles[0]}.fileName() == sourceInfo.fileName()) {
sourcePath = selectedFiles[0];
sourceInfo.setFile(sourcePath);
}
}
if (sourceInfo.exists()) {
// In case of a selected node inside an imported component, preselect
// any file pointed to by a "source" property of the node.
QSet<QString> preselectedFiles;
if (preselectNodeSource && updateNode.hasProperty("source")) {
QString source = updateNode.variantProperty("source").value().toString();
if (QFileInfo{source}.isRelative())
source = QDir{compFileInfo.absolutePath()}.absoluteFilePath(source);
preselectedFiles.insert(source);
}
auto importDlg = new ItemLibraryAssetImportDialog(
{sourceInfo.absoluteFilePath()},
node.model()->fileUrl().toLocalFile(),
supportedExts, supportedOpts, options,
preselectedFiles, Core::ICore::mainWindow());
importDlg->show();
} else {
errorMsg = QCoreApplication::translate(
"ModelNodeOperations", "Unable to locate source scene '%1'.")
.arg(sourceInfo.fileName());
}
}
} else {
errorMsg = jsonError.errorString();
}
} else {
errorMsg = QCoreApplication::translate("ModelNodeOperations",
"Opening asset import data file '%1' failed.")
.arg(jsonFileName);
}
} else {
errorMsg = QCoreApplication::translate("ModelNodeOperations",
"Unable to resolve asset import path.");
}
}
if (!errorMsg.isEmpty()) {
QMessageBox::warning(
qobject_cast<QWidget *>(Core::ICore::dialogParent()),
QCoreApplication::translate("ModelNodeOperations", "Import Update Failed"),
QCoreApplication::translate("ModelNodeOperations",
"Failed to update import.\nError:\n%1").arg(errorMsg),
QMessageBox::Close);
}
}
void ItemLibraryAssetImportDialog::createTab(const QString &tabLabel, int optionsIndex,
const QJsonObject &groups)
{
@@ -610,7 +747,8 @@ void ItemLibraryAssetImportDialog::onImport()
if (!m_quick3DFiles.isEmpty()) {
m_importer.importQuick3D(m_quick3DFiles, m_quick3DImportPath,
m_importOptions, m_extToImportOptionsMap);
m_importOptions, m_extToImportOptionsMap,
m_preselectedFilesForOverwrite);
}
}

View File

@@ -25,9 +25,11 @@
#pragma once
#include "itemlibraryassetimporter.h"
#include "modelnode.h"
#include <QtWidgets/qdialog.h>
#include <QtCore/qjsonobject.h>
#include <QDialog>
#include <QJsonObject>
#include <QSet>
namespace Utils {
class OutputFormatter;
@@ -49,9 +51,15 @@ public:
const QString &defaulTargetDirectory,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts,
const QJsonObject &defaultOpts,
const QSet<QString> &preselectedFilesForOverwrite,
QWidget *parent = nullptr);
~ItemLibraryAssetImportDialog();
static void updateImport(const ModelNode &updateNode,
const QVariantMap &supportedExts,
const QVariantMap &supportedOpts);
protected:
void resizeEvent(QResizeEvent *event) override;
@@ -82,5 +90,6 @@ private:
QHash<QString, int> m_extToImportOptionsMap;
int m_optionsHeight = 0;
int m_optionsRows = 0;
QSet<QString> m_preselectedFilesForOverwrite;
};
}

View File

@@ -42,6 +42,7 @@
#include <QApplication>
#include <QMessageBox>
#include <QJsonDocument>
#include <QJsonObject>
#include <QPushButton>
namespace
@@ -64,7 +65,8 @@ ItemLibraryAssetImporter::~ItemLibraryAssetImporter() {
void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
const QString &importPath,
const QVector<QJsonObject> &options,
const QHash<QString, int> &extToImportOptionsMap)
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite)
{
if (m_isImporting)
cancelImport();
@@ -79,7 +81,7 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles,
m_importPath = importPath;
parseFiles(inputFiles, options, extToImportOptionsMap);
parseFiles(inputFiles, options, extToImportOptionsMap, preselectedFilesForOverwrite);
if (!isCancelled()) {
const auto parseData = m_parseData;
@@ -203,7 +205,8 @@ void ItemLibraryAssetImporter::reset()
void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
const QVector<QJsonObject> &options,
const QHash<QString, int> &extToImportOptionsMap)
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite)
{
if (isCancelled())
return;
@@ -219,7 +222,7 @@ void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
int index = extToImportOptionsMap.value(QFileInfo(file).suffix());
ParseData pd;
pd.options = options[index];
if (preParseQuick3DAsset(file, pd)) {
if (preParseQuick3DAsset(file, pd, preselectedFilesForOverwrite)) {
pd.importId = ++m_importIdCounter;
m_parseData.insert(pd.importId, pd);
}
@@ -227,7 +230,8 @@ void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths,
}
}
bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseData &pd)
bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseData &pd,
const QSet<QString> &preselectedFilesForOverwrite)
{
pd.targetDir = QDir(m_importPath);
pd.outDir = QDir(m_tempDir->path());
@@ -264,7 +268,9 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
pd.assetName = assetDirs[0];
pd.targetDirPath = pd.targetDir.filePath(pd.assetName);
}
OverwriteResult result = confirmAssetOverwrite(pd.assetName);
OverwriteResult result = preselectedFilesForOverwrite.isEmpty()
? confirmAssetOverwrite(pd.assetName)
: OverwriteResult::Update;
if (result == OverwriteResult::Skip) {
addWarning(tr("Skipped import of existing asset: \"%1\"").arg(pd.assetName));
return false;
@@ -282,8 +288,11 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa
alwaysOverwrite.insert(iconIt.fileInfo().absoluteFilePath());
}
alwaysOverwrite.insert(sourceSceneTargetFilePath(pd));
alwaysOverwrite.insert(pd.targetDirPath + '/' + Constants::QUICK_3D_ASSET_IMPORT_DATA_NAME);
Internal::AssetImportUpdateDialog dlg {pd.targetDirPath, {}, alwaysOverwrite,
Internal::AssetImportUpdateDialog dlg {pd.targetDirPath,
preselectedFilesForOverwrite,
alwaysOverwrite,
qobject_cast<QWidget *>(parent())};
int exitVal = dlg.exec();
@@ -421,6 +430,18 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
}
}
// Generate import metadata file
const QString sourcePath = pd.sourceInfo.absoluteFilePath();
QString importDataFileName = outDir.absoluteFilePath(Constants::QUICK_3D_ASSET_IMPORT_DATA_NAME);
QSaveFile importDataFile(importDataFileName);
if (importDataFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
QJsonObject optObj;
optObj.insert(Constants::QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY, pd.options);
optObj.insert(Constants::QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY, sourcePath);
importDataFile.write(QJsonDocument{optObj}.toJson());
importDataFile.commit();
}
// Gather all generated files
QDirIterator dirIt(outDir.path(), QDir::Files, QDirIterator::Subdirectories);
while (dirIt.hasNext()) {
@@ -429,7 +450,7 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
}
// Copy the original asset into a subdirectory
assetFiles.insert(pd.sourceInfo.absoluteFilePath(), sourceSceneTargetFilePath(pd));
assetFiles.insert(sourcePath, sourceSceneTargetFilePath(pd));
m_importFiles.insert(assetFiles);
}

View File

@@ -54,7 +54,8 @@ public:
void importQuick3D(const QStringList &inputFiles, const QString &importPath,
const QVector<QJsonObject> &options,
const QHash<QString, int> &extToImportOptionsMap);
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite);
bool isImporting() const;
void cancelImport();
@@ -91,8 +92,10 @@ private:
void notifyFinished();
void reset();
void parseFiles(const QStringList &filePaths, const QVector<QJsonObject> &options,
const QHash<QString, int> &extToImportOptionsMap);
bool preParseQuick3DAsset(const QString &file, ParseData &pd);
const QHash<QString, int> &extToImportOptionsMap,
const QSet<QString> &preselectedFilesForOverwrite);
bool preParseQuick3DAsset(const QString &file, ParseData &pd,
const QSet<QString> &preselectedFilesForOverwrite);
void postParseQuick3DAsset(const ParseData &pd);
void copyImportedFiles();

View File

@@ -48,6 +48,7 @@
#include <utils/algorithm.h>
#include <qmldesignerplugin.h>
#include <qmlitemnode.h>
#include <qmldesignerconstants.h>
namespace QmlDesigner {
@@ -240,7 +241,7 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap)
auto handle3DModel = [this](const QStringList &fileNames, const QString &defaultDir) -> bool {
auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir,
m_importableExtensions3DMap,
m_importOptions3DMap,
m_importOptions3DMap, {}, {},
Core::ICore::mainWindow());
importDlg->show();
return true;
@@ -263,4 +264,15 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap)
m_importOptions3DMap = qvariant_cast<QVariantMap>(supportMap.value("options"));
}
void ItemLibraryView::customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data)
{
if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) {
ItemLibraryAssetImportDialog::updateImport(nodeList[0], m_importableExtensions3DMap,
m_importOptions3DMap);
} else {
AbstractView::customNotification(view, identifier, nodeList, data);
}
}
} // namespace QmlDesigner

View File

@@ -55,6 +55,8 @@ public:
void usedImportsChanged(const QList<Import> &usedImports) override;
void documentMessagesChanged(const QList<DocumentMessage> &errors, const QList<DocumentMessage> &warnings) override;
void updateImport3DSupport(const QVariantMap &supportMap) override;
void customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
void setResourcePath(const QString &resourcePath);

View File

@@ -69,6 +69,9 @@ const char QML_DESIGNER_SUBFOLDER[] = "/designer/";
const char QUICK_3D_ASSETS_FOLDER[] = "/Quick3DAssets";
const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon";
const char QUICK_3D_ASSET_ICON_DIR[] = "_icons";
const char QUICK_3D_ASSET_IMPORT_DATA_NAME[] = "_importdata.json";
const char QUICK_3D_ASSET_IMPORT_DATA_OPTIONS_KEY[] = "import_options";
const char QUICK_3D_ASSET_IMPORT_DATA_SOURCE_KEY[] = "source_scene";
const char DEFAULT_ASSET_IMPORT_FOLDER[] = "/asset_imports";
// Menus