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 | |
56 | namespace |
57 | { |
58 | // Used to make cache keys for icons with no group. Result type is QString* |
59 | QString 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 | */ |
72 | KICONTHEMES_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 | |
85 | static 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 | |
100 | class KIconThemeNode |
101 | { |
102 | public: |
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 | |
116 | KIconThemeNode::KIconThemeNode(KIconTheme *_theme) |
117 | { |
118 | theme = _theme; |
119 | } |
120 | |
121 | KIconThemeNode::~KIconThemeNode() |
122 | { |
123 | delete theme; |
124 | } |
125 | |
126 | void 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 | |
132 | void 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 | |
138 | QString KIconThemeNode::findIcon(const QString &name, int size, KIconLoader::MatchType match) const |
139 | { |
140 | return theme->iconPath(name, size, match); |
141 | } |
142 | |
143 | extern KICONTHEMES_EXPORT int kiconloader_ms_between_checks; |
144 | KICONTHEMES_EXPORT int kiconloader_ms_between_checks = 5000; |
145 | |
146 | class KIconLoaderGlobalData : public QObject |
147 | { |
148 | Q_OBJECT |
149 | |
150 | public: |
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 | |
183 | Q_SIGNALS: |
184 | void iconChanged(int group); |
185 | |
186 | private: |
187 | void parseGenericIconsFiles(const QString &fileName); |
188 | QHash<QString, QString> m_genericIcons; |
189 | }; |
190 | |
191 | void 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 | |
218 | Q_GLOBAL_STATIC(KIconLoaderGlobalData, s_globalData) |
219 | |
220 | KIconLoaderPrivate::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 | |
230 | KIconLoaderPrivate::~KIconLoaderPrivate() |
231 | { |
232 | clear(); |
233 | } |
234 | |
235 | KIconLoaderPrivate *KIconLoaderPrivate::get(KIconLoader *loader) |
236 | { |
237 | return loader->d.get(); |
238 | } |
239 | |
240 | void 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 | |
255 | void 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 | |
334 | void 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 | |
351 | bool 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 | |
360 | KIconLoader::KIconLoader(const QString &appname, const QStringList &, QObject *parent) |
361 | : QObject(parent) |
362 | , d(new KIconLoaderPrivate(appname, extraSearchPaths, this)) |
363 | { |
364 | setObjectName(appname); |
365 | } |
366 | |
367 | void KIconLoader::reconfigure(const QString &_appname, const QStringList &) |
368 | { |
369 | d->clear(); |
370 | d->init(_appname, extraSearchPaths); |
371 | } |
372 | |
373 | void KIconLoaderPrivate::init(const QString &_appname, const QStringList &) |
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 | |
404 | void 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 | |
439 | KIconLoader::~KIconLoader() = default; |
440 | |
441 | QStringList KIconLoader::searchPaths() const |
442 | { |
443 | return d->searchPaths; |
444 | } |
445 | |
446 | void KIconLoader::addAppDir(const QString &appname, const QString &themeBaseDir) |
447 | { |
448 | d->searchPaths.append(appname + QStringLiteral("/pics" )); |
449 | d->addAppThemes(appname, themeBaseDir); |
450 | } |
451 | |
452 | void 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 | |
475 | void 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 | |
492 | void 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 | |
507 | void 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 | |
523 | void KIconLoaderPrivate::() |
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 | |
562 | void KIconLoader::drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state) const |
563 | { |
564 | d->drawOverlays(this, group, state, pixmap, overlays); |
565 | } |
566 | |
567 | QString 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 | |
580 | void 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 | |
612 | QString 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 | |
639 | QByteArray 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 | |
680 | QImage 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 | |
715 | void 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 | |
728 | bool 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 | |
742 | QString 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 | |
756 | QString 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 | |
837 | QString 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 | |
860 | inline 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 | |
870 | QString 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 | |
890 | QString 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 | |
895 | QString 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 | |
954 | QPixmap |
955 | KIconLoader::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 | |
978 | QPixmap 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 | |
989 | QPixmap 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 | |
1001 | QPixmap 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 | |
1013 | QPixmap 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 | |
1162 | QMovie *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 | |
1181 | QString 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 | |
1227 | QStringList 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 | |
1278 | KIconTheme *KIconLoader::theme() const |
1279 | { |
1280 | if (d->mpThemeRoot) { |
1281 | return d->mpThemeRoot->theme; |
1282 | } |
1283 | return nullptr; |
1284 | } |
1285 | |
1286 | int 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 | |
1299 | QStringList 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 | |
1311 | QStringList 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 | |
1349 | QStringList 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 |
1390 | bool 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 | |
1400 | KIconEffect *KIconLoader::iconEffect() const |
1401 | { |
1402 | return &d->mpEffect; |
1403 | } |
1404 | |
1405 | QPixmap 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 | |
1424 | bool KIconLoader::hasIcon(const QString &name) const |
1425 | { |
1426 | return !d->preferredIconPath(name).isEmpty(); |
1427 | } |
1428 | |
1429 | void KIconLoader::setCustomPalette(const QPalette &palette) |
1430 | { |
1431 | d->mCustomColors = true; |
1432 | d->mColors = KIconColors(palette); |
1433 | } |
1434 | |
1435 | QPalette KIconLoader::customPalette() const |
1436 | { |
1437 | return d->mCustomColors ? d->mPalette : QPalette(); |
1438 | } |
1439 | |
1440 | void KIconLoader::resetPalette() |
1441 | { |
1442 | d->mCustomColors = false; |
1443 | } |
1444 | |
1445 | bool KIconLoader::hasCustomPalette() const |
1446 | { |
1447 | return d->mCustomColors; |
1448 | } |
1449 | |
1450 | /*** the global icon loader ***/ |
1451 | Q_GLOBAL_STATIC(KIconLoader, globalIconLoader) |
1452 | |
1453 | KIconLoader *KIconLoader::global() |
1454 | { |
1455 | return globalIconLoader(); |
1456 | } |
1457 | |
1458 | void KIconLoader::newIconLoader() |
1459 | { |
1460 | if (global() == this) { |
1461 | KIconTheme::reconfigure(); |
1462 | } |
1463 | |
1464 | reconfigure(objectName()); |
1465 | Q_EMIT iconLoaderSettingsChanged(); |
1466 | } |
1467 | |
1468 | void KIconLoader::emitChange(KIconLoader::Group g) |
1469 | { |
1470 | s_globalData->emitChange(g); |
1471 | } |
1472 | |
1473 | #include <kiconengine.h> |
1474 | QIcon KDE::icon(const QString &iconName, KIconLoader *iconLoader) |
1475 | { |
1476 | return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global())); |
1477 | } |
1478 | |
1479 | QIcon KDE::icon(const QString &iconName, const QStringList &overlays, KIconLoader *iconLoader) |
1480 | { |
1481 | return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global(), overlays)); |
1482 | } |
1483 | |
1484 | QIcon 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 | |