aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp')
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp424
1 files changed, 424 insertions, 0 deletions
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
new file mode 100644
index 0000000000..ed9723a151
--- /dev/null
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp
@@ -0,0 +1,424 @@
+// 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 "contentlibraryusermodel.h"
+
+#include "contentlibrarybundleimporter.h"
+#include "contentlibraryeffect.h"
+#include "contentlibrarymaterial.h"
+#include "contentlibrarymaterialscategory.h"
+#include "contentlibrarytexture.h"
+#include "contentlibrarywidget.h"
+
+#include <designerpaths.h>
+#include <imageutils.h>
+#include <qmldesignerplugin.h>
+
+#include <utils/algorithm.h>
+#include <utils/hostosinfo.h>
+#include <utils/qtcassert.h>
+
+#include <QCoreApplication>
+#include <QFileInfo>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QQmlEngine>
+#include <QStandardPaths>
+#include <QUrl>
+
+namespace QmlDesigner {
+
+ContentLibraryUserModel::ContentLibraryUserModel(ContentLibraryWidget *parent)
+ : QAbstractListModel(parent)
+ , m_widget(parent)
+{
+ m_userCategories = {tr("Materials"), tr("Textures")/*, tr("3D"), tr("Effects"), tr("2D components")*/}; // TODO
+
+ loadMaterialBundle();
+ loadTextureBundle();
+}
+
+int ContentLibraryUserModel::rowCount(const QModelIndex &) const
+{
+ return m_userCategories.size();
+}
+
+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() == 0)
+ return QVariant::fromValue(m_userMaterials);
+ if (index.row() == 1)
+ return QVariant::fromValue(m_userTextures);
+ if (index.row() == 2)
+ return QVariant::fromValue(m_user3DItems);
+ if (index.row() == 3)
+ return QVariant::fromValue(m_userEffects);
+ }
+
+ if (role == VisibleRole)
+ return true; // TODO
+
+ return {};
+}
+
+bool ContentLibraryUserModel::isValidIndex(int idx) const
+{
+ return idx > -1 && idx < rowCount();
+}
+
+void ContentLibraryUserModel::updateIsEmpty()
+{
+ bool anyMatVisible = Utils::anyOf(m_userMaterials, [&](ContentLibraryMaterial *mat) {
+ return mat->visible();
+ });
+
+ bool newEmpty = !anyMatVisible || !m_widget->hasMaterialLibrary() || !hasRequiredQuick3DImport();
+
+ if (newEmpty != m_isEmpty) {
+ m_isEmpty = newEmpty;
+ emit isEmptyChanged();
+ }
+}
+
+void ContentLibraryUserModel::addMaterial(const QString &name, const QString &qml,
+ const QUrl &icon, const QStringList &files)
+{
+ auto libMat = new ContentLibraryMaterial(this, name, qml, qmlToModule(qml), icon, files,
+ Paths::bundlesPathSetting().append("/User/materials"));
+
+ m_userMaterials.append(libMat);
+ int matSectionIdx = 0;
+ emit dataChanged(index(matSectionIdx), index(matSectionIdx));
+}
+
+void ContentLibraryUserModel::addTextures(const QStringList &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);
+ }
+
+ int texSectionIdx = 1;
+ emit dataChanged(index(texSectionIdx), index(texSectionIdx));
+}
+
+// returns unique library material's name and qml component
+QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNameAndQml(const QString &matName) const
+{
+ QTC_ASSERT(!m_bundleObj.isEmpty(), return {});
+
+ const QJsonObject matsObj = m_bundleObj.value("materials").toObject();
+ const QStringList matNames = matsObj.keys();
+
+ QStringList matQmls;
+ for (const QString &matName : matNames)
+ matQmls.append(matsObj.value(matName).toObject().value("qml").toString().chopped(4)); // remove .qml
+
+ QString retName = matName.isEmpty() ? "Material" : matName;
+ retName = retName.trimmed();
+
+ QString retQml = retName;
+ retQml.remove(' ');
+ if (retQml.at(0).isLower())
+ retQml[0] = retQml.at(0).toUpper();
+ retQml.prepend("My");
+
+ int num = 1;
+ if (matNames.contains(retName) || matQmls.contains(retQml)) {
+ while (matNames.contains(retName + QString::number(num))
+ || matQmls.contains(retQml + QString::number(num))) {
+ ++num;
+ }
+
+ retName += QString::number(num);
+ retQml += QString::number(num);
+ }
+
+ return {retName, retQml + ".qml"};
+}
+
+TypeName ContentLibraryUserModel::qmlToModule(const QString &qmlName) const
+{
+ return QLatin1String("%1.%2.%3").arg(QmlDesignerPlugin::instance()->documentManager()
+ .generatedComponentUtils().componentBundlesTypePrefix(),
+ m_bundleId,
+ qmlName.chopped(4)).toLatin1(); // chopped(4): remove .qml
+}
+
+QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
+{
+ static const QHash<int, QByteArray> roles {
+ {NameRole, "categoryName"},
+ {VisibleRole, "categoryVisible"},
+ {ItemsRole, "categoryItems"}
+ };
+ return roles;
+}
+
+void ContentLibraryUserModel::createImporter(const QString &bundlePath, const QString &bundleId,
+ const QStringList &sharedFiles)
+{
+ m_importer = new Internal::ContentLibraryBundleImporter(bundlePath, bundleId, sharedFiles);
+#ifdef QDS_USE_PROJECTSTORAGE
+ connect(m_importer,
+ &Internal::ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::TypeName &typeName) {
+ m_importerRunning = false;
+ emit importerRunningChanged();
+ if (typeName.size())
+ emit bundleMaterialImported(typeName);
+ });
+#else
+ connect(m_importer,
+ &Internal::ContentLibraryBundleImporter::importFinished,
+ this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
+ m_importerRunning = false;
+ emit importerRunningChanged();
+ if (metaInfo.isValid())
+ emit bundleMaterialImported(metaInfo);
+ });
+#endif
+
+ connect(m_importer, &Internal::ContentLibraryBundleImporter::unimportFinished, this,
+ [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
+ Q_UNUSED(metaInfo)
+ m_importerRunning = false;
+ emit importerRunningChanged();
+ emit bundleMaterialUnimported(metaInfo);
+ });
+
+ resetModel();
+ updateIsEmpty();
+}
+
+QJsonObject &ContentLibraryUserModel::bundleJsonObjectRef()
+{
+ return m_bundleObj;
+}
+
+void ContentLibraryUserModel::loadMaterialBundle()
+{
+ if (m_matBundleExists)
+ return;
+
+ QDir bundleDir{Paths::bundlesPathSetting() + "/User/materials"};
+ bundleDir.mkpath(".");
+
+ if (m_bundleObj.isEmpty()) {
+ auto jsonFilePath = Utils::FilePath::fromString(bundleDir.filePath("user_materials_bundle.json"));
+ if (!jsonFilePath.exists()) {
+ QString jsonContent = "{\n";
+ jsonContent += " \"id\": \"UserMaterialBundle\",\n";
+ jsonContent += " \"materials\": {\n";
+ jsonContent += " }\n";
+ jsonContent += "}";
+ jsonFilePath.writeFileContents(jsonContent.toLatin1());
+ }
+
+ QFile jsonFile(jsonFilePath.path());
+ if (!jsonFile.open(QIODevice::ReadOnly)) {
+ qWarning("Couldn't open user_materials_bundle.json");
+ return;
+ }
+
+ QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(jsonFile.readAll());
+ if (matBundleJsonDoc.isNull()) {
+ qWarning("Invalid user_materials_bundle.json file");
+ return;
+ } else {
+ m_bundleObj = matBundleJsonDoc.object();
+ }
+ }
+
+ m_bundleId = m_bundleObj.value("id").toString();
+
+ // parse materials
+ const QJsonObject matsObj = m_bundleObj.value("materials").toObject();
+ const QStringList materialNames = matsObj.keys();
+ for (const QString &matName : materialNames) {
+ const QJsonObject matObj = matsObj.value(matName).toObject();
+
+ QStringList files;
+ const QJsonArray assetsArr = matObj.value("files").toArray();
+ for (const auto /*QJson{Const,}ValueRef*/ &asset : assetsArr)
+ files.append(asset.toString());
+
+ QUrl icon = QUrl::fromLocalFile(bundleDir.filePath(matObj.value("icon").toString()));
+ QString qml = matObj.value("qml").toString();
+
+ TypeName type = qmlToModule(qml);
+
+ auto userMat = new ContentLibraryMaterial(this, matName, qml, type, icon, files,
+ bundleDir.path(), "");
+
+ m_userMaterials.append(userMat);
+ }
+
+ QStringList sharedFiles;
+ const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray();
+ for (const auto /*QJson{Const,}ValueRef*/ &file : sharedFilesArr)
+ sharedFiles.append(file.toString());
+
+ createImporter(bundleDir.path(), m_bundleId, sharedFiles);
+
+ m_matBundleExists = true;
+ emit matBundleExistsChanged();
+}
+
+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);
+ }
+
+ int texSectionIdx = 1;
+ emit dataChanged(index(texSectionIdx), index(texSectionIdx));
+}
+
+bool ContentLibraryUserModel::hasRequiredQuick3DImport() const
+{
+ return m_widget->hasQuick3DImport() && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3;
+}
+
+bool ContentLibraryUserModel::matBundleExists() const
+{
+ return m_matBundleExists;
+}
+
+Internal::ContentLibraryBundleImporter *ContentLibraryUserModel::bundleImporter() const
+{
+ return m_importer;
+}
+
+void ContentLibraryUserModel::setSearchText(const QString &searchText)
+{
+ QString lowerSearchText = searchText.toLower();
+
+ if (m_searchText == lowerSearchText)
+ return;
+
+ m_searchText = lowerSearchText;
+
+ for (ContentLibraryMaterial *mat : std::as_const(m_userMaterials))
+ mat->filter(m_searchText);
+
+ updateIsEmpty();
+}
+
+void ContentLibraryUserModel::updateImportedState(const QStringList &importedMats)
+{
+ bool changed = false;
+
+ for (ContentLibraryMaterial *mat : std::as_const(m_userMaterials))
+ changed |= mat->setImported(importedMats.contains(mat->qml().chopped(4)));
+
+ if (changed)
+ resetModel();
+}
+
+void ContentLibraryUserModel::setQuick3DImportVersion(int major, int minor)
+{
+ bool oldRequiredImport = hasRequiredQuick3DImport();
+
+ m_quick3dMajorVersion = major;
+ m_quick3dMinorVersion = minor;
+
+ bool newRequiredImport = hasRequiredQuick3DImport();
+
+ if (oldRequiredImport == newRequiredImport)
+ return;
+
+ emit hasRequiredQuick3DImportChanged();
+
+ updateIsEmpty();
+}
+
+void ContentLibraryUserModel::resetModel()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+void ContentLibraryUserModel::applyToSelected(ContentLibraryMaterial *mat, bool add)
+{
+ emit applyToSelectedTriggered(mat, add);
+}
+
+void ContentLibraryUserModel::addToProject(ContentLibraryMaterial *mat)
+{
+ QString err = m_importer->importComponent(mat->qml(), mat->files());
+
+ if (err.isEmpty()) {
+ m_importerRunning = true;
+ emit importerRunningChanged();
+ } else {
+ qWarning() << __FUNCTION__ << err;
+ }
+}
+
+void ContentLibraryUserModel::removeFromProject(ContentLibraryMaterial *mat)
+{
+ emit bundleMaterialAboutToUnimport(mat->type());
+
+ QString err = m_importer->unimportComponent(mat->qml());
+
+ if (err.isEmpty()) {
+ m_importerRunning = true;
+ emit importerRunningChanged();
+ } else {
+ qWarning() << __FUNCTION__ << err;
+ }
+}
+
+bool ContentLibraryUserModel::hasModelSelection() const
+{
+ return m_hasModelSelection;
+}
+
+void ContentLibraryUserModel::setHasModelSelection(bool b)
+{
+ if (b == m_hasModelSelection)
+ return;
+
+ m_hasModelSelection = b;
+ emit hasModelSelectionChanged();
+}
+
+} // namespace QmlDesigner