forked from qt-creator/qt-creator
QmlDesigner: Allow exporting multiple items to a bundle
Fixes: QDS-13201 Change-Id: Id9a1981b91d9cbc3e98bd21fc01b76d89126c167 Reviewed-by: Shrief Gabr <shrief.gabr@qt.io> Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
@@ -182,7 +182,6 @@ void BundleHelper::importBundleToProject()
|
|||||||
// TODO: before overwriting remove old item's dependencies (not harmful but for cleanup)
|
// TODO: before overwriting remove old item's dependencies (not harmful but for cleanup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add entry to model
|
|
||||||
QStringList files = itemObj.value("files").toVariant().toStringList();
|
QStringList files = itemObj.value("files").toVariant().toStringList();
|
||||||
QString icon = itemObj.value("icon").toString();
|
QString icon = itemObj.value("icon").toString();
|
||||||
|
|
||||||
@@ -207,28 +206,57 @@ void BundleHelper::importBundleToProject()
|
|||||||
zipReader.close();
|
zipReader.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BundleHelper::exportBundle(const ModelNode &node, const QPixmap &iconPixmap)
|
void BundleHelper::exportBundle(const QList<ModelNode> &nodes, const QPixmap &iconPixmap)
|
||||||
{
|
{
|
||||||
if (node.isComponent())
|
QTC_ASSERT(!nodes.isEmpty(), return);
|
||||||
exportComponent(node);
|
|
||||||
else
|
|
||||||
exportNode(node, iconPixmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void BundleHelper::exportComponent(const ModelNode &node)
|
QString exportPath = getExportPath(nodes.at(0));
|
||||||
{
|
|
||||||
QString exportPath = getExportPath(node);
|
|
||||||
if (exportPath.isEmpty())
|
if (exportPath.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_zipWriter = std::make_unique<ZipWriter>(exportPath);
|
m_zipWriter = std::make_unique<ZipWriter>(exportPath);
|
||||||
|
|
||||||
Utils::FilePath compFilePath = componentPath(node);
|
m_tempDir = std::make_unique<QTemporaryDir>();
|
||||||
|
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<ModelNode> 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();
|
Utils::FilePath compDir = compFilePath.parentDir();
|
||||||
QString compBaseName = compFilePath.completeBaseName();
|
QString compBaseName = compFilePath.completeBaseName();
|
||||||
QString compFileName = compFilePath.fileName();
|
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<AssetPath> compDependencies = getComponentDependencies(compFilePath, compDir);
|
const QSet<AssetPath> compDependencies = getComponentDependencies(compFilePath, compDir);
|
||||||
|
|
||||||
@@ -250,43 +278,25 @@ void BundleHelper::exportComponent(const ModelNode &node)
|
|||||||
filesList.append(asset.relativePath);
|
filesList.append(asset.relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the item to the bundle json
|
// add icon
|
||||||
QJsonObject jsonObj;
|
QString filePath = compFilePath.path();
|
||||||
QJsonArray itemsArr;
|
getImageFromCache(filePath, [this, iconPath](const QImage &image) {
|
||||||
itemsArr.append(QJsonObject {
|
addIconAndCloseZip(iconPath, image);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
{"name", node.simplifiedTypeName()},
|
{"name", node.simplifiedTypeName()},
|
||||||
{"qml", compFileName},
|
{"qml", compFileName},
|
||||||
{"icon", m_iconPath},
|
{"icon", iconPath},
|
||||||
{"files", QJsonArray::fromStringList(filesList)}
|
{"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);
|
// tempPath is a temp path for collecting and zipping assets, actual export target is where
|
||||||
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)
|
// the user chose to export (i.e. exportPath)
|
||||||
m_tempDir = std::make_unique<QTemporaryDir>();
|
auto tempPath = Utils::FilePath::fromString(m_tempDir->path());
|
||||||
QTC_ASSERT(m_tempDir->isValid(), return);
|
|
||||||
auto targetPath = Utils::FilePath::fromString(m_tempDir->path());
|
|
||||||
|
|
||||||
m_zipWriter = std::make_unique<ZipWriter>(exportPath);
|
|
||||||
|
|
||||||
QString name = node.variantProperty("objectName").value().toString();
|
QString name = node.variantProperty("objectName").value().toString();
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
@@ -294,7 +304,7 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap)
|
|||||||
|
|
||||||
QString qml = nodeNameToComponentFileName(name);
|
QString qml = nodeNameToComponentFileName(name);
|
||||||
QString iconBaseName = UniqueName::generateId(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
|
// generate and save Qml file
|
||||||
auto [qmlString, depAssets] = modelNodeToQmlString(node);
|
auto [qmlString, depAssets] = modelNodeToQmlString(node);
|
||||||
@@ -304,37 +314,17 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap)
|
|||||||
for (const AssetPath &assetPath : depAssetsList)
|
for (const AssetPath &assetPath : depAssetsList)
|
||||||
depAssetsRelativePaths.append(assetPath.relativePath);
|
depAssetsRelativePaths.append(assetPath.relativePath);
|
||||||
|
|
||||||
auto qmlFilePath = targetPath.pathAppended(qml);
|
auto qmlFilePath = tempPath.pathAppended(qml);
|
||||||
auto result = qmlFilePath.writeFileContents(qmlString.toUtf8());
|
auto result = qmlFilePath.writeFileContents(qmlString.toUtf8());
|
||||||
QTC_ASSERT_EXPECTED(result, return);
|
QTC_ASSERT_EXPECTED(result, return {});
|
||||||
m_zipWriter->addFile(qmlFilePath.fileName(), qmlString.toUtf8());
|
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)
|
// add item's dependency assets to the bundle zip and target path (for icon generation)
|
||||||
for (const AssetPath &assetPath : depAssetsList) {
|
for (const AssetPath &assetPath : depAssetsList) {
|
||||||
QByteArray assetContent = assetPath.fileContent();
|
QByteArray assetContent = assetPath.fileContent();
|
||||||
m_zipWriter->addFile(assetPath.relativePath, assetContent);
|
m_zipWriter->addFile(assetPath.relativePath, assetContent);
|
||||||
|
|
||||||
Utils::FilePath assetTargetPath = targetPath.pathAppended(assetPath.relativePath);
|
Utils::FilePath assetTargetPath = tempPath.pathAppended(assetPath.relativePath);
|
||||||
assetTargetPath.parentDir().ensureWritableDir();
|
assetTargetPath.parentDir().ensureWritableDir();
|
||||||
assetTargetPath.writeFileContents(assetContent);
|
assetTargetPath.writeFileContents(assetContent);
|
||||||
}
|
}
|
||||||
@@ -353,12 +343,19 @@ void BundleHelper::exportNode(const ModelNode &node, const QPixmap &iconPixmap)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (iconPixmapToSave.isNull()) {
|
if (iconPixmapToSave.isNull()) {
|
||||||
getImageFromCache(qmlFilePath.toFSPathString(), [&](const QImage &image) {
|
getImageFromCache(qmlFilePath.toFSPathString(), [this, iconPath](const QImage &image) {
|
||||||
addIconAndCloseZip(image);
|
addIconAndCloseZip(iconPath, image);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
addIconAndCloseZip(iconPixmapToSave);
|
addIconAndCloseZip(iconPath, iconPixmapToSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
{"name", name},
|
||||||
|
{"qml", qml},
|
||||||
|
{"icon", iconPath},
|
||||||
|
{"files", QJsonArray::fromStringList(depAssetsRelativePaths)}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<QString, QSet<AssetPath>> BundleHelper::modelNodeToQmlString(const ModelNode &node, int depth)
|
QPair<QString, QSet<AssetPath>> 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;
|
QByteArray iconByteArray;
|
||||||
QBuffer buffer(&iconByteArray);
|
QBuffer buffer(&iconByteArray);
|
||||||
buffer.open(QIODevice::WriteOnly);
|
buffer.open(QIODevice::WriteOnly);
|
||||||
image.save(&buffer, "PNG");
|
image.save(&buffer, "PNG");
|
||||||
|
|
||||||
m_zipWriter->addFile(m_iconPath, iconByteArray);
|
m_zipWriter->addFile(iconPath, iconByteArray);
|
||||||
m_zipWriter->close();
|
|
||||||
|
if (--m_remainingIcons <= 0)
|
||||||
|
m_zipWriter->close();
|
||||||
};
|
};
|
||||||
|
|
||||||
QString BundleHelper::getImportPath() const
|
QString BundleHelper::getImportPath() const
|
||||||
@@ -689,6 +688,10 @@ QSet<AssetPath> BundleHelper::getComponentDependencies(const Utils::FilePath &fi
|
|||||||
parseNode = [&](const ModelNode &node) {
|
parseNode = [&](const ModelNode &node) {
|
||||||
// workaround node.isComponent() as it is not working here
|
// workaround node.isComponent() as it is not working here
|
||||||
QString nodeType = QString::fromLatin1(node.type());
|
QString nodeType = QString::fromLatin1(node.type());
|
||||||
|
|
||||||
|
#ifdef QDS_USE_PROJECTSTORAGE
|
||||||
|
// TODO
|
||||||
|
#else
|
||||||
if (!nodeType.startsWith("QtQuick")) {
|
if (!nodeType.startsWith("QtQuick")) {
|
||||||
Utils::FilePath compFilPath = getComponentFilePath(nodeType, mainCompDir);
|
Utils::FilePath compFilPath = getComponentFilePath(nodeType, mainCompDir);
|
||||||
if (!compFilPath.isEmpty()) {
|
if (!compFilPath.isEmpty()) {
|
||||||
@@ -705,6 +708,7 @@ QSet<AssetPath> BundleHelper::getComponentDependencies(const Utils::FilePath &fi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const QList<AbstractProperty> nodeProps = node.properties();
|
const QList<AbstractProperty> nodeProps = node.properties();
|
||||||
for (const AbstractProperty &p : nodeProps) {
|
for (const AbstractProperty &p : nodeProps) {
|
||||||
|
@@ -57,7 +57,7 @@ public:
|
|||||||
~BundleHelper();
|
~BundleHelper();
|
||||||
|
|
||||||
void importBundleToProject();
|
void importBundleToProject();
|
||||||
void exportBundle(const ModelNode &node, const QPixmap &iconPixmap = QPixmap());
|
void exportBundle(const QList<ModelNode> &nodes, const QPixmap &iconPixmap = QPixmap());
|
||||||
void getImageFromCache(const QString &qmlPath,
|
void getImageFromCache(const QString &qmlPath,
|
||||||
std::function<void(const QImage &image)> successCallback);
|
std::function<void(const QImage &image)> successCallback);
|
||||||
QString nodeNameToComponentFileName(const QString &name) const;
|
QString nodeNameToComponentFileName(const QString &name) const;
|
||||||
@@ -71,18 +71,18 @@ private:
|
|||||||
QString getExportPath(const ModelNode &node) const;
|
QString getExportPath(const ModelNode &node) const;
|
||||||
bool isMaterialBundle(const QString &bundleId) const;
|
bool isMaterialBundle(const QString &bundleId) const;
|
||||||
bool isItemBundle(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;
|
Utils::FilePath componentPath(const ModelNode &node) const;
|
||||||
QSet<AssetPath> getBundleComponentDependencies(const ModelNode &node) const;
|
QSet<AssetPath> getBundleComponentDependencies(const ModelNode &node) const;
|
||||||
void exportComponent(const ModelNode &node);
|
QJsonObject exportComponent(const ModelNode &node);
|
||||||
void exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap());
|
QJsonObject exportNode(const ModelNode &node, const QPixmap &iconPixmap = QPixmap());
|
||||||
|
|
||||||
QPointer<AbstractView> m_view;
|
QPointer<AbstractView> m_view;
|
||||||
QPointer<QWidget> m_widget;
|
QPointer<QWidget> m_widget;
|
||||||
Utils::UniqueObjectPtr<BundleImporter> m_importer;
|
Utils::UniqueObjectPtr<BundleImporter> m_importer;
|
||||||
std::unique_ptr<ZipWriter> m_zipWriter;
|
std::unique_ptr<ZipWriter> m_zipWriter;
|
||||||
std::unique_ptr<QTemporaryDir> m_tempDir;
|
std::unique_ptr<QTemporaryDir> m_tempDir;
|
||||||
QString m_iconPath;
|
int m_remainingIcons = 0;
|
||||||
|
|
||||||
static constexpr char BUNDLE_VERSION[] = "1.0";
|
static constexpr char BUNDLE_VERSION[] = "1.0";
|
||||||
};
|
};
|
||||||
|
@@ -2033,10 +2033,10 @@ void DesignerActionManager::createDefaultDesignerActions()
|
|||||||
QKeySequence(),
|
QKeySequence(),
|
||||||
Priorities::ExportComponent,
|
Priorities::ExportComponent,
|
||||||
[&](const SelectionContext &context) {
|
[&](const SelectionContext &context) {
|
||||||
m_bundleHelper->exportBundle(context.currentSingleSelectedNode());
|
m_bundleHelper->exportBundle(context.selectedModelNodes());
|
||||||
},
|
},
|
||||||
&is3DNode,
|
&are3DNodes,
|
||||||
&is3DNode));
|
&are3DNodes));
|
||||||
|
|
||||||
addDesignerAction(new ModelNodeContextMenuAction(
|
addDesignerAction(new ModelNodeContextMenuAction(
|
||||||
editMaterialCommandId,
|
editMaterialCommandId,
|
||||||
|
@@ -80,11 +80,15 @@ inline bool enableAddToContentLib(const SelectionContext &selectionState)
|
|||||||
return isNode3D && !isInBundle;
|
return isNode3D && !isInBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is3DNode(const SelectionContext &selectionState)
|
inline bool are3DNodes(const SelectionContext &selectionState)
|
||||||
{
|
{
|
||||||
ModelNode modelNode = selectionState.currentSingleSelectedNode();
|
const QList<ModelNode> 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)
|
inline bool hasEditableMaterial(const SelectionContext &selectionState)
|
||||||
|
@@ -385,7 +385,7 @@ void Edit3DWidget::createContextMenu()
|
|||||||
m_exportBundleAction = m_contextMenu->addAction(
|
m_exportBundleAction = m_contextMenu->addAction(
|
||||||
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
||||||
tr("Export Component"), [&] {
|
tr("Export Component"), [&] {
|
||||||
m_bundleHelper->exportBundle(m_contextMenuTarget);
|
m_bundleHelper->exportBundle(m_view->selectedModelNodes());
|
||||||
});
|
});
|
||||||
|
|
||||||
m_contextMenu->addSeparator();
|
m_contextMenu->addSeparator();
|
||||||
|
@@ -382,10 +382,11 @@ void MaterialBrowserWidget::importMaterial()
|
|||||||
{
|
{
|
||||||
m_bundleHelper->importBundleToProject();
|
m_bundleHelper->importBundleToProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialBrowserWidget::exportMaterial()
|
void MaterialBrowserWidget::exportMaterial()
|
||||||
{
|
{
|
||||||
ModelNode mat = m_materialBrowserModel->selectedMaterial();
|
ModelNode mat = m_materialBrowserModel->selectedMaterial();
|
||||||
m_bundleHelper->exportBundle(mat, m_previewImageProvider->getPixmap(mat));
|
m_bundleHelper->exportBundle({mat}, m_previewImageProvider->getPixmap(mat));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MaterialBrowserWidget::qmlSourcesPath()
|
QString MaterialBrowserWidget::qmlSourcesPath()
|
||||||
|
Reference in New Issue
Block a user