mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 10:52:49 +00:00

requires the following commit from the Katie repo: 97fea30784dfdc41a6599ea31219d0b366d8efaa Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
608 lines
18 KiB
C++
608 lines
18 KiB
C++
/* vi: ts=8 sts=4 sw=4
|
|
*
|
|
* kicontheme.cpp: Lowlevel icon theme handling.
|
|
*
|
|
* This file is part of the KDE project, module kdecore.
|
|
* Copyright (C) 2000 Geert Jansen <jansen@kde.org>
|
|
* Antonio Larrosa <larrosa@kde.org>
|
|
*
|
|
* 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 "kicontheme.h"
|
|
#include "kdebug.h"
|
|
#include "kicon.h"
|
|
#include "kstandarddirs.h"
|
|
#include "kglobal.h"
|
|
#include "ksharedconfig.h"
|
|
#include "kconfig.h"
|
|
#include "kcomponentdata.h"
|
|
#include "klocale.h"
|
|
#include "kde_file.h"
|
|
#include "kconfiggroup.h"
|
|
|
|
#include <QtGui/QAction>
|
|
#include <QtCore/qstring.h>
|
|
#include <QtCore/qstringlist.h>
|
|
#include <QtCore/QMap>
|
|
#include <QtCore/QSet>
|
|
#include <QtGui/QPixmap>
|
|
#include <QtGui/QPixmapCache>
|
|
#include <QtGui/QImage>
|
|
#include <QtCore/QFileInfo>
|
|
#include <QtCore/QDir>
|
|
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
|
|
/**
|
|
* A subdirectory in an icon theme.
|
|
*/
|
|
class KIconThemeDir
|
|
{
|
|
public:
|
|
enum KIconType {
|
|
Fixed,
|
|
Scalable,
|
|
Threshold
|
|
};
|
|
|
|
|
|
KIconThemeDir(const QString& basedir, const QString &themedir, const KConfigGroup &config);
|
|
|
|
bool isValid() const { return mbValid; }
|
|
QString iconPath(const QString& name) const;
|
|
QStringList iconList() const;
|
|
QString dir() const { return mBaseDirThemeDir; }
|
|
|
|
KIconLoader::Context context() const { return mContext; }
|
|
KIconType type() const { return mType; }
|
|
int size() const { return mSize; }
|
|
int minSize() const { return mMinSize; }
|
|
int maxSize() const { return mMaxSize; }
|
|
int threshold() const { return mThreshold; }
|
|
|
|
private:
|
|
bool mbValid;
|
|
KIconType mType;
|
|
KIconLoader::Context mContext;
|
|
int mSize, mMinSize, mMaxSize;
|
|
int mThreshold;
|
|
|
|
QString mBaseDirThemeDir;
|
|
};
|
|
|
|
/*** KIconTheme ***/
|
|
class KIconTheme::KIconThemePrivate
|
|
{
|
|
public:
|
|
QString example;
|
|
QString linkOverlay, lockOverlay, zipOverlay, shareOverlay;
|
|
bool hidden;
|
|
|
|
int mDefSize[6];
|
|
QList<int> mSizes[6];
|
|
|
|
QString mDir, mName, mInternalName, mDesc;
|
|
QStringList mInherits;
|
|
QList<KIconThemeDir *> mDirs;
|
|
};
|
|
K_GLOBAL_STATIC(QString, _theme)
|
|
K_GLOBAL_STATIC(QStringList, _theme_list)
|
|
|
|
KIconTheme::KIconTheme(const QString &name, const QString &appName)
|
|
:d(new KIconThemePrivate)
|
|
{
|
|
|
|
d->mInternalName = name;
|
|
|
|
QStringList icnlibs;
|
|
QStringList::ConstIterator it, itDir;
|
|
QStringList themeDirs;
|
|
QSet<QString> addedDirs; // Used for avoiding duplicates.
|
|
|
|
// Applications can have local additions to the global "hicolor" icon
|
|
// themes. For these, the _global_ theme description files are used..
|
|
if (!appName.isEmpty() &&
|
|
( name == defaultThemeName() || name== "hicolor" ) ) {
|
|
icnlibs = KGlobal::dirs()->resourceDirs("data");
|
|
for (it=icnlibs.constBegin(); it!=icnlibs.constEnd(); ++it) {
|
|
const QString cDir = *it + appName + "/icons/" + name;
|
|
if (QDir( cDir ).exists()) {
|
|
themeDirs += cDir + '/';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the theme description file. These are always global.
|
|
icnlibs = KGlobal::dirs()->resourceDirs("icon")
|
|
<< KGlobal::dirs()->resourceDirs("xdgdata-icon")
|
|
// These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
|
|
<< KGlobal::dirs()->resourceDirs("xdgdata-pixmap");
|
|
icnlibs.removeDuplicates();
|
|
|
|
for (it=icnlibs.constBegin(); it!=icnlibs.constEnd(); ++it) {
|
|
const QString cDir = *it + name + '/';
|
|
if (KGlobal::dirs()->exists(cDir)) {
|
|
themeDirs += cDir;
|
|
if (d->mDir.isEmpty()) {
|
|
if (KGlobal::dirs()->exists(cDir + "index.theme")) {
|
|
d->mDir = cDir;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (d->mDir.isEmpty()) {
|
|
kDebug(264) << "Icon theme" << name << "not found.";
|
|
return;
|
|
}
|
|
|
|
// Use KSharedConfig to avoid parsing the file many times, from each kinstance.
|
|
// Need to keep a ref to it to make this useful
|
|
KSharedConfig::Ptr sharedConfig = KSharedConfig::openConfig(d->mDir + "index.theme", KConfig::NoGlobals);
|
|
|
|
KConfigGroup cfg(sharedConfig, "Icon Theme");
|
|
d->mName = cfg.readEntry("Name");
|
|
d->mDesc = cfg.readEntry("Comment");
|
|
d->mInherits = cfg.readEntry("Inherits", QStringList());
|
|
if (name != defaultThemeName()) {
|
|
for (QStringList::Iterator it = d->mInherits.begin(); it != d->mInherits.end(); ++it) {
|
|
if (*it == "default" || *it == "hicolor") {
|
|
*it = defaultThemeName();
|
|
}
|
|
}
|
|
}
|
|
|
|
d->hidden = cfg.readEntry("Hidden", false);
|
|
d->example = cfg.readPathEntry("Example", QString());
|
|
|
|
const QStringList dirs = cfg.readPathEntry("Directories", QStringList());
|
|
for (it=dirs.begin(); it!=dirs.end(); ++it) {
|
|
KConfigGroup cg(sharedConfig, *it);
|
|
for (itDir=themeDirs.constBegin(); itDir!=themeDirs.constEnd(); ++itDir) {
|
|
const QString currentDir(*itDir + *it + '/');
|
|
if (!addedDirs.contains(currentDir) && KGlobal::dirs()->exists(currentDir)) {
|
|
addedDirs.insert(currentDir);
|
|
KIconThemeDir *dir = new KIconThemeDir(*itDir, *it, cg);
|
|
if (!dir->isValid()) {
|
|
delete dir;
|
|
}
|
|
else {
|
|
d->mDirs.append(dir);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expand available sizes for scalable icons to their full range
|
|
int i;
|
|
QMap<int,QList<int> > scIcons;
|
|
foreach(const KIconThemeDir *dir, d->mDirs) {
|
|
if (!dir) {
|
|
break;
|
|
}
|
|
if ((dir->type() == KIconThemeDir::Scalable) && !scIcons.contains(dir->size())) {
|
|
QList<int> lst;
|
|
for (i=dir->minSize(); i<=dir->maxSize(); ++i) {
|
|
lst += i;
|
|
}
|
|
scIcons[dir->size()] = lst;
|
|
}
|
|
}
|
|
|
|
QStringList groups;
|
|
groups += "Desktop";
|
|
groups += "Toolbar";
|
|
groups += "MainToolbar";
|
|
groups += "Small";
|
|
groups += "Panel";
|
|
groups += "Dialog";
|
|
const int defDefSizes[] = { 32, 22, 22, 16, 32, 32 };
|
|
for (it=groups.constBegin(), i=0; it!=groups.constEnd(); ++it, i++) {
|
|
d->mDefSize[i] = cfg.readEntry(*it + "Default", defDefSizes[i]);
|
|
const QList<int> lst = cfg.readEntry(*it + "Sizes", QList<int>());
|
|
QList<int> exp;
|
|
QList<int>::ConstIterator it2;
|
|
for (it2=lst.begin(); it2!=lst.end(); ++it2) {
|
|
if (scIcons.contains(*it2)) {
|
|
exp += scIcons[*it2];
|
|
} else {
|
|
exp += *it2;
|
|
}
|
|
}
|
|
d->mSizes[i] = exp;
|
|
}
|
|
}
|
|
|
|
KIconTheme::~KIconTheme()
|
|
{
|
|
qDeleteAll(d->mDirs);
|
|
delete d;
|
|
}
|
|
|
|
QString KIconTheme::name() const
|
|
{
|
|
return d->mName;
|
|
}
|
|
|
|
QString KIconTheme::internalName() const
|
|
{
|
|
return d->mInternalName;
|
|
}
|
|
|
|
QString KIconTheme::description() const
|
|
{
|
|
return d->mDesc;
|
|
}
|
|
|
|
QString KIconTheme::example() const
|
|
{
|
|
return d->example;
|
|
}
|
|
|
|
QString KIconTheme::dir() const
|
|
{
|
|
return d->mDir;
|
|
}
|
|
|
|
QStringList KIconTheme::inherits() const
|
|
{
|
|
return d->mInherits;
|
|
}
|
|
|
|
bool KIconTheme::isValid() const
|
|
{
|
|
return !d->mDirs.isEmpty();
|
|
}
|
|
|
|
bool KIconTheme::isHidden() const
|
|
{
|
|
return d->hidden;
|
|
}
|
|
|
|
int KIconTheme::defaultSize(KIconLoader::Group group) const
|
|
{
|
|
if ((group < 0) || (group >= KIconLoader::LastGroup)) {
|
|
kDebug(264) << "Illegal icon group: " << group << "\n";
|
|
return -1;
|
|
}
|
|
return d->mDefSize[group];
|
|
}
|
|
|
|
QList<int> KIconTheme::querySizes(KIconLoader::Group group) const
|
|
{
|
|
QList<int> empty;
|
|
if ((group < 0) || (group >= KIconLoader::LastGroup)) {
|
|
kDebug(264) << "Illegal icon group: " << group << "\n";
|
|
return empty;
|
|
}
|
|
return d->mSizes[group];
|
|
}
|
|
|
|
QStringList KIconTheme::queryIcons(int size, KIconLoader::Context context) const
|
|
{
|
|
KIconThemeDir *dir;
|
|
|
|
// Try to find exact match
|
|
QStringList result;
|
|
for (int i=0; i<d->mDirs.size(); ++i) {
|
|
dir = d->mDirs.at(i);
|
|
if ((context != KIconLoader::Any) && (context != dir->context()))
|
|
continue;
|
|
if ((dir->type() == KIconThemeDir::Fixed) && (dir->size() == size)) {
|
|
result += dir->iconList();
|
|
continue;
|
|
}
|
|
if ((dir->type() == KIconThemeDir::Scalable) &&
|
|
(size >= dir->minSize()) && (size <= dir->maxSize())) {
|
|
result += dir->iconList();
|
|
continue;
|
|
}
|
|
if ((dir->type() == KIconThemeDir::Threshold) &&
|
|
(abs(size-dir->size())<dir->threshold())) {
|
|
result+=dir->iconList();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QStringList KIconTheme::queryIconsByContext(int size, KIconLoader::Context context) const
|
|
{
|
|
int dw;
|
|
KIconThemeDir *dir;
|
|
|
|
// We want all the icons for a given context, but we prefer icons
|
|
// of size size . Note that this may (will) include duplicate icons
|
|
//QStringList iconlist[34]; // 33 == 48-16+1
|
|
QStringList iconlist[128]; // 33 == 48-16+1
|
|
// Usually, only the 0, 6 (22-16), 10 (32-22), 16 (48-32 or 32-16),
|
|
// 26 (48-22) and 32 (48-16) will be used, but who knows if someone
|
|
// will make icon themes with different icon sizes.
|
|
|
|
for (int i=0;i<d->mDirs.size();++i) {
|
|
dir = d->mDirs.at(i);
|
|
if ((context != KIconLoader::Any) && (context != dir->context()))
|
|
continue;
|
|
dw = abs(dir->size() - size);
|
|
iconlist[(dw<127)?dw:127]+=dir->iconList();
|
|
}
|
|
|
|
QStringList iconlistResult;
|
|
for (int i=0; i<128; i++) iconlistResult+=iconlist[i];
|
|
|
|
return iconlistResult;
|
|
}
|
|
|
|
bool KIconTheme::hasContext(KIconLoader::Context context) const
|
|
{
|
|
foreach(const KIconThemeDir *dir, d->mDirs) {
|
|
if ((context == KIconLoader::Any) || (context == dir->context())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QString KIconTheme::iconPath(const QString& name, int size, KIconLoader::MatchType match) const
|
|
{
|
|
QString icon;
|
|
QString path;
|
|
int delta = -INT_MAX; // current icon size delta of 'icon'
|
|
int dw = INT_MAX; // icon size delta of current directory
|
|
KIconThemeDir *dir;
|
|
|
|
const int dirCount = d->mDirs.size();
|
|
|
|
// Search the directory that contains the icon which matches best to the requested
|
|
// size. If there is no directory which matches exactly to the requested size, the
|
|
// following criterias get applied:
|
|
// - Take a directory having icons with a minimum difference to the requested size.
|
|
// - Prefer directories that allow a downscaling even if the difference to
|
|
// the requested size is bigger than a directory where an upscaling is required.
|
|
for (int i = 0; i < dirCount; ++i) {
|
|
dir = d->mDirs.at(i);
|
|
|
|
if (match == KIconLoader::MatchExact) {
|
|
if ((dir->type() == KIconThemeDir::Fixed) && (dir->size() != size)) {
|
|
continue;
|
|
}
|
|
if ((dir->type() == KIconThemeDir::Scalable) &&
|
|
((size < dir->minSize()) || (size > dir->maxSize()))) {
|
|
continue;
|
|
}
|
|
if ((dir->type() == KIconThemeDir::Threshold) &&
|
|
(abs(dir->size() - size) > dir->threshold())) {
|
|
continue;
|
|
}
|
|
} else {
|
|
// dw < 0 means need to scale up to get an icon of the requested size.
|
|
// Upscaling should only be done if no larger icon is available.
|
|
if (dir->type() == KIconThemeDir::Fixed) {
|
|
dw = dir->size() - size;
|
|
} else if (dir->type() == KIconThemeDir::Scalable) {
|
|
if (size < dir->minSize()) {
|
|
dw = dir->minSize() - size;
|
|
} else if (size > dir->maxSize()) {
|
|
dw = dir->maxSize() - size;
|
|
} else {
|
|
dw = 0;
|
|
}
|
|
} else if (dir->type() == KIconThemeDir::Threshold) {
|
|
if (size < dir->size() - dir->threshold()) {
|
|
dw = dir->size() - dir->threshold() - size;
|
|
} else if (size > dir->size() + dir->threshold()) {
|
|
dw = dir->size() + dir->threshold() - size;
|
|
} else {
|
|
dw = 0;
|
|
}
|
|
}
|
|
// Usually if the delta (= 'dw') of the current directory is
|
|
// not smaller than the delta (= 'delta') of the currently best
|
|
// matching icon, this candidate can be skipped. But skipping
|
|
// the candidate may only be done, if this does not imply
|
|
// in an upscaling of the icon (it is OK to use a directory with
|
|
// smaller icons that what we've already found, however).
|
|
if ((abs(dw) >= abs(delta)) && ((dw < 0) || (delta > 0))) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
path = dir->iconPath(name);
|
|
if (path.isEmpty()) {
|
|
continue;
|
|
}
|
|
icon = path;
|
|
|
|
// if we got in MatchExact that far, we find no better
|
|
if (match == KIconLoader::MatchExact) {
|
|
return icon;
|
|
}
|
|
delta = dw;
|
|
if (delta == 0) {
|
|
return icon; // We won't find a better match anyway
|
|
}
|
|
}
|
|
return icon;
|
|
}
|
|
|
|
// static
|
|
QString KIconTheme::current()
|
|
{
|
|
// Static pointer because of unloading problems wrt DSO's.
|
|
if (!_theme->isEmpty()) {
|
|
return *_theme;
|
|
}
|
|
|
|
KConfigGroup cg(KGlobal::config(), "Icons");
|
|
*_theme = cg.readEntry("Theme", defaultThemeName());
|
|
if ( *_theme == QLatin1String("hicolor") ) {
|
|
*_theme = defaultThemeName();
|
|
}
|
|
return *_theme;
|
|
}
|
|
|
|
// static
|
|
QStringList KIconTheme::list()
|
|
{
|
|
// Static pointer because of unloading problems wrt DSO's.
|
|
if (!_theme_list->isEmpty()) {
|
|
return *_theme_list;
|
|
}
|
|
|
|
QStringList icnlibs = KGlobal::dirs()->resourceDirs("icon")
|
|
<< KGlobal::dirs()->resourceDirs("xdgdata-icon")
|
|
// These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
|
|
<< KGlobal::dirs()->resourceDirs("xdgdata-pixmap");
|
|
icnlibs.removeDuplicates();
|
|
|
|
QStringList::ConstIterator it;
|
|
for (it=icnlibs.begin(); it!=icnlibs.end(); ++it) {
|
|
QDir dir(*it);
|
|
if (!dir.exists()) {
|
|
continue;
|
|
}
|
|
const QStringList lst = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
|
QStringList::ConstIterator it2;
|
|
for (it2=lst.begin(); it2!=lst.end(); ++it2) {
|
|
if ((*it2).startsWith(QLatin1String("default.")) ) {
|
|
continue;
|
|
}
|
|
if (!KGlobal::dirs()->exists(*it + *it2 + "/index.theme")) {
|
|
continue;
|
|
}
|
|
KIconTheme oink(*it2);
|
|
if (!oink.isValid()) {
|
|
continue;
|
|
}
|
|
|
|
if (!_theme_list->contains(*it2)) {
|
|
_theme_list->append(*it2);
|
|
}
|
|
}
|
|
}
|
|
return *_theme_list;
|
|
}
|
|
|
|
// static
|
|
void KIconTheme::reconfigure()
|
|
{
|
|
_theme->clear();
|
|
_theme_list->clear();
|
|
}
|
|
|
|
// static
|
|
QString KIconTheme::defaultThemeName()
|
|
{
|
|
return QLatin1String("ariya");
|
|
}
|
|
|
|
/*** KIconThemeDir ***/
|
|
|
|
KIconThemeDir::KIconThemeDir(const QString& basedir, const QString &themedir, const KConfigGroup &config)
|
|
{
|
|
mbValid = false;
|
|
mBaseDirThemeDir = basedir + themedir;
|
|
|
|
mSize = config.readEntry("Size", 0);
|
|
mMinSize = 1; // just set the variables to something
|
|
mMaxSize = 50; // meaningful in case someone calls minSize or maxSize
|
|
mType = KIconThemeDir::Fixed;
|
|
|
|
if (mSize == 0) {
|
|
return;
|
|
}
|
|
|
|
QString tmp = config.readEntry("Context");
|
|
if (tmp == "Devices")
|
|
mContext = KIconLoader::Device;
|
|
else if (tmp == "MimeTypes")
|
|
mContext = KIconLoader::MimeType;
|
|
else if (tmp == "Applications")
|
|
mContext = KIconLoader::Application;
|
|
else if (tmp == "Actions")
|
|
mContext = KIconLoader::Action;
|
|
else if (tmp == "Animations")
|
|
mContext = KIconLoader::Animation;
|
|
else if (tmp == "Categories")
|
|
mContext = KIconLoader::Category;
|
|
else if (tmp == "Emblems")
|
|
mContext = KIconLoader::Emblem;
|
|
else if (tmp == "Emotes")
|
|
mContext = KIconLoader::Emote;
|
|
else if (tmp == "International")
|
|
mContext = KIconLoader::International;
|
|
else if (tmp == "Places")
|
|
mContext = KIconLoader::Place;
|
|
else if (tmp == "Status")
|
|
mContext = KIconLoader::StatusIcon;
|
|
else if (tmp == "Stock") // invalid, but often present context, skip warning
|
|
return;
|
|
else {
|
|
kDebug(264) << "Invalid Context=" << tmp << "line for icon theme: " << dir() << "\n";
|
|
return;
|
|
}
|
|
tmp = config.readEntry("Type");
|
|
if (tmp == "Fixed")
|
|
mType = KIconThemeDir::Fixed;
|
|
else if (tmp == "Scalable")
|
|
mType = KIconThemeDir::Scalable;
|
|
else if (tmp == "Threshold")
|
|
mType = KIconThemeDir::Threshold;
|
|
else {
|
|
kDebug(264) << "Invalid Type=" << tmp << "line for icon theme: " << dir() << "\n";
|
|
return;
|
|
}
|
|
if (mType == KIconThemeDir::Scalable) {
|
|
mMinSize = config.readEntry("MinSize", mSize);
|
|
mMaxSize = config.readEntry("MaxSize", mSize);
|
|
} else if (mType == KIconThemeDir::Threshold) {
|
|
mThreshold = config.readEntry("Threshold", 2);
|
|
}
|
|
mbValid = true;
|
|
}
|
|
|
|
QString KIconThemeDir::iconPath(const QString& name) const
|
|
{
|
|
if (!mbValid) {
|
|
return QString();
|
|
}
|
|
|
|
QString file = dir() + '/' + name;
|
|
|
|
if (KDE::access(file, R_OK) == 0) {
|
|
return KGlobal::hasLocale() ? KGlobal::locale()->localizedFilePath(file) : file;
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
QStringList KIconThemeDir::iconList() const
|
|
{
|
|
const QDir icondir = dir();
|
|
|
|
const QStringList formats = QStringList() << "*.png" << "*.svg" << "*.svgz" << "*.xpm";
|
|
const QStringList lst = icondir.entryList( formats, QDir::Files);
|
|
|
|
QStringList result;
|
|
QStringList::ConstIterator it;
|
|
for (it=lst.begin(); it!=lst.end(); ++it) {
|
|
result += dir() + '/' + *it;
|
|
}
|
|
return result;
|
|
}
|