diff options
author | Alexander Volkov <a.volkov@rusbitech.ru> | 2017-12-20 16:03:42 +0300 |
---|---|---|
committer | Alexander Volkov <a.volkov@rusbitech.ru> | 2018-01-15 10:12:22 +0000 |
commit | 85aa0fd041fbaa258f089d86f227311e53f6206a (patch) | |
tree | bafaed0633d4034232af4a9d725d009e0c2a7155 | |
parent | 537af273026eaf0a18d2151ad6928b7797d7955b (diff) |
Introduce QIcon::fallbackSearchPaths()
... that will be used if an icon can't be found in the
current theme.
The Icon Theme Specification
https://standards.freedesktop.org/icon-theme-spec/latest/ar01s05.html
states that unthemed icons must be searched in the base directories,
i.e. /usr/share/icons, ... But in practice unthemed icons are
installed into /usr/share/pixmaps and this dir is not used as
a base dir for icon themes. So it's better to explicitly specify
fallback dirs to avoid needless access to the filesystem.
Also some KDE application install their own unthemed icons
(into /usr/share/<appname>/pics), that can't be found by
QIconLoader. With this change it would be possible for them
to specify dirs with unthemed icons and thus be displayed
correctly in non-KDE environments.
[ChangeLog][QtGui][QIcon] Added fallbackSearchPaths() that
will be used to find icons missing in the current icon theme.
Change-Id: I0dc55ba958b29356a3b0a2123d6b8faa24d4c91e
Task-number: QTBUG-33123
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
-rw-r--r-- | src/gui/doc/snippets/code/src_gui_image_qicon.cpp | 3 | ||||
-rw-r--r-- | src/gui/image/qicon.cpp | 35 | ||||
-rw-r--r-- | src/gui/image/qicon.h | 3 | ||||
-rw-r--r-- | src/gui/image/qiconloader.cpp | 88 | ||||
-rw-r--r-- | src/gui/image/qiconloader_p.h | 7 | ||||
-rw-r--r-- | src/gui/kernel/qplatformtheme.cpp | 2 | ||||
-rw-r--r-- | src/gui/kernel/qplatformtheme.h | 3 | ||||
-rw-r--r-- | src/platformsupport/themes/genericunix/qgenericunixthemes.cpp | 8 | ||||
-rw-r--r-- | src/platformsupport/themes/genericunix/qgenericunixthemes_p.h | 1 | ||||
-rw-r--r-- | tests/auto/gui/image/qicon/fallback_icons/red.png | bin | 0 -> 105 bytes | |||
-rw-r--r-- | tests/auto/gui/image/qicon/qicon.pro | 2 | ||||
-rw-r--r-- | tests/auto/gui/image/qicon/tst_qicon.cpp | 11 | ||||
-rw-r--r-- | tests/auto/gui/image/qicon/tst_qicon.qrc | 1 |
13 files changed, 155 insertions, 9 deletions
diff --git a/src/gui/doc/snippets/code/src_gui_image_qicon.cpp b/src/gui/doc/snippets/code/src_gui_image_qicon.cpp index f472494e4a..faad6574a7 100644 --- a/src/gui/doc/snippets/code/src_gui_image_qicon.cpp +++ b/src/gui/doc/snippets/code/src_gui_image_qicon.cpp @@ -79,3 +79,6 @@ void MyWidget::drawIcon(QPainter *painter, QPoint pos) QIcon undoicon = QIcon::fromTheme("edit-undo", QIcon(":/undo.png")); //! [4] +//! [5] + QIcon::setFallbackSearchPaths(QIcon::fallbackSearchPaths() << "my/search/path"); +//! [5] diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp index 9b2e96d4b0..32fa9e75ac 100644 --- a/src/gui/image/qicon.cpp +++ b/src/gui/image/qicon.cpp @@ -1158,6 +1158,36 @@ QStringList QIcon::themeSearchPaths() } /*! + \since 5.11 + + Returns the fallback search paths for icons. + + The default value will depend on the platform. + + \sa setFallbackSearchPaths(), themeSearchPaths() +*/ +QStringList QIcon::fallbackSearchPaths() +{ + return QIconLoader::instance()->fallbackSearchPaths(); +} + +/*! + \since 5.11 + + Sets the fallback search paths for icons to \a paths. + + \note To add some path without replacing existing ones: + + \snippet code/src_gui_image_qicon.cpp 5 + + \sa fallbackSearchPaths(), setThemeSearchPaths() +*/ +void QIcon::setFallbackSearchPaths(const QStringList &paths) +{ + QIconLoader::instance()->setFallbackSearchPaths(paths); +} + +/*! \since 4.6 Sets the current icon theme to \a name. @@ -1216,7 +1246,10 @@ QString QIcon::themeName() the lookup. These caches can be generated using gtk-update-icon-cache: \l{https://developer.gnome.org/gtk3/stable/gtk-update-icon-cache.html}. - \sa themeName(), setThemeName(), themeSearchPaths() + \note If an icon can't be found in the current theme, then it will be + searched in fallbackSearchPaths() as an unthemed icon. + + \sa themeName(), setThemeName(), themeSearchPaths(), fallbackSearchPaths() */ QIcon QIcon::fromTheme(const QString &name) { diff --git a/src/gui/image/qicon.h b/src/gui/image/qicon.h index 4832455c9f..653ba6fda4 100644 --- a/src/gui/image/qicon.h +++ b/src/gui/image/qicon.h @@ -118,6 +118,9 @@ public: static QStringList themeSearchPaths(); static void setThemeSearchPaths(const QStringList &searchpath); + static QStringList fallbackSearchPaths(); + static void setFallbackSearchPaths(const QStringList &paths); + static QString themeName(); static void setThemeName(const QString &path); diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp index 3cb6f46bd6..1ea4f1340b 100644 --- a/src/gui/image/qiconloader.cpp +++ b/src/gui/image/qiconloader.cpp @@ -95,6 +95,16 @@ static inline QStringList systemIconSearchPaths() return QStringList(); } +static inline QStringList systemFallbackSearchPaths() +{ + if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { + const QVariant themeHint = theme->themeHint(QPlatformTheme::IconFallbackSearchPaths); + if (themeHint.isValid()) + return themeHint.toStringList(); + } + return QStringList(); +} + extern QFactoryLoader *qt_iconEngineFactoryLoader(); // qicon.cpp void QIconLoader::ensureInitialized() @@ -158,6 +168,20 @@ QStringList QIconLoader::themeSearchPaths() const return m_iconDirs; } +void QIconLoader::setFallbackSearchPaths(const QStringList &searchPaths) +{ + m_fallbackDirs = searchPaths; + invalidateKey(); +} + +QStringList QIconLoader::fallbackSearchPaths() const +{ + if (m_fallbackDirs.isEmpty()) { + m_fallbackDirs = systemFallbackSearchPaths(); + } + return m_fallbackDirs; +} + /*! \internal Helper class that reads and looks up into the icon-theme.cache generated with @@ -481,11 +505,54 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, return info; } +QThemeIconInfo QIconLoader::lookupFallbackIcon(const QString &iconName) const +{ + QThemeIconInfo info; + + const QString pngIconName = iconName + QLatin1String(".png"); + const QString xpmIconName = iconName + QLatin1String(".xpm"); + const QString svgIconName = iconName + QLatin1String(".svg"); + + const auto searchPaths = QIcon::fallbackSearchPaths(); + for (const QString &iconDir: searchPaths) { + QDir currentDir(iconDir); + if (currentDir.exists(pngIconName)) { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir.type = QIconDirInfo::Fallback; + iconEntry->filename = currentDir.filePath(pngIconName); + info.entries.append(iconEntry); + break; + } else if (currentDir.exists(xpmIconName)) { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir.type = QIconDirInfo::Fallback; + iconEntry->filename = currentDir.filePath(xpmIconName); + info.entries.append(iconEntry); + break; + } else if (m_supportsSvg && + currentDir.exists(svgIconName)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir.type = QIconDirInfo::Fallback; + iconEntry->filename = currentDir.filePath(svgIconName); + info.entries.append(iconEntry); + break; + } + } + + if (!info.entries.isEmpty()) + info.iconName = iconName; + + return info; +} + QThemeIconInfo QIconLoader::loadIcon(const QString &name) const { if (!themeName().isEmpty()) { QStringList visited; - return findIconHelper(themeName(), name, visited); + const QThemeIconInfo iconInfo = findIconHelper(themeName(), name, visited); + if (!iconInfo.entries.isEmpty()) + return iconInfo; + + return lookupFallbackIcon(name); } return QThemeIconInfo(); @@ -573,6 +640,8 @@ static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize, int icon } else if (dir.type == QIconDirInfo::Threshold) { return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold; + } else if (dir.type == QIconDirInfo::Fallback) { + return true; } Q_ASSERT(1); // Not a valid value @@ -603,6 +672,8 @@ static int directorySizeDistance(const QIconDirInfo &dir, int iconsize, int icon else if (scaledIconSize > (dir.size + dir.threshold) * dir.scale) return scaledIconSize - dir.maxSize * dir.scale; else return 0; + } else if (dir.type == QIconDirInfo::Fallback) { + return 0; } Q_ASSERT(1); // Not a valid value @@ -657,9 +728,11 @@ QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, QIconLoaderEngineEntry *entry = entryForSize(m_info, size); if (entry) { const QIconDirInfo &dir = entry->dir; - if (dir.type == QIconDirInfo::Scalable) + if (dir.type == QIconDirInfo::Scalable) { return size; - else { + } else if (dir.type == QIconDirInfo::Fallback) { + return QIcon(entry->filename).actualSize(size, mode, state); + } else { int result = qMin<int>(dir.size, qMin(size.width(), size.height())); return QSize(result, result); } @@ -745,8 +818,13 @@ void QIconLoaderEngine::virtual_hook(int id, void *data) // Gets all sizes from the DirectoryInfo entries for (int i = 0; i < N; ++i) { - int size = m_info.entries.at(i)->dir.size; - sizes.append(QSize(size, size)); + const QIconLoaderEngineEntry *entry = m_info.entries.at(i); + if (entry->dir.type == QIconDirInfo::Fallback) { + sizes.append(QIcon(entry->filename).availableSizes()); + } else { + int size = entry->dir.size; + sizes.append(QSize(size, size)); + } } arg.sizes.swap(sizes); // commit } diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h index 0007437ee9..746e871fb1 100644 --- a/src/gui/image/qiconloader_p.h +++ b/src/gui/image/qiconloader_p.h @@ -69,7 +69,7 @@ class QIconLoader; struct QIconDirInfo { - enum Type { Fixed, Scalable, Threshold }; + enum Type { Fixed, Scalable, Threshold, Fallback }; QIconDirInfo(const QString &_path = QString()) : path(_path), size(0), @@ -180,6 +180,8 @@ public: QIconTheme theme() { return themeList.value(themeName()); } void setThemeSearchPath(const QStringList &searchPaths); QStringList themeSearchPaths() const; + void setFallbackSearchPaths(const QStringList &searchPaths); + QStringList fallbackSearchPaths() const; QIconDirInfo dirInfo(int dirindex); static QIconLoader *instance(); void updateSystemTheme(); @@ -191,6 +193,8 @@ private: QThemeIconInfo findIconHelper(const QString &themeName, const QString &iconName, QStringList &visited) const; + QThemeIconInfo lookupFallbackIcon(const QString &iconName) const; + uint m_themeKey; bool m_supportsSvg; bool m_initialized; @@ -199,6 +203,7 @@ private: mutable QString m_systemTheme; mutable QStringList m_iconDirs; mutable QHash <QString, QIconTheme> themeList; + mutable QStringList m_fallbackDirs; }; QT_END_NAMESPACE diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp index c8ba86bc9a..1856952805 100644 --- a/src/gui/kernel/qplatformtheme.cpp +++ b/src/gui/kernel/qplatformtheme.cpp @@ -516,6 +516,8 @@ QVariant QPlatformTheme::defaultThemeHint(ThemeHint hint) return QVariant(QString()); case QPlatformTheme::IconThemeSearchPaths: return QVariant(QStringList()); + case QPlatformTheme::IconFallbackSearchPaths: + return QVariant(QStringList()); case QPlatformTheme::StyleNames: return QVariant(QStringList()); case QPlatformTheme::ShowShortcutsInContextMenus: diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h index f4ff418db6..87873d446f 100644 --- a/src/gui/kernel/qplatformtheme.h +++ b/src/gui/kernel/qplatformtheme.h @@ -116,7 +116,8 @@ public: MouseDoubleClickDistance, WheelScrollLines, TouchDoubleTapDistance, - ShowShortcutsInContextMenus + ShowShortcutsInContextMenus, + IconFallbackSearchPaths }; enum DialogType { diff --git a/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp b/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp index 5eac0a3584..63a860f251 100644 --- a/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp +++ b/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp @@ -178,6 +178,12 @@ QStringList QGenericUnixTheme::xdgIconThemePaths() paths.append(xdgIconsDir.absoluteFilePath()); } + return paths; +} + +QStringList QGenericUnixTheme::iconFallbackPaths() +{ + QStringList paths; const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps")); if (pixmapsIconsDir.isDir()) paths.append(pixmapsIconsDir.absoluteFilePath()); @@ -210,6 +216,8 @@ QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const return QVariant(QString(QStringLiteral("hicolor"))); case QPlatformTheme::IconThemeSearchPaths: return xdgIconThemePaths(); + case QPlatformTheme::IconFallbackSearchPaths: + return iconFallbackPaths(); case QPlatformTheme::DialogButtonBoxButtonsHaveIcons: return QVariant(true); case QPlatformTheme::StyleNames: { diff --git a/src/platformsupport/themes/genericunix/qgenericunixthemes_p.h b/src/platformsupport/themes/genericunix/qgenericunixthemes_p.h index 186e5a7dff..865a624694 100644 --- a/src/platformsupport/themes/genericunix/qgenericunixthemes_p.h +++ b/src/platformsupport/themes/genericunix/qgenericunixthemes_p.h @@ -85,6 +85,7 @@ public: QVariant themeHint(ThemeHint hint) const override; static QStringList xdgIconThemePaths(); + static QStringList iconFallbackPaths(); #ifndef QT_NO_DBUS QPlatformMenuBar *createPlatformMenuBar() const override; #endif diff --git a/tests/auto/gui/image/qicon/fallback_icons/red.png b/tests/auto/gui/image/qicon/fallback_icons/red.png Binary files differnew file mode 100644 index 0000000000..4a843e744f --- /dev/null +++ b/tests/auto/gui/image/qicon/fallback_icons/red.png diff --git a/tests/auto/gui/image/qicon/qicon.pro b/tests/auto/gui/image/qicon/qicon.pro index b3c60bf32b..c96f0555ad 100644 --- a/tests/auto/gui/image/qicon/qicon.pro +++ b/tests/auto/gui/image/qicon/qicon.pro @@ -6,4 +6,4 @@ qtHaveModule(widgets): QT += widgets SOURCES += tst_qicon.cpp RESOURCES = tst_qicon.qrc tst_qicon.cpp -TESTDATA += icons/* second_icons/* *.png *.svg *.svgz +TESTDATA += icons/* second_icons/* fallback_icons/* *.png *.svg *.svgz diff --git a/tests/auto/gui/image/qicon/tst_qicon.cpp b/tests/auto/gui/image/qicon/tst_qicon.cpp index bf8f7ade9e..b1a4e4312f 100644 --- a/tests/auto/gui/image/qicon/tst_qicon.cpp +++ b/tests/auto/gui/image/qicon/tst_qicon.cpp @@ -554,6 +554,11 @@ void tst_QIcon::fromTheme() QCOMPARE(firstSearchPath, QIcon::themeSearchPaths()[0]); QCOMPARE(secondSearchPath, QIcon::themeSearchPaths()[1]); + QString fallbackSearchPath = QStringLiteral(":/fallback_icons"); + QIcon::setFallbackSearchPaths(QStringList() << fallbackSearchPath); + QCOMPARE(QIcon::fallbackSearchPaths().size(), 1); + QCOMPARE(fallbackSearchPath, QIcon::fallbackSearchPaths().at(0)); + QString themeName("testtheme"); QIcon::setThemeName(themeName); QCOMPARE(QIcon::themeName(), themeName); @@ -580,6 +585,12 @@ void tst_QIcon::fromTheme() QVERIFY(QIcon::hasThemeIcon("address-book-new")); QVERIFY(!abIcon.availableSizes().isEmpty()); + // Test icon from fallback path + QIcon fallbackIcon = QIcon::fromTheme("red"); + QVERIFY(!fallbackIcon.isNull()); + QVERIFY(QIcon::hasThemeIcon("red")); + QCOMPARE(fallbackIcon.availableSizes().size(), 1); + // Test non existing icon QIcon noIcon = QIcon::fromTheme("broken-icon"); QVERIFY(noIcon.isNull()); diff --git a/tests/auto/gui/image/qicon/tst_qicon.qrc b/tests/auto/gui/image/qicon/tst_qicon.qrc index 3c8fbba7c2..4b347ec88c 100644 --- a/tests/auto/gui/image/qicon/tst_qicon.qrc +++ b/tests/auto/gui/image/qicon/tst_qicon.qrc @@ -6,6 +6,7 @@ <file>./icons/testtheme/16x16/actions/appointment-new.png</file> <file>./icons/testtheme/22x22/actions/appointment-new.png</file> <file>./second_icons/testtheme/32x32/actions/appointment-new.png</file> +<file>./fallback_icons/red.png</file> <file>./icons/testtheme/index.theme</file> <file>./icons/testtheme/scalable/actions/svg-only.svg</file> <file>./icons/themeparent/16x16/actions/address-book-new.png</file> |