diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp index 42074fe126d..671cbcc8f52 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.cpp @@ -182,7 +182,6 @@ void BundleHelper::importBundleToProject() // TODO: before overwriting remove old item's dependencies (not harmful but for cleanup) } - // add entry to model QStringList files = itemObj.value("files").toVariant().toStringList(); QString icon = itemObj.value("icon").toString(); @@ -207,28 +206,57 @@ void BundleHelper::importBundleToProject() zipReader.close(); } -void BundleHelper::exportBundle(const ModelNode &node, const QPixmap &iconPixmap) +void BundleHelper::exportBundle(const QList &nodes, const QPixmap &iconPixmap) { - if (node.isComponent()) - exportComponent(node); - else - exportNode(node, iconPixmap); -} + QTC_ASSERT(!nodes.isEmpty(), return); -void BundleHelper::exportComponent(const ModelNode &node) -{ - QString exportPath = getExportPath(node); + QString exportPath = getExportPath(nodes.at(0)); if (exportPath.isEmpty()) return; m_zipWriter = std::make_unique(exportPath); - Utils::FilePath compFilePath = componentPath(node); + m_tempDir = std::make_unique(); + QTC_ASSERT(m_tempDir->isValid(), return); + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + QJsonObject jsonObj; + jsonObj["id"] = compUtils.user3DBundleId(); + jsonObj["version"] = BUNDLE_VERSION; + QJsonArray itemsArr; + + // remove nested nodes (they will be exported anyway as dependency of the parent) + QList nodesToExport; + for (const ModelNode &node : nodes) { + bool isChild = std::ranges::any_of(nodes, [&](const ModelNode &possibleParent) { + return &node != &possibleParent && possibleParent.isAncestorOf(node); + }); + + if (!isChild) + nodesToExport.append(node); + } + + m_remainingIcons = nodesToExport.size(); + + for (const ModelNode &node : std::as_const(nodesToExport)) { + if (node.isComponent()) + itemsArr.append(exportComponent(node)); + else + itemsArr.append(exportNode(node, iconPixmap)); + } + + jsonObj["items"] = itemsArr; + m_zipWriter->addFile(Constants::BUNDLE_JSON_FILENAME, QJsonDocument(jsonObj).toJson()); +} + +QJsonObject BundleHelper::exportComponent(const ModelNode &node) +{ + Utils::FilePath compFilePath = Utils::FilePath::fromString(ModelUtils::componentFilePath(node)); Utils::FilePath compDir = compFilePath.parentDir(); QString compBaseName = compFilePath.completeBaseName(); QString compFileName = compFilePath.fileName(); - m_iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); + QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); const QSet compDependencies = getComponentDependencies(compFilePath, compDir); @@ -250,43 +278,25 @@ void BundleHelper::exportComponent(const ModelNode &node) filesList.append(asset.relativePath); } - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { + // add icon + QString filePath = compFilePath.path(); + getImageFromCache(filePath, [this, iconPath](const QImage &image) { + addIconAndCloseZip(iconPath, image); + }); + + return { {"name", node.simplifiedTypeName()}, {"qml", compFileName}, - {"icon", m_iconPath}, + {"icon", iconPath}, {"files", QJsonArray::fromStringList(filesList)} - }); - - jsonObj["items"] = itemsArr; - - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - jsonObj["id"] = compUtils.user3DBundleId(); - jsonObj["version"] = BUNDLE_VERSION; - - m_zipWriter->addFile(Constants::BUNDLE_JSON_FILENAME, QJsonDocument(jsonObj).toJson()); - - // add icon - getImageFromCache(compFilePath.path(), [&](const QImage &image) { - addIconAndCloseZip(image); - }); + }; } -void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) +QJsonObject BundleHelper::exportNode(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 + // tempPath 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); + auto tempPath = Utils::FilePath::fromString(m_tempDir->path()); QString name = node.variantProperty("objectName").value().toString(); if (name.isEmpty()) @@ -294,7 +304,7 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) QString qml = nodeNameToComponentFileName(name); QString iconBaseName = UniqueName::generateId(name); - m_iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); + QString iconPath = QLatin1String("icons/%1.png").arg(iconBaseName); // generate and save Qml file auto [qmlString, depAssets] = modelNodeToQmlString(node); @@ -304,37 +314,17 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) for (const AssetPath &assetPath : depAssetsList) depAssetsRelativePaths.append(assetPath.relativePath); - auto qmlFilePath = targetPath.pathAppended(qml); + auto qmlFilePath = tempPath.pathAppended(qml); auto result = qmlFilePath.writeFileContents(qmlString.toUtf8()); - QTC_ASSERT_EXPECTED(result, return); + QTC_ASSERT_EXPECTED(result, return {}); m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8()); - // add the item to the bundle json - QJsonObject jsonObj; - QJsonArray itemsArr; - itemsArr.append(QJsonObject { - {"name", name}, - {"qml", qml}, - {"icon", m_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 and target path (for icon generation) for (const AssetPath &assetPath : depAssetsList) { QByteArray assetContent = assetPath.fileContent(); m_zipWriter->addFile(assetPath.relativePath, assetContent); - Utils::FilePath assetTargetPath = targetPath.pathAppended(assetPath.relativePath); + Utils::FilePath assetTargetPath = tempPath.pathAppended(assetPath.relativePath); assetTargetPath.parentDir().ensureWritableDir(); assetTargetPath.writeFileContents(assetContent); } @@ -353,12 +343,19 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap) } if (iconPixmapToSave.isNull()) { - getImageFromCache(qmlFilePath.toFSPathString(), [&](const QImage &image) { - addIconAndCloseZip(image); + getImageFromCache(qmlFilePath.toFSPathString(), [this, iconPath](const QImage &image) { + addIconAndCloseZip(iconPath, image); }); } else { - addIconAndCloseZip(iconPixmapToSave); + addIconAndCloseZip(iconPath, iconPixmapToSave); } + + return { + {"name", name}, + {"qml", qml}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(depAssetsRelativePaths)} + }; } QPair> BundleHelper::modelNodeToQmlString(const ModelNode &node, int depth) @@ -544,14 +541,16 @@ void BundleHelper::getImageFromCache(const QString &qmlPath, }); } -void BundleHelper::addIconAndCloseZip(const auto &image) { // auto: QImage or QPixmap +void BundleHelper::addIconAndCloseZip(const QString &iconPath, const auto &image) { // auto: QImage or QPixmap QByteArray iconByteArray; QBuffer buffer(&iconByteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); - m_zipWriter->addFile(m_iconPath, iconByteArray); - m_zipWriter->close(); + m_zipWriter->addFile(iconPath, iconByteArray); + + if (--m_remainingIcons <= 0) + m_zipWriter->close(); }; QString BundleHelper::getImportPath() const @@ -689,6 +688,10 @@ QSet BundleHelper::getComponentDependencies(const Utils::FilePath &fi parseNode = [&](const ModelNode &node) { // workaround node.isComponent() as it is not working here QString nodeType = QString::fromLatin1(node.type()); + +#ifdef QDS_USE_PROJECTSTORAGE + // TODO +#else if (!nodeType.startsWith("QtQuick")) { Utils::FilePath compFilPath = getComponentFilePath(nodeType, mainCompDir); if (!compFilPath.isEmpty()) { @@ -705,6 +708,7 @@ QSet BundleHelper::getComponentDependencies(const Utils::FilePath &fi return; } } +#endif const QList nodeProps = node.properties(); for (const AbstractProperty &p : nodeProps) { diff --git a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h index 14d90ef951e..ddd79f33158 100644 --- a/src/plugins/qmldesigner/components/componentcore/bundlehelper.h +++ b/src/plugins/qmldesigner/components/componentcore/bundlehelper.h @@ -57,7 +57,7 @@ public: ~BundleHelper(); void importBundleToProject(); - void exportBundle(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + void exportBundle(const QList &nodes, const QPixmap &iconPixmap = QPixmap()); void getImageFromCache(const QString &qmlPath, std::function successCallback); QString nodeNameToComponentFileName(const QString &name) const; @@ -71,18 +71,18 @@ private: QString getExportPath(const ModelNode &node) const; bool isMaterialBundle(const QString &bundleId) const; bool isItemBundle(const QString &bundleId) const; - void addIconAndCloseZip(const auto &image); + void addIconAndCloseZip(const QString &iconPath, const auto &image); Utils::FilePath componentPath(const ModelNode &node) const; QSet getBundleComponentDependencies(const ModelNode &node) const; - void exportComponent(const ModelNode &node); - void exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap()); + QJsonObject exportComponent(const ModelNode &node); + QJsonObject exportNode(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; - QString m_iconPath; + int m_remainingIcons = 0; 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 fe687a99ce1..6cb88e5f411 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -2033,10 +2033,10 @@ void DesignerActionManager::createDefaultDesignerActions() QKeySequence(), Priorities::ExportComponent, [&](const SelectionContext &context) { - m_bundleHelper->exportBundle(context.currentSingleSelectedNode()); + m_bundleHelper->exportBundle(context.selectedModelNodes()); }, - &is3DNode, - &is3DNode)); + &are3DNodes, + &are3DNodes)); addDesignerAction(new ModelNodeContextMenuAction( editMaterialCommandId, diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h index 95cf541340b..fbf27083127 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h @@ -80,11 +80,15 @@ inline bool enableAddToContentLib(const SelectionContext &selectionState) return isNode3D && !isInBundle; } -inline bool is3DNode(const SelectionContext &selectionState) +inline bool are3DNodes(const SelectionContext &selectionState) { - ModelNode modelNode = selectionState.currentSingleSelectedNode(); + const QList nodes = selectionState.selectedModelNodes(); + if (nodes.isEmpty()) + return false; - return modelNode.metaInfo().isQtQuick3DNode(); + return std::all_of(nodes.cbegin(), nodes.cend(), [](const ModelNode &node) { + return node.metaInfo().isQtQuick3DNode(); + }); } inline bool hasEditableMaterial(const SelectionContext &selectionState) diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 9029001c5de..219da704d36 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -385,7 +385,7 @@ void Edit3DWidget::createContextMenu() m_exportBundleAction = m_contextMenu->addAction( contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon tr("Export Component"), [&] { - m_bundleHelper->exportBundle(m_contextMenuTarget); + m_bundleHelper->exportBundle(m_view->selectedModelNodes()); }); m_contextMenu->addSeparator(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 9ac95dba432..3e93a0922b4 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -382,10 +382,11 @@ void MaterialBrowserWidget::importMaterial() { m_bundleHelper->importBundleToProject(); } + void MaterialBrowserWidget::exportMaterial() { ModelNode mat = m_materialBrowserModel->selectedMaterial(); - m_bundleHelper->exportBundle(mat, m_previewImageProvider->getPixmap(mat)); + m_bundleHelper->exportBundle({mat}, m_previewImageProvider->getPixmap(mat)); } QString MaterialBrowserWidget::qmlSourcesPath()