aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickcontrols/qquickstyle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quickcontrols/qquickstyle.cpp')
-rw-r--r--src/quickcontrols/qquickstyle.cpp477
1 files changed, 477 insertions, 0 deletions
diff --git a/src/quickcontrols/qquickstyle.cpp b/src/quickcontrols/qquickstyle.cpp
new file mode 100644
index 0000000000..a40e9535e2
--- /dev/null
+++ b/src/quickcontrols/qquickstyle.cpp
@@ -0,0 +1,477 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickstyle.h"
+#include "qquickstyle_p.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qdebug.h>
+#if QT_CONFIG(settings)
+#include <QtCore/qsettings.h>
+#endif
+#include <QtCore/qfileselector.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmetaobject.h>
+#include <QtGui/qcolor.h>
+#include <QtGui/qfont.h>
+#include <QtGui/qpalette.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformtheme.h>
+#include <QtQml/private/qqmlmetatype_p.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlfile.h>
+
+#include <functional>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQtQuickControlsStyle, "qt.quick.controls.style")
+
+/*!
+ \class QQuickStyle
+ \brief The QQuickStyle class allows configuring the application style.
+ \inmodule QtQuickControls2
+ \since 5.7
+
+ QQuickStyle provides API for querying and configuring the application
+ \l {Styling Qt Quick Controls}{styles} of Qt Quick Controls.
+
+ \code
+ #include <QGuiApplication>
+ #include <QQmlApplicationEngine>
+ #include <QQuickStyle>
+
+ int main(int argc, char *argv[])
+ {
+ QGuiApplication app(argc, argv);
+
+ QQuickStyle::setStyle("Material");
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl("qrc:/main.qml"));
+
+ return app.exec();
+ }
+ \endcode
+
+ \note The style must be configured \b before loading QML that imports
+ Qt Quick Controls. It is not possible to change the style after the QML
+ types have been registered.
+
+ \note QQuickStyle is not supported when using
+ \l {Compile-Time Style Selection}{compile-time style selection}.
+
+ To create your own custom style, see \l {Creating a Custom Style}. Custom
+ styles do not need to implement all controls. By default, the styling
+ system uses the \l {Basic style} as a fallback for controls that a custom
+ style does not provide. It is possible to specify a different fallback
+ style to customize or extend one of the built-in styles.
+
+ \code
+ QQuickStyle::setStyle("MyStyle");
+ QQuickStyle::setFallbackStyle("Material");
+ \endcode
+
+ \sa {Styling Qt Quick Controls}
+*/
+
+struct QQuickStyleSpec
+{
+ QQuickStyleSpec() { }
+
+ QString name()
+ {
+ if (!resolved)
+ resolve();
+ return style.mid(style.lastIndexOf(QLatin1Char('/')) + 1);
+ }
+
+ QString path()
+ {
+ if (!resolved)
+ resolve();
+ QString s = style;
+ if (QQmlFile::isLocalFile(s))
+ s = QQmlFile::urlToLocalFileOrQrc(s);
+ return s.left(s.lastIndexOf(QLatin1Char('/')) + 1);
+ }
+
+ void setStyle(const QString &s)
+ {
+ qCDebug(lcQtQuickControlsStyle) << "style" << s << "set on QQuickStyleSpec";
+ if (s.contains(QLatin1Char('/'))) {
+ qWarning() << "Style names must not contain paths; see the \"Definition of a Style\" documentation for more information";
+ return;
+ }
+
+ qCDebug(lcQtQuickControlsStyle) << "clearing resolved flag and resolving";
+ style = s;
+ resolved = false;
+ resolve();
+ }
+
+ void setFallbackStyle(const QString &fallback, const QByteArray &method)
+ {
+ if (!fallback.isEmpty())
+ qCDebug(lcQtQuickControlsStyle) << "fallback style" << fallback << "set on QQuickStyleSpec via" << method;
+
+ fallbackStyle = fallback;
+ fallbackMethod = method;
+ }
+
+ void resolve()
+ {
+ qCDebug(lcQtQuickControlsStyle) << "resolving style";
+
+ if (style.isEmpty())
+ style = QGuiApplicationPrivate::styleOverride;
+ if (style.isEmpty())
+ style = QString::fromLocal8Bit(qgetenv("QT_QUICK_CONTROLS_STYLE"));
+ if (fallbackStyle.isEmpty())
+ setFallbackStyle(QString::fromLocal8Bit(qgetenv("QT_QUICK_CONTROLS_FALLBACK_STYLE")), "QT_QUICK_CONTROLS_FALLBACK_STYLE");
+#if QT_CONFIG(settings)
+ if (style.isEmpty() || fallbackStyle.isEmpty()) {
+ QSharedPointer<QSettings> settings = QQuickStylePrivate::settings(QStringLiteral("Controls"));
+ if (settings) {
+ if (style.isEmpty())
+ style = settings->value(QStringLiteral("Style")).toString();
+ if (fallbackStyle.isEmpty())
+ setFallbackStyle(settings->value(QStringLiteral("FallbackStyle")).toString(), ":/qtquickcontrols2.conf");
+ }
+ }
+#endif
+
+ auto builtInStyleList = QQuickStylePrivate::builtInStyles();
+ if (!fallbackStyle.isEmpty() && !builtInStyleList.contains(fallbackStyle)) {
+ qWarning().nospace().noquote() << fallbackMethod << ": the specified fallback style \"" <<
+ fallbackStyle << "\" is not one of the built-in Qt Quick Controls 2 styles";
+ fallbackStyle.clear();
+ }
+
+ // Find the config file.
+ resolveConfigFilePath();
+
+ usingDefaultStyle = false;
+
+ if (style.isEmpty() || style.toLower() == QStringLiteral("default")) {
+ usingDefaultStyle = true;
+ style.clear();
+
+ qCDebug(lcQtQuickControlsStyle) << "no style (or Default) was specified;"
+ << "checking if we have an appropriate style for this platform";
+
+ // If these defaults are changed, ensure that the "Using Styles in Qt Quick Controls"
+ // section of qtquickcontrols-styles.qdoc is updated.
+#if defined(Q_OS_MACOS)
+ style = QLatin1String("macOS");
+#elif defined(Q_OS_WINDOWS)
+ style = QLatin1String("Windows");
+#elif defined(Q_OS_ANDROID)
+ style = QLatin1String("Material");
+#elif defined(Q_OS_LINUX)
+ style = QLatin1String("Fusion");
+#elif defined(Q_OS_IOS)
+ style = QLatin1String("iOS");
+#endif
+ if (!style.isEmpty())
+ qCDebug(lcQtQuickControlsStyle) << "using" << style << "as a default";
+ else
+ qCDebug(lcQtQuickControlsStyle) << "no appropriate style found; using Basic as a default";
+ }
+
+ // If it's still empty by this point, then it means we have no native style available for this platform,
+ // as is the case on e.g. embedded. In that case, we want to default to the Basic style,
+ // which is what effectiveStyleName() returns when "style" is empty.
+ custom = !builtInStyleList.contains(QQuickStylePrivate::effectiveStyleName(style));
+
+ resolved = true;
+
+ qCDebug(lcQtQuickControlsStyle).nospace() << "done resolving:"
+ << "\n style=" << style
+ << "\n custom=" << custom
+ << "\n resolved=" << resolved
+ << "\n fallbackStyle=" << fallbackStyle
+ << "\n fallbackMethod=" << fallbackMethod
+ << "\n configFilePath=" << configFilePath;
+ }
+
+ void reset()
+ {
+ qCDebug(lcQtQuickControlsStyle) << "resetting values to their defaults";
+
+ custom = false;
+ resolved = false;
+ usingDefaultStyle = false;
+ style.clear();
+ fallbackStyle.clear();
+ fallbackMethod.clear();
+ configFilePath.clear();
+ }
+
+ QString resolveConfigFilePath()
+ {
+ if (configFilePath.isEmpty()) {
+ configFilePath = QFile::decodeName(qgetenv("QT_QUICK_CONTROLS_CONF"));
+ if (configFilePath.isEmpty() || !QFile::exists(configFilePath)) {
+ if (!configFilePath.isEmpty())
+ qWarning("QT_QUICK_CONTROLS_CONF=%s: No such file", qPrintable(configFilePath));
+
+ configFilePath = QStringLiteral(":/qtquickcontrols2.conf");
+ }
+ }
+ return configFilePath;
+ }
+
+ // Is this a custom style defined by the user and not "built-in" style?
+ bool custom = false;
+ // Have we resolved the style yet?
+ bool resolved = false;
+ // Are we using the default style for this platform (because no style was specified)?
+ bool usingDefaultStyle = false;
+ // The name of the style.
+ QString style;
+ // The built-in style to use if the requested style cannot be found.
+ QString fallbackStyle;
+ // A description of the way in which fallbackStyle was set, used in e.g. warning messages shown to the user.
+ QByteArray fallbackMethod;
+ // The path to the qtquickcontrols2.conf file.
+ QString configFilePath;
+};
+
+Q_GLOBAL_STATIC(QQuickStyleSpec, styleSpec)
+
+/*
+ Note that most of these functions (with the exception of e.g. isResolved())
+ should not be called before the style has been resolved, as it's only after
+ that happens that they will have been set.
+*/
+QString QQuickStylePrivate::style()
+{
+ return styleSpec()->style;
+}
+
+QString QQuickStylePrivate::effectiveStyleName(const QString &styleName)
+{
+ return !styleName.isEmpty() ? styleName : QLatin1String("Basic");
+}
+
+QString QQuickStylePrivate::fallbackStyle()
+{
+ return styleSpec()->fallbackStyle;
+}
+
+bool QQuickStylePrivate::isCustomStyle()
+{
+ return styleSpec()->custom;
+}
+
+bool QQuickStylePrivate::isResolved()
+{
+ return styleSpec()->resolved;
+}
+
+bool QQuickStylePrivate::isUsingDefaultStyle()
+{
+ return styleSpec()->usingDefaultStyle;
+}
+
+void QQuickStylePrivate::init()
+{
+ QQuickStyleSpec *spec = styleSpec();
+ spec->resolve();
+}
+
+void QQuickStylePrivate::reset()
+{
+ if (styleSpec())
+ styleSpec()->reset();
+}
+
+QString QQuickStylePrivate::configFilePath()
+{
+ return styleSpec()->resolveConfigFilePath();
+}
+
+QSharedPointer<QSettings> QQuickStylePrivate::settings(const QString &group)
+{
+#ifndef QT_NO_SETTINGS
+ const QString filePath = QQuickStylePrivate::configFilePath();
+ if (QFile::exists(filePath)) {
+ QFileSelector selector;
+ QSettings *settings = new QSettings(selector.select(filePath), QSettings::IniFormat);
+ if (!group.isEmpty())
+ settings->beginGroup(group);
+ return QSharedPointer<QSettings>(settings);
+ }
+#endif // QT_NO_SETTINGS
+ Q_UNUSED(group)
+ return QSharedPointer<QSettings>();
+}
+
+#if QT_CONFIG(settings)
+static void readValue(const QSharedPointer<QSettings> &settings, const QString &name, std::function<void(const QVariant &)> setValue)
+{
+ const QVariant var = settings->value(name);
+ if (var.isValid())
+ setValue(var);
+}
+
+template <typename Enum>
+static Enum toEnumValue(const QVariant &var)
+{
+ // ### TODO: expose QFont enums to the meta object system using Q_ENUM
+ //QMetaEnum enumeration = QMetaEnum::fromType<Enum>();
+ //bool ok = false;
+ //int value = enumeration.keyToValue(var.toByteArray(), &ok);
+ //if (!ok)
+ // value = var.toInt();
+ //return static_cast<Enum>(value);
+
+ return static_cast<Enum>(var.toInt());
+}
+
+const QFont *QQuickStylePrivate::readFont(const QSharedPointer<QSettings> &settings)
+{
+ const QVariant var = settings->value(QStringLiteral("Font"));
+ if (var.isValid())
+ return new QFont(var.value<QFont>());
+
+ QFont f;
+ settings->beginGroup(QStringLiteral("Font"));
+ readValue(settings, QStringLiteral("Family"), [&f](const QVariant &var) { f.setFamilies(QStringList{var.toString()}); });
+ readValue(settings, QStringLiteral("PointSize"), [&f](const QVariant &var) { f.setPointSizeF(var.toReal()); });
+ readValue(settings, QStringLiteral("PixelSize"), [&f](const QVariant &var) { f.setPixelSize(var.toInt()); });
+ readValue(settings, QStringLiteral("StyleHint"), [&f](const QVariant &var) { f.setStyleHint(toEnumValue<QFont::StyleHint>(var.toInt())); });
+ readValue(settings, QStringLiteral("Weight"), [&f](const QVariant &var) { f.setWeight(toEnumValue<QFont::Weight>(var)); });
+ readValue(settings, QStringLiteral("Style"), [&f](const QVariant &var) { f.setStyle(toEnumValue<QFont::Style>(var.toInt())); });
+ settings->endGroup();
+ return new QFont(f);
+}
+
+static void readColorGroup(const QSharedPointer<QSettings> &settings, QPalette::ColorGroup group, QPalette *palette)
+{
+ const QStringList keys = settings->childKeys();
+ if (keys.isEmpty())
+ return;
+
+ static const int index = QPalette::staticMetaObject.indexOfEnumerator("ColorRole");
+ Q_ASSERT(index != -1);
+ QMetaEnum metaEnum = QPalette::staticMetaObject.enumerator(index);
+
+ for (const QString &key : keys) {
+ bool ok = false;
+ int role = metaEnum.keyToValue(key.toUtf8(), &ok);
+ if (ok)
+ palette->setColor(group, static_cast<QPalette::ColorRole>(role), settings->value(key).value<QColor>());
+ }
+}
+
+const QPalette *QQuickStylePrivate::readPalette(const QSharedPointer<QSettings> &settings)
+{
+ QPalette p;
+ settings->beginGroup(QStringLiteral("Palette"));
+ readColorGroup(settings, QPalette::All, &p);
+
+ settings->beginGroup(QStringLiteral("Normal"));
+ readColorGroup(settings, QPalette::Normal, &p);
+ settings->endGroup();
+
+ settings->beginGroup(QStringLiteral("Disabled"));
+ readColorGroup(settings, QPalette::Disabled, &p);
+ settings->endGroup();
+ return new QPalette(p);
+}
+#endif // QT_CONFIG(settings)
+
+bool QQuickStylePrivate::isDarkSystemTheme()
+{
+ const bool dark = [](){
+ if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
+ return theme->colorScheme() == Qt::ColorScheme::Dark;
+ return false;
+ }();
+ return dark;
+}
+
+QStringList QQuickStylePrivate::builtInStyles()
+{
+ return {
+ QLatin1String("Basic"),
+ QLatin1String("Fusion"),
+ QLatin1String("Imagine"),
+#ifdef Q_OS_MACOS
+ QLatin1String("macOS"),
+ QLatin1String("iOS"),
+#endif
+#ifdef Q_OS_IOS
+ QLatin1String("iOS"),
+#endif
+ QLatin1String("Material"),
+ QLatin1String("Universal"),
+#ifdef Q_OS_WINDOWS
+ QLatin1String("Windows")
+#endif
+ };
+}
+
+/*!
+ Returns the name of the application style.
+
+ \note The application style can be specified by passing a \c -style command
+ line argument. Therefore \c name() may not return a fully resolved
+ value if called before constructing a QGuiApplication.
+*/
+QString QQuickStyle::name()
+{
+ return styleSpec()->name();
+}
+
+/*!
+ Sets the application style to \a style.
+
+ \note The style must be configured \b before loading QML that imports Qt Quick Controls.
+ It is not possible to change the style after the QML types have been registered.
+
+ \sa setFallbackStyle(), {Using Styles in Qt Quick Controls}
+*/
+void QQuickStyle::setStyle(const QString &style)
+{
+ qCDebug(lcQtQuickControlsStyle) << "setStyle called with" << style;
+
+ if (QQmlMetaType::matchingModuleVersion(
+ QStringLiteral("QtQuick.Controls"), QTypeRevision::fromVersion(2, 0)).isValid()) {
+ qWarning() << "ERROR: QQuickStyle::setStyle() must be called before loading QML that imports Qt Quick Controls 2.";
+ return;
+ }
+
+ styleSpec()->setStyle(style);
+}
+
+/*!
+ \since 5.8
+ Sets the application fallback style to \a style.
+
+ \note The fallback style must be the name of one of the built-in Qt Quick Controls styles, e.g. "Material".
+
+ \note The style must be configured \b before loading QML that imports Qt Quick Controls.
+ It is not possible to change the style after the QML types have been registered.
+
+ The fallback style can be also specified by setting the \c QT_QUICK_CONTROLS_FALLBACK_STYLE
+ \l {Supported Environment Variables in Qt Quick Controls}{environment variable}.
+
+ \sa setStyle(), {Using Styles in Qt Quick Controls}
+*/
+void QQuickStyle::setFallbackStyle(const QString &style)
+{
+ if (QQmlMetaType::matchingModuleVersion(
+ QStringLiteral("QtQuick.Controls"), QTypeRevision::fromVersion(2, 0)).isValid()) {
+ qWarning() << "ERROR: QQuickStyle::setFallbackStyle() must be called before loading QML that imports Qt Quick Controls 2.";
+ return;
+ }
+
+ styleSpec()->setFallbackStyle(style, "QQuickStyle::setFallbackStyle()");
+}
+
+QT_END_NAMESPACE