File indexing completed on 2024-04-14 03:52:25

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