File indexing completed on 2024-03-24 15:33:01

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