forked from qt-creator/qt-creator
QmlDesigner: Allow adding a folder to content library
Change-Id: If44fdc0f0a7c59011854fd358f0542ce35ac1079 Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Ali Kianian <ali.kianian@qt.io>
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
import Qt.labs.qmlmodels
|
import Qt.labs.qmlmodels
|
||||||
import HelperWidgets as HelperWidgets
|
import HelperWidgets as HelperWidgets
|
||||||
import StudioControls as StudioControls
|
import StudioControls as StudioControls
|
||||||
@@ -79,9 +80,34 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: col
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: toolbar
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: StudioTheme.Values.toolbarHeight
|
||||||
|
color: StudioTheme.Values.themeToolbarBackground
|
||||||
|
|
||||||
|
HelperWidgets.AbstractButton {
|
||||||
|
style: StudioTheme.Values.viewBarButtonStyle
|
||||||
|
buttonIcon: StudioTheme.Constants.add_medium
|
||||||
|
enabled: hasMaterial && hasModelSelection && hasQuick3DImport && hasMaterialLibrary
|
||||||
|
tooltip: qsTr("Add a custom bundle folder.")
|
||||||
|
onClicked: ContentLibraryBackend.rootView.browseBundleFolder()
|
||||||
|
x: 5 // left margin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HelperWidgets.ScrollView {
|
HelperWidgets.ScrollView {
|
||||||
id: scrollView
|
id: scrollView
|
||||||
anchors.fill: parent
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
interactive: !ctxMenuItem.opened && !ctxMenuTexture.opened
|
interactive: !ctxMenuItem.opened && !ctxMenuTexture.opened
|
||||||
@@ -106,12 +132,20 @@ Item {
|
|||||||
caption: categoryTitle
|
caption: categoryTitle
|
||||||
dropEnabled: true
|
dropEnabled: true
|
||||||
category: "ContentLib_User"
|
category: "ContentLib_User"
|
||||||
|
showCloseButton: section.isCustomCat
|
||||||
|
closeButtonToolTip: qsTr("Remove folder")
|
||||||
|
closeButtonIcon: StudioTheme.Constants.deletepermanently_small
|
||||||
|
|
||||||
|
onCloseButtonClicked: {
|
||||||
|
ContentLibraryBackend.userModel.removeBundleDir(index)
|
||||||
|
}
|
||||||
|
|
||||||
function expandSection() {
|
function expandSection() {
|
||||||
section.expanded = true
|
section.expanded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
property alias count: repeater.count
|
property alias count: repeater.count
|
||||||
|
property bool isCustomCat: !["Textures", "Materials", "3D"].includes(section.caption);
|
||||||
|
|
||||||
onCountChanged: root.assignMaxCount()
|
onCountChanged: root.assignMaxCount()
|
||||||
|
|
||||||
@@ -125,6 +159,7 @@ Item {
|
|||||||
drag.accepted = (categoryTitle === "Textures" && hasTexture)
|
drag.accepted = (categoryTitle === "Textures" && hasTexture)
|
||||||
|| (categoryTitle === "Materials" && drag.formats[0] === "application/vnd.qtdesignstudio.material")
|
|| (categoryTitle === "Materials" && drag.formats[0] === "application/vnd.qtdesignstudio.material")
|
||||||
|| (categoryTitle === "3D" && has3DNode)
|
|| (categoryTitle === "3D" && has3DNode)
|
||||||
|
|| (section.isCustomCat && hasTexture)
|
||||||
|
|
||||||
section.highlight = drag.accepted
|
section.highlight = drag.accepted
|
||||||
}
|
}
|
||||||
@@ -147,6 +182,11 @@ Item {
|
|||||||
ContentLibraryBackend.rootView.acceptMaterialDrop(drag.getDataAsString(drag.formats[0]))
|
ContentLibraryBackend.rootView.acceptMaterialDrop(drag.getDataAsString(drag.formats[0]))
|
||||||
} else if (categoryTitle === "3D") {
|
} else if (categoryTitle === "3D") {
|
||||||
ContentLibraryBackend.rootView.accept3DDrop(drag.getDataAsArrayBuffer(drag.formats[0]))
|
ContentLibraryBackend.rootView.accept3DDrop(drag.getDataAsArrayBuffer(drag.formats[0]))
|
||||||
|
} else { // custom bundle folder
|
||||||
|
if (drag.formats[0] === "application/vnd.qtdesignstudio.assets")
|
||||||
|
ContentLibraryBackend.rootView.acceptTexturesDrop(drag.urls, categoryBundlePath)
|
||||||
|
else if (drag.formats[0] === "application/vnd.qtdesignstudio.texture")
|
||||||
|
ContentLibraryBackend.rootView.acceptTextureDrop(drag.getDataAsString(drag.formats[0]), categoryBundlePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +197,7 @@ Item {
|
|||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: repeater
|
id: repeater
|
||||||
|
|
||||||
model: categoryItems
|
model: categoryItems
|
||||||
|
|
||||||
delegate: DelegateChooser {
|
delegate: DelegateChooser {
|
||||||
@@ -215,17 +256,19 @@ Item {
|
|||||||
: categoryTitle.toLowerCase()
|
: categoryTitle.toLowerCase()
|
||||||
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
if (!ContentLibraryBackend.rootView.isQt6Project) {
|
||||||
qsTr("<b>Content Library</b> is not supported in Qt5 projects.")
|
qsTr("<b>Content Library</b> is not supported in Qt5 projects.")
|
||||||
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport && categoryTitle !== "Textures") {
|
} else if (!ContentLibraryBackend.rootView.hasQuick3DImport
|
||||||
|
&& categoryTitle !== "Textures" && !section.isCustomCat) {
|
||||||
qsTr('To use %1, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
qsTr('To use %1, add the <b>QtQuick3D</b> module and the <b>View3D</b>
|
||||||
component in the <b>Components</b> view, or click
|
component in the <b>Components</b> view, or click
|
||||||
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%2\">
|
<a href=\"#add_import\"><span style=\"text-decoration:none;color:%2\">
|
||||||
here</span></a>.')
|
here</span></a>.')
|
||||||
.arg(categoryName)
|
.arg(categoryName)
|
||||||
.arg(StudioTheme.Values.themeInteraction)
|
.arg(StudioTheme.Values.themeInteraction)
|
||||||
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary && categoryTitle !== "Textures") {
|
} else if (!ContentLibraryBackend.rootView.hasMaterialLibrary
|
||||||
|
&& categoryTitle !== "Textures" && !section.isCustomCat) {
|
||||||
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
qsTr("<b>Content Library</b> is disabled inside a non-visual component.")
|
||||||
} else if (categoryEmpty) {
|
} else if (categoryEmpty) {
|
||||||
qsTr("There are no "+ categoryName + " in the <b>User Assets</b>.")
|
qsTr("There are no items in this category.")
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
@@ -250,4 +293,5 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
#include "contentlibrarytexture.h"
|
#include "contentlibrarytexture.h"
|
||||||
#include "contentlibrarywidget.h"
|
#include "contentlibrarywidget.h"
|
||||||
|
|
||||||
|
#include <asset.h>
|
||||||
#include <bundleimporter.h>
|
#include <bundleimporter.h>
|
||||||
#include <designerpaths.h>
|
#include <designerpaths.h>
|
||||||
#include <imageutils.h>
|
#include <imageutils.h>
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
#include <qmldesignerplugin.h>
|
#include <qmldesignerplugin.h>
|
||||||
|
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/filesystemwatcher.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@@ -28,10 +30,18 @@ namespace QmlDesigner {
|
|||||||
ContentLibraryUserModel::ContentLibraryUserModel(ContentLibraryWidget *parent)
|
ContentLibraryUserModel::ContentLibraryUserModel(ContentLibraryWidget *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_widget(parent)
|
, m_widget(parent)
|
||||||
|
, m_fileWatcher(Utils::makeUniqueObjectPtr<Utils::FileSystemWatcher>(parent))
|
||||||
{
|
{
|
||||||
createCategories();
|
createCategories();
|
||||||
|
|
||||||
|
connect(m_fileWatcher.get(), &Utils::FileSystemWatcher::directoryChanged, this,
|
||||||
|
[this] (const QString &dirPath) {
|
||||||
|
reloadTextureCategory(Utils::FilePath::fromString(dirPath));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentLibraryUserModel::~ContentLibraryUserModel() = default;
|
||||||
|
|
||||||
int ContentLibraryUserModel::rowCount(const QModelIndex &) const
|
int ContentLibraryUserModel::rowCount(const QModelIndex &) const
|
||||||
{
|
{
|
||||||
return m_userCategories.size();
|
return m_userCategories.size();
|
||||||
@@ -44,17 +54,22 @@ QVariant ContentLibraryUserModel::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
UserCategory *currCat = m_userCategories.at(index.row());
|
UserCategory *currCat = m_userCategories.at(index.row());
|
||||||
|
|
||||||
if (role == TitleRole)
|
switch (role) {
|
||||||
|
case TitleRole:
|
||||||
return currCat->title();
|
return currCat->title();
|
||||||
|
|
||||||
if (role == ItemsRole)
|
case BundlePathRole:
|
||||||
|
return currCat->bundlePath().toVariant();
|
||||||
|
|
||||||
|
case ItemsRole:
|
||||||
return QVariant::fromValue(currCat->items());
|
return QVariant::fromValue(currCat->items());
|
||||||
|
|
||||||
if (role == NoMatchRole)
|
case NoMatchRole:
|
||||||
return currCat->noMatch();
|
return currCat->noMatch();
|
||||||
|
|
||||||
if (role == EmptyRole)
|
case EmptyRole:
|
||||||
return currCat->isEmpty();
|
return currCat->isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -76,6 +91,71 @@ void ContentLibraryUserModel::createCategories()
|
|||||||
compUtils.user3DBundleId()};
|
compUtils.user3DBundleId()};
|
||||||
|
|
||||||
m_userCategories << catMaterial << catTexture << cat3D;
|
m_userCategories << catMaterial << catTexture << cat3D;
|
||||||
|
|
||||||
|
loadCustomCategories(userBundlePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentLibraryUserModel::loadCustomCategories(const Utils::FilePath &userBundlePath)
|
||||||
|
{
|
||||||
|
auto jsonFilePath = userBundlePath.pathAppended(Constants::CUSTOM_BUNDLES_JSON_FILENAME);
|
||||||
|
if (!jsonFilePath.exists()) {
|
||||||
|
const QString jsonContent = QStringLiteral(R"({ "version": "%1", "items": {}})")
|
||||||
|
.arg(CUSTOM_BUNDLES_JSON_FILE_VERSION);
|
||||||
|
Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent.toLatin1());
|
||||||
|
QTC_ASSERT_EXPECTED(res, return);
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents();
|
||||||
|
QTC_ASSERT_EXPECTED(jsonContents, return);
|
||||||
|
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonContents.value());
|
||||||
|
QTC_ASSERT(!jsonDoc.isNull(), return);
|
||||||
|
|
||||||
|
m_customCatsRootObj = jsonDoc.object();
|
||||||
|
m_customCatsObj = m_customCatsRootObj.value("items").toObject();
|
||||||
|
|
||||||
|
for (auto it = m_customCatsObj.constBegin(); it != m_customCatsObj.constEnd(); ++it) {
|
||||||
|
auto dirPath = Utils::FilePath::fromString(it.key());
|
||||||
|
if (!dirPath.exists())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
addBundleDir(dirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContentLibraryUserModel::bundleDirExists(const QString &dirPath) const
|
||||||
|
{
|
||||||
|
return m_customCatsObj.contains(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentLibraryUserModel::addBundleDir(const Utils::FilePath &dirPath)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!dirPath.isEmpty(), return);
|
||||||
|
|
||||||
|
// TODO: detect if a bundle exists in the dir, determine its type, and create a matching category.
|
||||||
|
// For now we consider a custom folder as a texture bundle
|
||||||
|
|
||||||
|
auto newCat = new UserTextureCategory{dirPath.fileName(), dirPath};
|
||||||
|
newCat->loadBundle();
|
||||||
|
|
||||||
|
beginInsertRows({}, m_userCategories.size(), m_userCategories.size());
|
||||||
|
m_userCategories << newCat;
|
||||||
|
endInsertRows();
|
||||||
|
|
||||||
|
m_fileWatcher->addDirectory(dirPath, Utils::FileSystemWatcher::WatchAllChanges);
|
||||||
|
|
||||||
|
// add the folder to custom bundles json file if it is missing
|
||||||
|
const QString dirPathStr = dirPath.toFSPathString();
|
||||||
|
if (!m_customCatsObj.contains(dirPathStr)) {
|
||||||
|
m_customCatsObj.insert(dirPathStr, QJsonObject{});
|
||||||
|
|
||||||
|
m_customCatsRootObj["items"] = m_customCatsObj;
|
||||||
|
|
||||||
|
auto userBundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User");
|
||||||
|
auto jsonFilePath = userBundlePath.pathAppended(Constants::CUSTOM_BUNDLES_JSON_FILENAME);
|
||||||
|
auto result = jsonFilePath.writeFileContents(QJsonDocument(m_customCatsRootObj).toJson());
|
||||||
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentLibraryUserModel::addItem(const QString &bundleId, const QString &name,
|
void ContentLibraryUserModel::addItem(const QString &bundleId, const QString &name,
|
||||||
@@ -102,22 +182,36 @@ void ContentLibraryUserModel::refreshSection(const QString &bundleId)
|
|||||||
updateIsEmpty();
|
updateIsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentLibraryUserModel::addTextures(const Utils::FilePaths &paths)
|
void ContentLibraryUserModel::addTextures(const Utils::FilePaths &paths, const Utils::FilePath &bundlePath)
|
||||||
{
|
{
|
||||||
auto texCat = qobject_cast<UserTextureCategory *>(m_userCategories[TexturesSectionIdx]);
|
int catIdx = bundlePathToIndex(bundlePath);
|
||||||
|
UserTextureCategory *texCat = qobject_cast<UserTextureCategory *>(m_userCategories.at(catIdx));
|
||||||
QTC_ASSERT(texCat, return);
|
QTC_ASSERT(texCat, return);
|
||||||
|
|
||||||
texCat->addItems(paths);
|
texCat->addItems(paths);
|
||||||
|
|
||||||
emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx), {ItemsRole, EmptyRole});
|
emit dataChanged(index(catIdx), index(catIdx), {ItemsRole, EmptyRole});
|
||||||
updateIsEmpty();
|
updateIsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentLibraryUserModel::removeTextures(const QStringList &fileNames)
|
void ContentLibraryUserModel::reloadTextureCategory(const Utils::FilePath &dirPath)
|
||||||
|
{
|
||||||
|
int catIdx = bundlePathToIndex(dirPath);
|
||||||
|
UserTextureCategory *texCat = qobject_cast<UserTextureCategory *>(m_userCategories.at(catIdx));
|
||||||
|
QTC_ASSERT(texCat, return);
|
||||||
|
|
||||||
|
const Utils::FilePaths &paths = dirPath.dirEntries({Asset::supportedImageSuffixes(), QDir::Files});
|
||||||
|
|
||||||
|
texCat->clearItems();
|
||||||
|
addTextures(paths, dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentLibraryUserModel::removeTextures(const QStringList &fileNames, const Utils::FilePath &bundlePath)
|
||||||
{
|
{
|
||||||
// note: this method doesn't refresh the model after textures removal
|
// note: this method doesn't refresh the model after textures removal
|
||||||
|
|
||||||
auto texCat = qobject_cast<UserTextureCategory *>(m_userCategories[TexturesSectionIdx]);
|
int catIdx = bundlePathToIndex(bundlePath);
|
||||||
|
UserTextureCategory *texCat = qobject_cast<UserTextureCategory *>(m_userCategories.at(catIdx));
|
||||||
QTC_ASSERT(texCat, return);
|
QTC_ASSERT(texCat, return);
|
||||||
|
|
||||||
const QObjectList items = texCat->items();
|
const QObjectList items = texCat->items();
|
||||||
@@ -137,13 +231,28 @@ void ContentLibraryUserModel::removeTexture(ContentLibraryTexture *tex, bool ref
|
|||||||
Utils::FilePath::fromString(tex->iconPath()).removeFile();
|
Utils::FilePath::fromString(tex->iconPath()).removeFile();
|
||||||
|
|
||||||
// remove from model
|
// remove from model
|
||||||
m_userCategories[TexturesSectionIdx]->removeItem(tex);
|
UserTextureCategory *itemCat = qobject_cast<UserTextureCategory *>(tex->parent());
|
||||||
|
QTC_ASSERT(itemCat, return);
|
||||||
|
itemCat->removeItem(tex);
|
||||||
|
|
||||||
// update model
|
// update model
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx));
|
int catIdx = bundlePathToIndex(itemCat->bundlePath());
|
||||||
|
emit dataChanged(index(catIdx), index(catIdx));
|
||||||
updateIsEmpty();
|
updateIsEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString bundlePathStr = itemCat->bundlePath().toFSPathString();
|
||||||
|
if (m_customCatsObj.contains(bundlePathStr)) {
|
||||||
|
m_customCatsObj.remove(bundlePathStr);
|
||||||
|
|
||||||
|
m_customCatsRootObj["items"] = m_customCatsObj;
|
||||||
|
|
||||||
|
auto userBundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User");
|
||||||
|
auto jsonFilePath = userBundlePath.pathAppended(Constants::CUSTOM_BUNDLES_JSON_FILENAME);
|
||||||
|
auto result = jsonFilePath.writeFileContents(QJsonDocument(m_customCatsRootObj).toJson());
|
||||||
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentLibraryUserModel::removeFromContentLib(QObject *item)
|
void ContentLibraryUserModel::removeFromContentLib(QObject *item)
|
||||||
@@ -154,6 +263,30 @@ void ContentLibraryUserModel::removeFromContentLib(QObject *item)
|
|||||||
removeItem(castedItem);
|
removeItem(castedItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContentLibraryUserModel::removeBundleDir(int catIdx)
|
||||||
|
{
|
||||||
|
auto texCat = qobject_cast<UserTextureCategory *>(m_userCategories.at(catIdx));
|
||||||
|
QTC_ASSERT(texCat, return);
|
||||||
|
|
||||||
|
QString dirPath = texCat->bundlePath().toFSPathString();
|
||||||
|
|
||||||
|
// remove from json
|
||||||
|
QTC_ASSERT(m_customCatsObj.contains(dirPath), return);
|
||||||
|
m_customCatsObj.remove(dirPath);
|
||||||
|
m_customCatsRootObj["items"] = m_customCatsObj;
|
||||||
|
|
||||||
|
auto userBundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User");
|
||||||
|
auto jsonFilePath = userBundlePath.pathAppended(Constants::CUSTOM_BUNDLES_JSON_FILENAME);
|
||||||
|
auto result = jsonFilePath.writeFileContents(QJsonDocument(m_customCatsRootObj).toJson());
|
||||||
|
QTC_ASSERT_EXPECTED(result, return);
|
||||||
|
|
||||||
|
// remove from model
|
||||||
|
beginRemoveRows({}, catIdx, catIdx);
|
||||||
|
delete texCat;
|
||||||
|
m_userCategories.removeAt(catIdx);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
|
||||||
void ContentLibraryUserModel::removeItemByName(const QString &qmlFileName, const QString &bundleId)
|
void ContentLibraryUserModel::removeItemByName(const QString &qmlFileName, const QString &bundleId)
|
||||||
{
|
{
|
||||||
ContentLibraryItem *itemToRemove = nullptr;
|
ContentLibraryItem *itemToRemove = nullptr;
|
||||||
@@ -239,10 +372,27 @@ ContentLibraryUserModel::SectionIndex ContentLibraryUserModel::bundleIdToSection
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ContentLibraryUserModel::bundlePathToIndex(const QString &bundlePath) const
|
||||||
|
{
|
||||||
|
return bundlePathToIndex(Utils::FilePath::fromString(bundlePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContentLibraryUserModel::bundlePathToIndex(const Utils::FilePath &bundlePath) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_userCategories.size(); ++i) {
|
||||||
|
if (m_userCategories.at(i)->bundlePath() == bundlePath)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << __FUNCTION__ << "Invalid bundlePath:" << bundlePath;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
|
QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
|
||||||
{
|
{
|
||||||
static const QHash<int, QByteArray> roles {
|
static const QHash<int, QByteArray> roles {
|
||||||
{TitleRole, "categoryTitle"},
|
{TitleRole, "categoryTitle"},
|
||||||
|
{BundlePathRole, "categoryBundlePath"},
|
||||||
{EmptyRole, "categoryEmpty"},
|
{EmptyRole, "categoryEmpty"},
|
||||||
{ItemsRole, "categoryItems"},
|
{ItemsRole, "categoryItems"},
|
||||||
{NoMatchRole, "categoryNoMatch"}
|
{NoMatchRole, "categoryNoMatch"}
|
||||||
|
@@ -6,10 +6,15 @@
|
|||||||
#include "usercategory.h"
|
#include "usercategory.h"
|
||||||
|
|
||||||
#include <utils/filepath.h>
|
#include <utils/filepath.h>
|
||||||
|
#include <utils/uniqueobjectptr.h>
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
class FileSystemWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
QT_FORWARD_DECLARE_CLASS(QUrl)
|
QT_FORWARD_DECLARE_CLASS(QUrl)
|
||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
@@ -28,6 +33,7 @@ class ContentLibraryUserModel : public QAbstractListModel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
ContentLibraryUserModel(ContentLibraryWidget *parent = nullptr);
|
ContentLibraryUserModel(ContentLibraryWidget *parent = nullptr);
|
||||||
|
~ContentLibraryUserModel();
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
@@ -50,8 +56,9 @@ public:
|
|||||||
void addItem(const QString &bundleId, const QString &name, const QString &qml,const QUrl &icon,
|
void addItem(const QString &bundleId, const QString &name, const QString &qml,const QUrl &icon,
|
||||||
const QStringList &files);
|
const QStringList &files);
|
||||||
void refreshSection(const QString &bundleId);
|
void refreshSection(const QString &bundleId);
|
||||||
void addTextures(const Utils::FilePaths &paths);
|
void addTextures(const Utils::FilePaths &paths, const Utils::FilePath &bundlePath);
|
||||||
void removeTextures(const QStringList &fileNames);
|
void reloadTextureCategory(const Utils::FilePath &dirPath);
|
||||||
|
void removeTextures(const QStringList &fileNames, const Utils::FilePath &bundlePath);
|
||||||
|
|
||||||
void removeItemByName(const QString &qmlFileName, const QString &bundleId);
|
void removeItemByName(const QString &qmlFileName, const QString &bundleId);
|
||||||
|
|
||||||
@@ -59,12 +66,15 @@ public:
|
|||||||
QJsonObject &bundleObjectRef(const QString &bundleId);
|
QJsonObject &bundleObjectRef(const QString &bundleId);
|
||||||
|
|
||||||
void loadBundles(bool force = false);
|
void loadBundles(bool force = false);
|
||||||
|
void addBundleDir(const Utils::FilePath &dirPath);
|
||||||
|
bool bundleDirExists(const QString &dirPath) const;
|
||||||
|
|
||||||
Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryItem *mat, bool add = false);
|
Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryItem *mat, bool add = false);
|
||||||
Q_INVOKABLE void addToProject(ContentLibraryItem *item);
|
Q_INVOKABLE void addToProject(ContentLibraryItem *item);
|
||||||
Q_INVOKABLE void removeFromProject(QObject *item);
|
Q_INVOKABLE void removeFromProject(QObject *item);
|
||||||
Q_INVOKABLE void removeTexture(QmlDesigner::ContentLibraryTexture *tex, bool refresh = true);
|
Q_INVOKABLE void removeTexture(QmlDesigner::ContentLibraryTexture *tex, bool refresh = true);
|
||||||
Q_INVOKABLE void removeFromContentLib(QObject *item);
|
Q_INVOKABLE void removeFromContentLib(QObject *item);
|
||||||
|
Q_INVOKABLE void removeBundleDir(int catIdx);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void hasRequiredQuick3DImportChanged();
|
void hasRequiredQuick3DImportChanged();
|
||||||
@@ -79,14 +89,20 @@ private:
|
|||||||
EffectsSectionIdx };
|
EffectsSectionIdx };
|
||||||
|
|
||||||
void createCategories();
|
void createCategories();
|
||||||
|
void loadCustomCategories(const Utils::FilePath &userBundlePath);
|
||||||
void loadMaterialBundle();
|
void loadMaterialBundle();
|
||||||
void load3DBundle();
|
void load3DBundle();
|
||||||
void loadTextureBundle();
|
void loadTextureBundle();
|
||||||
void removeItem(ContentLibraryItem *item);
|
void removeItem(ContentLibraryItem *item);
|
||||||
SectionIndex bundleIdToSectionIndex(const QString &bundleId) const;
|
SectionIndex bundleIdToSectionIndex(const QString &bundleId) const;
|
||||||
|
int bundlePathToIndex(const QString &bundlePath) const;
|
||||||
|
int bundlePathToIndex(const Utils::FilePath &bundlePath) const;
|
||||||
|
|
||||||
ContentLibraryWidget *m_widget = nullptr;
|
ContentLibraryWidget *m_widget = nullptr;
|
||||||
|
QJsonObject m_customCatsRootObj;
|
||||||
|
QJsonObject m_customCatsObj;
|
||||||
QString m_searchText;
|
QString m_searchText;
|
||||||
|
Utils::UniqueObjectPtr<Utils::FileSystemWatcher> m_fileWatcher;
|
||||||
|
|
||||||
QList<UserCategory *> m_userCategories;
|
QList<UserCategory *> m_userCategories;
|
||||||
|
|
||||||
@@ -95,7 +111,9 @@ private:
|
|||||||
int m_quick3dMajorVersion = -1;
|
int m_quick3dMajorVersion = -1;
|
||||||
int m_quick3dMinorVersion = -1;
|
int m_quick3dMinorVersion = -1;
|
||||||
|
|
||||||
enum Roles { TitleRole = Qt::UserRole + 1, ItemsRole, EmptyRole, NoMatchRole };
|
enum Roles { TitleRole = Qt::UserRole + 1, BundlePathRole, ItemsRole, EmptyRole, NoMatchRole };
|
||||||
|
|
||||||
|
static constexpr char CUSTOM_BUNDLES_JSON_FILE_VERSION[] = "1.0";
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -91,7 +91,7 @@ WidgetInfo ContentLibraryView::widgetInfo()
|
|||||||
});
|
});
|
||||||
|
|
||||||
connect(m_widget, &ContentLibraryWidget::acceptTextureDrop, this,
|
connect(m_widget, &ContentLibraryWidget::acceptTextureDrop, this,
|
||||||
[this](const QString &internalId) {
|
[this](const QString &internalId, const QString &bundlePath) {
|
||||||
ModelNode texNode = QmlDesignerPlugin::instance()->viewManager()
|
ModelNode texNode = QmlDesignerPlugin::instance()->viewManager()
|
||||||
.view()->modelNodeForInternalId(internalId.toInt());
|
.view()->modelNodeForInternalId(internalId.toInt());
|
||||||
auto [qmlString, depAssets] = m_bundleHelper->modelNodeToQmlString(texNode);
|
auto [qmlString, depAssets] = m_bundleHelper->modelNodeToQmlString(texNode);
|
||||||
@@ -104,11 +104,11 @@ WidgetInfo ContentLibraryView::widgetInfo()
|
|||||||
paths.append(path);
|
paths.append(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
addLibAssets(paths);
|
addLibAssets(paths, bundlePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_widget, &ContentLibraryWidget::acceptTexturesDrop, this,
|
connect(m_widget, &ContentLibraryWidget::acceptTexturesDrop, this,
|
||||||
[this](const QList<QUrl> &urls) {
|
[this](const QList<QUrl> &urls, const QString &bundlePath) {
|
||||||
QStringList paths;
|
QStringList paths;
|
||||||
|
|
||||||
for (const QUrl &url : urls) {
|
for (const QUrl &url : urls) {
|
||||||
@@ -117,7 +117,7 @@ WidgetInfo ContentLibraryView::widgetInfo()
|
|||||||
if (Asset(path).isValidTextureSource())
|
if (Asset(path).isValidTextureSource())
|
||||||
paths.append(path);
|
paths.append(path);
|
||||||
}
|
}
|
||||||
addLibAssets(paths);
|
addLibAssets(paths, bundlePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_widget, &ContentLibraryWidget::acceptMaterialDrop, this,
|
connect(m_widget, &ContentLibraryWidget::acceptMaterialDrop, this,
|
||||||
@@ -569,14 +569,16 @@ void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundle
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void ContentLibraryView::addLibAssets(const QStringList &paths)
|
void ContentLibraryView::addLibAssets(const QStringList &paths, const QString &bundlePath)
|
||||||
{
|
{
|
||||||
auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/textures");
|
auto fullBundlePath = Utils::FilePath::fromString(bundlePath.isEmpty()
|
||||||
|
? Paths::bundlesPathSetting() + "/User/textures"
|
||||||
|
: bundlePath);
|
||||||
Utils::FilePaths sourcePathsToAdd;
|
Utils::FilePaths sourcePathsToAdd;
|
||||||
Utils::FilePaths targetPathsToAdd;
|
Utils::FilePaths targetPathsToAdd;
|
||||||
QStringList fileNamesToRemove;
|
QStringList fileNamesToRemove;
|
||||||
|
|
||||||
const QStringList existingAssetsFileNames = Utils::transform(bundlePath.dirEntries(QDir::Files),
|
const QStringList existingAssetsFileNames = Utils::transform(fullBundlePath.dirEntries(QDir::Files),
|
||||||
&Utils::FilePath::fileName);
|
&Utils::FilePath::fileName);
|
||||||
|
|
||||||
for (const QString &path : paths) {
|
for (const QString &path : paths) {
|
||||||
@@ -598,21 +600,13 @@ void ContentLibraryView::addLibAssets(const QStringList &paths)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove the to-be-overwritten resources from target bundle path
|
// remove the to-be-overwritten resources from target bundle path
|
||||||
m_widget->userModel()->removeTextures(fileNamesToRemove);
|
m_widget->userModel()->removeTextures(fileNamesToRemove, fullBundlePath);
|
||||||
|
|
||||||
// copy resources to target bundle path
|
// copy resources to target bundle path
|
||||||
for (const Utils::FilePath &sourcePath : sourcePathsToAdd) {
|
for (const Utils::FilePath &sourcePath : sourcePathsToAdd) {
|
||||||
Utils::FilePath targetPath = bundlePath.pathAppended(sourcePath.fileName());
|
Utils::FilePath targetPath = fullBundlePath.pathAppended(sourcePath.fileName());
|
||||||
Asset asset{sourcePath.toFSPathString()};
|
Asset asset{sourcePath.toFSPathString()};
|
||||||
|
|
||||||
// save icon
|
|
||||||
QString iconSavePath = bundlePath.pathAppended("icons/" + sourcePath.baseName() + ".png")
|
|
||||||
.toFSPathString();
|
|
||||||
QPixmap icon = asset.pixmap({120, 120});
|
|
||||||
bool iconSaved = icon.save(iconSavePath);
|
|
||||||
if (!iconSaved)
|
|
||||||
qWarning() << __FUNCTION__ << "icon save failed";
|
|
||||||
|
|
||||||
// save asset
|
// save asset
|
||||||
auto result = sourcePath.copyFile(targetPath);
|
auto result = sourcePath.copyFile(targetPath);
|
||||||
QTC_ASSERT_EXPECTED(result,);
|
QTC_ASSERT_EXPECTED(result,);
|
||||||
@@ -620,7 +614,7 @@ void ContentLibraryView::addLibAssets(const QStringList &paths)
|
|||||||
targetPathsToAdd.append(targetPath);
|
targetPathsToAdd.append(targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_widget->userModel()->addTextures(targetPathsToAdd);
|
m_widget->userModel()->addTextures(targetPathsToAdd, fullBundlePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: combine this method with BundleHelper::exportComponent()
|
// TODO: combine this method with BundleHelper::exportComponent()
|
||||||
|
@@ -63,7 +63,7 @@ private:
|
|||||||
bool isItemBundle(const QString &bundleId) const;
|
bool isItemBundle(const QString &bundleId) const;
|
||||||
void active3DSceneChanged(qint32 sceneId);
|
void active3DSceneChanged(qint32 sceneId);
|
||||||
void updateBundlesQuick3DVersion();
|
void updateBundlesQuick3DVersion();
|
||||||
void addLibAssets(const QStringList &paths);
|
void addLibAssets(const QStringList &paths, const QString &bundlePath = {});
|
||||||
void addLib3DComponent(const ModelNode &node);
|
void addLib3DComponent(const ModelNode &node);
|
||||||
void addLibItem(const ModelNode &node, const QPixmap &iconPixmap = {});
|
void addLibItem(const ModelNode &node, const QPixmap &iconPixmap = {});
|
||||||
void importBundleToContentLib();
|
void importBundleToContentLib();
|
||||||
|
@@ -190,6 +190,21 @@ ContentLibraryWidget::~ContentLibraryWidget()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContentLibraryWidget::browseBundleFolder()
|
||||||
|
{
|
||||||
|
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();
|
||||||
|
QTC_ASSERT(document, return);
|
||||||
|
const QString currentDir = document->fileName().parentDir().toUrlishString();
|
||||||
|
|
||||||
|
QString dir = QFileDialog::getExistingDirectory(Core::ICore::dialogParent(),
|
||||||
|
tr("Choose Directory"),
|
||||||
|
currentDir,
|
||||||
|
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
|
||||||
|
|
||||||
|
if (!dir.isEmpty() && !m_userModel->bundleDirExists(dir))
|
||||||
|
m_userModel->addBundleDir(Utils::FilePath::fromString(dir));
|
||||||
|
}
|
||||||
|
|
||||||
void ContentLibraryWidget::createImporter()
|
void ContentLibraryWidget::createImporter()
|
||||||
{
|
{
|
||||||
m_importer = new BundleImporter();
|
m_importer = new BundleImporter();
|
||||||
|
@@ -104,6 +104,7 @@ public:
|
|||||||
Q_INVOKABLE bool has3DNode(const QByteArray &data) const;
|
Q_INVOKABLE bool has3DNode(const QByteArray &data) const;
|
||||||
Q_INVOKABLE bool hasTexture(const QString &format, const QVariant &data) const;
|
Q_INVOKABLE bool hasTexture(const QString &format, const QVariant &data) const;
|
||||||
Q_INVOKABLE void addQtQuick3D();
|
Q_INVOKABLE void addQtQuick3D();
|
||||||
|
Q_INVOKABLE void browseBundleFolder();
|
||||||
|
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
@@ -127,8 +128,8 @@ signals:
|
|||||||
void hasModelSelectionChanged();
|
void hasModelSelectionChanged();
|
||||||
void importBundle();
|
void importBundle();
|
||||||
void requestTab(int tabIndex);
|
void requestTab(int tabIndex);
|
||||||
void acceptTexturesDrop(const QList<QUrl> &urls);
|
void acceptTexturesDrop(const QList<QUrl> &urls, const QString &bundlePath = {});
|
||||||
void acceptTextureDrop(const QString &internalId);
|
void acceptTextureDrop(const QString &internalId, const QString &bundlePath = {});
|
||||||
void acceptMaterialDrop(const QString &internalId);
|
void acceptMaterialDrop(const QString &internalId);
|
||||||
void accept3DDrop(const QByteArray &internalIds);
|
void accept3DDrop(const QByteArray &internalIds);
|
||||||
void importQtQuick3D();
|
void importQtQuick3D();
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "contentlibrarytexture.h"
|
#include "contentlibrarytexture.h"
|
||||||
|
|
||||||
#include <designerpaths.h>
|
#include <asset.h>
|
||||||
#include <imageutils.h>
|
#include <imageutils.h>
|
||||||
|
|
||||||
namespace QmlDesigner {
|
namespace QmlDesigner {
|
||||||
@@ -27,7 +27,7 @@ void UserTextureCategory::loadBundle(bool force)
|
|||||||
m_bundlePath.ensureWritableDir();
|
m_bundlePath.ensureWritableDir();
|
||||||
m_bundlePath.pathAppended("icons").ensureWritableDir();
|
m_bundlePath.pathAppended("icons").ensureWritableDir();
|
||||||
|
|
||||||
addItems(m_bundlePath.dirEntries(QDir::Files));
|
addItems(m_bundlePath.dirEntries({Asset::supportedImageSuffixes(), QDir::Files}));
|
||||||
|
|
||||||
m_bundleLoaded = true;
|
m_bundleLoaded = true;
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,14 @@ void UserTextureCategory::addItems(const Utils::FilePaths &paths)
|
|||||||
QSize imgDims = info.first;
|
QSize imgDims = info.first;
|
||||||
qint64 imgFileSize = info.second;
|
qint64 imgFileSize = info.second;
|
||||||
|
|
||||||
|
if (!iconFileInfo.exists()) { // generate an icon if one doesn't exist
|
||||||
|
Asset asset{filePath.toFSPathString()};
|
||||||
|
QPixmap icon = asset.pixmap({120, 120});
|
||||||
|
bool iconSaved = icon.save(iconFileInfo.filePath());
|
||||||
|
if (!iconSaved)
|
||||||
|
qWarning() << __FUNCTION__ << "icon save failed";
|
||||||
|
}
|
||||||
|
|
||||||
auto tex = new ContentLibraryTexture(this, iconFileInfo, dirPath, suffix, imgDims, imgFileSize);
|
auto tex = new ContentLibraryTexture(this, iconFileInfo, dirPath, suffix, imgDims, imgFileSize);
|
||||||
m_items.append(tex);
|
m_items.append(tex);
|
||||||
}
|
}
|
||||||
@@ -63,4 +71,13 @@ void UserTextureCategory::addItems(const Utils::FilePaths &paths)
|
|||||||
emit itemsChanged();
|
emit itemsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UserTextureCategory::clearItems()
|
||||||
|
{
|
||||||
|
qDeleteAll(m_items);
|
||||||
|
m_items.clear();
|
||||||
|
|
||||||
|
setIsEmpty(true);
|
||||||
|
emit itemsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -16,10 +16,11 @@ class UserTextureCategory : public UserCategory
|
|||||||
public:
|
public:
|
||||||
UserTextureCategory(const QString &title, const Utils::FilePath &bundlePath);
|
UserTextureCategory(const QString &title, const Utils::FilePath &bundlePath);
|
||||||
|
|
||||||
void loadBundle(bool force) override;
|
void loadBundle(bool force = false) override;
|
||||||
void filter(const QString &searchText) override;
|
void filter(const QString &searchText) override;
|
||||||
|
|
||||||
void addItems(const Utils::FilePaths &paths);
|
void addItems(const Utils::FilePaths &paths);
|
||||||
|
void clearItems();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace QmlDesigner
|
} // namespace QmlDesigner
|
||||||
|
@@ -59,6 +59,7 @@ inline constexpr char EDIT3D_SNAP_CONFIG[] = "QmlDesigner.Editor3D.SnapConfig";
|
|||||||
inline constexpr char EDIT3D_CAMERA_SPEED_CONFIG[] = "QmlDesigner.Editor3D.CameraSpeedConfig";
|
inline constexpr char EDIT3D_CAMERA_SPEED_CONFIG[] = "QmlDesigner.Editor3D.CameraSpeedConfig";
|
||||||
|
|
||||||
inline constexpr char BUNDLE_JSON_FILENAME[] = "bundle.json";
|
inline constexpr char BUNDLE_JSON_FILENAME[] = "bundle.json";
|
||||||
|
inline constexpr char CUSTOM_BUNDLES_JSON_FILENAME[] = "custom_bundles.json";
|
||||||
inline constexpr char BUNDLE_SUFFIX[] = "qdsbundle";
|
inline constexpr char BUNDLE_SUFFIX[] = "qdsbundle";
|
||||||
inline constexpr char COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE[] = "Effects";
|
inline constexpr char COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE[] = "Effects";
|
||||||
inline constexpr char COMPONENT_BUNDLES_ASSET_REF_FILE[] = "_asset_ref.json";
|
inline constexpr char COMPONENT_BUNDLES_ASSET_REF_FILE[] = "_asset_ref.json";
|
||||||
|
Reference in New Issue
Block a user