From 6d2d3a4eceef79ca6756263f0d1441644fabcd6a Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Thu, 29 Aug 2024 10:51:12 +0300 Subject: [PATCH] QmlDesigner: Refactor .qdsbundle exporting out of content lib Task-number: QDS-13389 Change-Id: Ie9e3edb634f94973e4a6157b991bc4af9608e19b Reviewed-by: Miikka Heikkinen --- .../components/componentcore/bundlehelper.cpp | 374 ++++++++++++++++- .../components/componentcore/bundlehelper.h | 42 ++ .../componentcore/designeractionmanager.cpp | 4 +- .../componentcore/modelnodeoperations.cpp | 9 - .../componentcore/modelnodeoperations.h | 1 - .../contentlibrary/contentlibraryview.cpp | 390 +----------------- .../contentlibrary/contentlibraryview.h | 38 +- .../components/edit3d/edit3dwidget.cpp | 5 +- .../materialbrowser/materialbrowserwidget.cpp | 6 +- 9 files changed, 429 insertions(+), 440 deletions(-) diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp index d1764842e9d..09d756365d3 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp @@ -4,19 +4,26 @@ #include "bundlehelper.h" #include "bundleimporter.h" +#include "utils3d.h" #include +#include +#include #include #include #include #include -#include +#include +#include + +#include #include #include #include +#include #include #include #include @@ -172,6 +179,353 @@ void BundleHelper::importBundleToProject() zipReader.close(); } +void BundleHelper::exportBundle(const ModelNode &node, const QPixmap &iconPixmap) +{ + if (node.isComponent()) + export3DComponent(node); + else + exportItem(node, iconPixmap); +} + +void BundleHelper::export3DComponent(const ModelNode &node) +{ + QString exportPath = getExportPath(node); + if (exportPath.isEmpty()) + return; + + // targetPath is a temp path for collecting and zipping assets, actual export target is where + // the user chose to export (i.e. exportPath) + QTemporaryDir tempDir; + QTC_ASSERT(tempDir.isValid(), return); + auto targetPath = Utils::FilePath::fromString(tempDir.path()); + + m_zipWriter = std::make_unique(exportPath); + + QString compBaseName = node.simplifiedTypeName(); + QString compFileName = compBaseName + ".qml"; + + auto compDir = Utils::FilePath::fromString(ModelUtils::componentFilePath(node)).parentDir(); + + QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); + + const Utils::FilePaths sourceFiles = compDir.dirEntries({{}, QDir::Files, QDirIterator::Subdirectories}); + const QStringList ignoreList {"_importdata.json", "qmldir", compBaseName + ".hints"}; + QStringList filesList; // 3D component's assets (dependencies) + + for (const Utils::FilePath &sourcePath : sourceFiles) { + Utils::FilePath relativePath = sourcePath.relativePathFrom(compDir); + if (ignoreList.contains(sourcePath.fileName()) || relativePath.startsWith("source scene")) + continue; + + m_zipWriter->addFile(relativePath.toFSPathString(), sourcePath.fileContents().value_or("")); + + if (sourcePath.fileName() != compFileName) // skip component file (only collect dependencies) + filesList.append(relativePath.path()); + } + + // add the item to the bundle json + QJsonObject jsonObj; + QJsonArray itemsArr; + itemsArr.append(QJsonObject { + {"name", node.simplifiedTypeName()}, + {"qml", compFileName}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(filesList)} + }); + + jsonObj["items"] = itemsArr; + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + jsonObj["id"] = compUtils.user3DBundleId(); + jsonObj["version"] = BUNDLE_VERSION; + + Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME); + m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson()); + + // add icon + m_iconSavePath = targetPath.pathAppended(iconPath); + m_iconSavePath.parentDir().ensureWritableDir(); + getImageFromCache(compDir.pathAppended(compFileName).path(), [&](const QImage &image) { + addIconAndCloseZip(image); + }); +} + +void BundleHelper::exportItem(const ModelNode &node, const QPixmap &iconPixmap) +{ + QString exportPath = getExportPath(node); + if (exportPath.isEmpty()) + return; + + // targetPath is a temp path for collecting and zipping assets, actual export target is where + // the user chose to export (i.e. exportPath) + m_tempDir = std::make_unique(); + QTC_ASSERT(m_tempDir->isValid(), return); + auto targetPath = Utils::FilePath::fromString(m_tempDir->path()); + + m_zipWriter = std::make_unique(exportPath); + + QString name = node.variantProperty("objectName").value().toString(); + if (name.isEmpty()) + name = node.displayName(); + + QString qml = nodeNameToComponentFileName(name); + QString iconBaseName = UniqueName::generateId(name); + + // generate and save Qml file + auto [qmlString, depAssets] = modelNodeToQmlString(node); + const QList depAssetsList = depAssets.values(); + + QStringList depAssetsRelativePaths; + for (const AssetPath &assetPath : depAssetsList) + depAssetsRelativePaths.append(assetPath.relativePath); + + auto qmlFilePath = targetPath.pathAppended(qml); + auto result = qmlFilePath.writeFileContents(qmlString.toUtf8()); + QTC_ASSERT_EXPECTED(result, return); + m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8()); + + QString iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); + + // add the item to the bundle json + QJsonObject jsonObj; + QJsonArray itemsArr; + itemsArr.append(QJsonObject { + {"name", name}, + {"qml", qml}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(depAssetsRelativePaths)} + }); + + jsonObj["items"] = itemsArr; + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + jsonObj["id"] = node.metaInfo().isQtQuick3DMaterial() ? compUtils.userMaterialsBundleId() + : compUtils.user3DBundleId(); + jsonObj["version"] = BUNDLE_VERSION; + + Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME); + m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson()); + + // add item's dependency assets to the bundle zip + for (const AssetPath &assetPath : depAssetsList) + m_zipWriter->addFile(assetPath.relativePath, assetPath.absFilPath().fileContents().value_or("")); + + // add icon + QPixmap iconPixmapToSave; + + if (node.metaInfo().isQtQuick3DCamera()) { + iconPixmapToSave = Core::ICore::resourcePath("qmldesigner/contentLibraryImages/camera.png") + .toFSPathString(); + } else if (node.metaInfo().isQtQuick3DLight()) { + iconPixmapToSave = Core::ICore::resourcePath("qmldesigner/contentLibraryImages/light.png") + .toFSPathString(); + } else { + iconPixmapToSave = iconPixmap; + } + + m_iconSavePath = targetPath.pathAppended(iconPath); + if (iconPixmapToSave.isNull()) { + getImageFromCache(qmlFilePath.toFSPathString(), [&](const QImage &image) { + addIconAndCloseZip(image); + }); + } else { + addIconAndCloseZip(iconPixmapToSave); + } +} + +QPair> BundleHelper::modelNodeToQmlString(const ModelNode &node, int depth) +{ + static QStringList depListIds; + + QString qml; + QSet assets; + + if (depth == 0) { + qml.append("import QtQuick\nimport QtQuick3D\n\n"); + depListIds.clear(); + } + + QString indent = QString(" ").repeated(depth * 4); + + qml += indent + node.simplifiedTypeName() + " {\n"; + + indent = QString(" ").repeated((depth + 1) * 4); + + qml += indent + "id: " + (depth == 0 ? "root" : node.id()) + " \n\n"; + + const QList excludedProps = {"x", "y", "z", "eulerRotation.x", "eulerRotation.y", + "eulerRotation.z", "scale.x", "scale.y", "scale.z", + "pivot.x", "pivot.y", "pivot.z"}; + const QList matProps = node.properties(); + for (const AbstractProperty &p : matProps) { + if (excludedProps.contains(p.name())) + continue; + + if (p.isVariantProperty()) { + QVariant pValue = p.toVariantProperty().value(); + QString val; + + if (!pValue.typeName()) { + // dynamic property with no value assigned + } else if (strcmp(pValue.typeName(), "QString") == 0 || strcmp(pValue.typeName(), "QColor") == 0) { + val = QLatin1String("\"%1\"").arg(pValue.toString()); + } else if (strcmp(pValue.typeName(), "QUrl") == 0) { + QString pValueStr = pValue.toString(); + val = QLatin1String("\"%1\"").arg(pValueStr); + if (!pValueStr.startsWith("#")) { + assets.insert({DocumentManager::currentResourcePath().toFSPathString(), + pValue.toString()}); + } + } else if (strcmp(pValue.typeName(), "QmlDesigner::Enumeration") == 0) { + val = pValue.value().toString(); + } else { + val = pValue.toString(); + } + if (p.isDynamic()) { + QString valWithColon = val.isEmpty() ? QString() : (": " + val); + qml += indent + "property " + p.dynamicTypeName() + " " + p.name() + valWithColon + "\n"; + } else { + qml += indent + p.name() + ": " + val + "\n"; + } + } else if (p.isBindingProperty()) { + ModelNode depNode = m_view->modelNodeForId(p.toBindingProperty().expression()); + QTC_ASSERT(depNode.isValid(), continue); + + if (p.isDynamic()) + qml += indent + "property " + p.dynamicTypeName() + " " + p.name() + ": " + depNode.id() + "\n"; + else + qml += indent + p.name() + ": " + depNode.id() + "\n"; + + if (depNode && !depListIds.contains(depNode.id())) { + depListIds.append(depNode.id()); + auto [depQml, depAssets] = modelNodeToQmlString(depNode, depth + 1); + qml += "\n" + depQml + "\n"; + assets.unite(depAssets); + } + } + } + + // add child nodes + const ModelNodes nodeChildren = node.directSubModelNodes(); + for (const ModelNode &childNode : nodeChildren) { + if (childNode && !depListIds.contains(childNode.id())) { + depListIds.append(childNode.id()); + auto [depQml, depAssets] = modelNodeToQmlString(childNode, depth + 1); + qml += "\n" + depQml + "\n"; + assets.unite(depAssets); + } + } + + indent = QString(" ").repeated(depth * 4); + + qml += indent + "}\n"; + + if (node.isComponent()) { + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + bool isBundle = node.type().startsWith(compUtils.componentBundlesTypePrefix().toLatin1()); + + if (depth > 0) { + // add component file to the dependency assets + Utils::FilePath compFilePath = componentPath(node.metaInfo()); + assets.insert({compFilePath.parentDir().path(), compFilePath.fileName()}); + } + + if (isBundle) + assets.unite(getBundleComponentDependencies(node)); + } + + return {qml, assets}; +} + +QSet BundleHelper::getBundleComponentDependencies(const ModelNode &node) const +{ + const QString compFileName = node.simplifiedTypeName() + ".qml"; + + Utils::FilePath compPath = componentPath(node.metaInfo()).parentDir(); + + QTC_ASSERT(compPath.exists(), return {}); + + QSet depList; + + Utils::FilePath assetRefPath = compPath.pathAppended(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE); + + Utils::expected_str assetRefContents = assetRefPath.fileContents(); + if (!assetRefContents.has_value()) { + qWarning() << __FUNCTION__ << assetRefContents.error(); + return {}; + } + + QJsonDocument jsonDoc = QJsonDocument::fromJson(assetRefContents.value()); + if (jsonDoc.isNull()) { + qWarning() << __FUNCTION__ << "Invalid json file" << assetRefPath; + return {}; + } + + const QJsonObject rootObj = jsonDoc.object(); + const QStringList bundleAssets = rootObj.keys(); + + for (const QString &asset : bundleAssets) { + if (rootObj.value(asset).toArray().contains(compFileName)) + depList.insert({compPath.toFSPathString(), asset}); + } + + return depList; +} + +Utils::FilePath BundleHelper::componentPath([[maybe_unused]] const NodeMetaInfo &metaInfo) const +{ +#ifdef QDS_USE_PROJECTSTORAGE + // TODO + return {}; +#else + return Utils::FilePath::fromString(metaInfo.componentFileName()); +#endif +} + +QString BundleHelper::nodeNameToComponentFileName(const QString &name) const +{ + QString fileName = UniqueName::generateId(name, "Component"); + fileName[0] = fileName.at(0).toUpper(); + fileName.prepend("My"); + + return fileName + ".qml"; +} + +/** + * @brief Generates an icon image from a qml component + * @param qmlPath path to the qml component file to be rendered + * @param iconPath output save path of the generated icon + */ +void BundleHelper::getImageFromCache(const QString &qmlPath, + std::function successCallback) +{ + QmlDesignerPlugin::imageCache().requestSmallImage( + Utils::PathString{qmlPath}, + successCallback, + [&](ImageCache::AbortReason abortReason) { + if (abortReason == ImageCache::AbortReason::Abort) { + qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " + "failed for path %1, reason: Abort").arg(qmlPath); + } else if (abortReason == ImageCache::AbortReason::Failed) { + qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " + "failed for path %1, reason: Failed").arg(qmlPath); + } else if (abortReason == ImageCache::AbortReason::NoEntry) { + qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " + "failed for path %1, reason: NoEntry").arg(qmlPath); + } + }); +} + +void BundleHelper::addIconAndCloseZip(const auto &image) { // auto: QImage or QPixmap + QByteArray iconByteArray; + QBuffer buffer(&iconByteArray); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "PNG"); + + m_zipWriter->addFile("icons/" + m_iconSavePath.fileName(), iconByteArray); + m_zipWriter->close(); +}; + QString BundleHelper::getImportPath() const { Utils::FilePath projectFP = DocumentManager::currentProjectDirPath(); @@ -186,6 +540,24 @@ QString BundleHelper::getImportPath() const .arg(Constants::BUNDLE_SUFFIX)); } +QString BundleHelper::getExportPath(const ModelNode &node) const +{ + QString defaultExportFileName = QLatin1String("%1.%2").arg(node.displayName(), + Constants::BUNDLE_SUFFIX); + Utils::FilePath projectFP = DocumentManager::currentProjectDirPath(); + if (projectFP.isEmpty()) { + projectFP = QmlDesignerPlugin::instance()->documentManager() + .currentDesignDocument()->fileName().parentDir(); + } + + QString dialogTitle = node.metaInfo().isQtQuick3DMaterial() ? QObject::tr("Export Material") + : QObject::tr("Export Component"); + return QFileDialog::getSaveFileName(m_widget, dialogTitle, + projectFP.pathAppended(defaultExportFileName).toFSPathString(), + QObject::tr("Qt Design Studio Bundle Files (*.%1)") + .arg(Constants::BUNDLE_SUFFIX)); +} + bool BundleHelper::isMaterialBundle(const QString &bundleId) const { auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h index 47f038a4672..de70dfc60f1 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h @@ -3,17 +3,45 @@ #pragma once +#include #include +#include #include QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QTemporaryDir) QT_FORWARD_DECLARE_CLASS(QWidget) +class ZipWriter; + namespace QmlDesigner { class AbstractView; class BundleImporter; +class ModelNode; +class NodeMetaInfo; + +struct AssetPath +{ + QString basePath; + QString relativePath; + + Utils::FilePath absFilPath() const + { + return Utils::FilePath::fromString(basePath).pathAppended(relativePath); + } + + bool operator==(const AssetPath &other) const + { + return basePath == other.basePath && relativePath == other.relativePath; + } + + friend size_t qHash(const AssetPath &asset) + { + return ::qHash(asset.relativePath); + } +}; class BundleHelper { @@ -22,16 +50,30 @@ public: ~BundleHelper(); void importBundleToProject(); + void exportBundle(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + void getImageFromCache(const QString &qmlPath, + std::function successCallback); + QString nodeNameToComponentFileName(const QString &name) const; + QPair> modelNodeToQmlString(const ModelNode &node, int depth = 0); QString getImportPath() const; private: void createImporter(); + QString getExportPath(const ModelNode &node) const; bool isMaterialBundle(const QString &bundleId) const; bool isItemBundle(const QString &bundleId) const; + void addIconAndCloseZip(const auto &image); + Utils::FilePath componentPath(const NodeMetaInfo &metaInfo) const; + QSet getBundleComponentDependencies(const ModelNode &node) const; + void export3DComponent(const ModelNode &node); + void exportItem(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); QPointer m_view; QPointer m_widget; Utils::UniqueObjectPtr m_importer; + std::unique_ptr m_zipWriter; + std::unique_ptr m_tempDir; + Utils::FilePath m_iconSavePath; static constexpr char BUNDLE_VERSION[] = "1.0"; }; diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index a11f420258a..9b9b7f75a91 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -2009,7 +2009,9 @@ void DesignerActionManager::createDefaultDesignerActions() rootCategory, QKeySequence(), Priorities::ExportComponent, - &exportComponent, + [&](const SelectionContext &context) { + m_bundleHelper->exportBundle(context.currentSingleSelectedNode()); + }, &is3DNode, &is3DNode)); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index f12d07d1fc5..9b4f6b134dd 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -798,15 +798,6 @@ void add3DAssetToContentLibrary(const SelectionContext &selectionContext) selectionContext.view()->emitCustomNotification("add_3d_to_content_lib", {node}); } -void exportComponent(const SelectionContext &selectionContext) -{ -#ifdef DETACH_DISABLED_VIEWS - QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("ContentLibrary"); -#endif - ModelNode node = selectionContext.currentSingleSelectedNode(); - selectionContext.view()->emitCustomNotification("export_item_as_bundle", {node}); -} - void goImplementation(const SelectionContext &selectionState) { addSignalHandlerOrGotoImplementation(selectionState, false); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index 45006067f02..d8cf8fe6230 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -97,7 +97,6 @@ void removeLayout(const SelectionContext &selectionContext); void removePositioner(const SelectionContext &selectionContext); void moveToComponent(const SelectionContext &selectionContext); void add3DAssetToContentLibrary(const SelectionContext &selectionContext); -void exportComponent(const SelectionContext &selectionContext); PropertyName getIndexPropertyName(const ModelNode &modelNode); void addItemToStackedContainer(const SelectionContext &selectionContext); void increaseIndexOfStackedContainer(const SelectionContext &selectionContext); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index fa7836870f5..16f550e9385 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,6 @@ #include #include -#include #include @@ -43,14 +43,12 @@ #include #endif -#include #include #include #include #include #include #include -#include #include namespace QmlDesigner { @@ -75,6 +73,8 @@ WidgetInfo ContentLibraryView::widgetInfo() if (m_widget.isNull()) { m_widget = new ContentLibraryWidget(); + m_bundleHelper = std::make_unique(this, m_widget); + connect(m_widget, &ContentLibraryWidget::bundleMaterialDragStarted, this, [&] (QmlDesigner::ContentLibraryMaterial *mat) { m_draggedBundleMaterial = mat; @@ -399,14 +399,6 @@ void ContentLibraryView::customNotification(const AbstractView *view, else addLibItem(nodeList.first()); m_widget->showTab(ContentLibraryWidget::TabIndex::UserAssetsTab); - } else if (identifier == "export_item_as_bundle") { - // TODO: support exporting 2D items - if (nodeList.first().isComponent()) - exportLib3DComponent(nodeList.first()); - else - exportLibItem(nodeList.first()); - } else if (identifier == "export_material_as_bundle") { - exportLibItem(nodeList.first(), data.first().value()); } } @@ -439,8 +431,6 @@ void ContentLibraryView::modelNodePreviewPixmapChanged(const ModelNode &, { if (requestId == ADD_ITEM_REQ_ID) saveIconToBundle(pixmap); - else if (requestId == EXPORT_ITEM_REQ_ID) - addIconAndCloseZip(pixmap); } #ifdef QDS_USE_PROJECTSTORAGE @@ -502,123 +492,6 @@ void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundle } #endif -namespace { -Utils::FilePath componentPath([[maybe_unused]] const NodeMetaInfo &metaInfo) -{ -#ifdef QDS_USE_PROJECTSTORAGE - // TODO - return {}; -#else - return Utils::FilePath::fromString(metaInfo.componentFileName()); -#endif -} - -} // namespace - -QPair> ContentLibraryView::modelNodeToQmlString(const ModelNode &node, int depth) -{ - static QStringList depListIds; - - QString qml; - QSet assets; - - if (depth == 0) { - qml.append("import QtQuick\nimport QtQuick3D\n\n"); - depListIds.clear(); - } - - QString indent = QString(" ").repeated(depth * 4); - - qml += indent + node.simplifiedTypeName() + " {\n"; - - indent = QString(" ").repeated((depth + 1) * 4); - - qml += indent + "id: " + (depth == 0 ? "root" : node.id()) + " \n\n"; - - const QList excludedProps = {"x", "y", "z", "eulerRotation.x", "eulerRotation.y", - "eulerRotation.z", "scale.x", "scale.y", "scale.z", - "pivot.x", "pivot.y", "pivot.z"}; - const QList matProps = node.properties(); - for (const AbstractProperty &p : matProps) { - if (excludedProps.contains(p.name())) - continue; - - if (p.isVariantProperty()) { - QVariant pValue = p.toVariantProperty().value(); - QString val; - - if (!pValue.typeName()) { - // dynamic property with no value assigned - } else if (strcmp(pValue.typeName(), "QString") == 0 || strcmp(pValue.typeName(), "QColor") == 0) { - val = QLatin1String("\"%1\"").arg(pValue.toString()); - } else if (strcmp(pValue.typeName(), "QUrl") == 0) { - QString pValueStr = pValue.toString(); - val = QLatin1String("\"%1\"").arg(pValueStr); - if (!pValueStr.startsWith("#")) { - assets.insert({DocumentManager::currentResourcePath().toFSPathString(), - pValue.toString()}); - } - } else if (strcmp(pValue.typeName(), "QmlDesigner::Enumeration") == 0) { - val = pValue.value().toString(); - } else { - val = pValue.toString(); - } - if (p.isDynamic()) { - QString valWithColon = val.isEmpty() ? QString() : (": " + val); - qml += indent + "property " + p.dynamicTypeName() + " " + p.name() + valWithColon + "\n"; - } else { - qml += indent + p.name() + ": " + val + "\n"; - } - } else if (p.isBindingProperty()) { - ModelNode depNode = modelNodeForId(p.toBindingProperty().expression()); - QTC_ASSERT(depNode.isValid(), continue); - - if (p.isDynamic()) - qml += indent + "property " + p.dynamicTypeName() + " " + p.name() + ": " + depNode.id() + "\n"; - else - qml += indent + p.name() + ": " + depNode.id() + "\n"; - - if (depNode && !depListIds.contains(depNode.id())) { - depListIds.append(depNode.id()); - auto [depQml, depAssets] = modelNodeToQmlString(depNode, depth + 1); - qml += "\n" + depQml + "\n"; - assets.unite(depAssets); - } - } - } - - // add child nodes - const ModelNodes nodeChildren = node.directSubModelNodes(); - for (const ModelNode &childNode : nodeChildren) { - if (childNode && !depListIds.contains(childNode.id())) { - depListIds.append(childNode.id()); - auto [depQml, depAssets] = modelNodeToQmlString(childNode, depth + 1); - qml += "\n" + depQml + "\n"; - assets.unite(depAssets); - } - } - - indent = QString(" ").repeated(depth * 4); - - qml += indent + "}\n"; - - if (node.isComponent()) { - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - bool isBundle = node.type().startsWith(compUtils.componentBundlesTypePrefix().toLatin1()); - - if (depth > 0) { - // add component file to the dependency assets - Utils::FilePath compFilePath = componentPath(node.metaInfo()); - assets.insert({compFilePath.parentDir().path(), compFilePath.fileName()}); - } - - if (isBundle) - assets.unite(getBundleComponentDependencies(node)); - } - - return {qml, assets}; -} - void ContentLibraryView::addLibAssets(const QStringList &paths) { auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/textures"); @@ -744,83 +617,11 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) filesList); // generate and save icon - getImageFromCache(compDir.pathAppended(compFileName).path(), [&](const QImage &image) { + m_bundleHelper->getImageFromCache(compDir.pathAppended(compFileName).path(), [&](const QImage &image) { saveIconToBundle(image); }); } -void ContentLibraryView::exportLib3DComponent(const ModelNode &node) -{ - QString exportPath = getExportPath(node); - if (exportPath.isEmpty()) - return; - - // targetPath is a temp path for collecting and zipping assets, actual export target is where - // the user chose to export (i.e. exportPath) - QTemporaryDir tempDir; - QTC_ASSERT(tempDir.isValid(), return); - auto targetPath = Utils::FilePath::fromString(tempDir.path()); - - m_zipWriter = std::make_unique(exportPath); - - QString compBaseName = node.simplifiedTypeName(); - QString compFileName = compBaseName + ".qml"; - - auto compDir = Utils::FilePath::fromString(ModelUtils::componentFilePath(node)).parentDir(); - - QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); - - const Utils::FilePaths sourceFiles = compDir.dirEntries({{}, QDir::Files, QDirIterator::Subdirectories}); - const QStringList ignoreList {"_importdata.json", "qmldir", compBaseName + ".hints"}; - QStringList filesList; // 3D component's assets (dependencies) - - for (const Utils::FilePath &sourcePath : sourceFiles) { - Utils::FilePath relativePath = sourcePath.relativePathFrom(compDir); - if (ignoreList.contains(sourcePath.fileName()) || relativePath.startsWith("source scene")) - continue; - - m_zipWriter->addFile(relativePath.toFSPathString(), sourcePath.fileContents().value_or("")); - - if (sourcePath.fileName() != compFileName) // skip component file (only collect dependencies) - filesList.append(relativePath.path()); - } - - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { - {"name", node.simplifiedTypeName()}, - {"qml", compFileName}, - {"icon", iconPath}, - {"files", QJsonArray::fromStringList(filesList)} - }); - - jsonObj["items"] = itemsArr; - - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - jsonObj["id"] = compUtils.user3DBundleId(); - jsonObj["version"] = BUNDLE_VERSION; - - Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME); - m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson()); - - // add icon - m_iconSavePath = targetPath.pathAppended(iconPath); - m_iconSavePath.parentDir().ensureWritableDir(); - getImageFromCache(compDir.pathAppended(compFileName).path(), [&](const QImage &image) { - addIconAndCloseZip(image); - }); -} - -QString ContentLibraryView::nodeNameToComponentFileName(const QString &name) const -{ - QString fileName = UniqueName::generateId(name, "Component"); - fileName[0] = fileName.at(0).toUpper(); - fileName.prepend("My"); - - return fileName + ".qml"; -} - void ContentLibraryView::addLibItem(const ModelNode &node, const QPixmap &iconPixmap) { auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); @@ -849,7 +650,7 @@ void ContentLibraryView::addLibItem(const ModelNode &node, const QPixmap &iconPi QJsonObject &jsonRef = m_widget->userModel()->bundleObjectRef(m_bundleId); QJsonArray itemsArr = jsonRef.value("items").toArray(); - QString qml = nodeNameToComponentFileName(name); + QString qml = m_bundleHelper->nodeNameToComponentFileName(name); // confirm overwrite if an item with same name exists if (m_widget->userModel()->jsonPropertyExists("qml", qml, m_bundleId)) { @@ -866,7 +667,7 @@ void ContentLibraryView::addLibItem(const ModelNode &node, const QPixmap &iconPi } // generate and save Qml file - auto [qmlString, depAssets] = modelNodeToQmlString(node); + auto [qmlString, depAssets] = m_bundleHelper->modelNodeToQmlString(node); const QList depAssetsList = depAssets.values(); QStringList depAssetsRelativePaths; @@ -932,123 +733,6 @@ void ContentLibraryView::addLibItem(const ModelNode &node, const QPixmap &iconPi } } -QString ContentLibraryView::getExportPath(const ModelNode &node) const -{ - QString defaultExportFileName = QLatin1String("%1.%2").arg(node.displayName(), - Constants::BUNDLE_SUFFIX); - Utils::FilePath projectFP = DocumentManager::currentProjectDirPath(); - if (projectFP.isEmpty()) { - projectFP = QmlDesignerPlugin::instance()->documentManager() - .currentDesignDocument()->fileName().parentDir(); - } - - QString dialogTitle = node.metaInfo().isQtQuick3DMaterial() ? tr("Export Material") - : tr("Export Component"); - return QFileDialog::getSaveFileName(m_widget, dialogTitle, - projectFP.pathAppended(defaultExportFileName).toFSPathString(), - tr("Qt Design Studio Bundle Files (*.%1)").arg(Constants::BUNDLE_SUFFIX)); -} - -QString ContentLibraryView::getImportPath() const -{ - Utils::FilePath projectFP = DocumentManager::currentProjectDirPath(); - if (projectFP.isEmpty()) { - projectFP = QmlDesignerPlugin::instance()->documentManager() - .currentDesignDocument()->fileName().parentDir(); - } - - return QFileDialog::getOpenFileName(m_widget, tr("Import Component"), projectFP.toFSPathString(), - tr("Qt Design Studio Bundle Files (*.%1)").arg(Constants::BUNDLE_SUFFIX)); -} - -void ContentLibraryView::exportLibItem(const ModelNode &node, const QPixmap &iconPixmap) -{ - QString exportPath = getExportPath(node); - if (exportPath.isEmpty()) - return; - - // targetPath is a temp path for collecting and zipping assets, actual export target is where - // the user chose to export (i.e. exportPath) - m_tempDir = std::make_unique(); - QTC_ASSERT(m_tempDir->isValid(), return); - auto targetPath = Utils::FilePath::fromString(m_tempDir->path()); - - m_zipWriter = std::make_unique(exportPath); - - QString name = node.variantProperty("objectName").value().toString(); - if (name.isEmpty()) - name = node.displayName(); - - QString qml = nodeNameToComponentFileName(name); - QString iconBaseName = UniqueName::generateId(name); - - // generate and save Qml file - auto [qmlString, depAssets] = modelNodeToQmlString(node); - const QList depAssetsList = depAssets.values(); - - QStringList depAssetsRelativePaths; - for (const AssetPath &assetPath : depAssetsList) - depAssetsRelativePaths.append(assetPath.relativePath); - - auto qmlFilePath = targetPath.pathAppended(qml); - auto result = qmlFilePath.writeFileContents(qmlString.toUtf8()); - QTC_ASSERT_EXPECTED(result, return); - m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8()); - - QString iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); - - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { - {"name", name}, - {"qml", qml}, - {"icon", iconPath}, - {"files", QJsonArray::fromStringList(depAssetsRelativePaths)} - }); - - jsonObj["items"] = itemsArr; - - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - jsonObj["id"] = node.metaInfo().isQtQuick3DMaterial() ? compUtils.userMaterialsBundleId() - : compUtils.user3DBundleId(); - jsonObj["version"] = BUNDLE_VERSION; - - Utils::FilePath jsonFilePath = targetPath.pathAppended(Constants::BUNDLE_JSON_FILENAME); - m_zipWriter->addFile(jsonFilePath.fileName(), QJsonDocument(jsonObj).toJson()); - - // add item's dependency assets to the bundle zip - for (const AssetPath &assetPath : depAssetsList) - m_zipWriter->addFile(assetPath.relativePath, assetPath.absFilPath().fileContents().value_or("")); - - // add icon - QPixmap iconPixmapToSave; - if (node.metaInfo().isQtQuick3DCamera()) - iconPixmapToSave = m_widget->iconProvider()->requestPixmap("camera.png", nullptr, {}); - else if (node.metaInfo().isQtQuick3DLight()) - iconPixmapToSave = m_widget->iconProvider()->requestPixmap("light.png", nullptr, {}); - else - iconPixmapToSave = iconPixmap; - - m_iconSavePath = targetPath.pathAppended(iconPath); - if (iconPixmapToSave.isNull()) { - static_cast(model()->nodeInstanceView()) - ->previewImageDataForGenericNode(node, {}, {}, EXPORT_ITEM_REQ_ID); - } else { - addIconAndCloseZip(iconPixmapToSave); - } -} - -void ContentLibraryView::addIconAndCloseZip(const auto &image) { // auto: QImage or QPixmap - QByteArray iconByteArray; - QBuffer buffer(&iconByteArray); - buffer.open(QIODevice::WriteOnly); - image.save(&buffer, "PNG"); - - m_zipWriter->addFile("icons/" + m_iconSavePath.fileName(), iconByteArray); - m_zipWriter->close(); -}; - void ContentLibraryView::saveIconToBundle(const auto &image) { // auto: QImage or QPixmap bool iconSaved = image.save(m_iconSavePath.toFSPathString()); if (iconSaved) @@ -1061,7 +745,7 @@ void ContentLibraryView::saveIconToBundle(const auto &image) { // auto: QImage o void ContentLibraryView::importBundleToContentLib() { - QString importPath = getImportPath(); + QString importPath = m_bundleHelper->getImportPath(); if (importPath.isEmpty()) return; @@ -1145,66 +829,6 @@ void ContentLibraryView::importBundleToContentLib() QTC_ASSERT_EXPECTED(result,); } -/** - * @brief Generates an icon image from a qml component - * @param qmlPath path to the qml component file to be rendered - * @param iconPath output save path of the generated icon - */ -void ContentLibraryView::getImageFromCache(const QString &qmlPath, - std::function successCallback) -{ - m_imageCache.requestSmallImage( - Utils::PathString{qmlPath}, - successCallback, - [&](ImageCache::AbortReason abortReason) { - if (abortReason == ImageCache::AbortReason::Abort) { - qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " - "failed for path %1, reason: Abort").arg(qmlPath); - } else if (abortReason == ImageCache::AbortReason::Failed) { - qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " - "failed for path %1, reason: Failed").arg(qmlPath); - } else if (abortReason == ImageCache::AbortReason::NoEntry) { - qWarning() << QLatin1String("ContentLibraryView::getImageFromCache(): icon generation " - "failed for path %1, reason: NoEntry").arg(qmlPath); - } - }); -} - -QSet ContentLibraryView::getBundleComponentDependencies(const ModelNode &node) const -{ - const QString compFileName = node.simplifiedTypeName() + ".qml"; - - Utils::FilePath compPath = componentPath(node.metaInfo()).parentDir(); - - QTC_ASSERT(compPath.exists(), return {}); - - QSet depList; - - Utils::FilePath assetRefPath = compPath.pathAppended(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE); - - Utils::expected_str assetRefContents = assetRefPath.fileContents(); - if (!assetRefContents.has_value()) { - qWarning() << __FUNCTION__ << assetRefContents.error(); - return {}; - } - - QJsonDocument jsonDoc = QJsonDocument::fromJson(assetRefContents.value()); - if (jsonDoc.isNull()) { - qWarning() << __FUNCTION__ << "Invalid json file" << assetRefPath; - return {}; - } - - const QJsonObject rootObj = jsonDoc.object(); - const QStringList bundleAssets = rootObj.keys(); - - for (const QString &asset : bundleAssets) { - if (rootObj.value(asset).toArray().contains(compFileName)) - depList.insert({compPath.toFSPathString(), asset}); - } - - return depList; -} - ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &type) { ModelNode matLib = Utils3D::materialLibraryNode(this); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index fc5288389ad..deba7b93a61 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -13,41 +13,19 @@ #include #include -class ZipWriter; - QT_FORWARD_DECLARE_CLASS(QImage) QT_FORWARD_DECLARE_CLASS(QPixmap) QT_FORWARD_DECLARE_CLASS(QTemporaryDir) namespace QmlDesigner { +class BundleHelper; class ContentLibraryItem; class ContentLibraryMaterial; class ContentLibraryTexture; class ContentLibraryWidget; class Model; -struct AssetPath -{ - QString basePath; - QString relativePath; - - Utils::FilePath absFilPath() const - { - return Utils::FilePath::fromString(basePath).pathAppended(relativePath); - } - - bool operator==(const AssetPath &other) const - { - return basePath == other.basePath && relativePath == other.relativePath; - } - - friend size_t qHash(const AssetPath &asset) - { - return ::qHash(asset.relativePath); - } -}; - class ContentLibraryView : public AbstractView { Q_OBJECT @@ -87,18 +65,8 @@ private: void updateBundlesQuick3DVersion(); void addLibAssets(const QStringList &paths); void addLib3DComponent(const ModelNode &node); - void exportLib3DComponent(const ModelNode &node); void addLibItem(const ModelNode &node, const QPixmap &iconPixmap = {}); - void exportLibItem(const ModelNode &node, const QPixmap &iconPixmap = {}); void importBundleToContentLib(); - void getImageFromCache(const QString &qmlPath, - std::function successCallback); - QSet getBundleComponentDependencies(const ModelNode &node) const; - QString getExportPath(const ModelNode &node) const; - QString getImportPath() const; - QString nodeNameToComponentFileName(const QString &name) const; - QPair> modelNodeToQmlString(const ModelNode &node, int depth = 0); - void addIconAndCloseZip(const auto &image); void saveIconToBundle(const auto &image); #ifdef QDS_USE_PROJECTSTORAGE @@ -117,6 +85,7 @@ private: ContentLibraryMaterial *m_draggedBundleMaterial = nullptr; ContentLibraryTexture *m_draggedBundleTexture = nullptr; ContentLibraryItem *m_draggedBundleItem = nullptr; + std::unique_ptr m_bundleHelper; AsynchronousImageCache &m_imageCache; bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; @@ -125,12 +94,9 @@ private: Utils::FilePath m_iconSavePath; QString m_generatedFolderName; QString m_bundleId; - std::unique_ptr m_zipWriter; - std::unique_ptr m_tempDir; static constexpr char BUNDLE_VERSION[] = "1.0"; static constexpr char ADD_ITEM_REQ_ID[] = "AddItemReqId"; - static constexpr char EXPORT_ITEM_REQ_ID[] = "ExportItemReqId"; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 07ff875b89a..3bb04e2301c 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -380,10 +380,7 @@ void Edit3DWidget::createContextMenu() m_exportBundleAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon tr("Export Component"), [&] { -#ifdef DETACH_DISABLED_VIEWS - QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("ContentLibrary"); -#endif - view()->emitCustomNotification("export_item_as_bundle", {m_contextMenuTarget}); // To ContentLibrary + m_bundleHelper->exportBundle(m_contextMenuTarget); }); m_contextMenu->addSeparator(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 775ae4f6a94..bb22c9b5959 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -385,12 +385,8 @@ void MaterialBrowserWidget::importMaterial() } void MaterialBrowserWidget::exportMaterial() { -#ifdef DETACH_DISABLED_VIEWS - QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("ContentLibrary"); -#endif ModelNode mat = m_materialBrowserModel->selectedMaterial(); - m_materialBrowserView->emitCustomNotification("export_material_as_bundle", {mat}, - {m_previewImageProvider->getPixmap(mat)}); // to ContentLibrary + m_bundleHelper->exportBundle(mat, m_previewImageProvider->getPixmap(mat)); } QString MaterialBrowserWidget::qmlSourcesPath()