From 54879113f17a1f5446564de1f5bea6980940a20e Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Mon, 13 May 2024 16:29:27 +0300 Subject: [PATCH] QmlDesigner: Ensure unique content lib user item icon names Also update user materials model to have the improvements done to the 3D model. Also trying to make the 2 mdoels as similar as possible to make it easy for further future refactoring. Change-Id: I5a32e1dcd7919bdf3cb638b068b0cdb5d4afecd9 Fixes: QDS-12736 Reviewed-by: Miikka Heikkinen --- .../contentlibraryusermodel.cpp | 193 +++++++++--------- .../contentlibrary/contentlibraryusermodel.h | 7 +- .../contentlibrary/contentlibraryview.cpp | 47 ++--- .../contentlibrary/contentlibraryview.h | 2 +- 4 files changed, 120 insertions(+), 129 deletions(-) diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp index 3e40b9c12c5..bb732080d7e 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp @@ -186,42 +186,48 @@ void ContentLibraryUserModel::removeFromContentLib(QObject *item) remove3DFromContentLib(itm); } -void ContentLibraryUserModel::removeMaterialFromContentLib(ContentLibraryMaterial *mat) +void ContentLibraryUserModel::removeMaterialFromContentLib(ContentLibraryMaterial *item) { auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/"); - QJsonObject matsObj = m_bundleObjMaterial.value("materials").toObject(); + QJsonArray itemsArr = m_bundleObjMaterial.value("items").toArray(); // remove qml and icon files - Utils::FilePath::fromString(mat->qmlFilePath()).removeFile(); - Utils::FilePath::fromUrl(mat->icon()).removeFile(); + Utils::FilePath::fromString(item->qmlFilePath()).removeFile(); + Utils::FilePath::fromUrl(item->icon()).removeFile(); // remove from the bundle json file - matsObj.remove(mat->name()); - m_bundleObjMaterial.insert("materials", matsObj); - auto result = bundlePath.pathAppended("user_materials_bundle.json") + for (int i = 0; i < itemsArr.size(); ++i) { + if (itemsArr.at(i).toObject().value("qml") == item->qml()) { + itemsArr.removeAt(i); + break; + } + } + m_bundleObjMaterial.insert("items", itemsArr); + + auto result = bundlePath.pathAppended("user_material_bundle.json") .writeFileContents(QJsonDocument(m_bundleObjMaterial).toJson()); if (!result) qWarning() << __FUNCTION__ << result.error(); // delete dependency files if they are only used by the deleted material QStringList allFiles; - for (const QJsonValueConstRef &mat : std::as_const(matsObj)) - allFiles.append(mat.toObject().value("files").toVariant().toStringList()); + for (const QJsonValueConstRef &itemRef : std::as_const(itemsArr)) + allFiles.append(itemRef.toObject().value("files").toVariant().toStringList()); - const QStringList matFiles = mat->files(); - for (const QString &matFile : matFiles) { - if (allFiles.count(matFile) == 0) // only used by the deleted material - bundlePath.pathAppended(matFile).removeFile(); + const QStringList itemFiles = item->files(); + for (const QString &file : itemFiles) { + if (allFiles.count(file) == 0) // only used by the deleted item + bundlePath.pathAppended(file).removeFile(); } // remove from model - m_userMaterials.removeOne(mat); - mat->deleteLater(); + m_userMaterials.removeOne(item); + item->deleteLater(); // update model - int matSectionIdx = 0; - emit dataChanged(index(matSectionIdx), index(matSectionIdx)); + int sectionIdx = 0; + emit dataChanged(index(sectionIdx), index(sectionIdx)); } void ContentLibraryUserModel::remove3DFromContentLib(ContentLibraryItem *item) @@ -266,51 +272,41 @@ void ContentLibraryUserModel::remove3DFromContentLib(ContentLibraryItem *item) emit dataChanged(index(sectionIdx), index(sectionIdx)); } -// returns unique library material's name and qml component -QPair ContentLibraryUserModel::getUniqueLibMaterialNameAndQml(const QString &defaultName) const +/** + * @brief Gets unique Qml component and icon file material names from a given name + * @param defaultName input name + * @return file names + */ +QPair ContentLibraryUserModel::getUniqueLibMaterialNames(const QString &defaultName) const { - QTC_ASSERT(!m_bundleObjMaterial.isEmpty(), return {}); - - const QJsonObject matsObj = m_bundleObjMaterial.value("materials").toObject(); - const QStringList matNames = matsObj.keys(); - - QStringList matQmls; - for (const QString &matName : matNames) - matQmls.append(matsObj.value(matName).toObject().value("qml").toString().chopped(4)); // remove .qml - - QString retName = defaultName.isEmpty() ? "Material" : defaultName.trimmed(); - QString retQml = retName; - - retQml.remove(' '); - if (retQml.at(0).isLower()) - retQml[0] = retQml.at(0).toUpper(); - retQml.prepend("My"); - - int num = 1; - if (matNames.contains(retName) || matQmls.contains(retQml)) { - while (matNames.contains(retName + QString::number(num)) - || matQmls.contains(retQml + QString::number(num))) { - ++num; - } - - retName += QString::number(num); - retQml += QString::number(num); - } - - return {retName, retQml + ".qml"}; + return getUniqueLibItemNames(defaultName, m_bundleObjMaterial); } -QString ContentLibraryUserModel::getUniqueLib3DQmlName(const QString &defaultName) const +/** + * @brief Gets unique Qml component and icon file 3d item names from a given name + * @param defaultName input name + * @return file names + */ +QPair ContentLibraryUserModel::getUniqueLib3DNames(const QString &defaultName) const { - QTC_ASSERT(!m_bundleObj3D.isEmpty(), return {}); + return getUniqueLibItemNames(defaultName, m_bundleObj3D); +} - const QJsonArray itemsArr = m_bundleObj3D.value("items").toArray(); +QPair ContentLibraryUserModel::getUniqueLibItemNames(const QString &defaultName, + const QJsonObject &bundleObj) const +{ + QTC_ASSERT(!bundleObj.isEmpty(), return {}); - QStringList itemQmls; - for (const QJsonValueConstRef &itemRef : itemsArr) - itemQmls.append(itemRef.toObject().value("qml").toString().chopped(4)); // remove .qml + const QJsonArray itemsArr = bundleObj.value("items").toArray(); - QString baseQml = defaultName.isEmpty() ? "Item" : defaultName.trimmed(); + QStringList itemQmls, itemIcons; + for (const QJsonValueConstRef &itemRef : itemsArr) { + const QJsonObject &obj = itemRef.toObject(); + itemQmls.append(obj.value("qml").toString().chopped(4)); // remove .qml + itemIcons.append(QFileInfo(obj.value("icon").toString()).baseName()); + } + + QString baseQml = defaultName.trimmed(); baseQml.remove(' '); baseQml[0] = baseQml.at(0).toUpper(); baseQml.prepend("My"); @@ -319,7 +315,11 @@ QString ContentLibraryUserModel::getUniqueLib3DQmlName(const QString &defaultNam return !itemQmls.contains(name); }); - return uniqueQml + ".qml"; + QString uniqueIcon = UniqueName::get(defaultName, [&] (const QString &name) { + return !itemIcons.contains(name); + }); + + return {uniqueQml + ".qml", uniqueIcon + ".png"}; } QHash ContentLibraryUserModel::roleNames() const @@ -364,59 +364,61 @@ void ContentLibraryUserModel::loadMaterialBundle() m_bundleObjMaterial = {}; m_bundleIdMaterial.clear(); - int matSectionIdx = 0; + int sectionIdx = 0; - QDir bundleDir{Paths::bundlesPathSetting() + "/User/materials"}; - bundleDir.mkpath("."); + m_bundlePathMaterial = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials"); + m_bundlePathMaterial.ensureWritableDir(); + m_bundlePathMaterial.pathAppended("icons").ensureWritableDir(); - auto jsonFilePath = Utils::FilePath::fromString(bundleDir.filePath("user_materials_bundle.json")); + auto jsonFilePath = m_bundlePathMaterial.pathAppended("user_materials_bundle.json"); if (!jsonFilePath.exists()) { QString jsonContent = "{\n"; jsonContent += " \"id\": \"UserMaterials\",\n"; - jsonContent += " \"materials\": {\n"; - jsonContent += " }\n"; + jsonContent += " \"items\": []\n"; jsonContent += "}"; - jsonFilePath.writeFileContents(jsonContent.toLatin1()); + Utils::expected_str res = jsonFilePath.writeFileContents(jsonContent.toLatin1()); + if (!res.has_value()) { + qWarning() << __FUNCTION__ << res.error(); + emit dataChanged(index(sectionIdx), index(sectionIdx)); + return; + } } - QFile jsonFile(jsonFilePath.path()); - if (!jsonFile.open(QIODevice::ReadOnly)) { - qWarning() << __FUNCTION__ << "Couldn't open user_materials_bundle.json"; - emit dataChanged(index(matSectionIdx), index(matSectionIdx)); + Utils::expected_str jsonContents = jsonFilePath.fileContents(); + if (!jsonContents.has_value()) { + qWarning() << __FUNCTION__ << jsonContents.error(); + emit dataChanged(index(sectionIdx), index(sectionIdx)); return; } - QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(jsonFile.readAll()); - if (matBundleJsonDoc.isNull()) { + QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value()); + if (bundleJsonDoc.isNull()) { qWarning() << __FUNCTION__ << "Invalid user_materials_bundle.json file"; - emit dataChanged(index(matSectionIdx), index(matSectionIdx)); + emit dataChanged(index(sectionIdx), index(sectionIdx)); return; } - m_bundleObjMaterial = matBundleJsonDoc.object(); - m_bundleObjMaterial["id"] = compUtils.userMaterialsBundleId(); m_bundleIdMaterial = compUtils.userMaterialsBundleId(); + m_bundleObjMaterial = bundleJsonDoc.object(); + m_bundleObjMaterial["id"] = m_bundleIdMaterial; - // parse materials - const QJsonObject matsObj = m_bundleObjMaterial.value("materials").toObject(); - const QStringList materialNames = matsObj.keys(); + // parse items QString typePrefix = compUtils.userMaterialsBundleType(); - for (const QString &matName : materialNames) { - const QJsonObject matObj = matsObj.value(matName).toObject(); + const QJsonArray itemsArr = m_bundleObj3D.value("items").toArray(); + for (const QJsonValueConstRef &itemRef : itemsArr) { + const QJsonObject itemObj = itemRef.toObject(); + QString name = itemObj.value("name").toString(); + QString qml = itemObj.value("qml").toString(); + TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); + QUrl icon = m_bundlePathMaterial.pathAppended(itemObj.value("icon").toString()).toUrl(); QStringList files; - const QJsonArray assetsArr = matObj.value("files").toArray(); + const QJsonArray assetsArr = itemObj.value("files").toArray(); for (const QJsonValueConstRef &asset : assetsArr) files.append(asset.toString()); - QUrl icon = QUrl::fromLocalFile(bundleDir.filePath(matObj.value("icon").toString())); - QString qml = matObj.value("qml").toString(); - TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); - - auto userMat = new ContentLibraryMaterial(this, matName, qml, type, icon, files, - bundleDir.path(), ""); - - m_userMaterials.append(userMat); + m_userMaterials.append(new ContentLibraryMaterial(this, name, qml, type, icon, files, + m_bundlePathMaterial.path(), "")); } m_bundleMaterialSharedFiles.clear(); @@ -424,13 +426,9 @@ void ContentLibraryUserModel::loadMaterialBundle() for (const QJsonValueConstRef &file : sharedFilesArr) m_bundleMaterialSharedFiles.append(file.toString()); - m_matBundleExists = true; - emit matBundleExistsChanged(); - emit dataChanged(index(matSectionIdx), index(matSectionIdx)); - m_matBundleExists = true; updateIsEmptyMaterials(); - resetModel(); + emit dataChanged(index(sectionIdx), index(sectionIdx)); } void ContentLibraryUserModel::load3DBundle() @@ -448,14 +446,13 @@ void ContentLibraryUserModel::load3DBundle() m_bundleObj3D = {}; m_bundleId3D.clear(); - int section3DIdx = 2; + int sectionIdx = 2; m_bundlePath3D = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d"); m_bundlePath3D.ensureWritableDir(); m_bundlePath3D.pathAppended("icons").ensureWritableDir(); auto jsonFilePath = m_bundlePath3D.pathAppended("user_3d_bundle.json"); - if (!jsonFilePath.exists()) { QByteArray jsonContent = "{\n"; jsonContent += " \"id\": \"User3D\",\n"; @@ -464,7 +461,7 @@ void ContentLibraryUserModel::load3DBundle() Utils::expected_str res = jsonFilePath.writeFileContents(jsonContent); if (!res.has_value()) { qWarning() << __FUNCTION__ << res.error(); - emit dataChanged(index(section3DIdx), index(section3DIdx)); + emit dataChanged(index(sectionIdx), index(sectionIdx)); return; } } @@ -472,14 +469,14 @@ void ContentLibraryUserModel::load3DBundle() Utils::expected_str jsonContents = jsonFilePath.fileContents(); if (!jsonContents.has_value()) { qWarning() << __FUNCTION__ << jsonContents.error(); - emit dataChanged(index(section3DIdx), index(section3DIdx)); + emit dataChanged(index(sectionIdx), index(sectionIdx)); return; } QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value()); if (bundleJsonDoc.isNull()) { qWarning() << __FUNCTION__ << "Invalid user_3d_bundle.json file"; - emit dataChanged(index(section3DIdx), index(section3DIdx)); + emit dataChanged(index(sectionIdx), index(sectionIdx)); return; } @@ -487,7 +484,7 @@ void ContentLibraryUserModel::load3DBundle() m_bundleObj3D = bundleJsonDoc.object(); m_bundleObj3D["id"] = m_bundleId3D; - // parse 3d items + // parse items QString typePrefix = compUtils.user3DBundleType(); const QJsonArray itemsArr = m_bundleObj3D.value("items").toArray(); for (const QJsonValueConstRef &itemRef : itemsArr) { @@ -512,7 +509,7 @@ void ContentLibraryUserModel::load3DBundle() m_bundle3DExists = true; updateIsEmpty3D(); - emit dataChanged(index(section3DIdx), index(section3DIdx)); + emit dataChanged(index(sectionIdx), index(sectionIdx)); } void ContentLibraryUserModel::loadTextureBundle() diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h index 7af5e861749..e38e84e5c08 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h @@ -43,8 +43,8 @@ public: void updateMaterialsImportedState(const QStringList &importedItems); void update3DImportedState(const QStringList &importedItems); - QPair getUniqueLibMaterialNameAndQml(const QString &defaultName = {}) const; - QString getUniqueLib3DQmlName(const QString &defaultName = {}) const; + QPair getUniqueLibMaterialNames(const QString &defaultName = "Material") const; + QPair getUniqueLib3DNames(const QString &defaultName = "Item") const; void setQuick3DImportVersion(int major, int minor); @@ -94,6 +94,8 @@ private: bool isValidIndex(int idx) const; void removeMaterialFromContentLib(ContentLibraryMaterial *mat); void remove3DFromContentLib(ContentLibraryItem *item); + QPair getUniqueLibItemNames(const QString &defaultName, + const QJsonObject &bundleObj) const; ContentLibraryWidget *m_widget = nullptr; QString m_searchText; @@ -101,6 +103,7 @@ private: QString m_bundleId3D; QStringList m_bundleMaterialSharedFiles; QStringList m_bundle3DSharedFiles; + Utils::FilePath m_bundlePathMaterial; Utils::FilePath m_bundlePath3D; QList m_userMaterials; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 5d7efdf2c93..2f32a36b973 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -496,41 +496,39 @@ void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundle #endif // Add a project material to Content Library's user tab -void ContentLibraryView::addLibMaterial(const ModelNode &mat, const QPixmap &icon) +void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &iconPixmap) { auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/"); - auto [name, qml] = m_widget->userModel()->getUniqueLibMaterialNameAndQml( - mat.variantProperty("objectName").value().toString()); + QString name = node.variantProperty("objectName").value().toString(); + auto [qml, icon] = m_widget->userModel()->getUniqueLibMaterialNames(node.id()); - bundlePath.pathAppended("icons").createDir(); - bundlePath.pathAppended("images").createDir(); - bundlePath.pathAppended("shaders").createDir(); - - QString iconPath = QLatin1String("icons/%1.png").arg(mat.id()); + QString iconPath = QLatin1String("icons/%1").arg(icon); QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); // save icon - bool iconSaved = icon.save(fullIconPath); + bool iconSaved = iconPixmap.save(fullIconPath); if (!iconSaved) qWarning() << __FUNCTION__ << "icon save failed"; // generate and save material Qml file - const QStringList depAssets = writeLibItemQml(mat, qml); + const QStringList depAssets = writeLibItemQml(node, qml); // add the material to the bundle json QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef(); - QJsonObject matsObj = jsonRef.value("materials").toObject(); - QJsonObject matObj; - matObj.insert("qml", qml); - matObj.insert("icon", iconPath); + QJsonArray itemsArr = jsonRef.value("items").toArray(); + QJsonObject itemObj; + itemObj.insert("name", name); + itemObj.insert("qml", qml); + itemObj.insert("icon", iconPath); QJsonArray filesArr; for (const QString &assetPath : depAssets) filesArr.append(assetPath); - matObj.insert("files", filesArr); + itemObj.insert("files", filesArr); + + itemsArr.append(itemObj); + jsonRef["items"] = itemsArr; - matsObj.insert(name, matObj); - jsonRef.insert("materials", matsObj); auto result = bundlePath.pathAppended("user_materials_bundle.json") .writeFileContents(QJsonDocument(jsonRef).toJson()); if (!result) @@ -538,16 +536,9 @@ void ContentLibraryView::addLibMaterial(const ModelNode &mat, const QPixmap &ico // copy material assets to bundle folder for (const QString &assetPath : depAssets) { - Asset asset(assetPath); - QString subDir; - if (asset.isImage()) - subDir = "images"; - else if (asset.isShader()) - subDir = "shaders"; - Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath); - Utils::FilePath assetPathTarget = bundlePath.pathAppended(QString("%1/%2") - .arg(subDir, "/" + asset.fileName())); + Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath); + assetPathTarget.parentDir().ensureWritableDir(); auto result = assetPathSource.copyFile(assetPathTarget); if (!result) @@ -667,8 +658,8 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node) auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/"); QString name = node.variantProperty("objectName").value().toString(); - QString qml = m_widget->userModel()->getUniqueLib3DQmlName(node.id()); - QString iconPath = QLatin1String("icons/%1.png").arg(node.id()); // TODO: make sure path is unique + auto [qml, icon] = m_widget->userModel()->getUniqueLib3DNames(node.id()); + QString iconPath = QLatin1String("icons/%1").arg(icon); // generate and save item Qml file const QStringList depAssets = writeLibItemQml(node, qml); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index 2891960f337..2041cc37a11 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -55,7 +55,7 @@ private: bool isItemBundle(const QString &bundleId) const; void active3DSceneChanged(qint32 sceneId); void updateBundlesQuick3DVersion(); - void addLibMaterial(const ModelNode &mat, const QPixmap &icon); + void addLibMaterial(const ModelNode &node, const QPixmap &iconPixmap); void addLibAssets(const QStringList &paths); void addLib3DItem(const ModelNode &node); void genAndSaveIcon(const QString &qmlPath, const QString &iconPath);