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