QmlDesigner: Refactor content lib user model

Add category classes for the model to make it tidier and easy to
extend (i.e. adding more categories).

Change-Id: Ied8641802f600c5cb0b036aba6ad44dbde612a09
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2024-06-14 12:14:09 +03:00
parent 8106d29cf4
commit f0e49fc12a
19 changed files with 579 additions and 471 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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());
UserCategory *currCat = 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);
}
if (role == TitleRole)
return currCat->title();
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 == ItemsRole)
return QVariant::fromValue(currCat->items());
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 == NoMatchRole)
return currCat->noMatch();
if (role == EmptyRole)
return currCat->isEmpty();
return {};
}
void ContentLibraryUserModel::updateNoMatchMaterials()
void ContentLibraryUserModel::createCategories()
{
m_noMatchMaterials = Utils::allOf(m_userMaterials, [&](ContentLibraryItem *item) {
return !item->visible();
});
QTC_ASSERT(m_userCategories.isEmpty(), return);
auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
auto userBundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User");
auto catMaterial = new UserItemCategory{tr("Materials"),
userBundlePath.pathAppended("materials"),
compUtils.userMaterialsBundleId()};
auto catTexture = new UserTextureCategory{tr("Textures"), userBundlePath.pathAppended("textures")};
auto cat3D = new UserItemCategory{tr("3D"), userBundlePath.pathAppended("3d"),
compUtils.user3DBundleId()};
m_userCategories << catMaterial << catTexture << cat3D;
}
void ContentLibraryUserModel::updateNoMatchTextures()
{
m_noMatchTextures = Utils::allOf(m_userTextures, [&](ContentLibraryTexture *item) {
return !item->visible();
});
}
void ContentLibraryUserModel::updateNoMatch3D()
{
m_noMatch3D = Utils::allOf(m_user3DItems, [&](ContentLibraryItem *item) {
return !item->visible();
});
}
void ContentLibraryUserModel::addMaterial(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.userMaterialsBundleType();
QString typePrefix = compUtils.userBundleType(bundleId);
TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
auto libMat = new ContentLibraryItem(this, name, qml, type, icon, files, "material");
m_userMaterials.append(libMat);
emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
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::add3DItem(const QString &name, const QString &qml,
const QUrl &icon, const QStringList &files)
void ContentLibraryUserModel::refreshSection(const QString &bundleId)
{
auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
QString typePrefix = compUtils.user3DBundleType();
TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
m_user3DItems.append(new ContentLibraryItem(this, name, qml, type, icon, files, "3d"));
}
void ContentLibraryUserModel::refreshSection(SectionIndex sectionIndex)
{
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");
auto texCat = qobject_cast<UserTextureCategory *>(m_userCategories[TexturesSectionIdx]);
QTC_ASSERT(texCat, return);
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;
texCat->addItems(paths);
auto tex = new ContentLibraryTexture(this, iconFileInfo, dirPath, suffix, imgDims, imgFileSize);
m_userTextures.append(tex);
}
emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx));
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);
if (castedItem->qml() == qmlFileName) {
itemToRemove = castedItem;
break;
}
}
void ContentLibraryUserModel::removeMaterialFromContentLibByName(const QString &qmlFileName)
{
ContentLibraryItem *itemToRemove = Utils::findOr(m_userMaterials, nullptr,
[&qmlFileName](ContentLibraryItem *item) {
return item->qml() == qmlFileName;
});
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;
UserItemCategory *itemCat = qobject_cast<UserItemCategory *>(item->parent());
QTC_ASSERT(itemCat, return);
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;
}
Utils::FilePath bundlePath = itemCat->bundlePath();
QJsonObject &bundleObj = itemCat->bundleObjRef();
QJsonArray itemsArr = bundleObj->value("items").toArray();
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();
}
for (UserCategory *cat : std::as_const(m_userCategories))
cat->loadBundle();
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();
resetModel();
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());
m_bundle3DLoaded = true;
updateNoMatch3D();
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 (UserCategory *cat : std::as_const(m_userCategories))
cat->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);
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)));
SectionIndex secIdx = bundleIdToSectionIndex(bundleId);
UserItemCategory *cat = qobject_cast<UserItemCategory *>(m_userCategories.at(secIdx));
const QObjectList items = cat->items();
if (changed)
emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx));
bool changed = false;
for (QObject *item : items) {
ContentLibraryItem *castedItem = qobject_cast<ContentLibraryItem *>(item);
changed |= castedItem->setImported(importedItems.contains(castedItem->qml().chopped(4)));
}
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)));
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);
UserItemCategory *itemCat = qobject_cast<UserItemCategory *>(item->parent());
QTC_ASSERT(itemCat, return);
addItemToProject(castedItem);
}
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);

View File

@@ -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 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 addItemToProject(ContentLibraryItem *item);
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;
QList<UserCategory *> m_userCategories;
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;
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

View File

@@ -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);
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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;