From 5334158bebbab9911ce86a9c1a9daa237ee47e3a Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Fri, 17 May 2024 14:27:48 +0200 Subject: [PATCH] QmlDesigner: Add headless QML theme generator Task-number: QDS-11956 Change-Id: Ibc8058dd5e8ac5f8363efdd9007d267ce2bf9685 Reviewed-by: Thomas Hartmann --- src/plugins/qmldesigner/CMakeLists.txt | 9 + .../components/designsystem/dsconstants.h | 33 ++++ .../components/designsystem/dsthemegroup.cpp | 184 ++++++++++++++++++ .../components/designsystem/dsthemegroup.h | 51 +++++ .../designsystem/dsthememanager.cpp | 178 +++++++++++++++++ .../components/designsystem/dsthememanager.h | 50 +++++ 6 files changed, 505 insertions(+) create mode 100644 src/plugins/qmldesigner/components/designsystem/dsconstants.h create mode 100644 src/plugins/qmldesigner/components/designsystem/dsthemegroup.cpp create mode 100644 src/plugins/qmldesigner/components/designsystem/dsthemegroup.h create mode 100644 src/plugins/qmldesigner/components/designsystem/dsthememanager.cpp create mode 100644 src/plugins/qmldesigner/components/designsystem/dsthememanager.h diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index add610e0268..96feb7eac9d 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -516,6 +516,7 @@ add_qtc_plugin(QmlDesigner ${CMAKE_CURRENT_LIST_DIR}/components/propertyeditor ${CMAKE_CURRENT_LIST_DIR}/components/stateseditor ${CMAKE_CURRENT_LIST_DIR}/components/texteditor + ${CMAKE_CURRENT_LIST_DIR}/components/designsystem PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/designercore #can not be a public dependency -> EXCLUDE_FROM_INSTALL in QmlDesignerCore @@ -1157,6 +1158,14 @@ extend_qtc_plugin(QmlDesigner toolbarbackend.h ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/designsystem + SOURCES + dsconstants.h + dsthememanager.h dsthememanager.cpp + dsthemegroup.cpp dsthemegroup.h +) + add_qtc_plugin(assetexporterplugin PLUGIN_CLASS AssetExporterPlugin CONDITION TARGET QmlDesigner diff --git a/src/plugins/qmldesigner/components/designsystem/dsconstants.h b/src/plugins/qmldesigner/components/designsystem/dsconstants.h new file mode 100644 index 00000000000..73474b4d36d --- /dev/null +++ b/src/plugins/qmldesigner/components/designsystem/dsconstants.h @@ -0,0 +1,33 @@ +// 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 "nodeinstanceglobal.h" +namespace QmlDesigner +{ + +using ThemeId = ushort; + +enum class GroupType { Colors, Flags, Numbers, Strings }; + +class ThemeProperty +{ +public: + bool isValid() const { return !name.trimmed().isEmpty() && value.isValid(); } + + PropertyName name; + QVariant value; + bool isBinding = false; +}; + +constexpr const char *GroupId(const GroupType type) { + if (type == GroupType::Colors) return "colors"; + if (type == GroupType::Flags) return "flags"; + if (type == GroupType::Numbers) return "numbers"; + if (type == GroupType::Strings) return "strings"; + + return "unknown"; +} + +} diff --git a/src/plugins/qmldesigner/components/designsystem/dsthemegroup.cpp b/src/plugins/qmldesigner/components/designsystem/dsthemegroup.cpp new file mode 100644 index 00000000000..07c7a868008 --- /dev/null +++ b/src/plugins/qmldesigner/components/designsystem/dsthemegroup.cpp @@ -0,0 +1,184 @@ +// 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 "dsthemegroup.h" + +#include +#include +#include +#include + +#include + +namespace QmlDesigner { +using namespace std; + +namespace { +Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg) + +std::optional groupTypeName(GroupType type) +{ + switch (type) { + case QmlDesigner::GroupType::Colors: return "color"; break; + case QmlDesigner::GroupType::Flags: return "bool"; break; + case QmlDesigner::GroupType::Numbers: return "real"; break; + case QmlDesigner::GroupType::Strings: return "string"; break; + } + return {}; +} + +QDebug &operator<<(QDebug &s, const ThemeProperty &p) +{ + s << "{Name:" << p.name << ", Value:" << p.value << ", isBinding:" << p.isBinding << "}"; + return s; +} +} + +DSThemeGroup::DSThemeGroup(GroupType type): + m_type(type) +{} + +DSThemeGroup::~DSThemeGroup() {} + +bool DSThemeGroup::addProperty(ThemeId theme, const ThemeProperty &prop) +{ + if (!prop.isValid()) { + qCDebug(dsLog) << "Add property failed. Invalid property." << prop; + return false; + } + + if (!m_values.contains(prop.name)) + m_values[prop.name] = make_unique(); + + const auto &tValues = m_values.at(prop.name); + if (tValues->contains(theme)) { + qCDebug(dsLog) << "Add property failed. Duplicate property name." << prop; + return false; + } + + tValues->insert({theme, {prop.value, prop.isBinding}}); + return true; +} + +std::optional DSThemeGroup::propertyValue(ThemeId theme, const PropertyName &name) const +{ + if (!m_values.contains(name)) + return {}; + + const auto &tValues = m_values.at(name); + const auto itr = tValues->find(theme); + if (itr != tValues->end()) { + auto &[value, isBindind] = itr->second; + return ThemeProperty{name, value, isBindind}; + } + return {}; +} + +void DSThemeGroup::updateProperty(ThemeId theme, PropertyName newName, const ThemeProperty &prop) +{ + if (!m_values.contains(prop.name)) { + qCDebug(dsLog) << "Property update failure. Can't find property" << prop; + return; + } + + if (!ThemeProperty{newName, prop.value, prop.isBinding}.isValid()) { + qCDebug(dsLog) << "Property update failure. Invalid property" << prop << newName; + return; + } + + if (newName != prop.name && m_values.contains(newName)) { + qCDebug(dsLog) << "Property update failure. Property name update already exists" << newName + << prop; + return; + } + + auto &tValues = m_values.at(prop.name); + const auto itr = tValues->find(theme); + if (itr == tValues->end()) { + qCDebug(dsLog) << "Property update failure. No property for the theme" << theme << prop; + return; + } + + tValues->at(theme) = {prop.value, prop.isBinding}; + if (newName != prop.name) { + m_values[newName] = std::move(tValues); + m_values.erase(prop.name); + } +} + +void DSThemeGroup::removeProperty(const PropertyName &name) +{ + m_values.erase(name); +} + +size_t DSThemeGroup::count(ThemeId theme) const +{ + return std::accumulate(m_values.cbegin(), + m_values.cend(), + 0, + [theme](size_t c, const GroupProperties::value_type &p) { + return c + (p.second->contains(theme) ? 1 : 0); + }); +} + +size_t DSThemeGroup::count() const +{ + return m_values.size(); +} + +void DSThemeGroup::removeTheme(ThemeId theme) +{ + for (auto itr = m_values.cbegin(); itr != m_values.cend();) { + itr->second->erase(theme); + itr = itr->second->size() == 0 ? m_values.erase(itr) : std::next(itr); + } +} + +void DSThemeGroup::duplicateValues(ThemeId from, ThemeId to) +{ + for (auto itr = m_values.cbegin(); itr != m_values.cend(); ++itr) { + const auto &[propName, values] = *itr; + if (!values) + continue; + + auto fromValueItr = values->find(from); + if (fromValueItr != values->end()) + (*values)[to] = fromValueItr->second; + } +} + +void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode) +{ + if (!count(theme)) + return; // No props for this theme in this group. + + const auto groupName = GroupId(m_type); + const auto typeName = groupTypeName(m_type); + auto groupNode = themeNode.model()->createModelNode("QtObject"); + auto groupProperty = themeNode.nodeProperty(groupName); + + if (!groupProperty || !typeName || !groupNode) { + qCDebug(dsLog) << "Adding group node failed." << groupName << theme; + return; + } + + for (auto itr = m_values.cbegin(); itr != m_values.cend(); ++itr) { + const auto &[propName, values] = *itr; + auto themeValue = values->find(theme); + if (themeValue != values->end()) { + const auto &propData = themeValue->second; + if (propData.isBinding) { + auto bindingProp = groupNode.bindingProperty(propName); + if (bindingProp) + bindingProp.setDynamicTypeNameAndExpression(*typeName, propData.value.toString()); + } else { + auto nodeProp = groupNode.variantProperty(propName); + if (nodeProp) + nodeProp.setDynamicTypeNameAndValue(*typeName, propData.value); + } + } + } + + groupProperty.setDynamicTypeNameAndsetModelNode("QtObject", groupNode); +} +} diff --git a/src/plugins/qmldesigner/components/designsystem/dsthemegroup.h b/src/plugins/qmldesigner/components/designsystem/dsthemegroup.h new file mode 100644 index 00000000000..d1a80d6c9af --- /dev/null +++ b/src/plugins/qmldesigner/components/designsystem/dsthemegroup.h @@ -0,0 +1,51 @@ +// 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 "dsconstants.h" +#include "nodeinstanceglobal.h" +#include + +#include +#include + +namespace QmlDesigner { + +class DSThemeGroup +{ + struct PropertyData + { + QVariant value; + bool isBinding = false; + }; + + using ThemeValues = std::map; + using GroupProperties = std::map>; + +public: + DSThemeGroup(GroupType type); + ~DSThemeGroup(); + + bool addProperty(ThemeId, const ThemeProperty &prop); + std::optional propertyValue(ThemeId theme, const PropertyName &name) const; + + 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; + + void removeTheme(ThemeId theme); + + void duplicateValues(ThemeId from, ThemeId to); + void decorate(ThemeId theme, ModelNode themeNode); + +private: + const GroupType m_type; + GroupProperties m_values; +}; +} diff --git a/src/plugins/qmldesigner/components/designsystem/dsthememanager.cpp b/src/plugins/qmldesigner/components/designsystem/dsthememanager.cpp new file mode 100644 index 00000000000..3fd393d2f80 --- /dev/null +++ b/src/plugins/qmldesigner/components/designsystem/dsthememanager.cpp @@ -0,0 +1,178 @@ +// 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 "dsthememanager.h" + +#include "dsconstants.h" +#include "dsthemegroup.h" + +#include +#include +#include + +#include + +namespace { +Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg) +} + +namespace QmlDesigner { + +DSThemeManager::DSThemeManager() {} + +DSThemeManager::~DSThemeManager() {} + +std::optional DSThemeManager::addTheme(const ThemeName &themeName) +{ + if (themeName.trimmed().isEmpty() || themeId(themeName)) { + qCDebug(dsLog) << "Can not add new Theme. Duplicate theme name"; + return {}; + } + + const ThemeId newThemeId = m_themes.empty() ? 1 : m_themes.rbegin()->first + 1; + m_themes.insert({newThemeId, themeName}); + + // Copy the new theme properties from an old theme(first one). + if (m_themes.size() > 1) + duplicateTheme(m_themes.begin()->first, newThemeId); + + return newThemeId; +} + +std::optional DSThemeManager::themeId(const ThemeName &themeName) const +{ + for (auto &[id, name] : m_themes) { + if (themeName == name) + return id; + } + return {}; +} + +size_t DSThemeManager::themeCount() const +{ + return m_themes.size(); +} + +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); + + 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); +} + +std::optional DSThemeManager::property(ThemeId themeId, + GroupType gType, + const PropertyName &name) const +{ + if (m_themes.contains(themeId)) { + auto groupItr = m_groups.find(gType); + if (groupItr != m_groups.end()) + return groupItr->second->propertyValue(themeId, name); + } + + qCDebug(dsLog) << "Error fetching property: {" << themeId << GroupId(gType) << name << "}"; + return {}; +} + +bool DSThemeManager::addProperty(GroupType gType, const ThemeProperty &p) +{ + if (!m_themes.size()) { + qCDebug(dsLog) << "Can not add proprty. Themes empty"; + return false; + } + + // A property is added to all themes. + DSThemeGroup *dsGroup = propertyGroup(gType); + QTC_ASSERT(dsGroup, return false); + + bool success = true; + for (auto itr = m_themes.begin(); itr != m_themes.end(); ++itr) + success &= dsGroup->addProperty(itr->first, p); + + return success; +} + +void DSThemeManager::removeProperty(GroupType gType, const PropertyName &name) +{ + // A property is removed from all themes. + DSThemeGroup *dsGroup = propertyGroup(gType); + QTC_ASSERT(dsGroup, return); + dsGroup->removeProperty(name); +} + +void DSThemeManager::updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p) +{ + updateProperty(id, gType, p, p.name); +} + +void DSThemeManager::updateProperty(ThemeId id, + GroupType gType, + const ThemeProperty &p, + const PropertyName &newName) +{ + if (!m_themes.contains(id)) + return; + + DSThemeGroup *dsGroup = propertyGroup(gType); + QTC_ASSERT(dsGroup, return); + + dsGroup->updateProperty(id, newName, p); +} + +void DSThemeManager::decorate(ModelNode rootNode) const +{ + if (!m_themes.size()) + return; + + auto p = rootNode.bindingProperty("currentTheme"); + p.setDynamicTypeNameAndExpression("QtObject", QString::fromLatin1(m_themes.begin()->second)); + addGroupAliases(rootNode); + + auto model = rootNode.model(); + for (auto itr = m_themes.begin(); itr != m_themes.end(); ++itr) { + auto themeNode = model->createModelNode("QtObject"); + auto themeProperty = model->rootModelNode().nodeProperty(itr->second); + themeProperty.setDynamicTypeNameAndsetModelNode("QtObject", themeNode); + + // Add property groups + for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) + groupItr->second->decorate(itr->first, themeNode); + } +} + +DSThemeGroup *DSThemeManager::propertyGroup(GroupType type) +{ + auto itr = m_groups.find(type); + if (itr == m_groups.end()) + itr = m_groups.insert({type, std::make_unique(type)}).first; + + return itr->second.get(); +} + +void DSThemeManager::addGroupAliases(ModelNode rootNode) const +{ + QSet groupNames; + for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) { + DSThemeGroup *group = groupItr->second.get(); + const PropertyName groupName = GroupId(group->type()); + if (group->count()) + groupNames.insert(groupName); + } + + for (const auto &name : groupNames) { + auto p = rootNode.bindingProperty(name); + auto binding = QString("currentTheme.%1").arg(QString::fromLatin1(name)); + p.setDynamicTypeNameAndExpression("QtObject", binding); + } +} +} diff --git a/src/plugins/qmldesigner/components/designsystem/dsthememanager.h b/src/plugins/qmldesigner/components/designsystem/dsthememanager.h new file mode 100644 index 00000000000..afc5325e9d6 --- /dev/null +++ b/src/plugins/qmldesigner/components/designsystem/dsthememanager.h @@ -0,0 +1,50 @@ +// 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 + +#include "dsconstants.h" + +#include + +namespace QmlDesigner { +class DSThemeGroup; + +using ThemeName = PropertyName; + +class DSTheme; +class QMLDESIGNERCORE_EXPORT DSThemeManager +{ + +public: + DSThemeManager(); + ~DSThemeManager(); + + std::optional addTheme(const ThemeName &themeName); + std::optional themeId(const ThemeName &themeName) const; + void removeTheme(ThemeId id); + size_t themeCount() const; + + void duplicateTheme(ThemeId from, ThemeId to); + + bool addProperty(GroupType gType, const ThemeProperty &p); + std::optional property(ThemeId themeId, + GroupType gType, + const PropertyName &name) const; + void removeProperty(GroupType gType, const PropertyName &p); + 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; + +private: + DSThemeGroup *propertyGroup(GroupType type); + void addGroupAliases(ModelNode rootNode) const; + +private: + std::map m_themes; + std::map> m_groups; +}; +}