1/*
2
3 kiconloader.cpp: An icon loader for KDE with theming functionality.
4
5 This file is part of the KDE project, module kdeui.
6 SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
7 SPDX-FileCopyrightText: 2000 Antonio Larrosa <larrosa@kde.org>
8 SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
9
10 SPDX-License-Identifier: LGPL-2.0-only
11*/
12
13#include "kiconloader.h"
14#include "kiconloader_p.h"
15
16// kdecore
17#include <KConfigGroup>
18#include <KSharedConfig>
19#ifdef WITH_QTDBUS
20#include <QDBusConnection>
21#include <QDBusMessage>
22#endif
23#include <QCryptographicHash>
24#include <QXmlStreamReader>
25#include <QXmlStreamWriter>
26
27// kdeui
28#include "debug.h"
29#include "kiconcolors.h"
30#include "kiconeffect.h"
31#include "kicontheme.h"
32
33#include <KColorScheme>
34#include <KCompressionDevice>
35
36#include <QBuffer>
37#include <QByteArray>
38#include <QDataStream>
39#include <QDir>
40#include <QElapsedTimer>
41#include <QFileInfo>
42#include <QGuiApplication>
43#include <QIcon>
44#include <QImage>
45#include <QMimeDatabase>
46#include <QMovie>
47#include <QPainter>
48#include <QPixmap>
49#include <QPixmapCache>
50#include <QStringBuilder> // % operator for QString
51#include <QtGui/private/qiconloader_p.h>
52
53#include <qplatformdefs.h> //for readlink
54
55namespace
56{
57
58/**
59 * Checks if name ends in one of the supported icon formats (i.e. .png)
60 * and returns the name without the extension if it does.
61 */
62QString removeIconExtension(const QString &name)
63{
64 if (name.endsWith(s: QLatin1String(".png")) //
65 || name.endsWith(s: QLatin1String(".xpm")) //
66 || name.endsWith(s: QLatin1String(".svg"))) {
67 return name.left(n: name.length() - 4);
68 } else if (name.endsWith(s: QLatin1String(".svgz"))) {
69 return name.left(n: name.length() - 5);
70 }
71
72 return name;
73}
74
75QStringList deduplicateIconsByName(const QStringList &icons)
76{
77 // Eliminate duplicate entries (same icon in different directories)
78 QStringList result;
79 QStringList entries;
80 for (const auto &icon : icons) {
81 const int n = icon.lastIndexOf(c: QLatin1Char('/'));
82 QString name;
83 if (n == -1) {
84 name = icon;
85 } else {
86 name = icon.mid(position: n + 1);
87 }
88 name = removeIconExtension(name);
89 if (!entries.contains(str: name)) {
90 entries += name;
91 result += icon;
92 }
93 }
94 return result;
95}
96
97} // namespace
98
99/*
100 * Function to convert an uint32_t to AARRGGBB hex values.
101 *
102 * W A R N I N G !
103 * This function is for internal use!
104 */
105KICONTHEMES_EXPORT void uintToHex(uint32_t colorData, QChar *buffer)
106{
107 static const char hexLookup[] = "0123456789abcdef";
108 buffer += 7;
109 uchar *colorFields = reinterpret_cast<uchar *>(&colorData);
110
111 for (int i = 0; i < 4; i++) {
112 *buffer-- = hexLookup[*colorFields & 0xf];
113 *buffer-- = hexLookup[*colorFields >> 4];
114 colorFields++;
115 }
116}
117
118static QString paletteId(const KIconColors &colors)
119{
120 // 8 per color. We want 3 colors thus 8*4=32.
121 QString buffer(32, Qt::Uninitialized);
122
123 uintToHex(colorData: colors.text().rgba(), buffer: buffer.data());
124 uintToHex(colorData: colors.highlight().rgba(), buffer: buffer.data() + 8);
125 uintToHex(colorData: colors.highlightedText().rgba(), buffer: buffer.data() + 16);
126 uintToHex(colorData: colors.background().rgba(), buffer: buffer.data() + 24);
127
128 return buffer;
129}
130
131/* KIconThemeNode: A node in the icon theme dependency tree. */
132
133class KIconThemeNode
134{
135public:
136 KIconThemeNode(KIconTheme *_theme);
137 ~KIconThemeNode();
138
139 KIconThemeNode(const KIconThemeNode &) = delete;
140 KIconThemeNode &operator=(const KIconThemeNode &) = delete;
141
142 [[nodiscard]] QStringList queryIcons() const;
143 void queryIcons(QStringList *lst, int size, KIconLoader::Context context) const;
144 void queryIconsByContext(QStringList *lst, int size, KIconLoader::Context context) const;
145 QString findIcon(const QString &name, int size, KIconLoader::MatchType match) const;
146
147 KIconTheme *theme;
148};
149
150KIconThemeNode::KIconThemeNode(KIconTheme *_theme)
151{
152 theme = _theme;
153}
154
155KIconThemeNode::~KIconThemeNode()
156{
157 delete theme;
158}
159
160QStringList KIconThemeNode::queryIcons() const
161{
162 return theme->queryIcons();
163}
164
165void KIconThemeNode::queryIcons(QStringList *result, int size, KIconLoader::Context context) const
166{
167 // add the icons of this theme to it
168 *result += theme->queryIcons(size, context);
169}
170
171void KIconThemeNode::queryIconsByContext(QStringList *result, int size, KIconLoader::Context context) const
172{
173 // add the icons of this theme to it
174 *result += theme->queryIconsByContext(size, context);
175}
176
177QString KIconThemeNode::findIcon(const QString &name, int size, KIconLoader::MatchType match) const
178{
179 return theme->iconPath(name, size, match);
180}
181
182extern KICONTHEMES_EXPORT int kiconloader_ms_between_checks;
183KICONTHEMES_EXPORT int kiconloader_ms_between_checks = 5000;
184
185class KIconLoaderGlobalData : public QObject
186{
187 Q_OBJECT
188
189public:
190 KIconLoaderGlobalData()
191 {
192#ifdef WITH_QTDBUS
193 if (QDBusConnection::sessionBus().interface()) {
194 QDBusConnection::sessionBus().connect(service: QString(),
195 QStringLiteral("/KIconLoader"),
196 QStringLiteral("org.kde.KIconLoader"),
197 QStringLiteral("iconChanged"),
198 receiver: this,
199 SIGNAL(iconChanged(int)));
200 }
201#endif
202 }
203
204 void emitChange(KIconLoader::Group group)
205 {
206#ifdef WITH_QTDBUS
207 if (QDBusConnection::sessionBus().interface()) {
208 QDBusMessage message =
209 QDBusMessage::createSignal(QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"));
210 message.setArguments(QList<QVariant>() << int(group));
211 QDBusConnection::sessionBus().send(message);
212 }
213#endif
214 }
215
216 QString genericIconFor(const QString &icon) const
217 {
218 if (!m_loaded) {
219 // load icons lazily as initializing the icons is very expensive
220 const_cast<KIconLoaderGlobalData *>(this)->loadGenericIcons();
221 }
222 return m_genericIcons.value(key: icon);
223 }
224
225Q_SIGNALS:
226 void iconChanged(int group);
227
228private:
229 void loadGenericIcons()
230 {
231 if (m_loaded) {
232 return;
233 }
234 m_loaded = true;
235 // use Qt to fill in the generic mime-type icon information
236 const auto allMimeTypes = QMimeDatabase().allMimeTypes();
237 for (const auto &mimeType : allMimeTypes) {
238 m_genericIcons.insert(key: mimeType.iconName(), value: mimeType.genericIconName());
239 }
240 }
241
242private:
243 QHash<QString, QString> m_genericIcons;
244 bool m_loaded = false;
245};
246
247Q_GLOBAL_STATIC(KIconLoaderGlobalData, s_globalData)
248
249KIconLoaderPrivate::KIconLoaderPrivate(const QString &_appname, const QStringList &extraSearchPaths, KIconLoader *qq)
250 : q(qq)
251 , m_appname(_appname)
252{
253 q->connect(sender: s_globalData, signal: &KIconLoaderGlobalData::iconChanged, context: q, slot: [this](int group) {
254 _k_refreshIcons(group);
255 });
256 init(appname: m_appname, extraSearchPaths);
257}
258
259KIconLoaderPrivate::~KIconLoaderPrivate()
260{
261 clear();
262}
263
264KIconLoaderPrivate *KIconLoaderPrivate::get(KIconLoader *loader)
265{
266 return loader->d.get();
267}
268
269void KIconLoaderPrivate::clear()
270{
271 /* antlarr: There's no need to delete d->mpThemeRoot as it's already
272 deleted when the elements of d->links are deleted */
273 qDeleteAll(c: links);
274 mpGroups.clear();
275 mPixmapCache.clear();
276 m_appname.clear();
277 searchPaths.clear();
278 links.clear();
279 mIconThemeInited = false;
280 mThemesInTree.clear();
281}
282
283#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
284void KIconLoaderPrivate::drawOverlays(const KIconLoader *iconLoader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays)
285{
286 if (overlays.isEmpty()) {
287 return;
288 }
289
290 const int width = pix.size().width();
291 const int height = pix.size().height();
292 const int iconSize = qMin(a: width, b: height);
293 int overlaySize;
294
295 if (iconSize < 32) {
296 overlaySize = 8;
297 } else if (iconSize <= 48) {
298 overlaySize = 16;
299 } else if (iconSize <= 96) {
300 overlaySize = 22;
301 } else if (iconSize < 256) {
302 overlaySize = 32;
303 } else {
304 overlaySize = 64;
305 }
306
307 QPainter painter(&pix);
308
309 int count = 0;
310 for (const QString &overlay : overlays) {
311 // Ensure empty strings fill up a emblem spot
312 // Needed when you have several emblems to ensure they're always painted
313 // at the same place, even if one is not here
314 if (overlay.isEmpty()) {
315 ++count;
316 continue;
317 }
318
319 // TODO: should we pass in the kstate? it results in a slower
320 // path, and perhaps emblems should remain in the default state
321 // anyways?
322 QPixmap pixmap = iconLoader->loadIcon(name: overlay, group, size: overlaySize, state, overlays: QStringList(), path_store: nullptr, canReturnNull: true);
323
324 if (pixmap.isNull()) {
325 continue;
326 }
327
328 // match the emblem's devicePixelRatio to the original pixmap's
329 pixmap.setDevicePixelRatio(pix.devicePixelRatio());
330 const int margin = pixmap.devicePixelRatio() * 0.05 * iconSize;
331
332 QPoint startPoint;
333 switch (count) {
334 case 0:
335 // bottom right corner
336 startPoint = QPoint(width - overlaySize - margin, height - overlaySize - margin);
337 break;
338 case 1:
339 // bottom left corner
340 startPoint = QPoint(margin, height - overlaySize - margin);
341 break;
342 case 2:
343 // top left corner
344 startPoint = QPoint(margin, margin);
345 break;
346 case 3:
347 // top right corner
348 startPoint = QPoint(width - overlaySize - margin, margin);
349 break;
350 }
351
352 startPoint /= pix.devicePixelRatio();
353
354 painter.drawPixmap(p: startPoint, pm: pixmap);
355
356 ++count;
357 if (count > 3) {
358 break;
359 }
360 }
361}
362#endif
363
364void KIconLoaderPrivate::_k_refreshIcons(int group)
365{
366 KSharedConfig::Ptr sharedConfig = KSharedConfig::openConfig();
367 sharedConfig->reparseConfiguration();
368 const QString newThemeName = sharedConfig->group(group: "Icons").readEntry(key: "Theme", QStringLiteral("breeze"));
369 if (!newThemeName.isEmpty()) {
370 // NOTE Do NOT use QIcon::setThemeName here it makes Qt not use icon engine of the platform theme
371 // anymore (KIconEngine on Plasma, which breaks recoloring) and overwrites a user set themeName
372 // TODO KF6 this should be done in the Plasma QPT
373 QIconLoader::instance()->updateSystemTheme();
374 }
375
376 q->newIconLoader();
377 mIconAvailability.clear();
378 Q_EMIT q->iconChanged(group);
379}
380
381bool KIconLoaderPrivate::shouldCheckForUnknownIcons()
382{
383 if (mLastUnknownIconCheck.isValid() && mLastUnknownIconCheck.elapsed() < kiconloader_ms_between_checks) {
384 return false;
385 }
386 mLastUnknownIconCheck.start();
387 return true;
388}
389
390KIconLoader::KIconLoader(const QString &appname, const QStringList &extraSearchPaths, QObject *parent)
391 : QObject(parent)
392 , d(new KIconLoaderPrivate(appname, extraSearchPaths, this))
393{
394 setObjectName(appname);
395}
396
397void KIconLoader::reconfigure(const QString &_appname, const QStringList &extraSearchPaths)
398{
399 d->clear();
400 d->init(_appname, extraSearchPaths);
401}
402
403void KIconLoaderPrivate::init(const QString &_appname, const QStringList &extraSearchPaths)
404{
405 extraDesktopIconsLoaded = false;
406 mIconThemeInited = false;
407 mpThemeRoot = nullptr;
408
409 searchPaths = extraSearchPaths;
410
411 m_appname = !_appname.isEmpty() ? _appname : QCoreApplication::applicationName();
412
413 // Cost here is number of pixels
414 mPixmapCache.setMaxCost(10 * 1024 * 1024);
415
416 // These have to match the order in kiconloader.h
417 static const char *const groups[] = {"Desktop", "Toolbar", "MainToolbar", "Small", "Panel", "Dialog", nullptr};
418
419 // load default sizes
420 initIconThemes();
421 KIconTheme *defaultSizesTheme = links.empty() ? nullptr : links.first()->theme;
422 mpGroups.resize(new_size: int(KIconLoader::LastGroup));
423 for (KIconLoader::Group i = KIconLoader::FirstGroup; i < KIconLoader::LastGroup; ++i) {
424 if (groups[i] == nullptr) {
425 break;
426 }
427
428 if (defaultSizesTheme) {
429 mpGroups[i].size = defaultSizesTheme->defaultSize(group: i);
430 }
431 }
432}
433
434void KIconLoaderPrivate::initIconThemes()
435{
436 if (mIconThemeInited) {
437 return;
438 }
439 // qCDebug(KICONTHEMES);
440 mIconThemeInited = true;
441
442 // Add the default theme and its base themes to the theme tree
443 KIconTheme *def = new KIconTheme(KIconTheme::current(), m_appname);
444 if (!def->isValid()) {
445 delete def;
446 // warn, as this is actually a small penalty hit
447 qCDebug(KICONTHEMES) << "Couldn't find current icon theme, falling back to default.";
448 def = new KIconTheme(KIconTheme::defaultThemeName(), m_appname);
449 if (!def->isValid()) {
450 qCDebug(KICONTHEMES) << "Standard icon theme" << KIconTheme::defaultThemeName() << "not found!";
451 delete def;
452 return;
453 }
454 }
455 mpThemeRoot = new KIconThemeNode(def);
456 mThemesInTree.append(t: def->internalName());
457 links.append(t: mpThemeRoot);
458 addBaseThemes(node: mpThemeRoot, appname: m_appname);
459
460 // Insert application specific themes at the top.
461 searchPaths.append(t: m_appname + QStringLiteral("/pics"));
462
463 // Add legacy icon dirs.
464 searchPaths.append(QStringLiteral("icons")); // was xdgdata-icon in KStandardDirs
465 // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
466 searchPaths.append(QStringLiteral("pixmaps")); // was xdgdata-pixmaps in KStandardDirs
467}
468
469KIconLoader::~KIconLoader() = default;
470
471QStringList KIconLoader::searchPaths() const
472{
473 return d->searchPaths;
474}
475
476void KIconLoader::addAppDir(const QString &appname, const QString &themeBaseDir)
477{
478 d->searchPaths.append(t: appname + QStringLiteral("/pics"));
479 d->addAppThemes(appname, themeBaseDir);
480}
481
482void KIconLoaderPrivate::addAppThemes(const QString &appname, const QString &themeBaseDir)
483{
484 KIconTheme *def = new KIconTheme(QStringLiteral("hicolor"), appname, themeBaseDir);
485 if (!def->isValid()) {
486 delete def;
487 def = new KIconTheme(KIconTheme::defaultThemeName(), appname, themeBaseDir);
488 }
489 KIconThemeNode *node = new KIconThemeNode(def);
490 bool addedToLinks = false;
491
492 if (!mThemesInTree.contains(str: appname)) {
493 mThemesInTree.append(t: appname);
494 links.append(t: node);
495 addedToLinks = true;
496 }
497 addBaseThemes(node, appname);
498
499 if (!addedToLinks) {
500 // Nodes in links are being deleted later - this one needs manual care.
501 delete node;
502 }
503}
504
505void KIconLoaderPrivate::addBaseThemes(KIconThemeNode *node, const QString &appname)
506{
507 // Quote from the icon theme specification:
508 // The lookup is done first in the current theme, and then recursively
509 // in each of the current theme's parents, and finally in the
510 // default theme called "hicolor" (implementations may add more
511 // default themes before "hicolor", but "hicolor" must be last).
512 //
513 // So we first make sure that all inherited themes are added, then we
514 // add the KDE default theme as fallback for all icons that might not be
515 // present in an inherited theme, and hicolor goes last.
516
517 addInheritedThemes(node, appname);
518 addThemeByName(themename: QIcon::fallbackThemeName(), appname);
519 addThemeByName(QStringLiteral("hicolor"), appname);
520}
521
522void KIconLoaderPrivate::addInheritedThemes(KIconThemeNode *node, const QString &appname)
523{
524 const QStringList inheritedThemes = node->theme->inherits();
525
526 for (const auto &inheritedTheme : inheritedThemes) {
527 if (inheritedTheme == QLatin1String("hicolor")) {
528 // The icon theme spec says that "hicolor" must be the very last
529 // of all inherited themes, so don't add it here but at the very end
530 // of addBaseThemes().
531 continue;
532 }
533 addThemeByName(themename: inheritedTheme, appname);
534 }
535}
536
537void KIconLoaderPrivate::addThemeByName(const QString &themename, const QString &appname)
538{
539 if (mThemesInTree.contains(t: themename + appname)) {
540 return;
541 }
542 KIconTheme *theme = new KIconTheme(themename, appname);
543 if (!theme->isValid()) {
544 delete theme;
545 return;
546 }
547 KIconThemeNode *n = new KIconThemeNode(theme);
548 mThemesInTree.append(t: themename + appname);
549 links.append(t: n);
550 addInheritedThemes(node: n, appname);
551}
552
553void KIconLoaderPrivate::addExtraDesktopThemes()
554{
555 if (extraDesktopIconsLoaded) {
556 return;
557 }
558
559 QStringList list;
560 const QStringList icnlibs = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("icons"), options: QStandardPaths::LocateDirectory);
561 for (const auto &iconDir : icnlibs) {
562 QDir dir(iconDir);
563 if (!dir.exists()) {
564 continue;
565 }
566 const auto defaultEntries = dir.entryInfoList(nameFilters: QStringList(QStringLiteral("default.*")), filters: QDir::Dirs);
567 for (const auto &defaultEntry : defaultEntries) {
568 if (!QFileInfo::exists(file: defaultEntry.filePath() + QLatin1String("/index.desktop")) //
569 && !QFileInfo::exists(file: defaultEntry.filePath() + QLatin1String("/index.theme"))) {
570 continue;
571 }
572 if (defaultEntry.isSymbolicLink()) {
573 const QString themeName = QDir(defaultEntry.symLinkTarget()).dirName();
574 if (!list.contains(str: themeName)) {
575 list.append(t: themeName);
576 }
577 }
578 }
579 }
580
581 for (const auto &theme : list) {
582 // Don't add the KDE defaults once more, we have them anyways.
583 if (theme == QLatin1String("default.kde") || theme == QLatin1String("default.kde4")) {
584 continue;
585 }
586 addThemeByName(themename: theme, appname: QLatin1String(""));
587 }
588
589 extraDesktopIconsLoaded = true;
590}
591
592void KIconLoader::drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state) const
593{
594 d->drawOverlays(iconLoader: this, group, state, pix&: pixmap, overlays);
595}
596
597void KIconLoaderPrivate::normalizeIconMetadata(KIconLoader::Group &group, QSize &size, int &state) const
598{
599 if ((state < 0) || (state >= KIconLoader::LastState)) {
600 qCWarning(KICONTHEMES) << "Invalid icon state:" << state << ", should be one of KIconLoader::States";
601 state = KIconLoader::DefaultState;
602 }
603
604 if (size.width() < 0 || size.height() < 0) {
605 size = {};
606 }
607
608 // For "User" icons, bail early since the size should be based on the size on disk,
609 // which we've already checked.
610 if (group == KIconLoader::User) {
611 return;
612 }
613
614 if ((group < -1) || (group >= KIconLoader::LastGroup)) {
615 qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
616 group = KIconLoader::Desktop;
617 }
618
619 // If size == 0, use default size for the specified group.
620 if (size.isNull()) {
621 if (group < 0) {
622 qWarning() << "Neither size nor group specified!";
623 group = KIconLoader::Desktop;
624 }
625 size = QSize(mpGroups[group].size, mpGroups[group].size);
626 }
627}
628
629QString KIconLoaderPrivate::makeCacheKey(const QString &name,
630 KIconLoader::Group group,
631 const QStringList &overlays,
632 const QSize &size,
633 qreal scale,
634 int state,
635 const KIconColors &colors) const
636{
637 // The KSharedDataCache is shared so add some namespacing. The following code
638 // uses QStringBuilder (new in Qt 4.6)
639
640 QString effectKey = QStringLiteral("noeffect");
641
642 if ((group == KIconLoader::Desktop || group == KIconLoader::Panel) && state == KIconLoader::ActiveState) {
643 effectKey = QStringLiteral("active");
644 } else if (state == KIconLoader::DisabledState && group >= 0 && group < KIconLoader::LastGroup) {
645 effectKey = QStringLiteral("disabled");
646 }
647
648 /* clang-format off */
649 return (group == KIconLoader::User ? QLatin1String("$kicou_") : QLatin1String("$kico_"))
650 % name
651 % QLatin1Char('_')
652 % (size.width() == size.height() ? QString::number(size.height()) : QString::number(size.height()) % QLatin1Char('x') % QString::number(size.width()))
653 % QLatin1Char('@')
654 % QString::number(scale, format: 'f', precision: 1)
655 % QLatin1Char('_')
656 % overlays.join(sep: QLatin1Char('_'))
657 % effectKey
658 % QLatin1Char('_')
659 % paletteId(colors)
660 % (q->theme() && q->theme()->followsColorScheme() && state == KIconLoader::SelectedState ? QStringLiteral("_selected") : QString());
661 /* clang-format on */
662}
663
664QByteArray KIconLoaderPrivate::processSvg(const QString &path, KIconLoader::States state, const KIconColors &colors) const
665{
666 std::unique_ptr<QIODevice> device;
667
668 if (path.endsWith(s: QLatin1String("svgz"))) {
669 device.reset(p: new KCompressionDevice(path, KCompressionDevice::GZip));
670 } else {
671 device.reset(p: new QFile(path));
672 }
673
674 if (!device->open(mode: QIODevice::ReadOnly)) {
675 return QByteArray();
676 }
677
678 const QString styleSheet = colors.stylesheet(state);
679 QByteArray processedContents;
680 QXmlStreamReader reader(device.get());
681
682 QBuffer buffer(&processedContents);
683 buffer.open(openMode: QIODevice::WriteOnly);
684 QXmlStreamWriter writer(&buffer);
685 bool foundStyleSheet = false;
686 while (!reader.atEnd()) {
687 reader.readNext();
688 if (!foundStyleSheet //
689 && reader.tokenType() == QXmlStreamReader::StartElement //
690 && reader.qualifiedName() == QLatin1String("style") //
691 && reader.attributes().value(qualifiedName: QLatin1String("id")) == QLatin1String("current-color-scheme")) {
692 writer.writeStartElement(QStringLiteral("style"));
693 writer.writeAttributes(attributes: reader.attributes());
694 writer.writeCharacters(text: styleSheet);
695 writer.writeEndElement();
696 while (reader.tokenType() != QXmlStreamReader::EndElement) {
697 reader.readNext();
698 }
699 foundStyleSheet = true;
700 } else if (reader.tokenType() != QXmlStreamReader::Invalid) {
701 writer.writeCurrentToken(reader);
702 }
703 }
704 buffer.close();
705
706 return processedContents;
707}
708
709QImage KIconLoaderPrivate::createIconImage(const QString &path, const QSize &size, qreal scale, KIconLoader::States state, const KIconColors &colors)
710{
711 // TODO: metadata in the theme to make it do this only if explicitly supported?
712 QImageReader reader;
713 QBuffer buffer;
714
715 if (q->theme() && q->theme()->followsColorScheme() && (path.endsWith(s: QLatin1String("svg")) || path.endsWith(s: QLatin1String("svgz")))) {
716 buffer.setData(processSvg(path, state, colors));
717 reader.setDevice(&buffer);
718 reader.setFormat("svg");
719 } else {
720 reader.setFileName(path);
721 }
722
723 if (!reader.canRead()) {
724 return QImage();
725 }
726
727 if (!size.isNull()) {
728 // ensure we keep aspect ratio
729 const QSize wantedSize = size * scale;
730 QSize finalSize(reader.size());
731 if (finalSize.isNull()) {
732 // nothing to scale
733 finalSize = wantedSize;
734 } else {
735 // like QSvgIconEngine::pixmap try to keep aspect ratio
736 finalSize.scale(s: wantedSize, mode: Qt::KeepAspectRatio);
737 }
738 reader.setScaledSize(finalSize);
739 }
740
741 return reader.read();
742}
743
744void KIconLoaderPrivate::insertCachedPixmapWithPath(const QString &key, const QPixmap &data, const QString &path = QString())
745{
746 // Even if the pixmap is null, we add it to the caches so that we record
747 // the fact that whatever icon led to us getting a null pixmap doesn't
748 // exist.
749
750 PixmapWithPath *pixmapPath = new PixmapWithPath;
751 pixmapPath->pixmap = data;
752 pixmapPath->path = path;
753
754 mPixmapCache.insert(key, object: pixmapPath, cost: data.width() * data.height() + 1);
755}
756
757bool KIconLoaderPrivate::findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path)
758{
759 // If the pixmap is present in our local process cache, use that since we
760 // don't need to decompress and upload it to the X server/graphics card.
761 const PixmapWithPath *pixmapPath = mPixmapCache.object(key);
762 if (pixmapPath) {
763 path = pixmapPath->path;
764 data = pixmapPath->pixmap;
765 return true;
766 }
767
768 return false;
769}
770
771QString KIconLoaderPrivate::findMatchingIconWithGenericFallbacks(const QString &name, int size, qreal scale) const
772{
773 QString path = findMatchingIcon(name, size, scale);
774 if (!path.isEmpty()) {
775 return path;
776 }
777
778 const QString genericIcon = s_globalData()->genericIconFor(icon: name);
779 if (!genericIcon.isEmpty()) {
780 path = findMatchingIcon(name: genericIcon, size, scale);
781 }
782 return path;
783}
784
785QString KIconLoaderPrivate::findMatchingIcon(const QString &name, int size, qreal scale) const
786{
787 // This looks for the exact match and its
788 // generic fallbacks in each themeNode one after the other.
789
790 // In theory we should only do this for mimetype icons, not for app icons,
791 // but that would require different APIs. The long term solution is under
792 // development for Qt >= 5.8, QFileIconProvider calling QPlatformTheme::fileIcon,
793 // using QMimeType::genericIconName() to get the proper -x-generic fallback.
794 // Once everyone uses that to look up mimetype icons, we can kill the fallback code
795 // from this method.
796
797 bool genericFallback = name.endsWith(s: QLatin1String("-x-generic"));
798 bool isSymbolic = name.endsWith(s: QLatin1String("-symbolic"));
799 QString path;
800 for (KIconThemeNode *themeNode : std::as_const(t: links)) {
801 QString currentName = name;
802
803 while (!currentName.isEmpty()) {
804 path = themeNode->theme->iconPathByName(name: currentName, size, match: KIconLoader::MatchBest, scale);
805 if (!path.isEmpty()) {
806 return path;
807 }
808
809 if (genericFallback) {
810 // we already tested the base name
811 break;
812 }
813
814 // If the icon was originally symbolic, we want to keep that suffix at the end.
815 // The next block removes the last word including the -, "a-b-symbolic" will become "a-b"
816 // We remove it beforehand, "a-b-symbolic" now is "a-symbolic" and we'll add it back later.
817 if (isSymbolic) {
818 currentName.chop(n: strlen(s: "-symbolic"));
819
820 // Handle cases where the icon lacks a symbolic version.
821 // For example, "knotes-symbolic" doesn't exist and has no fallback or generic version.
822 // "knotes" does exist, so let's check if a non-symbolic icon works before continuing.
823 path = themeNode->theme->iconPathByName(name: currentName, size, match: KIconLoader::MatchBest, scale);
824 if (!path.isEmpty()) {
825 return path;
826 }
827 }
828
829 int rindex = currentName.lastIndexOf(c: QLatin1Char('-'));
830 if (rindex > 1) { // > 1 so that we don't split x-content or x-epoc
831 currentName.truncate(pos: rindex);
832
833 if (currentName.endsWith(s: QLatin1String("-x"))) {
834 currentName.chop(n: 2);
835 }
836
837 // Add back the -symbolic if requested
838 if (isSymbolic) {
839 currentName += QLatin1String("-symbolic");
840 }
841 } else {
842 // From update-mime-database.c
843 static const QSet<QString> mediaTypes = QSet<QString>{QStringLiteral("text"),
844 QStringLiteral("application"),
845 QStringLiteral("image"),
846 QStringLiteral("audio"),
847 QStringLiteral("inode"),
848 QStringLiteral("video"),
849 QStringLiteral("message"),
850 QStringLiteral("model"),
851 QStringLiteral("multipart"),
852 QStringLiteral("x-content"),
853 QStringLiteral("x-epoc")};
854 // Shared-mime-info spec says:
855 // "If [generic-icon] is not specified then the mimetype is used to generate the
856 // generic icon by using the top-level media type (e.g. "video" in "video/ogg")
857 // and appending "-x-generic" (i.e. "video-x-generic" in the previous example)."
858 if (mediaTypes.contains(value: currentName)) {
859 currentName += QLatin1String("-x-generic");
860 genericFallback = true;
861 } else {
862 break;
863 }
864 }
865 }
866 }
867
868 if (path.isEmpty()) {
869 const QStringList fallbackPaths = QIcon::fallbackSearchPaths();
870
871 for (const QString &path : fallbackPaths) {
872 const QString extensions[] = {QStringLiteral(".png"), QStringLiteral(".svg"), QStringLiteral(".svgz"), QStringLiteral(".xpm")};
873
874 for (const QString &ext : extensions) {
875 const QString file = path + '/' + name + ext;
876
877 if (QFileInfo::exists(file)) {
878 return file;
879 }
880 }
881 }
882 }
883
884 return path;
885}
886
887QString KIconLoaderPrivate::preferredIconPath(const QString &name)
888{
889 QString path;
890
891 auto it = mIconAvailability.constFind(key: name);
892 const auto end = mIconAvailability.constEnd();
893
894 if (it != end && it.value().isEmpty() && !shouldCheckForUnknownIcons()) {
895 return path; // known to be unavailable
896 }
897
898 if (it != end) {
899 path = it.value();
900 }
901
902 if (path.isEmpty()) {
903 path = q->iconPath(name, group_or_size: KIconLoader::Desktop, canReturnNull: KIconLoader::MatchBest);
904 mIconAvailability.insert(key: name, value: path);
905 }
906
907 return path;
908}
909
910inline QString KIconLoaderPrivate::unknownIconPath(int size, qreal scale) const
911{
912 QString path = findMatchingIcon(QStringLiteral("unknown"), size, scale);
913 if (path.isEmpty()) {
914 qCDebug(KICONTHEMES) << "Warning: could not find \"unknown\" icon for size" << size << "at scale" << scale;
915 return QString();
916 }
917 return path;
918}
919
920QString KIconLoaderPrivate::locate(const QString &fileName)
921{
922 for (const QString &dir : std::as_const(t&: searchPaths)) {
923 const QString path = dir + QLatin1Char('/') + fileName;
924 if (QDir(dir).isAbsolute()) {
925 if (QFileInfo::exists(file: path)) {
926 return path;
927 }
928 } else {
929 const QString fullPath = QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, fileName: path);
930 if (!fullPath.isEmpty()) {
931 return fullPath;
932 }
933 }
934 }
935 return QString();
936}
937
938// Finds the absolute path to an icon.
939
940QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull) const
941{
942 return iconPath(name: _name, group_or_size, canReturnNull, scale: 1 /*scale*/);
943}
944
945QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull, qreal scale) const
946{
947 // we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
948 if (_name.isEmpty() || QDir::isAbsolutePath(path: _name)) {
949 // we have either an absolute path or nothing to work with
950 return _name;
951 }
952
953 QString name = removeIconExtension(name: _name);
954
955 QString path;
956 if (group_or_size == KIconLoader::User) {
957 path = d->locate(fileName: name + QLatin1String(".png"));
958 if (path.isEmpty()) {
959 path = d->locate(fileName: name + QLatin1String(".svgz"));
960 }
961 if (path.isEmpty()) {
962 path = d->locate(fileName: name + QLatin1String(".svg"));
963 }
964 if (path.isEmpty()) {
965 path = d->locate(fileName: name + QLatin1String(".xpm"));
966 }
967 return path;
968 }
969
970 if (group_or_size >= KIconLoader::LastGroup) {
971 qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
972 return path;
973 }
974
975 int size;
976 if (group_or_size >= 0) {
977 size = d->mpGroups[group_or_size].size;
978 } else {
979 size = -group_or_size;
980 }
981
982 if (_name.isEmpty()) {
983 if (canReturnNull) {
984 return QString();
985 } else {
986 return d->unknownIconPath(size, scale);
987 }
988 }
989
990 path = d->findMatchingIconWithGenericFallbacks(name, size, scale);
991
992 if (path.isEmpty()) {
993 // Try "User" group too.
994 path = iconPath(name: name, group_or_size: KIconLoader::User, canReturnNull: true);
995 if (!path.isEmpty() || canReturnNull) {
996 return path;
997 }
998
999 return d->unknownIconPath(size, scale);
1000 }
1001 return path;
1002}
1003
1004QPixmap
1005KIconLoader::loadMimeTypeIcon(const QString &_iconName, KIconLoader::Group group, int size, int state, const QStringList &overlays, QString *path_store) const
1006{
1007 QString iconName = _iconName;
1008 const int slashindex = iconName.indexOf(ch: QLatin1Char('/'));
1009 if (slashindex != -1) {
1010 iconName[slashindex] = QLatin1Char('-');
1011 }
1012
1013 if (!d->extraDesktopIconsLoaded) {
1014 const QPixmap pixmap = loadIcon(name: iconName, group, size, state, overlays, path_store, canReturnNull: true);
1015 if (!pixmap.isNull()) {
1016 return pixmap;
1017 }
1018 d->addExtraDesktopThemes();
1019 }
1020 const QPixmap pixmap = loadIcon(name: iconName, group, size, state, overlays, path_store, canReturnNull: true);
1021 if (pixmap.isNull()) {
1022 // Icon not found, fallback to application/octet-stream
1023 return loadIcon(QStringLiteral("application-octet-stream"), group, size, state, overlays, path_store, canReturnNull: false);
1024 }
1025 return pixmap;
1026}
1027
1028QPixmap KIconLoader::loadIcon(const QString &_name,
1029 KIconLoader::Group group,
1030 int size,
1031 int state,
1032 const QStringList &overlays,
1033 QString *path_store,
1034 bool canReturnNull) const
1035{
1036 return loadScaledIcon(name: _name, group, scale: 1.0 /*scale*/, size, state, overlays, path_store, canReturnNull);
1037}
1038
1039QPixmap KIconLoader::loadScaledIcon(const QString &_name,
1040 KIconLoader::Group group,
1041 qreal scale,
1042 int size,
1043 int state,
1044 const QStringList &overlays,
1045 QString *path_store,
1046 bool canReturnNull) const
1047{
1048 return loadScaledIcon(name: _name, group, scale, size: QSize(size, size), state, overlays, path_store, canReturnNull);
1049}
1050
1051QPixmap KIconLoader::loadScaledIcon(const QString &_name,
1052 KIconLoader::Group group,
1053 qreal scale,
1054 const QSize &size,
1055 int state,
1056 const QStringList &overlays,
1057 QString *path_store,
1058 bool canReturnNull) const
1059{
1060 return loadScaledIcon(name: _name, group, scale, size, state, overlays, path_store, canReturnNull, colorScheme: {});
1061}
1062
1063QPixmap KIconLoader::loadScaledIcon(const QString &_name,
1064 KIconLoader::Group group,
1065 qreal scale,
1066 const QSize &_size,
1067 int state,
1068 const QStringList &overlays,
1069 QString *path_store,
1070 bool canReturnNull,
1071 const std::optional<KIconColors> &colors) const
1072
1073{
1074 QString name = _name;
1075 bool favIconOverlay = false;
1076
1077 if (_size.width() < 0 || _size.height() < 0 || _name.isEmpty()) {
1078 return QPixmap();
1079 }
1080
1081 QSize size = _size;
1082
1083 /*
1084 * This method works in a kind of pipeline, with the following steps:
1085 * 1. Sanity checks.
1086 * 2. Convert _name, group, size, etc. to a key name.
1087 * 3. Check if the key is already cached.
1088 * 4. If not, initialize the theme and find/load the icon.
1089 * 4a Apply overlays
1090 * 4b Re-add to cache.
1091 */
1092
1093 // Special case for absolute path icons.
1094 if (name.startsWith(s: QLatin1String("favicons/"))) {
1095 favIconOverlay = true;
1096 name = QStandardPaths::writableLocation(type: QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + name + QStringLiteral(".png");
1097 }
1098
1099 // we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
1100 const bool absolutePath = QDir::isAbsolutePath(path: name);
1101 if (!absolutePath) {
1102 name = removeIconExtension(name);
1103 }
1104
1105 // Don't bother looking for an icon with no name.
1106 if (name.isEmpty()) {
1107 return QPixmap();
1108 }
1109
1110 // May modify group, size, or state. This function puts them into sane
1111 // states.
1112 d->normalizeIconMetadata(group, size, state);
1113
1114 // See if the image is already cached.
1115 auto usedColors = colors ? *colors : d->mCustomColors ? d->mColors : KIconColors(qApp->palette());
1116 QString key = d->makeCacheKey(name, group, overlays, size, scale, state, colors: usedColors);
1117 QPixmap pix;
1118
1119 bool iconWasUnknown = false;
1120 QString path;
1121
1122 if (d->findCachedPixmapWithPath(key, data&: pix, path)) {
1123 if (path_store) {
1124 *path_store = path;
1125 }
1126
1127 if (!path.isEmpty()) {
1128 return pix;
1129 } else {
1130 // path is empty for "unknown" icons, which should be searched for
1131 // anew regularly
1132 if (!d->shouldCheckForUnknownIcons()) {
1133 return canReturnNull ? QPixmap() : pix;
1134 }
1135 }
1136 }
1137
1138 // Image is not cached... go find it and apply effects.
1139
1140 favIconOverlay = favIconOverlay && std::min(a: size.height(), b: size.width()) > 22;
1141
1142 // First we look for non-User icons. If we don't find one we'd search in
1143 // the User space anyways...
1144 if (group != KIconLoader::User) {
1145 if (absolutePath && !favIconOverlay) {
1146 path = name;
1147 } else {
1148 path = d->findMatchingIconWithGenericFallbacks(name: favIconOverlay ? QStringLiteral("text-html") : name, size: std::min(a: size.height(), b: size.width()), scale);
1149 }
1150 }
1151
1152 if (path.isEmpty()) {
1153 // We do have a "User" icon, or we couldn't find the non-User one.
1154 path = (absolutePath) ? name : iconPath(name: name, group_or_size: KIconLoader::User, canReturnNull);
1155 }
1156
1157 // Still can't find it? Use "unknown" if we can't return null.
1158 // We keep going in the function so we can ensure this result gets cached.
1159 if (path.isEmpty() && !canReturnNull) {
1160 path = d->unknownIconPath(size: std::min(a: size.height(), b: size.width()), scale);
1161 iconWasUnknown = true;
1162 }
1163
1164 QImage img;
1165 if (!path.isEmpty()) {
1166 img = d->createIconImage(path, size, scale, state: static_cast<KIconLoader::States>(state), colors: usedColors);
1167 }
1168
1169 // apply effects. When changing the logic here also adapt makeCacheKey
1170 if ((group == KIconLoader::Desktop || group == KIconLoader::Panel) && state == KIconLoader::ActiveState) {
1171 KIconEffect::toActive(image&: img);
1172 }
1173
1174 if (state == KIconLoader::DisabledState && group >= 0 && group < KIconLoader::LastGroup) {
1175 KIconEffect::toDisabled(image&: img);
1176 }
1177
1178 if (favIconOverlay) {
1179 QImage favIcon(name, "PNG");
1180 if (!favIcon.isNull()) { // if favIcon not there yet, don't try to blend it
1181 QPainter p(&img);
1182
1183 // Align the favicon overlay
1184 QRect r(favIcon.rect());
1185 r.moveBottomRight(p: img.rect().bottomRight());
1186 r.adjust(dx1: -1, dy1: -1, dx2: -1, dy2: -1); // Move off edge
1187
1188 // Blend favIcon over img.
1189 p.drawImage(r, image: favIcon);
1190 }
1191 }
1192
1193 pix = QPixmap::fromImage(image: std::move(img));
1194 pix.setDevicePixelRatio(scale);
1195
1196 // TODO: If we make a loadIcon that returns the image we can convert
1197 // drawOverlays to use the image instead of pixmaps as well so we don't
1198 // have to transfer so much to the graphics card.
1199 d->drawOverlays(iconLoader: this, group, state, pix, overlays);
1200
1201 // Don't add the path to our unknown icon to the cache, only cache the
1202 // actual image.
1203 if (iconWasUnknown) {
1204 path.clear();
1205 }
1206
1207 d->insertCachedPixmapWithPath(key, data: pix, path);
1208
1209 if (path_store) {
1210 *path_store = path;
1211 }
1212
1213 return pix;
1214}
1215
1216#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
1217QMovie *KIconLoader::loadMovie(const QString &name, KIconLoader::Group group, int size, QObject *parent) const
1218{
1219 QString file = moviePath(name, group, size);
1220 if (file.isEmpty()) {
1221 return nullptr;
1222 }
1223 int dirLen = file.lastIndexOf(c: QLatin1Char('/'));
1224 const QString icon = iconPath(name: name, group_or_size: size ? -size : group, canReturnNull: true);
1225 if (!icon.isEmpty() && file.left(n: dirLen) != icon.left(n: dirLen)) {
1226 return nullptr;
1227 }
1228 QMovie *movie = new QMovie(file, QByteArray(), parent);
1229 if (!movie->isValid()) {
1230 delete movie;
1231 return nullptr;
1232 }
1233 return movie;
1234}
1235#endif
1236
1237#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
1238QString KIconLoader::moviePath(const QString &name, KIconLoader::Group group, int size) const
1239{
1240 if (d->mpGroups.empty()) {
1241 return QString();
1242 }
1243
1244 if ((group < -1 || group >= KIconLoader::LastGroup) && group != KIconLoader::User) {
1245 qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1246 group = KIconLoader::Desktop;
1247 }
1248 if (size == 0 && group < 0) {
1249 qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1250 group = KIconLoader::Desktop;
1251 }
1252
1253 QString file = name + QStringLiteral(".mng");
1254 if (group == KIconLoader::User) {
1255 file = d->locate(fileName: file);
1256 } else {
1257 if (size == 0) {
1258 size = d->mpGroups[group].size;
1259 }
1260
1261 QString path;
1262
1263 for (KIconThemeNode *themeNode : std::as_const(t&: d->links)) {
1264 path = themeNode->theme->iconPath(name: file, size, match: KIconLoader::MatchExact);
1265 if (!path.isEmpty()) {
1266 break;
1267 }
1268 }
1269
1270 if (path.isEmpty()) {
1271 for (KIconThemeNode *themeNode : std::as_const(t&: d->links)) {
1272 path = themeNode->theme->iconPath(name: file, size, match: KIconLoader::MatchBest);
1273 if (!path.isEmpty()) {
1274 break;
1275 }
1276 }
1277 }
1278
1279 file = path;
1280 }
1281 return file;
1282}
1283#endif
1284
1285#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
1286QStringList KIconLoader::loadAnimated(const QString &name, KIconLoader::Group group, int size) const
1287{
1288 QStringList lst;
1289
1290 if (d->mpGroups.empty()) {
1291 return lst;
1292 }
1293
1294 d->initIconThemes();
1295
1296 if ((group < -1) || (group >= KIconLoader::LastGroup)) {
1297 qCDebug(KICONTHEMES) << "Invalid icon group: " << group << ", should be one of KIconLoader::Group";
1298 group = KIconLoader::Desktop;
1299 }
1300 if ((size == 0) && (group < 0)) {
1301 qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1302 group = KIconLoader::Desktop;
1303 }
1304
1305 QString file = name + QStringLiteral("/0001");
1306 if (group == KIconLoader::User) {
1307 file = d->locate(fileName: file + QStringLiteral(".png"));
1308 } else {
1309 if (size == 0) {
1310 size = d->mpGroups[group].size;
1311 }
1312 file = d->findMatchingIcon(name: file, size, scale: 1); // FIXME scale
1313 }
1314 if (file.isEmpty()) {
1315 return lst;
1316 }
1317
1318 QString path = file.left(n: file.length() - 8);
1319 QDir dir(QFile::encodeName(fileName: path));
1320 if (!dir.exists()) {
1321 return lst;
1322 }
1323
1324 const auto entryList = dir.entryList();
1325 for (const QString &entry : entryList) {
1326 const QStringView chunk = QStringView(entry).left(n: 4);
1327 if (!chunk.toUInt()) {
1328 continue;
1329 }
1330
1331 lst += path + entry;
1332 }
1333 lst.sort();
1334 return lst;
1335}
1336#endif
1337
1338KIconTheme *KIconLoader::theme() const
1339{
1340 if (d->mpThemeRoot) {
1341 return d->mpThemeRoot->theme;
1342 }
1343 return nullptr;
1344}
1345
1346int KIconLoader::currentSize(KIconLoader::Group group) const
1347{
1348 if (d->mpGroups.empty()) {
1349 return -1;
1350 }
1351
1352 if (group < 0 || group >= KIconLoader::LastGroup) {
1353 qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1354 return -1;
1355 }
1356 return d->mpGroups[group].size;
1357}
1358
1359QStringList KIconLoader::queryIconsByDir(const QString &iconsDir) const
1360{
1361 const QDir dir(iconsDir);
1362 const QStringList formats = QStringList() << QStringLiteral("*.png") << QStringLiteral("*.xpm") << QStringLiteral("*.svg") << QStringLiteral("*.svgz");
1363 const QStringList lst = dir.entryList(nameFilters: formats, filters: QDir::Files);
1364 QStringList result;
1365 for (const auto &file : lst) {
1366 result += iconsDir + QLatin1Char('/') + file;
1367 }
1368 return result;
1369}
1370
1371QStringList KIconLoader::queryIconsByContext(int group_or_size, KIconLoader::Context context) const
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(t&: d->links)) {
1386 themeNode->queryIconsByContext(result: &result, size, context);
1387 }
1388
1389 return deduplicateIconsByName(icons: result);
1390}
1391
1392QStringList KIconLoader::queryIcons() const
1393{
1394 d->initIconThemes();
1395
1396 QStringList result;
1397 for (const auto &themeNode : std::as_const(t&: d->links)) {
1398 result.append(other: themeNode->queryIcons());
1399 }
1400
1401 return deduplicateIconsByName(icons: result);
1402}
1403
1404QStringList KIconLoader::queryIcons(int group_or_size, KIconLoader::Context context) const
1405{
1406 d->initIconThemes();
1407
1408 QStringList result;
1409 if (group_or_size >= KIconLoader::LastGroup) {
1410 qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
1411 return result;
1412 }
1413 int size;
1414 if (group_or_size >= 0) {
1415 size = d->mpGroups[group_or_size].size;
1416 } else {
1417 size = -group_or_size;
1418 }
1419
1420 for (KIconThemeNode *themeNode : std::as_const(t&: d->links)) {
1421 themeNode->queryIcons(result: &result, size, context);
1422 }
1423
1424 return deduplicateIconsByName(icons: result);
1425}
1426
1427// used by KIconDialog to find out which contexts to offer in a combobox
1428bool KIconLoader::hasContext(KIconLoader::Context context) const
1429{
1430 for (KIconThemeNode *themeNode : std::as_const(t&: d->links)) {
1431 if (themeNode->theme->hasContext(context)) {
1432 return true;
1433 }
1434 }
1435 return false;
1436}
1437
1438#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
1439KIconEffect *KIconLoader::iconEffect() const
1440{
1441 return &d->mpEffect;
1442}
1443#endif
1444
1445QPixmap KIconLoader::unknown()
1446{
1447 QPixmap pix;
1448 if (QPixmapCache::find(QStringLiteral("unknown"), pixmap: &pix)) { // krazy:exclude=iconnames
1449 return pix;
1450 }
1451
1452 const QString path = global()->iconPath(QStringLiteral("unknown"), group_or_size: KIconLoader::Small, canReturnNull: true); // krazy:exclude=iconnames
1453 if (path.isEmpty()) {
1454 qCDebug(KICONTHEMES) << "Warning: Cannot find \"unknown\" icon.";
1455 pix = QPixmap(32, 32);
1456 } else {
1457 pix.load(fileName: path);
1458 QPixmapCache::insert(QStringLiteral("unknown"), pixmap: pix); // krazy:exclude=iconnames
1459 }
1460
1461 return pix;
1462}
1463
1464bool KIconLoader::hasIcon(const QString &name) const
1465{
1466 return !d->preferredIconPath(name).isEmpty();
1467}
1468
1469void KIconLoader::setCustomPalette(const QPalette &palette)
1470{
1471 d->mCustomColors = true;
1472 d->mColors = KIconColors(palette);
1473}
1474
1475QPalette KIconLoader::customPalette() const
1476{
1477 return d->mCustomColors ? d->mPalette : QPalette();
1478}
1479
1480void KIconLoader::resetPalette()
1481{
1482 d->mCustomColors = false;
1483}
1484
1485bool KIconLoader::hasCustomPalette() const
1486{
1487 return d->mCustomColors;
1488}
1489
1490KIconLoader *KIconLoader::global()
1491{
1492 thread_local KIconLoader loader;
1493 return &loader;
1494}
1495
1496void KIconLoader::newIconLoader()
1497{
1498 if (global() == this) {
1499 KIconTheme::reconfigure();
1500 }
1501
1502 reconfigure(appname: objectName());
1503 Q_EMIT iconLoaderSettingsChanged();
1504}
1505
1506void KIconLoader::emitChange(KIconLoader::Group g)
1507{
1508 s_globalData->emitChange(group: g);
1509}
1510
1511#include <kiconengine.h>
1512QIcon KDE::icon(const QString &iconName, KIconLoader *iconLoader)
1513{
1514 return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global()));
1515}
1516
1517QIcon KDE::icon(const QString &iconName, const QStringList &overlays, KIconLoader *iconLoader)
1518{
1519 return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global(), overlays));
1520}
1521
1522QIcon KDE::icon(const QString &iconName, const KIconColors &colors, KIconLoader *iconLoader)
1523{
1524 return QIcon(new KIconEngine(iconName, colors, iconLoader ? iconLoader : KIconLoader::global()));
1525}
1526
1527#include "kiconloader.moc"
1528#include "moc_kiconloader.cpp"
1529

source code of kiconthemes/src/kiconloader.cpp