cf12defd28
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
1513 lines
48 KiB
C++
1513 lines
48 KiB
C++
/*
|
|
|
|
kiconloader.cpp: An icon loader for KDE with theming functionality.
|
|
|
|
This file is part of the KDE project, module kdeui.
|
|
SPDX-FileCopyrightText: 2000 Geert Jansen <jansen@kde.org>
|
|
SPDX-FileCopyrightText: 2000 Antonio Larrosa <larrosa@kde.org>
|
|
SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-only
|
|
*/
|
|
|
|
#include "kiconloader.h"
|
|
#include "kiconloader_p.h"
|
|
|
|
// kdecore
|
|
#include <KConfigGroup>
|
|
#include <KSharedConfig>
|
|
#ifdef WITH_QTDBUS
|
|
#include <QDBusConnection>
|
|
#include <QDBusMessage>
|
|
#endif
|
|
#include <QCryptographicHash>
|
|
#include <QXmlStreamReader>
|
|
#include <QXmlStreamWriter>
|
|
|
|
// kdeui
|
|
#include "debug.h"
|
|
#include "kiconcolors.h"
|
|
#include "kiconeffect.h"
|
|
#include "kicontheme.h"
|
|
|
|
#include <KColorScheme>
|
|
#include <KCompressionDevice>
|
|
|
|
#include <QBuffer>
|
|
#include <QByteArray>
|
|
#include <QDataStream>
|
|
#include <QDir>
|
|
#include <QElapsedTimer>
|
|
#include <QFileInfo>
|
|
#include <QGuiApplication>
|
|
#include <QIcon>
|
|
#include <QImage>
|
|
#include <QMimeDatabase>
|
|
#include <QMovie>
|
|
#include <QPainter>
|
|
#include <QPixmap>
|
|
#include <QPixmapCache>
|
|
#include <QStringBuilder> // % operator for QString
|
|
#include <QtGui/private/qiconloader_p.h>
|
|
|
|
#include <qplatformdefs.h> //for readlink
|
|
|
|
/**
|
|
* Function to convert an uint32_t to AARRGGBB hex values.
|
|
*
|
|
* W A R N I N G !
|
|
* This function is for internal use!
|
|
*/
|
|
KICONTHEMES_EXPORT void uintToHex(uint32_t colorData, QChar *buffer)
|
|
{
|
|
static const char hexLookup[] = "0123456789abcdef";
|
|
buffer += 7;
|
|
uchar *colorFields = reinterpret_cast<uchar *>(&colorData);
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
*buffer-- = hexLookup[*colorFields & 0xf];
|
|
*buffer-- = hexLookup[*colorFields >> 4];
|
|
colorFields++;
|
|
}
|
|
}
|
|
|
|
static QString paletteId(const KIconColors &colors)
|
|
{
|
|
// 8 per color. We want 3 colors thus 8*4=32.
|
|
QString buffer(32, Qt::Uninitialized);
|
|
|
|
uintToHex(colors.text().rgba(), buffer.data());
|
|
uintToHex(colors.highlight().rgba(), buffer.data() + 8);
|
|
uintToHex(colors.highlightedText().rgba(), buffer.data() + 16);
|
|
uintToHex(colors.background().rgba(), buffer.data() + 24);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/*** KIconThemeNode: A node in the icon theme dependency tree. ***/
|
|
|
|
class KIconThemeNode
|
|
{
|
|
public:
|
|
KIconThemeNode(KIconTheme *_theme);
|
|
~KIconThemeNode();
|
|
|
|
KIconThemeNode(const KIconThemeNode &) = delete;
|
|
KIconThemeNode &operator=(const KIconThemeNode &) = delete;
|
|
|
|
void queryIcons(QStringList *lst, int size, KIconLoader::Context context) const;
|
|
void queryIconsByContext(QStringList *lst, int size, KIconLoader::Context context) const;
|
|
QString findIcon(const QString &name, int size, KIconLoader::MatchType match) const;
|
|
|
|
KIconTheme *theme;
|
|
};
|
|
|
|
KIconThemeNode::KIconThemeNode(KIconTheme *_theme)
|
|
{
|
|
theme = _theme;
|
|
}
|
|
|
|
KIconThemeNode::~KIconThemeNode()
|
|
{
|
|
delete theme;
|
|
}
|
|
|
|
void KIconThemeNode::queryIcons(QStringList *result, int size, KIconLoader::Context context) const
|
|
{
|
|
// add the icons of this theme to it
|
|
*result += theme->queryIcons(size, context);
|
|
}
|
|
|
|
void KIconThemeNode::queryIconsByContext(QStringList *result, int size, KIconLoader::Context context) const
|
|
{
|
|
// add the icons of this theme to it
|
|
*result += theme->queryIconsByContext(size, context);
|
|
}
|
|
|
|
QString KIconThemeNode::findIcon(const QString &name, int size, KIconLoader::MatchType match) const
|
|
{
|
|
return theme->iconPath(name, size, match);
|
|
}
|
|
|
|
extern KICONTHEMES_EXPORT int kiconloader_ms_between_checks;
|
|
KICONTHEMES_EXPORT int kiconloader_ms_between_checks = 5000;
|
|
|
|
class KIconLoaderGlobalData : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
KIconLoaderGlobalData()
|
|
{
|
|
#ifdef WITH_QTDBUS
|
|
if (QDBusConnection::sessionBus().interface()) {
|
|
QDBusConnection::sessionBus().connect(QString(),
|
|
QStringLiteral("/KIconLoader"),
|
|
QStringLiteral("org.kde.KIconLoader"),
|
|
QStringLiteral("iconChanged"),
|
|
this,
|
|
SIGNAL(iconChanged(int)));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void emitChange(KIconLoader::Group group)
|
|
{
|
|
#ifdef WITH_QTDBUS
|
|
if (QDBusConnection::sessionBus().interface()) {
|
|
QDBusMessage message =
|
|
QDBusMessage::createSignal(QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"));
|
|
message.setArguments(QList<QVariant>() << int(group));
|
|
QDBusConnection::sessionBus().send(message);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
QString genericIconFor(const QString &icon) const
|
|
{
|
|
if (!m_loaded) {
|
|
// load icons lazily as initializing the icons is very expensive
|
|
const_cast<KIconLoaderGlobalData *>(this)->loadGenericIcons();
|
|
}
|
|
return m_genericIcons.value(icon);
|
|
}
|
|
|
|
Q_SIGNALS:
|
|
void iconChanged(int group);
|
|
|
|
private:
|
|
void loadGenericIcons()
|
|
{
|
|
if (m_loaded) {
|
|
return;
|
|
}
|
|
m_loaded = true;
|
|
// use Qt to fill in the generic mime-type icon information
|
|
const auto allMimeTypes = QMimeDatabase().allMimeTypes();
|
|
for (const auto &mimeType : allMimeTypes) {
|
|
m_genericIcons.insert(mimeType.iconName(), mimeType.genericIconName());
|
|
}
|
|
}
|
|
|
|
private:
|
|
QHash<QString, QString> m_genericIcons;
|
|
bool m_loaded = false;
|
|
};
|
|
|
|
Q_GLOBAL_STATIC(KIconLoaderGlobalData, s_globalData)
|
|
|
|
KIconLoaderPrivate::KIconLoaderPrivate(const QString &_appname, const QStringList &extraSearchPaths, KIconLoader *qq)
|
|
: q(qq)
|
|
, m_appname(_appname)
|
|
{
|
|
q->connect(s_globalData, &KIconLoaderGlobalData::iconChanged, q, [this](int group) {
|
|
_k_refreshIcons(group);
|
|
});
|
|
init(m_appname, extraSearchPaths);
|
|
}
|
|
|
|
KIconLoaderPrivate::~KIconLoaderPrivate()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
KIconLoaderPrivate *KIconLoaderPrivate::get(KIconLoader *loader)
|
|
{
|
|
return loader->d.get();
|
|
}
|
|
|
|
void KIconLoaderPrivate::clear()
|
|
{
|
|
/* antlarr: There's no need to delete d->mpThemeRoot as it's already
|
|
deleted when the elements of d->links are deleted */
|
|
qDeleteAll(links);
|
|
delete[] mpGroups;
|
|
mpGroups = nullptr;
|
|
mPixmapCache.clear();
|
|
m_appname.clear();
|
|
searchPaths.clear();
|
|
links.clear();
|
|
mIconThemeInited = false;
|
|
mThemesInTree.clear();
|
|
}
|
|
|
|
#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
|
|
void KIconLoaderPrivate::drawOverlays(const KIconLoader *iconLoader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays)
|
|
{
|
|
if (overlays.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
const int width = pix.size().width();
|
|
const int height = pix.size().height();
|
|
const int iconSize = qMin(width, height);
|
|
int overlaySize;
|
|
|
|
if (iconSize < 32) {
|
|
overlaySize = 8;
|
|
} else if (iconSize <= 48) {
|
|
overlaySize = 16;
|
|
} else if (iconSize <= 96) {
|
|
overlaySize = 22;
|
|
} else if (iconSize < 256) {
|
|
overlaySize = 32;
|
|
} else {
|
|
overlaySize = 64;
|
|
}
|
|
|
|
QPainter painter(&pix);
|
|
|
|
int count = 0;
|
|
for (const QString &overlay : overlays) {
|
|
// Ensure empty strings fill up a emblem spot
|
|
// Needed when you have several emblems to ensure they're always painted
|
|
// at the same place, even if one is not here
|
|
if (overlay.isEmpty()) {
|
|
++count;
|
|
continue;
|
|
}
|
|
|
|
// TODO: should we pass in the kstate? it results in a slower
|
|
// path, and perhaps emblems should remain in the default state
|
|
// anyways?
|
|
QPixmap pixmap = iconLoader->loadIcon(overlay, group, overlaySize, state, QStringList(), nullptr, true);
|
|
|
|
if (pixmap.isNull()) {
|
|
continue;
|
|
}
|
|
|
|
// match the emblem's devicePixelRatio to the original pixmap's
|
|
pixmap.setDevicePixelRatio(pix.devicePixelRatio());
|
|
const int margin = pixmap.devicePixelRatio() * 0.05 * iconSize;
|
|
|
|
QPoint startPoint;
|
|
switch (count) {
|
|
case 0:
|
|
// bottom right corner
|
|
startPoint = QPoint(width - overlaySize - margin, height - overlaySize - margin);
|
|
break;
|
|
case 1:
|
|
// bottom left corner
|
|
startPoint = QPoint(margin, height - overlaySize - margin);
|
|
break;
|
|
case 2:
|
|
// top left corner
|
|
startPoint = QPoint(margin, margin);
|
|
break;
|
|
case 3:
|
|
// top right corner
|
|
startPoint = QPoint(width - overlaySize - margin, margin);
|
|
break;
|
|
}
|
|
|
|
startPoint /= pix.devicePixelRatio();
|
|
|
|
painter.drawPixmap(startPoint, pixmap);
|
|
|
|
++count;
|
|
if (count > 3) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void KIconLoaderPrivate::_k_refreshIcons(int group)
|
|
{
|
|
KSharedConfig::Ptr sharedConfig = KSharedConfig::openConfig();
|
|
sharedConfig->reparseConfiguration();
|
|
const QString newThemeName = sharedConfig->group("Icons").readEntry("Theme", QStringLiteral("breeze"));
|
|
if (!newThemeName.isEmpty()) {
|
|
// NOTE Do NOT use QIcon::setThemeName here it makes Qt not use icon engine of the platform theme
|
|
// anymore (KIconEngine on Plasma, which breaks recoloring) and overwrites a user set themeName
|
|
// TODO KF6 this should be done in the Plasma QPT
|
|
QIconLoader::instance()->updateSystemTheme();
|
|
}
|
|
|
|
q->newIconLoader();
|
|
mIconAvailability.clear();
|
|
Q_EMIT q->iconChanged(group);
|
|
}
|
|
|
|
bool KIconLoaderPrivate::shouldCheckForUnknownIcons()
|
|
{
|
|
if (mLastUnknownIconCheck.isValid() && mLastUnknownIconCheck.elapsed() < kiconloader_ms_between_checks) {
|
|
return false;
|
|
}
|
|
mLastUnknownIconCheck.start();
|
|
return true;
|
|
}
|
|
|
|
KIconLoader::KIconLoader(const QString &appname, const QStringList &extraSearchPaths, QObject *parent)
|
|
: QObject(parent)
|
|
, d(new KIconLoaderPrivate(appname, extraSearchPaths, this))
|
|
{
|
|
setObjectName(appname);
|
|
}
|
|
|
|
void KIconLoader::reconfigure(const QString &_appname, const QStringList &extraSearchPaths)
|
|
{
|
|
d->clear();
|
|
d->init(_appname, extraSearchPaths);
|
|
}
|
|
|
|
void KIconLoaderPrivate::init(const QString &_appname, const QStringList &extraSearchPaths)
|
|
{
|
|
extraDesktopIconsLoaded = false;
|
|
mIconThemeInited = false;
|
|
mpThemeRoot = nullptr;
|
|
|
|
searchPaths = extraSearchPaths;
|
|
|
|
m_appname = !_appname.isEmpty() ? _appname : QCoreApplication::applicationName();
|
|
|
|
// Cost here is number of pixels
|
|
mPixmapCache.setMaxCost(10 * 1024 * 1024);
|
|
|
|
// These have to match the order in kiconloader.h
|
|
static const char *const groups[] = {"Desktop", "Toolbar", "MainToolbar", "Small", "Panel", "Dialog", nullptr};
|
|
|
|
// load default sizes
|
|
initIconThemes();
|
|
KIconTheme *defaultSizesTheme = links.empty() ? nullptr : links.first()->theme;
|
|
mpGroups = new KIconGroup[static_cast<int>(KIconLoader::LastGroup)];
|
|
for (KIconLoader::Group i = KIconLoader::FirstGroup; i < KIconLoader::LastGroup; ++i) {
|
|
if (groups[i] == nullptr) {
|
|
break;
|
|
}
|
|
|
|
if (defaultSizesTheme) {
|
|
mpGroups[i].size = defaultSizesTheme->defaultSize(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void KIconLoaderPrivate::initIconThemes()
|
|
{
|
|
if (mIconThemeInited) {
|
|
return;
|
|
}
|
|
// qCDebug(KICONTHEMES);
|
|
mIconThemeInited = true;
|
|
|
|
// Add the default theme and its base themes to the theme tree
|
|
KIconTheme *def = new KIconTheme(KIconTheme::current(), m_appname);
|
|
if (!def->isValid()) {
|
|
delete def;
|
|
// warn, as this is actually a small penalty hit
|
|
qCDebug(KICONTHEMES) << "Couldn't find current icon theme, falling back to default.";
|
|
def = new KIconTheme(KIconTheme::defaultThemeName(), m_appname);
|
|
if (!def->isValid()) {
|
|
qCDebug(KICONTHEMES) << "Standard icon theme" << KIconTheme::defaultThemeName() << "not found!";
|
|
delete def;
|
|
return;
|
|
}
|
|
}
|
|
mpThemeRoot = new KIconThemeNode(def);
|
|
mThemesInTree.append(def->internalName());
|
|
links.append(mpThemeRoot);
|
|
addBaseThemes(mpThemeRoot, m_appname);
|
|
|
|
// Insert application specific themes at the top.
|
|
searchPaths.append(m_appname + QStringLiteral("/pics"));
|
|
|
|
// Add legacy icon dirs.
|
|
searchPaths.append(QStringLiteral("icons")); // was xdgdata-icon in KStandardDirs
|
|
// These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
|
|
searchPaths.append(QStringLiteral("pixmaps")); // was xdgdata-pixmaps in KStandardDirs
|
|
}
|
|
|
|
KIconLoader::~KIconLoader() = default;
|
|
|
|
QStringList KIconLoader::searchPaths() const
|
|
{
|
|
return d->searchPaths;
|
|
}
|
|
|
|
void KIconLoader::addAppDir(const QString &appname, const QString &themeBaseDir)
|
|
{
|
|
d->searchPaths.append(appname + QStringLiteral("/pics"));
|
|
d->addAppThemes(appname, themeBaseDir);
|
|
}
|
|
|
|
void KIconLoaderPrivate::addAppThemes(const QString &appname, const QString &themeBaseDir)
|
|
{
|
|
KIconTheme *def = new KIconTheme(QStringLiteral("hicolor"), appname, themeBaseDir);
|
|
if (!def->isValid()) {
|
|
delete def;
|
|
def = new KIconTheme(KIconTheme::defaultThemeName(), appname, themeBaseDir);
|
|
}
|
|
KIconThemeNode *node = new KIconThemeNode(def);
|
|
bool addedToLinks = false;
|
|
|
|
if (!mThemesInTree.contains(appname)) {
|
|
mThemesInTree.append(appname);
|
|
links.append(node);
|
|
addedToLinks = true;
|
|
}
|
|
addBaseThemes(node, appname);
|
|
|
|
if (!addedToLinks) {
|
|
// Nodes in links are being deleted later - this one needs manual care.
|
|
delete node;
|
|
}
|
|
}
|
|
|
|
void KIconLoaderPrivate::addBaseThemes(KIconThemeNode *node, const QString &appname)
|
|
{
|
|
// Quote from the icon theme specification:
|
|
// The lookup is done first in the current theme, and then recursively
|
|
// in each of the current theme's parents, and finally in the
|
|
// default theme called "hicolor" (implementations may add more
|
|
// default themes before "hicolor", but "hicolor" must be last).
|
|
//
|
|
// So we first make sure that all inherited themes are added, then we
|
|
// add the KDE default theme as fallback for all icons that might not be
|
|
// present in an inherited theme, and hicolor goes last.
|
|
|
|
addInheritedThemes(node, appname);
|
|
addThemeByName(QIcon::fallbackThemeName(), appname);
|
|
addThemeByName(QStringLiteral("hicolor"), appname);
|
|
}
|
|
|
|
void KIconLoaderPrivate::addInheritedThemes(KIconThemeNode *node, const QString &appname)
|
|
{
|
|
const QStringList inheritedThemes = node->theme->inherits();
|
|
|
|
for (const auto &inheritedTheme : inheritedThemes) {
|
|
if (inheritedTheme == QLatin1String("hicolor")) {
|
|
// The icon theme spec says that "hicolor" must be the very last
|
|
// of all inherited themes, so don't add it here but at the very end
|
|
// of addBaseThemes().
|
|
continue;
|
|
}
|
|
addThemeByName(inheritedTheme, appname);
|
|
}
|
|
}
|
|
|
|
void KIconLoaderPrivate::addThemeByName(const QString &themename, const QString &appname)
|
|
{
|
|
if (mThemesInTree.contains(themename + appname)) {
|
|
return;
|
|
}
|
|
KIconTheme *theme = new KIconTheme(themename, appname);
|
|
if (!theme->isValid()) {
|
|
delete theme;
|
|
return;
|
|
}
|
|
KIconThemeNode *n = new KIconThemeNode(theme);
|
|
mThemesInTree.append(themename + appname);
|
|
links.append(n);
|
|
addInheritedThemes(n, appname);
|
|
}
|
|
|
|
void KIconLoaderPrivate::addExtraDesktopThemes()
|
|
{
|
|
if (extraDesktopIconsLoaded) {
|
|
return;
|
|
}
|
|
|
|
QStringList list;
|
|
const QStringList icnlibs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory);
|
|
for (const auto &iconDir : icnlibs) {
|
|
QDir dir(iconDir);
|
|
if (!dir.exists()) {
|
|
continue;
|
|
}
|
|
const auto defaultEntries = dir.entryInfoList(QStringList(QStringLiteral("default.*")), QDir::Dirs);
|
|
for (const auto &defaultEntry : defaultEntries) {
|
|
if (!QFileInfo::exists(defaultEntry.filePath() + QLatin1String("/index.desktop")) //
|
|
&& !QFileInfo::exists(defaultEntry.filePath() + QLatin1String("/index.theme"))) {
|
|
continue;
|
|
}
|
|
if (defaultEntry.isSymbolicLink()) {
|
|
const QString themeName = QDir(defaultEntry.symLinkTarget()).dirName();
|
|
if (!list.contains(themeName)) {
|
|
list.append(themeName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto &theme : list) {
|
|
// Don't add the KDE defaults once more, we have them anyways.
|
|
if (theme == QLatin1String("default.kde") || theme == QLatin1String("default.kde4")) {
|
|
continue;
|
|
}
|
|
addThemeByName(theme, QLatin1String(""));
|
|
}
|
|
|
|
extraDesktopIconsLoaded = true;
|
|
}
|
|
|
|
void KIconLoader::drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state) const
|
|
{
|
|
d->drawOverlays(this, group, state, pixmap, overlays);
|
|
}
|
|
|
|
QString KIconLoaderPrivate::removeIconExtension(const QString &name) const
|
|
{
|
|
if (name.endsWith(QLatin1String(".png")) //
|
|
|| name.endsWith(QLatin1String(".xpm")) //
|
|
|| name.endsWith(QLatin1String(".svg"))) {
|
|
return name.left(name.length() - 4);
|
|
} else if (name.endsWith(QLatin1String(".svgz"))) {
|
|
return name.left(name.length() - 5);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
void KIconLoaderPrivate::normalizeIconMetadata(KIconLoader::Group &group, QSize &size, int &state) const
|
|
{
|
|
if ((state < 0) || (state >= KIconLoader::LastState)) {
|
|
qCWarning(KICONTHEMES) << "Invalid icon state:" << state << ", should be one of KIconLoader::States";
|
|
state = KIconLoader::DefaultState;
|
|
}
|
|
|
|
if (size.width() < 0 || size.height() < 0) {
|
|
size = {};
|
|
}
|
|
|
|
// For "User" icons, bail early since the size should be based on the size on disk,
|
|
// which we've already checked.
|
|
if (group == KIconLoader::User) {
|
|
return;
|
|
}
|
|
|
|
if ((group < -1) || (group >= KIconLoader::LastGroup)) {
|
|
qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
|
|
group = KIconLoader::Desktop;
|
|
}
|
|
|
|
// If size == 0, use default size for the specified group.
|
|
if (size.isNull()) {
|
|
if (group < 0) {
|
|
qWarning() << "Neither size nor group specified!";
|
|
group = KIconLoader::Desktop;
|
|
}
|
|
size = QSize(mpGroups[group].size, mpGroups[group].size);
|
|
}
|
|
}
|
|
|
|
QString KIconLoaderPrivate::makeCacheKey(const QString &name,
|
|
KIconLoader::Group group,
|
|
const QStringList &overlays,
|
|
const QSize &size,
|
|
qreal scale,
|
|
int state,
|
|
const KIconColors &colors) const
|
|
{
|
|
// The KSharedDataCache is shared so add some namespacing. The following code
|
|
// uses QStringBuilder (new in Qt 4.6)
|
|
|
|
QString effectKey = QStringLiteral("noeffect");
|
|
|
|
if ((group == KIconLoader::Desktop || group == KIconLoader::Panel) && state == KIconLoader::ActiveState) {
|
|
effectKey = QStringLiteral("active");
|
|
} else if (state == KIconLoader::DisabledState && group >= 0 && group < KIconLoader::LastGroup) {
|
|
effectKey = QStringLiteral("disabled");
|
|
}
|
|
|
|
/* clang-format off */
|
|
return (group == KIconLoader::User ? QLatin1String("$kicou_") : QLatin1String("$kico_"))
|
|
% name
|
|
% QLatin1Char('_')
|
|
% (size.width() == size.height() ? QString::number(size.height()) : QString::number(size.height()) % QLatin1Char('x') % QString::number(size.width()))
|
|
% QLatin1Char('@')
|
|
% QString::number(scale, 'f', 1)
|
|
% QLatin1Char('_')
|
|
% overlays.join(QLatin1Char('_'))
|
|
% effectKey
|
|
% QLatin1Char('_')
|
|
% paletteId(colors)
|
|
% (q->theme() && q->theme()->followsColorScheme() && state == KIconLoader::SelectedState ? QStringLiteral("_selected") : QString());
|
|
/* clang-format on */
|
|
}
|
|
|
|
QByteArray KIconLoaderPrivate::processSvg(const QString &path, KIconLoader::States state, const KIconColors &colors) const
|
|
{
|
|
std::unique_ptr<QIODevice> device;
|
|
|
|
if (path.endsWith(QLatin1String("svgz"))) {
|
|
device.reset(new KCompressionDevice(path, KCompressionDevice::GZip));
|
|
} else {
|
|
device.reset(new QFile(path));
|
|
}
|
|
|
|
if (!device->open(QIODevice::ReadOnly)) {
|
|
return QByteArray();
|
|
}
|
|
|
|
const QString styleSheet = colors.stylesheet(state);
|
|
QByteArray processedContents;
|
|
QXmlStreamReader reader(device.get());
|
|
|
|
QBuffer buffer(&processedContents);
|
|
buffer.open(QIODevice::WriteOnly);
|
|
QXmlStreamWriter writer(&buffer);
|
|
while (!reader.atEnd()) {
|
|
if (reader.readNext() == QXmlStreamReader::StartElement //
|
|
&& reader.qualifiedName() == QLatin1String("style") //
|
|
&& reader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) {
|
|
writer.writeStartElement(QStringLiteral("style"));
|
|
writer.writeAttributes(reader.attributes());
|
|
writer.writeCharacters(styleSheet);
|
|
writer.writeEndElement();
|
|
while (reader.tokenType() != QXmlStreamReader::EndElement) {
|
|
reader.readNext();
|
|
}
|
|
} else if (reader.tokenType() != QXmlStreamReader::Invalid) {
|
|
writer.writeCurrentToken(reader);
|
|
}
|
|
}
|
|
buffer.close();
|
|
|
|
return processedContents;
|
|
}
|
|
|
|
QImage KIconLoaderPrivate::createIconImage(const QString &path, const QSize &size, qreal scale, KIconLoader::States state, const KIconColors &colors)
|
|
{
|
|
// TODO: metadata in the theme to make it do this only if explicitly supported?
|
|
QImageReader reader;
|
|
QBuffer buffer;
|
|
|
|
if (q->theme() && q->theme()->followsColorScheme() && (path.endsWith(QLatin1String("svg")) || path.endsWith(QLatin1String("svgz")))) {
|
|
buffer.setData(processSvg(path, state, colors));
|
|
reader.setDevice(&buffer);
|
|
reader.setFormat("svg");
|
|
} else {
|
|
reader.setFileName(path);
|
|
}
|
|
|
|
if (!reader.canRead()) {
|
|
return QImage();
|
|
}
|
|
|
|
if (!size.isNull()) {
|
|
// ensure we keep aspect ratio
|
|
const QSize wantedSize = size * scale;
|
|
QSize finalSize(reader.size());
|
|
if (finalSize.isNull()) {
|
|
// nothing to scale
|
|
finalSize = wantedSize;
|
|
} else {
|
|
// like QSvgIconEngine::pixmap try to keep aspect ratio
|
|
finalSize.scale(wantedSize, Qt::KeepAspectRatio);
|
|
}
|
|
reader.setScaledSize(finalSize);
|
|
}
|
|
|
|
return reader.read();
|
|
}
|
|
|
|
void KIconLoaderPrivate::insertCachedPixmapWithPath(const QString &key, const QPixmap &data, const QString &path = QString())
|
|
{
|
|
// Even if the pixmap is null, we add it to the caches so that we record
|
|
// the fact that whatever icon led to us getting a null pixmap doesn't
|
|
// exist.
|
|
|
|
PixmapWithPath *pixmapPath = new PixmapWithPath;
|
|
pixmapPath->pixmap = data;
|
|
pixmapPath->path = path;
|
|
|
|
mPixmapCache.insert(key, pixmapPath, data.width() * data.height() + 1);
|
|
}
|
|
|
|
bool KIconLoaderPrivate::findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path)
|
|
{
|
|
// If the pixmap is present in our local process cache, use that since we
|
|
// don't need to decompress and upload it to the X server/graphics card.
|
|
const PixmapWithPath *pixmapPath = mPixmapCache.object(key);
|
|
if (pixmapPath) {
|
|
path = pixmapPath->path;
|
|
data = pixmapPath->pixmap;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KIconLoaderPrivate::findMatchingIconWithGenericFallbacks(const QString &name, int size, qreal scale) const
|
|
{
|
|
QString path = findMatchingIcon(name, size, scale);
|
|
if (!path.isEmpty()) {
|
|
return path;
|
|
}
|
|
|
|
const QString genericIcon = s_globalData()->genericIconFor(name);
|
|
if (!genericIcon.isEmpty()) {
|
|
path = findMatchingIcon(genericIcon, size, scale);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
QString KIconLoaderPrivate::findMatchingIcon(const QString &name, int size, qreal scale) const
|
|
{
|
|
// This looks for the exact match and its
|
|
// generic fallbacks in each themeNode one after the other.
|
|
|
|
// In theory we should only do this for mimetype icons, not for app icons,
|
|
// but that would require different APIs. The long term solution is under
|
|
// development for Qt >= 5.8, QFileIconProvider calling QPlatformTheme::fileIcon,
|
|
// using QMimeType::genericIconName() to get the proper -x-generic fallback.
|
|
// Once everyone uses that to look up mimetype icons, we can kill the fallback code
|
|
// from this method.
|
|
|
|
bool genericFallback = name.endsWith(QLatin1String("-x-generic"));
|
|
bool isSymbolic = name.endsWith(QLatin1String("-symbolic"));
|
|
QString path;
|
|
for (KIconThemeNode *themeNode : std::as_const(links)) {
|
|
QString currentName = name;
|
|
|
|
while (!currentName.isEmpty()) {
|
|
path = themeNode->theme->iconPathByName(currentName, size, KIconLoader::MatchBest, scale);
|
|
if (!path.isEmpty()) {
|
|
return path;
|
|
}
|
|
|
|
if (genericFallback) {
|
|
// we already tested the base name
|
|
break;
|
|
}
|
|
|
|
// If the icon was originally symbolic, we want to keep that suffix at the end.
|
|
// The next block removes the last word including the -, "a-b-symbolic" will become "a-b"
|
|
// We remove it beforehand, "a-b-symbolic" now is "a-symbolic" and we'll add it back later.
|
|
if (isSymbolic) {
|
|
currentName.remove(QLatin1String("-symbolic"));
|
|
|
|
// Handle cases where the icon lacks a symbolic version.
|
|
// For example, "knotes-symbolic" doesn't exist and has no fallback or generic version.
|
|
// "knotes" does exist, so let's check if a non-symbolic icon works before continuing.
|
|
path = themeNode->theme->iconPathByName(currentName, size, KIconLoader::MatchBest, scale);
|
|
if (!path.isEmpty()) {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
int rindex = currentName.lastIndexOf(QLatin1Char('-'));
|
|
if (rindex > 1) { // > 1 so that we don't split x-content or x-epoc
|
|
currentName.truncate(rindex);
|
|
|
|
if (currentName.endsWith(QLatin1String("-x"))) {
|
|
currentName.chop(2);
|
|
}
|
|
|
|
// Add back the -symbolic if requested
|
|
if (isSymbolic) {
|
|
currentName += QLatin1String("-symbolic");
|
|
}
|
|
} else {
|
|
// From update-mime-database.c
|
|
static const QSet<QString> mediaTypes = QSet<QString>{QStringLiteral("text"),
|
|
QStringLiteral("application"),
|
|
QStringLiteral("image"),
|
|
QStringLiteral("audio"),
|
|
QStringLiteral("inode"),
|
|
QStringLiteral("video"),
|
|
QStringLiteral("message"),
|
|
QStringLiteral("model"),
|
|
QStringLiteral("multipart"),
|
|
QStringLiteral("x-content"),
|
|
QStringLiteral("x-epoc")};
|
|
// Shared-mime-info spec says:
|
|
// "If [generic-icon] is not specified then the mimetype is used to generate the
|
|
// generic icon by using the top-level media type (e.g. "video" in "video/ogg")
|
|
// and appending "-x-generic" (i.e. "video-x-generic" in the previous example)."
|
|
if (mediaTypes.contains(currentName)) {
|
|
currentName += QLatin1String("-x-generic");
|
|
genericFallback = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (path.isEmpty()) {
|
|
const QStringList fallbackPaths = QIcon::fallbackSearchPaths();
|
|
|
|
for (const QString &path : fallbackPaths) {
|
|
const QString extensions[] = {QStringLiteral(".png"), QStringLiteral(".svg"), QStringLiteral(".svgz"), QStringLiteral(".xpm")};
|
|
|
|
for (const QString &ext : extensions) {
|
|
const QString file = path + '/' + name + ext;
|
|
|
|
if (QFileInfo::exists(file)) {
|
|
return file;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
QString KIconLoaderPrivate::preferredIconPath(const QString &name)
|
|
{
|
|
QString path;
|
|
|
|
auto it = mIconAvailability.constFind(name);
|
|
const auto end = mIconAvailability.constEnd();
|
|
|
|
if (it != end && it.value().isEmpty() && !shouldCheckForUnknownIcons()) {
|
|
return path; // known to be unavailable
|
|
}
|
|
|
|
if (it != end) {
|
|
path = it.value();
|
|
}
|
|
|
|
if (path.isEmpty()) {
|
|
path = q->iconPath(name, KIconLoader::Desktop, KIconLoader::MatchBest);
|
|
mIconAvailability.insert(name, path);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
inline QString KIconLoaderPrivate::unknownIconPath(int size, qreal scale) const
|
|
{
|
|
QString path = findMatchingIcon(QStringLiteral("unknown"), size, scale);
|
|
if (path.isEmpty()) {
|
|
qCDebug(KICONTHEMES) << "Warning: could not find \"unknown\" icon for size" << size << "at scale" << scale;
|
|
return QString();
|
|
}
|
|
return path;
|
|
}
|
|
|
|
QString KIconLoaderPrivate::locate(const QString &fileName)
|
|
{
|
|
for (const QString &dir : std::as_const(searchPaths)) {
|
|
const QString path = dir + QLatin1Char('/') + fileName;
|
|
if (QDir(dir).isAbsolute()) {
|
|
if (QFileInfo::exists(path)) {
|
|
return path;
|
|
}
|
|
} else {
|
|
const QString fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, path);
|
|
if (!fullPath.isEmpty()) {
|
|
return fullPath;
|
|
}
|
|
}
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
// Finds the absolute path to an icon.
|
|
|
|
QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull) const
|
|
{
|
|
return iconPath(_name, group_or_size, canReturnNull, 1 /*scale*/);
|
|
}
|
|
|
|
QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull, qreal scale) const
|
|
{
|
|
// we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
|
|
if (_name.isEmpty() || QDir::isAbsolutePath(_name)) {
|
|
// we have either an absolute path or nothing to work with
|
|
return _name;
|
|
}
|
|
|
|
QString name = d->removeIconExtension(_name);
|
|
|
|
QString path;
|
|
if (group_or_size == KIconLoader::User) {
|
|
path = d->locate(name + QLatin1String(".png"));
|
|
if (path.isEmpty()) {
|
|
path = d->locate(name + QLatin1String(".svgz"));
|
|
}
|
|
if (path.isEmpty()) {
|
|
path = d->locate(name + QLatin1String(".svg"));
|
|
}
|
|
if (path.isEmpty()) {
|
|
path = d->locate(name + QLatin1String(".xpm"));
|
|
}
|
|
return path;
|
|
}
|
|
|
|
if (group_or_size >= KIconLoader::LastGroup) {
|
|
qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
|
|
return path;
|
|
}
|
|
|
|
int size;
|
|
if (group_or_size >= 0) {
|
|
size = d->mpGroups[group_or_size].size;
|
|
} else {
|
|
size = -group_or_size;
|
|
}
|
|
|
|
if (_name.isEmpty()) {
|
|
if (canReturnNull) {
|
|
return QString();
|
|
} else {
|
|
return d->unknownIconPath(size, scale);
|
|
}
|
|
}
|
|
|
|
path = d->findMatchingIconWithGenericFallbacks(name, size, scale);
|
|
|
|
if (path.isEmpty()) {
|
|
// Try "User" group too.
|
|
path = iconPath(name, KIconLoader::User, true);
|
|
if (!path.isEmpty() || canReturnNull) {
|
|
return path;
|
|
}
|
|
|
|
return d->unknownIconPath(size, scale);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
QPixmap
|
|
KIconLoader::loadMimeTypeIcon(const QString &_iconName, KIconLoader::Group group, int size, int state, const QStringList &overlays, QString *path_store) const
|
|
{
|
|
QString iconName = _iconName;
|
|
const int slashindex = iconName.indexOf(QLatin1Char('/'));
|
|
if (slashindex != -1) {
|
|
iconName[slashindex] = QLatin1Char('-');
|
|
}
|
|
|
|
if (!d->extraDesktopIconsLoaded) {
|
|
const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
|
|
if (!pixmap.isNull()) {
|
|
return pixmap;
|
|
}
|
|
d->addExtraDesktopThemes();
|
|
}
|
|
const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
|
|
if (pixmap.isNull()) {
|
|
// Icon not found, fallback to application/octet-stream
|
|
return loadIcon(QStringLiteral("application-octet-stream"), group, size, state, overlays, path_store, false);
|
|
}
|
|
return pixmap;
|
|
}
|
|
|
|
QPixmap KIconLoader::loadIcon(const QString &_name,
|
|
KIconLoader::Group group,
|
|
int size,
|
|
int state,
|
|
const QStringList &overlays,
|
|
QString *path_store,
|
|
bool canReturnNull) const
|
|
{
|
|
return loadScaledIcon(_name, group, 1.0 /*scale*/, size, state, overlays, path_store, canReturnNull);
|
|
}
|
|
|
|
QPixmap KIconLoader::loadScaledIcon(const QString &_name,
|
|
KIconLoader::Group group,
|
|
qreal scale,
|
|
int size,
|
|
int state,
|
|
const QStringList &overlays,
|
|
QString *path_store,
|
|
bool canReturnNull) const
|
|
{
|
|
return loadScaledIcon(_name, group, scale, QSize(size, size), state, overlays, path_store, canReturnNull);
|
|
}
|
|
|
|
QPixmap KIconLoader::loadScaledIcon(const QString &_name,
|
|
KIconLoader::Group group,
|
|
qreal scale,
|
|
const QSize &size,
|
|
int state,
|
|
const QStringList &overlays,
|
|
QString *path_store,
|
|
bool canReturnNull) const
|
|
{
|
|
return loadScaledIcon(_name, group, scale, size, state, overlays, path_store, canReturnNull, {});
|
|
}
|
|
|
|
QPixmap KIconLoader::loadScaledIcon(const QString &_name,
|
|
KIconLoader::Group group,
|
|
qreal scale,
|
|
const QSize &_size,
|
|
int state,
|
|
const QStringList &overlays,
|
|
QString *path_store,
|
|
bool canReturnNull,
|
|
const std::optional<KIconColors> &colors) const
|
|
|
|
{
|
|
QString name = _name;
|
|
bool favIconOverlay = false;
|
|
|
|
if (_size.width() < 0 || _size.height() < 0 || _name.isEmpty()) {
|
|
return QPixmap();
|
|
}
|
|
|
|
QSize size = _size;
|
|
|
|
/*
|
|
* This method works in a kind of pipeline, with the following steps:
|
|
* 1. Sanity checks.
|
|
* 2. Convert _name, group, size, etc. to a key name.
|
|
* 3. Check if the key is already cached.
|
|
* 4. If not, initialize the theme and find/load the icon.
|
|
* 4a Apply overlays
|
|
* 4b Re-add to cache.
|
|
*/
|
|
|
|
// Special case for absolute path icons.
|
|
if (name.startsWith(QLatin1String("favicons/"))) {
|
|
favIconOverlay = true;
|
|
name = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + name + QStringLiteral(".png");
|
|
}
|
|
|
|
// we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
|
|
const bool absolutePath = QDir::isAbsolutePath(name);
|
|
if (!absolutePath) {
|
|
name = d->removeIconExtension(name);
|
|
}
|
|
|
|
// Don't bother looking for an icon with no name.
|
|
if (name.isEmpty()) {
|
|
return QPixmap();
|
|
}
|
|
|
|
// May modify group, size, or state. This function puts them into sane
|
|
// states.
|
|
d->normalizeIconMetadata(group, size, state);
|
|
|
|
// See if the image is already cached.
|
|
auto usedColors = colors ? *colors : d->mCustomColors ? d->mColors : KIconColors(qApp->palette());
|
|
QString key = d->makeCacheKey(name, group, overlays, size, scale, state, usedColors);
|
|
QPixmap pix;
|
|
|
|
bool iconWasUnknown = false;
|
|
QString path;
|
|
|
|
if (d->findCachedPixmapWithPath(key, pix, path)) {
|
|
if (path_store) {
|
|
*path_store = path;
|
|
}
|
|
|
|
if (!path.isEmpty()) {
|
|
return pix;
|
|
} else {
|
|
// path is empty for "unknown" icons, which should be searched for
|
|
// anew regularly
|
|
if (!d->shouldCheckForUnknownIcons()) {
|
|
return canReturnNull ? QPixmap() : pix;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Image is not cached... go find it and apply effects.
|
|
|
|
favIconOverlay = favIconOverlay && std::min(size.height(), size.width()) > 22;
|
|
|
|
// First we look for non-User icons. If we don't find one we'd search in
|
|
// the User space anyways...
|
|
if (group != KIconLoader::User) {
|
|
if (absolutePath && !favIconOverlay) {
|
|
path = name;
|
|
} else {
|
|
path = d->findMatchingIconWithGenericFallbacks(favIconOverlay ? QStringLiteral("text-html") : name, std::min(size.height(), size.width()), scale);
|
|
}
|
|
}
|
|
|
|
if (path.isEmpty()) {
|
|
// We do have a "User" icon, or we couldn't find the non-User one.
|
|
path = (absolutePath) ? name : iconPath(name, KIconLoader::User, canReturnNull);
|
|
}
|
|
|
|
// Still can't find it? Use "unknown" if we can't return null.
|
|
// We keep going in the function so we can ensure this result gets cached.
|
|
if (path.isEmpty() && !canReturnNull) {
|
|
path = d->unknownIconPath(std::min(size.height(), size.width()), scale);
|
|
iconWasUnknown = true;
|
|
}
|
|
|
|
QImage img;
|
|
if (!path.isEmpty()) {
|
|
img = d->createIconImage(path, size, scale, static_cast<KIconLoader::States>(state), usedColors);
|
|
}
|
|
|
|
// apply effects. When changing the logic here also adapt makeCacheKey
|
|
if ((group == KIconLoader::Desktop || group == KIconLoader::Panel) && state == KIconLoader::ActiveState) {
|
|
KIconEffect::toActive(img);
|
|
}
|
|
|
|
if (state == KIconLoader::DisabledState && group >= 0 && group < KIconLoader::LastGroup) {
|
|
KIconEffect::toDisabled(img);
|
|
}
|
|
|
|
if (favIconOverlay) {
|
|
QImage favIcon(name, "PNG");
|
|
if (!favIcon.isNull()) { // if favIcon not there yet, don't try to blend it
|
|
QPainter p(&img);
|
|
|
|
// Align the favicon overlay
|
|
QRect r(favIcon.rect());
|
|
r.moveBottomRight(img.rect().bottomRight());
|
|
r.adjust(-1, -1, -1, -1); // Move off edge
|
|
|
|
// Blend favIcon over img.
|
|
p.drawImage(r, favIcon);
|
|
}
|
|
}
|
|
|
|
pix = QPixmap::fromImage(std::move(img));
|
|
pix.setDevicePixelRatio(scale);
|
|
|
|
// TODO: If we make a loadIcon that returns the image we can convert
|
|
// drawOverlays to use the image instead of pixmaps as well so we don't
|
|
// have to transfer so much to the graphics card.
|
|
d->drawOverlays(this, group, state, pix, overlays);
|
|
|
|
// Don't add the path to our unknown icon to the cache, only cache the
|
|
// actual image.
|
|
if (iconWasUnknown) {
|
|
path.clear();
|
|
}
|
|
|
|
d->insertCachedPixmapWithPath(key, pix, path);
|
|
|
|
if (path_store) {
|
|
*path_store = path;
|
|
}
|
|
|
|
return pix;
|
|
}
|
|
|
|
#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
|
|
QMovie *KIconLoader::loadMovie(const QString &name, KIconLoader::Group group, int size, QObject *parent) const
|
|
{
|
|
QString file = moviePath(name, group, size);
|
|
if (file.isEmpty()) {
|
|
return nullptr;
|
|
}
|
|
int dirLen = file.lastIndexOf(QLatin1Char('/'));
|
|
const QString icon = iconPath(name, size ? -size : group, true);
|
|
if (!icon.isEmpty() && file.left(dirLen) != icon.left(dirLen)) {
|
|
return nullptr;
|
|
}
|
|
QMovie *movie = new QMovie(file, QByteArray(), parent);
|
|
if (!movie->isValid()) {
|
|
delete movie;
|
|
return nullptr;
|
|
}
|
|
return movie;
|
|
}
|
|
#endif
|
|
|
|
#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
|
|
QString KIconLoader::moviePath(const QString &name, KIconLoader::Group group, int size) const
|
|
{
|
|
if (!d->mpGroups) {
|
|
return QString();
|
|
}
|
|
|
|
if ((group < -1 || group >= KIconLoader::LastGroup) && group != KIconLoader::User) {
|
|
qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
|
|
group = KIconLoader::Desktop;
|
|
}
|
|
if (size == 0 && group < 0) {
|
|
qCDebug(KICONTHEMES) << "Neither size nor group specified!";
|
|
group = KIconLoader::Desktop;
|
|
}
|
|
|
|
QString file = name + QStringLiteral(".mng");
|
|
if (group == KIconLoader::User) {
|
|
file = d->locate(file);
|
|
} else {
|
|
if (size == 0) {
|
|
size = d->mpGroups[group].size;
|
|
}
|
|
|
|
QString path;
|
|
|
|
for (KIconThemeNode *themeNode : std::as_const(d->links)) {
|
|
path = themeNode->theme->iconPath(file, size, KIconLoader::MatchExact);
|
|
if (!path.isEmpty()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (path.isEmpty()) {
|
|
for (KIconThemeNode *themeNode : std::as_const(d->links)) {
|
|
path = themeNode->theme->iconPath(file, size, KIconLoader::MatchBest);
|
|
if (!path.isEmpty()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
file = path;
|
|
}
|
|
return file;
|
|
}
|
|
#endif
|
|
|
|
#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
|
|
QStringList KIconLoader::loadAnimated(const QString &name, KIconLoader::Group group, int size) const
|
|
{
|
|
QStringList lst;
|
|
|
|
if (!d->mpGroups) {
|
|
return lst;
|
|
}
|
|
|
|
d->initIconThemes();
|
|
|
|
if ((group < -1) || (group >= KIconLoader::LastGroup)) {
|
|
qCDebug(KICONTHEMES) << "Invalid icon group: " << group << ", should be one of KIconLoader::Group";
|
|
group = KIconLoader::Desktop;
|
|
}
|
|
if ((size == 0) && (group < 0)) {
|
|
qCDebug(KICONTHEMES) << "Neither size nor group specified!";
|
|
group = KIconLoader::Desktop;
|
|
}
|
|
|
|
QString file = name + QStringLiteral("/0001");
|
|
if (group == KIconLoader::User) {
|
|
file = d->locate(file + QStringLiteral(".png"));
|
|
} else {
|
|
if (size == 0) {
|
|
size = d->mpGroups[group].size;
|
|
}
|
|
file = d->findMatchingIcon(file, size, 1); // FIXME scale
|
|
}
|
|
if (file.isEmpty()) {
|
|
return lst;
|
|
}
|
|
|
|
QString path = file.left(file.length() - 8);
|
|
QDir dir(QFile::encodeName(path));
|
|
if (!dir.exists()) {
|
|
return lst;
|
|
}
|
|
|
|
const auto entryList = dir.entryList();
|
|
for (const QString &entry : entryList) {
|
|
const QStringView chunk = QStringView(entry).left(4);
|
|
if (!chunk.toUInt()) {
|
|
continue;
|
|
}
|
|
|
|
lst += path + entry;
|
|
}
|
|
lst.sort();
|
|
return lst;
|
|
}
|
|
#endif
|
|
|
|
KIconTheme *KIconLoader::theme() const
|
|
{
|
|
if (d->mpThemeRoot) {
|
|
return d->mpThemeRoot->theme;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int KIconLoader::currentSize(KIconLoader::Group group) const
|
|
{
|
|
if (!d->mpGroups) {
|
|
return -1;
|
|
}
|
|
|
|
if (group < 0 || group >= KIconLoader::LastGroup) {
|
|
qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
|
|
return -1;
|
|
}
|
|
return d->mpGroups[group].size;
|
|
}
|
|
|
|
QStringList KIconLoader::queryIconsByDir(const QString &iconsDir) const
|
|
{
|
|
const QDir dir(iconsDir);
|
|
const QStringList formats = QStringList() << QStringLiteral("*.png") << QStringLiteral("*.xpm") << QStringLiteral("*.svg") << QStringLiteral("*.svgz");
|
|
const QStringList lst = dir.entryList(formats, QDir::Files);
|
|
QStringList result;
|
|
for (const auto &file : lst) {
|
|
result += iconsDir + QLatin1Char('/') + file;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QStringList KIconLoader::queryIconsByContext(int group_or_size, KIconLoader::Context context) const
|
|
{
|
|
QStringList result;
|
|
if (group_or_size >= KIconLoader::LastGroup) {
|
|
qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
|
|
return result;
|
|
}
|
|
int size;
|
|
if (group_or_size >= 0) {
|
|
size = d->mpGroups[group_or_size].size;
|
|
} else {
|
|
size = -group_or_size;
|
|
}
|
|
|
|
for (KIconThemeNode *themeNode : std::as_const(d->links)) {
|
|
themeNode->queryIconsByContext(&result, size, context);
|
|
}
|
|
|
|
// Eliminate duplicate entries (same icon in different directories)
|
|
QString name;
|
|
QStringList res2;
|
|
QStringList entries;
|
|
for (const auto &icon : std::as_const(result)) {
|
|
const int n = icon.lastIndexOf(QLatin1Char('/'));
|
|
if (n == -1) {
|
|
name = icon;
|
|
} else {
|
|
name = icon.mid(n + 1);
|
|
}
|
|
name = d->removeIconExtension(name);
|
|
if (!entries.contains(name)) {
|
|
entries += name;
|
|
res2 += icon;
|
|
}
|
|
}
|
|
return res2;
|
|
}
|
|
|
|
QStringList KIconLoader::queryIcons(int group_or_size, KIconLoader::Context context) const
|
|
{
|
|
d->initIconThemes();
|
|
|
|
QStringList result;
|
|
if (group_or_size >= KIconLoader::LastGroup) {
|
|
qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
|
|
return result;
|
|
}
|
|
int size;
|
|
if (group_or_size >= 0) {
|
|
size = d->mpGroups[group_or_size].size;
|
|
} else {
|
|
size = -group_or_size;
|
|
}
|
|
|
|
for (KIconThemeNode *themeNode : std::as_const(d->links)) {
|
|
themeNode->queryIcons(&result, size, context);
|
|
}
|
|
|
|
// Eliminate duplicate entries (same icon in different directories)
|
|
QString name;
|
|
QStringList res2;
|
|
QStringList entries;
|
|
for (const auto &icon : std::as_const(result)) {
|
|
const int n = icon.lastIndexOf(QLatin1Char('/'));
|
|
if (n == -1) {
|
|
name = icon;
|
|
} else {
|
|
name = icon.mid(n + 1);
|
|
}
|
|
name = d->removeIconExtension(name);
|
|
if (!entries.contains(name)) {
|
|
entries += name;
|
|
res2 += icon;
|
|
}
|
|
}
|
|
return res2;
|
|
}
|
|
|
|
// used by KIconDialog to find out which contexts to offer in a combobox
|
|
bool KIconLoader::hasContext(KIconLoader::Context context) const
|
|
{
|
|
for (KIconThemeNode *themeNode : std::as_const(d->links)) {
|
|
if (themeNode->theme->hasContext(context)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if KICONTHEMES_BUILD_DEPRECATED_SINCE(6, 5)
|
|
KIconEffect *KIconLoader::iconEffect() const
|
|
{
|
|
return &d->mpEffect;
|
|
}
|
|
#endif
|
|
|
|
QPixmap KIconLoader::unknown()
|
|
{
|
|
QPixmap pix;
|
|
if (QPixmapCache::find(QStringLiteral("unknown"), &pix)) { // krazy:exclude=iconnames
|
|
return pix;
|
|
}
|
|
|
|
const QString path = global()->iconPath(QStringLiteral("unknown"), KIconLoader::Small, true); // krazy:exclude=iconnames
|
|
if (path.isEmpty()) {
|
|
qCDebug(KICONTHEMES) << "Warning: Cannot find \"unknown\" icon.";
|
|
pix = QPixmap(32, 32);
|
|
} else {
|
|
pix.load(path);
|
|
QPixmapCache::insert(QStringLiteral("unknown"), pix); // krazy:exclude=iconnames
|
|
}
|
|
|
|
return pix;
|
|
}
|
|
|
|
bool KIconLoader::hasIcon(const QString &name) const
|
|
{
|
|
return !d->preferredIconPath(name).isEmpty();
|
|
}
|
|
|
|
void KIconLoader::setCustomPalette(const QPalette &palette)
|
|
{
|
|
d->mCustomColors = true;
|
|
d->mColors = KIconColors(palette);
|
|
}
|
|
|
|
QPalette KIconLoader::customPalette() const
|
|
{
|
|
return d->mCustomColors ? d->mPalette : QPalette();
|
|
}
|
|
|
|
void KIconLoader::resetPalette()
|
|
{
|
|
d->mCustomColors = false;
|
|
}
|
|
|
|
bool KIconLoader::hasCustomPalette() const
|
|
{
|
|
return d->mCustomColors;
|
|
}
|
|
|
|
/*** the global icon loader ***/
|
|
Q_GLOBAL_STATIC(KIconLoader, globalIconLoader)
|
|
|
|
KIconLoader *KIconLoader::global()
|
|
{
|
|
return globalIconLoader();
|
|
}
|
|
|
|
void KIconLoader::newIconLoader()
|
|
{
|
|
if (global() == this) {
|
|
KIconTheme::reconfigure();
|
|
}
|
|
|
|
reconfigure(objectName());
|
|
Q_EMIT iconLoaderSettingsChanged();
|
|
}
|
|
|
|
void KIconLoader::emitChange(KIconLoader::Group g)
|
|
{
|
|
s_globalData->emitChange(g);
|
|
}
|
|
|
|
#include <kiconengine.h>
|
|
QIcon KDE::icon(const QString &iconName, KIconLoader *iconLoader)
|
|
{
|
|
return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global()));
|
|
}
|
|
|
|
QIcon KDE::icon(const QString &iconName, const QStringList &overlays, KIconLoader *iconLoader)
|
|
{
|
|
return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global(), overlays));
|
|
}
|
|
|
|
QIcon KDE::icon(const QString &iconName, const KIconColors &colors, KIconLoader *iconLoader)
|
|
{
|
|
return QIcon(new KIconEngine(iconName, colors, iconLoader ? iconLoader : KIconLoader::global()));
|
|
}
|
|
|
|
#include "kiconloader.moc"
|
|
#include "moc_kiconloader.cpp"
|