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