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

source code of kiconthemes/src/kiconloader.cpp