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 Q_UNUSED(group)
310 return QSharedPointer<QSettings>();
311}
312
313#if QT_CONFIG(settings)
314static void readValue(const QSharedPointer<QSettings> &settings, const QString &name, std::function<void(const QVariant &)> setValue)
315{
316 const QVariant var = settings->value(key: name);
317 if (var.isValid())
318 setValue(var);
319}
320
321template <typename Enum>
322static Enum toEnumValue(const QVariant &var)
323{
324 // ### TODO: expose QFont enums to the meta object system using Q_ENUM
325 //QMetaEnum enumeration = QMetaEnum::fromType<Enum>();
326 //bool ok = false;
327 //int value = enumeration.keyToValue(var.toByteArray(), &ok);
328 //if (!ok)
329 // value = var.toInt();
330 //return static_cast<Enum>(value);
331
332 return static_cast<Enum>(var.toInt());
333}
334
335const QFont *QQuickStylePrivate::readFont(const QSharedPointer<QSettings> &settings)
336{
337 const QVariant var = settings->value(QStringLiteral("Font"));
338 if (var.isValid())
339 return new QFont(var.value<QFont>());
340
341 QFont f;
342 settings->beginGroup(QStringLiteral("Font"));
343 readValue(settings, QStringLiteral("Family"), setValue: [&f](const QVariant &var) { f.setFamilies(QStringList{var.toString()}); });
344 readValue(settings, QStringLiteral("PointSize"), setValue: [&f](const QVariant &var) { f.setPointSizeF(var.toReal()); });
345 readValue(settings, QStringLiteral("PixelSize"), setValue: [&f](const QVariant &var) { f.setPixelSize(var.toInt()); });
346 readValue(settings, QStringLiteral("StyleHint"), setValue: [&f](const QVariant &var) { f.setStyleHint(toEnumValue<QFont::StyleHint>(var: var.toInt())); });
347 readValue(settings, QStringLiteral("Weight"), setValue: [&f](const QVariant &var) { f.setWeight(toEnumValue<QFont::Weight>(var)); });
348 readValue(settings, QStringLiteral("Style"), setValue: [&f](const QVariant &var) { f.setStyle(toEnumValue<QFont::Style>(var: var.toInt())); });
349 settings->endGroup();
350 return new QFont(f);
351}
352
353static void readColorGroup(const QSharedPointer<QSettings> &settings, QPalette::ColorGroup group, QPalette *palette)
354{
355 const QStringList keys = settings->childKeys();
356 if (keys.isEmpty())
357 return;
358
359 static const int index = QPalette::staticMetaObject.indexOfEnumerator(name: "ColorRole");
360 Q_ASSERT(index != -1);
361 QMetaEnum metaEnum = QPalette::staticMetaObject.enumerator(index);
362
363 for (const QString &key : keys) {
364 bool ok = false;
365 int role = metaEnum.keyToValue(key: key.toUtf8(), ok: &ok);
366 if (ok)
367 palette->setColor(acg: group, acr: static_cast<QPalette::ColorRole>(role), acolor: settings->value(key).value<QColor>());
368 }
369}
370
371const QPalette *QQuickStylePrivate::readPalette(const QSharedPointer<QSettings> &settings)
372{
373 QPalette p;
374 settings->beginGroup(QStringLiteral("Palette"));
375 readColorGroup(settings, group: QPalette::All, palette: &p);
376
377 settings->beginGroup(QStringLiteral("Normal"));
378 readColorGroup(settings, group: QPalette::Normal, palette: &p);
379 settings->endGroup();
380
381 settings->beginGroup(QStringLiteral("Disabled"));
382 readColorGroup(settings, group: QPalette::Disabled, palette: &p);
383 settings->endGroup();
384 return new QPalette(p);
385}
386#endif // QT_CONFIG(settings)
387
388bool QQuickStylePrivate::isDarkSystemTheme()
389{
390 const bool dark = [](){
391 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
392 return theme->colorScheme() == Qt::ColorScheme::Dark;
393 return false;
394 }();
395 return dark;
396}
397
398QStringList QQuickStylePrivate::builtInStyles()
399{
400 return {
401 QLatin1String("Basic"),
402 QLatin1String("Fusion"),
403 QLatin1String("FluentWinUI3"),
404 QLatin1String("Imagine"),
405#ifdef Q_OS_MACOS
406 QLatin1String("macOS"),
407 QLatin1String("iOS"),
408#endif
409#ifdef Q_OS_IOS
410 QLatin1String("iOS"),
411#endif
412 QLatin1String("Material"),
413 QLatin1String("Universal"),
414#ifdef Q_OS_WINDOWS
415 QLatin1String("Windows")
416#endif
417 };
418}
419
420/*!
421 Returns the name of the application style.
422
423 \note The application style can be specified by passing a \c -style command
424 line argument. Therefore \c name() may not return a fully resolved
425 value if called before constructing a QGuiApplication.
426*/
427QString QQuickStyle::name()
428{
429 return styleSpec()->name();
430}
431
432/*!
433 Sets the application style to \a style.
434
435 \note The style must be configured \b before loading QML that imports Qt Quick Controls.
436 It is not possible to change the style after the QML types have been registered.
437
438 \sa setFallbackStyle(), {Using Styles in Qt Quick Controls}
439*/
440void QQuickStyle::setStyle(const QString &style)
441{
442 qCDebug(lcQtQuickControlsStyle) << "setStyle called with" << style;
443
444 if (QQmlMetaType::matchingModuleVersion(
445 QStringLiteral("QtQuick.Controls"), version: QTypeRevision::fromVersion(majorVersion: 2, minorVersion: 0)).isValid()) {
446 qWarning() << "ERROR: QQuickStyle::setStyle() must be called before loading QML that imports Qt Quick Controls 2.";
447 return;
448 }
449
450 styleSpec()->setStyle(style);
451}
452
453/*!
454 \since 5.8
455 Sets the application fallback style to \a style.
456
457 \note The fallback style must be the name of one of the built-in Qt Quick Controls styles, e.g. "Material".
458
459 \note The style must be configured \b before loading QML that imports Qt Quick Controls.
460 It is not possible to change the style after the QML types have been registered.
461
462 The fallback style can be also specified by setting the \c QT_QUICK_CONTROLS_FALLBACK_STYLE
463 \l {Supported Environment Variables in Qt Quick Controls}{environment variable}.
464
465 \sa setStyle(), {Using Styles in Qt Quick Controls}
466*/
467void QQuickStyle::setFallbackStyle(const QString &style)
468{
469 if (QQmlMetaType::matchingModuleVersion(
470 QStringLiteral("QtQuick.Controls"), version: QTypeRevision::fromVersion(majorVersion: 2, minorVersion: 0)).isValid()) {
471 qWarning() << "ERROR: QQuickStyle::setFallbackStyle() must be called before loading QML that imports Qt Quick Controls 2.";
472 return;
473 }
474
475 styleSpec()->setFallbackStyle(fallback: style, method: "QQuickStyle::setFallbackStyle()");
476}
477
478QT_END_NAMESPACE
479

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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