diff options
Diffstat (limited to 'src/plugins/qmldesigner/components')
82 files changed, 2323 insertions, 587 deletions
diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp index dc5a1c9741..b821cc6595 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.cpp @@ -2,9 +2,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "assetslibraryiconprovider.h" -#include "asset.h" -#include "modelnodeoperations.h" +#include <modelnodeoperations.h> #include <theme.h> #include <utils/hdrimage.h> #include <utils/ktximage.h> diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h index fb38605ea6..d52779232f 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryiconprovider.h @@ -3,12 +3,11 @@ #pragma once +#include <asset.h> #include <synchronousimagecache.h> #include <QQuickImageProvider> -#include "asset.h" - namespace QmlDesigner { struct Thumbnail diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index c2359409eb..9d09f52d8f 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -1,21 +1,19 @@ // Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include <QCheckBox> -#include <QFileInfo> -#include <QFileSystemModel> -#include <QMessageBox> -#include <QSortFilterProxyModel> - -#include "asset.h" #include "assetslibrarymodel.h" +#include <asset.h> #include <modelnodeoperations.h> #include <qmldesignerplugin.h> #include <coreplugin/icore.h> #include <utils/algorithm.h> -#include <utils/qtcassert.h> +#include <utils/filesystemwatcher.h> + +#include <QFileInfo> +#include <QFileSystemModel> +#include <QMessageBox> namespace QmlDesigner { @@ -38,7 +36,7 @@ void AssetsLibraryModel::createBackendModel() QObject::connect(m_sourceFsModel, &QFileSystemModel::directoryLoaded, this, [this]([[maybe_unused]] const QString &dir) { - syncHaveFiles(); + syncHasFiles(); }); m_fileWatcher = new Utils::FileSystemWatcher(parent()); @@ -207,7 +205,7 @@ bool AssetsLibraryModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour } } -bool AssetsLibraryModel::checkHaveFiles(const QModelIndex &parentIdx) const +bool AssetsLibraryModel::checkHasFiles(const QModelIndex &parentIdx) const { if (!parentIdx.isValid()) return false; @@ -218,30 +216,30 @@ bool AssetsLibraryModel::checkHaveFiles(const QModelIndex &parentIdx) const if (!isDirectory(newIdx)) return true; - if (checkHaveFiles(newIdx)) + if (checkHasFiles(newIdx)) return true; } return false; } -void AssetsLibraryModel::setHaveFiles(bool value) +void AssetsLibraryModel::setHasFiles(bool value) { - if (m_haveFiles != value) { - m_haveFiles = value; - emit haveFilesChanged(); + if (m_hasFiles != value) { + m_hasFiles = value; + emit hasFilesChanged(); } } -bool AssetsLibraryModel::checkHaveFiles() const +bool AssetsLibraryModel::checkHasFiles() const { auto rootIdx = indexForPath(m_rootPath); - return checkHaveFiles(rootIdx); + return checkHasFiles(rootIdx); } -void AssetsLibraryModel::syncHaveFiles() +void AssetsLibraryModel::syncHasFiles() { - setHaveFiles(checkHaveFiles()); + setHasFiles(checkHasFiles()); } QString AssetsLibraryModel::getUniqueName(const QString &oldName) { diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index 9334e86e9b..2516be787f 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h @@ -3,12 +3,13 @@ #pragma once -#include <QFileInfo> -#include <QFileSystemModel> #include <QSortFilterProxyModel> -#include <utils/filesystemwatcher.h> -#include <utils/qtcassert.h> +namespace Utils { +class FileSystemWatcher; +} + +QT_FORWARD_DECLARE_CLASS(QFileSystemModel) namespace QmlDesigner { @@ -22,7 +23,7 @@ public: void setRootPath(const QString &newPath); void setSearchText(const QString &searchText); - Q_PROPERTY(bool haveFiles READ haveFiles NOTIFY haveFilesChanged); + Q_PROPERTY(bool hasFiles READ hasFiles NOTIFY hasFilesChanged) Q_INVOKABLE QString rootPath() const; Q_INVOKABLE QString filePath(const QModelIndex &index) const; @@ -35,7 +36,7 @@ public: Q_INVOKABLE QModelIndex parentDirIndex(const QString &path) const; Q_INVOKABLE QModelIndex parentDirIndex(const QModelIndex &index) const; Q_INVOKABLE QString parentDirPath(const QString &path) const; - Q_INVOKABLE void syncHaveFiles(); + Q_INVOKABLE void syncHasFiles(); Q_INVOKABLE QList<QModelIndex> parentIndices(const QModelIndex &index) const; Q_INVOKABLE bool indexIsValid(const QModelIndex &index) const; @@ -55,30 +56,30 @@ public: return std::min(result, 1); } - bool haveFiles() const { return m_haveFiles; } + bool hasFiles() const { return m_hasFiles; } QString getUniqueName(const QString &oldName); signals: void directoryLoaded(const QString &path); void rootPathChanged(); - void haveFilesChanged(); + void hasFilesChanged(); void fileChanged(const QString &path); void effectsDeleted(const QStringList &effectNames); private: - void setHaveFiles(bool value); + void setHasFiles(bool value); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; void resetModel(); void createBackendModel(); void destroyBackendModel(); - bool checkHaveFiles(const QModelIndex &parentIdx) const; - bool checkHaveFiles() const; + bool checkHasFiles(const QModelIndex &parentIdx) const; + bool checkHasFiles() const; QString m_searchText; QString m_rootPath; QFileSystemModel *m_sourceFsModel = nullptr; - bool m_haveFiles = false; + bool m_hasFiles = false; Utils::FileSystemWatcher *m_fileWatcher = nullptr; }; diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 3b98eb6baf..4b270c8902 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -3,20 +3,22 @@ #include "assetslibrarywidget.h" -#include "asset.h" #include "assetslibraryiconprovider.h" #include "assetslibrarymodel.h" #include "assetslibraryview.h" -#include "designeractionmanager.h" -#include "import.h" -#include "modelnodeoperations.h" -#include "nodemetainfo.h" -#include "qmldesignerconstants.h" -#include "qmldesignerplugin.h" -#include "theme.h" -#include <utils3d.h> +#include <asset.h> +#include <designeractionmanager.h> +#include <designerpaths.h> +#include <hdrimage.h> +#include <import.h> +#include <modelnodeoperations.h> +#include <nodemetainfo.h> +#include <qmldesignerconstants.h> +#include <qmldesignerplugin.h> #include <studioquickwidget.h> +#include <theme.h> +#include <utils3d.h> #include <coreplugin/fileutils.h> #include <coreplugin/icore.h> @@ -287,14 +289,16 @@ void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList // Remove usages of deleted effects from the current document m_assetsView->executeInTransaction(__FUNCTION__, [&]() { QList<ModelNode> allNodes = m_assetsView->allModelNodes(); - const QString typeTemplate = "Effects.%1.%1"; - const QString importUrlTemplate = "Effects.%1"; + const QString typeTemplate = "%1.%2.%2"; + const QString importUrlTemplate = "%1.%2"; const Imports imports = m_assetsView->model()->imports(); Imports removedImports; + const QString typePrefix = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().composedEffectsTypePrefix(); for (const QString &effectName : effectNames) { if (effectName.isEmpty()) continue; - const TypeName type = typeTemplate.arg(effectName).toUtf8(); + const TypeName type = typeTemplate.arg(typePrefix, effectName).toUtf8(); for (ModelNode &node : allNodes) { if (node.metaInfo().typeName() == type) { clearStacks = true; @@ -302,7 +306,7 @@ void AssetsLibraryWidget::handleDeleteEffects([[maybe_unused]] const QStringList } } - const QString importPath = importUrlTemplate.arg(effectName); + const QString importPath = importUrlTemplate.arg(typePrefix, effectName); Import removedImport = Utils::findOrDefault(imports, [&importPath](const Import &import) { return import.url() == importPath; }); @@ -374,7 +378,7 @@ QList<QToolButton *> AssetsLibraryWidget::createToolBarWidgets() void AssetsLibraryWidget::handleSearchFilterChanged(const QString &filterText) { - if (filterText == m_filterText || (!m_assetsModel->haveFiles() + if (filterText == m_filterText || (!m_assetsModel->hasFiles() && filterText.contains(m_filterText, Qt::CaseInsensitive))) return; @@ -643,4 +647,15 @@ void AssetsLibraryWidget::addResources(const QStringList &files, bool showDialog } } +bool AssetsLibraryWidget::userBundleEnabled() const +{ + // TODO: this method is to be removed after user bundle implementation is complete + return Core::ICore::settings()->value("QML/Designer/UseExperimentalFeatures45", false).toBool(); +} + +void AssetsLibraryWidget::addAssetsToContentLibrary(const QStringList &assetPaths) +{ + m_assetsView->emitCustomNotification("add_assets_to_content_lib", {}, {assetPaths}); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h index ed987d14de..8b59ae0785 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -98,6 +98,8 @@ public: Q_INVOKABLE void showInGraphicalShell(const QString &path); Q_INVOKABLE QString showInGraphicalShellMsg() const; + Q_INVOKABLE bool userBundleEnabled() const; + Q_INVOKABLE void addAssetsToContentLibrary(const QStringList &assetPaths); signals: void itemActivated(const QString &itemName); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp index ddfb82746c..8b506affc4 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp @@ -60,28 +60,21 @@ inline static bool isValidColorName(const QString &colorName) /** * @brief getCustomUrl - * MimeType = <MainType/SubType> * Address = <Url|LocalFile> * * @param value The input value to be evaluated - * @param dataType if the value is a valid url or image, the data type + * @param dataType if the value is a valid url, the data type * will be stored to this parameter, otherwise, it will be Unknown - * @param urlResult if the value is a valid url or image, the address + * @param urlResult if the value is a valid url, the address * will be stored in this parameter, otherwise it will be empty. - * @param subType if the value is a valid image, the image subtype - * will be stored in this parameter, otherwise it will be empty. - * @return true if the result is either url or image + * @return true if the result is url */ static bool getCustomUrl(const QString &value, CollectionDetails::DataType &dataType, - QUrl *urlResult = nullptr, - QString *subType = nullptr) + QUrl *urlResult = nullptr) { static const QRegularExpression urlRegex{ - "^(?<MimeType>" - "(?<MainType>image)\\/" - "(?<SubType>apng|avif|gif|jpeg|png|(?:svg\\+xml)|webp|xyz)\\:)?" // end of MimeType - "(?<Address>" + "^(?<Address>" "(?<Url>https?:\\/\\/" "(?:www\\.|(?!www))[A-z0-9][A-z0-9-]+[A-z0-9]\\.[^\\s]{2,}|www\\.[A-z0-9][A-z0-9-]+" "[A-z0-9]\\.[^\\s]{2,}|https?:\\/\\/" @@ -92,29 +85,18 @@ static bool getCustomUrl(const QString &value, }; const QRegularExpressionMatch match = urlRegex.match(value.trimmed()); - if (match.hasMatch()) { - if (match.hasCaptured("Address")) { - if (match.hasCaptured("MimeType") && match.captured("MainType") == "image") - dataType = CollectionDetails::DataType::Image; - else - dataType = CollectionDetails::DataType::Url; + if (match.hasCaptured("Address")) { + dataType = CollectionDetails::DataType::Url; - if (urlResult) - urlResult->setUrl(match.captured("Address")); + if (urlResult) + urlResult->setUrl(match.captured("Address")); - if (subType) - *subType = match.captured("SubType"); - - return true; - } + return true; } if (urlResult) urlResult->clear(); - if (subType) - subType->clear(); - dataType = CollectionDetails::DataType::Unknown; return false; } @@ -248,14 +230,8 @@ static QVariant valueToVariant(const QJsonValue &value, CollectionDetails::DataT return variantValue.toBool(); case DataType::Color: return variantValue.value<QColor>(); - case DataType::Image: { - DataType type; - QUrl url; - if (getCustomUrl(variantValue.toString(), type, &url)) - return url; - return variantValue.toString(); - } case DataType::Url: + case DataType::Image: return variantValue.value<QUrl>(); default: return variantValue; @@ -285,12 +261,7 @@ static QJsonValue variantToJsonValue( return variant.toDouble(); case DataType::Integer: return variant.toInt(); - case DataType::Image: { - const QUrl url(variant.toUrl()); - if (url.isValid()) - return QString("image/xyz:%1").arg(url.toString()); - return {}; - } + case DataType::Image: case DataType::String: case DataType::Color: case DataType::Url: @@ -569,13 +540,6 @@ QVariant CollectionDetails::data(int row, int column) const const QJsonValue cellValue = d->dataRecords.at(row).at(column); - if (typeAt(column) == DataType::Image) { - const QUrl imageUrl = valueToVariant(cellValue, DataType::Image).toUrl(); - - if (imageUrl.isValid()) - return imageUrl; - } - return cellValue.toVariant(); } @@ -614,7 +578,10 @@ DataTypeWarning::Warning CollectionDetails::cellWarningCheck(int row, int column if (columnType == DataType::Unknown || isEmptyJsonValue(cellValue)) return DataTypeWarning::Warning::None; - if (columnType == DataType::Real && cellType == DataType::Integer) + if ((columnType == DataType::String || columnType == DataType::Real) && cellType == DataType::Integer) + return DataTypeWarning::Warning::None; + + if ((columnType == DataType::Url || columnType == DataType::Image) && cellType == DataType::String) return DataTypeWarning::Warning::None; if (columnType != cellType) diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp index b26b1a845e..d2917ec302 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp @@ -93,6 +93,7 @@ bool CollectionDetailsModel::setData(const QModelIndex &index, const QVariant &v if (prevWarning != m_currentCollection.cellWarningCheck(index.row(), index.column())) roles << DataTypeWarningRole; + setHasUnsavedChanges(true); emit dataChanged(index, index, roles); } @@ -128,11 +129,11 @@ bool CollectionDetailsModel::insertRows(int row, int count, [[maybe_unused]] con row = qBound(0, row, rowCount()); - beginResetModel(); + beginInsertRows({}, row, row + count - 1); m_currentCollection.insertEmptyRows(row, count); - endResetModel(); + endInsertRows(); + setHasUnsavedChanges(true); - selectRow(row); return true; } @@ -151,12 +152,6 @@ bool CollectionDetailsModel::removeColumns(int column, int count, const QModelIn if (!columnCount(parent)) removeRows(0, rowCount(parent), parent); - int nextColumn = column - 1; - if (nextColumn < 0 && columnCount(parent) > 0) - nextColumn = 0; - - selectColumn(nextColumn); - ensureSingleCell(); return columnsRemoved; } @@ -254,6 +249,7 @@ bool CollectionDetailsModel::addColumn(int column, const QString &name, const QS {}, CollectionDataTypeModel::dataTypeFromString(propertyType)); endInsertColumns(); + setHasUnsavedChanges(true); return m_currentCollection.containsPropertyName(name); } @@ -309,6 +305,7 @@ bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue {Qt::DisplayRole, Qt::EditRole, DataTypeRole, DataTypeWarningRole, ColumnDataTypeRole}); } + setHasUnsavedChanges(true); return changed; } @@ -441,6 +438,7 @@ bool CollectionDetailsModel::saveDataStoreCollections() if (reference != currentReference) closeCollectionIfSaved(reference); } + setHasUnsavedChanges(false); return true; } } @@ -618,4 +616,12 @@ QString CollectionDetailsModel::warningToString(DataTypeWarning::Warning warning return DataTypeWarning::getDataTypeWarningString(warning); } +void CollectionDetailsModel::setHasUnsavedChanges(bool val) +{ + if (m_hasUnsavedChanges == val) + return; + m_hasUnsavedChanges = val; + emit hasUnsavedChangesChanged(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h index 24a040cce6..8844ff4a3e 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h @@ -20,6 +20,7 @@ class CollectionDetailsModel : public QAbstractTableModel Q_PROPERTY(int selectedColumn READ selectedColumn WRITE selectColumn NOTIFY selectedColumnChanged) Q_PROPERTY(int selectedRow READ selectedRow WRITE selectRow NOTIFY selectedRowChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + Q_PROPERTY(bool hasUnsavedChanges MEMBER m_hasUnsavedChanges WRITE setHasUnsavedChanges NOTIFY hasUnsavedChangesChanged) public: enum DataRoles { SelectedRole = Qt::UserRole + 1, DataTypeRole, ColumnDataTypeRole, DataTypeWarningRole }; @@ -70,12 +71,14 @@ public: const CollectionDetails upToDateConstCollection(const CollectionReference &reference) const; bool collectionHasColumn(const CollectionReference &reference, const QString &columnName) const; QString getFirstColumnName(const CollectionReference &reference) const; + void setHasUnsavedChanges(bool val); signals: void collectionNameChanged(const QString &collectionName); void selectedColumnChanged(int); void selectedRowChanged(int); void isEmptyChanged(bool); + void hasUnsavedChangesChanged(); void warning(const QString &title, const QString &body); private slots: @@ -93,6 +96,7 @@ private: QHash<CollectionReference, CollectionDetails> m_openedCollections; CollectionDetails m_currentCollection; bool m_isEmpty = true; + bool m_hasUnsavedChanges = false; int m_selectedColumn = -1; int m_selectedRow = -1; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp index f56bb36e88..2cc6ac05a6 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp @@ -62,6 +62,12 @@ bool CollectionDetailsSortFilterModel::selectColumn(int column) return m_source->selectColumn(mapToSource(index(0, column)).column()); } +void CollectionDetailsSortFilterModel::deselectAll() +{ + QTC_ASSERT(m_source, return); + m_source->deselectAll(); +} + CollectionDetailsSortFilterModel::~CollectionDetailsSortFilterModel() = default; bool CollectionDetailsSortFilterModel::filterAcceptsRow(int sourceRow, diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h index 93305f3ca2..10f6e09b05 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h @@ -31,6 +31,7 @@ public: Q_INVOKABLE bool selectRow(int row); Q_INVOKABLE bool selectColumn(int column); + Q_INVOKABLE void deselectAll(); signals: void selectedColumnChanged(int); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp index 4725987f12..29b833cc2c 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp @@ -3,6 +3,7 @@ #include "collectioneditorutils.h" +#include "collectiondatatypemodel.h" #include "model.h" #include "nodemetainfo.h" #include "propertymetainfo.h" @@ -95,13 +96,17 @@ Utils::FilePath dataStoreDir() if (!currentProject) return {}; - return currentProject->projectDirectory().pathAppended("/imports/" - + currentProject->displayName()); + FilePath oldImportDirectory = currentProject->projectDirectory().pathAppended( + "imports/" + currentProject->displayName()); + if (oldImportDirectory.exists()) + return oldImportDirectory; + + return currentProject->projectDirectory().pathAppended(currentProject->displayName()); } inline Utils::FilePath collectionPath(const QString &filePath) { - return dataStoreDir().pathAppended("/" + filePath); + return dataStoreDir().pathAppended(filePath); } inline Utils::FilePath qmlDirFilePath() @@ -288,7 +293,7 @@ QJsonObject defaultCollection() QJsonArray columns; QJsonObject defaultColumn; defaultColumn.insert("name", "Column 1"); - defaultColumn.insert("type", "string"); + defaultColumn.insert("type", CollectionDataTypeModel::dataTypeToString(DataType::String)); columns.append(defaultColumn); QJsonArray collectionData; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp index f6ec821fde..0c9a2eed94 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp @@ -35,6 +35,12 @@ bool isStudioCollectionModel(const QmlDesigner::ModelNode &node) return node.metaInfo().isQtQuickStudioUtilsJsonListModel(); } +inline bool isProjectImport(const QmlDesigner::Import &import) +{ + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::startupProject(); + return currentProject && import.toString() == currentProject->displayName(); +} + inline void setVariantPropertyValue(const QmlDesigner::ModelNode &node, const QmlDesigner::PropertyName &propertyName, const QVariant &value) @@ -60,14 +66,10 @@ CollectionView::CollectionView(ExternalDependenciesInterface &externalDependenci , m_dataStore(std::make_unique<DataStoreModelNode>()) { - connect(ProjectExplorer::ProjectManager::instance(), - &ProjectExplorer::ProjectManager::startupProjectChanged, this, [this] { - resetDataStoreNode(); - if (m_widget.get()) - m_widget->collectionDetailsModel()->removeAllCollections(); - }); } +CollectionView::~CollectionView() = default; + bool CollectionView::hasWidget() const { return true; @@ -75,11 +77,16 @@ bool CollectionView::hasWidget() const QmlDesigner::WidgetInfo CollectionView::widgetInfo() { - if (m_widget.isNull()) { - m_widget = new CollectionWidget(this); + if (!m_widget) { + m_widget = Utils::makeUniqueObjectPtr<CollectionWidget>(this); m_widget->setMinimumSize(m_widget->minimumSizeHint()); + connect(ProjectExplorer::ProjectManager::instance(), + &ProjectExplorer::ProjectManager::startupProjectChanged, m_widget.get(), [&] { + resetDataStoreNode(); + m_widget->collectionDetailsModel()->removeAllCollections(); + }); - auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data()); + auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.get()); Core::ICore::addContextObject(collectionEditorContext); CollectionListModel *listModel = m_widget->listModel().data(); @@ -97,7 +104,7 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() connect(listModel, &CollectionListModel::modelReset, this, [this] { CollectionListModel *listModel = m_widget->listModel().data(); - if (listModel->sourceNode() == m_dataStore->modelNode()) + if (listModel->sourceNode() == dataStoreNode()) m_dataStore->setCollectionNames(listModel->collections()); }); @@ -128,7 +135,7 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() }); } - return createWidgetInfo(m_widget.data(), + return createWidgetInfo(m_widget.get(), "CollectionEditor", WidgetInfo::LeftPane, 0, @@ -139,23 +146,22 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo() void CollectionView::modelAttached(Model *model) { AbstractView::modelAttached(model); + m_widget->setProjectImportExists(Utils::anyOf(model->imports(), isProjectImport)); resetDataStoreNode(); } void CollectionView::modelAboutToBeDetached([[maybe_unused]] Model *model) { - m_libraryInfoIsUpdated = false; - m_reloadCounter = 0; - m_rewriterAmended = false; - m_dataStoreTypeFound = false; - disconnect(m_documentUpdateConnection); - QTC_ASSERT(m_delayedTasks.isEmpty(), m_delayedTasks.clear()); - m_widget->listModel()->setDataStoreNode(); + unloadDataStore(); + m_widget->setProjectImportExists(false); } void CollectionView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList, [[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList) { + if (!m_widget) + return; + QList<ModelNode> selectedCollectionNodes = Utils::filtered(selectedNodeList, &isStudioCollectionModel); @@ -170,10 +176,17 @@ void CollectionView::selectedNodesChanged(const QList<ModelNode> &selectedNodeLi } m_widget->setTargetNodeSelected(singleSelectedHasModelProperty); +} - // More than one model is selected. So ignore them - if (selectedCollectionNodes.size() > 1) - return; +void CollectionView::importsChanged(const Imports &addedImports, const Imports &removedImports) +{ + if (Utils::anyOf(addedImports, isProjectImport)) { + m_widget->setProjectImportExists(true); + resetDataStoreNode(); + } else if (Utils::anyOf(removedImports, isProjectImport)) { + m_widget->setProjectImportExists(false); + unloadDataStore(); + } } void CollectionView::customNotification(const AbstractView *, @@ -181,6 +194,9 @@ void CollectionView::customNotification(const AbstractView *, const QList<ModelNode> &nodeList, const QList<QVariant> &data) { + if (!m_widget) + return; + if (identifier == QLatin1String("item_library_created_by_drop") && !nodeList.isEmpty()) onItemLibraryNodeCreated(nodeList.first()); else if (identifier == QLatin1String("open_collection_by_id") && !data.isEmpty()) @@ -219,8 +235,27 @@ void CollectionView::addResource(const QUrl &url, const QString &name) }); } +void CollectionView::addProjectImport() +{ + if (!m_widget) + return; + + ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::startupProject(); + if (!currentProject) + return; + + executeInTransaction(__FUNCTION__, [&] { + Import import = Import::createLibraryImport(currentProject->displayName()); + if (!model()->hasImport(import, true, true)) + model()->changeImports({import}, {}); + }); +} + void CollectionView::assignCollectionToNode(const QString &collectionName, const ModelNode &node) { + if (!m_widget) + return; + using DataType = CollectionDetails::DataType; executeInTransaction("CollectionView::assignCollectionToNode", [&]() { m_dataStore->assignCollectionToNode( @@ -279,12 +314,18 @@ void CollectionView::assignCollectionToSelectedNode(const QString &collectionNam void CollectionView::addNewCollection(const QString &collectionName, const QJsonObject &localCollection) { + if (!m_widget) + return; + addTask(QSharedPointer<CollectionTask>( new AddCollectionTask(this, m_widget->listModel(), localCollection, collectionName))); } void CollectionView::openCollection(const QString &collectionName) { + if (!m_widget) + return; + m_widget->openCollection(collectionName); } @@ -296,9 +337,13 @@ void CollectionView::registerDeclarativeType() void CollectionView::resetDataStoreNode() { + if (!m_widget) + return; + m_dataStore->reloadModel(); - ModelNode dataStore = m_dataStore->modelNode(); + ModelNode dataStore = dataStoreNode(); + m_widget->setDataStoreExists(dataStore.isValid()); if (!dataStore || m_widget->listModel()->sourceNode() == dataStore) return; @@ -339,28 +384,11 @@ void CollectionView::ensureDataStoreExists() { bool filesJustCreated = false; bool filesExist = CollectionEditorUtils::ensureDataStoreExists(filesJustCreated); - if (filesExist) { - if (filesJustCreated) { - // Force code model reset to notice changes to existing module - auto modelManager = QmlJS::ModelManagerInterface::instance(); - if (modelManager) { - m_libraryInfoIsUpdated = false; - - m_expectedDocumentUpdates.clear(); - m_expectedDocumentUpdates << CollectionEditorUtils::dataStoreQmlFilePath() - << CollectionEditorUtils::dataStoreJsonFilePath(); - - m_documentUpdateConnection = connect(modelManager, - &QmlJS::ModelManagerInterface::documentUpdated, - this, - &CollectionView::onDocumentUpdated); - - modelManager->resetCodeModel(); - } - resetDataStoreNode(); - } else { - m_libraryInfoIsUpdated = true; - } + if (filesExist && filesJustCreated) { + // Force code model reset to notice changes to existing module + if (auto modelManager = QmlJS::ModelManagerInterface::instance()) + modelManager->resetCodeModel(); + resetDataStoreNode(); } } @@ -380,6 +408,18 @@ NodeMetaInfo CollectionView::jsonCollectionMetaInfo() const return model()->metaInfo(CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME); } +void CollectionView::unloadDataStore() +{ + m_reloadCounter = 0; + m_rewriterAmended = false; + m_dataStoreTypeFound = false; + QTC_ASSERT(m_delayedTasks.isEmpty(), m_delayedTasks.clear()); + if (m_widget) { + m_widget->setDataStoreExists(dataStoreNode().isValid()); + m_widget->listModel()->setDataStoreNode(); + } +} + void CollectionView::ensureStudioModelImport() { executeInTransaction(__FUNCTION__, [&] { @@ -395,29 +435,21 @@ void CollectionView::ensureStudioModelImport() void CollectionView::onItemLibraryNodeCreated(const ModelNode &node) { + if (!m_widget) + return; + if (node.metaInfo().isQtQuickListView()) { addTask(QSharedPointer<CollectionTask>( new DropListViewTask(this, m_widget->listModel(), node))); } } -void CollectionView::onDocumentUpdated(const QSharedPointer<const QmlJS::Document> &doc) -{ - if (m_expectedDocumentUpdates.contains(doc->fileName())) - m_expectedDocumentUpdates.remove(doc->fileName()); - - if (m_expectedDocumentUpdates.isEmpty()) { - disconnect(m_documentUpdateConnection); - m_libraryInfoIsUpdated = true; - } -} - void CollectionView::addTask(QSharedPointer<CollectionTask> task) { ensureDataStoreExists(); if (m_dataStoreTypeFound) task->process(); - else if (m_dataStore->modelNode()) + else if (dataStoreNode()) m_delayedTasks << task; } diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h index a4b16c4c27..3de3bd7ae6 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h @@ -3,9 +3,12 @@ #pragma once -#include "abstractview.h" #include "datastoremodelnode.h" -#include "modelnode.h" + +#include <abstractview.h> +#include <modelnode.h> + +#include <utils/uniqueobjectptr.h> #include <QJsonObject> @@ -27,6 +30,7 @@ class CollectionView : public AbstractView public: explicit CollectionView(ExternalDependenciesInterface &externalDependencies); + ~CollectionView(); bool hasWidget() const override; WidgetInfo widgetInfo() override; @@ -37,6 +41,8 @@ public: void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &lastSelectedNodeList) override; + void importsChanged(const Imports &addedImports, const Imports &removedImports) override; + void customNotification(const AbstractView *view, const QString &identifier, const QList<ModelNode> &nodeList, @@ -44,6 +50,7 @@ public: void addResource(const QUrl &url, const QString &name); + void addProjectImport(); void assignCollectionToNode(const QString &collectionName, const ModelNode &node); void assignCollectionToSelectedNode(const QString &collectionName); void addNewCollection(const QString &collectionName, const QJsonObject &localCollection); @@ -61,17 +68,14 @@ private: friend class CollectionTask; NodeMetaInfo jsonCollectionMetaInfo() const; + void unloadDataStore(); void ensureStudioModelImport(); void onItemLibraryNodeCreated(const ModelNode &node); - void onDocumentUpdated(const QSharedPointer<const QmlJS::Document> &doc); void addTask(QSharedPointer<CollectionTask> task); - QPointer<CollectionWidget> m_widget; std::unique_ptr<DataStoreModelNode> m_dataStore; - QSet<Utils::FilePath> m_expectedDocumentUpdates; + Utils::UniqueObjectPtr<CollectionWidget> m_widget; QList<QSharedPointer<CollectionTask>> m_delayedTasks; - QMetaObject::Connection m_documentUpdateConnection; - bool m_libraryInfoIsUpdated = false; bool m_dataStoreTypeFound = false; bool m_rewriterAmended = false; int m_reloadCounter = 0; diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp index 093729dc67..dd706145cf 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp @@ -54,8 +54,7 @@ QString getPreferredCollectionName(const QUrl &url, const QString &collectionNam namespace QmlDesigner { CollectionWidget::CollectionWidget(CollectionView *view) - : QFrame() - , m_view(view) + : m_view(view) , m_listModel(new CollectionListModel) , m_collectionDetailsModel(new CollectionDetailsModel) , m_collectionDetailsSortFilterModel(std::make_unique<CollectionDetailsSortFilterModel>()) @@ -104,6 +103,8 @@ CollectionWidget::CollectionWidget(CollectionView *view) QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_MODELEDITOR_TIME); } +CollectionWidget::~CollectionWidget() = default; + void CollectionWidget::contextHelp(const Core::IContext::HelpCallback &callback) const { if (m_view) @@ -250,6 +251,11 @@ bool CollectionWidget::importFile(const QString &collectionName, return false; } +void CollectionWidget::addProjectImport() +{ + m_view->addProjectImport(); +} + void CollectionWidget::addCollectionToDataStore(const QString &collectionName) { m_view->addNewCollection(collectionName, CollectionEditorUtils::defaultCollection()); @@ -288,6 +294,24 @@ void CollectionWidget::setTargetNodeSelected(bool selected) emit targetNodeSelectedChanged(m_targetNodeSelected); } +void CollectionWidget::setProjectImportExists(bool exists) +{ + if (m_projectImportExists == exists) + return; + + m_projectImportExists = exists; + emit projectImportExistsChanged(m_projectImportExists); +} + +void CollectionWidget::setDataStoreExists(bool exists) +{ + if (m_dataStoreExists == exists) + return; + + m_dataStoreExists = exists; + emit dataStoreExistsChanged(m_dataStoreExists); +} + void CollectionWidget::deleteSelectedCollection() { QMetaObject::invokeMethod(m_quickWidget->quickWidget()->rootObject(), "deleteSelectedCollection"); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h index 0957bd81e0..13c3566c78 100644 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h @@ -22,9 +22,12 @@ class CollectionWidget : public QFrame Q_OBJECT Q_PROPERTY(bool targetNodeSelected MEMBER m_targetNodeSelected NOTIFY targetNodeSelectedChanged) + Q_PROPERTY(bool projectImportExists MEMBER m_projectImportExists NOTIFY projectImportExistsChanged) + Q_PROPERTY(bool dataStoreExists MEMBER m_dataStoreExists NOTIFY dataStoreExistsChanged) public: CollectionWidget(CollectionView *view); + ~CollectionWidget(); void contextHelp(const Core::IContext::HelpCallback &callback) const; QPointer<CollectionListModel> listModel() const; @@ -32,7 +35,7 @@ public: void reloadQmlSource(); - virtual QSize minimumSizeHint() const; + QSize minimumSizeHint() const override; Q_INVOKABLE bool loadJsonFile(const QUrl &url, const QString &collectionName = {}); Q_INVOKABLE bool loadCsvFile(const QUrl &url, const QString &collectionName = {}); @@ -44,6 +47,7 @@ public: const QUrl &url, const bool &firstRowIsHeader = true); + Q_INVOKABLE void addProjectImport(); Q_INVOKABLE void addCollectionToDataStore(const QString &collectionName); Q_INVOKABLE void assignCollectionToSelectedNode(const QString collectionName); Q_INVOKABLE void openCollection(const QString &collectionName); @@ -51,11 +55,15 @@ public: void warn(const QString &title, const QString &body); void setTargetNodeSelected(bool selected); + void setProjectImportExists(bool exists); + void setDataStoreExists(bool exists); void deleteSelectedCollection(); signals: void targetNodeSelectedChanged(bool); + void projectImportExistsChanged(bool); + void dataStoreExistsChanged(bool); private: QString generateUniqueCollectionName(const ModelNode &node, const QString &name); @@ -66,6 +74,8 @@ private: std::unique_ptr<CollectionDetailsSortFilterModel> m_collectionDetailsSortFilterModel; QScopedPointer<StudioQuickWidget> m_quickWidget; bool m_targetNodeSelected = false; + bool m_projectImportExists = false; + bool m_dataStoreExists = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/dialogutils.cpp b/src/plugins/qmldesigner/components/componentcore/dialogutils.cpp new file mode 100644 index 0000000000..f882ae528d --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/dialogutils.cpp @@ -0,0 +1,32 @@ +// 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 <model.h> + +#include <coreplugin/messagebox.h> + +namespace QmlDesigner { + +namespace DialogUtils { + +void showWarningForInvalidId(const QString &id) +{ + constexpr char text[] = R"( +The ID <b>'%1'</b> is invalid. + +Make sure the ID is: +<ul> +<li>Unique within the QML file.</li> +<li>Beginning with a lowercase letter.</li> +<li>Without any blank space or symbol.</li> +<li>Not a reserved QML keyword. </li> +</ul> +)"; + + Core::AsynchronousMessageBox::warning(Model::tr("Invalid Id"), + Model::tr(text).arg(id)); +} + +} // namespace DialogUtils + +} //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/dialogutils.h b/src/plugins/qmldesigner/components/componentcore/dialogutils.h new file mode 100644 index 0000000000..3ca98016dd --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/dialogutils.h @@ -0,0 +1,17 @@ +// 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 <qmldesignercomponents_global.h> + +#include <QString> + +namespace QmlDesigner { + +namespace DialogUtils { + +QMLDESIGNERCOMPONENTS_EXPORT void showWarningForInvalidId(const QString &id); + +} // namespace DialogUtils +} //QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp index 8d3412e0e8..89b50c4d1a 100644 --- a/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp +++ b/src/plugins/qmldesigner/components/componentcore/layoutingridlayout.cpp @@ -452,7 +452,9 @@ void LayoutInGridLayout::removeSpacersBySpanning(QList<ModelNode> &nodes) { for (const ModelNode &node : std::as_const(m_spacerNodes)) { if (int index = nodes.indexOf(node)) { - ModelNode before = nodes.at(index -1); + ModelNode before; + if (index > 0) + before = nodes.at(index - 1); if (m_spacerNodes.contains(before)) { m_spacerNodes.removeAll(node); m_layoutedNodes.removeAll(node); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp index f6e18458b2..4cbebd738d 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.cpp @@ -105,8 +105,10 @@ bool selectionIsImported3DAsset(const SelectionContext &selectionState) // Node is not a file component, so we have to check if the current doc itself is fileName = node.model()->fileUrl().toLocalFile(); } - if (fileName.contains(Constants::QUICK_3D_ASSETS_FOLDER)) + if (QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().isImport3dPath(fileName)) { return true; + } } return false; } diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index cebe7d7c53..a5274c70e2 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -1138,18 +1138,12 @@ static QString getAssetDefaultDirectory(const QString &assetDir, const QString & { QString adjustedDefaultDirectory = defaultDirectory; - Utils::FilePath contentPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); - - if (contentPath.pathAppended("content").exists()) - contentPath = contentPath.pathAppended("content"); + Utils::FilePath contentPath = QmlDesignerPlugin::instance()->documentManager().currentResourcePath(); Utils::FilePath assetPath = contentPath.pathAppended(assetDir); - if (!assetPath.exists()) { - // Create the default asset type directory if it doesn't exist - QDir dir(contentPath.toString()); - dir.mkpath(assetDir); - } + if (!assetPath.exists()) + assetPath.createDir(); if (assetPath.exists() && assetPath.isDir()) adjustedDefaultDirectory = assetPath.toString(); @@ -1694,7 +1688,14 @@ void editIn3dView(const SelectionContext &selectionContext) if (selectionContext.view() && selectionContext.hasSingleSelectedModelNode() && selectionContext.currentSingleSelectedNode().metaInfo().isQtQuick3DView3D()) { QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("Editor3D", true); - selectionContext.view()->emitView3DAction(View3DActionType::AlignViewToCamera, true); + const QPointF scenePos = selectionContext.scenePosition(); + if (scenePos.isNull()) { + selectionContext.view()->emitView3DAction(View3DActionType::AlignViewToCamera, true); + } else { + selectionContext.view()->emitCustomNotification("pick_3d_node_from_2d_scene", + {selectionContext.currentSingleSelectedNode()}, + {scenePos}); + } } } @@ -1727,13 +1728,12 @@ void openOldEffectMaker(const QString &filePath) return; } - Utils::FilePath projectPath = target->project()->projectDirectory(); - QString effectName = QFileInfo(filePath).baseName(); - QString effectResDir = QLatin1String(Constants::DEFAULT_EFFECTS_IMPORT_FOLDER) - + "/" + effectName; - Utils::FilePath effectResPath = projectPath.pathAppended(effectResDir); + Utils::FilePath effectResPath = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().composedEffectsBasePath() + .pathAppended(QFileInfo(filePath).baseName()); + if (!effectResPath.exists()) - QDir().mkpath(effectResPath.toString()); + effectResPath.createDir(); const QtSupport::QtVersion *baseQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit()); if (baseQtVersion) { @@ -1769,14 +1769,11 @@ void openOldEffectMaker(const QString &filePath) Utils::FilePath getEffectsImportDirectory() { - QString defaultDir = QLatin1String(Constants::DEFAULT_EFFECTS_IMPORT_FOLDER); - Utils::FilePath projectPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); - Utils::FilePath effectsPath = projectPath.pathAppended(defaultDir); + Utils::FilePath effectsPath = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().composedEffectsBasePath(); - if (!effectsPath.exists()) { - QDir dir(projectPath.toString()); - dir.mkpath(effectsPath.toString()); - } + if (!effectsPath.exists()) + effectsPath.createDir(); return effectsPath; } @@ -1794,12 +1791,9 @@ QString getEffectsDefaultDirectory(const QString &defaultDir) QString getEffectIcon(const QString &effectPath) { - Utils::FilePath projectPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); - QString effectName = QFileInfo(effectPath).baseName(); - QString effectResDir = "asset_imports/Effects/" + effectName; - Utils::FilePath effectResPath = projectPath.resolvePath(effectResDir + "/" + effectName + ".qml"); - - return effectResPath.exists() ? QString("effectExported") : QString("effectClass"); + Utils::FilePath effectFile = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().composedEffectPath(effectPath); + return effectFile.exists() ? QString("effectExported") : QString("effectClass"); } bool useLayerEffect() diff --git a/src/plugins/qmldesigner/components/componentcore/resourcegenerator.cpp b/src/plugins/qmldesigner/components/componentcore/resourcegenerator.cpp index 4a229564c6..24047f650f 100644 --- a/src/plugins/qmldesigner/components/componentcore/resourcegenerator.cpp +++ b/src/plugins/qmldesigner/components/componentcore/resourcegenerator.cpp @@ -222,6 +222,10 @@ bool createQmlrcFile(const FilePath &qmlrcFilePath) rccProcess.setWorkingDirectory(project->projectDirectory()); const QStringList arguments = {"--binary", + "--compress", + "9", + "--threshold", + "30", "--output", qmlrcFilePath.toString(), tempQrcFile.toString()}; diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index 73184d391c..392f6c94f6 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -83,6 +83,7 @@ public: binding_medium, bounds_small, branch_medium, + cameraSpeed_medium, camera_medium, camera_small, centerHorizontal, diff --git a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp index 05d6f5fdf0..b011d9fbbf 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -67,16 +67,20 @@ public: , collectionView{externalDependencies} , contentLibraryView{externalDependencies} , componentView{externalDependencies} +#ifndef QTC_USE_QML_DESIGNER_LITE , edit3DView{externalDependencies} +#endif , formEditorView{externalDependencies} , textEditorView{externalDependencies} , assetsLibraryView{externalDependencies} , itemLibraryView(imageCache, externalDependencies) , navigatorView{externalDependencies} , propertyEditorView(imageCache, externalDependencies) +#ifndef QTC_USE_QML_DESIGNER_LITE , materialEditorView{externalDependencies} , materialBrowserView{imageCache, externalDependencies} , textureEditorView{imageCache, externalDependencies} +#endif , statesEditorView{externalDependencies} {} @@ -89,16 +93,20 @@ public: CollectionView collectionView; ContentLibraryView contentLibraryView; ComponentView componentView; +#ifndef QTC_USE_QML_DESIGNER_LITE Edit3DView edit3DView; +#endif FormEditorView formEditorView; TextEditorView textEditorView; AssetsLibraryView assetsLibraryView; ItemLibraryView itemLibraryView; NavigatorView navigatorView; PropertyEditorView propertyEditorView; +#ifndef QTC_USE_QML_DESIGNER_LITE MaterialEditorView materialEditorView; MaterialBrowserView materialBrowserView; TextureEditorView textureEditorView; +#endif StatesEditorView statesEditorView; std::vector<std::unique_ptr<AbstractView>> additionalViews; @@ -203,6 +211,7 @@ QList<AbstractView *> ViewManager::views() const QList<AbstractView *> ViewManager::standardViews() const { +#ifndef QTC_USE_QML_DESIGNER_LITE QList<AbstractView *> list = {&d->edit3DView, &d->formEditorView, &d->textEditorView, @@ -215,6 +224,16 @@ QList<AbstractView *> ViewManager::standardViews() const &d->textureEditorView, &d->statesEditorView, &d->designerActionManagerView}; +#else + QList<AbstractView *> list = {&d->formEditorView, + &d->textEditorView, + &d->assetsLibraryView, + &d->itemLibraryView, + &d->navigatorView, + &d->propertyEditorView, + &d->statesEditorView, + &d->designerActionManagerView}; +#endif if (enableModelEditor()) list.append(&d->collectionView); @@ -384,16 +403,20 @@ QList<WidgetInfo> ViewManager::widgetInfos() const { QList<WidgetInfo> widgetInfoList; +#ifndef QTC_USE_QML_DESIGNER_LITE widgetInfoList.append(d->edit3DView.widgetInfo()); +#endif widgetInfoList.append(d->formEditorView.widgetInfo()); widgetInfoList.append(d->textEditorView.widgetInfo()); widgetInfoList.append(d->assetsLibraryView.widgetInfo()); widgetInfoList.append(d->itemLibraryView.widgetInfo()); widgetInfoList.append(d->navigatorView.widgetInfo()); widgetInfoList.append(d->propertyEditorView.widgetInfo()); +#ifndef QTC_USE_QML_DESIGNER_LITE widgetInfoList.append(d->materialEditorView.widgetInfo()); widgetInfoList.append(d->materialBrowserView.widgetInfo()); widgetInfoList.append(d->textureEditorView.widgetInfo()); +#endif widgetInfoList.append(d->statesEditorView.widgetInfo()); if (enableModelEditor()) widgetInfoList.append(d->collectionView.widgetInfo()); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp index 9e6bdd03b9..5c8d42a306 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp @@ -32,9 +32,6 @@ ContentLibraryBundleImporter::ContentLibraryBundleImporter(const QString &bundle { m_importTimer.setInterval(200); connect(&m_importTimer, &QTimer::timeout, this, &ContentLibraryBundleImporter::handleImportTimer); - m_moduleName = QStringLiteral("%1.%2").arg( - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER), - m_bundleId).mid(1); // Chop leading slash } // Returns empty string on success or an error message on failure. @@ -69,7 +66,7 @@ QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile, QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents().value_or(QByteArray())); if (qmldirContent.isEmpty()) { qmldirContent.append("module "); - qmldirContent.append(m_moduleName); + qmldirContent.append(moduleName()); qmldirContent.append('\n'); } @@ -77,7 +74,9 @@ QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile, const bool qmlFileExists = qmlSourceFile.exists(); const QString qmlType = qmlSourceFile.baseName(); const QString fullTypeName = QStringLiteral("%1.%2.%3") - .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), m_bundleId, qmlType); + .arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().componentBundlesTypePrefix(), + m_bundleId, qmlType); if (m_pendingTypes.contains(fullTypeName) && !m_pendingTypes[fullTypeName]) return QStringLiteral("Unable to import while unimporting the same type: '%1'").arg(fullTypeName); if (!qmldirContent.contains(qmlFile)) { @@ -126,7 +125,7 @@ QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile, if (!model) return "Model not available, cannot add import statement or update code model"; - Import import = Import::createLibraryImport(m_moduleName, "1.0"); + Import import = Import::createLibraryImport(moduleName(), "1.0"); if (!model->hasImport(import)) { if (model->possibleImports().contains(import)) { m_importAddPending = false; @@ -134,7 +133,7 @@ QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile, model->changeImports({import}, {}); } catch (const RewritingException &) { // No point in trying to add import asynchronously either, so just fail out - return QStringLiteral("Failed to add import statement for: '%1'").arg(m_moduleName); + return QStringLiteral("Failed to add import statement for: '%1'").arg(moduleName()); } } else { // If import is not yet possible, import statement needs to be added asynchronously to @@ -188,7 +187,7 @@ void ContentLibraryBundleImporter::handleImportTimer() if (m_importAddPending) { try { - Import import = Import::createLibraryImport(m_moduleName, "1.0"); + Import import = Import::createLibraryImport(moduleName(), "1.0"); if (model->possibleImports().contains(import)) { model->changeImports({import}, {}); m_importAddPending = false; @@ -253,6 +252,13 @@ void ContentLibraryBundleImporter::writeAssetRefMap(const Utils::FilePath &bundl } } +QString ContentLibraryBundleImporter::moduleName() +{ + return QStringLiteral("%1.%2").arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().componentBundlesTypePrefix(), + m_bundleId); +} + QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) { FilePath bundleImportPath = resolveBundleImportPath(); @@ -275,7 +281,9 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) QString qmlType = qmlFilePath.baseName(); const QString fullTypeName = QStringLiteral("%1.%2.%3") - .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), m_bundleId, qmlType); + .arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().componentBundlesTypePrefix(), + m_bundleId, qmlType); if (m_pendingTypes.contains(fullTypeName) && m_pendingTypes[fullTypeName]) return QStringLiteral("Unable to unimport while importing the same type: '%1'").arg(fullTypeName); @@ -327,7 +335,7 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); Model *model = doc ? doc->currentModel() : nullptr; if (model) { - Import import = Import::createLibraryImport(m_moduleName, "1.0"); + Import import = Import::createLibraryImport(moduleName(), "1.0"); if (model->imports().contains(import)) model->changeImports({}, {import}); } @@ -342,16 +350,12 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) FilePath ContentLibraryBundleImporter::resolveBundleImportPath() { - FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); + FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().componentBundlesBasePath(); if (bundleImportPath.isEmpty()) return bundleImportPath; - const QString projectBundlePath = QStringLiteral("%1%2/%3").arg( - QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER), - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER), - m_bundleId).mid(1); // Chop leading slash - - return bundleImportPath.resolvePath(projectBundlePath); + return bundleImportPath.resolvePath(m_bundleId); } } // namespace QmlDesigner::Internal diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h index 3aff09fe34..7fb2a48886 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h @@ -46,10 +46,10 @@ private: void handleImportTimer(); QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath); void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap); + QString moduleName(); Utils::FilePath m_bundleDir; QString m_bundleId; - QString m_moduleName; QStringList m_sharedFiles; QTimer m_importTimer; int m_importTimerCount = 0; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp index 6b1de2d2a7..334c017116 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp @@ -7,8 +7,8 @@ #include "contentlibraryeffect.h" #include "contentlibraryeffectscategory.h" #include "contentlibrarywidget.h" -#include "qmldesignerconstants.h" +#include <qmldesignerplugin.h> #include <utils/algorithm.h> #include <utils/qtcassert.h> #include <utils/hostosinfo.h> @@ -187,10 +187,11 @@ void ContentLibraryEffectsModel::loadBundle() QUrl icon = QUrl::fromLocalFile(bundleDir.filePath(itemObj.value("icon").toString())); QString qml = itemObj.value("qml").toString(); - TypeName type = QLatin1String("%1.%2.%3").arg( - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), - bundleId, - qml.chopped(4)).toLatin1(); // chopped(4): remove .qml + TypeName type = QLatin1String("%1.%2.%3") + .arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().componentBundlesTypePrefix(), + bundleId, + qml.chopped(4)).toLatin1(); // chopped(4): remove .qml auto bundleItem = new ContentLibraryEffect(category, item, qml, type, icon, files); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h index f546ea98cd..55af2accbd 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h @@ -3,9 +3,8 @@ #pragma once -#include "qmldesignercorelib_global.h" +#include "nodeinstanceglobal.h" -#include <QDataStream> #include <QObject> #include <QUrl> @@ -22,6 +21,7 @@ class ContentLibraryMaterial : public QObject Q_PROPERTY(QString bundleMaterialBaseWebUrl MEMBER m_baseWebUrl CONSTANT) Q_PROPERTY(QString bundleMaterialParentPath READ parentDirPath CONSTANT) Q_PROPERTY(QStringList bundleMaterialFiles READ allFiles CONSTANT) + Q_PROPERTY(QString itemType MEMBER m_itemType CONSTANT) public: ContentLibraryMaterial(QObject *parent, @@ -31,7 +31,7 @@ public: const QUrl &icon, const QStringList &files, const QString &downloadPath, - const QString &baseWebUrl); + const QString &baseWebUrl = {}); bool filter(const QString &searchText); @@ -66,6 +66,7 @@ private: QString m_downloadPath; QString m_baseWebUrl; QStringList m_allFiles; + const QString m_itemType = "material"; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp index 7594c691b5..26747d359c 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp @@ -8,12 +8,12 @@ #include "contentlibrarymaterialscategory.h" #include "contentlibrarywidget.h" -#include <designerpaths.h> +#include "designerpaths.h" #include "filedownloader.h" #include "fileextractor.h" #include "multifiledownloader.h" -#include "qmldesignerconstants.h" -#include "qmldesignerplugin.h" + +#include <qmldesignerplugin.h> #include <utils/algorithm.h> #include <utils/hostosinfo.h> @@ -275,9 +275,9 @@ void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &matBundleDir) auto category = new ContentLibraryMaterialsCategory(this, cat); const QJsonObject matsObj = catsObj.value(cat).toObject(); - const QStringList mats = matsObj.keys(); - for (const QString &mat : mats) { - const QJsonObject matObj = matsObj.value(mat).toObject(); + const QStringList matsNames = matsObj.keys(); + for (const QString &matName : matsNames) { + const QJsonObject matObj = matsObj.value(matName).toObject(); QStringList files; const QJsonArray assetsArr = matObj.value("files").toArray(); @@ -286,12 +286,13 @@ void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &matBundleDir) QUrl icon = QUrl::fromLocalFile(matBundleDir.filePath(matObj.value("icon").toString())); QString qml = matObj.value("qml").toString(); - TypeName type = QLatin1String("%1.%2.%3").arg( - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), - bundleId, - qml.chopped(4)).toLatin1(); // chopped(4): remove .qml + TypeName type = QLatin1String("%1.%2.%3") + .arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().componentBundlesTypePrefix(), + bundleId, + qml.chopped(4)).toLatin1(); // chopped(4): remove .qml - auto bundleMat = new ContentLibraryMaterial(category, mat, qml, type, icon, files, + auto bundleMat = new ContentLibraryMaterial(category, matName, qml, type, icon, files, m_downloadPath, m_baseUrl); category->addBundleMaterial(bundleMat); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp index 7ab239aab4..80dd7e816f 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp @@ -12,20 +12,19 @@ namespace QmlDesigner { ContentLibraryTexture::ContentLibraryTexture(QObject *parent, const QFileInfo &iconFileInfo, - const QString &downloadPath, const QUrl &icon, - const QString &key, const QString &webTextureUrl, - const QString &webIconUrl, const QString &fileExt, + const QString &dirPath, const QString &suffix, const QSize &dimensions, const qint64 sizeInBytes, - bool hasUpdate, bool isNew) + const QString &key, const QString &textureUrl, + const QString &iconUrl, bool hasUpdate, bool isNew) : QObject(parent) , m_iconPath(iconFileInfo.filePath()) - , m_downloadPath(downloadPath) - , m_webTextureUrl(webTextureUrl) - , m_webIconUrl(webIconUrl) + , m_dirPath(dirPath) + , m_textureUrl(textureUrl) + , m_iconUrl(iconUrl) , m_baseName{iconFileInfo.baseName()} - , m_fileExt(fileExt) + , m_suffix(suffix) , m_textureKey(key) - , m_icon(icon) + , m_icon(QUrl::fromLocalFile(iconFileInfo.absoluteFilePath())) , m_dimensions(dimensions) , m_sizeInBytes(sizeInBytes) , m_hasUpdate(hasUpdate) @@ -54,9 +53,9 @@ QString ContentLibraryTexture::iconPath() const return m_iconPath; } -QString ContentLibraryTexture::resolveFileExt() +QString ContentLibraryTexture::resolveSuffix() { - const QFileInfoList files = QDir(m_downloadPath).entryInfoList(QDir::Files); + const QFileInfoList files = QDir(m_dirPath).entryInfoList(QDir::Files); const QFileInfoList textureFiles = Utils::filtered(files, [this](const QFileInfo &fi) { return fi.baseName() == m_baseName; }); @@ -76,22 +75,20 @@ QString ContentLibraryTexture::resolveFileExt() QString ContentLibraryTexture::resolveToolTipText() { - if (m_fileExt.isEmpty()) { - // No supplied or resolved extension means we have just the icon and no other data - return m_baseName; - } + if (m_suffix.isEmpty()) + return m_baseName; // empty suffix means we have just the icon and no other data - QString fileName = m_baseName + m_fileExt; + QString fileName = m_baseName + m_suffix; QString imageInfo; if (!m_isDownloaded && m_sizeInBytes > 0 && !m_dimensions.isNull()) { - imageInfo = ImageUtils::imageInfo(m_dimensions, m_sizeInBytes); + imageInfo = ImageUtils::imageInfoString(m_dimensions, m_sizeInBytes); } else { - QString fullDownloadPath = m_downloadPath + '/' + fileName; - imageInfo = ImageUtils::imageInfo(fullDownloadPath); + QString fullDownloadPath = m_dirPath + '/' + fileName; + imageInfo = ImageUtils::imageInfoString(fullDownloadPath); } - return QStringLiteral("%1\n%2").arg(fileName, imageInfo); + return QString("%1\n%2").arg(fileName, imageInfo); } bool ContentLibraryTexture::isDownloaded() const @@ -99,9 +96,9 @@ bool ContentLibraryTexture::isDownloaded() const return m_isDownloaded; } -QString ContentLibraryTexture::downloadedTexturePath() const +QString ContentLibraryTexture::texturePath() const { - return m_downloadPath + '/' + m_baseName + m_fileExt; + return m_dirPath + '/' + m_baseName + m_suffix; } void ContentLibraryTexture::setDownloaded() @@ -116,16 +113,16 @@ void ContentLibraryTexture::setDownloaded() void ContentLibraryTexture::doSetDownloaded() { - if (m_fileExt.isEmpty()) - m_fileExt = resolveFileExt(); + if (m_suffix.isEmpty()) + m_suffix = resolveSuffix(); - m_isDownloaded = QFileInfo::exists(downloadedTexturePath()); + m_isDownloaded = QFileInfo::exists(texturePath()); m_toolTip = resolveToolTipText(); } QString ContentLibraryTexture::parentDirPath() const { - return m_downloadPath; + return m_dirPath; } QString ContentLibraryTexture::textureKey() const diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h index 9f5b46630f..8f7197bc72 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.h @@ -19,17 +19,18 @@ class ContentLibraryTexture : public QObject Q_PROPERTY(QString textureToolTip MEMBER m_toolTip NOTIFY textureToolTipChanged) Q_PROPERTY(QUrl textureIcon MEMBER m_icon CONSTANT) Q_PROPERTY(bool textureVisible MEMBER m_visible NOTIFY textureVisibleChanged) - Q_PROPERTY(QString textureWebUrl MEMBER m_webTextureUrl CONSTANT) - Q_PROPERTY(QString textureWebIconUrl MEMBER m_webIconUrl CONSTANT) + Q_PROPERTY(QString textureUrl MEMBER m_textureUrl CONSTANT) + Q_PROPERTY(QString textureIconUrl MEMBER m_iconUrl CONSTANT) 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) public: - ContentLibraryTexture(QObject *parent, const QFileInfo &iconFileInfo, const QString &downloadPath, - const QUrl &icon, const QString &key, const QString &webTextureUrl, - const QString &webIconUrl, const QString &fileExt, const QSize &dimensions, - const qint64 sizeInBytes, bool hasUpdate, bool isNew); + ContentLibraryTexture(QObject *parent, const QFileInfo &iconFileInfo, const QString &dirPath, + const QString &suffix, const QSize &dimensions, const qint64 sizeInBytes, + const QString &key = {}, const QString &textureUrl = {}, + const QString &iconUrl = {}, bool hasUpdate = false, bool isNew = false); Q_INVOKABLE bool isDownloaded() const; Q_INVOKABLE void setDownloaded(); @@ -38,7 +39,7 @@ public: QUrl icon() const; QString iconPath() const; - QString downloadedTexturePath() const; + QString texturePath() const; QString parentDirPath() const; QString textureKey() const; @@ -51,17 +52,17 @@ signals: void hasUpdateChanged(); private: - QString resolveFileExt(); + QString resolveSuffix(); QString resolveToolTipText(); void doSetDownloaded(); QString m_iconPath; - QString m_downloadPath; - QString m_webTextureUrl; - QString m_webIconUrl; + QString m_dirPath; + QString m_textureUrl; + QString m_iconUrl; QString m_toolTip; QString m_baseName; - QString m_fileExt; + QString m_suffix; QString m_textureKey; QUrl m_icon; QSize m_dimensions; @@ -71,6 +72,7 @@ private: bool m_visible = true; bool m_hasUpdate = false; bool m_isNew = false; + const QString m_itemType = "texture"; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp index 77519ad88f..0cafe8d138 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.cpp @@ -14,17 +14,15 @@ namespace QmlDesigner { ContentLibraryTexturesCategory::ContentLibraryTexturesCategory(QObject *parent, const QString &name) : QObject(parent), m_name(name) {} -void ContentLibraryTexturesCategory::addTexture(const QFileInfo &tex, const QString &downloadPath, +void ContentLibraryTexturesCategory::addTexture(const QFileInfo &texIcon, const QString &downloadPath, const QString &key, const QString &webTextureUrl, - const QString &webIconUrl, const QString &fileExt, + const QString &iconUrl, const QString &suffix, const QSize &dimensions, const qint64 sizeInBytes, bool hasUpdate, bool isNew) { - QUrl icon = QUrl::fromLocalFile(tex.absoluteFilePath()); - m_categoryTextures.append(new ContentLibraryTexture( - this, tex, downloadPath, icon, key, webTextureUrl, webIconUrl, - fileExt, dimensions, sizeInBytes, hasUpdate, isNew)); + this, texIcon, downloadPath, suffix, dimensions, sizeInBytes, + key, webTextureUrl, iconUrl, hasUpdate, isNew)); } bool ContentLibraryTexturesCategory::filter(const QString &searchText) diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h index 166528f05a..857346df06 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturescategory.h @@ -28,7 +28,7 @@ public: ContentLibraryTexturesCategory(QObject *parent, const QString &name); void addTexture(const QFileInfo &tex, const QString &subPath, const QString &key, - const QString &webTextureUrl, const QString &webIconUrl, const QString &fileExt, + const QString &webTextureUrl, const QString &iconUrl, const QString &suffix, const QSize &dimensions, const qint64 sizeInBytes, bool hasUpdate, bool isNew); bool filter(const QString &searchText); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp index 319ca2686f..b575b6b9b2 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.cpp @@ -95,37 +95,37 @@ QHash<int, QByteArray> ContentLibraryTexturesModel::roleNames() const /** * @brief Load the bundle categorized icons. Actual textures are downloaded on demand * - * @param bundlePath local path to the bundle folder and icons - * @param metaData bundle textures metadata + * @param textureBundleUrl remote url to the texture bundle + * @param bundleIconPath local path to the texture bundle icons folder + * @param jsonData bundle textures information from the bundle json */ -void ContentLibraryTexturesModel::loadTextureBundle(const QString &remoteUrl, const QString &iconsUrl, +void ContentLibraryTexturesModel::loadTextureBundle(const QString &textureBundleUrl, const QString &bundleIconPath, - const QVariantMap &metaData) + const QVariantMap &jsonData) { if (!m_bundleCategories.isEmpty()) return; QDir bundleDir = QString("%1/%2").arg(bundleIconPath, m_category); - if (!bundleDir.exists()) { - qWarning() << __FUNCTION__ << "textures bundle folder doesn't exist." << bundleDir.absolutePath(); - return; - } + QTC_ASSERT(bundleDir.exists(), return); - const QVariantMap imageItems = metaData.value("image_items").toMap(); + const QVariantMap imageItems = jsonData.value("image_items").toMap(); const QFileInfoList dirs = bundleDir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &dir : dirs) { auto category = new ContentLibraryTexturesCategory(this, dir.fileName()); - const QFileInfoList texFiles = QDir(dir.filePath()).entryInfoList(QDir::Files); - for (const QFileInfo &tex : texFiles) { - QString textureUrl = QString("%1/%2/%3.zip").arg(remoteUrl, dir.fileName(), tex.baseName()); - QString iconUrl = QString("%1/%2/%3.png").arg(iconsUrl, dir.fileName(), tex.baseName()); - - QString localDownloadPath = QString("%1/%2/%3") + const QFileInfoList texIconFiles = QDir(dir.filePath()).entryInfoList(QDir::Files); + for (const QFileInfo &texIcon : texIconFiles) { + QString textureUrl = QString("%1/%2/%3/%4.zip").arg(textureBundleUrl, m_category, + dir.fileName(), texIcon.baseName()); + QString iconUrl = QString("%1/icons/%2/%3/%4.png").arg(textureBundleUrl, m_category, + dir.fileName(), texIcon.baseName()); + + QString texturePath = QString("%1/%2/%3") .arg(Paths::bundlesPathSetting(), m_category, dir.fileName()); - QString key = QString("%1/%2/%3").arg(m_category, dir.fileName(), tex.baseName()); + QString key = QString("%1/%2/%3").arg(m_category, dir.fileName(), texIcon.baseName()); QString fileExt; QSize dimensions; qint64 sizeInBytes = -1; @@ -141,7 +141,7 @@ void ContentLibraryTexturesModel::loadTextureBundle(const QString &remoteUrl, co isNew = m_newFiles.contains(key); } - category->addTexture(tex, localDownloadPath, key, textureUrl, iconUrl, fileExt, + category->addTexture(texIcon, texturePath, key, textureUrl, iconUrl, fileExt, dimensions, sizeInBytes, hasUpdate, isNew); } m_bundleCategories.append(category); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h index 92db4151a8..94e223a251 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexturesmodel.h @@ -37,8 +37,8 @@ public: void setHasSceneEnv(bool b); void resetModel(); - void loadTextureBundle(const QString &remoteUrl, const QString &iconsUrl, - const QString &bundlePath, const QVariantMap &metaData); + void loadTextureBundle(const QString &textureBundleUrl, const QString &bundlePath, + const QVariantMap &metaData); signals: void isEmptyChanged(); diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp new file mode 100644 index 0000000000..18d6e45fa8 --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp @@ -0,0 +1,423 @@ +// 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 "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 diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h new file mode 100644 index 0000000000..3e9a96fd9d --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h @@ -0,0 +1,132 @@ +// 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 "modelfwd.h" + +#include <QAbstractListModel> +#include <QJsonObject> + +QT_FORWARD_DECLARE_CLASS(QUrl) + +namespace QmlDesigner { + +class ContentLibraryEffect; +class ContentLibraryMaterial; +class ContentLibraryTexture; +class ContentLibraryWidget; +class NodeMetaInfo; + +namespace Internal { +class ContentLibraryBundleImporter; +} + +class ContentLibraryUserModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged) + Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) + Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged) + Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged) + Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged) + Q_PROPERTY(QList<ContentLibraryMaterial *> userMaterials MEMBER m_userMaterials NOTIFY userMaterialsChanged) + Q_PROPERTY(QList<ContentLibraryTexture *> userTextures MEMBER m_userTextures NOTIFY userTexturesChanged) + Q_PROPERTY(QList<ContentLibraryEffect *> user3DItems MEMBER m_user3DItems NOTIFY user3DItemsChanged) + Q_PROPERTY(QList<ContentLibraryEffect *> userEffects MEMBER m_userEffects NOTIFY userEffectsChanged) + +public: + ContentLibraryUserModel(ContentLibraryWidget *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash<int, QByteArray> roleNames() const override; + + void setSearchText(const QString &searchText); + void updateImportedState(const QStringList &importedMats); + + QPair<QString, QString> getUniqueLibMaterialNameAndQml(const QString &matName) const; + TypeName qmlToModule(const QString &qmlName) const; + + void setQuick3DImportVersion(int major, int minor); + + bool hasRequiredQuick3DImport() const; + + bool matBundleExists() const; + + bool hasModelSelection() const; + void setHasModelSelection(bool b); + + void resetModel(); + void updateIsEmpty(); + + void addMaterial(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files); + void addTextures(const QStringList &paths); + + void setBundleObj(const QJsonObject &newBundleObj); + QJsonObject &bundleJsonObjectRef(); + + Internal::ContentLibraryBundleImporter *bundleImporter() const; + + Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryMaterial *mat, bool add = false); + Q_INVOKABLE void addToProject(QmlDesigner::ContentLibraryMaterial *mat); + Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryMaterial *mat); + +signals: + void isEmptyChanged(); + void hasRequiredQuick3DImportChanged(); + void hasModelSelectionChanged(); + void userMaterialsChanged(); + void userTexturesChanged(); + void user3DItemsChanged(); + void userEffectsChanged(); + + void applyToSelectedTriggered(QmlDesigner::ContentLibraryMaterial *mat, bool add = false); + +#ifdef QDS_USE_PROJECTSTORAGE + void bundleMaterialImported(const QmlDesigner::TypeName &typeName); +#else + void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo); +#endif + void bundleMaterialAboutToUnimport(const QmlDesigner::TypeName &type); + void bundleMaterialUnimported(const QmlDesigner::NodeMetaInfo &metaInfo); + void importerRunningChanged(); + void matBundleExistsChanged(); + +private: + void loadMaterialBundle(); + void loadTextureBundle(); + bool isValidIndex(int idx) const; + void createImporter(const QString &bundlePath, const QString &bundleId, + const QStringList &sharedFiles); + + ContentLibraryWidget *m_widget = nullptr; + QString m_searchText; + QString m_bundleId; + + QList<ContentLibraryMaterial *> m_userMaterials; + QList<ContentLibraryTexture *> m_userTextures; + QList<ContentLibraryEffect *> m_userEffects; + QList<ContentLibraryEffect *> m_user3DItems; + QStringList m_userCategories; + + QJsonObject m_bundleObj; + Internal::ContentLibraryBundleImporter *m_importer = nullptr; + + bool m_isEmpty = true; + bool m_matBundleExists = false; + bool m_hasModelSelection = false; + bool m_importerRunning = false; + + int m_quick3dMajorVersion = -1; + int m_quick3dMinorVersion = -1; + + QString m_importerBundlePath; + QString m_importerBundleId; + QStringList m_importerSharedFiles; + + enum Roles { NameRole = Qt::UserRole + 1, VisibleRole, ItemsRole }; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 61ae078ea8..dd8a4d9919 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -3,6 +3,8 @@ #include "contentlibraryview.h" +#include "asset.h" +#include "bindingproperty.h" #include "contentlibrarybundleimporter.h" #include "contentlibraryeffect.h" #include "contentlibraryeffectsmodel.h" @@ -10,13 +12,17 @@ #include "contentlibrarymaterialsmodel.h" #include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" +#include "contentlibraryusermodel.h" #include "contentlibrarywidget.h" +#include "documentmanager.h" #include "externaldependenciesinterface.h" #include "nodelistproperty.h" #include "qmldesignerconstants.h" #include "qmlobjectnode.h" #include "variantproperty.h" -#include <utils3d.h> +#include "utils3d.h" + +#include <designerpaths.h> #include <coreplugin/messagebox.h> #include <enumeration.h> @@ -30,6 +36,10 @@ #include <qtsupport/qtkitaspect.h> #endif +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QPixmap> #include <QVector3D> namespace QmlDesigner { @@ -204,6 +214,8 @@ WidgetInfo ContentLibraryView::widgetInfo() connect(effectsModel, &ContentLibraryEffectsModel::bundleItemUnimported, this, &ContentLibraryView::updateBundleEffectsImportedState); + + connectUserBundle(); } return createWidgetInfo(m_widget.data(), @@ -213,6 +225,64 @@ WidgetInfo ContentLibraryView::widgetInfo() tr("Content Library")); } +void ContentLibraryView::connectUserBundle() +{ + ContentLibraryUserModel *userModel = m_widget->userModel().data(); + + connect(userModel, + &ContentLibraryUserModel::applyToSelectedTriggered, + this, + [&](ContentLibraryMaterial *bundleMat, bool add) { + if (m_selectedModels.isEmpty()) + return; + + m_bundleMaterialTargets = m_selectedModels; + m_bundleMaterialAddToSelected = add; + + ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type()); + if (defaultMat.isValid()) + applyBundleMaterialToDropTarget(defaultMat); + else + m_widget->userModel()->addToProject(bundleMat); + }); + +#ifdef QDS_USE_PROJECTSTORAGE + connect(userModel, + &ContentLibraryUserModel::bundleMaterialImported, + this, + [&](const QmlDesigner::TypeName &typeName) { + applyBundleMaterialToDropTarget({}, typeName); + updateBundleUserMaterialsImportedState(); + }); +#else + connect(userModel, + &ContentLibraryUserModel::bundleMaterialImported, + this, + [&](const QmlDesigner::NodeMetaInfo &metaInfo) { + applyBundleMaterialToDropTarget({}, metaInfo); + updateBundleUserMaterialsImportedState(); + }); +#endif + + connect(userModel, &ContentLibraryUserModel::bundleMaterialAboutToUnimport, this, + [&] (const QmlDesigner::TypeName &type) { + // delete instances of the bundle material that is about to be unimported + executeInTransaction("ContentLibraryView::connectUserModel", [&] { + ModelNode matLib = Utils3D::materialLibraryNode(this); + if (!matLib.isValid()) + return; + + Utils::reverseForeach(matLib.directSubModelNodes(), [&](const ModelNode &mat) { + if (mat.isValid() && mat.type() == type) + QmlObjectNode(mat).destroy(); + }); + }); + }); + + connect(userModel, &ContentLibraryUserModel::bundleMaterialUnimported, this, + &ContentLibraryView::updateBundleUserMaterialsImportedState); +} + void ContentLibraryView::modelAttached(Model *model) { AbstractView::modelAttached(model); @@ -276,6 +346,7 @@ void ContentLibraryView::selectedNodesChanged(const QList<ModelNode> &selectedNo }); m_widget->materialsModel()->setHasModelSelection(!m_selectedModels.isEmpty()); + m_widget->userModel()->setHasModelSelection(!m_selectedModels.isEmpty()); } void ContentLibraryView::customNotification(const AbstractView *view, @@ -283,8 +354,6 @@ void ContentLibraryView::customNotification(const AbstractView *view, const QList<ModelNode> &nodeList, const QList<QVariant> &data) { - Q_UNUSED(data) - if (view == this) return; @@ -324,6 +393,12 @@ void ContentLibraryView::customNotification(const AbstractView *view, m_bundleEffectPos = data.size() == 1 ? data.first() : QVariant(); m_widget->effectsModel()->addInstance(m_draggedBundleEffect); m_bundleEffectTarget = nodeList.first() ? nodeList.first() : Utils3D::active3DSceneNode(this); + } else if (identifier == "add_material_to_content_lib") { + QTC_ASSERT(nodeList.size() == 1 && data.size() == 1, return); + + addLibMaterial(nodeList.first(), data.first().value<QPixmap>()); + } else if (identifier == "add_assets_to_content_lib") { + addLibAssets(data.first().toStringList()); } } @@ -452,6 +527,163 @@ void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundle } #endif +// Add a project material to Content Library's user tab +void ContentLibraryView::addLibMaterial(const ModelNode &mat, const QPixmap &icon) +{ + auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/"); + + auto [name, qml] = m_widget->userModel()->getUniqueLibMaterialNameAndQml( + mat.variantProperty("objectName").value().toString()); + + bundlePath.pathAppended("icons").createDir(); + bundlePath.pathAppended("images").createDir(); + bundlePath.pathAppended("shaders").createDir(); + + QString iconPath = QLatin1String("icons/%1.png").arg(mat.id()); + QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); + + // save icon + bool iconSaved = icon.save(fullIconPath); + if (!iconSaved) + qWarning() << __FUNCTION__ << "icon save failed"; + + // generate and save material Qml file + const QStringList depAssets = writeLibMaterialQml(mat, qml); + + // add the material to the bundle json + QJsonObject &jsonRef = m_widget->userModel()->bundleJsonObjectRef(); + QJsonObject matsObj = jsonRef.value("materials").toObject(); + QJsonObject matObj; + matObj.insert("qml", qml); + matObj.insert("icon", iconPath); + QJsonArray filesArr; + for (const QString &assetPath : depAssets) + filesArr.append(assetPath); + matObj.insert("files", filesArr); + + matsObj.insert(name, matObj); + jsonRef.insert("materials", matsObj); + auto result = bundlePath.pathAppended("user_materials_bundle.json") + .writeFileContents(QJsonDocument(jsonRef).toJson()); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + // copy material assets to bundle folder + for (const QString &assetPath : depAssets) { + Asset asset(assetPath); + QString subDir; + if (asset.isImage()) + subDir = "images"; + else if (asset.isShader()) + subDir = "shaders"; + + Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath); + Utils::FilePath assetPathTarget = bundlePath.pathAppended(QString("%1/%2") + .arg(subDir, "/" + asset.fileName())); + + auto result = assetPathSource.copyFile(assetPathTarget); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + } + + m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets); +} + +QStringList ContentLibraryView::writeLibMaterialQml(const ModelNode &mat, const QString &qml) +{ + QStringList depListIds; + auto [qmlString, assets] = modelNodeToQmlString(mat, depListIds); + + qmlString.prepend("import QtQuick\nimport QtQuick3D\n\n"); + + auto qmlPath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/" + qml); + auto result = qmlPath.writeFileContents(qmlString.toUtf8()); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + return assets.values(); +} + +QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const ModelNode &node, + QStringList &depListIds, + int depth) +{ + QString qml; + QSet<QString> assets; + + QString indent = QString(" ").repeated(depth * 4); + + qml += indent + node.simplifiedTypeName() + " {\n"; + + indent = QString(" ").repeated((depth + 1) * 4); + + qml += indent + "id: " + (depth == 0 ? "root" : node.id()) + " \n\n"; + + const QList<AbstractProperty> matProps = node.properties(); + for (const AbstractProperty &p : matProps) { + if (p.isVariantProperty()) { + QVariant pValue = p.toVariantProperty().value(); + QString val; + if (strcmp(pValue.typeName(), "QString") == 0 || strcmp(pValue.typeName(), "QColor") == 0) { + val = QLatin1String("\"%1\"").arg(pValue.toString()); + } else if (strcmp(pValue.typeName(), "QUrl") == 0) { + val = QLatin1String("\"%1\"").arg(pValue.toString()); + assets.insert(pValue.toString()); + } else if (strcmp(pValue.typeName(), "QmlDesigner::Enumeration") == 0) { + val = pValue.value<QmlDesigner::Enumeration>().toString(); + } else { + val = pValue.toString(); + } + + qml += indent + p.name() + ": " + val + "\n"; + } else if (p.isBindingProperty()) { + qml += indent + p.name() + ": " + p.toBindingProperty().expression() + "\n"; + + ModelNode depNode = modelNodeForId(p.toBindingProperty().expression()); + + if (depNode && !depListIds.contains(depNode.id())) { + depListIds.append(depNode.id()); + auto [depQml, depAssets] = modelNodeToQmlString(depNode, depListIds, depth + 1); + qml += "\n" + depQml + "\n"; + assets.unite(depAssets); + } + } + } + + indent = QString(" ").repeated(depth * 4); + + qml += indent + "}\n"; + + return {qml, assets}; +} + +void ContentLibraryView::addLibAssets(const QStringList &paths) +{ + auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/textures"); + QStringList pathsInBundle; + + for (const QString &path : paths) { + Asset asset(path); + auto assetPath = Utils::FilePath::fromString(path); + + // save icon + QString iconSavePath = bundlePath.pathAppended("icons/" + assetPath.baseName() + ".png").toString(); + QPixmap icon = asset.pixmap({120, 120}); + bool iconSaved = icon.save(iconSavePath); + if (!iconSaved) + qWarning() << __FUNCTION__ << "icon save failed"; + + // save asset + auto result = assetPath.copyFile(bundlePath.pathAppended(asset.fileName())); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + pathsInBundle.append(bundlePath.pathAppended(asset.fileName()).toString()); + } + + m_widget->userModel()->addTextures(pathsInBundle); +} + ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &type) { ModelNode matLib = Utils3D::materialLibraryNode(this); @@ -477,6 +709,7 @@ ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &t return {}; } + #ifdef QDS_USE_PROJECTSTORAGE ModelNode ContentLibraryView::createMaterial(const TypeName &typeName) { @@ -548,6 +781,25 @@ void ContentLibraryView::updateBundleMaterialsImportedState() m_widget->materialsModel()->updateImportedState(importedBundleMats); } +void ContentLibraryView::updateBundleUserMaterialsImportedState() +{ + using namespace Utils; + + if (!m_widget->userModel()->bundleImporter()) + return; + + QStringList importedBundleMats; + + FilePath bundlePath = m_widget->userModel()->bundleImporter()->resolveBundleImportPath(); + + if (bundlePath.exists()) { + importedBundleMats = transform(bundlePath.dirEntries({{"*.qml"}, QDir::Files}), + [](const FilePath &f) { return f.fileName().chopped(4); }); + } + + m_widget->userModel()->updateImportedState(importedBundleMats); +} + void ContentLibraryView::updateBundleEffectsImportedState() { using namespace Utils; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index 3b57b7a4ab..03d42fa8bc 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -10,6 +10,8 @@ #include <QObject> #include <QPointer> +QT_FORWARD_DECLARE_CLASS(QPixmap) + namespace QmlDesigner { class ContentLibraryEffect; @@ -46,10 +48,18 @@ public: const QVariant &data) override; private: + void connectUserBundle(); void active3DSceneChanged(qint32 sceneId); void updateBundleMaterialsImportedState(); + void updateBundleUserMaterialsImportedState(); void updateBundleEffectsImportedState(); void updateBundlesQuick3DVersion(); + void addLibMaterial(const ModelNode &mat, const QPixmap &icon); + void addLibAssets(const QStringList &paths); + QStringList writeLibMaterialQml(const ModelNode &mat, const QString &qml); + QPair<QString, QSet<QString>> modelNodeToQmlString(const ModelNode &node, QStringList &depListIds, + int depth = 0); + #ifdef QDS_USE_PROJECTSTORAGE void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const TypeName &typeName = {}); #else diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index c885a76ba7..9375d43fd4 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -10,6 +10,7 @@ #include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" #include "contentlibraryiconprovider.h" +#include "contentlibraryusermodel.h" #include "utils/filedownloader.h" #include "utils/fileextractor.h" @@ -100,10 +101,10 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) && m_textureToDrag->isDownloaded()) { QMimeData *mimeData = new QMimeData; mimeData->setData(Constants::MIME_TYPE_BUNDLE_TEXTURE, - {m_textureToDrag->downloadedTexturePath().toUtf8()}); + {m_textureToDrag->texturePath().toUtf8()}); // Allows standard file drag-n-drop. As of now needed to drop on Assets view - mimeData->setUrls({QUrl::fromLocalFile(m_textureToDrag->downloadedTexturePath())}); + mimeData->setUrls({QUrl::fromLocalFile(m_textureToDrag->texturePath())}); emit bundleTextureDragStarted(m_textureToDrag); model->startDrag(mimeData, m_textureToDrag->icon().toLocalFile()); @@ -126,6 +127,7 @@ ContentLibraryWidget::ContentLibraryWidget() , m_texturesModel(new ContentLibraryTexturesModel("Textures", this)) , m_environmentsModel(new ContentLibraryTexturesModel("Environments", this)) , m_effectsModel(new ContentLibraryEffectsModel(this)) + , m_userModel(new ContentLibraryUserModel(this)) { qmlRegisterType<QmlDesigner::FileDownloader>("WebFetcher", 1, 0, "FileDownloader"); qmlRegisterType<QmlDesigner::FileExtractor>("WebFetcher", 1, 0, "FileExtractor"); @@ -140,18 +142,12 @@ ContentLibraryWidget::ContentLibraryWidget() m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground)); - m_baseUrl = QmlDesignerPlugin::settings() - .value(DesignerSettingsKey::DOWNLOADABLE_BUNDLES_URL).toString() - + "/textures"; + m_textureBundleUrl = QmlDesignerPlugin::settings() + .value(DesignerSettingsKey::DOWNLOADABLE_BUNDLES_URL).toString() + "/textures"; - m_texturesUrl = m_baseUrl + "/Textures"; - m_textureIconsUrl = m_baseUrl + "/icons/Textures"; - m_environmentIconsUrl = m_baseUrl + "/icons/Environments"; - m_environmentsUrl = m_baseUrl + "/Environments"; + m_bundlePath = Paths::bundlesPathSetting(); - m_downloadPath = Paths::bundlesPathSetting(); - - loadTextureBundle(); + loadTextureBundles(); Theme::setupTheme(m_quickWidget->engine()); m_quickWidget->quickWidget()->installEventFilter(this); @@ -177,38 +173,34 @@ ContentLibraryWidget::ContentLibraryWidget() {"materialsModel", QVariant::fromValue(m_materialsModel.data())}, {"texturesModel", QVariant::fromValue(m_texturesModel.data())}, {"environmentsModel", QVariant::fromValue(m_environmentsModel.data())}, - {"effectsModel", QVariant::fromValue(m_effectsModel.data())}}); + {"effectsModel", QVariant::fromValue(m_effectsModel.data())}, + {"userModel", QVariant::fromValue(m_userModel.data())}}); reloadQmlSource(); } -QVariantMap ContentLibraryWidget::readBundleMetadata() +QVariantMap ContentLibraryWidget::readTextureBundleJson() { - QVariantMap metaData; - QFile jsonFile(m_downloadPath + "/texture_bundle.json"); + QVariantMap jsonData; + QFile jsonFile(m_bundlePath + "/texture_bundle.json"); if (jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) - metaData = QJsonDocument::fromJson(jsonFile.readAll()).toVariant().toMap(); + jsonData = QJsonDocument::fromJson(jsonFile.readAll()).toVariant().toMap(); - int version = metaData["version"].toInt(); + int version = jsonData["version"].toInt(); if (version > TextureBundleMetadataVersion) { qWarning() << "Unrecognized texture metadata file version: " << version; return {}; } - return metaData; + return jsonData; } -void ContentLibraryWidget::loadTextureBundle() +void ContentLibraryWidget::loadTextureBundles() { - QDir bundleDir{m_downloadPath}; + QDir bundleDir{m_bundlePath}; - if (fetchTextureBundleMetadata(bundleDir) && fetchTextureBundleIcons(bundleDir)) { - QString bundleIconPath = m_downloadPath + "/TextureBundleIcons"; - QVariantMap metaData = readBundleMetadata(); - m_texturesModel->loadTextureBundle(m_texturesUrl, m_textureIconsUrl, bundleIconPath, metaData); - m_environmentsModel->loadTextureBundle(m_environmentsUrl, m_environmentIconsUrl, - bundleIconPath, metaData); - } + if (fetchTextureBundleJson(bundleDir) && fetchTextureBundleIcons(bundleDir)) + populateTextureBundleModels(); } std::tuple<QVariantMap, QVariantMap, QVariantMap> ContentLibraryWidget::compareTextureMetaFiles( @@ -272,9 +264,9 @@ void ContentLibraryWidget::fetchNewTextureIcons(const QVariantMap &existingFiles }); auto multidownloader = new MultiFileDownloader(this); - multidownloader->setBaseUrl(QString(m_baseUrl + "/icons")); + multidownloader->setBaseUrl(QString(m_textureBundleUrl + "/icons")); multidownloader->setFiles(fileList); - multidownloader->setTargetDirPath(m_downloadPath + "/TextureBundleIcons"); + multidownloader->setTargetDirPath(m_bundlePath + "/TextureBundleIcons"); auto downloader = new FileDownloader(this); downloader->setDownloadEnabled(true); @@ -314,15 +306,8 @@ void ContentLibraryWidget::fetchNewTextureIcons(const QVariantMap &existingFiles existingFile.flush(); } - if (fetchTextureBundleIcons(bundleDir)) { - QString bundleIconPath = m_downloadPath + "/TextureBundleIcons"; - QVariantMap metaData = readBundleMetadata(); - m_texturesModel->loadTextureBundle(m_texturesUrl, m_textureIconsUrl, bundleIconPath, - metaData); - m_environmentsModel->loadTextureBundle(m_environmentsUrl, m_environmentIconsUrl, - bundleIconPath, metaData); - } - + if (fetchTextureBundleIcons(bundleDir)) + populateTextureBundleModels(); }); multidownloader->start(); @@ -433,50 +418,45 @@ QStringList ContentLibraryWidget::saveNewTextures(const QDir &bundleDir, const Q } } -bool ContentLibraryWidget::fetchTextureBundleMetadata(const QDir &bundleDir) +bool ContentLibraryWidget::fetchTextureBundleJson(const QDir &bundleDir) { QString filePath = bundleDir.filePath("texture_bundle.json"); QFileInfo fi(filePath); - bool metaFileExists = fi.exists() && fi.size() > 0; + bool jsonFileExists = fi.exists() && fi.size() > 0; - QString metaFileUrl = m_baseUrl + "/texture_bundle.zip"; + QString bundleZipUrl = m_textureBundleUrl + "/texture_bundle.zip"; FileDownloader *downloader = new FileDownloader(this); - downloader->setUrl(metaFileUrl); + downloader->setUrl(bundleZipUrl); downloader->setProbeUrl(false); downloader->setDownloadEnabled(true); + downloader->start(); QObject::connect(downloader, &FileDownloader::downloadFailed, this, - [this, metaFileExists, bundleDir] { - if (metaFileExists) { - if (fetchTextureBundleIcons(bundleDir)) { - QString bundleIconPath = m_downloadPath + "/TextureBundleIcons"; - QVariantMap metaData = readBundleMetadata(); - m_texturesModel->loadTextureBundle(m_texturesUrl, m_textureIconsUrl, bundleIconPath, - metaData); - m_environmentsModel->loadTextureBundle(m_environmentsUrl, m_environmentIconsUrl, - bundleIconPath, metaData); - } + [this, jsonFileExists, bundleDir] { + if (jsonFileExists) { + if (fetchTextureBundleIcons(bundleDir)) + populateTextureBundleModels(); } }); QObject::connect(downloader, &FileDownloader::finishedChanged, this, - [this, downloader, bundleDir, metaFileExists, filePath] { + [this, downloader, bundleDir, jsonFileExists, filePath] { FileExtractor *extractor = new FileExtractor(this); extractor->setArchiveName(downloader->completeBaseName()); extractor->setSourceFile(downloader->outputFile()); - if (!metaFileExists) + if (!jsonFileExists) extractor->setTargetPath(bundleDir.absolutePath()); extractor->setAlwaysCreateDir(false); extractor->setClearTargetPathContents(false); QObject::connect(extractor, &FileExtractor::finishedChanged, this, - [this, downloader, bundleDir, extractor, metaFileExists, filePath] { + [this, downloader, bundleDir, extractor, jsonFileExists, filePath] { downloader->deleteLater(); extractor->deleteLater(); - if (metaFileExists) { + if (jsonFileExists) { QVariantMap newFiles, existing; QVariantMap modifiedFilesEntries; @@ -498,32 +478,35 @@ bool ContentLibraryWidget::fetchTextureBundleMetadata(const QDir &bundleDir) } } - if (fetchTextureBundleIcons(bundleDir)) { - QString bundleIconPath = m_downloadPath + "/TextureBundleIcons"; - QVariantMap metaData = readBundleMetadata(); - m_texturesModel->loadTextureBundle(m_texturesUrl, m_textureIconsUrl, bundleIconPath, - metaData); - m_environmentsModel->loadTextureBundle(m_environmentsUrl, m_environmentIconsUrl, - bundleIconPath, metaData); - } + if (fetchTextureBundleIcons(bundleDir)) + populateTextureBundleModels(); }); extractor->extract(); }); - downloader->start(); return false; } +void ContentLibraryWidget::populateTextureBundleModels() +{ + QVariantMap jsonData = readTextureBundleJson(); + + QString bundleIconPath = m_bundlePath + "/TextureBundleIcons"; + + m_texturesModel->loadTextureBundle(m_textureBundleUrl, bundleIconPath, jsonData); + m_environmentsModel->loadTextureBundle(m_textureBundleUrl, bundleIconPath, jsonData); +} + bool ContentLibraryWidget::fetchTextureBundleIcons(const QDir &bundleDir) { QString iconsPath = bundleDir.filePath("TextureBundleIcons"); QDir iconsDir(iconsPath); - if (iconsDir.exists() && iconsDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot).length() > 0) + if (iconsDir.exists() && !iconsDir.isEmpty()) return true; - QString zipFileUrl = m_baseUrl + "/icons.zip"; + QString zipFileUrl = m_textureBundleUrl + "/icons.zip"; FileDownloader *downloader = new FileDownloader(this); downloader->setUrl(zipFileUrl); @@ -543,13 +526,7 @@ bool ContentLibraryWidget::fetchTextureBundleIcons(const QDir &bundleDir) [this, downloader, extractor] { downloader->deleteLater(); extractor->deleteLater(); - - QString bundleIconPath = m_downloadPath + "/TextureBundleIcons"; - QVariantMap metaData = readBundleMetadata(); - m_texturesModel->loadTextureBundle(m_texturesUrl, m_textureIconsUrl, bundleIconPath, - metaData); - m_environmentsModel->loadTextureBundle(m_environmentsUrl, m_environmentIconsUrl, - bundleIconPath, metaData); + populateTextureBundleModels(); }); extractor->extract(); @@ -572,7 +549,7 @@ void ContentLibraryWidget::markTextureUpdated(const QString &textureKey) checksumOnServer = m_environmentsModel->removeModifiedFileEntry(textureKey); QJsonObject metaDataObj; - QFile jsonFile(m_downloadPath + "/texture_bundle.json"); + QFile jsonFile(m_bundlePath + "/texture_bundle.json"); if (jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) { metaDataObj = QJsonDocument::fromJson(jsonFile.readAll()).object(); jsonFile.close(); @@ -589,7 +566,7 @@ void ContentLibraryWidget::markTextureUpdated(const QString &textureKey) QJsonDocument outDoc(metaDataObj); QByteArray data = outDoc.toJson(); - QFile outFile(m_downloadPath + "/texture_bundle.json"); + QFile outFile(m_bundlePath + "/texture_bundle.json"); if (outFile.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) { outFile.write(data); outFile.flush(); @@ -601,6 +578,12 @@ void ContentLibraryWidget::markTextureUpdated(const QString &textureKey) m_environmentsModel->markTextureHasNoUpdates(subcategory, textureKey); } +bool ContentLibraryWidget::userBundleEnabled() const +{ + // TODO: this method is to be removed after user bundle implementation is complete + return Core::ICore::settings()->value("QML/Designer/UseExperimentalFeatures45", false).toBool(); +} + QSize ContentLibraryWidget::sizeHint() const { return {420, 420}; @@ -715,6 +698,7 @@ void ContentLibraryWidget::updateSearch() m_effectsModel->setSearchText(m_filterText); m_texturesModel->setSearchText(m_filterText); m_environmentsModel->setSearchText(m_filterText); + m_userModel->setSearchText(m_filterText); m_quickWidget->update(); } @@ -777,7 +761,7 @@ void ContentLibraryWidget::addImage(ContentLibraryTexture *tex) if (!tex->isDownloaded()) return; - emit addTextureRequested(tex->downloadedTexturePath(), AddTextureMode::Image); + emit addTextureRequested(tex->texturePath(), AddTextureMode::Image); } void ContentLibraryWidget::addTexture(ContentLibraryTexture *tex) @@ -785,7 +769,7 @@ void ContentLibraryWidget::addTexture(ContentLibraryTexture *tex) if (!tex->isDownloaded()) return; - emit addTextureRequested(tex->downloadedTexturePath(), AddTextureMode::Texture); + emit addTextureRequested(tex->texturePath(), AddTextureMode::Texture); } void ContentLibraryWidget::addLightProbe(ContentLibraryTexture *tex) @@ -793,7 +777,7 @@ void ContentLibraryWidget::addLightProbe(ContentLibraryTexture *tex) if (!tex->isDownloaded()) return; - emit addTextureRequested(tex->downloadedTexturePath(), AddTextureMode::LightProbe); + emit addTextureRequested(tex->texturePath(), AddTextureMode::LightProbe); } void ContentLibraryWidget::updateSceneEnvState() @@ -821,4 +805,9 @@ QPointer<ContentLibraryEffectsModel> ContentLibraryWidget::effectsModel() const return m_effectsModel; } +QPointer<ContentLibraryUserModel> ContentLibraryWidget::userModel() const +{ + return m_userModel; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index ab71a3dc79..c4d51d0362 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -24,6 +24,7 @@ class ContentLibraryMaterial; class ContentLibraryMaterialsModel; class ContentLibraryTexture; class ContentLibraryTexturesModel; +class ContentLibraryUserModel; class ContentLibraryWidget : public QFrame { @@ -65,6 +66,7 @@ public: QPointer<ContentLibraryTexturesModel> texturesModel() const; QPointer<ContentLibraryTexturesModel> environmentsModel() const; QPointer<ContentLibraryEffectsModel> effectsModel() const; + QPointer<ContentLibraryUserModel> userModel() const; Q_INVOKABLE void startDragEffect(QmlDesigner::ContentLibraryEffect *eff, const QPointF &mousePos); Q_INVOKABLE void startDragMaterial(QmlDesigner::ContentLibraryMaterial *mat, const QPointF &mousePos); @@ -74,6 +76,7 @@ public: Q_INVOKABLE void addLightProbe(QmlDesigner::ContentLibraryTexture *tex); Q_INVOKABLE void updateSceneEnvState(); Q_INVOKABLE void markTextureUpdated(const QString &textureKey); + Q_INVOKABLE bool userBundleEnabled() const; QSize sizeHint() const override; @@ -97,21 +100,23 @@ private: void updateSearch(); void setIsDragging(bool val); QString findTextureBundlePath(); - void loadTextureBundle(); - QVariantMap readBundleMetadata(); - bool fetchTextureBundleMetadata(const QDir &bundleDir); + void loadTextureBundles(); + QVariantMap readTextureBundleJson(); + bool fetchTextureBundleJson(const QDir &bundleDir); bool fetchTextureBundleIcons(const QDir &bundleDir); void fetchNewTextureIcons(const QVariantMap &existingFiles, const QVariantMap &newFiles, const QString &existingMetaFilePath, const QDir &bundleDir); std::tuple<QVariantMap, QVariantMap, QVariantMap> compareTextureMetaFiles( const QString &existingMetaFile, const QString downloadedMetaFile); QStringList saveNewTextures(const QDir &bundleDir, const QStringList &newFiles); + void populateTextureBundleModels(); QScopedPointer<StudioQuickWidget> m_quickWidget; QPointer<ContentLibraryMaterialsModel> m_materialsModel; QPointer<ContentLibraryTexturesModel> m_texturesModel; QPointer<ContentLibraryTexturesModel> m_environmentsModel; QPointer<ContentLibraryEffectsModel> m_effectsModel; + QPointer<ContentLibraryUserModel> m_userModel; QShortcut *m_qmlSourceUpdateShortcut = nullptr; @@ -127,12 +132,8 @@ private: bool m_hasQuick3DImport = false; bool m_isDragging = false; bool m_isQt6Project = false; - QString m_baseUrl; - QString m_texturesUrl; - QString m_textureIconsUrl; - QString m_environmentIconsUrl; - QString m_environmentsUrl; - QString m_downloadPath; + QString m_textureBundleUrl; + QString m_bundlePath; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/createtexture.cpp b/src/plugins/qmldesigner/components/createtexture.cpp index b6e99ae972..56056e3fd2 100644 --- a/src/plugins/qmldesigner/components/createtexture.cpp +++ b/src/plugins/qmldesigner/components/createtexture.cpp @@ -17,6 +17,7 @@ #include <coreplugin/messagebox.h> #include <QTimer> +#include <QUrl> namespace QmlDesigner { @@ -94,7 +95,7 @@ ModelNode CreateTexture::createTextureFromImage(const Utils::FilePath &assetPat newTexNode.setIdWithoutRefactoring(m_view->model()->generateNewId(assetPath.baseName())); VariantProperty sourceProp = newTexNode.variantProperty("source"); - sourceProp.setValue(textureSource); + sourceProp.setValue(QUrl(textureSource)); matLib.defaultNodeListProperty().reparentHere(newTexNode); } diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp index e84623e26c..36ce3373e5 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditor.cpp @@ -77,8 +77,8 @@ CurveEditor::CurveEditor(CurveEditorModel *model, QWidget *parent) connect(m_toolbar, &CurveEditorToolBar::currentFrameChanged, [this, model](int frame) { model->setCurrentFrame(frame); + m_view->setCurrentFrame(frame, false); updateStatusLine(); - m_view->viewport()->update(); }); connect(m_toolbar, &CurveEditorToolBar::zoomChanged, [this](double zoom) { diff --git a/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp b/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp index 72410ffb07..8da87ce119 100644 --- a/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/curveeditortoolbar.cpp @@ -129,6 +129,8 @@ CurveEditorToolBar::CurveEditorToolBar(CurveEditorModel *model, QWidget* parent) m_currentSpin->setFrame(false); connect(m_currentSpin, &QSpinBox::valueChanged, this, &CurveEditorToolBar::currentFrameChanged); + connect(model, &CurveEditorModel::commitCurrentFrame, + this, [this](int frame) { m_currentSpin->setValue(frame); }); addSpacer(); diff --git a/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp b/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp index 95a260c26f..a1dfcc8f98 100644 --- a/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp +++ b/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp @@ -15,6 +15,8 @@ #include "qmlobjectnode.h" #include "variantproperty.h" +#include <model/modelutils.h> + #include <utils3d.h> #include <utils/expected.h> @@ -292,7 +294,7 @@ bool BakeLightsDataModel::reset() if (!hasExposedProps && node.metaInfo().isFileComponent() && node.metaInfo().isQtQuick3DNode()) { - const QString compFile = node.metaInfo().componentFileName(); + const QString compFile = ModelUtils::componentFilePath(node); const QString projPath = m_view->externalDependencies().currentProjectDirPath(); if (compFile.startsWith(projPath)) { // Quick and dirty scan of the component source to check if it potentially has diff --git a/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.cpp b/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.cpp index 76560ac192..f5a74ee864 100644 --- a/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.cpp +++ b/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.cpp @@ -66,9 +66,11 @@ void CameraSpeedConfiguration::resetDefaults() void CameraSpeedConfiguration::hideCursor() { - if (QGuiApplication::overrideCursor()) + if (m_cursorHidden) return; + m_cursorHidden = true; + QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); if (QWindow *w = QGuiApplication::focusWindow()) @@ -77,9 +79,11 @@ void CameraSpeedConfiguration::hideCursor() void CameraSpeedConfiguration::restoreCursor() { - if (!QGuiApplication::overrideCursor()) + if (!m_cursorHidden) return; + m_cursorHidden = false; + QGuiApplication::restoreOverrideCursor(); if (QWindow *w = QGuiApplication::focusWindow()) @@ -88,7 +92,7 @@ void CameraSpeedConfiguration::restoreCursor() void CameraSpeedConfiguration::holdCursorInPlace() { - if (!QGuiApplication::overrideCursor()) + if (!m_cursorHidden) return; if (QWindow *w = QGuiApplication::focusWindow()) diff --git a/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.h b/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.h index 55256890cb..fef06efc49 100644 --- a/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.h +++ b/src/plugins/qmldesigner/components/edit3d/cameraspeedconfiguration.h @@ -71,6 +71,7 @@ private: double m_multiplier = 0.; bool m_changes = false; QPoint m_lastPos; + bool m_cursorHidden = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 911ee1fb15..a20904b2e5 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -279,6 +279,14 @@ void Edit3DView::modelAttached(Model *model) { AbstractView::modelAttached(model); + QString currProjectPath = QmlDesigner::DocumentManager::currentProjectDirPath().toString(); + if (m_currProjectPath != currProjectPath) { + // Opening a new project -> reset camera speeds + m_currProjectPath = currProjectPath; + m_previousCameraSpeed = -1.; + m_previousCameraMultiplier = -1.; + } + syncSnapAuxPropsToSettings(); rootModelNode().setAuxiliaryData(edit3dGridColorProperty, @@ -356,7 +364,12 @@ void Edit3DView::handleEntriesChanged() append(model()->qtQuick3DOrthographicCameraMetaInfo(), EK_cameras); append(model()->qtQuick3DPerspectiveCameraMetaInfo(), EK_cameras); - auto assetsModule = model()->module("Quick3DAssets"); + Utils::PathString import3dTypePrefix = QmlDesignerPlugin::instance() + ->documentManager() + .generatedComponentUtils() + .import3dTypePrefix(); + + auto assetsModule = model()->module(import3dTypePrefix); for (const auto &metaInfo : model()->metaInfosForModule(assetsModule)) append(metaInfo, EK_importedModels); @@ -373,7 +386,8 @@ void Edit3DView::handleEntriesChanged() } else if (entry.typeName() == "QtQuick3D.OrthographicCamera" || entry.typeName() == "QtQuick3D.PerspectiveCamera") { entryKey = EK_cameras; - } else if (entry.typeName().startsWith("Quick3DAssets.") + } else if (entry.typeName().startsWith(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().import3dTypePrefix().toUtf8()) && NodeHints::fromItemLibraryEntry(entry).canBeDroppedInView3D()) { entryKey = EK_importedModels; } else { diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 781b26d8d8..fad87aae1f 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -192,6 +192,7 @@ private: double m_previousCameraSpeed = -1.; double m_previousCameraMultiplier = -1.; + QString m_currProjectPath; friend class Edit3DAction; }; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 07102ae893..6f1cf2e183 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -766,7 +766,11 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) QString fileName = QFileInfo(assetPath).baseName(); fileName = fileName.at(0).toUpper() + fileName.mid(1); // capitalize first letter auto model = m_view->model(); - auto metaInfo = model->metaInfo(model->module("Quick3DAssets"), fileName.toUtf8()); + Utils::PathString import3dTypePrefix = QmlDesignerPlugin::instance() + ->documentManager() + .generatedComponentUtils() + .import3dTypePrefix(); + auto metaInfo = model->metaInfo(model->module(import3dTypePrefix), fileName.toUtf8()); if (auto entries = metaInfo.itemLibrariesEntries(); entries.size()) { auto entry = ItemLibraryEntry{entries.front(), *model->projectStorage()}; QmlVisualNode::createQml3DNode(view(), entry, m_canvas->activeScene(), {}, false); @@ -780,7 +784,9 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) for (const QString &assetPath : added3DAssets) { QString fileName = QFileInfo(assetPath).baseName(); fileName = fileName.at(0).toUpper() + fileName.mid(1); // capitalize first letter - QString type = QString("Quick3DAssets.%1.%1").arg(fileName); + QString type = QString("%1.%2.%2").arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().import3dTypePrefix(), + fileName); QList<ItemLibraryEntry> entriesForType = itemLibInfo->entriesForType(type.toUtf8()); if (!entriesForType.isEmpty()) { // should always be true, but just in case QmlVisualNode::createQml3DNode(view(), diff --git a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp index 26c5fe0eb2..8890ea8964 100644 --- a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp +++ b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.cpp @@ -87,9 +87,11 @@ void SnapConfiguration::resetDefaults() void SnapConfiguration::hideCursor() { - if (QGuiApplication::overrideCursor()) + if (m_cursorHidden) return; + m_cursorHidden = true; + QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); if (QWindow *w = QGuiApplication::focusWindow()) @@ -98,9 +100,11 @@ void SnapConfiguration::hideCursor() void SnapConfiguration::restoreCursor() { - if (!QGuiApplication::overrideCursor()) + if (!m_cursorHidden) return; + m_cursorHidden = false; + QGuiApplication::restoreOverrideCursor(); if (QWindow *w = QGuiApplication::focusWindow()) @@ -109,7 +113,7 @@ void SnapConfiguration::restoreCursor() void SnapConfiguration::holdCursorInPlace() { - if (!QGuiApplication::overrideCursor()) + if (!m_cursorHidden) return; if (QWindow *w = QGuiApplication::focusWindow()) diff --git a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h index 729e6ce7d1..ae401f60f9 100644 --- a/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h +++ b/src/plugins/qmldesigner/components/edit3d/snapconfiguration.h @@ -103,6 +103,7 @@ private: double m_scaleInterval = 0.; bool m_changes = false; QPoint m_lastPos; + bool m_cursorHidden = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index c0bebbb82b..804ac076e6 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -64,13 +64,14 @@ namespace QmlDesigner { DesignDocument acts as a facade to a model representing a qml document, and the different views/widgets accessing it. */ -DesignDocument::DesignDocument(ProjectStorageDependencies projectStorageDependencies, +DesignDocument::DesignDocument([[maybe_unused]] const QUrl &filePath, + ProjectStorageDependencies projectStorageDependencies, ExternalDependenciesInterface &externalDependencies) #ifdef QDS_USE_PROJECTSTORAGE : m_documentModel(Model::create(projectStorageDependencies, "Item", {Import::createLibraryImport("QtQuick")}, - {}, + filePath, std::make_unique<ModelResourceManagement>())) #else : m_documentModel( diff --git a/src/plugins/qmldesigner/components/integration/designdocument.h b/src/plugins/qmldesigner/components/integration/designdocument.h index 0d75141205..52089d67c1 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.h +++ b/src/plugins/qmldesigner/components/integration/designdocument.h @@ -41,7 +41,8 @@ class QMLDESIGNERCOMPONENTS_EXPORT DesignDocument : public QObject Q_OBJECT public: - DesignDocument(ProjectStorageDependencies projectStorageDependencies, + DesignDocument(const QUrl &filePath, + ProjectStorageDependencies projectStorageDependencies, ExternalDependenciesInterface &externalDependencies); ~DesignDocument() override; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp index 29fff4b359..2c69072602 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -12,6 +12,7 @@ #include <variantproperty.h> #include <theme.h> +#include <utils/filepath.h> #include <utils/outputformatter.h> #include <projectexplorer/project.h> @@ -130,43 +131,8 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog( if (targetDir.isEmpty()) targetDir = defaulTargetDirectory; - // Import is always done under known folder. The order of preference for folder is: - // 1) An existing QUICK_3D_ASSETS_FOLDER under DEFAULT_ASSET_IMPORT_FOLDER project import path - // 2) An existing QUICK_3D_ASSETS_FOLDER under any project import path - // 3) New QUICK_3D_ASSETS_FOLDER under DEFAULT_ASSET_IMPORT_FOLDER project import path - // 4) New QUICK_3D_ASSETS_FOLDER under any project import path - // 5) New QUICK_3D_ASSETS_FOLDER under new DEFAULT_ASSET_IMPORT_FOLDER under project - const QString defaultAssetFolder = QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER); - const QString quick3DFolder = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER); - QString candidatePath = targetDir + defaultAssetFolder + quick3DFolder; - int candidatePriority = 5; - - for (const auto &importPath : std::as_const(importPaths)) { - if (importPath.startsWith(targetDir)) { - const bool isDefaultFolder = importPath.endsWith(defaultAssetFolder); - const QString assetFolder = importPath + quick3DFolder; - const bool exists = QFileInfo::exists(assetFolder); - if (exists) { - if (isDefaultFolder) { - // Priority one location, stop looking - candidatePath = assetFolder; - break; - } else if (candidatePriority > 2) { - candidatePriority = 2; - candidatePath = assetFolder; - } - } else { - if (candidatePriority > 3 && isDefaultFolder) { - candidatePriority = 3; - candidatePath = assetFolder; - } else if (candidatePriority > 4) { - candidatePriority = 4; - candidatePath = assetFolder; - } - } - } - } - m_quick3DImportPath = candidatePath; + m_quick3DImportPath = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().import3dBasePath().toString(); if (!m_quick3DFiles.isEmpty()) { QVector<QJsonObject> groups; @@ -294,11 +260,14 @@ void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode, QFileInfo compFileInfo{compFileName}; // Find to top asset folder - const QString assetFolder = QLatin1String(Constants::QUICK_3D_ASSETS_FOLDER).mid(1); + const QString oldAssetFolder = QLatin1String(Constants::OLD_QUICK_3D_ASSETS_FOLDER); + QString assetFolder = QLatin1String(Constants::QUICK_3D_COMPONENTS_FOLDER); const QStringList parts = compFileName.split('/'); int i = parts.size() - 1; int previousSize = 0; for (; i >= 0; --i) { + if (parts[i] == oldAssetFolder) + assetFolder = oldAssetFolder; if (parts[i] == assetFolder) break; previousSize = parts[i].size(); @@ -733,8 +702,10 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid( // and move the remaining member to ungrouped options // Note: <= 2 instead of < 2 because each group has group label member if (i != 0 && groupWidgets.size() <= 2) { - widgets[0].prepend(groupWidgets[1]); - groupWidgets[0].first->hide(); // hide group label + if (groupWidgets.size() == 2) + widgets[0].prepend(groupWidgets[1]); + if (groupWidgets.size() >= 1) + groupWidgets[0].first->hide(); // hide group label groupWidgets.clear(); } } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 48958ceec9..ed1f8041e9 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "itemlibraryassetimporter.h" + #include "assetimportupdatedialog.h" #include "qmldesignerplugin.h" #include "qmldesignerconstants.h" @@ -329,12 +330,15 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd) if (qmldirFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QString qmlInfo; qmlInfo.append("module "); - qmlInfo.append(m_importPath.split('/').last()); + qmlInfo.append(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().import3dTypePrefix()); qmlInfo.append("."); qmlInfo.append(pd.assetName); qmlInfo.append('\n'); m_requiredImports.append( - QStringLiteral("%1.%2").arg(pd.targetDir.dirName(), pd.assetName)); + QStringLiteral("%1.%2").arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().import3dTypePrefix(), + pd.assetName)); while (qmlIt.hasNext()) { qmlIt.next(); QFileInfo fi = QFileInfo(qmlIt.filePath()); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index dbeacc7595..3bff210520 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -296,15 +296,16 @@ void ItemLibraryModel::setSearchText(const QString &searchText) Import ItemLibraryModel::entryToImport(const ItemLibraryEntry &entry) { +#ifndef QDS_USE_PROJECTSTORAGE if (entry.majorVersion() == -1 && entry.minorVersion() == -1) return Import::createFileImport(entry.requiredImport()); - +#endif return Import::createLibraryImport(entry.requiredImport(), QString::number(entry.majorVersion()) + QLatin1Char('.') + QString::number(entry.minorVersion())); } -void ItemLibraryModel::update([[maybe_unused]] ItemLibraryInfo *itemLibraryInfo, Model *model) +void ItemLibraryModel::update(Model *model) { if (!model) return; @@ -312,9 +313,12 @@ void ItemLibraryModel::update([[maybe_unused]] ItemLibraryInfo *itemLibraryInfo, beginResetModel(); clearSections(); + GeneratedComponentUtils compUtils = QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils(); + QStringList excludedImports { - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1) + ".MaterialBundle", - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1) + ".EffectBundle" + compUtils.componentBundlesTypePrefix() + ".MaterialBundle", + compUtils.componentBundlesTypePrefix() + ".EffectBundle" }; // create import sections @@ -323,10 +327,12 @@ void ItemLibraryModel::update([[maybe_unused]] ItemLibraryInfo *itemLibraryInfo, QHash<QString, ItemLibraryImport *> importHash; for (const Import &import : model->imports()) { if (import.url() != projectName) { - if (excludedImports.contains(import.url()) || import.url().startsWith("Effects.")) + if (excludedImports.contains(import.url()) + || import.url().startsWith(compUtils.composedEffectsTypePrefix())) { continue; + } bool addNew = true; - bool isQuick3DAsset = import.url().startsWith("Quick3DAssets."); + bool isQuick3DAsset = import.url().startsWith(compUtils.import3dTypePrefix()); QString importUrl = import.url(); if (isQuick3DAsset) importUrl = ItemLibraryImport::quick3DAssetsTitle(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h index 212ddf8e04..b04ea492d2 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.h @@ -37,7 +37,7 @@ public: QString searchText() const; ItemLibraryImport *importByUrl(const QString &importName) const; - void update(ItemLibraryInfo *itemLibraryInfo, Model *model); + void update(Model *model); void updateUsedImports(const Imports &usedImports); QMimeData *getMimeData(const ItemLibraryEntry &itemLibraryEntry); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index b710a8226f..ffefc43178 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -118,9 +118,9 @@ void ItemLibraryWidget::resizeEvent(QResizeEvent *event) ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache) : m_itemIconSize(24, 24) - , m_itemLibraryModel(new ItemLibraryModel(this)) - , m_addModuleModel(new ItemLibraryAddImportModel(this)) - , m_itemsWidget(new StudioQuickWidget(this)) + , m_itemLibraryModel(std::make_unique<ItemLibraryModel>()) + , m_addModuleModel(std::make_unique<ItemLibraryAddImportModel>()) + , m_itemsWidget(Utils::makeUniqueObjectPtr<StudioQuickWidget>()) , m_imageCache{imageCache} { m_compressionTimer.setInterval(1000); @@ -146,7 +146,7 @@ ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache) auto layout = new QVBoxLayout(this); layout->setContentsMargins({}); layout->setSpacing(0); - layout->addWidget(m_itemsWidget.data()); + layout->addWidget(m_itemsWidget.get()); updateSearch(); @@ -167,8 +167,8 @@ ItemLibraryWidget::ItemLibraryWidget(AsynchronousImageCache &imageCache) auto map = m_itemsWidget->registerPropertyMap("ItemLibraryBackend"); - map->setProperties({{"itemLibraryModel", QVariant::fromValue(m_itemLibraryModel.data())}, - {"addModuleModel", QVariant::fromValue(m_addModuleModel.data())}, + map->setProperties({{"itemLibraryModel", QVariant::fromValue(m_itemLibraryModel.get())}, + {"addModuleModel", QVariant::fromValue(m_addModuleModel.get())}, {"itemLibraryIconWidth", m_itemIconSize.width()}, {"itemLibraryIconHeight", m_itemIconSize.height()}, {"rootView", QVariant::fromValue(this)}, @@ -333,9 +333,7 @@ void ItemLibraryWidget::updateModel() m_compressionTimer.stop(); } -#ifndef QDS_USE_PROJECTSTORAGE - m_itemLibraryModel->update(m_itemLibraryInfo.data(), m_model.data()); -#endif + m_itemLibraryModel->update(m_model.data()); if (m_itemLibraryModel->rowCount() == 0 && !m_updateRetry) { m_updateRetry = true; // Only retry once to avoid endless loops diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h index b56532b218..2940db7a73 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h @@ -10,9 +10,10 @@ #include <studioquickwidget.h> -#include <utils/fancylineedit.h> -#include <utils/dropsupport.h> #include <previewtooltip/previewtooltipbackend.h> +#include <utils/dropsupport.h> +#include <utils/fancylineedit.h> +#include <utils/uniqueobjectptr.h> #include <QFileIconProvider> #include <QFrame> @@ -104,10 +105,10 @@ private: #ifndef QDS_USE_PROJECTSTORAGE QPointer<ItemLibraryInfo> m_itemLibraryInfo; #endif - QPointer<ItemLibraryModel> m_itemLibraryModel; - QPointer<ItemLibraryAddImportModel> m_addModuleModel; + std::unique_ptr<ItemLibraryModel> m_itemLibraryModel; + std::unique_ptr<ItemLibraryAddImportModel> m_addModuleModel; - QScopedPointer<StudioQuickWidget> m_itemsWidget; + Utils::UniqueObjectPtr<StudioQuickWidget> m_itemsWidget; std::unique_ptr<PreviewTooltipBackend> m_previewTooltipBackend; QShortcut *m_qmlSourceUpdateShortcut; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp index ec95f3e5d3..918956a04c 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsertexturesmodel.cpp @@ -63,7 +63,7 @@ QVariant MaterialBrowserTexturesModel::data(const QModelIndex &index, int role) return tr("Texture has no source image."); ModelNode texNode = m_textureList.at(index.row()); - QString info = ImageUtils::imageInfo(source); + QString info = ImageUtils::imageInfoString(source); if (info.isEmpty()) return tr("Texture has no data."); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 47a1e8d293..7cf0a875bc 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp @@ -56,6 +56,13 @@ public: m_pixmaps.insert(node.internalId(), pixmap); } + QPixmap getPixmap(const ModelNode &node) + { + QTC_ASSERT(node, return {}); + + return m_pixmaps.value(node.internalId()); + } + void clearPixmapCache() { m_pixmaps.clear(); @@ -357,6 +364,13 @@ void MaterialBrowserWidget::focusMaterialSection(bool focusMatSec) } } +void MaterialBrowserWidget::addMaterialToContentLibrary() +{ + ModelNode mat = m_materialBrowserModel->selectedMaterial(); + m_materialBrowserView->emitCustomNotification("add_material_to_content_lib", {mat}, + {m_previewImageProvider->getPixmap(mat)}); +} + QString MaterialBrowserWidget::qmlSourcesPath() { #ifdef SHARE_QML_PATH @@ -420,4 +434,10 @@ QPointer<MaterialBrowserTexturesModel> MaterialBrowserWidget::materialBrowserTex return m_materialBrowserTexturesModel; } +bool MaterialBrowserWidget::userBundleEnabled() const +{ + // TODO: this method is to be removed after user bundle implementation is complete + return Core::ICore::settings()->value("QML/Designer/UseExperimentalFeatures45", false).toBool(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index bfe7ace34d..97c994e565 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -60,6 +60,8 @@ public: Q_INVOKABLE void acceptAssetsDropOnMaterial(int matIndex, const QList<QUrl> &urls); Q_INVOKABLE void acceptTextureDropOnMaterial(int matIndex, const QString &texId); Q_INVOKABLE void focusMaterialSection(bool focusMatSec); + Q_INVOKABLE void addMaterialToContentLibrary(); + Q_INVOKABLE bool userBundleEnabled() const; StudioQuickWidget *quickWidget() const; diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp index 58b4c42749..a3ab5f2cd7 100644 --- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp +++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp @@ -5,6 +5,8 @@ #include "nodemetainfo.h" #include "ui_choosefrompropertylistdialog.h" +#include <qmldesignerplugin.h> + namespace QmlDesigner { // This will filter and return possible properties that the given type can be bound to @@ -100,7 +102,10 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i #ifdef QDS_USE_PROJECTSTORAGE // TODO add the types here or use the module #else - } else if (insertInfo.typeName().startsWith("ComponentBundles.MaterialBundle")) { + } else if (insertInfo.typeName().startsWith( + QString("%1.MaterialBundle").arg(QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().componentBundlesTypePrefix()) + .toUtf8())) { if (parentInfo.isQtQuick3DModel()) propertyList.append("materials"); #endif diff --git a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp index aacaf6dc0d..223e04fd96 100644 --- a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp @@ -9,9 +9,8 @@ #include "navigatortreeview.h" #include "navigatorwidget.h" #include "choosefrompropertylistdialog.h" -#include "qproxystyle.h" - #include <model/modelutils.h> +#include <dialogutils.h> #include <modelnodecontextmenu.h> #include <theme.h> #include <qmldesignerconstants.h> @@ -169,8 +168,7 @@ static void setId(const QModelIndex &index, const QString &newId) return; if (!ModelNode::isValidId(newId)) { - Core::AsynchronousMessageBox::warning(NavigatorTreeView::tr("Invalid Id"), - NavigatorTreeView::tr("%1 is an invalid id.").arg(newId)); + DialogUtils::showWarningForInvalidId(newId); } else if (modelNode.view()->hasId(newId)) { Core::AsynchronousMessageBox::warning(NavigatorTreeView::tr("Invalid Id"), NavigatorTreeView::tr("%1 already exists.").arg(newId)); diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp index 45f89ae339..fdd58c77a3 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp @@ -143,7 +143,7 @@ QString DynamicPropertiesProxyModel::newPropertyName() const { DynamicPropertiesModel *propsModel = dynamicPropertiesModel(); - return QString::fromUtf8(uniquePropertyName("property", propsModel->singleSelectedNode())); + return QString::fromUtf8(uniquePropertyName("newName", propsModel->singleSelectedNode())); } void DynamicPropertiesProxyModel::createProperty(const QString &name, const QString &type) @@ -167,6 +167,10 @@ void DynamicPropertiesProxyModel::createProperty(const QString &name, const QStr QVariant value = defaultValueForType(typeName); VariantProperty variantProp = modelNode.variantProperty(name.toUtf8()); variantProp.setDynamicTypeNameAndValue(typeName, value); + } else if (type == "signal") { + SignalDeclarationProperty signalDeclarationProperty + = modelNode.signalDeclarationProperty(name.toUtf8()); + signalDeclarationProperty.setSignature("()"); } else { QString expression = defaultExpressionForType(typeName); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 7f1ab00bb9..6f3242a18e 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -284,6 +284,27 @@ void PropertyEditorQmlBackend::setupAuxiliaryProperties(const QmlObjectNode &qml } } +void PropertyEditorQmlBackend::handleInstancePropertyChangedInModelNodeProxy( + const ModelNode &modelNode, const PropertyName &propertyName) +{ + m_backendModelNode.handleInstancePropertyChanged(modelNode, propertyName); +} + +void PropertyEditorQmlBackend::handleVariantPropertyChangedInModelNodeProxy(const VariantProperty &property) +{ + m_backendModelNode.handleVariantPropertyChanged(property); +} + +void PropertyEditorQmlBackend::handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property) +{ + m_backendModelNode.handleBindingPropertyChanged(property); +} + +void PropertyEditorQmlBackend::handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property) +{ + m_backendModelNode.handlePropertiesRemoved(property); +} + void PropertyEditorQmlBackend::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value, diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index b677258488..d4e158fca4 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -3,10 +3,10 @@ #pragma once -#include "qmlanchorbindingproxy.h" #include "designerpropertymap.h" -#include "propertyeditorvalue.h" #include "propertyeditorcontextobject.h" +#include "propertyeditorvalue.h" +#include "qmlanchorbindingproxy.h" #include "qmlmodelnodeproxy.h" #include "quick2propertyeditorview.h" @@ -71,7 +71,15 @@ public: PropertyEditorView *propertyEditor); void setupInsightAttachedProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor); - void setupAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor); + void setupAuxiliaryProperties(const QmlObjectNode &qmlObjectNode, + PropertyEditorView *propertyEditor); + + void handleInstancePropertyChangedInModelNodeProxy(const ModelNode &modelNode, + const PropertyName &propertyName); + + void handleVariantPropertyChangedInModelNodeProxy(const VariantProperty &property); + void handleBindingPropertyChangedInModelNodeProxy(const BindingProperty &property); + void handlePropertiesRemovedInModelNodeProxy(const AbstractProperty &property); static NodeMetaInfo findCommonAncestor(const ModelNode &node); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 663ebafb65..70686f31ae 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -10,9 +10,12 @@ #include "designmodewidget.h" #include "nodemetainfo.h" #include "nodeproperty.h" +#include "propertyeditorview.h" +#include "qmldesignerplugin.h" #include "qmlitemnode.h" #include "qmlobjectnode.h" -#include "qmldesignerplugin.h" +#include "rewritertransaction.h" +#include "rewritingexception.h" #include <enumeration.h> @@ -153,7 +156,8 @@ void PropertyEditorValue::setExpressionWithEmit(const QString &expression) if (m_expression != expression) { setExpression(expression); m_value.clear(); - emit expressionChanged(nameAsQString()); // Note that we set the name in this case + emit expressionChanged(nameAsQString()); + emit expressionChangedQml();// Note that we set the name in this case } } @@ -180,7 +184,7 @@ bool PropertyEditorValue::isInSubState() const bool PropertyEditorValue::isBound() const { const QmlObjectNode objectNode(modelNode()); - return objectNode.isValid() && objectNode.hasBindingProperty(name()); + return m_forceBound || (objectNode.isValid() && objectNode.hasBindingProperty(name())); } bool PropertyEditorValue::isInModel() const @@ -334,6 +338,7 @@ void PropertyEditorValue::resetValue() m_expression = QString(); emit valueChanged(nameAsQString(), QVariant()); emit expressionChanged({}); + emit expressionChangedQml(); } } @@ -425,6 +430,34 @@ QStringList PropertyEditorValue::getExpressionAsList() const return generateStringList(expression()); } +QVector<double> PropertyEditorValue::getExpressionAsVector() const +{ + const QRegularExpression rx( + QRegularExpression::anchoredPattern("Qt.vector(2|3|4)d\\((.*?)\\)")); + const QRegularExpressionMatch match = rx.match(expression()); + if (!match.hasMatch()) + return {}; + + const QStringList floats = match.captured(2).split(',', Qt::SkipEmptyParts); + + bool ok; + + const int num = match.captured(1).toInt(); + + if (num != floats.count()) + return {}; + + QVector<double> ret; + for (const QString &number : floats) { + ret.append(number.toDouble(&ok)); + + if (!ok) + return {}; + } + + return ret; +} + bool PropertyEditorValue::idListAdd(const QString &value) { const QmlObjectNode objectNode(modelNode()); @@ -507,6 +540,15 @@ void PropertyEditorValue::openMaterialEditor(int idx) m_modelNode.view()->emitCustomNotification("select_material", {}, {idx}); } +void PropertyEditorValue::setForceBound(bool b) +{ + if (m_forceBound == b) + return; + m_forceBound = b; + + emit isBoundChanged(); +} + QStringList PropertyEditorValue::generateStringList(const QString &string) const { QString copy = string; @@ -687,4 +729,260 @@ void PropertyEditorNodeWrapper::update() emit typeChanged(); } +QQmlPropertyMap *PropertyEditorSubSelectionWrapper::properties() +{ + return &m_valuesPropertyMap; +} + +static QObject *variantToQObject(const QVariant &value) +{ + if (value.typeId() == QMetaType::QObjectStar || value.typeId() > QMetaType::User) + return *(QObject **)value.constData(); + + return nullptr; +} + +void PropertyEditorSubSelectionWrapper::createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, + const PropertyName &name, + const QVariant &value) +{ + PropertyName propertyName(name); + propertyName.replace('.', '_'); + auto valueObject = qobject_cast<PropertyEditorValue*>(variantToQObject(m_valuesPropertyMap.value(QString::fromUtf8(propertyName)))); + if (!valueObject) { + valueObject = new PropertyEditorValue(&m_valuesPropertyMap); + QObject::connect(valueObject, &PropertyEditorValue::valueChanged, this, &PropertyEditorSubSelectionWrapper::changeValue); + QObject::connect(valueObject, &PropertyEditorValue::expressionChanged, this, &PropertyEditorSubSelectionWrapper::changeExpression); + QObject::connect(valueObject, &PropertyEditorValue::exportPropertyAsAliasRequested, this, &PropertyEditorSubSelectionWrapper::exportPropertyAsAlias); + QObject::connect(valueObject, &PropertyEditorValue::removeAliasExportRequested, this, &PropertyEditorSubSelectionWrapper::removeAliasExport); + m_valuesPropertyMap.insert(QString::fromUtf8(propertyName), QVariant::fromValue(valueObject)); + } + valueObject->setName(name); + valueObject->setModelNode(qmlObjectNode); + + if (qmlObjectNode.propertyAffectedByCurrentState(name) && !(qmlObjectNode.modelNode().property(name).isBindingProperty())) + valueObject->setValue(qmlObjectNode.modelValue(name)); + + else + valueObject->setValue(value); + + if (propertyName != "id" && + qmlObjectNode.currentState().isBaseState() && + qmlObjectNode.modelNode().property(propertyName).isBindingProperty()) { + valueObject->setExpression(qmlObjectNode.modelNode().bindingProperty(propertyName).expression()); + } else { + if (qmlObjectNode.hasBindingProperty(name)) + valueObject->setExpression(qmlObjectNode.expression(name)); + else + valueObject->setExpression(qmlObjectNode.instanceValue(name).toString()); + } +} + +void PropertyEditorSubSelectionWrapper::exportPropertyAsAlias(const QString &name) +{ + if (name.isNull()) + return; + + if (locked()) + return; + + QTC_ASSERT(m_modelNode.isValid(), return); + + view()->executeInTransaction("PropertyEditorView::exportPropertyAsAlias", [this, name](){ + PropertyEditorView::generateAliasForProperty(m_modelNode, name); + }); +} + +void PropertyEditorSubSelectionWrapper::removeAliasExport(const QString &name) +{ + if (name.isNull()) + return; + + if (locked()) + return; + + QTC_ASSERT(m_modelNode.isValid(), return ); + + view()->executeInTransaction("PropertyEditorView::exportPropertyAsAlias", [this, name]() { + PropertyEditorView::removeAliasForProperty(m_modelNode, name); + }); +} + +bool PropertyEditorSubSelectionWrapper::locked() const +{ + return m_locked; +} + +PropertyEditorSubSelectionWrapper::PropertyEditorSubSelectionWrapper(const ModelNode &modelNode) + : m_modelNode(modelNode) +{ + QmlObjectNode qmlObjectNode(modelNode); + + QTC_ASSERT(qmlObjectNode.isValid(), return ); + + for (const auto &property : qmlObjectNode.modelNode().metaInfo().properties()) { + auto propertyName = property.name(); + createPropertyEditorValue(qmlObjectNode, + propertyName, + qmlObjectNode.instanceValue(propertyName)); + } +} + +ModelNode PropertyEditorSubSelectionWrapper::modelNode() const +{ + return m_modelNode; +} + +void PropertyEditorSubSelectionWrapper::deleteModelNode() +{ + QmlObjectNode objectNode(m_modelNode); + + view()->executeInTransaction("PropertyEditorView::changeExpression", [&] { + if (objectNode.isValid()) + objectNode.destroy(); + }); +} + +void PropertyEditorSubSelectionWrapper::changeValue(const QString &name) +{ + QTC_ASSERT(m_modelNode.isValid(), return ); + + if (name.isNull()) + return; + + if (locked()) + return; + + const QScopeGuard cleanup([&] { m_locked = false; }); + m_locked = true; + + const NodeMetaInfo metaInfo = m_modelNode.metaInfo(); + QVariant castedValue; + PropertyEditorValue *value = qobject_cast<PropertyEditorValue *>( + variantToQObject(m_valuesPropertyMap.value(name))); + + if (auto property = metaInfo.property(name.toUtf8())) { + castedValue = property.castedValue(value->value()); + + if (castedValue.typeId() == QVariant::Color) { + QColor color = castedValue.value<QColor>(); + QColor newColor = QColor(color.name()); + newColor.setAlpha(color.alpha()); + castedValue = QVariant(newColor); + } + + if (!value->value().isValid()) { // reset + removePropertyFromModel(name.toUtf8()); + } else { + if (castedValue.isValid()) + commitVariantValueToModel(name.toUtf8(), castedValue); + } + } +} + +void PropertyEditorSubSelectionWrapper::setValueFromModel(const PropertyName &name, + const QVariant &value) +{ + m_locked = true; + + QmlObjectNode qmlObjectNode(m_modelNode); + + PropertyName propertyName = name; + propertyName.replace('.', '_'); + auto propertyValue = qobject_cast<PropertyEditorValue *>( + variantToQObject(m_valuesPropertyMap.value(QString::fromUtf8(propertyName)))); + if (propertyValue) + propertyValue->setValue(value); + m_locked = false; +} + +void PropertyEditorSubSelectionWrapper::resetValue(const PropertyName &name) +{ + auto propertyValue = qobject_cast<PropertyEditorValue *>( + variantToQObject(m_valuesPropertyMap.value(QString::fromUtf8(name)))); + if (propertyValue) + propertyValue->resetValue(); +} + +bool PropertyEditorSubSelectionWrapper::isRelevantModelNode(const ModelNode &modelNode) const +{ + QmlObjectNode objectNode(m_modelNode); + return modelNode == m_modelNode || objectNode.propertyChangeForCurrentState() == modelNode; +} + +void PropertyEditorSubSelectionWrapper::changeExpression(const QString &propertyName) +{ + PropertyName name = propertyName.toUtf8(); + + QTC_ASSERT(m_modelNode.isValid(), return ); + + if (name.isNull()) + return; + + if (locked()) + return; + + const QScopeGuard cleanup([&] { m_locked = false; }); + m_locked = true; + + view()->executeInTransaction("PropertyEditorView::changeExpression", [this, name, propertyName] { + QmlObjectNode qmlObjectNode{m_modelNode}; + PropertyEditorValue *value = qobject_cast<PropertyEditorValue *>( + variantToQObject(m_valuesPropertyMap.value(propertyName))); + + if (!value) { + qWarning() << "PropertyEditor::changeExpression no value for " << propertyName; + return; + } + + if (value->expression().isEmpty()) { + value->resetValue(); + return; + } + PropertyEditorView::setExpressionOnObjectNode(qmlObjectNode, name, value->expression()); + }); /* end of transaction */ +} + +void PropertyEditorSubSelectionWrapper::removePropertyFromModel(const PropertyName &propertyName) +{ + QTC_ASSERT(m_modelNode.isValid(), return ); + + m_locked = true; + try { + RewriterTransaction transaction = view()->beginRewriterTransaction( + "PropertyEditorView::removePropertyFromModel"); + + QmlObjectNode(m_modelNode).removeProperty(propertyName); + + transaction.commit(); + } catch (const RewritingException &e) { + e.showException(); + } + m_locked = false; +} + +void PropertyEditorSubSelectionWrapper::commitVariantValueToModel(const PropertyName &propertyName, + const QVariant &value) +{ + QTC_ASSERT(m_modelNode.isValid(), return ); + + try { + RewriterTransaction transaction = view()->beginRewriterTransaction( + "PropertyEditorView::commitVariantValueToMode"); + + QmlObjectNode(m_modelNode).setVariantProperty(propertyName, value); + + transaction.commit(); + } catch (const RewritingException &e) { + e.showException(); + } +} + +AbstractView *PropertyEditorSubSelectionWrapper::view() const +{ + QTC_CHECK(m_modelNode.isValid()); + + return m_modelNode.view(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h index 59236c4fe2..70a51fffc2 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.h @@ -14,6 +14,43 @@ namespace QmlDesigner { class PropertyEditorValue; +class PropertyEditorSubSelectionWrapper : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QQmlPropertyMap *properties READ properties NOTIFY propertiesChanged) + +signals: + void propertiesChanged(); + +public: + QQmlPropertyMap *properties(); + PropertyEditorSubSelectionWrapper(const ModelNode &modelNode); + ModelNode modelNode() const; + + Q_INVOKABLE void deleteModelNode(); + + void setValueFromModel(const PropertyName &name, const QVariant &value); + void resetValue(const PropertyName &name); + + bool isRelevantModelNode(const ModelNode &modelNode) const; + +private: + void changeValue(const QString &name); + void changeExpression(const QString &propertyName); + void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value); + void exportPropertyAsAlias(const QString &name); + void removeAliasExport(const QString &name); + bool locked() const; + + ModelNode m_modelNode; + QQmlPropertyMap m_valuesPropertyMap; + bool m_locked = false; + void removePropertyFromModel(const PropertyName &propertyName); + void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value); + AbstractView *view() const; +}; + class PropertyEditorNodeWrapper : public QObject { Q_OBJECT @@ -126,12 +163,15 @@ public: bool isIdList() const; Q_INVOKABLE QStringList getExpressionAsList() const; + Q_INVOKABLE QVector<double> getExpressionAsVector() const; Q_INVOKABLE bool idListAdd(const QString &value); Q_INVOKABLE bool idListRemove(int idx); Q_INVOKABLE bool idListReplace(int idx, const QString &value); Q_INVOKABLE void commitDrop(const QString &dropData); Q_INVOKABLE void openMaterialEditor(int idx); + Q_INVOKABLE void setForceBound(bool b); + public slots: void resetValue(); void setEnumeration(const QString &scope, const QString &name); @@ -143,6 +183,8 @@ signals: void expressionChanged(const QString &name); // HACK - We use the same notifer for the backend and frontend. // If name is empty the signal is used for QML. + void expressionChangedQml(); + void exportPropertyAsAliasRequested(const QString &name); void removeAliasExportRequested(const QString &name); @@ -168,6 +210,7 @@ private: bool m_hasActiveDrag = false; bool m_isValid = false; // if the property value belongs to a non-existing complexProperty it is invalid PropertyEditorNodeWrapper *m_complexNode; + bool m_forceBound = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 1ff098f4ea..b22b39e238 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -256,66 +256,19 @@ void PropertyEditorView::changeExpression(const QString &propertyName) underscoreName.replace('.', '_'); QmlObjectNode qmlObjectNode{m_selectedNode}; - PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName(QString::fromLatin1(underscoreName)); + PropertyEditorValue *value = m_qmlBackEndForCurrentType->propertyValueForName( + QString::fromUtf8(underscoreName)); if (!value) { qWarning() << "PropertyEditor::changeExpression no value for " << underscoreName; return; } - if (auto property = qmlObjectNode.modelNode().metaInfo().property(name)) { - const auto &propertType = property.propertyType(); - if (propertType.isColor()) { - if (QColor(value->expression().remove('"')).isValid()) { - qmlObjectNode.setVariantProperty(name, QColor(value->expression().remove('"'))); - return; - } - } else if (propertType.isBool()) { - if (isTrueFalseLiteral(value->expression())) { - if (value->expression().compare("true", Qt::CaseInsensitive) == 0) - qmlObjectNode.setVariantProperty(name, true); - else - qmlObjectNode.setVariantProperty(name, false); - return; - } - } else if (propertType.isInteger()) { - bool ok; - int intValue = value->expression().toInt(&ok); - if (ok) { - qmlObjectNode.setVariantProperty(name, intValue); - return; - } - } else if (propertType.isFloat()) { - bool ok; - qreal realValue = value->expression().toDouble(&ok); - if (ok) { - qmlObjectNode.setVariantProperty(name, realValue); - return; - } - } else if (propertType.isVariant()) { - bool ok; - qreal realValue = value->expression().toDouble(&ok); - if (ok) { - qmlObjectNode.setVariantProperty(name, realValue); - return; - } else if (isTrueFalseLiteral(value->expression())) { - if (value->expression().compare("true", Qt::CaseInsensitive) == 0) - qmlObjectNode.setVariantProperty(name, true); - else - qmlObjectNode.setVariantProperty(name, false); - return; - } - } - } - if (value->expression().isEmpty()) { value->resetValue(); return; } - - if (qmlObjectNode.expression(name) != value->expression() - || !qmlObjectNode.propertyAffectedByCurrentState(name)) - qmlObjectNode.setBindingProperty(name, value->expression()); + setExpressionOnObjectNode(qmlObjectNode, name, value->expression()); }); /* end of transaction */ } @@ -330,21 +283,8 @@ void PropertyEditorView::exportPropertyAsAlias(const QString &name) if (noValidSelection()) return; - executeInTransaction("PropertyEditorView::exportPropertyAsAlias", [this, name](){ - const QString id = m_selectedNode.validId(); - QString upperCasePropertyName = name; - upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper()); - QString aliasName = id + upperCasePropertyName; - aliasName.replace(".", ""); //remove all dots - - PropertyName propertyName = aliasName.toUtf8(); - if (rootModelNode().hasProperty(propertyName)) { - Core::AsynchronousMessageBox::warning(tr("Cannot Export Property as Alias"), - tr("Property %1 does already exist for root component.").arg(aliasName)); - return; - } - rootModelNode().bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name); - }); + executeInTransaction("PropertyEditorView::exportPropertyAsAlias", + [this, name]() { generateAliasForProperty(m_selectedNode, name); }); } void PropertyEditorView::removeAliasExport(const QString &name) @@ -358,15 +298,8 @@ void PropertyEditorView::removeAliasExport(const QString &name) if (noValidSelection()) return; - executeInTransaction("PropertyEditorView::exportPropertyAsAlias", [this, name](){ - const QString id = m_selectedNode.validId(); - - for (const BindingProperty &property : rootModelNode().bindingProperties()) - if (property.expression() == (id + "." + name)) { - rootModelNode().removeProperty(property.name()); - break; - } - }); + executeInTransaction("PropertyEditorView::exportPropertyAsAlias", + [this, name]() { removeAliasForProperty(m_selectedNode, name); }); } bool PropertyEditorView::locked() const @@ -384,11 +317,113 @@ void PropertyEditorView::refreshMetaInfos(const TypeIds &deletedTypeIds) m_propertyComponentGenerator.refreshMetaInfos(deletedTypeIds); } +void PropertyEditorView::setExpressionOnObjectNode(const QmlObjectNode &constObjectNode, + const PropertyName &name, + const QString &newExpression) +{ + auto qmlObjectNode = constObjectNode; + auto expression = newExpression; + if (auto property = qmlObjectNode.modelNode().metaInfo().property(name)) { + const auto &propertType = property.propertyType(); + if (propertType.isColor()) { + if (QColor(expression.remove('"')).isValid()) { + qmlObjectNode.setVariantProperty(name, QColor(expression.remove('"'))); + return; + } + } else if (propertType.isBool()) { + if (isTrueFalseLiteral(expression)) { + if (expression.compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } else if (propertType.isInteger()) { + bool ok; + int intValue = expression.toInt(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, intValue); + return; + } + } else if (propertType.isFloat()) { + bool ok; + qreal realValue = expression.toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } + } else if (propertType.isVariant()) { + bool ok; + qreal realValue = expression.toDouble(&ok); + if (ok) { + qmlObjectNode.setVariantProperty(name, realValue); + return; + } else if (isTrueFalseLiteral(expression)) { + if (expression.compare("true", Qt::CaseInsensitive) == 0) + qmlObjectNode.setVariantProperty(name, true); + else + qmlObjectNode.setVariantProperty(name, false); + return; + } + } + } + + if (qmlObjectNode.expression(name) != expression + || !qmlObjectNode.propertyAffectedByCurrentState(name)) + qmlObjectNode.setBindingProperty(name, expression); +} + +void PropertyEditorView::generateAliasForProperty(const ModelNode &modelNode, const QString &name) +{ + QTC_ASSERT(modelNode.isValid(), return ); + + auto view = modelNode.view(); + + auto rootNode = view->rootModelNode(); + + auto nonConstModelNode = modelNode; + const QString id = nonConstModelNode.validId(); + + QString upperCasePropertyName = name; + upperCasePropertyName.replace(0, 1, upperCasePropertyName.at(0).toUpper()); + QString aliasName = id + upperCasePropertyName; + aliasName.replace(".", ""); //remove all dots + + PropertyName propertyName = aliasName.toUtf8(); + if (rootNode.hasProperty(propertyName)) { + Core::AsynchronousMessageBox::warning( + tr("Cannot Export Property as Alias"), + tr("Property %1 does already exist for root component.").arg(aliasName)); + return; + } + rootNode.bindingProperty(propertyName).setDynamicTypeNameAndExpression("alias", id + "." + name); +} + +void PropertyEditorView::removeAliasForProperty(const ModelNode &modelNode, const QString &propertyName) +{ + QTC_ASSERT(modelNode.isValid(), return ); + + auto view = modelNode.view(); + + auto rootNode = view->rootModelNode(); + + auto nonConstModelNode = modelNode; + + const QString id = nonConstModelNode.validId(); + + for (const BindingProperty &property : rootNode.bindingProperties()) { + if (property.expression() == (id + "." + propertyName)) { + rootNode.removeProperty(property.name()); + break; + } + } +} + void PropertyEditorView::updateSize() { if (!m_qmlBackEndForCurrentType) return; - auto frame = m_qmlBackEndForCurrentType->widget()->findChild<QWidget*>("propertyEditorFrame"); + auto frame = m_qmlBackEndForCurrentType->widget()->findChild<QWidget *>("propertyEditorFrame"); if (frame) frame->resize(m_stackedWidget->size()); } @@ -747,7 +782,11 @@ void PropertyEditorView::propertiesRemoved(const QList<AbstractProperty> &proper if (noValidSelection()) return; + QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + for (const AbstractProperty &property : propertyList) { + m_qmlBackEndForCurrentType->handlePropertiesRemovedInModelNodeProxy(property); + ModelNode node(property.parentModelNode()); if (node.isRootNode() && !m_selectedNode.isRootNode()) @@ -805,7 +844,11 @@ void PropertyEditorView::variantPropertiesChanged(const QList<VariantProperty>& if (noValidSelection()) return; + QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + for (const VariantProperty &property : propertyList) { + m_qmlBackEndForCurrentType->handleVariantPropertyChangedInModelNodeProxy(property); + ModelNode node(property.parentModelNode()); if (propertyIsAttachedLayoutProperty(property.name())) @@ -830,7 +873,11 @@ void PropertyEditorView::bindingPropertiesChanged(const QList<BindingProperty> & if (locked() || noValidSelection()) return; + QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + for (const BindingProperty &property : propertyList) { + m_qmlBackEndForCurrentType->handleBindingPropertyChangedInModelNodeProxy(property); + ModelNode node(property.parentModelNode()); if (property.isAliasExport()) @@ -952,6 +999,9 @@ void PropertyEditorView::instancePropertyChanged(const QList<QPair<ModelNode, Pr { if (!m_selectedNode.isValid()) return; + + QTC_ASSERT(m_qmlBackEndForCurrentType, return ); + m_locked = true; using ModelNodePropertyPair = QPair<ModelNode, PropertyName>; @@ -960,7 +1010,11 @@ void PropertyEditorView::instancePropertyChanged(const QList<QPair<ModelNode, Pr const QmlObjectNode qmlObjectNode(modelNode); const PropertyName propertyName = propertyPair.second; - if (qmlObjectNode.isValid() && m_qmlBackEndForCurrentType && modelNode == m_selectedNode && qmlObjectNode.currentState().isValid()) { + m_qmlBackEndForCurrentType->handleInstancePropertyChangedInModelNodeProxy(modelNode, + propertyName); + + if (qmlObjectNode.isValid() && m_qmlBackEndForCurrentType && modelNode == m_selectedNode + && qmlObjectNode.currentState().isValid()) { const AbstractProperty property = modelNode.property(propertyName); if (modelNode == m_selectedNode || qmlObjectNode.propertyChangeForCurrentState() == qmlObjectNode) { if ( !modelNode.hasProperty(propertyName) || modelNode.property(property.name()).isBindingProperty() ) @@ -969,7 +1023,6 @@ void PropertyEditorView::instancePropertyChanged(const QList<QPair<ModelNode, Pr setValue(modelNode, property.name(), qmlObjectNode.modelValue(property.name())); } } - } m_locked = false; diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index cc9b522839..ef2b71f558 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -84,6 +84,16 @@ public: void refreshMetaInfos(const TypeIds &deletedTypeIds) override; + static void setExpressionOnObjectNode(const QmlObjectNode &objectNode, + const PropertyName &name, + const QString &expression); + + static void generateAliasForProperty(const ModelNode &modelNode, + const QString &propertyName); + + static void removeAliasForProperty(const ModelNode &modelNode, + const QString &propertyName); + protected: void timerEvent(QTimerEvent *event) override; void setupPane(const TypeName &typeName); diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp index 96ec5f92e7..fc39b37137 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp @@ -4,6 +4,15 @@ #include "abstractview.h" #include "qmlmodelnodeproxy.h" +#include <nodemetainfo.h> + +#include <nodeabstractproperty.h> +#include <nodelistproperty.h> +#include <variantproperty.h> + +#include <utils/qtcassert.h> +#include <utils/algorithm.h> + #include <QtQml> namespace QmlDesigner { @@ -17,6 +26,8 @@ void QmlModelNodeProxy::setup(const QmlObjectNode &objectNode) { m_qmlObjectNode = objectNode; + m_subselection.clear(); + emit modelNodeChanged(); } @@ -75,4 +86,227 @@ QString QmlModelNodeProxy::simplifiedTypeName() const return m_qmlObjectNode.simplifiedTypeName(); } +static QList<int> toInternalIdList(const QList<ModelNode> &nodes) +{ + return Utils::transform(nodes, [](const ModelNode &node) { return node.internalId(); }); +} + +QList<int> QmlModelNodeProxy::allChildren(int internalId) const +{ + ModelNode modelNode = m_qmlObjectNode.modelNode(); + + QTC_ASSERT(modelNode.isValid(), return {}); + + if (internalId >= 0) + modelNode = modelNode.view()->modelNodeForInternalId(internalId); + + return allChildren(modelNode); +} + +QList<int> QmlModelNodeProxy::allChildrenOfType(const QString &typeName, int internalId) const +{ + ModelNode modelNode = m_qmlObjectNode.modelNode(); + + QTC_ASSERT(modelNode.isValid(), return {}); + + if (internalId >= 0) + modelNode = modelNode.view()->modelNodeForInternalId(internalId); + + return allChildrenOfType(modelNode, typeName); +} + +QString QmlModelNodeProxy::simplifiedTypeName(int internalId) const +{ + ModelNode modelNode = m_qmlObjectNode.modelNode(); + + QTC_ASSERT(modelNode.isValid(), return {}); + + return modelNode.view()->modelNodeForInternalId(internalId).simplifiedTypeName(); +} + +PropertyEditorSubSelectionWrapper *QmlModelNodeProxy::findWrapper(int internalId) const +{ + for (const auto &item : qAsConst(m_subselection)) { + if (item->modelNode().internalId() == internalId) + return item.data(); + } + + return nullptr; +} + +PropertyEditorSubSelectionWrapper *QmlModelNodeProxy::registerSubSelectionWrapper(int internalId) +{ + auto result = findWrapper(internalId); + + if (result) + return result; + + QTC_ASSERT(m_qmlObjectNode.isValid(), return nullptr); + + ModelNode node = m_qmlObjectNode.view()->modelNodeForInternalId(internalId); + + QTC_ASSERT(node.isValid(), return nullptr); + + QSharedPointer<PropertyEditorSubSelectionWrapper> wrapper( + new PropertyEditorSubSelectionWrapper(node)); + m_subselection.append(wrapper); + + QJSEngine::setObjectOwnership(wrapper.data(), QJSEngine::CppOwnership); + + return wrapper.data(); +} + +void QmlModelNodeProxy::createModelNode(int internalIdParent, + const QString &property, + const QString &typeName, + const QString &requiredImport) +{ + QTC_ASSERT(m_qmlObjectNode.isValid(), return ); + + auto modelNode = m_qmlObjectNode.modelNode(); + + AbstractView *view = modelNode.view(); + + auto parentModelNode = m_qmlObjectNode.modelNode(); + if (internalIdParent >= 0) + parentModelNode = view->modelNodeForInternalId(internalIdParent); + + QTC_ASSERT(parentModelNode.isValid(), return ); + + Import import; + Q_ASSERT(import.isEmpty()); + + if (!requiredImport.isEmpty() && !view->model()->hasImport(requiredImport)) + import = Import::createLibraryImport(requiredImport); + + view->executeInTransaction("QmlModelNodeProxy::createModelNode", [&] { + if (!import.isEmpty()) + view->model()->changeImports({import}, {}); + +#ifdef QDS_USE_PROJECTSTORAGE + ModelNode newNode = view->createModelNode(typeName.toUtf8()); +#else + NodeMetaInfo metaInfo = modelNode.model()->metaInfo(typeName.toUtf8()); + ModelNode newNode = view->createModelNode(metaInfo.typeName(), + metaInfo.majorVersion(), + metaInfo.minorVersion()); +#endif + parentModelNode.nodeAbstractProperty(property.toUtf8()).reparentHere(newNode); + }); +} + +void QmlModelNodeProxy::moveNode(int internalIdParent, + const QString &propertyName, + int fromIndex, + int toIndex) +{ + QTC_ASSERT(m_qmlObjectNode.isValid(), return ); + + ModelNode node = m_qmlObjectNode.view()->modelNodeForInternalId(internalIdParent); + + QTC_ASSERT(node.isValid(), return ); + AbstractView *view = m_qmlObjectNode.view(); + view->executeInTransaction("QmlModelNodeProxy::swapNode", [&] { + node.nodeListProperty(propertyName.toUtf8()).slide(fromIndex, toIndex); + }); +} + +bool QmlModelNodeProxy::isInstanceOf(const QString &typeName, int internalId) const +{ + ModelNode modelNode = m_qmlObjectNode.modelNode(); + + QTC_ASSERT(modelNode.isValid(), return {}); + + if (internalId >= 0) + modelNode = modelNode.view()->modelNodeForInternalId(internalId); + + NodeMetaInfo metaInfo = modelNode.model()->metaInfo(typeName.toUtf8()); + + return modelNode.metaInfo().isBasedOn(metaInfo); +} + +void QmlModelNodeProxy::changeType(int internalId, const QString &typeName) +{ + QTC_ASSERT(m_qmlObjectNode.isValid(), return ); + + ModelNode node = m_qmlObjectNode.view()->modelNodeForInternalId(internalId); + + QTC_ASSERT(node.isValid(), return ); + + QTC_ASSERT(!node.isRootNode(), return ); +#ifdef QDS_USE_PROJECTSTORAGE + node.changeType(typeName.toUtf8(), -1, -1); +#else + NodeMetaInfo metaInfo = node.model()->metaInfo(typeName.toUtf8()); + node.changeType(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion()); +#endif +} + +void QmlModelNodeProxy::handleInstancePropertyChanged(const ModelNode &modelNode, + const PropertyName &propertyName) +{ + const QmlObjectNode qmlObjectNode(modelNode); + + for (const auto &item : qAsConst(m_subselection)) { + if (item && item->isRelevantModelNode(modelNode)) { + if (!modelNode.hasProperty(propertyName) + || modelNode.property(propertyName).isBindingProperty()) { + item->setValueFromModel(propertyName, qmlObjectNode.instanceValue(propertyName)); + } else { + item->setValueFromModel(propertyName, qmlObjectNode.modelValue(propertyName)); + } + } + } +} + +void QmlModelNodeProxy::handleBindingPropertyChanged(const BindingProperty &property) +{ + for (const auto &item : qAsConst(m_subselection)) { + if (item && item->isRelevantModelNode(property.parentModelNode())) { + QmlObjectNode objectNode(item->modelNode()); + if (objectNode.modelNode().property(property.name()).isBindingProperty()) + item->setValueFromModel(property.name(), objectNode.instanceValue(property.name())); + else + item->setValueFromModel(property.name(), objectNode.modelValue(property.name())); + } + } +} + +void QmlModelNodeProxy::handleVariantPropertyChanged(const VariantProperty &property) +{ + for (const auto &item : qAsConst(m_subselection)) { + if (item && item->isRelevantModelNode(property.parentModelNode())) { + QmlObjectNode objectNode(item->modelNode()); + if (objectNode.modelNode().property(property.name()).isBindingProperty()) + item->setValueFromModel(property.name(), objectNode.instanceValue(property.name())); + else + item->setValueFromModel(property.name(), objectNode.modelValue(property.name())); + } + } +} + +void QmlModelNodeProxy::handlePropertiesRemoved(const AbstractProperty &property) +{ + for (const auto &item : qAsConst(m_subselection)) { + if (item && item->isRelevantModelNode(property.parentModelNode())) { + QmlObjectNode objectNode(item->modelNode()); + item->resetValue(property.name()); + item->setValueFromModel(property.name(), objectNode.instanceValue(property.name())); + } + } +} + +QList<int> QmlModelNodeProxy::allChildren(const ModelNode &modelNode) const +{ + return toInternalIdList(modelNode.directSubModelNodes()); +} + +QList<int> QmlModelNodeProxy::allChildrenOfType(const ModelNode &modelNode, const QString &typeName) const +{ + QTC_ASSERT(modelNode.isValid(), return {}); + + NodeMetaInfo metaInfo = modelNode.model()->metaInfo(typeName.toUtf8()); + + return toInternalIdList(modelNode.directSubModelNodesOfType(metaInfo)); } +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h index 4740b01fbd..d8a49d7e10 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h @@ -3,6 +3,8 @@ #pragma once +#include "propertyeditorvalue.h" + #include <qmlitemnode.h> #include <QObject> @@ -16,6 +18,7 @@ class QMLDESIGNERCORE_EXPORT QmlModelNodeProxy : public QObject Q_PROPERTY(QmlDesigner::ModelNode modelNode READ modelNode NOTIFY modelNodeChanged) Q_PROPERTY(bool multiSelection READ multiSelection NOTIFY modelNodeChanged) + public: explicit QmlModelNodeProxy(QObject *parent = nullptr); @@ -36,13 +39,45 @@ public: QString simplifiedTypeName() const; + Q_INVOKABLE QList<int> allChildren(int internalId = -1) const; + Q_INVOKABLE QList<int> allChildrenOfType(const QString &typeName, int internalId = -1) const; + + Q_INVOKABLE QString simplifiedTypeName(int internalId) const; + + Q_INVOKABLE PropertyEditorSubSelectionWrapper *registerSubSelectionWrapper(int internalId); + + Q_INVOKABLE void createModelNode(int internalIdParent, + const QString &property, + const QString &typeName, + const QString &requiredImport = {}); + + Q_INVOKABLE void moveNode(int internalIdParent, + const QString &propertyName, + int fromIndex, + int toIndex); + + Q_INVOKABLE bool isInstanceOf(const QString &typeName, int internalId = -1) const; + + Q_INVOKABLE void changeType(int internalId, const QString &typeName); + + void handleInstancePropertyChanged(const ModelNode &modelNode, const PropertyName &propertyName); + + void handleBindingPropertyChanged(const BindingProperty &property); + void handleVariantPropertyChanged(const VariantProperty &property); + void handlePropertiesRemoved(const AbstractProperty &property); + signals: void modelNodeChanged(); void selectionToBeChanged(); void selectionChanged(); private: + QList<int> allChildren(const ModelNode &modelNode) const; + QList<int> allChildrenOfType(const ModelNode &modelNode, const QString &typeName) const; + PropertyEditorSubSelectionWrapper *findWrapper(int internalId) const; + QmlObjectNode m_qmlObjectNode; + QList<QSharedPointer<PropertyEditorSubSelectionWrapper>> m_subselection; }; } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp index 2d8998404a..f289583cc3 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/setframevaluedialog.cpp @@ -40,7 +40,7 @@ SetFrameValueDialog::SetFrameValueDialog(qreal frame, const QVariant &value, valueLabel->setAlignment(Qt::AlignRight); valueLabel->setFixedWidth(labelWidth); - m_frameControl->setRange(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()); + m_frameControl->setRange(std::numeric_limits<int>::lowest(), std::numeric_limits<int>::max()); m_frameControl->setValue(static_cast<int>(frame)); m_frameControl->setAlignment(Qt::AlignRight); @@ -86,7 +86,6 @@ QWidget* SetFrameValueDialog::createValueControl(const QVariant& value) switch (value.metaType().id()) { - case QMetaType::QColor: { auto* widget = new ColorControl(value.value<QColor>()); m_valueGetter = [widget]() { return widget->value(); }; @@ -102,7 +101,7 @@ QWidget* SetFrameValueDialog::createValueControl(const QVariant& value) case QMetaType::Int: { auto* widget = new QSpinBox; - widget->setRange(std::numeric_limits<int>::min(), std::numeric_limits<int>::max()); + widget->setRange(std::numeric_limits<int>::lowest(), std::numeric_limits<int>::max()); widget->setAlignment(Qt::AlignRight); widget->setValue(value.toInt()); m_valueGetter = [widget]() { return widget->value(); }; @@ -120,7 +119,7 @@ QWidget* SetFrameValueDialog::createValueControl(const QVariant& value) case QMetaType::Float: { auto* widget = new QDoubleSpinBox; - widget->setRange(std::numeric_limits<float>::min(), std::numeric_limits<float>::max()); + widget->setRange(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max()); widget->setAlignment(Qt::AlignRight); widget->setValue(value.toFloat()); m_valueGetter = [widget]() { return static_cast<float>(widget->value()); }; @@ -132,7 +131,7 @@ QWidget* SetFrameValueDialog::createValueControl(const QVariant& value) default: { auto* widget = new QDoubleSpinBox; - widget->setRange(std::numeric_limits<double>::min(), std::numeric_limits<double>::max()); + widget->setRange(std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max()); widget->setAlignment(Qt::AlignRight); widget->setValue(value.toDouble()); m_valueGetter = [widget]() { return widget->value(); }; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp index 83551378ee..e65c05c39f 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineanimationform.cpp @@ -13,6 +13,7 @@ #include <variantproperty.h> #include <qmlitemnode.h> #include <qmlobjectnode.h> +#include <dialogutils.h> #include <coreplugin/messagebox.h> @@ -141,8 +142,7 @@ TimelineAnimationForm::TimelineAnimationForm(QWidget *parent) bool error = false; if (!ModelNode::isValidId(newId)) { - Core::AsynchronousMessageBox::warning(tr("Invalid Id"), - tr("%1 is an invalid id.").arg(newId)); + DialogUtils::showWarningForInvalidId(newId); error = true; } else if (animation().view()->hasId(newId)) { Core::AsynchronousMessageBox::warning(tr("Invalid Id"), diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp index b3d408dc0d..08915b6577 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineform.cpp @@ -10,6 +10,7 @@ #include <nodemetainfo.h> #include <rewritertransaction.h> #include <variantproperty.h> +#include <dialogutils.h> #include <coreplugin/messagebox.h> @@ -125,8 +126,7 @@ TimelineForm::TimelineForm(QWidget *parent) bool error = false; if (!ModelNode::isValidId(newId)) { - Core::AsynchronousMessageBox::warning(tr("Invalid Id"), - tr("%1 is an invalid id.").arg(newId)); + DialogUtils::showWarningForInvalidId(newId); error = true; } else if (m_timeline.view()->hasId(newId)) { Core::AsynchronousMessageBox::warning(tr("Invalid Id"), diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp index 7905df68e9..8288e69316 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp @@ -644,11 +644,12 @@ void TimelineView::registerActions() TimelineWidget *TimelineView::createWidget() { - if (!m_timelineWidget) + if (!m_timelineWidget) { m_timelineWidget = new TimelineWidget(this); - auto *timelineContext = new TimelineContext(m_timelineWidget); - Core::ICore::addContextObject(timelineContext); + auto *timelineContext = new TimelineContext(m_timelineWidget); + Core::ICore::addContextObject(timelineContext); + } return m_timelineWidget; } diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp index e9df928c96..cb84183f92 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.cpp @@ -21,6 +21,9 @@ #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/icore.h> #include <coreplugin/modemanager.h> + +#include <texteditor/textdocument.h> + #include <projectexplorer/kitmanager.h> #include <projectexplorer/project.h> #include <projectexplorer/projectexplorer.h> @@ -302,6 +305,20 @@ ToolBarBackend::ToolBarBackend(QObject *parent) this, &ToolBarBackend::documentIndexChanged); + connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, this, [this]() { + static QMetaObject::Connection *lastConnection = nullptr; + delete lastConnection; + + if (auto textDocument = qobject_cast<TextEditor::TextDocument *>( + Core::EditorManager::currentDocument())) { + connect(textDocument->document(), + &QTextDocument::modificationChanged, + this, + &ToolBarBackend::isDocumentDirtyChanged); + emit isDocumentDirtyChanged(); + } + }); + connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged, this, @@ -740,6 +757,12 @@ bool ToolBarBackend::isSharingEnabled() return QmlDesigner::checkEnterpriseLicense(); } +bool ToolBarBackend::isDocumentDirty() const +{ + return Core::EditorManager::currentDocument() + && Core::EditorManager::currentDocument()->isModified(); +} + void ToolBarBackend::launchGlobalAnnotations() { QmlDesignerPlugin::emitUsageStatistics(Constants::EVENT_TOOLBAR_EDIT_GLOBAL_ANNOTATION); diff --git a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h index 5d0b0e712a..02bdae1717 100644 --- a/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h +++ b/src/plugins/qmldesigner/components/toolbar/toolbarbackend.h @@ -97,6 +97,7 @@ class ToolBarBackend : public QObject Q_PROPERTY(bool isMCUs READ isMCUs NOTIFY isMCUsChanged) Q_PROPERTY(bool projectOpened READ projectOpened NOTIFY projectOpenedChanged) Q_PROPERTY(bool isSharingEnabled READ isSharingEnabled NOTIFY isSharingEnabledChanged) + Q_PROPERTY(bool isDocumentDirty READ isDocumentDirty NOTIFY isDocumentDirtyChanged) public: ToolBarBackend(QObject *parent = nullptr); @@ -147,6 +148,8 @@ public: bool isSharingEnabled(); + bool isDocumentDirty() const; + static void launchGlobalAnnotations(); signals: @@ -167,6 +170,7 @@ signals: void isMCUsChanged(); void projectOpenedChanged(); void isSharingEnabledChanged(); + void isDocumentDirtyChanged(); private: void setupWorkspaces(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp index 104127bd49..9f9e888823 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp @@ -318,11 +318,12 @@ ModelNode TransitionEditorView::addNewTransition() TransitionEditorWidget *TransitionEditorView::createWidget() { - if (!m_transitionEditorWidget) + if (!m_transitionEditorWidget) { m_transitionEditorWidget = new TransitionEditorWidget(this); - auto *transitionContext = new TransitionContext(m_transitionEditorWidget); - Core::ICore::addContextObject(transitionContext); + auto *transitionContext = new TransitionContext(m_transitionEditorWidget); + Core::ICore::addContextObject(transitionContext); + } return m_transitionEditorWidget; } diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp index 1770ba63fc..dcbb0b23b3 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp @@ -13,6 +13,7 @@ #include <rewritertransaction.h> #include <variantproperty.h> #include <qmlitemnode.h> +#include <dialogutils.h> #include <coreplugin/messagebox.h> @@ -45,8 +46,7 @@ TransitionForm::TransitionForm(QWidget *parent) bool error = false; if (!ModelNode::isValidId(newId)) { - Core::AsynchronousMessageBox::warning(tr("Invalid ID"), - tr("%1 is an invalid ID.").arg(newId)); + DialogUtils::showWarningForInvalidId(newId); error = true; } else if (m_transition.view()->hasId(newId)) { Core::AsynchronousMessageBox::warning(tr("Invalid ID"), |