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