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"