1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39#ifndef QT_NO_ICON
40#include <private/qiconloader_p.h>
41
42#include <private/qguiapplication_p.h>
43#include <private/qicon_p.h>
44
45#include <QtGui/QIconEnginePlugin>
46#include <QtGui/QPixmapCache>
47#include <qpa/qplatformtheme.h>
48#include <QtGui/QIconEngine>
49#include <QtGui/QPalette>
50#include <QtCore/qmath.h>
51#include <QtCore/QList>
52#include <QtCore/QDir>
53#if QT_CONFIG(settings)
54#include <QtCore/QSettings>
55#endif
56#include <QtGui/QPainter>
57
58#include <private/qhexstring_p.h>
59
60QT_BEGIN_NAMESPACE
61
62Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance)
63
64/* Theme to use in last resort, if the theme does not have the icon, neither the parents */
65static QString systemFallbackThemeName()
66{
67 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
68 const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::SystemIconFallbackThemeName);
69 if (themeHint.isValid())
70 return themeHint.toString();
71 }
72 return QString();
73}
74
75QIconLoader::QIconLoader() :
76 m_themeKey(1), m_supportsSvg(false), m_initialized(false)
77{
78}
79
80static inline QString systemThemeName()
81{
82 const auto override = qgetenv(varName: "QT_QPA_SYSTEM_ICON_THEME");
83 if (!override.isEmpty())
84 return QString::fromLocal8Bit(str: override);
85 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
86 const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::SystemIconThemeName);
87 if (themeHint.isValid())
88 return themeHint.toString();
89 }
90 return QString();
91}
92
93static inline QStringList systemIconSearchPaths()
94{
95 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
96 const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::IconThemeSearchPaths);
97 if (themeHint.isValid())
98 return themeHint.toStringList();
99 }
100 return QStringList();
101}
102
103static inline QStringList systemFallbackSearchPaths()
104{
105 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
106 const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::IconFallbackSearchPaths);
107 if (themeHint.isValid())
108 return themeHint.toStringList();
109 }
110 return QStringList();
111}
112
113extern QFactoryLoader *qt_iconEngineFactoryLoader(); // qicon.cpp
114
115void QIconLoader::ensureInitialized()
116{
117 if (!m_initialized) {
118 if (!QGuiApplicationPrivate::platformTheme())
119 return; // it's too early: try again later (QTBUG-74252)
120 m_initialized = true;
121 m_systemTheme = systemThemeName();
122
123 if (m_systemTheme.isEmpty())
124 m_systemTheme = systemFallbackThemeName();
125 if (qt_iconEngineFactoryLoader()->keyMap().key(avalue: QLatin1String("svg"), defaultKey: -1) != -1)
126 m_supportsSvg = true;
127 }
128}
129
130/*!
131 \internal
132 Gets an instance.
133
134 \l QIcon::setFallbackThemeName() should be called before QGuiApplication is
135 created, to avoid a race condition (QTBUG-74252). When this function is
136 called from there, ensureInitialized() does not succeed because there
137 is no QPlatformTheme yet, so systemThemeName() is empty, and we don't want
138 m_systemTheme to get intialized to the fallback theme instead of the normal one.
139*/
140QIconLoader *QIconLoader::instance()
141{
142 iconLoaderInstance()->ensureInitialized();
143 return iconLoaderInstance();
144}
145
146// Queries the system theme and invalidates existing
147// icons if the theme has changed.
148void QIconLoader::updateSystemTheme()
149{
150 // Only change if this is not explicitly set by the user
151 if (m_userTheme.isEmpty()) {
152 QString theme = systemThemeName();
153 if (theme.isEmpty())
154 theme = fallbackThemeName();
155 if (theme != m_systemTheme) {
156 m_systemTheme = theme;
157 invalidateKey();
158 }
159 }
160}
161
162void QIconLoader::setThemeName(const QString &themeName)
163{
164 m_userTheme = themeName;
165 invalidateKey();
166}
167
168QString QIconLoader::fallbackThemeName() const
169{
170 return m_userFallbackTheme.isEmpty() ? systemFallbackThemeName() : m_userFallbackTheme;
171}
172
173void QIconLoader::setFallbackThemeName(const QString &themeName)
174{
175 m_userFallbackTheme = themeName;
176}
177
178void QIconLoader::setThemeSearchPath(const QStringList &searchPaths)
179{
180 m_iconDirs = searchPaths;
181 themeList.clear();
182 invalidateKey();
183}
184
185QStringList QIconLoader::themeSearchPaths() const
186{
187 if (m_iconDirs.isEmpty()) {
188 m_iconDirs = systemIconSearchPaths();
189 // Always add resource directory as search path
190 m_iconDirs.append(t: QLatin1String(":/icons"));
191 }
192 return m_iconDirs;
193}
194
195void QIconLoader::setFallbackSearchPaths(const QStringList &searchPaths)
196{
197 m_fallbackDirs = searchPaths;
198 invalidateKey();
199}
200
201QStringList QIconLoader::fallbackSearchPaths() const
202{
203 if (m_fallbackDirs.isEmpty()) {
204 m_fallbackDirs = systemFallbackSearchPaths();
205 }
206 return m_fallbackDirs;
207}
208
209/*!
210 \internal
211 Helper class that reads and looks up into the icon-theme.cache generated with
212 gtk-update-icon-cache. If at any point we detect a corruption in the file
213 (because the offsets point at wrong locations for example), the reader
214 is marked as invalid.
215*/
216class QIconCacheGtkReader
217{
218public:
219 explicit QIconCacheGtkReader(const QString &themeDir);
220 QVector<const char *> lookup(const QStringRef &);
221 bool isValid() const { return m_isValid; }
222private:
223 QFile m_file;
224 const unsigned char *m_data;
225 quint64 m_size;
226 bool m_isValid;
227
228 quint16 read16(uint offset)
229 {
230 if (offset > m_size - 2 || (offset & 0x1)) {
231 m_isValid = false;
232 return 0;
233 }
234 return m_data[offset+1] | m_data[offset] << 8;
235 }
236 quint32 read32(uint offset)
237 {
238 if (offset > m_size - 4 || (offset & 0x3)) {
239 m_isValid = false;
240 return 0;
241 }
242 return m_data[offset+3] | m_data[offset+2] << 8
243 | m_data[offset+1] << 16 | m_data[offset] << 24;
244 }
245};
246
247
248QIconCacheGtkReader::QIconCacheGtkReader(const QString &dirName)
249 : m_isValid(false)
250{
251 QFileInfo info(dirName + QLatin1String("/icon-theme.cache"));
252 if (!info.exists() || info.lastModified() < QFileInfo(dirName).lastModified())
253 return;
254 m_file.setFileName(info.absoluteFilePath());
255 if (!m_file.open(flags: QFile::ReadOnly))
256 return;
257 m_size = m_file.size();
258 m_data = m_file.map(offset: 0, size: m_size);
259 if (!m_data)
260 return;
261 if (read16(offset: 0) != 1) // VERSION_MAJOR
262 return;
263
264 m_isValid = true;
265
266 // Check that all the directories are older than the cache
267 auto lastModified = info.lastModified();
268 quint32 dirListOffset = read32(offset: 8);
269 quint32 dirListLen = read32(offset: dirListOffset);
270 for (uint i = 0; i < dirListLen; ++i) {
271 quint32 offset = read32(offset: dirListOffset + 4 + 4 * i);
272 if (!m_isValid || offset >= m_size || lastModified < QFileInfo(dirName + QLatin1Char('/')
273 + QString::fromUtf8(str: reinterpret_cast<const char*>(m_data + offset))).lastModified()) {
274 m_isValid = false;
275 return;
276 }
277 }
278}
279
280static quint32 icon_name_hash(const char *p)
281{
282 quint32 h = static_cast<signed char>(*p);
283 for (p += 1; *p != '\0'; p++)
284 h = (h << 5) - h + *p;
285 return h;
286}
287
288/*! \internal
289 lookup the icon name and return the list of subdirectories in which an icon
290 with this name is present. The char* are pointers to the mapped data.
291 For example, this would return { "32x32/apps", "24x24/apps" , ... }
292 */
293QVector<const char *> QIconCacheGtkReader::lookup(const QStringRef &name)
294{
295 QVector<const char *> ret;
296 if (!isValid() || name.isEmpty())
297 return ret;
298
299 QByteArray nameUtf8 = name.toUtf8();
300 quint32 hash = icon_name_hash(p: nameUtf8);
301
302 quint32 hashOffset = read32(offset: 4);
303 quint32 hashBucketCount = read32(offset: hashOffset);
304
305 if (!isValid() || hashBucketCount == 0) {
306 m_isValid = false;
307 return ret;
308 }
309
310 quint32 bucketIndex = hash % hashBucketCount;
311 quint32 bucketOffset = read32(offset: hashOffset + 4 + bucketIndex * 4);
312 while (bucketOffset > 0 && bucketOffset <= m_size - 12) {
313 quint32 nameOff = read32(offset: bucketOffset + 4);
314 if (nameOff < m_size && strcmp(s1: reinterpret_cast<const char*>(m_data + nameOff), s2: nameUtf8) == 0) {
315 quint32 dirListOffset = read32(offset: 8);
316 quint32 dirListLen = read32(offset: dirListOffset);
317
318 quint32 listOffset = read32(offset: bucketOffset+8);
319 quint32 listLen = read32(offset: listOffset);
320
321 if (!m_isValid || listOffset + 4 + 8 * listLen > m_size) {
322 m_isValid = false;
323 return ret;
324 }
325
326 ret.reserve(asize: listLen);
327 for (uint j = 0; j < listLen && m_isValid; ++j) {
328 quint32 dirIndex = read16(offset: listOffset + 4 + 8 * j);
329 quint32 o = read32(offset: dirListOffset + 4 + dirIndex*4);
330 if (!m_isValid || dirIndex >= dirListLen || o >= m_size) {
331 m_isValid = false;
332 return ret;
333 }
334 ret.append(t: reinterpret_cast<const char*>(m_data) + o);
335 }
336 return ret;
337 }
338 bucketOffset = read32(offset: bucketOffset);
339 }
340 return ret;
341}
342
343QIconTheme::QIconTheme(const QString &themeName)
344 : m_valid(false)
345{
346 QFile themeIndex;
347
348 const QStringList iconDirs = QIcon::themeSearchPaths();
349 for ( int i = 0 ; i < iconDirs.size() ; ++i) {
350 QDir iconDir(iconDirs[i]);
351 QString themeDir = iconDir.path() + QLatin1Char('/') + themeName;
352 QFileInfo themeDirInfo(themeDir);
353
354 if (themeDirInfo.isDir()) {
355 m_contentDirs << themeDir;
356 m_gtkCaches << QSharedPointer<QIconCacheGtkReader>::create(arguments&: themeDir);
357 }
358
359 if (!m_valid) {
360 themeIndex.setFileName(themeDir + QLatin1String("/index.theme"));
361 if (themeIndex.exists())
362 m_valid = true;
363 }
364 }
365#if QT_CONFIG(settings)
366 if (themeIndex.exists()) {
367 const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
368 const QStringList keys = indexReader.allKeys();
369 for (const QString &key : keys) {
370 if (key.endsWith(s: QLatin1String("/Size"))) {
371 // Note the QSettings ini-format does not accept
372 // slashes in key names, hence we have to cheat
373 if (int size = indexReader.value(key).toInt()) {
374 QString directoryKey = key.left(n: key.size() - 5);
375 QIconDirInfo dirInfo(directoryKey);
376 dirInfo.size = size;
377 QString type = indexReader.value(key: directoryKey +
378 QLatin1String("/Type")
379 ).toString();
380
381 if (type == QLatin1String("Fixed"))
382 dirInfo.type = QIconDirInfo::Fixed;
383 else if (type == QLatin1String("Scalable"))
384 dirInfo.type = QIconDirInfo::Scalable;
385 else
386 dirInfo.type = QIconDirInfo::Threshold;
387
388 dirInfo.threshold = indexReader.value(key: directoryKey +
389 QLatin1String("/Threshold"),
390 defaultValue: 2).toInt();
391
392 dirInfo.minSize = indexReader.value(key: directoryKey +
393 QLatin1String("/MinSize"),
394 defaultValue: size).toInt();
395
396 dirInfo.maxSize = indexReader.value(key: directoryKey +
397 QLatin1String("/MaxSize"),
398 defaultValue: size).toInt();
399
400 dirInfo.scale = indexReader.value(key: directoryKey +
401 QLatin1String("/Scale"),
402 defaultValue: 1).toInt();
403 m_keyList.append(t: dirInfo);
404 }
405 }
406 }
407
408 // Parent themes provide fallbacks for missing icons
409 m_parents = indexReader.value(
410 key: QLatin1String("Icon Theme/Inherits")).toStringList();
411 m_parents.removeAll(t: QString());
412
413 // Ensure a default platform fallback for all themes
414 if (m_parents.isEmpty()) {
415 const QString fallback = QIconLoader::instance()->fallbackThemeName();
416 if (!fallback.isEmpty())
417 m_parents.append(t: fallback);
418 }
419
420 // Ensure that all themes fall back to hicolor
421 if (!m_parents.contains(str: QLatin1String("hicolor")))
422 m_parents.append(t: QLatin1String("hicolor"));
423 }
424#endif // settings
425}
426
427QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
428 const QString &iconName,
429 QStringList &visited) const
430{
431 QThemeIconInfo info;
432 Q_ASSERT(!themeName.isEmpty());
433
434 // Used to protect against potential recursions
435 visited << themeName;
436
437 QIconTheme &theme = themeList[themeName];
438 if (!theme.isValid()) {
439 theme = QIconTheme(themeName);
440 if (!theme.isValid())
441 theme = QIconTheme(fallbackThemeName());
442 }
443
444 const QStringList contentDirs = theme.contentDirs();
445
446 QStringRef iconNameFallback(&iconName);
447
448 // Iterate through all icon's fallbacks in current theme
449 while (info.entries.isEmpty()) {
450 const QString svgIconName = iconNameFallback + QLatin1String(".svg");
451 const QString pngIconName = iconNameFallback + QLatin1String(".png");
452
453 // Add all relevant files
454 for (int i = 0; i < contentDirs.size(); ++i) {
455 QVector<QIconDirInfo> subDirs = theme.keyList();
456
457 // Try to reduce the amount of subDirs by looking in the GTK+ cache in order to save
458 // a massive amount of file stat (especially if the icon is not there)
459 auto cache = theme.m_gtkCaches.at(i);
460 if (cache->isValid()) {
461 const auto result = cache->lookup(name: iconNameFallback);
462 if (cache->isValid()) {
463 const QVector<QIconDirInfo> subDirsCopy = subDirs;
464 subDirs.clear();
465 subDirs.reserve(asize: result.count());
466 for (const char *s : result) {
467 QString path = QString::fromUtf8(str: s);
468 auto it = std::find_if(first: subDirsCopy.cbegin(), last: subDirsCopy.cend(),
469 pred: [&](const QIconDirInfo &info) {
470 return info.path == path; } );
471 if (it != subDirsCopy.cend()) {
472 subDirs.append(t: *it);
473 }
474 }
475 }
476 }
477
478 QString contentDir = contentDirs.at(i) + QLatin1Char('/');
479 for (int j = 0; j < subDirs.size() ; ++j) {
480 const QIconDirInfo &dirInfo = subDirs.at(i: j);
481 const QString subDir = contentDir + dirInfo.path + QLatin1Char('/');
482 const QString pngPath = subDir + pngIconName;
483 if (QFile::exists(fileName: pngPath)) {
484 PixmapEntry *iconEntry = new PixmapEntry;
485 iconEntry->dir = dirInfo;
486 iconEntry->filename = pngPath;
487 // Notice we ensure that pixmap entries always come before
488 // scalable to preserve search order afterwards
489 info.entries.prepend(t: iconEntry);
490 } else if (m_supportsSvg) {
491 const QString svgPath = subDir + svgIconName;
492 if (QFile::exists(fileName: svgPath)) {
493 ScalableEntry *iconEntry = new ScalableEntry;
494 iconEntry->dir = dirInfo;
495 iconEntry->filename = svgPath;
496 info.entries.append(t: iconEntry);
497 }
498 }
499 }
500 }
501
502 if (!info.entries.isEmpty()) {
503 info.iconName = iconNameFallback.toString();
504 break;
505 }
506
507 // If it's possible - find next fallback for the icon
508 const int indexOfDash = iconNameFallback.lastIndexOf(ch: QLatin1Char('-'));
509 if (indexOfDash == -1)
510 break;
511
512 iconNameFallback.truncate(pos: indexOfDash);
513 }
514
515 if (info.entries.isEmpty()) {
516 const QStringList parents = theme.parents();
517 // Search recursively through inherited themes
518 for (int i = 0 ; i < parents.size() ; ++i) {
519
520 const QString parentTheme = parents.at(i).trimmed();
521
522 if (!visited.contains(str: parentTheme)) // guard against recursion
523 info = findIconHelper(themeName: parentTheme, iconName, visited);
524
525 if (!info.entries.isEmpty()) // success
526 break;
527 }
528 }
529 return info;
530}
531
532QThemeIconInfo QIconLoader::lookupFallbackIcon(const QString &iconName) const
533{
534 QThemeIconInfo info;
535
536 const QString pngIconName = iconName + QLatin1String(".png");
537 const QString xpmIconName = iconName + QLatin1String(".xpm");
538 const QString svgIconName = iconName + QLatin1String(".svg");
539
540 const auto searchPaths = QIcon::fallbackSearchPaths();
541 for (const QString &iconDir: searchPaths) {
542 QDir currentDir(iconDir);
543 if (currentDir.exists(name: pngIconName)) {
544 PixmapEntry *iconEntry = new PixmapEntry;
545 iconEntry->dir.type = QIconDirInfo::Fallback;
546 iconEntry->filename = currentDir.filePath(fileName: pngIconName);
547 info.entries.append(t: iconEntry);
548 break;
549 } else if (currentDir.exists(name: xpmIconName)) {
550 PixmapEntry *iconEntry = new PixmapEntry;
551 iconEntry->dir.type = QIconDirInfo::Fallback;
552 iconEntry->filename = currentDir.filePath(fileName: xpmIconName);
553 info.entries.append(t: iconEntry);
554 break;
555 } else if (m_supportsSvg &&
556 currentDir.exists(name: svgIconName)) {
557 ScalableEntry *iconEntry = new ScalableEntry;
558 iconEntry->dir.type = QIconDirInfo::Fallback;
559 iconEntry->filename = currentDir.filePath(fileName: svgIconName);
560 info.entries.append(t: iconEntry);
561 break;
562 }
563 }
564
565 if (!info.entries.isEmpty())
566 info.iconName = iconName;
567
568 return info;
569}
570
571QThemeIconInfo QIconLoader::loadIcon(const QString &name) const
572{
573 if (!themeName().isEmpty()) {
574 QStringList visited;
575 const QThemeIconInfo iconInfo = findIconHelper(themeName: themeName(), iconName: name, visited);
576 if (!iconInfo.entries.isEmpty())
577 return iconInfo;
578
579 return lookupFallbackIcon(iconName: name);
580 }
581
582 return QThemeIconInfo();
583}
584
585
586// -------- Icon Loader Engine -------- //
587
588
589QIconLoaderEngine::QIconLoaderEngine(const QString& iconName)
590 : m_iconName(iconName), m_key(0)
591{
592}
593
594QIconLoaderEngine::~QIconLoaderEngine()
595{
596 qDeleteAll(c: m_info.entries);
597}
598
599QIconLoaderEngine::QIconLoaderEngine(const QIconLoaderEngine &other)
600 : QIconEngine(other),
601 m_iconName(other.m_iconName),
602 m_key(0)
603{
604}
605
606QIconEngine *QIconLoaderEngine::clone() const
607{
608 return new QIconLoaderEngine(*this);
609}
610
611bool QIconLoaderEngine::read(QDataStream &in) {
612 in >> m_iconName;
613 return true;
614}
615
616bool QIconLoaderEngine::write(QDataStream &out) const
617{
618 out << m_iconName;
619 return true;
620}
621
622bool QIconLoaderEngine::hasIcon() const
623{
624 return !(m_info.entries.isEmpty());
625}
626
627// Lazily load the icon
628void QIconLoaderEngine::ensureLoaded()
629{
630 if (!(QIconLoader::instance()->themeKey() == m_key)) {
631 qDeleteAll(c: m_info.entries);
632 m_info.entries.clear();
633 m_info.iconName.clear();
634
635 Q_ASSERT(m_info.entries.size() == 0);
636 m_info = QIconLoader::instance()->loadIcon(name: m_iconName);
637 m_key = QIconLoader::instance()->themeKey();
638 }
639}
640
641void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect,
642 QIcon::Mode mode, QIcon::State state)
643{
644 const qreal dpr = !qApp->testAttribute(attribute: Qt::AA_UseHighDpiPixmaps) ?
645 qreal(1.0) : painter->device()->devicePixelRatioF();
646
647 QSize pixmapSize = rect.size() * dpr;
648 painter->drawPixmap(r: rect, pm: pixmap(size: pixmapSize, mode, state));
649}
650
651/*
652 * This algorithm is defined by the freedesktop spec:
653 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
654 */
655static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize, int iconscale)
656{
657 if (dir.scale != iconscale)
658 return false;
659
660 if (dir.type == QIconDirInfo::Fixed) {
661 return dir.size == iconsize;
662
663 } else if (dir.type == QIconDirInfo::Scalable) {
664 return iconsize <= dir.maxSize &&
665 iconsize >= dir.minSize;
666
667 } else if (dir.type == QIconDirInfo::Threshold) {
668 return iconsize >= dir.size - dir.threshold &&
669 iconsize <= dir.size + dir.threshold;
670 } else if (dir.type == QIconDirInfo::Fallback) {
671 return true;
672 }
673
674 Q_ASSERT(1); // Not a valid value
675 return false;
676}
677
678/*
679 * This algorithm is defined by the freedesktop spec:
680 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
681 */
682static int directorySizeDistance(const QIconDirInfo &dir, int iconsize, int iconscale)
683{
684 const int scaledIconSize = iconsize * iconscale;
685 if (dir.type == QIconDirInfo::Fixed) {
686 return qAbs(t: dir.size * dir.scale - scaledIconSize);
687
688 } else if (dir.type == QIconDirInfo::Scalable) {
689 if (scaledIconSize < dir.minSize * dir.scale)
690 return dir.minSize * dir.scale - scaledIconSize;
691 else if (scaledIconSize > dir.maxSize * dir.scale)
692 return scaledIconSize - dir.maxSize * dir.scale;
693 else
694 return 0;
695
696 } else if (dir.type == QIconDirInfo::Threshold) {
697 if (scaledIconSize < (dir.size - dir.threshold) * dir.scale)
698 return dir.minSize * dir.scale - scaledIconSize;
699 else if (scaledIconSize > (dir.size + dir.threshold) * dir.scale)
700 return scaledIconSize - dir.maxSize * dir.scale;
701 else return 0;
702 } else if (dir.type == QIconDirInfo::Fallback) {
703 return 0;
704 }
705
706 Q_ASSERT(1); // Not a valid value
707 return INT_MAX;
708}
709
710QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QThemeIconInfo &info, const QSize &size, int scale)
711{
712 int iconsize = qMin(a: size.width(), b: size.height());
713
714 // Note that m_info.entries are sorted so that png-files
715 // come first
716
717 const int numEntries = info.entries.size();
718
719 // Search for exact matches first
720 for (int i = 0; i < numEntries; ++i) {
721 QIconLoaderEngineEntry *entry = info.entries.at(i);
722 if (directoryMatchesSize(dir: entry->dir, iconsize, iconscale: scale)) {
723 return entry;
724 }
725 }
726
727 // Find the minimum distance icon
728 int minimalSize = INT_MAX;
729 QIconLoaderEngineEntry *closestMatch = nullptr;
730 for (int i = 0; i < numEntries; ++i) {
731 QIconLoaderEngineEntry *entry = info.entries.at(i);
732 int distance = directorySizeDistance(dir: entry->dir, iconsize, iconscale: scale);
733 if (distance < minimalSize) {
734 minimalSize = distance;
735 closestMatch = entry;
736 }
737 }
738 return closestMatch;
739}
740
741/*
742 * Returns the actual icon size. For scalable svg's this is equivalent
743 * to the requested size. Otherwise the closest match is returned but
744 * we can never return a bigger size than the requested size.
745 *
746 */
747QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode,
748 QIcon::State state)
749{
750 Q_UNUSED(mode);
751 Q_UNUSED(state);
752
753 ensureLoaded();
754
755 QIconLoaderEngineEntry *entry = entryForSize(info: m_info, size);
756 if (entry) {
757 const QIconDirInfo &dir = entry->dir;
758 if (dir.type == QIconDirInfo::Scalable) {
759 return size;
760 } else if (dir.type == QIconDirInfo::Fallback) {
761 return QIcon(entry->filename).actualSize(size, mode, state);
762 } else {
763 int result = qMin<int>(a: dir.size, b: qMin(a: size.width(), b: size.height()));
764 return QSize(result, result);
765 }
766 }
767 return QSize(0, 0);
768}
769
770QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
771{
772 Q_UNUSED(state);
773
774 // Ensure that basePixmap is lazily initialized before generating the
775 // key, otherwise the cache key is not unique
776 if (basePixmap.isNull())
777 basePixmap.load(fileName: filename);
778
779 QSize actualSize = basePixmap.size();
780 // If the size of the best match we have (basePixmap) is larger than the
781 // requested size, we downscale it to match.
782 if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
783 actualSize.scale(s: size, mode: Qt::KeepAspectRatio);
784
785 QString key = QLatin1String("$qt_theme_")
786 % HexString<qint64>(basePixmap.cacheKey())
787 % HexString<int>(mode)
788 % HexString<qint64>(QGuiApplication::palette().cacheKey())
789 % HexString<int>(actualSize.width())
790 % HexString<int>(actualSize.height());
791
792 QPixmap cachedPixmap;
793 if (QPixmapCache::find(key, pixmap: &cachedPixmap)) {
794 return cachedPixmap;
795 } else {
796 if (basePixmap.size() != actualSize)
797 cachedPixmap = basePixmap.scaled(s: actualSize, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation);
798 else
799 cachedPixmap = basePixmap;
800 if (QGuiApplication *guiApp = qobject_cast<QGuiApplication *>(qApp))
801 cachedPixmap = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(o: guiApp))->applyQIconStyleHelper(mode, basePixmap: cachedPixmap);
802 QPixmapCache::insert(key, pixmap: cachedPixmap);
803 }
804 return cachedPixmap;
805}
806
807QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
808{
809 if (svgIcon.isNull())
810 svgIcon = QIcon(filename);
811
812 // Bypass QIcon API, as that will scale by device pixel ratio of the
813 // highest DPR screen since we're not passing on any QWindow.
814 if (QIconEngine *engine = svgIcon.data_ptr() ? svgIcon.data_ptr()->engine : nullptr)
815 return engine->pixmap(size, mode, state);
816
817 return QPixmap();
818}
819
820QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode,
821 QIcon::State state)
822{
823 ensureLoaded();
824
825 QIconLoaderEngineEntry *entry = entryForSize(info: m_info, size);
826 if (entry)
827 return entry->pixmap(size, mode, state);
828
829 return QPixmap();
830}
831
832QString QIconLoaderEngine::key() const
833{
834 return QLatin1String("QIconLoaderEngine");
835}
836
837void QIconLoaderEngine::virtual_hook(int id, void *data)
838{
839 ensureLoaded();
840
841 switch (id) {
842 case QIconEngine::AvailableSizesHook:
843 {
844 QIconEngine::AvailableSizesArgument &arg
845 = *reinterpret_cast<QIconEngine::AvailableSizesArgument*>(data);
846 const int N = m_info.entries.size();
847 QList<QSize> sizes;
848 sizes.reserve(alloc: N);
849
850 // Gets all sizes from the DirectoryInfo entries
851 for (int i = 0; i < N; ++i) {
852 const QIconLoaderEngineEntry *entry = m_info.entries.at(i);
853 if (entry->dir.type == QIconDirInfo::Fallback) {
854 sizes.append(t: QIcon(entry->filename).availableSizes());
855 } else {
856 int size = entry->dir.size;
857 sizes.append(t: QSize(size, size));
858 }
859 }
860 arg.sizes.swap(other&: sizes); // commit
861 }
862 break;
863 case QIconEngine::IconNameHook:
864 {
865 QString &name = *reinterpret_cast<QString*>(data);
866 name = m_info.iconName;
867 }
868 break;
869 case QIconEngine::IsNullHook:
870 {
871 *reinterpret_cast<bool*>(data) = m_info.entries.isEmpty();
872 }
873 break;
874 case QIconEngine::ScaledPixmapHook:
875 {
876 QIconEngine::ScaledPixmapArgument &arg = *reinterpret_cast<QIconEngine::ScaledPixmapArgument*>(data);
877 // QIcon::pixmap() multiplies size by the device pixel ratio.
878 const int integerScale = qCeil(v: arg.scale);
879 QIconLoaderEngineEntry *entry = entryForSize(info: m_info, size: arg.size / integerScale, scale: integerScale);
880 arg.pixmap = entry ? entry->pixmap(size: arg.size, mode: arg.mode, state: arg.state) : QPixmap();
881 }
882 break;
883 default:
884 QIconEngine::virtual_hook(id, data);
885 }
886}
887
888QT_END_NAMESPACE
889
890#endif //QT_NO_ICON
891

source code of qtbase/src/gui/image/qiconloader.cpp