diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp index bacb30a1032..97735e4af63 100644 --- a/src/gui/image/qiconloader.cpp +++ b/src/gui/image/qiconloader.cpp @@ -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 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(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."; diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h index 3cfa9381d1a..4a8079a3e98 100644 --- a/src/gui/image/qiconloader_p.h +++ b/src/gui/image/qiconloader_p.h @@ -30,6 +30,7 @@ #include #include +#include QT_BEGIN_NAMESPACE @@ -161,6 +162,8 @@ public: QList> 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 m_factory; bool m_supportsSvg; bool m_initialized; diff --git a/tests/auto/gui/image/qicon/CMakeLists.txt b/tests/auto/gui/image/qicon/CMakeLists.txt index c693c559cc3..93f75741c0e 100644 --- a/tests/auto/gui/image/qicon/CMakeLists.txt +++ b/tests/auto/gui/image/qicon/CMakeLists.txt @@ -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) diff --git a/tests/auto/gui/image/qicon/plugin/CMakeLists.txt b/tests/auto/gui/image/qicon/plugin/CMakeLists.txt new file mode 100644 index 00000000000..cae49b2df17 --- /dev/null +++ b/tests/auto/gui/image/qicon/plugin/CMakeLists.txt @@ -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 +) diff --git a/tests/auto/gui/image/qicon/plugin/main.cpp b/tests/auto/gui/image/qicon/plugin/main.cpp new file mode 100644 index 00000000000..58d9807142c --- /dev/null +++ b/tests/auto/gui/image/qicon/plugin/main.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include +#include + +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 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" diff --git a/tests/auto/gui/image/qicon/plugin/plugin.json b/tests/auto/gui/image/qicon/plugin/plugin.json new file mode 100644 index 00000000000..96b59aa79e1 --- /dev/null +++ b/tests/auto/gui/image/qicon/plugin/plugin.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "plugintheme", "SpecialTheme" ] +} diff --git a/tests/auto/gui/image/qicon/tst_qicon.cpp b/tests/auto/gui/image/qicon/tst_qicon.cpp index 99b4a0589ef..d95ee66fb68 100644 --- a/tests/auto/gui/image/qicon/tst_qicon.cpp +++ b/tests/auto/gui/image/qicon/tst_qicon.cpp @@ -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("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"