// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QT_NO_ICON #include #include #include #include #include #include #include #include #include #include #include #include #if QT_CONFIG(settings) #include #endif #include #include QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcIconLoader, "qt.gui.icon.loader") using namespace Qt::StringLiterals; Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) /* Theme to use in last resort, if the theme does not have the icon, neither the parents */ static QString systemFallbackThemeName() { if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { const QVariant themeHint = theme->themeHint(QPlatformTheme::SystemIconFallbackThemeName); if (themeHint.isValid()) return themeHint.toString(); } return QString(); } QIconLoader::QIconLoader() : m_themeKey(1), m_supportsSvg(false), m_initialized(false) { } static inline QString systemThemeName() { const auto override = qgetenv("QT_QPA_SYSTEM_ICON_THEME"); if (!override.isEmpty()) return QString::fromLocal8Bit(override); if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { const QVariant themeHint = theme->themeHint(QPlatformTheme::SystemIconThemeName); if (themeHint.isValid()) return themeHint.toString(); } return QString(); } static inline QStringList systemIconSearchPaths() { if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { const QVariant themeHint = theme->themeHint(QPlatformTheme::IconThemeSearchPaths); if (themeHint.isValid()) return themeHint.toStringList(); } return QStringList(); } static inline QStringList systemFallbackSearchPaths() { if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { const QVariant themeHint = theme->themeHint(QPlatformTheme::IconFallbackSearchPaths); if (themeHint.isValid()) return themeHint.toStringList(); } return QStringList(); } extern QFactoryLoader *qt_iconEngineFactoryLoader(); // qicon.cpp void QIconLoader::ensureInitialized() { if (!m_initialized) { if (!QGuiApplicationPrivate::platformTheme()) return; // it's too early: try again later (QTBUG-74252) m_initialized = true; m_systemTheme = systemThemeName(); if (m_systemTheme.isEmpty()) m_systemTheme = systemFallbackThemeName(); if (qt_iconEngineFactoryLoader()->keyMap().key("svg"_L1, -1) != -1) m_supportsSvg = true; qCDebug(lcIconLoader) << "Initialized icon loader with system theme" << m_systemTheme << "and SVG support" << m_supportsSvg; } } /*! \internal Gets an instance. \l QIcon::setFallbackThemeName() should be called before QGuiApplication is created, to avoid a race condition (QTBUG-74252). When this function is called from there, ensureInitialized() does not succeed because there is no QPlatformTheme yet, so systemThemeName() is empty, and we don't want m_systemTheme to get initialized to the fallback theme instead of the normal one. */ QIconLoader *QIconLoader::instance() { iconLoaderInstance()->ensureInitialized(); return iconLoaderInstance(); } // Queries the system theme and invalidates existing // icons if the theme has changed. void QIconLoader::updateSystemTheme() { const QString currentSystemTheme = m_systemTheme; m_systemTheme = systemThemeName(); if (m_systemTheme.isEmpty()) m_systemTheme = systemFallbackThemeName(); if (m_systemTheme != currentSystemTheme) qCDebug(lcIconLoader) << "Updated system theme to" << m_systemTheme; // Invalidate even if the system theme name hasn't changed, as the // theme itself may have changed its underlying icon lookup logic. if (!hasUserTheme()) invalidateKey(); } void QIconLoader::invalidateKey() { // Invalidating the key here will result in QThemeIconEngine // recreating the actual engine the next time the icon is used. // We don't need to clear the QIcon cache itself. m_themeKey++; } QString QIconLoader::themeName() const { return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; } void QIconLoader::setThemeName(const QString &themeName) { if (m_userTheme == themeName) return; qCDebug(lcIconLoader) << "Setting user theme name to" << themeName; const bool hadUserTheme = hasUserTheme(); m_userTheme = themeName; // if we cleared the user theme, then reset search paths as well, // otherwise we'll keep looking in the user-defined search paths for // a system-provide theme, which will never work. if (!hasUserTheme() && hadUserTheme) setThemeSearchPath(systemIconSearchPaths()); invalidateKey(); } QString QIconLoader::fallbackThemeName() const { return m_userFallbackTheme.isEmpty() ? systemFallbackThemeName() : m_userFallbackTheme; } void QIconLoader::setFallbackThemeName(const QString &themeName) { qCDebug(lcIconLoader) << "Setting fallback theme name to" << themeName; m_userFallbackTheme = themeName; invalidateKey(); } void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) { qCDebug(lcIconLoader) << "Setting theme search path to" << searchPaths; m_iconDirs = searchPaths; themeList.clear(); invalidateKey(); } QStringList QIconLoader::themeSearchPaths() const { if (m_iconDirs.isEmpty()) { m_iconDirs = systemIconSearchPaths(); // Always add resource directory as search path m_iconDirs.append(":/icons"_L1); } return m_iconDirs; } void QIconLoader::setFallbackSearchPaths(const QStringList &searchPaths) { qCDebug(lcIconLoader) << "Setting fallback search path to" << searchPaths; m_fallbackDirs = searchPaths; invalidateKey(); } QStringList QIconLoader::fallbackSearchPaths() const { if (m_fallbackDirs.isEmpty()) { m_fallbackDirs = systemFallbackSearchPaths(); } return m_fallbackDirs; } /*! \internal Helper class that reads and looks up into the icon-theme.cache generated with gtk-update-icon-cache. If at any point we detect a corruption in the file (because the offsets point at wrong locations for example), the reader is marked as invalid. */ class QIconCacheGtkReader { public: explicit QIconCacheGtkReader(const QString &themeDir); QList lookup(QStringView); bool isValid() const { return m_isValid; } private: QFile m_file; const unsigned char *m_data; quint64 m_size; bool m_isValid; quint16 read16(uint offset) { if (offset > m_size - 2 || (offset & 0x1)) { m_isValid = false; return 0; } return m_data[offset+1] | m_data[offset] << 8; } quint32 read32(uint offset) { if (offset > m_size - 4 || (offset & 0x3)) { m_isValid = false; return 0; } return m_data[offset+3] | m_data[offset+2] << 8 | m_data[offset+1] << 16 | m_data[offset] << 24; } }; QIconCacheGtkReader::QIconCacheGtkReader(const QString &dirName) : m_isValid(false) { QFileInfo info(dirName + "/icon-theme.cache"_L1); if (!info.exists() || info.lastModified(QTimeZone::UTC) < QFileInfo(dirName).lastModified(QTimeZone::UTC)) return; m_file.setFileName(info.absoluteFilePath()); if (!m_file.open(QFile::ReadOnly)) return; m_size = m_file.size(); m_data = m_file.map(0, m_size); if (!m_data) return; if (read16(0) != 1) // VERSION_MAJOR return; m_isValid = true; // Check that all the directories are older than the cache const QDateTime lastModified = info.lastModified(QTimeZone::UTC); quint32 dirListOffset = read32(8); quint32 dirListLen = read32(dirListOffset); for (uint i = 0; i < dirListLen; ++i) { quint32 offset = read32(dirListOffset + 4 + 4 * i); if (!m_isValid || offset >= m_size || lastModified < QFileInfo(dirName + u'/' + QString::fromUtf8(reinterpret_cast(m_data + offset))).lastModified(QTimeZone::UTC)) { m_isValid = false; return; } } } static quint32 icon_name_hash(const char *p) { quint32 h = static_cast(*p); for (p += 1; *p != '\0'; p++) h = (h << 5) - h + *p; return h; } /*! \internal lookup the icon name and return the list of subdirectories in which an icon with this name is present. The char* are pointers to the mapped data. For example, this would return { "32x32/apps", "24x24/apps" , ... } */ QList QIconCacheGtkReader::lookup(QStringView name) { QList ret; if (!isValid() || name.isEmpty()) return ret; QByteArray nameUtf8 = name.toUtf8(); quint32 hash = icon_name_hash(nameUtf8); quint32 hashOffset = read32(4); quint32 hashBucketCount = read32(hashOffset); if (!isValid() || hashBucketCount == 0) { m_isValid = false; return ret; } quint32 bucketIndex = hash % hashBucketCount; quint32 bucketOffset = read32(hashOffset + 4 + bucketIndex * 4); while (bucketOffset > 0 && bucketOffset <= m_size - 12) { quint32 nameOff = read32(bucketOffset + 4); if (nameOff < m_size && strcmp(reinterpret_cast(m_data + nameOff), nameUtf8) == 0) { quint32 dirListOffset = read32(8); quint32 dirListLen = read32(dirListOffset); quint32 listOffset = read32(bucketOffset+8); quint32 listLen = read32(listOffset); if (!m_isValid || listOffset + 4 + 8 * listLen > m_size) { m_isValid = false; return ret; } ret.reserve(listLen); for (uint j = 0; j < listLen && m_isValid; ++j) { quint32 dirIndex = read16(listOffset + 4 + 8 * j); quint32 o = read32(dirListOffset + 4 + dirIndex*4); if (!m_isValid || dirIndex >= dirListLen || o >= m_size) { m_isValid = false; return ret; } ret.append(reinterpret_cast(m_data) + o); } return ret; } bucketOffset = read32(bucketOffset); } return ret; } QIconTheme::QIconTheme(const QString &themeName) : m_valid(false) { QFile themeIndex; const QStringList iconDirs = QIcon::themeSearchPaths(); for ( int i = 0 ; i < iconDirs.size() ; ++i) { QDir iconDir(iconDirs[i]); QString themeDir = iconDir.path() + u'/' + themeName; QFileInfo themeDirInfo(themeDir); if (themeDirInfo.isDir()) { m_contentDirs << themeDir; m_gtkCaches << QSharedPointer::create(themeDir); } if (!m_valid) { themeIndex.setFileName(themeDir + "/index.theme"_L1); m_valid = themeIndex.exists(); qCDebug(lcIconLoader) << "Probing theme file at" << themeIndex.fileName() << m_valid; } } #if QT_CONFIG(settings) if (m_valid) { const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); const QStringList keys = indexReader.allKeys(); for (const QString &key : keys) { if (key.endsWith("/Size"_L1)) { // Note the QSettings ini-format does not accept // slashes in key names, hence we have to cheat if (int size = indexReader.value(key).toInt()) { QString directoryKey = key.left(key.size() - 5); QIconDirInfo dirInfo(directoryKey); dirInfo.size = size; QString type = indexReader.value(directoryKey + "/Type"_L1).toString(); if (type == "Fixed"_L1) dirInfo.type = QIconDirInfo::Fixed; else if (type == "Scalable"_L1) dirInfo.type = QIconDirInfo::Scalable; else dirInfo.type = QIconDirInfo::Threshold; dirInfo.threshold = indexReader.value(directoryKey + "/Threshold"_L1, 2).toInt(); dirInfo.minSize = indexReader.value(directoryKey + "/MinSize"_L1, size).toInt(); dirInfo.maxSize = indexReader.value(directoryKey + "/MaxSize"_L1, size).toInt(); dirInfo.scale = indexReader.value(directoryKey + "/Scale"_L1, 1).toInt(); const QString context = indexReader.value(directoryKey + "/Context"_L1).toString(); dirInfo.context = [context]() { if (context == "Applications"_L1) return QIconDirInfo::Applications; else if (context == "MimeTypes"_L1) return QIconDirInfo::MimeTypes; else return QIconDirInfo::UnknownContext; }(); m_keyList.append(dirInfo); } } } // Parent themes provide fallbacks for missing icons m_parents = indexReader.value("Icon Theme/Inherits"_L1).toStringList(); m_parents.removeAll(QString()); } #endif // settings } QStringList QIconTheme::parents() const { // Respect explicitly declared parents QStringList result = m_parents; // Ensure a default fallback for all themes const QString fallback = QIconLoader::instance()->fallbackThemeName(); if (!fallback.isEmpty()) result.append(fallback); // Ensure that all themes fall back to hicolor as the last theme result.removeAll("hicolor"_L1); result.append("hicolor"_L1); return result; } QDebug operator<<(QDebug debug, const std::unique_ptr &entry) { QDebugStateSaver saver(debug); debug.noquote() << entry->filename; return debug; } QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, const QString &iconName, QStringList &visited, DashRule rule) const { qCDebug(lcIconLoader) << "Finding icon" << iconName << "in theme" << themeName << "skipping" << visited; QThemeIconInfo info; Q_ASSERT(!themeName.isEmpty()); // Used to protect against potential recursions visited << themeName; QIconTheme &theme = themeList[themeName]; if (!theme.isValid()) { theme = QIconTheme(themeName); if (!theme.isValid()) { qCDebug(lcIconLoader) << "Theme" << themeName << "not found"; return info; } } const QStringList contentDirs = theme.contentDirs(); QStringView iconNameFallback(iconName); bool searchingGenericFallback = m_iconName.length() > iconName.length(); // Iterate through all icon's fallbacks in current theme if (info.entries.empty()) { const QString svgIconName = iconNameFallback + ".svg"_L1; const QString pngIconName = iconNameFallback + ".png"_L1; // Add all relevant files for (int i = 0; i < contentDirs.size(); ++i) { QList subDirs = theme.keyList(); // Try to reduce the amount of subDirs by looking in the GTK+ cache in order to save // a massive amount of file stat (especially if the icon is not there) auto cache = theme.m_gtkCaches.at(i); if (cache->isValid()) { const auto result = cache->lookup(iconNameFallback); if (cache->isValid()) { const QList subDirsCopy = subDirs; subDirs.clear(); subDirs.reserve(result.size()); for (const char *s : result) { QString path = QString::fromUtf8(s); auto it = std::find_if(subDirsCopy.cbegin(), subDirsCopy.cend(), [&](const QIconDirInfo &info) { return info.path == path; } ); if (it != subDirsCopy.cend()) { subDirs.append(*it); } } } } QString contentDir = contentDirs.at(i) + u'/'; for (int j = 0; j < subDirs.size() ; ++j) { const QIconDirInfo &dirInfo = subDirs.at(j); if (searchingGenericFallback && (dirInfo.context == QIconDirInfo::Applications || dirInfo.context == QIconDirInfo::MimeTypes)) continue; const QString subDir = contentDir + dirInfo.path + u'/'; const QString pngPath = subDir + pngIconName; if (QFile::exists(pngPath)) { auto iconEntry = std::make_unique(); iconEntry->dir = dirInfo; iconEntry->filename = pngPath; // Notice we ensure that pixmap entries always come before // scalable to preserve search order afterwards info.entries.insert(info.entries.begin(), std::move(iconEntry)); } else if (m_supportsSvg) { const QString svgPath = subDir + svgIconName; if (QFile::exists(svgPath)) { auto iconEntry = std::make_unique(); iconEntry->dir = dirInfo; iconEntry->filename = svgPath; info.entries.push_back(std::move(iconEntry)); } } } } if (!info.entries.empty()) { info.iconName = iconNameFallback.toString(); } } if (info.entries.empty()) { const QStringList parents = theme.parents(); qCDebug(lcIconLoader) << "Did not find matching icons in theme;" << "trying parent themes" << parents << "skipping visited" << visited; // Search recursively through inherited themes for (int i = 0 ; i < parents.size() ; ++i) { const QString parentTheme = parents.at(i).trimmed(); if (!visited.contains(parentTheme)) // guard against recursion info = findIconHelper(parentTheme, iconName, visited, QIconLoader::NoFallBack); if (!info.entries.empty()) // success break; } } if (rule == QIconLoader::FallBack && info.entries.empty()) { // If it's possible - find next fallback for the icon const int indexOfDash = iconNameFallback.lastIndexOf(u'-'); if (indexOfDash != -1) { qCDebug(lcIconLoader) << "Did not find matching icons in all themes;" << "trying dash fallback"; iconNameFallback.truncate(indexOfDash); QStringList _visited; info = findIconHelper(themeName, iconNameFallback.toString(), _visited, QIconLoader::FallBack); } } return info; } QThemeIconInfo QIconLoader::lookupFallbackIcon(const QString &iconName) const { qCDebug(lcIconLoader) << "Looking up fallback icon" << iconName; QThemeIconInfo info; const QString pngIconName = iconName + ".png"_L1; const QString xpmIconName = iconName + ".xpm"_L1; const QString svgIconName = iconName + ".svg"_L1; const auto searchPaths = QIcon::fallbackSearchPaths(); for (const QString &iconDir: searchPaths) { QDir currentDir(iconDir); std::unique_ptr iconEntry; if (currentDir.exists(pngIconName)) { iconEntry = std::make_unique(); iconEntry->dir.type = QIconDirInfo::Fallback; iconEntry->filename = currentDir.filePath(pngIconName); } else if (currentDir.exists(xpmIconName)) { iconEntry = std::make_unique(); iconEntry->dir.type = QIconDirInfo::Fallback; iconEntry->filename = currentDir.filePath(xpmIconName); } else if (m_supportsSvg && currentDir.exists(svgIconName)) { iconEntry = std::make_unique(); iconEntry->dir.type = QIconDirInfo::Fallback; iconEntry->filename = currentDir.filePath(svgIconName); } if (iconEntry) { info.entries.push_back(std::move(iconEntry)); break; } } if (!info.entries.empty()) info.iconName = iconName; return info; } QThemeIconInfo QIconLoader::loadIcon(const QString &name) const { qCDebug(lcIconLoader) << "Loading icon" << name; m_iconName = name; QThemeIconInfo iconInfo; QStringList visitedThemes; if (!themeName().isEmpty()) iconInfo = findIconHelper(themeName(), name, visitedThemes, QIconLoader::FallBack); if (iconInfo.entries.empty() && !fallbackThemeName().isEmpty()) iconInfo = findIconHelper(fallbackThemeName(), name, visitedThemes, QIconLoader::FallBack); if (iconInfo.entries.empty()) iconInfo = lookupFallbackIcon(name); qCDebug(lcIconLoader) << "Resulting icon entries" << iconInfo.entries; return iconInfo; } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug debug, QIconEngine *engine) { QDebugStateSaver saver(debug); debug.nospace(); if (engine) { debug.noquote() << engine->key() << "("; debug << static_cast(engine); if (!engine->isNull()) debug.quote() << ", " << engine->iconName(); else debug << ", null"; debug << ")"; } else { debug << "QIconEngine(nullptr)"; } return debug; } #endif QIconEngine *QIconLoader::iconEngine(const QString &iconName) const { qCDebug(lcIconLoader) << "Resolving icon engine for icon" << iconName; std::unique_ptr iconEngine; if (hasUserTheme()) iconEngine.reset(new QIconLoaderEngine(iconName)); if (!iconEngine || iconEngine->isNull()) { qCDebug(lcIconLoader) << "Icon is not available from theme or fallback theme."; if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) { qCDebug(lcIconLoader) << "Trying platform engine."; std::unique_ptr themeEngine(platformTheme->createIconEngine(iconName)); if (themeEngine && !themeEngine->isNull()) { iconEngine = std::move(themeEngine); qCDebug(lcIconLoader) << "Icon provided by platform engine."; } } } // We need to maintain the invariant that the QIcon has a valid engine if (!iconEngine) iconEngine.reset(new QIconLoaderEngine(iconName)); qCDebug(lcIconLoader) << "Resulting engine" << iconEngine.get(); return iconEngine.release(); } /*! \internal \class QThemeIconEngine \inmodule QtGui \brief A named-based icon engine for providing theme icons. The engine supports invalidation of prior lookups, e.g. when the platform theme changes or the user sets an explicit icon theme. The actual icon lookup is handed over to an engine provided by QIconLoader::iconEngine(). */ QThemeIconEngine::QThemeIconEngine(const QString& iconName) : QProxyIconEngine() , m_iconName(iconName) { } QThemeIconEngine::QThemeIconEngine(const QThemeIconEngine &other) : QProxyIconEngine() , m_iconName(other.m_iconName) { } QString QThemeIconEngine::key() const { // Although we proxy the underlying engine, that's an implementation // detail, so from the point of view of QIcon, and in terms of // serialization, we are the one and only theme icon engine. return u"QThemeIconEngine"_s; } QIconEngine *QThemeIconEngine::clone() const { return new QThemeIconEngine(*this); } bool QThemeIconEngine::read(QDataStream &in) { in >> m_iconName; return true; } bool QThemeIconEngine::write(QDataStream &out) const { out << m_iconName; return true; } QIconEngine *QThemeIconEngine::proxiedEngine() const { const auto *iconLoader = QIconLoader::instance(); auto mostRecentThemeKey = iconLoader->themeKey(); if (mostRecentThemeKey != m_themeKey) { qCDebug(lcIconLoader) << "Theme key" << mostRecentThemeKey << "is different" << "than cached key" << m_themeKey << "for icon" << m_iconName; m_proxiedEngine.reset(iconLoader->iconEngine(m_iconName)); m_themeKey = mostRecentThemeKey; } return m_proxiedEngine.get(); } /*! \internal \class QIconLoaderEngine \inmodule QtGui \brief An icon engine based on icon entries collected by QIconLoader. The design and implementation of QIconLoader is based on the XDG icon specification. */ QIconLoaderEngine::QIconLoaderEngine(const QString& iconName) : m_iconName(iconName) , m_info(QIconLoader::instance()->loadIcon(m_iconName)) { } QIconLoaderEngine::~QIconLoaderEngine() = default; QIconEngine *QIconLoaderEngine::clone() const { Q_UNREACHABLE(); return nullptr; // Cannot be cloned } bool QIconLoaderEngine::hasIcon() const { return !(m_info.entries.empty()); } void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) { QSize pixmapSize = rect.size() * painter->device()->devicePixelRatio(); painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); } /* * This algorithm is defined by the freedesktop spec: * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize, int iconscale) { if (dir.scale != iconscale) return false; if (dir.type == QIconDirInfo::Fixed) { return dir.size == iconsize; } else if (dir.type == QIconDirInfo::Scalable) { return iconsize <= dir.maxSize && iconsize >= dir.minSize; } else if (dir.type == QIconDirInfo::Threshold) { return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold; } else if (dir.type == QIconDirInfo::Fallback) { return true; } Q_ASSERT(1); // Not a valid value return false; } /* * This algorithm is defined by the freedesktop spec: * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ static int directorySizeDistance(const QIconDirInfo &dir, int iconsize, int iconscale) { const int scaledIconSize = iconsize * iconscale; if (dir.type == QIconDirInfo::Fixed) { return qAbs(dir.size * dir.scale - scaledIconSize); } else if (dir.type == QIconDirInfo::Scalable) { if (scaledIconSize < dir.minSize * dir.scale) return dir.minSize * dir.scale - scaledIconSize; else if (scaledIconSize > dir.maxSize * dir.scale) return scaledIconSize - dir.maxSize * dir.scale; else return 0; } else if (dir.type == QIconDirInfo::Threshold) { if (scaledIconSize < (dir.size - dir.threshold) * dir.scale) return dir.minSize * dir.scale - scaledIconSize; else if (scaledIconSize > (dir.size + dir.threshold) * dir.scale) return scaledIconSize - dir.maxSize * dir.scale; else return 0; } else if (dir.type == QIconDirInfo::Fallback) { return 0; } Q_ASSERT(1); // Not a valid value return INT_MAX; } QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QThemeIconInfo &info, const QSize &size, int scale) { int iconsize = qMin(size.width(), size.height()); // Note that m_info.entries are sorted so that png-files // come first // Search for exact matches first for (const auto &entry : info.entries) { if (directoryMatchesSize(entry->dir, iconsize, scale)) { return entry.get(); } } // Find the minimum distance icon int minimalSize = INT_MAX; QIconLoaderEngineEntry *closestMatch = nullptr; for (const auto &entry : info.entries) { int distance = directorySizeDistance(entry->dir, iconsize, scale); if (distance < minimalSize) { minimalSize = distance; closestMatch = entry.get(); } } return closestMatch; } /* * Returns the actual icon size. For scalable svg's this is equivalent * to the requested size. Otherwise the closest match is returned but * we can never return a bigger size than the requested size. * */ QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) { Q_UNUSED(mode); Q_UNUSED(state); QIconLoaderEngineEntry *entry = entryForSize(m_info, size); if (entry) { const QIconDirInfo &dir = entry->dir; if (dir.type == QIconDirInfo::Scalable) { return size; } else if (dir.type == QIconDirInfo::Fallback) { return QIcon(entry->filename).actualSize(size, mode, state); } else { int result = qMin(dir.size * dir.scale, qMin(size.width(), size.height())); return QSize(result, result); } } return QSize(0, 0); } QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { Q_UNUSED(state); // Ensure that basePixmap is lazily initialized before generating the // key, otherwise the cache key is not unique if (basePixmap.isNull()) basePixmap.load(filename); QSize actualSize = basePixmap.size(); // If the size of the best match we have (basePixmap) is larger than the // requested size, we downscale it to match. if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height())) actualSize.scale(size, Qt::KeepAspectRatio); QString key = "$qt_theme_"_L1 % HexString(basePixmap.cacheKey()) % HexString(mode) % HexString(QGuiApplication::palette().cacheKey()) % HexString(actualSize.width()) % HexString(actualSize.height()); QPixmap cachedPixmap; if (QPixmapCache::find(key, &cachedPixmap)) { return cachedPixmap; } else { if (basePixmap.size() != actualSize) cachedPixmap = basePixmap.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); else cachedPixmap = basePixmap; if (QGuiApplication *guiApp = qobject_cast(qApp)) cachedPixmap = static_cast(QObjectPrivate::get(guiApp))->applyQIconStyleHelper(mode, cachedPixmap); QPixmapCache::insert(key, cachedPixmap); } return cachedPixmap; } QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { if (svgIcon.isNull()) svgIcon = QIcon(filename); // Bypass QIcon API, as that will scale by device pixel ratio of the // highest DPR screen since we're not passing on any QWindow. if (QIconEngine *engine = svgIcon.data_ptr() ? svgIcon.data_ptr()->engine : nullptr) return engine->pixmap(size, mode, state); return QPixmap(); } QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { QIconLoaderEngineEntry *entry = entryForSize(m_info, size); if (entry) return entry->pixmap(size, mode, state); return QPixmap(); } QString QIconLoaderEngine::key() const { return u"QIconLoaderEngine"_s; } QString QIconLoaderEngine::iconName() { return m_info.iconName; } bool QIconLoaderEngine::isNull() { return m_info.entries.empty(); } QPixmap QIconLoaderEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) { const int integerScale = qCeil(scale); QIconLoaderEngineEntry *entry = entryForSize(m_info, size / integerScale, integerScale); return entry ? entry->pixmap(size, mode, state) : QPixmap(); } QList QIconLoaderEngine::availableSizes(QIcon::Mode mode, QIcon::State state) { Q_UNUSED(mode); Q_UNUSED(state); const qsizetype N = qsizetype(m_info.entries.size()); QList sizes; sizes.reserve(N); // Gets all sizes from the DirectoryInfo entries for (const auto &entry : m_info.entries) { if (entry->dir.type == QIconDirInfo::Fallback) { sizes.append(QIcon(entry->filename).availableSizes()); } else { int size = entry->dir.size; sizes.append(QSize(size, size)); } } return sizes; } QT_END_NAMESPACE #endif //QT_NO_ICON