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