File indexing completed on 2025-03-09 03:40:24
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"