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 const qreal dpr = !qApp->testAttribute(Qt::AA_UseHighDpiPixmaps) ?
633 qreal(1.0) : painter->device()->devicePixelRatioF();
634
635 QSize pixmapSize = rect.size() * dpr;
636 painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
637}
638
639/*
640 * This algorithm is defined by the freedesktop spec:
641 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
642 */
643static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize, int iconscale)
644{
645 if (dir.scale != iconscale)
646 return false;
647
648 if (dir.type == QIconDirInfo::Fixed) {
649 return dir.size == iconsize;
650
651 } else if (dir.type == QIconDirInfo::Scalable) {
652 return iconsize <= dir.maxSize &&
653 iconsize >= dir.minSize;
654
655 } else if (dir.type == QIconDirInfo::Threshold) {
656 return iconsize >= dir.size - dir.threshold &&
657 iconsize <= dir.size + dir.threshold;
658 } else if (dir.type == QIconDirInfo::Fallback) {
659 return true;
660 }
661
662 Q_ASSERT(1); // Not a valid value
663 return false;
664}
665
666/*
667 * This algorithm is defined by the freedesktop spec:
668 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
669 */
670static int directorySizeDistance(const QIconDirInfo &dir, int iconsize, int iconscale)
671{
672 const int scaledIconSize = iconsize * iconscale;
673 if (dir.type == QIconDirInfo::Fixed) {
674 return qAbs(dir.size * dir.scale - scaledIconSize);
675
676 } else if (dir.type == QIconDirInfo::Scalable) {
677 if (scaledIconSize < dir.minSize * dir.scale)
678 return dir.minSize * dir.scale - scaledIconSize;
679 else if (scaledIconSize > dir.maxSize * dir.scale)
680 return scaledIconSize - dir.maxSize * dir.scale;
681 else
682 return 0;
683
684 } else if (dir.type == QIconDirInfo::Threshold) {
685 if (scaledIconSize < (dir.size - dir.threshold) * dir.scale)
686 return dir.minSize * dir.scale - scaledIconSize;
687 else if (scaledIconSize > (dir.size + dir.threshold) * dir.scale)
688 return scaledIconSize - dir.maxSize * dir.scale;
689 else return 0;
690 } else if (dir.type == QIconDirInfo::Fallback) {
691 return 0;
692 }
693
694 Q_ASSERT(1); // Not a valid value
695 return INT_MAX;
696}
697
698QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QThemeIconInfo &info, const QSize &size, int scale)
699{
700 int iconsize = qMin(size.width(), size.height());
701
702 // Note that m_info.entries are sorted so that png-files
703 // come first
704
705 const int numEntries = info.entries.size();
706
707 // Search for exact matches first
708 for (int i = 0; i < numEntries; ++i) {
709 QIconLoaderEngineEntry *entry = info.entries.at(i);
710 if (directoryMatchesSize(entry->dir, iconsize, scale)) {
711 return entry;
712 }
713 }
714
715 // Find the minimum distance icon
716 int minimalSize = INT_MAX;
717 QIconLoaderEngineEntry *closestMatch = 0;
718 for (int i = 0; i < numEntries; ++i) {
719 QIconLoaderEngineEntry *entry = info.entries.at(i);
720 int distance = directorySizeDistance(entry->dir, iconsize, scale);
721 if (distance < minimalSize) {
722 minimalSize = distance;
723 closestMatch = entry;
724 }
725 }
726 return closestMatch;
727}
728
729/*
730 * Returns the actual icon size. For scalable svg's this is equivalent
731 * to the requested size. Otherwise the closest match is returned but
732 * we can never return a bigger size than the requested size.
733 *
734 */
735QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode,
736 QIcon::State state)
737{
738 Q_UNUSED(mode);
739 Q_UNUSED(state);
740
741 ensureLoaded();
742
743 QIconLoaderEngineEntry *entry = entryForSize(m_info, size);
744 if (entry) {
745 const QIconDirInfo &dir = entry->dir;
746 if (dir.type == QIconDirInfo::Scalable) {
747 return size;
748 } else if (dir.type == QIconDirInfo::Fallback) {
749 return QIcon(entry->filename).actualSize(size, mode, state);
750 } else {
751 int result = qMin<int>(dir.size, qMin(size.width(), size.height()));
752 return QSize(result, result);
753 }
754 }
755 return QSize(0, 0);
756}
757
758QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
759{
760 Q_UNUSED(state);
761
762 // Ensure that basePixmap is lazily initialized before generating the
763 // key, otherwise the cache key is not unique
764 if (basePixmap.isNull())
765 basePixmap.load(filename);
766
767 QSize actualSize = basePixmap.size();
768 // If the size of the best match we have (basePixmap) is larger than the
769 // requested size, we downscale it to match.
770 if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
771 actualSize.scale(size, Qt::KeepAspectRatio);
772
773 QString key = QLatin1String("$qt_theme_")
774 % HexString<qint64>(basePixmap.cacheKey())
775 % HexString<int>(mode)
776 % HexString<qint64>(QGuiApplication::palette().cacheKey())
777 % HexString<int>(actualSize.width())
778 % HexString<int>(actualSize.height());
779
780 QPixmap cachedPixmap;
781 if (QPixmapCache::find(key, &cachedPixmap)) {
782 return cachedPixmap;
783 } else {
784 if (basePixmap.size() != actualSize)
785 cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
786 else
787 cachedPixmap = basePixmap;
788 if (QGuiApplication *guiApp = qobject_cast<QGuiApplication *>(qApp))
789 cachedPixmap = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(guiApp))->applyQIconStyleHelper(mode, cachedPixmap);
790 QPixmapCache::insert(key, cachedPixmap);
791 }
792 return cachedPixmap;
793}
794
795QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
796{
797 if (svgIcon.isNull())
798 svgIcon = QIcon(filename);
799
800 // Simply reuse svg icon engine
801 return svgIcon.pixmap(size, mode, state);
802}
803
804QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode,
805 QIcon::State state)
806{
807 ensureLoaded();
808
809 QIconLoaderEngineEntry *entry = entryForSize(m_info, size);
810 if (entry)
811 return entry->pixmap(size, mode, state);
812
813 return QPixmap();
814}
815
816QString QIconLoaderEngine::key() const
817{
818 return QLatin1String("QIconLoaderEngine");
819}
820
821void QIconLoaderEngine::virtual_hook(int id, void *data)
822{
823 ensureLoaded();
824
825 switch (id) {
826 case QIconEngine::AvailableSizesHook:
827 {
828 QIconEngine::AvailableSizesArgument &arg
829 = *reinterpret_cast<QIconEngine::AvailableSizesArgument*>(data);
830 const int N = m_info.entries.size();
831 QList<QSize> sizes;
832 sizes.reserve(N);
833
834 // Gets all sizes from the DirectoryInfo entries
835 for (int i = 0; i < N; ++i) {
836 const QIconLoaderEngineEntry *entry = m_info.entries.at(i);
837 if (entry->dir.type == QIconDirInfo::Fallback) {
838 sizes.append(QIcon(entry->filename).availableSizes());
839 } else {
840 int size = entry->dir.size;
841 sizes.append(QSize(size, size));
842 }
843 }
844 arg.sizes.swap(sizes); // commit
845 }
846 break;
847 case QIconEngine::IconNameHook:
848 {
849 QString &name = *reinterpret_cast<QString*>(data);
850 name = m_info.iconName;
851 }
852 break;
853 case QIconEngine::IsNullHook:
854 {
855 *reinterpret_cast<bool*>(data) = m_info.entries.isEmpty();
856 }
857 break;
858 case QIconEngine::ScaledPixmapHook:
859 {
860 QIconEngine::ScaledPixmapArgument &arg = *reinterpret_cast<QIconEngine::ScaledPixmapArgument*>(data);
861 // QIcon::pixmap() multiplies size by the device pixel ratio.
862 const int integerScale = qCeil(arg.scale);
863 QIconLoaderEngineEntry *entry = entryForSize(m_info, arg.size / integerScale, integerScale);
864 arg.pixmap = entry ? entry->pixmap(arg.size, arg.mode, arg.state) : QPixmap();
865 }
866 break;
867 default:
868 QIconEngine::virtual_hook(id, data);
869 }
870}
871
872QT_END_NAMESPACE
873
874#endif //QT_NO_ICON
875