QmlDesigner: Add headless QML theme generator

Task-number: QDS-11956
Change-Id: Ibc8058dd5e8ac5f8363efdd9007d267ce2bf9685
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Vikas Pachdha
2024-05-17 14:27:48 +02:00
parent 3fd293a567
commit 5334158beb
6 changed files with 505 additions and 0 deletions

View File

@@ -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

View File

@@ -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";
}
}

View File

@@ -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 <model.h>
#include <nodeproperty.h>
#include <variantproperty.h>
#include <utils/qtcassert.h>
#include <QLoggingCategory>
namespace QmlDesigner {
using namespace std;
namespace {
Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg)
std::optional<TypeName> 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<ThemeValues>();
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<ThemeProperty> 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);
}
}

View File

@@ -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 <modelnode.h>
#include <map>
#include <optional>
namespace QmlDesigner {
class DSThemeGroup
{
struct PropertyData
{
QVariant value;
bool isBinding = false;
};
using ThemeValues = std::map<ThemeId, PropertyData>;
using GroupProperties = std::map<PropertyName, std::unique_ptr<ThemeValues>>;
public:
DSThemeGroup(GroupType type);
~DSThemeGroup();
bool addProperty(ThemeId, const ThemeProperty &prop);
std::optional<ThemeProperty> 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;
};
}

View File

@@ -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 <nodeproperty.h>
#include <model.h>
#include <utils/qtcassert.h>
#include <QLoggingCategory>
namespace {
Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg)
}
namespace QmlDesigner {
DSThemeManager::DSThemeManager() {}
DSThemeManager::~DSThemeManager() {}
std::optional<ThemeId> 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<ThemeId> 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<ThemeProperty> 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<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());
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);
}
}
}

View File

@@ -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 <qmldesignercorelib_global.h>
#include "dsconstants.h"
#include <modelnode.h>
namespace QmlDesigner {
class DSThemeGroup;
using ThemeName = PropertyName;
class DSTheme;
class QMLDESIGNERCORE_EXPORT DSThemeManager
{
public:
DSThemeManager();
~DSThemeManager();
std::optional<ThemeId> addTheme(const ThemeName &themeName);
std::optional<ThemeId> 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<ThemeProperty> 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<ThemeId, ThemeName> m_themes;
std::map<GroupType, std::unique_ptr<DSThemeGroup>> m_groups;
};
}