diff options
Diffstat (limited to 'src/plugins/qmldesigner/components')
165 files changed, 4420 insertions, 6559 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..7f488bd615 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -1,21 +1,22 @@ // 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 <modelnodeoperations.h> #include <qmldesignerplugin.h> +#include <uniquename.h> #include <coreplugin/icore.h> + #include <utils/algorithm.h> -#include <utils/qtcassert.h> +#include <utils/asset.h> +#include <utils/filepath.h> +#include <utils/filesystemwatcher.h> + +#include <QFileInfo> +#include <QFileSystemModel> +#include <QMessageBox> namespace QmlDesigner { @@ -38,7 +39,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()); @@ -153,16 +154,15 @@ bool AssetsLibraryModel::renameFolder(const QString &folderPath, const QString & QString AssetsLibraryModel::addNewFolder(const QString &folderPath) { - QString iterPath = folderPath; - QDir dir{folderPath}; - - while (dir.exists()) { - iterPath = getUniqueName(iterPath); + Utils::FilePath uniqueDirPath = Utils::FilePath::fromString(UniqueName::generatePath(folderPath)); - dir.setPath(iterPath); + auto res = uniqueDirPath.ensureWritableDir(); + if (!res.has_value()) { + qWarning() << __FUNCTION__ << res.error(); + return {}; } - return dir.mkpath(iterPath) ? iterPath : ""; + return uniqueDirPath.path(); } bool AssetsLibraryModel::urlPathExistsInModel(const QUrl &url) const @@ -207,7 +207,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,60 +218,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()); -} - -QString AssetsLibraryModel::getUniqueName(const QString &oldName) { - static QRegularExpression rgx("\\d+$"); // matches a number at the end of a string - - QString uniqueName = oldName; - // if the folder name ends with a number, increment it - QRegularExpressionMatch match = rgx.match(uniqueName); - if (match.hasMatch()) { // ends with a number - QString numStr = match.captured(0); - int num = match.captured(0).toInt(); - - // get number of padding zeros, ex: for "005" = 2 - int nPaddingZeros = 0; - for (; nPaddingZeros < numStr.size() && numStr[nPaddingZeros] == '0'; ++nPaddingZeros); - - ++num; - - // if the incremented number's digits increased, decrease the padding zeros - if (std::fmod(std::log10(num), 1.0) == 0) - --nPaddingZeros; - - uniqueName = oldName.mid(0, match.capturedStart()) - + QString('0').repeated(nPaddingZeros) - + QString::number(num); - } else { - uniqueName = oldName + '1'; - } - - return uniqueName; + setHasFiles(checkHasFiles()); } void AssetsLibraryModel::setRootPath(const QString &newPath) diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.h index 9334e86e9b..f08578651a 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,28 @@ public: return std::min(result, 1); } - bool haveFiles() const { return m_haveFiles; } - - QString getUniqueName(const QString &oldName); + bool hasFiles() const { return m_hasFiles; } 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..5e2211ce0d 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -3,26 +3,29 @@ #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 <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 <uniquename.h> +#include <utils3d.h> #include <coreplugin/fileutils.h> #include <coreplugin/icore.h> #include <coreplugin/messagebox.h> #include <utils/algorithm.h> +#include <utils/asset.h> #include <utils/environment.h> #include <utils/filepath.h> #include <utils/qtcassert.h> @@ -92,7 +95,7 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon , m_assetsModel{new AssetsLibraryModel(this)} , m_assetsView{view} , m_createTextures{view} - , m_assetsWidget{new StudioQuickWidget(this)} + , m_assetsWidget{Utils::makeUniqueObjectPtr<StudioQuickWidget>(this)} { setWindowTitle(tr("Assets Library", "Title of assets library widget")); setMinimumWidth(250); @@ -128,7 +131,7 @@ AssetsLibraryWidget::AssetsLibraryWidget(AsynchronousImageCache &asynchronousFon auto layout = new QVBoxLayout(this); layout->setContentsMargins({}); layout->setSpacing(0); - layout->addWidget(m_assetsWidget.data()); + layout->addWidget(m_assetsWidget.get()); updateSearch(); @@ -172,23 +175,10 @@ void AssetsLibraryWidget::deleteSelectedAssets() QString AssetsLibraryWidget::getUniqueEffectPath(const QString &parentFolder, const QString &effectName) { - auto genEffectPath = [&parentFolder](const QString &name) { - QString effectsDir = ModelNodeOperations::getEffectsDefaultDirectory(parentFolder); - return QLatin1String("%1/%2.qep").arg(effectsDir, name); - }; - - QString uniqueName = effectName; - QString path = genEffectPath(uniqueName); - QFileInfo file{path}; + QString effectsDir = ModelNodeOperations::getEffectsDefaultDirectory(parentFolder); + QString effectPath = QLatin1String("%1/%2.qep").arg(effectsDir, effectName); - while (file.exists()) { - uniqueName = m_assetsModel->getUniqueName(uniqueName); - - path = genEffectPath(uniqueName); - file.setFile(path); - } - - return path; + return UniqueName::generatePath(effectPath); } bool AssetsLibraryWidget::createNewEffect(const QString &effectPath, bool openInEffectComposer) @@ -287,14 +277,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 +294,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 +366,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 +635,9 @@ void AssetsLibraryWidget::addResources(const QStringList &files, bool showDialog } } +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..f2d476c842 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.h @@ -8,6 +8,8 @@ #include <coreplugin/icontext.h> +#include <utils/uniqueobjectptr.h> + #include <QFrame> #include <QQmlPropertyMap> #include <QQuickWidget> @@ -98,6 +100,7 @@ public: Q_INVOKABLE void showInGraphicalShell(const QString &path); Q_INVOKABLE QString showInGraphicalShellMsg() const; + Q_INVOKABLE void addAssetsToContentLibrary(const QStringList &assetPaths); signals: void itemActivated(const QString &itemName); @@ -135,7 +138,7 @@ private: AssetsLibraryView *m_assetsView = nullptr; CreateTextures m_createTextures = nullptr; - QScopedPointer<StudioQuickWidget> m_assetsWidget; + Utils::UniqueObjectPtr<StudioQuickWidget> m_assetsWidget; std::unique_ptr<PreviewTooltipBackend> m_fontPreviewTooltipBackend; QShortcut *m_qmlSourceUpdateShortcut = nullptr; diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp index ff2361aa36..b067dc8d46 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp @@ -147,7 +147,6 @@ BindingEditorFactory::BindingEditorFactory() { setId(BINDINGEDITOR_CONTEXT_ID); setDisplayName(::Core::Tr::tr("Binding Editor")); - setEditorActionHandlers(0); addMimeType(BINDINGEDITOR_CONTEXT_ID); addMimeType(Utils::Constants::QML_MIMETYPE); addMimeType(Utils::Constants::QMLTYPES_MIMETYPE); diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.cpp deleted file mode 100644 index cb306c58cb..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// 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 "collectiondatatypemodel.h" - -#include <QHash> -#include <QtQml/QmlTypeAndRevisionsRegistration> - -namespace QmlDesigner { - -struct CollectionDataTypeModel::Details -{ - CollectionDetails::DataType type; - QString name; - QString description; -}; - -const QList<CollectionDataTypeModel::Details> CollectionDataTypeModel::m_orderedDetails{ - {DataType::String, "String", "Text"}, - {DataType::Integer, "Integer", "Whole number that can be positive, negative, or zero"}, - {DataType::Real, "Real", "Number with a decimal"}, - {DataType::Image, "Image", "Image resource"}, - {DataType::Color, "Color", "HEX value"}, - {DataType::Url, "Url", "Resource locator"}, - {DataType::Boolean, "Boolean", "True/false"}, - {DataType::Unknown, "Unknown", "Unknown data type"}, -}; - -CollectionDataTypeModel::CollectionDataTypeModel(QObject *parent) - : QAbstractListModel(parent) -{ -} - -int CollectionDataTypeModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_orderedDetails.size(); -} - -QVariant CollectionDataTypeModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return {}; - - if (role == Qt::DisplayRole) - return m_orderedDetails.at(index.row()).name; - if (role == Qt::ToolTipRole) - return m_orderedDetails.at(index.row()).description; - - return {}; -} - -QString CollectionDataTypeModel::dataTypeToString(DataType dataType) -{ - static const QHash<DataType, QString> dataTypeHash = []() -> QHash<DataType, QString> { - QHash<DataType, QString> result; - for (const Details &details : m_orderedDetails) - result.insert(details.type, details.name); - return result; - }(); - - if (dataTypeHash.contains(dataType)) - return dataTypeHash.value(dataType); - - return "Unknown"; -} - -CollectionDetails::DataType CollectionDataTypeModel::dataTypeFromString(const QString &dataType) -{ - static const QHash<QString, DataType> stringTypeHash = []() -> QHash<QString, DataType> { - QHash<QString, DataType> result; - for (const Details &details : m_orderedDetails) - result.insert(details.name, details.type); - return result; - }(); - - if (stringTypeHash.contains(dataType)) - return stringTypeHash.value(dataType); - - return DataType::Unknown; -} - -void CollectionDataTypeModel::registerDeclarativeType() -{ - qmlRegisterType<CollectionDataTypeModel>("CollectionDetails", 1, 0, "CollectionDataTypeModel"); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.h deleted file mode 100644 index 1f91aecbff..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondatatypemodel.h +++ /dev/null @@ -1,34 +0,0 @@ -// 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 "collectiondetails.h" - -#include <QAbstractListModel> -#include <QList> - -namespace QmlDesigner { - -class CollectionDataTypeModel : public QAbstractListModel -{ - Q_OBJECT - -public: - using DataType = CollectionDetails::DataType; - CollectionDataTypeModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - static Q_INVOKABLE QString dataTypeToString(DataType dataType); - static Q_INVOKABLE DataType dataTypeFromString(const QString &dataType); - - static void registerDeclarativeType(); - -private: - struct Details; - static const QList<Details> m_orderedDetails; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp deleted file mode 100644 index ddfb82746c..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.cpp +++ /dev/null @@ -1,1011 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "collectiondetails.h" - -#include "collectiondatatypemodel.h" -#include "collectioneditorutils.h" - -#include <utils/span.h> -#include <qmljs/parser/qmljsast_p.h> -#include <qmljs/parser/qmljsastvisitor_p.h> -#include <qmljs/qmljsdocument.h> -#include <qqml.h> - -#include <QJsonArray> -#include <QJsonDocument> -#include <QJsonObject> -#include <QRegularExpression> -#include <QTextStream> -#include <QUrl> -#include <QVariant> - -namespace QmlDesigner { -#define COLLERR_OK QT_TRANSLATE_NOOP("CollectioParseError", "no error occurred") -#define COLLERR_MAINOBJECT QT_TRANSLATE_NOOP("CollectioParseError", "Document object not found") -#define COLLERR_COLLECTIONNAME QT_TRANSLATE_NOOP("CollectioParseError", "Model name not found") -#define COLLERR_COLLECTIONOBJ QT_TRANSLATE_NOOP("CollectioParseError", "Model is not an object") -#define COLLERR_COLUMNARRAY QT_TRANSLATE_NOOP("CollectioParseError", "Column is not an array") -#define COLLERR_UNKNOWN QT_TRANSLATE_NOOP("CollectioParseError", "Unknown error") - -struct CollectionProperty -{ - using DataType = CollectionDetails::DataType; - - QString name; - DataType type; -}; - -const QMap<DataTypeWarning::Warning, QString> DataTypeWarning::dataTypeWarnings = { - {DataTypeWarning::CellDataTypeMismatch, "Cell and column data types do not match."} -}; - -class CollectionDetails::Private -{ -public: - QList<CollectionProperty> properties; - QList<QJsonArray> dataRecords; - CollectionReference reference; - bool isChanged = false; - - bool isValidColumnId(int column) const { return column > -1 && column < properties.size(); } - - bool isValidRowId(int row) const { return row > -1 && row < dataRecords.size(); } -}; - -inline static bool isValidColorName(const QString &colorName) -{ - return QColor::isValidColorName(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 - * will be stored to this parameter, otherwise, it will be Unknown - * @param urlResult if the value is a valid url or image, 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 - */ -static bool getCustomUrl(const QString &value, - CollectionDetails::DataType &dataType, - QUrl *urlResult = nullptr, - QString *subType = nullptr) -{ - static const QRegularExpression urlRegex{ - "^(?<MimeType>" - "(?<MainType>image)\\/" - "(?<SubType>apng|avif|gif|jpeg|png|(?:svg\\+xml)|webp|xyz)\\:)?" // end of MimeType - "(?<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?:\\/\\/" - "(?:www\\.|(?!www))[A-z0-9]+\\.[^\\s]{2,}|www\\.[A-z0-9]+\\.[^\\s]{2,})|" // end of Url - "(?<LocalFile>(" - "?:(?:[A-z]:)|(?:(?:\\\\|\\/){1,2}\\w+)\\$?)(?:(?:\\\\|\\/)(?:\\w[\\w ]*.*))+)" // end of LocalFile - "){1}$" // end of Address - }; - - 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 (urlResult) - urlResult->setUrl(match.captured("Address")); - - if (subType) - *subType = match.captured("SubType"); - - return true; - } - } - - if (urlResult) - urlResult->clear(); - - if (subType) - subType->clear(); - - dataType = CollectionDetails::DataType::Unknown; - return false; -} - -/** - * @brief dataTypeFromString - * @param value The string value to be evaluated - * @return Unknown if the string is empty, But returns Bool, Color, Integer, - * Real, Url, Image if these types are detected within the non-empty string, - * Otherwise it returns String. - * If the value is integer, but it's out of the int range, it will be - * considered as a Real. - */ -static CollectionDetails::DataType dataTypeFromString(const QString &value) -{ - using DataType = CollectionDetails::DataType; - static const QRegularExpression validator{ - "(?<boolean>^(?:true|false)$)|" - "(?<color>^(?:#(?:(?:[0-9a-fA-F]{2}){3,4}|(?:[0-9a-fA-F]){3,4}))$)|" - "(?<integer>^\\d+$)|" - "(?<real>^(?:-?(?:0|[1-9]\\d*)?(?:\\.\\d*)?(?<=\\d|\\.)" - "(?:e-?(?:0|[1-9]\\d*))?|0x[0-9a-f]+)$)"}; - static const int boolIndex = validator.namedCaptureGroups().indexOf("boolean"); - static const int colorIndex = validator.namedCaptureGroups().indexOf("color"); - static const int integerIndex = validator.namedCaptureGroups().indexOf("integer"); - static const int realIndex = validator.namedCaptureGroups().indexOf("real"); - - [[maybe_unused]] static const bool allIndexesFound = - [](const std::initializer_list<int> &captureIndexes) { - QTC_ASSERT(Utils::allOf(captureIndexes, [](int val) { return val > -1; }), return false); - return true; - }({boolIndex, colorIndex, integerIndex, realIndex}); - - if (value.isEmpty()) - return DataType::Unknown; - - const QString trimmedValue = value.trimmed(); - QRegularExpressionMatch match = validator.match(trimmedValue); - - if (match.hasCaptured(boolIndex)) - return DataType::Boolean; - if (match.hasCaptured(colorIndex)) - return DataType::Color; - if (match.hasCaptured(integerIndex)) { - bool isInt = false; - trimmedValue.toInt(&isInt); - return isInt ? DataType::Integer : DataType::Real; - } - if (match.hasCaptured(realIndex)) - return DataType::Real; - - DataType urlType; - if (getCustomUrl(trimmedValue, urlType)) - return urlType; - - return DataType::String; -} - -static CollectionProperty::DataType dataTypeFromJsonValue(const QJsonValue &value) -{ - using DataType = CollectionDetails::DataType; - using JsonType = QJsonValue::Type; - - switch (value.type()) { - case JsonType::Null: - case JsonType::Undefined: - return DataType::Unknown; - case JsonType::Bool: - return DataType::Boolean; - case JsonType::Double: { - if (qFuzzyIsNull(std::remainder(value.toDouble(), 1))) - return DataType::Integer; - return DataType::Real; - } - case JsonType::String: - return dataTypeFromString(value.toString()); - default: - return DataType::Unknown; - } -} - -static QList<CollectionProperty> getColumnsFromImportedJsonArray(const QJsonArray &importedArray) -{ - using DataType = CollectionDetails::DataType; - - QHash<QString, int> resultSet; - QList<CollectionProperty> result; - - for (const QJsonValue &value : importedArray) { - if (value.isObject()) { - const QJsonObject object = value.toObject(); - QJsonObject::ConstIterator element = object.constBegin(); - const QJsonObject::ConstIterator stopItem = object.constEnd(); - - while (element != stopItem) { - const QString propertyName = element.key(); - if (resultSet.contains(propertyName)) { - CollectionProperty &property = result[resultSet.value(propertyName)]; - if (property.type == DataType::Unknown) { - property.type = dataTypeFromJsonValue(element.value()); - } else if (property.type == DataType::Integer) { - const DataType currentCellDataType = dataTypeFromJsonValue(element.value()); - if (currentCellDataType == DataType::Real) - property.type = currentCellDataType; - } - } else { - result.append({propertyName, dataTypeFromJsonValue(element.value())}); - resultSet.insert(propertyName, resultSet.size()); - } - ++element; - } - } - } - - return result; -} - -static QVariant valueToVariant(const QJsonValue &value, CollectionDetails::DataType type) -{ - using DataType = CollectionDetails::DataType; - QVariant variantValue = value.toVariant(); - - switch (type) { - case DataType::String: - return variantValue.toString(); - case DataType::Integer: - return variantValue.toInt(); - case DataType::Real: - return variantValue.toDouble(); - case DataType::Boolean: - 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: - return variantValue.value<QUrl>(); - default: - return variantValue; - } -} - -static QJsonValue variantToJsonValue( - const QVariant &variant, CollectionDetails::DataType type = CollectionDetails::DataType::Unknown) -{ - using VariantType = QVariant::Type; - using DataType = CollectionDetails::DataType; - - if (type == CollectionDetails::DataType::Unknown) { - static const QHash<VariantType, DataType> typeMap = {{VariantType::Bool, DataType::Boolean}, - {VariantType::Double, DataType::Real}, - {VariantType::Int, DataType::Integer}, - {VariantType::String, DataType::String}, - {VariantType::Color, DataType::Color}, - {VariantType::Url, DataType::Url}}; - type = typeMap.value(variant.type(), DataType::Unknown); - } - - switch (type) { - case DataType::Boolean: - return variant.toBool(); - case DataType::Real: - 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::String: - case DataType::Color: - case DataType::Url: - default: - return variant.toString(); - } -} - -inline static bool isEmptyJsonValue(const QJsonValue &value) -{ - return value.isNull() || value.isUndefined() || (value.isString() && value.toString().isEmpty()); -} - -QStringList csvReadLine(const QString &line) -{ - static const QRegularExpression lineRegex{ - "(?:,\\\"|^\\\")(?<value>\\\"\\\"|[\\w\\W]*?)(?=\\\",|\\\"$)" - "|(?:,(?!\\\")|^(?!\\\"))(?<quote>[^,]*?)(?=$|,)|(\\\\r\\\\n|\\\\n)"}; - static const int valueIndex = lineRegex.namedCaptureGroups().indexOf("value"); - static const int quoteIndex = lineRegex.namedCaptureGroups().indexOf("quote"); - Q_ASSERT(valueIndex > 0 && quoteIndex > 0); - - QStringList result; - QRegularExpressionMatchIterator iterator = lineRegex.globalMatch(line, 0); - while (iterator.hasNext()) { - const QRegularExpressionMatch match = iterator.next(); - - if (match.hasCaptured(valueIndex)) - result.append(match.captured(valueIndex)); - else if (match.hasCaptured(quoteIndex)) - result.append(match.captured(quoteIndex)); - } - return result; -} - -class PropertyOrderFinder : public QmlJS::AST::Visitor -{ -public: - static QStringList parse(const QString &jsonContent) - { - PropertyOrderFinder finder; - QmlJS::Document::MutablePtr jsonDoc = QmlJS::Document::create(Utils::FilePath::fromString( - "<expression>"), - QmlJS::Dialect::Json); - - jsonDoc->setSource(jsonContent); - jsonDoc->parseJavaScript(); - - if (!jsonDoc->isParsedCorrectly()) - return {}; - - jsonDoc->ast()->accept(&finder); - return finder.m_orderedList; - } - -protected: - bool visit(QmlJS::AST::PatternProperty *patternProperty) override - { - const QString propertyName = patternProperty->name->asString(); - if (!m_propertySet.contains(propertyName)) { - m_propertySet.insert(propertyName); - m_orderedList.append(propertyName); - } - return true; - } - - void throwRecursionDepthError() override - { - qWarning() << Q_FUNC_INFO << __LINE__ << "Recursion depth error"; - }; - -private: - QSet<QString> m_propertySet; - QStringList m_orderedList; -}; - -QString CollectionParseError::errorString() const -{ - switch (errorNo) { - case NoError: - return COLLERR_OK; - case MainObjectMissing: - return COLLERR_MAINOBJECT; - case CollectionNameNotFound: - return COLLERR_COLLECTIONNAME; - case CollectionIsNotObject: - return COLLERR_COLLECTIONOBJ; - case ColumnsBlockIsNotArray: - return COLLERR_COLUMNARRAY; - case UnknownError: - default: - return COLLERR_UNKNOWN; - } -} - -CollectionDetails::CollectionDetails() - : d(new Private()) -{} - -CollectionDetails::CollectionDetails(const CollectionReference &reference) - : CollectionDetails() -{ - d->reference = reference; -} - -void CollectionDetails::resetData(const QJsonDocument &localDocument, - const QString &collectionToImport, - CollectionParseError *error) -{ - CollectionDetails importedCollection = fromLocalJson(localDocument, collectionToImport, error); - d->properties.swap(importedCollection.d->properties); - d->dataRecords.swap(importedCollection.d->dataRecords); -} - -CollectionDetails::CollectionDetails(const CollectionDetails &other) = default; - -CollectionDetails::~CollectionDetails() = default; - -void CollectionDetails::insertColumn(const QString &propertyName, - int colIdx, - const QVariant &defaultValue, - DataType type) -{ - if (containsPropertyName(propertyName)) - return; - - CollectionProperty property = {propertyName, type}; - if (d->isValidColumnId(colIdx)) { - d->properties.insert(colIdx, property); - } else { - colIdx = d->properties.size(); - d->properties.append(property); - } - - const QJsonValue defaultJsonValue = QJsonValue::fromVariant(defaultValue); - for (QJsonArray &record : d->dataRecords) - record.insert(colIdx, defaultJsonValue); - - markChanged(); -} - -bool CollectionDetails::removeColumns(int colIdx, int count) -{ - if (!d->isValidColumnId(colIdx)) - return false; - - int maxCount = d->properties.count() - colIdx; - count = std::min(maxCount, count); - - if (count < 1) - return false; - - d->properties.remove(colIdx, count); - - for (QJsonArray &record : d->dataRecords) { - QJsonArray newElement; - - auto elementItr = record.constBegin(); - auto elementEnd = elementItr + colIdx; - while (elementItr != elementEnd) - newElement.append(*(elementItr++)); - - elementItr += count; - elementEnd = record.constEnd(); - - while (elementItr != elementEnd) - newElement.append(*(elementItr++)); - - record = newElement; - } - - markChanged(); - - return true; -} - -void CollectionDetails::insertEmptyRows(int row, int count) -{ - if (count < 1) - return; - - row = qBound(0, row, rows()); - - insertRecords({}, row, count); - - markChanged(); -} - -bool CollectionDetails::removeRows(int row, int count) -{ - if (!d->isValidRowId(row)) - return false; - - int maxCount = d->dataRecords.count() - row; - count = std::min(maxCount, count); - - if (count < 1) - return false; - - d->dataRecords.remove(row, count); - markChanged(); - return true; -} - -bool CollectionDetails::setPropertyValue(int row, int column, const QVariant &value) -{ - if (!d->isValidRowId(row) || !d->isValidColumnId(column)) - return false; - - QVariant currentValue = data(row, column); - if (value == currentValue) - return false; - - QJsonArray &record = d->dataRecords[row]; - record.replace(column, variantToJsonValue(value, typeAt(column))); - markChanged(); - return true; -} - -bool CollectionDetails::setPropertyName(int column, const QString &value) -{ - if (!d->isValidColumnId(column)) - return false; - - const CollectionProperty &oldProperty = d->properties.at(column); - if (oldProperty.name == value) - return false; - - d->properties.replace(column, {value, oldProperty.type}); - - markChanged(); - return true; -} - -bool CollectionDetails::setPropertyType(int column, DataType type) -{ - if (!d->isValidColumnId(column)) - return false; - - bool changed = false; - CollectionProperty &property = d->properties[column]; - if (property.type != type) - changed = true; - - const DataType formerType = property.type; - property.type = type; - - for (QJsonArray &rowData : d->dataRecords) { - if (column < rowData.size()) { - const QJsonValue value = rowData.at(column); - const QVariant properTypedValue = valueToVariant(value, formerType); - const QJsonValue properTypedJsonValue = variantToJsonValue(properTypedValue, type); - rowData.replace(column, properTypedJsonValue); - changed = true; - } - } - - if (changed) - markChanged(); - - return changed; -} - -CollectionReference CollectionDetails::reference() const -{ - return d->reference; -} - -QVariant CollectionDetails::data(int row, int column) const -{ - if (!d->isValidRowId(row)) - return {}; - - if (!d->isValidColumnId(column)) - return {}; - - 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(); -} - -QString CollectionDetails::propertyAt(int column) const -{ - if (!d->isValidColumnId(column)) - return {}; - - return d->properties.at(column).name; -} - -CollectionDetails::DataType CollectionDetails::typeAt(int column) const -{ - if (!d->isValidColumnId(column)) - return {}; - - return d->properties.at(column).type; -} - -CollectionDetails::DataType CollectionDetails::typeAt(int row, int column) const -{ - if (!d->isValidRowId(row) || !d->isValidColumnId(column)) - return {}; - - const QJsonValue cellData = d->dataRecords.at(row).at(column); - return dataTypeFromJsonValue(cellData); -} - -DataTypeWarning::Warning CollectionDetails::cellWarningCheck(int row, int column) const -{ - const QJsonValue cellValue = d->dataRecords.at(row).at(column); - - const DataType columnType = typeAt(column); - const DataType cellType = typeAt(row, column); - - if (columnType == DataType::Unknown || isEmptyJsonValue(cellValue)) - return DataTypeWarning::Warning::None; - - if (columnType == DataType::Real && cellType == DataType::Integer) - return DataTypeWarning::Warning::None; - - if (columnType != cellType) - return DataTypeWarning::Warning::CellDataTypeMismatch; - - return DataTypeWarning::Warning::None; -} - -bool CollectionDetails::containsPropertyName(const QString &propertyName) const -{ - return Utils::anyOf(d->properties, [&propertyName](const CollectionProperty &property) { - return property.name == propertyName; - }); -} - -bool CollectionDetails::hasValidReference() const -{ - return d->reference.node.isValid() && d->reference.name.size(); -} - -bool CollectionDetails::isChanged() const -{ - return d->isChanged; -} - -int CollectionDetails::columns() const -{ - return d->properties.size(); -} - -int CollectionDetails::rows() const -{ - return d->dataRecords.size(); -} - -bool CollectionDetails::markSaved() -{ - if (d->isChanged) { - d->isChanged = false; - return true; - } - return false; -} - -void CollectionDetails::swap(CollectionDetails &other) -{ - d.swap(other.d); -} - -void CollectionDetails::resetReference(const CollectionReference &reference) -{ - if (d->reference != reference) { - d->reference = reference; - markChanged(); - } -} - -QString CollectionDetails::toJson() const -{ - QJsonArray exportedArray; - const int propertyCount = d->properties.count(); - - for (const QJsonArray &record : std::as_const(d->dataRecords)) { - const int valueCount = std::min(int(record.count()), propertyCount); - - QJsonObject exportedElement; - for (int i = 0; i < valueCount; ++i) { - const QJsonValue &value = record.at(i); - if (isEmptyJsonValue(value)) - exportedElement.insert(d->properties.at(i).name, QJsonValue::Null); - else - exportedElement.insert(d->properties.at(i).name, value); - } - - exportedArray.append(exportedElement); - } - - return QString::fromUtf8(QJsonDocument(exportedArray).toJson()); -} - -QString CollectionDetails::toCsv() const -{ - QString content; - - auto gotoNextLine = [&content]() { - if (content.size() && content.back() == ',') - content.back() = '\n'; - else - content += "\n"; - }; - - const int propertyCount = d->properties.count(); - if (propertyCount <= 0) - return ""; - - for (const CollectionProperty &property : std::as_const(d->properties)) - content += property.name + ','; - - gotoNextLine(); - - for (const QJsonArray &record : std::as_const(d->dataRecords)) { - const int valueCount = std::min(int(record.count()), propertyCount); - int i = 0; - for (; i < valueCount; ++i) { - const QJsonValue &value = record.at(i); - - if (value.isDouble()) - content += QString::number(value.toDouble()) + ','; - else if (value.isBool()) - content += value.toBool() ? QString("true,") : QString("false,"); - else - content += value.toString() + ','; - } - - for (; i < propertyCount; ++i) - content += ','; - - gotoNextLine(); - } - - return content; -} - -QJsonObject CollectionDetails::toLocalJson() const -{ - QJsonObject collectionObject; - QJsonArray columnsArray; - QJsonArray dataArray; - - for (const CollectionProperty &property : std::as_const(d->properties)) { - QJsonObject columnObject; - columnObject.insert("name", property.name); - columnObject.insert("type", CollectionDataTypeModel::dataTypeToString(property.type)); - columnsArray.append(columnObject); - } - - for (const QJsonArray &record : std::as_const(d->dataRecords)) - dataArray.append(record); - - collectionObject.insert("columns", columnsArray); - collectionObject.insert("data", dataArray); - - return collectionObject; -} - -void CollectionDetails::registerDeclarativeType() -{ - typedef CollectionDetails::DataType DataType; - qRegisterMetaType<DataType>("DataType"); - qmlRegisterUncreatableType<CollectionDetails>("CollectionDetails", 1, 0, "DataType", "Enum type"); - - qRegisterMetaType<DataTypeWarning::Warning>("Warning"); - qmlRegisterUncreatableType<DataTypeWarning>("CollectionDetails", 1, 0, "Warning", "Enum type"); -} - -CollectionDetails CollectionDetails::fromImportedCsv(const QByteArray &document, - const bool &firstRowIsHeader) -{ - QStringList headers; - QJsonArray importedArray; - - QTextStream stream(document); - stream.setEncoding(QStringConverter::Latin1); - - if (firstRowIsHeader && !stream.atEnd()) { - headers = Utils::transform(csvReadLine(stream.readLine()), - [](const QString &value) -> QString { return value.trimmed(); }); - } - - while (!stream.atEnd()) { - const QStringList recordDataList = csvReadLine(stream.readLine()); - int column = -1; - QJsonObject recordData; - for (const QString &cellData : recordDataList) { - if (++column == headers.size()) { - QString proposalName; - int proposalId = column; - do - proposalName = QString("Column %1").arg(++proposalId); - while (headers.contains(proposalName)); - headers.append(proposalName); - } - recordData.insert(headers.at(column), cellData); - } - importedArray.append(recordData); - } - - return fromImportedJson(importedArray, headers); -} - -CollectionDetails CollectionDetails::fromImportedJson(const QByteArray &json, QJsonParseError *error) -{ - QJsonArray importedCollection; - auto refineJsonArray = [](const QJsonArray &array) -> QJsonArray { - QJsonArray resultArray; - for (const QJsonValue &collectionData : array) { - if (collectionData.isObject()) { - QJsonObject rowObject = collectionData.toObject(); - const QStringList rowKeys = rowObject.keys(); - for (const QString &key : rowKeys) { - const QJsonValue cellValue = rowObject.value(key); - if (cellValue.isArray()) - rowObject.remove(key); - } - resultArray.push_back(rowObject); - } - } - return resultArray; - }; - - QJsonParseError parseError; - QJsonDocument document = QJsonDocument::fromJson(json, &parseError); - if (error) - *error = parseError; - - if (parseError.error != QJsonParseError::NoError) - return CollectionDetails{}; - - if (document.isArray()) { - importedCollection = refineJsonArray(document.array()); - } else if (document.isObject()) { - QJsonObject documentObject = document.object(); - const QStringList mainKeys = documentObject.keys(); - - bool arrayFound = false; - for (const QString &key : mainKeys) { - const QJsonValue value = documentObject.value(key); - if (value.isArray()) { - arrayFound = true; - importedCollection = refineJsonArray(value.toArray()); - break; - } - } - - if (!arrayFound) { - QJsonObject singleObject; - for (const QString &key : mainKeys) { - const QJsonValue value = documentObject.value(key); - - if (!value.isObject()) - singleObject.insert(key, value); - } - importedCollection.push_back(singleObject); - } - } - - return fromImportedJson(importedCollection, PropertyOrderFinder::parse(QLatin1String(json))); -} - -CollectionDetails CollectionDetails::fromLocalJson(const QJsonDocument &document, - const QString &collectionName, - CollectionParseError *error) -{ - auto setError = [&error](CollectionParseError::ParseError parseError) { - if (error) - error->errorNo = parseError; - }; - - setError(CollectionParseError::NoError); - - if (document.isObject()) { - QJsonObject collectionMap = document.object(); - if (collectionMap.contains(collectionName)) { - QJsonValue collectionValue = collectionMap.value(collectionName); - if (collectionValue.isObject()) - return fromLocalCollection(collectionValue.toObject()); - else - setError(CollectionParseError::CollectionIsNotObject); - } else { - setError(CollectionParseError::CollectionNameNotFound); - } - } else { - setError(CollectionParseError::MainObjectMissing); - } - - return CollectionDetails{}; -} - -CollectionDetails &CollectionDetails::operator=(const CollectionDetails &other) -{ - CollectionDetails value(other); - swap(value); - return *this; -} - -void CollectionDetails::markChanged() -{ - d->isChanged = true; -} - -void CollectionDetails::insertRecords(const QJsonArray &record, int idx, int count) -{ - if (count < 1) - return; - - QJsonArray localRecord; - const int columnsCount = columns(); - for (int i = 0; i < columnsCount; i++) { - const QJsonValue originalCellData = record.at(i); - if (originalCellData.isArray()) - localRecord.append({}); - else - localRecord.append(originalCellData); - } - - if (idx > d->dataRecords.size() || idx < 0) - idx = d->dataRecords.size(); - - d->dataRecords.insert(idx, count, localRecord); -} - -CollectionDetails CollectionDetails::fromImportedJson(const QJsonArray &importedArray, - const QStringList &propertyPriority) -{ - QList<CollectionProperty> columnData = getColumnsFromImportedJsonArray(importedArray); - if (!propertyPriority.isEmpty()) { - QMap<QString, int> priorityMap; - for (const QString &propertyName : propertyPriority) { - if (!priorityMap.contains(propertyName)) - priorityMap.insert(propertyName, priorityMap.size()); - } - const int lowestPriority = priorityMap.size(); - - Utils::sort(columnData, [&](const CollectionProperty &a, const CollectionProperty &b) { - return priorityMap.value(a.name, lowestPriority) - < priorityMap.value(b.name, lowestPriority); - }); - } - - QList<QJsonArray> localJsonArray; - for (const QJsonValue &importedRowValue : importedArray) { - QJsonObject importedRowObject = importedRowValue.toObject(); - QJsonArray localRow; - for (const CollectionProperty &property : columnData) - localRow.append(importedRowObject.value(property.name)); - localJsonArray.append(localRow); - } - CollectionDetails result; - result.d->properties = columnData; - result.d->dataRecords = localJsonArray; - result.markSaved(); - - return result; -} - -CollectionDetails CollectionDetails::fromLocalCollection(const QJsonObject &localCollection, - CollectionParseError *error) -{ - auto setError = [&error](CollectionParseError::ParseError parseError) { - if (error) - error->errorNo = parseError; - }; - - CollectionDetails result; - setError(CollectionParseError::NoError); - - if (localCollection.contains("columns")) { - const QJsonValue columnsValue = localCollection.value("columns"); - if (columnsValue.isArray()) { - const QJsonArray columns = columnsValue.toArray(); - for (const QJsonValue &columnValue : columns) { - if (columnValue.isObject()) { - const QJsonObject column = columnValue.toObject(); - const QString columnName = column.value("name").toString(); - if (!columnName.isEmpty()) { - result.insertColumn(columnName, - -1, - {}, - CollectionDataTypeModel::dataTypeFromString( - column.value("type").toString())); - } - } - } - - if (int columnsCount = result.columns()) { - const QJsonArray dataRecords = localCollection.value("data").toArray(); - for (const QJsonValue &dataRecordValue : dataRecords) { - QJsonArray dataRecord = dataRecordValue.toArray(); - while (dataRecord.count() > columnsCount) - dataRecord.removeLast(); - - result.insertRecords(dataRecord); - } - } - } else { - setError(CollectionParseError::ColumnsBlockIsNotArray); - return result; - } - } - - return result; -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h deleted file mode 100644 index b84c214570..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetails.h +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "modelnode.h" - -#include <QSharedPointer> - -QT_BEGIN_NAMESPACE -class QJsonObject; -struct QJsonParseError; -class QVariant; -QT_END_NAMESPACE - -namespace QmlDesigner { - -struct CollectionReference -{ - ModelNode node; - QString name; - - friend auto qHash(const CollectionReference &collection) - { - return qHash(collection.node) ^ ::qHash(collection.name); - } - - bool operator==(const CollectionReference &other) const - { - return node == other.node && name == other.name; - } - - bool operator!=(const CollectionReference &other) const { return !(*this == other); } -}; - -struct CollectionProperty; - -struct DataTypeWarning { -public: - enum Warning { None, CellDataTypeMismatch }; - Q_ENUM(Warning) - - Warning warning = None; - DataTypeWarning(Warning warning) - : warning(warning) - {} - - static QString getDataTypeWarningString(Warning warning) - { - return dataTypeWarnings.value(warning); - } - -private: - Q_GADGET - static const QMap<Warning, QString> dataTypeWarnings; -}; - -struct CollectionParseError -{ - enum ParseError { - NoError, - MainObjectMissing, - CollectionNameNotFound, - CollectionIsNotObject, - ColumnsBlockIsNotArray, - UnknownError - }; - - ParseError errorNo = ParseError::NoError; - QString errorString() const; -}; - -class CollectionDetails -{ - Q_GADGET - -public: - enum class DataType { Unknown, String, Url, Integer, Real, Boolean, Image, Color }; - Q_ENUM(DataType) - - explicit CollectionDetails(); - CollectionDetails(const CollectionReference &reference); - CollectionDetails(const CollectionDetails &other); - ~CollectionDetails(); - - void resetData(const QJsonDocument &localDocument, - const QString &collectionToImport, - CollectionParseError *error = nullptr); - - void insertColumn(const QString &propertyName, - int colIdx = -1, - const QVariant &defaultValue = {}, - DataType type = DataType::Unknown); - bool removeColumns(int colIdx, int count = 1); - - void insertEmptyRows(int row = 0, int count = 1); - bool removeRows(int row, int count = 1); - bool setPropertyValue(int row, int column, const QVariant &value); - - bool setPropertyName(int column, const QString &value); - bool setPropertyType(int column, DataType type); - - CollectionReference reference() const; - QVariant data(int row, int column) const; - QString propertyAt(int column) const; - DataType typeAt(int column) const; - DataType typeAt(int row, int column) const; - DataTypeWarning::Warning cellWarningCheck(int row, int column) const; - bool containsPropertyName(const QString &propertyName) const; - - bool hasValidReference() const; - bool isChanged() const; - - int columns() const; - int rows() const; - - bool markSaved(); - - void swap(CollectionDetails &other); - void resetReference(const CollectionReference &reference); - - QString toJson() const; - QString toCsv() const; - QJsonObject toLocalJson() const; - - static void registerDeclarativeType(); - - static CollectionDetails fromImportedCsv(const QByteArray &document, - const bool &firstRowIsHeader = true); - static CollectionDetails fromImportedJson(const QByteArray &json, - QJsonParseError *error = nullptr); - static CollectionDetails fromLocalJson(const QJsonDocument &document, - const QString &collectionName, - CollectionParseError *error = nullptr); - - CollectionDetails &operator=(const CollectionDetails &other); - -private: - void markChanged(); - void insertRecords(const QJsonArray &record, int idx = -1, int count = 1); - - static CollectionDetails fromImportedJson(const QJsonArray &importedArray, - const QStringList &propertyPriority = {}); - static CollectionDetails fromLocalCollection(const QJsonObject &localCollection, - CollectionParseError *error = nullptr); - - // The private data is supposed to be shared between the copies - class Private; - QSharedPointer<Private> d; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp deleted file mode 100644 index b26b1a845e..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.cpp +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "collectiondetailsmodel.h" - -#include "collectiondatatypemodel.h" -#include "collectioneditorutils.h" -#include "modelnode.h" - -#include <coreplugin/editormanager/editormanager.h> - -#include <utils/fileutils.h> -#include <utils/qtcassert.h> -#include <utils/textfileformat.h> - -#include <QJsonArray> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonParseError> - -namespace QmlDesigner { - -CollectionDetailsModel::CollectionDetailsModel(QObject *parent) - : QAbstractTableModel(parent) -{ - connect(this, &CollectionDetailsModel::modelReset, this, &CollectionDetailsModel::updateEmpty); - connect(this, &CollectionDetailsModel::rowsInserted, this, &CollectionDetailsModel::updateEmpty); - connect(this, &CollectionDetailsModel::rowsRemoved, this, &CollectionDetailsModel::updateEmpty); -} - -QHash<int, QByteArray> CollectionDetailsModel::roleNames() const -{ - static QHash<int, QByteArray> roles; - if (roles.isEmpty()) { - roles.insert(QAbstractTableModel::roleNames()); - roles.insert(SelectedRole, "itemSelected"); - roles.insert(DataTypeRole, "dataType"); - roles.insert(ColumnDataTypeRole, "columnType"); - roles.insert(DataTypeWarningRole, "dataTypeWarning"); - } - return roles; -} - -int CollectionDetailsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_currentCollection.rows(); -} - -int CollectionDetailsModel::columnCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_currentCollection.columns(); -} - -QVariant CollectionDetailsModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return {}; - - QTC_ASSERT(m_currentCollection.hasValidReference(), return {}); - - if (role == SelectedRole) - return (index.column() == m_selectedColumn || index.row() == m_selectedRow); - - if (role == DataTypeRole) - return QVariant::fromValue(m_currentCollection.typeAt(index.row(), index.column())); - - if (role == ColumnDataTypeRole) - return QVariant::fromValue(m_currentCollection.typeAt(index.column())); - - if (role == Qt::EditRole) - return m_currentCollection.data(index.row(), index.column()); - - if (role == DataTypeWarningRole ) - return QVariant::fromValue(m_currentCollection.cellWarningCheck(index.row(), index.column())); - - return m_currentCollection.data(index.row(), index.column()).toString(); -} - -bool CollectionDetailsModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - if (!index.isValid()) - return {}; - - if (role == Qt::EditRole) { - DataTypeWarning::Warning prevWarning = m_currentCollection.cellWarningCheck(index.row(), index.column()); - bool changed = m_currentCollection.setPropertyValue(index.row(), index.column(), value); - - if (changed) { - QList<int> roles = {Qt::DisplayRole, Qt::EditRole}; - - if (prevWarning != m_currentCollection.cellWarningCheck(index.row(), index.column())) - roles << DataTypeWarningRole; - - emit dataChanged(index, index, roles); - } - - return true; - } - - return false; -} - -bool CollectionDetailsModel::setHeaderData(int section, - Qt::Orientation orientation, - const QVariant &value, - [[maybe_unused]] int role) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - if (orientation == Qt::Vertical) - return false; - - bool headerChanged = m_currentCollection.setPropertyName(section, value.toString()); - if (headerChanged) - emit this->headerDataChanged(orientation, section, section); - - return headerChanged; -} - -bool CollectionDetailsModel::insertRows(int row, int count, [[maybe_unused]] const QModelIndex &parent) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - if (count < 1) - return false; - - row = qBound(0, row, rowCount()); - - beginResetModel(); - m_currentCollection.insertEmptyRows(row, count); - endResetModel(); - - selectRow(row); - return true; -} - -bool CollectionDetailsModel::removeColumns(int column, int count, const QModelIndex &parent) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - if (column < 0 || column >= columnCount(parent) || count < 1) - return false; - - count = std::min(count, columnCount(parent) - column); - beginRemoveColumns(parent, column, column + count - 1); - bool columnsRemoved = m_currentCollection.removeColumns(column, count); - endRemoveColumns(); - - 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; -} - -bool CollectionDetailsModel::removeRows(int row, int count, const QModelIndex &parent) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - if (row < 0 || row >= rowCount(parent) || count < 1) - return false; - - count = std::min(count, rowCount(parent) - row); - beginRemoveRows(parent, row, row + count - 1); - bool rowsRemoved = m_currentCollection.removeRows(row, count); - endRemoveRows(); - - ensureSingleCell(); - return rowsRemoved; -} - -Qt::ItemFlags CollectionDetailsModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return {}; - - return {Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable}; -} - -QVariant CollectionDetailsModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) { - if (role == DataTypeRole) - return CollectionDataTypeModel::dataTypeToString(m_currentCollection.typeAt(section)); - else - return m_currentCollection.propertyAt(section); - } - - if (orientation == Qt::Vertical) - return section + 1; - - return {}; -} - -CollectionDetails::DataType CollectionDetailsModel::propertyDataType(int column) const -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return CollectionDetails::DataType::Unknown); - - return m_currentCollection.typeAt(column); -} - -int CollectionDetailsModel::selectedColumn() const -{ - return m_selectedColumn; -} - -int CollectionDetailsModel::selectedRow() const -{ - return m_selectedRow; -} - -QString CollectionDetailsModel::propertyName(int column) const -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return {}); - - return m_currentCollection.propertyAt(column); -} - -QString CollectionDetailsModel::propertyType(int column) const -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return {}); - - return CollectionDataTypeModel::dataTypeToString(m_currentCollection.typeAt(column)); -} - -bool CollectionDetailsModel::isPropertyAvailable(const QString &name) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - return m_currentCollection.containsPropertyName(name); -} - -bool CollectionDetailsModel::addColumn(int column, const QString &name, const QString &propertyType) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - if (m_currentCollection.containsPropertyName(name)) - return false; - - if (column < 0 || column > columnCount()) - column = columnCount(); - - beginInsertColumns({}, column, column); - m_currentCollection.insertColumn(name, - column, - {}, - CollectionDataTypeModel::dataTypeFromString(propertyType)); - endInsertColumns(); - return m_currentCollection.containsPropertyName(name); -} - -bool CollectionDetailsModel::selectColumn(int section) -{ - if (m_selectedColumn == section) - return false; - - const int columns = columnCount(); - - if (section >= columns) - section = columns - 1; - - selectRow(-1); - - const int rows = rowCount(); - const int previousColumn = m_selectedColumn; - - m_selectedColumn = section; - emit this->selectedColumnChanged(m_selectedColumn); - - auto notifySelectedDataChanged = [this, columns, rows](int notifyingColumn) { - if (notifyingColumn > -1 && notifyingColumn < columns && rows) { - emit dataChanged(index(0, notifyingColumn), - index(rows - 1, notifyingColumn), - {SelectedRole}); - } - }; - - notifySelectedDataChanged(previousColumn); - notifySelectedDataChanged(m_selectedColumn); - - return true; -} - -bool CollectionDetailsModel::renameColumn(int section, const QString &newValue) -{ - return setHeaderData(section, Qt::Horizontal, newValue); -} - -bool CollectionDetailsModel::setPropertyType(int column, const QString &newValue) -{ - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - bool changed = m_currentCollection.setPropertyType(column, - CollectionDataTypeModel::dataTypeFromString( - newValue)); - if (changed) { - emit headerDataChanged(Qt::Horizontal, column, column); - emit dataChanged( - index(0, column), - index(rowCount() - 1, column), - {Qt::DisplayRole, Qt::EditRole, DataTypeRole, DataTypeWarningRole, ColumnDataTypeRole}); - } - - return changed; -} - -bool CollectionDetailsModel::selectRow(int row) -{ - if (m_selectedRow == row) - return false; - - const int rows = rowCount(); - - if (row >= rows) - row = rows - 1; - - selectColumn(-1); - - const int columns = columnCount(); - const int previousRow = m_selectedRow; - - m_selectedRow = row; - emit this->selectedRowChanged(m_selectedRow); - - auto notifySelectedDataChanged = [this, rows, columns](int notifyingRow) { - if (notifyingRow > -1 && notifyingRow < rows && columns) - emit dataChanged(index(notifyingRow, 0), index(notifyingRow, columns - 1), {SelectedRole}); - }; - - notifySelectedDataChanged(previousRow); - notifySelectedDataChanged(m_selectedRow); - - return true; -} - -void CollectionDetailsModel::deselectAll() -{ - selectColumn(-1); - selectRow(-1); -} - -void CollectionDetailsModel::loadCollection(const ModelNode &sourceNode, const QString &collection) -{ - QString fileName = CollectionEditorUtils::getSourceCollectionPath(sourceNode); - - CollectionReference newReference{sourceNode, collection}; - bool alreadyOpen = m_openedCollections.contains(newReference); - - if (alreadyOpen) { - if (m_currentCollection.reference() != newReference) { - deselectAll(); - beginResetModel(); - switchToCollection(newReference); - ensureSingleCell(); - endResetModel(); - } - } else { - deselectAll(); - switchToCollection(newReference); - loadJsonCollection(fileName, collection); - } -} - -void CollectionDetailsModel::removeCollection(const ModelNode &sourceNode, const QString &collection) -{ - CollectionReference collectionRef{sourceNode, collection}; - if (!m_openedCollections.contains(collectionRef)) - return; - - if (m_currentCollection.reference() == collectionRef) - loadCollection({}, {}); - - m_openedCollections.remove(collectionRef); -} - -void CollectionDetailsModel::removeAllCollections() -{ - loadCollection({}, {}); - m_openedCollections.clear(); -} - -void CollectionDetailsModel::renameCollection(const ModelNode &sourceNode, - const QString &oldName, - const QString &newName) -{ - CollectionReference oldRef{sourceNode, oldName}; - if (!m_openedCollections.contains(oldRef)) - return; - - CollectionReference newReference{sourceNode, newName}; - bool collectionIsSelected = m_currentCollection.reference() == oldRef; - CollectionDetails collection = m_openedCollections.take(oldRef); - collection.resetReference(newReference); - m_openedCollections.insert(newReference, collection); - - if (collectionIsSelected) - setCollectionName(newName); -} - -bool CollectionDetailsModel::saveDataStoreCollections() -{ - const ModelNode node = m_currentCollection.reference().node; - const Utils::FilePath path = CollectionEditorUtils::dataStoreJsonFilePath(); - Utils::FileReader fileData; - - if (!fileData.fetch(path)) { - qWarning() << Q_FUNC_INFO << "Cannot read the json file:" << fileData.errorString(); - return false; - } - - QJsonParseError jpe; - QJsonDocument document = QJsonDocument::fromJson(fileData.data(), &jpe); - - if (jpe.error == QJsonParseError::NoError) { - QJsonObject obj = document.object(); - - QList<CollectionDetails> collectionsToBeSaved; - for (CollectionDetails &openedCollection : m_openedCollections) { - const CollectionReference reference = openedCollection.reference(); - if (reference.node == node) { - obj.insert(reference.name, openedCollection.toLocalJson()); - collectionsToBeSaved << openedCollection; - } - } - - document.setObject(obj); - - if (CollectionEditorUtils::writeToJsonDocument(path, document)) { - const CollectionReference currentReference = m_currentCollection.reference(); - for (CollectionDetails &collection : collectionsToBeSaved) { - collection.markSaved(); - const CollectionReference reference = collection.reference(); - if (reference != currentReference) - closeCollectionIfSaved(reference); - } - return true; - } - } - return false; -} - -bool CollectionDetailsModel::exportCollection(const QUrl &url) -{ - using Core::EditorManager; - using Utils::FilePath; - using Utils::TextFileFormat; - - QTC_ASSERT(m_currentCollection.hasValidReference(), return false); - - bool saved = false; - const FilePath filePath = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() - : url.toString()); - const QString saveFormat = filePath.toFileInfo().suffix().toLower(); - const QString content = saveFormat == "csv" ? m_currentCollection.toCsv() - : m_currentCollection.toJson(); - - TextFileFormat textFileFormat; - textFileFormat.codec = EditorManager::defaultTextCodec(); - textFileFormat.lineTerminationMode = EditorManager::defaultLineEnding(); - QString errorMessage; - saved = textFileFormat.writeFile(filePath, content, &errorMessage); - - if (!saved) - qWarning() << Q_FUNC_INFO << "Unable to write file" << errorMessage; - - return saved; -} - -const CollectionDetails CollectionDetailsModel::upToDateConstCollection( - const CollectionReference &reference) const -{ - using Utils::FilePath; - using Utils::FileReader; - CollectionDetails collection; - - if (m_openedCollections.contains(reference)) { - collection = m_openedCollections.value(reference); - } else { - QUrl url = CollectionEditorUtils::getSourceCollectionPath(reference.node); - FilePath path = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() - : url.toString()); - FileReader file; - - if (!file.fetch(path)) - return collection; - - QJsonParseError jpe; - QJsonDocument document = QJsonDocument::fromJson(file.data(), &jpe); - - if (jpe.error != QJsonParseError::NoError) - return collection; - - collection = CollectionDetails::fromLocalJson(document, reference.name); - collection.resetReference(reference); - } - return collection; -} - -bool CollectionDetailsModel::collectionHasColumn(const CollectionReference &reference, - const QString &columnName) const -{ - const CollectionDetails collection = upToDateConstCollection(reference); - return collection.containsPropertyName(columnName); -} - -QString CollectionDetailsModel::getFirstColumnName(const CollectionReference &reference) const -{ - const CollectionDetails collection = upToDateConstCollection(reference); - return collection.propertyAt(0); -} - -void CollectionDetailsModel::updateEmpty() -{ - bool isEmptyNow = rowCount() == 0; - if (m_isEmpty != isEmptyNow) { - m_isEmpty = isEmptyNow; - emit isEmptyChanged(m_isEmpty); - } -} - -void CollectionDetailsModel::switchToCollection(const CollectionReference &collection) -{ - if (m_currentCollection.reference() == collection) - return; - - closeCurrentCollectionIfSaved(); - - if (!m_openedCollections.contains(collection)) - m_openedCollections.insert(collection, CollectionDetails(collection)); - - m_currentCollection = m_openedCollections.value(collection); - - setCollectionName(collection.name); -} - -void CollectionDetailsModel::closeCollectionIfSaved(const CollectionReference &collection) -{ - if (!m_openedCollections.contains(collection)) - return; - - const CollectionDetails &collectionDetails = m_openedCollections.value(collection); - - if (!collectionDetails.isChanged()) - m_openedCollections.remove(collection); -} - -void CollectionDetailsModel::closeCurrentCollectionIfSaved() -{ - if (m_currentCollection.hasValidReference()) { - closeCollectionIfSaved(m_currentCollection.reference()); - m_currentCollection = CollectionDetails{}; - } -} - -void CollectionDetailsModel::loadJsonCollection(const QString &filePath, const QString &collection) -{ - QJsonDocument document = readJsonFile(filePath); - - beginResetModel(); - m_currentCollection.resetData(document, collection); - ensureSingleCell(); - endResetModel(); -} - -void CollectionDetailsModel::ensureSingleCell() -{ - if (!m_currentCollection.hasValidReference()) - return; - - if (!columnCount()) - addColumn(0, "Column 1", "String"); - - if (!rowCount()) - insertRow(0); - - updateEmpty(); -} - -QJsonDocument CollectionDetailsModel::readJsonFile(const QUrl &url) -{ - using Utils::FilePath; - using Utils::FileReader; - FilePath path = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() : url.toString()); - FileReader file; - - if (!file.fetch(path)) { - emit warning(tr("File reading problem"), file.errorString()); - return {}; - } - - QJsonParseError jpe; - QJsonDocument document = QJsonDocument::fromJson(file.data(), &jpe); - - if (jpe.error != QJsonParseError::NoError) - emit warning(tr("Json parse error"), jpe.errorString()); - - return document; -} - -void CollectionDetailsModel::setCollectionName(const QString &newCollectionName) -{ - if (m_collectionName != newCollectionName) { - m_collectionName = newCollectionName; - emit this->collectionNameChanged(m_collectionName); - } -} - -QString CollectionDetailsModel::warningToString(DataTypeWarning::Warning warning) const -{ - return DataTypeWarning::getDataTypeWarningString(warning); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h deleted file mode 100644 index 24a040cce6..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailsmodel.h +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "collectiondetails.h" - -#include <QAbstractTableModel> -#include <QHash> - -namespace QmlDesigner { - -class ModelNode; - -class CollectionDetailsModel : public QAbstractTableModel -{ - Q_OBJECT - - Q_PROPERTY(QString collectionName MEMBER m_collectionName NOTIFY collectionNameChanged) - 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) - -public: - enum DataRoles { SelectedRole = Qt::UserRole + 1, DataTypeRole, ColumnDataTypeRole, DataTypeWarningRole }; - explicit CollectionDetailsModel(QObject *parent = nullptr); - - QHash<int, QByteArray> roleNames() const override; - int rowCount(const QModelIndex &parent = {}) const override; - int columnCount(const QModelIndex &parent = {}) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - bool setHeaderData(int section, - Qt::Orientation orientation, - const QVariant &value, - int role = Qt::EditRole) override; - bool insertRows(int row, int count, const QModelIndex &parent = {}) override; - bool removeColumns(int column, int count, const QModelIndex &parent = {}) override; - bool removeRows(int row, int count, const QModelIndex &parent = {}) override; - - Qt::ItemFlags flags(const QModelIndex &index) const override; - QVariant headerData(int section, - Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - - CollectionDetails::DataType propertyDataType(int column) const; - - int selectedColumn() const; - int selectedRow() const; - Q_INVOKABLE QString propertyName(int column) const; - Q_INVOKABLE QString propertyType(int column) const; - - Q_INVOKABLE bool isPropertyAvailable(const QString &name); - Q_INVOKABLE bool addColumn(int column, const QString &name, const QString &propertyType = {}); - Q_INVOKABLE bool selectColumn(int section); - Q_INVOKABLE bool renameColumn(int section, const QString &newValue); - Q_INVOKABLE bool setPropertyType(int column, const QString &newValue); - Q_INVOKABLE bool selectRow(int row); - Q_INVOKABLE void deselectAll(); - Q_INVOKABLE QString warningToString(DataTypeWarning::Warning warning) const; - - void loadCollection(const ModelNode &sourceNode, const QString &collection); - void removeCollection(const ModelNode &sourceNode, const QString &collection); - void removeAllCollections(); - void renameCollection(const ModelNode &sourceNode, const QString &oldName, const QString &newName); - - Q_INVOKABLE bool saveDataStoreCollections(); - Q_INVOKABLE bool exportCollection(const QUrl &url); - - const CollectionDetails upToDateConstCollection(const CollectionReference &reference) const; - bool collectionHasColumn(const CollectionReference &reference, const QString &columnName) const; - QString getFirstColumnName(const CollectionReference &reference) const; - -signals: - void collectionNameChanged(const QString &collectionName); - void selectedColumnChanged(int); - void selectedRowChanged(int); - void isEmptyChanged(bool); - void warning(const QString &title, const QString &body); - -private slots: - void updateEmpty(); - -private: - void switchToCollection(const CollectionReference &collection); - void closeCollectionIfSaved(const CollectionReference &collection); - void closeCurrentCollectionIfSaved(); - void setCollectionName(const QString &newCollectionName); - void loadJsonCollection(const QString &filePath, const QString &collection); - void ensureSingleCell(); - QJsonDocument readJsonFile(const QUrl &url); - - QHash<CollectionReference, CollectionDetails> m_openedCollections; - CollectionDetails m_currentCollection; - bool m_isEmpty = true; - int m_selectedColumn = -1; - int m_selectedRow = -1; - - QString m_collectionName; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp deleted file mode 100644 index f56bb36e88..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "collectiondetailssortfiltermodel.h" - -#include "collectiondetailsmodel.h" -#include "collectioneditorutils.h" - -#include <utils/qtcassert.h> - -namespace QmlDesigner { - -CollectionDetailsSortFilterModel::CollectionDetailsSortFilterModel(QObject *parent) - : QSortFilterProxyModel(parent) -{ - connect(this, &CollectionDetailsSortFilterModel::rowsInserted, - this, &CollectionDetailsSortFilterModel::updateRowCountChanges); - connect(this, &CollectionDetailsSortFilterModel::rowsRemoved, - this, &CollectionDetailsSortFilterModel::updateRowCountChanges); - connect(this, &CollectionDetailsSortFilterModel::modelReset, - this, &CollectionDetailsSortFilterModel::updateRowCountChanges); - - setDynamicSortFilter(true); -} - -void CollectionDetailsSortFilterModel::setSourceModel(CollectionDetailsModel *model) -{ - m_source = model; - Super::setSourceModel(model); - connect(m_source, &CollectionDetailsModel::selectedColumnChanged, - this, &CollectionDetailsSortFilterModel::updateSelectedColumn); - - connect(m_source, &CollectionDetailsModel::selectedRowChanged, - this, &CollectionDetailsSortFilterModel::updateSelectedRow); -} - -int CollectionDetailsSortFilterModel::selectedRow() const -{ - QTC_ASSERT(m_source, return -1); - - return mapFromSource(m_source->index(m_source->selectedRow(), 0)).row(); -} - -int CollectionDetailsSortFilterModel::selectedColumn() const -{ - QTC_ASSERT(m_source, return -1); - - return mapFromSource(m_source->index(0, m_source->selectedColumn())).column(); -} - -bool CollectionDetailsSortFilterModel::selectRow(int row) -{ - QTC_ASSERT(m_source, return false); - - return m_source->selectRow(mapToSource(index(row, 0)).row()); -} - -bool CollectionDetailsSortFilterModel::selectColumn(int column) -{ - QTC_ASSERT(m_source, return false); - - return m_source->selectColumn(mapToSource(index(0, column)).column()); -} - -CollectionDetailsSortFilterModel::~CollectionDetailsSortFilterModel() = default; - -bool CollectionDetailsSortFilterModel::filterAcceptsRow(int sourceRow, - const QModelIndex &sourceParent) const -{ - QTC_ASSERT(m_source, return false); - QModelIndex sourceIndex(m_source->index(sourceRow, 0, sourceParent)); - return sourceIndex.isValid(); -} - -bool CollectionDetailsSortFilterModel::lessThan(const QModelIndex &sourceleft, - const QModelIndex &sourceRight) const -{ - QTC_ASSERT(m_source, return false); - - if (sourceleft.column() == sourceRight.column()) { - int column = sourceleft.column(); - CollectionDetails::DataType columnType = m_source->propertyDataType(column); - return CollectionEditorUtils::variantIslessThan(sourceleft.data(), - sourceRight.data(), - columnType); - } - - return false; -} - -void CollectionDetailsSortFilterModel::updateEmpty() -{ - bool newValue = rowCount() == 0; - if (m_isEmpty != newValue) { - m_isEmpty = newValue; - emit isEmptyChanged(m_isEmpty); - } -} - -void CollectionDetailsSortFilterModel::updateSelectedRow() -{ - const int upToDateSelectedRow = selectedRow(); - if (m_selectedRow == upToDateSelectedRow) - return; - - const int rows = rowCount(); - const int columns = columnCount(); - const int previousRow = m_selectedRow; - - m_selectedRow = upToDateSelectedRow; - emit this->selectedRowChanged(m_selectedRow); - - auto notifySelectedDataChanged = [this, rows, columns](int notifyingRow) { - if (notifyingRow > -1 && notifyingRow < rows && columns) { - emit dataChanged(index(notifyingRow, 0), - index(notifyingRow, columns - 1), - {CollectionDetailsModel::SelectedRole}); - } - }; - - notifySelectedDataChanged(previousRow); - notifySelectedDataChanged(m_selectedRow); -} - -void CollectionDetailsSortFilterModel::updateSelectedColumn() -{ - const int upToDateSelectedColumn = selectedColumn(); - if (m_selectedColumn == upToDateSelectedColumn) - return; - - const int rows = rowCount(); - const int columns = columnCount(); - const int previousColumn = m_selectedColumn; - - m_selectedColumn = upToDateSelectedColumn; - emit this->selectedColumnChanged(m_selectedColumn); - - auto notifySelectedDataChanged = [this, rows, columns](int notifyingCol) { - if (notifyingCol > -1 && notifyingCol < columns && rows) { - emit dataChanged(index(0, notifyingCol), - index(rows - 1, notifyingCol), - {CollectionDetailsModel::SelectedRole}); - } - }; - - notifySelectedDataChanged(previousColumn); - notifySelectedDataChanged(m_selectedColumn); -} - -void CollectionDetailsSortFilterModel::updateRowCountChanges() -{ - updateEmpty(); - updateSelectedRow(); - invalidate(); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h deleted file mode 100644 index 93305f3ca2..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectiondetailssortfiltermodel.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include <QPointer> -#include <QSortFilterProxyModel> - -namespace QmlDesigner { - -class CollectionDetailsModel; - -class CollectionDetailsSortFilterModel : public QSortFilterProxyModel -{ - Q_OBJECT - - 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) - - using Super = QSortFilterProxyModel; - -public: - explicit CollectionDetailsSortFilterModel(QObject *parent = nullptr); - virtual ~CollectionDetailsSortFilterModel(); - - void setSourceModel(CollectionDetailsModel *model); - - int selectedRow() const; - int selectedColumn() const; - - Q_INVOKABLE bool selectRow(int row); - Q_INVOKABLE bool selectColumn(int column); - -signals: - void selectedColumnChanged(int); - void selectedRowChanged(int); - void isEmptyChanged(bool); - -protected: - using Super::setSourceModel; - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - bool lessThan(const QModelIndex &sourceleft, const QModelIndex &sourceRight) const override; - -private: - void updateEmpty(); - void updateSelectedRow(); - void updateSelectedColumn(); - void updateRowCountChanges(); - - QPointer<CollectionDetailsModel> m_source; - int m_selectedColumn = -1; - int m_selectedRow = -1; - bool m_isEmpty = true; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h deleted file mode 100644 index 76524762ed..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorconstants.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -namespace QmlDesigner::CollectionEditorConstants { - -enum class SourceFormat { Unknown, Json }; - -inline constexpr char SOURCEFILE_PROPERTY[] = "source"; -inline constexpr char ALLMODELS_PROPERTY[] = "allModels"; -inline constexpr char JSONCHILDMODELNAME_PROPERTY[] = "modelName"; - -inline constexpr char COLLECTIONMODEL_IMPORT[] = "QtQuick.Studio.Utils"; -inline constexpr char JSONCOLLECTIONMODEL_TYPENAME[] = "QtQuick.Studio.Utils.JsonListModel"; -inline constexpr char JSONCOLLECTIONCHILDMODEL_TYPENAME[] = "QtQuick.Studio.Utils.ChildListModel"; -inline constexpr char JSONBACKEND_TYPENAME[] = "JsonData"; - -} // namespace QmlDesigner::CollectionEditorConstants diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp deleted file mode 100644 index 4725987f12..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.cpp +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "collectioneditorutils.h" - -#include "model.h" -#include "nodemetainfo.h" -#include "propertymetainfo.h" - -#include <coreplugin/documentmanager.h> -#include <coreplugin/icore.h> -#include <projectexplorer/project.h> -#include <projectexplorer/projectexplorer.h> -#include <projectexplorer/projectmanager.h> -#include <qmljs/qmljsmodelmanagerinterface.h> -#include <utils/qtcassert.h> - -#include <variant> - -#include <QColor> -#include <QJsonArray> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonParseError> -#include <QJsonValue> -#include <QUrl> - -using DataType = QmlDesigner::CollectionDetails::DataType; - -namespace { - -using CollectionDataVariant = std::variant<QString, bool, double, int, QUrl, QColor>; - -inline bool operator<(const QColor &a, const QColor &b) -{ - return a.name(QColor::HexArgb) < b.name(QColor::HexArgb); -} - -inline CollectionDataVariant valueToVariant(const QVariant &value, DataType type) -{ - switch (type) { - case DataType::String: - return value.toString(); - case DataType::Real: - return value.toDouble(); - case DataType::Integer: - return value.toInt(); - case DataType::Boolean: - return value.toBool(); - case DataType::Color: - return value.value<QColor>(); - case DataType::Image: - case DataType::Url: - return value.value<QUrl>(); - default: - return false; - } -} - -struct LessThanVisitor -{ - template<typename T1, typename T2> - bool operator()(const T1 &a, const T2 &b) const - { - return CollectionDataVariant(a).index() < CollectionDataVariant(b).index(); - } - - template<typename T> - bool operator()(const T &a, const T &b) const - { - return a < b; - } -}; - -Utils::FilePath findFile(const Utils::FilePath &path, const QString &fileName) -{ - QDirIterator it(path.toString(), QDirIterator::Subdirectories); - - while (it.hasNext()) { - QFileInfo file(it.next()); - if (file.isDir()) - continue; - - if (file.fileName() == fileName) - return Utils::FilePath::fromFileInfo(file); - } - return {}; -} - -Utils::FilePath dataStoreDir() -{ - using Utils::FilePath; - ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::startupProject(); - - if (!currentProject) - return {}; - - return currentProject->projectDirectory().pathAppended("/imports/" - + currentProject->displayName()); -} - -inline Utils::FilePath collectionPath(const QString &filePath) -{ - return dataStoreDir().pathAppended("/" + filePath); -} - -inline Utils::FilePath qmlDirFilePath() -{ - return collectionPath("qmldir"); -} - -} // namespace - -namespace QmlDesigner::CollectionEditorUtils { - -bool variantIslessThan(const QVariant &a, const QVariant &b, DataType type) -{ - return std::visit(LessThanVisitor{}, valueToVariant(a, type), valueToVariant(b, type)); -} - -QString getSourceCollectionType(const ModelNode &node) -{ - using namespace QmlDesigner; - if (node.type() == CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME) - return "json"; - - return {}; -} - -Utils::FilePath dataStoreJsonFilePath() -{ - return collectionPath("models.json"); -} - -Utils::FilePath dataStoreQmlFilePath() -{ - return collectionPath("DataStore.qml"); -} - -bool canAcceptCollectionAsModel(const ModelNode &node) -{ - const NodeMetaInfo nodeMetaInfo = node.metaInfo(); - if (!nodeMetaInfo.isValid()) - return false; - - const PropertyMetaInfo modelProperty = nodeMetaInfo.property("model"); - if (!modelProperty.isValid()) - return false; - - return modelProperty.isWritable() && !modelProperty.isPrivate() - && modelProperty.propertyType().isVariant(); -} - -bool hasTextRoleProperty(const ModelNode &node) -{ - const NodeMetaInfo nodeMetaInfo = node.metaInfo(); - if (!nodeMetaInfo.isValid()) - return false; - - const PropertyMetaInfo textRoleProperty = nodeMetaInfo.property("textRole"); - if (!textRoleProperty.isValid()) - return false; - - return textRoleProperty.isWritable() && !textRoleProperty.isPrivate() - && textRoleProperty.propertyType().isString(); -} - -QString getSourceCollectionPath(const ModelNode &dataStoreNode) -{ - using Utils::FilePath; - if (!dataStoreNode.isValid()) - return {}; - - const FilePath expectedFile = dataStoreJsonFilePath(); - - if (expectedFile.exists()) - return expectedFile.toFSPathString(); - - return {}; -} - -bool isDataStoreNode(const ModelNode &dataStoreNode) -{ - using Utils::FilePath; - - if (!dataStoreNode.isValid()) - return false; - - const FilePath expectedFile = dataStoreQmlFilePath(); - - if (!expectedFile.exists()) - return false; - - FilePath modelPath = FilePath::fromUserInput(dataStoreNode.model()->fileUrl().toLocalFile()); - - return modelPath.isSameFile(expectedFile); -} - -bool ensureDataStoreExists(bool &justCreated) -{ - using Utils::FilePath; - using Utils::FileReader; - using Utils::FileSaver; - - FilePath qmlDestinationPath = dataStoreQmlFilePath(); - justCreated = false; - - auto extractDependency = [&justCreated](const FilePath &filePath) -> bool { - if (filePath.exists()) - return true; - - const QString templateFileName = filePath.fileName() + u".tpl"; - const FilePath templatePath = findFile(Core::ICore::resourcePath(), templateFileName); - if (!templatePath.exists()) { - qWarning() << Q_FUNC_INFO << __LINE__ << templateFileName << "does not exist"; - return false; - } - - if (!filePath.parentDir().ensureWritableDir()) { - qWarning() << Q_FUNC_INFO << __LINE__ << "Cannot create directory" - << filePath.parentDir(); - return false; - } - - if (templatePath.copyFile(filePath)) { - justCreated = true; - return true; - } - - qWarning() << Q_FUNC_INFO << __LINE__ << "Cannot copy" << templateFileName << "to" << filePath; - return false; - }; - - if (!extractDependency(dataStoreJsonFilePath())) - return false; - - if (!extractDependency(collectionPath("data.json"))) - return false; - - if (!extractDependency(collectionPath("JsonData.qml"))) - return false; - - if (!qmlDestinationPath.exists()) { - if (qmlDestinationPath.ensureExistingFile()) { - justCreated = true; - } else { - qWarning() << Q_FUNC_INFO << __LINE__ << "Can't create DataStore Qml File"; - return false; - } - } - - FilePath qmlDirPath = qmlDirFilePath(); - qmlDirPath.ensureExistingFile(); - - FileReader qmlDirReader; - if (!qmlDirReader.fetch(qmlDirPath)) { - qWarning() << Q_FUNC_INFO << __LINE__ << "Can't read the content of the qmldir"; - return false; - } - - QByteArray qmlDirContent = qmlDirReader.data(); - const QList<QByteArray> qmlDirLines = qmlDirContent.split('\n'); - for (const QByteArray &line : qmlDirLines) { - if (line.startsWith("singleton DataStore ")) - return true; - } - - if (!qmlDirContent.isEmpty() && qmlDirContent.back() != '\n') - qmlDirContent.append("\n"); - qmlDirContent.append("singleton DataStore 1.0 DataStore.qml\n"); - - FileSaver qmlDirSaver(qmlDirPath); - qmlDirSaver.write(qmlDirContent); - - if (qmlDirSaver.finalize()) { - justCreated = true; - return true; - } - - qWarning() << Q_FUNC_INFO << __LINE__ << "Can't write to the qmldir file"; - return false; -} - -QJsonObject defaultCollection() -{ - QJsonObject collectionObject; - - QJsonArray columns; - QJsonObject defaultColumn; - defaultColumn.insert("name", "Column 1"); - defaultColumn.insert("type", "string"); - columns.append(defaultColumn); - - QJsonArray collectionData; - QJsonArray cellData; - cellData.append(QString{}); - collectionData.append(cellData); - - collectionObject.insert("columns", columns); - collectionObject.insert("data", collectionData); - - return collectionObject; -} - -QJsonObject defaultColorCollection() -{ - using Utils::FilePath; - using Utils::FileReader; - const FilePath templatePath = findFile(Core::ICore::resourcePath(), "Colors.json.tpl"); - - FileReader fileReader; - if (!fileReader.fetch(templatePath)) { - qWarning() << Q_FUNC_INFO << __LINE__ << "Can't read the content of the file" << templatePath; - return {}; - } - - QJsonParseError parseError; - const CollectionDetails collection = CollectionDetails::fromImportedJson(fileReader.data(), - &parseError); - if (parseError.error != QJsonParseError::NoError) { - qWarning() << Q_FUNC_INFO << __LINE__ << "Error in template file" << parseError.errorString(); - return {}; - } - - return collection.toLocalJson(); -} - -bool writeToJsonDocument(const Utils::FilePath &path, const QJsonDocument &document, QString *errorString) -{ - Core::FileChangeBlocker fileBlocker(path); - Utils::FileSaver jsonFile(path); - if (jsonFile.write(document.toJson())) - jsonFile.finalize(); - if (errorString) - *errorString = jsonFile.errorString(); - - return !jsonFile.hasError(); -} - -} // namespace QmlDesigner::CollectionEditorUtils diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h b/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h deleted file mode 100644 index 355addf59b..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectioneditorutils.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "collectiondetails.h" -#include "collectioneditorconstants.h" - -QT_BEGIN_NAMESPACE -class QJsonArray; -class QJsonObject; -QT_END_NAMESPACE - -namespace Utils { -class FilePath; -} - -namespace QmlDesigner::CollectionEditorUtils { - -bool variantIslessThan(const QVariant &a, const QVariant &b, CollectionDetails::DataType type); - -QString getSourceCollectionType(const QmlDesigner::ModelNode &node); - -QString getSourceCollectionPath(const QmlDesigner::ModelNode &dataStoreNode); - -Utils::FilePath dataStoreJsonFilePath(); - -Utils::FilePath dataStoreQmlFilePath(); - -bool writeToJsonDocument(const Utils::FilePath &path, - const QJsonDocument &document, - QString *errorString = nullptr); - -bool isDataStoreNode(const ModelNode &dataStoreNode); - -bool ensureDataStoreExists(bool &justCreated); - -bool canAcceptCollectionAsModel(const ModelNode &node); - -bool hasTextRoleProperty(const ModelNode &node); - -QJsonObject defaultCollection(); - -QJsonObject defaultColorCollection(); - -} // namespace QmlDesigner::CollectionEditorUtils diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp deleted file mode 100644 index d27a077d2a..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.cpp +++ /dev/null @@ -1,521 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "collectionlistmodel.h" - -#include "collectioneditorutils.h" - -#include <utils/algorithm.h> -#include <utils/fileutils.h> -#include <utils/qtcassert.h> - -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonParseError> - -namespace { - -template<typename ValueType> -bool containsItem(const std::initializer_list<ValueType> &container, const ValueType &value) -{ - auto begin = std::cbegin(container); - auto end = std::cend(container); - - auto it = std::find(begin, end, value); - return it != end; -} - -bool sameCollectionNames(QStringList a, QStringList b) -{ - if (a.size() != b.size()) - return false; - - a.sort(Qt::CaseSensitive); - b.sort(Qt::CaseSensitive); - - return a == b; -} - -} // namespace - -namespace QmlDesigner { - -CollectionListModel::CollectionListModel() - : QAbstractListModel() -{ - connect(this, &CollectionListModel::modelReset, this, &CollectionListModel::updateEmpty); - connect(this, &CollectionListModel::rowsRemoved, this, &CollectionListModel::updateEmpty); - connect(this, &CollectionListModel::rowsInserted, this, &CollectionListModel::updateEmpty); -} - -QHash<int, QByteArray> CollectionListModel::roleNames() const -{ - static QHash<int, QByteArray> roles; - if (roles.isEmpty()) { - roles.insert(Super::roleNames()); - roles.insert({ - {IdRole, "collectionId"}, - {NameRole, "collectionName"}, - {SelectedRole, "collectionIsSelected"}, - }); - } - return roles; -} - -int CollectionListModel::rowCount([[maybe_unused]] const QModelIndex &parent) const -{ - return m_data.count(); -} - -bool CollectionListModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid()) - return false; - - if (containsItem<int>({Qt::EditRole, Qt::DisplayRole, NameRole}, role)) { - if (collectionExists(value.toString())) - return false; - - QString oldName = collectionNameAt(index.row()); - bool nameChanged = value != data(index); - if (nameChanged) { - QString newName = value.toString(); - QString errorString; - if (renameCollectionInDataStore(oldName, newName, errorString)) { - m_data.replace(index.row(), newName); - emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole, NameRole}); - emit this->collectionNameChanged(oldName, newName); - if (m_selectedCollectionName == oldName) - updateSelectedCollectionName(); - return true; - } else { - emit warning("Rename Model", errorString); - return false; - } - } - } else if (role == SelectedRole) { - if (value.toBool() != index.data(SelectedRole).toBool()) { - setSelectedIndex(value.toBool() ? index.row() : -1); - return true; - } - } - return false; -} - -bool CollectionListModel::removeRows(int row, int count, const QModelIndex &parent) -{ - const int rows = rowCount(parent); - if (row >= rows) - return false; - - row = qBound(0, row, rows - 1); - count = qBound(0, count, rows - row); - - if (count < 1) - return false; - - QString errorString; - QStringList removedCollections = m_data.mid(row, count); - if (removeCollectionsFromDataStore(removedCollections, errorString)) { - beginRemoveRows(parent, row, row + count - 1); - m_data.remove(row, count); - endRemoveRows(); - - emit collectionsRemoved(removedCollections); - if (m_selectedIndex >= row) { - int preferredIndex = m_selectedIndex - count; - if (preferredIndex < 0) // If the selected item is deleted, reset selection - selectCollectionIndex(-1); - selectCollectionIndex(preferredIndex, true); - } - - updateSelectedCollectionName(); - return true; - } else { - emit warning("Remove Model", errorString); - return false; - } -} - -QVariant CollectionListModel::data(const QModelIndex &index, int role) const -{ - QTC_ASSERT(index.isValid(), return {}); - - switch (role) { - case IdRole: - return index.row(); - case SelectedRole: - return index.row() == m_selectedIndex; - case NameRole: - default: - return m_data.at(index.row()); - } -} - -void CollectionListModel::setDataStoreNode(const ModelNode &dataStoreNode) -{ - m_dataStoreNode = dataStoreNode; - update(); -} - -int CollectionListModel::selectedIndex() const -{ - return m_selectedIndex; -} - -ModelNode CollectionListModel::sourceNode() const -{ - return m_dataStoreNode; -} - -bool CollectionListModel::collectionExists(const QString &collectionName) const -{ - return m_data.contains(collectionName); -} - -QStringList CollectionListModel::collections() const -{ - return m_data; -} - -QString CollectionListModel::getUniqueCollectionName(const QString &baseName) const -{ - QString name = baseName.isEmpty() ? "Model" : baseName; - QString nameTemplate = name + "%1"; - - int num = 0; - - while (collectionExists(name)) - name = nameTemplate.arg(++num, 2, 10, QChar('0')); - - return name; -} - -void CollectionListModel::selectCollectionIndex(int idx, bool selectAtLeastOne) -{ - int collectionCount = m_data.size(); - int preferredIndex = -1; - if (collectionCount) { - if (selectAtLeastOne) - preferredIndex = std::max(0, std::min(idx, collectionCount - 1)); - else if (idx > -1 && idx < collectionCount) - preferredIndex = idx; - } - - setSelectedIndex(preferredIndex); -} - -void CollectionListModel::selectCollectionName(QString collectionName, bool selectAtLeastOne) -{ - int idx = m_data.indexOf(collectionName); - if (idx > -1) - selectCollectionIndex(idx); - else - selectCollectionIndex(selectedIndex(), selectAtLeastOne); - - collectionName = collectionNameAt(selectedIndex()); - if (m_selectedCollectionName == collectionName) - return; - - m_selectedCollectionName = collectionName; - emit selectedCollectionNameChanged(m_selectedCollectionName); -} - -QString CollectionListModel::collectionNameAt(int idx) const -{ - return index(idx).data(NameRole).toString(); -} - -QString CollectionListModel::selectedCollectionName() const -{ - return m_selectedCollectionName; -} - -void CollectionListModel::update() -{ - using Utils::FilePath; - using Utils::FileReader; - - FileReader sourceFile; - QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode); - FilePath path = FilePath::fromUserInput(sourceFileAddress); - bool fileRead = false; - if (path.exists()) { - fileRead = sourceFile.fetch(path); - if (!fileRead) - emit this->warning(tr("Model Editor"), - tr("Cannot read the dataStore file\n%1").arg(sourceFile.errorString())); - } - - QStringList collectionNames; - if (fileRead) { - QJsonParseError parseError; - QJsonDocument document = QJsonDocument::fromJson(sourceFile.data(), &parseError); - if (parseError.error != QJsonParseError::NoError) { - emit this->warning(tr("Model Editor"), - tr("There is an error in the JSON file.\n%1") - .arg(parseError.errorString())); - } else { - if (document.isObject()) - collectionNames = document.object().toVariantMap().keys(); - else - emit this->warning(tr("Model Editor"), tr("The JSON document be an object.")); - } - } - - if (!sameCollectionNames(m_data, collectionNames)) { - QString prevSelectedCollection = selectedIndex() > -1 ? m_data.at(selectedIndex()) - : QString(); - beginResetModel(); - m_data = collectionNames; - endResetModel(); - emit this->collectionNamesChanged(collections()); - selectCollectionName(prevSelectedCollection, true); - } -} - -bool CollectionListModel::addCollection(const QString &collectionName, - const QJsonObject &localCollection) -{ - if (collectionExists(collectionName)) { - emit warning(tr("Add Model"), tr("Model \"%1\" already exists.").arg(collectionName)); - return false; - } - - QString errorMessage; - if (addCollectionToDataStore(collectionName, localCollection, errorMessage)) { - int row = rowCount(); - beginInsertRows({}, row, row); - m_data.append(collectionName); - endInsertRows(); - - selectCollectionName(collectionName); - emit collectionAdded(collectionName); - return true; - } else { - emit warning(tr("Add Collection"), errorMessage); - } - return false; -} - -void CollectionListModel::setSelectedIndex(int idx) -{ - idx = (idx > -1 && idx < rowCount()) ? idx : -1; - - if (m_selectedIndex != idx) { - QModelIndex previousIndex = index(m_selectedIndex); - QModelIndex newIndex = index(idx); - - m_selectedIndex = idx; - - if (previousIndex.isValid()) - emit dataChanged(previousIndex, previousIndex, {SelectedRole}); - - if (newIndex.isValid()) - emit dataChanged(newIndex, newIndex, {SelectedRole}); - - emit selectedIndexChanged(idx); - updateSelectedCollectionName(); - } -} - -bool CollectionListModel::removeCollectionsFromDataStore(const QStringList &removedCollections, - QString &error) const -{ - using Utils::FilePath; - using Utils::FileReader; - auto setErrorAndReturn = [&error](const QString &msg) -> bool { - error = msg; - return false; - }; - - if (m_dataStoreNode.type() != CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME) - return setErrorAndReturn(tr("Invalid node type")); - - QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode); - - QFileInfo sourceFileInfo(sourceFileAddress); - if (!sourceFileInfo.isFile()) - return setErrorAndReturn(tr("The selected node has an invalid source address")); - - FilePath jsonPath = FilePath::fromUserInput(sourceFileAddress); - FileReader jsonFile; - if (!jsonFile.fetch(jsonPath)) { - return setErrorAndReturn(tr("Can't read file \"%1\".\n%2") - .arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString())); - } - - QJsonParseError parseError; - QJsonDocument document = QJsonDocument::fromJson(jsonFile.data(), &parseError); - if (parseError.error != QJsonParseError::NoError) { - return setErrorAndReturn(tr("\"%1\" is corrupted.\n%2") - .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString())); - } - - if (document.isObject()) { - QJsonObject rootObject = document.object(); - - for (const QString &collectionName : removedCollections) { - bool sourceContainsCollection = rootObject.contains(collectionName); - if (sourceContainsCollection) { - rootObject.remove(collectionName); - } else { - setErrorAndReturn(tr("The model group doesn't contain the model name (%1).") - .arg(sourceContainsCollection)); - } - } - - document.setObject(rootObject); - - if (CollectionEditorUtils::writeToJsonDocument(jsonPath, document)) { - error.clear(); - return true; - } else { - return setErrorAndReturn( - tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath())); - } - } else { - return setErrorAndReturn(tr("Local Json Document should be an object")); - } - - return false; -} - -bool CollectionListModel::renameCollectionInDataStore(const QString &oldName, - const QString &newName, - QString &error) -{ - using Utils::FilePath; - using Utils::FileReader; - using Utils::FileSaver; - - auto setErrorAndReturn = [&error](const QString &msg) -> bool { - error = msg; - return false; - }; - - if (m_dataStoreNode.type() != CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME) - return setErrorAndReturn(tr("Invalid node type")); - - QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode); - - QFileInfo sourceFileInfo(sourceFileAddress); - if (!sourceFileInfo.isFile()) - return setErrorAndReturn(tr("Selected node must have a valid source file address")); - - FilePath jsonPath = FilePath::fromUserInput(sourceFileAddress); - FileReader jsonFile; - if (!jsonFile.fetch(jsonPath)) { - return setErrorAndReturn( - tr("Can't read \"%1\".\n%2").arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString())); - } - - QJsonParseError parseError; - QJsonDocument document = QJsonDocument::fromJson(jsonFile.data(), &parseError); - if (parseError.error != QJsonParseError::NoError) { - return setErrorAndReturn(tr("\"%1\" is corrupted.\n%2") - .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString())); - } - - if (document.isObject()) { - QJsonObject rootObject = document.object(); - - bool collectionContainsOldName = rootObject.contains(oldName); - bool collectionContainsNewName = rootObject.contains(newName); - - if (!collectionContainsOldName) { - return setErrorAndReturn( - tr("The model group doesn't contain the old model name (%1).").arg(oldName)); - } - - if (collectionContainsNewName) { - return setErrorAndReturn( - tr("The model name \"%1\" already exists in the model group.").arg(newName)); - } - - QJsonValue oldValue = rootObject.value(oldName); - rootObject.insert(newName, oldValue); - rootObject.remove(oldName); - - document.setObject(rootObject); - - if (CollectionEditorUtils::writeToJsonDocument(jsonPath, document)) { - error.clear(); - return true; - } else { - return setErrorAndReturn( - tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath())); - } - } else { - return setErrorAndReturn(tr("Local Json Document should be an object")); - } - return false; -} - -bool CollectionListModel::addCollectionToDataStore(const QString &collectionName, - const QJsonObject &localCollection, - QString &errorString) const -{ - using Utils::FilePath; - using Utils::FileReader; - auto returnError = [&errorString](const QString &msg) -> bool { - errorString = msg; - return false; - }; - - if (collectionExists(collectionName)) - return returnError(tr("A model with the identical name already exists.")); - - QString sourceFileAddress = CollectionEditorUtils::getSourceCollectionPath(m_dataStoreNode); - - QFileInfo sourceFileInfo(sourceFileAddress); - if (!sourceFileInfo.isFile()) - return returnError(tr("Selected node must have a valid source file address")); - - FilePath jsonPath = FilePath::fromUserInput(sourceFileAddress); - FileReader jsonFile; - if (!jsonFile.fetch(jsonPath)) { - return returnError( - tr("Can't read \"%1\".\n%2").arg(sourceFileInfo.absoluteFilePath(), jsonFile.errorString())); - } - - QJsonParseError parseError; - QJsonDocument document = QJsonDocument::fromJson(jsonFile.data(), &parseError); - if (parseError.error != QJsonParseError::NoError) - return returnError(tr("\"%1\" is corrupted.\n%2") - .arg(sourceFileInfo.absoluteFilePath(), parseError.errorString())); - - if (document.isObject()) { - QJsonObject sourceObject = document.object(); - sourceObject.insert(collectionName, localCollection); - document.setObject(sourceObject); - - if (CollectionEditorUtils::writeToJsonDocument(jsonPath, document)) - return true; - else - return returnError(tr("Can't write to \"%1\".").arg(sourceFileInfo.absoluteFilePath())); - } else { - return returnError(tr("JSON document type should be an object containing models.")); - } -} - -void CollectionListModel::updateEmpty() -{ - bool isEmptyNow = m_data.isEmpty(); - if (m_isEmpty != isEmptyNow) { - m_isEmpty = isEmptyNow; - emit isEmptyChanged(m_isEmpty); - - if (m_isEmpty) - setSelectedIndex(-1); - } -} - -void CollectionListModel::updateSelectedCollectionName() -{ - QString selectedCollectionByIndex = collectionNameAt(selectedIndex()); - if (selectedCollectionByIndex != selectedCollectionName()) - selectCollectionName(selectedCollectionByIndex); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h b/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h deleted file mode 100644 index 7902fd5909..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionlistmodel.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include <QAbstractListModel> -#include <QHash> - -#include "modelnode.h" - -namespace QmlDesigner { - -class CollectionListModel : public QAbstractListModel -{ - Q_OBJECT - - Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) - Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) - Q_PROPERTY(QString selectedCollectionName - READ selectedCollectionName - WRITE selectCollectionName - NOTIFY selectedCollectionNameChanged) - -public: - enum Roles { IdRole = Qt::UserRole + 1, NameRole, SelectedRole }; - - explicit CollectionListModel(); - QHash<int, QByteArray> roleNames() const override; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role) override; - bool removeRows(int row, int count, const QModelIndex &parent = {}) override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - void setDataStoreNode(const ModelNode &dataStoreNode = {}); - - Q_INVOKABLE int selectedIndex() const; - Q_INVOKABLE ModelNode sourceNode() const; - Q_INVOKABLE bool collectionExists(const QString &collectionName) const; - Q_INVOKABLE QStringList collections() const; - Q_INVOKABLE QString getUniqueCollectionName(const QString &baseName = {}) const; - - void selectCollectionIndex(int idx, bool selectAtLeastOne = false); - void selectCollectionName(QString collectionName, bool selectAtLeastOne = false); - QString collectionNameAt(int idx) const; - QString selectedCollectionName() const; - - void update(); - bool addCollection(const QString &collectionName, const QJsonObject &localCollection); - -signals: - void selectedIndexChanged(int idx); - void isEmptyChanged(bool); - void collectionNameChanged(const QString &oldName, const QString &newName); - void collectionNamesChanged(const QStringList &collectionNames); - void collectionsRemoved(const QStringList &names); - void collectionAdded(const QString &name); - void selectedCollectionNameChanged(const QString &selectedCollectionName); - void warning(const QString &title, const QString &body); - -private: - void setSelectedIndex(int idx); - bool removeCollectionsFromDataStore(const QStringList &removedCollections, QString &error) const; - bool renameCollectionInDataStore(const QString &oldName, const QString &newName, QString &error); - bool addCollectionToDataStore(const QString &collectionName, - const QJsonObject &localCollection, - QString &errorString) const; - - void updateEmpty(); - void updateSelectedCollectionName(); - - using Super = QAbstractListModel; - int m_selectedIndex = -1; - bool m_isEmpty = false; - ModelNode m_dataStoreNode; - QString m_selectedCollectionName; - QStringList m_data; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp deleted file mode 100644 index f6ec821fde..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.cpp +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "collectionview.h" - -#include "collectiondatatypemodel.h" -#include "collectiondetailsmodel.h" -#include "collectioneditorconstants.h" -#include "collectioneditorutils.h" -#include "collectionlistmodel.h" -#include "collectionwidget.h" -#include "datastoremodelnode.h" -#include "designmodecontext.h" -#include "nodeabstractproperty.h" -#include "nodemetainfo.h" -#include "nodeproperty.h" -#include "qmldesignerplugin.h" -#include "variantproperty.h" - -#include <projectexplorer/project.h> -#include <projectexplorer/projectexplorer.h> -#include <projectexplorer/projectmanager.h> -#include <qmljs/qmljsmodelmanagerinterface.h> - -#include <coreplugin/icore.h> -#include <utils/algorithm.h> -#include <utils/qtcassert.h> - -#include <QTimer> - -namespace { - -bool isStudioCollectionModel(const QmlDesigner::ModelNode &node) -{ - return node.metaInfo().isQtQuickStudioUtilsJsonListModel(); -} - -inline void setVariantPropertyValue(const QmlDesigner::ModelNode &node, - const QmlDesigner::PropertyName &propertyName, - const QVariant &value) -{ - QmlDesigner::VariantProperty property = node.variantProperty(propertyName); - property.setValue(value); -} - -inline void setBindingPropertyExpression(const QmlDesigner::ModelNode &node, - const QmlDesigner::PropertyName &propertyName, - const QString &expression) -{ - QmlDesigner::BindingProperty property = node.bindingProperty(propertyName); - property.setExpression(expression); -} - -} // namespace - -namespace QmlDesigner { - -CollectionView::CollectionView(ExternalDependenciesInterface &externalDependencies) - : AbstractView(externalDependencies) - , m_dataStore(std::make_unique<DataStoreModelNode>()) - -{ - connect(ProjectExplorer::ProjectManager::instance(), - &ProjectExplorer::ProjectManager::startupProjectChanged, this, [this] { - resetDataStoreNode(); - if (m_widget.get()) - m_widget->collectionDetailsModel()->removeAllCollections(); - }); -} - -bool CollectionView::hasWidget() const -{ - return true; -} - -QmlDesigner::WidgetInfo CollectionView::widgetInfo() -{ - if (m_widget.isNull()) { - m_widget = new CollectionWidget(this); - m_widget->setMinimumSize(m_widget->minimumSizeHint()); - - auto collectionEditorContext = new Internal::CollectionEditorContext(m_widget.data()); - Core::ICore::addContextObject(collectionEditorContext); - CollectionListModel *listModel = m_widget->listModel().data(); - - connect(listModel, - &CollectionListModel::selectedCollectionNameChanged, - this, - [this](const QString &collection) { - m_widget->collectionDetailsModel()->loadCollection(dataStoreNode(), collection); - }); - - connect(listModel, &CollectionListModel::isEmptyChanged, this, [this](bool isEmpty) { - if (isEmpty) - m_widget->collectionDetailsModel()->loadCollection({}, {}); - }); - - connect(listModel, &CollectionListModel::modelReset, this, [this] { - CollectionListModel *listModel = m_widget->listModel().data(); - if (listModel->sourceNode() == m_dataStore->modelNode()) - m_dataStore->setCollectionNames(listModel->collections()); - }); - - connect(listModel, - &CollectionListModel::collectionAdded, - this, - [this](const QString &collectionName) { m_dataStore->addCollection(collectionName); }); - - connect(listModel, - &CollectionListModel::collectionNameChanged, - this, - [this](const QString &oldName, const QString &newName) { - m_dataStore->renameCollection(oldName, newName); - m_widget->collectionDetailsModel()->renameCollection(dataStoreNode(), - oldName, - newName); - }); - - connect(listModel, - &CollectionListModel::collectionsRemoved, - this, - [this](const QStringList &collectionNames) { - m_dataStore->removeCollections(collectionNames); - for (const QString &collectionName : collectionNames) { - m_widget->collectionDetailsModel()->removeCollection(dataStoreNode(), - collectionName); - } - }); - } - - return createWidgetInfo(m_widget.data(), - "CollectionEditor", - WidgetInfo::LeftPane, - 0, - tr("Model Editor [beta]"), - tr("Model Editor view")); -} - -void CollectionView::modelAttached(Model *model) -{ - AbstractView::modelAttached(model); - 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(); -} - -void CollectionView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList, - [[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList) -{ - QList<ModelNode> selectedCollectionNodes = Utils::filtered(selectedNodeList, - &isStudioCollectionModel); - - bool singleNonCollectionNodeSelected = selectedNodeList.size() == 1 - && selectedCollectionNodes.isEmpty(); - - bool singleSelectedHasModelProperty = false; - if (singleNonCollectionNodeSelected) { - const ModelNode selectedNode = selectedNodeList.first(); - singleSelectedHasModelProperty = CollectionEditorUtils::canAcceptCollectionAsModel( - selectedNode); - } - - m_widget->setTargetNodeSelected(singleSelectedHasModelProperty); - - // More than one model is selected. So ignore them - if (selectedCollectionNodes.size() > 1) - return; -} - -void CollectionView::customNotification(const AbstractView *, - const QString &identifier, - const QList<ModelNode> &nodeList, - const QList<QVariant> &data) -{ - if (identifier == QLatin1String("item_library_created_by_drop") && !nodeList.isEmpty()) - onItemLibraryNodeCreated(nodeList.first()); - else if (identifier == QLatin1String("open_collection_by_id") && !data.isEmpty()) - m_widget->openCollection(collectionNameFromDataStoreChildren(data.first().toByteArray())); - else if (identifier == "delete_selected_collection") - m_widget->deleteSelectedCollection(); -} - -void CollectionView::addResource(const QUrl &url, const QString &name) -{ - executeInTransaction(Q_FUNC_INFO, [this, &url, &name]() { - ensureStudioModelImport(); - QString sourceAddress; - if (url.isLocalFile()) { - Utils::FilePath fp = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName().parentDir(); - sourceAddress = Utils::FilePath::calcRelativePath(url.toLocalFile(), - fp.absoluteFilePath().toString()); - } else { - sourceAddress = url.toString(); - } -#ifdef QDS_USE_PROJECTSTORAGE - ModelNode resourceNode = createModelNode("JsonListModel"); -#else - const NodeMetaInfo resourceMetaInfo = jsonCollectionMetaInfo(); - ModelNode resourceNode = createModelNode(resourceMetaInfo.typeName(), - resourceMetaInfo.majorVersion(), - resourceMetaInfo.minorVersion()); -#endif - VariantProperty sourceProperty = resourceNode.variantProperty( - CollectionEditorConstants::SOURCEFILE_PROPERTY); - VariantProperty nameProperty = resourceNode.variantProperty("objectName"); - sourceProperty.setValue(sourceAddress); - nameProperty.setValue(name); - resourceNode.setIdWithoutRefactoring(model()->generateIdFromName(name, "model")); - rootModelNode().defaultNodeAbstractProperty().reparentHere(resourceNode); - }); -} - -void CollectionView::assignCollectionToNode(const QString &collectionName, const ModelNode &node) -{ - using DataType = CollectionDetails::DataType; - executeInTransaction("CollectionView::assignCollectionToNode", [&]() { - m_dataStore->assignCollectionToNode( - this, - node, - collectionName, - [&](const QString &collectionName, const QString &columnName) -> bool { - const CollectionReference reference{dataStoreNode(), collectionName}; - return m_widget->collectionDetailsModel()->collectionHasColumn(reference, columnName); - }, - [&](const QString &collectionName) -> QString { - const CollectionReference reference{dataStoreNode(), collectionName}; - return m_widget->collectionDetailsModel()->getFirstColumnName(reference); - }); - - // Create and assign a delegate to the list view item - if (node.metaInfo().isQtQuickListView()) { - CollectionDetails collection = m_widget->collectionDetailsModel()->upToDateConstCollection( - {dataStoreNode(), collectionName}); - - ModelNode rowItem(createModelNode("QtQuick.Row")); - ::setVariantPropertyValue(rowItem, "spacing", 5); - - const int columnsCount = collection.columns(); - for (int column = 0; column < columnsCount; ++column) { - const DataType dataType = collection.typeAt(column); - const QString columnName = collection.propertyAt(column); - ModelNode cellItem; - if (dataType == DataType::Color) { - cellItem = createModelNode("QtQuick.Rectangle"); - ::setBindingPropertyExpression(cellItem, "color", columnName); - ::setVariantPropertyValue(cellItem, "height", 20); - } else { - cellItem = createModelNode("QtQuick.Text"); - ::setBindingPropertyExpression(cellItem, "text", columnName); - } - ::setVariantPropertyValue(cellItem, "width", 100); - rowItem.defaultNodeAbstractProperty().reparentHere(cellItem); - } - - NodeProperty delegateProperty = node.nodeProperty("delegate"); - // Remove the old model node if is available - if (delegateProperty.modelNode()) - delegateProperty.modelNode().destroy(); - - delegateProperty.setModelNode(rowItem); - } - }); -} - -void CollectionView::assignCollectionToSelectedNode(const QString &collectionName) -{ - QTC_ASSERT(dataStoreNode() && hasSingleSelectedModelNode(), return); - assignCollectionToNode(collectionName, singleSelectedModelNode()); -} - -void CollectionView::addNewCollection(const QString &collectionName, const QJsonObject &localCollection) -{ - addTask(QSharedPointer<CollectionTask>( - new AddCollectionTask(this, m_widget->listModel(), localCollection, collectionName))); -} - -void CollectionView::openCollection(const QString &collectionName) -{ - m_widget->openCollection(collectionName); -} - -void CollectionView::registerDeclarativeType() -{ - CollectionDetails::registerDeclarativeType(); - CollectionDataTypeModel::registerDeclarativeType(); -} - -void CollectionView::resetDataStoreNode() -{ - m_dataStore->reloadModel(); - - ModelNode dataStore = m_dataStore->modelNode(); - if (!dataStore || m_widget->listModel()->sourceNode() == dataStore) - return; - - bool dataStoreSingletonFound = m_dataStoreTypeFound; - if (!dataStoreSingletonFound && rewriterView() && rewriterView()->isAttached()) { - const QList<QmlTypeData> types = rewriterView()->getQMLTypes(); - for (const QmlTypeData &cppTypeData : types) { - if (cppTypeData.isSingleton && cppTypeData.typeName == "DataStore") { - dataStoreSingletonFound = true; - break; - } - } - if (!dataStoreSingletonFound && !m_rewriterAmended) { - rewriterView()->forceAmend(); - m_rewriterAmended = true; - } - } - - if (dataStoreSingletonFound) { - m_widget->listModel()->setDataStoreNode(dataStore); - m_dataStoreTypeFound = true; - - while (!m_delayedTasks.isEmpty()) - m_delayedTasks.takeFirst()->process(); - } else if (++m_reloadCounter < 50) { - QTimer::singleShot(200, this, &CollectionView::resetDataStoreNode); - } else { - QTC_ASSERT(false, m_delayedTasks.clear()); - } -} - -ModelNode CollectionView::dataStoreNode() const -{ - return m_dataStore->modelNode(); -} - -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; - } - } -} - -QString CollectionView::collectionNameFromDataStoreChildren(const PropertyName &childPropertyName) const -{ - return dataStoreNode() - .nodeProperty(childPropertyName) - .modelNode() - .property(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY) - .toVariantProperty() - .value() - .toString(); -} - -NodeMetaInfo CollectionView::jsonCollectionMetaInfo() const -{ - return model()->metaInfo(CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME); -} - -void CollectionView::ensureStudioModelImport() -{ - executeInTransaction(__FUNCTION__, [&] { - Import import = Import::createLibraryImport(CollectionEditorConstants::COLLECTIONMODEL_IMPORT); - try { - if (!model()->hasImport(import, true, true)) - model()->changeImports({import}, {}); - } catch (const Exception &) { - QTC_ASSERT(false, return); - } - }); -} - -void CollectionView::onItemLibraryNodeCreated(const ModelNode &node) -{ - 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()) - m_delayedTasks << task; -} - -CollectionTask::CollectionTask(CollectionView *view, CollectionListModel *listModel) - : m_collectionView(view) - , m_listModel(listModel) -{} - -DropListViewTask::DropListViewTask(CollectionView *view, - CollectionListModel *listModel, - const ModelNode &node) - : CollectionTask(view, listModel) - , m_node(node) -{} - -void DropListViewTask::process() -{ - AbstractView *view = m_node.view(); - if (!m_node || !m_collectionView || !m_listModel || !view) - return; - - const QString newCollectionName = m_listModel->getUniqueCollectionName("ListModel"); - m_listModel->addCollection(newCollectionName, CollectionEditorUtils::defaultColorCollection()); - m_collectionView->openCollection(newCollectionName); - m_collectionView->assignCollectionToNode(newCollectionName, m_node); -} - -AddCollectionTask::AddCollectionTask(CollectionView *view, - CollectionListModel *listModel, - const QJsonObject &localJsonObject, - const QString &collectionName) - : CollectionTask(view, listModel) - , m_localJsonObject(localJsonObject) - , m_name(collectionName) -{} - -void AddCollectionTask::process() -{ - if (!m_listModel) - return; - - const QString newCollectionName = m_listModel->collectionExists(m_name) - ? m_listModel->getUniqueCollectionName(m_name) - : m_name; - - m_listModel->addCollection(newCollectionName, m_localJsonObject); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h b/src/plugins/qmldesigner/components/collectioneditor/collectionview.h deleted file mode 100644 index a4b16c4c27..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionview.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include "abstractview.h" -#include "datastoremodelnode.h" -#include "modelnode.h" - -#include <QJsonObject> - -namespace QmlJS { -class Document; -} - -namespace QmlDesigner { - -class CollectionDetails; -class CollectionListModel; -class CollectionTask; -class CollectionWidget; -class DataStoreModelNode; - -class CollectionView : public AbstractView -{ - Q_OBJECT - -public: - explicit CollectionView(ExternalDependenciesInterface &externalDependencies); - - bool hasWidget() const override; - WidgetInfo widgetInfo() override; - - void modelAttached(Model *model) override; - void modelAboutToBeDetached(Model *model) override; - - void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, - const QList<ModelNode> &lastSelectedNodeList) override; - - void customNotification(const AbstractView *view, - const QString &identifier, - const QList<ModelNode> &nodeList, - const QList<QVariant> &data) override; - - void addResource(const QUrl &url, const QString &name); - - void assignCollectionToNode(const QString &collectionName, const ModelNode &node); - void assignCollectionToSelectedNode(const QString &collectionName); - void addNewCollection(const QString &collectionName, const QJsonObject &localCollection); - - void openCollection(const QString &collectionName); - - static void registerDeclarativeType(); - - void resetDataStoreNode(); - ModelNode dataStoreNode() const; - void ensureDataStoreExists(); - QString collectionNameFromDataStoreChildren(const PropertyName &childPropertyName) const; - -private: - friend class CollectionTask; - - NodeMetaInfo jsonCollectionMetaInfo() const; - 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; - 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; -}; - -class CollectionTask -{ -public: - CollectionTask(CollectionView *view, CollectionListModel *listModel); - CollectionTask() = delete; - virtual ~CollectionTask() = default; - - virtual void process() = 0; - -protected: - QPointer<CollectionView> m_collectionView; - QPointer<CollectionListModel> m_listModel; -}; - -class DropListViewTask : public CollectionTask -{ -public: - DropListViewTask(CollectionView *view, CollectionListModel *listModel, const ModelNode &node); - - void process() override; - -private: - ModelNode m_node; -}; - -class AddCollectionTask : public CollectionTask -{ -public: - AddCollectionTask(CollectionView *view, - CollectionListModel *listModel, - const QJsonObject &localJsonObject, - const QString &collectionName); - - void process() override; - -private: - QJsonObject m_localJsonObject; - QString m_name; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp deleted file mode 100644 index 093729dc67..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.cpp +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "collectionwidget.h" - -#include "collectiondetails.h" -#include "collectiondetailsmodel.h" -#include "collectiondetailssortfiltermodel.h" -#include "collectioneditorutils.h" -#include "collectionlistmodel.h" -#include "collectionview.h" -#include "designmodewidget.h" -#include "qmldesignerconstants.h" -#include "qmldesignerplugin.h" -#include "theme.h" - -#include <coreplugin/icore.h> -#include <coreplugin/messagebox.h> -#include <studioquickwidget.h> - -#include <QFileInfo> -#include <QJsonArray> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonParseError> -#include <QMetaObject> -#include <QQmlEngine> -#include <QQuickItem> -#include <QShortcut> -#include <QVBoxLayout> - -namespace { - -QString collectionViewResourcesPath() -{ -#ifdef SHARE_QML_PATH - if (qEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) - return QLatin1String(SHARE_QML_PATH) + "/collectionEditorQmlSource"; -#endif - return Core::ICore::resourcePath("qmldesigner/collectionEditorQmlSource").toString(); -} - -QString getPreferredCollectionName(const QUrl &url, const QString &collectionName) -{ - if (collectionName.isEmpty()) { - QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString()); - return fileInfo.completeBaseName(); - } - - return collectionName; -} - -} // namespace - -namespace QmlDesigner { -CollectionWidget::CollectionWidget(CollectionView *view) - : QFrame() - , m_view(view) - , m_listModel(new CollectionListModel) - , m_collectionDetailsModel(new CollectionDetailsModel) - , m_collectionDetailsSortFilterModel(std::make_unique<CollectionDetailsSortFilterModel>()) - , m_quickWidget(new StudioQuickWidget(this)) -{ - setWindowTitle(tr("Model Editor", "Title of model editor widget")); - - Core::IContext *icontext = nullptr; - Core::Context context(Constants::C_QMLCOLLECTIONEDITOR); - icontext = new Core::IContext(this); - icontext->setContext(context); - icontext->setWidget(this); - - connect(m_listModel, &CollectionListModel::warning, this, &CollectionWidget::warn); - - m_collectionDetailsSortFilterModel->setSourceModel(m_collectionDetailsModel); - - m_quickWidget->quickWidget()->setObjectName(Constants::OBJECT_NAME_COLLECTION_EDITOR); - m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); - m_quickWidget->engine()->addImportPath(collectionViewResourcesPath() + "/imports"); - m_quickWidget->setClearColor(Theme::getColor(Theme::Color::DSpanelBackground)); - - Theme::setupTheme(m_quickWidget->engine()); - m_quickWidget->quickWidget()->installEventFilter(this); - - auto layout = new QVBoxLayout(this); - layout->setContentsMargins({}); - layout->setSpacing(0); - layout->addWidget(m_quickWidget.data()); - - qmlRegisterAnonymousType<CollectionWidget>("CollectionEditorBackend", 1); - auto map = m_quickWidget->registerPropertyMap("CollectionEditorBackend"); - map->setProperties({ - {"rootView", QVariant::fromValue(this)}, - {"model", QVariant::fromValue(m_listModel.data())}, - {"collectionDetailsModel", QVariant::fromValue(m_collectionDetailsModel.data())}, - {"collectionDetailsSortFilterModel", - QVariant::fromValue(m_collectionDetailsSortFilterModel.get())}, - }); - - auto hotReloadShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_F4), this); - connect(hotReloadShortcut, &QShortcut::activated, this, &CollectionWidget::reloadQmlSource); - - reloadQmlSource(); - - QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_MODELEDITOR_TIME); -} - -void CollectionWidget::contextHelp(const Core::IContext::HelpCallback &callback) const -{ - if (m_view) - QmlDesignerPlugin::contextHelp(callback, m_view->contextHelpId()); - else - callback({}); -} - -QPointer<CollectionListModel> CollectionWidget::listModel() const -{ - return m_listModel; -} - -QPointer<CollectionDetailsModel> CollectionWidget::collectionDetailsModel() const -{ - return m_collectionDetailsModel; -} - -void CollectionWidget::reloadQmlSource() -{ - const QString collectionViewQmlPath = collectionViewResourcesPath() + "/CollectionView.qml"; - - QTC_ASSERT(QFileInfo::exists(collectionViewQmlPath), return); - - m_quickWidget->setSource(QUrl::fromLocalFile(collectionViewQmlPath)); - - if (!m_quickWidget->rootObject()) { - QString errorString; - const auto errors = m_quickWidget->errors(); - for (const QQmlError &error : errors) - errorString.append("\n" + error.toString()); - - Core::AsynchronousMessageBox::warning(tr("Cannot Create QtQuick View"), - tr("StatesEditorWidget: %1 cannot be created.%2") - .arg(collectionViewQmlPath, errorString)); - return; - } -} - -QSize CollectionWidget::minimumSizeHint() const -{ - return {300, 300}; -} - -bool CollectionWidget::loadJsonFile(const QUrl &url, const QString &collectionName) -{ - if (!isJsonFile(url)) - return false; - - m_view->addResource(url, getPreferredCollectionName(url, collectionName)); - - return true; -} - -bool CollectionWidget::loadCsvFile(const QUrl &url, const QString &collectionName) -{ - m_view->addResource(url, getPreferredCollectionName(url, collectionName)); - - return true; -} - -bool CollectionWidget::isJsonFile(const QUrl &url) const -{ - Utils::FilePath filePath = Utils::FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() - : url.toString()); - Utils::FileReader file; - if (!file.fetch(filePath)) - return false; - - QJsonParseError error; - QJsonDocument::fromJson(file.data(), &error); - if (error.error) - return false; - - return true; -} - -bool CollectionWidget::isCsvFile(const QUrl &url) const -{ - QString filePath = url.isLocalFile() ? url.toLocalFile() : url.toString(); - QFileInfo fileInfo(filePath); - return fileInfo.exists() && !fileInfo.suffix().compare("csv", Qt::CaseInsensitive); -} - -bool CollectionWidget::isValidUrlToImport(const QUrl &url) const -{ - using Utils::FilePath; - FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() - : url.toString()); - if (fileInfo.suffix() == "json") - return isJsonFile(url); - - if (fileInfo.suffix() == "csv") - return isCsvFile(url); - - return false; -} - -bool CollectionWidget::importFile(const QString &collectionName, - const QUrl &url, - const bool &firstRowIsHeader) -{ - using Utils::FilePath; - - FilePath fileInfo = FilePath::fromUserInput(url.isLocalFile() ? url.toLocalFile() - : url.toString()); - CollectionDetails loadedCollection; - QByteArray fileContent; - - auto loadUrlContent = [&]() -> bool { - Utils::FileReader file; - if (file.fetch(fileInfo)) { - fileContent = file.data(); - return true; - } - - warn(tr("Import from file"), tr("Cannot import from file \"%1\"").arg(fileInfo.fileName())); - return false; - }; - - if (fileInfo.suffix() == "json") { - if (!loadUrlContent()) - return false; - - QJsonParseError parseError; - loadedCollection = CollectionDetails::fromImportedJson(fileContent, &parseError); - if (parseError.error != QJsonParseError::NoError) { - warn(tr("Json file Import error"), - tr("Cannot parse json content\n%1").arg(parseError.errorString())); - } - } else if (fileInfo.suffix() == "csv") { - if (!loadUrlContent()) - return false; - loadedCollection = CollectionDetails::fromImportedCsv(fileContent, firstRowIsHeader); - } - - if (loadedCollection.columns()) { - m_view->addNewCollection(collectionName, loadedCollection.toLocalJson()); - return true; - } else { - warn(tr("Can not add a model to the JSON file"), - tr("The imported model is empty or is not supported.")); - } - return false; -} - -void CollectionWidget::addCollectionToDataStore(const QString &collectionName) -{ - m_view->addNewCollection(collectionName, CollectionEditorUtils::defaultCollection()); -} - -void CollectionWidget::assignCollectionToSelectedNode(const QString collectionName) -{ - m_view->assignCollectionToSelectedNode(collectionName); -} - -void CollectionWidget::openCollection(const QString &collectionName) -{ - m_listModel->selectCollectionName(collectionName); - QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("CollectionEditor", true); -} - -ModelNode CollectionWidget::dataStoreNode() const -{ - return m_view->dataStoreNode(); -} - -void CollectionWidget::warn(const QString &title, const QString &body) -{ - QMetaObject::invokeMethod(m_quickWidget->rootObject(), - "showWarning", - Q_ARG(QVariant, title), - Q_ARG(QVariant, body)); -} - -void CollectionWidget::setTargetNodeSelected(bool selected) -{ - if (m_targetNodeSelected == selected) - return; - - m_targetNodeSelected = selected; - emit targetNodeSelectedChanged(m_targetNodeSelected); -} - -void CollectionWidget::deleteSelectedCollection() -{ - QMetaObject::invokeMethod(m_quickWidget->quickWidget()->rootObject(), "deleteSelectedCollection"); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h b/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h deleted file mode 100644 index 0957bd81e0..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/collectionwidget.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include <QFrame> - -#include <coreplugin/icontext.h> - -class StudioQuickWidget; - -namespace QmlDesigner { - -class CollectionDetailsModel; -class CollectionDetailsSortFilterModel; -class CollectionListModel; -class CollectionView; -class ModelNode; - -class CollectionWidget : public QFrame -{ - Q_OBJECT - - Q_PROPERTY(bool targetNodeSelected MEMBER m_targetNodeSelected NOTIFY targetNodeSelectedChanged) - -public: - CollectionWidget(CollectionView *view); - void contextHelp(const Core::IContext::HelpCallback &callback) const; - - QPointer<CollectionListModel> listModel() const; - QPointer<CollectionDetailsModel> collectionDetailsModel() const; - - void reloadQmlSource(); - - virtual QSize minimumSizeHint() const; - - Q_INVOKABLE bool loadJsonFile(const QUrl &url, const QString &collectionName = {}); - Q_INVOKABLE bool loadCsvFile(const QUrl &url, const QString &collectionName = {}); - Q_INVOKABLE bool isJsonFile(const QUrl &url) const; - Q_INVOKABLE bool isCsvFile(const QUrl &url) const; - Q_INVOKABLE bool isValidUrlToImport(const QUrl &url) const; - - Q_INVOKABLE bool importFile(const QString &collectionName, - const QUrl &url, - const bool &firstRowIsHeader = true); - - Q_INVOKABLE void addCollectionToDataStore(const QString &collectionName); - Q_INVOKABLE void assignCollectionToSelectedNode(const QString collectionName); - Q_INVOKABLE void openCollection(const QString &collectionName); - Q_INVOKABLE ModelNode dataStoreNode() const; - - void warn(const QString &title, const QString &body); - void setTargetNodeSelected(bool selected); - - void deleteSelectedCollection(); - -signals: - void targetNodeSelectedChanged(bool); - -private: - QString generateUniqueCollectionName(const ModelNode &node, const QString &name); - - QPointer<CollectionView> m_view; - QPointer<CollectionListModel> m_listModel; - QPointer<CollectionDetailsModel> m_collectionDetailsModel; - std::unique_ptr<CollectionDetailsSortFilterModel> m_collectionDetailsSortFilterModel; - QScopedPointer<StudioQuickWidget> m_quickWidget; - bool m_targetNodeSelected = false; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp deleted file mode 100644 index 5be9c20f9e..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.cpp +++ /dev/null @@ -1,511 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "datastoremodelnode.h" - -#include "abstractview.h" -#include "collectioneditorconstants.h" -#include "collectioneditorutils.h" -#include "model/qmltextgenerator.h" -#include "plaintexteditmodifier.h" -#include "qmldesignerbase/qmldesignerbaseplugin.h" -#include "qmldesignerexternaldependencies.h" -#include "rewriterview.h" - -#include <model.h> -#include <nodemetainfo.h> -#include <nodeproperty.h> -#include <variantproperty.h> - -#include <qmljstools/qmljscodestylepreferences.h> -#include <qmljstools/qmljstoolssettings.h> - -#include <coreplugin/documentmanager.h> -#include <projectexplorer/project.h> -#include <projectexplorer/projectexplorer.h> -#include <projectexplorer/projectmanager.h> - -#include <utils/fileutils.h> -#include <utils/qtcassert.h> - -#include <QPlainTextEdit> -#include <QRegularExpression> -#include <QRegularExpressionMatch> -#include <QScopedPointer> - -namespace { - -inline constexpr char CHILDLISTMODEL_TYPENAME[] = "ChildListModel"; - -QmlDesigner::PropertyNameList createNameList(const QmlDesigner::ModelNode &node) -{ - using QmlDesigner::AbstractProperty; - using QmlDesigner::PropertyName; - using QmlDesigner::PropertyNameList; - static PropertyNameList defaultsNodeProps = { - "id", - QmlDesigner::CollectionEditorConstants::SOURCEFILE_PROPERTY, - QmlDesigner::CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY, - "backend"}; - PropertyNameList dynamicPropertyNames = Utils::transform( - node.dynamicProperties(), - [](const AbstractProperty &property) -> PropertyName { return property.name(); }); - - Utils::sort(dynamicPropertyNames); - - return defaultsNodeProps + dynamicPropertyNames; -} - -bool isValidCollectionPropertyName(const QString &collectionId) -{ - static const QmlDesigner::PropertyNameList reservedKeywords = { - QmlDesigner::CollectionEditorConstants::SOURCEFILE_PROPERTY, - QmlDesigner::CollectionEditorConstants::JSONBACKEND_TYPENAME, - "backend", - "models", - }; - - return QmlDesigner::ModelNode::isValidId(collectionId) - && !reservedKeywords.contains(collectionId.toLatin1()); -} - -QMap<QString, QmlDesigner::PropertyName> getModelIdMap(const QmlDesigner::ModelNode &rootNode) -{ - using namespace QmlDesigner; - QMap<QString, PropertyName> modelNameForId; - - const QList<AbstractProperty> propertyNames = rootNode.dynamicProperties(); - - for (const AbstractProperty &property : std::as_const(propertyNames)) { - if (!property.isNodeProperty()) - continue; - - NodeProperty nodeProperty = property.toNodeProperty(); - if (!nodeProperty.hasDynamicTypeName(CHILDLISTMODEL_TYPENAME)) - continue; - - ModelNode childNode = nodeProperty.modelNode(); - if (childNode.hasProperty(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY)) { - QString modelName = childNode - .property(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY) - .toVariantProperty() - .value() - .toString(); - - if (!modelName.isEmpty()) - modelNameForId.insert(modelName, property.name()); - } - } - return modelNameForId; -} - -void setQmlContextToModel(QmlDesigner::Model *model, const QString &qmlContext) -{ - using namespace QmlDesigner; - Q_ASSERT(model); - - QScopedPointer<QPlainTextEdit> textEdit(new QPlainTextEdit); - QScopedPointer<NotIndentingTextEditModifier> modifier( - new NotIndentingTextEditModifier(textEdit.data())); - textEdit->hide(); - textEdit->setPlainText(qmlContext); - QmlDesigner::ExternalDependencies externalDependencies{QmlDesignerBasePlugin::settings()}; - QScopedPointer<RewriterView> rewriter( - new RewriterView(externalDependencies, QmlDesigner::RewriterView::Validate)); - - rewriter->setParent(model); - rewriter->setTextModifier(modifier.get()); - rewriter->setCheckSemanticErrors(false); - - model->attachView(rewriter.get()); - model->detachView(rewriter.get()); -} - -} // namespace - -namespace QmlDesigner { - -DataStoreModelNode::DataStoreModelNode() -{ - reloadModel(); -} - -void DataStoreModelNode::reloadModel() -{ - using Utils::FilePath; - if (!ProjectExplorer::ProjectManager::startupProject()) { - reset(); - return; - } - bool forceUpdate = false; - - const FilePath dataStoreQmlPath = CollectionEditorUtils::dataStoreQmlFilePath(); - const FilePath dataStoreJsonPath = CollectionEditorUtils::dataStoreJsonFilePath(); - QUrl dataStoreQmlUrl = dataStoreQmlPath.toUrl(); - - if (dataStoreQmlPath.exists() && dataStoreJsonPath.exists()) { - if (!m_model.get() || m_model->fileUrl() != dataStoreQmlUrl) { -#ifdef QDS_USE_PROJECTSTORAGE - m_model = model()->createModel("JsonListModel"); - forceUpdate = true; - Import import = Import::createLibraryImport("QtQuick.Studio.Utils"); - m_model->changeImports({import}, {}); -#else - m_model = Model::create(CollectionEditorConstants::JSONCOLLECTIONMODEL_TYPENAME, 1, 1); - forceUpdate = true; - Import import = Import::createLibraryImport( - CollectionEditorConstants::COLLECTIONMODEL_IMPORT); - try { - if (!m_model->hasImport(import, true, true)) - m_model->changeImports({import}, {}); - } catch (const Exception &) { - QTC_ASSERT(false, return); - } -#endif - } - } else { - reset(); - } - - if (!m_model.get()) - return; - - if (forceUpdate) { - m_model->setFileUrl(dataStoreQmlUrl); - m_dataRelativePath = dataStoreJsonPath.relativePathFrom(dataStoreQmlPath).toFSPathString(); - preloadFile(); - update(); - } -} - -QStringList DataStoreModelNode::collectionNames() const -{ - return m_collectionPropertyNames.keys(); -} - -Model *DataStoreModelNode::model() const -{ - return m_model.get(); -} - -ModelNode DataStoreModelNode::modelNode() const -{ - if (!m_model.get()) - return {}; - return m_model->rootModelNode(); -} - -QString DataStoreModelNode::getModelQmlText() -{ - ModelNode node = modelNode(); - QTC_ASSERT(node, return {}); - - Internal::QmlTextGenerator textGen(createNameList(node), - QmlJSTools::QmlJSToolsSettings::globalCodeStyle()->tabSettings()); - - QString genText = textGen(node); - return genText; -} - -void DataStoreModelNode::reset() -{ - if (m_model) - m_model.reset(); - - m_dataRelativePath.clear(); - setCollectionNames({}); -} - -void DataStoreModelNode::preloadFile() -{ - using Utils::FilePath; - using Utils::FileReader; - - if (!m_model) - return; - - const FilePath dataStoreQmlPath = dataStoreQmlFilePath(); - FileReader dataStoreQmlFile; - QString sourceQmlContext; - - if (dataStoreQmlFile.fetch(dataStoreQmlPath)) - sourceQmlContext = QString::fromLatin1(dataStoreQmlFile.data()); - - setQmlContextToModel(m_model.get(), sourceQmlContext); - m_collectionPropertyNames = getModelIdMap(m_model->rootModelNode()); -} - -void DataStoreModelNode::updateDataStoreProperties() -{ - QTC_ASSERT(model(), return); - - ModelNode rootNode = modelNode(); - QTC_ASSERT(rootNode.isValid(), return); - - QSet<QString> collectionNamesToBeAdded; - const QStringList allCollectionNames = m_collectionPropertyNames.keys(); - for (const QString &collectionName : allCollectionNames) - collectionNamesToBeAdded << collectionName; - - const QList<AbstractProperty> formerPropertyNames = rootNode.dynamicProperties(); - - // Remove invalid collection names from the properties - for (const AbstractProperty &property : formerPropertyNames) { - if (!property.isNodeProperty()) - continue; - - NodeProperty nodeProprty = property.toNodeProperty(); - if (!nodeProprty.hasDynamicTypeName(CHILDLISTMODEL_TYPENAME)) - continue; - - ModelNode childNode = nodeProprty.modelNode(); - if (childNode.hasProperty(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY)) { - QString modelName = childNode - .property(CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY) - .toVariantProperty() - .value() - .toString(); - if (collectionNamesToBeAdded.contains(modelName)) { - m_collectionPropertyNames.insert(modelName, property.name()); - collectionNamesToBeAdded.remove(modelName); - } else { - rootNode.removeProperty(property.name()); - } - } else { - rootNode.removeProperty(property.name()); - } - } - - rootNode.setIdWithoutRefactoring("models"); - - QStringList collectionNamesLeft = collectionNamesToBeAdded.values(); - Utils::sort(collectionNamesLeft); - for (const QString &collectionName : std::as_const(collectionNamesLeft)) - addCollectionNameToTheModel(collectionName, getUniquePropertyName(collectionName)); - - // Backend Property - ModelNode backendNode = model()->createModelNode(CollectionEditorConstants::JSONBACKEND_TYPENAME); - NodeProperty backendProperty = rootNode.nodeProperty("backend"); - backendProperty.setDynamicTypeNameAndsetModelNode(CollectionEditorConstants::JSONBACKEND_TYPENAME, - backendNode); - // Source Property - VariantProperty sourceProp = rootNode.variantProperty( - CollectionEditorConstants::SOURCEFILE_PROPERTY); - sourceProp.setValue(m_dataRelativePath); -} - -void DataStoreModelNode::updateSingletonFile() -{ - using Utils::FilePath; - using Utils::FileSaver; - QTC_ASSERT(m_model.get(), return); - - const QString pragmaSingleTone = "pragma Singleton\n"; - QString imports; - - for (const Import &import : m_model->imports()) - imports += QStringLiteral("import %1\n").arg(import.toString(true)); - - QString content = pragmaSingleTone + imports + getModelQmlText(); - Core::DocumentManager::expectFileChange(dataStoreQmlFilePath()); - FileSaver file(dataStoreQmlFilePath()); - file.write(content.toLatin1()); - file.finalize(); -} - -void DataStoreModelNode::update() -{ - if (!m_model.get()) - return; - - updateDataStoreProperties(); - updateSingletonFile(); -} - -void DataStoreModelNode::addCollectionNameToTheModel(const QString &collectionName, - const PropertyName &dataStorePropertyName) -{ - ModelNode rootNode = modelNode(); - QTC_ASSERT(rootNode.isValid(), return); - - if (dataStorePropertyName.isEmpty()) { - qWarning() << __FUNCTION__ << __LINE__ - << QString("The property name cannot be generated from \"%1\"").arg(collectionName); - return; - } - - ModelNode collectionNode = model()->createModelNode(CHILDLISTMODEL_TYPENAME); - VariantProperty modelNameProperty = collectionNode.variantProperty( - CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY); - modelNameProperty.setValue(collectionName); - - NodeProperty nodeProp = rootNode.nodeProperty(dataStorePropertyName); - nodeProp.setDynamicTypeNameAndsetModelNode(CHILDLISTMODEL_TYPENAME, collectionNode); - - m_collectionPropertyNames.insert(collectionName, dataStorePropertyName); -} - -Utils::FilePath DataStoreModelNode::dataStoreQmlFilePath() const -{ - QUrl modelUrl = m_model->fileUrl(); - return Utils::FilePath::fromUserInput(modelUrl.isLocalFile() ? modelUrl.toLocalFile() - : modelUrl.toString()); -} - -PropertyName DataStoreModelNode::getUniquePropertyName(const QString &collectionName) -{ - ModelNode dataStoreNode = modelNode(); - QTC_ASSERT(!collectionName.isEmpty() && dataStoreNode.isValid(), return {}); - - QString newProperty; - - // convert to camel case - QStringList nameWords = collectionName.split(' '); - nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1); - for (int i = 1; i < nameWords.size(); ++i) - nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1); - newProperty = nameWords.join(""); - - // if id starts with a number prepend an underscore - if (newProperty.at(0).isDigit()) - newProperty.prepend('_'); - - // If the new id is not valid (e.g. qml keyword match), prepend an underscore - if (!isValidCollectionPropertyName(newProperty)) - newProperty.prepend('_'); - - static const QRegularExpression rgx("\\d+$"); // matches a number at the end of a string - while (dataStoreNode.hasProperty(newProperty.toLatin1())) { // id exists - QRegularExpressionMatch match = rgx.match(newProperty); - if (match.hasMatch()) { // ends with a number, increment it - QString numStr = match.captured(); - int num = numStr.toInt() + 1; - newProperty = newProperty.mid(0, match.capturedStart()) + QString::number(num); - } else { - newProperty.append('1'); - } - } - - return newProperty.toLatin1(); -} - -void DataStoreModelNode::setCollectionNames(const QStringList &newCollectionNames) -{ - m_collectionPropertyNames.clear(); - for (const QString &collectionName : newCollectionNames) - m_collectionPropertyNames.insert(collectionName, {}); - update(); -} - -void DataStoreModelNode::addCollection(const QString &collectionName) -{ - if (!m_collectionPropertyNames.contains(collectionName)) { - m_collectionPropertyNames.insert(collectionName, {}); - update(); - } -} - -void DataStoreModelNode::renameCollection(const QString &oldName, const QString &newName) -{ - ModelNode dataStoreNode = modelNode(); - QTC_ASSERT(dataStoreNode.isValid(), return); - - if (m_collectionPropertyNames.contains(oldName)) { - const PropertyName oldPropertyName = m_collectionPropertyNames.value(oldName); - if (!oldPropertyName.isEmpty() && dataStoreNode.hasProperty(oldPropertyName)) { - NodeProperty collectionNode = dataStoreNode.property(oldPropertyName).toNodeProperty(); - if (collectionNode.isValid()) { - VariantProperty modelNameProperty = collectionNode.modelNode().variantProperty( - CollectionEditorConstants::JSONCHILDMODELNAME_PROPERTY); - modelNameProperty.setValue(newName); - m_collectionPropertyNames.remove(oldName); - m_collectionPropertyNames.insert(newName, collectionNode.name()); - update(); - return; - } - qWarning() << __FUNCTION__ << __LINE__ - << "There is no valid node for the old collection name"; - return; - } - qWarning() << __FUNCTION__ << __LINE__ << QString("Invalid old property name") - << oldPropertyName; - return; - } - qWarning() << __FUNCTION__ << __LINE__ - << QString("There is no old collection name registered with this name \"%1\"").arg(oldName); -} - -void DataStoreModelNode::removeCollections(const QStringList &collectionNames) -{ - bool updateRequired = false; - for (const QString &collectionName : collectionNames) { - if (m_collectionPropertyNames.contains(collectionName)) { - m_collectionPropertyNames.remove(collectionName); - updateRequired = true; - } - } - - if (updateRequired) - update(); -} - -void DataStoreModelNode::assignCollectionToNode(AbstractView *view, - const ModelNode &targetNode, - const QString &collectionName, - CollectionColumnFinder collectionHasColumn, - FirstColumnProvider firstColumnProvider) -{ - QTC_ASSERT(targetNode.isValid(), return); - - if (!CollectionEditorUtils::canAcceptCollectionAsModel(targetNode)) - return; - - if (!m_collectionPropertyNames.contains(collectionName)) { - qWarning() << __FUNCTION__ << __LINE__ << "Collection doesn't exist in the DataStore" - << collectionName; - return; - } - - PropertyName propertyName = m_collectionPropertyNames.value(collectionName); - - const ModelNode dataStore = modelNode(); - VariantProperty sourceProperty = dataStore.variantProperty(propertyName); - if (!sourceProperty.exists()) { - qWarning() << __FUNCTION__ << __LINE__ - << "The source property doesn't exist in the DataStore."; - return; - } - - view->executeInTransaction("assignCollectionToNode", [&]() { - QString identifier = QString("DataStore.%1").arg(QString::fromLatin1(sourceProperty.name())); - - // Remove the old model node property if exists - NodeProperty modelNodeProperty = targetNode.nodeProperty("model"); - if (modelNodeProperty.modelNode()) - modelNodeProperty.modelNode().destroy(); - - // Assign the collection to the node - BindingProperty modelProperty = targetNode.bindingProperty("model"); - modelProperty.setExpression(identifier); - - if (CollectionEditorUtils::hasTextRoleProperty(targetNode)) { - VariantProperty textRoleProperty = targetNode.variantProperty("textRole"); - const QVariant currentTextRoleValue = textRoleProperty.value(); - - if (currentTextRoleValue.isValid() && !currentTextRoleValue.isNull()) { - if (currentTextRoleValue.type() == QVariant::String) { - const QString currentTextRole = currentTextRoleValue.toString(); - if (collectionHasColumn(collectionName, currentTextRole)) - return; - } else { - return; - } - } - - QString textRoleValue = firstColumnProvider(collectionName); - textRoleProperty.setValue(textRoleValue); - } - }); -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h b/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h deleted file mode 100644 index 6cd969edbe..0000000000 --- a/src/plugins/qmldesigner/components/collectioneditor/datastoremodelnode.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include <modelnode.h> - -#include <QMap> - -namespace Utils { -class FilePath; -} - -namespace QmlDesigner { - -class Model; - -class DataStoreModelNode -{ -public: - using CollectionColumnFinder = std::function<bool(const QString &collectionName, - const QString &columnName)>; - using FirstColumnProvider = std::function<QString(const QString &collectionName)>; - - DataStoreModelNode(); - - void reloadModel(); - QStringList collectionNames() const; - - Model *model() const; - ModelNode modelNode() const; - - void setCollectionNames(const QStringList &newCollectionNames); - void addCollection(const QString &collectionName); - void renameCollection(const QString &oldName, const QString &newName); - void removeCollections(const QStringList &collectionNames); - - void assignCollectionToNode(AbstractView *view, - const ModelNode &targetNode, - const QString &collectionName, - CollectionColumnFinder collectionHasColumn, - FirstColumnProvider firstColumnProvider); - -private: - QString getModelQmlText(); - - void reset(); - void preloadFile(); - void updateDataStoreProperties(); - void updateSingletonFile(); - void update(); - void addCollectionNameToTheModel(const QString &collectionName, - const PropertyName &dataStorePropertyName); - Utils::FilePath dataStoreQmlFilePath() const; - - PropertyName getUniquePropertyName(const QString &collectionName); - - ModelPointer m_model; - QMap<QString, PropertyName> m_collectionPropertyNames; - QString m_dataRelativePath; -}; - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp index 559e8ea69c..3379d99834 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.cpp @@ -8,7 +8,7 @@ namespace QmlDesigner { AbstractAction::AbstractAction(const QString &description) - : m_pureAction(new DefaultAction(description)) + : m_pureAction(std::make_unique<DefaultAction>(description)) { const Utils::Icon defaultIcon({ {":/utils/images/select.png", Utils::Theme::QmlDesigner_FormEditorForegroundColor}}, Utils::Icon::MenuTintedStyle); @@ -56,7 +56,7 @@ void AbstractAction::setCheckable(bool checkable) PureActionInterface *AbstractAction::pureAction() const { - return m_pureAction.data(); + return m_pureAction.get(); } SelectionContext AbstractAction::selectionContext() const diff --git a/src/plugins/qmldesigner/components/componentcore/abstractaction.h b/src/plugins/qmldesigner/components/componentcore/abstractaction.h index ca4cc582ce..53b540cc7a 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractaction.h +++ b/src/plugins/qmldesigner/components/componentcore/abstractaction.h @@ -6,7 +6,8 @@ #include "actioninterface.h" #include <QAction> -#include <QScopedPointer> + +#include <memory> namespace QmlDesigner { @@ -58,7 +59,7 @@ protected: SelectionContext selectionContext() const; private: - QScopedPointer<PureActionInterface> m_pureAction; + std::unique_ptr<PureActionInterface> m_pureAction; SelectionContext m_selectionContext; }; diff --git a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp index 288b8e409d..5b340343e7 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp +++ b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.cpp @@ -8,14 +8,14 @@ namespace QmlDesigner { -AbstractActionGroup::AbstractActionGroup(const QString &displayName) : - m_displayName(displayName), - m_menu(new QmlEditorMenu) +AbstractActionGroup::AbstractActionGroup(const QString &displayName) + : m_displayName(displayName) + , m_menu(Utils::makeUniqueObjectPtr<QmlEditorMenu>()) { m_menu->setTitle(displayName); m_action = m_menu->menuAction(); - QmlEditorMenu *qmlEditorMenu = qobject_cast<QmlEditorMenu *>(m_menu.data()); + QmlEditorMenu *qmlEditorMenu = qobject_cast<QmlEditorMenu *>(m_menu.get()); if (qmlEditorMenu) qmlEditorMenu->setIconsVisible(false); } @@ -32,7 +32,7 @@ QAction *AbstractActionGroup::action() const QMenu *AbstractActionGroup::menu() const { - return m_menu.data(); + return m_menu.get(); } SelectionContext AbstractActionGroup::selectionContext() const diff --git a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h index dd89849ecf..f239eeab3d 100644 --- a/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h +++ b/src/plugins/qmldesigner/components/componentcore/abstractactiongroup.h @@ -5,9 +5,10 @@ #include "actioninterface.h" +#include <utils/uniqueobjectptr.h> + #include <QAction> #include <QMenu> -#include <QScopedPointer> namespace QmlDesigner { @@ -29,7 +30,7 @@ public: private: const QString m_displayName; SelectionContext m_selectionContext; - QScopedPointer<QMenu> m_menu; + Utils::UniqueObjectPtr<QMenu> m_menu; QAction *m_action; }; diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index d992a6a5bf..da7c5bf72e 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -69,7 +69,6 @@ const char mergeTemplateCommandId[] = "MergeTemplate"; const char goToImplementationCommandId[] = "GoToImplementation"; const char makeComponentCommandId[] = "MakeComponent"; const char editMaterialCommandId[] = "EditMaterial"; -const char editCollectionCommandId[] = "EditCollection"; const char addItemToStackedContainerCommandId[] = "AddItemToStackedContainer"; const char addTabBarToStackedContainerCommandId[] = "AddTabBarToStackedContainer"; const char increaseIndexOfStackedContainerCommandId[] = "IncreaseIndexOfStackedContainer"; @@ -128,7 +127,6 @@ const char mergeTemplateDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMen const char goToImplementationDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Go to Implementation"); const char makeComponentDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Create Component"); const char editMaterialDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Material"); -const char editCollectionDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Model"); const char editAnnotationsDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit Annotations"); const char addMouseAreaFillDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add Mouse Area"); const char editIn3dViewDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Edit in 3D View"); @@ -214,7 +212,6 @@ enum PrioritiesEnum : int { ArrangeCategory, EditCategory, EditListModel, - EditCollection, /******** Section *****************************/ PositionSection = 2000, SnappingCategory, diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index 4e32237ee9..bbe64935f6 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -61,11 +61,6 @@ inline static QString captionForModelNode(const ModelNode &modelNode) return modelNode.id(); } -inline static bool contains(const QmlItemNode &node, const QPointF &position) -{ - return node.isValid() && node.instanceSceneTransform().mapRect(node.instanceBoundingRect()).contains(position); -} - DesignerActionManagerView *DesignerActionManager::view() { return m_designerActionManagerView; @@ -118,7 +113,6 @@ void DesignerActionManager::polishActions() const Core::Context qmlDesignerNavigatorContext(Constants::C_QMLNAVIGATOR); Core::Context qmlDesignerMaterialBrowserContext(Constants::C_QMLMATERIALBROWSER); Core::Context qmlDesignerAssetsLibraryContext(Constants::C_QMLASSETSLIBRARY); - Core::Context qmlDesignerCollectionEditorContext(Constants::C_QMLCOLLECTIONEDITOR); Core::Context qmlDesignerUIContext; qmlDesignerUIContext.add(qmlDesignerFormEditorContext); @@ -126,7 +120,6 @@ void DesignerActionManager::polishActions() const qmlDesignerUIContext.add(qmlDesignerNavigatorContext); qmlDesignerUIContext.add(qmlDesignerMaterialBrowserContext); qmlDesignerUIContext.add(qmlDesignerAssetsLibraryContext); - qmlDesignerUIContext.add(qmlDesignerCollectionEditorContext); for (auto *action : actions) { if (!action->menuId().isEmpty()) { @@ -438,8 +431,8 @@ public: } for (const ModelNode &node : selectionContext().view()->allModelNodes()) { if (node != selectionContext().currentSingleSelectedNode() && node != parentNode - && contains(node, selectionContext().scenePosition()) && !node.isRootNode() - && !ModelUtils::isThisOrAncestorLocked(node)) { + && SelectionContextHelpers::contains(node, selectionContext().scenePosition()) + && !node.isRootNode() && !ModelUtils::isThisOrAncestorLocked(node)) { selectionContext().setTargetNode(node); QString what = QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select: %1")).arg(captionForModelNode(node)); ActionTemplate *selectionAction = new ActionTemplate("SELECT", what, &ModelNodeOperations::select); @@ -1971,7 +1964,7 @@ void DesignerActionManager::createDefaultDesignerActions() QKeySequence(), Priorities::ComponentActions + 1, &editIn3dView, - &singleSelectionView3D, + &SelectionContextFunctors::always, // If action is visible, it is usable &singleSelectionView3D)); addDesignerAction(new ModelNodeContextMenuAction( @@ -1993,8 +1986,8 @@ void DesignerActionManager::createDefaultDesignerActions() QKeySequence(), 44, &editMaterial, - &modelHasMaterial, - &isModel)); + &hasEditableMaterial, + &isModelOrMaterial)); addDesignerAction(new ModelNodeContextMenuAction( mergeTemplateCommandId, @@ -2016,16 +2009,6 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new EditListModelAction); - addDesignerAction(new ModelNodeContextMenuAction(editCollectionCommandId, - editCollectionDisplayName, - contextIcon(DesignerIcons::EditIcon), - rootCategory, - QKeySequence("Alt+e"), - ComponentCoreConstants::Priorities::EditCollection, - &editCollection, - &hasCollectionAsModel, - &hasCollectionAsModel)); - addDesignerAction(new ModelNodeContextMenuAction(openSignalDialogCommandId, openSignalDialogDisplayName, {}, @@ -2198,7 +2181,8 @@ void DesignerActionManager::addCustomTransitionEffectAction() void DesignerActionManager::setupIcons() { - m_designerIcons.reset(new DesignerIcons("qtds_propertyIconFont.ttf", designerIconResourcesPath())); + m_designerIcons = std::make_unique<DesignerIcons>("qtds_propertyIconFont.ttf", + designerIconResourcesPath()); } QString DesignerActionManager::designerIconResourcesPath() const diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h index 16d6219cd6..89505fcbe8 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h @@ -138,7 +138,7 @@ private: QList<AddResourceHandler> m_addResourceHandler; QList<ModelNodePreviewImageHandler> m_modelNodePreviewImageHandlers; ExternalDependenciesInterface &m_externalDependencies; - QScopedPointer<DesignerIcons> m_designerIcons; + std::unique_ptr<DesignerIcons> m_designerIcons; QList<ActionAddedInterface> m_callBacks; }; 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/modelnodecontextmenu_helper.h b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h index eb4915b1d0..6734bac568 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodecontextmenu_helper.h @@ -25,6 +25,16 @@ namespace QmlDesigner { using SelectionContextPredicate = std::function<bool (const SelectionContext&)>; using SelectionContextOperation = std::function<void (const SelectionContext&)>; +namespace SelectionContextHelpers { + +inline bool contains(const QmlItemNode &node, const QPointF &position) +{ + return node.isValid() + && node.instanceSceneTransform().mapRect(node.instanceBoundingRect()).contains(position); +} + +} // namespace SelectionContextHelpers + namespace SelectionContextFunctors { inline bool always(const SelectionContext &) @@ -54,33 +64,24 @@ inline bool addMouseAreaFillCheck(const SelectionContext &selectionContext) return false; } -inline bool isModel(const SelectionContext &selectionState) +inline bool isModelOrMaterial(const SelectionContext &selectionState) { ModelNode node = selectionState.currentSingleSelectedNode(); - return node.metaInfo().isQtQuick3DModel(); + return node.metaInfo().isQtQuick3DModel() || node.metaInfo().isQtQuick3DMaterial(); } -inline bool modelHasMaterial(const SelectionContext &selectionState) +inline bool hasEditableMaterial(const SelectionContext &selectionState) { ModelNode node = selectionState.currentSingleSelectedNode(); + if (node.metaInfo().isQtQuick3DMaterial()) + return true; + BindingProperty prop = node.bindingProperty("materials"); return prop.exists() && (!prop.expression().isEmpty() || !prop.resolveToModelNodeList().empty()); } -inline bool hasCollectionAsModel(const SelectionContext &selectionState) -{ - if (!selectionState.isInBaseState() || !selectionState.singleNodeIsSelected()) - return false; - - const ModelNode singleSelectedNode = selectionState.currentSingleSelectedNode(); - - return singleSelectedNode.metaInfo().isQtQuickListView() - && singleSelectedNode.property("model").toBindingProperty().expression().startsWith( - "DataStore."); -} - inline bool selectionEnabled(const SelectionContext &selectionState) { return selectionState.showSelectionTools(); @@ -99,8 +100,22 @@ inline bool singleSelectionNotRoot(const SelectionContext &selectionState) inline bool singleSelectionView3D(const SelectionContext &selectionState) { - return selectionState.singleNodeIsSelected() - && selectionState.currentSingleSelectedNode().metaInfo().isQtQuick3DView3D(); + if (selectionState.singleNodeIsSelected() + && selectionState.currentSingleSelectedNode().metaInfo().isQtQuick3DView3D()) { + return true; + } + + // If currently selected node is not View3D, check if there is a View3D under the cursor. + if (!selectionState.scenePosition().isNull()) { + // Assumption is that last match in allModelNodes() list is the topmost one. + const QList<ModelNode> allNodes = selectionState.view()->allModelNodes(); + for (int i = allNodes.size() - 1; i >= 0; --i) { + if (SelectionContextHelpers::contains(allNodes[i], selectionState.scenePosition())) + return allNodes[i].metaInfo().isQtQuick3DView3D(); + } + } + + return false; } inline bool selectionHasProperty(const SelectionContext &selectionState, const char *property) diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index cebe7d7c53..bf8e78a2c7 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -817,21 +817,25 @@ void editMaterial(const SelectionContext &selectionContext) QTC_ASSERT(modelNode.isValid(), return); - BindingProperty prop = modelNode.bindingProperty("materials"); - if (!prop.exists()) - return; - AbstractView *view = selectionContext.view(); ModelNode material; - if (view->hasId(prop.expression())) { - material = view->modelNodeForId(prop.expression()); + if (modelNode.metaInfo().isQtQuick3DMaterial()) { + material = modelNode; } else { - QList<ModelNode> materials = prop.resolveToModelNodeList(); + BindingProperty prop = modelNode.bindingProperty("materials"); + if (!prop.exists()) + return; + + if (view->hasId(prop.expression())) { + material = view->modelNodeForId(prop.expression()); + } else { + QList<ModelNode> materials = prop.resolveToModelNodeList(); - if (materials.size() > 0) - material = materials.first(); + if (materials.size() > 0) + material = materials.first(); + } } if (material.isValid()) { @@ -842,30 +846,6 @@ void editMaterial(const SelectionContext &selectionContext) } } -// Open a collection in the collection editor -void editCollection(const SelectionContext &selectionContext) -{ - ModelNode modelNode = selectionContext.targetNode(); - - if (!modelNode) - modelNode = selectionContext.currentSingleSelectedNode(); - - if (!modelNode) - return; - - const QString dataStoreExpression = "DataStore."; - - BindingProperty prop = modelNode.bindingProperty("model"); - if (!prop.exists() || !prop.expression().startsWith(dataStoreExpression)) - return; - - AbstractView *view = selectionContext.view(); - const QString collectionId = prop.expression().mid(dataStoreExpression.size()); - - // to CollectionEditor... - view->emitCustomNotification("open_collection_by_id", {}, {collectionId}); -} - void addItemToStackedContainer(const SelectionContext &selectionContext) { AbstractView *view = selectionContext.view(); @@ -1138,18 +1118,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(); @@ -1691,16 +1665,44 @@ void updateImported3DAsset(const SelectionContext &selectionContext) void editIn3dView(const SelectionContext &selectionContext) { - if (selectionContext.view() && selectionContext.hasSingleSelectedModelNode() + if (!selectionContext.view()) + return; + + ModelNode targetNode; + + if (selectionContext.hasSingleSelectedModelNode() && selectionContext.currentSingleSelectedNode().metaInfo().isQtQuick3DView3D()) { + targetNode = selectionContext.currentSingleSelectedNode(); + } + + const QPointF scenePos = selectionContext.scenePosition(); + if (!targetNode.isValid() && !scenePos.isNull()) { + // If currently selected node is not View3D, check if there is a View3D under the cursor. + // Assumption is that last match in allModelNodes() list is the topmost one. + const QList<ModelNode> allNodes = selectionContext.view()->allModelNodes(); + for (int i = allNodes.size() - 1; i >= 0; --i) { + if (SelectionContextHelpers::contains(allNodes[i], selectionContext.scenePosition())) { + if (allNodes[i].metaInfo().isQtQuick3DView3D()) + targetNode = allNodes[i]; + break; + } + } + } + + if (targetNode.isValid()) { QmlDesignerPlugin::instance()->mainWidget()->showDockWidget("Editor3D", true); - selectionContext.view()->emitView3DAction(View3DActionType::AlignViewToCamera, true); + if (scenePos.isNull()) { + selectionContext.view()->emitView3DAction(View3DActionType::AlignViewToCamera, true); + } else { + selectionContext.view()->emitCustomNotification("pick_3d_node_from_2d_scene", + {targetNode}, {scenePos}); + } } } bool isEffectComposerActivated() { - const QVector<ExtensionSystem::PluginSpec *> specs = ExtensionSystem::PluginManager::plugins(); + const ExtensionSystem::PluginSpecs specs = ExtensionSystem::PluginManager::plugins(); return std::find_if(specs.begin(), specs.end(), [](ExtensionSystem::PluginSpec *spec) { return spec->name() == "EffectComposer" && spec->isEffectivelyEnabled(); @@ -1727,13 +1729,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 +1770,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 +1792,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/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h index a67cef4942..26562f429a 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h @@ -92,7 +92,6 @@ void layoutGridLayout(const SelectionContext &selectionState); void goImplementation(const SelectionContext &selectionState); void addNewSignalHandler(const SelectionContext &selectionState); void editMaterial(const SelectionContext &selectionContext); -void editCollection(const SelectionContext &selectionContext); void addSignalHandlerOrGotoImplementation(const SelectionContext &selectionState, bool addAlwaysNewSlot); void removeLayout(const SelectionContext &selectionContext); void removePositioner(const SelectionContext &selectionContext); diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp index 8cc84058d2..58326dc77a 100644 --- a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp +++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp @@ -37,7 +37,7 @@ Type getProperty(const QmlJS::SimpleReaderNode *node, const QString &name) { if (auto property = node->property(name)) { const auto &value = property.value; - if (value.type() == QVariant::List) { + if (value.typeId() == QMetaType::QVariantList) { auto list = value.toList(); if (list.size()) return list.front().value<Type>(); @@ -179,7 +179,7 @@ std::optional<PropertyComponentGenerator::Entry> createEntry(QmlJS::SimpleReader if (moduleName.isEmpty()) return {}; - auto module = model->module(moduleName); + auto module = model->module(moduleName, Storage::ModuleKind::QmlLibrary); auto typeName = getProperty<QByteArray>(node, "typeNames"); 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.cpp b/src/plugins/qmldesigner/components/componentcore/theme.cpp index af495fd3b5..ef618447e7 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.cpp +++ b/src/plugins/qmldesigner/components/componentcore/theme.cpp @@ -10,6 +10,7 @@ #include <utils/stylehelper.h> +#include <qqml.h> #include <QApplication> #include <QMainWindow> #include <QPointer> @@ -18,7 +19,7 @@ #include <QQmlProperty> #include <QRegularExpression> #include <QScreen> -#include <qqml.h> +#include <QWindow> static Q_LOGGING_CATEGORY(themeLog, "qtc.qmldesigner.theme", QtWarningMsg) @@ -140,7 +141,9 @@ bool Theme::highPixelDensity() const QWindow *Theme::mainWindowHandle() const { - return Core::ICore::mainWindow()->windowHandle(); + QWindow *handle = Core::ICore::mainWindow()->windowHandle(); + QQmlEngine::setObjectOwnership(handle, QJSEngine::CppOwnership); + return handle; } QPixmap Theme::getPixmap(const QString &id) 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..a56735862f 100644 --- a/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/viewmanager.cpp @@ -7,7 +7,6 @@ #include <abstractview.h> #include <assetslibraryview.h> #include <capturingconnectionmanager.h> -#include <collectionview.h> #include <componentaction.h> #include <componentview.h> #include <contentlibraryview.h> @@ -42,14 +41,6 @@ namespace QmlDesigner { -static bool enableModelEditor() -{ - Utils::QtcSettings *settings = Core::ICore::settings(); - const Utils::Key enableModelManagerKey = "QML/Designer/UseExperimentalFeatures44"; - - return settings->value(enableModelManagerKey, false).toBool(); -} - static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg) class ViewManagerData @@ -64,19 +55,22 @@ public: : connectionManager, externalDependencies, true) - , collectionView{externalDependencies} - , contentLibraryView{externalDependencies} + , contentLibraryView{imageCache, 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} {} @@ -86,19 +80,22 @@ public: Internal::DebugView debugView; DesignerActionManagerView designerActionManagerView; NodeInstanceView nodeInstanceView; - 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 +200,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,9 +213,16 @@ QList<AbstractView *> ViewManager::standardViews() const &d->textureEditorView, &d->statesEditorView, &d->designerActionManagerView}; - - if (enableModelEditor()) - list.append(&d->collectionView); +#else + QList<AbstractView *> list = {&d->formEditorView, + &d->textEditorView, + &d->assetsLibraryView, + &d->itemLibraryView, + &d->navigatorView, + &d->propertyEditorView, + &d->statesEditorView, + &d->designerActionManagerView}; +#endif if (QmlDesignerPlugin::instance() ->settings() @@ -384,19 +389,21 @@ 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()); if (checkEnterpriseLicense()) widgetInfoList.append(d->contentLibraryView.widgetInfo()); diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp index f871bae84b..2cee7b0f97 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.cpp @@ -22,6 +22,7 @@ namespace QmlDesigner { BindingModel::BindingModel(ConnectionView *view) : m_connectionView(view) + , m_delegate(*this) { setHorizontalHeaderLabels(BindingModelItem::headerLabels()); } @@ -246,11 +247,8 @@ void BindingModel::addModelNode(const ModelNode &node) appendRow(new BindingModelItem(property)); } -BindingModelBackendDelegate::BindingModelBackendDelegate() - : m_targetNode() - , m_property() - , m_sourceNode() - , m_sourceNodeProperty() +BindingModelBackendDelegate::BindingModelBackendDelegate(BindingModel &model) + : m_model{model} { connect(&m_sourceNode, &StudioQmlComboBoxBackend::activated, this, [this] { sourceNodeChanged(); @@ -322,17 +320,14 @@ StudioQmlComboBoxBackend *BindingModelBackendDelegate::sourceProperty() void BindingModelBackendDelegate::sourceNodeChanged() { - BindingModel *model = qobject_cast<BindingModel *>(parent()); - QTC_ASSERT(model, return); - - ConnectionView *view = model->connectionView(); + ConnectionView *view = m_model.connectionView(); QTC_ASSERT(view, return); QTC_ASSERT(view->isAttached(), return ); const QString sourceNode = m_sourceNode.currentText(); const QString sourceProperty = m_sourceNodeProperty.currentText(); - BindingProperty targetProperty = model->currentProperty(); + BindingProperty targetProperty = m_model.currentProperty(); QStringList properties = availableSourceProperties(sourceNode, targetProperty, view); if (!properties.contains(sourceProperty)) { @@ -351,9 +346,6 @@ void BindingModelBackendDelegate::sourcePropertyNameChanged() const return; auto commit = [this, sourceProperty]() { - BindingModel *model = qobject_cast<BindingModel *>(parent()); - QTC_ASSERT(model, return); - const QString sourceNode = m_sourceNode.currentText(); QString expression; if (sourceProperty.isEmpty()) @@ -361,8 +353,8 @@ void BindingModelBackendDelegate::sourcePropertyNameChanged() const else expression = sourceNode + QLatin1String(".") + sourceProperty; - int row = model->currentIndex(); - model->commitExpression(row, expression); + int row = m_model.currentIndex(); + m_model.commitExpression(row, expression); }; callLater(commit); @@ -371,11 +363,9 @@ void BindingModelBackendDelegate::sourcePropertyNameChanged() const void BindingModelBackendDelegate::targetPropertyNameChanged() const { auto commit = [this] { - BindingModel *model = qobject_cast<BindingModel *>(parent()); - QTC_ASSERT(model, return); const PropertyName propertyName = m_property.currentText().toUtf8(); - int row = model->currentIndex(); - model->commitPropertyName(row, propertyName); + int row = m_model.currentIndex(); + m_model.commitPropertyName(row, propertyName); }; callLater(commit); diff --git a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h index b57cc5c958..69b137e78a 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/bindingmodel.h @@ -30,7 +30,7 @@ signals: void targetNodeChanged(); public: - BindingModelBackendDelegate(); + BindingModelBackendDelegate(class BindingModel &model); void update(const BindingProperty &property, AbstractView *view); @@ -44,6 +44,7 @@ private: StudioQmlComboBoxBackend *sourceNode(); StudioQmlComboBoxBackend *sourceProperty(); + BindingModel &m_model; QString m_targetNode; StudioQmlComboBoxBackend m_property; StudioQmlComboBoxBackend m_sourceNode; diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp index 57ca619a70..3cbfb8c038 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectioneditorutils.cpp @@ -212,7 +212,8 @@ bool isDynamicVariantPropertyType(const TypeName &type) { // "variant" is considered value type as it is initialized as one. // This may need to change if we provide any kind of proper editor for it. - static const QSet<TypeName> valueTypes{"int", "real", "color", "string", "bool", "url", "var", "variant"}; + static const QSet<TypeName> valueTypes{ + "int", "real", "double", "color", "string", "bool", "url", "var", "variant"}; return valueTypes.contains(type); } diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp index 9fdd3daec3..fa29c6c8a1 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.cpp @@ -25,7 +25,7 @@ namespace QmlDesigner { DynamicPropertiesModel::DynamicPropertiesModel(bool exSelection, AbstractView *view) : m_view(view) - , m_delegate(std::make_unique<DynamicPropertiesModelBackendDelegate>()) + , m_delegate(std::make_unique<DynamicPropertiesModelBackendDelegate>(*this)) , m_explicitSelection(exSelection) { setHorizontalHeaderLabels(DynamicPropertiesItem::headerLabels()); @@ -382,8 +382,8 @@ void DynamicPropertiesModel::setSelectedNode(const ModelNode &node) reset(); } -DynamicPropertiesModelBackendDelegate::DynamicPropertiesModelBackendDelegate() - : m_internalNodeId(std::nullopt) +DynamicPropertiesModelBackendDelegate::DynamicPropertiesModelBackendDelegate(DynamicPropertiesModel &model) + : m_model(model) { m_type.setModel({"int", "bool", "var", "real", "string", "url", "color"}); connect(&m_type, &StudioQmlComboBoxBackend::activated, this, [this] { handleTypeChanged(); }); @@ -411,32 +411,26 @@ void DynamicPropertiesModelBackendDelegate::update(const AbstractProperty &prope void DynamicPropertiesModelBackendDelegate::handleTypeChanged() { - DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent()); - QTC_ASSERT(model, return); - const PropertyName name = m_name.text().toUtf8(); - int current = model->currentIndex(); + int current = m_model.currentIndex(); const TypeName type = m_type.currentText().toUtf8(); - model->commitPropertyType(current, type); + m_model.commitPropertyType(current, type); // The order might have changed! - model->setCurrent(m_internalNodeId.value_or(-1), name); + m_model.setCurrent(m_internalNodeId.value_or(-1), name); } void DynamicPropertiesModelBackendDelegate::handleNameChanged() { - DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent()); - QTC_ASSERT(model, return); - const PropertyName name = m_name.text().toUtf8(); QTC_ASSERT(!name.isEmpty(), return); - int current = model->currentIndex(); - model->commitPropertyName(current, name); + int current = m_model.currentIndex(); + m_model.commitPropertyName(current, name); // The order might have changed! - model->setCurrent(m_internalNodeId.value_or(-1), name); + m_model.setCurrent(m_internalNodeId.value_or(-1), name); } // TODO: Maybe replace with utils typeConvertVariant? @@ -456,12 +450,9 @@ QVariant valueFromText(const QString &value, const QString &type) void DynamicPropertiesModelBackendDelegate::handleValueChanged() { - DynamicPropertiesModel *model = qobject_cast<DynamicPropertiesModel *>(parent()); - QTC_ASSERT(model, return); - - int current = model->currentIndex(); + int current = m_model.currentIndex(); QVariant value = valueFromText(m_value.text(), m_type.currentText()); - model->commitPropertyValue(current, value); + m_model.commitPropertyValue(current, value); } QString DynamicPropertiesModelBackendDelegate::targetNode() const diff --git a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h index c2beed8730..071c72bef8 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/dynamicpropertiesmodel.h @@ -97,7 +97,7 @@ class DynamicPropertiesModelBackendDelegate : public QObject Q_PROPERTY(StudioQmlTextBackend *value READ value CONSTANT) public: - DynamicPropertiesModelBackendDelegate(); + DynamicPropertiesModelBackendDelegate(DynamicPropertiesModel &model); void update(const AbstractProperty &property); @@ -116,6 +116,7 @@ private: StudioQmlTextBackend *value(); QString targetNode() const; + DynamicPropertiesModel &m_model; std::optional<int> m_internalNodeId; StudioQmlComboBoxBackend m_type; StudioQmlTextBackend m_name; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp index 9e6bdd03b9..6388c93fa9 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.cpp @@ -3,83 +3,67 @@ #include "contentlibrarybundleimporter.h" -#include "documentmanager.h" -#include "import.h" -#include "model.h" -#include "qmldesignerconstants.h" -#include "qmldesignerplugin.h" -#include "rewritingexception.h" +#include <documentmanager.h> +#include <import.h> +#include <model.h> +#include <nodemetainfo.h> +#include <qmldesignerconstants.h> +#include <qmldesignerplugin.h> +#include <rewritingexception.h> #include <qmljs/qmljsmodelmanagerinterface.h> -#include <QFileInfo> #include <QJsonDocument> #include <QJsonObject> #include <QStringList> using namespace Utils; -namespace QmlDesigner::Internal { +namespace QmlDesigner { -ContentLibraryBundleImporter::ContentLibraryBundleImporter(const QString &bundleDir, - const QString &bundleId, - const QStringList &sharedFiles, - QObject *parent) +ContentLibraryBundleImporter::ContentLibraryBundleImporter(QObject *parent) : QObject(parent) - , m_bundleDir(FilePath::fromString(bundleDir)) - , m_bundleId(bundleId) - , m_sharedFiles(sharedFiles) { 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. // Note that there is also an asynchronous portion to the import, which will only // be done if this method returns success. Once the asynchronous portion of the // import is completed, importFinished signal will be emitted. -QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile, +QString ContentLibraryBundleImporter::importComponent(const QString &bundleDir, + const TypeName &type, + const QString &qmlFile, const QStringList &files) { - FilePath bundleImportPath = resolveBundleImportPath(); + QString module = QString::fromLatin1(type.left(type.lastIndexOf('.'))); + m_bundleId = module.mid(module.lastIndexOf('.') + 1); + + FilePath bundleDirPath = FilePath::fromString(bundleDir); // source dir + FilePath bundleImportPath = resolveBundleImportPath(m_bundleId); // target dir + if (bundleImportPath.isEmpty()) return "Failed to resolve bundle import folder"; - bool bundleImportPathExists = bundleImportPath.exists(); - - if (!bundleImportPathExists && !bundleImportPath.createDir()) + if (!bundleImportPath.exists() && !bundleImportPath.createDir()) return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString()); - for (const QString &file : std::as_const(m_sharedFiles)) { - FilePath target = bundleImportPath.resolvePath(file); - if (!target.exists()) { - FilePath parentDir = target.parentDir(); - if (!parentDir.exists() && !parentDir.createDir()) - return QStringLiteral("Failed to create folder for: '%1'").arg(target.toString()); - FilePath source = m_bundleDir.resolvePath(file); - if (!source.copyFile(target)) - return QStringLiteral("Failed to copy shared file: '%1'").arg(source.toString()); - } - } - - FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); + FilePath qmldirPath = bundleImportPath.pathAppended("qmldir"); QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents().value_or(QByteArray())); if (qmldirContent.isEmpty()) { qmldirContent.append("module "); - qmldirContent.append(m_moduleName); + qmldirContent.append(module); qmldirContent.append('\n'); } - FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile)); + FilePath qmlSourceFile = bundleImportPath.pathAppended(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); - if (m_pendingTypes.contains(fullTypeName) && !m_pendingTypes[fullTypeName]) - return QStringLiteral("Unable to import while unimporting the same type: '%1'").arg(fullTypeName); + + if (m_pendingTypes.contains(type) && !m_pendingTypes.value(type)) + return QStringLiteral("Unable to import while unimporting the same type: '%1'").arg(QLatin1String(type)); + if (!qmldirContent.contains(qmlFile)) { qmldirContent.append(qmlType); qmldirContent.append(" 1.0 "); @@ -92,12 +76,12 @@ QString ContentLibraryBundleImporter::importComponent(const QString &qmlFile, allFiles.append(files); allFiles.append(qmlFile); for (const QString &file : std::as_const(allFiles)) { - FilePath target = bundleImportPath.resolvePath(file); + FilePath target = bundleImportPath.pathAppended(file); FilePath parentDir = target.parentDir(); if (!parentDir.exists() && !parentDir.createDir()) return QStringLiteral("Failed to create folder for: '%1'").arg(target.toString()); - FilePath source = m_bundleDir.resolvePath(file); + FilePath source = bundleDirPath.pathAppended(file); if (target.exists()) { if (source.lastModified() == target.lastModified()) continue; @@ -126,23 +110,23 @@ 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(module, "1.0"); if (!model->hasImport(import)) { if (model->possibleImports().contains(import)) { - m_importAddPending = false; + m_pendingImport.clear(); try { 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(module); } } else { // If import is not yet possible, import statement needs to be added asynchronously to // avoid errors, as code model update takes a while. - m_importAddPending = true; + m_pendingImport = module; } } - m_pendingTypes.insert(fullTypeName, true); + m_pendingTypes.insert(type, true); m_importTimerCount = 0; m_importTimer.start(); @@ -154,17 +138,19 @@ void ContentLibraryBundleImporter::handleImportTimer() auto handleFailure = [this] { m_importTimer.stop(); m_fullReset = false; - m_importAddPending = false; + m_pendingImport.clear(); m_importTimerCount = 0; // Emit dummy finished signals for all pending types - const QStringList pendingTypes = m_pendingTypes.keys(); - for (const QString &pendingType : pendingTypes) { + const QList<TypeName> pendingTypes = m_pendingTypes.keys(); + for (const TypeName &pendingType : pendingTypes) { m_pendingTypes.remove(pendingType); - if (m_pendingTypes[pendingType]) - emit importFinished({}); + if (m_pendingTypes.value(pendingType)) + emit importFinished({}, m_bundleId); else - emit unimportFinished({}); + emit unimportFinished({}, m_bundleId); + + m_bundleId.clear(); } }; @@ -186,12 +172,12 @@ void ContentLibraryBundleImporter::handleImportTimer() QmlDesignerPlugin::instance()->documentManager().resetPossibleImports(); - if (m_importAddPending) { + if (!m_pendingImport.isEmpty()) { try { - Import import = Import::createLibraryImport(m_moduleName, "1.0"); + Import import = Import::createLibraryImport(m_pendingImport, "1.0"); if (model->possibleImports().contains(import)) { model->changeImports({import}, {}); - m_importAddPending = false; + m_pendingImport.clear(); } } catch (const RewritingException &) { // Import adding is unlikely to succeed later, either, so just bail out @@ -201,21 +187,23 @@ void ContentLibraryBundleImporter::handleImportTimer() } // Detect when the code model has the new material(s) fully available - const QStringList pendingTypes = m_pendingTypes.keys(); - for (const QString &pendingType : pendingTypes) { - NodeMetaInfo metaInfo = model->metaInfo(pendingType.toUtf8()); - const bool isImport = m_pendingTypes[pendingType]; + const QList<TypeName> pendingTypes = m_pendingTypes.keys(); + for (const TypeName &pendingType : pendingTypes) { + NodeMetaInfo metaInfo = model->metaInfo(pendingType); + const bool isImport = m_pendingTypes.value(pendingType); const bool typeComplete = metaInfo.isValid() && !metaInfo.prototypes().empty(); if (isImport == typeComplete) { m_pendingTypes.remove(pendingType); if (isImport) #ifdef QDS_USE_PROJECTSTORAGE - emit importFinished(pendingType.toUtf8()); + emit importFinished(pendingType, m_bundleId); #else - emit importFinished(metaInfo); + emit importFinished(metaInfo, m_bundleId); #endif else - emit unimportFinished(metaInfo); + emit unimportFinished(metaInfo, m_bundleId); + + m_bundleId.clear(); } } @@ -225,10 +213,10 @@ void ContentLibraryBundleImporter::handleImportTimer() } } -QVariantHash ContentLibraryBundleImporter::loadAssetRefMap(const Utils::FilePath &bundlePath) +QVariantHash ContentLibraryBundleImporter::loadAssetRefMap(const FilePath &bundlePath) { FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE)); - const Utils::expected_str<QByteArray> content = assetRefPath.fileContents(); + const expected_str<QByteArray> content = assetRefPath.fileContents(); if (content) { QJsonParseError error; QJsonDocument bundleDataJsonDoc = QJsonDocument::fromJson(*content, &error); @@ -242,7 +230,7 @@ QVariantHash ContentLibraryBundleImporter::loadAssetRefMap(const Utils::FilePath return {}; } -void ContentLibraryBundleImporter::writeAssetRefMap(const Utils::FilePath &bundlePath, +void ContentLibraryBundleImporter::writeAssetRefMap(const FilePath &bundlePath, const QVariantHash &assetRefMap) { FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE)); @@ -253,9 +241,14 @@ void ContentLibraryBundleImporter::writeAssetRefMap(const Utils::FilePath &bundl } } -QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) +QString ContentLibraryBundleImporter::unimportComponent(const TypeName &type, const QString &qmlFile) { - FilePath bundleImportPath = resolveBundleImportPath(); + QString module = QString::fromLatin1(type.left(type.lastIndexOf('.'))); + m_bundleId = module.mid(module.lastIndexOf('.') + 1); + + emit aboutToUnimport(type, m_bundleId); + + FilePath bundleImportPath = resolveBundleImportPath(m_bundleId); if (bundleImportPath.isEmpty()) return QStringLiteral("Failed to resolve bundle import folder for: '%1'").arg(qmlFile); @@ -274,10 +267,10 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) QByteArray newContent; QString qmlType = qmlFilePath.baseName(); - const QString fullTypeName = QStringLiteral("%1.%2.%3") - .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), m_bundleId, qmlType); - if (m_pendingTypes.contains(fullTypeName) && m_pendingTypes[fullTypeName]) - return QStringLiteral("Unable to unimport while importing the same type: '%1'").arg(fullTypeName); + if (m_pendingTypes.contains(type) && m_pendingTypes.value(type)) { + return QStringLiteral("Unable to unimport while importing the same type: '%1'") + .arg(QString::fromLatin1(type)); + } if (qmldirContent) { int typeIndex = qmldirContent->indexOf(qmlType.toUtf8()); @@ -293,7 +286,7 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) } } - m_pendingTypes.insert(fullTypeName, false); + m_pendingTypes.insert(type, false); QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath); bool writeAssetRefs = false; @@ -327,7 +320,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(module, "1.0"); if (model->imports().contains(import)) model->changeImports({}, {import}); } @@ -340,18 +333,14 @@ QString ContentLibraryBundleImporter::unimportComponent(const QString &qmlFile) return {}; } -FilePath ContentLibraryBundleImporter::resolveBundleImportPath() +FilePath ContentLibraryBundleImporter::resolveBundleImportPath(const QString &bundleId) { - 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 {}; - return bundleImportPath.resolvePath(projectBundlePath); + return bundleImportPath.resolvePath(bundleId); } -} // namespace QmlDesigner::Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h index 3aff09fe34..8155311e3e 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarybundleimporter.h @@ -3,59 +3,53 @@ #pragma once -#include <utils/filepath.h> +#include <modelfwd.h> -#include "nodemetainfo.h" +#include <utils/filepath.h> #include <QTimer> #include <QVariantHash> -QT_BEGIN_NAMESPACE -QT_END_NAMESPACE +namespace QmlDesigner { -namespace QmlDesigner::Internal { +class NodeMetaInfo; class ContentLibraryBundleImporter : public QObject { Q_OBJECT public: - ContentLibraryBundleImporter(const QString &bundleDir, - const QString &bundleId, - const QStringList &sharedFiles, - QObject *parent = nullptr); + ContentLibraryBundleImporter(QObject *parent = nullptr); ~ContentLibraryBundleImporter() = default; - QString importComponent(const QString &qmlFile, + QString importComponent(const QString &bundleDir, const TypeName &type, const QString &qmlFile, const QStringList &files); - QString unimportComponent(const QString &qmlFile); - Utils::FilePath resolveBundleImportPath(); + QString unimportComponent(const TypeName &type, const QString &qmlFile); + Utils::FilePath resolveBundleImportPath(const QString &bundleId); signals: // The metaInfo parameter will be invalid if an error was encountered during // asynchronous part of the import. In this case all remaining pending imports have been // terminated, and will not receive separate importFinished notifications. #ifdef QDS_USE_PROJECTSTORAGE - void importFinished(const QmlDesigner::TypeName &typeName); + void importFinished(const QmlDesigner::TypeName &typeName, const QString &bundleId); #else - void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo); + void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId); #endif - void unimportFinished(const QmlDesigner::NodeMetaInfo &metaInfo); + void unimportFinished(const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId); + void aboutToUnimport(const TypeName &type, const QString &bundleId); private: void handleImportTimer(); QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath); void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap); - Utils::FilePath m_bundleDir; - QString m_bundleId; - QString m_moduleName; - QStringList m_sharedFiles; QTimer m_importTimer; int m_importTimerCount = 0; - bool m_importAddPending = false; + QString m_pendingImport; + QString m_bundleId; bool m_fullReset = false; - QHash<QString, bool> m_pendingTypes; // <type, isImport> + QHash<TypeName, bool> m_pendingTypes; // <type, isImport> }; -} // namespace QmlDesigner::Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.cpp deleted file mode 100644 index f572fbe65f..0000000000 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2023 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "contentlibraryeffect.h" - -#include <QFileInfo> - -namespace QmlDesigner { - -ContentLibraryEffect::ContentLibraryEffect(QObject *parent, - const QString &name, - const QString &qml, - const TypeName &type, - const QUrl &icon, - const QStringList &files) - : QObject(parent), m_name(name), m_qml(qml), m_type(type), m_icon(icon), m_files(files) -{ - m_allFiles = m_files; - m_allFiles.push_back(m_qml); -} - -bool ContentLibraryEffect::filter(const QString &searchText) -{ - if (m_visible != m_name.contains(searchText, Qt::CaseInsensitive)) { - m_visible = !m_visible; - emit itemVisibleChanged(); - } - - return m_visible; -} - -QUrl ContentLibraryEffect::icon() const -{ - return m_icon; -} - -QString ContentLibraryEffect::qml() const -{ - return m_qml; -} - -TypeName ContentLibraryEffect::type() const -{ - return m_type; -} - -QStringList ContentLibraryEffect::files() const -{ - return m_files; -} - -bool ContentLibraryEffect::visible() const -{ - return m_visible; -} - -bool ContentLibraryEffect::setImported(bool imported) -{ - if (m_imported != imported) { - m_imported = imported; - emit itemImportedChanged(); - return true; - } - - return false; -} - -bool ContentLibraryEffect::imported() const -{ - return m_imported; -} - -QStringList ContentLibraryEffect::allFiles() const -{ - return m_allFiles; -} - -} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp index 38e6eed3da..f904775d52 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.cpp @@ -3,14 +3,12 @@ #include "contentlibraryeffectscategory.h" -#include "contentlibraryeffect.h" - namespace QmlDesigner { ContentLibraryEffectsCategory::ContentLibraryEffectsCategory(QObject *parent, const QString &name) : QObject(parent), m_name(name) {} -void ContentLibraryEffectsCategory::addBundleItem(ContentLibraryEffect *bundleItem) +void ContentLibraryEffectsCategory::addBundleItem(ContentLibraryItem *bundleItem) { m_categoryItems.append(bundleItem); } @@ -19,7 +17,7 @@ bool ContentLibraryEffectsCategory::updateImportedState(const QStringList &impor { bool changed = false; - for (ContentLibraryEffect *item : std::as_const(m_categoryItems)) + for (ContentLibraryItem *item : std::as_const(m_categoryItems)) changed |= item->setImported(importedItems.contains(item->qml().chopped(4))); return changed; @@ -28,7 +26,7 @@ bool ContentLibraryEffectsCategory::updateImportedState(const QStringList &impor bool ContentLibraryEffectsCategory::filter(const QString &searchText) { bool visible = false; - for (ContentLibraryEffect *item : std::as_const(m_categoryItems)) + for (ContentLibraryItem *item : std::as_const(m_categoryItems)) visible |= item->filter(searchText); if (visible != m_visible) { @@ -55,7 +53,7 @@ bool ContentLibraryEffectsCategory::expanded() const return m_expanded; } -QList<ContentLibraryEffect *> ContentLibraryEffectsCategory::categoryItems() const +QList<ContentLibraryItem *> ContentLibraryEffectsCategory::categoryItems() const { return m_categoryItems; } diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h index 79737c1ec2..9f56de3c6b 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectscategory.h @@ -3,12 +3,12 @@ #pragma once +#include "contentlibraryitem.h" + #include <QObject> namespace QmlDesigner { -class ContentLibraryEffect; - class ContentLibraryEffectsCategory : public QObject { Q_OBJECT @@ -16,20 +16,20 @@ class ContentLibraryEffectsCategory : public QObject Q_PROPERTY(QString bundleCategoryName MEMBER m_name CONSTANT) Q_PROPERTY(bool bundleCategoryVisible MEMBER m_visible NOTIFY categoryVisibleChanged) Q_PROPERTY(bool bundleCategoryExpanded MEMBER m_expanded NOTIFY categoryExpandChanged) - Q_PROPERTY(QList<ContentLibraryEffect *> bundleCategoryItems MEMBER m_categoryItems + Q_PROPERTY(QList<ContentLibraryItem *> bundleCategoryItems MEMBER m_categoryItems NOTIFY categoryItemsChanged) public: ContentLibraryEffectsCategory(QObject *parent, const QString &name); - void addBundleItem(ContentLibraryEffect *bundleItem); + void addBundleItem(ContentLibraryItem *bundleItem); bool updateImportedState(const QStringList &importedMats); bool filter(const QString &searchText); QString name() const; bool visible() const; bool expanded() const; - QList<ContentLibraryEffect *> categoryItems() const; + QList<ContentLibraryItem *> categoryItems() const; signals: void categoryVisibleChanged(); @@ -41,7 +41,7 @@ private: bool m_visible = true; bool m_expanded = true; - QList<ContentLibraryEffect *> m_categoryItems; + QList<ContentLibraryItem *> m_categoryItems; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp index 6b1de2d2a7..e0fe2b6c54 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.cpp @@ -4,11 +4,11 @@ #include "contentlibraryeffectsmodel.h" #include "contentlibrarybundleimporter.h" -#include "contentlibraryeffect.h" #include "contentlibraryeffectscategory.h" +#include "contentlibraryitem.h" #include "contentlibrarywidget.h" -#include "qmldesignerconstants.h" +#include <qmldesignerplugin.h> #include <utils/algorithm.h> #include <utils/qtcassert.h> #include <utils/hostosinfo.h> @@ -63,6 +63,11 @@ bool ContentLibraryEffectsModel::isValidIndex(int idx) const return idx > -1 && idx < rowCount(); } +QString ContentLibraryEffectsModel::bundleId() const +{ + return m_bundleId; +} + void ContentLibraryEffectsModel::updateIsEmpty() { bool anyCatVisible = Utils::anyOf(m_bundleCategories, [&](ContentLibraryEffectsCategory *cat) { @@ -88,49 +93,23 @@ QHash<int, QByteArray> ContentLibraryEffectsModel::roleNames() const return roles; } -void ContentLibraryEffectsModel::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 bundleItemImported(typeName); - }); -#else - connect(m_importer, - &Internal::ContentLibraryBundleImporter::importFinished, - this, - [&](const QmlDesigner::NodeMetaInfo &metaInfo) { - m_importerRunning = false; - emit importerRunningChanged(); - if (metaInfo.isValid()) - emit bundleItemImported(metaInfo); - }); -#endif - - connect(m_importer, &Internal::ContentLibraryBundleImporter::unimportFinished, this, - [&](const QmlDesigner::NodeMetaInfo &metaInfo) { - Q_UNUSED(metaInfo) - m_importerRunning = false; - emit importerRunningChanged(); - emit bundleItemUnimported(metaInfo); - }); - - resetModel(); - updateIsEmpty(); -} - void ContentLibraryEffectsModel::loadBundle() { - if (m_bundleExists || m_probeBundleDir) + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + if (m_probeBundleDir || (m_bundleExists && m_bundleId == compUtils.effectsBundleId())) return; + // clean up + qDeleteAll(m_bundleCategories); + m_bundleCategories.clear(); + m_bundleExists = false; + m_isEmpty = true; + m_probeBundleDir = false; + m_bundleObj = {}; + m_bundleId.clear(); + m_bundlePath.clear(); + QDir bundleDir; if (!qEnvironmentVariable("EFFECT_BUNDLE_PATH").isEmpty()) @@ -145,30 +124,32 @@ void ContentLibraryEffectsModel::loadBundle() while (!bundleDir.cd("effect_bundle") && bundleDir.cdUp()) ; // do nothing - if (bundleDir.dirName() != "effect_bundle") // bundlePathDir not found + if (bundleDir.dirName() != "effect_bundle") { // bundlePathDir not found + resetModel(); return; + } } QString bundlePath = bundleDir.filePath("effect_bundle.json"); - if (m_bundleObj.isEmpty()) { - QFile propsFile(bundlePath); - - if (!propsFile.open(QIODevice::ReadOnly)) { - qWarning("Couldn't open effect_bundle.json"); - return; - } + QFile bundleFile(bundlePath); + if (!bundleFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open effect_bundle.json"); + resetModel(); + return; + } - QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(propsFile.readAll()); - if (bundleJsonDoc.isNull()) { - qWarning("Invalid effect_bundle.json file"); - return; - } else { - m_bundleObj = bundleJsonDoc.object(); - } + QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(bundleFile.readAll()); + if (bundleJsonDoc.isNull()) { + qWarning("Invalid effect_bundle.json file"); + resetModel(); + return; } - QString bundleId = m_bundleObj.value("id").toString(); + m_bundleObj = bundleJsonDoc.object(); + + QString bundleType = compUtils.effectsBundleType(); + m_bundleId = compUtils.effectsBundleId(); const QJsonObject catsObj = m_bundleObj.value("categories").toObject(); const QStringList categories = catsObj.keys(); @@ -176,38 +157,36 @@ void ContentLibraryEffectsModel::loadBundle() auto category = new ContentLibraryEffectsCategory(this, cat); const QJsonObject itemsObj = catsObj.value(cat).toObject(); - const QStringList items = itemsObj.keys(); - for (const QString &item : items) { - const QJsonObject itemObj = itemsObj.value(item).toObject(); + const QStringList itemsNames = itemsObj.keys(); + for (const QString &itemName : itemsNames) { + const QJsonObject itemObj = itemsObj.value(itemName).toObject(); QStringList files; const QJsonArray assetsArr = itemObj.value("files").toArray(); - for (const auto /*QJson{Const,}ValueRef*/ &asset : assetsArr) + for (const QJsonValueConstRef &asset : assetsArr) files.append(asset.toString()); 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") + .arg(bundleType, qml.chopped(4)).toLatin1(); // chopped(4): remove .qml - auto bundleItem = new ContentLibraryEffect(category, item, qml, type, icon, files); + auto bundleItem = new ContentLibraryItem(category, itemName, qml, type, icon, files); category->addBundleItem(bundleItem); } m_bundleCategories.append(category); } - QStringList sharedFiles; + m_bundleSharedFiles.clear(); const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray(); - for (const auto /*QJson{Const,}ValueRef*/ &file : sharedFilesArr) - sharedFiles.append(file.toString()); - - createImporter(bundleDir.path(), bundleId, sharedFiles); + for (const QJsonValueConstRef &file : sharedFilesArr) + m_bundleSharedFiles.append(file.toString()); + m_bundlePath = bundleDir.path(); m_bundleExists = true; - emit bundleExistsChanged(); + updateIsEmpty(); + resetModel(); } bool ContentLibraryEffectsModel::hasRequiredQuick3DImport() const @@ -220,11 +199,6 @@ bool ContentLibraryEffectsModel::bundleExists() const return m_bundleExists; } -Internal::ContentLibraryBundleImporter *ContentLibraryEffectsModel::bundleImporter() const -{ - return m_importer; -} - void ContentLibraryEffectsModel::setSearchText(const QString &searchText) { QString lowerSearchText = searchText.toLower(); @@ -277,30 +251,26 @@ void ContentLibraryEffectsModel::resetModel() endResetModel(); } -void ContentLibraryEffectsModel::addInstance(ContentLibraryEffect *bundleItem) +void ContentLibraryEffectsModel::addInstance(ContentLibraryItem *bundleItem) { - QString err = m_importer->importComponent(bundleItem->qml(), bundleItem->files()); + QString err = m_widget->importer()->importComponent(m_bundlePath, bundleItem->type(), + bundleItem->qml(), + bundleItem->files() + m_bundleSharedFiles); - if (err.isEmpty()) { - m_importerRunning = true; - emit importerRunningChanged(); - } else { + if (err.isEmpty()) + m_widget->setImporterRunning(true); + else qWarning() << __FUNCTION__ << err; - } } -void ContentLibraryEffectsModel::removeFromProject(ContentLibraryEffect *bundleItem) +void ContentLibraryEffectsModel::removeFromProject(ContentLibraryItem *bundleItem) { - emit bundleItemAboutToUnimport(bundleItem->type()); + QString err = m_widget->importer()->unimportComponent(bundleItem->type(), bundleItem->qml()); - QString err = m_importer->unimportComponent(bundleItem->qml()); - - if (err.isEmpty()) { - m_importerRunning = true; - emit importerRunningChanged(); - } else { + if (err.isEmpty()) + m_widget->setImporterRunning(true); + else qWarning() << __FUNCTION__ << err; - } } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h index 5d67ac3da8..5bcb857fca 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffectsmodel.h @@ -3,22 +3,15 @@ #pragma once -#include "nodemetainfo.h" - #include <QAbstractListModel> -#include <QDir> #include <QJsonObject> namespace QmlDesigner { -class ContentLibraryEffect; +class ContentLibraryItem; class ContentLibraryEffectsCategory; class ContentLibraryWidget; -namespace Internal { -class ContentLibraryBundleImporter; -} - class ContentLibraryEffectsModel : public QAbstractListModel { Q_OBJECT @@ -26,7 +19,6 @@ class ContentLibraryEffectsModel : public QAbstractListModel Q_PROPERTY(bool bundleExists READ bundleExists NOTIFY bundleExistsChanged) Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged) - Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged) public: ContentLibraryEffectsModel(ContentLibraryWidget *parent = nullptr); @@ -49,46 +41,33 @@ public: void resetModel(); void updateIsEmpty(); - Internal::ContentLibraryBundleImporter *bundleImporter() const; + Q_INVOKABLE void addInstance(QmlDesigner::ContentLibraryItem *bundleItem); + Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryItem *bundleItem); - Q_INVOKABLE void addInstance(QmlDesigner::ContentLibraryEffect *bundleItem); - Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryEffect *bundleItem); + QString bundleId() const; signals: void isEmptyChanged(); void hasRequiredQuick3DImportChanged(); -#ifdef QDS_USE_PROJECTSTORAGE - void bundleItemImported(const QmlDesigner::TypeName &typeName); -#else - void bundleItemImported(const QmlDesigner::NodeMetaInfo &metaInfo); -#endif - void bundleItemAboutToUnimport(const QmlDesigner::TypeName &type); - void bundleItemUnimported(const QmlDesigner::NodeMetaInfo &metaInfo); - void importerRunningChanged(); void bundleExistsChanged(); private: 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_bundlePath; + QString m_bundleId; + QStringList m_bundleSharedFiles; QList<ContentLibraryEffectsCategory *> m_bundleCategories; QJsonObject m_bundleObj; - Internal::ContentLibraryBundleImporter *m_importer = nullptr; bool m_isEmpty = true; bool m_bundleExists = false; bool m_probeBundleDir = false; - bool m_importerRunning = false; int m_quick3dMajorVersion = -1; int m_quick3dMinorVersion = -1; - - QString m_importerBundlePath; - QString m_importerBundleId; - QStringList m_importerSharedFiles; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp new file mode 100644 index 0000000000..38fb69abbc --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "contentlibraryitem.h" + +namespace QmlDesigner { + +ContentLibraryItem::ContentLibraryItem(QObject *parent, + const QString &name, + const QString &qml, + const TypeName &type, + const QUrl &icon, + const QStringList &files) + : QObject(parent), m_name(name), m_qml(qml), m_type(type), m_icon(icon), m_files(files) +{ + m_allFiles = m_files; + m_allFiles.push_back(m_qml); +} + +bool ContentLibraryItem::filter(const QString &searchText) +{ + if (m_visible != m_name.contains(searchText, Qt::CaseInsensitive)) { + m_visible = !m_visible; + emit itemVisibleChanged(); + } + + return m_visible; +} + +QUrl ContentLibraryItem::icon() const +{ + return m_icon; +} + +QString ContentLibraryItem::qml() const +{ + return m_qml; +} + +TypeName ContentLibraryItem::type() const +{ + return m_type; +} + +QStringList ContentLibraryItem::files() const +{ + return m_files; +} + +bool ContentLibraryItem::visible() const +{ + return m_visible; +} + +bool ContentLibraryItem::setImported(bool imported) +{ + if (m_imported != imported) { + m_imported = imported; + emit itemImportedChanged(); + return true; + } + + return false; +} + +bool ContentLibraryItem::imported() const +{ + return m_imported; +} + +QStringList ContentLibraryItem::allFiles() const +{ + return m_allFiles; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h index fdb302b613..bdf8736728 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryeffect.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryitem.h @@ -10,7 +10,7 @@ namespace QmlDesigner { -class ContentLibraryEffect : public QObject +class ContentLibraryItem : public QObject { Q_OBJECT @@ -19,14 +19,15 @@ class ContentLibraryEffect : public QObject Q_PROPERTY(QStringList bundleItemFiles READ allFiles CONSTANT) Q_PROPERTY(bool bundleItemVisible MEMBER m_visible NOTIFY itemVisibleChanged) Q_PROPERTY(bool bundleItemImported READ imported WRITE setImported NOTIFY itemImportedChanged) + Q_PROPERTY(QString itemType MEMBER m_itemType CONSTANT) public: - ContentLibraryEffect(QObject *parent, - const QString &name, - const QString &qml, - const TypeName &type, - const QUrl &icon, - const QStringList &files); + ContentLibraryItem(QObject *parent, + const QString &name, + const QString &qml, + const TypeName &type, + const QUrl &icon, + const QStringList &files); bool filter(const QString &searchText); @@ -35,11 +36,9 @@ public: TypeName type() const; QStringList files() const; bool visible() const; - QString qmlFilePath() const; bool setImported(bool imported); bool imported() const; - QString parentDirPath() const; QStringList allFiles() const; signals: @@ -57,6 +56,7 @@ private: bool m_imported = false; QStringList m_allFiles; + const QString m_itemType = "item"; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp index 834bc8aa30..25d1523199 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.cpp @@ -32,6 +32,11 @@ bool ContentLibraryMaterial::filter(const QString &searchText) return m_visible; } +QString ContentLibraryMaterial::name() const +{ + return m_name; +} + QUrl ContentLibraryMaterial::icon() const { return m_icon; @@ -84,7 +89,7 @@ QString ContentLibraryMaterial::qmlFilePath() const return m_downloadPath + "/" + m_qml; } -QString ContentLibraryMaterial::parentDirPath() const +QString ContentLibraryMaterial::dirPath() const { return m_downloadPath; } diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterial.h index f546ea98cd..aaaf9517f9 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> @@ -18,10 +17,11 @@ class ContentLibraryMaterial : public QObject Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT) Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT) Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged) - Q_PROPERTY(bool bundleMaterialImported READ imported WRITE setImported NOTIFY materialImportedChanged) + Q_PROPERTY(bool bundleItemImported READ imported WRITE setImported NOTIFY materialImportedChanged) Q_PROPERTY(QString bundleMaterialBaseWebUrl MEMBER m_baseWebUrl CONSTANT) - Q_PROPERTY(QString bundleMaterialParentPath READ parentDirPath CONSTANT) + Q_PROPERTY(QString bundleMaterialDirPath READ dirPath CONSTANT) Q_PROPERTY(QStringList bundleMaterialFiles READ allFiles CONSTANT) + Q_PROPERTY(QString itemType MEMBER m_itemType CONSTANT) public: ContentLibraryMaterial(QObject *parent, @@ -31,12 +31,13 @@ public: const QUrl &icon, const QStringList &files, const QString &downloadPath, - const QString &baseWebUrl); + const QString &baseWebUrl = {}); bool filter(const QString &searchText); Q_INVOKABLE bool isDownloaded() const; + QString name() const; QUrl icon() const; QString qml() const; TypeName type() const; @@ -46,7 +47,7 @@ public: bool setImported(bool imported); bool imported() const; - QString parentDirPath() const; + QString dirPath() const; QStringList allFiles() const; signals: @@ -66,6 +67,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..adb47108a9 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.cpp @@ -8,18 +8,19 @@ #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> #include <utils/qtcassert.h> #include <QCoreApplication> +#include <QDir> #include <QJsonArray> #include <QJsonDocument> #include <QQmlEngine> @@ -40,7 +41,10 @@ ContentLibraryMaterialsModel::ContentLibraryMaterialsModel(ContentLibraryWidget qmlRegisterType<QmlDesigner::FileDownloader>("WebFetcher", 1, 0, "FileDownloader"); qmlRegisterType<QmlDesigner::MultiFileDownloader>("WebFetcher", 1, 0, "MultiFileDownloader"); +} +void ContentLibraryMaterialsModel::loadBundle() +{ QDir bundleDir{m_downloadPath}; if (fetchBundleMetadata(bundleDir) && fetchBundleIcons(bundleDir)) loadMaterialBundle(bundleDir); @@ -192,11 +196,9 @@ void ContentLibraryMaterialsModel::downloadSharedFiles(const QDir &targetDir, co extractor->setAlwaysCreateDir(false); extractor->setClearTargetPathContents(false); - QObject::connect(extractor, &FileExtractor::finishedChanged, this, [this, downloader, extractor]() { + QObject::connect(extractor, &FileExtractor::finishedChanged, this, [downloader, extractor]() { downloader->deleteLater(); extractor->deleteLater(); - - createImporter(m_importerBundlePath, m_importerBundleId, m_importerSharedFiles); }); extractor->extract(); @@ -205,93 +207,68 @@ void ContentLibraryMaterialsModel::downloadSharedFiles(const QDir &targetDir, co downloader->start(); } -void ContentLibraryMaterialsModel::createImporter(const QString &bundlePath, const QString &bundleId, - const QStringList &sharedFiles) +QString ContentLibraryMaterialsModel::bundleId() const { - 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(); + return m_bundleId; } -void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &matBundleDir) +void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &bundleDir) { - if (m_matBundleExists) + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + if (m_bundleExists && m_bundleId == compUtils.materialsBundleId()) return; - QString matBundlePath = matBundleDir.filePath("material_bundle.json"); + // clean up + qDeleteAll(m_bundleCategories); + m_bundleCategories.clear(); + m_bundleExists = false; + m_isEmpty = true; + m_bundleObj = {}; + m_bundleId.clear(); - if (m_matBundleObj.isEmpty()) { - QFile matPropsFile(matBundlePath); + QString bundlePath = bundleDir.filePath("material_bundle.json"); - if (!matPropsFile.open(QIODevice::ReadOnly)) { - qWarning("Couldn't open material_bundle.json"); - return; - } + QFile bundleFile(bundlePath); + if (!bundleFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open material_bundle.json"); + resetModel(); + return; + } - QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(matPropsFile.readAll()); - if (matBundleJsonDoc.isNull()) { - qWarning("Invalid material_bundle.json file"); - return; - } else { - m_matBundleObj = matBundleJsonDoc.object(); - } + QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(bundleFile.readAll()); + if (bundleJsonDoc.isNull()) { + qWarning("Invalid material_bundle.json file"); + resetModel(); + return; } - QString bundleId = m_matBundleObj.value("id").toString(); + m_bundleObj = bundleJsonDoc.object(); - const QJsonObject catsObj = m_matBundleObj.value("categories").toObject(); + QString bundleType = compUtils.materialsBundleType(); + m_bundleId = compUtils.materialsBundleId(); + + const QJsonObject catsObj = m_bundleObj.value("categories").toObject(); const QStringList categories = catsObj.keys(); for (const QString &cat : categories) { 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(); - for (const auto /*QJson{Const,}ValueRef*/ &asset : assetsArr) + for (const QJsonValueConstRef &asset : assetsArr) files.append(asset.toString()); - QUrl icon = QUrl::fromLocalFile(matBundleDir.filePath(matObj.value("icon").toString())); + QUrl icon = QUrl::fromLocalFile(bundleDir.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") + .arg(bundleType, 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); @@ -299,30 +276,23 @@ void ContentLibraryMaterialsModel::loadMaterialBundle(const QDir &matBundleDir) m_bundleCategories.append(category); } - QStringList sharedFiles; - const QJsonArray sharedFilesArr = m_matBundleObj.value("sharedFiles").toArray(); - for (const auto /*QJson{Const,}ValueRef*/ &file : sharedFilesArr) - sharedFiles.append(file.toString()); + m_bundleSharedFiles.clear(); + const QJsonArray sharedFilesArr = m_bundleObj.value("sharedFiles").toArray(); + for (const QJsonValueConstRef &file : sharedFilesArr) + m_bundleSharedFiles.append(file.toString()); QStringList missingSharedFiles; - for (const QString &s : std::as_const(sharedFiles)) { - const QString fullSharedFilePath = matBundleDir.filePath(s); - - if (!QFileInfo::exists(fullSharedFilePath)) + for (const QString &s : std::as_const(m_bundleSharedFiles)) { + if (!QFileInfo::exists(bundleDir.filePath(s))) missingSharedFiles.push_back(s); } - if (missingSharedFiles.length() > 0) { - m_importerBundlePath = matBundleDir.path(); - m_importerBundleId = bundleId; - m_importerSharedFiles = sharedFiles; - downloadSharedFiles(matBundleDir, missingSharedFiles); - } else { - createImporter(matBundleDir.path(), bundleId, sharedFiles); - } + if (missingSharedFiles.length() > 0) + downloadSharedFiles(bundleDir, missingSharedFiles); - m_matBundleExists = true; - emit matBundleExistsChanged(); + m_bundleExists = true; + updateIsEmpty(); + resetModel(); } bool ContentLibraryMaterialsModel::hasRequiredQuick3DImport() const @@ -332,12 +302,7 @@ bool ContentLibraryMaterialsModel::hasRequiredQuick3DImport() const bool ContentLibraryMaterialsModel::matBundleExists() const { - return m_matBundleExists; -} - -Internal::ContentLibraryBundleImporter *ContentLibraryMaterialsModel::bundleImporter() const -{ - return m_importer; + return m_bundleExists; } void ContentLibraryMaterialsModel::setSearchText(const QString &searchText) @@ -359,11 +324,11 @@ void ContentLibraryMaterialsModel::setSearchText(const QString &searchText) updateIsEmpty(); } -void ContentLibraryMaterialsModel::updateImportedState(const QStringList &importedMats) +void ContentLibraryMaterialsModel::updateImportedState(const QStringList &importedItems) { bool changed = false; for (ContentLibraryMaterialsCategory *cat : std::as_const(m_bundleCategories)) - changed |= cat->updateImportedState(importedMats); + changed |= cat->updateImportedState(importedItems); if (changed) resetModel(); @@ -399,42 +364,23 @@ void ContentLibraryMaterialsModel::applyToSelected(ContentLibraryMaterial *mat, void ContentLibraryMaterialsModel::addToProject(ContentLibraryMaterial *mat) { - QString err = m_importer->importComponent(mat->qml(), mat->files()); + QString err = m_widget->importer()->importComponent(mat->dirPath(), mat->type(), mat->qml(), + mat->files() + m_bundleSharedFiles); - if (err.isEmpty()) { - m_importerRunning = true; - emit importerRunningChanged(); - } else { + if (err.isEmpty()) + m_widget->setImporterRunning(true); + else qWarning() << __FUNCTION__ << err; - } } void ContentLibraryMaterialsModel::removeFromProject(ContentLibraryMaterial *mat) { - emit bundleMaterialAboutToUnimport(mat->type()); + QString err = m_widget->importer()->unimportComponent(mat->type(), mat->qml()); - QString err = m_importer->unimportComponent(mat->qml()); - - if (err.isEmpty()) { - m_importerRunning = true; - emit importerRunningChanged(); - } else { + if (err.isEmpty()) + m_widget->setImporterRunning(true); + else qWarning() << __FUNCTION__ << err; - } -} - -bool ContentLibraryMaterialsModel::hasModelSelection() const -{ - return m_hasModelSelection; -} - -void ContentLibraryMaterialsModel::setHasModelSelection(bool b) -{ - if (b == m_hasModelSelection) - return; - - m_hasModelSelection = b; - emit hasModelSelectionChanged(); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h index 21bd374137..c0840dc3cc 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarymaterialsmodel.h @@ -3,22 +3,17 @@ #pragma once -#include "nodemetainfo.h" - #include <QAbstractListModel> -#include <QDir> #include <QJsonObject> +QT_FORWARD_DECLARE_CLASS(QDir) + namespace QmlDesigner { class ContentLibraryMaterial; class ContentLibraryMaterialsCategory; class ContentLibraryWidget; -namespace Internal { -class ContentLibraryBundleImporter; -} - class ContentLibraryMaterialsModel : public QAbstractListModel { Q_OBJECT @@ -26,8 +21,6 @@ class ContentLibraryMaterialsModel : public QAbstractListModel 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) public: ContentLibraryMaterialsModel(ContentLibraryWidget *parent = nullptr); @@ -38,40 +31,27 @@ public: QHash<int, QByteArray> roleNames() const override; void setSearchText(const QString &searchText); - void updateImportedState(const QStringList &importedMats); - + void updateImportedState(const QStringList &importedItems); void setQuick3DImportVersion(int major, int minor); bool hasRequiredQuick3DImport() const; - bool matBundleExists() const; - bool hasModelSelection() const; - void setHasModelSelection(bool b); - void resetModel(); void updateIsEmpty(); - - Internal::ContentLibraryBundleImporter *bundleImporter() const; + void loadBundle(); Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryMaterial *mat, bool add = false); Q_INVOKABLE void addToProject(QmlDesigner::ContentLibraryMaterial *mat); Q_INVOKABLE void removeFromProject(QmlDesigner::ContentLibraryMaterial *mat); + QString bundleId() const; + signals: void isEmptyChanged(); void hasRequiredQuick3DImportChanged(); - void hasModelSelectionChanged(); void materialVisibleChanged(); 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: @@ -80,29 +60,22 @@ private: bool fetchBundleMetadata(const QDir &bundleDir); bool isValidIndex(int idx) const; void downloadSharedFiles(const QDir &targetDir, const QStringList &files); - void createImporter(const QString &bundlePath, const QString &bundleId, - const QStringList &sharedFiles); ContentLibraryWidget *m_widget = nullptr; QString m_searchText; + QString m_bundleId; + QStringList m_bundleSharedFiles; QList<ContentLibraryMaterialsCategory *> m_bundleCategories; - QJsonObject m_matBundleObj; - Internal::ContentLibraryBundleImporter *m_importer = nullptr; + QJsonObject m_bundleObj; bool m_isEmpty = true; - bool m_matBundleExists = false; - bool m_hasModelSelection = false; - bool m_importerRunning = false; + bool m_bundleExists = false; int m_quick3dMajorVersion = -1; int m_quick3dMinorVersion = -1; QString m_downloadPath; QString m_baseUrl; - - QString m_importerBundlePath; - QString m_importerBundleId; - QStringList m_importerSharedFiles; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarytexture.cpp index 7ab239aab4..d2c2df2baa 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,21 @@ 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(); } +bool ContentLibraryTexture::visible() const +{ + return m_visible; +} + 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..7f5db6d7d6 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,30 +39,32 @@ public: QUrl icon() const; QString iconPath() const; - QString downloadedTexturePath() const; + QString texturePath() const; QString parentDirPath() const; QString textureKey() const; void setHasUpdate(bool value); bool hasUpdate() const; + bool visible() const; + signals: void textureVisibleChanged(); void textureToolTipChanged(); 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 +74,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..8dcf4575f7 --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.cpp @@ -0,0 +1,686 @@ +// 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 "contentlibraryitem.h" +#include "contentlibrarymaterial.h" +#include "contentlibrarymaterialscategory.h" +#include "contentlibrarytexture.h" +#include "contentlibrarywidget.h" + +#include <designerpaths.h> +#include <imageutils.h> +#include <qmldesignerconstants.h> +#include <qmldesignerplugin.h> +#include <uniquename.h> + +#include <utils/algorithm.h> +#include <utils/qtcassert.h> + +#include <QFileInfo> +#include <QJsonArray> +#include <QJsonDocument> +#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 +} + +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() == MaterialsSectionIdx) + return QVariant::fromValue(m_userMaterials); + if (index.row() == TexturesSectionIdx) + return QVariant::fromValue(m_userTextures); + if (index.row() == Items3DSectionIdx) + return QVariant::fromValue(m_user3DItems); + if (index.row() == EffectsSectionIdx) + return QVariant::fromValue(m_userEffects); + } + + if (role == NoMatchRole) { + if (index.row() == MaterialsSectionIdx) + return m_noMatchMaterials; + if (index.row() == TexturesSectionIdx) + return m_noMatchTextures; + if (index.row() == Items3DSectionIdx) + return m_noMatch3D; + if (index.row() == EffectsSectionIdx) + return m_noMatchEffects; + } + + if (role == VisibleRole) { + if (index.row() == MaterialsSectionIdx) + return !m_userMaterials.isEmpty(); + if (index.row() == TexturesSectionIdx) + return !m_userTextures.isEmpty(); + if (index.row() == Items3DSectionIdx) + return !m_user3DItems.isEmpty(); + if (index.row() == EffectsSectionIdx) + return !m_userEffects.isEmpty(); + } + + return {}; +} + +bool ContentLibraryUserModel::isValidIndex(int idx) const +{ + return idx > -1 && idx < rowCount(); +} + +void ContentLibraryUserModel::updateNoMatchMaterials() +{ + m_noMatchMaterials = Utils::allOf(m_userMaterials, [&](ContentLibraryMaterial *item) { + return !item->visible(); + }); +} + +void ContentLibraryUserModel::updateNoMatchTextures() +{ + m_noMatchTextures = Utils::allOf(m_userTextures, [&](ContentLibraryTexture *item) { + return !item->visible(); + }); +} + +void ContentLibraryUserModel::updateNoMatch3D() +{ + m_noMatch3D = Utils::allOf(m_user3DItems, [&](ContentLibraryItem *item) { + return !item->visible(); + }); +} + +void ContentLibraryUserModel::addMaterial(const QString &name, const QString &qml, + const QUrl &icon, const QStringList &files) +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + QString typePrefix = compUtils.userMaterialsBundleType(); + TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); + + auto libMat = new ContentLibraryMaterial(this, name, qml, type, icon, files, + Paths::bundlesPathSetting().append("/User/materials")); + m_userMaterials.append(libMat); + + emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); +} + +void ContentLibraryUserModel::add3DItem(const QString &name, const QString &qml, + const QUrl &icon, const QStringList &files) +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + QString typePrefix = compUtils.user3DBundleType(); + TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); + + m_user3DItems.append(new ContentLibraryItem(this, name, qml, type, icon, files)); +} + +void ContentLibraryUserModel::refresh3DSection() +{ + emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); +} + +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); + } + + emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx)); +} + +void ContentLibraryUserModel::add3DInstance(ContentLibraryItem *bundleItem) +{ + QString err = m_widget->importer()->importComponent(m_bundlePath3D.path(), bundleItem->type(), + bundleItem->qml(), + bundleItem->files() + m_bundle3DSharedFiles); + + if (err.isEmpty()) + m_widget->setImporterRunning(true); + else + qWarning() << __FUNCTION__ << err; +} + +void ContentLibraryUserModel::removeTexture(ContentLibraryTexture *tex) +{ + // remove resources + Utils::FilePath::fromString(tex->texturePath()).removeFile(); + Utils::FilePath::fromString(tex->iconPath()).removeFile(); + + // remove from model + m_userTextures.removeOne(tex); + tex->deleteLater(); + + // update model + emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx)); +} + +void ContentLibraryUserModel::removeFromContentLib(QObject *item) +{ + if (auto mat = qobject_cast<ContentLibraryMaterial *>(item)) + removeMaterialFromContentLib(mat); + else if (auto itm = qobject_cast<ContentLibraryItem *>(item)) + remove3DFromContentLib(itm); +} + +void ContentLibraryUserModel::removeMaterialFromContentLib(ContentLibraryMaterial *item) +{ + auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/"); + + QJsonArray itemsArr = m_bundleObjMaterial.value("items").toArray(); + + // remove qml and icon files + Utils::FilePath::fromString(item->qmlFilePath()).removeFile(); + Utils::FilePath::fromUrl(item->icon()).removeFile(); + + // remove from the bundle json file + for (int i = 0; i < itemsArr.size(); ++i) { + if (itemsArr.at(i).toObject().value("qml") == item->qml()) { + itemsArr.removeAt(i); + break; + } + } + m_bundleObjMaterial.insert("items", itemsArr); + + auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) + .writeFileContents(QJsonDocument(m_bundleObjMaterial).toJson()); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + // delete dependency files if they are only used by the deleted material + QStringList allFiles; + for (const QJsonValueConstRef &itemRef : std::as_const(itemsArr)) + allFiles.append(itemRef.toObject().value("files").toVariant().toStringList()); + + const QStringList itemFiles = item->files(); + for (const QString &file : itemFiles) { + if (allFiles.count(file) == 0) // only used by the deleted item + bundlePath.pathAppended(file).removeFile(); + } + + // remove from model + m_userMaterials.removeOne(item); + item->deleteLater(); + + // update model + emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); +} + +void ContentLibraryUserModel::remove3DFromContentLibByName(const QString &qmlFileName) +{ + ContentLibraryItem *itemToRemove = Utils::findOr(m_user3DItems, nullptr, + [&qmlFileName](ContentLibraryItem *item) { + return item->qml() == qmlFileName; + }); + + if (itemToRemove) + remove3DFromContentLib(itemToRemove); +} + +void ContentLibraryUserModel::remove3DFromContentLib(ContentLibraryItem *item) +{ + QJsonArray itemsArr = m_bundleObj3D.value("items").toArray(); + + // remove qml and icon files + m_bundlePath3D.pathAppended(item->qml()).removeFile(); + Utils::FilePath::fromUrl(item->icon()).removeFile(); + + // remove from the bundle json file + for (int i = 0; i < itemsArr.size(); ++i) { + if (itemsArr.at(i).toObject().value("qml") == item->qml()) { + itemsArr.removeAt(i); + break; + } + } + m_bundleObj3D.insert("items", itemsArr); + + auto result = m_bundlePath3D.pathAppended(Constants::BUNDLE_JSON_FILENAME) + .writeFileContents(QJsonDocument(m_bundleObj3D).toJson()); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + // delete dependency files if they are only used by the deleted item + QStringList allFiles; + for (const QJsonValueConstRef &itemRef : std::as_const(itemsArr)) + allFiles.append(itemRef.toObject().value("files").toVariant().toStringList()); + + const QStringList itemFiles = item->files(); + for (const QString &file : itemFiles) { + if (allFiles.count(file) == 0) // only used by the deleted item + m_bundlePath3D.pathAppended(file).removeFile(); + } + + // remove from model + m_user3DItems.removeOne(item); + item->deleteLater(); + + // update model + emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); +} + +/** + * @brief Gets unique Qml component and icon file material names from a given name + * @param defaultName input name + * @return <Qml, icon> file names + */ +QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNames(const QString &defaultName) const +{ + return getUniqueLibItemNames(defaultName, m_bundleObjMaterial); +} + +/** + * @brief Gets unique Qml component and icon file 3d item names from a given name + * @param defaultName input name + * @return <Qml, icon> file names + */ +QPair<QString, QString> ContentLibraryUserModel::getUniqueLib3DNames(const QString &defaultName) const +{ + return getUniqueLibItemNames(defaultName, m_bundleObj3D); +} + +QPair<QString, QString> ContentLibraryUserModel::getUniqueLibItemNames(const QString &defaultName, + const QJsonObject &bundleObj) const +{ + QTC_ASSERT(!bundleObj.isEmpty(), return {}); + + const QJsonArray itemsArr = bundleObj.value("items").toArray(); + + QStringList itemQmls, itemIcons; + for (const QJsonValueConstRef &itemRef : itemsArr) { + const QJsonObject &obj = itemRef.toObject(); + itemQmls.append(obj.value("qml").toString().chopped(4)); // remove .qml + itemIcons.append(QFileInfo(obj.value("icon").toString()).baseName()); + } + + QString baseQml = UniqueName::generateId(defaultName); + baseQml[0] = baseQml.at(0).toUpper(); + baseQml.prepend("My"); + + QString uniqueQml = UniqueName::generate(baseQml, [&] (const QString &name) { + return itemQmls.contains(name); + }); + + QString uniqueIcon = UniqueName::generate(defaultName, [&] (const QString &name) { + return itemIcons.contains(name); + }); + + return {uniqueQml + ".qml", uniqueIcon + ".png"}; +} + +QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const +{ + static const QHash<int, QByteArray> roles { + {NameRole, "categoryName"}, + {VisibleRole, "categoryVisible"}, + {ItemsRole, "categoryItems"}, + {NoMatchRole, "categoryNoMatch"} + }; + return roles; +} + +QJsonObject &ContentLibraryUserModel::bundleJsonMaterialObjectRef() +{ + return m_bundleObjMaterial; +} + +QJsonObject &ContentLibraryUserModel::bundleJson3DObjectRef() +{ + return m_bundleObj3D; +} + +void ContentLibraryUserModel::loadBundles() +{ + loadMaterialBundle(); + load3DBundle(); + loadTextureBundle(); +} + +void ContentLibraryUserModel::loadMaterialBundle() +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + if (m_matBundleExists && m_bundleIdMaterial == compUtils.userMaterialsBundleId()) + return; + + // clean up + qDeleteAll(m_userMaterials); + m_userMaterials.clear(); + m_matBundleExists = false; + m_noMatchMaterials = true; + m_bundleObjMaterial = {}; + m_bundleIdMaterial.clear(); + + m_bundlePathMaterial = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials"); + m_bundlePathMaterial.ensureWritableDir(); + m_bundlePathMaterial.pathAppended("icons").ensureWritableDir(); + + auto jsonFilePath = m_bundlePathMaterial.pathAppended(Constants::BUNDLE_JSON_FILENAME); + if (!jsonFilePath.exists()) { + QString jsonContent = "{\n"; + jsonContent += " \"id\": \"UserMaterials\",\n"; + jsonContent += " \"items\": []\n"; + jsonContent += "}"; + Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent.toLatin1()); + if (!res.has_value()) { + qWarning() << __FUNCTION__ << res.error(); + emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); + return; + } + } + + Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents(); + if (!jsonContents.has_value()) { + qWarning() << __FUNCTION__ << jsonContents.error(); + emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); + return; + } + + QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value()); + if (bundleJsonDoc.isNull()) { + qWarning() << __FUNCTION__ << "Invalid json file" << jsonFilePath; + emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); + return; + } + + m_bundleIdMaterial = compUtils.userMaterialsBundleId(); + m_bundleObjMaterial = bundleJsonDoc.object(); + m_bundleObjMaterial["id"] = m_bundleIdMaterial; + + // parse items + QString typePrefix = compUtils.userMaterialsBundleType(); + const QJsonArray itemsArr = m_bundleObjMaterial.value("items").toArray(); + for (const QJsonValueConstRef &itemRef : itemsArr) { + const QJsonObject itemObj = itemRef.toObject(); + + QString name = itemObj.value("name").toString(); + QString qml = itemObj.value("qml").toString(); + TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); + QUrl icon = m_bundlePathMaterial.pathAppended(itemObj.value("icon").toString()).toUrl(); + QStringList files; + const QJsonArray assetsArr = itemObj.value("files").toArray(); + for (const QJsonValueConstRef &asset : assetsArr) + files.append(asset.toString()); + + m_userMaterials.append(new ContentLibraryMaterial(this, name, qml, type, icon, files, + m_bundlePathMaterial.path(), "")); + } + + m_bundleMaterialSharedFiles.clear(); + const QJsonArray sharedFilesArr = m_bundleObjMaterial.value("sharedFiles").toArray(); + for (const QJsonValueConstRef &file : sharedFilesArr) + m_bundleMaterialSharedFiles.append(file.toString()); + + m_matBundleExists = true; + updateNoMatchMaterials(); + emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); +} + +void ContentLibraryUserModel::load3DBundle() +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + if (m_bundle3DExists && m_bundleId3D == compUtils.user3DBundleId()) + return; + + // clean up + qDeleteAll(m_user3DItems); + m_user3DItems.clear(); + m_bundle3DExists = false; + m_noMatch3D = true; + m_bundleObj3D = {}; + m_bundleId3D.clear(); + + m_bundlePath3D = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d"); + m_bundlePath3D.ensureWritableDir(); + m_bundlePath3D.pathAppended("icons").ensureWritableDir(); + + auto jsonFilePath = m_bundlePath3D.pathAppended(Constants::BUNDLE_JSON_FILENAME); + if (!jsonFilePath.exists()) { + QByteArray jsonContent = "{\n"; + jsonContent += " \"id\": \"User3D\",\n"; + jsonContent += " \"items\": []\n"; + jsonContent += "}"; + Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent); + if (!res.has_value()) { + qWarning() << __FUNCTION__ << res.error(); + emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); + return; + } + } + + Utils::expected_str<QByteArray> jsonContents = jsonFilePath.fileContents(); + if (!jsonContents.has_value()) { + qWarning() << __FUNCTION__ << jsonContents.error(); + emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); + return; + } + + QJsonDocument bundleJsonDoc = QJsonDocument::fromJson(jsonContents.value()); + if (bundleJsonDoc.isNull()) { + qWarning() << __FUNCTION__ << "Invalid json file" << jsonFilePath; + emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); + return; + } + + m_bundleId3D = compUtils.user3DBundleId(); + m_bundleObj3D = bundleJsonDoc.object(); + m_bundleObj3D["id"] = m_bundleId3D; + + // parse items + QString typePrefix = compUtils.user3DBundleType(); + const QJsonArray itemsArr = m_bundleObj3D.value("items").toArray(); + for (const QJsonValueConstRef &itemRef : itemsArr) { + const QJsonObject itemObj = itemRef.toObject(); + + QString name = itemObj.value("name").toString(); + QString qml = itemObj.value("qml").toString(); + TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1(); + QUrl icon = m_bundlePath3D.pathAppended(itemObj.value("icon").toString()).toUrl(); + QStringList files; + const QJsonArray assetsArr = itemObj.value("files").toArray(); + for (const QJsonValueConstRef &asset : assetsArr) + files.append(asset.toString()); + + m_user3DItems.append(new ContentLibraryItem(nullptr, name, qml, type, icon, files)); + } + + m_bundle3DSharedFiles.clear(); + const QJsonArray sharedFilesArr = m_bundleObj3D.value("sharedFiles").toArray(); + for (const QJsonValueConstRef &file : sharedFilesArr) + m_bundle3DSharedFiles.append(file.toString()); + + m_bundle3DExists = true; + updateNoMatch3D(); + emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); +} + +void ContentLibraryUserModel::loadTextureBundle() +{ + if (!m_userTextures.isEmpty()) + return; + + QDir bundleDir{Paths::bundlesPathSetting() + "/User/textures"}; + bundleDir.mkpath("."); + bundleDir.mkdir("icons"); + + const QFileInfoList fileInfos = bundleDir.entryInfoList(QDir::Files); + for (const QFileInfo &fileInfo : fileInfos) { + QString suffix = '.' + fileInfo.suffix(); + auto iconFileInfo = QFileInfo(fileInfo.path().append("/icons/").append(fileInfo.baseName() + ".png")); + QPair<QSize, qint64> info = ImageUtils::imageInfo(fileInfo.path()); + QString dirPath = fileInfo.path(); + QSize imgDims = info.first; + qint64 imgFileSize = info.second; + + auto tex = new ContentLibraryTexture(this, iconFileInfo, dirPath, suffix, imgDims, imgFileSize); + m_userTextures.append(tex); + } + + updateNoMatchTextures(); + emit dataChanged(index(TexturesSectionIdx), index(TexturesSectionIdx)); +} + +bool ContentLibraryUserModel::hasRequiredQuick3DImport() const +{ + return m_widget->hasQuick3DImport() && m_quick3dMajorVersion == 6 && m_quick3dMinorVersion >= 3; +} + +bool ContentLibraryUserModel::matBundleExists() const +{ + return m_matBundleExists; +} + +void ContentLibraryUserModel::setSearchText(const QString &searchText) +{ + QString lowerSearchText = searchText.toLower(); + + if (m_searchText == lowerSearchText) + return; + + m_searchText = lowerSearchText; + + for (ContentLibraryMaterial *item : std::as_const(m_userMaterials)) + item->filter(m_searchText); + + for (ContentLibraryTexture *item : std::as_const(m_userTextures)) + item->filter(m_searchText); + + for (ContentLibraryItem *item : std::as_const(m_user3DItems)) + item->filter(m_searchText); + + updateNoMatchMaterials(); + updateNoMatchTextures(); + updateNoMatch3D(); + resetModel(); +} + +void ContentLibraryUserModel::updateMaterialsImportedState(const QStringList &importedItems) +{ + bool changed = false; + for (ContentLibraryMaterial *mat : std::as_const(m_userMaterials)) + changed |= mat->setImported(importedItems.contains(mat->qml().chopped(4))); + + if (changed) + emit dataChanged(index(MaterialsSectionIdx), index(MaterialsSectionIdx)); +} + +void ContentLibraryUserModel::update3DImportedState(const QStringList &importedItems) +{ + bool changed = false; + for (ContentLibraryItem *item : std::as_const(m_user3DItems)) + changed |= item->setImported(importedItems.contains(item->qml().chopped(4))); + + if (changed) + emit dataChanged(index(Items3DSectionIdx), index(Items3DSectionIdx)); +} + +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(); +} + +void ContentLibraryUserModel::resetModel() +{ + beginResetModel(); + endResetModel(); +} + +void ContentLibraryUserModel::applyToSelected(ContentLibraryMaterial *mat, bool add) +{ + emit applyToSelectedTriggered(mat, add); +} + +void ContentLibraryUserModel::addToProject(QObject *item) +{ + QString bundleDir; + TypeName type; + QString qmlFile; + QStringList files; + if (auto mat = qobject_cast<ContentLibraryMaterial *>(item)) { + bundleDir = mat->dirPath(); + type = mat->type(); + qmlFile = mat->qml(); + files = mat->files() + m_bundleMaterialSharedFiles; + } else if (auto itm = qobject_cast<ContentLibraryItem *>(item)) { + bundleDir = m_bundlePath3D.toString(); + type = itm->type(); + qmlFile = itm->qml(); + files = itm->files() + m_bundle3DSharedFiles; + } else { + qWarning() << __FUNCTION__ << "Unsupported Item"; + return; + } + + QString err = m_widget->importer()->importComponent(bundleDir, type, qmlFile, files); + + if (err.isEmpty()) + m_widget->setImporterRunning(true); + else + qWarning() << __FUNCTION__ << err; +} + +void ContentLibraryUserModel::removeFromProject(QObject *item) +{ + TypeName type; + QString qml; + if (auto mat = qobject_cast<ContentLibraryMaterial *>(item)) { + type = mat->type(); + qml = mat->qml(); + } else if (auto itm = qobject_cast<ContentLibraryItem *>(item)) { + type = itm->type(); + qml = itm->qml(); + } else { + qWarning() << __FUNCTION__ << "Unsupported Item"; + return; + } + + QString err = m_widget->importer()->unimportComponent(type, qml); + + if (err.isEmpty()) + m_widget->setImporterRunning(true); + else + qWarning() << __FUNCTION__ << err; +} + +} // 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..2a7f9a66f3 --- /dev/null +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryusermodel.h @@ -0,0 +1,135 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include <utils/filepath.h> + +#include <QAbstractListModel> +#include <QJsonObject> + +QT_FORWARD_DECLARE_CLASS(QUrl) + +namespace QmlDesigner { + +class ContentLibraryItem; +class ContentLibraryMaterial; +class ContentLibraryTexture; +class ContentLibraryWidget; +class NodeMetaInfo; + +class ContentLibraryUserModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(bool matBundleExists READ matBundleExists NOTIFY matBundleExistsChanged) + Q_PROPERTY(bool bundle3DExists MEMBER m_bundle3DExists NOTIFY bundle3DExistsChanged) + Q_PROPERTY(bool hasRequiredQuick3DImport READ hasRequiredQuick3DImport NOTIFY hasRequiredQuick3DImportChanged) + Q_PROPERTY(QList<ContentLibraryMaterial *> userMaterials MEMBER m_userMaterials NOTIFY userMaterialsChanged) + Q_PROPERTY(QList<ContentLibraryTexture *> userTextures MEMBER m_userTextures NOTIFY userTexturesChanged) + Q_PROPERTY(QList<ContentLibraryItem *> user3DItems MEMBER m_user3DItems NOTIFY user3DItemsChanged) + Q_PROPERTY(QList<ContentLibraryItem *> userEffects MEMBER m_userEffects NOTIFY userEffectsChanged) + +public: + 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 updateMaterialsImportedState(const QStringList &importedItems); + void update3DImportedState(const QStringList &importedItems); + + QPair<QString, QString> getUniqueLibMaterialNames(const QString &defaultName = "Material") const; + QPair<QString, QString> getUniqueLib3DNames(const QString &defaultName = "Item") const; + + void setQuick3DImportVersion(int major, int minor); + + bool hasRequiredQuick3DImport() const; + + bool matBundleExists() const; + + void resetModel(); + void updateNoMatchMaterials(); + void updateNoMatchTextures(); + void updateNoMatch3D(); + + void addMaterial(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files); + void add3DItem(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files); + void refresh3DSection(); + void addTextures(const QStringList &paths); + + void add3DInstance(ContentLibraryItem *bundleItem); + + void remove3DFromContentLibByName(const QString &qmlFileName); + + void setBundleObj(const QJsonObject &newBundleObj); + QJsonObject &bundleJsonMaterialObjectRef(); + QJsonObject &bundleJson3DObjectRef(); + + void loadBundles(); + + Q_INVOKABLE void applyToSelected(QmlDesigner::ContentLibraryMaterial *mat, bool add = false); + Q_INVOKABLE void addToProject(QObject *item); + Q_INVOKABLE void removeFromProject(QObject *item); + Q_INVOKABLE void removeTexture(QmlDesigner::ContentLibraryTexture *tex); + Q_INVOKABLE void removeFromContentLib(QObject *item); + +signals: + void hasRequiredQuick3DImportChanged(); + void userMaterialsChanged(); + void userTexturesChanged(); + void user3DItemsChanged(); + void userEffectsChanged(); + void applyToSelectedTriggered(QmlDesigner::ContentLibraryMaterial *mat, bool add = false); + void matBundleExistsChanged(); + void bundle3DExistsChanged(); + +private: + enum SectionIndex { MaterialsSectionIdx = 0, + TexturesSectionIdx, + Items3DSectionIdx, + EffectsSectionIdx }; + + void loadMaterialBundle(); + void load3DBundle(); + void loadTextureBundle(); + bool isValidIndex(int idx) const; + void removeMaterialFromContentLib(ContentLibraryMaterial *mat); + void remove3DFromContentLib(ContentLibraryItem *item); + QPair<QString, QString> getUniqueLibItemNames(const QString &defaultName, + const QJsonObject &bundleObj) const; + + ContentLibraryWidget *m_widget = nullptr; + QString m_searchText; + QString m_bundleIdMaterial; + QString m_bundleId3D; + QStringList m_bundleMaterialSharedFiles; + QStringList m_bundle3DSharedFiles; + Utils::FilePath m_bundlePathMaterial; + Utils::FilePath m_bundlePath3D; + + QList<ContentLibraryMaterial *> m_userMaterials; + QList<ContentLibraryTexture *> m_userTextures; + QList<ContentLibraryItem *> m_userEffects; + QList<ContentLibraryItem *> m_user3DItems; + QStringList m_userCategories; + + QJsonObject m_bundleObjMaterial; + QJsonObject m_bundleObj3D; + + bool m_noMatchMaterials = true; + bool m_noMatchTextures = true; + bool m_noMatch3D = true; + bool m_noMatchEffects = true; + bool m_matBundleExists = false; + bool m_bundle3DExists = false; + + int m_quick3dMajorVersion = -1; + int m_quick3dMinorVersion = -1; + + enum Roles { NameRole = Qt::UserRole + 1, VisibleRole, ItemsRole, NoMatchRole }; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp index 61ae078ea8..9258faaf33 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp @@ -4,22 +4,29 @@ #include "contentlibraryview.h" #include "contentlibrarybundleimporter.h" -#include "contentlibraryeffect.h" +#include "contentlibraryitem.h" #include "contentlibraryeffectsmodel.h" #include "contentlibrarymaterial.h" #include "contentlibrarymaterialsmodel.h" #include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" +#include "contentlibraryusermodel.h" #include "contentlibrarywidget.h" -#include "externaldependenciesinterface.h" -#include "nodelistproperty.h" -#include "qmldesignerconstants.h" -#include "qmlobjectnode.h" -#include "variantproperty.h" -#include <utils3d.h> -#include <coreplugin/messagebox.h> +#include <asset.h> +#include <bindingproperty.h> +#include <designerpaths.h> +#include <documentmanager.h> #include <enumeration.h> +#include <externaldependenciesinterface.h> +#include <nodelistproperty.h> +#include <qmldesignerconstants.h> +#include <qmldesignerplugin.h> +#include <qmlobjectnode.h> +#include <uniquename.h> +#include <utils3d.h> +#include <variantproperty.h> + #include <utils/algorithm.h> #ifndef QMLDESIGNER_TEST @@ -30,12 +37,19 @@ #include <qtsupport/qtkitaspect.h> #endif +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> +#include <QMessageBox> +#include <QPixmap> #include <QVector3D> namespace QmlDesigner { -ContentLibraryView::ContentLibraryView(ExternalDependenciesInterface &externalDependencies) +ContentLibraryView::ContentLibraryView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies) : AbstractView(externalDependencies) + , m_imageCache(imageCache) , m_createTexture(this) {} @@ -60,9 +74,9 @@ WidgetInfo ContentLibraryView::widgetInfo() [&] (QmlDesigner::ContentLibraryTexture *tex) { m_draggedBundleTexture = tex; }); - connect(m_widget, &ContentLibraryWidget::bundleEffectDragStarted, this, - [&] (QmlDesigner::ContentLibraryEffect *eff) { - m_draggedBundleEffect = eff; + connect(m_widget, &ContentLibraryWidget::bundleItemDragStarted, this, + [&] (QmlDesigner::ContentLibraryItem *item) { + m_draggedBundleItem = item; }); connect(m_widget, &ContentLibraryWidget::addTextureRequested, this, @@ -79,138 +93,150 @@ WidgetInfo ContentLibraryView::widgetInfo() m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists); }); - ContentLibraryMaterialsModel *materialsModel = m_widget->materialsModel().data(); - - connect(materialsModel, + connect(m_widget->materialsModel(), &ContentLibraryMaterialsModel::applyToSelectedTriggered, this, [&](ContentLibraryMaterial *bundleMat, bool add) { - if (m_selectedModels.isEmpty()) - return; + if (m_selectedModels.isEmpty()) + return; - m_bundleMaterialTargets = m_selectedModels; - m_bundleMaterialAddToSelected = add; + m_bundleMaterialTargets = m_selectedModels; + m_bundleMaterialAddToSelected = add; - ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type()); - if (defaultMat.isValid()) - applyBundleMaterialToDropTarget(defaultMat); - else - m_widget->materialsModel()->addToProject(bundleMat); - }); + ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type()); + if (defaultMat.isValid()) + applyBundleMaterialToDropTarget(defaultMat); + else + m_widget->materialsModel()->addToProject(bundleMat); + }); -#ifdef QDS_USE_PROJECTSTORAGE - connect(materialsModel, - &ContentLibraryMaterialsModel::bundleMaterialImported, - this, - [&](const QmlDesigner::TypeName &typeName) { - applyBundleMaterialToDropTarget({}, typeName); - updateBundleMaterialsImportedState(); - }); -#else - connect(materialsModel, - &ContentLibraryMaterialsModel::bundleMaterialImported, + connect(m_widget->userModel(), + &ContentLibraryUserModel::applyToSelectedTriggered, this, - [&](const QmlDesigner::NodeMetaInfo &metaInfo) { - applyBundleMaterialToDropTarget({}, metaInfo); - updateBundleMaterialsImportedState(); - }); -#endif + [&](ContentLibraryMaterial *bundleMat, bool add) { + if (m_selectedModels.isEmpty()) + return; - connect(materialsModel, &ContentLibraryMaterialsModel::bundleMaterialAboutToUnimport, this, - [&] (const QmlDesigner::TypeName &type) { - // delete instances of the bundle material that is about to be unimported - executeInTransaction("ContentLibraryView::widgetInfo", [&] { - ModelNode matLib = Utils3D::materialLibraryNode(this); - if (!matLib.isValid()) - return; + m_bundleMaterialTargets = m_selectedModels; + m_bundleMaterialAddToSelected = add; - Utils::reverseForeach(matLib.directSubModelNodes(), [&](const ModelNode &mat) { - if (mat.isValid() && mat.type() == type) - QmlObjectNode(mat).destroy(); - }); - }); + ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type()); + if (defaultMat.isValid()) + applyBundleMaterialToDropTarget(defaultMat); + else + m_widget->userModel()->addToProject(bundleMat); }); - connect(materialsModel, &ContentLibraryMaterialsModel::bundleMaterialUnimported, this, - &ContentLibraryView::updateBundleMaterialsImportedState); + connectImporter(); + } - ContentLibraryEffectsModel *effectsModel = m_widget->effectsModel().data(); + return createWidgetInfo(m_widget.data(), + "ContentLibrary", + WidgetInfo::LeftPane, + 0, + tr("Content Library")); +} +void ContentLibraryView::connectImporter() +{ #ifdef QDS_USE_PROJECTSTORAGE - connect(effectsModel, - &ContentLibraryEffectsModel::bundleItemImported, - this, - [&](const QmlDesigner::TypeName &typeName) { - QTC_ASSERT(typeName.size(), return); - - if (!m_bundleEffectTarget) - m_bundleEffectTarget = Utils3D::active3DSceneNode(this); + connect(m_widget->importer(), + &ContentLibraryBundleImporter::importFinished, + this, + [&](const QmlDesigner::TypeName &typeName, const QString &bundleId) { + QTC_ASSERT(typeName.size(), return); + if (isMaterialBundle(bundleId)) { + applyBundleMaterialToDropTarget({}, typeName); + } else if (isItemBundle(bundleId)) { + if (!m_bundleItemTarget) + m_bundleItemTarget = Utils3D::active3DSceneNode(this); - QTC_ASSERT(m_bundleEffectTarget, return); + QTC_ASSERT(m_bundleItemTarget, return); executeInTransaction("ContentLibraryView::widgetInfo", [&] { - QVector3D pos = m_bundleEffectPos.value<QVector3D>(); - ModelNode newEffNode = createModelNode( + QVector3D pos = m_bundleItemPos.value<QVector3D>(); + ModelNode newNode = createModelNode( typeName, -1, -1, {{"x", pos.x()}, {"y", pos.y()}, {"z", pos.z()}}); - m_bundleEffectTarget.defaultNodeListProperty().reparentHere(newEffNode); + m_bundleItemTarget.defaultNodeListProperty().reparentHere(newNode); clearSelectedModelNodes(); - selectModelNode(newEffNode); + selectModelNode(newNode); }); - updateBundleEffectsImportedState(); - m_bundleEffectTarget = {}; - m_bundleEffectPos = {}; - }); + m_bundleItemTarget = {}; + m_bundleItemPos = {}; + } + }); #else - connect(effectsModel, - &ContentLibraryEffectsModel::bundleItemImported, - this, - [&](const QmlDesigner::NodeMetaInfo &metaInfo) { - QTC_ASSERT(metaInfo.isValid(), return); - - if (!m_bundleEffectTarget) - m_bundleEffectTarget = Utils3D::active3DSceneNode(this); + connect(m_widget->importer(), + &ContentLibraryBundleImporter::importFinished, + this, + [&](const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId) { + QTC_ASSERT(metaInfo.isValid(), return); + if (isMaterialBundle(bundleId)) { + applyBundleMaterialToDropTarget({}, metaInfo); + } else if (isItemBundle(bundleId)) { + if (!m_bundleItemTarget) + m_bundleItemTarget = Utils3D::active3DSceneNode(this); - QTC_ASSERT(m_bundleEffectTarget, return); + QTC_ASSERT(m_bundleItemTarget, return); - executeInTransaction("ContentLibraryView::widgetInfo", [&] { - QVector3D pos = m_bundleEffectPos.value<QVector3D>(); - ModelNode newEffNode = createModelNode(metaInfo.typeName(), + executeInTransaction("ContentLibraryView::connectImporter", [&] { + QVector3D pos = m_bundleItemPos.value<QVector3D>(); + ModelNode newNode = createModelNode(metaInfo.typeName(), metaInfo.majorVersion(), metaInfo.minorVersion(), {{"x", pos.x()}, {"y", pos.y()}, {"z", pos.z()}}); - m_bundleEffectTarget.defaultNodeListProperty().reparentHere(newEffNode); + m_bundleItemTarget.defaultNodeListProperty().reparentHere(newNode); clearSelectedModelNodes(); - selectModelNode(newEffNode); + selectModelNode(newNode); }); - updateBundleEffectsImportedState(); - m_bundleEffectTarget = {}; - m_bundleEffectPos = {}; - }); + m_bundleItemTarget = {}; + m_bundleItemPos = {}; + } + }); #endif - connect(effectsModel, &ContentLibraryEffectsModel::bundleItemAboutToUnimport, this, - [&] (const QmlDesigner::TypeName &type) { - // delete instances of the bundle effect that is about to be unimported - executeInTransaction("ContentLibraryView::widgetInfo", [&] { - NodeMetaInfo metaInfo = model()->metaInfo(type); - QList<ModelNode> effects = allModelNodesOfType(metaInfo); - for (ModelNode &eff : effects) - eff.destroy(); - }); + + connect(m_widget->importer(), &ContentLibraryBundleImporter::aboutToUnimport, this, + [&] (const QmlDesigner::TypeName &type, const QString &bundleId) { + if (isMaterialBundle(bundleId)) { + // delete instances of the bundle material that is about to be unimported + executeInTransaction("ContentLibraryView::connectImporter", [&] { + 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(); }); + }); + } else if (isItemBundle(bundleId)) { + // delete instances of the bundle item that is about to be unimported + executeInTransaction("ContentLibraryView::connectImporter", [&] { + NodeMetaInfo metaInfo = model()->metaInfo(type); + QList<ModelNode> nodes = allModelNodesOfType(metaInfo); + for (ModelNode &node : nodes) + node.destroy(); + }); + } + }); +} - connect(effectsModel, &ContentLibraryEffectsModel::bundleItemUnimported, this, - &ContentLibraryView::updateBundleEffectsImportedState); - } +bool ContentLibraryView::isMaterialBundle(const QString &bundleId) const +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + return bundleId == compUtils.materialsBundleId() || bundleId == compUtils.userMaterialsBundleId(); +} - return createWidgetInfo(m_widget.data(), - "ContentLibrary", - WidgetInfo::LeftPane, - 0, - tr("Content Library")); +// item bundle includes effects and 3D components +bool ContentLibraryView::isItemBundle(const QString &bundleId) const +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + return bundleId == compUtils.effectsBundleId() || bundleId == compUtils.userEffectsBundleId() + || bundleId == compUtils.user3DBundleId(); } void ContentLibraryView::modelAttached(Model *model) @@ -220,7 +246,6 @@ void ContentLibraryView::modelAttached(Model *model) m_hasQuick3DImport = model->hasImport("QtQuick3D"); updateBundlesQuick3DVersion(); - updateBundleMaterialsImportedState(); const bool hasLibrary = Utils3D::materialLibraryNode(this).isValid(); m_widget->setHasMaterialLibrary(hasLibrary); @@ -232,8 +257,17 @@ void ContentLibraryView::modelAttached(Model *model) m_widget->setHasActive3DScene(m_sceneId != -1); m_widget->clearSearchFilter(); + // bundles loading has to happen here, otherwise project path is not ready which will + // cause bundle items types to resolve incorrectly + m_widget->materialsModel()->loadBundle(); m_widget->effectsModel()->loadBundle(); - updateBundleEffectsImportedState(); + m_widget->userModel()->loadBundles(); + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + m_widget->updateImportedState(compUtils.materialsBundleId()); + m_widget->updateImportedState(compUtils.effectsBundleId()); + m_widget->updateImportedState(compUtils.userMaterialsBundleId()); + m_widget->updateImportedState(compUtils.user3DBundleId()); } void ContentLibraryView::modelAboutToBeDetached(Model *model) @@ -275,7 +309,7 @@ void ContentLibraryView::selectedNodesChanged(const QList<ModelNode> &selectedNo return node.metaInfo().isQtQuick3DModel(); }); - m_widget->materialsModel()->setHasModelSelection(!m_selectedModels.isEmpty()); + m_widget->setHasModelSelection(!m_selectedModels.isEmpty()); } void ContentLibraryView::customNotification(const AbstractView *view, @@ -283,8 +317,6 @@ void ContentLibraryView::customNotification(const AbstractView *view, const QList<ModelNode> &nodeList, const QList<QVariant> &data) { - Q_UNUSED(data) - if (view == this) return; @@ -318,12 +350,29 @@ void ContentLibraryView::customNotification(const AbstractView *view, m_widget->addTexture(m_draggedBundleTexture); m_draggedBundleTexture = nullptr; - } else if (identifier == "drop_bundle_effect") { + } else if (identifier == "drop_bundle_item") { QTC_ASSERT(nodeList.size() == 1, return); - m_bundleEffectPos = data.size() == 1 ? data.first() : QVariant(); - m_widget->effectsModel()->addInstance(m_draggedBundleEffect); - m_bundleEffectTarget = nodeList.first() ? nodeList.first() : Utils3D::active3DSceneNode(this); + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + bool is3D = m_draggedBundleItem->type().startsWith(compUtils.user3DBundleType().toLatin1()); + + m_bundleItemPos = data.size() == 1 ? data.first() : QVariant(); + if (is3D) + m_widget->userModel()->add3DInstance(m_draggedBundleItem); + else + m_widget->effectsModel()->addInstance(m_draggedBundleItem); + m_bundleItemTarget = 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()); + } else if (identifier == "add_3d_to_content_lib") { + if (nodeList.first().isComponent()) + addLib3DComponent(nodeList.first()); + else + addLib3DItem(nodeList.first()); } } @@ -452,6 +501,325 @@ void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundle } #endif +// Add a project material to Content Library's user tab +void ContentLibraryView::addLibMaterial(const ModelNode &node, const QPixmap &iconPixmap) +{ + auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/"); + + QString name = node.variantProperty("objectName").value().toString(); + auto [qml, icon] = m_widget->userModel()->getUniqueLibMaterialNames(node.id()); + + QString iconPath = QLatin1String("icons/%1").arg(icon); + QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); + + // save icon + bool iconSaved = iconPixmap.save(fullIconPath); + if (!iconSaved) + qWarning() << __FUNCTION__ << "icon save failed"; + + // generate and save material Qml file + const QStringList depAssets = writeLibItemQml(node, qml); + + // add the material to the bundle json + QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef(); + QJsonArray itemsArr = jsonRef.value("items").toArray(); + QJsonObject itemObj; + itemObj.insert("name", name); + itemObj.insert("qml", qml); + itemObj.insert("icon", iconPath); + QJsonArray filesArr; + for (const QString &assetPath : depAssets) + filesArr.append(assetPath); + itemObj.insert("files", filesArr); + + itemsArr.append(itemObj); + jsonRef["items"] = itemsArr; + + auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) + .writeFileContents(QJsonDocument(jsonRef).toJson()); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + // copy material assets to bundle folder + for (const QString &assetPath : depAssets) { + Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath); + Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath); + assetPathTarget.parentDir().ensureWritableDir(); + + auto result = assetPathSource.copyFile(assetPathTarget); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + } + + m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets); +} + +QStringList ContentLibraryView::writeLibItemQml(const ModelNode &node, const QString &qml) +{ + QStringList depListIds; + auto [qmlString, assets] = modelNodeToQmlString(node, depListIds); + + qmlString.prepend("import QtQuick\nimport QtQuick3D\n\n"); + + QString itemType = QLatin1String(node.metaInfo().isQtQuick3DMaterial() ? "materials" : "3d"); + auto qmlPath = Utils::FilePath::fromString(QLatin1String("%1/User/%2/%3") + .arg(Paths::bundlesPathSetting(), itemType, 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<PropertyName> excludedProps = {"x", "y", "z", "eulerRotation.x", "eulerRotation.y", + "eulerRotation.z", "scale.x", "scale.y", "scale.z", + "pivot.x", "pivot.y", "pivot.z"}; + const QList<AbstractProperty> matProps = node.properties(); + for (const AbstractProperty &p : matProps) { + if (excludedProps.contains(p.name())) + continue; + + 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) { + QString pValueStr = pValue.toString(); + val = QLatin1String("\"%1\"").arg(pValueStr); + if (!pValueStr.startsWith("#")) + 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; + + const QStringList existingTextures = Utils::transform(bundlePath.dirEntries(QDir::Files), + [](const Utils::FilePath &path) { + return path.fileName(); + }); + + for (const QString &path : paths) { + auto assetFilePath = Utils::FilePath::fromString(path); + if (existingTextures.contains(assetFilePath.fileName())) + continue; + + Asset asset(path); + + // save icon + QString iconSavePath = bundlePath.pathAppended("icons/" + assetFilePath.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 = assetFilePath.copyFile(bundlePath.pathAppended(asset.fileName())); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + pathsInBundle.append(bundlePath.pathAppended(asset.fileName()).toString()); + } + + m_widget->userModel()->addTextures(pathsInBundle); +} + +void ContentLibraryView::addLib3DComponent(const ModelNode &node) +{ + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + + QString compBaseName = node.simplifiedTypeName(); + QString compFileName = compBaseName + ".qml"; + + Utils::FilePath compDir = DocumentManager::currentProjectDirPath() + .pathAppended(compUtils.import3dTypePath() + '/' + compBaseName); + + auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/"); + + // confirm overwrite if an item with same name exists + if (bundlePath.pathAppended(compFileName).exists()) { + // Show a QML confirmation dialog before proceeding + QMessageBox::StandardButton reply = QMessageBox::question(m_widget, tr("3D Item Exists"), + tr("A 3D item with the same name '%1' already exists in the Content Library, are you sure you want to overwrite it?") + .arg(compFileName), QMessageBox::Yes | QMessageBox::No); + if (reply == QMessageBox::No) + return; + + // before overwriting remove old item (to avoid partial items and dangling assets) + m_widget->userModel()->remove3DFromContentLibByName(compFileName); + } + + // generate and save icon + QString iconPath = QLatin1String("icons/%1").arg(UniqueName::generateId(compBaseName) + ".png"); + QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); + genAndSaveIcon(compDir.pathAppended(compFileName).path(), fullIconPath); + + const Utils::FilePaths sourceFiles = compDir.dirEntries({{}, QDir::Files, QDirIterator::Subdirectories}); + const QStringList ignoreList {"_importdata.json", "qmldir", compBaseName + ".hints"}; + QStringList filesList; // 3D component's assets (dependencies) + + for (const Utils::FilePath &sourcePath : sourceFiles) { + Utils::FilePath relativePath = sourcePath.relativePathFrom(compDir); + if (ignoreList.contains(sourcePath.fileName()) || relativePath.startsWith("source scene")) + continue; + + Utils::FilePath targetPath = bundlePath.pathAppended(relativePath.path()); + targetPath.parentDir().ensureWritableDir(); + + // copy item from project to user bundle + auto result = sourcePath.copyFile(targetPath); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + if (sourcePath.fileName() != compFileName) // skip component file (only collect dependencies) + filesList.append(relativePath.path()); + } + + // add the item to the bundle json + QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef(); + QJsonArray itemsArr = jsonRef.value("items").toArray(); + itemsArr.append(QJsonObject { + {"name", node.simplifiedTypeName()}, + {"qml", compFileName}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(filesList)} + }); + + jsonRef["items"] = itemsArr; + + auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) + .writeFileContents(QJsonDocument(jsonRef).toJson()); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + m_widget->userModel()->add3DItem(compBaseName, compFileName, QUrl::fromLocalFile(fullIconPath), + filesList); +} + +void ContentLibraryView::addLib3DItem(const ModelNode &node) +{ + auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/"); + + QString name = node.variantProperty("objectName").value().toString(); + auto [qml, icon] = m_widget->userModel()->getUniqueLib3DNames(node.id()); + QString iconPath = QLatin1String("icons/%1").arg(icon); + + if (name.isEmpty()) + name = node.id(); + + // generate and save item Qml file + const QStringList depAssets = writeLibItemQml(node, qml); + + // generate and save icon + QString qmlPath = QLatin1String("%1/User/3d/%2").arg(Paths::bundlesPathSetting(), qml); + QString fullIconPath = bundlePath.pathAppended(iconPath).toString(); + genAndSaveIcon(qmlPath, fullIconPath); + + // add the item to the bundle json + QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef(); + QJsonArray itemsArr = jsonRef.value("items").toArray(); + itemsArr.append(QJsonObject { + {"name", name}, + {"qml", qml}, + {"icon", iconPath}, + {"files", QJsonArray::fromStringList(depAssets)} + }); + + jsonRef["items"] = itemsArr; + + auto result = bundlePath.pathAppended(Constants::BUNDLE_JSON_FILENAME) + .writeFileContents(QJsonDocument(jsonRef).toJson()); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + + // copy item's assets to bundle folder + for (const QString &assetPath : depAssets) { + Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath); + Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath); + assetPathTarget.parentDir().ensureWritableDir(); + + auto result = assetPathSource.copyFile(assetPathTarget); + if (!result) + qWarning() << __FUNCTION__ << result.error(); + } + + m_widget->userModel()->add3DItem(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets); +} + +/** + * @brief Generates an icon image from a qml component + * @param qmlPath path to the qml component file to be rendered + * @param iconPath output save path of the generated icon + */ +void ContentLibraryView::genAndSaveIcon(const QString &qmlPath, const QString &iconPath) +{ + m_imageCache.requestSmallImage( + Utils::PathString{qmlPath}, + [&, qmlPath, iconPath](const QImage &image) { + bool iconSaved = image.save(iconPath); + if (iconSaved) + m_widget->userModel()->refresh3DSection(); + else + qWarning() << "ContentLibraryView::genAndSaveIcon(): icon save failed"; + }, + [&](ImageCache::AbortReason abortReason) { + if (abortReason == ImageCache::AbortReason::Abort) { + qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation " + "failed for path %1, reason: Abort").arg(qmlPath); + } else if (abortReason == ImageCache::AbortReason::Failed) { + qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation " + "failed for path %1, reason: Failed").arg(qmlPath); + } else if (abortReason == ImageCache::AbortReason::NoEntry) { + qWarning() << QLatin1String("ContentLibraryView::genAndSaveIcon(): icon generation " + "failed for path %1, reason: NoEntry").arg(qmlPath); + } + }); +} + ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &type) { ModelNode matLib = Utils3D::materialLibraryNode(this); @@ -477,6 +845,7 @@ ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &t return {}; } + #ifdef QDS_USE_PROJECTSTORAGE ModelNode ContentLibraryView::createMaterial(const TypeName &typeName) { @@ -491,7 +860,7 @@ ModelNode ContentLibraryView::createMaterial(const TypeName &typeName) QString newName = QString::fromUtf8(typeName).replace(rgx, " \\1\\2").trimmed(); if (newName.endsWith(" Material")) newName.chop(9); // remove trailing " Material" - QString newId = model()->generateIdFromName(newName, "material"); + QString newId = model()->generateNewId(newName, "material"); newMatNode.setIdWithRefactoring(newId); VariantProperty objNameProp = newMatNode.variantProperty("objectName"); @@ -517,7 +886,7 @@ ModelNode ContentLibraryView::createMaterial(const NodeMetaInfo &metaInfo) QString newName = QString::fromLatin1(metaInfo.simplifiedTypeName()).replace(rgx, " \\1\\2").trimmed(); if (newName.endsWith(" Material")) newName.chop(9); // remove trailing " Material" - QString newId = model()->generateIdFromName(newName, "material"); + QString newId = model()->generateNewId(newName, "material"); newMatNode.setIdWithRefactoring(newId); VariantProperty objNameProp = newMatNode.variantProperty("objectName"); @@ -529,44 +898,6 @@ ModelNode ContentLibraryView::createMaterial(const NodeMetaInfo &metaInfo) } #endif -void ContentLibraryView::updateBundleMaterialsImportedState() -{ - using namespace Utils; - - if (!m_widget->materialsModel()->bundleImporter()) - return; - - QStringList importedBundleMats; - - FilePath materialBundlePath = m_widget->materialsModel()->bundleImporter()->resolveBundleImportPath(); - - if (materialBundlePath.exists()) { - importedBundleMats = transform(materialBundlePath.dirEntries({{"*.qml"}, QDir::Files}), - [](const FilePath &f) { return f.fileName().chopped(4); }); - } - - m_widget->materialsModel()->updateImportedState(importedBundleMats); -} - -void ContentLibraryView::updateBundleEffectsImportedState() -{ - using namespace Utils; - - if (!m_widget->effectsModel()->bundleImporter()) - return; - - QStringList importedBundleEffs; - - FilePath bundlePath = m_widget->effectsModel()->bundleImporter()->resolveBundleImportPath(); - - if (bundlePath.exists()) { - importedBundleEffs = transform(bundlePath.dirEntries({{"*.qml"}, QDir::Files}), - [](const FilePath &f) { return f.fileName().chopped(4); }); - } - - m_widget->effectsModel()->updateImportedState(importedBundleEffs); -} - void ContentLibraryView::updateBundlesQuick3DVersion() { bool hasImport = false; @@ -601,6 +932,7 @@ void ContentLibraryView::updateBundlesQuick3DVersion() #endif m_widget->materialsModel()->setQuick3DImportVersion(major, minor); m_widget->effectsModel()->setQuick3DImportVersion(major, minor); + m_widget->userModel()->setQuick3DImportVersion(major, minor); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h index 3b57b7a4ab..914a8b8ea0 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h @@ -3,16 +3,19 @@ #pragma once -#include "abstractview.h" -#include "createtexture.h" -#include "nodemetainfo.h" +#include <asynchronousimagecache.h> +#include <abstractview.h> +#include <createtexture.h> +#include <nodemetainfo.h> #include <QObject> #include <QPointer> +QT_FORWARD_DECLARE_CLASS(QPixmap) + namespace QmlDesigner { -class ContentLibraryEffect; +class ContentLibraryItem; class ContentLibraryMaterial; class ContentLibraryTexture; class ContentLibraryWidget; @@ -23,7 +26,8 @@ class ContentLibraryView : public AbstractView Q_OBJECT public: - ContentLibraryView(ExternalDependenciesInterface &externalDependencies); + ContentLibraryView(AsynchronousImageCache &imageCache, + ExternalDependenciesInterface &externalDependencies); ~ContentLibraryView() override; bool hasWidget() const override; @@ -46,10 +50,20 @@ public: const QVariant &data) override; private: + void connectImporter(); + bool isMaterialBundle(const QString &bundleId) const; + bool isItemBundle(const QString &bundleId) const; void active3DSceneChanged(qint32 sceneId); - void updateBundleMaterialsImportedState(); - void updateBundleEffectsImportedState(); void updateBundlesQuick3DVersion(); + void addLibMaterial(const ModelNode &node, const QPixmap &iconPixmap); + void addLibAssets(const QStringList &paths); + void addLib3DComponent(const ModelNode &node); + void addLib3DItem(const ModelNode &node); + void genAndSaveIcon(const QString &qmlPath, const QString &iconPath); + QStringList writeLibItemQml(const ModelNode &node, 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 @@ -64,12 +78,13 @@ private: #endif QPointer<ContentLibraryWidget> m_widget; QList<ModelNode> m_bundleMaterialTargets; - ModelNode m_bundleEffectTarget; // target of the dropped bundle effect - QVariant m_bundleEffectPos; // pos of the dropped bundle effect + ModelNode m_bundleItemTarget; // target of the dropped bundle item + QVariant m_bundleItemPos; // pos of the dropped bundle item QList<ModelNode> m_selectedModels; // selected 3D model nodes ContentLibraryMaterial *m_draggedBundleMaterial = nullptr; ContentLibraryTexture *m_draggedBundleTexture = nullptr; - ContentLibraryEffect *m_draggedBundleEffect = nullptr; + ContentLibraryItem *m_draggedBundleItem = nullptr; + AsynchronousImageCache &m_imageCache; bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; qint32 m_sceneId = -1; diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp index c885a76ba7..72bece4c98 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.cpp @@ -3,13 +3,15 @@ #include "contentlibrarywidget.h" -#include "contentlibraryeffect.h" +#include "contentlibrarybundleimporter.h" #include "contentlibraryeffectsmodel.h" +#include "contentlibraryitem.h" #include "contentlibrarymaterial.h" #include "contentlibrarymaterialsmodel.h" #include "contentlibrarytexture.h" #include "contentlibrarytexturesmodel.h" #include "contentlibraryiconprovider.h" +#include "contentlibraryusermodel.h" #include "utils/filedownloader.h" #include "utils/fileextractor.h" @@ -17,6 +19,7 @@ #include <coreplugin/icore.h> #include <designerpaths.h> +#include <nodemetainfo.h> #include <qmldesignerconstants.h> #include <qmldesignerplugin.h> @@ -39,7 +42,6 @@ #include <QQuickWidget> #include <QRegularExpression> #include <QShortcut> -#include <QStandardPaths> #include <QVBoxLayout> namespace QmlDesigner { @@ -66,18 +68,18 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) Model *model = document->currentModel(); QTC_ASSERT(model, return false); - if (m_effectToDrag) { + if (m_itemToDrag) { QMouseEvent *me = static_cast<QMouseEvent *>(event); if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) { QByteArray data; QMimeData *mimeData = new QMimeData; QDataStream stream(&data, QIODevice::WriteOnly); - stream << m_effectToDrag->type(); - mimeData->setData(Constants::MIME_TYPE_BUNDLE_EFFECT, data); + stream << m_itemToDrag->type(); + mimeData->setData(Constants::MIME_TYPE_BUNDLE_ITEM, data); - emit bundleEffectDragStarted(m_effectToDrag); - model->startDrag(mimeData, m_effectToDrag->icon().toLocalFile()); - m_effectToDrag = nullptr; + emit bundleItemDragStarted(m_itemToDrag); + model->startDrag(mimeData, m_itemToDrag->icon().toLocalFile()); + m_itemToDrag = nullptr; } } else if (m_materialToDrag) { QMouseEvent *me = static_cast<QMouseEvent *>(event); @@ -100,10 +102,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()); @@ -111,7 +113,7 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) } } } else if (event->type() == QMouseEvent::MouseButtonRelease) { - m_effectToDrag = nullptr; + m_itemToDrag = nullptr; m_materialToDrag = nullptr; m_textureToDrag = nullptr; setIsDragging(false); @@ -121,11 +123,12 @@ bool ContentLibraryWidget::eventFilter(QObject *obj, QEvent *event) } ContentLibraryWidget::ContentLibraryWidget() - : m_quickWidget(new StudioQuickWidget(this)) + : m_quickWidget(Utils::makeUniqueObjectPtr<StudioQuickWidget>(this)) , m_materialsModel(new ContentLibraryMaterialsModel(this)) , 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 +143,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); @@ -159,7 +156,7 @@ ContentLibraryWidget::ContentLibraryWidget() auto layout = new QVBoxLayout(this); layout->setContentsMargins({}); layout->setSpacing(0); - layout->addWidget(m_quickWidget.data()); + layout->addWidget(m_quickWidget.get()); updateSearch(); @@ -177,38 +174,95 @@ 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(); + createImporter(); +} + +void ContentLibraryWidget::createImporter() +{ + m_importer = new ContentLibraryBundleImporter(); +#ifdef QDS_USE_PROJECTSTORAGE + connect(m_importer, + &ContentLibraryBundleImporter::importFinished, + this, + [&](const QmlDesigner::TypeName &typeName, const QString &bundleId) { + setImporterRunning(false); + if (typeName.size()) + updateImportedState(bundleId); + }); +#else + connect(m_importer, + &ContentLibraryBundleImporter::importFinished, + this, + [&](const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId) { + setImporterRunning(false); + if (metaInfo.isValid()) + updateImportedState(bundleId); + }); +#endif + + connect(m_importer, &ContentLibraryBundleImporter::unimportFinished, this, + [&](const QmlDesigner::NodeMetaInfo &metaInfo, const QString &bundleId) { + Q_UNUSED(metaInfo) + setImporterRunning(false); + updateImportedState(bundleId); + }); +} + +void ContentLibraryWidget::updateImportedState(const QString &bundleId) +{ + if (!m_importer) + return; + + Utils::FilePath bundlePath = m_importer->resolveBundleImportPath(bundleId); + + QStringList importedItems; + if (bundlePath.exists()) { + importedItems = transform(bundlePath.dirEntries({{"*.qml"}, QDir::Files}), + [](const Utils::FilePath &f) { return f.baseName(); }); + } + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + if (bundleId == compUtils.materialsBundleId()) + m_materialsModel->updateImportedState(importedItems); + else if (bundleId == compUtils.effectsBundleId()) + m_effectsModel->updateImportedState(importedItems); + else if (bundleId == compUtils.userMaterialsBundleId()) + m_userModel->updateMaterialsImportedState(importedItems); + else if (bundleId == compUtils.user3DBundleId()) + m_userModel->update3DImportedState(importedItems); +} + +ContentLibraryBundleImporter *ContentLibraryWidget::importer() const +{ + return m_importer; } -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 +326,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 +368,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 +480,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 +540,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 +588,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 +611,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 +628,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(); @@ -700,6 +739,20 @@ void ContentLibraryWidget::setIsQt6Project(bool b) emit isQt6ProjectChanged(); } +bool ContentLibraryWidget::importerRunning() const +{ + return m_importerRunning; +} + +void ContentLibraryWidget::setImporterRunning(bool b) +{ + if (m_importerRunning == b) + return; + + m_importerRunning = b; + emit importerRunningChanged(); +} + void ContentLibraryWidget::reloadQmlSource() { const QString materialBrowserQmlPath = qmlSourcesPath() + "/ContentLibrary.qml"; @@ -715,6 +768,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(); } @@ -726,32 +780,9 @@ void ContentLibraryWidget::setIsDragging(bool val) } } -QString ContentLibraryWidget::findTextureBundlePath() -{ - QDir texBundleDir; - - if (!qEnvironmentVariable("TEXTURE_BUNDLE_PATH").isEmpty()) - texBundleDir.setPath(qEnvironmentVariable("TEXTURE_BUNDLE_PATH")); - else if (Utils::HostOsInfo::isMacHost()) - texBundleDir.setPath(QCoreApplication::applicationDirPath() + "/../Resources/texture_bundle"); - - // search for matBundleDir from exec dir and up - if (texBundleDir.dirName() == ".") { - texBundleDir.setPath(QCoreApplication::applicationDirPath()); - while (!texBundleDir.cd("texture_bundle") && texBundleDir.cdUp()) - ; // do nothing - - if (texBundleDir.dirName() != "texture_bundle") // bundlePathDir not found - return {}; - } - - return texBundleDir.path(); -} - -void ContentLibraryWidget::startDragEffect(QmlDesigner::ContentLibraryEffect *eff, - const QPointF &mousePos) +void ContentLibraryWidget::startDragItem(QmlDesigner::ContentLibraryItem *item, const QPointF &mousePos) { - m_effectToDrag = eff; + m_itemToDrag = item; m_dragStartPoint = mousePos.toPoint(); setIsDragging(true); } @@ -777,7 +808,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 +816,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 +824,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 +852,23 @@ QPointer<ContentLibraryEffectsModel> ContentLibraryWidget::effectsModel() const return m_effectsModel; } +QPointer<ContentLibraryUserModel> ContentLibraryWidget::userModel() const +{ + return m_userModel; +} + +bool ContentLibraryWidget::hasModelSelection() const +{ + return m_hasModelSelection; +} + +void ContentLibraryWidget::setHasModelSelection(bool b) +{ + if (b == m_hasModelSelection) + return; + + m_hasModelSelection = b; + emit hasModelSelectionChanged(); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h index ab71a3dc79..8e96d9d2f3 100644 --- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h +++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h @@ -5,6 +5,10 @@ #include "createtexture.h" +#include <modelfwd.h> + +#include <utils/uniqueobjectptr.h> + #include <QFrame> #include <QPointer> @@ -18,12 +22,15 @@ class StudioQuickWidget; namespace QmlDesigner { -class ContentLibraryEffect; +class ContentLibraryBundleImporter; class ContentLibraryEffectsModel; +class ContentLibraryItem; class ContentLibraryMaterial; class ContentLibraryMaterialsModel; class ContentLibraryTexture; class ContentLibraryTexturesModel; +class ContentLibraryUserModel; +class NodeMetaInfo; class ContentLibraryWidget : public QFrame { @@ -33,6 +40,8 @@ class ContentLibraryWidget : public QFrame Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary NOTIFY hasMaterialLibraryChanged) Q_PROPERTY(bool hasActive3DScene READ hasActive3DScene WRITE setHasActive3DScene NOTIFY hasActive3DSceneChanged) Q_PROPERTY(bool isQt6Project READ isQt6Project NOTIFY isQt6ProjectChanged) + Q_PROPERTY(bool importerRunning READ importerRunning WRITE setImporterRunning NOTIFY importerRunningChanged) + Q_PROPERTY(bool hasModelSelection READ hasModelSelection NOTIFY hasModelSelectionChanged) // Needed for a workaround for a bug where after drag-n-dropping an item, the ScrollView scrolls to a random position Q_PROPERTY(bool isDragging MEMBER m_isDragging NOTIFY isDraggingChanged) @@ -57,16 +66,23 @@ public: bool isQt6Project() const; void setIsQt6Project(bool b); - Q_INVOKABLE void handleSearchFilterChanged(const QString &filterText); + bool importerRunning() const; + void setImporterRunning(bool b); + + bool hasModelSelection() const; + void setHasModelSelection(bool b); void setMaterialsModel(QPointer<ContentLibraryMaterialsModel> newMaterialsModel); + void updateImportedState(const QString &bundleId); QPointer<ContentLibraryMaterialsModel> materialsModel() const; 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 handleSearchFilterChanged(const QString &filterText); + Q_INVOKABLE void startDragItem(QmlDesigner::ContentLibraryItem *item, const QPointF &mousePos); Q_INVOKABLE void startDragMaterial(QmlDesigner::ContentLibraryMaterial *mat, const QPointF &mousePos); Q_INVOKABLE void startDragTexture(QmlDesigner::ContentLibraryTexture *tex, const QPointF &mousePos); Q_INVOKABLE void addImage(QmlDesigner::ContentLibraryTexture *tex); @@ -77,8 +93,10 @@ public: QSize sizeHint() const override; + ContentLibraryBundleImporter *importer() const; + signals: - void bundleEffectDragStarted(QmlDesigner::ContentLibraryEffect *bundleEff); + void bundleItemDragStarted(QmlDesigner::ContentLibraryItem *item); void bundleMaterialDragStarted(QmlDesigner::ContentLibraryMaterial *bundleMat); void bundleTextureDragStarted(QmlDesigner::ContentLibraryTexture *bundleTex); void addTextureRequested(const QString texPath, QmlDesigner::AddTextureMode mode); @@ -88,6 +106,8 @@ signals: void hasActive3DSceneChanged(); void isDraggingChanged(); void isQt6ProjectChanged(); + void importerRunningChanged(); + void hasModelSelectionChanged(); protected: bool eventFilter(QObject *obj, QEvent *event) override; @@ -96,28 +116,31 @@ private: void reloadQmlSource(); 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(); + void createImporter(); - QScopedPointer<StudioQuickWidget> m_quickWidget; + Utils::UniqueObjectPtr<StudioQuickWidget> m_quickWidget; QPointer<ContentLibraryMaterialsModel> m_materialsModel; QPointer<ContentLibraryTexturesModel> m_texturesModel; QPointer<ContentLibraryTexturesModel> m_environmentsModel; QPointer<ContentLibraryEffectsModel> m_effectsModel; + QPointer<ContentLibraryUserModel> m_userModel; + ContentLibraryBundleImporter *m_importer = nullptr; QShortcut *m_qmlSourceUpdateShortcut = nullptr; QString m_filterText; - ContentLibraryEffect *m_effectToDrag = nullptr; + ContentLibraryItem *m_itemToDrag = nullptr; ContentLibraryMaterial *m_materialToDrag = nullptr; ContentLibraryTexture *m_textureToDrag = nullptr; QPoint m_dragStartPoint; @@ -127,12 +150,10 @@ 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; + bool m_importerRunning = false; + bool m_hasModelSelection = false; + 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/curveeditor/detail/keyframeitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp index a1c229f57e..159e7c31ee 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/keyframeitem.cpp @@ -422,7 +422,7 @@ QVariant KeyframeItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons rseg.moveLeftTo(position); if (legalLeft() && legalRight()) { - if (qApp->keyboardModifiers().testFlag(Qt::ShiftModifier) && m_validPos.has_value()) { + if (qApp->keyboardModifiers().testFlag(Qt::ShiftModifier) && m_validPos) { if (m_firstPos) { auto firstToNow = QLineF(*m_firstPos, position); if (std::abs(firstToNow.dx()) > std::abs(firstToNow.dy())) 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/edit3dcanvas.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp index 2e8ef8304f..63d5e958b1 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.cpp @@ -51,6 +51,8 @@ Edit3DCanvas::Edit3DCanvas(Edit3DWidget *parent) setAcceptDrops(true); setFocusPolicy(Qt::ClickFocus); m_busyIndicator->show(); + + installEventFilter(this); } void Edit3DCanvas::updateRenderImage(const QImage &img) @@ -79,11 +81,20 @@ QWidget *Edit3DCanvas::busyIndicator() const return m_busyIndicator; } +#ifdef Q_OS_MACOS +extern "C" bool AXIsProcessTrusted(); +#endif + void Edit3DCanvas::setFlyMode(bool enabled, const QPoint &pos) { if (m_flyMode == enabled) return; +#ifdef Q_OS_MACOS + if (!AXIsProcessTrusted()) + m_isTrusted = false; +#endif + m_flyMode = enabled; if (enabled) { @@ -132,6 +143,23 @@ void Edit3DCanvas::setFlyMode(bool enabled, const QPoint &pos) m_parent->view()->setFlyMode(enabled); } +bool Edit3DCanvas::eventFilter(QObject *obj, QEvent *event) +{ + if (m_flyMode && event->type() == QEvent::ShortcutOverride) { + // Suppress shortcuts that conflict with fly mode keys + const QList<int> controlKeys = { Qt::Key_W, Qt::Key_A, Qt::Key_S, + Qt::Key_D, Qt::Key_Q, Qt::Key_E, + Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, + Qt::Key_Right, Qt::Key_PageDown, Qt::Key_PageUp, + Qt::Key_Alt, Qt::Key_Shift }; + auto ke = static_cast<QKeyEvent *>(event); + if (controlKeys.contains(ke->key())) + event->accept(); + } + + return QObject::eventFilter(obj, event); +} + void Edit3DCanvas::mousePressEvent(QMouseEvent *e) { m_contextMenuPending = false; @@ -171,7 +199,8 @@ void Edit3DCanvas::mouseMoveEvent(QMouseEvent *e) // We notify explicit camera rotation need for puppet rather than rely in mouse events, // as mouse isn't grabbed on puppet side and can't handle fast movements that go out of // edit camera mouse area. This also simplifies split view handling. - QPointF diff = m_hiddenCursorPos - e->globalPos(); + QPointF diff = m_isTrusted ? (m_hiddenCursorPos - e->globalPos()) : (m_lastCursorPos - e->globalPos()); + if (e->buttons() == (Qt::LeftButton | Qt::RightButton)) { m_parent->view()->emitView3DAction(View3DActionType::EditCameraMove, QVector3D{float(-diff.x()), float(-diff.y()), 0.f}); @@ -182,13 +211,26 @@ void Edit3DCanvas::mouseMoveEvent(QMouseEvent *e) // Skip first move to avoid undesirable jump occasionally when initiating flight mode m_flyModeFirstUpdate = false; } - QCursor::setPos(m_hiddenCursorPos); + + if (m_isTrusted) + QCursor::setPos(m_hiddenCursorPos); + else + m_lastCursorPos = e->globalPos(); } } void Edit3DCanvas::wheelEvent(QWheelEvent *e) { - m_parent->view()->sendInputEvent(e); + if (m_flyMode) { + // In fly mode, wheel controls the camera speed slider (value range 1-100) + double speed; + double mult; + m_parent->view()->getCameraSpeedAuxData(speed, mult); + speed = qMin(100., qMax(1., speed + double(e->angleDelta().y()) / 40.)); + m_parent->view()->setCameraSpeedAuxData(speed, mult); + } else { + m_parent->view()->sendInputEvent(e); + } QWidget::wheelEvent(e); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h index 39207554a7..16c1063dd6 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dcanvas.h @@ -30,6 +30,7 @@ public: bool isFlyMode() const { return m_flyMode; } protected: + bool eventFilter(QObject *obj, QEvent *event) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseDoubleClickEvent(QMouseEvent *e) override; @@ -52,10 +53,12 @@ private: qint32 m_activeScene = -1; QElapsedTimer m_usageTimer; qreal m_opacity = 1.0; + bool m_isTrusted = true; QWidget *m_busyIndicator = nullptr; bool m_flyMode = false; QPoint m_flyModeStartCursorPos; QPoint m_hiddenCursorPos; + QPoint m_lastCursorPos; qint64 m_flyModeStartTime = 0; bool m_flyModeFirstUpdate = false; bool m_contextMenuPending = false; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 911ee1fb15..bb7404f252 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -20,9 +20,11 @@ #include "nodeinstanceview.h" #include "qmldesignerconstants.h" #include "qmldesignerplugin.h" +#include "qmlitemnode.h" #include "qmlvisualnode.h" #include "seekerslider.h" #include "snapconfiguration.h" +#include "variantproperty.h" #include <auxiliarydataproperties.h> #include <model/modelutils.h> @@ -133,6 +135,7 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) const QString orientationKey = QStringLiteral("globalOrientation"); const QString editLightKey = QStringLiteral("showEditLight"); const QString gridKey = QStringLiteral("showGrid"); + const QString showLookAtKey = QStringLiteral("showLookAt"); const QString selectionBoxKey = QStringLiteral("showSelectionBox"); const QString iconGizmoKey = QStringLiteral("showIconGizmo"); const QString cameraFrustumKey = QStringLiteral("showCameraFrustum"); @@ -187,6 +190,11 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) else m_showGridAction->action()->setChecked(false); + if (sceneState.contains(showLookAtKey)) + m_showLookAtAction->action()->setChecked(sceneState[showLookAtKey].toBool()); + else + m_showLookAtAction->action()->setChecked(false); + if (sceneState.contains(selectionBoxKey)) m_showSelectionBoxAction->action()->setChecked(sceneState[selectionBoxKey].toBool()); else @@ -235,36 +243,10 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) state.showWireframe = false; } - // Syncing background color only makes sense for children of View3D instances - bool syncValue = false; - bool syncEnabled = false; - bool desiredSyncValue = false; if (sceneState.contains(syncEnvBgKey)) - desiredSyncValue = sceneState[syncEnvBgKey].toBool(); - ModelNode checkNode = Utils3D::active3DSceneNode(this); - const bool activeSceneValid = checkNode.isValid(); - - while (checkNode.isValid()) { - if (checkNode.metaInfo().isQtQuick3DView3D()) { - syncValue = desiredSyncValue; - syncEnabled = true; - break; - } - if (checkNode.hasParentProperty()) - checkNode = checkNode.parentProperty().parentModelNode(); - else - break; - } - - if (activeSceneValid && syncValue != desiredSyncValue) { - // Update actual toolstate as well if we overrode it. - QTimer::singleShot(0, this, [this, syncValue]() { - emitView3DAction(View3DActionType::SyncEnvBackground, syncValue); - }); - } - - m_syncEnvBackgroundAction->action()->setChecked(syncValue); - m_syncEnvBackgroundAction->action()->setEnabled(syncEnabled); + m_syncEnvBackgroundAction->action()->setChecked(sceneState[syncEnvBgKey].toBool()); + else + m_syncEnvBackgroundAction->action()->setChecked(false); // Selection context change updates visible and enabled states SelectionContext selectionContext(this); @@ -273,12 +255,22 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) m_bakeLightsAction->currentContextChanged(selectionContext); syncCameraSpeedToNewView(); + + storeCurrentSceneEnvironment(); } 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, @@ -342,11 +334,10 @@ void Edit3DView::handleEntriesChanged() {EK_importedModels, {tr("Imported Models"), contextIcon(DesignerIcons::ImportedModelsIcon)}}}; #ifdef QDS_USE_PROJECTSTORAGE - const auto &projectStorage = *model()->projectStorage(); auto append = [&](const NodeMetaInfo &metaInfo, ItemLibraryEntryKeys key) { auto entries = metaInfo.itemLibrariesEntries(); if (entries.size()) - entriesMap[key].entryList.append(toItemLibraryEntries(entries, projectStorage)); + entriesMap[key].entryList.append(toItemLibraryEntries(entries)); }; append(model()->qtQuick3DModelMetaInfo(), EK_primitives); @@ -356,7 +347,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, Storage::ModuleKind::QmlLibrary); for (const auto &metaInfo : model()->metaInfosForModule(assetsModule)) append(metaInfo, EK_importedModels); @@ -373,8 +369,12 @@ void Edit3DView::handleEntriesChanged() } else if (entry.typeName() == "QtQuick3D.OrthographicCamera" || entry.typeName() == "QtQuick3D.PerspectiveCamera") { entryKey = EK_cameras; - } else if (entry.typeName().startsWith("Quick3DAssets.") - && NodeHints::fromItemLibraryEntry(entry).canBeDroppedInView3D()) { + } else if (entry.typeName().startsWith(QmlDesignerPlugin::instance() + ->documentManager() + .generatedComponentUtils() + .import3dTypePrefix() + .toUtf8()) + && NodeHints::fromItemLibraryEntry(entry, model()).canBeDroppedInView3D()) { entryKey = EK_importedModels; } else { continue; @@ -448,6 +448,7 @@ void Edit3DView::customNotification([[maybe_unused]] const AbstractView *view, self->emitView3DAction(View3DActionType::GetNodeAtMainScenePos, QVariantList{data[0], nodeList[0].internalId()}); self->m_nodeAtPosReqType = NodeAtPosReqType::MainScenePick; + self->m_pickView3dNode = nodeList[0]; }); } } @@ -490,7 +491,7 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) { emitCustomNotification("drop_bundle_material", {modelNode}); // To ContentLibraryView } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleEffectDrop) { - emitCustomNotification("drop_bundle_effect", {modelNode}, {pos3d}); // To ContentLibraryView + emitCustomNotification("drop_bundle_item", {modelNode}, {pos3d}); // To ContentLibraryView } else if (m_nodeAtPosReqType == NodeAtPosReqType::TextureDrop) { emitCustomNotification("apply_texture_to_model3D", {modelNode, m_droppedModelNode}); } else if (m_nodeAtPosReqType == NodeAtPosReqType::AssetDrop) { @@ -500,6 +501,8 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos } else if (m_nodeAtPosReqType == NodeAtPosReqType::MainScenePick) { if (modelNode.isValid()) setSelectedModelNode(modelNode); + else if (m_pickView3dNode.isValid() && !m_pickView3dNode.isSelected()) + setSelectedModelNode(m_pickView3dNode); emitView3DAction(View3DActionType::AlignViewToCamera, true); } @@ -523,6 +526,21 @@ void Edit3DView::nodeRemoved(const ModelNode &, updateAlignActionStates(); } +void Edit3DView::propertiesRemoved(const QList<AbstractProperty> &propertyList) +{ + maybeStoreCurrentSceneEnvironment(propertyList); +} + +void Edit3DView::bindingPropertiesChanged(const QList<BindingProperty> &propertyList, PropertyChangeFlags) +{ + maybeStoreCurrentSceneEnvironment(propertyList); +} + +void Edit3DView::variantPropertiesChanged(const QList<VariantProperty> &propertyList, PropertyChangeFlags) +{ + maybeStoreCurrentSceneEnvironment(propertyList); +} + void Edit3DView::sendInputEvent(QEvent *e) const { if (nodeInstanceView()) @@ -699,6 +717,30 @@ QPoint Edit3DView::resolveToolbarPopupPos(Edit3DAction *action) const return pos; } +template<typename T, typename> +void Edit3DView::maybeStoreCurrentSceneEnvironment(const QList<T> &propertyList) +{ + QSet<qint32> handledNodes; + QmlObjectNode sceneEnv; + for (const AbstractProperty &prop : propertyList) { + ModelNode node = prop.parentModelNode(); + const qint32 id = node.internalId(); + if (handledNodes.contains(id)) + continue; + + handledNodes.insert(id); + if (!node.metaInfo().isQtQuick3DSceneEnvironment()) + continue; + + if (!sceneEnv.isValid()) + sceneEnv = currentSceneEnv(); + if (sceneEnv == node) { + storeCurrentSceneEnvironment(); + break; + } + } +} + void Edit3DView::showContextMenu() { // If request for context menu is still pending, skip for now @@ -719,32 +761,6 @@ void Edit3DView::showContextMenu() void Edit3DView::setFlyMode(bool enabled) { emitView3DAction(View3DActionType::FlyModeToggle, enabled); - - // Disable any actions with conflicting hotkeys - if (enabled) { - m_flyModeDisabledActions.clear(); - const QList<QKeySequence> controlKeys = { Qt::Key_W, Qt::Key_A, Qt::Key_S, - Qt::Key_D, Qt::Key_Q, Qt::Key_E, - Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, - Qt::Key_Right, Qt::Key_PageDown, Qt::Key_PageUp}; - for (auto i = m_edit3DActions.cbegin(), end = m_edit3DActions.cend(); i != end; ++i) { - for (const QKeySequence &controlKey : controlKeys) { - if (Core::Command *cmd = m_edit3DWidget->actionToCommandHash().value(i.value()->action())) { - if (cmd->keySequence().matches(controlKey) == QKeySequence::ExactMatch) { - if (i.value()->action()->isEnabled()) { - m_flyModeDisabledActions.append(i.value()); - i.value()->action()->setEnabled(false); - } - break; - } - } - } - } - } else { - for (Edit3DAction *action : std::as_const(m_flyModeDisabledActions)) - action->action()->setEnabled(true); - m_flyModeDisabledActions.clear(); - } } void Edit3DView::syncSnapAuxPropsToSettings() @@ -814,6 +830,75 @@ void Edit3DView::syncCameraSpeedToNewView() setCameraSpeedAuxData(speed, multiplier); } +QmlObjectNode Edit3DView::currentSceneEnv() +{ + PropertyName envProp{"environment"}; + ModelNode checkNode = Utils3D::active3DSceneNode(this); + while (checkNode.isValid()) { + if (checkNode.metaInfo().isQtQuick3DView3D()) { + QmlObjectNode sceneEnvNode = QmlItemNode(checkNode).bindingProperty(envProp) + .resolveToModelNode(); + if (sceneEnvNode.isValid()) + return sceneEnvNode; + break; + } + if (checkNode.hasParentProperty()) + checkNode = checkNode.parentProperty().parentModelNode(); + else + break; + } + return {}; +} + +void Edit3DView::storeCurrentSceneEnvironment() +{ + // If current active scene has scene environment, store relevant properties + QmlObjectNode sceneEnvNode = currentSceneEnv(); + if (sceneEnvNode.isValid()) { + QVariantMap lastSceneEnvData; + + auto insertPropValue = [](const PropertyName prop, const QmlObjectNode &node, + QVariantMap &map) { + if (!node.hasProperty(prop)) + return; + + map.insert(QString::fromUtf8(prop), node.modelValue(prop)); + }; + + auto insertTextureProps = [&](const PropertyName prop) { + // For now we just grab the absolute path of texture source for simplicity + if (!sceneEnvNode.hasProperty(prop)) + return; + + QmlObjectNode bindNode = QmlItemNode(sceneEnvNode).bindingProperty(prop) + .resolveToModelNode(); + if (bindNode.isValid()) { + QVariantMap props; + const PropertyName sourceProp = "source"; + if (bindNode.hasProperty(sourceProp)) { + Utils::FilePath qmlPath = Utils::FilePath::fromUrl( + model()->fileUrl()).absolutePath(); + Utils::FilePath sourcePath = Utils::FilePath::fromUrl( + bindNode.modelValue(sourceProp).toUrl()); + + sourcePath = qmlPath.resolvePath(sourcePath); + + props.insert(QString::fromUtf8(sourceProp), + sourcePath.absoluteFilePath().toUrl()); + } + lastSceneEnvData.insert(QString::fromUtf8(prop), props); + } + }; + + insertPropValue("backgroundMode", sceneEnvNode, lastSceneEnvData); + insertPropValue("clearColor", sceneEnvNode, lastSceneEnvData); + insertTextureProps("lightProbe"); + insertTextureProps("skyBoxCubeMap"); + + emitView3DAction(View3DActionType::SetLastSceneEnvData, lastSceneEnvData); + } +} + const QList<Edit3DView::SplitToolState> &Edit3DView::splitToolStates() const { return m_splitToolStates; @@ -961,6 +1046,18 @@ void Edit3DView::createEdit3DActions() nullptr, QCoreApplication::translate("ShowGridAction", "Toggle the visibility of the helper grid.")); + m_showLookAtAction = std::make_unique<Edit3DAction>( + QmlDesigner::Constants::EDIT3D_EDIT_SHOW_LOOKAT, + View3DActionType::ShowLookAt, + QCoreApplication::translate("ShowLookAtAction", "Show Look-at"), + QKeySequence(Qt::Key_L), + true, + true, + QIcon(), + this, + nullptr, + QCoreApplication::translate("ShowLookAtAction", "Toggle the visibility of the edit camera look-at indicator.")); + m_showSelectionBoxAction = std::make_unique<Edit3DAction>( QmlDesigner::Constants::EDIT3D_EDIT_SHOW_SELECTION_BOX, View3DActionType::ShowSelectionBox, @@ -1264,6 +1361,7 @@ void Edit3DView::createEdit3DActions() m_rightActions << m_resetAction.get(); m_visibilityToggleActions << m_showGridAction.get(); + m_visibilityToggleActions << m_showLookAtAction.get(); m_visibilityToggleActions << m_showSelectionBoxAction.get(); m_visibilityToggleActions << m_showIconGizmoAction.get(); m_visibilityToggleActions << m_showCameraFrustumAction.get(); diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 781b26d8d8..755efc0ae3 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -8,6 +8,7 @@ #include <abstractview.h> #include <modelcache.h> +#include <qmlobjectnode.h> #include <QImage> #include <QPointer> @@ -59,6 +60,11 @@ public: PropertyChangeFlags propertyChange) override; void nodeRemoved(const ModelNode &removedNode, const NodeAbstractProperty &parentProperty, PropertyChangeFlags propertyChange) override; + void propertiesRemoved(const QList<AbstractProperty> &propertyList) override; + void bindingPropertiesChanged(const QList<BindingProperty> &propertyList, + PropertyChangeFlags propertyChange) override; + void variantPropertiesChanged(const QList<VariantProperty> &propertyList, + PropertyChangeFlags propertyChange) override; void sendInputEvent(QEvent *e) const; void edit3DViewResized(const QSize &size) const; @@ -127,9 +133,14 @@ private: void createSyncEnvBackgroundAction(); void createSeekerSliderAction(); void syncCameraSpeedToNewView(); + QmlObjectNode currentSceneEnv(); + void storeCurrentSceneEnvironment(); QPoint resolveToolbarPopupPos(Edit3DAction *action) const; + template<typename T, typename = typename std::enable_if<std::is_base_of<AbstractProperty , T>::value>::type> + void maybeStoreCurrentSceneEnvironment(const QList<T> &propertyList); + QPointer<Edit3DWidget> m_edit3DWidget; QVector<Edit3DAction *> m_leftActions; QVector<Edit3DAction *> m_rightActions; @@ -148,6 +159,7 @@ private: std::unique_ptr<Edit3DAction> m_orientationModeAction; std::unique_ptr<Edit3DAction> m_editLightAction; std::unique_ptr<Edit3DAction> m_showGridAction; + std::unique_ptr<Edit3DAction> m_showLookAtAction; std::unique_ptr<Edit3DAction> m_showSelectionBoxAction; std::unique_ptr<Edit3DAction> m_showIconGizmoAction; std::unique_ptr<Edit3DAction> m_showCameraFrustumAction; @@ -187,11 +199,12 @@ private: int m_activeSplit = 0; QList<SplitToolState> m_splitToolStates; - QList<Edit3DAction *> m_flyModeDisabledActions; ModelNode m_contextMenuPendingNode; + ModelNode m_pickView3dNode; 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..f6bbf8d794 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -2,37 +2,41 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "edit3dwidget.h" -#include "designdocument.h" -#include "designericons.h" + #include "edit3dactions.h" #include "edit3dcanvas.h" #include "edit3dtoolbarmenu.h" #include "edit3dview.h" -#include "externaldependenciesinterface.h" -#include "materialutils.h" -#include "metainfo.h" -#include "modelnodeoperations.h" -#include "nodeabstractproperty.h" -#include "nodehints.h" -#include "qmldesignerconstants.h" -#include "qmldesignerplugin.h" -#include "qmleditormenu.h" -#include "qmlvisualnode.h" -#include "viewmanager.h" -#include <utils3d.h> #include <auxiliarydataproperties.h> #include <designeractionmanager.h> +#include <designdocument.h> +#include <designericons.h> #include <designermcumanager.h> +#include <externaldependenciesinterface.h> +#include <generatedcomponentutils.h> #include <import.h> -#include <model/modelutils.h> +#include <materialutils.h> +#include <metainfo.h> +#include <modelnodeoperations.h> +#include <nodeabstractproperty.h> +#include <nodehints.h> #include <nodeinstanceview.h> +#include <qmldesignerconstants.h> +#include <qmldesignerplugin.h> +#include <qmleditormenu.h> +#include <qmlvisualnode.h> #include <seekerslider.h> +#include <toolbox.h> +#include <viewmanager.h> +#include <utils3d.h> #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/command.h> #include <coreplugin/icore.h> -#include <toolbox.h> + +#include <model/modelutils.h> + #include <utils/asset.h> #include <utils/qtcassert.h> #include <utils/utilsicons.h> @@ -359,6 +363,14 @@ void Edit3DWidget::createContextMenu() resetAction->setToolTip(tr("Reset all shading options for all viewports.")); m_contextMenu->addSeparator(); + + m_addToContentLibAction = m_contextMenu->addAction( + contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon + tr("Add to Content Library"), [&] { + view()->emitCustomNotification("add_3d_to_content_lib", {m_contextMenuTarget}); + }); + + m_contextMenu->addSeparator(); } bool Edit3DWidget::isPasteAvailable() const @@ -402,7 +414,7 @@ void Edit3DWidget::showOnboardingLabel() " in the" " <b>Assets</b>" " view."); - text = labelText.arg(Utils::creatorTheme()->color(Utils::Theme::TextColorLink).name()); + text = labelText.arg(Utils::creatorColor(Utils::Theme::TextColorLink).name()); } else { text = tr("3D view is not supported in Qt5 projects."); } @@ -608,14 +620,18 @@ void Edit3DWidget::showBackgroundColorMenu(bool show, const QPoint &pos) void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode, const QVector3D &pos3d) { + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + m_contextMenuTarget = modelNode; m_contextMenuPos3d = pos3d; const bool isModel = modelNode.metaInfo().isQtQuick3DModel(); + const bool isNode = modelNode.metaInfo().isQtQuick3DNode(); const bool allowAlign = view()->edit3DAction(View3DActionType::AlignCamerasToView)->action()->isEnabled(); const bool isSingleComponent = view()->hasSingleSelectedModelNode() && modelNode.isComponent(); const bool anyNodeSelected = view()->hasSelectedModelNodes(); const bool selectionExcludingRoot = anyNodeSelected && !view()->rootModelNode().isSelected(); + const bool isInBundle = modelNode.type().startsWith(compUtils.componentBundlesTypePrefix().toLatin1()); if (m_createSubMenu) m_createSubMenu->setEnabled(!isSceneLocked()); @@ -633,6 +649,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode m_toggleGroupAction->setEnabled(true); m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible()); m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled()); + m_addToContentLibAction->setEnabled(isNode && !isInBundle); if (m_view) { int idx = m_view->activeSplit(); @@ -685,7 +702,7 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) } else if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL) - || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_EFFECT) + || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_ITEM) || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_TEXTURE)) { if (Utils3D::active3DSceneNode(m_view).isValid()) dragEnterEvent->acceptProposedAction(); @@ -694,7 +711,7 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) if (!data.isEmpty()) { QDataStream stream(data); stream >> m_draggedEntry; - if (NodeHints::fromItemLibraryEntry(m_draggedEntry).canBeDroppedInView3D()) + if (NodeHints::fromItemLibraryEntry(m_draggedEntry, view()->model()).canBeDroppedInView3D()) dragEnterEvent->acceptProposedAction(); } } @@ -730,8 +747,8 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) return; } - // handle dropping bundle effects - if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_EFFECT)) { + // handle dropping bundle items + if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_ITEM)) { m_view->dropBundleEffect(pos); m_view->model()->endDrag(); return; @@ -766,9 +783,14 @@ 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 moduleId = model->module(import3dTypePrefix, Storage::ModuleKind::QmlLibrary); + auto metaInfo = model->metaInfo(moduleId, fileName.toUtf8()); if (auto entries = metaInfo.itemLibrariesEntries(); entries.size()) { - auto entry = ItemLibraryEntry{entries.front(), *model->projectStorage()}; + auto entry = ItemLibraryEntry{entries.front()}; QmlVisualNode::createQml3DNode(view(), entry, m_canvas->activeScene(), {}, false); } } @@ -780,7 +802,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/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index 211b044d41..97c0469668 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -100,6 +100,7 @@ private: QPointer<QAction> m_selectParentAction; QPointer<QAction> m_toggleGroupAction; QPointer<QAction> m_wireFrameAction; + QPointer<QAction> m_addToContentLibAction; QHash<int, QPointer<QAction>> m_matOverrideActions; QPointer<QMenu> m_createSubMenu; ModelNode m_contextMenuTarget; 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/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index 0b7d199b50..a6494811b6 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -206,9 +206,16 @@ static ItemLibraryEntry itemLibraryEntryFromMimeData(const QMimeData *mimeData) return itemLibraryEntry; } -static bool canBeDropped(const QMimeData *mimeData) +static bool canBeDropped(const QMimeData *mimeData, Model *model) { - return NodeHints::fromItemLibraryEntry(itemLibraryEntryFromMimeData(mimeData)).canBeDroppedInFormEditor(); +#ifdef QDS_USE_PROJECTSTORAGE + auto itemLibraryEntry = itemLibraryEntryFromMimeData(mimeData); + NodeMetaInfo metaInfo{itemLibraryEntry.typeId(), model->projectStorage()}; + return metaInfo.canBeDroppedInFormEditor() == FlagIs::True; +#else + return NodeHints::fromItemLibraryEntry(itemLibraryEntryFromMimeData(mimeData), model) + .canBeDroppedInFormEditor(); +#endif } static bool hasItemLibraryInfo(const QMimeData *mimeData) @@ -218,7 +225,7 @@ static bool hasItemLibraryInfo(const QMimeData *mimeData) void DragTool::dropEvent(const QList<QGraphicsItem *> &itemList, QGraphicsSceneDragDropEvent *event) { - if (canBeDropped(event->mimeData())) { + if (canBeDropped(event->mimeData(), view()->model())) { event->accept(); end(generateUseSnapping(event->modifiers())); @@ -290,7 +297,7 @@ void DragTool::dropEvent(const QList<QGraphicsItem *> &itemList, QGraphicsSceneD void DragTool::dragEnterEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSceneDragDropEvent *event) { - if (canBeDropped(event->mimeData())) { + if (canBeDropped(event->mimeData(), view()->model())) { m_blockMove = false; if (hasItemLibraryInfo(event->mimeData())) { @@ -306,7 +313,7 @@ void DragTool::dragEnterEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraph void DragTool::dragLeaveEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSceneDragDropEvent *event) { - if (canBeDropped(event->mimeData())) { + if (canBeDropped(event->mimeData(), view()->model())) { event->accept(); m_moveManipulator.end(); @@ -363,10 +370,8 @@ void DragTool::dragMoveEvent(const QList<QGraphicsItem *> &itemList, QGraphicsSc ->data(Constants::MIME_TYPE_ASSETS)).split(','); QString assetType = AssetsLibraryWidget::getAssetTypeAndData(assetPaths[0]).first; - if (!m_blockMove - && !m_isAborted - && canBeDropped(event->mimeData()) - && assetType != Constants::MIME_TYPE_ASSET_EFFECT) { + if (!m_blockMove && !m_isAborted && canBeDropped(event->mimeData(), view()->model()) + && assetType != Constants::MIME_TYPE_ASSET_EFFECT) { event->accept(); if (!m_dragNodes.isEmpty()) { if (targetContainerItem) { diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.h b/src/plugins/qmldesigner/components/formeditor/dragtool.h index 1cd2c9f487..c1d5626d28 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.h +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.h @@ -7,7 +7,6 @@ #include "selectionindicator.h" #include <QObject> -#include <QScopedPointer> #include <QPointer> namespace QmlDesigner { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp index 35a48a6b6d..dd239dd966 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorannotationicon.cpp @@ -302,9 +302,9 @@ QGraphicsItem *FormEditorAnnotationIcon::createCommentBubble(QRectF rect, const const QString &author, const QString &text, const QString &date, QGraphicsItem *parent) { - static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::DStextColor); - static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker); - static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor); + static QColor textColor = Utils::creatorColor(Utils::Theme::DStextColor); + static QColor backgroundColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColorDarker); + static QColor frameColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColor); QFont font; font.setBold(true); @@ -405,9 +405,9 @@ QGraphicsItem *FormEditorAnnotationIcon::createCommentBubble(QRectF rect, const QGraphicsItem *FormEditorAnnotationIcon::createTitleBubble(const QRectF &rect, const QString &text, QGraphicsItem *parent) { - static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::DStextColor); - static QColor backgroundColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColorDarker); - static QColor frameColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_BackgroundColor); + static QColor textColor = Utils::creatorColor(Utils::Theme::DStextColor); + static QColor backgroundColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColorDarker); + static QColor frameColor = Utils::creatorColor(Utils::Theme::QmlDesigner_BackgroundColor); QFont font; font.setBold(true); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp index 9549ce9dd4..d835274a97 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.cpp @@ -3,7 +3,6 @@ #include "formeditorgraphicsview.h" #include "backgroundaction.h" -#include "formeditoritem.h" #include "formeditorwidget.h" #include "navigation2d.h" @@ -76,11 +75,11 @@ bool FormEditorGraphicsView::eventFilter(QObject *watched, QEvent *event) auto mouseEvent = static_cast<QMouseEvent *>(event); if (!m_panningStartPosition.isNull()) { horizontalScrollBar()->setValue(horizontalScrollBar()->value() - - (mouseEvent->x() - m_panningStartPosition.x())); + (mouseEvent->position().x() - m_panningStartPosition.x())); verticalScrollBar()->setValue(verticalScrollBar()->value() - - (mouseEvent->y() - m_panningStartPosition.y())); + (mouseEvent->position().y() - m_panningStartPosition.y())); } - m_panningStartPosition = mouseEvent->pos(); + m_panningStartPosition = mouseEvent->position(); event->accept(); return true; } @@ -215,7 +214,7 @@ void FormEditorGraphicsView::drawBackground(QPainter *painter, const QRectF &rec painter->fillRect(rectangle.intersected(rootItemRect()), backgroundBrush()); } - QPen pen(Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor)); + QPen pen(Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor)); pen.setStyle(Qt::DotLine); pen.setWidth(1); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h index 60e02582cd..e1e61a8f1e 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorgraphicsview.h @@ -46,7 +46,7 @@ private: void stopPanning(QEvent *event); Panning m_isPanning = Panning::NotStarted; - QPoint m_panningStartPosition; + QPointF m_panningStartPosition; QRectF m_rootItemRect; QImage m_backgroundImage; }; diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 12da85e2c4..b3df6b8fe7 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -48,23 +48,19 @@ void drawIcon(QPainter *painter, int iconSize, const QColor &penColor) { - static QFontDatabase a; - const QString fontName = "qtds_propertyIconFont.ttf"; - Q_ASSERT(a.hasFamily(fontName)); + QTC_ASSERT(QFontDatabase::hasFamily(fontName), return); - if (a.hasFamily(fontName)) { - QFont font(fontName); - font.setPixelSize(fontSize); + QFont font(fontName); + font.setPixelSize(fontSize); - painter->save(); - painter->setPen(penColor); - painter->setFont(font); - painter->drawText(QRectF(x, y, iconSize, iconSize), iconSymbol); + painter->save(); + painter->setPen(penColor); + painter->setFont(font); + painter->drawText(QRectF(x, y, iconSize, iconSize), iconSymbol); - painter->restore(); - } + painter->restore(); } FormEditorScene *FormEditorItem::scene() const { @@ -309,7 +305,7 @@ void FormEditorItem::paintBoundingRect(QPainter *painter) const pen.setJoinStyle(Qt::MiterJoin); const QColor frameColor(0xaa, 0xaa, 0xaa); - static const QColor selectionColor = Utils::creatorTheme()->color( + static const QColor selectionColor = Utils::creatorColor( Utils::Theme::QmlDesigner_FormEditorSelectionColor); if (scene()->showBoundingRects()) { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp b/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp index 555d0d90e3..f3aa96ada4 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditortoolbutton.cpp @@ -42,7 +42,7 @@ void FormEditorToolButton::paint(QPainter *painter, const QStyleOptionGraphicsIt QRectF adjustedRect(size().width() - toolButtonSize, size().height() - toolButtonSize, toolButtonSize, toolButtonSize); painter->setPen(Qt::NoPen); - static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor); + static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor); if (m_state == Hovered) painter->setBrush(selectionColor.lighter(110)); diff --git a/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp b/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp index 0f5b5f4438..45d26f831e 100644 --- a/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp +++ b/src/plugins/qmldesigner/components/formeditor/selectionindicator.cpp @@ -78,7 +78,7 @@ void SelectionIndicator::setItems(const QList<FormEditorItem*> &itemList) { clear(); - static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor); + static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor); for (FormEditorItem *item : itemList) { if (!item->qmlItemNode().isValid()) @@ -119,7 +119,7 @@ void SelectionIndicator::setItems(const QList<FormEditorItem*> &itemList) m_annotationItem = nullptr; } - static QColor textColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorForegroundColor); + static QColor textColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorForegroundColor); textItem->setDefaultTextColor(textColor); QPolygonF labelPolygon = boundingRectInLayerItemSpaceForItem(selectedItem, m_layerItem.data()); diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index c0bebbb82b..aa2dfd3b28 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( @@ -149,7 +150,10 @@ bool DesignDocument::loadInFileComponent(const ModelNode &componentNode) if (!componentNode.isRootNode()) { //change to subcomponent model - changeToInFileComponentModel(createComponentTextModifier(m_documentTextModifier.data(), rewriterView(), componentText, componentNode)); + changeToInFileComponentModel(createComponentTextModifier(m_documentTextModifier.get(), + rewriterView(), + componentText, + componentNode)); } return true; @@ -280,11 +284,10 @@ void DesignDocument::moveNodesToPosition(const QList<ModelNode> &nodes, const st parentProperty.reparentHere(pastedNode); QmlVisualNode visualNode(pastedNode); - if (!firstVisualNode.has_value() && visualNode.isValid()){ + if (!firstVisualNode && visualNode) { firstVisualNode = visualNode; - translationVect = (position.has_value() && firstVisualNode.has_value()) - ? position.value() - firstVisualNode->position() - : QVector3D(); + translationVect = (position && firstVisualNode) ? *position - firstVisualNode->position() + : QVector3D(); } visualNode.translate(translationVect); } @@ -376,9 +379,12 @@ void DesignDocument::loadDocument(QPlainTextEdit *edit) m_documentTextModifier.reset(new BaseTextEditModifier(qobject_cast<TextEditor::TextEditorWidget *>(plainTextEdit()))); - connect(m_documentTextModifier.data(), &TextModifier::textChanged, this, &DesignDocument::updateQrcFiles); + connect(m_documentTextModifier.get(), + &TextModifier::textChanged, + this, + &DesignDocument::updateQrcFiles); - m_rewriterView->setTextModifier(m_documentTextModifier.data()); + m_rewriterView->setTextModifier(m_documentTextModifier.get()); m_inFileComponentTextModifier.reset(); @@ -398,7 +404,7 @@ void DesignDocument::changeToDocumentModel() if (edit) edit->document()->clearUndoRedoStacks(); - m_rewriterView->setTextModifier(m_documentTextModifier.data()); + m_rewriterView->setTextModifier(m_documentTextModifier.get()); m_inFileComponentModel.reset(); m_inFileComponentTextModifier.reset(); @@ -431,7 +437,7 @@ bool DesignDocument::hasProject() const void DesignDocument::setModified() { - if (!m_documentTextModifier.isNull()) + if (m_documentTextModifier) m_documentTextModifier->textDocument()->setModified(true); } @@ -447,7 +453,7 @@ void DesignDocument::changeToInFileComponentModel(ComponentTextModifier *textMod m_inFileComponentModel = createInFileComponentModel(); - m_rewriterView->setTextModifier(m_inFileComponentTextModifier.data()); + m_rewriterView->setTextModifier(m_inFileComponentTextModifier.get()); viewManager().attachRewriterView(); viewManager().attachViewsExceptRewriterAndComponetView(); @@ -674,7 +680,7 @@ void DesignDocument::selectAll() RewriterView *DesignDocument::rewriterView() const { - return m_rewriterView.data(); + return m_rewriterView.get(); } void DesignDocument::setEditor(Core::IEditor *editor) diff --git a/src/plugins/qmldesigner/components/integration/designdocument.h b/src/plugins/qmldesigner/components/integration/designdocument.h index 0d75141205..1f67ff4b30 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.h +++ b/src/plugins/qmldesigner/components/integration/designdocument.h @@ -16,9 +16,10 @@ #include <QObject> #include <QString> - #include <QStackedWidget> +#include <memory> + QT_BEGIN_NAMESPACE class QPlainTextEdit; QT_END_NAMESPACE @@ -41,7 +42,8 @@ class QMLDESIGNERCOMPONENTS_EXPORT DesignDocument : public QObject Q_OBJECT public: - DesignDocument(ProjectStorageDependencies projectStorageDependencies, + DesignDocument(const QUrl &filePath, + ProjectStorageDependencies projectStorageDependencies, ExternalDependenciesInterface &externalDependencies); ~DesignDocument() override; @@ -142,12 +144,12 @@ private: // variables ModelPointer m_documentModel; ModelPointer m_inFileComponentModel; QPointer<Core::IEditor> m_textEditor; - QScopedPointer<BaseTextEditModifier> m_documentTextModifier; - QScopedPointer<ComponentTextModifier> m_inFileComponentTextModifier; + std::unique_ptr<BaseTextEditModifier> m_documentTextModifier; + std::unique_ptr<ComponentTextModifier> m_inFileComponentTextModifier; #ifndef QDS_USE_PROJECTSTORAGE - QScopedPointer<SubComponentManager> m_subComponentManager; + std::unique_ptr<SubComponentManager> m_subComponentManager; #endif - QScopedPointer<RewriterView> m_rewriterView; + std::unique_ptr<RewriterView> m_rewriterView; bool m_documentLoaded; ProjectExplorer::Target *m_currentTarget; ProjectStorageDependencies m_projectStorageDependencies; diff --git a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp index 6ef95bf4c4..d97b9ff06f 100644 --- a/src/plugins/qmldesigner/components/integration/designdocumentview.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocumentview.cpp @@ -23,6 +23,8 @@ #include <utils/algorithm.h> #include <utils/qtcassert.h> +#include <memory> + namespace QmlDesigner { DesignDocumentView::DesignDocumentView(ExternalDependenciesInterface &externalDependencies) @@ -116,14 +118,14 @@ QString DesignDocumentView::toText() const textEdit.setPlainText(imports + QStringLiteral("Item {\n}\n")); NotIndentingTextEditModifier modifier(&textEdit); - QScopedPointer<RewriterView> rewriterView( - new RewriterView(externalDependencies(), RewriterView::Amend)); + std::unique_ptr<RewriterView> rewriterView = std::make_unique<RewriterView>(externalDependencies(), + RewriterView::Amend); rewriterView->setCheckSemanticErrors(false); rewriterView->setPossibleImportsEnabled(false); rewriterView->setTextModifier(&modifier); - outputModel->setRewriterView(rewriterView.data()); + outputModel->setRewriterView(rewriterView.get()); - ModelMerger merger(rewriterView.data()); + ModelMerger merger(rewriterView.get()); merger.replaceModel(rootModelNode()); diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.cpp b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.cpp new file mode 100644 index 0000000000..608bf42eb3 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.cpp @@ -0,0 +1,82 @@ +// 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 "import3dcanvas.h" + +#include <QImage> +#include <QLinearGradient> +#include <QMouseEvent> +#include <QPainter> + +namespace QmlDesigner { + +static QImage createGradientImage(int width, int height) { + QImage image(width, height, QImage::Format_ARGB32_Premultiplied); + + QLinearGradient gradient(0, 0, 0, height); + gradient.setColorAt(0, QColor(0x999999)); + gradient.setColorAt(1, QColor(0x222222)); + + QPainter painter(&image); + painter.fillRect(0, 0, width, height, gradient); + + return image; +} + +Import3dCanvas::Import3dCanvas(QWidget *parent) + : QWidget(parent) +{ +} + +void Import3dCanvas::updateRenderImage(const QImage &img) +{ + m_image = img; + update(); +} + +void Import3dCanvas::paintEvent([[maybe_unused]] QPaintEvent *e) +{ + QWidget::paintEvent(e); + + QPainter painter(this); + + if (m_image.isNull()) { + QImage image = createGradientImage(width(), height()); + painter.drawImage(rect(), image, QRect(0, 0, image.width(), image.height())); + } else { + painter.drawImage(rect(), m_image, QRect(0, 0, m_image.width(), m_image.height())); + } +} + +void Import3dCanvas::resizeEvent(QResizeEvent *) +{ + emit requestImageUpdate(); +} + +void Import3dCanvas::mousePressEvent(QMouseEvent *e) +{ + if (e->buttons() == Qt::LeftButton) + m_dragPos = e->position(); + else + m_dragPos = {}; +} + +void Import3dCanvas::mouseReleaseEvent(QMouseEvent *) +{ + m_dragPos = {}; +} + +void Import3dCanvas::mouseMoveEvent(QMouseEvent *e) +{ + if (m_dragPos.isNull()) + return; + + const QPointF curPos = e->position(); + const QPointF delta = curPos - m_dragPos; + + m_dragPos = curPos; + + emit requestRotation(delta); +} + +} diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.h b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.h new file mode 100644 index 0000000000..72fb19acff --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/import3dcanvas.h @@ -0,0 +1,37 @@ +// 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 <QEvent> +#include <QImage> +#include <QPointF> +#include <QWidget> + +namespace QmlDesigner { + +class Import3dCanvas : public QWidget +{ + Q_OBJECT + +public: + Import3dCanvas(QWidget *parent); + + void updateRenderImage(const QImage &img); + +signals: + void requestImageUpdate(); + void requestRotation(const QPointF &delta); + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + +private: + QImage m_image; + QPointF m_dragPos; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp new file mode 100644 index 0000000000..4c455e3c6d --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.cpp @@ -0,0 +1,47 @@ +// 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 "import3dconnectionmanager.h" + +#include <imagecontainer.h> +#include <puppettocreatorcommand.h> + +#include <QImage> + +namespace QmlDesigner { + +Import3dConnectionManager::Import3dConnectionManager() +{ + connections().clear(); // Remove default interactive puppets + connections().emplace_back("Import 3D", "import3dmode"); +} + +void Import3dConnectionManager::setPreviewImageCallback(ImageCallback callback) +{ + m_previewImageCallback = std::move(callback); +} + +void Import3dConnectionManager::dispatchCommand(const QVariant &command, + ConnectionManagerInterface::Connection &connection) +{ + static const int commandType = QMetaType::type("PuppetToCreatorCommand"); + + if (command.typeId() == commandType) { + auto cmd = command.value<PuppetToCreatorCommand>(); + switch (cmd.type()) { + case PuppetToCreatorCommand::Import3DPreviewImage: { + ImageContainer container = qvariant_cast<ImageContainer>(cmd.data()); + QImage image = container.image(); + if (!image.isNull()) + m_previewImageCallback(image); + break; + } + default: + break; + } + } else { + InteractiveConnectionManager::dispatchCommand(command, connection); + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h new file mode 100644 index 0000000000..458d612ad2 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/import3dconnectionmanager.h @@ -0,0 +1,28 @@ +// 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 "interactiveconnectionmanager.h" + +QT_FORWARD_DECLARE_CLASS(QImage) + +namespace QmlDesigner { + +class Import3dConnectionManager : public InteractiveConnectionManager +{ +public: + using ImageCallback = std::function<void(const QImage &)>; + + Import3dConnectionManager(); + + void setPreviewImageCallback(ImageCallback callback); + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + ImageCallback m_previewImageCallback; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp index 29fff4b359..271410233b 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp @@ -4,11 +4,16 @@ #include "itemlibraryassetimportdialog.h" #include "ui_itemlibraryassetimportdialog.h" +#include "import3dcanvas.h" +#include "import3dconnectionmanager.h" + #include <model.h> #include <model/modelutils.h> +#include <nodeinstanceview.h> #include <nodemetainfo.h> #include <qmldesignerconstants.h> #include <qmldesignerplugin.h> +#include <rewriterview.h> #include <variantproperty.h> #include <theme.h> @@ -73,9 +78,10 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog( const QStringList &importFiles, const QString &defaulTargetDirectory, const QVariantMap &supportedExts, const QVariantMap &supportedOpts, const QJsonObject &defaultOpts, const QSet<QString> &preselectedFilesForOverwrite, - QWidget *parent) + AbstractView *view, QWidget *parent) : QDialog(parent) , ui(new Ui::ItemLibraryAssetImportDialog) + , m_view(view) , m_importer(this) , m_preselectedFilesForOverwrite(preselectedFilesForOverwrite) { @@ -106,17 +112,15 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog( if (m_quick3DFiles.size() != importFiles.size()) addWarning("Cannot import 3D and other assets simultaneously. Skipping non-3D assets."); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Import")); - connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, - this, &ItemLibraryAssetImportDialog::onImport); + connect(ui->importButton, &QPushButton::clicked, this, &ItemLibraryAssetImportDialog::onImport); - ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + ui->importButton->setDefault(true); ui->advancedSettingsButton->setStyleSheet( "QPushButton#advancedSettingsButton {background-color: transparent}"); ui->advancedSettingsButton->setStyleSheet( QString("QPushButton { border: none; color :%1 }").arg( - Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_HighlightColor).name())); + Utils::creatorColor(Utils::Theme::QmlDesigner_HighlightColor).name())); QStringList importPaths; auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); @@ -130,43 +134,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; @@ -244,10 +213,14 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog( ui->tabWidget->setCurrentIndex(0); } - connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, + connect(ui->closeButton, &QPushButton::clicked, this, &ItemLibraryAssetImportDialog::onClose); connect(ui->tabWidget, &QTabWidget::currentChanged, this, &ItemLibraryAssetImportDialog::updateUi); + connect(canvas(), &Import3dCanvas::requestImageUpdate, + this, &ItemLibraryAssetImportDialog::onRequestImageUpdate); + connect(canvas(), &Import3dCanvas::requestRotation, + this, &ItemLibraryAssetImportDialog::onRequestRotation); connect(&m_importer, &ItemLibraryAssetImporter::errorReported, this, &ItemLibraryAssetImportDialog::addError); @@ -261,23 +234,35 @@ ItemLibraryAssetImportDialog::ItemLibraryAssetImportDialog( this, &ItemLibraryAssetImportDialog::onImportFinished); connect(&m_importer, &ItemLibraryAssetImporter::progressChanged, this, &ItemLibraryAssetImportDialog::setImportProgress); - - addInfo(tr("Select import options and press \"Import\" to import the following files:")); - for (const auto &file : std::as_const(m_quick3DFiles)) - addInfo(file); + connect(&m_importer, &ItemLibraryAssetImporter::importReadyForPreview, + this, &ItemLibraryAssetImportDialog::onImportReadyForPreview); connect(ui->advancedSettingsButton, &QPushButton::clicked, this, &ItemLibraryAssetImportDialog::toggleAdvanced); QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::updateUi); + + if (m_quick3DFiles.size() != 1) { + addInfo(tr("Select import options and press \"Import\" to import the following files:")); + } else { + addInfo(tr("Importing:")); + QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::onImport); + } + + for (const auto &file : std::as_const(m_quick3DFiles)) + addInfo(file); + + updateImportButtonState(); } ItemLibraryAssetImportDialog::~ItemLibraryAssetImportDialog() { + cleanupPreviewPuppet(); delete ui; } -void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode, +void ItemLibraryAssetImportDialog::updateImport(AbstractView *view, + const ModelNode &updateNode, const QVariantMap &supportedExts, const QVariantMap &supportedOpts) { @@ -294,11 +279,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(); @@ -363,7 +351,8 @@ void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode, {sourceInfo.absoluteFilePath()}, node.model()->fileUrl().toLocalFile(), supportedExts, supportedOpts, options, - preselectedFiles, Core::ICore::dialogParent()); + preselectedFiles, view, + Core::ICore::dialogParent()); importDlg->show(); } else { @@ -512,6 +501,7 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid( QJsonValue value(optCheck->isChecked()); optObj.insert("value", value); m_importOptions[optionsIndex].insert(optKey, optObj); + updateImportButtonState(); }); } else { // Simple options also exist in advanced, so don't connect simple controls directly @@ -519,13 +509,17 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid( auto *advCheck = qobject_cast<QCheckBox *>( m_labelToControlWidgetMaps[optionsIndex].value(optKey)); if (advCheck) { - QObject::connect(optCheck, &QCheckBox::toggled, this, [optCheck, advCheck]() { - if (advCheck->isChecked() != optCheck->isChecked()) + QObject::connect(optCheck, &QCheckBox::toggled, this, [this, optCheck, advCheck]() { + if (advCheck->isChecked() != optCheck->isChecked()) { advCheck->setChecked(optCheck->isChecked()); + updateImportButtonState(); + } }); - QObject::connect(advCheck, &QCheckBox::toggled, this, [optCheck, advCheck]() { - if (advCheck->isChecked() != optCheck->isChecked()) + QObject::connect(advCheck, &QCheckBox::toggled, this, [this, optCheck, advCheck]() { + if (advCheck->isChecked() != optCheck->isChecked()) { optCheck->setChecked(advCheck->isChecked()); + updateImportButtonState(); + } }); } } @@ -561,6 +555,7 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid( QJsonValue value(optSpin->value()); optObj.insert("value", value); m_importOptions[optionsIndex].insert(optKey, optObj); + updateImportButtonState(); }); } else { auto *advSpin = qobject_cast<QDoubleSpinBox *>( @@ -568,14 +563,18 @@ QGridLayout *ItemLibraryAssetImportDialog::createOptionsGrid( if (advSpin) { // Connect corresponding advanced control QObject::connect(optSpin, &QDoubleSpinBox::valueChanged, - this, [optSpin, advSpin] { - if (advSpin->value() != optSpin->value()) + this, [this, optSpin, advSpin] { + if (advSpin->value() != optSpin->value()) { advSpin->setValue(optSpin->value()); + updateImportButtonState(); + } }); QObject::connect(advSpin, &QDoubleSpinBox::valueChanged, - this, [optSpin, advSpin] { - if (advSpin->value() != optSpin->value()) + this, [this, optSpin, advSpin] { + if (advSpin->value() != optSpin->value()) { optSpin->setValue(advSpin->value()); + updateImportButtonState(); + } }); } } @@ -733,8 +732,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(); } } @@ -858,6 +859,145 @@ bool ItemLibraryAssetImportDialog::isHiddenOption(const QString &id) return hiddenOptions.contains(id); } +void ItemLibraryAssetImportDialog::startPreview() +{ + cleanupPreviewPuppet(); + + // Preview is done via custom QML file added into the temporary folder of the preview + QString previewQml = +R"( +import QtQuick +import QtQuick3D + +Rectangle { + id: root + width: %1 + height: %2 + + property alias sceneNode: sceneNode + property alias view3d: view3d + property string extents + property string sceneModelName: "%3" + + gradient: Gradient { + GradientStop { position: 1.0; color: "#222222" } + GradientStop { position: 0.0; color: "#999999" } + } + + View3D { + id: view3d + anchors.fill: parent + camera: viewCamera + + environment: SceneEnvironment { + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.VeryHigh + } + + PerspectiveCamera { + id: viewCamera + x: 600 + y: 600 + z: 600 + eulerRotation.x: -45 + eulerRotation.y: -45 + clipFar: 100000 + clipNear: 10 + } + + DirectionalLight { + rotation: viewCamera.rotation + } + + Node { + id: sceneNode + } + } + + Text { + anchors.bottom: parent.bottom + anchors.left: parent.left + color: "white" + text: root.extents + font.pixelSize: 14 + } +} +)"; + + QSize size = canvas()->size(); + previewQml = previewQml.arg(size.width()).arg(size.height()).arg(m_previewCompName); + + m_previewFile.writeFileContents(previewQml.toUtf8()); + + if (!m_previewFile.exists()) { + addWarning("Failed to write preview file."); + return; + } + + m_connectionManager = new Import3dConnectionManager; + m_rewriterView = new RewriterView{m_view->externalDependencies(), RewriterView::Amend}; + m_nodeInstanceView = new NodeInstanceView{*m_connectionManager, m_view->externalDependencies()}; + +#ifdef QDS_USE_PROJECTSTORAGE + m_model = m_view->model()->createModel("Item"); +#else + m_model = QmlDesigner::Model::create("QtQuick/Item", 2, 1); + m_model->setFileUrl(m_previewFile.toUrl()); +#endif + + auto textDocument = std::make_unique<QTextDocument>(previewQml); + auto modifier = std::make_unique<NotIndentingTextEditModifier>(textDocument.get(), + QTextCursor{textDocument.get()}); + m_rewriterView->setTextModifier(modifier.get()); + m_model->setRewriterView(m_rewriterView); + + if (!m_rewriterView->errors().isEmpty()) { + addWarning("Preview scene creation failed."); + cleanupPreviewPuppet(); + return; + } + + m_nodeInstanceView->setTarget(m_view->nodeInstanceView()->target()); + + auto previewImageCallback = [this](const QImage &image) { + canvas()->updateRenderImage(image); + }; + + auto crashCallback = [&] { + addWarning("Preview process crashed."); + cleanupPreviewPuppet(); + }; + + m_connectionManager->setPreviewImageCallback(std::move(previewImageCallback)); + m_nodeInstanceView->setCrashCallback(std::move(crashCallback)); + + m_model->setNodeInstanceView(m_nodeInstanceView); +} + +void ItemLibraryAssetImportDialog::cleanupPreviewPuppet() +{ + if (m_model) { + m_model->setNodeInstanceView({}); + m_model->setRewriterView({}); + m_model.reset(); + } + + if (m_nodeInstanceView) + m_nodeInstanceView->setCrashCallback({}); + + if (m_connectionManager) + m_connectionManager->setPreviewImageCallback({}); + + delete m_rewriterView; + delete m_nodeInstanceView; + delete m_connectionManager; +} + +Import3dCanvas *ItemLibraryAssetImportDialog::canvas() +{ + return ui->import3dcanvas; +} + void ItemLibraryAssetImportDialog::resizeEvent(QResizeEvent *event) { m_dialogHeight = event->size().height(); @@ -866,8 +1006,13 @@ void ItemLibraryAssetImportDialog::resizeEvent(QResizeEvent *event) void ItemLibraryAssetImportDialog::setCloseButtonState(bool importing) { - ui->buttonBox->button(QDialogButtonBox::Close)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::Close)->setText(importing ? tr("Cancel") : tr("Close")); + ui->closeButton->setEnabled(true); + ui->closeButton->setText(importing ? tr("Cancel") : tr("Close")); +} + +void ItemLibraryAssetImportDialog::updateImportButtonState() +{ + ui->importButton->setText(m_previewOptions == m_importOptions ? tr("Accept") : tr("Import")); } void ItemLibraryAssetImportDialog::addError(const QString &error, const QString &srcPath) @@ -889,14 +1034,25 @@ void ItemLibraryAssetImportDialog::addInfo(const QString &info, const QString &s void ItemLibraryAssetImportDialog::onImport() { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->importButton->setEnabled(false); + + if (!m_previewCompName.isEmpty() && m_previewOptions == m_importOptions) { + cleanupPreviewPuppet(); + m_importer.finalizeQuick3DImport(); + return; + } + setCloseButtonState(true); ui->progressBar->setValue(0); if (!m_quick3DFiles.isEmpty()) { - m_importer.importQuick3D(m_quick3DFiles, m_quick3DImportPath, - m_importOptions, m_extToImportOptionsMap, - m_preselectedFilesForOverwrite); + if (!m_previewCompName.isEmpty()) { + m_importer.reImportQuick3D(m_previewCompName, m_importOptions); + } else { + m_importer.importQuick3D(m_quick3DFiles, m_quick3DImportPath, + m_importOptions, m_extToImportOptionsMap, + m_preselectedFilesForOverwrite); + } } } @@ -910,10 +1066,37 @@ void ItemLibraryAssetImportDialog::setImportProgress(int value, const QString &t ui->progressBar->setValue(value); } +void ItemLibraryAssetImportDialog::onImportReadyForPreview(const QString &path, const QString &compName) +{ + addInfo(tr("Import is ready for preview.")); + if (m_previewCompName.isEmpty()) + addInfo(tr("Click \"Accept\" to finish the import or adjust options and click \"Import\" to import again.")); + + m_previewFile = Utils::FilePath::fromString(path).pathAppended(m_importer.previewFileName()); + m_previewCompName = compName; + m_previewOptions = m_importOptions; + QTimer::singleShot(0, this, &ItemLibraryAssetImportDialog::startPreview); + + ui->importButton->setEnabled(true); + updateImportButtonState(); +} + +void ItemLibraryAssetImportDialog::onRequestImageUpdate() +{ + if (m_nodeInstanceView) + m_nodeInstanceView->view3DAction(View3DActionType::Import3dUpdatePreviewImage, canvas()->size()); +} + +void ItemLibraryAssetImportDialog::onRequestRotation(const QPointF &delta) +{ + if (m_nodeInstanceView) + m_nodeInstanceView->view3DAction(View3DActionType::Import3dRotatePreviewModel, delta); +} + void ItemLibraryAssetImportDialog::onImportNearlyFinished() { // Canceling import is no longer doable - ui->buttonBox->button(QDialogButtonBox::Close)->setEnabled(false); + ui->closeButton->setEnabled(false); } void ItemLibraryAssetImportDialog::onImportFinished() @@ -923,19 +1106,28 @@ void ItemLibraryAssetImportDialog::onImportFinished() QString interruptStr = tr("Import interrupted."); addError(interruptStr); setImportProgress(0, interruptStr); + if (m_explicitClose) + QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::doClose); } else { QString doneStr = tr("Import done."); addInfo(doneStr); setImportProgress(100, doneStr); if (m_closeOnFinish) { // Add small delay to allow user to visually confirm import finishing - QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::onClose); + QTimer::singleShot(1000, this, &ItemLibraryAssetImportDialog::doClose); } } } void ItemLibraryAssetImportDialog::onClose() { + ui->importButton->setEnabled(false); + m_explicitClose = true; + doClose(); +} + +void ItemLibraryAssetImportDialog::doClose() +{ if (m_importer.isImporting()) { addInfo(tr("Canceling import.")); m_importer.cancelImport(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h index c5da478232..e7c4933056 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.h @@ -3,14 +3,19 @@ #pragma once #include "itemlibraryassetimporter.h" -#include "modelnode.h" + +#include <modelnode.h> + +#include <utils/filepath.h> #include <QDialog> #include <QJsonObject> +#include <QPointer> #include <QSet> QT_BEGIN_NAMESPACE class QGridLayout; +class QPushButton; QT_END_NAMESPACE namespace Utils { @@ -19,6 +24,10 @@ class OutputFormatter; namespace QmlDesigner { class ItemLibraryAssetImporter; +class Import3dCanvas; +class Import3dConnectionManager; +class NodeInstanceView; +class RewriterView; namespace Ui { class ItemLibraryAssetImportDialog; @@ -35,10 +44,12 @@ public: const QVariantMap &supportedOpts, const QJsonObject &defaultOpts, const QSet<QString> &preselectedFilesForOverwrite, + AbstractView *view, QWidget *parent = nullptr); ~ItemLibraryAssetImportDialog(); - static void updateImport(const ModelNode &updateNode, + static void updateImport(AbstractView *view, + const ModelNode &updateNode, const QVariantMap &supportedExts, const QVariantMap &supportedOpts); @@ -52,12 +63,17 @@ private slots: private: void setCloseButtonState(bool importing); + void updateImportButtonState(); void onImport(); void setImportProgress(int value, const QString &text); + void onImportReadyForPreview(const QString &path, const QString &compName); + void onRequestImageUpdate(); + void onRequestRotation(const QPointF &delta); void onImportNearlyFinished(); void onImportFinished(); void onClose(); + void doClose(); void toggleAdvanced(); void createTab(const QString &tabLabel, int optionsIndex, const QJsonObject &groups); @@ -69,8 +85,19 @@ private: bool isSimpleOption(const QString &id); bool isHiddenOption(const QString &id); + void startPreview(); + void cleanupPreviewPuppet(); + Import3dCanvas *canvas(); + Ui::ItemLibraryAssetImportDialog *ui = nullptr; Utils::OutputFormatter *m_outputFormatter = nullptr; + QPointer<Import3dConnectionManager> m_connectionManager; + QPointer<NodeInstanceView> m_nodeInstanceView; + QPointer<RewriterView> m_rewriterView; + QPointer<AbstractView> m_view; + ModelPointer m_model; + Utils::FilePath m_previewFile; + QString m_previewCompName; struct OptionsData { @@ -83,6 +110,7 @@ private: QString m_quick3DImportPath; ItemLibraryAssetImporter m_importer; QVector<QJsonObject> m_importOptions; + QVector<QJsonObject> m_previewOptions; QHash<QString, int> m_extToImportOptionsMap; QSet<QString> m_preselectedFilesForOverwrite; bool m_closeOnFinish = true; @@ -91,5 +119,6 @@ private: OptionsData m_advancedData; bool m_advancedMode = false; int m_dialogHeight = 350; + bool m_explicitClose = false; }; } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui index 081bc36a3d..e0b9d925fc 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.ui @@ -6,15 +6,15 @@ <rect> <x>0</x> <y>0</y> - <width>630</width> + <width>1100</width> <height>350</height> </rect> </property> <property name="windowTitle"> <string>Asset Import</string> </property> - <layout class="QFormLayout" name="formLayout"> - <item row="0" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QTabWidget" name="tabWidget"> @@ -24,6 +24,12 @@ <verstretch>2</verstretch> </sizepolicy> </property> + <property name="minimumSize"> + <size> + <width>550</width> + <height>0</height> + </size> + </property> <property name="currentIndex"> <number>0</number> </property> @@ -98,6 +104,12 @@ <verstretch>0</verstretch> </sizepolicy> </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> <property name="text"> <string/> </property> @@ -111,16 +123,64 @@ </widget> </item> <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="standardButtons"> - <set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="closeButton"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="importButton"> + <property name="text"> + <string>Import</string> + </property> + </widget> + </item> + </layout> </item> </layout> </item> + <item> + <widget class="Import3dCanvas" name="import3dcanvas" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>300</height> + </size> + </property> + </widget> + </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>Import3dCanvas</class> + <extends>QWidget</extends> + <header>import3dcanvas.h</header> + <container>1</container> + </customwidget> + </customwidgets> <resources/> <connections/> </ui> diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 48958ceec9..010d00a970 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" @@ -20,6 +21,7 @@ #include <utils/algorithm.h> #include <utils/async.h> +#include <utils/filepath.h> #include <utils/qtcassert.h> #include <QApplication> @@ -57,8 +59,6 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles, const QHash<QString, int> &extToImportOptionsMap, const QSet<QString> &preselectedFilesForOverwrite) { - if (m_isImporting) - cancelImport(); reset(); m_isImporting = true; @@ -91,6 +91,53 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles, } } +void ItemLibraryAssetImporter::reImportQuick3D(const QString &assetName, + const QVector<QJsonObject> &options) +{ + if (!assetName.isEmpty() && !m_parseData.contains(assetName)) { + addError(tr("Attempted to reimport non-existing asset: %1").arg(assetName)); + return; + } + + ParseData &pd = m_parseData[assetName]; + // Change outDir just in case reimport generates different files + QDir oldDir = pd.outDir; + QString assetFolder = generateAssetFolderName(pd.assetName); + pd.outDir.cdUp(); + pd.outDir.mkpath(assetFolder); + + if (!pd.outDir.cd(assetFolder)) { + addError(tr("Could not access temporary asset directory: \"%1\".") + .arg(pd.outDir.filePath(assetFolder))); + return; + } + + if (oldDir.absolutePath().contains(tempDirNameBase())) + oldDir.removeRecursively(); + + m_isImporting = false; + m_cancelled = false; + + m_puppetProcess.reset(); + m_requiredImports.clear(); + m_currentImportId = 0; + m_puppetQueue.clear(); + + for (ParseData &pd : m_parseData) + pd.importId = -1; + + pd.options = options[pd.optionsIndex]; + pd.importId = 1; + + m_importFiles.remove(assetName); + + m_importIdToAssetNameMap.clear(); + m_importIdToAssetNameMap[pd.importId] = assetName; + + m_puppetQueue.append(pd.importId); + startNextImportProcess(); +} + bool ItemLibraryAssetImporter::isImporting() const { return m_isImporting; @@ -103,19 +150,19 @@ void ItemLibraryAssetImporter::cancelImport() notifyFinished(); } -void ItemLibraryAssetImporter::addError(const QString &errMsg, const QString &srcPath) const +void ItemLibraryAssetImporter::addError(const QString &errMsg, const QString &srcPath) { qCDebug(importerLog) << "Error: "<< errMsg << srcPath; emit errorReported(errMsg, srcPath); } -void ItemLibraryAssetImporter::addWarning(const QString &warningMsg, const QString &srcPath) const +void ItemLibraryAssetImporter::addWarning(const QString &warningMsg, const QString &srcPath) { qCDebug(importerLog) << "Warning: " << warningMsg << srcPath; emit warningReported(warningMsg, srcPath); } -void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &srcPath) const +void ItemLibraryAssetImporter::addInfo(const QString &infoMsg, const QString &srcPath) { qCDebug(importerLog) << "Info: " << infoMsg << srcPath; emit infoReported(infoMsg, srcPath); @@ -126,8 +173,8 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo { m_puppetProcess.reset(); - if (m_parseData.contains(m_currentImportId)) { - const ParseData &pd = m_parseData[m_currentImportId]; + if (m_importIdToAssetNameMap.contains(m_currentImportId)) { + const ParseData &pd = m_parseData[m_importIdToAssetNameMap[m_currentImportId]]; QString errStr; if (exitStatus == QProcess::ExitStatus::CrashExit) { errStr = tr("Import process crashed."); @@ -150,11 +197,12 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo addError(tr("Asset import process failed: \"%1\".") .arg(pd.sourceInfo.absoluteFilePath())); addError(errStr); - m_parseData.remove(m_currentImportId); + m_parseData.remove(m_importIdToAssetNameMap[m_currentImportId]); + m_importIdToAssetNameMap.remove(m_currentImportId); } } - int finishedCount = m_parseData.size() - m_puppetQueue.size(); + int finishedCount = m_importIdToAssetNameMap.size() - m_puppetQueue.size(); if (!m_puppetQueue.isEmpty()) startNextImportProcess(); @@ -162,7 +210,7 @@ void ItemLibraryAssetImporter::importProcessFinished([[maybe_unused]] int exitCo notifyProgress(100); QTimer::singleShot(0, this, &ItemLibraryAssetImporter::postImport); } else { - notifyProgress(int(100. * (double(finishedCount) / double(m_parseData.size())))); + notifyProgress(int(100. * (double(finishedCount) / double(m_importIdToAssetNameMap.size())))); } } @@ -178,7 +226,7 @@ void ItemLibraryAssetImporter::reset() m_cancelled = false; delete m_tempDir; - m_tempDir = new QTemporaryDir; + m_tempDir = new QTemporaryDir(QDir::tempPath() + tempDirNameBase()); m_importFiles.clear(); m_overwrittenImports.clear(); m_puppetProcess.reset(); @@ -186,6 +234,7 @@ void ItemLibraryAssetImporter::reset() m_requiredImports.clear(); m_currentImportId = 0; m_puppetQueue.clear(); + m_importIdToAssetNameMap.clear(); } void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths, @@ -207,9 +256,11 @@ void ItemLibraryAssetImporter::parseFiles(const QStringList &filePaths, int index = extToImportOptionsMap.value(QFileInfo(file).suffix()); ParseData pd; pd.options = options[index]; + pd.optionsIndex = index; if (preParseQuick3DAsset(file, pd, preselectedFilesForOverwrite)) { pd.importId = ++m_importIdCounter; - m_parseData.insert(pd.importId, pd); + m_importIdToAssetNameMap[pd.importId] = pd.assetName; + m_parseData.insert(pd.assetName, pd); } notifyProgress(qRound(++count * quota), progressTitle); } @@ -238,7 +289,7 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa pd.targetDirPath = pd.targetDir.filePath(pd.assetName); - if (pd.outDir.exists(pd.assetName)) { + if (m_parseData.contains(pd.assetName)) { addWarning(tr("Skipped import of duplicate asset: \"%1\".").arg(pd.assetName)); return false; } @@ -287,11 +338,12 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa } } - pd.outDir.mkpath(pd.assetName); + QString assetFolder = generateAssetFolderName(pd.assetName); + pd.outDir.mkpath(assetFolder); - if (!pd.outDir.cd(pd.assetName)) { + if (!pd.outDir.cd(assetFolder)) { addError(tr("Could not access temporary asset directory: \"%1\".") - .arg(pd.outDir.filePath(pd.assetName))); + .arg(pd.outDir.filePath(assetFolder))); return false; } return true; @@ -329,12 +381,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()); @@ -421,7 +476,7 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(ParseData &pd) // Copy the original asset into a subdirectory assetFiles.insert(sourcePath, sourceSceneTargetFilePath(pd)); - m_importFiles.insert(assetFiles); + m_importFiles.insert(pd.assetName, assetFiles); } void ItemLibraryAssetImporter::copyImportedFiles() @@ -496,6 +551,12 @@ void ItemLibraryAssetImporter::keepUiAlive() const QApplication::processEvents(); } +QString ItemLibraryAssetImporter::generateAssetFolderName(const QString &assetName) const +{ + static int counter = 0; + return assetName + "_QDS_" + QString::number(counter++); +} + ItemLibraryAssetImporter::OverwriteResult ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName) { const QString title = tr("Overwrite Existing Asset?"); @@ -530,7 +591,8 @@ void ItemLibraryAssetImporter::startNextImportProcess() if (model && view) { bool done = false; while (!m_puppetQueue.isEmpty() && !done) { - const ParseData pd = m_parseData.value(m_puppetQueue.takeLast()); + const ParseData pd = m_parseData.value( + m_importIdToAssetNameMap.value(m_puppetQueue.takeLast())); QStringList puppetArgs; QJsonDocument optDoc(pd.options); @@ -553,7 +615,8 @@ void ItemLibraryAssetImporter::startNextImportProcess() } else { addError(tr("Failed to start import 3D asset process."), pd.sourceInfo.absoluteFilePath()); - m_parseData.remove(pd.importId); + const QString assetName = m_importIdToAssetNameMap.take(pd.importId); + m_parseData.remove(assetName); m_puppetProcess.reset(); } } @@ -569,8 +632,16 @@ void ItemLibraryAssetImporter::postImport() postParseQuick3DAsset(pd); } - if (!isCancelled()) - finalizeQuick3DImport(); + if (!isCancelled()) { + // TODO: Currently we only support import preview for single imports + if (m_parseData.size() != 1) { + finalizeQuick3DImport(); + } else { + const ParseData &pd = m_parseData[m_parseData.keys().first()]; + const QString importedComponentName = pd.assetName; + emit importReadyForPreview(pd.outDir.absolutePath(), importedComponentName); + } + } } void ItemLibraryAssetImporter::finalizeQuick3DImport() diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h index 8ad7b5a2de..abe9690951 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #pragma once -#include "import.h" - #include <qprocessuniqueptr.h> #include <QSet> @@ -35,20 +33,28 @@ public: const QHash<QString, int> &extToImportOptionsMap, const QSet<QString> &preselectedFilesForOverwrite); + void reImportQuick3D(const QString &assetName, const QVector<QJsonObject> &options); + bool isImporting() const; void cancelImport(); bool isCancelled() const; - void addError(const QString &errMsg, const QString &srcPath = {}) const; - void addWarning(const QString &warningMsg, const QString &srcPath = {}) const; - void addInfo(const QString &infoMsg, const QString &srcPath = {}) const; + void addError(const QString &errMsg, const QString &srcPath = {}); + void addWarning(const QString &warningMsg, const QString &srcPath = {}); + void addInfo(const QString &infoMsg, const QString &srcPath = {}); + + QString previewFileName() const { return "QDSImport3dPreviewScene.qml"; } + QString tempDirNameBase() const { return "/qds3dimport"; } + + void finalizeQuick3DImport(); signals: - void errorReported(const QString &, const QString &) const; - void warningReported(const QString &, const QString &) const; - void infoReported(const QString &, const QString &) const; - void progressChanged(int value, const QString &text) const; - void importNearlyFinished() const; + void errorReported(const QString &, const QString &); + void warningReported(const QString &, const QString &); + void infoReported(const QString &, const QString &); + void progressChanged(int value, const QString &text); + void importReadyForPreview(const QString &path, const QString &compName); + void importNearlyFinished(); void importFinished(); private slots: @@ -63,7 +69,8 @@ private: QFileInfo sourceInfo; QString assetName; QString originalAssetName; - int importId; + int importId = -1; + int optionsIndex = -1; }; void notifyFinished(); @@ -79,6 +86,7 @@ private: void notifyProgress(int value, const QString &text); void notifyProgress(int value); void keepUiAlive() const; + QString generateAssetFolderName(const QString &assetName) const; enum class OverwriteResult { Skip, @@ -89,10 +97,9 @@ private: OverwriteResult confirmAssetOverwrite(const QString &assetName); void startNextImportProcess(); void postImport(); - void finalizeQuick3DImport(); QString sourceSceneTargetFilePath(const ParseData &pd); - QSet<QHash<QString, QString>> m_importFiles; + QHash<QString, QHash<QString, QString>> m_importFiles; // Key: asset name QHash<QString, QStringList> m_overwrittenImports; bool m_isImporting = false; bool m_cancelled = false; @@ -101,7 +108,8 @@ private: QProcessUniquePointer m_puppetProcess; int m_importIdCounter = 0; int m_currentImportId = 0; - QHash<int, ParseData> m_parseData; + QHash<int, QString> m_importIdToAssetNameMap; + QHash<QString, ParseData> m_parseData; // Key: asset name QString m_progressTitle; QStringList m_requiredImports; QList<int> m_puppetQueue; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index dbeacc7595..8ef9512e26 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,46 +313,54 @@ void ItemLibraryModel::update([[maybe_unused]] ItemLibraryInfo *itemLibraryInfo, beginResetModel(); clearSections(); + const QString projectName = DocumentManager::currentProjectName(); + + auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils(); + QStringList excludedImports { - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1) + ".MaterialBundle", - QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1) + ".EffectBundle" + projectName, + compUtils.materialsBundleType(), + compUtils.effectsBundleType(), + compUtils.userMaterialsBundleType(), + compUtils.user3DBundleType(), + compUtils.userEffectsBundleType() }; // create import sections - const QString projectName = DocumentManager::currentProjectName(); const Imports usedImports = model->usedImports(); QHash<QString, ItemLibraryImport *> importHash; for (const Import &import : model->imports()) { - if (import.url() != projectName) { - if (excludedImports.contains(import.url()) || import.url().startsWith("Effects.")) - continue; - bool addNew = true; - bool isQuick3DAsset = import.url().startsWith("Quick3DAssets."); - QString importUrl = import.url(); - if (isQuick3DAsset) - importUrl = ItemLibraryImport::quick3DAssetsTitle(); - else if (import.isFileImport()) - importUrl = import.toString(true, true).remove("\""); - - ItemLibraryImport *oldImport = importHash.value(importUrl); - if (oldImport && oldImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets - && isQuick3DAsset) { - addNew = false; // add only 1 Quick3DAssets import section - } else if (oldImport && oldImport->importEntry().url() == import.url()) { - // Retain the higher version if multiples exist - if (oldImport->importEntry().toVersion() >= import.toVersion() || import.hasVersion()) - addNew = false; - else - delete oldImport; - } + if (excludedImports.contains(import.url()) + || import.url().startsWith(compUtils.composedEffectsTypePrefix())) { + continue; + } - if (addNew) { - auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets - : ItemLibraryImport::SectionType::Default; - ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType); - itemLibImport->setImportUsed(usedImports.contains(import)); - importHash.insert(importUrl, itemLibImport); - } + bool addNew = true; + bool isQuick3DAsset = import.url().startsWith(compUtils.import3dTypePrefix()); + QString importUrl = import.url(); + if (isQuick3DAsset) + importUrl = ItemLibraryImport::quick3DAssetsTitle(); + else if (import.isFileImport()) + importUrl = import.toString(true, true).remove("\""); + + ItemLibraryImport *oldImport = importHash.value(importUrl); + if (oldImport && oldImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets + && isQuick3DAsset) { + addNew = false; // add only 1 Quick3DAssets import section + } else if (oldImport && oldImport->importEntry().url() == import.url()) { + // Retain the higher version if multiples exist + if (oldImport->importEntry().toVersion() >= import.toVersion() || import.hasVersion()) + addNew = false; + else + delete oldImport; + } + + if (addNew) { + auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets + : ItemLibraryImport::SectionType::Default; + ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType); + itemLibImport->setImportUsed(usedImports.contains(import)); + importHash.insert(importUrl, itemLibImport); } } @@ -367,7 +376,7 @@ void ItemLibraryModel::update([[maybe_unused]] ItemLibraryInfo *itemLibraryInfo, NodeMetaInfo metaInfo; if constexpr (useProjectStorage()) - metaInfo = entry.metaInfo(); + metaInfo = NodeMetaInfo{entry.typeId(), model->projectStorage()}; else metaInfo = model->metaInfo(entry.typeName()); @@ -379,7 +388,8 @@ void ItemLibraryModel::update([[maybe_unused]] ItemLibraryInfo *itemLibraryInfo, || metaInfo.majorVersion() < 0); #endif bool isItem = valid && metaInfo.isQtQuickItem(); - bool forceVisibility = valid && NodeHints::fromItemLibraryEntry(entry).visibleInLibrary(); + bool forceVisibility = valid + && NodeHints::fromItemLibraryEntry(entry, model).visibleInLibrary(); if (m_flowMode) { isItem = metaInfo.isFlowViewItem(); 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/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index feff523b9c..e4f4ffcd9c 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -164,7 +164,7 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap) auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir, m_importableExtensions3DMap, m_importOptions3DMap, {}, {}, - Core::ICore::dialogParent()); + this, Core::ICore::dialogParent()); int result = importDlg->exec(); return result == QDialog::Accepted ? AddFilesResult::succeeded() : AddFilesResult::cancelled(); @@ -198,7 +198,7 @@ void ItemLibraryView::customNotification(const AbstractView *view, const QString const QList<ModelNode> &nodeList, const QList<QVariant> &data) { if (identifier == "UpdateImported3DAsset" && nodeList.size() > 0) { - ItemLibraryAssetImportDialog::updateImport(nodeList[0], + ItemLibraryAssetImportDialog::updateImport(this, nodeList[0], m_importableExtensions3DMap, m_importOptions3DMap); 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/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp index b6009edc77..f97b6f74ac 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp @@ -29,10 +29,10 @@ public: QVariant maybeConvertToNumber(const QVariant &value) { - if (value.typeId() == QVariant::Bool) + if (value.typeId() == QMetaType::Bool) return value; - if (value.typeId() == QVariant::String) { + if (value.typeId() == QMetaType::QString) { const QString text = value.toString(); if (text == "true") return QVariant(true); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index d36e78512b..b74f310741 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -366,6 +366,9 @@ void MaterialBrowserModel::selectMaterial(int idx, bool force) if (idx != m_selectedIndex || force) { m_selectedIndex = idx; emit selectedIndexChanged(idx); + + m_selectedMaterialIsComponent = selectedMaterial().isComponent(); + emit selectedMaterialIsComponentChanged(); } } @@ -434,22 +437,20 @@ void MaterialBrowserModel::copyMaterialProperties(int idx, const QString §io QJsonObject propsSpecObj = m_propertyGroupsObj.value(m_copiedMaterialType).toObject(); if (propsSpecObj.contains(section)) { // should always be true const QJsonArray propNames = propsSpecObj.value(section).toArray(); - // auto == QJsonValueConstRef after 04dc959d49e5e3 / Qt 6.4, QJsonValueRef before - for (const auto &propName : propNames) + for (const QJsonValueConstRef &propName : propNames) copiedProps.append(propName.toString().toLatin1()); if (section == "Base") { // add QtQuick3D.Material base props as well QJsonObject propsMatObj = m_propertyGroupsObj.value("Material").toObject(); const QJsonArray propNames = propsMatObj.value("Base").toArray(); - // auto == QJsonValueConstRef after 04dc959d49e5e3 / Qt 6.4, QJsonValueRef before - for (const auto &propName : propNames) + for (const QJsonValueConstRef &propName : propNames) copiedProps.append(propName.toString().toLatin1()); } } } m_copiedMaterialProps.clear(); - for (const auto &propName : copiedProps) { + for (const PropertyName &propName : copiedProps) { PropertyCopyData data; data.name = propName; data.isValid = m_allPropsCopied || validProps.contains(propName); diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index 337dce0550..3b6b64ec86 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -3,7 +3,7 @@ #pragma once -#include "modelnode.h" +#include <modelnode.h> #include <QAbstractListModel> #include <QJsonObject> @@ -20,6 +20,7 @@ class MaterialBrowserModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(int selectedIndex MEMBER m_selectedIndex NOTIFY selectedIndexChanged) + Q_PROPERTY(bool selectedMaterialIsComponent MEMBER m_selectedMaterialIsComponent NOTIFY selectedMaterialIsComponentChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasModelSelection READ hasModelSelection WRITE setHasModelSelection NOTIFY hasModelSelectionChanged) Q_PROPERTY(bool hasMaterialLibrary READ hasMaterialLibrary WRITE setHasMaterialLibrary NOTIFY hasMaterialLibraryChanged) @@ -110,6 +111,7 @@ signals: const QList<QmlDesigner::MaterialBrowserModel::PropertyCopyData> &props, bool all); void isQt6ProjectChanged(); + void selectedMaterialIsComponentChanged(); private: bool isValidIndex(int idx) const; @@ -132,6 +134,7 @@ private: bool m_hasMaterialLibrary = false; bool m_allPropsCopied = true; bool m_isQt6Project = false; + bool m_selectedMaterialIsComponent = false; QString m_copiedMaterialType; QPointer<MaterialBrowserView> m_view; 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/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 8bd5761728..7d90dffffc 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -446,8 +446,13 @@ void QmlDesigner::MaterialBrowserView::loadPropertyGroups() if (!m_hasQuick3DImport || m_propertyGroupsLoaded || !model()) return; +#ifdef QDS_USE_PROJECTSTORAGE + // TODO + QString matPropsPath; +#else QString matPropsPath = model()->metaInfo("QtQuick3D.Material").importDirectoryPath() + "/designer/propertyGroups.json"; +#endif m_propertyGroupsLoaded = m_widget->materialBrowserModel()->loadPropertyGroups(matPropsPath); } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp index 47a1e8d293..8723611be0 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(); @@ -143,7 +150,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache, : m_materialBrowserView(view) , m_materialBrowserModel(new MaterialBrowserModel(view, this)) , m_materialBrowserTexturesModel(new MaterialBrowserTexturesModel(view, this)) - , m_quickWidget(new StudioQuickWidget(this)) + , m_quickWidget(Utils::makeUniqueObjectPtr<StudioQuickWidget>(this)) , m_previewImageProvider(new PreviewImageProvider()) { QImage defaultImage; @@ -172,7 +179,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(AsynchronousImageCache &imageCache, auto layout = new QVBoxLayout(this); layout->setContentsMargins({}); layout->setSpacing(0); - layout->addWidget(m_quickWidget.data()); + layout->addWidget(m_quickWidget.get()); updateSearch(); @@ -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 @@ -397,7 +411,7 @@ void MaterialBrowserWidget::setIsDragging(bool val) StudioQuickWidget *MaterialBrowserWidget::quickWidget() const { - return m_quickWidget.data(); + return m_quickWidget.get(); } void MaterialBrowserWidget::clearPreviewCache() diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h index bfe7ace34d..6506283f85 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h @@ -7,6 +7,8 @@ #include <coreplugin/icontext.h> +#include <utils/uniqueobjectptr.h> + #include <QFrame> QT_BEGIN_NAMESPACE @@ -60,6 +62,7 @@ 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(); StudioQuickWidget *quickWidget() const; @@ -83,7 +86,7 @@ private: QPointer<MaterialBrowserView> m_materialBrowserView; QPointer<MaterialBrowserModel> m_materialBrowserModel; QPointer<MaterialBrowserTexturesModel> m_materialBrowserTexturesModel; - QScopedPointer<StudioQuickWidget> m_quickWidget; + Utils::UniqueObjectPtr<StudioQuickWidget> m_quickWidget; QShortcut *m_qmlSourceUpdateShortcut = nullptr; PreviewImageProvider *m_previewImageProvider = nullptr; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp index 664006fb7a..f583498db7 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorcontextobject.cpp @@ -46,9 +46,9 @@ QString MaterialEditorContextObject::convertColorToString(const QVariant &color) { QString colorString; QColor theColor; - if (color.canConvert(QVariant::Color)) { + if (color.canConvert(QMetaType(QMetaType::QColor))) { theColor = color.value<QColor>(); - } else if (color.canConvert(QVariant::Vector3D)) { + } else if (color.canConvert(QMetaType(QMetaType::QVector3D))) { auto vec = color.value<QVector3D>(); theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z()); } diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp index ecc460ae51..0e508f8f36 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.cpp @@ -80,8 +80,8 @@ public: MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialEditor) : m_quickWidget(Utils::makeUniqueObjectPtr<QQuickWidget>()) - , m_materialEditorTransaction(new MaterialEditorTransaction(materialEditor)) - , m_contextObject(new MaterialEditorContextObject(m_quickWidget.get())) + , m_materialEditorTransaction(std::make_unique<MaterialEditorTransaction>(materialEditor)) + , m_contextObject(std::make_unique<MaterialEditorContextObject>(m_quickWidget.get())) , m_materialEditorImageProvider(new MaterialEditorImageProvider()) { m_quickWidget->setObjectName(Constants::OBJECT_NAME_MATERIAL_EDITOR); @@ -90,7 +90,7 @@ MaterialEditorQmlBackend::MaterialEditorQmlBackend(MaterialEditorView *materialE m_quickWidget->engine()->addImageProvider("materialEditor", m_materialEditorImageProvider); m_contextObject->setBackendValues(&m_backendValuesPropertyMap); m_contextObject->setModel(materialEditor->model()); - context()->setContextObject(m_contextObject.data()); + context()->setContextObject(m_contextObject.get()); QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged, materialEditor, &MaterialEditorView::changeValue); @@ -193,7 +193,7 @@ QQmlContext *MaterialEditorQmlBackend::context() const MaterialEditorContextObject *MaterialEditorQmlBackend::contextObject() const { - return m_contextObject.data(); + return m_contextObject.get(); } QQuickWidget *MaterialEditorQmlBackend::widget() const @@ -224,7 +224,7 @@ DesignerPropertyMap &MaterialEditorQmlBackend::backendValuesPropertyMap() MaterialEditorTransaction *MaterialEditorQmlBackend::materialEditorTransaction() const { - return m_materialEditorTransaction.data(); + return m_materialEditorTransaction.get(); } PropertyEditorValue *MaterialEditorQmlBackend::propertyValueForName(const QString &propertyName) @@ -267,12 +267,9 @@ void MaterialEditorQmlBackend::setup(const QmlObjectNode &selectedMaterialNode, // anchors m_backendAnchorBinding.setup(selectedMaterialNode.modelNode()); - context()->setContextProperties( - QVector<QQmlContext::PropertyPair>{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"transaction"}, QVariant::fromValue(m_materialEditorTransaction.data())} - } - ); + context()->setContextProperties(QVector<QQmlContext::PropertyPair>{ + {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, + {{"transaction"}, QVariant::fromValue(m_materialEditorTransaction.get())}}); contextObject()->setSpecificsUrl(qmlSpecificsFile); contextObject()->setStateName(stateName); diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h index 6f9d6014e9..9fd5fc2399 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorqmlbackend.h @@ -11,6 +11,8 @@ #include <nodemetainfo.h> +#include <memory> + class PropertyEditorValue; QT_BEGIN_NAMESPACE @@ -60,12 +62,15 @@ private: MaterialEditorView *materialEditor); PropertyName auxNamePostFix(const PropertyName &propertyName); + // to avoid a crash while destructing DesignerPropertyMap in the QQmlData + // this needs be destructed after m_quickWidget->engine() is destructed + DesignerPropertyMap m_backendValuesPropertyMap; + Utils::UniqueObjectPtr<QQuickWidget> m_quickWidget = nullptr; QmlAnchorBindingProxy m_backendAnchorBinding; QmlModelNodeProxy m_backendModelNode; - DesignerPropertyMap m_backendValuesPropertyMap; - QScopedPointer<MaterialEditorTransaction> m_materialEditorTransaction; - QScopedPointer<MaterialEditorContextObject> m_contextObject; + std::unique_ptr<MaterialEditorTransaction> m_materialEditorTransaction; + std::unique_ptr<MaterialEditorContextObject> m_contextObject; QPointer<MaterialEditorImageProvider> m_materialEditorImageProvider; }; diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp index e083310cdb..21114267cb 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.cpp @@ -24,7 +24,6 @@ #include "qmldesignerplugin.h" #include "qmltimeline.h" #include "variantproperty.h" -#include <itemlibraryentry.h> #include <utils3d.h> #include <coreplugin/icore.h> @@ -63,10 +62,6 @@ MaterialEditorView::MaterialEditorView(ExternalDependenciesInterface &externalDe } }); - m_typeUpdateTimer.setSingleShot(true); - m_typeUpdateTimer.setInterval(500); - connect(&m_typeUpdateTimer, &QTimer::timeout, this, &MaterialEditorView::updatePossibleTypes); - QmlDesignerPlugin::trackWidgetFocusTime(m_stackedWidget, Constants::EVENT_MATERIALEDITOR_TIME); MaterialEditorDynamicPropertiesProxyModel::registerDeclarativeType(); @@ -333,8 +328,10 @@ void MaterialEditorView::resetView() setupQmlBackend(); - if (m_qmlBackEnd) + if (m_qmlBackEnd) { m_qmlBackEnd->emitSelectionChanged(); + updatePossibleTypes(); + } QTimer::singleShot(0, this, &MaterialEditorView::requestPreviewRender); @@ -605,7 +602,6 @@ void MaterialEditorView::setupQmlBackend() else m_dynamicPropertiesModel->reset(); - delayedTypeUpdate(); initPreviewData(); m_stackedWidget->setCurrentWidget(m_qmlBackEnd->widget()); @@ -690,21 +686,6 @@ void MaterialEditorView::initPreviewData() } } -void MaterialEditorView::delayedTypeUpdate() -{ - m_typeUpdateTimer.start(); -} - -[[maybe_unused]] static Import entryToImport(const ItemLibraryEntry &entry) -{ - if (entry.majorVersion() == -1 && entry.minorVersion() == -1) - return Import::createFileImport(entry.requiredImport()); - - return Import::createLibraryImport(entry.requiredImport(), - QString::number(entry.majorVersion()) + QLatin1Char('.') + - QString::number(entry.minorVersion())); -} - void MaterialEditorView::updatePossibleTypes() { QTC_ASSERT(model(), return); @@ -712,49 +693,21 @@ void MaterialEditorView::updatePossibleTypes() if (!m_qmlBackEnd) return; -#ifdef QDS_USE_PROJECTSTORAGE - auto heirs = model()->qtQuick3DMaterialMetaInfo().heirs(); - heirs.push_back(model()->qtQuick3DMaterialMetaInfo()); - auto entries = Utils::transform<ItemLibraryEntries>(heirs, [&](const auto &heir) { - return toItemLibraryEntries(heir.itemLibrariesEntries(), *model()->projectStorage()); - }); + static const QStringList basicTypes { + "CustomMaterial", + "DefaultMaterial", + "PrincipledMaterial", + "SpecularGlossyMaterial" + }; - // I am unsure about the code intention here -#else // Ensure basic types are always first - QStringList nonQuick3dTypes; - QStringList allTypes; - - const QList<ItemLibraryEntry> itemLibEntries = m_itemLibraryInfo->entries(); - for (const ItemLibraryEntry &entry : itemLibEntries) { - NodeMetaInfo metaInfo = model()->metaInfo(entry.typeName()); - bool valid = metaInfo.isValid() - && (metaInfo.majorVersion() >= entry.majorVersion() - || metaInfo.majorVersion() < 0); - if (valid && metaInfo.isQtQuick3DMaterial()) { - bool addImport = entry.requiredImport().isEmpty(); - if (!addImport) { - Import import = entryToImport(entry); - addImport = model()->hasImport(import, true, true); - } - if (addImport) { - const QList<QByteArray> typeSplit = entry.typeName().split('.'); - const QString typeName = QString::fromLatin1(typeSplit.last()); - if (typeSplit.size() == 2 && typeSplit.first() == "QtQuick3D") { - if (!allTypes.contains(typeName)) - allTypes.append(typeName); - } else if (!nonQuick3dTypes.contains(typeName)) { - nonQuick3dTypes.append(typeName); - } - } - } - } + const QString matType = m_selectedMaterial.simplifiedTypeName(); - allTypes.sort(); - nonQuick3dTypes.sort(); - allTypes.append(nonQuick3dTypes); + if (basicTypes.contains(matType)) { + m_qmlBackEnd->contextObject()->setPossibleTypes(basicTypes); + return; + } - m_qmlBackEnd->contextObject()->setPossibleTypes(allTypes); -#endif + m_qmlBackEnd->contextObject()->setPossibleTypes({matType}); } void MaterialEditorView::modelAttached(Model *model) @@ -774,20 +727,6 @@ void MaterialEditorView::modelAttached(Model *model) m_ensureMatLibTimer.start(500); } -#ifndef QDS_USE_PROJECTSTORAGE - if (m_itemLibraryInfo.data() != model->metaInfo().itemLibraryInfo()) { - if (m_itemLibraryInfo) { - disconnect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged, - this, &MaterialEditorView::delayedTypeUpdate); - } - m_itemLibraryInfo = model->metaInfo().itemLibraryInfo(); - if (m_itemLibraryInfo) { - connect(m_itemLibraryInfo.data(), &ItemLibraryInfo::entriesChanged, - this, &MaterialEditorView::delayedTypeUpdate); - } - } -#endif - if (!m_setupCompleted) { reloadQml(); m_setupCompleted = true; @@ -801,7 +740,8 @@ void MaterialEditorView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); m_dynamicPropertiesModel->reset(); - m_qmlBackEnd->materialEditorTransaction()->end(); + if (auto transaction = m_qmlBackEnd->materialEditorTransaction()) + transaction->end(); m_qmlBackEnd->contextObject()->setHasMaterialLibrary(false); m_selectedMaterial = {}; } @@ -938,7 +878,8 @@ void MaterialEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNo m_selectedModels.append(node); } - m_qmlBackEnd->contextObject()->setHasModelSelection(!m_selectedModels.isEmpty()); + if (m_qmlBackEnd) + m_qmlBackEnd->contextObject()->setHasModelSelection(!m_selectedModels.isEmpty()); } void MaterialEditorView::currentStateChanged(const ModelNode &node) @@ -1020,7 +961,7 @@ void MaterialEditorView::renameMaterial(ModelNode &material, const QString &newN return; executeInTransaction(__FUNCTION__, [&] { - material.setIdWithRefactoring(model()->generateIdFromName(newName, "material")); + material.setIdWithRefactoring(model()->generateNewId(newName, "material")); VariantProperty objNameProp = material.variantProperty("objectName"); objNameProp.setValue(newName); @@ -1057,7 +998,7 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material) QString newName = sourceMat.modelNode().variantProperty("objectName").value().toString() + " copy"; VariantProperty objNameProp = duplicateMatNode.variantProperty("objectName"); objNameProp.setValue(newName); - duplicateMatNode.setIdWithoutRefactoring(model()->generateIdFromName(newName, "material")); + duplicateMatNode.setIdWithoutRefactoring(model()->generateNewId(newName, "material")); // sync properties. Only the base state is duplicated. const QList<AbstractProperty> props = material.properties(); diff --git a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h index c201742bd5..11bea46063 100644 --- a/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h +++ b/src/plugins/qmldesigner/components/materialeditor/materialeditorview.h @@ -109,12 +109,10 @@ private: bool noValidSelection() const; void initPreviewData(); - void delayedTypeUpdate(); void updatePossibleTypes(); ModelNode m_selectedMaterial; QTimer m_ensureMatLibTimer; - QTimer m_typeUpdateTimer; QShortcut *m_updateShortcut = nullptr; int m_timerId = 0; QStackedWidget *m_stackedWidget = nullptr; diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp index 58b4c42749..fee3218af0 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,9 @@ 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( + QmlDesignerPlugin::instance()->documentManager() + .generatedComponentUtils().materialsBundleType().toUtf8())) { if (parentInfo.isQtQuick3DModel()) propertyList.append("materials"); #endif diff --git a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp index 5b36bee7f9..09cf5945e8 100644 --- a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp @@ -79,15 +79,12 @@ void IconCheckboxItemDelegate::paint(QPainter *painter, if (rowIsPropertyRole(modelIndex.model(), modelIndex) || getModelNode(modelIndex).isRootNode()) return; // Do not paint icons for property rows or root node - QWindow *window = dynamic_cast<QWidget*>(painter->device())->window()->windowHandle(); - QTC_ASSERT(window, return); - const QSize iconSize(16, 16); QPoint iconPosition(styleOption.rect.left() + (styleOption.rect.width() - iconSize.width()) / 2, styleOption.rect.top() + 2 + delegateMargin); const QIcon::State state = isChecked(modelIndex) ? QIcon::State::On : QIcon::State::Off; - const QPixmap iconPixmap = m_icon.pixmap(window, iconSize, mode, state); + const QPixmap iconPixmap = m_icon.pixmap(iconSize, painter->device()->devicePixelRatio(), mode, state); // Shift the lock icon (last column) slightly to the left due to vertical scrollbar width if (modelIndex.column() == NavigatorTreeModel::ColumnType::Lock) 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/navigator/navigatorsearchwidget.cpp b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp index 24d4377ef2..e7fe0ebb77 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorsearchwidget.cpp @@ -97,10 +97,10 @@ void LineEdit::paintEvent(QPaintEvent *event) QPalette p(palette()); p.setColor(QPalette::Active, QPalette::PlaceholderText, - Utils::creatorTheme()->color(Utils::Theme::DSplaceholderTextColor)); + Utils::creatorColor(Utils::Theme::DSplaceholderTextColor)); p.setColor(QPalette::Inactive, QPalette::PlaceholderText, - Utils::creatorTheme()->color(Utils::Theme::DSplaceholderTextColor)); + Utils::creatorColor(Utils::Theme::DSplaceholderTextColor)); setPalette(p); } QLineEdit::paintEvent(event); diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index d305753ead..c5fa30fc7d 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -455,7 +455,7 @@ QStringList NavigatorTreeModel::mimeTypes() const Constants::MIME_TYPE_MATERIAL, Constants::MIME_TYPE_BUNDLE_TEXTURE, Constants::MIME_TYPE_BUNDLE_MATERIAL, - Constants::MIME_TYPE_BUNDLE_EFFECT, + Constants::MIME_TYPE_BUNDLE_ITEM, Constants::MIME_TYPE_ASSETS}); return types; @@ -570,9 +570,9 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, } else if (mimeData->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) { if (targetNode.isValid()) m_view->emitCustomNotification("drop_bundle_material", {targetNode}); // To ContentLibraryView - } else if (mimeData->hasFormat(Constants::MIME_TYPE_BUNDLE_EFFECT)) { + } else if (mimeData->hasFormat(Constants::MIME_TYPE_BUNDLE_ITEM)) { if (targetNode.isValid()) - m_view->emitCustomNotification("drop_bundle_effect", {targetNode}); // To ContentLibraryView + m_view->emitCustomNotification("drop_bundle_item", {targetNode}); // To ContentLibraryView } else if (mimeData->hasFormat(Constants::MIME_TYPE_ASSETS)) { const QStringList assetsPaths = QString::fromUtf8(mimeData->data(Constants::MIME_TYPE_ASSETS)).split(','); NodeAbstractProperty targetProperty; @@ -705,7 +705,7 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in const ItemLibraryEntry itemLibraryEntry = createItemLibraryEntryFromMimeData(mimeData->data(Constants::MIME_TYPE_ITEM_LIBRARY_INFO)); - const NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry); + const NodeHints hints = NodeHints::fromItemLibraryEntry(itemLibraryEntry, m_view->model()); const QString targetPropertyName = hints.forceNonDefaultProperty(); diff --git a/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp b/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp index f99b964f2d..e5e42697e2 100644 --- a/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp +++ b/src/plugins/qmldesigner/components/navigator/previewtooltip.cpp @@ -21,7 +21,8 @@ PreviewToolTip::PreviewToolTip(QWidget *parent) m_ui->idLabel->setElideMode(Qt::ElideLeft); m_ui->typeLabel->setElideMode(Qt::ElideLeft); m_ui->infoLabel->setElideMode(Qt::ElideLeft); - setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name())); + setStyleSheet(QString("QWidget { background-color: %1 }") + .arg(Utils::creatorColor(Utils::Theme::BackgroundColorNormal).name())); m_ui->imageLabel->setStyleSheet("background-color: rgba(0, 0, 0, 0)"); static QPixmap checkers; diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp index 248a9ec429..4034254420 100644 --- a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp +++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp @@ -20,7 +20,8 @@ PreviewImageTooltip::PreviewImageTooltip(QWidget *parent) m_ui->nameLabel->setElideMode(Qt::ElideLeft); m_ui->pathLabel->setElideMode(Qt::ElideLeft); m_ui->infoLabel->setElideMode(Qt::ElideLeft); - setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name())); + setStyleSheet(QString("QWidget { background-color: %1 }") + .arg(Utils::creatorColor(Utils::Theme::BackgroundColorNormal).name())); } PreviewImageTooltip::~PreviewImageTooltip() = default; diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp index 16328ae532..b6d5e2ae47 100644 --- a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp +++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp @@ -42,7 +42,18 @@ void PreviewTooltipBackend::showTooltip() } }); }, - [](auto) {}, + [&](ImageCache::AbortReason abortReason) { + if (abortReason == ImageCache::AbortReason::Abort) { + qWarning() << QLatin1String("PreviewTooltipBackend::showTooltip(): preview generation " + "failed for path %1, reason: Abort").arg(m_path); + } else if (abortReason == ImageCache::AbortReason::Failed) { + qWarning() << QLatin1String("PreviewTooltipBackend::showTooltip(): preview generation " + "failed for path %1, reason: Failed").arg(m_path); + } else if (abortReason == ImageCache::AbortReason::NoEntry) { + qWarning() << QLatin1String("PreviewTooltipBackend::showTooltip(): preview generation " + "failed for path %1, reason: NoEntry").arg(m_path); + } + }, Utils::PathString{m_extraId}, m_auxiliaryData); diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp index 45f89ae339..4898366619 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); @@ -270,6 +274,8 @@ PropertyEditorValue *DynamicPropertyRow::createProxyBackendValue() auto *newValue = new PropertyEditorValue(this); m_proxyBackendValues.append(newValue); + QQmlEngine::setObjectOwnership(newValue, QJSEngine::CppOwnership); + return newValue; } diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp index 591ce5a57f..1a49ce0c39 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorcontextobject.cpp @@ -87,9 +87,9 @@ QString PropertyEditorContextObject::convertColorToString(const QVariant &color) { QString colorString; QColor theColor; - if (color.canConvert(QVariant::Color)) { + if (color.canConvert(QMetaType(QMetaType::QColor))) { theColor = color.value<QColor>(); - } else if (color.canConvert(QVariant::Vector3D)) { + } else if (color.canConvert(QMetaType(QMetaType::QVector3D))) { auto vec = color.value<QVector3D>(); theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z()); } @@ -586,8 +586,7 @@ int PropertyEditorContextObject::devicePixelRatio() QStringList PropertyEditorContextObject::styleNamesForFamily(const QString &family) { - const QFontDatabase dataBase; - return dataBase.styles(family); + return QFontDatabase::styles(family); } QStringList PropertyEditorContextObject::allStatesForId(const QString &id) diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 7f1ab00bb9..c397d445b1 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -82,17 +82,18 @@ namespace QmlDesigner { PropertyEditorQmlBackend::PropertyEditorQmlBackend(PropertyEditorView *propertyEditor, AsynchronousImageCache &imageCache) - : m_view(new Quick2PropertyEditorView(imageCache)) - , m_propertyEditorTransaction(new PropertyEditorTransaction(propertyEditor)) - , m_dummyPropertyEditorValue(new PropertyEditorValue()) - , m_contextObject(new PropertyEditorContextObject(m_view)) + : m_view(Utils::makeUniqueObjectPtr<Quick2PropertyEditorView>(imageCache)) + , m_propertyEditorTransaction(std::make_unique<PropertyEditorTransaction>(propertyEditor)) + , m_dummyPropertyEditorValue(std::make_unique<PropertyEditorValue>()) + , m_contextObject(std::make_unique<PropertyEditorContextObject>(m_view.get())) { m_view->engine()->setOutputWarningsToStandardError(QmlDesignerPlugin::instance() ->settings().value(DesignerSettingsKey::SHOW_PROPERTYEDITOR_WARNINGS).toBool()); m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); m_dummyPropertyEditorValue->setValue(QLatin1String("#000000")); - context()->setContextProperty(QLatin1String("dummyBackendValue"), m_dummyPropertyEditorValue.data()); + context()->setContextProperty(QLatin1String("dummyBackendValue"), + m_dummyPropertyEditorValue.get()); m_contextObject->setBackendValues(&m_backendValuesPropertyMap); m_contextObject->setModel(propertyEditor->model()); m_contextObject->insertInQmlContext(context()); @@ -284,6 +285,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, @@ -381,12 +403,12 @@ QQmlContext *PropertyEditorQmlBackend::context() PropertyEditorContextObject *PropertyEditorQmlBackend::contextObject() { - return m_contextObject.data(); + return m_contextObject.get(); } QQuickWidget *PropertyEditorQmlBackend::widget() { - return m_view; + return m_view.get(); } void PropertyEditorQmlBackend::setSource(const QUrl &url) @@ -411,7 +433,7 @@ DesignerPropertyMap &PropertyEditorQmlBackend::backendValuesPropertyMap() { } PropertyEditorTransaction *PropertyEditorQmlBackend::propertyEditorTransaction() { - return m_propertyEditorTransaction.data(); + return m_propertyEditorTransaction.get(); } PropertyEditorValue *PropertyEditorQmlBackend::propertyValueForName(const QString &propertyName) @@ -474,12 +496,9 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q // anchors m_backendAnchorBinding.setup(qmlObjectNode.modelNode()); - context()->setContextProperties( - QVector<QQmlContext::PropertyPair>{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.data())} - } - ); + context()->setContextProperties(QVector<QQmlContext::PropertyPair>{ + {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, + {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}}); contextObject()->setHasMultiSelection( !qmlObjectNode.view()->singleSelectedModelNode().isValid()); @@ -571,13 +590,10 @@ void PropertyEditorQmlBackend::initialSetup(const TypeName &typeName, const QUrl QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); m_backendValuesPropertyMap.insert(QLatin1String("id"), QVariant::fromValue(valueObject)); - context()->setContextProperties( - QVector<QQmlContext::PropertyPair>{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"modelNodeBackend"}, QVariant::fromValue(&m_backendModelNode)}, - {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.data())} - } - ); + context()->setContextProperties(QVector<QQmlContext::PropertyPair>{ + {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, + {{"modelNodeBackend"}, QVariant::fromValue(&m_backendModelNode)}, + {{"transaction"}, QVariant::fromValue(m_propertyEditorTransaction.get())}}); contextObject()->setSpecificsUrl(qmlSpecificsFile); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index b677258488..1c9b808ac3 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -3,17 +3,21 @@ #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" +#include <utils/uniqueobjectptr.h> + #include <nodemetainfo.h> #include <QQmlPropertyMap> +#include <memory> + class PropertyEditorValue; namespace QmlDesigner { @@ -71,7 +75,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); @@ -92,13 +104,16 @@ private: static TypeName fixTypeNameForPanes(const TypeName &typeName); private: - Quick2PropertyEditorView *m_view; + // to avoid a crash while destructing DesignerPropertyMap in the QQmlData + // this needs be destructed after m_quickWidget->engine() is destructed + DesignerPropertyMap m_backendValuesPropertyMap; + + Utils::UniqueObjectPtr<Quick2PropertyEditorView> m_view = nullptr; QmlAnchorBindingProxy m_backendAnchorBinding; QmlModelNodeProxy m_backendModelNode; - DesignerPropertyMap m_backendValuesPropertyMap; - QScopedPointer<PropertyEditorTransaction> m_propertyEditorTransaction; - QScopedPointer<PropertyEditorValue> m_dummyPropertyEditorValue; - QScopedPointer<PropertyEditorContextObject> m_contextObject; + std::unique_ptr<PropertyEditorTransaction> m_propertyEditorTransaction; + std::unique_ptr<PropertyEditorValue> m_dummyPropertyEditorValue; + std::unique_ptr<PropertyEditorContextObject> m_contextObject; }; } //QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp index 663ebafb65..27319c15f5 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorvalue.cpp @@ -10,16 +10,18 @@ #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> #include <utils/qtcassert.h> #include <QRegularExpression> -#include <QScopedPointer> #include <QUrl> namespace QmlDesigner { @@ -57,10 +59,10 @@ static bool cleverColorCompare(const QVariant &value1, const QVariant &value2) return c1.name() == c2.name() && c1.alpha() == c2.alpha(); } - if (value1.typeId() == QVariant::String && value2.typeId() == QVariant::Color) + if (value1.typeId() == QMetaType::QString && value2.typeId() == QVariant::Color) return cleverColorCompare(QVariant(QColor(value1.toString())), value2); - if (value1.typeId() == QVariant::Color && value2.typeId() == QVariant::String) + if (value1.typeId() == QVariant::Color && value2.typeId() == QMetaType::QString) return cleverColorCompare(value1, QVariant(QColor(value2.toString()))); return false; @@ -153,7 +155,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 +183,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 +337,7 @@ void PropertyEditorValue::resetValue() m_expression = QString(); emit valueChanged(nameAsQString(), QVariant()); emit expressionChanged({}); + emit expressionChangedQml(); } } @@ -425,6 +429,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 +539,32 @@ 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(); +} + +void PropertyEditorValue::insertKeyframe() +{ + if (!m_modelNode.isValid()) + return; + + /*If we add more code here we have to forward the property editor view */ + AbstractView *view = m_modelNode.view(); + + QmlTimeline timeline = view->currentTimeline(); + + QTC_ASSERT(timeline.isValid(), return ); + QTC_ASSERT(m_modelNode.isValid(), return ); + + view->executeInTransaction("PropertyEditorContextObject::insertKeyframe", + [&] { timeline.insertKeyframe(m_modelNode, name()); }); +} + QStringList PropertyEditorValue::generateStringList(const QString &string) const { QString copy = string; @@ -687,4 +745,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..c4b09f6b5a 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,17 @@ 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); + + Q_INVOKABLE void insertKeyframe(); + public slots: void resetValue(); void setEnumeration(const QString &scope, const QString &name); @@ -143,6 +185,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 +212,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..e0d5759617 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -38,7 +38,6 @@ #include <QFileSystemWatcher> #include <QQuickItem> #include <QScopeGuard> -#include <QScopedPointer> #include <QShortcut> #include <QTimer> @@ -256,66 +255,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 +282,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 +297,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 +316,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 +781,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 +843,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 +872,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 +998,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 +1009,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 +1022,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..1cff0e55e6 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,229 @@ 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) +{ + auto parentModelNode = m_qmlObjectNode.modelNode(); + + QTC_ASSERT(parentModelNode.isValid(), return ); + + AbstractView *view = parentModelNode.view(); + + 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 = parentModelNode.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) +{ + ModelNode modelNode = m_qmlObjectNode.modelNode(); + + QTC_ASSERT(modelNode.isValid(), return ); + + if (internalIdParent >= 0) + modelNode = m_qmlObjectNode.view()->modelNodeForInternalId(internalIdParent); + + QTC_ASSERT(modelNode.isValid(), return ); + AbstractView *view = m_qmlObjectNode.view(); + view->executeInTransaction("QmlModelNodeProxy::moveNode", [&] { + modelNode.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/texteditor/texteditorstatusbar.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp index a8981ff2f0..3f6f6769fb 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorstatusbar.cpp @@ -15,7 +15,8 @@ TextEditorStatusBar::TextEditorStatusBar(QWidget *parent) : QToolBar(parent), m_ addWidget(m_label); /* We have to set another .css, since the central widget has already a style sheet */ - m_label->setStyleSheet(QString("QLabel { color :%1 }").arg(Utils::creatorTheme()->color(Utils::Theme::TextColorError).name())); + m_label->setStyleSheet(QString("QLabel { color :%1 }") + .arg(Utils::creatorColor(Utils::Theme::TextColorError).name())); } void TextEditorStatusBar::clearText() diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp index d400251648..2e52b3358b 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorview.cpp @@ -96,7 +96,8 @@ void TextEditorView::modelAboutToBeDetached(Model *model) { AbstractView::modelAboutToBeDetached(model); - m_widget->setTextEditor(nullptr); + if (m_widget) + m_widget->setTextEditor(nullptr); // in case the user closed it explicit we do not want to do anything with the editor if (Core::ModeManager::currentModeId() == Core::Constants::MODE_DESIGN) { diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp index 97ef6c4b68..127780f918 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp @@ -265,7 +265,7 @@ void TextEditorWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) void TextEditorWidget::dragMoveEvent(QDragMoveEvent *dragMoveEvent) { - QTextCursor cursor = m_textEditor->editorWidget()->cursorForPosition(dragMoveEvent->pos()); + QTextCursor cursor = m_textEditor->editorWidget()->cursorForPosition(dragMoveEvent->position().toPoint()); const int cursorPosition = cursor.position(); RewriterView *rewriterView = m_textEditorView->model()->rewriterView(); @@ -279,7 +279,7 @@ void TextEditorWidget::dragMoveEvent(QDragMoveEvent *dragMoveEvent) void TextEditorWidget::dropEvent(QDropEvent *dropEvent) { - QTextCursor cursor = m_textEditor->editorWidget()->cursorForPosition(dropEvent->pos()); + QTextCursor cursor = m_textEditor->editorWidget()->cursorForPosition(dropEvent->position().toPoint()); const int cursorPosition = cursor.position(); RewriterView *rewriterView = m_textEditorView->model()->rewriterView(); diff --git a/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp b/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp index b9c5868bd8..dc57cf30c8 100644 --- a/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp +++ b/src/plugins/qmldesigner/components/texttool/textedititemwidget.cpp @@ -38,26 +38,26 @@ void TextEditItemWidget::paint(QPainter *painter, const QStyleOptionGraphicsItem QLineEdit* TextEditItemWidget::lineEdit() const { - if (m_lineEdit.isNull()) { - m_lineEdit.reset(new QLineEdit); + if (!m_lineEdit) { + m_lineEdit = std::make_unique<QLineEdit>(); m_lineEdit->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); QPalette palette = m_lineEdit->palette(); - static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor); + static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor); palette.setColor(QPalette::Highlight, selectionColor); palette.setColor(QPalette::HighlightedText, Qt::white); palette.setColor(QPalette::Base, Qt::white); palette.setColor(QPalette::Text, Qt::black); m_lineEdit->setPalette(palette); } - return m_lineEdit.data(); + return m_lineEdit.get(); } QTextEdit* TextEditItemWidget::textEdit() const { - if (m_textEdit.isNull()) { - m_textEdit.reset(new QTextEdit); + if (!m_textEdit) { + m_textEdit = std::make_unique<QTextEdit>(); QPalette palette = m_textEdit->palette(); - static QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor); + static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor); palette.setColor(QPalette::Highlight, selectionColor); palette.setColor(QPalette::HighlightedText, Qt::white); palette.setColor(QPalette::Base, Qt::white); @@ -65,7 +65,7 @@ QTextEdit* TextEditItemWidget::textEdit() const m_textEdit->setPalette(palette); } - return m_textEdit.data(); + return m_textEdit.get(); } void TextEditItemWidget::activateTextEdit(const QSize &maximumSize) @@ -83,19 +83,19 @@ void TextEditItemWidget::activateLineEdit() QString TextEditItemWidget::text() const { - if (widget() == m_lineEdit.data()) + if (widget() == m_lineEdit.get()) return m_lineEdit->text(); - else if (widget() == m_textEdit.data()) + else if (widget() == m_textEdit.get()) return m_textEdit->toPlainText(); return QString(); } void TextEditItemWidget::updateText(const QString &text) { - if (widget() == m_lineEdit.data()) { + if (widget() == m_lineEdit.get()) { m_lineEdit->setText(text); m_lineEdit->selectAll(); - } else if (widget() == m_textEdit.data()) { + } else if (widget() == m_textEdit.get()) { m_textEdit->setText(text); m_textEdit->selectAll(); } diff --git a/src/plugins/qmldesigner/components/texttool/textedititemwidget.h b/src/plugins/qmldesigner/components/texttool/textedititemwidget.h index 8aa6c409f9..f975f1a3da 100644 --- a/src/plugins/qmldesigner/components/texttool/textedititemwidget.h +++ b/src/plugins/qmldesigner/components/texttool/textedititemwidget.h @@ -3,7 +3,8 @@ #pragma once #include <QGraphicsProxyWidget> -#include <QScopedPointer> + +#include <memory> QT_BEGIN_NAMESPACE class QTextEdit; @@ -32,7 +33,7 @@ protected: QString text() const; private: - mutable QScopedPointer<QLineEdit> m_lineEdit; - mutable QScopedPointer<QTextEdit> m_textEdit; + mutable std::unique_ptr<QLineEdit> m_lineEdit; + mutable std::unique_ptr<QTextEdit> m_textEdit; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp index 73e784846b..30f276a48d 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorcontextobject.cpp @@ -47,9 +47,9 @@ QString TextureEditorContextObject::convertColorToString(const QVariant &color) { QString colorString; QColor theColor; - if (color.canConvert(QVariant::Color)) { + if (color.canConvert(QMetaType(QMetaType::QColor))) { theColor = color.value<QColor>(); - } else if (color.canConvert(QVariant::Vector3D)) { + } else if (color.canConvert(QMetaType(QMetaType::QVector3D))) { auto vec = color.value<QVector3D>(); theColor = QColor::fromRgbF(vec.x(), vec.y(), vec.z()); } diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp index 80cf5693d2..415d943723 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.cpp @@ -42,21 +42,22 @@ static QObject *variantToQObject(const QVariant &value) namespace QmlDesigner { -TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor, AsynchronousImageCache &imageCache) - : m_view(new QQuickWidget) - , m_textureEditorTransaction(new TextureEditorTransaction(textureEditor)) - , m_contextObject(new TextureEditorContextObject(m_view->rootContext())) +TextureEditorQmlBackend::TextureEditorQmlBackend(TextureEditorView *textureEditor, + AsynchronousImageCache &imageCache) + : m_quickWidget(Utils::makeUniqueObjectPtr<QQuickWidget>()) + , m_textureEditorTransaction(std::make_unique<TextureEditorTransaction>(textureEditor)) + , m_contextObject(std::make_unique<TextureEditorContextObject>(m_quickWidget->rootContext())) { QImage defaultImage; defaultImage.load(Utils::StyleHelper::dpiSpecificImageFile(":/textureeditor/images/texture_default.png")); m_textureEditorImageProvider = new AssetImageProvider(imageCache, defaultImage); - m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); - m_view->setObjectName(Constants::OBJECT_NAME_TEXTURE_EDITOR); - m_view->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); - m_view->engine()->addImageProvider("qmldesigner_thumbnails", m_textureEditorImageProvider); + m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_quickWidget->setObjectName(Constants::OBJECT_NAME_TEXTURE_EDITOR); + m_quickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); + m_quickWidget->engine()->addImageProvider("qmldesigner_thumbnails", m_textureEditorImageProvider); m_contextObject->setBackendValues(&m_backendValuesPropertyMap); m_contextObject->setModel(textureEditor->model()); - context()->setContextObject(m_contextObject.data()); + context()->setContextObject(m_contextObject.get()); QObject::connect(&m_backendValuesPropertyMap, &DesignerPropertyMap::valueChanged, textureEditor, &TextureEditorView::changeValue); @@ -154,22 +155,22 @@ void TextureEditorQmlBackend::setValue(const QmlObjectNode &, const PropertyName QQmlContext *TextureEditorQmlBackend::context() const { - return m_view->rootContext(); + return m_quickWidget->rootContext(); } TextureEditorContextObject *TextureEditorQmlBackend::contextObject() const { - return m_contextObject.data(); + return m_contextObject.get(); } QQuickWidget *TextureEditorQmlBackend::widget() const { - return m_view; + return m_quickWidget.get(); } void TextureEditorQmlBackend::setSource(const QUrl &url) { - m_view->setSource(url); + m_quickWidget->setSource(url); } QmlAnchorBindingProxy &TextureEditorQmlBackend::backendAnchorBinding() @@ -184,7 +185,7 @@ DesignerPropertyMap &TextureEditorQmlBackend::backendValuesPropertyMap() TextureEditorTransaction *TextureEditorQmlBackend::textureEditorTransaction() const { - return m_textureEditorTransaction.data(); + return m_textureEditorTransaction.get(); } PropertyEditorValue *TextureEditorQmlBackend::propertyValueForName(const QString &propertyName) @@ -227,12 +228,9 @@ void TextureEditorQmlBackend::setup(const QmlObjectNode &selectedTextureNode, co // anchors m_backendAnchorBinding.setup(selectedTextureNode.modelNode()); - context()->setContextProperties( - QVector<QQmlContext::PropertyPair>{ - {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, - {{"transaction"}, QVariant::fromValue(m_textureEditorTransaction.data())} - } - ); + context()->setContextProperties(QVector<QQmlContext::PropertyPair>{ + {{"anchorBackend"}, QVariant::fromValue(&m_backendAnchorBinding)}, + {{"transaction"}, QVariant::fromValue(m_textureEditorTransaction.get())}}); contextObject()->setSpecificsUrl(qmlSpecificsFile); contextObject()->setStateName(stateName); diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h index b6d3fddf22..f69c46a569 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorqmlbackend.h @@ -7,8 +7,12 @@ #include "qmlanchorbindingproxy.h" #include "qmlmodelnodeproxy.h" +#include <utils/uniqueobjectptr.h> + #include <nodemetainfo.h> +#include <memory> + class PropertyEditorValue; QT_BEGIN_NAMESPACE @@ -58,12 +62,15 @@ private: TextureEditorView *textureEditor); PropertyName auxNamePostFix(const PropertyName &propertyName); - QQuickWidget *m_view = nullptr; + // to avoid a crash while destructing DesignerPropertyMap in the QQmlData + // this needs be destructed after m_quickWidget->engine() is destructed + DesignerPropertyMap m_backendValuesPropertyMap; + + Utils::UniqueObjectPtr<QQuickWidget> m_quickWidget; QmlAnchorBindingProxy m_backendAnchorBinding; QmlModelNodeProxy m_backendModelNode; - DesignerPropertyMap m_backendValuesPropertyMap; - QScopedPointer<TextureEditorTransaction> m_textureEditorTransaction; - QScopedPointer<TextureEditorContextObject> m_contextObject; + std::unique_ptr<TextureEditorTransaction> m_textureEditorTransaction; + std::unique_ptr<TextureEditorContextObject> m_contextObject; AssetImageProvider *m_textureEditorImageProvider = nullptr; }; diff --git a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp index 5de3730c97..a637431c4d 100644 --- a/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp +++ b/src/plugins/qmldesigner/components/textureeditor/textureeditorview.cpp @@ -43,7 +43,6 @@ #include <QFileInfo> #include <QQuickWidget> #include <QQuickItem> -#include <QScopedPointer> #include <QStackedWidget> #include <QShortcut> #include <QTimer> @@ -698,7 +697,8 @@ void TextureEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNod m_selectedModel = selectedNodeList.at(0); bool hasValidSelection = QmlObjectNode(m_selectedModel).hasBindingProperty("materials"); - m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasValidSelection); + if (m_qmlBackEnd) + m_qmlBackEnd->contextObject()->setHasSingleModelSelection(hasValidSelection); } void TextureEditorView::currentStateChanged(const ModelNode &node) 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..56e22dab1b 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> @@ -96,10 +97,10 @@ TimelineAnimationForm::TimelineAnimationForm(QWidget *parent) using namespace Layouting; Grid { Span(4, mainL), br, - empty(), br, + empty, br, idL, Span(2, m_idLineEdit), Span(2, Row{ runningL, m_running }), br, - empty(), startFrameL, m_startFrame, endFrameL, m_endFrame, durationL, m_duration, br, - empty(), continuousL, m_continuous, loopsL, m_loops, pingPongL, m_pingPong, str, br, + empty, startFrameL, m_startFrame, endFrameL, m_endFrame, durationL, m_duration, br, + empty, continuousL, m_continuous, loopsL, m_loops, pingPongL, m_pingPong, str, br, tr("Transition to state:"), transitionToStateL, m_transitionToState, br, }.attachTo(this); @@ -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..2d1e70cd77 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> @@ -77,8 +78,8 @@ TimelineForm::TimelineForm(QWidget *parent) Grid { Span(2, mainL), br, idL, m_idLineEdit, br, - empty(), Row { startFrameL, m_startFrame, st(), endFrameL, m_endFrame }, str, br, - empty(), Row { m_expressionBinding, m_animation, st() }, br, + empty, Row { startFrameL, m_startFrame, st, endFrameL, m_endFrame }, str, br, + empty, Row { m_expressionBinding, m_animation, st }, br, expressionBindingL, m_expressionBindingLineEdit, br, }.attachTo(this); @@ -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/timelinepropertyitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp index 698ef0f03b..04cc8ebebc 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp @@ -35,7 +35,6 @@ #include <QLineEdit> #include <QMenu> #include <QPainter> -#include <QScopedPointer> #include <algorithm> @@ -108,14 +107,14 @@ static void editValue(const ModelNode &frameNode, const std::pair<qreal, qreal> int userType = value.typeId(); QVariant newValue = dialog->value(); - if (newValue.canConvert(userType)) { + if (newValue.canConvert(QMetaType(userType))) { QVariant newValueConverted = newValue; - bool converted = newValueConverted.convert(userType); + bool converted = newValueConverted.convert(QMetaType(userType)); if (!converted) { // convert() fails for int to double, so we try this combination newValueConverted = newValue; - converted = newValueConverted.convert(QMetaType::Double); + converted = newValueConverted.convert(QMetaType(QMetaType::Double)); } if (converted) diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp index 39c1f01ce6..9b85599ead 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesettingsmodel.cpp @@ -68,7 +68,7 @@ TimelineEditorDelegate::TimelineEditorDelegate(QWidget *parent) if (factory == nullptr) { factory = new QItemEditorFactory; QItemEditorCreatorBase *creator = new QItemEditorCreator<QComboBox>("currentText"); - factory->registerEditor(QVariant::String, creator); + factory->registerEditor(QMetaType::QString, creator); } setItemEditorFactory(factory); 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/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp index 591926d3f5..592cf0dff9 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp @@ -221,10 +221,10 @@ TimelineWidget::TimelineWidget(TimelineView *view) { QPalette timelinePalette; - timelinePalette.setColor(QPalette::Text, Utils::creatorTheme()->color( + timelinePalette.setColor(QPalette::Text, Utils::creatorColor( Utils::Theme::DStextColor)); timelinePalette.setColor(QPalette::WindowText, timelinePalette.color(QPalette::Text)); - timelinePalette.setColor(QPalette::Window, Utils::creatorTheme()->color( + timelinePalette.setColor(QPalette::Window, Utils::creatorColor( Utils::Theme::QmlDesigner_BackgroundColorDarkAlternate)); onboardingTopLabel->setPalette(timelinePalette); 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"), |