diff options
19 files changed, 578 insertions, 470 deletions
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml index 6f505124300..a2e72335e9f 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml @@ -23,7 +23,7 @@ StudioControls.Menu { { root.targetItem = item - let isMaterial = root.targetItem.itemType === "material" + let isMaterial = root.targetItem.bundleId === "UserMaterials" applyToSelectedReplace.visible = isMaterial applyToSelectedAdd.visible = isMaterial diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml index d93895ec554..f0aa94bba87 100644 --- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml +++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml @@ -80,8 +80,8 @@ HelperWidgets.ScrollView { topPadding: StudioTheme.Values.sectionPadding bottomPadding: StudioTheme.Values.sectionPadding - caption: categoryName - visible: categoryVisible && infoText.text === "" + caption: categoryTitle + visible: !categoryEmpty && infoText.text === "" category: "ContentLib_User" function expandSection() { @@ -102,10 +102,10 @@ HelperWidgets.ScrollView { model: categoryItems delegate: DelegateChooser { - role: "itemType" + role: "bundleId" DelegateChoice { - roleValue: "material" + roleValue: "UserMaterials" ContentLibraryItem { width: root.cellWidth height: root.cellHeight @@ -115,7 +115,7 @@ HelperWidgets.ScrollView { } } DelegateChoice { - roleValue: "texture" + roleValue: "UserTextures" delegate: ContentLibraryTexture { width: root.cellWidth height: root.cellWidth // for textures use a square size since there is no name row @@ -124,7 +124,7 @@ HelperWidgets.ScrollView { } } DelegateChoice { - roleValue: "3d" + roleValue: "User3D" delegate: ContentLibraryItem { width: root.cellWidth height: root.cellHeight diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 1ee5b7ca139..add610e0268 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -839,6 +839,9 @@ extend_qtc_plugin(QmlDesigner contentlibraryeffectscategory.cpp contentlibraryeffectscategory.h contentlibraryeffectsmodel.cpp contentlibraryeffectsmodel.h contentlibraryusermodel.cpp contentlibraryusermodel.h + usercategory.cpp usercategory.h + useritemcategory.cpp useritemcategory.h + usertexturecategory.cpp usertexturecategory.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp index d8bcdf5b4a2..ac4a75a293d 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp @@ -171,7 +171,7 @@ void ContentLibraryEffectsModel::loadBundle() TypeName type = QLatin1String("%1.%2") .arg(bundleType, qml.chopped(4)).toLatin1(); // chopped(4): remove .qml - auto bundleItem = new ContentLibraryItem(category, itemName, qml, type, icon, files, "effect"); + auto bundleItem = new ContentLibraryItem(category, itemName, qml, type, icon, files, m_bundleId); category->addBundleItem(bundleItem); } diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp index 6ab26885d4f..010cc065a53 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp @@ -11,9 +11,9 @@ ContentLibraryItem::ContentLibraryItem(QObject *parent, const TypeName &type, const QUrl &icon, const QStringList &files, - const QString &itemType) + const QString &bundleId) : QObject(parent), m_name(name), m_qml(qml), m_type(type), m_icon(icon), m_files(files), - m_itemType(itemType) + m_bundleId(bundleId) { m_allFiles = m_files; m_allFiles.push_back(m_qml); @@ -75,9 +75,9 @@ QStringList ContentLibraryItem::allFiles() const return m_allFiles; } -QString ContentLibraryItem::itemType() const +QString ContentLibraryItem::bundleId() const { - return m_itemType; + return m_bundleId; } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h index b2610953ae8..36fd9464c08 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h @@ -19,7 +19,7 @@ class ContentLibraryItem : public QObject Q_PROPERTY(QStringList bundleItemFiles READ allFiles CONSTANT) Q_PROPERTY(bool bundleItemVisible MEMBER m_visible NOTIFY itemVisibleChanged) Q_PROPERTY(bool bundleItemImported READ imported WRITE setImported NOTIFY itemImportedChanged) - Q_PROPERTY(QString itemType READ itemType CONSTANT) + Q_PROPERTY(QString bundleId READ bundleId CONSTANT) public: ContentLibraryItem(QObject *parent, @@ -28,7 +28,7 @@ public: const TypeName &type, const QUrl &icon, const QStringList &files, - const QString &itemType); + const QString &bundleId); bool filter(const QString &searchText); @@ -38,7 +38,7 @@ public: QStringList files() const; bool visible() const; - QString itemType() const; + QString bundleId() const; bool setImported(bool imported); bool imported() const; @@ -59,7 +59,7 @@ private: bool m_imported = false; QStringList m_allFiles; - const QString m_itemType; + const QString m_bundleId; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h index 7f5db6d7d60..aa05667a1cd 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h @@ -24,7 +24,7 @@ class ContentLibraryTexture : public QObject Q_PROPERTY(bool textureHasUpdate WRITE setHasUpdate READ hasUpdate NOTIFY hasUpdateChanged) Q_PROPERTY(bool textureIsNew MEMBER m_isNew CONSTANT) Q_PROPERTY(QString textureKey MEMBER m_textureKey CONSTANT) - Q_PROPERTY(QString itemType MEMBER m_itemType CONSTANT) + Q_PROPERTY(QString bundleId MEMBER m_bundleId CONSTANT) public: ContentLibraryTexture(QObject *parent, const QFileInfo &iconFileInfo, const QString &dirPath, @@ -74,7 +74,7 @@ private: bool m_visible = true; bool m_hasUpdate = false; bool m_isNew = false; - const QString m_itemType = "texture"; + const QString m_bundleId = "UserTextures"; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp index 65489dc9c05..030e2022c6b 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "contentlibraryusermodel.h" +#include "useritemcategory.h" +#include "usertexturecategory.h" #include "contentlibrarybundleimporter.h" #include "contentlibraryitem.h" @@ -28,6 +30,7 @@ ContentLibraryUserModel::ContentLibraryUserModel(ContentLibraryWidget *parent) : QAbstractListModel(parent) , m_widget(parent) { + createCategories(); } int ContentLibraryUserModel::rowCount(const QModelIndex &) const @@ -40,119 +43,73 @@ QVariant ContentLibraryUserModel::data(const QModelIndex &index, int role) const QTC_ASSERT(index.isValid() && index.row() < m_userCategories.size(), return {}); QTC_ASSERT(roleNames().contains(role), return {}); - if (role == NameRole) - return m_userCategories.at(index.row()); - - if (role == ItemsRole) { - if (index.row() == MaterialsSectionIdx) - return QVariant::fromValue(m_userMaterials); - if (index.row() == TexturesSectionIdx) - return QVariant::fromValue(m_userTextures); - if (index.row() == Items3DSectionIdx) - return QVariant::fromValue(m_user3DItems); - if (index.row() == EffectsSectionIdx) - return QVariant::fromValue(m_userEffects); - } + UserCategory *currCat = m_userCategories.at(index.row()); - if (role == NoMatchRole) { - if (index.row() == MaterialsSectionIdx) - return m_noMatchMaterials; - if (index.row() == TexturesSectionIdx) - return m_noMatchTextures; - if (index.row() == Items3DSectionIdx) - return m_noMatch3D; - if (index.row() == EffectsSectionIdx) - return m_noMatchEffects; - } + if (role == TitleRole) + return currCat->title(); - if (role == VisibleRole) { - if (index.row() == MaterialsSectionIdx) - return !m_userMaterials.isEmpty(); - if (index.row() == TexturesSectionIdx) - return !m_userTextures.isEmpty(); - if (index.row() == Items3DSectionIdx) - return !m_user3DItems.isEmpty(); - if (index.row() == EffectsSectionIdx) - return !m_userEffects.isEmpty(); - } + if (role == ItemsRole) + return QVariant::fromValue(currCat->items()); - return {}; -} + if (role == NoMatchRole) + return currCat->noMatch(); -void ContentLibraryUserModel::updateNoMatchMaterials() -{ - m_noMatchMaterials = Utils::allOf(m_userMaterials, [&](ContentLibraryItem *item) { - return !item->visible(); - }); -} + if (role == EmptyRole) + return currCat->isEmpty(); -void ContentLibraryUserModel::updateNoMatchTextures() -{ - m_noMatchTextures = Utils::allOf(m_userTextures, [&](ContentLibraryTexture *item) { - return !item->visible(); - }); + return {}; } -void ContentLibraryUserModel::updateNoMatch3D() +void ContentLibraryUserModel::createCategories() { - m_noMatch3D = Utils::allOf(m_user3DItems, [&](ContentLibraryItem *item) { - return !item->visible(); - }); -} + QTC_ASSERT(m_userCategories.isEmpty(), return); -void ContentLibraryUserModel::addMaterial(const QString &name, const QString &qml, - const QUrl &icon, const QStringList &files) -{ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + auto userBundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User"); - QString typePrefix = compUtils.userMaterialsBundleType(); - TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); + auto catMaterial = new UserItemCategory{tr("Materials"), + userBundlePath.pathAppended("materials"), + compUtils.userMaterialsBundleId()}; - auto libMat = new ContentLibraryItem(this, name, qml, type, icon, files, "material"); - m_userMaterials.append(libMat); + auto catTexture = new UserTextureCategory{tr("Textures"), userBundlePath.pathAppended("textures")}; - emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); + auto cat3D = new UserItemCategory{tr("3D"), userBundlePath.pathAppended("3d"), + compUtils.user3DBundleId()}; - updateIsEmpty(); + m_userCategories << catMaterial << catTexture << cat3D; } -void ContentLibraryUserModel::add3DItem(const QString &name, const QString &qml, - const QUrl &icon, const QStringList &files) +void ContentLibraryUserModel::addItem(const QString &bundleId, const QString &name, + const QString &qml, const QUrl &icon, const QStringList &files) { auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - QString typePrefix = compUtils.user3DBundleType(); + QString typePrefix = compUtils.userBundleType(bundleId); TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); - m_user3DItems.append(new ContentLibraryItem(this, name, qml, type, icon, files, "3d")); + SectionIndex sectionIndex = bundleIdToSectionIndex(bundleId); + + UserCategory *cat = m_userCategories[sectionIndex]; + cat->addItem(new ContentLibraryItem(cat, name, qml, type, icon, files, bundleId)); + emit dataChanged(index(sectionIndex), index(sectionIndex), {ItemsRole, EmptyRole}); + updateIsEmpty(); } -void ContentLibraryUserModel::refreshSection(SectionIndex sectionIndex) +void ContentLibraryUserModel::refreshSection(const QString &bundleId) { - emit dataChanged(index(sectionIndex), index(sectionIndex)); + SectionIndex sectionIdx = bundleIdToSectionIndex(bundleId); + emit dataChanged(index(sectionIdx), index(sectionIdx), {ItemsRole, EmptyRole}); updateIsEmpty(); } -void ContentLibraryUserModel::addTextures(const QStringList &paths) +void ContentLibraryUserModel::addTextures(const Utils::FilePaths &paths) { - QDir bundleDir{Paths::bundlesPathSetting() + "/User/textures"}; - bundleDir.mkpath("."); - bundleDir.mkdir("icons"); - - for (const QString &path : paths) { - QFileInfo fileInfo(path); - QString suffix = '.' + fileInfo.suffix(); - auto iconFileInfo = QFileInfo(fileInfo.path().append("/icons/").append(fileInfo.baseName() + ".png")); - QPair<QSize, qint64> info = ImageUtils::imageInfo(path); - QString dirPath = fileInfo.path(); - QSize imgDims = info.first; - qint64 imgFileSize = info.second; - - auto tex = new ContentLibraryTexture(this, iconFileInfo, dirPath, suffix, imgDims, imgFileSize); - m_userTextures.append(tex); - } + auto texCat = qobject_cast<UserTextureCategory *>(m_userCategories[TexturesSectionIdx]); + QTC_ASSERT(texCat, return); - emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx)); + texCat->addItems(paths); + + emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx), {ItemsRole, EmptyRole}); } void ContentLibraryUserModel::removeTexture(ContentLibraryTexture *tex) @@ -162,8 +119,7 @@ void ContentLibraryUserModel::removeTexture(ContentLibraryTexture *tex) Utils::FilePath::fromString(tex->iconPath()).removeFile(); // remove from model - m_userTextures.removeOne(tex); - tex->deleteLater(); + m_userCategories[TexturesSectionIdx]->removeItem(tex); // update model emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx)); @@ -178,23 +134,20 @@ void ContentLibraryUserModel::removeFromContentLib(QObject *item) removeItem(castedItem); } -void ContentLibraryUserModel::remove3DFromContentLibByName(const QString &qmlFileName) +void ContentLibraryUserModel::removeItemByName(const QString &qmlFileName, const QString &bundleId) { - ContentLibraryItem *itemToRemove = Utils::findOr(m_user3DItems, nullptr, - [&qmlFileName](ContentLibraryItem *item) { - return item->qml() == qmlFileName; - }); + ContentLibraryItem *itemToRemove = nullptr; + const QObjectList items = m_userCategories[bundleIdToSectionIndex(bundleId)]->items(); - if (itemToRemove) - removeItem(itemToRemove); -} + for (QObject *item : items) { + ContentLibraryItem *castedItem = qobject_cast<ContentLibraryItem *>(item); + QTC_ASSERT(castedItem, return); -void ContentLibraryUserModel::removeMaterialFromContentLibByName(const QString &qmlFileName) -{ - ContentLibraryItem *itemToRemove = Utils::findOr(m_userMaterials, nullptr, - [&qmlFileName](ContentLibraryItem *item) { - return item->qml() == qmlFileName; - }); + if (castedItem->qml() == qmlFileName) { + itemToRemove = castedItem; + break; + } + } if (itemToRemove) removeItem(itemToRemove); @@ -202,30 +155,16 @@ void ContentLibraryUserModel::removeMaterialFromContentLibByName(const QString & void ContentLibraryUserModel::removeItem(ContentLibraryItem *item) { - Utils::FilePath *bundlePath = nullptr; - QJsonObject *bundleObj = nullptr; - QList<ContentLibraryItem *> *userItems = nullptr; - SectionIndex sectionIdx; - - if (item->itemType() == "material") { - bundlePath = &m_bundlePathMaterial; - bundleObj = &m_bundleObjMaterial; - userItems = &m_userMaterials; - sectionIdx = MaterialsSectionIdx; - } else if (item->itemType() == "3d") { - bundlePath = &m_bundlePath3D; - bundleObj = &m_bundleObj3D; - userItems = &m_user3DItems; - sectionIdx = Items3DSectionIdx; - } else { - qWarning() << __FUNCTION__ << "Unsupported item"; - return; - } + UserItemCategory *itemCat = qobject_cast<UserItemCategory *>(item->parent()); + QTC_ASSERT(itemCat, return); - QJsonArray itemsArr = bundleObj->value("items").toArray(); + Utils::FilePath bundlePath = itemCat->bundlePath(); + QJsonObject &bundleObj = itemCat->bundleObjRef(); + + QJsonArray itemsArr = bundleObj.value("items").toArray(); // remove qml and icon files - bundlePath->pathAppended(item->qml()).removeFile(); + bundlePath.pathAppended(item->qml()).removeFile(); Utils::FilePath::fromUrl(item->icon()).removeFile(); // remove from the bundle json file @@ -235,10 +174,10 @@ void ContentLibraryUserModel::removeItem(ContentLibraryItem *item) break; } } - bundleObj->insert("items", itemsArr); + bundleObj.insert("items", itemsArr); - auto result = bundlePath->pathAppended(Constants::BUNDLE_JSON_FILENAME) - .writeFileContents(QJsonDocument(*bundleObj).toJson()); + auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) + .writeFileContents(QJsonDocument(bundleObj).toJson()); if (!result) qWarning() << __FUNCTION__ << result.error(); @@ -250,18 +189,36 @@ void ContentLibraryUserModel::removeItem(ContentLibraryItem *item) 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(); + bundlePath.pathAppended(file).removeFile(); } // remove from model - userItems->removeOne(item); - item->deleteLater(); + itemCat->removeItem(item); // update model - emit dataChanged(index(sectionIdx), index(sectionIdx)); + SectionIndex sectionIdx = bundleIdToSectionIndex(item->bundleId()); + emit dataChanged(index(sectionIdx), index(sectionIdx), {ItemsRole, EmptyRole}); updateIsEmpty(); } +ContentLibraryUserModel::SectionIndex ContentLibraryUserModel::bundleIdToSectionIndex( + const QString &bundleId) const +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + if (bundleId == compUtils.userMaterialsBundleId()) + return MaterialsSectionIdx; + + if (bundleId == compUtils.user3DBundleId()) + return Items3DSectionIdx; + + if (bundleId == compUtils.userEffectsBundleId()) + return EffectsSectionIdx; + + qWarning() << __FUNCTION__ << "Invalid section index for bundleId:" << bundleId; + return {}; +} + /** * @brief Gets unique Qml component and icon file material names from a given name * @param defaultName input name @@ -269,7 +226,9 @@ void ContentLibraryUserModel::removeItem(ContentLibraryItem *item) */ QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNames(const QString &defaultName) const { - return getUniqueLibItemNames(defaultName, m_bundleObjMaterial); + const QJsonObject bundleObj = qobject_cast<UserItemCategory *>( + m_userCategories.at(Items3DSectionIdx))->bundleObjRef(); + return getUniqueLibItemNames(defaultName, bundleObj); } /** @@ -279,7 +238,9 @@ QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNames(const */ QPair<QString, QString> ContentLibraryUserModel::getUniqueLib3DNames(const QString &defaultName) const { - return getUniqueLibItemNames(defaultName, m_bundleObj3D); + const QJsonObject bundleObj = qobject_cast<UserItemCategory *>( + m_userCategories.at(Items3DSectionIdx))->bundleObjRef(); + return getUniqueLibItemNames(defaultName, bundleObj); } QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QString &defaultName, @@ -319,208 +280,27 @@ QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QSt QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const { static const QHash<int, QByteArray> roles { - {NameRole, "categoryName"}, - {VisibleRole, "categoryVisible"}, + {TitleRole, "categoryTitle"}, + {EmptyRole, "categoryEmpty"}, {ItemsRole, "categoryItems"}, {NoMatchRole, "categoryNoMatch"} }; return roles; } -QJsonObject &ContentLibraryUserModel::bundleJsonMaterialObjectRef() +QJsonObject &ContentLibraryUserModel::bundleObjectRef(const QString &bundleId) { - return m_bundleObjMaterial; -} - -QJsonObject &ContentLibraryUserModel::bundleJson3DObjectRef() -{ - return m_bundleObj3D; + auto secIdx = bundleIdToSectionIndex(bundleId); + return qobject_cast<UserItemCategory *>(m_userCategories[secIdx])->bundleObjRef(); } void ContentLibraryUserModel::loadBundles() { - loadMaterialBundle(); - load3DBundle(); - loadTextureBundle(); -} - -void ContentLibraryUserModel::loadMaterialBundle() -{ - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - if (m_matBundleLoaded && m_bundleIdMaterial == compUtils.userMaterialsBundleId()) - return; - - // clean up - qDeleteAll(m_userMaterials); - m_userMaterials.clear(); - m_matBundleLoaded = false; - m_noMatchMaterials = true; - m_bundleObjMaterial = {}; - m_bundleIdMaterial.clear(); - - m_bundlePathMaterial = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials"); - m_bundlePathMaterial.ensureWritableDir(); - m_bundlePathMaterial.pathAppended("icons").ensureWritableDir(); - - auto jsonFilePath = m_bundlePathMaterial.pathAppended(Constants::BUNDLE_JSON_FILENAME); - if (!jsonFilePath.exists()) { - QString jsonContent = "{\n"; - jsonContent += " \"id\": \"UserMaterials\",\n"; - jsonContent += " \"items\": []\n"; - jsonContent += "}"; - Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent.toLatin1()); - if (!res.has_value()) { - qWarning() << __FUNCTION__ << res.error(); - emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); - return; - } - } - - Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents(); - if (!jsonContents.has_value()) { - qWarning() << __FUNCTION__ << jsonContents.error(); - emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); - return; - } - - QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value()); - if (bundleJsonDoc.isNull()) { - qWarning() << __FUNCTION__ << "Invalid json file" << jsonFilePath; - emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); - return; - } - - m_bundleIdMaterial = compUtils.userMaterialsBundleId(); - m_bundleObjMaterial = bundleJsonDoc.object(); - m_bundleObjMaterial["id"] = m_bundleIdMaterial; - - // parse items - QString typePrefix = compUtils.userMaterialsBundleType(); - const QJsonArray itemsArr = m_bundleObjMaterial.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 = itemObj.value("files").toVariant().toStringList(); - - m_userMaterials.append(new ContentLibraryItem(this, name, qml, type, icon, files, "material")); - } - - m_bundleMaterialSharedFiles.clear(); - const QJsonArray sharedFilesArr = m_bundleObjMaterial.value("sharedFiles").toArray(); - for (const QJsonValueConstRef &file : sharedFilesArr) - m_bundleMaterialSharedFiles.append(file.toString()); - - m_matBundleLoaded = true; - updateNoMatchMaterials(); - updateIsEmpty(); - emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); -} - -void ContentLibraryUserModel::load3DBundle() -{ - auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); - - if (m_bundle3DLoaded && m_bundleId3D == compUtils.user3DBundleId()) - return; - - // clean up - qDeleteAll(m_user3DItems); - m_user3DItems.clear(); - m_bundle3DLoaded = false; - m_noMatch3D = true; - m_bundleObj3D = {}; - m_bundleId3D.clear(); - - m_bundlePath3D = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d"); - m_bundlePath3D.ensureWritableDir(); - m_bundlePath3D.pathAppended("icons").ensureWritableDir(); - - auto jsonFilePath = m_bundlePath3D.pathAppended(Constants::BUNDLE_JSON_FILENAME); - if (!jsonFilePath.exists()) { - QByteArray jsonContent = "{\n"; - jsonContent += " \"id\": \"User3D\",\n"; - jsonContent += " \"items\": []\n"; - jsonContent += "}"; - Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent); - if (!res.has_value()) { - qWarning() << __FUNCTION__ << res.error(); - emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); - return; - } - } - - Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents(); - if (!jsonContents.has_value()) { - qWarning() << __FUNCTION__ << jsonContents.error(); - emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); - return; - } - - QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value()); - if (bundleJsonDoc.isNull()) { - qWarning() << __FUNCTION__ << "Invalid json file" << jsonFilePath; - emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); - return; - } - - m_bundleId3D = compUtils.user3DBundleId(); - m_bundleObj3D = bundleJsonDoc.object(); - m_bundleObj3D["id"] = m_bundleId3D; - - // parse items - QString typePrefix = compUtils.user3DBundleType(); - 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_bundlePath3D.pathAppended(itemObj.value("icon").toString()).toUrl(); - QStringList files = itemObj.value("files").toVariant().toStringList(); - - m_user3DItems.append(new ContentLibraryItem(nullptr, name, qml, type, icon, files, "3d")); - } - - m_bundle3DSharedFiles.clear(); - const QJsonArray sharedFilesArr = m_bundleObj3D.value("sharedFiles").toArray(); - for (const QJsonValueConstRef &file : sharedFilesArr) - m_bundle3DSharedFiles.append(file.toString()); + for (UserCategory *cat : std::as_const(m_userCategories)) + cat->loadBundle(); - m_bundle3DLoaded = true; - updateNoMatch3D(); + resetModel(); updateIsEmpty(); - emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); -} - -void ContentLibraryUserModel::loadTextureBundle() -{ - if (!m_userTextures.isEmpty()) - return; - - QDir bundleDir{Paths::bundlesPathSetting() + "/User/textures"}; - bundleDir.mkpath("."); - bundleDir.mkdir("icons"); - - const QFileInfoList fileInfos = bundleDir.entryInfoList(QDir::Files); - for (const QFileInfo &fileInfo : fileInfos) { - QString suffix = '.' + fileInfo.suffix(); - auto iconFileInfo = QFileInfo(fileInfo.path().append("/icons/").append(fileInfo.baseName() + ".png")); - QPair<QSize, qint64> info = ImageUtils::imageInfo(fileInfo.path()); - QString dirPath = fileInfo.path(); - QSize imgDims = info.first; - qint64 imgFileSize = info.second; - - auto tex = new ContentLibraryTexture(this, iconFileInfo, dirPath, suffix, imgDims, imgFileSize); - m_userTextures.append(tex); - } - - updateNoMatchTextures(); - emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx)); } bool ContentLibraryUserModel::hasRequiredQuick3DImport() const @@ -530,8 +310,9 @@ bool ContentLibraryUserModel::hasRequiredQuick3DImport() const void ContentLibraryUserModel::updateIsEmpty() { - bool newIsEmpty = m_user3DItems.isEmpty() && m_userMaterials.isEmpty() && m_userTextures.isEmpty() - && m_userEffects.isEmpty(); + bool newIsEmpty = std::ranges::all_of(std::as_const(m_userCategories), + [](UserCategory *cat) { return cat->isEmpty(); }); + if (m_isEmpty == newIsEmpty) return; @@ -548,39 +329,27 @@ void ContentLibraryUserModel::setSearchText(const QString &searchText) m_searchText = lowerSearchText; - for (ContentLibraryItem *item : std::as_const(m_userMaterials)) - item->filter(m_searchText); - - for (ContentLibraryTexture *item : std::as_const(m_userTextures)) - item->filter(m_searchText); - - for (ContentLibraryItem *item : std::as_const(m_user3DItems)) - item->filter(m_searchText); + for (UserCategory *cat : std::as_const(m_userCategories)) + cat->filter(m_searchText); - updateNoMatchMaterials(); - updateNoMatchTextures(); - updateNoMatch3D(); resetModel(); } -void ContentLibraryUserModel::updateMaterialsImportedState(const QStringList &importedItems) +void ContentLibraryUserModel::updateImportedState(const QStringList &importedItems, + const QString &bundleId) { - bool changed = false; - for (ContentLibraryItem *mat : std::as_const(m_userMaterials)) - changed |= mat->setImported(importedItems.contains(mat->qml().chopped(4))); - - if (changed) - emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); -} + SectionIndex secIdx = bundleIdToSectionIndex(bundleId); + UserItemCategory *cat = qobject_cast<UserItemCategory *>(m_userCategories.at(secIdx)); + const QObjectList items = cat->items(); -void ContentLibraryUserModel::update3DImportedState(const QStringList &importedItems) -{ bool changed = false; - for (ContentLibraryItem *item : std::as_const(m_user3DItems)) - changed |= item->setImported(importedItems.contains(item->qml().chopped(4))); + for (QObject *item : items) { + ContentLibraryItem *castedItem = qobject_cast<ContentLibraryItem *>(item); + changed |= castedItem->setImported(importedItems.contains(castedItem->qml().chopped(4))); + } if (changed) - emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); + emit dataChanged(index(secIdx), index(secIdx), {ItemsRole}); } void ContentLibraryUserModel::setQuick3DImportVersion(int major, int minor) @@ -609,31 +378,15 @@ void ContentLibraryUserModel::applyToSelected(ContentLibraryItem *mat, bool add) emit applyToSelectedTriggered(mat, add); } -void ContentLibraryUserModel::addToProject(QObject *item) +void ContentLibraryUserModel::addToProject(ContentLibraryItem *item) { - auto castedItem = qobject_cast<ContentLibraryItem *>(item); - QTC_ASSERT(castedItem, return); - - addItemToProject(castedItem); -} + UserItemCategory *itemCat = qobject_cast<UserItemCategory *>(item->parent()); + QTC_ASSERT(itemCat, return); -void ContentLibraryUserModel::addItemToProject(ContentLibraryItem *item) -{ - QString bundlePath; + QString bundlePath = itemCat->bundlePath().toFSPathString(); TypeName type = item->type(); QString qmlFile = item->qml(); - QStringList files = item->files(); - - if (item->itemType() == "material") { - bundlePath = m_bundlePathMaterial.toFSPathString(); - files << m_bundleMaterialSharedFiles; - } else if (item->itemType() == "3d") { - bundlePath = m_bundlePath3D.toFSPathString(); - files << m_bundle3DSharedFiles; - } else { - qWarning() << __FUNCTION__ << "Unsupported Item"; - return; - } + QStringList files = item->files() + itemCat->sharedFiles(); QString err = m_widget->importer()->importComponent(bundlePath, type, qmlFile, files); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h index f5805946fb6..094a5c75178 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h @@ -3,6 +3,8 @@ #pragma once +#include "usercategory.h" + #include <utils/filepath.h> #include <QAbstractListModel> @@ -23,17 +25,8 @@ class ContentLibraryUserModel : public QAbstractListModel Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(QList<ContentLibraryItem *> userMaterials MEMBER m_userMaterials NOTIFY userMaterialsChanged) - Q_PROPERTY(QList<ContentLibraryTexture *> userTextures MEMBER m_userTextures NOTIFY userTexturesChanged) - Q_PROPERTY(QList<ContentLibraryItem *> user3DItems MEMBER m_user3DItems NOTIFY user3DItemsChanged) - Q_PROPERTY(QList<ContentLibraryItem *> userEffects MEMBER m_userEffects NOTIFY userEffectsChanged) public: - enum SectionIndex { MaterialsSectionIdx = 0, - TexturesSectionIdx, - Items3DSectionIdx, - EffectsSectionIdx }; - ContentLibraryUserModel(ContentLibraryWidget *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -41,8 +34,7 @@ public: QHash<int, QByteArray> roleNames() const override; void setSearchText(const QString &searchText); - void updateMaterialsImportedState(const QStringList &importedItems); - void update3DImportedState(const QStringList &importedItems); + void updateImportedState(const QStringList &importedItems, const QString &bundleId); QPair<QString, QString> getUniqueLibMaterialNames(const QString &defaultName = "Material") const; QPair<QString, QString> getUniqueLib3DNames(const QString &defaultName = "Item") const; @@ -56,28 +48,21 @@ public: void updateIsEmpty(); void resetModel(); - void updateNoMatchMaterials(); - void updateNoMatchTextures(); - void updateNoMatch3D(); - - void addMaterial(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files); - void add3DItem(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files); - void refreshSection(SectionIndex sectionIndex); - void addTextures(const QStringList &paths); - void addItemToProject(ContentLibraryItem *item); + void addItem(const QString &bundleId, const QString &name, const QString &qml,const QUrl &icon, + const QStringList &files); + void refreshSection(const QString &bundleId); + void addTextures(const Utils::FilePaths &paths); - void remove3DFromContentLibByName(const QString &qmlFileName); - void removeMaterialFromContentLibByName(const QString &qmlFileName); + void removeItemByName(const QString &qmlFileName, const QString &bundleId); void setBundleObj(const QJsonObject &newBundleObj); - QJsonObject &bundleJsonMaterialObjectRef(); - QJsonObject &bundleJson3DObjectRef(); + QJsonObject &bundleObjectRef(const QString &bundleId); void loadBundles(); Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryItem *mat, bool add = false); - Q_INVOKABLE void addToProject(QObject *item); + Q_INVOKABLE void addToProject(ContentLibraryItem *item); Q_INVOKABLE void removeFromProject(QObject *item); Q_INVOKABLE void removeTexture(QmlDesigner::ContentLibraryTexture *tex); Q_INVOKABLE void removeFromContentLib(QObject *item); @@ -85,49 +70,33 @@ public: signals: void hasRequiredQuick3DImportChanged(); void isEmptyChanged(); - void userMaterialsChanged(); - void userTexturesChanged(); - void user3DItemsChanged(); - void userEffectsChanged(); void applyToSelectedTriggered(QmlDesigner::ContentLibraryItem *mat, bool add = false); private: + // section indices must match the order in initModel() + enum SectionIndex { MaterialsSectionIdx = 0, + TexturesSectionIdx, + Items3DSectionIdx, + EffectsSectionIdx }; + + void createCategories(); void loadMaterialBundle(); void load3DBundle(); void loadTextureBundle(); void removeItem(ContentLibraryItem *item); + SectionIndex bundleIdToSectionIndex(const QString &bundleId) const; ContentLibraryWidget *m_widget = nullptr; QString m_searchText; - QString m_bundleIdMaterial; - QString m_bundleId3D; - QStringList m_bundleMaterialSharedFiles; - QStringList m_bundle3DSharedFiles; - Utils::FilePath m_bundlePathMaterial; - Utils::FilePath m_bundlePath3D; - - QList<ContentLibraryItem *> m_userMaterials; - QList<ContentLibraryTexture *> m_userTextures; - QList<ContentLibraryItem *> m_userEffects; - QList<ContentLibraryItem *> m_user3DItems; - const QStringList m_userCategories = {tr("Materials"), tr("Textures"), tr("3D"), - /*tr("Effects"), tr("2D components")*/}; // TODO; - - QJsonObject m_bundleObjMaterial; - QJsonObject m_bundleObj3D; - - bool m_noMatchMaterials = true; - bool m_noMatchTextures = true; - bool m_noMatch3D = true; - bool m_noMatchEffects = true; - bool m_matBundleLoaded = false; - bool m_bundle3DLoaded = false; + + QList<UserCategory *> m_userCategories; + bool m_isEmpty = true; int m_quick3dMajorVersion = -1; int m_quick3dMinorVersion = -1; - enum Roles { NameRole = Qt::UserRole + 1, VisibleRole, ItemsRole, NoMatchRole }; + enum Roles { TitleRole = Qt::UserRole + 1, ItemsRole, EmptyRole, NoMatchRole }; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 675b60e0bcc..c0b16b6c556 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -365,7 +365,7 @@ void ContentLibraryView::customNotification(const AbstractView *view, m_bundleItemPos = data.size() == 1 ? data.first() : QVariant(); if (is3D) - m_widget->userModel()->addItemToProject(m_draggedBundleItem); + m_widget->userModel()->addToProject(m_draggedBundleItem); else m_widget->effectsModel()->addInstance(m_draggedBundleItem); m_bundleItemTarget = nodeList.first() ? nodeList.first() : Utils3D::active3DSceneNode(this); @@ -545,7 +545,9 @@ void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &ic QTC_ASSERT_EXPECTED(result,); // add the material to the bundle json - QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef(); + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + QString bundleId = compUtils.userMaterialsBundleId(); + QJsonObject &jsonRef = m_widget->userModel()->bundleObjectRef(bundleId); QJsonArray itemsArr = jsonRef.value("items").toArray(); itemsArr.append(QJsonObject { {"name", name}, @@ -570,7 +572,7 @@ void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &ic QTC_ASSERT_EXPECTED(result,); } - m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssetsList); + m_widget->userModel()->addItem(bundleId, name, qml, QUrl::fromLocalFile(fullIconPath), depAssetsList); } QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const ModelNode &node, int depth) @@ -653,7 +655,7 @@ QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const Mod void ContentLibraryView::addLibAssets(const QStringList &paths) { auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/textures"); - QStringList pathsInBundle; + Utils::FilePaths pathsInBundle; const QStringList existingTextures = Utils::transform(bundlePath.dirEntries(QDir::Files), [](const Utils::FilePath &path) { @@ -679,7 +681,7 @@ void ContentLibraryView::addLibAssets(const QStringList &paths) auto result = assetFilePath.copyFile(bundlePath.pathAppended(asset.fileName())); QTC_ASSERT_EXPECTED(result,); - pathsInBundle.append(bundlePath.pathAppended(asset.fileName()).toFSPathString()); + pathsInBundle.append(bundlePath.pathAppended(asset.fileName())); } m_widget->userModel()->addTextures(pathsInBundle); @@ -687,6 +689,10 @@ void ContentLibraryView::addLibAssets(const QStringList &paths) void ContentLibraryView::addLib3DComponent(const ModelNode &node) { + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + QString bundleId = compUtils.user3DBundleId(); + QString compBaseName = node.simplifiedTypeName(); QString compFileName = compBaseName + ".qml"; @@ -703,7 +709,7 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) return; // before overwriting remove old item (to avoid partial items and dangling assets) - m_widget->userModel()->remove3DFromContentLibByName(compFileName); + m_widget->userModel()->removeItemByName(compFileName, bundleId); } // generate and save icon @@ -713,7 +719,7 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) getImageFromCache(compDir.pathAppended(compFileName).path(), [&](const QImage &image) { bool iconSaved = image.save(m_iconSavePath.toFSPathString()); if (iconSaved) - m_widget->userModel()->refreshSection(ContentLibraryUserModel::Items3DSectionIdx); + m_widget->userModel()->refreshSection(compUtils.user3DBundleId()); else qWarning() << "ContentLibraryView::getImageFromCache(): icon save failed" << iconPath; }); @@ -739,7 +745,7 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) } // add the item to the bundle json - QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef(); + QJsonObject &jsonRef = m_widget->userModel()->bundleObjectRef(bundleId); QJsonArray itemsArr = jsonRef.value("items").toArray(); itemsArr.append(QJsonObject { {"name", node.simplifiedTypeName()}, @@ -754,7 +760,7 @@ void ContentLibraryView::addLib3DComponent(const ModelNode &node) .writeFileContents(QJsonDocument(jsonRef).toJson()); QTC_ASSERT_EXPECTED(result,); - m_widget->userModel()->add3DItem(compBaseName, compFileName, m_iconSavePath.toUrl(), filesList); + m_widget->userModel()->addItem(bundleId, compBaseName, compFileName, m_iconSavePath.toUrl(), filesList); } void ContentLibraryView::exportLib3DComponent(const ModelNode &node) @@ -828,14 +834,17 @@ void ContentLibraryView::exportLib3DComponent(const ModelNode &node) void ContentLibraryView::addLib3DItem(const ModelNode &node) { + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/"); + QString bundleId = compUtils.user3DBundleId(); QString name = node.variantProperty("objectName").value().toString(); if (name.isEmpty()) name = node.displayName(); - auto [qml, icon] = m_widget->userModel()->getUniqueLibItemNames(name, - m_widget->userModel()->bundleJson3DObjectRef()); + QJsonObject &jsonRef = m_widget->userModel()->bundleObjectRef(bundleId); + + auto [qml, icon] = m_widget->userModel()->getUniqueLibItemNames(name, jsonRef); // generate and save Qml file auto [qmlString, depAssets] = modelNodeToQmlString(node); @@ -852,13 +861,12 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node) getImageFromCache(qmlPath, [&](const QImage &image) { bool iconSaved = image.save(m_iconSavePath.toFSPathString()); if (iconSaved) - m_widget->userModel()->refreshSection(ContentLibraryUserModel::Items3DSectionIdx); + m_widget->userModel()->refreshSection(compUtils.user3DBundleId()); else qWarning() << "ContentLibraryView::getImageFromCache(): icon save failed" << iconPath; }); // add the item to the bundle json - QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef(); QJsonArray itemsArr = jsonRef.value("items").toArray(); itemsArr.append(QJsonObject { {"name", name}, @@ -883,7 +891,7 @@ void ContentLibraryView::addLib3DItem(const ModelNode &node) QTC_ASSERT_EXPECTED(result,); } - m_widget->userModel()->add3DItem(name, qml, m_iconSavePath.toUrl(), depAssetsList); + m_widget->userModel()->addItem(bundleId, name, qml, m_iconSavePath.toUrl(), depAssetsList); } QString ContentLibraryView::getExportPath(const ModelNode &node) const @@ -1013,14 +1021,14 @@ void ContentLibraryView::importBundle() tr("The chosen bundle was created with an incompatible version of Qt Design Studio")); return; } - bool isMat = isMaterialBundle(importedJsonObj.value("id").toString()); + QString bundleId = importedJsonObj.value("id").toString(); + bool isMat = isMaterialBundle(bundleId); QString bundleFolderName = isMat ? QLatin1String("materials") : QLatin1String("3d"); auto bundlePath = Utils::FilePath::fromString(QLatin1String("%1/User/%3/") .arg(Paths::bundlesPathSetting(), bundleFolderName)); - QJsonObject &jsonRef = isMat ? m_widget->userModel()->bundleJsonMaterialObjectRef() - : m_widget->userModel()->bundleJson3DObjectRef(); + QJsonObject &jsonRef = m_widget->userModel()->bundleObjectRef(bundleId); QJsonArray itemsArr = jsonRef.value("items").toArray(); QStringList existingQmls; @@ -1042,10 +1050,7 @@ void ContentLibraryView::importBundle() continue; // before overwriting remove old item (to avoid partial items and dangling assets) - if (isMat) - m_widget->userModel()->removeMaterialFromContentLibByName(qml); - else - m_widget->userModel()->remove3DFromContentLibByName(qml); + m_widget->userModel()->removeItemByName(qml, bundleId); } // add entry to json @@ -1065,10 +1070,7 @@ void ContentLibraryView::importBundle() QTC_ASSERT_EXPECTED(filePath.writeFileContents(zipReader.fileData(file)),); } - if (isMat) - m_widget->userModel()->addMaterial(name, qml, iconUrl, files); - else - m_widget->userModel()->add3DItem(name, qml, iconUrl, files); + m_widget->userModel()->addItem(bundleId, name, qml, iconUrl, files); } zipReader.close(); @@ -1078,10 +1080,6 @@ void ContentLibraryView::importBundle() auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) .writeFileContents(QJsonDocument(jsonRef).toJson()); QTC_ASSERT_EXPECTED(result,); - - auto sectionIdx = isMat ? ContentLibraryUserModel::MaterialsSectionIdx - : ContentLibraryUserModel::Items3DSectionIdx; - m_widget->userModel()->refreshSection(sectionIdx); } /** diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index e4346f7a991..c5cf6d21e9b 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -230,10 +230,8 @@ void ContentLibraryWidget::updateImportedState(const QString &bundleId) m_materialsModel->updateImportedState(importedItems); else if (bundleId == compUtils.effectsBundleId()) m_effectsModel->updateImportedState(importedItems); - else if (bundleId == compUtils.userMaterialsBundleId()) - m_userModel->updateMaterialsImportedState(importedItems); - else if (bundleId == compUtils.user3DBundleId()) - m_userModel->update3DImportedState(importedItems); + else + m_userModel->updateImportedState(importedItems, bundleId); } ContentLibraryBundleImporter *ContentLibraryWidget::importer() const diff --git a/src/plugins/qmldesigner/components/contentlibrary/usercategory.cpp b/src/plugins/qmldesigner/components/contentlibrary/usercategory.cpp new file mode 100644 index 00000000000..0d413752995 --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/usercategory.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "usercategory.h" + +namespace QmlDesigner { + +UserCategory::UserCategory(const QString &title, const Utils::FilePath &bundlePath) + : m_title(title) + , m_bundlePath(bundlePath) +{ +} + +QString UserCategory::title() const +{ + return m_title; +} + +bool UserCategory::isEmpty() const +{ + return m_isEmpty; +} + +void UserCategory::setIsEmpty(bool val) +{ + if (m_isEmpty == val) + return; + + m_isEmpty = val; + emit isEmptyChanged(); +} + +bool UserCategory::noMatch() const +{ + return m_noMatch; +} + +void UserCategory::setNoMatch(bool val) +{ + if (m_noMatch == val) + return; + + m_noMatch = val; + emit noMatchChanged(); +} + +void UserCategory::addItem(QObject *item) +{ + m_items.append(item); + emit itemsChanged(); + + setIsEmpty(false); +} + +void UserCategory::removeItem(QObject *item) +{ + m_items.removeOne(item); + item->deleteLater(); + emit itemsChanged(); + + setIsEmpty(m_items.isEmpty()); +} + +Utils::FilePath UserCategory::bundlePath() const +{ + return m_bundlePath; +} + +QObjectList UserCategory::items() const +{ + return m_items; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/usercategory.h b/src/plugins/qmldesigner/components/contentlibrary/usercategory.h new file mode 100644 index 00000000000..38421c293cb --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/usercategory.h @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <utils/filepath.h> + +#include <QObject> + +namespace QmlDesigner { + +class UserCategory : public QObject +{ + Q_OBJECT + +public: + UserCategory(const QString &title, const Utils::FilePath &bundlePath); + + QString title() const; + QObjectList items() const; + + bool isEmpty() const; + void setIsEmpty(bool val); + + bool noMatch() const; + void setNoMatch(bool val); + + virtual void loadBundle() = 0; + virtual void filter(const QString &searchText) = 0; + + void addItem(QObject *item); + void removeItem(QObject *item); + + Utils::FilePath bundlePath() const; + +signals: + void itemsChanged(); + void isEmptyChanged(); + void noMatchChanged(); + +protected: + QString m_title; + Utils::FilePath m_bundlePath; + QObjectList m_items; + bool m_isEmpty = true; + bool m_noMatch = true; + bool m_bundleLoaded = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/useritemcategory.cpp b/src/plugins/qmldesigner/components/contentlibrary/useritemcategory.cpp new file mode 100644 index 00000000000..40a467b8dd9 --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/useritemcategory.cpp @@ -0,0 +1,121 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "useritemcategory.h" + +#include "contentlibraryitem.h" + +#include <qmldesignerconstants.h> +#include <qmldesignerplugin.h> + +#include <QJsonArray> +#include <QJsonDocument> + +namespace QmlDesigner { + +UserItemCategory::UserItemCategory(const QString &title, const Utils::FilePath &bundlePath, + const QString &bundleId) + : UserCategory(title, bundlePath) + , m_bundleId(bundleId) +{ +} + +void UserItemCategory::loadBundle() +{ + if (m_bundleLoaded) + return; + + // clean up + qDeleteAll(m_items); + m_items.clear(); + m_bundleLoaded = false; + m_noMatch = false; + m_bundleObj = {}; + + m_bundlePath.ensureWritableDir(); + m_bundlePath.pathAppended("icons").ensureWritableDir(); + + auto jsonFilePath = m_bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME); + if (!jsonFilePath.exists()) { + QString jsonContent = "{\n"; + jsonContent += " \"id\": \"" + m_bundleId + "\",\n"; + jsonContent += " \"items\": []\n"; + jsonContent += "}"; + Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent.toLatin1()); + if (!res.has_value()) { + qWarning() << __FUNCTION__ << res.error(); + setIsEmpty(true); + emit itemsChanged(); + return; + } + } + + Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents(); + if (!jsonContents.has_value()) { + qWarning() << __FUNCTION__ << jsonContents.error(); + setIsEmpty(true); + emit itemsChanged(); + return; + } + + QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value()); + if (bundleJsonDoc.isNull()) { + qWarning() << __FUNCTION__ << "Invalid json file" << jsonFilePath; + setIsEmpty(true); + emit itemsChanged(); + return; + } + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + m_bundleObj = bundleJsonDoc.object(); + m_bundleObj["id"] = m_bundleId; + + // parse items + QString typePrefix = compUtils.userBundleType(m_bundleId); + const QJsonArray itemsArr = m_bundleObj.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_bundlePath.pathAppended(itemObj.value("icon").toString()).toUrl(); + QStringList files = itemObj.value("files").toVariant().toStringList(); + + m_items.append(new ContentLibraryItem(this, name, qml, type, icon, files, m_bundleId)); + } + + m_sharedFiles.clear(); + const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray(); + for (const QJsonValueConstRef &file : sharedFilesArr) + m_sharedFiles.append(file.toString()); + + m_bundleLoaded = true; + setIsEmpty(m_items.isEmpty()); + emit itemsChanged(); +} + +void UserItemCategory::filter(const QString &searchText) +{ + bool noMatch = true; + for (QObject *item : std::as_const(m_items)) { + ContentLibraryItem *castedItem = qobject_cast<ContentLibraryItem *>(item); + bool itemVisible = castedItem->filter(searchText); + if (itemVisible) + noMatch = false; + } + setNoMatch(noMatch); +} + +QStringList UserItemCategory::sharedFiles() const +{ + return m_sharedFiles; +} + +QJsonObject &UserItemCategory::bundleObjRef() +{ + return m_bundleObj; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/useritemcategory.h b/src/plugins/qmldesigner/components/contentlibrary/useritemcategory.h new file mode 100644 index 00000000000..1d55fc077a9 --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/useritemcategory.h @@ -0,0 +1,35 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "usercategory.h" + +#include <QJsonObject> + +namespace QmlDesigner { + +class ContentLibraryItem; + +class UserItemCategory : public UserCategory +{ + Q_OBJECT + +public: + UserItemCategory(const QString &title, const Utils::FilePath &bundlePath, const QString &bundleId); + + void loadBundle() override; + void filter(const QString &searchText) override; + + QStringList sharedFiles() const; + + QJsonObject &bundleObjRef(); + +private: + QString m_bundleId; + QJsonObject m_bundleObj; + QStringList m_sharedFiles; + +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/usertexturecategory.cpp b/src/plugins/qmldesigner/components/contentlibrary/usertexturecategory.cpp new file mode 100644 index 00000000000..5c3e99069fc --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/usertexturecategory.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "usertexturecategory.h" + +#include "contentlibrarytexture.h" + +#include <designerpaths.h> +#include <imageutils.h> + +namespace QmlDesigner { + +UserTextureCategory::UserTextureCategory(const QString &title, const Utils::FilePath &bundlePath) + : UserCategory(title, bundlePath) +{ +} + +void UserTextureCategory::loadBundle() +{ + if (m_bundleLoaded) + return; + + // clean up + qDeleteAll(m_items); + m_items.clear(); + + m_bundlePath.ensureWritableDir(); + m_bundlePath.pathAppended("icons").ensureWritableDir(); + + addItems(m_bundlePath.dirEntries(QDir::Files)); + + m_bundleLoaded = true; +} + +void UserTextureCategory::filter(const QString &searchText) +{ + bool noMatch = true; + for (QObject *item : std::as_const(m_items)) { + ContentLibraryTexture *castedItem = qobject_cast<ContentLibraryTexture *>(item); + bool itemVisible = castedItem->filter(searchText); + if (itemVisible) + noMatch = false; + } + setNoMatch(noMatch); +} + +void UserTextureCategory::addItems(const Utils::FilePaths &paths) +{ + for (const Utils::FilePath &filePath : paths) { + QString suffix = '.' + filePath.suffix(); + auto iconFileInfo = filePath.parentDir().pathAppended("icons/" + filePath.baseName() + ".png") + .toFileInfo(); + QPair<QSize, qint64> info = ImageUtils::imageInfo(filePath.path()); + QString dirPath = filePath.parentDir().toFSPathString(); + QSize imgDims = info.first; + qint64 imgFileSize = info.second; + + auto tex = new ContentLibraryTexture(this, iconFileInfo, dirPath, suffix, imgDims, imgFileSize); + m_items.append(tex); + } + + setIsEmpty(m_items.isEmpty()); + emit itemsChanged(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/usertexturecategory.h b/src/plugins/qmldesigner/components/contentlibrary/usertexturecategory.h new file mode 100644 index 00000000000..5e58f8f0059 --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/usertexturecategory.h @@ -0,0 +1,25 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "usercategory.h" + +namespace QmlDesigner { + +class ContentLibraryTexture; + +class UserTextureCategory : public UserCategory +{ + Q_OBJECT + +public: + UserTextureCategory(const QString &title, const Utils::FilePath &bundlePath); + + void loadBundle() override; + void filter(const QString &searchText) override; + + void addItems(const Utils::FilePaths &paths); +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp b/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp index da40c4d387d..9d67d1cbf82 100644 --- a/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp +++ b/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp @@ -273,6 +273,21 @@ QString GeneratedComponentUtils::effectsBundleType() const return componentBundlesTypePrefix() + '.' + effectsBundleId(); } +QString GeneratedComponentUtils::userBundleType(const QString &bundleId) const +{ + if (bundleId == userMaterialsBundleId()) + return userMaterialsBundleType(); + + if (bundleId == userEffectsBundleId()) + return userEffectsBundleType(); + + if (bundleId == user3DBundleId()) + return user3DBundleType(); + + qWarning() << __FUNCTION__ << "no bundleType for bundleId:" << bundleId; + return {}; +} + QString GeneratedComponentUtils::userMaterialsBundleType() const { return componentBundlesTypePrefix() + '.' + userMaterialsBundleId(); diff --git a/src/plugins/qmldesigner/designercore/generatedcomponentutils.h b/src/plugins/qmldesigner/designercore/generatedcomponentutils.h index ceddb405a34..df15d252cc3 100644 --- a/src/plugins/qmldesigner/designercore/generatedcomponentutils.h +++ b/src/plugins/qmldesigner/designercore/generatedcomponentutils.h @@ -44,6 +44,7 @@ public: QString materialsBundleType() const; QString effectsBundleType() const; + QString userBundleType(const QString &bundleId) const; QString userMaterialsBundleType() const; QString userEffectsBundleType() const; QString user3DBundleType() const; |