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