1// Copyright (C) 2017 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
4#include "qquickstyle.h"
5#include "qquickstyle_p.h"
6
7#include <QtCore/qdir.h>
8#include <QtCore/qfile.h>
9#include <QtCore/qdebug.h>
10#if QT_CONFIG(settings)
11#include <QtCore/qsettings.h>
12#endif
13#include <QtCore/qfileselector.h>
14#include <QtCore/qlibraryinfo.h>
15#include <QtCore/qloggingcategory.h>
16#include <QtCore/qmetaobject.h>
17#include <QtGui/qcolor.h>
18#include <QtGui/qfont.h>
19#include <QtGui/qpalette.h>
20#include <QtGui/private/qguiapplication_p.h>
21#include <QtGui/qpa/qplatformtheme.h>
22#include <QtQml/private/qqmlmetatype_p.h>
23#include <QtQml/qqmlengine.h>
24#include <QtQml/qqmlfile.h>
25
26#include <functional>
27
28QT_BEGIN_NAMESPACE
29
30Q_LOGGING_CATEGORY(lcQtQuickControlsStyle, "qt.quick.controls.style")
31
32/*!
33 \class QQuickStyle
34 \brief The QQuickStyle class allows configuring the application style.
35 \inmodule QtQuickControls2
36 \since 5.7
37
38 QQuickStyle provides API for querying and configuring the application
39 \l {Styling Qt Quick Controls}{styles} of Qt Quick Controls.
40
41 \code
42 #include <QGuiApplication>
43 #include <QQmlApplicationEngine>
44 #include <QQuickStyle>
45
46 int main(int argc, char *argv[])
47 {
48 QGuiApplication app(argc, argv);
49
50 QQuickStyle::setStyle("Material");
51
52 QQmlApplicationEngine engine;
53 engine.load(QUrl("qrc:/main.qml"));
54
55 return app.exec();
56 }
57 \endcode
58
59 \note The style must be configured \b before loading QML that imports
60 Qt Quick Controls. It is not possible to change the style after the QML
61 types have been registered.
62
63 \note QQuickStyle is not supported when using
64 \l {Compile-Time Style Selection}{compile-time style selection}.
65
66 To create your own custom style, see \l {Creating a Custom Style}. Custom
67 styles do not need to implement all controls. By default, the styling
68 system uses the \l {Basic style} as a fallback for controls that a custom
69 style does not provide. It is possible to specify a different fallback
70 style to customize or extend one of the built-in styles.
71
72 \code
73 QQuickStyle::setStyle("MyStyle");
74 QQuickStyle::setFallbackStyle("Material");
75 \endcode
76
77 \sa {Styling Qt Quick Controls}
78*/
79
80struct QQuickStyleSpec
81{
82 QQuickStyleSpec() { }
83
84 QString name()
85 {
86 if (!resolved)
87 resolve();
88 return style.mid(position: style.lastIndexOf(c: QLatin1Char('/')) + 1);
89 }
90
91 QString path()
92 {
93 if (!resolved)
94 resolve();
95 QString s = style;
96 if (QQmlFile::isLocalFile(url: s))
97 s = QQmlFile::urlToLocalFileOrQrc(s);
98 return s.left(n: s.lastIndexOf(c: QLatin1Char('/')) + 1);
99 }
100
101 void setStyle(const QString &s)
102 {
103 qCDebug(lcQtQuickControlsStyle) << "style" << s << "set on QQuickStyleSpec";
104 if (s.contains(c: QLatin1Char('/'))) {
105 qWarning() << "Style names must not contain paths; see the \"Definition of a Style\" documentation for more information";
106 return;
107 }
108
109 qCDebug(lcQtQuickControlsStyle) << "clearing resolved flag and resolving";
110 style = s;
111 resolved = false;
112 resolve();
113 }
114
115 void setFallbackStyle(const QString &fallback, const QByteArray &method)
116 {
117 if (!fallback.isEmpty())
118 qCDebug(lcQtQuickControlsStyle) << "fallback style" << fallback << "set on QQuickStyleSpec via" << method;
119
120 fallbackStyle = fallback;
121 fallbackMethod = method;
122 }
123
124 void resolve()
125 {
126 qCDebug(lcQtQuickControlsStyle) << "resolving style";
127
128 if (style.isEmpty())
129 style = QGuiApplicationPrivate::styleOverride;
130 if (style.isEmpty())
131 style = QString::fromLocal8Bit(ba: qgetenv(varName: "QT_QUICK_CONTROLS_STYLE"));
132 if (fallbackStyle.isEmpty())
133 setFallbackStyle(fallback: QString::fromLocal8Bit(ba: qgetenv(varName: "QT_QUICK_CONTROLS_FALLBACK_STYLE")), method: "QT_QUICK_CONTROLS_FALLBACK_STYLE");
134#if QT_CONFIG(settings)
135 if (style.isEmpty() || fallbackStyle.isEmpty()) {
136 QSharedPointer<QSettings> settings = QQuickStylePrivate::settings(QStringLiteral("Controls"));
137 if (settings) {
138 if (style.isEmpty())
139 style = settings->value(QStringLiteral("Style")).toString();
140 if (fallbackStyle.isEmpty())
141 setFallbackStyle(fallback: settings->value(QStringLiteral("FallbackStyle")).toString(), method: ":/qtquickcontrols2.conf");
142 }
143 }
144#endif
145
146 auto builtInStyleList = QQuickStylePrivate::builtInStyles();
147 if (!fallbackStyle.isEmpty() && !builtInStyleList.contains(str: fallbackStyle)) {
148 qWarning().nospace().noquote() << fallbackMethod << ": the specified fallback style \"" <<
149 fallbackStyle << "\" is not one of the built-in Qt Quick Controls 2 styles";
150 fallbackStyle.clear();
151 }
152
153 // Find the config file.
154 resolveConfigFilePath();
155
156 usingDefaultStyle = false;
157
158 if (style.isEmpty() || style.toLower() == QStringLiteral("default")) {
159 usingDefaultStyle = true;
160 style.clear();
161
162 qCDebug(lcQtQuickControlsStyle) << "no style (or Default) was specified;"
163 << "checking if we have an appropriate style for this platform";
164
165 // If these defaults are changed, ensure that the "Using Styles in Qt Quick Controls"
166 // section of qtquickcontrols-styles.qdoc is updated.
167#if defined(Q_OS_MACOS)
168 style = QLatin1String("macOS");
169#elif defined(Q_OS_WINDOWS)
170 style = QLatin1String("Windows");
171#elif defined(Q_OS_ANDROID)
172 style = QLatin1String("Material");
173#elif defined(Q_OS_LINUX)
174 style = QLatin1String("Fusion");
175#elif defined(Q_OS_IOS)
176 style = QLatin1String("iOS");
177#endif
178 if (!style.isEmpty())
179 qCDebug(lcQtQuickControlsStyle) << "using" << style << "as a default";
180 else
181 qCDebug(lcQtQuickControlsStyle) << "no appropriate style found; using Basic as a default";
182 }
183
184 // If it's still empty by this point, then it means we have no native style available for this platform,
185 // as is the case on e.g. embedded. In that case, we want to default to the Basic style,
186 // which is what effectiveStyleName() returns when "style" is empty.
187 custom = !builtInStyleList.contains(str: QQuickStylePrivate::effectiveStyleName(styleName: style));
188
189 resolved = true;
190
191 qCDebug(lcQtQuickControlsStyle).nospace() << "done resolving:"
192 << "\n style=" << style
193 << "\n custom=" << custom
194 << "\n resolved=" << resolved
195 << "\n fallbackStyle=" << fallbackStyle
196 << "\n fallbackMethod=" << fallbackMethod
197 << "\n configFilePath=" << configFilePath;
198 }
199
200 void reset()
201 {
202 qCDebug(lcQtQuickControlsStyle) << "resetting values to their defaults";
203
204 custom = false;
205 resolved = false;
206 usingDefaultStyle = false;
207 style.clear();
208 fallbackStyle.clear();
209 fallbackMethod.clear();
210 configFilePath.clear();
211 }
212
213 QString resolveConfigFilePath()
214 {
215 if (configFilePath.isEmpty()) {
216 configFilePath = QFile::decodeName(localFileName: qgetenv(varName: "QT_QUICK_CONTROLS_CONF"));
217 if (configFilePath.isEmpty() || !QFile::exists(fileName: configFilePath)) {
218 if (!configFilePath.isEmpty())
219 qWarning(msg: "QT_QUICK_CONTROLS_CONF=%s: No such file", qPrintable(configFilePath));
220
221 configFilePath = QStringLiteral(":/qtquickcontrols2.conf");
222 }
223 }
224 return configFilePath;
225 }
226
227 // Is this a custom style defined by the user and not "built-in" style?
228 bool custom = false;
229 // Have we resolved the style yet?
230 bool resolved = false;
231 // Are we using the default style for this platform (because no style was specified)?
232 bool usingDefaultStyle = false;
233 // The name of the style.
234 QString style;
235 // The built-in style to use if the requested style cannot be found.
236 QString fallbackStyle;
237 // A description of the way in which fallbackStyle was set, used in e.g. warning messages shown to the user.
238 QByteArray fallbackMethod;
239 // The path to the qtquickcontrols2.conf file.
240 QString configFilePath;
241};
242
243Q_GLOBAL_STATIC(QQuickStyleSpec, styleSpec)
244
245/*
246 Note that most of these functions (with the exception of e.g. isResolved())
247 should not be called before the style has been resolved, as it's only after
248 that happens that they will have been set.
249*/
250QString QQuickStylePrivate::style()
251{
252 return styleSpec()->style;
253}
254
255QString QQuickStylePrivate::effectiveStyleName(const QString &styleName)
256{
257 return !styleName.isEmpty() ? styleName : QLatin1String("Basic");
258}
259
260QString QQuickStylePrivate::fallbackStyle()
261{
262 return styleSpec()->fallbackStyle;
263}
264
265bool QQuickStylePrivate::isCustomStyle()
266{
267 return styleSpec()->custom;
268}
269
270bool QQuickStylePrivate::isResolved()
271{
272 return styleSpec()->resolved;
273}
274
275bool QQuickStylePrivate::isUsingDefaultStyle()
276{
277 return styleSpec()->usingDefaultStyle;
278}
279
280void QQuickStylePrivate::init()
281{
282 QQuickStyleSpec *spec = styleSpec();
283 spec->resolve();
284}
285
286void QQuickStylePrivate::reset()
287{
288 if (styleSpec())
289 styleSpec()->reset();
290}
291
292QString QQuickStylePrivate::configFilePath()
293{
294 return styleSpec()->resolveConfigFilePath();
295}
296
297QSharedPointer<QSettings> QQuickStylePrivate::settings(const QString &group)
298{
299#ifndef QT_NO_SETTINGS
300 const QString filePath = QQuickStylePrivate::configFilePath();
301 if (QFile::exists(fileName: filePath)) {
302 QFileSelector selector;
303 QSettings *settings = new QSettings(selector.select(filePath), QSettings::IniFormat);
304 if (!group.isEmpty())
305 settings->beginGroup(prefix: group);
306 return QSharedPointer<QSettings>(settings);
307 }
308#endif // QT_NO_SETTINGS
309 return QSharedPointer<QSettings>();
310}
311
312#if QT_CONFIG(settings)
313static void readValue(const QSharedPointer<QSettings> &settings, const QString &name, std::function<void(const QVariant &)> setValue)
314{
315 const QVariant var = settings->value(key: name);
316 if (var.isValid())
317 setValue(var);
318}
319
320template <typename Enum>
321static Enum toEnumValue(const QVariant &var)
322{
323 // ### TODO: expose QFont enums to the meta object system using Q_ENUM
324 //QMetaEnum enumeration = QMetaEnum::fromType<Enum>();
325 //bool ok = false;
326 //int value = enumeration.keyToValue(var.toByteArray(), &ok);
327 //if (!ok)
328 // value = var.toInt();
329 //return static_cast<Enum>(value);
330
331 return static_cast<Enum>(var.toInt());
332}
333
334const QFont *QQuickStylePrivate::readFont(const QSharedPointer<QSettings> &settings)
335{
336 const QVariant var = settings->value(QStringLiteral("Font"));
337 if (var.isValid())
338 return new QFont(var.value<QFont>());
339
340 QFont f;
341 settings->beginGroup(QStringLiteral("Font"));
342 readValue(settings, QStringLiteral("Family"), setValue: [&f](const QVariant &var) { f.setFamilies(QStringList{var.toString()}); });
343 readValue(settings, QStringLiteral("PointSize"), setValue: [&f](const QVariant &var) { f.setPointSizeF(var.toReal()); });
344 readValue(settings, QStringLiteral("PixelSize"), setValue: [&f](const QVariant &var) { f.setPixelSize(var.toInt()); });
345 readValue(settings, QStringLiteral("StyleHint"), setValue: [&f](const QVariant &var) { f.setStyleHint(toEnumValue<QFont::StyleHint>(var: var.toInt())); });
346 readValue(settings, QStringLiteral("Weight"), setValue: [&f](const QVariant &var) { f.setWeight(toEnumValue<QFont::Weight>(var)); });
347 readValue(settings, QStringLiteral("Style"), setValue: [&f](const QVariant &var) { f.setStyle(toEnumValue<QFont::Style>(var: var.toInt())); });
348 settings->endGroup();
349 return new QFont(f);
350}
351
352static void readColorGroup(const QSharedPointer<QSettings> &settings, QPalette::ColorGroup group, QPalette *palette)
353{
354 const QStringList keys = settings->childKeys();
355 if (keys.isEmpty())
356 return;
357
358 static const int index = QPalette::staticMetaObject.indexOfEnumerator(name: "ColorRole");
359 Q_ASSERT(index != -1);
360 QMetaEnum metaEnum = QPalette::staticMetaObject.enumerator(index);
361
362 for (const QString &key : keys) {
363 bool ok = false;
364 int role = metaEnum.keyToValue(key: key.toUtf8(), ok: &ok);
365 if (ok)
366 palette->setColor(acg: group, acr: static_cast<QPalette::ColorRole>(role), acolor: settings->value(key).value<QColor>());
367 }
368}
369
370const QPalette *QQuickStylePrivate::readPalette(const QSharedPointer<QSettings> &settings)
371{
372 QPalette p;
373 settings->beginGroup(QStringLiteral("Palette"));
374 readColorGroup(settings, group: QPalette::All, palette: &p);
375
376 settings->beginGroup(QStringLiteral("Normal"));
377 readColorGroup(settings, group: QPalette::Normal, palette: &p);
378 settings->endGroup();
379
380 settings->beginGroup(QStringLiteral("Disabled"));
381 readColorGroup(settings, group: QPalette::Disabled, palette: &p);
382 settings->endGroup();
383 return new QPalette(p);
384}
385#endif // QT_CONFIG(settings)
386
387bool QQuickStylePrivate::isDarkSystemTheme()
388{
389 const bool dark = [](){
390 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
391 return theme->colorScheme() == Qt::ColorScheme::Dark;
392 return false;
393 }();
394 return dark;
395}
396
397QStringList QQuickStylePrivate::builtInStyles()
398{
399 return {
400 QLatin1String("Basic"),
401 QLatin1String("Fusion"),
402 QLatin1String("Imagine"),
403#ifdef Q_OS_MACOS
404 QLatin1String("macOS"),
405 QLatin1String("iOS"),
406#endif
407#ifdef Q_OS_IOS
408 QLatin1String("iOS"),
409#endif
410 QLatin1String("Material"),
411 QLatin1String("Universal"),
412#ifdef Q_OS_WINDOWS
413 QLatin1String("Windows")
414#endif
415 };
416}
417
418/*!
419 Returns the name of the application style.
420
421 \note The application style can be specified by passing a \c -style command
422 line argument. Therefore \c name() may not return a fully resolved
423 value if called before constructing a QGuiApplication.
424*/
425QString QQuickStyle::name()
426{
427 return styleSpec()->name();
428}
429
430/*!
431 Sets the application style to \a style.
432
433 \note The style must be configured \b before loading QML that imports Qt Quick Controls.
434 It is not possible to change the style after the QML types have been registered.
435
436 \sa setFallbackStyle(), {Using Styles in Qt Quick Controls}
437*/
438void QQuickStyle::setStyle(const QString &style)
439{
440 qCDebug(lcQtQuickControlsStyle) << "setStyle called with" << style;
441
442 if (QQmlMetaType::matchingModuleVersion(
443 QStringLiteral("QtQuick.Controls"), version: QTypeRevision::fromVersion(majorVersion: 2, minorVersion: 0)).isValid()) {
444 qWarning() << "ERROR: QQuickStyle::setStyle() must be called before loading QML that imports Qt Quick Controls 2.";
445 return;
446 }
447
448 styleSpec()->setStyle(style);
449}
450
451/*!
452 \since 5.8
453 Sets the application fallback style to \a style.
454
455 \note The fallback style must be the name of one of the built-in Qt Quick Controls styles, e.g. "Material".
456
457 \note The style must be configured \b before loading QML that imports Qt Quick Controls.
458 It is not possible to change the style after the QML types have been registered.
459
460 The fallback style can be also specified by setting the \c QT_QUICK_CONTROLS_FALLBACK_STYLE
461 \l {Supported Environment Variables in Qt Quick Controls}{environment variable}.
462
463 \sa setStyle(), {Using Styles in Qt Quick Controls}
464*/
465void QQuickStyle::setFallbackStyle(const QString &style)
466{
467 if (QQmlMetaType::matchingModuleVersion(
468 QStringLiteral("QtQuick.Controls"), version: QTypeRevision::fromVersion(majorVersion: 2, minorVersion: 0)).isValid()) {
469 qWarning() << "ERROR: QQuickStyle::setFallbackStyle() must be called before loading QML that imports Qt Quick Controls 2.";
470 return;
471 }
472
473 styleSpec()->setFallbackStyle(fallback: style, method: "QQuickStyle::setFallbackStyle()");
474}
475
476QT_END_NAMESPACE
477

source code of qtdeclarative/src/quickcontrols/qquickstyle.cpp