Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqstylekitdebug.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
10
12
13/*!
14 \qmltype StyleKitDebug
15 \inqmlmodule Qt.labs.StyleKit
16 \brief Traces how style properties are resolved for a control.
17
18 StyleKitDebug is a diagnostic in-app tool that logs style property reads to the
19 debug output. It is useful for understanding why a control ends up with a
20 particular appearance, especially in complex styles that involve multiple
21 \l {Theme}{themes} or \l {StyleVariation}{style variations}.
22
23 It is accessed through \l {StyleKit::debug}{StyleKit.debug}.
24
25 \note This tool is experimental. Enabling it will severely degrade performance and should only
26 be used for debugging. The output format may change in future versions.
27
28 To start tracing, assign the control you want to introspect to the \l control property:
29
30 \snippet StyleKitDebug.qml trace
31
32 Each resolved property is printed as a single line showing where the value
33 came from and what it resolved to:
34
35 \code
36 [read] StyleReader[Hovered].button.background.color -> Style.Theme(Dark).button[Hovered] = #add8e6
37 \endcode
38
39 The \c StyleReader in the output refers to the \l StyleReader that the \l Button
40 uses internally to read its style property values.
41
42 Tracing the property reads of a StyleReader can produce a large amount of output. Use \l filter to
43 limit the output to properties of interest.
44
45 \labs
46
47 \sa StyleKit::debug, {StyleKit Property Resolution}
48*/
49
50/*!
51 \qmlproperty Item StyleKitDebug::control
52
53 The \l{The id Attribute}{id} of the \l Control or \l StyleReader to trace.
54
55 When set, StyleKit logs all style property reads
56 for this item to the debug output. Set to \c null to stop tracing.
57
58 \sa filter
59*/
60
61/*!
62 \qmlproperty string StyleKitDebug::filter
63
64 A regular expression used to filter the debug output. Only lines matching
65 the pattern are printed. By default, all output is shown.
66
67 For example, to only trace the background color:
68
69 \code
70 StyleKit.debug.filter: "background.color"
71 \endcode
72
73 To trace all background colors, including properties
74 such as \l {DelegateStyle::color}{background.color},
75 \l {BorderStyle::color}{background.border.color},
76 \l {ImageStyle::color}{background.image.color},
77 and \l {ShadowStyle::color}{background.shadow.color},
78 use a regular expression:
79
80 \code
81 StyleKit.debug.filter: "background.*color"
82 \endcode
83
84 \sa control
85*/
86
87const QQStyleKitPropertyGroup *QQStyleKitDebug::groupBeingRead = nullptr;
88QPointer<QQuickItem> QQStyleKitDebug::m_item;
89QString QQStyleKitDebug::m_filter;
90int QQStyleKitDebug::m_outputCount = 0;
91
92static const QChar kDot = '.'_L1;
93
94template <typename EnumType>
95QString QQStyleKitDebug::enumToString(EnumType enumValue)
96{
97 auto propertyMetaEnum = QMetaEnum::fromType<EnumType>();
98 return QString::fromUtf8(propertyMetaEnum.valueToKeys(quint64(enumValue)));
99}
100
101QString QQStyleKitDebug::objectName(const QObject *obj) {
102 if (!obj->objectName().isEmpty())
103 return obj->objectName();
104
105 QString str = QString::fromLatin1(obj->metaObject()->className());
106 int idx = str.indexOf("_QMLTYPE"_L1);
107 if (idx != -1)
108 str = str.left(idx);
109 else {
110 const QString prefix("QQStyleKit"_L1);
111 if (str.startsWith(prefix))
112 str = str.mid(prefix.length());
113 }
114
115 return str;
116}
117
118QString QQStyleKitDebug::stateToString(const QQSK::State state)
119{
120 const QStringList list = enumToString(state).split('|'_L1);
121 return "["_L1 + list.join(','_L1) + "]"_L1;
122}
123
124QString QQStyleKitDebug::styleReaderToString(const QQStyleKitReader *reader)
125{
126 return "StyleKitReader"_L1 + stateToString(reader->controlState());
127}
128
129QString QQStyleKitDebug::propertyPath(const QQStyleKitPropertyGroup *group, const PropertyPathId property)
130{
131 const QString path = group->pathToString();
132 QString propertyName = enumToString(property.property());
133 propertyName[0] = propertyName[0].toLower();
134 if (path.isEmpty())
135 return propertyName;
136 return path + kDot + propertyName;
137}
138
139QString QQStyleKitDebug::controlToString(const QQStyleKitControlProperties *control)
140{
141 if (!control->objectName().isEmpty())
142 return control->objectName();
143
144 const QObject *parentObj = control->parent();
145 if (!parentObj)
146 return "<no parent>"_L1;
147
148 auto *controls = qobject_cast<const QQStyleKitControls *>(parentObj);
149 if (!controls)
150 return "<"_L1 + QString::fromUtf8(parentObj->metaObject()->className()) + ">"_L1;
151
152 const int startIndex = QQStyleKitControls::staticMetaObject.propertyOffset();
153 const int endIndex = QQStyleKitControls::staticMetaObject.propertyCount();
154
155 const QMetaObject* parentMeta = parentObj->metaObject();
156 for (int i = startIndex; i < endIndex; ++i) {
157 const QMetaProperty prop = parentMeta->property(i);
158 const QMetaObject* typeMeta = QMetaType::fromName(prop.typeName()).metaObject();
159 if (!typeMeta || !typeMeta->inherits(&QQStyleKitControl::staticMetaObject))
160 continue;
161
162 QObject *propObj = prop.read(parentObj).value<QObject *>();
163 if (propObj == control)
164 return QString::fromUtf8(prop.name());
165 }
166
167 return "<unknown control>"_L1;
168}
169
170QString QQStyleKitDebug::objectPath(const QQStyleKitControlProperties *properties, QObject *from)
171{
172 QString path;
173 const QObject *obj = properties;
174
175 while (obj) {
176 if (!path.isEmpty())
177 path.prepend(kDot);
178
179 if (auto *theme = qobject_cast<const QQStyleKitCustomTheme *>(obj)) {
180 path.prepend(theme->name() + kDot);
181 } else if (auto *theme = qobject_cast<const QQStyleKitTheme *>(obj)) {
182 // Note: only one theme is instantiated at a time
183 path.prepend("Theme("_L1 + theme->style()->m_effectiveThemeName + ")"_L1);
184 } else if (auto *variation = qobject_cast<const QQStyleKitVariation *>(obj)) {
185 path.prepend("StyleVariation("_L1 + variation->name() + ")"_L1);
186 } else if (auto *control = qobject_cast<const QQStyleKitControl *>(obj)) {
187 path.prepend(controlToString(control));
188 } else if (auto *reader = qobject_cast<const QQStyleKitReader *>(obj)) {
189 path.prepend(styleReaderToString(reader));
190 } else {
191 path.prepend(objectName(obj));
192 }
193
194 if (obj == from)
195 break;
196
197 obj = obj->parent();
198 }
199
200 return path;
201}
202
203void QQStyleKitDebug::notifyPropertyRead(
204 const PropertyPathId property,
205 const QQStyleKitControlProperties *storage,
206 const QQSK::State state,
207 const QVariant &value)
208{
209 Q_ASSERT(enabled());
210
211 const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
212 if (reader->subclass() == QQSK::Subclass::QQStyleKitState) {
213 /* The reader is in the UnfiedStyle, and not in the users application (which can happen
214 * when e.g resolving local bindings between properties in the style). Those are not
215 * interesting to print out when inspecting control-to-style mappings. Ignore. */
216 return;
217 }
218
219 if (!insideControl(reader)) {
220 // We should only debug reads that targets m_item. So return.
221 return;
222 }
223
224 const QString _readerPath = objectPath(reader, m_item);
225 const QString _readPropertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property);
226 const QString queriedPath = _readerPath + kDot +_readPropertyPath;
227
228 QString storagePath;
229 if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
230 /* We read an interpolated value stored directly in the reader itself. While this
231 * can be interesting to print out when debugging the styling engine itself, it
232 * comes across as noise when inspecting control-to-style mappings. Ignore. */
233#if 0
234 storagePath = "[local storage] "_L1;
235#else
236 return;
237#endif
238 } else {
239 const QString _controlPathInStyle = objectPath(storage, storage->style());
240 const QString _statePath = stateToString(state);
241 storagePath = _controlPathInStyle + _statePath;
242 }
243
244 QString valueString = value.toString();
245 if (!value.isValid()) // value was set, but probably to undefined
246 valueString = "<undefined>"_L1;
247 else if (valueString.isEmpty())
248 valueString = "<object>"_L1;
249
250 const QString output = queriedPath + " -> "_L1 + storagePath + " = "_L1 + valueString;
251
252 if (!QRegularExpression(m_filter).match(output).hasMatch())
253 return;
254
255 qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output;
256}
257
258void QQStyleKitDebug::notifyPropertyWrite(
259 const QQStyleKitPropertyGroup *group,
260 const QQSK::Property property,
261 const QQStyleKitControlProperties *storage,
262 const QQSK::State state,
263 const PropertyStorageId key,
264 const QVariant &value)
265{
266#if 1
267 Q_UNUSED(group);
268 Q_UNUSED(property);
269 Q_UNUSED(storage);
270 Q_UNUSED(state);
271 Q_UNUSED(key);
272 Q_UNUSED(value);
273#else
274 /* Note: in order to catch _all_ writes, we cannot depend on enabling writes from
275 * QML using a property, as that would resolve to 'true' too late. */
276 QString storagePath;
277 if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
278 storagePath = "[local storage]"_L1;
279 } else {
280 const QString _controlPathInStyle = objectPath(storage, storage->style());
281 const QString _statePath = stateToString(state);
282 storagePath = _controlPathInStyle + _statePath;
283 }
284
285 QString valueString = value.toString();
286 if (!value.isValid()) // value was set, but probably to undefined
287 valueString = "<undefined>"_L1;
288 else if (valueString.isEmpty())
289 valueString = "<object>"_L1;
290
291 const QString path = propertyPath(group, property);
292 const QString output = storagePath + kDot + path + " (storage key:"_L1 + QString::number(key) + ") = "_L1 + valueString;
293
294 qDebug().nospace().noquote() << m_outputCount++ << " | [write] "_L1 << output;
295#endif
296}
297
298void QQStyleKitDebug::notifyPropertyNotResolved(const PropertyPathId property)
299{
300 const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
301 if (!insideControl(reader)) {
302 // We should only debug reads that targets m_item. So return.
303 return;
304 }
305
306 const QString _readerPath = objectPath(reader, m_item);
307 const QString _propertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property);
308 const QString queriedPath = _readerPath + kDot +_propertyPath;
309 const QString output = queriedPath + " -> <property not set>"_L1;
310
311 if (!QRegularExpression(m_filter).match(output).hasMatch())
312 return;
313
314 qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output;
315}
316
317void QQStyleKitDebug::trace(
318 const PropertyPathId property,
319 const QQStyleKitControlProperties *storage,
320 const QQSK::State state,
321 const PropertyStorageId key)
322{
323#if 1
324 Q_UNUSED(property);
325 Q_UNUSED(storage);
326 Q_UNUSED(state);
327 Q_UNUSED(key);
328#else
329 const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
330 if (reader->subclass() == QQSK::Subclass::QQStyleKitState) {
331 /* The reader is in the UnfiedStyle, and not in the users application (which can happen
332 * when e.g resolving local bindings between properties in the style). Those are not
333 * interesting to print out when inspecting control-to-style mappings. Ignore. */
334 return;
335 }
336
337 if (!insideControl(reader)) {
338 // We should only debug reads that targets m_item. So return.
339 return;
340 }
341
342 const QString _readerPath = objectPath(reader, m_item);
343 const QString _readPropertyPath = propertyPath(QQStyleKitDebug::groupBeingRead, property);
344 const QString queriedPath = _readerPath + kDot +_readPropertyPath;
345
346 QString storagePath;
347 if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
348 /* We read an interpolated value stored directly in the reader itself. While this
349 * can be interesting to print out whe debugging the styling engine itself, it
350 * comes across as noise when inspecting control-to-style mappings. Ignore. */
351#if 0
352 storagePath = "[local storage]"_L1;
353#else
354 return;
355#endif
356 } else {
357 const QString _controlPathInStyle = objectPath(storage, storage->style());
358 const QString _statePath = stateToString(state);
359 storagePath = _controlPathInStyle + _statePath;
360 }
361
362 const QString output = queriedPath + ", checking "_L1 + storagePath + " (storage key:"_L1 + QString::number(key)+ ")"_L1;
363
364 if (!QRegularExpression(m_filter).match(output).hasMatch())
365 return;
366
367 qDebug().nospace().noquote() << m_outputCount++ << " | [trace] "_L1 << output;
368#endif
369}
370
371QQuickItem *QQStyleKitDebug::control() const
372{
373 return m_item;
374}
375
376void QQStyleKitDebug::setControl(QQuickItem *item)
377{
378 if (m_item == item)
379 return;
380
381 m_item = item;
382 emit controlChanged();
383}
384
386{
387 return m_filter;
388}
389
390void QQStyleKitDebug::setFilter(const QString &filter)
391{
392 if (m_filter == filter)
393 return;
394
395 m_filter = filter;
396 emit filterChanged();
397}
398
399bool QQStyleKitDebug::insideControl(const QObject *child)
400{
401 if (!m_item)
402 return false;
403 const QObject *obj = child;
404 do {
405 if (obj == m_item)
406 return true;
407 obj = obj->parent();
408 } while (obj);
409 return false;
410}
411
412QT_END_NAMESPACE
413
414#include "moc_qqstylekitdebug_p.cpp"
QString filter() const
void setFilter(const QString &filter)
void setControl(QQuickItem *item)
QQStyleKitControlProperties * controlProperties() const
Combined button and popup list for selecting options.
static const QChar kDot