1/*
2
3 kicontheme.cpp: Lowlevel icon theme handling.
4
5 This file is part of the KDE project, module kdecore.
6 SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
7 SPDX-FileCopyrightText: 2000 Antonio Larrosa <larrosa@kde.org>
8
9 SPDX-License-Identifier: LGPL-2.0-only
10*/
11
12#include "kicontheme.h"
13
14#include "debug.h"
15
16#include <KColorSchemeManager>
17#include <KConfigGroup>
18#include <KLocalizedString> // KLocalizedString::localizedFilePath. Need such functionality in, hmm, QLocale? QStandardPaths?
19#include <KSharedConfig>
20
21#include <QAction>
22#include <QCoreApplication>
23#include <QDebug>
24#include <QDir>
25#include <QFileInfo>
26#include <QMap>
27#include <QResource>
28#include <QSet>
29#include <QTimer>
30
31#include <private/qguiapplication_p.h>
32#include <qpa/qplatformtheme.h>
33
34#include <qplatformdefs.h>
35
36#include <array>
37#include <cmath>
38
39#include "config.h"
40
41Q_GLOBAL_STATIC(QString, _themeOverride)
42
43#if !USE_BreezeIcons
44
45// on Android icon theme loading works differently and is managed by code in Kirigami
46// so don't actually touch anything icon-related here
47static void initThemeHelper()
48{
49 // postpone until QGuiApplication applies initial palette
50 QTimer::singleShot(0, [] {
51 // follow the system color, construct the global manager for that
52 (void)KColorSchemeManager::instance();
53 });
54}
55
56void KIconTheme::initTheme()
57{
58}
59
60#else
61
62#include <BreezeIcons>
63
64// do init only once and avoid later helpers to mess with it again
65static bool initThemeUsed = false;
66
67// startup function to set theme once the app got constructed
68static void initThemeHelper()
69{
70 // make sure we add application install path to search path, for e.g. bundles on Windows
71 if (initThemeUsed) {
72 // do that similar to QCoreApplicationPrivate::appendApplicationPathToLibraryPaths() with minimal extra API use
73 QString path = QCoreApplication::applicationFilePath();
74 path.truncate(pos: path.lastIndexOf(c: QLatin1Char('/')));
75 if (const QString ourPath = path + QStringLiteral("/kiconthemes6"); QFile::exists(fileName: ourPath)) {
76 QCoreApplication::addLibraryPath(ourPath);
77 }
78 }
79
80 // Makes sure the icon theme fallback is set to breeze or one of its
81 // variants. Most of our apps use "lots" of icons that most of the times
82 // are only available with breeze, we still honour the user icon theme
83 // but if the icon is not found there, we go to breeze since it's almost
84 // sure it'll be there
85 BreezeIcons::initIcons();
86
87 // ensure lib call above did the job
88 Q_ASSERT(!QIcon::fallbackThemeName().isEmpty());
89
90 // only do further stuff if we requested it
91 if (!initThemeUsed) {
92 return;
93 }
94
95 // do nothing if we have the proper platform theme already
96 if (QGuiApplicationPrivate::platformTheme() && QGuiApplicationPrivate::platformTheme()->name() == QLatin1String("kde")) {
97 return;
98 }
99
100 // get config, with fallback to kdeglobals
101 const auto config = KSharedConfig::openConfig();
102
103 // enforce the theme configured by the user, with kdeglobals fallback
104 // if not set, use Breeze
105 const QString themeToUse = KConfigGroup(config, "Icons").readEntry(key: "Theme", QStringLiteral("breeze"));
106
107#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
108 // set our theme, Qt internally will still not fully use our engine and lookup
109 QIcon::setThemeName(themeToUse);
110#else
111 // use Qt API to really fully override the engine, if we set KIconEngine the Key in our plugin will
112 // enforce that our engine is used
113 // https://codereview.qt-project.org/c/qt/qtbase/+/563241
114 QIcon::setThemeName(QStringLiteral("KIconEngine"));
115#endif
116
117 // Tell KIconTheme about the theme, in case KIconLoader is used directly
118 *_themeOverride() = themeToUse;
119 qCDebug(KICONTHEMES) << "KIconTheme::initTheme() enforces the icon theme:" << themeToUse;
120
121 // postpone until QGuiApplication applies initial palette
122 QTimer::singleShot(interval: 0, slot: [] {
123 // follow the system color, construct the global manager for that
124 (void)KColorSchemeManager::instance();
125 });
126}
127
128void KIconTheme::initTheme()
129{
130 // inject paths only once
131 if (!initThemeUsed) {
132 // inject our icon engine in the search path
133 // it will be used as the first found engine for a suffix will be taken
134 // this must be done before the QCoreApplication is constructed
135 const auto paths = QCoreApplication::libraryPaths();
136 for (const auto &path : paths) {
137 if (const QString ourPath = path + QStringLiteral("/kiconthemes6"); QFile::exists(fileName: ourPath)) {
138 QCoreApplication::addLibraryPath(ourPath);
139 }
140 }
141 }
142
143 // initThemeHelper will do the remaining work via Q_COREAPP_STARTUP_FUNCTION(initThemeHelper) above
144 initThemeUsed = true;
145}
146
147#endif
148
149Q_COREAPP_STARTUP_FUNCTION(initThemeHelper)
150
151class KIconThemeDir;
152class KIconThemePrivate
153{
154public:
155 QString example, screenshot;
156 bool hidden;
157 KSharedConfig::Ptr sharedConfig;
158
159 struct GroupInfo {
160 KIconLoader::Group type;
161 const char *name;
162 int defaultSize;
163 QList<int> availableSizes{};
164 };
165 std::array<GroupInfo, KIconLoader::LastGroup> m_iconGroups = {._M_elems: {
166 {.type: KIconLoader::Desktop, .name: "Desktop", .defaultSize: 32},
167 {.type: KIconLoader::Toolbar, .name: "Toolbar", .defaultSize: 22},
168 {.type: KIconLoader::MainToolbar, .name: "MainToolbar", .defaultSize: 22},
169 {.type: KIconLoader::Small, .name: "Small", .defaultSize: 16},
170 {.type: KIconLoader::Panel, .name: "Panel", .defaultSize: 48},
171 {.type: KIconLoader::Dialog, .name: "Dialog", .defaultSize: 32},
172 }};
173
174 int mDepth;
175 QString mDir, mName, mInternalName, mDesc;
176 QStringList mInherits;
177 QStringList mExtensions;
178 QList<KIconThemeDir *> mDirs;
179 QList<KIconThemeDir *> mScaledDirs;
180 bool followsColorScheme : 1;
181
182 /// Searches the given dirs vector for a matching icon
183 QString iconPath(const QList<KIconThemeDir *> &dirs, const QString &name, int size, qreal scale, KIconLoader::MatchType match) const;
184};
185Q_GLOBAL_STATIC(QString, _theme)
186Q_GLOBAL_STATIC(QStringList, _theme_list)
187
188/*
189 * A subdirectory in an icon theme.
190 */
191class KIconThemeDir
192{
193public:
194 KIconThemeDir(const QString &basedir, const QString &themedir, const KConfigGroup &config);
195
196 bool isValid() const
197 {
198 return mbValid;
199 }
200 QString iconPath(const QString &name) const;
201 QStringList iconList() const;
202 QString constructFileName(const QString &file) const
203 {
204 return mBaseDir + mThemeDir + QLatin1Char('/') + file;
205 }
206
207 KIconLoader::Context context() const
208 {
209 return mContext;
210 }
211 KIconLoader::Type type() const
212 {
213 return mType;
214 }
215 int size() const
216 {
217 return mSize;
218 }
219 int scale() const
220 {
221 return mScale;
222 }
223 int minSize() const
224 {
225 return mMinSize;
226 }
227 int maxSize() const
228 {
229 return mMaxSize;
230 }
231 int threshold() const
232 {
233 return mThreshold;
234 }
235
236private:
237 bool mbValid = false;
238 KIconLoader::Type mType = KIconLoader::Fixed;
239 KIconLoader::Context mContext;
240 int mSize = 0;
241 int mScale = 1;
242 int mMinSize = 1;
243 int mMaxSize = 50;
244 int mThreshold = 2;
245
246 const QString mBaseDir;
247 const QString mThemeDir;
248};
249
250QString KIconThemePrivate::iconPath(const QList<KIconThemeDir *> &dirs, const QString &name, int size, qreal scale, KIconLoader::MatchType match) const
251{
252 QString path;
253 QString tempPath; // used to cache icon path if it exists
254
255 int delta = -INT_MAX; // current icon size delta of 'icon'
256 int dw = INT_MAX; // icon size delta of current directory
257
258 // Rather downsample than upsample
259 int integerScale = std::ceil(x: scale);
260
261 // Search the directory that contains the icon which matches best to the requested
262 // size. If there is no directory which matches exactly to the requested size, the
263 // following criteria get applied:
264 // - Take a directory having icons with a minimum difference to the requested size.
265 // - Prefer directories that allow a downscaling even if the difference to
266 // the requested size is bigger than a directory where an upscaling is required.
267 for (KIconThemeDir *dir : dirs) {
268 if (dir->scale() != integerScale) {
269 continue;
270 }
271
272 if (match == KIconLoader::MatchExact) {
273 if ((dir->type() == KIconLoader::Fixed) && (dir->size() != size)) {
274 continue;
275 }
276 if ((dir->type() == KIconLoader::Scalable) //
277 && ((size < dir->minSize()) || (size > dir->maxSize()))) {
278 continue;
279 }
280 if ((dir->type() == KIconLoader::Threshold) //
281 && (abs(x: dir->size() - size) > dir->threshold())) {
282 continue;
283 }
284 } else {
285 // dw < 0 means need to scale up to get an icon of the requested size.
286 // Upscaling should only be done if no larger icon is available.
287 if (dir->type() == KIconLoader::Fixed) {
288 dw = dir->size() - size;
289 } else if (dir->type() == KIconLoader::Scalable) {
290 if (size < dir->minSize()) {
291 dw = dir->minSize() - size;
292 } else if (size > dir->maxSize()) {
293 dw = dir->maxSize() - size;
294 } else {
295 dw = 0;
296 }
297 } else if (dir->type() == KIconLoader::Threshold) {
298 if (size < dir->size() - dir->threshold()) {
299 dw = dir->size() - dir->threshold() - size;
300 } else if (size > dir->size() + dir->threshold()) {
301 dw = dir->size() + dir->threshold() - size;
302 } else {
303 dw = 0;
304 }
305 }
306 // Usually if the delta (= 'dw') of the current directory is
307 // not smaller than the delta (= 'delta') of the currently best
308 // matching icon, this candidate can be skipped. But skipping
309 // the candidate may only be done, if this does not imply
310 // in an upscaling of the icon (it is OK to use a directory with
311 // smaller icons that what we've already found, however).
312 if ((abs(x: dw) >= abs(x: delta)) && ((dw < 0) || (delta > 0))) {
313 continue;
314 }
315
316 if (match == KIconLoader::MatchBestOrGreaterSize && dw < 0) {
317 continue;
318 }
319 }
320
321 // cache the result of iconPath() call which checks if file exists
322 tempPath = dir->iconPath(name);
323
324 if (tempPath.isEmpty()) {
325 continue;
326 }
327
328 path = tempPath;
329
330 // if we got in MatchExact that far, we find no better
331 if (match == KIconLoader::MatchExact) {
332 return path;
333 }
334 delta = dw;
335 if (delta == 0) {
336 return path; // We won't find a better match anyway
337 }
338 }
339 return path;
340}
341
342KIconTheme::KIconTheme(const QString &name, const QString &appName, const QString &basePathHint)
343 : d(new KIconThemePrivate)
344{
345 d->mInternalName = name;
346
347 QStringList themeDirs;
348
349 // Applications can have local additions to the global "locolor" and
350 // "hicolor" icon themes. For these, the _global_ theme description
351 // files are used..
352
353 /* clang-format off */
354 if (!appName.isEmpty()
355 && (name == defaultThemeName()
356 || name == QLatin1String("hicolor")
357 || name == QLatin1String("locolor"))) { /* clang-format on */
358 const QString suffix = QLatin1Char('/') + appName + QLatin1String("/icons/") + name + QLatin1Char('/');
359 QStringList dataDirs = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation);
360 for (auto &cDir : dataDirs) {
361 cDir += suffix;
362 if (QFileInfo::exists(file: cDir)) {
363 themeDirs += cDir;
364 }
365 }
366
367 if (!basePathHint.isEmpty()) {
368 // Checks for dir existing are done below
369 themeDirs += basePathHint + QLatin1Char('/') + name + QLatin1Char('/');
370 }
371 }
372
373 // Find the theme description file. These are either locally in the :/icons resource path or global.
374 QStringList icnlibs;
375
376#ifdef Q_OS_ANDROID
377 // Android icon theme installed by Kirigami
378 icnlibs << QStringLiteral("assets:/qml/org/kde/kirigami");
379#endif
380
381 // global icons
382 icnlibs += QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("icons"), options: QStandardPaths::LocateDirectory);
383
384 // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
385 icnlibs += QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("pixmaps"), options: QStandardPaths::LocateDirectory);
386
387 // local embedded icons
388 icnlibs << QStringLiteral(":/icons");
389
390 QString fileName;
391 QString mainSection;
392 const QString pathSuffix = QLatin1Char('/') + name + QLatin1Char('/');
393 const QLatin1String indexTheme("index.theme");
394 const QLatin1String indexDesktop("theme.desktop");
395 for (auto &iconDir : icnlibs) {
396 iconDir += pathSuffix;
397 const QFileInfo fi(iconDir);
398 if (!fi.exists() || !fi.isDir()) {
399 continue;
400 }
401 themeDirs.append(t: iconDir);
402
403 if (d->mDir.isEmpty()) {
404 QString possiblePath;
405 if (possiblePath = iconDir + indexTheme; QFileInfo::exists(file: possiblePath)) {
406 d->mDir = iconDir;
407 fileName = possiblePath;
408 mainSection = QStringLiteral("Icon Theme");
409 } else if (possiblePath = iconDir + indexDesktop; QFileInfo::exists(file: possiblePath)) {
410 d->mDir = iconDir;
411 fileName = possiblePath;
412 mainSection = QStringLiteral("KDE Icon Theme");
413 }
414 }
415 }
416
417 if (d->mDir.isEmpty()) {
418 qCDebug(KICONTHEMES) << "Icon theme" << name << "not found.";
419 return;
420 }
421
422 // Use KSharedConfig to avoid parsing the file many times, from each component.
423 // Need to keep a ref to it to make this useful
424 d->sharedConfig = KSharedConfig::openConfig(fileName, mode: KConfig::SimpleConfig);
425
426 KConfigGroup cfg(d->sharedConfig, mainSection);
427 d->mName = cfg.readEntry(key: "Name");
428 d->mDesc = cfg.readEntry(key: "Comment");
429 d->mDepth = cfg.readEntry(key: "DisplayDepth", defaultValue: 32);
430 d->mInherits = cfg.readEntry(key: "Inherits", aDefault: QStringList());
431 if (name != defaultThemeName()) {
432 for (auto &inheritedTheme : d->mInherits) {
433 if (inheritedTheme == QLatin1String("default")) {
434 inheritedTheme = defaultThemeName();
435 }
436 }
437 }
438
439 d->hidden = cfg.readEntry(key: "Hidden", defaultValue: false);
440 d->followsColorScheme = cfg.readEntry(key: "FollowsColorScheme", defaultValue: false);
441 d->example = cfg.readPathEntry(key: "Example", aDefault: QString());
442 d->screenshot = cfg.readPathEntry(key: "ScreenShot", aDefault: QString());
443 d->mExtensions =
444 cfg.readEntry(key: "KDE-Extensions", aDefault: QStringList{QStringLiteral(".png"), QStringLiteral(".svgz"), QStringLiteral(".svg"), QStringLiteral(".xpm")});
445
446 QSet<QString> addedDirs; // Used for avoiding duplicates.
447 const QStringList dirs = cfg.readPathEntry(key: "Directories", aDefault: QStringList()) + cfg.readPathEntry(key: "ScaledDirectories", aDefault: QStringList());
448 for (const auto &dirName : dirs) {
449 KConfigGroup cg(d->sharedConfig, dirName);
450 for (const auto &themeDir : std::as_const(t&: themeDirs)) {
451 const QString currentDir(themeDir + dirName + QLatin1Char('/'));
452 if (!addedDirs.contains(value: currentDir) && QFileInfo::exists(file: currentDir)) {
453 addedDirs.insert(value: currentDir);
454 KIconThemeDir *dir = new KIconThemeDir(themeDir, dirName, cg);
455 if (dir->isValid()) {
456 if (dir->scale() > 1) {
457 d->mScaledDirs.append(t: dir);
458 } else {
459 d->mDirs.append(t: dir);
460 }
461 } else {
462 delete dir;
463 }
464 }
465 }
466 }
467
468 KConfigGroup cg(d->sharedConfig, mainSection);
469 for (auto &iconGroup : d->m_iconGroups) {
470 iconGroup.defaultSize = cg.readEntry(key: iconGroup.name + QLatin1String("Default"), aDefault: iconGroup.defaultSize);
471 iconGroup.availableSizes = cg.readEntry(key: iconGroup.name + QLatin1String("Sizes"), aDefault: QList<int>());
472 }
473}
474
475KIconTheme::~KIconTheme()
476{
477 qDeleteAll(c: d->mDirs);
478 qDeleteAll(c: d->mScaledDirs);
479}
480
481QString KIconTheme::name() const
482{
483 return d->mName;
484}
485
486QString KIconTheme::internalName() const
487{
488 return d->mInternalName;
489}
490
491QString KIconTheme::description() const
492{
493 return d->mDesc;
494}
495
496QString KIconTheme::example() const
497{
498 return d->example;
499}
500
501QString KIconTheme::screenshot() const
502{
503 return d->screenshot;
504}
505
506QString KIconTheme::dir() const
507{
508 return d->mDir;
509}
510
511QStringList KIconTheme::inherits() const
512{
513 return d->mInherits;
514}
515
516bool KIconTheme::isValid() const
517{
518 return !d->mDirs.isEmpty() || !d->mScaledDirs.isEmpty();
519}
520
521bool KIconTheme::isHidden() const
522{
523 return d->hidden;
524}
525
526int KIconTheme::depth() const
527{
528 return d->mDepth;
529}
530
531int KIconTheme::defaultSize(KIconLoader::Group group) const
532{
533 if (group < 0 || group >= KIconLoader::LastGroup) {
534 qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
535 return -1;
536 }
537 return d->m_iconGroups[group].defaultSize;
538}
539
540QList<int> KIconTheme::querySizes(KIconLoader::Group group) const
541{
542 if (group < 0 || group >= KIconLoader::LastGroup) {
543 qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
544 return QList<int>();
545 }
546 return d->m_iconGroups[group].availableSizes;
547}
548
549static bool isAnyOrDirContext(const KIconThemeDir *dir, KIconLoader::Context context)
550{
551 return context == KIconLoader::Any || context == dir->context();
552}
553
554QStringList KIconTheme::queryIcons() const
555{
556 QStringList result;
557 const auto listDirs = d->mDirs + d->mScaledDirs;
558 for (const auto &dir : listDirs) {
559 result.append(other: dir->iconList());
560 }
561 return result;
562}
563
564QStringList KIconTheme::queryIcons(int size, KIconLoader::Context context) const
565{
566 // Try to find exact match
567 QStringList result;
568 const QList<KIconThemeDir *> listDirs = d->mDirs + d->mScaledDirs;
569 for (const KIconThemeDir *dir : listDirs) {
570 if (!isAnyOrDirContext(dir, context)) {
571 continue;
572 }
573
574 const int dirSize = dir->size();
575 if ((dir->type() == KIconLoader::Fixed && dirSize == size) //
576 || (dir->type() == KIconLoader::Scalable && size >= dir->minSize() && size <= dir->maxSize())
577 || (dir->type() == KIconLoader::Threshold && abs(x: size - dirSize) < dir->threshold())) {
578 result += dir->iconList();
579 }
580 }
581
582 return result;
583}
584
585QStringList KIconTheme::queryIconsByContext(int size, KIconLoader::Context context) const
586{
587 int dw;
588
589 // We want all the icons for a given context, but we prefer icons
590 // of size "size" . Note that this may (will) include duplicate icons
591 // QStringList iconlist[34]; // 33 == 48-16+1
592 QStringList iconlist[128]; // 33 == 48-16+1
593 // Usually, only the 0, 6 (22-16), 10 (32-22), 16 (48-32 or 32-16),
594 // 26 (48-22) and 32 (48-16) will be used, but who knows if someone
595 // will make icon themes with different icon sizes.
596 const auto listDirs = d->mDirs + d->mScaledDirs;
597 for (KIconThemeDir *dir : listDirs) {
598 if (!isAnyOrDirContext(dir, context)) {
599 continue;
600 }
601 dw = abs(x: dir->size() - size);
602 iconlist[(dw < 127) ? dw : 127] += dir->iconList();
603 }
604
605 QStringList iconlistResult;
606 for (int i = 0; i < 128; i++) {
607 iconlistResult += iconlist[i];
608 }
609
610 return iconlistResult;
611}
612
613bool KIconTheme::hasContext(KIconLoader::Context context) const
614{
615 const auto listDirs = d->mDirs + d->mScaledDirs;
616 for (KIconThemeDir *dir : listDirs) {
617 if (isAnyOrDirContext(dir, context)) {
618 return true;
619 }
620 }
621 return false;
622}
623
624QString KIconTheme::iconPathByName(const QString &iconName, int size, KIconLoader::MatchType match) const
625{
626 return iconPathByName(name: iconName, size, match, scale: 1 /*scale*/);
627}
628
629QString KIconTheme::iconPathByName(const QString &iconName, int size, KIconLoader::MatchType match, qreal scale) const
630{
631 for (const QString &current : std::as_const(t&: d->mExtensions)) {
632 const QString path = iconPath(name: iconName + current, size, match, scale);
633 if (!path.isEmpty()) {
634 return path;
635 }
636 }
637 return QString();
638}
639
640bool KIconTheme::followsColorScheme() const
641{
642 return d->followsColorScheme;
643}
644
645QString KIconTheme::iconPath(const QString &name, int size, KIconLoader::MatchType match) const
646{
647 return iconPath(name, size, match, scale: 1 /*scale*/);
648}
649
650QString KIconTheme::iconPath(const QString &name, int size, KIconLoader::MatchType match, qreal scale) const
651{
652 // first look for a scaled image at exactly the requested size
653 QString path = d->iconPath(dirs: d->mScaledDirs, name, size, scale, match: KIconLoader::MatchExact);
654
655 // then look for an unscaled one but request it at larger size so it doesn't become blurry
656 if (path.isEmpty()) {
657 path = d->iconPath(dirs: d->mDirs, name, size: size * scale, scale: 1, match);
658 }
659 return path;
660}
661
662// static
663QString KIconTheme::current()
664{
665 // Static pointers because of unloading problems wrt DSO's.
666 if (_themeOverride && !_themeOverride->isEmpty()) {
667 *_theme() = *_themeOverride();
668 }
669 if (!_theme()->isEmpty()) {
670 return *_theme();
671 }
672
673 QString theme;
674 // Check application specific config for a theme setting.
675 KConfigGroup app_cg(KSharedConfig::openConfig(fileName: QString(), mode: KConfig::NoGlobals), "Icons");
676 theme = app_cg.readEntry(key: "Theme", aDefault: QString());
677 if (theme.isEmpty() || theme == QLatin1String("hicolor")) {
678 // No theme, try to use Qt's. A Platform plugin might have set
679 // a good theme there.
680 theme = QIcon::themeName();
681 }
682 if (theme.isEmpty() || theme == QLatin1String("hicolor")) {
683 // Still no theme, try config with kdeglobals.
684 KConfigGroup cg(KSharedConfig::openConfig(), "Icons");
685 theme = cg.readEntry(key: "Theme", QStringLiteral("breeze"));
686 }
687 if (theme.isEmpty() || theme == QLatin1String("hicolor")) {
688 // Still no good theme, use default.
689 theme = defaultThemeName();
690 }
691 *_theme() = theme;
692 return *_theme();
693}
694
695void KIconTheme::forceThemeForTests(const QString &themeName)
696{
697 *_themeOverride() = themeName;
698 _theme()->clear(); // ::current sets this again based on conditions
699}
700
701// static
702QStringList KIconTheme::list()
703{
704 // Static pointer because of unloading problems wrt DSO's.
705 if (!_theme_list()->isEmpty()) {
706 return *_theme_list();
707 }
708
709 // Find the theme description file. These are either locally in the :/icons resource path or global.
710 QStringList icnlibs;
711
712 // global icons
713 icnlibs += QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("icons"), options: QStandardPaths::LocateDirectory);
714
715 // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
716 icnlibs += QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("pixmaps"), options: QStandardPaths::LocateDirectory);
717
718 // local embedded icons
719 icnlibs << QStringLiteral(":/icons");
720
721 for (const QString &iconDir : std::as_const(t&: icnlibs)) {
722 QDir dir(iconDir);
723 const QStringList themeDirs = dir.entryList(filters: QDir::Dirs | QDir::NoDotAndDotDot);
724 for (const auto &theme : themeDirs) {
725 if (theme.startsWith(s: QLatin1String("default."))) {
726 continue;
727 }
728
729 const QString prefix = iconDir + QLatin1Char('/') + theme;
730 if (!QFileInfo::exists(file: prefix + QLatin1String("/index.desktop")) //
731 && !QFileInfo::exists(file: prefix + QLatin1String("/index.theme"))) {
732 continue;
733 }
734
735 if (!KIconTheme(theme).isValid()) {
736 continue;
737 }
738
739 if (!_theme_list()->contains(str: theme)) {
740 _theme_list()->append(t: theme);
741 }
742 }
743 }
744 return *_theme_list();
745}
746
747// static
748void KIconTheme::reconfigure()
749{
750 _theme()->clear();
751 _theme_list()->clear();
752}
753
754// static
755QString KIconTheme::defaultThemeName()
756{
757 return QStringLiteral("hicolor");
758}
759
760KIconThemeDir::KIconThemeDir(const QString &basedir, const QString &themedir, const KConfigGroup &config)
761 : mSize(config.readEntry(key: "Size", defaultValue: 0))
762 , mScale(config.readEntry(key: "Scale", defaultValue: 1))
763 , mBaseDir(basedir)
764 , mThemeDir(themedir)
765{
766 if (mSize == 0) {
767 return;
768 }
769
770 QString tmp = config.readEntry(key: "Context", aDefault: QString());
771 if (tmp == QLatin1String("Devices")) {
772 mContext = KIconLoader::Device;
773 } else if (tmp == QLatin1String("MimeTypes")) {
774 mContext = KIconLoader::MimeType;
775 } else if (tmp == QLatin1String("Applications")) {
776 mContext = KIconLoader::Application;
777 } else if (tmp == QLatin1String("Actions")) {
778 mContext = KIconLoader::Action;
779 } else if (tmp == QLatin1String("Animations")) {
780 mContext = KIconLoader::Animation;
781 } else if (tmp == QLatin1String("Categories")) {
782 mContext = KIconLoader::Category;
783 } else if (tmp == QLatin1String("Emblems")) {
784 mContext = KIconLoader::Emblem;
785 } else if (tmp == QLatin1String("Emotes")) {
786 mContext = KIconLoader::Emote;
787 } else if (tmp == QLatin1String("International")) {
788 mContext = KIconLoader::International;
789 } else if (tmp == QLatin1String("Places")) {
790 mContext = KIconLoader::Place;
791 } else if (tmp == QLatin1String("Status")) {
792 mContext = KIconLoader::StatusIcon;
793 } else if (tmp == QLatin1String("Stock")) { // invalid, but often present context, skip warning
794 return;
795 } else if (tmp == QLatin1String("FileSystems")) { // invalid, but present context for hicolor, skip warning
796 return;
797 } else if (tmp == QLatin1String("Legacy")) { // invalid, but often present context for Adwaita, skip warning
798 return;
799 } else if (tmp == QLatin1String("UI")) { // invalid, but often present context for Adwaita, skip warning
800 return;
801 } else if (tmp.isEmpty()) {
802 // do nothing. key not required
803 } else {
804 qCDebug(KICONTHEMES) << "Invalid Context=" << tmp << "line for icon theme: " << constructFileName(file: QString());
805 return;
806 }
807 tmp = config.readEntry(key: "Type", QStringLiteral("Threshold"));
808 if (tmp == QLatin1String("Fixed")) {
809 mType = KIconLoader::Fixed;
810 } else if (tmp == QLatin1String("Scalable")) {
811 mType = KIconLoader::Scalable;
812 } else if (tmp == QLatin1String("Threshold")) {
813 mType = KIconLoader::Threshold;
814 } else {
815 qCDebug(KICONTHEMES) << "Invalid Type=" << tmp << "line for icon theme: " << constructFileName(file: QString());
816 return;
817 }
818 if (mType == KIconLoader::Scalable) {
819 mMinSize = config.readEntry(key: "MinSize", defaultValue: mSize);
820 mMaxSize = config.readEntry(key: "MaxSize", defaultValue: mSize);
821 } else if (mType == KIconLoader::Threshold) {
822 mThreshold = config.readEntry(key: "Threshold", defaultValue: 2);
823 }
824 mbValid = true;
825}
826
827QString KIconThemeDir::iconPath(const QString &name) const
828{
829 if (!mbValid) {
830 return QString();
831 }
832
833 const QString file = constructFileName(file: name);
834 if (QFileInfo::exists(file)) {
835 return KLocalizedString::localizedFilePath(filePath: file);
836 }
837
838 return QString();
839}
840
841QStringList KIconThemeDir::iconList() const
842{
843 const QDir icondir = constructFileName(file: QString());
844
845 const QStringList formats = QStringList() << QStringLiteral("*.png") << QStringLiteral("*.svg") << QStringLiteral("*.svgz") << QStringLiteral("*.xpm");
846 const QStringList lst = icondir.entryList(nameFilters: formats, filters: QDir::Files);
847
848 QStringList result;
849 result.reserve(asize: lst.size());
850 for (const QString &file : lst) {
851 result += constructFileName(file);
852 }
853 return result;
854}
855

source code of kiconthemes/src/kicontheme.cpp