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>bb10
parent
537af27302
commit
85aa0fd041
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -1157,6 +1157,36 @@ QStringList QIcon::themeSearchPaths()
|
|||
return QIconLoader::instance()->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
|
||||
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ public:
|
|||
MouseDoubleClickDistance,
|
||||
WheelScrollLines,
|
||||
TouchDoubleTapDistance,
|
||||
ShowShortcutsInContextMenus
|
||||
ShowShortcutsInContextMenus,
|
||||
IconFallbackSearchPaths
|
||||
};
|
||||
|
||||
enum DialogType {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 105 B |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue