diff options
Diffstat (limited to 'src/gui/image/qiconloader.cpp')
-rw-r--r-- | src/gui/image/qiconloader.cpp | 314 |
1 files changed, 226 insertions, 88 deletions
diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp index 077eefacb3..97735e4af6 100644 --- a/src/gui/image/qiconloader.cpp +++ b/src/gui/image/qiconloader.cpp @@ -119,27 +119,33 @@ QIconLoader *QIconLoader::instance() // icons if the theme has changed. void QIconLoader::updateSystemTheme() { - // Only change if this is not explicitly set by the user - if (m_userTheme.isEmpty()) { - QString theme = systemThemeName(); - if (theme.isEmpty()) - theme = fallbackThemeName(); - if (theme != m_systemTheme) { - m_systemTheme = theme; - qCDebug(lcIconLoader) << "Updated system theme to" << m_systemTheme; - invalidateKey(); - } - } else { - qCDebug(lcIconLoader) << "Ignoring system theme update because" - << "user theme" << m_userTheme << "has been set"; - } + 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++; - QIconPrivate::clearIconCache(); + // invalidating the factory results in us looking once for + // a plugin that provides icon for the new themeName() + m_factory = std::nullopt; +} + +QString QIconLoader::themeName() const +{ + return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; } void QIconLoader::setThemeName(const QString &themeName) @@ -149,7 +155,13 @@ void QIconLoader::setThemeName(const QString &themeName) 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(); } @@ -162,6 +174,7 @@ void QIconLoader::setFallbackThemeName(const QString &themeName) { qCDebug(lcIconLoader) << "Setting fallback theme name to" << themeName; m_userFallbackTheme = themeName; + invalidateKey(); } void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) @@ -349,12 +362,12 @@ QIconTheme::QIconTheme(const QString &themeName) if (!m_valid) { themeIndex.setFileName(themeDir + "/index.theme"_L1); - if (themeIndex.exists()) - m_valid = true; + m_valid = themeIndex.exists(); + qCDebug(lcIconLoader) << "Probing theme file at" << themeIndex.fileName() << m_valid; } } #if QT_CONFIG(settings) - if (themeIndex.exists()) { + if (m_valid) { const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); const QStringList keys = indexReader.allKeys(); for (const QString &key : keys) { @@ -383,6 +396,17 @@ QIconTheme::QIconTheme(const QString &themeName) 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); } } @@ -391,21 +415,27 @@ QIconTheme::QIconTheme(const QString &themeName) // Parent themes provide fallbacks for missing icons m_parents = indexReader.value("Icon Theme/Inherits"_L1).toStringList(); m_parents.removeAll(QString()); - - // Ensure a default platform fallback for all themes - if (m_parents.isEmpty()) { - const QString fallback = QIconLoader::instance()->fallbackThemeName(); - if (!fallback.isEmpty()) - m_parents.append(fallback); - } - - // Ensure that all themes fall back to hicolor - if (!m_parents.contains("hicolor"_L1)) - m_parents.append("hicolor"_L1); } #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<QIconLoaderEngineEntry> &entry) { QDebugStateSaver saver(debug); @@ -415,9 +445,11 @@ QDebug operator<<(QDebug debug, const std::unique_ptr<QIconLoaderEngineEntry> &e QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, const QString &iconName, - QStringList &visited) const + QStringList &visited, + DashRule rule) const { - qCDebug(lcIconLoader) << "Finding icon" << iconName << "in theme" << themeName; + qCDebug(lcIconLoader) << "Finding icon" << iconName << "in theme" << themeName + << "skipping" << visited; QThemeIconInfo info; Q_ASSERT(!themeName.isEmpty()); @@ -429,18 +461,18 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, if (!theme.isValid()) { theme = QIconTheme(themeName); if (!theme.isValid()) { - qCDebug(lcIconLoader) << "Theme" << themeName << "not found;" - << "trying fallback theme" << fallbackThemeName(); - theme = QIconTheme(fallbackThemeName()); + 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 - while (info.entries.empty()) { + if (info.entries.empty()) { const QString svgIconName = iconNameFallback + ".svg"_L1; const QString pngIconName = iconNameFallback + ".png"_L1; @@ -472,6 +504,11 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, 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)) { @@ -495,15 +532,7 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, if (!info.entries.empty()) { info.iconName = iconNameFallback.toString(); - break; } - - // If it's possible - find next fallback for the icon - const int indexOfDash = iconNameFallback.lastIndexOf(u'-'); - if (indexOfDash == -1) - break; - - iconNameFallback.truncate(indexOfDash); } if (info.entries.empty()) { @@ -518,13 +547,25 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName, const QString parentTheme = parents.at(i).trimmed(); if (!visited.contains(parentTheme)) // guard against recursion - info = findIconHelper(parentTheme, iconName, visited); + 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; } @@ -572,64 +613,171 @@ QThemeIconInfo QIconLoader::loadIcon(const QString &name) const { qCDebug(lcIconLoader) << "Loading icon" << name; + m_iconName = name; QThemeIconInfo iconInfo; - if (!themeName().isEmpty()) { - QStringList visited; - iconInfo = findIconHelper(themeName(), name, visited); - if (iconInfo.entries.empty()) - iconInfo = lookupFallbackIcon(name); - } + 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<const void *>(engine); + if (!engine->isNull()) + debug.quote() << ", " << engine->iconName(); + else + debug << ", null"; + debug << ")"; + } else { + debug << "QIconEngine(nullptr)"; + } + return debug; +} +#endif -// -------- Icon Loader Engine -------- // +QIconEngine *QIconLoader::iconEngine(const QString &iconName) const +{ + qCDebug(lcIconLoader) << "Resolving icon engine for icon" << iconName; + std::unique_ptr<QIconEngine> iconEngine; -QIconLoaderEngine::QIconLoaderEngine(const QString& iconName) - : m_iconName(iconName), m_key(0) + if (!m_factory) { + qCDebug(lcIconLoader) << "Finding a plugin for theme" << themeName(); + // try to find a plugin that supports the current theme + const int factoryIndex = qt_iconEngineFactoryLoader()->indexOf(themeName()); + if (factoryIndex >= 0) + m_factory = qobject_cast<QIconEnginePlugin *>(qt_iconEngineFactoryLoader()->instance(factoryIndex)); + } + if (m_factory && *m_factory) + iconEngine.reset(m_factory.value()->create(iconName)); + + if (hasUserTheme() && (!iconEngine || iconEngine->isNull())) + 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<QIconEngine> 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) { } -QIconLoaderEngine::~QIconLoaderEngine() = default; +QThemeIconEngine::QThemeIconEngine(const QThemeIconEngine &other) + : QProxyIconEngine() + , m_iconName(other.m_iconName) +{ +} -QIconLoaderEngine::QIconLoaderEngine(const QIconLoaderEngine &other) - : QIconEngine(other), - m_iconName(other.m_iconName), - m_key(0) +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 *QIconLoaderEngine::clone() const +QIconEngine *QThemeIconEngine::clone() const { - return new QIconLoaderEngine(*this); + return new QThemeIconEngine(*this); } -bool QIconLoaderEngine::read(QDataStream &in) { +bool QThemeIconEngine::read(QDataStream &in) { in >> m_iconName; return true; } -bool QIconLoaderEngine::write(QDataStream &out) const +bool QThemeIconEngine::write(QDataStream &out) const { out << m_iconName; return true; } -bool QIconLoaderEngine::hasIcon() const +QIconEngine *QThemeIconEngine::proxiedEngine() const { - return !(m_info.entries.empty()); + 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 } -// Lazily load the icon -void QIconLoaderEngine::ensureLoaded() +bool QIconLoaderEngine::hasIcon() const { - if (QIconLoader::instance()->themeKey() != m_key) { - m_info = QIconLoader::instance()->loadIcon(m_iconName); - m_key = QIconLoader::instance()->themeKey(); - } + return !(m_info.entries.empty()); } void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect, @@ -737,8 +885,6 @@ QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, Q_UNUSED(mode); Q_UNUSED(state); - ensureLoaded(); - QIconLoaderEngineEntry *entry = entryForSize(m_info, size); if (entry) { const QIconDirInfo &dir = entry->dir; @@ -747,7 +893,7 @@ QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, } else if (dir.type == QIconDirInfo::Fallback) { return QIcon(entry->filename).actualSize(size, mode, state); } else { - int result = qMin<int>(dir.size, qMin(size.width(), size.height())); + int result = qMin<int>(dir.size * dir.scale, qMin(size.width(), size.height())); return QSize(result, result); } } @@ -763,18 +909,15 @@ QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State st 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); - + const auto actualSize = QPixmapIconEngine::adjustSize(size, basePixmap.size()); QString key = "$qt_theme_"_L1 - % HexString<qint64>(basePixmap.cacheKey()) - % HexString<int>(mode) - % HexString<qint64>(QGuiApplication::palette().cacheKey()) - % HexString<int>(actualSize.width()) - % HexString<int>(actualSize.height()); + % HexString<quint64>(basePixmap.cacheKey()) + % HexString<quint8>(mode) + % HexString<quint64>(QGuiApplication::palette().cacheKey()) + % HexString<uint>(actualSize.width()) + % HexString<uint>(actualSize.height()); QPixmap cachedPixmap; if (QPixmapCache::find(key, &cachedPixmap)) { @@ -807,8 +950,6 @@ QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) { - ensureLoaded(); - QIconLoaderEngineEntry *entry = entryForSize(m_info, size); if (entry) return entry->pixmap(size, mode, state); @@ -823,19 +964,16 @@ QString QIconLoaderEngine::key() const QString QIconLoaderEngine::iconName() { - ensureLoaded(); return m_info.iconName; } bool QIconLoaderEngine::isNull() { - ensureLoaded(); return m_info.entries.empty(); } QPixmap QIconLoaderEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) { - ensureLoaded(); const int integerScale = qCeil(scale); QIconLoaderEngineEntry *entry = entryForSize(m_info, size / integerScale, integerScale); return entry ? entry->pixmap(size, mode, state) : QPixmap(); @@ -845,7 +983,7 @@ QList<QSize> QIconLoaderEngine::availableSizes(QIcon::Mode mode, QIcon::State st { Q_UNUSED(mode); Q_UNUSED(state); - ensureLoaded(); + const qsizetype N = qsizetype(m_info.entries.size()); QList<QSize> sizes; sizes.reserve(N); |