QIcon: enable icon engine plugins to implement themes
So far, the keys of icon engine plugins were only interpreted as the suffix of icon files, loaded via QIcon(filename). However, an icon engine could provide a lot more flexibility if it could implement an entire theme. Match the list of keys a plugin can register itself with also against the current theme name. If a matching plugin is found, use that plugin to create the icon engine. Store the factory from the plugin to avoid costly lookups for each icon. Extend the QIcon test case by adding a custom plugin that supports two themes. Since the plugin and icon engine creation infrastructure doesn't communicate which theme the plugin was created for, use QIcon::themeName to record the current theme when the engine gets created. [ChangeLog][QtGui][QIconEnginePlugin] The keys registered by an QIconEnginePlugin implementation are now also matched against the current theme (system or user theme), allowing engine providers to implement entire themes through a plugin. Change-Id: I8a5e30ff8b5bb7c78b5204e82760e4328671e4c1 Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
This commit is contained in:
parent
5831c31235
commit
87896c03c1
@ -137,6 +137,10 @@ void QIconLoader::invalidateKey()
|
||||
// recreating the actual engine the next time the icon is used.
|
||||
// We don't need to clear the QIcon cache itself.
|
||||
m_themeKey++;
|
||||
|
||||
// invalidating the factory results in us looking once for
|
||||
// a plugin that provides icon for the new themeName()
|
||||
m_factory = std::nullopt;
|
||||
}
|
||||
|
||||
QString QIconLoader::themeName() const
|
||||
@ -650,7 +654,18 @@ QIconEngine *QIconLoader::iconEngine(const QString &iconName) const
|
||||
qCDebug(lcIconLoader) << "Resolving icon engine for icon" << iconName;
|
||||
|
||||
std::unique_ptr<QIconEngine> iconEngine;
|
||||
if (hasUserTheme())
|
||||
|
||||
if (!m_factory) {
|
||||
qCDebug(lcIconLoader) << "Finding a plugin for theme" << themeName();
|
||||
// try to find a plugin that supports the current theme
|
||||
const int factoryIndex = qt_iconEngineFactoryLoader()->indexOf(themeName());
|
||||
if (factoryIndex >= 0)
|
||||
m_factory = qobject_cast<QIconEnginePlugin *>(qt_iconEngineFactoryLoader()->instance(factoryIndex));
|
||||
}
|
||||
if (m_factory && *m_factory)
|
||||
iconEngine.reset(m_factory.value()->create(iconName));
|
||||
|
||||
if (hasUserTheme() && (!iconEngine || iconEngine->isNull()))
|
||||
iconEngine.reset(new QIconLoaderEngine(iconName));
|
||||
if (!iconEngine || iconEngine->isNull()) {
|
||||
qCDebug(lcIconLoader) << "Icon is not available from theme or fallback theme.";
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -161,6 +162,8 @@ public:
|
||||
QList<QSharedPointer<QIconCacheGtkReader>> m_gtkCaches;
|
||||
};
|
||||
|
||||
class QIconEnginePlugin;
|
||||
|
||||
class Q_GUI_EXPORT QIconLoader
|
||||
{
|
||||
public:
|
||||
@ -195,6 +198,7 @@ private:
|
||||
QThemeIconInfo lookupFallbackIcon(const QString &iconName) const;
|
||||
|
||||
uint m_themeKey;
|
||||
mutable std::optional<QIconEnginePlugin *> m_factory;
|
||||
bool m_supportsSvg;
|
||||
bool m_initialized;
|
||||
|
||||
|
@ -37,11 +37,14 @@ file(GLOB_RECURSE test_data_glob
|
||||
*.svgz)
|
||||
list(APPEND test_data ${test_data_glob})
|
||||
|
||||
add_subdirectory(plugin)
|
||||
|
||||
qt_internal_add_test(tst_qicon
|
||||
SOURCES
|
||||
tst_qicon.cpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
TestIconPlugin
|
||||
TESTDATA ${test_data}
|
||||
)
|
||||
|
||||
@ -105,3 +108,5 @@ qt_internal_extend_target(tst_qicon CONDITION TARGET Qt::Widgets
|
||||
LIBRARIES
|
||||
Qt::Widgets
|
||||
)
|
||||
|
||||
add_dependencies(tst_qicon TestIconPlugin)
|
||||
|
20
tests/auto/gui/image/qicon/plugin/CMakeLists.txt
Normal file
20
tests/auto/gui/image/qicon/plugin/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#####################################################################
|
||||
## TestIconEngine Plugin:
|
||||
#####################################################################
|
||||
|
||||
qt_internal_add_plugin(TestIconPlugin
|
||||
STATIC
|
||||
OUTPUT_NAME qtesticonplugin
|
||||
OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
SKIP_INSTALL
|
||||
PLUGIN_TYPE iconengines
|
||||
DEFAULT_IF TRUE
|
||||
SOURCES
|
||||
main.cpp
|
||||
LIBRARIES
|
||||
Qt::Core
|
||||
Qt::Gui
|
||||
)
|
73
tests/auto/gui/image/qicon/plugin/main.cpp
Normal file
73
tests/auto/gui/image/qicon/plugin/main.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include <qiconengineplugin.h>
|
||||
#include <qiconengine.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class TestIconPlugin : public QIconEnginePlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QIconEngineFactoryInterface" FILE "plugin.json")
|
||||
|
||||
public:
|
||||
QIconEngine *create(const QString &icon) override;
|
||||
};
|
||||
|
||||
class TestIconEngine : public QIconEngine
|
||||
{
|
||||
public:
|
||||
TestIconEngine(const QString &icon)
|
||||
: m_iconName(QIcon::themeName() + "/" + icon)
|
||||
{
|
||||
}
|
||||
|
||||
~TestIconEngine()
|
||||
{}
|
||||
|
||||
QIconEngine *clone() const override
|
||||
{
|
||||
return new TestIconEngine(m_iconName);
|
||||
}
|
||||
|
||||
QString key() const override
|
||||
{
|
||||
return QStringLiteral("TestIconEngine");
|
||||
}
|
||||
|
||||
QString iconName() override
|
||||
{
|
||||
return m_iconName;
|
||||
}
|
||||
|
||||
bool isNull() override
|
||||
{
|
||||
return m_iconName.isNull();
|
||||
}
|
||||
|
||||
QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override
|
||||
{
|
||||
return {{16, 16}, {48, 48}, {64, 64}};
|
||||
}
|
||||
|
||||
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override
|
||||
{
|
||||
Q_UNUSED(painter);
|
||||
Q_UNUSED(rect);
|
||||
Q_UNUSED(mode);
|
||||
Q_UNUSED(state);
|
||||
}
|
||||
|
||||
private:
|
||||
const QString m_iconName;
|
||||
};
|
||||
|
||||
QIconEngine *TestIconPlugin::create(const QString &icon)
|
||||
{
|
||||
return new TestIconEngine(icon);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "main.moc"
|
3
tests/auto/gui/image/qicon/plugin/plugin.json
Normal file
3
tests/auto/gui/image/qicon/plugin/plugin.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Keys": [ "plugintheme", "SpecialTheme" ]
|
||||
}
|
@ -48,6 +48,9 @@ private slots:
|
||||
#endif
|
||||
void task223279_inconsistentAddFile();
|
||||
|
||||
void themeFromPlugin_data();
|
||||
void themeFromPlugin();
|
||||
|
||||
private:
|
||||
bool haveImageFormat(QByteArray const&);
|
||||
|
||||
@ -872,6 +875,32 @@ void tst_QIcon::task223279_inconsistentAddFile()
|
||||
QCOMPARE(pm1.size(), pm2.size());
|
||||
}
|
||||
|
||||
Q_IMPORT_PLUGIN(TestIconPlugin)
|
||||
|
||||
void tst_QIcon::themeFromPlugin_data()
|
||||
{
|
||||
QTest::addColumn<QString>("themeName");
|
||||
|
||||
QTest::addRow("plugintheme") << "plugintheme";
|
||||
QTest::addRow("specialtheme") << "specialTheme"; // deliberately not matching case
|
||||
}
|
||||
|
||||
void tst_QIcon::themeFromPlugin()
|
||||
{
|
||||
QFETCH(const QString, themeName);
|
||||
auto restoreTheme = qScopeGuard([oldTheme = QIcon::themeName()]{
|
||||
QIcon::setThemeName(oldTheme);
|
||||
});
|
||||
|
||||
QIcon icon = QIcon::fromTheme("icon1");
|
||||
QVERIFY(icon.isNull());
|
||||
|
||||
QIcon::setThemeName(themeName);
|
||||
|
||||
icon = QIcon::fromTheme("icon1");
|
||||
QVERIFY(!icon.isNull());
|
||||
QCOMPARE(icon.name(), themeName + "/icon1");
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QIcon)
|
||||
#include "tst_qicon.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user