/* vi: ts=8 sts=4 sw=4 * * kiconloader.cpp: An icon loader for KDE with theming functionality. * * This file is part of the KDE project, module kdeui. * Copyright (C) 2000 Geert Jansen * Antonio Larrosa * 2010 Michael Pyne * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kiconloader.h" #include #include //for abs #include //for readlink #include #include #include #include #include #include #include #include #include #include #include // kdecore #include #include #include #include #include #include #include #include // kdeui #include "kicontheme.h" #include "kiconeffect.h" /** * Checks for relative paths quickly on UNIX-alikes */ static bool pathIsRelative(const QString &path) { return (!path.isEmpty() && path[0] != QChar('/')); } /** * Holds a QPixmap for this process, along with its associated path on disk. */ struct PixmapWithPath { QPixmap pixmap; QString path; }; /*** KIconThemeNode: A node in the icon theme dependancy tree. ***/ class KIconThemeNode { public: KIconThemeNode(KIconTheme *_theme); ~KIconThemeNode(); 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; void printTree(QString& dbgString) const; KIconTheme *theme; }; KIconThemeNode::KIconThemeNode(KIconTheme *_theme) { theme = _theme; } KIconThemeNode::~KIconThemeNode() { delete theme; } void KIconThemeNode::printTree(QString& dbgString) const { /* This method doesn't have much sense anymore, so maybe it should be removed in the (near?) future */ dbgString += '('; dbgString += theme->name(); dbgString += ')'; } 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); } /*** KIconGroup: Icon type description. ***/ struct KIconGroup { int size; bool alphaBlending; }; /*** d pointer for KIconLoader. ***/ class KIconLoaderPrivate { public: KIconLoaderPrivate(KIconLoader *q) : q(q) , mpGroups(0) { } ~KIconLoaderPrivate() { /* 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; } /** * @internal */ void init(const QString &_appname, KStandardDirs *_dirs); /** * @internal */ bool initIconThemes(); /** * @internal * tries to find an icon with the name. It tries some extension and * match strategies */ QString findMatchingIcon(const QString &name, int size) const; /** * @internal * tries to find an icon with the name. * This is one layer above findMatchingIcon -- it also implements generic fallbacks * such as generic icons for mimetypes. */ QString findMatchingIconWithGenericFallbacks(const QString &name, int size) const; /** * @internal * Adds themes installed in the application's directory. **/ void addAppThemes(const QString& appname); /** * @internal * Adds all themes that are part of this node and the themes * below (the fallbacks of the theme) into the tree. */ void addBaseThemes(KIconThemeNode *node, const QString &appname); /** * @internal * Recursively adds all themes that are specified in the "Inherits" * property of the given theme into the tree. */ void addInheritedThemes(KIconThemeNode *node, const QString &appname); /** * @internal * Creates a KIconThemeNode out of a theme name, and adds this theme * as well as all its inherited themes into the tree. Themes that already * exist in the tree will be ignored and not added twice. */ void addThemeByName(const QString &themename, const QString &appname); /** * @internal * return the path for the unknown icon in that size */ QString unknownIconPath(int size) const; /** * Checks if name ends in one of the supported icon formats (i.e. .png) * and returns the name without the extension if it does. */ QString removeIconExtension(const QString &name) const; /** * @internal * Used with KIconLoader::loadIcon to convert the given name, size, group, * and icon state information to valid states. All parameters except the * name can be modified as well to be valid. */ void normalizeIconMetadata(KIconLoader::Group &group, int &size, int &state) const; /** * @internal * Used with KIconLoader::loadIcon to get a base key name from the given * icon metadata. Ensure the metadata is normalized first. */ QString makeCacheKey(const QString &name, KIconLoader::Group group, const QStringList &overlays, int size, int state) const; /** * @internal * Creates the QImage for @p path. If @p size is not zero image will be scaled. */ QImage createIconImage(const QString &path, int size = 0); /** * @internal * Adds an QPixmap with its associated path to the shared icon cache. */ void insertCachedPixmapWithPath(const QString &key, const QPixmap &data, const QString &path); /** * @internal * Retrieves the path and pixmap of the given key from the shared * icon cache. */ bool findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path); /** * @internal * Returns size suitable for overlay image based on the width and height of @p size. */ int overlaySize(const QSize &size) const; KIconLoader *const q; QStringList mThemesInTree; KIconGroup *mpGroups; KIconThemeNode *mpThemeRoot; KStandardDirs *mpDirs; KIconEffect mpEffect; QList links; // This caches rendered QPixmaps in just this process. QCache mPixmapCache; bool extraDesktopIconsLoaded; // lazy loading: initIconThemes() is only needed when the "links" list is needed // mIconThemeInited is used inside initIconThemes() to init only once bool mIconThemeInited; QString appname; void drawOverlays(const KIconLoader *loader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays); }; class KIconLoaderGlobalData { public: KIconLoaderGlobalData() { const QStringList genericIconsFiles = KGlobal::dirs()->findAllResources("xdgdata-mime", "generic-icons"); //kDebug() << genericIconsFiles; foreach (const QString &file, genericIconsFiles) { parseGenericIconsFiles(file); } } QString genericIconFor(const QString& icon) const { return m_genericIcons.value(icon); } private: void parseGenericIconsFiles(const QString& fileName); QHash m_genericIcons; }; void KIconLoaderGlobalData::parseGenericIconsFiles(const QString& fileName) { QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { const QByteArray line = file.readLine().trimmed(); if (line.isEmpty() || line[0] == '#') { continue; } const int pos = line.indexOf(':'); if (pos == -1) { // syntax error continue; } QByteArray mimeIcon = line.left(pos); const int slashindex = mimeIcon.indexOf('/'); if (slashindex != -1) { mimeIcon[slashindex] = '-'; } const QString mimeIconStr = QString::fromLatin1(mimeIcon.constData(), mimeIcon.size()); const QByteArray genericIcon = line.mid(pos+1); const QString genericIconStr = QString::fromLatin1(genericIcon.constData(), genericIcon.size()); m_genericIcons.insert(mimeIconStr, genericIconStr); //kDebug(264) << mimeIconStr << "->" << genericIconStr; } } } K_GLOBAL_STATIC(KIconLoaderGlobalData, s_globalData) void KIconLoaderPrivate::drawOverlays(const KIconLoader *iconLoader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays) { if (overlays.isEmpty()) { return; } const QSize pixSize = pix.size(); const int iconSize = overlaySize(pixSize); QPainter painter(&pix); int count = 0; foreach (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 state? it results in a slower // path, and perhaps emblems should remain in the default state // anyways? const QPixmap pixmap = iconLoader->loadIcon(overlay, group, iconSize, state, QStringList(), 0, true); if (pixmap.isNull()) { continue; } QPoint startPoint; switch (count) { case 0: { // bottom left corner startPoint = QPoint(2, pixSize.height() - iconSize - 2); break; } case 1: { // bottom right corner startPoint = QPoint(pixSize.width() - iconSize - 2, pixSize.height() - iconSize - 2); break; } case 2: { // top right corner startPoint = QPoint(pixSize.width() - iconSize - 2, 2); break; } case 3: { // top left corner startPoint = QPoint(2, 2); break; } } painter.drawPixmap(startPoint, pixmap); ++count; if (count > 3) { break; } } } KIconLoader::KIconLoader(const QString &_appname, KStandardDirs *_dirs, QObject* parent) : QObject(parent) { setObjectName(_appname); d = new KIconLoaderPrivate(this); connect( KGlobalSettings::self(), SIGNAL(iconChanged(int)), this, SLOT(newIconLoader()) ); d->init(_appname, _dirs); } KIconLoader::KIconLoader(const KComponentData &componentData, QObject* parent) : QObject(parent) { setObjectName(componentData.componentName()); d = new KIconLoaderPrivate(this); connect( KGlobalSettings::self(), SIGNAL(iconChanged(int)), this, SLOT(newIconLoader()) ); d->init(componentData.componentName(), componentData.dirs()); } void KIconLoader::reconfigure(const QString &_appname, KStandardDirs *_dirs) { delete d; d = new KIconLoaderPrivate(this); d->init(_appname, _dirs); } void KIconLoaderPrivate::init( const QString &_appname, KStandardDirs *_dirs) { extraDesktopIconsLoaded=false; mIconThemeInited = false; mpThemeRoot = 0; if (_dirs) { mpDirs = _dirs; } else { mpDirs = KGlobal::dirs(); } appname = _appname; if (appname.isEmpty()) { appname = KGlobal::mainComponent().componentName(); } // Initialize icon cache mPixmapCache.setMaxCost(10240); mPixmapCache.clear(); // These have to match the order in kiconloader.h static const char * const groups[] = { "Desktop", "Toolbar", "MainToolbar", "Small", "Panel", "Dialog", 0L }; KSharedConfig::Ptr config = KGlobal::config(); // loading config and default sizes initIconThemes(); KIconTheme *defaultSizesTheme = links.empty() ? 0 : links.first()->theme; mpGroups = new KIconGroup[(int) KIconLoader::LastGroup]; for (KIconLoader::Group i = KIconLoader::FirstGroup; i < KIconLoader::LastGroup; ++i) { if (groups[i] == 0L) { break; } KConfigGroup cg(config, QLatin1String(groups[i]) + "Icons"); mpGroups[i].size = cg.readEntry("Size", 0); if (QX11Info::appDepth() > 8) { mpGroups[i].alphaBlending = cg.readEntry("AlphaBlending", true); } else { mpGroups[i].alphaBlending = false; } if (!mpGroups[i].size && defaultSizesTheme) { mpGroups[i].size = defaultSizesTheme->defaultSize(i); } } } bool KIconLoaderPrivate::initIconThemes() { if (mIconThemeInited) { // If mpThemeRoot isn't 0 then initing has succeeded return (mpThemeRoot != 0); } //kDebug(264); mIconThemeInited = true; // Add the default theme and its base themes to the theme tree KIconTheme *def = new KIconTheme(KIconTheme::current(), appname); if (!def->isValid()) { delete def; // warn, as this is actually a small penalty hit kDebug(264) << "Couldn't find current icon theme, falling back to default."; def = new KIconTheme(KIconTheme::defaultThemeName(), appname); if (!def->isValid()) { kError(264) << "Error: standard icon theme" << KIconTheme::defaultThemeName() << "not found!"; delete def; return false; } } mpThemeRoot = new KIconThemeNode(def); mThemesInTree.append(def->internalName()); links.append(mpThemeRoot); addBaseThemes(mpThemeRoot, appname); // Insert application specific themes at the top. mpDirs->addResourceType("appicon", "data", appname + "/pics/"); // Add legacy icon dirs. QStringList dirs; dirs += mpDirs->resourceDirs("icon"); dirs += mpDirs->resourceDirs("pixmap"); dirs += mpDirs->resourceDirs("pixmaps"); dirs += mpDirs->resourceDirs("xdgdata-icon"); // These are not in the icon spec, but e.g. GNOME puts some icons there anyway. dirs += mpDirs->resourceDirs("xdgdata-pixmap"); foreach (const QString &it, dirs) { mpDirs->addResourceDir("appicon", it); } #ifndef NDEBUG QString dbgString = "Theme tree: "; mpThemeRoot->printTree(dbgString); kDebug(264) << dbgString; #endif return true; } KIconLoader::~KIconLoader() { delete d; } void KIconLoader::addAppDir(const QString& appname) { d->initIconThemes(); d->mpDirs->addResourceType("appicon", "data", appname + "/pics/"); d->addAppThemes(appname); } void KIconLoaderPrivate::addAppThemes(const QString& appname) { initIconThemes(); KIconTheme *def = new KIconTheme(KIconTheme::current(), appname); if (!def->isValid()) { delete def; def = new KIconTheme(KIconTheme::defaultThemeName(), appname); } KIconThemeNode* node = new KIconThemeNode(def); bool addedToLinks = false; if (!mThemesInTree.contains(node->theme->internalName())) { mThemesInTree.append(node->theme->internalName()); 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(KIconTheme::defaultThemeName(), appname); addThemeByName("hicolor", appname); } void KIconLoaderPrivate::addInheritedThemes(KIconThemeNode *node, const QString &appname) { foreach (const QString &it, node->theme->inherits()) { if (it == 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(it, 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 KIconLoader::addExtraDesktopThemes() { if ( d->extraDesktopIconsLoaded ) return; d->initIconThemes(); QStringList list; const QStringList icnlibs = KGlobal::dirs()->resourceDirs("icon"); QStringList::ConstIterator it; char buf[1000]; int r; for (it=icnlibs.begin(); it!=icnlibs.end(); ++it) { QDir dir(*it); if (!dir.exists()) continue; const QStringList lst = dir.entryList(QStringList( "default.*" ), QDir::Dirs); QStringList::ConstIterator it2; for (it2=lst.begin(); it2!=lst.end(); ++it2) { if (!KStandardDirs::exists(*it + *it2 + "/index.theme")) continue; r=readlink( QFile::encodeName(*it + *it2) , buf, sizeof(buf)-1); if ( r>0 ) { buf[r]=0; const QDir dir2( buf ); QString themeName=dir2.dirName(); if (!list.contains(themeName)) list.append(themeName); } } } for (it = list.constBegin(); it != list.constEnd(); ++it) { // Don't add the KDE defaults once more, we have them anyways. if (*it == QLatin1String("default.kde") || *it == QLatin1String("default.kde4")) { continue; } d->addThemeByName(*it, ""); } d->extraDesktopIconsLoaded=true; } bool KIconLoader::extraDesktopThemesAdded() const { return d->extraDesktopIconsLoaded; } 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, int &size, int &state) const { if ((state < 0) || (state >= KIconLoader::LastState)) { kWarning(264) << "Illegal icon state: " << state; state = KIconLoader::DefaultState; } if (size < 0) { size = 0; } // 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)) { kWarning(264) << "Illegal icon group: " << group; group = KIconLoader::Desktop; } // If size == 0, use default size for the specified group. if (size == 0) { if (group < 0) { kWarning(264) << "Neither size nor group specified!"; group = KIconLoader::Desktop; } size = mpGroups[group].size; } } QString KIconLoaderPrivate::makeCacheKey(const QString &name, KIconLoader::Group group, const QStringList &overlays, int size, int state) const { // The icon cache is shared so add some namespacing. return (group == KIconLoader::User ? QLatin1String("$kicou_") : QLatin1String("$kico_")) + name + QLatin1Char('_') + QString::number(size) + QLatin1Char('_') + overlays.join("_") + ( group >= 0 ? mpEffect.fingerprint(group, state) : QLatin1String("noeffect")); } QImage KIconLoaderPrivate::createIconImage(const QString &path, int size) { QImage img(path); if (size != 0 && !img.isNull()) { img = img.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } return img; } void KIconLoaderPrivate::insertCachedPixmapWithPath( const QString &key, const QPixmap &data, const QString &path) { // reject anything bigger than 1024x1024, it is questionable what the limit should be because // in some projects the icon loader is abused with pixmaps bigger than that if (data.width() > 1024 || data.height() > 1024) { kWarning(264) << "trying to insert pixmap bigger than 1024x1024 with path:" << path; return; } PixmapWithPath *pixmapPath = mPixmapCache.object(key); if (pixmapPath && pixmapPath->pixmap == data && pixmapPath->path == path) { return; } // 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. pixmapPath = new PixmapWithPath; pixmapPath->pixmap = data; pixmapPath->path = path; mPixmapCache.insert(key, pixmapPath); } bool KIconLoaderPrivate::findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path) { // If the pixmap is present in our local process cache, use that const PixmapWithPath *pixmapPath = mPixmapCache.object(key); if (pixmapPath) { path = pixmapPath->path; data = pixmapPath->pixmap; return true; } return false; } int KIconLoaderPrivate::overlaySize(const QSize &size) const { const int minSize = qMin(size.width(), size.height()); if (minSize < 32) { return 8; } else if (minSize <= 48) { return 16; } else if (minSize <= 96) { return 22; } else if (minSize < 256) { return 32; } return 64; } QString KIconLoaderPrivate::findMatchingIconWithGenericFallbacks(const QString& name, int size) const { QString icon = findMatchingIcon(name, size); if (!icon.isEmpty()) { return icon; } const QString genericIcon = s_globalData->genericIconFor(name); if (!genericIcon.isEmpty()) { icon = findMatchingIcon(genericIcon, size); } return icon; } QString KIconLoaderPrivate::findMatchingIcon(const QString& name, int size) const { const_cast(this)->initIconThemes(); QString icon; const char * const ext[4] = { ".png", ".svgz", ".svg", ".xpm" }; bool genericFallback = name.endsWith(QLatin1String("-x-generic")); // Do two passes through themeNodes. // // The first pass looks for an exact match in each themeNode one after the other. // If one is found and it is an app icon then return that icon. // // In the next pass (assuming the first pass failed), it looks for exact matches // and then generic fallbacks in each themeNode one after the other // // The reasoning is that application icons should always match exactly, all other // icons may fallback. Since we do not know what the context is here when we start // looking for it, we can only go by the path found. foreach (const KIconThemeNode *themeNode, links) { for (int i = 0 ; i < 4 ; i++) { icon = themeNode->theme->iconPath(name + ext[i], size, KIconLoader::MatchExact); if (!icon.isEmpty()) { break; } icon = themeNode->theme->iconPath(name + ext[i], size, KIconLoader::MatchBest); if (!icon.isEmpty()) { break; } } if (icon.contains("/apps/")) { return icon; } } foreach (const KIconThemeNode *themeNode, links) { QString currentName = name; while (!currentName.isEmpty()) { //kDebug(264) << "Looking up" << currentName; for (int i = 0 ; i < 4 ; i++) { icon = themeNode->theme->iconPath(currentName + ext[i], size, KIconLoader::MatchExact); if (!icon.isEmpty()) { return icon; } icon = themeNode->theme->iconPath(currentName + ext[i], size, KIconLoader::MatchBest); if (!icon.isEmpty()) { return icon; } } //kDebug(264) << "Looking up" << currentName; if (genericFallback) { // we already tested the base name break; } int rindex = currentName.lastIndexOf('-'); 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); } else { // From update-mime-database.c static const QSet mediaTypes = QSet() << "text" << "application" << "image" << "audio" << "inode" << "video" << "message" << "model" << "multipart" << "x-content" << "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; } } } } return icon; } inline QString KIconLoaderPrivate::unknownIconPath(int size) const { static const QString str_unknown = QString::fromLatin1("unknown"); QString icon = findMatchingIcon(str_unknown, size); if (icon.isEmpty()) { kDebug(264) << "Warning: could not find \"Unknown\" icon for size = " << size; } return icon; } // Finds the absolute path to an icon. QString KIconLoader::iconPath(const QString& _name, int group_or_size, bool canReturnNull) const { if (!d->initIconThemes()) { return QString(); } if (_name.isEmpty() || !pathIsRelative(_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->mpDirs->findResource("appicon", name + QLatin1String(".png")); if (path.isEmpty()) { path = d->mpDirs->findResource("appicon", name + QLatin1String(".svgz")); } if (path.isEmpty()) { path = d->mpDirs->findResource("appicon", name + QLatin1String(".svg")); } if (path.isEmpty()) { path = d->mpDirs->findResource("appicon", name + QLatin1String(".xpm")); } return path; } if (group_or_size >= KIconLoader::LastGroup) { kDebug(264) << "Illegal 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(); } return d->unknownIconPath(size); } QString icon = d->findMatchingIconWithGenericFallbacks(name, size); if (icon.isEmpty()) { // Try "User" group too. path = iconPath(name, KIconLoader::User, true); if (!path.isEmpty() || canReturnNull) { return path; } return d->unknownIconPath(size); } return icon; } 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; } const_cast(this)->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("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 { QString name = _name; bool favIconOverlay = false; if (size < 0 || _name.isEmpty()) { return QPixmap(); } /* * 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 = KStandardDirs::locateLocal("cache", name+".png"); } bool absolutePath = !pathIsRelative(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. QString key = d->makeCacheKey(name, group, overlays, size, state); QPixmap pix; bool iconWasUnknown = false; QString icon; // icon would be empty for "unknown" icons, which should be searched for // anew each time. if (d->findCachedPixmapWithPath(key, pix, icon) && !icon.isEmpty()) { if (path_store) { *path_store = icon; } return pix; } // Image is not cached... go find it and apply effects. if (!d->initIconThemes()) { return QPixmap(); } favIconOverlay = favIconOverlay && size > 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) { icon = name; } else { icon = d->findMatchingIconWithGenericFallbacks(favIconOverlay ? QString("text-html") : name, size); } } if (icon.isEmpty()) { // We do have a "User" icon, or we couldn't find the non-User one. icon = (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 (icon.isEmpty() && !canReturnNull) { icon = d->unknownIconPath(size); iconWasUnknown = true; } QImage img = d->createIconImage(icon, size); if (group >= 0) { img = d->mpEffect.apply(img, group, state); } if (favIconOverlay) { QImage favIcon(name, "PNG"); // if favIcon not there yet, don't try to blend it if (!favIcon.isNull()) { // ensure it is format supported by QPainter first switch (img.format()) { case QImage::Format_Mono: case QImage::Format_MonoLSB: { img = img.convertToFormat(QImage::Format_ARGB32); break; } default: break; } // ensure the icon size is suitable for overlay const int iconSize = d->overlaySize(img.size()); favIcon = favIcon.scaled(QSize(iconSize, iconSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); 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(img); // 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) { icon.clear(); } d->insertCachedPixmapWithPath(key, pix, icon); if (path_store) { *path_store = icon; } return pix; } 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) { kDebug(264) << "Illegal icon group: " << group; group = KIconLoader::Desktop; } if (size == 0 && group < 0) { kDebug(264) << "Neither size nor group specified!"; group = KIconLoader::Desktop; } QString file = name + "/0001"; if (group == KIconLoader::User) { file = d->mpDirs->findResource("appicon", file + ".png"); } else { if (size == 0) { size = d->mpGroups[group].size; } file = d->findMatchingIcon(file, size); } if (file.isEmpty()) { return lst; } QString path = file.left(file.length()-8); DIR* dp = opendir( QFile::encodeName(path) ); if (!dp) { return lst; } KDE_struct_dirent* ep; while( ( ep = KDE_readdir( dp ) ) != 0L ) { QString fn(QFile::decodeName(ep->d_name)); if(!(fn.left(4)).toUInt()) continue; lst += path + fn; } closedir ( dp ); lst.sort(); return lst; } KIconTheme *KIconLoader::theme() const { d->initIconThemes(); if (d->mpThemeRoot) { return d->mpThemeRoot->theme; } return 0L; } int KIconLoader::currentSize(KIconLoader::Group group) const { if (!d->mpGroups) { return -1; } if (group < 0 || group >= KIconLoader::LastGroup) { kDebug(264) << "Illegal icon group: " << group; return -1; } return d->mpGroups[group].size; } QStringList KIconLoader::queryIconsByDir( const QString& iconsDir ) const { const QDir dir(iconsDir); const QStringList formats = QStringList() << "*.png" << "*.xpm" << "*.svg" << "*.svgz"; QStringList result; foreach (const QString &it, dir.entryList(formats, QDir::Files)) { result.append(iconsDir + '/' + it); } return result; } QStringList KIconLoader::queryIconsByContext(int group_or_size, KIconLoader::Context context) const { d->initIconThemes(); QStringList result; if (group_or_size >= KIconLoader::LastGroup) { kDebug(264) << "Illegal 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; } foreach (const KIconThemeNode *themeNode, d->links) { themeNode->queryIconsByContext(&result, size, context); } // Eliminate duplicate entries (same icon in different directories) QString name; QStringList res2, entries; QStringList::ConstIterator it; for (it=result.constBegin(); it!=result.constEnd(); ++it) { int n = (*it).lastIndexOf('/'); if (n == -1) name = *it; else name = (*it).mid(n+1); name = d->removeIconExtension(name); if (!entries.contains(name)) { entries += name; res2 += *it; } } return res2; } QStringList KIconLoader::queryIcons(int group_or_size, KIconLoader::Context context) const { d->initIconThemes(); QStringList result; if (group_or_size >= KIconLoader::LastGroup) { kDebug(264) << "Illegal 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; } foreach (const KIconThemeNode *themeNode, d->links) { themeNode->queryIcons(&result, size, context); } // Eliminate duplicate entries (same icon in different directories) QString name; QStringList res2, entries; QStringList::ConstIterator it; for (it=result.constBegin(); it!=result.constEnd(); ++it) { int n = (*it).lastIndexOf('/'); if (n == -1) name = *it; else name = (*it).mid(n+1); name = d->removeIconExtension(name); if (!entries.contains(name)) { entries += name; res2 += *it; } } return res2; } // used by KIconDialog to find out which contexts to offer in a combobox bool KIconLoader::hasContext(KIconLoader::Context context) const { d->initIconThemes(); foreach (const KIconThemeNode *themeNode, d->links) { if (themeNode->theme->hasContext(context)) { return true; } } return false; } KIconEffect * KIconLoader::iconEffect() const { return &d->mpEffect; } bool KIconLoader::alphaBlending(KIconLoader::Group group) const { if (!d->mpGroups) { return false; } if (group < 0 || group >= KIconLoader::LastGroup) { kDebug(264) << "Illegal icon group: " << group; return false; } return d->mpGroups[group].alphaBlending; } // Easy access functions QPixmap DesktopIcon(const QString& name, int force_size, int state, const QStringList &overlays) { KIconLoader *loader = KIconLoader::global(); return loader->loadIcon(name, KIconLoader::Desktop, force_size, state, overlays); } QPixmap BarIcon(const QString& name, int force_size, int state, const QStringList &overlays) { KIconLoader *loader = KIconLoader::global(); return loader->loadIcon(name, KIconLoader::Toolbar, force_size, state, overlays); } QPixmap SmallIcon(const QString& name, int force_size, int state, const QStringList &overlays) { KIconLoader *loader = KIconLoader::global(); return loader->loadIcon(name, KIconLoader::Small, force_size, state, overlays); } QPixmap MainBarIcon(const QString& name, int force_size, int state, const QStringList &overlays) { KIconLoader *loader = KIconLoader::global(); return loader->loadIcon(name, KIconLoader::MainToolbar, force_size, state, overlays); } QPixmap UserIcon(const QString& name, int state, const QStringList &overlays) { KIconLoader *loader = KIconLoader::global(); return loader->loadIcon(name, KIconLoader::User, 0, state, overlays); } int IconSize(KIconLoader::Group group) { KIconLoader *loader = KIconLoader::global(); return loader->currentSize(group); } QPixmap KIconLoader::unknown() { QPixmap pix; if (QPixmapCache::find("unknown", pix)) { return pix; } QString path = global()->iconPath("unknown", KIconLoader::Small, true); if (path.isEmpty()) { kDebug(264) << "Warning: Cannot find \"unknown\" icon."; pix = QPixmap(32,32); } else { pix.load(path); QPixmapCache::insert("unknown", pix); } return pix; } /*** the global icon loader ***/ K_GLOBAL_STATIC_WITH_ARGS(KIconLoader, globalIconLoader, (KGlobal::mainComponent(), 0)) KIconLoader *KIconLoader::global() { return globalIconLoader; } void KIconLoader::newIconLoader() { if ( global() == this) { KIconTheme::reconfigure(); } reconfigure( objectName(), d->mpDirs ); emit iconLoaderSettingsChanged(); } #include "moc_kiconloader.cpp"