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