Coverage Report

Created: 2025-12-31 07:23

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