| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | // Qt-Security score:critical reason:data-parser |
| 4 | #ifndef QT_NO_ICON |
| 5 | #include <private/qiconloader_p.h> |
| 6 | |
| 7 | #include <private/qguiapplication_p.h> |
| 8 | #include <private/qicon_p.h> |
| 9 | |
| 10 | #include <QtGui/QIconEnginePlugin> |
| 11 | #include <QtGui/QPixmapCache> |
| 12 | #include <qpa/qplatformtheme.h> |
| 13 | #include <QtGui/qfontdatabase.h> |
| 14 | #include <QtGui/QPalette> |
| 15 | #include <QtCore/qmath.h> |
| 16 | #include <QtCore/QList> |
| 17 | #include <QtCore/QDir> |
| 18 | #include <QtCore/qloggingcategory.h> |
| 19 | #if QT_CONFIG(settings) |
| 20 | #include <QtCore/QSettings> |
| 21 | #endif |
| 22 | #include <QtGui/QPainter> |
| 23 | |
| 24 | #include <private/qhexstring_p.h> |
| 25 | #include <private/qfactoryloader_p.h> |
| 26 | #include <private/qfonticonengine_p.h> |
| 27 | |
| 28 | QT_BEGIN_NAMESPACE |
| 29 | |
| 30 | Q_STATIC_LOGGING_CATEGORY(lcIconLoader, "qt.gui.icon.loader" ) |
| 31 | |
| 32 | using namespace Qt::StringLiterals; |
| 33 | |
| 34 | Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) |
| 35 | |
| 36 | /* Theme to use in last resort, if the theme does not have the icon, neither the parents */ |
| 37 | static QString systemFallbackThemeName() |
| 38 | { |
| 39 | if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { |
| 40 | const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::SystemIconFallbackThemeName); |
| 41 | if (themeHint.isValid()) |
| 42 | return themeHint.toString(); |
| 43 | } |
| 44 | return QString(); |
| 45 | } |
| 46 | |
| 47 | QIconLoader::QIconLoader() : |
| 48 | m_themeKey(1), m_supportsSvg(false), m_initialized(false) |
| 49 | { |
| 50 | } |
| 51 | |
| 52 | static inline QString systemThemeName() |
| 53 | { |
| 54 | if (QString override = qEnvironmentVariable(varName: "QT_QPA_SYSTEM_ICON_THEME" ); !override.isEmpty()) |
| 55 | return override; |
| 56 | if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { |
| 57 | const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::SystemIconThemeName); |
| 58 | if (themeHint.isValid()) |
| 59 | return themeHint.toString(); |
| 60 | } |
| 61 | return QString(); |
| 62 | } |
| 63 | |
| 64 | static inline QStringList systemIconSearchPaths() |
| 65 | { |
| 66 | if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { |
| 67 | const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::IconThemeSearchPaths); |
| 68 | if (themeHint.isValid()) |
| 69 | return themeHint.toStringList(); |
| 70 | } |
| 71 | return QStringList(); |
| 72 | } |
| 73 | |
| 74 | static inline QStringList systemFallbackSearchPaths() |
| 75 | { |
| 76 | if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme()) { |
| 77 | const QVariant themeHint = theme->themeHint(hint: QPlatformTheme::IconFallbackSearchPaths); |
| 78 | if (themeHint.isValid()) |
| 79 | return themeHint.toStringList(); |
| 80 | } |
| 81 | return QStringList(); |
| 82 | } |
| 83 | |
| 84 | extern QFactoryLoader *qt_iconEngineFactoryLoader(); // qicon.cpp |
| 85 | |
| 86 | void QIconLoader::ensureInitialized() |
| 87 | { |
| 88 | if (!m_initialized) { |
| 89 | if (!QGuiApplicationPrivate::platformTheme()) |
| 90 | return; // it's too early: try again later (QTBUG-74252) |
| 91 | m_initialized = true; |
| 92 | m_systemTheme = systemThemeName(); |
| 93 | |
| 94 | if (m_systemTheme.isEmpty()) |
| 95 | m_systemTheme = systemFallbackThemeName(); |
| 96 | if (qt_iconEngineFactoryLoader()->keyMap().key(value: "svg"_L1 , defaultKey: -1) != -1) |
| 97 | m_supportsSvg = true; |
| 98 | |
| 99 | qCDebug(lcIconLoader) << "Initialized icon loader with system theme" |
| 100 | << m_systemTheme << "and SVG support" << m_supportsSvg; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | /*! |
| 105 | \internal |
| 106 | Gets an instance. |
| 107 | |
| 108 | \l QIcon::setFallbackThemeName() should be called before QGuiApplication is |
| 109 | created, to avoid a race condition (QTBUG-74252). When this function is |
| 110 | called from there, ensureInitialized() does not succeed because there |
| 111 | is no QPlatformTheme yet, so systemThemeName() is empty, and we don't want |
| 112 | m_systemTheme to get initialized to the fallback theme instead of the normal one. |
| 113 | */ |
| 114 | QIconLoader *QIconLoader::instance() |
| 115 | { |
| 116 | iconLoaderInstance()->ensureInitialized(); |
| 117 | return iconLoaderInstance(); |
| 118 | } |
| 119 | |
| 120 | // Queries the system theme and invalidates existing |
| 121 | // icons if the theme has changed. |
| 122 | void QIconLoader::updateSystemTheme() |
| 123 | { |
| 124 | const QString currentSystemTheme = m_systemTheme; |
| 125 | m_systemTheme = systemThemeName(); |
| 126 | if (m_systemTheme.isEmpty()) |
| 127 | m_systemTheme = systemFallbackThemeName(); |
| 128 | if (m_systemTheme != currentSystemTheme) |
| 129 | qCDebug(lcIconLoader) << "Updated system theme to" << m_systemTheme; |
| 130 | // Invalidate even if the system theme name hasn't changed, as the |
| 131 | // theme itself may have changed its underlying icon lookup logic. |
| 132 | if (!hasUserTheme()) |
| 133 | invalidateKey(); |
| 134 | } |
| 135 | |
| 136 | void QIconLoader::invalidateKey() |
| 137 | { |
| 138 | // Invalidating the key here will result in QThemeIconEngine |
| 139 | // recreating the actual engine the next time the icon is used. |
| 140 | // We don't need to clear the QIcon cache itself. |
| 141 | m_themeKey++; |
| 142 | |
| 143 | // invalidating the factory results in us looking once for |
| 144 | // a plugin that provides icon for the new themeName() |
| 145 | m_factory = std::nullopt; |
| 146 | } |
| 147 | |
| 148 | QString QIconLoader::themeName() const |
| 149 | { |
| 150 | return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; |
| 151 | } |
| 152 | |
| 153 | void QIconLoader::setThemeName(const QString &themeName) |
| 154 | { |
| 155 | if (m_userTheme == themeName) |
| 156 | return; |
| 157 | |
| 158 | qCDebug(lcIconLoader) << "Setting user theme name to" << themeName; |
| 159 | |
| 160 | const bool hadUserTheme = hasUserTheme(); |
| 161 | m_userTheme = themeName; |
| 162 | // if we cleared the user theme, then reset search paths as well, |
| 163 | // otherwise we'll keep looking in the user-defined search paths for |
| 164 | // a system-provide theme, which will never work. |
| 165 | if (!hasUserTheme() && hadUserTheme) |
| 166 | setThemeSearchPath(systemIconSearchPaths()); |
| 167 | invalidateKey(); |
| 168 | } |
| 169 | |
| 170 | QString QIconLoader::fallbackThemeName() const |
| 171 | { |
| 172 | return m_userFallbackTheme.isEmpty() ? systemFallbackThemeName() : m_userFallbackTheme; |
| 173 | } |
| 174 | |
| 175 | void QIconLoader::setFallbackThemeName(const QString &themeName) |
| 176 | { |
| 177 | qCDebug(lcIconLoader) << "Setting fallback theme name to" << themeName; |
| 178 | m_userFallbackTheme = themeName; |
| 179 | invalidateKey(); |
| 180 | } |
| 181 | |
| 182 | void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) |
| 183 | { |
| 184 | qCDebug(lcIconLoader) << "Setting theme search path to" << searchPaths; |
| 185 | m_iconDirs = searchPaths; |
| 186 | themeList.clear(); |
| 187 | invalidateKey(); |
| 188 | } |
| 189 | |
| 190 | QStringList QIconLoader::themeSearchPaths() const |
| 191 | { |
| 192 | if (m_iconDirs.isEmpty()) { |
| 193 | m_iconDirs = systemIconSearchPaths(); |
| 194 | // Always add resource directory as search path |
| 195 | m_iconDirs.append(t: ":/icons"_L1 ); |
| 196 | } |
| 197 | return m_iconDirs; |
| 198 | } |
| 199 | |
| 200 | void QIconLoader::setFallbackSearchPaths(const QStringList &searchPaths) |
| 201 | { |
| 202 | qCDebug(lcIconLoader) << "Setting fallback search path to" << searchPaths; |
| 203 | m_fallbackDirs = searchPaths; |
| 204 | invalidateKey(); |
| 205 | } |
| 206 | |
| 207 | QStringList QIconLoader::fallbackSearchPaths() const |
| 208 | { |
| 209 | if (m_fallbackDirs.isEmpty()) { |
| 210 | m_fallbackDirs = systemFallbackSearchPaths(); |
| 211 | } |
| 212 | return m_fallbackDirs; |
| 213 | } |
| 214 | |
| 215 | /*! |
| 216 | \internal |
| 217 | Helper class that reads and looks up into the icon-theme.cache generated with |
| 218 | gtk-update-icon-cache. If at any point we detect a corruption in the file |
| 219 | (because the offsets point at wrong locations for example), the reader |
| 220 | is marked as invalid. |
| 221 | */ |
| 222 | class QIconCacheGtkReader |
| 223 | { |
| 224 | public: |
| 225 | explicit QIconCacheGtkReader(const QString &themeDir); |
| 226 | QList<const char *> lookup(QStringView); |
| 227 | bool isValid() const { return m_isValid; } |
| 228 | private: |
| 229 | QFile m_file; |
| 230 | const unsigned char *m_data; |
| 231 | quint64 m_size; |
| 232 | bool m_isValid; |
| 233 | |
| 234 | quint16 read16(uint offset) |
| 235 | { |
| 236 | if (offset > m_size - 2 || (offset & 0x1)) { |
| 237 | m_isValid = false; |
| 238 | return 0; |
| 239 | } |
| 240 | return m_data[offset+1] | m_data[offset] << 8; |
| 241 | } |
| 242 | quint32 read32(uint offset) |
| 243 | { |
| 244 | if (offset > m_size - 4 || (offset & 0x3)) { |
| 245 | m_isValid = false; |
| 246 | return 0; |
| 247 | } |
| 248 | return m_data[offset+3] | m_data[offset+2] << 8 |
| 249 | | m_data[offset+1] << 16 | m_data[offset] << 24; |
| 250 | } |
| 251 | }; |
| 252 | |
| 253 | |
| 254 | QIconCacheGtkReader::QIconCacheGtkReader(const QString &dirName) |
| 255 | : m_isValid(false) |
| 256 | { |
| 257 | QFileInfo info(dirName + "/icon-theme.cache"_L1 ); |
| 258 | if (!info.exists() || info.lastModified(tz: QTimeZone::UTC) < QFileInfo(dirName).lastModified(tz: QTimeZone::UTC)) |
| 259 | return; |
| 260 | m_file.setFileName(info.absoluteFilePath()); |
| 261 | if (!m_file.open(flags: QFile::ReadOnly)) |
| 262 | return; |
| 263 | m_size = m_file.size(); |
| 264 | m_data = m_file.map(offset: 0, size: m_size); |
| 265 | if (!m_data) |
| 266 | return; |
| 267 | if (read16(offset: 0) != 1) // VERSION_MAJOR |
| 268 | return; |
| 269 | |
| 270 | m_isValid = true; |
| 271 | |
| 272 | // Check that all the directories are older than the cache |
| 273 | const QDateTime lastModified = info.lastModified(tz: QTimeZone::UTC); |
| 274 | quint32 dirListOffset = read32(offset: 8); |
| 275 | quint32 dirListLen = read32(offset: dirListOffset); |
| 276 | for (uint i = 0; i < dirListLen; ++i) { |
| 277 | quint32 offset = read32(offset: dirListOffset + 4 + 4 * i); |
| 278 | if (!m_isValid || offset >= m_size || lastModified < QFileInfo(dirName + u'/' |
| 279 | + QString::fromUtf8(utf8: reinterpret_cast<const char*>(m_data + offset))).lastModified(tz: QTimeZone::UTC)) { |
| 280 | m_isValid = false; |
| 281 | return; |
| 282 | } |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | static quint32 icon_name_hash(const char *p) |
| 287 | { |
| 288 | quint32 h = static_cast<signed char>(*p); |
| 289 | for (p += 1; *p != '\0'; p++) |
| 290 | h = (h << 5) - h + *p; |
| 291 | return h; |
| 292 | } |
| 293 | |
| 294 | /*! \internal |
| 295 | lookup the icon name and return the list of subdirectories in which an icon |
| 296 | with this name is present. The char* are pointers to the mapped data. |
| 297 | For example, this would return { "32x32/apps", "24x24/apps" , ... } |
| 298 | */ |
| 299 | QList<const char *> QIconCacheGtkReader::lookup(QStringView name) |
| 300 | { |
| 301 | QList<const char *> ret; |
| 302 | if (!isValid() || name.isEmpty()) |
| 303 | return ret; |
| 304 | |
| 305 | QByteArray nameUtf8 = name.toUtf8(); |
| 306 | quint32 hash = icon_name_hash(p: nameUtf8); |
| 307 | |
| 308 | quint32 hashOffset = read32(offset: 4); |
| 309 | quint32 hashBucketCount = read32(offset: hashOffset); |
| 310 | |
| 311 | if (!isValid() || hashBucketCount == 0) { |
| 312 | m_isValid = false; |
| 313 | return ret; |
| 314 | } |
| 315 | |
| 316 | quint32 bucketIndex = hash % hashBucketCount; |
| 317 | quint32 bucketOffset = read32(offset: hashOffset + 4 + bucketIndex * 4); |
| 318 | while (bucketOffset > 0 && bucketOffset <= m_size - 12) { |
| 319 | quint32 nameOff = read32(offset: bucketOffset + 4); |
| 320 | if (nameOff < m_size && strcmp(s1: reinterpret_cast<const char*>(m_data + nameOff), s2: nameUtf8) == 0) { |
| 321 | quint32 dirListOffset = read32(offset: 8); |
| 322 | quint32 dirListLen = read32(offset: dirListOffset); |
| 323 | |
| 324 | quint32 listOffset = read32(offset: bucketOffset+8); |
| 325 | quint32 listLen = read32(offset: listOffset); |
| 326 | |
| 327 | if (!m_isValid || listOffset + 4 + 8 * listLen > m_size) { |
| 328 | m_isValid = false; |
| 329 | return ret; |
| 330 | } |
| 331 | |
| 332 | ret.reserve(asize: listLen); |
| 333 | for (uint j = 0; j < listLen && m_isValid; ++j) { |
| 334 | quint32 dirIndex = read16(offset: listOffset + 4 + 8 * j); |
| 335 | quint32 o = read32(offset: dirListOffset + 4 + dirIndex*4); |
| 336 | if (!m_isValid || dirIndex >= dirListLen || o >= m_size) { |
| 337 | m_isValid = false; |
| 338 | return ret; |
| 339 | } |
| 340 | ret.append(t: reinterpret_cast<const char*>(m_data) + o); |
| 341 | } |
| 342 | return ret; |
| 343 | } |
| 344 | bucketOffset = read32(offset: bucketOffset); |
| 345 | } |
| 346 | return ret; |
| 347 | } |
| 348 | |
| 349 | QIconTheme::QIconTheme(const QString &themeName) |
| 350 | : m_valid(false) |
| 351 | { |
| 352 | QFile themeIndex; |
| 353 | |
| 354 | const QStringList iconDirs = QIcon::themeSearchPaths(); |
| 355 | for ( int i = 0 ; i < iconDirs.size() ; ++i) { |
| 356 | QDir iconDir(iconDirs[i]); |
| 357 | QString themeDir = iconDir.path() + u'/' + themeName; |
| 358 | QFileInfo themeDirInfo(themeDir); |
| 359 | |
| 360 | if (themeDirInfo.isDir()) { |
| 361 | m_contentDirs << themeDir; |
| 362 | m_gtkCaches << QSharedPointer<QIconCacheGtkReader>::create(arguments&: themeDir); |
| 363 | } |
| 364 | |
| 365 | if (!m_valid) { |
| 366 | themeIndex.setFileName(themeDir + "/index.theme"_L1 ); |
| 367 | m_valid = themeIndex.exists(); |
| 368 | qCDebug(lcIconLoader) << "Probing theme file at" << themeIndex.fileName() << m_valid; |
| 369 | } |
| 370 | } |
| 371 | #if QT_CONFIG(settings) |
| 372 | if (m_valid) { |
| 373 | const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); |
| 374 | const QStringList keys = indexReader.allKeys(); |
| 375 | for (const QString &key : keys) { |
| 376 | if (key.endsWith(s: "/Size"_L1 )) { |
| 377 | // Note the QSettings ini-format does not accept |
| 378 | // slashes in key names, hence we have to cheat |
| 379 | if (int size = indexReader.value(key).toInt()) { |
| 380 | QString directoryKey = key.left(n: key.size() - 5); |
| 381 | QIconDirInfo dirInfo(directoryKey); |
| 382 | dirInfo.size = size; |
| 383 | QString type = indexReader.value(key: directoryKey + "/Type"_L1 ).toString(); |
| 384 | |
| 385 | if (type == "Fixed"_L1 ) |
| 386 | dirInfo.type = QIconDirInfo::Fixed; |
| 387 | else if (type == "Scalable"_L1 ) |
| 388 | dirInfo.type = QIconDirInfo::Scalable; |
| 389 | else |
| 390 | dirInfo.type = QIconDirInfo::Threshold; |
| 391 | |
| 392 | dirInfo.threshold = indexReader.value(key: directoryKey + |
| 393 | "/Threshold"_L1 , |
| 394 | defaultValue: 2).toInt(); |
| 395 | |
| 396 | dirInfo.minSize = indexReader.value(key: directoryKey + "/MinSize"_L1 , defaultValue: size).toInt(); |
| 397 | |
| 398 | dirInfo.maxSize = indexReader.value(key: directoryKey + "/MaxSize"_L1 , defaultValue: size).toInt(); |
| 399 | |
| 400 | dirInfo.scale = indexReader.value(key: directoryKey + "/Scale"_L1 , defaultValue: 1).toInt(); |
| 401 | |
| 402 | const QString context = indexReader.value(key: directoryKey + "/Context"_L1 ).toString(); |
| 403 | dirInfo.context = [context]() { |
| 404 | if (context == "Applications"_L1 ) |
| 405 | return QIconDirInfo::Applications; |
| 406 | else if (context == "MimeTypes"_L1 ) |
| 407 | return QIconDirInfo::MimeTypes; |
| 408 | else |
| 409 | return QIconDirInfo::UnknownContext; |
| 410 | }(); |
| 411 | |
| 412 | m_keyList.append(t: dirInfo); |
| 413 | } |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | // Parent themes provide fallbacks for missing icons |
| 418 | m_parents = indexReader.value(key: "Icon Theme/Inherits"_L1 ).toStringList(); |
| 419 | m_parents.removeAll(t: QString()); |
| 420 | } |
| 421 | #endif // settings |
| 422 | } |
| 423 | |
| 424 | QStringList QIconTheme::parents() const |
| 425 | { |
| 426 | // Respect explicitly declared parents |
| 427 | QStringList result = m_parents; |
| 428 | |
| 429 | // Ensure a default fallback for all themes |
| 430 | const QString fallback = QIconLoader::instance()->fallbackThemeName(); |
| 431 | if (!fallback.isEmpty()) |
| 432 | result.append(t: fallback); |
| 433 | |
| 434 | // Ensure that all themes fall back to hicolor as the last theme |
| 435 | result.removeAll(t: "hicolor"_L1 ); |
| 436 | result.append(t: "hicolor"_L1 ); |
| 437 | |
| 438 | return result; |
| 439 | } |
| 440 | |
| 441 | QDebug operator<<(QDebug debug, const std::unique_ptr<QIconLoaderEngineEntry> &entry) |
| 442 | { |
| 443 | QDebugStateSaver saver(debug); |
| 444 | if (entry) return debug.noquote() << entry->filename; |
| 445 | return debug << "QIconLoaderEngineEntry(0x0)" ; |
| 446 | } |
| 447 | |
| 448 | QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, |
| 449 | const QString &iconName, |
| 450 | QStringList &visited, |
| 451 | DashRule rule) const |
| 452 | { |
| 453 | qCDebug(lcIconLoader) << "Finding icon" << iconName << "in theme" << themeName |
| 454 | << "skipping" << visited; |
| 455 | |
| 456 | QThemeIconInfo info; |
| 457 | Q_ASSERT(!themeName.isEmpty()); |
| 458 | |
| 459 | // Used to protect against potential recursions |
| 460 | visited << themeName; |
| 461 | |
| 462 | QIconTheme &theme = themeList[themeName]; |
| 463 | if (!theme.isValid()) { |
| 464 | theme = QIconTheme(themeName); |
| 465 | if (!theme.isValid()) { |
| 466 | qCDebug(lcIconLoader) << "Theme" << themeName << "not found" ; |
| 467 | return info; |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | const QStringList contentDirs = theme.contentDirs(); |
| 472 | |
| 473 | QStringView iconNameFallback(iconName); |
| 474 | bool searchingGenericFallback = m_iconName.length() > iconName.length(); |
| 475 | |
| 476 | // Iterate through all icon's fallbacks in current theme |
| 477 | if (info.entries.empty()) { |
| 478 | const QString svgIconName = iconNameFallback + ".svg"_L1 ; |
| 479 | const QString pngIconName = iconNameFallback + ".png"_L1 ; |
| 480 | |
| 481 | // Add all relevant files |
| 482 | for (int i = 0; i < contentDirs.size(); ++i) { |
| 483 | QList<QIconDirInfo> subDirs = theme.keyList(); |
| 484 | |
| 485 | // Try to reduce the amount of subDirs by looking in the GTK+ cache in order to save |
| 486 | // a massive amount of file stat (especially if the icon is not there) |
| 487 | auto cache = theme.m_gtkCaches.at(i); |
| 488 | if (cache->isValid()) { |
| 489 | const auto result = cache->lookup(name: iconNameFallback); |
| 490 | if (cache->isValid()) { |
| 491 | const QList<QIconDirInfo> subDirsCopy = subDirs; |
| 492 | subDirs.clear(); |
| 493 | subDirs.reserve(asize: result.size()); |
| 494 | for (const char *s : result) { |
| 495 | QString path = QString::fromUtf8(utf8: s); |
| 496 | auto it = std::find_if(first: subDirsCopy.cbegin(), last: subDirsCopy.cend(), |
| 497 | pred: [&](const QIconDirInfo &info) { |
| 498 | return info.path == path; } ); |
| 499 | if (it != subDirsCopy.cend()) { |
| 500 | subDirs.append(t: *it); |
| 501 | } |
| 502 | } |
| 503 | } |
| 504 | } |
| 505 | |
| 506 | QString contentDir = contentDirs.at(i) + u'/'; |
| 507 | for (int j = 0; j < subDirs.size() ; ++j) { |
| 508 | const QIconDirInfo &dirInfo = subDirs.at(i: j); |
| 509 | if (searchingGenericFallback && |
| 510 | (dirInfo.context == QIconDirInfo::Applications || |
| 511 | dirInfo.context == QIconDirInfo::MimeTypes)) |
| 512 | continue; |
| 513 | |
| 514 | const QString subDir = contentDir + dirInfo.path + u'/'; |
| 515 | const QString pngPath = subDir + pngIconName; |
| 516 | if (QFile::exists(fileName: pngPath)) { |
| 517 | auto iconEntry = std::make_unique<PixmapEntry>(); |
| 518 | iconEntry->dir = dirInfo; |
| 519 | iconEntry->filename = pngPath; |
| 520 | // Notice we ensure that pixmap entries always come before |
| 521 | // scalable to preserve search order afterwards |
| 522 | info.entries.insert(position: info.entries.begin(), x: std::move(iconEntry)); |
| 523 | } else if (m_supportsSvg) { |
| 524 | const QString svgPath = subDir + svgIconName; |
| 525 | if (QFile::exists(fileName: svgPath)) { |
| 526 | auto iconEntry = std::make_unique<ScalableEntry>(); |
| 527 | iconEntry->dir = dirInfo; |
| 528 | iconEntry->filename = svgPath; |
| 529 | info.entries.push_back(x: std::move(iconEntry)); |
| 530 | } |
| 531 | } |
| 532 | } |
| 533 | } |
| 534 | |
| 535 | if (!info.entries.empty()) { |
| 536 | info.iconName = iconNameFallback.toString(); |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | if (info.entries.empty()) { |
| 541 | const QStringList parents = theme.parents(); |
| 542 | qCDebug(lcIconLoader) << "Did not find matching icons in theme;" |
| 543 | << "trying parent themes" << parents |
| 544 | << "skipping visited" << visited; |
| 545 | |
| 546 | // Search recursively through inherited themes |
| 547 | for (int i = 0 ; i < parents.size() ; ++i) { |
| 548 | |
| 549 | const QString parentTheme = parents.at(i).trimmed(); |
| 550 | |
| 551 | if (!visited.contains(str: parentTheme)) // guard against recursion |
| 552 | info = findIconHelper(themeName: parentTheme, iconName, visited, rule: QIconLoader::NoFallBack); |
| 553 | |
| 554 | if (!info.entries.empty()) // success |
| 555 | break; |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | if (rule == QIconLoader::FallBack && info.entries.empty()) { |
| 560 | // If it's possible - find next fallback for the icon |
| 561 | const int indexOfDash = iconNameFallback.lastIndexOf(c: u'-'); |
| 562 | if (indexOfDash != -1) { |
| 563 | qCDebug(lcIconLoader) << "Did not find matching icons in all themes;" |
| 564 | << "trying dash fallback" ; |
| 565 | iconNameFallback.truncate(n: indexOfDash); |
| 566 | QStringList _visited; |
| 567 | info = findIconHelper(themeName, iconName: iconNameFallback.toString(), visited&: _visited, rule: QIconLoader::FallBack); |
| 568 | } |
| 569 | } |
| 570 | |
| 571 | return info; |
| 572 | } |
| 573 | |
| 574 | QThemeIconInfo QIconLoader::lookupFallbackIcon(const QString &iconName) const |
| 575 | { |
| 576 | qCDebug(lcIconLoader) << "Looking up fallback icon" << iconName; |
| 577 | |
| 578 | QThemeIconInfo info; |
| 579 | |
| 580 | const QString pngIconName = iconName + ".png"_L1 ; |
| 581 | const QString xpmIconName = iconName + ".xpm"_L1 ; |
| 582 | const QString svgIconName = iconName + ".svg"_L1 ; |
| 583 | |
| 584 | const auto searchPaths = QIcon::fallbackSearchPaths(); |
| 585 | for (const QString &iconDir: searchPaths) { |
| 586 | QDir currentDir(iconDir); |
| 587 | std::unique_ptr<QIconLoaderEngineEntry> iconEntry; |
| 588 | if (currentDir.exists(name: pngIconName)) { |
| 589 | iconEntry = std::make_unique<PixmapEntry>(); |
| 590 | iconEntry->dir.type = QIconDirInfo::Fallback; |
| 591 | iconEntry->filename = currentDir.filePath(fileName: pngIconName); |
| 592 | } else if (currentDir.exists(name: xpmIconName)) { |
| 593 | iconEntry = std::make_unique<PixmapEntry>(); |
| 594 | iconEntry->dir.type = QIconDirInfo::Fallback; |
| 595 | iconEntry->filename = currentDir.filePath(fileName: xpmIconName); |
| 596 | } else if (m_supportsSvg && |
| 597 | currentDir.exists(name: svgIconName)) { |
| 598 | iconEntry = std::make_unique<ScalableEntry>(); |
| 599 | iconEntry->dir.type = QIconDirInfo::Fallback; |
| 600 | iconEntry->filename = currentDir.filePath(fileName: svgIconName); |
| 601 | } |
| 602 | if (iconEntry) { |
| 603 | info.entries.push_back(x: std::move(iconEntry)); |
| 604 | break; |
| 605 | } |
| 606 | } |
| 607 | |
| 608 | if (!info.entries.empty()) |
| 609 | info.iconName = iconName; |
| 610 | |
| 611 | return info; |
| 612 | } |
| 613 | |
| 614 | QThemeIconInfo QIconLoader::loadIcon(const QString &name) const |
| 615 | { |
| 616 | qCDebug(lcIconLoader) << "Loading icon" << name; |
| 617 | |
| 618 | m_iconName = name; |
| 619 | QThemeIconInfo iconInfo; |
| 620 | QStringList visitedThemes; |
| 621 | if (!themeName().isEmpty()) |
| 622 | iconInfo = findIconHelper(themeName: themeName(), iconName: name, visited&: visitedThemes, rule: QIconLoader::FallBack); |
| 623 | |
| 624 | if (iconInfo.entries.empty() && !fallbackThemeName().isEmpty()) |
| 625 | iconInfo = findIconHelper(themeName: fallbackThemeName(), iconName: name, visited&: visitedThemes, rule: QIconLoader::FallBack); |
| 626 | |
| 627 | if (iconInfo.entries.empty()) |
| 628 | iconInfo = lookupFallbackIcon(iconName: name); |
| 629 | |
| 630 | qCDebug(lcIconLoader) << "Resulting icon entries" << iconInfo.entries; |
| 631 | return iconInfo; |
| 632 | } |
| 633 | |
| 634 | #ifndef QT_NO_DEBUG_STREAM |
| 635 | QDebug operator<<(QDebug debug, QIconEngine *engine) |
| 636 | { |
| 637 | QDebugStateSaver saver(debug); |
| 638 | debug.nospace(); |
| 639 | if (engine) { |
| 640 | debug.noquote() << engine->key() << "(" ; |
| 641 | debug << static_cast<const void *>(engine); |
| 642 | if (!engine->isNull()) |
| 643 | debug.quote() << ", " << engine->iconName(); |
| 644 | else |
| 645 | debug << ", null" ; |
| 646 | debug << ")" ; |
| 647 | } else { |
| 648 | debug << "QIconEngine(nullptr)" ; |
| 649 | } |
| 650 | return debug; |
| 651 | } |
| 652 | #endif |
| 653 | |
| 654 | QIconEngine *QIconLoader::iconEngine(const QString &iconName) const |
| 655 | { |
| 656 | qCDebug(lcIconLoader) << "Resolving icon engine for icon" << iconName; |
| 657 | |
| 658 | std::unique_ptr<QIconEngine> iconEngine; |
| 659 | |
| 660 | if (!m_factory) { |
| 661 | qCDebug(lcIconLoader) << "Finding a plugin for theme" << themeName(); |
| 662 | // try to find a plugin that supports the current theme |
| 663 | const int factoryIndex = qt_iconEngineFactoryLoader()->indexOf(needle: themeName()); |
| 664 | if (factoryIndex >= 0) |
| 665 | m_factory = qobject_cast<QIconEnginePlugin *>(object: qt_iconEngineFactoryLoader()->instance(index: factoryIndex)); |
| 666 | } |
| 667 | if (m_factory && *m_factory) |
| 668 | iconEngine.reset(p: m_factory.value()->create(filename: iconName)); |
| 669 | |
| 670 | if (hasUserTheme()) { |
| 671 | if (!iconEngine || iconEngine->isNull()) { |
| 672 | if (QFontDatabase::families().contains(str: themeName())) { |
| 673 | QFont maybeIconFont(themeName()); |
| 674 | maybeIconFont.setStyleStrategy(QFont::NoFontMerging); |
| 675 | qCDebug(lcIconLoader) << "Trying font icon engine." ; |
| 676 | iconEngine.reset(p: new QFontIconEngine(iconName, maybeIconFont)); |
| 677 | } |
| 678 | } |
| 679 | if (!iconEngine || iconEngine->isNull()) { |
| 680 | qCDebug(lcIconLoader) << "Trying loader engine for theme." ; |
| 681 | iconEngine.reset(p: new QIconLoaderEngine(iconName)); |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | if (!iconEngine || iconEngine->isNull()) { |
| 686 | qCDebug(lcIconLoader) << "Icon is not available from theme or fallback theme." ; |
| 687 | if (auto *platformTheme = QGuiApplicationPrivate::platformTheme()) { |
| 688 | qCDebug(lcIconLoader) << "Trying platform engine." ; |
| 689 | std::unique_ptr<QIconEngine> themeEngine(platformTheme->createIconEngine(iconName)); |
| 690 | if (themeEngine && !themeEngine->isNull()) { |
| 691 | iconEngine = std::move(themeEngine); |
| 692 | qCDebug(lcIconLoader) << "Icon provided by platform engine." ; |
| 693 | } |
| 694 | } |
| 695 | } |
| 696 | // We need to maintain the invariant that the QIcon has a valid engine |
| 697 | if (!iconEngine) |
| 698 | iconEngine.reset(p: new QIconLoaderEngine(iconName)); |
| 699 | |
| 700 | qCDebug(lcIconLoader) << "Resulting engine" << iconEngine.get(); |
| 701 | return iconEngine.release(); |
| 702 | } |
| 703 | |
| 704 | /*! |
| 705 | \internal |
| 706 | \class QThemeIconEngine |
| 707 | \inmodule QtGui |
| 708 | |
| 709 | \brief A named-based icon engine for providing theme icons. |
| 710 | |
| 711 | The engine supports invalidation of prior lookups, e.g. when |
| 712 | the platform theme changes or the user sets an explicit icon |
| 713 | theme. |
| 714 | |
| 715 | The actual icon lookup is handed over to an engine provided |
| 716 | by QIconLoader::iconEngine(). |
| 717 | */ |
| 718 | |
| 719 | QThemeIconEngine::QThemeIconEngine(const QString& iconName) |
| 720 | : QProxyIconEngine() |
| 721 | , m_iconName(iconName) |
| 722 | { |
| 723 | } |
| 724 | |
| 725 | QThemeIconEngine::QThemeIconEngine(const QThemeIconEngine &other) |
| 726 | : QProxyIconEngine() |
| 727 | , m_iconName(other.m_iconName) |
| 728 | { |
| 729 | } |
| 730 | |
| 731 | QString QThemeIconEngine::key() const |
| 732 | { |
| 733 | // Although we proxy the underlying engine, that's an implementation |
| 734 | // detail, so from the point of view of QIcon, and in terms of |
| 735 | // serialization, we are the one and only theme icon engine. |
| 736 | return u"QThemeIconEngine"_s ; |
| 737 | } |
| 738 | |
| 739 | QIconEngine *QThemeIconEngine::clone() const |
| 740 | { |
| 741 | return new QThemeIconEngine(*this); |
| 742 | } |
| 743 | |
| 744 | bool QThemeIconEngine::read(QDataStream &in) { |
| 745 | in >> m_iconName; |
| 746 | return true; |
| 747 | } |
| 748 | |
| 749 | bool QThemeIconEngine::write(QDataStream &out) const |
| 750 | { |
| 751 | out << m_iconName; |
| 752 | return true; |
| 753 | } |
| 754 | |
| 755 | QIconEngine *QThemeIconEngine::proxiedEngine() const |
| 756 | { |
| 757 | const auto *iconLoader = QIconLoader::instance(); |
| 758 | auto mostRecentThemeKey = iconLoader->themeKey(); |
| 759 | if (mostRecentThemeKey != m_themeKey) { |
| 760 | qCDebug(lcIconLoader) << "Theme key" << mostRecentThemeKey << "is different" |
| 761 | << "than cached key" << m_themeKey << "for icon" << m_iconName; |
| 762 | m_proxiedEngine.reset(p: iconLoader->iconEngine(iconName: m_iconName)); |
| 763 | m_themeKey = mostRecentThemeKey; |
| 764 | } |
| 765 | return m_proxiedEngine.get(); |
| 766 | } |
| 767 | |
| 768 | /*! |
| 769 | \internal |
| 770 | \class QIconLoaderEngine |
| 771 | \inmodule QtGui |
| 772 | |
| 773 | \brief An icon engine based on icon entries collected by QIconLoader. |
| 774 | |
| 775 | The design and implementation of QIconLoader is based on |
| 776 | the XDG icon specification. |
| 777 | */ |
| 778 | |
| 779 | QIconLoaderEngine::QIconLoaderEngine(const QString& iconName) |
| 780 | : m_iconName(iconName) |
| 781 | , m_info(QIconLoader::instance()->loadIcon(name: m_iconName)) |
| 782 | { |
| 783 | } |
| 784 | |
| 785 | QIconLoaderEngine::~QIconLoaderEngine() = default; |
| 786 | |
| 787 | QIconEngine *QIconLoaderEngine::clone() const |
| 788 | { |
| 789 | Q_UNREACHABLE(); |
| 790 | return nullptr; // Cannot be cloned |
| 791 | } |
| 792 | |
| 793 | bool QIconLoaderEngine::hasIcon() const |
| 794 | { |
| 795 | return !(m_info.entries.empty()); |
| 796 | } |
| 797 | |
| 798 | void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect, |
| 799 | QIcon::Mode mode, QIcon::State state) |
| 800 | { |
| 801 | const auto dpr = painter->device()->devicePixelRatio(); |
| 802 | painter->drawPixmap(r: rect, pm: scaledPixmap(size: rect.size(), mode, state, scale: dpr)); |
| 803 | } |
| 804 | |
| 805 | /* |
| 806 | * This algorithm is defined by the freedesktop spec: |
| 807 | * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html |
| 808 | */ |
| 809 | static bool directoryMatchesSizeAndScale(const QIconDirInfo &dir, int iconsize, int iconscale) |
| 810 | { |
| 811 | if (dir.scale != iconscale) |
| 812 | return false; |
| 813 | |
| 814 | switch (dir.type) { |
| 815 | case QIconDirInfo::Fixed: |
| 816 | return dir.size == iconsize; |
| 817 | case QIconDirInfo::Scalable: |
| 818 | return iconsize <= dir.maxSize && iconsize >= dir.minSize; |
| 819 | case QIconDirInfo::Threshold: |
| 820 | return iconsize >= dir.size - dir.threshold && iconsize <= dir.size + dir.threshold; |
| 821 | case QIconDirInfo::Fallback: |
| 822 | return false; // just because the scale matches it doesn't mean there is a better sized icon somewhere |
| 823 | } |
| 824 | |
| 825 | Q_ASSERT(1); // Not a valid value |
| 826 | return false; |
| 827 | } |
| 828 | |
| 829 | /* |
| 830 | * This algorithm is a modification of the algorithm defined by the freedesktop spec: |
| 831 | * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html |
| 832 | */ |
| 833 | static int directorySizeDelta(const QIconDirInfo &dir, int iconsize, int iconscale) |
| 834 | { |
| 835 | const auto scaledIconSize = iconsize * iconscale; |
| 836 | |
| 837 | switch (dir.type) { |
| 838 | case QIconDirInfo::Fixed: |
| 839 | return dir.size * dir.scale - scaledIconSize; |
| 840 | case QIconDirInfo::Scalable: { |
| 841 | const auto minScaled = dir.minSize * dir.scale; |
| 842 | if (scaledIconSize < minScaled) |
| 843 | return minScaled - scaledIconSize; |
| 844 | const auto maxScaled = dir.maxSize * dir.scale; |
| 845 | if (scaledIconSize > maxScaled) |
| 846 | return scaledIconSize - maxScaled; |
| 847 | return 0; |
| 848 | } |
| 849 | case QIconDirInfo::Threshold: |
| 850 | if (scaledIconSize < (dir.size - dir.threshold) * dir.scale) |
| 851 | return dir.minSize * dir.scale - scaledIconSize; |
| 852 | if (scaledIconSize > (dir.size + dir.threshold) * dir.scale) |
| 853 | return scaledIconSize - dir.maxSize * dir.scale; |
| 854 | return 0; |
| 855 | case QIconDirInfo::Fallback: |
| 856 | return INT_MAX; |
| 857 | } |
| 858 | |
| 859 | Q_ASSERT(1); // Not a valid value |
| 860 | return INT_MAX; |
| 861 | } |
| 862 | |
| 863 | QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QThemeIconInfo &info, const QSize &size, int scale) |
| 864 | { |
| 865 | if (info.entries.empty()) |
| 866 | return nullptr; |
| 867 | if (info.entries.size() == 1) |
| 868 | return info.entries.at(n: 0).get(); |
| 869 | |
| 870 | int iconsize = qMin(a: size.width(), b: size.height()); |
| 871 | |
| 872 | // Note that m_info.entries are sorted so that png-files |
| 873 | // come first |
| 874 | |
| 875 | int minimalDelta = INT_MIN; |
| 876 | QIconLoaderEngineEntry *closestMatch = nullptr; |
| 877 | for (const auto &entry : info.entries) { |
| 878 | // exact match in scale and dpr |
| 879 | if (directoryMatchesSizeAndScale(dir: entry->dir, iconsize, iconscale: scale)) |
| 880 | return entry.get(); |
| 881 | |
| 882 | // Find the minimum distance icon |
| 883 | const auto deltaValue = directorySizeDelta(dir: entry->dir, iconsize, iconscale: scale); |
| 884 | // always prefer downscaled icons over upscaled icons |
| 885 | if (deltaValue > minimalDelta && minimalDelta <= 0) { |
| 886 | minimalDelta = deltaValue; |
| 887 | closestMatch = entry.get(); |
| 888 | } else if (deltaValue > 0 && deltaValue < qAbs(t: minimalDelta)) { |
| 889 | minimalDelta = deltaValue; |
| 890 | closestMatch = entry.get(); |
| 891 | } else if (deltaValue == 0) { |
| 892 | // exact match but different dpr: |
| 893 | // --> size * scale == entry.size * entry.scale |
| 894 | minimalDelta = deltaValue; |
| 895 | closestMatch = entry.get(); |
| 896 | } |
| 897 | } |
| 898 | return closestMatch ? closestMatch : info.entries.at(n: 0).get(); |
| 899 | } |
| 900 | |
| 901 | /* |
| 902 | * Returns the actual icon size. For scalable svg's this is equivalent |
| 903 | * to the requested size. Otherwise the closest match is returned but |
| 904 | * we can never return a bigger size than the requested size. |
| 905 | * |
| 906 | */ |
| 907 | QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, |
| 908 | QIcon::State state) |
| 909 | { |
| 910 | Q_UNUSED(mode); |
| 911 | Q_UNUSED(state); |
| 912 | |
| 913 | QIconLoaderEngineEntry *entry = entryForSize(info: m_info, size); |
| 914 | if (entry) { |
| 915 | const QIconDirInfo &dir = entry->dir; |
| 916 | if (dir.type == QIconDirInfo::Scalable) { |
| 917 | return size; |
| 918 | } else if (dir.type == QIconDirInfo::Fallback) { |
| 919 | return QIcon(entry->filename).actualSize(size, mode, state); |
| 920 | } else { |
| 921 | int result = qMin<int>(a: dir.size * dir.scale, b: qMin(a: size.width(), b: size.height())); |
| 922 | return QSize(result, result); |
| 923 | } |
| 924 | } |
| 925 | return QSize(0, 0); |
| 926 | } |
| 927 | |
| 928 | QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) |
| 929 | { |
| 930 | Q_UNUSED(state); |
| 931 | |
| 932 | // Ensure that basePixmap is lazily initialized before generating the |
| 933 | // key, otherwise the cache key is not unique |
| 934 | if (basePixmap.isNull()) |
| 935 | basePixmap.load(fileName: filename); |
| 936 | |
| 937 | // If the size of the best match we have (basePixmap) is larger than the |
| 938 | // requested size, we downscale it to match. |
| 939 | const auto actualSize = QPixmapIconEngine::adjustSize(expectedSize: size * scale, size: basePixmap.size()); |
| 940 | const auto calculatedDpr = QIconPrivate::pixmapDevicePixelRatio(displayDevicePixelRatio: scale, requestedSize: size, actualSize); |
| 941 | QString key = "$qt_theme_"_L1 |
| 942 | % HexString<quint64>(basePixmap.cacheKey()) |
| 943 | % HexString<quint8>(mode) |
| 944 | % HexString<quint64>(QGuiApplication::palette().cacheKey()) |
| 945 | % HexString<uint>(actualSize.width()) |
| 946 | % HexString<uint>(actualSize.height()) |
| 947 | % HexString<quint16>(qRound(d: calculatedDpr * 1000)); |
| 948 | |
| 949 | QPixmap cachedPixmap; |
| 950 | if (QPixmapCache::find(key, pixmap: &cachedPixmap)) { |
| 951 | return cachedPixmap; |
| 952 | } else { |
| 953 | if (basePixmap.size() != actualSize) |
| 954 | cachedPixmap = basePixmap.scaled(s: actualSize, aspectMode: Qt::IgnoreAspectRatio, mode: Qt::SmoothTransformation); |
| 955 | else |
| 956 | cachedPixmap = basePixmap; |
| 957 | if (QGuiApplication *guiApp = qobject_cast<QGuiApplication *>(qApp)) |
| 958 | cachedPixmap = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(o: guiApp))->applyQIconStyleHelper(mode, basePixmap: cachedPixmap); |
| 959 | cachedPixmap.setDevicePixelRatio(calculatedDpr); |
| 960 | QPixmapCache::insert(key, pixmap: cachedPixmap); |
| 961 | } |
| 962 | return cachedPixmap; |
| 963 | } |
| 964 | |
| 965 | QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) |
| 966 | { |
| 967 | if (svgIcon.isNull()) |
| 968 | svgIcon = QIcon(filename); |
| 969 | |
| 970 | return svgIcon.pixmap(size, devicePixelRatio: scale, mode, state); |
| 971 | } |
| 972 | |
| 973 | QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode, |
| 974 | QIcon::State state) |
| 975 | { |
| 976 | return scaledPixmap(size, mode, state, scale: 1.0); |
| 977 | } |
| 978 | |
| 979 | QString QIconLoaderEngine::key() const |
| 980 | { |
| 981 | return u"QIconLoaderEngine"_s ; |
| 982 | } |
| 983 | |
| 984 | QString QIconLoaderEngine::iconName() |
| 985 | { |
| 986 | return m_info.iconName; |
| 987 | } |
| 988 | |
| 989 | bool QIconLoaderEngine::isNull() |
| 990 | { |
| 991 | return m_info.entries.empty(); |
| 992 | } |
| 993 | |
| 994 | QPixmap QIconLoaderEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) |
| 995 | { |
| 996 | const int integerScale = qCeil(v: scale); |
| 997 | QIconLoaderEngineEntry *entry = entryForSize(info: m_info, size, scale: integerScale); |
| 998 | return entry ? entry->pixmap(size, mode, state, scale) : QPixmap(); |
| 999 | } |
| 1000 | |
| 1001 | QList<QSize> QIconLoaderEngine::availableSizes(QIcon::Mode mode, QIcon::State state) |
| 1002 | { |
| 1003 | Q_UNUSED(mode); |
| 1004 | Q_UNUSED(state); |
| 1005 | |
| 1006 | const qsizetype N = qsizetype(m_info.entries.size()); |
| 1007 | QList<QSize> sizes; |
| 1008 | sizes.reserve(asize: N); |
| 1009 | |
| 1010 | // Gets all sizes from the DirectoryInfo entries |
| 1011 | for (const auto &entry : m_info.entries) { |
| 1012 | if (entry->dir.type == QIconDirInfo::Fallback) { |
| 1013 | sizes.append(other: QIcon(entry->filename).availableSizes()); |
| 1014 | } else { |
| 1015 | int size = entry->dir.size; |
| 1016 | sizes.append(t: QSize(size, size)); |
| 1017 | } |
| 1018 | } |
| 1019 | return sizes; |
| 1020 | } |
| 1021 | |
| 1022 | QT_END_NAMESPACE |
| 1023 | |
| 1024 | #endif //QT_NO_ICON |
| 1025 | |