diff options
author | Marco Bubke <marco.bubke@qt.io> | 2023-08-23 23:45:52 +0200 |
---|---|---|
committer | Marco Bubke <marco.bubke@qt.io> | 2023-10-02 20:22:10 +0000 |
commit | 1177bb4b1f3f913006d5ff0f2f05dfef2dc3a1de (patch) | |
tree | 64eea90c2a96037a9c13d3094d653397ebb1173f /src | |
parent | 3a0eff9e7959c53a0d068497ce645df387dab93b (diff) |
QmlDesigner: Add PropertyComponentGenerator
Task-number: QDS-10578
Change-Id: I2c20f5aaa6ebfe7736012a02d4c4596589007866
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Diffstat (limited to 'src')
11 files changed, 644 insertions, 2 deletions
diff --git a/src/libs/qmljs/qmljssimplereader.cpp b/src/libs/qmljs/qmljssimplereader.cpp index 761131b3a28..d4b8a21bf7c 100644 --- a/src/libs/qmljs/qmljssimplereader.cpp +++ b/src/libs/qmljs/qmljssimplereader.cpp @@ -89,7 +89,10 @@ static Q_LOGGING_CATEGORY(simpleReaderLog, "qtc.qmljs.simpleReader", QtWarningMs return newNode; } - const SimpleReaderNode::List SimpleReaderNode::children() const { return m_children; } + const SimpleReaderNode::List &SimpleReaderNode::children() const + { + return m_children; + } void SimpleReaderNode::setProperty(const QString &name, const SourceLocation &nameLocation, diff --git a/src/libs/qmljs/qmljssimplereader.h b/src/libs/qmljs/qmljssimplereader.h index de57bb62dad..e14f49d65dc 100644 --- a/src/libs/qmljs/qmljssimplereader.h +++ b/src/libs/qmljs/qmljssimplereader.h @@ -34,6 +34,7 @@ public: SourceLocation valueLocation; bool isValid() const { return !value.isNull() && value.isValid(); } + explicit operator bool() const { return isValid(); } bool isDefaultValue() const { return !value.isNull() && !nameLocation.isValid() && !valueLocation.isValid(); @@ -53,7 +54,7 @@ public: WeakPtr parent() const; QString name() const; SourceLocation nameLocation() const; - const List children() const; + const List &children() const; protected: SimpleReaderNode(); diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 8ea79bcdcf8..6df5a632472 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -238,6 +238,7 @@ extend_qtc_library(QmlDesignerCore modelmerger.h modelnode.h modelnodepositionstorage.h + module.h nodeabstractproperty.h nodeinstance.h nodelistproperty.h diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp new file mode 100644 index 00000000000..7e5487909a8 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp @@ -0,0 +1,272 @@ +// 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 "propertycomponentgenerator.h" + +#include <utils/algorithm.h> +#include <utils/environment.h> + +#include <model.h> + +#include <QFile> +#include <QString> + +#include <memory> + +using namespace Qt::StringLiterals; + +namespace QmlDesigner { + +namespace { + +QmlJS::SimpleReaderNode::Ptr createTemplateConfiguration(const QString &propertyEditorResourcesPath) +{ + QmlJS::SimpleReader reader; + const QString fileName = propertyEditorResourcesPath + u"/PropertyTemplates/TemplateTypes.qml"; + auto templateConfiguration = reader.readFile(fileName); + + if (!templateConfiguration) + qWarning().nospace() << "template definitions:" << reader.errors(); + + return templateConfiguration; +} + +template<typename Type> +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) { + auto list = value.toList(); + if (list.size()) + return list.front().value<Type>(); + } else { + return property.value.value<Type>(); + } + } + + return {}; +} + +QString getContent(const QString &path) +{ + QFile file{path}; + + if (file.open(QIODevice::ReadOnly)) + return QString::fromUtf8(file.readAll()); + + return {}; +} + +QString generateComponentText(Utils::SmallStringView propertyName, + QStringView source, + Utils::SmallStringView typeName, + bool needsTypeArg) +{ + QString underscoreName{propertyName}; + underscoreName.replace('.', '_'); + if (needsTypeArg) + return source.arg(QString{propertyName}, underscoreName, QString{typeName}); + + return source.arg(QString{propertyName}, underscoreName); +} + +PropertyComponentGenerator::Property generate(const PropertyMetaInfo &property, + const PropertyComponentGenerator::Entry &entry) +{ + auto propertyName = property.name(); + auto component = generateComponentText(propertyName, + entry.propertyTemplate, + entry.typeName, + entry.needsTypeArg); + + if (entry.separateSection) + return PropertyComponentGenerator::ComplexProperty{propertyName, component}; + + return PropertyComponentGenerator::BasicProperty{propertyName, component}; +} + +auto getRandomExportedName(const NodeMetaInfo &metaInfo) +{ + const auto &names = metaInfo.allExportedTypeNames(); + + using Result = decltype(names.front().name); + + if (!names.empty()) + return names.front().name; + + return Result{}; +} + +} // namespace + +QString PropertyComponentGenerator::generateSubComponentText(Utils::SmallStringView propertyBaseName, + const PropertyMetaInfo &subProperty) const +{ + auto propertyType = subProperty.propertyType(); + if (auto entry = findEntry(propertyType)) { + auto propertyName = Utils::SmallString::join({propertyBaseName, subProperty.name()}); + return generateComponentText(propertyName, + entry->propertyTemplate, + entry->typeName, + entry->needsTypeArg); + } + + return {}; +} + +QString PropertyComponentGenerator::generateComplexComponentText(Utils::SmallStringView propertyName, + const NodeMetaInfo &propertyType) const +{ + auto subProperties = propertyType.properties(); + + if (std::empty(subProperties)) + return {}; + + auto propertyTypeName = getRandomExportedName(propertyType); + static QString templateText = QStringLiteral( + R"xy( + Section { + caption: %1 - %2 + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 8 + rightPadding: 0 + expanded: false + level: 1 + SectionLayout { + )xy"); + + auto component = templateText.arg(QString{propertyName}, QString{propertyTypeName}); + + auto propertyBaseName = Utils::SmallString::join({propertyName, "."}); + + bool subPropertiesHaveContent = false; + for (const auto &subProperty : propertyType.properties()) { + auto subPropertyContent = generateSubComponentText(propertyBaseName, subProperty); + subPropertiesHaveContent = subPropertiesHaveContent || subPropertyContent.size(); + component += subPropertyContent; + } + + component += "}\n}\n"_L1; + + if (subPropertiesHaveContent) + return component; + + return {}; +} + +PropertyComponentGenerator::Property PropertyComponentGenerator::generateComplexComponent( + const PropertyMetaInfo &property, const NodeMetaInfo &propertyType) const +{ + auto propertyName = property.name(); + auto component = generateComplexComponentText(propertyName, propertyType); + + if (component.isEmpty()) + return {}; + + return ComplexProperty{propertyName, component}; +} + +namespace { + +std::optional<PropertyComponentGenerator::Entry> createEntry(QmlJS::SimpleReaderNode *node, + Model *model, + const QString &templatesPath) +{ + auto moduleName = getProperty<QByteArray>(node, "module"); + if (moduleName.isEmpty()) + return {}; + + auto module = model->module(moduleName); + + auto typeName = getProperty<QByteArray>(node, "typeNames"); + + auto type = model->metaInfo(module, typeName); + if (!type) + return {}; + + auto path = getProperty<QString>(node, "sourceFile"); + if (path.isEmpty()) + return {}; + + auto content = getContent(templatesPath + path); + if (content.isEmpty()) + return {}; + + bool needsTypeArg = getProperty<bool>(node, "needsTypeArg"); + + bool separateSection = getProperty<bool>(node, "separateSection"); + + return PropertyComponentGenerator::Entry{std::move(type), + std::move(typeName), + std::move(content), + separateSection, + needsTypeArg}; +} + +PropertyComponentGenerator::Entries createEntries(QmlJS::SimpleReaderNode::Ptr templateConfiguration, + Model *model, + const QString &templatesPath) +{ + PropertyComponentGenerator::Entries entries; + entries.reserve(32); + + const auto &nodes = templateConfiguration->children(); + for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) { + if (auto entry = createEntry(node.get(), model, templatesPath)) + entries.push_back(*entry); + } + + return entries; +} + +QStringList createImports(QmlJS::SimpleReaderNode::Ptr templateConfiguration) +{ + auto property = templateConfiguration->property("imports"); + return Utils::transform<QStringList>(property.value.toList(), + [](const auto &entry) { return entry.toString(); }); +} + +} // namespace + +PropertyComponentGenerator::PropertyComponentGenerator(const QString &propertyEditorResourcesPath, + Model *model) + : m_entries(createEntries(createTemplateConfiguration(propertyEditorResourcesPath), + model, + propertyEditorResourcesPath + "/PropertyTemplates/")) +{ + auto templateConfiguration = createTemplateConfiguration(propertyEditorResourcesPath); + + m_entries = createEntries(templateConfiguration, + model, + propertyEditorResourcesPath + "/PropertyTemplates/"); + + m_imports = createImports(templateConfiguration); +} + +PropertyComponentGenerator::Property PropertyComponentGenerator::create(const PropertyMetaInfo &property) const +{ + auto propertyType = property.propertyType(); + if (auto entry = findEntry(propertyType)) + return generate(property, *entry); + + if (property.isWritable() && property.isPointer()) + return {}; + + return generateComplexComponent(property, propertyType); +} + +const PropertyComponentGenerator::Entry *PropertyComponentGenerator::findEntry(const NodeMetaInfo &type) const +{ + auto found = std::find_if(m_entries.begin(), m_entries.end(), [&](const auto &entry) { + return entry.type == type; + }); + + if (found != m_entries.end()) + return std::addressof(*found); + + return nullptr; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h new file mode 100644 index 00000000000..67239c16a70 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h @@ -0,0 +1,53 @@ +// 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 "propertycomponentgeneratorinterface.h" + +#include <modelfwd.h> +#include <nodemetainfo.h> + +#include <qmljs/qmljssimplereader.h> + +#include <optional> +#include <variant> +#include <vector> + +namespace QmlDesigner { + +class PropertyComponentGenerator final : public PropertyComponentGeneratorInterface +{ +public: + PropertyComponentGenerator(const QString &propertyEditorResourcesPath, Model *model); + + Property create(const PropertyMetaInfo &property) const override; + + struct Entry + { + NodeMetaInfo type; + Utils::SmallString typeName; + QString propertyTemplate; + bool separateSection = false; + bool needsTypeArg = false; + }; + + using Entries = std::vector<Entry>; + + QStringList imports() const override { return m_imports; } + +private: + const Entry *findEntry(const NodeMetaInfo &type) const; + QString generateSubComponentText(Utils::SmallStringView propertyBaseName, + const PropertyMetaInfo &subProperty) const; + QString generateComplexComponentText(Utils::SmallStringView propertyName, + const NodeMetaInfo &propertyType) const; + Property generateComplexComponent(const PropertyMetaInfo &property, + const NodeMetaInfo &propertyType) const; + +private: + Entries m_entries; + QStringList m_imports; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h b/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h new file mode 100644 index 00000000000..ab4047c957b --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h @@ -0,0 +1,46 @@ +// 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 <modelfwd.h> +#include <nodemetainfo.h> + +#include <qmljs/qmljssimplereader.h> + +#include <optional> +#include <variant> +#include <vector> + +namespace QmlDesigner { + +class PropertyComponentGeneratorInterface +{ +public: + PropertyComponentGeneratorInterface() = default; + PropertyComponentGeneratorInterface(const PropertyComponentGeneratorInterface &) = delete; + PropertyComponentGeneratorInterface &operator=(const PropertyComponentGeneratorInterface &) = delete; + + struct BasicProperty + { + Utils::SmallString propertyName; + QString component; + }; + + struct ComplexProperty + { + Utils::SmallString propertyName; + QString component; + }; + + using Property = std::variant<std::monostate, BasicProperty, ComplexProperty>; + + virtual Property create(const PropertyMetaInfo &property) const = 0; + + virtual QStringList imports() const = 0; + +protected: + ~PropertyComponentGeneratorInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp new file mode 100644 index 00000000000..272130b8f33 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp @@ -0,0 +1,177 @@ +// 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 "propertyeditorcomponentgenerator.h" + +#include <utils/environment.h> +#include <utils/span.h> + +#include <modelnode.h> + +namespace QmlDesigner { + +namespace { + +using GeneratorProperty = PropertyComponentGeneratorInterface::Property; +using BasicProperty = PropertyComponentGeneratorInterface::BasicProperty; +using ComplexProperty = PropertyComponentGeneratorInterface::ComplexProperty; +using GeneratorProperties = std::vector<GeneratorProperty>; + +QString createImports(const QStringList &imports) +{ + QString importsContent; + importsContent.reserve(512); + + for (const QString &import : imports) { + importsContent += import; + importsContent += '\n'; + } + + return importsContent; +} + +QString componentButton(bool isComponent) +{ + if (isComponent) + return "ComponentButton {}"; + + return {}; +} + +void createBasicPropertySections(QString &components, + Utils::span<GeneratorProperty> generatorProperties) +{ + components += R"xy( + Column { + width: parent.width + leftPadding: 8 + bottomPadding: 10 + SectionLayout {)xy"; + + for (const auto &generatorProperty : generatorProperties) + components += std::get_if<BasicProperty>(&generatorProperty)->component; + + components += R"xy( + } + })xy"; +} + +void createComplexPropertySections(QString &components, + Utils::span<GeneratorProperty> generatorProperties) +{ + for (const auto &generatorProperty : generatorProperties) + components += std::get_if<ComplexProperty>(&generatorProperty)->component; +} + +Utils::SmallStringView propertyName(const GeneratorProperty &property) +{ + return std::visit( + [](const auto &p) -> Utils::SmallStringView { + if constexpr (!std::is_same_v<std::decay_t<decltype(p)>, std::monostate>) + return p.propertyName; + else + return {}; + }, + property); +} + +PropertyMetaInfos getUnmangedProperties(const NodeMetaInfo &nodeInfo) +{ + PropertyMetaInfos properties; + properties.reserve(128); + + auto prototypes = nodeInfo.selfAndPrototypes(); + + for (const auto &prototype : prototypes) { + if (prototype.propertyEditorPathId()) + break; + + auto localProperties = prototype.localProperties(); + + properties.insert(properties.end(), localProperties.begin(), localProperties.end()); + } + + return properties; +} + +GeneratorProperties createSortedGeneratorProperties( + const PropertyMetaInfos &properties, const PropertyComponentGeneratorType &propertyGenerator) +{ + GeneratorProperties generatorProperties; + generatorProperties.reserve(properties.size()); + + for (const auto &property : properties) { + auto generatedProperty = propertyGenerator.create(property); + if (!std::holds_alternative<std::monostate>(generatedProperty)) + generatorProperties.push_back(std::move(generatedProperty)); + } + std::sort(generatorProperties.begin(), + generatorProperties.end(), + [](const auto &first, const auto &second) { + // this is sensitive to the order of the variant types but the test should catch any error + return std::forward_as_tuple(first.index(), propertyName(first)) + < std::forward_as_tuple(second.index(), propertyName(second)); + }); + + return generatorProperties; +} + +QString createPropertySections(const PropertyComponentGeneratorType &propertyGenerator, + const NodeMetaInfo &nodeInfo) +{ + QString propertyComponents; + propertyComponents.reserve(100000); + + auto generatorProperties = createSortedGeneratorProperties(getUnmangedProperties(nodeInfo), + propertyGenerator); + + const auto begin = generatorProperties.begin(); + const auto end = generatorProperties.end(); + + auto point = std::partition_point(begin, end, [](const auto &p) { + return !std::holds_alternative<ComplexProperty>(p); + }); + + if (begin != point) + createBasicPropertySections(propertyComponents, Utils::span<GeneratorProperty>{begin, point}); + + if (point != end) + createComplexPropertySections(propertyComponents, Utils::span<GeneratorProperty>{point, end}); + + return propertyComponents; +} + +} // namespace + +PropertyEditorTemplateGenerator::PropertyEditorTemplateGenerator( + const PropertyComponentGeneratorType &propertyGenerator) + : m_propertyGenerator{propertyGenerator} +{} + +QString PropertyEditorTemplateGenerator::create(const NodeMetaInfo &nodeInfo, bool isComponent) +{ + return QString{R"xy( + %1 + Column { + width: parent.width + %2 + Section { + caption: "%3" + anchors.left: parent.left + anchors.right: parent.right + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + Column { + width: parent.width + %4 + } + } + })xy"} + .arg(createImports(m_propertyGenerator.imports()), + componentButton(isComponent), + QObject::tr("Exposed Custom Properties"), + createPropertySections(m_propertyGenerator, nodeInfo)); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h new file mode 100644 index 00000000000..37dd824d43f --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h @@ -0,0 +1,29 @@ +// 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 "propertycomponentgenerator.h" + +#include <nodemetainfo.h> + +namespace QmlDesigner { + +#ifdef UNIT_TESTS +using PropertyComponentGeneratorType = PropertyComponentGeneratorInterface; +#else +using PropertyComponentGeneratorType = PropertyComponentGenerator; +#endif +class PropertyEditorTemplateGenerator +{ + +public: + PropertyEditorTemplateGenerator(const PropertyComponentGeneratorType &propertyGenerator); + + QString create(const NodeMetaInfo &nodeInfo, bool isComponent); + +private: + const PropertyComponentGeneratorType &m_propertyGenerator; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index c404525a9c0..ac8182c0760 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -7,7 +7,9 @@ #include <documentmessage.h> #include <model/modelresourcemanagementinterface.h> +#include <module.h> #include <projectstorage/projectstoragefwd.h> +#include <projectstorage/projectstorageinfotypes.h> #include <projectstorageids.h> #include <QMimeData> @@ -125,7 +127,11 @@ public: const MetaInfo metaInfo() const; MetaInfo metaInfo(); + Module module(Utils::SmallStringView moduleName); NodeMetaInfo metaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; + NodeMetaInfo metaInfo(Module module, + Utils::SmallStringView typeName, + Storage::Version version = Storage::Version{}) const; bool hasNodeMetaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const; void setMetaInfo(const MetaInfo &metaInfo); diff --git a/src/plugins/qmldesigner/designercore/include/module.h b/src/plugins/qmldesigner/designercore/include/module.h new file mode 100644 index 00000000000..5078f3ff8f5 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/module.h @@ -0,0 +1,35 @@ +// 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 <modelfwd.h> +#include <projectstorageids.h> + +namespace QmlDesigner { + +class Module +{ +public: + Module() = default; + + Module(ModuleId moduleId, NotNullPointer<const ProjectStorageType> projectStorage) + : m_projectStorage{projectStorage} + , m_id{moduleId} + {} + + ModuleId id() const { return m_id; } + + bool isValid() const { return bool(m_id); } + + explicit operator bool() const { return isValid(); } + + const ProjectStorageType &projectStorage() const { return *m_projectStorage; } + + friend bool operator==(Module first, Module second) { return first.m_id == second.m_id; } + +private: + NotNullPointer<const ProjectStorageType> m_projectStorage = {}; + ModuleId m_id; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index 74afaaad86d..c0393d524c1 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -2436,6 +2436,16 @@ NodeMetaInfo Model::metaInfo(const TypeName &typeName, int majorVersion, int min } } +NodeMetaInfo Model::metaInfo(Module module, Utils::SmallStringView typeName, Storage::Version version) const +{ + if constexpr (useProjectStorage()) { + return NodeMetaInfo(d->projectStorage->typeId(module.id(), typeName, version), + d->projectStorage); + } else { + return {}; + } +} + /*! \brief Returns list of QML types available within the model. */ @@ -2444,6 +2454,15 @@ MetaInfo Model::metaInfo() return d->metaInfo(); } +Module Model::module(Utils::SmallStringView moduleName) +{ + if constexpr (useProjectStorage()) { + return Module(d->projectStorage->moduleId(moduleName), d->projectStorage); + } + + return {}; +} + /*! \name View related functions */ //\{ |