File indexing completed on 2024-04-21 14:59:18

0001 /* vi: ts=8 sts=4 sw=4
0002 
0003     kiconloader.cpp: An icon loader for KDE with theming functionality.
0004 
0005     This file is part of the KDE project, module kdeui.
0006     SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
0007     SPDX-FileCopyrightText: 2000 Antonio Larrosa <larrosa@kde.org>
0008     SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-only
0011 */
0012 
0013 #include "kiconloader.h"
0014 #include "kiconloader_p.h"
0015 
0016 // kdecore
0017 #include <KConfigGroup>
0018 #include <KSharedConfig>
0019 #include <kshareddatacache.h>
0020 #ifdef QT_DBUS_LIB
0021 #include <QDBusConnection>
0022 #include <QDBusMessage>
0023 #endif
0024 #include <QCryptographicHash>
0025 #include <QXmlStreamReader>
0026 #include <QXmlStreamWriter>
0027 
0028 // kdeui
0029 #include "debug.h"
0030 #include "kiconcolors.h"
0031 #include "kiconeffect.h"
0032 #include "kicontheme.h"
0033 
0034 // kwidgetsaddons
0035 #include <KPixmapSequence>
0036 
0037 #include <KColorScheme>
0038 #include <KCompressionDevice>
0039 
0040 #include <QBuffer>
0041 #include <QByteArray>
0042 #include <QDataStream>
0043 #include <QDir>
0044 #include <QElapsedTimer>
0045 #include <QFileInfo>
0046 #include <QGuiApplication>
0047 #include <QIcon>
0048 #include <QImage>
0049 #include <QMovie>
0050 #include <QPainter>
0051 #include <QPixmap>
0052 #include <QPixmapCache>
0053 #include <QStringBuilder> // % operator for QString
0054 #include <QtGui/private/qiconloader_p.h>
0055 
0056 #include <qplatformdefs.h> //for readlink
0057 
0058 #include <assert.h>
0059 
0060 namespace
0061 {
0062 // Used to make cache keys for icons with no group. Result type is QString*
0063 QString NULL_EFFECT_FINGERPRINT()
0064 {
0065     return QStringLiteral("noeffect");
0066 }
0067 
0068 }
0069 
0070 /**
0071  * Function to convert an uint32_t to AARRGGBB hex values.
0072  *
0073  * W A R N I N G !
0074  * This function is for internal use!
0075  */
0076 KICONTHEMES_EXPORT void uintToHex(uint32_t colorData, QChar *buffer)
0077 {
0078     static const char hexLookup[] = "0123456789abcdef";
0079     buffer += 7;
0080     uchar *colorFields = reinterpret_cast<uchar *>(&colorData);
0081 
0082     for (int i = 0; i < 4; i++) {
0083         *buffer-- = hexLookup[*colorFields & 0xf];
0084         *buffer-- = hexLookup[*colorFields >> 4];
0085         colorFields++;
0086     }
0087 }
0088 
0089 static QString paletteId(const KIconColors &colors)
0090 {
0091     // 8 per color. We want 3 colors thus 8*4=32.
0092     QString buffer(32, Qt::Uninitialized);
0093 
0094     uintToHex(colors.text().rgba(), buffer.data());
0095     uintToHex(colors.highlight().rgba(), buffer.data() + 8);
0096     uintToHex(colors.highlightedText().rgba(), buffer.data() + 16);
0097     uintToHex(colors.background().rgba(), buffer.data() + 24);
0098 
0099     return buffer;
0100 }
0101 
0102 /*** KIconThemeNode: A node in the icon theme dependency tree. ***/
0103 
0104 class KIconThemeNode
0105 {
0106 public:
0107     KIconThemeNode(KIconTheme *_theme);
0108     ~KIconThemeNode();
0109 
0110     KIconThemeNode(const KIconThemeNode &) = delete;
0111     KIconThemeNode &operator=(const KIconThemeNode &) = delete;
0112 
0113     void queryIcons(QStringList *lst, int size, KIconLoader::Context context) const;
0114     void queryIconsByContext(QStringList *lst, int size, KIconLoader::Context context) const;
0115     QString findIcon(const QString &name, int size, KIconLoader::MatchType match) const;
0116 
0117     KIconTheme *theme;
0118 };
0119 
0120 KIconThemeNode::KIconThemeNode(KIconTheme *_theme)
0121 {
0122     theme = _theme;
0123 }
0124 
0125 KIconThemeNode::~KIconThemeNode()
0126 {
0127     delete theme;
0128 }
0129 
0130 void KIconThemeNode::queryIcons(QStringList *result, int size, KIconLoader::Context context) const
0131 {
0132     // add the icons of this theme to it
0133     *result += theme->queryIcons(size, context);
0134 }
0135 
0136 void KIconThemeNode::queryIconsByContext(QStringList *result, int size, KIconLoader::Context context) const
0137 {
0138     // add the icons of this theme to it
0139     *result += theme->queryIconsByContext(size, context);
0140 }
0141 
0142 QString KIconThemeNode::findIcon(const QString &name, int size, KIconLoader::MatchType match) const
0143 {
0144     return theme->iconPath(name, size, match);
0145 }
0146 
0147 extern KICONTHEMES_EXPORT int kiconloader_ms_between_checks;
0148 KICONTHEMES_EXPORT int kiconloader_ms_between_checks = 5000;
0149 
0150 class KIconLoaderGlobalData : public QObject
0151 {
0152     Q_OBJECT
0153 
0154 public:
0155     KIconLoaderGlobalData()
0156     {
0157         const QStringList genericIconsFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime/generic-icons"));
0158         // qCDebug(KICONTHEMES) << genericIconsFiles;
0159         for (const QString &file : genericIconsFiles) {
0160             parseGenericIconsFiles(file);
0161         }
0162 
0163 #ifdef QT_DBUS_LIB
0164         QDBusConnection::sessionBus().connect(QString(),
0165                                               QStringLiteral("/KIconLoader"),
0166                                               QStringLiteral("org.kde.KIconLoader"),
0167                                               QStringLiteral("iconChanged"),
0168                                               this,
0169                                               SIGNAL(iconChanged(int)));
0170 #endif
0171     }
0172 
0173     void emitChange(KIconLoader::Group group)
0174     {
0175 #ifdef QT_DBUS_LIB
0176         QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"));
0177         message.setArguments(QList<QVariant>() << int(group));
0178         QDBusConnection::sessionBus().send(message);
0179 #endif
0180     }
0181 
0182     QString genericIconFor(const QString &icon) const
0183     {
0184         return m_genericIcons.value(icon);
0185     }
0186 
0187 Q_SIGNALS:
0188     void iconChanged(int group);
0189 
0190 private:
0191     void parseGenericIconsFiles(const QString &fileName);
0192     QHash<QString, QString> m_genericIcons;
0193 };
0194 
0195 void KIconLoaderGlobalData::parseGenericIconsFiles(const QString &fileName)
0196 {
0197     QFile file(fileName);
0198     if (file.open(QIODevice::ReadOnly)) {
0199         QTextStream stream(&file);
0200         // In Qt6 the encoding is UTF-8 by default, so it should work for icon file names;
0201         // I think this code had "ISO 8859-1" (i.e. Latin-1) as an optimization, but file
0202         // names on Linux are UTF-8 by default, so this would be more robust.
0203         // Note that in Qt6 we can have the same behaviour by using QTextStream::setEncoding().
0204         stream.setCodec("ISO 8859-1");
0205         while (!stream.atEnd()) {
0206             const QString line = stream.readLine();
0207             if (line.isEmpty() || line[0] == QLatin1Char('#')) {
0208                 continue;
0209             }
0210             const int pos = line.indexOf(QLatin1Char(':'));
0211             if (pos == -1) { // syntax error
0212                 continue;
0213             }
0214             QString mimeIcon = line.left(pos);
0215             const int slashindex = mimeIcon.indexOf(QLatin1Char('/'));
0216             if (slashindex != -1) {
0217                 mimeIcon[slashindex] = QLatin1Char('-');
0218             }
0219 
0220             const QString genericIcon = line.mid(pos + 1);
0221             m_genericIcons.insert(mimeIcon, genericIcon);
0222             // qCDebug(KICONTHEMES) << mimeIcon << "->" << genericIcon;
0223         }
0224     }
0225 }
0226 
0227 Q_GLOBAL_STATIC(KIconLoaderGlobalData, s_globalData)
0228 
0229 KIconLoaderPrivate::KIconLoaderPrivate(const QString &_appname, const QStringList &extraSearchPaths, KIconLoader *qq)
0230     : q(qq)
0231     , m_appname(_appname)
0232 {
0233     q->connect(s_globalData, &KIconLoaderGlobalData::iconChanged, q, [this](int group) {
0234         _k_refreshIcons(group);
0235     });
0236     init(m_appname, extraSearchPaths);
0237 }
0238 
0239 KIconLoaderPrivate::~KIconLoaderPrivate()
0240 {
0241     clear();
0242 }
0243 
0244 KIconLoaderPrivate *KIconLoaderPrivate::get(KIconLoader *loader)
0245 {
0246     return loader->d.get();
0247 }
0248 
0249 void KIconLoaderPrivate::clear()
0250 {
0251     /* antlarr: There's no need to delete d->mpThemeRoot as it's already
0252     deleted when the elements of d->links are deleted */
0253     qDeleteAll(links);
0254     delete[] mpGroups;
0255     delete mIconCache;
0256     mpGroups = nullptr;
0257     mIconCache = nullptr;
0258     mPixmapCache.clear();
0259     m_appname.clear();
0260     searchPaths.clear();
0261     links.clear();
0262     mIconThemeInited = false;
0263     mThemesInTree.clear();
0264 }
0265 
0266 void KIconLoaderPrivate::drawOverlays(const KIconLoader *iconLoader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays)
0267 {
0268     if (overlays.isEmpty()) {
0269         return;
0270     }
0271 
0272     const int width = pix.size().width();
0273     const int height = pix.size().height();
0274     const int iconSize = qMin(width, height);
0275     int overlaySize;
0276 
0277     if (iconSize < 32) {
0278         overlaySize = 8;
0279     } else if (iconSize <= 48) {
0280         overlaySize = 16;
0281     } else if (iconSize <= 96) {
0282         overlaySize = 22;
0283     } else if (iconSize < 256) {
0284         overlaySize = 32;
0285     } else {
0286         overlaySize = 64;
0287     }
0288 
0289     QPainter painter(&pix);
0290 
0291     int count = 0;
0292     for (const QString &overlay : overlays) {
0293         // Ensure empty strings fill up a emblem spot
0294         // Needed when you have several emblems to ensure they're always painted
0295         // at the same place, even if one is not here
0296         if (overlay.isEmpty()) {
0297             ++count;
0298             continue;
0299         }
0300 
0301         // TODO: should we pass in the kstate? it results in a slower
0302         //      path, and perhaps emblems should remain in the default state
0303         //      anyways?
0304         QPixmap pixmap = iconLoader->loadIcon(overlay, group, overlaySize, state, QStringList(), nullptr, true);
0305 
0306         if (pixmap.isNull()) {
0307             continue;
0308         }
0309 
0310         // match the emblem's devicePixelRatio to the original pixmap's
0311         pixmap.setDevicePixelRatio(pix.devicePixelRatio());
0312         const int margin = pixmap.devicePixelRatio() * 0.05 * iconSize;
0313 
0314         QPoint startPoint;
0315         switch (count) {
0316         case 0:
0317             // bottom right corner
0318             startPoint = QPoint(width - overlaySize - margin, height - overlaySize - margin);
0319             break;
0320         case 1:
0321             // bottom left corner
0322             startPoint = QPoint(margin, height - overlaySize - margin);
0323             break;
0324         case 2:
0325             // top left corner
0326             startPoint = QPoint(margin, margin);
0327             break;
0328         case 3:
0329             // top right corner
0330             startPoint = QPoint(width - overlaySize - margin, margin);
0331             break;
0332         }
0333 
0334         startPoint /= pix.devicePixelRatio();
0335 
0336         painter.drawPixmap(startPoint, pixmap);
0337 
0338         ++count;
0339         if (count > 3) {
0340             break;
0341         }
0342     }
0343 }
0344 
0345 void KIconLoaderPrivate::_k_refreshIcons(int group)
0346 {
0347     KSharedConfig::Ptr sharedConfig = KSharedConfig::openConfig();
0348     sharedConfig->reparseConfiguration();
0349     const QString newThemeName = sharedConfig->group("Icons").readEntry("Theme", QStringLiteral("breeze"));
0350     if (!newThemeName.isEmpty()) {
0351         // NOTE Do NOT use QIcon::setThemeName here it makes Qt not use icon engine of the platform theme
0352         //      anymore (KIconEngine on Plasma, which breaks recoloring) and overwrites a user set themeName
0353         // TODO KF6 this should be done in the Plasma QPT
0354         QIconLoader::instance()->updateSystemTheme();
0355     }
0356 
0357     q->newIconLoader();
0358     mIconAvailability.clear();
0359     Q_EMIT q->iconChanged(group);
0360 }
0361 
0362 bool KIconLoaderPrivate::shouldCheckForUnknownIcons()
0363 {
0364     if (mLastUnknownIconCheck.isValid() && mLastUnknownIconCheck.elapsed() < kiconloader_ms_between_checks) {
0365         return false;
0366     }
0367     mLastUnknownIconCheck.start();
0368     return true;
0369 }
0370 
0371 KIconLoader::KIconLoader(const QString &appname, const QStringList &extraSearchPaths, QObject *parent)
0372     : QObject(parent)
0373     , d(new KIconLoaderPrivate(appname, extraSearchPaths, this))
0374 {
0375     setObjectName(appname);
0376 }
0377 
0378 void KIconLoader::reconfigure(const QString &_appname, const QStringList &extraSearchPaths)
0379 {
0380     d->mIconCache->clear();
0381     d->clear();
0382     d->init(_appname, extraSearchPaths);
0383 }
0384 
0385 void KIconLoaderPrivate::init(const QString &_appname, const QStringList &extraSearchPaths)
0386 {
0387     extraDesktopIconsLoaded = false;
0388     mIconThemeInited = false;
0389     mpThemeRoot = nullptr;
0390 
0391     searchPaths = extraSearchPaths;
0392 
0393     m_appname = !_appname.isEmpty() ? _appname : QCoreApplication::applicationName();
0394 
0395     // Initialize icon cache
0396     mIconCache = new KSharedDataCache(QStringLiteral("icon-cache"), 10 * 1024 * 1024);
0397     // Cost here is number of pixels, not size. So this is actually a bit
0398     // smaller.
0399     mPixmapCache.setMaxCost(10 * 1024 * 1024);
0400 
0401     // These have to match the order in kiconloader.h
0402     static const char *const groups[] = {"Desktop", "Toolbar", "MainToolbar", "Small", "Panel", "Dialog", nullptr};
0403     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0404 
0405     // loading config and default sizes
0406     initIconThemes();
0407     KIconTheme *defaultSizesTheme = links.empty() ? nullptr : links.first()->theme;
0408     mpGroups = new KIconGroup[static_cast<int>(KIconLoader::LastGroup)];
0409     for (KIconLoader::Group i = KIconLoader::FirstGroup; i < KIconLoader::LastGroup; ++i) {
0410         if (groups[i] == nullptr) {
0411             break;
0412         }
0413 
0414         KConfigGroup cg(config, QLatin1String(groups[i]) + QStringLiteral("Icons"));
0415         mpGroups[i].size = cg.readEntry("Size", 0);
0416 
0417         if (!mpGroups[i].size && defaultSizesTheme) {
0418             mpGroups[i].size = defaultSizesTheme->defaultSize(i);
0419         }
0420     }
0421 }
0422 
0423 void KIconLoaderPrivate::initIconThemes()
0424 {
0425     if (mIconThemeInited) {
0426         return;
0427     }
0428     // qCDebug(KICONTHEMES);
0429     mIconThemeInited = true;
0430 
0431     // Add the default theme and its base themes to the theme tree
0432     KIconTheme *def = new KIconTheme(KIconTheme::current(), m_appname);
0433     if (!def->isValid()) {
0434         delete def;
0435         // warn, as this is actually a small penalty hit
0436         qCDebug(KICONTHEMES) << "Couldn't find current icon theme, falling back to default.";
0437         def = new KIconTheme(KIconTheme::defaultThemeName(), m_appname);
0438         if (!def->isValid()) {
0439             qCDebug(KICONTHEMES) << "Standard icon theme" << KIconTheme::defaultThemeName() << "not found!";
0440             delete def;
0441             return;
0442         }
0443     }
0444     mpThemeRoot = new KIconThemeNode(def);
0445     mThemesInTree.append(def->internalName());
0446     links.append(mpThemeRoot);
0447     addBaseThemes(mpThemeRoot, m_appname);
0448 
0449     // Insert application specific themes at the top.
0450     searchPaths.append(m_appname + QStringLiteral("/pics"));
0451 
0452     // Add legacy icon dirs.
0453     searchPaths.append(QStringLiteral("icons")); // was xdgdata-icon in KStandardDirs
0454     // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
0455     searchPaths.append(QStringLiteral("pixmaps")); // was xdgdata-pixmaps in KStandardDirs
0456 }
0457 
0458 KIconLoader::~KIconLoader() = default;
0459 
0460 QStringList KIconLoader::searchPaths() const
0461 {
0462     return d->searchPaths;
0463 }
0464 
0465 void KIconLoader::addAppDir(const QString &appname, const QString &themeBaseDir)
0466 {
0467     d->searchPaths.append(appname + QStringLiteral("/pics"));
0468     d->addAppThemes(appname, themeBaseDir);
0469 }
0470 
0471 void KIconLoaderPrivate::addAppThemes(const QString &appname, const QString &themeBaseDir)
0472 {
0473     KIconTheme *def = new KIconTheme(QStringLiteral("hicolor"), appname, themeBaseDir);
0474     if (!def->isValid()) {
0475         delete def;
0476         def = new KIconTheme(KIconTheme::defaultThemeName(), appname, themeBaseDir);
0477     }
0478     KIconThemeNode *node = new KIconThemeNode(def);
0479     bool addedToLinks = false;
0480 
0481     if (!mThemesInTree.contains(appname)) {
0482         mThemesInTree.append(appname);
0483         links.append(node);
0484         addedToLinks = true;
0485     }
0486     addBaseThemes(node, appname);
0487 
0488     if (!addedToLinks) {
0489         // Nodes in links are being deleted later - this one needs manual care.
0490         delete node;
0491     }
0492 }
0493 
0494 void KIconLoaderPrivate::addBaseThemes(KIconThemeNode *node, const QString &appname)
0495 {
0496     // Quote from the icon theme specification:
0497     //   The lookup is done first in the current theme, and then recursively
0498     //   in each of the current theme's parents, and finally in the
0499     //   default theme called "hicolor" (implementations may add more
0500     //   default themes before "hicolor", but "hicolor" must be last).
0501     //
0502     // So we first make sure that all inherited themes are added, then we
0503     // add the KDE default theme as fallback for all icons that might not be
0504     // present in an inherited theme, and hicolor goes last.
0505 
0506     addInheritedThemes(node, appname);
0507     addThemeByName(QIcon::fallbackThemeName(), appname);
0508     addThemeByName(QStringLiteral("hicolor"), appname);
0509 }
0510 
0511 void KIconLoaderPrivate::addInheritedThemes(KIconThemeNode *node, const QString &appname)
0512 {
0513     const QStringList inheritedThemes = node->theme->inherits();
0514 
0515     for (const auto &inheritedTheme : inheritedThemes) {
0516         if (inheritedTheme == QLatin1String("hicolor")) {
0517             // The icon theme spec says that "hicolor" must be the very last
0518             // of all inherited themes, so don't add it here but at the very end
0519             // of addBaseThemes().
0520             continue;
0521         }
0522         addThemeByName(inheritedTheme, appname);
0523     }
0524 }
0525 
0526 void KIconLoaderPrivate::addThemeByName(const QString &themename, const QString &appname)
0527 {
0528     if (mThemesInTree.contains(themename + appname)) {
0529         return;
0530     }
0531     KIconTheme *theme = new KIconTheme(themename, appname);
0532     if (!theme->isValid()) {
0533         delete theme;
0534         return;
0535     }
0536     KIconThemeNode *n = new KIconThemeNode(theme);
0537     mThemesInTree.append(themename + appname);
0538     links.append(n);
0539     addInheritedThemes(n, appname);
0540 }
0541 
0542 void KIconLoaderPrivate::addExtraDesktopThemes()
0543 {
0544     if (extraDesktopIconsLoaded) {
0545         return;
0546     }
0547 
0548     QStringList list;
0549     const QStringList icnlibs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory);
0550     for (const auto &iconDir : icnlibs) {
0551         QDir dir(iconDir);
0552         if (!dir.exists()) {
0553             continue;
0554         }
0555         const auto defaultEntries = dir.entryInfoList(QStringList(QStringLiteral("default.*")), QDir::Dirs);
0556         for (const auto &defaultEntry : defaultEntries) {
0557             if (!QFileInfo::exists(defaultEntry.filePath() + QLatin1String("/index.desktop")) //
0558                 && !QFileInfo::exists(defaultEntry.filePath() + QLatin1String("/index.theme"))) {
0559                 continue;
0560             }
0561             if (defaultEntry.isSymbolicLink()) {
0562                 const QString themeName = QDir(defaultEntry.symLinkTarget()).dirName();
0563                 if (!list.contains(themeName)) {
0564                     list.append(themeName);
0565                 }
0566             }
0567         }
0568     }
0569 
0570     for (const auto &theme : list) {
0571         // Don't add the KDE defaults once more, we have them anyways.
0572         if (theme == QLatin1String("default.kde") || theme == QLatin1String("default.kde4")) {
0573             continue;
0574         }
0575         addThemeByName(theme, QLatin1String(""));
0576     }
0577 
0578     extraDesktopIconsLoaded = true;
0579 }
0580 
0581 void KIconLoader::drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state) const
0582 {
0583     d->drawOverlays(this, group, state, pixmap, overlays);
0584 }
0585 
0586 QString KIconLoaderPrivate::removeIconExtension(const QString &name) const
0587 {
0588     if (name.endsWith(QLatin1String(".png")) //
0589         || name.endsWith(QLatin1String(".xpm")) //
0590         || name.endsWith(QLatin1String(".svg"))) {
0591         return name.left(name.length() - 4);
0592     } else if (name.endsWith(QLatin1String(".svgz"))) {
0593         return name.left(name.length() - 5);
0594     }
0595 
0596     return name;
0597 }
0598 
0599 void KIconLoaderPrivate::normalizeIconMetadata(KIconLoader::Group &group, QSize &size, int &state) const
0600 {
0601     if ((state < 0) || (state >= KIconLoader::LastState)) {
0602         qCWarning(KICONTHEMES) << "Invalid icon state:" << state << ", should be one of KIconLoader::States";
0603         state = KIconLoader::DefaultState;
0604     }
0605 
0606     if (size.width() < 0 || size.height() < 0) {
0607         size = {};
0608     }
0609 
0610     // For "User" icons, bail early since the size should be based on the size on disk,
0611     // which we've already checked.
0612     if (group == KIconLoader::User) {
0613         return;
0614     }
0615 
0616     if ((group < -1) || (group >= KIconLoader::LastGroup)) {
0617         qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
0618         group = KIconLoader::Desktop;
0619     }
0620 
0621     // If size == 0, use default size for the specified group.
0622     if (size.isNull()) {
0623         if (group < 0) {
0624             qWarning() << "Neither size nor group specified!";
0625             group = KIconLoader::Desktop;
0626         }
0627         size = QSize(mpGroups[group].size, mpGroups[group].size);
0628     }
0629 }
0630 
0631 QString KIconLoaderPrivate::makeCacheKey(const QString &name,
0632                                          KIconLoader::Group group,
0633                                          const QStringList &overlays,
0634                                          const QSize &size,
0635                                          qreal scale,
0636                                          int state,
0637                                          const KIconColors &colors) const
0638 {
0639     // The KSharedDataCache is shared so add some namespacing. The following code
0640     // uses QStringBuilder (new in Qt 4.6)
0641 
0642     /* clang-format off */
0643     return (group == KIconLoader::User ? QLatin1String("$kicou_") : QLatin1String("$kico_"))
0644             % name
0645             % QLatin1Char('_')
0646             % (size.width() == size.height() ? QString::number(size.height()) : QString::number(size.height()) % QLatin1Char('x') % QString::number(size.width()))
0647             % QLatin1Char('@')
0648             % QString::number(scale, 'f', 1)
0649             % QLatin1Char('_')
0650             % overlays.join(QLatin1Char('_'))
0651             % (group >= 0 ? mpEffect.fingerprint(group, state) : NULL_EFFECT_FINGERPRINT())
0652             % QLatin1Char('_')
0653             % paletteId(colors)
0654             % (q->theme() && q->theme()->followsColorScheme() && state == KIconLoader::SelectedState ? QStringLiteral("_selected") : QString());
0655     /* clang-format on */
0656 }
0657 
0658 QByteArray KIconLoaderPrivate::processSvg(const QString &path, KIconLoader::States state, const KIconColors &colors) const
0659 {
0660     std::unique_ptr<QIODevice> device;
0661 
0662     if (path.endsWith(QLatin1String("svgz"))) {
0663         device.reset(new KCompressionDevice(path, KCompressionDevice::GZip));
0664     } else {
0665         device.reset(new QFile(path));
0666     }
0667 
0668     if (!device->open(QIODevice::ReadOnly)) {
0669         return QByteArray();
0670     }
0671 
0672     const QString styleSheet = colors.stylesheet(state);
0673     QByteArray processedContents;
0674     QXmlStreamReader reader(device.get());
0675 
0676     QBuffer buffer(&processedContents);
0677     buffer.open(QIODevice::WriteOnly);
0678     QXmlStreamWriter writer(&buffer);
0679     while (!reader.atEnd()) {
0680         if (reader.readNext() == QXmlStreamReader::StartElement //
0681             && reader.qualifiedName() == QLatin1String("style") //
0682             && reader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) {
0683             writer.writeStartElement(QStringLiteral("style"));
0684             writer.writeAttributes(reader.attributes());
0685             writer.writeCharacters(styleSheet);
0686             writer.writeEndElement();
0687             while (reader.tokenType() != QXmlStreamReader::EndElement) {
0688                 reader.readNext();
0689             }
0690         } else if (reader.tokenType() != QXmlStreamReader::Invalid) {
0691             writer.writeCurrentToken(reader);
0692         }
0693     }
0694     buffer.close();
0695 
0696     return processedContents;
0697 }
0698 
0699 QImage KIconLoaderPrivate::createIconImage(const QString &path, const QSize &size, qreal scale, KIconLoader::States state, const KIconColors &colors)
0700 {
0701     // TODO: metadata in the theme to make it do this only if explicitly supported?
0702     QImageReader reader;
0703     QBuffer buffer;
0704 
0705     if (q->theme() && q->theme()->followsColorScheme() && (path.endsWith(QLatin1String("svg")) || path.endsWith(QLatin1String("svgz")))) {
0706         buffer.setData(processSvg(path, state, colors));
0707         reader.setDevice(&buffer);
0708     } else {
0709         reader.setFileName(path);
0710     }
0711 
0712     if (!reader.canRead()) {
0713         return QImage();
0714     }
0715 
0716     if (!size.isNull()) {
0717         // ensure we keep aspect ratio
0718         const QSize wantedSize = size * scale;
0719         QSize finalSize(reader.size());
0720         if (finalSize.isNull()) {
0721             // nothing to scale
0722             finalSize = wantedSize;
0723         } else {
0724             // like QSvgIconEngine::pixmap try to keep aspect ratio
0725             finalSize.scale(wantedSize, Qt::KeepAspectRatio);
0726         }
0727         reader.setScaledSize(finalSize);
0728     }
0729 
0730     return reader.read();
0731 }
0732 
0733 void KIconLoaderPrivate::insertCachedPixmapWithPath(const QString &key, const QPixmap &data, const QString &path = QString())
0734 {
0735     // Even if the pixmap is null, we add it to the caches so that we record
0736     // the fact that whatever icon led to us getting a null pixmap doesn't
0737     // exist.
0738 
0739     QBuffer output;
0740     output.open(QIODevice::WriteOnly);
0741 
0742     QDataStream outputStream(&output);
0743     outputStream.setVersion(QDataStream::Qt_4_6);
0744 
0745     outputStream << path;
0746 
0747     // Convert the QPixmap to PNG. This is actually done by Qt's own operator.
0748     outputStream << data;
0749 
0750     output.close();
0751 
0752     // The byte array contained in the QBuffer is what we want in the cache.
0753     mIconCache->insert(key, output.buffer());
0754 
0755     // Also insert the object into our process-local cache for even more
0756     // speed.
0757     PixmapWithPath *pixmapPath = new PixmapWithPath;
0758     pixmapPath->pixmap = data;
0759     pixmapPath->path = path;
0760 
0761     mPixmapCache.insert(key, pixmapPath, data.width() * data.height() + 1);
0762 }
0763 
0764 bool KIconLoaderPrivate::findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path)
0765 {
0766     // If the pixmap is present in our local process cache, use that since we
0767     // don't need to decompress and upload it to the X server/graphics card.
0768     const PixmapWithPath *pixmapPath = mPixmapCache.object(key);
0769     if (pixmapPath) {
0770         path = pixmapPath->path;
0771         data = pixmapPath->pixmap;
0772 
0773         return true;
0774     }
0775 
0776     // Otherwise try to find it in our shared memory cache since that will
0777     // be quicker than the disk, especially for SVGs.
0778     QByteArray result;
0779 
0780     if (!mIconCache->find(key, &result) || result.isEmpty()) {
0781         return false;
0782     }
0783 
0784     QBuffer buffer;
0785     buffer.setBuffer(&result);
0786     buffer.open(QIODevice::ReadOnly);
0787 
0788     QDataStream inputStream(&buffer);
0789     inputStream.setVersion(QDataStream::Qt_4_6);
0790 
0791     QString tempPath;
0792     inputStream >> tempPath;
0793 
0794     if (inputStream.status() == QDataStream::Ok) {
0795         QPixmap tempPixmap;
0796         inputStream >> tempPixmap;
0797 
0798         if (inputStream.status() == QDataStream::Ok) {
0799             data = tempPixmap;
0800             path = tempPath;
0801 
0802             // Since we're here we didn't have a QPixmap cache entry, add one now.
0803             PixmapWithPath *newPixmapWithPath = new PixmapWithPath;
0804             newPixmapWithPath->pixmap = data;
0805             newPixmapWithPath->path = path;
0806 
0807             mPixmapCache.insert(key, newPixmapWithPath, data.width() * data.height() + 1);
0808 
0809             return true;
0810         }
0811     }
0812 
0813     return false;
0814 }
0815 
0816 QString KIconLoaderPrivate::findMatchingIconWithGenericFallbacks(const QString &name, int size, qreal scale) const
0817 {
0818     QString path = findMatchingIcon(name, size, scale);
0819     if (!path.isEmpty()) {
0820         return path;
0821     }
0822 
0823     const QString genericIcon = s_globalData()->genericIconFor(name);
0824     if (!genericIcon.isEmpty()) {
0825         path = findMatchingIcon(genericIcon, size, scale);
0826     }
0827     return path;
0828 }
0829 
0830 QString KIconLoaderPrivate::findMatchingIcon(const QString &name, int size, qreal scale) const
0831 {
0832     // This looks for the exact match and its
0833     // generic fallbacks in each themeNode one after the other.
0834 
0835     // In theory we should only do this for mimetype icons, not for app icons,
0836     // but that would require different APIs. The long term solution is under
0837     // development for Qt >= 5.8, QFileIconProvider calling QPlatformTheme::fileIcon,
0838     // using QMimeType::genericIconName() to get the proper -x-generic fallback.
0839     // Once everyone uses that to look up mimetype icons, we can kill the fallback code
0840     // from this method.
0841 
0842     bool genericFallback = name.endsWith(QLatin1String("-x-generic"));;
0843     QString path;
0844     for (KIconThemeNode *themeNode : std::as_const(links)) {
0845         QString currentName = name;
0846 
0847         while (!currentName.isEmpty()) {
0848             path = themeNode->theme->iconPathByName(currentName, size, KIconLoader::MatchBest, scale);
0849             if (!path.isEmpty()) {
0850                 return path;
0851             }
0852 
0853             if (genericFallback) {
0854                 // we already tested the base name
0855                 break;
0856             }
0857 
0858             int rindex = currentName.lastIndexOf(QLatin1Char('-'));
0859             if (rindex > 1) { // > 1 so that we don't split x-content or x-epoc
0860                 currentName.truncate(rindex);
0861 
0862                 if (currentName.endsWith(QLatin1String("-x"))) {
0863                     currentName.chop(2);
0864                 }
0865             } else {
0866                 // From update-mime-database.c
0867                 static const QSet<QString> mediaTypes = QSet<QString>{QStringLiteral("text"),
0868                                                                       QStringLiteral("application"),
0869                                                                       QStringLiteral("image"),
0870                                                                       QStringLiteral("audio"),
0871                                                                       QStringLiteral("inode"),
0872                                                                       QStringLiteral("video"),
0873                                                                       QStringLiteral("message"),
0874                                                                       QStringLiteral("model"),
0875                                                                       QStringLiteral("multipart"),
0876                                                                       QStringLiteral("x-content"),
0877                                                                       QStringLiteral("x-epoc")};
0878                 // Shared-mime-info spec says:
0879                 // "If [generic-icon] is not specified then the mimetype is used to generate the
0880                 // generic icon by using the top-level media type (e.g. "video" in "video/ogg")
0881                 // and appending "-x-generic" (i.e. "video-x-generic" in the previous example)."
0882                 if (mediaTypes.contains(currentName)) {
0883                     currentName += QLatin1String("-x-generic");
0884                     genericFallback = true;
0885                 } else {
0886                     break;
0887                 }
0888             }
0889         }
0890     }
0891 
0892     if (path.isEmpty()) {
0893         const QStringList fallbackPaths = QIcon::fallbackSearchPaths();
0894 
0895         for (const QString &path : fallbackPaths) {
0896             const QString extensions[] = {QStringLiteral(".png"), QStringLiteral(".svg"), QStringLiteral(".svgz"), QStringLiteral(".xpm")};
0897 
0898             for (const QString &ext : extensions) {
0899                 const QString file = path + '/' + name + ext;
0900 
0901                 if (QFileInfo::exists(file)) {
0902                     return file;
0903                 }
0904             }
0905         }
0906     }
0907 
0908     return path;
0909 }
0910 
0911 QString KIconLoaderPrivate::preferredIconPath(const QString &name)
0912 {
0913     QString path;
0914 
0915     auto it = mIconAvailability.constFind(name);
0916     const auto end = mIconAvailability.constEnd();
0917 
0918     if (it != end && it.value().isEmpty() && !shouldCheckForUnknownIcons()) {
0919         return path; // known to be unavailable
0920     }
0921 
0922     if (it != end) {
0923         path = it.value();
0924     }
0925 
0926     if (path.isEmpty()) {
0927         path = q->iconPath(name, KIconLoader::Desktop, KIconLoader::MatchBest);
0928         mIconAvailability.insert(name, path);
0929     }
0930 
0931     return path;
0932 }
0933 
0934 inline QString KIconLoaderPrivate::unknownIconPath(int size, qreal scale) const
0935 {
0936     QString path = findMatchingIcon(QStringLiteral("unknown"), size, scale);
0937     if (path.isEmpty()) {
0938         qCDebug(KICONTHEMES) << "Warning: could not find \"unknown\" icon for size" << size << "at scale" << scale;
0939         return QString();
0940     }
0941     return path;
0942 }
0943 
0944 QString KIconLoaderPrivate::locate(const QString &fileName)
0945 {
0946     for (const QString &dir : std::as_const(searchPaths)) {
0947         const QString path = dir + QLatin1Char('/') + fileName;
0948         if (QDir(dir).isAbsolute()) {
0949             if (QFileInfo::exists(path)) {
0950                 return path;
0951             }
0952         } else {
0953             const QString fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, path);
0954             if (!fullPath.isEmpty()) {
0955                 return fullPath;
0956             }
0957         }
0958     }
0959     return QString();
0960 }
0961 
0962 // Finds the absolute path to an icon.
0963 
0964 QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull) const
0965 {
0966     return iconPath(_name, group_or_size, canReturnNull, 1 /*scale*/);
0967 }
0968 
0969 QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull, qreal scale) const
0970 {
0971     // we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
0972     if (_name.isEmpty() || QDir::isAbsolutePath(_name)) {
0973         // we have either an absolute path or nothing to work with
0974         return _name;
0975     }
0976 
0977     QString name = d->removeIconExtension(_name);
0978 
0979     QString path;
0980     if (group_or_size == KIconLoader::User) {
0981         path = d->locate(name + QLatin1String(".png"));
0982         if (path.isEmpty()) {
0983             path = d->locate(name + QLatin1String(".svgz"));
0984         }
0985         if (path.isEmpty()) {
0986             path = d->locate(name + QLatin1String(".svg"));
0987         }
0988         if (path.isEmpty()) {
0989             path = d->locate(name + QLatin1String(".xpm"));
0990         }
0991         return path;
0992     }
0993 
0994     if (group_or_size >= KIconLoader::LastGroup) {
0995         qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
0996         return path;
0997     }
0998 
0999     int size;
1000     if (group_or_size >= 0) {
1001         size = d->mpGroups[group_or_size].size;
1002     } else {
1003         size = -group_or_size;
1004     }
1005 
1006     if (_name.isEmpty()) {
1007         if (canReturnNull) {
1008             return QString();
1009         } else {
1010             return d->unknownIconPath(size, scale);
1011         }
1012     }
1013 
1014     path = d->findMatchingIconWithGenericFallbacks(name, size, scale);
1015 
1016     if (path.isEmpty()) {
1017         // Try "User" group too.
1018         path = iconPath(name, KIconLoader::User, true);
1019         if (!path.isEmpty() || canReturnNull) {
1020             return path;
1021         }
1022 
1023         return d->unknownIconPath(size, scale);
1024     }
1025     return path;
1026 }
1027 
1028 QPixmap
1029 KIconLoader::loadMimeTypeIcon(const QString &_iconName, KIconLoader::Group group, int size, int state, const QStringList &overlays, QString *path_store) const
1030 {
1031     QString iconName = _iconName;
1032     const int slashindex = iconName.indexOf(QLatin1Char('/'));
1033     if (slashindex != -1) {
1034         iconName[slashindex] = QLatin1Char('-');
1035     }
1036 
1037     if (!d->extraDesktopIconsLoaded) {
1038         const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
1039         if (!pixmap.isNull()) {
1040             return pixmap;
1041         }
1042         d->addExtraDesktopThemes();
1043     }
1044     const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
1045     if (pixmap.isNull()) {
1046         // Icon not found, fallback to application/octet-stream
1047         return loadIcon(QStringLiteral("application-octet-stream"), group, size, state, overlays, path_store, false);
1048     }
1049     return pixmap;
1050 }
1051 
1052 QPixmap KIconLoader::loadIcon(const QString &_name,
1053                               KIconLoader::Group group,
1054                               int size,
1055                               int state,
1056                               const QStringList &overlays,
1057                               QString *path_store,
1058                               bool canReturnNull) const
1059 {
1060     return loadScaledIcon(_name, group, 1.0 /*scale*/, size, state, overlays, path_store, canReturnNull);
1061 }
1062 
1063 QPixmap KIconLoader::loadScaledIcon(const QString &_name,
1064                                     KIconLoader::Group group,
1065                                     qreal scale,
1066                                     int size,
1067                                     int state,
1068                                     const QStringList &overlays,
1069                                     QString *path_store,
1070                                     bool canReturnNull) const
1071 {
1072     return loadScaledIcon(_name, group, scale, QSize(size, size), state, overlays, path_store, canReturnNull);
1073 }
1074 
1075 QPixmap KIconLoader::loadScaledIcon(const QString &_name,
1076                                     KIconLoader::Group group,
1077                                     qreal scale,
1078                                     const QSize &size,
1079                                     int state,
1080                                     const QStringList &overlays,
1081                                     QString *path_store,
1082                                     bool canReturnNull) const
1083 {
1084     return loadScaledIcon(_name, group, scale, size, state, overlays, path_store, canReturnNull, {});
1085 }
1086 
1087 QPixmap KIconLoader::loadScaledIcon(const QString &_name,
1088                                     KIconLoader::Group group,
1089                                     qreal scale,
1090                                     const QSize &_size,
1091                                     int state,
1092                                     const QStringList &overlays,
1093                                     QString *path_store,
1094                                     bool canReturnNull,
1095                                     const std::optional<KIconColors> &colors) const
1096 
1097 {
1098     QString name = _name;
1099     bool favIconOverlay = false;
1100 
1101     if (_size.width() < 0 || _size.height() < 0 || _name.isEmpty()) {
1102         return QPixmap();
1103     }
1104 
1105     QSize size = _size;
1106 
1107     /*
1108      * This method works in a kind of pipeline, with the following steps:
1109      * 1. Sanity checks.
1110      * 2. Convert _name, group, size, etc. to a key name.
1111      * 3. Check if the key is already cached.
1112      * 4. If not, initialize the theme and find/load the icon.
1113      * 4a Apply overlays
1114      * 4b Re-add to cache.
1115      */
1116 
1117     // Special case for absolute path icons.
1118     if (name.startsWith(QLatin1String("favicons/"))) {
1119         favIconOverlay = true;
1120         name = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + name + QStringLiteral(".png");
1121     }
1122 
1123     // we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
1124     const bool absolutePath = QDir::isAbsolutePath(name);
1125     if (!absolutePath) {
1126         name = d->removeIconExtension(name);
1127     }
1128 
1129     // Don't bother looking for an icon with no name.
1130     if (name.isEmpty()) {
1131         return QPixmap();
1132     }
1133 
1134     // May modify group, size, or state. This function puts them into sane
1135     // states.
1136     d->normalizeIconMetadata(group, size, state);
1137 
1138     // See if the image is already cached.
1139     auto usedColors = colors ? *colors : d->mCustomColors ? d->mColors : KIconColors(qApp->palette());
1140     QString key = d->makeCacheKey(name, group, overlays, size, scale, state, usedColors);
1141     QPixmap pix;
1142 
1143     bool iconWasUnknown = false;
1144     QString path;
1145 
1146     if (d->findCachedPixmapWithPath(key, pix, path)) {
1147         pix.setDevicePixelRatio(scale);
1148 
1149         if (path_store) {
1150             *path_store = path;
1151         }
1152 
1153         if (!path.isEmpty()) {
1154             return pix;
1155         } else {
1156             // path is empty for "unknown" icons, which should be searched for
1157             // anew regularly
1158             if (!d->shouldCheckForUnknownIcons()) {
1159                 return canReturnNull ? QPixmap() : pix;
1160             }
1161         }
1162     }
1163 
1164     // Image is not cached... go find it and apply effects.
1165 
1166     favIconOverlay = favIconOverlay && std::min(size.height(), size.width()) > 22;
1167 
1168     // First we look for non-User icons. If we don't find one we'd search in
1169     // the User space anyways...
1170     if (group != KIconLoader::User) {
1171         if (absolutePath && !favIconOverlay) {
1172             path = name;
1173         } else {
1174             path = d->findMatchingIconWithGenericFallbacks(favIconOverlay ? QStringLiteral("text-html") : name, std::min(size.height(), size.width()), scale);
1175         }
1176     }
1177 
1178     if (path.isEmpty()) {
1179         // We do have a "User" icon, or we couldn't find the non-User one.
1180         path = (absolutePath) ? name : iconPath(name, KIconLoader::User, canReturnNull);
1181     }
1182 
1183     // Still can't find it? Use "unknown" if we can't return null.
1184     // We keep going in the function so we can ensure this result gets cached.
1185     if (path.isEmpty() && !canReturnNull) {
1186         path = d->unknownIconPath(std::min(size.height(), size.width()), scale);
1187         iconWasUnknown = true;
1188     }
1189 
1190     QImage img;
1191     if (!path.isEmpty()) {
1192         img = d->createIconImage(path, size, scale, static_cast<KIconLoader::States>(state), usedColors);
1193     }
1194 
1195     if (group >= 0 && group < KIconLoader::LastGroup) {
1196         img = d->mpEffect.apply(img, group, state);
1197     }
1198 
1199     if (favIconOverlay) {
1200         QImage favIcon(name, "PNG");
1201         if (!favIcon.isNull()) { // if favIcon not there yet, don't try to blend it
1202             QPainter p(&img);
1203 
1204             // Align the favicon overlay
1205             QRect r(favIcon.rect());
1206             r.moveBottomRight(img.rect().bottomRight());
1207             r.adjust(-1, -1, -1, -1); // Move off edge
1208 
1209             // Blend favIcon over img.
1210             p.drawImage(r, favIcon);
1211         }
1212     }
1213 
1214     pix = QPixmap::fromImage(img);
1215 
1216     // TODO: If we make a loadIcon that returns the image we can convert
1217     // drawOverlays to use the image instead of pixmaps as well so we don't
1218     // have to transfer so much to the graphics card.
1219     d->drawOverlays(this, group, state, pix, overlays);
1220 
1221     // Don't add the path to our unknown icon to the cache, only cache the
1222     // actual image.
1223     if (iconWasUnknown) {
1224         path.clear();
1225     }
1226 
1227     d->insertCachedPixmapWithPath(key, pix, path);
1228 
1229     if (path_store) {
1230         *path_store = path;
1231     }
1232 
1233     return pix;
1234 }
1235 
1236 KPixmapSequence KIconLoader::loadPixmapSequence(const QString &xdgIconName, int size) const
1237 {
1238     return KPixmapSequence(iconPath(xdgIconName, -size), size);
1239 }
1240 
1241 QMovie *KIconLoader::loadMovie(const QString &name, KIconLoader::Group group, int size, QObject *parent) const
1242 {
1243     QString file = moviePath(name, group, size);
1244     if (file.isEmpty()) {
1245         return nullptr;
1246     }
1247     int dirLen = file.lastIndexOf(QLatin1Char('/'));
1248     const QString icon = iconPath(name, size ? -size : group, true);
1249     if (!icon.isEmpty() && file.left(dirLen) != icon.left(dirLen)) {
1250         return nullptr;
1251     }
1252     QMovie *movie = new QMovie(file, QByteArray(), parent);
1253     if (!movie->isValid()) {
1254         delete movie;
1255         return nullptr;
1256     }
1257     return movie;
1258 }
1259 
1260 QString KIconLoader::moviePath(const QString &name, KIconLoader::Group group, int size) const
1261 {
1262     if (!d->mpGroups) {
1263         return QString();
1264     }
1265 
1266     if ((group < -1 || group >= KIconLoader::LastGroup) && group != KIconLoader::User) {
1267         qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1268         group = KIconLoader::Desktop;
1269     }
1270     if (size == 0 && group < 0) {
1271         qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1272         group = KIconLoader::Desktop;
1273     }
1274 
1275     QString file = name + QStringLiteral(".mng");
1276     if (group == KIconLoader::User) {
1277         file = d->locate(file);
1278     } else {
1279         if (size == 0) {
1280             size = d->mpGroups[group].size;
1281         }
1282 
1283         QString path;
1284 
1285         for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1286             path = themeNode->theme->iconPath(file, size, KIconLoader::MatchExact);
1287             if (!path.isEmpty()) {
1288                 break;
1289             }
1290         }
1291 
1292         if (path.isEmpty()) {
1293             for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1294                 path = themeNode->theme->iconPath(file, size, KIconLoader::MatchBest);
1295                 if (!path.isEmpty()) {
1296                     break;
1297                 }
1298             }
1299         }
1300 
1301         file = path;
1302     }
1303     return file;
1304 }
1305 
1306 QStringList KIconLoader::loadAnimated(const QString &name, KIconLoader::Group group, int size) const
1307 {
1308     QStringList lst;
1309 
1310     if (!d->mpGroups) {
1311         return lst;
1312     }
1313 
1314     d->initIconThemes();
1315 
1316     if ((group < -1) || (group >= KIconLoader::LastGroup)) {
1317         qCDebug(KICONTHEMES) << "Invalid icon group: " << group << ", should be one of KIconLoader::Group";
1318         group = KIconLoader::Desktop;
1319     }
1320     if ((size == 0) && (group < 0)) {
1321         qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1322         group = KIconLoader::Desktop;
1323     }
1324 
1325     QString file = name + QStringLiteral("/0001");
1326     if (group == KIconLoader::User) {
1327         file = d->locate(file + QStringLiteral(".png"));
1328     } else {
1329         if (size == 0) {
1330             size = d->mpGroups[group].size;
1331         }
1332         file = d->findMatchingIcon(file, size, 1); // FIXME scale
1333     }
1334     if (file.isEmpty()) {
1335         return lst;
1336     }
1337 
1338     QString path = file.left(file.length() - 8);
1339     QDir dir(QFile::encodeName(path));
1340     if (!dir.exists()) {
1341         return lst;
1342     }
1343 
1344     const auto entryList = dir.entryList();
1345     for (const QString &entry : entryList) {
1346         const QStringRef chunk = entry.leftRef(4);
1347         if (!chunk.toUInt()) {
1348             continue;
1349         }
1350 
1351         lst += path + entry;
1352     }
1353     lst.sort();
1354     return lst;
1355 }
1356 
1357 KIconTheme *KIconLoader::theme() const
1358 {
1359     if (d->mpThemeRoot) {
1360         return d->mpThemeRoot->theme;
1361     }
1362     return nullptr;
1363 }
1364 
1365 int KIconLoader::currentSize(KIconLoader::Group group) const
1366 {
1367     if (!d->mpGroups) {
1368         return -1;
1369     }
1370 
1371     if (group < 0 || group >= KIconLoader::LastGroup) {
1372         qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1373         return -1;
1374     }
1375     return d->mpGroups[group].size;
1376 }
1377 
1378 QStringList KIconLoader::queryIconsByDir(const QString &iconsDir) const
1379 {
1380     const QDir dir(iconsDir);
1381     const QStringList formats = QStringList() << QStringLiteral("*.png") << QStringLiteral("*.xpm") << QStringLiteral("*.svg") << QStringLiteral("*.svgz");
1382     const QStringList lst = dir.entryList(formats, QDir::Files);
1383     QStringList result;
1384     for (const auto &file : lst) {
1385         result += iconsDir + QLatin1Char('/') + file;
1386     }
1387     return result;
1388 }
1389 
1390 QStringList KIconLoader::queryIconsByContext(int group_or_size, KIconLoader::Context context) const
1391 {
1392     QStringList result;
1393     if (group_or_size >= KIconLoader::LastGroup) {
1394         qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
1395         return result;
1396     }
1397     int size;
1398     if (group_or_size >= 0) {
1399         size = d->mpGroups[group_or_size].size;
1400     } else {
1401         size = -group_or_size;
1402     }
1403 
1404     for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1405         themeNode->queryIconsByContext(&result, size, context);
1406     }
1407 
1408     // Eliminate duplicate entries (same icon in different directories)
1409     QString name;
1410     QStringList res2;
1411     QStringList entries;
1412     for (const auto &icon : std::as_const(result)) {
1413         const int n = icon.lastIndexOf(QLatin1Char('/'));
1414         if (n == -1) {
1415             name = icon;
1416         } else {
1417             name = icon.mid(n + 1);
1418         }
1419         name = d->removeIconExtension(name);
1420         if (!entries.contains(name)) {
1421             entries += name;
1422             res2 += icon;
1423         }
1424     }
1425     return res2;
1426 }
1427 
1428 QStringList KIconLoader::queryIcons(int group_or_size, KIconLoader::Context context) const
1429 {
1430     d->initIconThemes();
1431 
1432     QStringList result;
1433     if (group_or_size >= KIconLoader::LastGroup) {
1434         qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
1435         return result;
1436     }
1437     int size;
1438     if (group_or_size >= 0) {
1439         size = d->mpGroups[group_or_size].size;
1440     } else {
1441         size = -group_or_size;
1442     }
1443 
1444     for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1445         themeNode->queryIcons(&result, size, context);
1446     }
1447 
1448     // Eliminate duplicate entries (same icon in different directories)
1449     QString name;
1450     QStringList res2;
1451     QStringList entries;
1452     for (const auto &icon : std::as_const(result)) {
1453         const int n = icon.lastIndexOf(QLatin1Char('/'));
1454         if (n == -1) {
1455             name = icon;
1456         } else {
1457             name = icon.mid(n + 1);
1458         }
1459         name = d->removeIconExtension(name);
1460         if (!entries.contains(name)) {
1461             entries += name;
1462             res2 += icon;
1463         }
1464     }
1465     return res2;
1466 }
1467 
1468 // used by KIconDialog to find out which contexts to offer in a combobox
1469 bool KIconLoader::hasContext(KIconLoader::Context context) const
1470 {
1471     for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1472         if (themeNode->theme->hasContext(context)) {
1473             return true;
1474         }
1475     }
1476     return false;
1477 }
1478 
1479 KIconEffect *KIconLoader::iconEffect() const
1480 {
1481     return &d->mpEffect;
1482 }
1483 
1484 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 82)
1485 bool KIconLoader::alphaBlending(KIconLoader::Group group) const
1486 {
1487     if (!d->mpGroups) {
1488         return false;
1489     }
1490 
1491     if (group < 0 || group >= KIconLoader::LastGroup) {
1492         qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1493         return false;
1494     }
1495     return true;
1496 }
1497 #endif
1498 
1499 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1500 QIcon KIconLoader::loadIconSet(const QString &name, KIconLoader::Group g, int s, bool canReturnNull)
1501 {
1502     QIcon iconset;
1503     QPixmap tmp = loadIcon(name, g, s, KIconLoader::ActiveState, QStringList(), nullptr, canReturnNull);
1504     iconset.addPixmap(tmp, QIcon::Active, QIcon::On);
1505     // we don't use QIconSet's resizing anyway
1506     tmp = loadIcon(name, g, s, KIconLoader::DisabledState, QStringList(), nullptr, canReturnNull);
1507     iconset.addPixmap(tmp, QIcon::Disabled, QIcon::On);
1508     tmp = loadIcon(name, g, s, KIconLoader::DefaultState, QStringList(), nullptr, canReturnNull);
1509     iconset.addPixmap(tmp, QIcon::Normal, QIcon::On);
1510     return iconset;
1511 }
1512 #endif
1513 
1514 // Easy access functions
1515 
1516 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1517 QPixmap DesktopIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1518 {
1519     KIconLoader *loader = KIconLoader::global();
1520     return loader->loadIcon(name, KIconLoader::Desktop, force_size, state, overlays);
1521 }
1522 #endif
1523 
1524 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1525 QIcon DesktopIconSet(const QString &name, int force_size)
1526 {
1527     KIconLoader *loader = KIconLoader::global();
1528     return loader->loadIconSet(name, KIconLoader::Desktop, force_size);
1529 }
1530 #endif
1531 
1532 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1533 QPixmap BarIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1534 {
1535     KIconLoader *loader = KIconLoader::global();
1536     return loader->loadIcon(name, KIconLoader::Toolbar, force_size, state, overlays);
1537 }
1538 #endif
1539 
1540 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1541 QIcon BarIconSet(const QString &name, int force_size)
1542 {
1543     KIconLoader *loader = KIconLoader::global();
1544     return loader->loadIconSet(name, KIconLoader::Toolbar, force_size);
1545 }
1546 #endif
1547 
1548 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1549 QPixmap SmallIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1550 {
1551     KIconLoader *loader = KIconLoader::global();
1552     return loader->loadIcon(name, KIconLoader::Small, force_size, state, overlays);
1553 }
1554 #endif
1555 
1556 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1557 QIcon SmallIconSet(const QString &name, int force_size)
1558 {
1559     KIconLoader *loader = KIconLoader::global();
1560     return loader->loadIconSet(name, KIconLoader::Small, force_size);
1561 }
1562 #endif
1563 
1564 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1565 QPixmap MainBarIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1566 {
1567     KIconLoader *loader = KIconLoader::global();
1568     return loader->loadIcon(name, KIconLoader::MainToolbar, force_size, state, overlays);
1569 }
1570 #endif
1571 
1572 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1573 QIcon MainBarIconSet(const QString &name, int force_size)
1574 {
1575     KIconLoader *loader = KIconLoader::global();
1576     return loader->loadIconSet(name, KIconLoader::MainToolbar, force_size);
1577 }
1578 #endif
1579 
1580 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 65)
1581 QPixmap UserIcon(const QString &name, int state, const QStringList &overlays)
1582 {
1583     KIconLoader *loader = KIconLoader::global();
1584     return loader->loadIcon(name, KIconLoader::User, 0, state, overlays);
1585 }
1586 #endif
1587 
1588 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1589 QIcon UserIconSet(const QString &name)
1590 {
1591     KIconLoader *loader = KIconLoader::global();
1592     return loader->loadIconSet(name, KIconLoader::User);
1593 }
1594 #endif
1595 
1596 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 66)
1597 int IconSize(KIconLoader::Group group)
1598 {
1599     KIconLoader *loader = KIconLoader::global();
1600     return loader->currentSize(group);
1601 }
1602 #endif
1603 
1604 QPixmap KIconLoader::unknown()
1605 {
1606     QPixmap pix;
1607     if (QPixmapCache::find(QStringLiteral("unknown"), &pix)) { // krazy:exclude=iconnames
1608         return pix;
1609     }
1610 
1611     const QString path = global()->iconPath(QStringLiteral("unknown"), KIconLoader::Small, true); // krazy:exclude=iconnames
1612     if (path.isEmpty()) {
1613         qCDebug(KICONTHEMES) << "Warning: Cannot find \"unknown\" icon.";
1614         pix = QPixmap(32, 32);
1615     } else {
1616         pix.load(path);
1617         QPixmapCache::insert(QStringLiteral("unknown"), pix); // krazy:exclude=iconnames
1618     }
1619 
1620     return pix;
1621 }
1622 
1623 bool KIconLoader::hasIcon(const QString &name) const
1624 {
1625     return !d->preferredIconPath(name).isEmpty();
1626 }
1627 
1628 void KIconLoader::setCustomPalette(const QPalette &palette)
1629 {
1630     d->mCustomColors = true;
1631     d->mColors = KIconColors(palette);
1632 }
1633 
1634 QPalette KIconLoader::customPalette() const
1635 {
1636     return d->mCustomColors ? d->mPalette : QPalette();
1637 }
1638 
1639 void KIconLoader::resetPalette()
1640 {
1641     d->mCustomColors = false;
1642 }
1643 
1644 bool KIconLoader::hasCustomPalette() const
1645 {
1646     return d->mCustomColors;
1647 }
1648 
1649 /*** the global icon loader ***/
1650 Q_GLOBAL_STATIC(KIconLoader, globalIconLoader)
1651 
1652 KIconLoader *KIconLoader::global()
1653 {
1654     return globalIconLoader();
1655 }
1656 
1657 void KIconLoader::newIconLoader()
1658 {
1659     if (global() == this) {
1660         KIconTheme::reconfigure();
1661     }
1662 
1663     reconfigure(objectName());
1664     Q_EMIT iconLoaderSettingsChanged();
1665 }
1666 
1667 void KIconLoader::emitChange(KIconLoader::Group g)
1668 {
1669     s_globalData->emitChange(g);
1670 }
1671 
1672 #include <kiconengine.h>
1673 QIcon KDE::icon(const QString &iconName, KIconLoader *iconLoader)
1674 {
1675     return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global()));
1676 }
1677 
1678 QIcon KDE::icon(const QString &iconName, const QStringList &overlays, KIconLoader *iconLoader)
1679 {
1680     return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global(), overlays));
1681 }
1682 
1683 QIcon KDE::icon(const QString &iconName, const KIconColors &colors, KIconLoader *iconLoader)
1684 {
1685     return QIcon(new KIconEngine(iconName, colors, iconLoader ? iconLoader : KIconLoader::global()));
1686 }
1687 
1688 #include "kiconloader.moc"
1689 #include "moc_kiconloader.cpp"