// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqstylekitdebug_p.h" #include "qqstylekitcontrol_p.h" #include "qqstylekitcontrols_p.h" #include "qqstylekitstyle_p.h" #include "qqstylekittheme_p.h" QT_BEGIN_NAMESPACE const QQStyleKitPropertyGroup *QQStyleKitDebug::groupBeingRead = nullptr; QPointer QQStyleKitDebug::m_item; QString QQStyleKitDebug::m_filter; int QQStyleKitDebug::m_outputCount = 0; static const QChar kDot = '.'_L1; template QString QQStyleKitDebug::enumToString(EnumType enumValue) { auto propertyMetaEnum = QMetaEnum::fromType(); return QString::fromUtf8(propertyMetaEnum.valueToKeys(quint64(enumValue))); } QString QQStyleKitDebug::objectName(const QObject *obj) { QString str = QString::fromLatin1(obj->metaObject()->className()); int idx = str.indexOf("_QMLTYPE"_L1); if (idx != -1) str = str.left(idx); else { const QString prefix("QQStyleKit"_L1); if (str.startsWith(prefix)) str = str.mid(prefix.length()); } const QString name = obj->objectName(); if (!name.isEmpty()) str = str + "("_L1 + name + ")"_L1; return str; } QString QQStyleKitDebug::stateToString(const QQSK::State state) { const QStringList list = enumToString(state).split('|'_L1); return "["_L1 + list.join(','_L1) + "]"_L1; } QString QQStyleKitDebug::styleReaderToString(const QQStyleKitReader *reader) { return "StyleKitReader"_L1 + stateToString(reader->controlState()); } QString QQStyleKitDebug::propertyPath(const QQStyleKitPropertyGroup *group, const PropertyPathId property) { QString path = enumToString(property.property()); path[0] = path[0].toLower(); const QQStyleKitPropertyGroup *childGroup = group; const int startIndex = QQStyleKitPropertyGroup::staticMetaObject.propertyOffset(); while (childGroup) { if (childGroup->isControlProperties()) break; const QQStyleKitPropertyGroup *parentGroup = qobject_cast(childGroup->parent()); if (!parentGroup) break; // Resolve group name by inspecting which property in the parent group it belongs to const QMetaObject* parentMeta = parentGroup->metaObject(); for (int i = startIndex; i < parentMeta->propertyCount(); ++i) { const QMetaProperty prop = parentMeta->property(i); const QMetaObject* typeMeta = QMetaType::fromName(prop.typeName()).metaObject(); if (!typeMeta || !typeMeta->inherits(&QQStyleKitPropertyGroup::staticMetaObject)) continue; QObject *propGroup = prop.read(parentGroup).value(); if (propGroup == childGroup) { path.prepend(QString::fromUtf8(prop.name()) + kDot); break; } } childGroup = parentGroup; } return path; } QString QQStyleKitDebug::controlToString(const QQStyleKitControlProperties *control) { const QObject *parentObj = control->parent(); if (!parentObj) return ""_L1; auto *controls = qobject_cast(parentObj); if (!controls) { return "<"_L1 + QString::fromUtf8(parentObj->metaObject()->className()) + ">"_L1; } const int startIndex = QQStyleKitControlProperties::staticMetaObject.propertyOffset(); const int endIndex = QQStyleKitControlProperties::staticMetaObject.propertyCount(); const QMetaObject* parentMeta = parentObj->metaObject(); for (int i = startIndex; i < endIndex; ++i) { const QMetaProperty prop = parentMeta->property(i); const QMetaObject* typeMeta = QMetaType::fromName(prop.typeName()).metaObject(); if (!typeMeta || !typeMeta->inherits(&QQStyleKitControl::staticMetaObject)) continue; QObject *propObj = prop.read(parentObj).value(); if (propObj == control) return QString::fromUtf8(prop.name()); } return ""_L1; } QString QQStyleKitDebug::objectPath(const QQStyleKitControlProperties *properties, QObject *from) { QString path; const QObject *obj = properties; while (obj) { if (!path.isEmpty()) path.prepend(kDot); if (auto *theme = qobject_cast(obj)) { path.prepend(theme->name() + kDot); } else if (auto *theme = qobject_cast(obj)) { // Note: only one theme is instantiated at a time if (auto style = theme->style()) path.prepend(style->themeName()); else path.prepend(objectName(obj)); } else if (auto *control = qobject_cast(obj)) { path.prepend(controlToString(control)); } else if (auto *reader = qobject_cast(obj)) { path.prepend(styleReaderToString(reader)); } else { path.prepend(objectName(obj)); } if (obj == from) break; obj = obj->parent(); } return path; } void QQStyleKitDebug::notifyPropertyRead( const PropertyPathId property, const QQStyleKitControlProperties *storage, const QQSK::State state, const QVariant &value) { Q_ASSERT(enabled()); const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties(); if (reader->subclass() == QQSK::Subclass::QQStyleKitState) { /* The reader is in the UnfiedStyle, and not in the users application (which can happen * when e.g resolving local bindings between properties in the style). Those are not * interesting to print out when inspecting control-to-style mappings. Ignore. */ return; } if (!insideControl(reader)) { // We should only debug reads that targets m_item. So return. return; } const QString _readerPath = objectPath(reader, m_item); const QString _readPropertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property); const QString queriedPath = _readerPath + kDot +_readPropertyPath; QString storagePath; if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) { /* We read an interpolated value stored directly in the reader itself. While this * can be interesting to print out whe debugging the styling engine itself, it * comes across as noise when inspecting control-to-style mappings. Ignore. */ #if 0 storagePath = "[local storage] "_L1; #else return; #endif } else { const QString _controlPathInStyle = objectPath(storage, storage->style()); const QString _statePath = stateToString(state); storagePath = _controlPathInStyle + _statePath; } QString valueString = value.toString(); if (!value.isValid()) // value was set, but probably to undefined valueString = ""_L1; else if (valueString.isEmpty()) valueString = ""_L1; const QString output = queriedPath + " -> "_L1 + storagePath + " = "_L1 + valueString; if (!QRegularExpression(m_filter).match(output).hasMatch()) return; qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output; } void QQStyleKitDebug::notifyPropertyWrite( const QQStyleKitPropertyGroup *group, const QQSK::Property property, const QQStyleKitControlProperties *storage, const QQSK::State state, const PropertyStorageId key, const QVariant &value) { #if 1 Q_UNUSED(group); Q_UNUSED(property); Q_UNUSED(storage); Q_UNUSED(state); Q_UNUSED(key); Q_UNUSED(value); #else /* Note: in order to catch _all_ writes, we cannot depend on enabling writes from * QML using a property, as that would resolve to 'true' too late. */ QString storagePath; if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) { storagePath = "[local storage]"_L1; } else { const QString _controlPathInStyle = objectPath(storage, storage->style()); const QString _statePath = stateToString(state); storagePath = _controlPathInStyle + _statePath; } QString valueString = value.toString(); if (!value.isValid()) // value was set, but probably to undefined valueString = ""_L1; else if (valueString.isEmpty()) valueString = ""_L1; const QString path = propertyPath(group, property); const QString output = storagePath + kDot + path + " (storage key:"_L1 + QString::number(key) + ") = "_L1 + valueString; qDebug().nospace().noquote() << m_outputCount++ << " | [write] "_L1 << output; #endif } void QQStyleKitDebug::notifyPropertyNotResolved(const PropertyPathId property) { const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties(); if (!insideControl(reader)) { // We should only debug reads that targets m_item. So return. return; } const QString _readerPath = objectPath(reader, m_item); const QString _propertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property); const QString queriedPath = _readerPath + kDot +_propertyPath; const QString output = queriedPath + " -> "_L1; if (!QRegularExpression(m_filter).match(output).hasMatch()) return; qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output; } void QQStyleKitDebug::trace( const PropertyPathId property, const QQStyleKitControlProperties *storage, const QQSK::State state, const PropertyStorageId key) { #if 1 Q_UNUSED(property); Q_UNUSED(storage); Q_UNUSED(state); Q_UNUSED(key); #else const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties(); if (reader->subclass() == QQSK::Subclass::QQStyleKitState) { /* The reader is in the UnfiedStyle, and not in the users application (which can happen * when e.g resolving local bindings between properties in the style). Those are not * interesting to print out when inspecting control-to-style mappings. Ignore. */ return; } if (!insideControl(reader)) { // We should only debug reads that targets m_item. So return. return; } const QString _readerPath = objectPath(reader, m_item); const QString _readPropertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property); const QString queriedPath = _readerPath + kDot +_readPropertyPath; QString storagePath; if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) { /* We read an interpolated value stored directly in the reader itself. While this * can be interesting to print out whe debugging the styling engine itself, it * comes across as noise when inspecting control-to-style mappings. Ignore. */ #if 0 storagePath = "[local storage]"_L1; #else return; #endif } else { const QString _controlPathInStyle = objectPath(storage, storage->style()); const QString _statePath = stateToString(state); storagePath = _controlPathInStyle + _statePath; } const QString output = queriedPath + ", checking "_L1 + storagePath + " (storage key:"_L1 + QString::number(key)+ ")"_L1; if (!QRegularExpression(m_filter).match(output).hasMatch()) return; qDebug().nospace().noquote() << m_outputCount++ << " | [trace] "_L1 << output; #endif } QQuickItem *QQStyleKitDebug::control() const { return m_item; } void QQStyleKitDebug::setControl(QQuickItem *item) { if (m_item == item) return; m_item = item; emit controlChanged(); } QString QQStyleKitDebug::filter() const { return m_filter; } void QQStyleKitDebug::setFilter(const QString &filter) { if (m_filter == filter) return; m_filter = filter; emit filterChanged(); } bool QQStyleKitDebug::insideControl(const QObject *child) { if (!m_item) return false; const QObject *obj = child; do { if (obj == m_item) return true; obj = obj->parent(); } while (obj); return false; } QT_END_NAMESPACE #include "moc_qqstylekitdebug_p.cpp"