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