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}.
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 return "["_L1 + enumToString(state) + "]"_L1;
121}
122
123QString QQStyleKitDebug::styleReaderToString(const QQStyleKitReader *reader)
124{
125 return "StyleKitReader"_L1 + stateToString(reader->controlState());
126}
127
128QString QQStyleKitDebug::controlToString(const QQStyleKitControlProperties *control)
129{
130 if (!control->objectName().isEmpty())
131 return control->objectName();
132
133 const QObject *parentObj = control->parent();
134 if (!parentObj)
135 return "<no parent>"_L1;
136
137 auto *controls = qobject_cast<const QQStyleKitControls *>(parentObj);
138 if (!controls)
139 return "<"_L1 + QString::fromUtf8(parentObj->metaObject()->className()) + ">"_L1;
140
141 const int startIndex = QQStyleKitControls::staticMetaObject.propertyOffset();
142 const int endIndex = QQStyleKitControls::staticMetaObject.propertyCount();
143
144 const QMetaObject* parentMeta = parentObj->metaObject();
145 for (int i = startIndex; i < endIndex; ++i) {
146 const QMetaProperty prop = parentMeta->property(i);
147 const QMetaObject* typeMeta = QMetaType::fromName(prop.typeName()).metaObject();
148 if (!typeMeta || !typeMeta->inherits(&QQStyleKitControl::staticMetaObject))
149 continue;
150
151 QObject *propObj = prop.read(parentObj).value<QObject *>();
152 if (propObj == control)
153 return QString::fromUtf8(prop.name());
154 }
155
156 return "<unknown control>"_L1;
157}
158
159QString QQStyleKitDebug::objectPath(const QQStyleKitControlProperties *properties, QObject *from)
160{
161 QString path;
162 const QObject *obj = properties;
163
164 while (obj) {
165 if (!path.isEmpty())
166 path.prepend(kDot);
167
168 if (auto *theme = qobject_cast<const QQStyleKitCustomTheme *>(obj)) {
169 path.prepend(theme->name() + kDot);
170 } else if (auto *theme = qobject_cast<const QQStyleKitTheme *>(obj)) {
171 // Note: only one theme is instantiated at a time
172 path.prepend("Theme("_L1 + theme->style()->m_effectiveThemeName + ")"_L1);
173 } else if (auto *variation = qobject_cast<const QQStyleKitVariation *>(obj)) {
174 path.prepend("StyleVariation("_L1 + variation->name() + ")"_L1);
175 } else if (auto *control = qobject_cast<const QQStyleKitControl *>(obj)) {
176 path.prepend(controlToString(control));
177 } else if (auto *reader = qobject_cast<const QQStyleKitReader *>(obj)) {
178 path.prepend(styleReaderToString(reader));
179 } else {
180 path.prepend(objectName(obj));
181 }
182
183 if (obj == from)
184 break;
185
186 obj = obj->parent();
187 }
188
189 return path;
190}
191
192void QQStyleKitDebug::notifyPropertyRead(
193 const QQStyleKitPropertyPath property,
194 const QQStyleKitControlProperties *storage,
195 const QQSK::State state,
196 const QVariant &value)
197{
198 Q_ASSERT(enabled());
199
200 const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
201 if (reader->subclass() == QQSK::Subclass::QQStyleKitState) {
202 /* The reader is in the UnfiedStyle, and not in the users application (which can happen
203 * when e.g resolving local bindings between properties in the style). Those are not
204 * interesting to print out when inspecting control-to-style mappings. Ignore. */
205 return;
206 }
207
208 if (!insideControl(reader)) {
209 // We should only debug reads that targets m_item. So return.
210 return;
211 }
212
213 const QString _readerPath = objectPath(reader, m_item);
214 const QString _readPropertyPath = property.toString();
215 const QString queriedPath = _readerPath + kDot +_readPropertyPath;
216
217 QString storagePath;
218 if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
219 /* We read an interpolated value stored directly in the reader itself. While this
220 * can be interesting to print out when debugging the styling engine itself, it
221 * comes across as noise when inspecting control-to-style mappings. Ignore. */
222#if 0
223 storagePath = "[local storage] "_L1;
224#else
225 return;
226#endif
227 } else {
228 const QString _controlPathInStyle = objectPath(storage, storage->style());
229 const QString _statePath = stateToString(state);
230 storagePath = _controlPathInStyle + _statePath;
231 }
232
233 QString valueString = value.toString();
234 if (!value.isValid()) // value was set, but probably to undefined
235 valueString = "<undefined>"_L1;
236 else if (valueString.isEmpty())
237 valueString = "<object>"_L1;
238
239 const QString output = queriedPath + " -> "_L1 + storagePath + " = "_L1 + valueString;
240
241 if (!QRegularExpression(m_filter).match(output).hasMatch())
242 return;
243
244 qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output;
245}
246
247void QQStyleKitDebug::notifyPropertyWrite(
248 const QQStyleKitPropertyGroup *group,
249 const QQSK::Property property,
250 const QQStyleKitControlProperties *storage,
251 const QQSK::State state,
252 const PropertyStorageId key,
253 const QVariant &value)
254{
255#if 1
256 Q_UNUSED(group);
257 Q_UNUSED(property);
258 Q_UNUSED(storage);
259 Q_UNUSED(state);
260 Q_UNUSED(key);
261 Q_UNUSED(value);
262#else
263 /* Note: in order to catch _all_ writes, we cannot depend on enabling writes from
264 * QML using a property, as that would resolve to 'true' too late. */
265 QString storagePath;
266 if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
267 storagePath = "[local storage]"_L1;
268 } else {
269 const QString _controlPathInStyle = objectPath(storage, storage->style());
270 const QString _statePath = stateToString(state);
271 storagePath = _controlPathInStyle + _statePath;
272 }
273
274 QString valueString = value.toString();
275 if (!value.isValid()) // value was set, but probably to undefined
276 valueString = "<undefined>"_L1;
277 else if (valueString.isEmpty())
278 valueString = "<object>"_L1;
279
280 const QString path = property.toString();
281 const QString output = storagePath + kDot + path + " (storage key:"_L1 + QString::number(key) + ") = "_L1 + valueString;
282
283 qDebug().nospace().noquote() << m_outputCount++ << " | [write] "_L1 << output;
284#endif
285}
286
287void QQStyleKitDebug::notifyPropertyNotResolved(const QQStyleKitPropertyPath property)
288{
289 const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
290 if (!insideControl(reader)) {
291 // We should only debug reads that targets m_item. So return.
292 return;
293 }
294
295 const QString _readerPath = objectPath(reader, m_item);
296 const QString _propertyPath = property.toString();
297 const QString queriedPath = _readerPath + kDot +_propertyPath;
298 const QString output = queriedPath + " -> <property not set>"_L1;
299
300 if (!QRegularExpression(m_filter).match(output).hasMatch())
301 return;
302
303 qDebug().nospace().noquote() << m_outputCount++ << " | [read] "_L1 << output;
304}
305
306void QQStyleKitDebug::trace(
307 const QQStyleKitPropertyPath property,
308 const QQStyleKitControlProperties *storage,
309 const QQSK::State state,
310 const PropertyStorageId key)
311{
312#if 1
313 Q_UNUSED(property);
314 Q_UNUSED(storage);
315 Q_UNUSED(state);
316 Q_UNUSED(key);
317#else
318 const QQStyleKitControlProperties *reader = QQStyleKitDebug::groupBeingRead->controlProperties();
319 if (reader->subclass() == QQSK::Subclass::QQStyleKitState) {
320 /* The reader is in the UnfiedStyle, and not in the users application (which can happen
321 * when e.g resolving local bindings between properties in the style). Those are not
322 * interesting to print out when inspecting control-to-style mappings. Ignore. */
323 return;
324 }
325
326 if (!insideControl(reader)) {
327 // We should only debug reads that targets m_item. So return.
328 return;
329 }
330
331 const QString _readerPath = objectPath(reader, m_item);
332 const QString _readPropertyPath = property.toString();
333 const QString queriedPath = _readerPath + kDot +_readPropertyPath;
334
335 QString storagePath;
336 if (storage->subclass() == QQSK::Subclass::QQStyleKitReader) {
337 /* We read an interpolated value stored directly in the reader itself. While this
338 * can be interesting to print out whe debugging the styling engine itself, it
339 * comes across as noise when inspecting control-to-style mappings. Ignore. */
340#if 0
341 storagePath = "[local storage]"_L1;
342#else
343 return;
344#endif
345 } else {
346 const QString _controlPathInStyle = objectPath(storage, storage->style());
347 const QString _statePath = stateToString(state);
348 storagePath = _controlPathInStyle + _statePath;
349 }
350
351 const QString output = queriedPath + ", checking "_L1 + storagePath + " (storage key:"_L1 + QString::number(key)+ ")"_L1;
352
353 if (!QRegularExpression(m_filter).match(output).hasMatch())
354 return;
355
356 qDebug().nospace().noquote() << m_outputCount++ << " | [trace] "_L1 << output;
357#endif
358}
359
360QQuickItem *QQStyleKitDebug::control() const
361{
362 return m_item;
363}
364
365void QQStyleKitDebug::setControl(QQuickItem *item)
366{
367 if (m_item == item)
368 return;
369
370 m_item = item;
371 emit controlChanged();
372}
373
375{
376 return m_filter;
377}
378
379void QQStyleKitDebug::setFilter(const QString &filter)
380{
381 if (m_filter == filter)
382 return;
383
384 m_filter = filter;
385 emit filterChanged();
386}
387
388bool QQStyleKitDebug::insideControl(const QObject *child)
389{
390 if (!m_item)
391 return false;
392 const QObject *obj = child;
393 do {
394 if (obj == m_item)
395 return true;
396 obj = obj->parent();
397 } while (obj);
398 return false;
399}
400
401QT_END_NAMESPACE
402
403#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