forked from qt-creator/qt-creator
DesignSystem: R/W design system module from the library
Task-number: QDS-13713 Change-Id: Ibc13272ac1e0b26352b84e74216b2fbfcd69ff4e Reviewed-by: Marco Bubke <marco.bubke@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
add_qtc_library(DesignSystem STATIC
|
||||
PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}
|
||||
DEPENDS
|
||||
Qt::Core Qt::Widgets QmlDesignerCore
|
||||
Qt::Core Qt::Widgets QmlDesignerCore TextEditorSupport
|
||||
SOURCES
|
||||
dsconstants.h
|
||||
dsstore.h dsstore.cpp
|
||||
dsthemegroup.h dsthemegroup.cpp
|
||||
dsthememanager.h dsthememanager.cpp
|
||||
)
|
||||
|
265
src/plugins/qmldesigner/libs/designsystem/dsstore.cpp
Normal file
265
src/plugins/qmldesigner/libs/designsystem/dsstore.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
// 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 "dsstore.h"
|
||||
#include "dsthememanager.h"
|
||||
|
||||
#include <generatedcomponentutils.h>
|
||||
#include <plaintexteditmodifier.h>
|
||||
#include <qmljs/parser/qmldirparser_p.h>
|
||||
#include <qmljs/qmljsreformatter.h>
|
||||
#include <rewriterview.h>
|
||||
#include <rewritingexception.h>
|
||||
#include <uniquename.h>
|
||||
#include <utils/fileutils.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char DesignModuleName[] = "DesignSystem";
|
||||
|
||||
QString capitalize(QStringView str)
|
||||
{
|
||||
if (str.isEmpty())
|
||||
return QString();
|
||||
QString tmp = str.toString();
|
||||
tmp[0] = str[0].toUpper();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
std::optional<Utils::FilePath> dsModuleDir(QmlDesigner::ExternalDependenciesInterface &ed)
|
||||
{
|
||||
auto componentsPath = QmlDesigner::GeneratedComponentUtils(ed).generatedComponentsPath();
|
||||
if (componentsPath.exists())
|
||||
return componentsPath.pathAppended(DesignModuleName);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static QByteArray reformatQml(const QString &content)
|
||||
{
|
||||
auto document = QmlJS::Document::create({}, QmlJS::Dialect::QmlQtQuick2);
|
||||
document->setSource(content);
|
||||
document->parseQml();
|
||||
if (document->isParsedCorrectly())
|
||||
return QmlJS::reformat(document).toUtf8();
|
||||
|
||||
return content.toUtf8();
|
||||
}
|
||||
|
||||
std::optional<QString> modelSerializeHelper(QmlDesigner::ExternalDependenciesInterface &ed,
|
||||
std::function<void(QmlDesigner::Model *)> callback,
|
||||
const Utils::FilePath &targetDir,
|
||||
const QString &typeName,
|
||||
bool isSingelton = false)
|
||||
{
|
||||
QString qmlText{"import QtQuick\nQtObject {}\n"};
|
||||
if (isSingelton)
|
||||
qmlText.prepend("pragma Singleton\n");
|
||||
|
||||
QmlDesigner::ModelPointer model(QmlDesigner::Model::create("QtObject"));
|
||||
QPlainTextEdit editor;
|
||||
editor.setPlainText(qmlText);
|
||||
QmlDesigner::NotIndentingTextEditModifier modifier(&editor);
|
||||
QmlDesigner::RewriterView view(ed, QmlDesigner::RewriterView::Validate);
|
||||
view.setPossibleImportsEnabled(false);
|
||||
view.setCheckSemanticErrors(false);
|
||||
view.setTextModifier(&modifier);
|
||||
model->attachView(&view);
|
||||
|
||||
try {
|
||||
callback(model.get());
|
||||
} catch (const QmlDesigner::RewritingException &e) {
|
||||
return e.description();
|
||||
}
|
||||
|
||||
Utils::FileSaver saver(targetDir / (typeName + ".qml"), QIODevice::Text);
|
||||
saver.write(reformatQml(modifier.text()));
|
||||
if (!saver.finalize())
|
||||
return saver.errorString();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
DSStore::DSStore(ExternalDependenciesInterface &ed)
|
||||
: m_ed(ed)
|
||||
{}
|
||||
|
||||
DSStore::~DSStore() {}
|
||||
|
||||
QString DSStore::moduleImportStr() const
|
||||
{
|
||||
auto prefix = GeneratedComponentUtils(m_ed).generatedComponentTypePrefix();
|
||||
if (!prefix.isEmpty())
|
||||
return QString("%1.%2").arg(prefix).arg(DesignModuleName);
|
||||
|
||||
return DesignModuleName;
|
||||
}
|
||||
|
||||
std::optional<QString> DSStore::load()
|
||||
{
|
||||
if (auto moduleDir = dsModuleDir(m_ed))
|
||||
return load(*moduleDir);
|
||||
|
||||
return tr("Can not locate design system module");
|
||||
}
|
||||
|
||||
std::optional<QString> DSStore::load(const Utils::FilePath &dsModuleDirPath)
|
||||
{
|
||||
// read qmldir
|
||||
const auto qmldirFile = dsModuleDirPath / "qmldir";
|
||||
const Utils::expected_str<QByteArray> contents = qmldirFile.fileContents();
|
||||
if (!contents)
|
||||
return tr("Can not read Design System qmldir");
|
||||
|
||||
m_collectionTypeNames.clear();
|
||||
m_collections.clear();
|
||||
|
||||
// Parse qmldir
|
||||
QString qmldirData = QString::fromUtf8(*contents);
|
||||
QmlDirParser qmlDirParser;
|
||||
qmlDirParser.parse(qmldirData);
|
||||
|
||||
// load collections
|
||||
QStringList collectionErrors;
|
||||
auto addCollectionErr = [&collectionErrors](const QString &name, const QString &e) {
|
||||
collectionErrors << QString("Error loading collection %1. %2").arg(name, e);
|
||||
};
|
||||
for (auto component : qmlDirParser.components()) {
|
||||
if (!component.fileName.isEmpty()) {
|
||||
const auto collectionPath = dsModuleDirPath.pathAppended(component.fileName);
|
||||
if (auto err = loadCollection(component.typeName, collectionPath))
|
||||
addCollectionErr(component.typeName, *err);
|
||||
} else {
|
||||
addCollectionErr(component.typeName, tr("Can not find component file."));
|
||||
}
|
||||
}
|
||||
|
||||
if (!collectionErrors.isEmpty())
|
||||
return collectionErrors.join("\n");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<QString> DSStore::save(bool mcuCompatible) const
|
||||
{
|
||||
if (auto moduleDir = dsModuleDir(m_ed))
|
||||
return save(*moduleDir, mcuCompatible);
|
||||
|
||||
return tr("Can not locate design system module");
|
||||
}
|
||||
|
||||
std::optional<QString> DSStore::save(const Utils::FilePath &moduleDirPath, bool mcuCompatible) const
|
||||
{
|
||||
if (!QDir().mkpath(moduleDirPath.absoluteFilePath().toString()))
|
||||
return tr("Can not create design system module directory %1.").arg(moduleDirPath.toString());
|
||||
|
||||
// dump collections
|
||||
QStringList singletons;
|
||||
QStringList errors;
|
||||
for (auto &[typeName, collection] : m_collections) {
|
||||
if (auto err = writeQml(collection, typeName, moduleDirPath, mcuCompatible))
|
||||
errors << *err;
|
||||
singletons << QString("singleton %1 1.0 %1.qml").arg(typeName);
|
||||
}
|
||||
|
||||
// Write qmldir
|
||||
Utils::FileSaver saver(moduleDirPath / "qmldir", QIODevice::Text);
|
||||
const QString qmldirContents = QString("Module %1\n%2").arg(moduleImportStr(), singletons.join("\n"));
|
||||
saver.write(qmldirContents.toUtf8());
|
||||
if (!saver.finalize())
|
||||
errors << tr("Can not write design system qmldir. %1").arg(saver.errorString());
|
||||
|
||||
if (!errors.isEmpty())
|
||||
return errors.join("\n");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DSThemeManager *DSStore::addCollection(const QString &qmlTypeName)
|
||||
{
|
||||
const QString uniqueTypeName = UniqueName::generateId(qmlTypeName,
|
||||
"designSystem",
|
||||
[this](const QString &t) {
|
||||
return m_collections.contains(t);
|
||||
});
|
||||
|
||||
const QString componentType = capitalize(uniqueTypeName);
|
||||
auto [itr, success] = m_collections.try_emplace(componentType, DSThemeManager{});
|
||||
if (success) {
|
||||
m_collectionTypeNames.insert({&itr->second, itr->first});
|
||||
return &itr->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<QString> DSStore::typeName(DSThemeManager *collection) const
|
||||
{
|
||||
auto itr = m_collectionTypeNames.find(collection);
|
||||
if (itr != m_collectionTypeNames.end())
|
||||
return itr->second;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<QString> DSStore::loadCollection(const QString &typeName,
|
||||
const Utils::FilePath &qmlFilePath)
|
||||
{
|
||||
Utils::FileReader reader;
|
||||
if (!reader.fetch(qmlFilePath, QFile::Text))
|
||||
return reader.errorString();
|
||||
|
||||
ModelPointer model(QmlDesigner::Model::create("QtObject"));
|
||||
|
||||
QPlainTextEdit editor;
|
||||
QString qmlContent = QString::fromUtf8(reader.data());
|
||||
editor.setPlainText(qmlContent);
|
||||
|
||||
QmlDesigner::NotIndentingTextEditModifier modifier(&editor);
|
||||
RewriterView view(m_ed, QmlDesigner::RewriterView::Validate);
|
||||
// QDS-8366
|
||||
view.setPossibleImportsEnabled(false);
|
||||
view.setCheckSemanticErrors(false);
|
||||
view.setTextModifier(&modifier);
|
||||
model->attachView(&view);
|
||||
|
||||
if (auto dsMgr = addCollection(typeName))
|
||||
return dsMgr->load(model->rootModelNode());
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<QString> DSStore::writeQml(const DSThemeManager &mgr,
|
||||
const QString &typeName,
|
||||
const Utils::FilePath &targetDir,
|
||||
bool mcuCompatible) const
|
||||
{
|
||||
if (mgr.themeCount() == 0)
|
||||
return {};
|
||||
|
||||
const QString themeInterfaceType = mcuCompatible ? QString("%1Theme").arg(typeName) : "QtObject";
|
||||
if (mcuCompatible) {
|
||||
auto decorateInterface = [&mgr](Model *interfaceModel) {
|
||||
mgr.decorateThemeInterface(interfaceModel->rootModelNode());
|
||||
};
|
||||
|
||||
if (auto error = modelSerializeHelper(m_ed, decorateInterface, targetDir, themeInterfaceType))
|
||||
return tr("Can not write theme interface %1.\n%2").arg(themeInterfaceType, *error);
|
||||
}
|
||||
|
||||
auto decorateCollection = [&](Model *collectionModel) {
|
||||
mgr.decorate(collectionModel->rootModelNode(), themeInterfaceType.toUtf8(), mcuCompatible);
|
||||
};
|
||||
|
||||
if (auto error = modelSerializeHelper(m_ed, decorateCollection, targetDir, typeName, true))
|
||||
return tr("Can not write collection %1.\n%2").arg(typeName, *error);
|
||||
|
||||
return {};
|
||||
}
|
||||
} // namespace QmlDesigner
|
48
src/plugins/qmldesigner/libs/designsystem/dsstore.h
Normal file
48
src/plugins/qmldesigner/libs/designsystem/dsstore.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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 <dsthememanager.h>
|
||||
#include <externaldependenciesinterface.h>
|
||||
|
||||
namespace QmlDesigner {
|
||||
class DSThemeManager;
|
||||
class ExternalDependenciesInterface;
|
||||
|
||||
using DSCollections = std::map<QString, DSThemeManager>;
|
||||
|
||||
class DSStore
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(DSStore);
|
||||
|
||||
public:
|
||||
DSStore(ExternalDependenciesInterface &ed);
|
||||
~DSStore();
|
||||
|
||||
QString moduleImportStr() const;
|
||||
|
||||
std::optional<QString> load();
|
||||
std::optional<QString> load(const Utils::FilePath &dsModuleDirPath);
|
||||
|
||||
std::optional<QString> save(bool mcuCompatible = false) const;
|
||||
std::optional<QString> save(const Utils::FilePath &moduleDirPath, bool mcuCompatible = false) const;
|
||||
|
||||
size_t collectionCount() const { return m_collections.size(); }
|
||||
|
||||
DSThemeManager *addCollection(const QString &qmlTypeName);
|
||||
std::optional<QString> typeName(DSThemeManager *collection) const;
|
||||
|
||||
private:
|
||||
std::optional<QString> loadCollection(const QString &typeName, const Utils::FilePath &qmlFilePath);
|
||||
std::optional<QString> writeQml(const DSThemeManager &mgr,
|
||||
const QString &typeName,
|
||||
const Utils::FilePath &targetDir,
|
||||
bool mcuCompatible) const;
|
||||
|
||||
private:
|
||||
ExternalDependenciesInterface &m_ed;
|
||||
DSCollections m_collections;
|
||||
std::map<DSThemeManager *, const QString &> m_collectionTypeNames;
|
||||
};
|
||||
} // namespace QmlDesigner
|
@@ -3,10 +3,12 @@
|
||||
|
||||
#include "dsthemegroup.h"
|
||||
|
||||
#include <abstractproperty.h>
|
||||
#include <model.h>
|
||||
#include <nodemetainfo.h>
|
||||
#include <nodeproperty.h>
|
||||
#include <variantproperty.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <variantproperty.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QVariant>
|
||||
@@ -142,64 +144,73 @@ void DSThemeGroup::removeTheme(ThemeId theme)
|
||||
|
||||
void DSThemeGroup::duplicateValues(ThemeId from, ThemeId to)
|
||||
{
|
||||
for (auto itr = m_values.begin(); itr != m_values.end(); ++itr) {
|
||||
auto &[propName, values] = *itr;
|
||||
auto fromValueItr = values.find(from);
|
||||
for (auto &[propName, values] : m_values) {
|
||||
ThemeValues::iterator fromValueItr = values.find(from);
|
||||
if (fromValueItr != values.end())
|
||||
values[to] = fromValueItr->second;
|
||||
}
|
||||
}
|
||||
|
||||
void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode, DECORATION_CONTEXT decorationContext)
|
||||
void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode, bool wrapInGroups)
|
||||
{
|
||||
if (!count(theme))
|
||||
return; // No props for this theme in this group.
|
||||
|
||||
ModelNode *targetNode = &themeNode;
|
||||
ModelNode targetNode = themeNode;
|
||||
const auto typeName = groupTypeName(m_type);
|
||||
|
||||
if (decorationContext == DECORATION_CONTEXT::MPU) {
|
||||
if (wrapInGroups) {
|
||||
// Create a group node
|
||||
const auto groupName = GroupId(m_type);
|
||||
auto groupNode = themeNode.model()->createModelNode("QtObject");
|
||||
auto groupProperty = themeNode.nodeProperty(groupName);
|
||||
NodeProperty groupProperty = themeNode.nodeProperty(groupName);
|
||||
|
||||
if (!groupProperty || !typeName || !groupNode) {
|
||||
qCDebug(dsLog) << "Adding group node failed." << groupName << theme;
|
||||
return;
|
||||
}
|
||||
groupProperty.setDynamicTypeNameAndsetModelNode("QtObject", groupNode);
|
||||
targetNode = &groupNode;
|
||||
targetNode = groupNode;
|
||||
}
|
||||
|
||||
// Add properties
|
||||
for (auto itr = m_values.begin(); itr != m_values.end(); ++itr) {
|
||||
auto &[propName, values] = *itr;
|
||||
for (auto &[propName, values] : m_values) {
|
||||
auto themeValue = values.find(theme);
|
||||
if (themeValue != values.end()) {
|
||||
auto &propData = themeValue->second;
|
||||
if (propData.isBinding) {
|
||||
auto bindingProp = targetNode->bindingProperty(propName);
|
||||
if (!bindingProp)
|
||||
continue;
|
||||
if (themeValue != values.end())
|
||||
addProperty(targetNode, propName, themeValue->second);
|
||||
}
|
||||
}
|
||||
|
||||
if (decorationContext == DECORATION_CONTEXT::MCU)
|
||||
bindingProp.setExpression(propData.value.toString());
|
||||
void DSThemeGroup::decorateComponent(ModelNode node)
|
||||
{
|
||||
const auto typeName = groupTypeName(m_type);
|
||||
// Add properties with type to the node
|
||||
for (auto &[propName, values] : m_values) {
|
||||
auto nodeProp = node.variantProperty(propName);
|
||||
nodeProp.setDynamicTypeNameAndValue(*typeName, nodeProp.value());
|
||||
}
|
||||
}
|
||||
|
||||
void DSThemeGroup::addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data) const
|
||||
{
|
||||
auto metaInfo = n.model()->metaInfo(n.type());
|
||||
const bool propDefined = metaInfo.property(propName).isValid();
|
||||
|
||||
const auto typeName = groupTypeName(m_type);
|
||||
if (data.isBinding) {
|
||||
if (propDefined)
|
||||
n.bindingProperty(propName).setExpression(data.value.toString());
|
||||
else if (auto bindingProp = n.bindingProperty(propName))
|
||||
bindingProp.setDynamicTypeNameAndExpression(*typeName, data.value.toString());
|
||||
else
|
||||
bindingProp.setDynamicTypeNameAndExpression(*typeName, propData.value.toString());
|
||||
|
||||
qCDebug(dsLog) << "Assigning invalid binding" << propName << n.id();
|
||||
} else {
|
||||
auto nodeProp = targetNode->variantProperty(propName);
|
||||
if (!nodeProp)
|
||||
continue;
|
||||
|
||||
if (decorationContext == DECORATION_CONTEXT::MCU)
|
||||
nodeProp.setValue(propData.value);
|
||||
if (propDefined)
|
||||
n.variantProperty(propName).setValue(data.value);
|
||||
else if (auto nodeProp = n.variantProperty(propName))
|
||||
nodeProp.setDynamicTypeNameAndValue(*typeName, data.value);
|
||||
else
|
||||
nodeProp.setDynamicTypeNameAndValue(*typeName, propData.value);
|
||||
qCDebug(dsLog) << "Assigning invalid variant property" << propName << n.id();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -12,12 +12,6 @@
|
||||
#include <optional>
|
||||
|
||||
namespace QmlDesigner {
|
||||
enum class DECORATION_CONTEXT {
|
||||
MCU,
|
||||
MPU,
|
||||
COMPONENT_THEME,
|
||||
};
|
||||
|
||||
class DESIGNSYSTEM_EXPORT DSThemeGroup
|
||||
{
|
||||
struct PropertyData
|
||||
@@ -46,8 +40,6 @@ public:
|
||||
void updateProperty(ThemeId theme, PropertyName newName, const ThemeProperty &prop);
|
||||
void removeProperty(const PropertyName &name);
|
||||
|
||||
GroupType type() const { return m_type; }
|
||||
|
||||
size_t count(ThemeId theme) const;
|
||||
size_t count() const;
|
||||
bool isEmpty() const;
|
||||
@@ -55,7 +47,11 @@ public:
|
||||
void removeTheme(ThemeId theme);
|
||||
|
||||
void duplicateValues(ThemeId from, ThemeId to);
|
||||
void decorate(ThemeId theme, ModelNode themeNode, DECORATION_CONTEXT decorationContext);
|
||||
void decorate(ThemeId theme, ModelNode themeNode, bool wrapInGroups = true);
|
||||
void decorateComponent(ModelNode node);
|
||||
|
||||
private:
|
||||
void addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data) const;
|
||||
|
||||
private:
|
||||
const GroupType m_type;
|
||||
|
@@ -5,21 +5,39 @@
|
||||
|
||||
#include "dsconstants.h"
|
||||
#include "dsthemegroup.h"
|
||||
#include "variantproperty.h"
|
||||
|
||||
#include <nodeproperty.h>
|
||||
#include <model.h>
|
||||
#include <nodeproperty.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QVariant>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg)
|
||||
|
||||
std::optional<QmlDesigner::GroupType> typeToGroupType(const QmlDesigner::TypeName type)
|
||||
{
|
||||
if (type == "color")
|
||||
return QmlDesigner::GroupType::Colors;
|
||||
if (type == "bool")
|
||||
return QmlDesigner::GroupType::Flags;
|
||||
if (type == "real")
|
||||
return QmlDesigner::GroupType::Numbers;
|
||||
if (type == "string")
|
||||
return QmlDesigner::GroupType::Strings;
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
DSThemeManager::DSThemeManager() {}
|
||||
DSThemeManager::DSThemeManager()
|
||||
{}
|
||||
|
||||
DSThemeManager::~DSThemeManager() {}
|
||||
|
||||
@@ -31,7 +49,8 @@ std::optional<ThemeId> DSThemeManager::addTheme(const ThemeName &themeName)
|
||||
}
|
||||
|
||||
const ThemeId newThemeId = m_themes.empty() ? 1 : m_themes.rbegin()->first + 1;
|
||||
m_themes.insert({newThemeId, themeName});
|
||||
if (!m_themes.try_emplace(newThemeId, themeName).second)
|
||||
return {};
|
||||
|
||||
// Copy the new theme properties from an old theme(first one).
|
||||
if (m_themes.size() > 1)
|
||||
@@ -59,16 +78,16 @@ void DSThemeManager::removeTheme(ThemeId id)
|
||||
if (!m_themes.contains(id))
|
||||
return;
|
||||
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr)
|
||||
groupItr->second->removeTheme(id);
|
||||
for (auto &[gt, group] : m_groups)
|
||||
group->removeTheme(id);
|
||||
|
||||
m_themes.erase(id);
|
||||
}
|
||||
|
||||
void DSThemeManager::duplicateTheme(ThemeId from, ThemeId to)
|
||||
{
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr)
|
||||
groupItr->second->duplicateValues(from, to);
|
||||
for (auto &[gt, group] : m_groups)
|
||||
group->duplicateValues(from, to);
|
||||
}
|
||||
|
||||
std::optional<ThemeProperty> DSThemeManager::property(ThemeId themeId,
|
||||
@@ -88,7 +107,7 @@ std::optional<ThemeProperty> DSThemeManager::property(ThemeId themeId,
|
||||
bool DSThemeManager::addProperty(GroupType gType, const ThemeProperty &p)
|
||||
{
|
||||
if (!m_themes.size()) {
|
||||
qCDebug(dsLog) << "Can not add proprty. Themes empty";
|
||||
qCDebug(dsLog) << "Can not add property. Themes empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -141,44 +160,41 @@ void DSThemeManager::decorate(ModelNode rootNode, const QByteArray &nodeType, bo
|
||||
addGroupAliases(rootNode);
|
||||
auto model = rootNode.model();
|
||||
|
||||
for (auto itr = m_themes.begin(); itr != m_themes.end(); ++itr) {
|
||||
for (auto &[themeId, themeName] : m_themes) {
|
||||
auto themeNode = model->createModelNode(nodeType);
|
||||
auto themeProperty = model->rootModelNode().nodeProperty(itr->second);
|
||||
auto themeProperty = model->rootModelNode().nodeProperty(themeName);
|
||||
themeProperty.setDynamicTypeNameAndsetModelNode(nodeType, themeNode);
|
||||
|
||||
// Add property groups
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr)
|
||||
groupItr->second->decorate(itr->first, themeNode, isMCU ? DECORATION_CONTEXT::MCU : DECORATION_CONTEXT::MPU);
|
||||
for (auto &[gt, group] : m_groups)
|
||||
group->decorate(themeId, themeNode, !isMCU);
|
||||
}
|
||||
}
|
||||
|
||||
void DSThemeManager::decorateThemeComponent(ModelNode rootNode) const
|
||||
void DSThemeManager::decorateThemeInterface(ModelNode rootNode) const
|
||||
{
|
||||
if (!m_themes.size())
|
||||
return;
|
||||
|
||||
auto itr = m_themes.begin();
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr)
|
||||
groupItr->second->decorate(itr->first, rootNode, DECORATION_CONTEXT::COMPONENT_THEME);
|
||||
for (auto &[gt, group] : m_groups)
|
||||
group->decorateComponent(rootNode);
|
||||
}
|
||||
|
||||
DSThemeGroup *DSThemeManager::propertyGroup(GroupType type)
|
||||
{
|
||||
auto itr = m_groups.find(type);
|
||||
if (itr == m_groups.end())
|
||||
itr = m_groups.insert({type, std::make_unique<DSThemeGroup>(type)}).first;
|
||||
itr = m_groups.try_emplace(type, std::make_unique<DSThemeGroup>(type)).first;
|
||||
|
||||
return itr->second.get();
|
||||
}
|
||||
|
||||
void DSThemeManager::addGroupAliases(ModelNode rootNode) const
|
||||
{
|
||||
QSet<PropertyName> groupNames;
|
||||
for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) {
|
||||
DSThemeGroup *group = groupItr->second.get();
|
||||
const PropertyName groupName = GroupId(group->type());
|
||||
std::set<PropertyName> groupNames;
|
||||
for (auto &[groupType, group] : m_groups) {
|
||||
if (group->count())
|
||||
groupNames.insert(groupName);
|
||||
groupNames.emplace(GroupId(groupType));
|
||||
}
|
||||
|
||||
for (const auto &name : groupNames) {
|
||||
@@ -187,4 +203,103 @@ void DSThemeManager::addGroupAliases(ModelNode rootNode) const
|
||||
p.setDynamicTypeNameAndExpression("QtObject", binding);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<QString> DSThemeManager::load(ModelNode rootModelNode)
|
||||
{
|
||||
// We need all properties under the theme node and its child nodes.
|
||||
// The properties must have a unique name.
|
||||
auto propWithSameName = [](const AbstractProperty &p1, const AbstractProperty &p2) {
|
||||
return p1.name() == p2.name();
|
||||
};
|
||||
using PropMap = std::set<AbstractProperty, decltype(propWithSameName)>;
|
||||
using ThemeProps = std::map<ThemeId, PropMap>;
|
||||
auto getAllProps = [](const ModelNode &n) -> PropMap {
|
||||
PropMap props;
|
||||
auto nodesUnderTheme = n.allSubModelNodesAndThisNode();
|
||||
for (auto &n : nodesUnderTheme) {
|
||||
for (const AbstractProperty &p : n.properties()) {
|
||||
if (!props.insert(p).second)
|
||||
qCDebug(dsLog) << "Duplicate Property, Skipping" << n << p;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
};
|
||||
|
||||
// First level child nodes are assumed to be the theme nodes.
|
||||
QList<NodeProperty> themes = rootModelNode.nodeProperties();
|
||||
if (themes.isEmpty())
|
||||
return tr("No themes objects in the collection.");
|
||||
|
||||
// Collect properties under each theme node.
|
||||
ThemeProps themeProps;
|
||||
for (auto &themeNodeProp : themes) {
|
||||
ModelNode themeNode = themeNodeProp.modelNode();
|
||||
if (auto themeId = addTheme(themeNodeProp.name().toByteArray()))
|
||||
themeProps.insert({*themeId, getAllProps(themeNode)});
|
||||
}
|
||||
|
||||
// Get properties from the first theme. We expect other theme nodes
|
||||
// have prpperties with same name and type. If not we don't consider those properties.
|
||||
auto themeItr = themeProps.begin();
|
||||
// Add default properties
|
||||
const PropMap &baseProps = themeItr->second;
|
||||
for (const AbstractProperty &baseNodeProp : baseProps) {
|
||||
GroupType basePropGroupType;
|
||||
ThemeProperty basethemeProp;
|
||||
if (findPropertyType(baseNodeProp, &basethemeProp, &basePropGroupType))
|
||||
addProperty(basePropGroupType, basethemeProp);
|
||||
else
|
||||
continue;
|
||||
|
||||
// Update values for rest of the themes.
|
||||
for (auto otherTheme = std::next(themeItr); otherTheme != themeProps.end(); ++otherTheme) {
|
||||
const PropMap &otherThemeProps = otherTheme->second;
|
||||
auto otherThemePropItr = otherThemeProps.find(baseNodeProp);
|
||||
if (otherThemePropItr == otherThemeProps.end()) {
|
||||
qCDebug(dsLog) << "Can't find expected prop" << baseNodeProp.name() << "in theme"
|
||||
<< otherTheme->first;
|
||||
continue;
|
||||
}
|
||||
|
||||
GroupType otherGroup;
|
||||
ThemeProperty otherThemeProp;
|
||||
if (findPropertyType(*otherThemePropItr, &otherThemeProp, &otherGroup)
|
||||
&& otherGroup == basePropGroupType) {
|
||||
updateProperty(otherTheme->first, basePropGroupType, otherThemeProp);
|
||||
} else {
|
||||
qCDebug(dsLog) << "Incompatible property" << baseNodeProp.name()
|
||||
<< " found in theme" << otherTheme->first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DSThemeManager::findPropertyType(const AbstractProperty &p,
|
||||
ThemeProperty *themeProp,
|
||||
GroupType *gt) const
|
||||
{
|
||||
auto group = typeToGroupType(p.dynamicTypeName());
|
||||
if (!group) {
|
||||
qCDebug(dsLog) << "Can't find suitable group for the property" << p.name();
|
||||
return false;
|
||||
}
|
||||
|
||||
*gt = *group;
|
||||
PropertyName pName = p.name().toByteArray();
|
||||
if (auto variantProp = p.toVariantProperty()) {
|
||||
themeProp->value = variantProp.value();
|
||||
themeProp->isBinding = false;
|
||||
} else if (auto binding = p.toBindingProperty()) {
|
||||
themeProp->value = binding.expression();
|
||||
themeProp->isBinding = true;
|
||||
} else {
|
||||
qCDebug(dsLog) << "Property type not supported for design system" << pName;
|
||||
return false;
|
||||
}
|
||||
|
||||
themeProp->name = pName;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,11 @@
|
||||
#include "dsconstants.h"
|
||||
#include "dsthemegroup.h"
|
||||
|
||||
#include <externaldependenciesinterface.h>
|
||||
#include <modelnode.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
using ThemeName = PropertyName;
|
||||
@@ -18,6 +21,7 @@ class DSTheme;
|
||||
|
||||
class DESIGNSYSTEM_EXPORT DSThemeManager
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(DSThemeManager);
|
||||
|
||||
public:
|
||||
DSThemeManager();
|
||||
@@ -27,7 +31,7 @@ public:
|
||||
DSThemeManager& operator=(const DSThemeManager&) = delete;
|
||||
|
||||
DSThemeManager(DSThemeManager&&) = default;
|
||||
DSThemeManager& operator=(DSThemeManager&&) = default;
|
||||
DSThemeManager &operator=(DSThemeManager &&) = default;
|
||||
|
||||
std::optional<ThemeId> addTheme(const ThemeName &themeName);
|
||||
std::optional<ThemeId> themeId(const ThemeName &themeName) const;
|
||||
@@ -44,13 +48,17 @@ public:
|
||||
void updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p);
|
||||
void updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p, const PropertyName &newName);
|
||||
|
||||
void decorate(ModelNode rootNode, const QByteArray& nodeType, bool isMCU) const;
|
||||
void decorateThemeComponent(ModelNode rootNode) const;
|
||||
void decorate(ModelNode rootNode, const QByteArray &nodeType = "QtObject", bool isMCU = false) const;
|
||||
void decorateThemeInterface(ModelNode rootNode) const;
|
||||
|
||||
std::optional<QString> load(ModelNode rootModelNode);
|
||||
|
||||
private:
|
||||
DSThemeGroup *propertyGroup(GroupType type);
|
||||
void addGroupAliases(ModelNode rootNode) const;
|
||||
|
||||
bool findPropertyType(const AbstractProperty &p, ThemeProperty *themeProp, GroupType *gt) const;
|
||||
|
||||
private:
|
||||
std::map<ThemeId, ThemeName> m_themes;
|
||||
std::map<GroupType, std::unique_ptr<DSThemeGroup>> m_groups;
|
||||
|
Reference in New Issue
Block a user