Warning, file /frameworks/kiconthemes/src/kiconloader.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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