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(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("QT_QPA_SYSTEM_ICON_THEME");
83 if (!override.isEmpty())
84 return QString::fromLocal8Bit(override);
85 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) {
86 const QVariant themeHint = theme->themeHint(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(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(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(QLatin1String("svg"), -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(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 QList<const char *> lookup(QStringView);
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(QFile::ReadOnly))
256 return;
257 m_size = m_file.size();
258 m_data = m_file.map(0, m_size);
259 if (!m_data)
260 return;
261 if (read16(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(8);
269 quint32 dirListLen = read32(dirListOffset);
270 for (uint i = 0; i < dirListLen; ++i) {
271 quint32 offset = read32(dirListOffset + 4 + 4 * i);
272 if (!m_isValid || offset >= m_size || lastModified < QFileInfo(dirName + QLatin1Char('/')
273 + QString::fromUtf8(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 */
293QList<const char *> QIconCacheGtkReader::lookup(QStringView name)
294{
295 QList<const char *> ret;
296 if (!isValid() || name.isEmpty())
297 return ret;
298
299 QByteArray nameUtf8 = name.toUtf8();
300 quint32 hash = icon_name_hash(nameUtf8);
301
302 quint32 hashOffset = read32(4);
303 quint32 hashBucketCount = read32(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(hashOffset + 4 + bucketIndex * 4);
312 while (bucketOffset > 0 && bucketOffset <= m_size - 12) {
313 quint32 nameOff = read32(bucketOffset + 4);
314 if (nameOff < m_size && strcmp(reinterpret_cast<const char*>(m_data + nameOff), nameUtf8) == 0) {
315 quint32 dirListOffset = read32(8);
316 quint32 dirListLen = read32(dirListOffset);
317
318 quint32 listOffset = read32(bucketOffset+8);
319 quint32 listLen = read32(listOffset);
320
321 if (!m_isValid || listOffset + 4 + 8 * listLen > m_size) {
322 m_isValid = false;
323 return ret;
324 }
325
326 ret.reserve(listLen);
327 for (uint j = 0; j < listLen && m_isValid; ++j) {
328 quint32 dirIndex = read16(listOffset + 4 + 8 * j);
329 quint32 o = read32(dirListOffset + 4 + dirIndex*4);
330 if (!m_isValid || dirIndex >= dirListLen || o >= m_size) {
331 m_isValid = false;
332 return ret;
333 }
334 ret.append(reinterpret_cast<const char*>(m_data) + o);
335 }
336 return ret;
337 }
338 bucketOffset = read32(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(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(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(key.size() - 5);
375 QIconDirInfo dirInfo(directoryKey);
376 dirInfo.size = size;
377 QString type = indexReader.value(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(directoryKey +
389 QLatin1String("/Threshold"),
390 2).toInt();
391
392 dirInfo.minSize = indexReader.value(directoryKey +
393 QLatin1String("/MinSize"),
394 size).toInt();
395
396 dirInfo.maxSize = indexReader.value(directoryKey +
397 QLatin1String("/MaxSize"),
398 size).toInt();
399
400 dirInfo.scale = indexReader.value(directoryKey +
401 QLatin1String("/Scale"),
402 1).toInt();
403 m_keyList.append(dirInfo);
404 }
405 }
406 }
407
408 // Parent themes provide fallbacks for missing icons
409 m_parents = indexReader.value(
410 QLatin1String("Icon Theme/Inherits")).toStringList();
411 m_parents.removeAll(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(fallback);
418 }
419
420 // Ensure that all themes fall back to hicolor
421 if (!m_parents.contains(QLatin1String("hicolor")))
422 m_parents.append(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 QStringView 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 QList<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(iconNameFallback);
462 if (cache->isValid()) {
463 const QList<QIconDirInfo> subDirsCopy = subDirs;
464 subDirs.clear();
465 subDirs.reserve(result.count());
466 for (const char *s : result) {
467 QString path = QString::fromUtf8(s);
468 auto it = std::find_if(subDirsCopy.cbegin(), subDirsCopy.cend(),
469 [&](const QIconDirInfo &info) {
470 return info.path == path; } );
471 if (it != subDirsCopy.cend()) {
472 subDirs.append(*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(j);
481 const QString subDir = contentDir + dirInfo.path + QLatin1Char('/');
482 const QString pngPath = subDir + pngIconName;
483 if (QFile::exists(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(iconEntry);
490 } else if (m_supportsSvg) {
491 const QString svgPath = subDir + svgIconName;
492 if (QFile::exists(svgPath)) {
493 ScalableEntry *iconEntry = new ScalableEntry;
494 iconEntry->dir = dirInfo;
495 iconEntry->filename = svgPath;
496 info.entries.append(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(QLatin1Char('-'));
509 if (indexOfDash == -1)
510 break;
511
512 iconNameFallback.truncate(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(parentTheme)) // guard against recursion
523 info = findIconHelper(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(pngIconName)) {
544 PixmapEntry *iconEntry = new PixmapEntry;
545 iconEntry->dir.type = QIconDirInfo::Fallback;
546 iconEntry->filename = currentDir.filePath(pngIconName);
547 info.entries.append(iconEntry);
548 break;
549 } else if (currentDir.exists(xpmIconName)) {
550 PixmapEntry *iconEntry = new PixmapEntry;
551 iconEntry->dir.type = QIconDirInfo::Fallback;
552 iconEntry->filename = currentDir.filePath(xpmIconName);
553 info.entries.append(iconEntry);
554 break;
555 } else if (m_supportsSvg &&
556 currentDir.exists(svgIconName)) {
557 ScalableEntry *iconEntry = new ScalableEntry;
558 iconEntry->dir.type = QIconDirInfo::Fallback;
559 iconEntry->filename = currentDir.filePath(svgIconName);
560 info.entries.append(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(), name, visited);
576 if (!iconInfo.entries.isEmpty())
577 return iconInfo;
578
579 return lookupFallbackIcon(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(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(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(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 QSize pixmapSize = rect.size() * painter->device()->devicePixelRatio();
645 painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
646}
647
648/*
649 * This algorithm is defined by the freedesktop spec:
650 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
651 */
652static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize, int iconscale)
653{
654 if (dir.scale != iconscale)
655 return false;
656
657 if (dir.type == QIconDirInfo::Fixed) {
658 return dir.size == iconsize;
659
660 } else if (dir.type == QIconDirInfo::Scalable) {
661 return iconsize <= dir.maxSize &&
662 iconsize >= dir.minSize;
663
664 } else if (dir.type == QIconDirInfo::Threshold) {
665 return iconsize >= dir.size - dir.threshold &&
666 iconsize <= dir.size + dir.threshold;
667 } else if (dir.type == QIconDirInfo::Fallback) {
668 return true;
669 }
670
671 Q_ASSERT(1); // Not a valid value
672 return false;
673}
674
675/*
676 * This algorithm is defined by the freedesktop spec:
677 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
678 */
679static int directorySizeDistance(const QIconDirInfo &dir, int iconsize, int iconscale)
680{
681 const int scaledIconSize = iconsize * iconscale;
682 if (dir.type == QIconDirInfo::Fixed) {
683 return qAbs(dir.size * dir.scale - scaledIconSize);
684
685 } else if (dir.type == QIconDirInfo::Scalable) {
686 if (scaledIconSize < dir.minSize * dir.scale)
687 return dir.minSize * dir.scale - scaledIconSize;
688 else if (scaledIconSize > dir.maxSize * dir.scale)
689 return scaledIconSize - dir.maxSize * dir.scale;
690 else
691 return 0;
692
693 } else if (dir.type == QIconDirInfo::Threshold) {
694 if (scaledIconSize < (dir.size - dir.threshold) * dir.scale)
695 return dir.minSize * dir.scale - scaledIconSize;
696 else if (scaledIconSize > (dir.size + dir.threshold) * dir.scale)
697 return scaledIconSize - dir.maxSize * dir.scale;
698 else return 0;
699 } else if (dir.type == QIconDirInfo::Fallback) {
700 return 0;
701 }
702
703 Q_ASSERT(1); // Not a valid value
704 return INT_MAX;
705}
706
707QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QThemeIconInfo &info, const QSize &size, int scale)
708{
709 int iconsize = qMin(size.width(), size.height());
710
711 // Note that m_info.entries are sorted so that png-files
712 // come first
713
714 const int numEntries = info.entries.size();
715
716 // Search for exact matches first
717 for (int i = 0; i < numEntries; ++i) {
718 QIconLoaderEngineEntry *entry = info.entries.at(i);
719 if (directoryMatchesSize(entry->dir, iconsize, scale)) {
720 return entry;
721 }
722 }
723
724 // Find the minimum distance icon
725 int minimalSize = INT_MAX;
726 QIconLoaderEngineEntry *closestMatch = nullptr;
727 for (int i = 0; i < numEntries; ++i) {
728 QIconLoaderEngineEntry *entry = info.entries.at(i);
729 int distance = directorySizeDistance(entry->dir, iconsize, scale);
730 if (distance < minimalSize) {
731 minimalSize = distance;
732 closestMatch = entry;
733 }
734 }
735 return closestMatch;
736}
737
738/*
739 * Returns the actual icon size. For scalable svg's this is equivalent
740 * to the requested size. Otherwise the closest match is returned but
741 * we can never return a bigger size than the requested size.
742 *
743 */
744QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode,
745 QIcon::State state)
746{
747 Q_UNUSED(mode);
748 Q_UNUSED(state);
749
750 ensureLoaded();
751
752 QIconLoaderEngineEntry *entry = entryForSize(m_info, size);
753 if (entry) {
754 const QIconDirInfo &dir = entry->dir;
755 if (dir.type == QIconDirInfo::Scalable) {
756 return size;
757 } else if (dir.type == QIconDirInfo::Fallback) {
758 return QIcon(entry->filename).actualSize(size, mode, state);
759 } else {
760 int result = qMin<int>(dir.size, qMin(size.width(), size.height()));
761 return QSize(result, result);
762 }
763 }
764 return QSize(0, 0);
765}
766
767QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
768{
769 Q_UNUSED(state);
770
771 // Ensure that basePixmap is lazily initialized before generating the
772 // key, otherwise the cache key is not unique
773 if (basePixmap.isNull())
774 basePixmap.load(filename);
775
776 QSize actualSize = basePixmap.size();
777 // If the size of the best match we have (basePixmap) is larger than the
778 // requested size, we downscale it to match.
779 if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
780 actualSize.scale(size, Qt::KeepAspectRatio);
781
782 QString key = QLatin1String("$qt_theme_")
783 % HexString<qint64>(basePixmap.cacheKey())
784 % HexString<int>(mode)
785 % HexString<qint64>(QGuiApplication::palette().cacheKey())
786 % HexString<int>(actualSize.width())
787 % HexString<int>(actualSize.height());
788
789 QPixmap cachedPixmap;
790 if (QPixmapCache::find(key, &cachedPixmap)) {
791 return cachedPixmap;
792 } else {
793 if (basePixmap.size() != actualSize)
794 cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
795 else
796 cachedPixmap = basePixmap;
797 if (QGuiApplication *guiApp = qobject_cast<QGuiApplication *>(qApp))
798 cachedPixmap = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(guiApp))->applyQIconStyleHelper(mode, cachedPixmap);
799 QPixmapCache::insert(key, cachedPixmap);
800 }
801 return cachedPixmap;
802}
803
804QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
805{
806 if (svgIcon.isNull())
807 svgIcon = QIcon(filename);
808
809 // Bypass QIcon API, as that will scale by device pixel ratio of the
810 // highest DPR screen since we're not passing on any QWindow.
811 if (QIconEngine *engine = svgIcon.data_ptr() ? svgIcon.data_ptr()->engine : nullptr)
812 return engine->pixmap(size, mode, state);
813
814 return QPixmap();
815}
816
817QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode,
818 QIcon::State state)
819{
820 ensureLoaded();
821
822 QIconLoaderEngineEntry *entry = entryForSize(m_info, size);
823 if (entry)
824 return entry->pixmap(size, mode, state);
825
826 return QPixmap();
827}
828
829QString QIconLoaderEngine::key() const
830{
831 return QLatin1String("QIconLoaderEngine");
832}
833
834QString QIconLoaderEngine::iconName()
835{
836 ensureLoaded();
837 return m_info.iconName;
838}
839
840bool QIconLoaderEngine::isNull()
841{
842 ensureLoaded();
843 return m_info.entries.isEmpty();
844}
845
846QPixmap QIconLoaderEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
847{
848 ensureLoaded();
849 const int integerScale = qCeil(scale);
850 QIconLoaderEngineEntry *entry = entryForSize(m_info, size / integerScale, integerScale);
851 return entry ? entry->pixmap(size, mode, state) : QPixmap();
852}
853
854QList<QSize> QIconLoaderEngine::availableSizes(QIcon::Mode mode, QIcon::State state)
855{
856 Q_UNUSED(mode);
857 Q_UNUSED(state);
858 ensureLoaded();
859 const int N = m_info.entries.size();
860 QList<QSize> sizes;
861 sizes.reserve(N);
862
863 // Gets all sizes from the DirectoryInfo entries
864 for (int i = 0; i < N; ++i) {
865 const QIconLoaderEngineEntry *entry = m_info.entries.at(i);
866 if (entry->dir.type == QIconDirInfo::Fallback) {
867 sizes.append(QIcon(entry->filename).availableSizes());
868 } else {
869 int size = entry->dir.size;
870 sizes.append(QSize(size, size));
871 }
872 }
873 return sizes;
874}
875
876QT_END_NAMESPACE
877
878#endif //QT_NO_ICON
879