Revision control
Copy as Markdown
// Copyright (C) 2020 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
// Qt-Security score:significant reason:default
#include "qquickstyle.h"
#include "qquickstyle_p.h"
#include "qquickstyleplugin_p.h"
#include <QtCore/private/qfileselector_p.h>
#include <QtCore/qloggingcategory.h>
#include <QtGui/qstylehints.h>
#include <QtGui/qguiapplication.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlfile.h>
#include <QtQml/private/qqmlmetatype_p.h>
#include <QtQuickTemplates2/private/qquicktheme_p_p.h>
#include <QtCore/qtyperevision.h>
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcStylePlugin, "qt.quick.controls.styleplugin")
class QQuickThemeChangeObserver : public QObject {
Q_OBJECT
public:
explicit QQuickThemeChangeObserver()
{
QStyleHints *styleHints = QGuiApplication::styleHints();
moveToThread(styleHints->thread());
styleHints->installEventFilter(this);
}
Q_SIGNALS:
void paletteOrThemeChanged();
protected:
bool eventFilter(QObject *object, QEvent *event) override {
Q_UNUSED(object);
if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ThemeChange)
emit paletteOrThemeChanged();
return false;
}
};
// The custom destructor ensures that all posted events gets processed before the event filter is deleted.
void QQuickStylePlugin::ObserverDeleter::operator()(QQuickThemeChangeObserver *observer)
{
observer->disconnect();
observer->deleteLater();
}
QQuickStylePlugin::QQuickStylePlugin(QObject *parent)
: QQmlExtensionPlugin(parent)
{
}
QQuickStylePlugin::~QQuickStylePlugin()
{
}
void QQuickStylePlugin::registerTypes(const char *uri)
{
qCDebug(lcStylePlugin).nospace() << "registerTypes called with uri " << uri << "; plugin name is " << name();
const QTypeRevision latestControlsRevision = QQmlMetaType::latestModuleVersion(QLatin1String("QtQuick.Controls"));
// Use the private function because we don't want to cause resolve() to be called,
// as the logic that assigns a default style if one wasn't set would interfere with compile-time style selection.
QString styleName = QQuickStylePrivate::style();
if (!latestControlsRevision.isValid() && styleName.isEmpty()) {
// The user hasn't imported QtQuick.Controls, nor set a style via the runtime methods.
qCDebug(lcStylePlugin).nospace() << uri << " imported before QtQuick.Controls; using compile-time style selection";
QQuickStyle::setStyle(name());
styleName = name();
}
// Even if this style plugin isn't for the style set by the user,
// we still want to create the theme object, because that function
// is also responsible for reading values from qtquickcontrols2.conf.
// So, even if a style doesn't have a QQuickTheme, it can still have
// values set for (e.g. fonts and palettes) in qtquickcontrols2.conf.
const QString effectiveCurrentStyleName = QQuickStylePrivate::effectiveStyleName(styleName);
auto theme = QQuickTheme::instance();
if (!theme) {
qCDebug(lcStylePlugin) << "creating theme";
theme = createTheme(effectiveCurrentStyleName);
}
// The primary fallback is the style set by the user. We need to check for that here
// so that we can ensure that fallback styles' themes are initialized (QTBUG-117403)
// without also allowing the Basic style to be initialized, as it is a secondary fallback
// for every built-in style (and only built-in styles can be fallbacks).
const bool thisPluginBelongsToCurrentStyle = name() == effectiveCurrentStyleName;
const bool isPrimaryFallback = name() == QQuickStylePrivate::fallbackStyle();
if (!thisPluginBelongsToCurrentStyle && !isPrimaryFallback) {
qCDebug(lcStylePlugin).nospace() << "this style plugin does not belong to the current ("
<< effectiveCurrentStyleName << ") or fallback (" << QQuickStylePrivate::fallbackStyle()
<< ") style; not calling initializeTheme()";
return;
}
if (thisPluginBelongsToCurrentStyle) {
qCDebug(lcStylePlugin).nospace() << "this style plugin belongs to the current style "
<< effectiveCurrentStyleName << "; calling initializeTheme()";
} else {
qCDebug(lcStylePlugin).nospace() << "this style plugin belongs to the fallback style "
<< QQuickStylePrivate::fallbackStyle() << "; calling initializeTheme()";
}
if (!isPrimaryFallback && !styleName.isEmpty())
QFileSelectorPrivate::addStatics(QStringList() << styleName);
// Create and initialize the theme, and hook it up to palette and theme changes.
// We are currently in the QML loader thread, but we have to return with an
// initialized theme so that controls get the right default values for
// palette and font. So we have to initialize the theme right now, even though
// that will access QGuiApplication globals (such as a the QPlatformTheme or
// QStyleHints singletons) from the wrong thread. It is ok, as the loader
// process is blocking the GUI thread anyway, so there are no concurrent
// writes to those singletons.
initializeTheme(theme);
// Updates must however go through the main thread. The Observer moves itself
// to the same thread as the QStyleHints instance, which is the main thread.
themeChangeObserver.reset(new QQuickThemeChangeObserver);
connect(themeChangeObserver.get(), &QQuickThemeChangeObserver::paletteOrThemeChanged,
this, &QQuickStylePlugin::updateTheme, Qt::DirectConnection);
}
void QQuickStylePlugin::unregisterTypes()
{
qCDebug(lcStylePlugin) << "unregisterTypes called; plugin name is" << name();
if (!QQuickThemePrivate::instance)
return;
themeChangeObserver.reset();
const bool isPrimaryFallback = name() == QQuickStylePrivate::fallbackStyle();
const QString styleName = QQuickStylePrivate::style();
if (!isPrimaryFallback && !styleName.isEmpty())
QFileSelectorPrivate::removeStatics(QStringList() << styleName);
// Not every style has a plugin - some styles are QML-only. So, we clean this
// stuff up when the first style plugin is unregistered rather than when the
// plugin for the current style is unregistered.
QQuickThemePrivate::instance.reset();
QQuickStylePrivate::reset();
}
/*!
\internal
Responsible for setting the font and palette settings that were specified in the
qtquickcontrols2.conf file.
Style-specific settings (e.g. Variant=Dense) are read in the constructor of the
appropriate style plugin (e.g. QtQuickControls2MaterialStylePlugin).
Implicit style-specific font and palette values are assigned in the relevant theme
(e.g. QQuickMaterialTheme).
*/
QQuickTheme *QQuickStylePlugin::createTheme(const QString &name)
{
qCDebug(lcStylePlugin) << "creating QQuickTheme instance to be initialized by style-specific theme of" << name;
QQuickTheme *theme = new QQuickTheme;
#if QT_CONFIG(settings)
QQuickThemePrivate *p = QQuickThemePrivate::get(theme);
QSharedPointer<QSettings> settings = QQuickStylePrivate::settings(name);
if (settings) {
p->defaultFont.reset(QQuickStylePrivate::readFont(settings));
// Set the default font as the System scope, because that's what
// QQuickControlPrivate::parentFont() uses as its fallback if no
// parent item has a font explicitly set. QQuickControlPrivate::parentFont()
// is used as the starting point for font inheritance/resolution.
// The same goes for palettes below.
theme->setFont(QQuickTheme::System, *p->defaultFont);
p->defaultPalette.reset(QQuickStylePrivate::readPalette(settings));
theme->setPalette(QQuickTheme::System, *p->defaultPalette);
}
#endif
QQuickThemePrivate::instance.reset(theme);
return theme;
}
QT_END_NAMESPACE
#include "qquickstyleplugin.moc"
#include "moc_qquickstyleplugin_p.cpp"