File indexing completed on 2024-04-21 03:54:33

0001 /* vi: ts=8 sts=4 sw=4
0002 
0003     kicontheme.cpp: Lowlevel icon theme handling.
0004 
0005     This file is part of the KDE project, module kdecore.
0006     SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
0007     SPDX-FileCopyrightText: 2000 Antonio Larrosa <larrosa@kde.org>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-only
0010 */
0011 
0012 #include "kicontheme.h"
0013 
0014 #include "debug.h"
0015 
0016 #include <KConfigGroup>
0017 #include <KLocalizedString> // KLocalizedString::localizedFilePath. Need such functionality in, hmm, QLocale? QStandardPaths?
0018 #include <KSharedConfig>
0019 
0020 #ifdef WITH_BREEZEICONS_LIB
0021 #include <BreezeIcons>
0022 #endif
0023 
0024 #include <QAction>
0025 #include <QCoreApplication>
0026 #include <QDebug>
0027 #include <QDir>
0028 #include <QFileInfo>
0029 #include <QMap>
0030 #include <QResource>
0031 #include <QSet>
0032 
0033 #include <private/qguiapplication_p.h>
0034 #include <qpa/qplatformtheme.h>
0035 
0036 #include <qplatformdefs.h>
0037 
0038 #include <array>
0039 #include <cmath>
0040 
0041 Q_GLOBAL_STATIC(QString, _themeOverride)
0042 
0043 // Support for icon themes in RCC files.
0044 // The intended use case is standalone apps on Windows / MacOS / etc.
0045 // For this reason we use AppDataLocation: BINDIR/data on Windows, Resources on OS X
0046 void initRCCIconTheme()
0047 {
0048 #ifdef WITH_BREEZEICONS
0049     BreezeIcons::initIcons()
0050 #else
0051     const QString iconThemeRcc = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("icontheme.rcc"));
0052     if (!iconThemeRcc.isEmpty()) {
0053         const QString iconThemeName = QStringLiteral("kf6_rcc_theme");
0054         const QString iconSubdir = QStringLiteral("/icons/") + iconThemeName;
0055         if (QResource::registerResource(iconThemeRcc, iconSubdir)) {
0056             if (QFileInfo::exists(QLatin1Char(':') + iconSubdir + QStringLiteral("/index.theme"))) {
0057                 // Tell Qt about the theme
0058                 // Note that since qtbase commit a8621a3f8, this means the QPA (i.e. KIconLoader) will NOT be used.
0059                 QIcon::setThemeName(iconThemeName); // Qt looks under :/icons automatically
0060                 // Tell KIconTheme about the theme, in case KIconLoader is used directly
0061                 *_themeOverride() = iconThemeName;
0062             } else {
0063                 qWarning() << "No index.theme found in" << iconThemeRcc;
0064                 QResource::unregisterResource(iconThemeRcc, iconSubdir);
0065             }
0066         } else {
0067             qWarning() << "Invalid rcc file" << iconThemeRcc;
0068         }
0069     }
0070 #endif
0071 }
0072 Q_COREAPP_STARTUP_FUNCTION(initRCCIconTheme)
0073 
0074 // Makes sure the icon theme fallback is set to breeze or one of its
0075 // variants. Most of our apps use "lots" of icons that most of the times
0076 // are only available with breeze, we still honour the user icon theme
0077 // but if the icon is not found there, we go to breeze since it's almost
0078 // sure it'll be there
0079 static void setBreezeFallback()
0080 {
0081     if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
0082         const QVariant themeHint = theme->themeHint(QPlatformTheme::SystemIconFallbackThemeName);
0083         if (themeHint.isValid()) {
0084             const QString iconTheme = themeHint.toString();
0085             if (iconTheme.contains(QStringLiteral("breeze"), Qt::CaseInsensitive)) {
0086                 QIcon::setFallbackThemeName(iconTheme);
0087                 return;
0088             }
0089         }
0090     }
0091 
0092     QIcon::setFallbackThemeName(QStringLiteral("breeze"));
0093 }
0094 
0095 #ifndef Q_OS_ANDROID
0096 Q_COREAPP_STARTUP_FUNCTION(setBreezeFallback)
0097 #endif
0098 class KIconThemeDir;
0099 class KIconThemePrivate
0100 {
0101 public:
0102     QString example, screenshot;
0103     bool hidden;
0104     KSharedConfig::Ptr sharedConfig;
0105 
0106     struct GroupInfo {
0107         KIconLoader::Group type;
0108         const char *name;
0109         int defaultSize;
0110         QList<int> availableSizes{};
0111     };
0112     std::array<GroupInfo, KIconLoader::LastGroup> m_iconGroups = {{
0113         {KIconLoader::Desktop, "Desktop", 32},
0114         {KIconLoader::Toolbar, "Toolbar", 22},
0115         {KIconLoader::MainToolbar, "MainToolbar", 22},
0116         {KIconLoader::Small, "Small", 16},
0117         {KIconLoader::Panel, "Panel", 48},
0118         {KIconLoader::Dialog, "Dialog", 32},
0119     }};
0120 
0121     int mDepth;
0122     QString mDir, mName, mInternalName, mDesc;
0123     QStringList mInherits;
0124     QStringList mExtensions;
0125     QList<KIconThemeDir *> mDirs;
0126     QList<KIconThemeDir *> mScaledDirs;
0127     bool followsColorScheme : 1;
0128 
0129     /// Searches the given dirs vector for a matching icon
0130     QString iconPath(const QList<KIconThemeDir *> &dirs, const QString &name, int size, qreal scale, KIconLoader::MatchType match) const;
0131 };
0132 Q_GLOBAL_STATIC(QString, _theme)
0133 Q_GLOBAL_STATIC(QStringList, _theme_list)
0134 
0135 /**
0136  * A subdirectory in an icon theme.
0137  */
0138 class KIconThemeDir
0139 {
0140 public:
0141     KIconThemeDir(const QString &basedir, const QString &themedir, const KConfigGroup &config);
0142 
0143     bool isValid() const
0144     {
0145         return mbValid;
0146     }
0147     QString iconPath(const QString &name) const;
0148     QStringList iconList() const;
0149     QString constructFileName(const QString &file) const
0150     {
0151         return mBaseDir + mThemeDir + QLatin1Char('/') + file;
0152     }
0153 
0154     KIconLoader::Context context() const
0155     {
0156         return mContext;
0157     }
0158     KIconLoader::Type type() const
0159     {
0160         return mType;
0161     }
0162     int size() const
0163     {
0164         return mSize;
0165     }
0166     int scale() const
0167     {
0168         return mScale;
0169     }
0170     int minSize() const
0171     {
0172         return mMinSize;
0173     }
0174     int maxSize() const
0175     {
0176         return mMaxSize;
0177     }
0178     int threshold() const
0179     {
0180         return mThreshold;
0181     }
0182 
0183 private:
0184     bool mbValid = false;
0185     KIconLoader::Type mType = KIconLoader::Fixed;
0186     KIconLoader::Context mContext;
0187     int mSize = 0;
0188     int mScale = 1;
0189     int mMinSize = 1;
0190     int mMaxSize = 50;
0191     int mThreshold = 2;
0192 
0193     const QString mBaseDir;
0194     const QString mThemeDir;
0195 };
0196 
0197 QString KIconThemePrivate::iconPath(const QList<KIconThemeDir *> &dirs, const QString &name, int size, qreal scale, KIconLoader::MatchType match) const
0198 {
0199     QString path;
0200     QString tempPath; // used to cache icon path if it exists
0201 
0202     int delta = -INT_MAX; // current icon size delta of 'icon'
0203     int dw = INT_MAX; // icon size delta of current directory
0204 
0205     // Rather downsample than upsample
0206     int integerScale = std::ceil(scale);
0207 
0208     // Search the directory that contains the icon which matches best to the requested
0209     // size. If there is no directory which matches exactly to the requested size, the
0210     // following criteria get applied:
0211     // - Take a directory having icons with a minimum difference to the requested size.
0212     // - Prefer directories that allow a downscaling even if the difference to
0213     //   the requested size is bigger than a directory where an upscaling is required.
0214     for (KIconThemeDir *dir : dirs) {
0215         if (dir->scale() != integerScale) {
0216             continue;
0217         }
0218 
0219         if (match == KIconLoader::MatchExact) {
0220             if ((dir->type() == KIconLoader::Fixed) && (dir->size() != size)) {
0221                 continue;
0222             }
0223             if ((dir->type() == KIconLoader::Scalable) //
0224                 && ((size < dir->minSize()) || (size > dir->maxSize()))) {
0225                 continue;
0226             }
0227             if ((dir->type() == KIconLoader::Threshold) //
0228                 && (abs(dir->size() - size) > dir->threshold())) {
0229                 continue;
0230             }
0231         } else {
0232             // dw < 0 means need to scale up to get an icon of the requested size.
0233             // Upscaling should only be done if no larger icon is available.
0234             if (dir->type() == KIconLoader::Fixed) {
0235                 dw = dir->size() - size;
0236             } else if (dir->type() == KIconLoader::Scalable) {
0237                 if (size < dir->minSize()) {
0238                     dw = dir->minSize() - size;
0239                 } else if (size > dir->maxSize()) {
0240                     dw = dir->maxSize() - size;
0241                 } else {
0242                     dw = 0;
0243                 }
0244             } else if (dir->type() == KIconLoader::Threshold) {
0245                 if (size < dir->size() - dir->threshold()) {
0246                     dw = dir->size() - dir->threshold() - size;
0247                 } else if (size > dir->size() + dir->threshold()) {
0248                     dw = dir->size() + dir->threshold() - size;
0249                 } else {
0250                     dw = 0;
0251                 }
0252             }
0253             // Usually if the delta (= 'dw') of the current directory is
0254             // not smaller than the delta (= 'delta') of the currently best
0255             // matching icon, this candidate can be skipped. But skipping
0256             // the candidate may only be done, if this does not imply
0257             // in an upscaling of the icon (it is OK to use a directory with
0258             // smaller icons that what we've already found, however).
0259             if ((abs(dw) >= abs(delta)) && ((dw < 0) || (delta > 0))) {
0260                 continue;
0261             }
0262 
0263             if (match == KIconLoader::MatchBestOrGreaterSize && dw < 0) {
0264                 continue;
0265             }
0266         }
0267 
0268         // cache the result of iconPath() call which checks if file exists
0269         tempPath = dir->iconPath(name);
0270 
0271         if (tempPath.isEmpty()) {
0272             continue;
0273         }
0274 
0275         path = tempPath;
0276 
0277         // if we got in MatchExact that far, we find no better
0278         if (match == KIconLoader::MatchExact) {
0279             return path;
0280         }
0281         delta = dw;
0282         if (delta == 0) {
0283             return path; // We won't find a better match anyway
0284         }
0285     }
0286     return path;
0287 }
0288 
0289 KIconTheme::KIconTheme(const QString &name, const QString &appName, const QString &basePathHint)
0290     : d(new KIconThemePrivate)
0291 {
0292     d->mInternalName = name;
0293 
0294     QStringList themeDirs;
0295 
0296     // Applications can have local additions to the global "locolor" and
0297     // "hicolor" icon themes. For these, the _global_ theme description
0298     // files are used..
0299 
0300     /* clang-format off */
0301     if (!appName.isEmpty()
0302         && (name == defaultThemeName()
0303             || name == QLatin1String("hicolor")
0304             || name == QLatin1String("locolor"))) { /* clang-format on */
0305         const QString suffix = QLatin1Char('/') + appName + QLatin1String("/icons/") + name + QLatin1Char('/');
0306         QStringList dataDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0307         for (auto &cDir : dataDirs) {
0308             cDir += suffix;
0309             if (QFileInfo::exists(cDir)) {
0310                 themeDirs += cDir;
0311             }
0312         }
0313 
0314         if (!basePathHint.isEmpty()) {
0315             // Checks for dir existing are done below
0316             themeDirs += basePathHint + QLatin1Char('/') + name + QLatin1Char('/');
0317         }
0318     }
0319 
0320     // Find the theme description file. These are either locally in the :/icons resource path or global.
0321     QStringList icnlibs;
0322 
0323     // local embedded icons have preference
0324     icnlibs << QStringLiteral(":/icons");
0325 
0326     // global icons
0327     icnlibs += QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory);
0328 
0329     // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
0330     icnlibs += QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("pixmaps"), QStandardPaths::LocateDirectory);
0331 
0332     QString fileName;
0333     QString mainSection;
0334     const QString pathSuffix = QLatin1Char('/') + name + QLatin1Char('/');
0335     const QLatin1String indexTheme("index.theme");
0336     const QLatin1String indexDesktop("theme.desktop");
0337     for (auto &iconDir : icnlibs) {
0338         iconDir += pathSuffix;
0339         const QFileInfo fi(iconDir);
0340         if (!fi.exists() || !fi.isDir()) {
0341             continue;
0342         }
0343         themeDirs.append(iconDir);
0344 
0345         if (d->mDir.isEmpty()) {
0346             QString possiblePath;
0347             if (possiblePath = iconDir + indexTheme; QFileInfo::exists(possiblePath)) {
0348                 d->mDir = iconDir;
0349                 fileName = possiblePath;
0350                 mainSection = QStringLiteral("Icon Theme");
0351             } else if (possiblePath = iconDir + indexDesktop; QFileInfo::exists(possiblePath)) {
0352                 d->mDir = iconDir;
0353                 fileName = possiblePath;
0354                 mainSection = QStringLiteral("KDE Icon Theme");
0355             }
0356         }
0357     }
0358 
0359     if (d->mDir.isEmpty()) {
0360         qCDebug(KICONTHEMES) << "Icon theme" << name << "not found.";
0361         return;
0362     }
0363 
0364     // Use KSharedConfig to avoid parsing the file many times, from each component.
0365     // Need to keep a ref to it to make this useful
0366     d->sharedConfig = KSharedConfig::openConfig(fileName, KConfig::NoGlobals);
0367 
0368     KConfigGroup cfg(d->sharedConfig, mainSection);
0369     d->mName = cfg.readEntry("Name");
0370     d->mDesc = cfg.readEntry("Comment");
0371     d->mDepth = cfg.readEntry("DisplayDepth", 32);
0372     d->mInherits = cfg.readEntry("Inherits", QStringList());
0373     if (name != defaultThemeName()) {
0374         for (auto &inheritedTheme : d->mInherits) {
0375             if (inheritedTheme == QLatin1String("default")) {
0376                 inheritedTheme = defaultThemeName();
0377             }
0378         }
0379     }
0380 
0381     d->hidden = cfg.readEntry("Hidden", false);
0382     d->followsColorScheme = cfg.readEntry("FollowsColorScheme", false);
0383     d->example = cfg.readPathEntry("Example", QString());
0384     d->screenshot = cfg.readPathEntry("ScreenShot", QString());
0385     d->mExtensions =
0386         cfg.readEntry("KDE-Extensions", QStringList{QStringLiteral(".png"), QStringLiteral(".svgz"), QStringLiteral(".svg"), QStringLiteral(".xpm")});
0387 
0388     QSet<QString> addedDirs; // Used for avoiding duplicates.
0389     const QStringList dirs = cfg.readPathEntry("Directories", QStringList()) + cfg.readPathEntry("ScaledDirectories", QStringList());
0390     for (const auto &dirName : dirs) {
0391         KConfigGroup cg(d->sharedConfig, dirName);
0392         for (const auto &themeDir : std::as_const(themeDirs)) {
0393             const QString currentDir(themeDir + dirName + QLatin1Char('/'));
0394             if (!addedDirs.contains(currentDir) && QDir(currentDir).exists()) {
0395                 addedDirs.insert(currentDir);
0396                 KIconThemeDir *dir = new KIconThemeDir(themeDir, dirName, cg);
0397                 if (dir->isValid()) {
0398                     if (dir->scale() > 1) {
0399                         d->mScaledDirs.append(dir);
0400                     } else {
0401                         d->mDirs.append(dir);
0402                     }
0403                 } else {
0404                     delete dir;
0405                 }
0406             }
0407         }
0408     }
0409 
0410     KConfigGroup cg(d->sharedConfig, mainSection);
0411     for (auto &iconGroup : d->m_iconGroups) {
0412         iconGroup.defaultSize = cg.readEntry(iconGroup.name + QLatin1String("Default"), iconGroup.defaultSize);
0413         iconGroup.availableSizes = cg.readEntry(iconGroup.name + QLatin1String("Sizes"), QList<int>());
0414     }
0415 }
0416 
0417 KIconTheme::~KIconTheme()
0418 {
0419     qDeleteAll(d->mDirs);
0420     qDeleteAll(d->mScaledDirs);
0421 }
0422 
0423 QString KIconTheme::name() const
0424 {
0425     return d->mName;
0426 }
0427 
0428 QString KIconTheme::internalName() const
0429 {
0430     return d->mInternalName;
0431 }
0432 
0433 QString KIconTheme::description() const
0434 {
0435     return d->mDesc;
0436 }
0437 
0438 QString KIconTheme::example() const
0439 {
0440     return d->example;
0441 }
0442 
0443 QString KIconTheme::screenshot() const
0444 {
0445     return d->screenshot;
0446 }
0447 
0448 QString KIconTheme::dir() const
0449 {
0450     return d->mDir;
0451 }
0452 
0453 QStringList KIconTheme::inherits() const
0454 {
0455     return d->mInherits;
0456 }
0457 
0458 bool KIconTheme::isValid() const
0459 {
0460     return !d->mDirs.isEmpty() || !d->mScaledDirs.isEmpty();
0461 }
0462 
0463 bool KIconTheme::isHidden() const
0464 {
0465     return d->hidden;
0466 }
0467 
0468 int KIconTheme::depth() const
0469 {
0470     return d->mDepth;
0471 }
0472 
0473 int KIconTheme::defaultSize(KIconLoader::Group group) const
0474 {
0475     if (group < 0 || group >= KIconLoader::LastGroup) {
0476         qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
0477         return -1;
0478     }
0479     return d->m_iconGroups[group].defaultSize;
0480 }
0481 
0482 QList<int> KIconTheme::querySizes(KIconLoader::Group group) const
0483 {
0484     if (group < 0 || group >= KIconLoader::LastGroup) {
0485         qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
0486         return QList<int>();
0487     }
0488     return d->m_iconGroups[group].availableSizes;
0489 }
0490 
0491 static bool isAnyOrDirContext(const KIconThemeDir *dir, KIconLoader::Context context)
0492 {
0493     return context == KIconLoader::Any || context == dir->context();
0494 }
0495 
0496 QStringList KIconTheme::queryIcons(int size, KIconLoader::Context context) const
0497 {
0498     // Try to find exact match
0499     QStringList result;
0500     const QList<KIconThemeDir *> listDirs = d->mDirs + d->mScaledDirs;
0501     for (const KIconThemeDir *dir : listDirs) {
0502         if (!isAnyOrDirContext(dir, context)) {
0503             continue;
0504         }
0505 
0506         const int dirSize = dir->size();
0507         if ((dir->type() == KIconLoader::Fixed && dirSize == size) //
0508             || (dir->type() == KIconLoader::Scalable && size >= dir->minSize() && size <= dir->maxSize())
0509             || (dir->type() == KIconLoader::Threshold && abs(size - dirSize) < dir->threshold())) {
0510             result += dir->iconList();
0511         }
0512     }
0513 
0514     return result;
0515 }
0516 
0517 QStringList KIconTheme::queryIconsByContext(int size, KIconLoader::Context context) const
0518 {
0519     int dw;
0520 
0521     // We want all the icons for a given context, but we prefer icons
0522     // of size "size" . Note that this may (will) include duplicate icons
0523     // QStringList iconlist[34]; // 33 == 48-16+1
0524     QStringList iconlist[128]; // 33 == 48-16+1
0525     // Usually, only the 0, 6 (22-16), 10 (32-22), 16 (48-32 or 32-16),
0526     // 26 (48-22) and 32 (48-16) will be used, but who knows if someone
0527     // will make icon themes with different icon sizes.
0528     const auto listDirs = d->mDirs + d->mScaledDirs;
0529     for (KIconThemeDir *dir : listDirs) {
0530         if (!isAnyOrDirContext(dir, context)) {
0531             continue;
0532         }
0533         dw = abs(dir->size() - size);
0534         iconlist[(dw < 127) ? dw : 127] += dir->iconList();
0535     }
0536 
0537     QStringList iconlistResult;
0538     for (int i = 0; i < 128; i++) {
0539         iconlistResult += iconlist[i];
0540     }
0541 
0542     return iconlistResult;
0543 }
0544 
0545 bool KIconTheme::hasContext(KIconLoader::Context context) const
0546 {
0547     const auto listDirs = d->mDirs + d->mScaledDirs;
0548     for (KIconThemeDir *dir : listDirs) {
0549         if (isAnyOrDirContext(dir, context)) {
0550             return true;
0551         }
0552     }
0553     return false;
0554 }
0555 
0556 QString KIconTheme::iconPathByName(const QString &iconName, int size, KIconLoader::MatchType match) const
0557 {
0558     return iconPathByName(iconName, size, match, 1 /*scale*/);
0559 }
0560 
0561 QString KIconTheme::iconPathByName(const QString &iconName, int size, KIconLoader::MatchType match, qreal scale) const
0562 {
0563     for (const QString &current : std::as_const(d->mExtensions)) {
0564         const QString path = iconPath(iconName + current, size, match, scale);
0565         if (!path.isEmpty()) {
0566             return path;
0567         }
0568     }
0569     return QString();
0570 }
0571 
0572 bool KIconTheme::followsColorScheme() const
0573 {
0574     return d->followsColorScheme;
0575 }
0576 
0577 QString KIconTheme::iconPath(const QString &name, int size, KIconLoader::MatchType match) const
0578 {
0579     return iconPath(name, size, match, 1 /*scale*/);
0580 }
0581 
0582 QString KIconTheme::iconPath(const QString &name, int size, KIconLoader::MatchType match, qreal scale) const
0583 {
0584     // first look for a scaled image at exactly the requested size
0585     QString path = d->iconPath(d->mScaledDirs, name, size, scale, KIconLoader::MatchExact);
0586 
0587     // then look for an unscaled one but request it at larger size so it doesn't become blurry
0588     if (path.isEmpty()) {
0589         path = d->iconPath(d->mDirs, name, size * scale, 1, match);
0590     }
0591     return path;
0592 }
0593 
0594 // static
0595 QString KIconTheme::current()
0596 {
0597     // Static pointers because of unloading problems wrt DSO's.
0598     if (_themeOverride && !_themeOverride->isEmpty()) {
0599         *_theme() = *_themeOverride();
0600     }
0601     if (!_theme()->isEmpty()) {
0602         return *_theme();
0603     }
0604 
0605     QString theme;
0606     // Check application specific config for a theme setting.
0607     KConfigGroup app_cg(KSharedConfig::openConfig(QString(), KConfig::NoGlobals), "Icons");
0608     theme = app_cg.readEntry("Theme", QString());
0609     if (theme.isEmpty() || theme == QLatin1String("hicolor")) {
0610         // No theme, try to use Qt's. A Platform plugin might have set
0611         // a good theme there.
0612         theme = QIcon::themeName();
0613     }
0614     if (theme.isEmpty() || theme == QLatin1String("hicolor")) {
0615         // Still no theme, try config with kdeglobals.
0616         KConfigGroup cg(KSharedConfig::openConfig(), "Icons");
0617         theme = cg.readEntry("Theme", QStringLiteral("breeze"));
0618     }
0619     if (theme.isEmpty() || theme == QLatin1String("hicolor")) {
0620         // Still no good theme, use default.
0621         theme = defaultThemeName();
0622     }
0623     *_theme() = theme;
0624     return *_theme();
0625 }
0626 
0627 void KIconTheme::forceThemeForTests(const QString &themeName)
0628 {
0629     *_themeOverride() = themeName;
0630     _theme()->clear(); // ::current sets this again based on conditions
0631 }
0632 
0633 // static
0634 QStringList KIconTheme::list()
0635 {
0636     // Static pointer because of unloading problems wrt DSO's.
0637     if (!_theme_list()->isEmpty()) {
0638         return *_theme_list();
0639     }
0640 
0641     // Find the theme description file. These are either locally in the :/icons resource path or global.
0642     QStringList icnlibs;
0643 
0644     // local embedded icons have preference
0645     icnlibs << QStringLiteral(":/icons");
0646 
0647     // global icons
0648     icnlibs += QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory);
0649 
0650     // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
0651     icnlibs += QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("pixmaps"), QStandardPaths::LocateDirectory);
0652 
0653     for (const QString &iconDir : std::as_const(icnlibs)) {
0654         QDir dir(iconDir);
0655         const QStringList themeDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0656         for (const auto &theme : themeDirs) {
0657             if (theme.startsWith(QLatin1String("default."))) {
0658                 continue;
0659             }
0660 
0661             const QString prefix = iconDir + QLatin1Char('/') + theme;
0662             if (!QFileInfo::exists(prefix + QLatin1String("/index.desktop")) //
0663                 && !QFileInfo::exists(prefix + QLatin1String("/index.theme"))) {
0664                 continue;
0665             }
0666 
0667             if (!KIconTheme(theme).isValid()) {
0668                 continue;
0669             }
0670 
0671             if (!_theme_list()->contains(theme)) {
0672                 _theme_list()->append(theme);
0673             }
0674         }
0675     }
0676     return *_theme_list();
0677 }
0678 
0679 // static
0680 void KIconTheme::reconfigure()
0681 {
0682     _theme()->clear();
0683     _theme_list()->clear();
0684 }
0685 
0686 // static
0687 QString KIconTheme::defaultThemeName()
0688 {
0689     return QStringLiteral("hicolor");
0690 }
0691 
0692 /*** KIconThemeDir ***/
0693 
0694 KIconThemeDir::KIconThemeDir(const QString &basedir, const QString &themedir, const KConfigGroup &config)
0695     : mSize(config.readEntry("Size", 0))
0696     , mScale(config.readEntry("Scale", 1))
0697     , mBaseDir(basedir)
0698     , mThemeDir(themedir)
0699 {
0700     if (mSize == 0) {
0701         return;
0702     }
0703 
0704     QString tmp = config.readEntry(QStringLiteral("Context"));
0705     if (tmp == QLatin1String("Devices")) {
0706         mContext = KIconLoader::Device;
0707     } else if (tmp == QLatin1String("MimeTypes")) {
0708         mContext = KIconLoader::MimeType;
0709     } else if (tmp == QLatin1String("Applications")) {
0710         mContext = KIconLoader::Application;
0711     } else if (tmp == QLatin1String("Actions")) {
0712         mContext = KIconLoader::Action;
0713     } else if (tmp == QLatin1String("Animations")) {
0714         mContext = KIconLoader::Animation;
0715     } else if (tmp == QLatin1String("Categories")) {
0716         mContext = KIconLoader::Category;
0717     } else if (tmp == QLatin1String("Emblems")) {
0718         mContext = KIconLoader::Emblem;
0719     } else if (tmp == QLatin1String("Emotes")) {
0720         mContext = KIconLoader::Emote;
0721     } else if (tmp == QLatin1String("International")) {
0722         mContext = KIconLoader::International;
0723     } else if (tmp == QLatin1String("Places")) {
0724         mContext = KIconLoader::Place;
0725     } else if (tmp == QLatin1String("Status")) {
0726         mContext = KIconLoader::StatusIcon;
0727     } else if (tmp == QLatin1String("Stock")) { // invalid, but often present context, skip warning
0728         return;
0729     } else if (tmp == QLatin1String("Legacy")) { // invalid, but often present context for Adwaita, skip warning
0730         return;
0731     } else if (tmp == QLatin1String("UI")) { // invalid, but often present context for Adwaita, skip warning
0732         return;
0733     } else if (tmp.isEmpty()) {
0734         // do nothing. key not required
0735     } else {
0736         qCDebug(KICONTHEMES) << "Invalid Context=" << tmp << "line for icon theme: " << constructFileName(QString());
0737         return;
0738     }
0739     tmp = config.readEntry(QStringLiteral("Type"), QStringLiteral("Threshold"));
0740     if (tmp == QLatin1String("Fixed")) {
0741         mType = KIconLoader::Fixed;
0742     } else if (tmp == QLatin1String("Scalable")) {
0743         mType = KIconLoader::Scalable;
0744     } else if (tmp == QLatin1String("Threshold")) {
0745         mType = KIconLoader::Threshold;
0746     } else {
0747         qCDebug(KICONTHEMES) << "Invalid Type=" << tmp << "line for icon theme: " << constructFileName(QString());
0748         return;
0749     }
0750     if (mType == KIconLoader::Scalable) {
0751         mMinSize = config.readEntry(QStringLiteral("MinSize"), mSize);
0752         mMaxSize = config.readEntry(QStringLiteral("MaxSize"), mSize);
0753     } else if (mType == KIconLoader::Threshold) {
0754         mThreshold = config.readEntry(QStringLiteral("Threshold"), 2);
0755     }
0756     mbValid = true;
0757 }
0758 
0759 QString KIconThemeDir::iconPath(const QString &name) const
0760 {
0761     if (!mbValid) {
0762         return QString();
0763     }
0764 
0765     const QString file = constructFileName(name);
0766     if (QFileInfo::exists(file)) {
0767         return KLocalizedString::localizedFilePath(file);
0768     }
0769 
0770     return QString();
0771 }
0772 
0773 QStringList KIconThemeDir::iconList() const
0774 {
0775     const QDir icondir = constructFileName(QString());
0776 
0777     const QStringList formats = QStringList() << QStringLiteral("*.png") << QStringLiteral("*.svg") << QStringLiteral("*.svgz") << QStringLiteral("*.xpm");
0778     const QStringList lst = icondir.entryList(formats, QDir::Files);
0779 
0780     QStringList result;
0781     result.reserve(lst.size());
0782     for (const QString &file : lst) {
0783         result += constructFileName(file);
0784     }
0785     return result;
0786 }