kicontheme.cpp

00001 /* vi: ts=8 sts=4 sw=4
00002  *
00003  * $Id: kicontheme.cpp 357668 2004-10-25 19:22:32Z wildfox $
00004  *
00005  * This file is part of the KDE project, module kdecore.
00006  * Copyright (C) 2000 Geert Jansen <jansen@kde.org>
00007  *                    Antonio Larrosa <larrosa@kde.org>
00008  *
00009  * This is free software; it comes under the GNU Library General
00010  * Public License, version 2. See the file "COPYING.LIB" for the
00011  * exact licensing terms.
00012  *
00013  * kicontheme.cpp: Lowlevel icon theme handling.
00014  */
00015 
00016 #include <sys/stat.h>
00017 #include <unistd.h>
00018 #include <stdlib.h>
00019 #include <config.h>
00020 
00021 #include <qstring.h>
00022 #include <qstringlist.h>
00023 #include <qvaluelist.h>
00024 #include <qmap.h>
00025 #include <qpixmap.h>
00026 #include <qpixmapcache.h>
00027 #include <qimage.h>
00028 #include <qfileinfo.h>
00029 #include <qdir.h>
00030 
00031 #include <kdebug.h>
00032 #include <kstandarddirs.h>
00033 #include <kglobal.h>
00034 #include <kconfig.h>
00035 #include <ksimpleconfig.h>
00036 #include <kinstance.h>
00037 
00038 #include "kicontheme.h"
00039 
00040 class KIconThemePrivate
00041 {
00042 public:
00043     QString example, screenshot;
00044     QString linkOverlay, lockOverlay, zipOverlay, shareOverlay;
00045     bool hidden;
00046     KSharedConfig::Ptr sharedConfig;
00047 };
00048 
00052 class KIconThemeDir
00053 {
00054 public:
00055     KIconThemeDir(const QString& dir, const KConfigBase *config);
00056 
00057     bool isValid() const { return mbValid; }
00058     QString iconPath(const QString& name) const;
00059     QStringList iconList() const;
00060     QString dir() const { return mDir; }
00061 
00062     KIcon::Context context() const { return mContext; }
00063     KIcon::Type type() const { return mType; }
00064     int size() const { return mSize; }
00065     int minSize() const { return mMinSize; }
00066     int maxSize() const { return mMaxSize; }
00067     int threshold() const { return mThreshold; }
00068 
00069 private:
00070     bool mbValid;
00071     KIcon::Type mType;
00072     KIcon::Context mContext;
00073     int mSize, mMinSize, mMaxSize;
00074     int mThreshold;
00075 
00076     QString mDir;
00077 };
00078 
00079 
00080 /*** KIconTheme ***/
00081 
00082 KIconTheme::KIconTheme(const QString& name, const QString& appName)
00083 {
00084     d = new KIconThemePrivate;
00085 
00086     QStringList icnlibs;
00087     QStringList::ConstIterator it, itDir;
00088     QStringList themeDirs;
00089     QString cDir;
00090 
00091     // Applications can have local additions to the global "locolor" and
00092     // "hicolor" icon themes. For these, the _global_ theme description
00093     // files are used..
00094 
00095     if (!appName.isEmpty() &&
00096        ( name == "crystalsvg" || name== "hicolor" || name == "locolor" ) )
00097     {
00098     icnlibs = KGlobal::dirs()->resourceDirs("data");
00099     for (it=icnlibs.begin(); it!=icnlibs.end(); ++it)
00100     {
00101         cDir = *it + appName + "/icons/" + name;
00102         if (QFile::exists( cDir ))
00103         themeDirs += cDir + "/";
00104     }
00105     }
00106     // Find the theme description file. These are always global.
00107 
00108     icnlibs = KGlobal::dirs()->resourceDirs("icon");
00109     for (it=icnlibs.begin(); it!=icnlibs.end(); ++it)
00110     {
00111         cDir = *it + name + "/";
00112         if (KStandardDirs::exists(cDir))
00113         {
00114             themeDirs += cDir;
00115         if (mDir.isEmpty()
00116             && (KStandardDirs::exists( cDir + "index.desktop") || KStandardDirs::exists( cDir + "index.theme")))
00117         mDir = cDir;
00118         }
00119     }
00120 
00121     if (mDir.isEmpty())
00122     {
00123         kdDebug(264) << "Icon theme " << name << " not found.\n";
00124         return;
00125     }
00126 
00127     QString fileName, mainSection;
00128     if(QFile::exists(mDir + "index.desktop")) {
00129     fileName = mDir + "index.desktop";
00130     mainSection="KDE Icon Theme";
00131     } else {
00132     fileName = mDir + "index.theme";
00133     mainSection="Icon Theme";
00134     }
00135     // Use KSharedConfig to avoid parsing the file many times, from each kinstance.
00136     // Need to keep a ref to it to make this useful
00137     d->sharedConfig = KSharedConfig::openConfig( fileName, true /*readonly*/, false /*useKDEGlobals*/ );
00138     KConfig& cfg = *d->sharedConfig;
00139     //was: KSimpleConfig cfg(fileName);
00140 
00141     cfg.setGroup(mainSection);
00142     mName = cfg.readEntry("Name");
00143     mDesc = cfg.readEntry("Comment");
00144     mDepth = cfg.readNumEntry("DisplayDepth", 32);
00145     mInherits = cfg.readListEntry("Inherits");
00146     if ( name != "crystalsvg" )
00147       for ( QStringList::Iterator it = mInherits.begin(); it != mInherits.end(); ++it )
00148          if ( *it == "default" || *it == "hicolor" ) *it="crystalsvg";
00149 
00150     d->hidden = cfg.readBoolEntry("Hidden", false);
00151     d->example = cfg.readPathEntry("Example");
00152     d->screenshot = cfg.readPathEntry("ScreenShot");
00153     d->linkOverlay = cfg.readEntry("LinkOverlay", "link");
00154     d->lockOverlay = cfg.readEntry("LockOverlay", "lock");
00155     d->zipOverlay = cfg.readEntry("ZipOverlay", "zip");
00156     d->shareOverlay = cfg.readEntry("ShareOverlay","share");
00157 
00158     QStringList dirs = cfg.readPathListEntry("Directories");
00159     mDirs.setAutoDelete(true);
00160     for (it=dirs.begin(); it!=dirs.end(); ++it)
00161     {
00162     cfg.setGroup(*it);
00163     for (itDir=themeDirs.begin(); itDir!=themeDirs.end(); ++itDir)
00164     {
00165         if (KStandardDirs::exists(*itDir + *it + "/"))
00166         {
00167             KIconThemeDir *dir = new KIconThemeDir(*itDir + *it, &cfg);
00168             if (!dir->isValid())
00169             {
00170                 kdDebug(264) << "Icon directory " << *itDir << " group " << *it << " not valid.\n";
00171                 delete dir;
00172             }
00173             else
00174                 mDirs.append(dir);
00175             }
00176         }
00177     }
00178 
00179     // Expand available sizes for scalable icons to their full range
00180     int i;
00181     QMap<int,QValueList<int> > scIcons;
00182     for (KIconThemeDir *dir=mDirs.first(); dir!=0L; dir=mDirs.next())
00183     {
00184         if ((dir->type() == KIcon::Scalable) && !scIcons.contains(dir->size()))
00185         {
00186             QValueList<int> lst;
00187             for (i=dir->minSize(); i<=dir->maxSize(); i++)
00188                 lst += i;
00189             scIcons[dir->size()] = lst;
00190         }
00191     }
00192 
00193     QStringList groups;
00194     groups += "Desktop";
00195     groups += "Toolbar";
00196     groups += "MainToolbar";
00197     groups += "Small";
00198     groups += "Panel";
00199     const int defDefSizes[] = { 32, 22, 22, 16, 32 };
00200     cfg.setGroup(mainSection);
00201     for (it=groups.begin(), i=0; it!=groups.end(); ++it, i++)
00202     {
00203         mDefSize[i] = cfg.readNumEntry(*it + "Default", defDefSizes[i]);
00204         QValueList<int> exp, lst = cfg.readIntListEntry(*it + "Sizes");
00205         QValueList<int>::ConstIterator it2;
00206         for (it2=lst.begin(); it2!=lst.end(); ++it2)
00207         {
00208             if (scIcons.contains(*it2))
00209                 exp += scIcons[*it2];
00210             else
00211                 exp += *it2;
00212         }
00213         mSizes[i] = exp;
00214     }
00215 
00216 }
00217 
00218 KIconTheme::~KIconTheme()
00219 {
00220     delete d;
00221 }
00222 
00223 bool KIconTheme::isValid() const
00224 {
00225     return !mDirs.isEmpty();
00226 }
00227 
00228 bool KIconTheme::isHidden() const
00229 {
00230     return d->hidden;
00231 }
00232 
00233 QString KIconTheme::example() const { return d->example; }
00234 QString KIconTheme::screenshot() const { return d->screenshot; }
00235 QString KIconTheme::linkOverlay() const { return d->linkOverlay; }
00236 QString KIconTheme::lockOverlay() const { return d->lockOverlay; }
00237 QString KIconTheme::zipOverlay() const { return d->zipOverlay; }
00238 QString KIconTheme::shareOverlay() const { return d->shareOverlay; }
00239 
00240 int KIconTheme::defaultSize(KIcon::Group group) const
00241 {
00242     if ((group < 0) || (group >= KIcon::LastGroup))
00243     {
00244         kdDebug(264) << "Illegal icon group: " << group << "\n";
00245         return -1;
00246     }
00247     return mDefSize[group];
00248 }
00249 
00250 QValueList<int> KIconTheme::querySizes(KIcon::Group group) const
00251 {
00252     QValueList<int> empty;
00253     if ((group < 0) || (group >= KIcon::LastGroup))
00254     {
00255         kdDebug(264) << "Illegal icon group: " << group << "\n";
00256         return empty;
00257     }
00258     return mSizes[group];
00259 }
00260 
00261 QStringList KIconTheme::queryIcons(int size, KIcon::Context context) const
00262 {
00263     int delta = 1000, dw;
00264 
00265     QPtrListIterator<KIconThemeDir> dirs(mDirs);
00266     KIconThemeDir *dir;
00267 
00268     // Try to find exact match
00269     QStringList result;
00270     for ( ; dirs.current(); ++dirs)
00271     {
00272         dir = dirs.current();
00273         if ((context != KIcon::Any) && (context != dir->context()))
00274             continue;
00275         if ((dir->type() == KIcon::Fixed) && (dir->size() == size))
00276         {
00277             result += dir->iconList();
00278             continue;
00279         }
00280         if ((dir->type() == KIcon::Scalable) &&
00281             (size >= dir->minSize()) && (size <= dir->maxSize()))
00282         {
00283             result += dir->iconList();
00284             continue;
00285         }
00286     if ((dir->type() == KIcon::Threshold) &&
00287             (abs(size-dir->size())<dir->threshold()))
00288             result+=dir->iconList();
00289     }
00290 
00291     return result;
00292 
00293     dirs.toFirst();
00294 
00295     // Find close match
00296     KIconThemeDir *best = 0L;
00297     for ( ; dirs.current(); ++dirs)
00298     {
00299         dir = dirs.current();
00300         if ((context != KIcon::Any) && (context != dir->context()))
00301             continue;
00302         dw = dir->size() - size;
00303         if ((dw > 6) || (abs(dw) >= abs(delta)))
00304             continue;
00305         delta = dw;
00306         best = dir;
00307     }
00308     if (best == 0L)
00309         return QStringList();
00310 
00311     return best->iconList();
00312 }
00313 
00314 QStringList KIconTheme::queryIconsByContext(int size, KIcon::Context context) const
00315 {
00316     QPtrListIterator<KIconThemeDir> dirs(mDirs);
00317     int dw;
00318     KIconThemeDir *dir;
00319 
00320     // We want all the icons for a given context, but we prefer icons
00321     // of size size . Note that this may (will) include duplicate icons
00322     //QStringList iconlist[34]; // 33 == 48-16+1
00323     QStringList iconlist[128]; // 33 == 48-16+1
00324     // Usually, only the 0, 6 (22-16), 10 (32-22), 16 (48-32 or 32-16),
00325     // 26 (48-22) and 32 (48-16) will be used, but who knows if someone
00326     // will make icon themes with different icon sizes.
00327 
00328     for ( ; dirs.current(); ++dirs)
00329     {
00330         dir = dirs.current();
00331         if ((context != KIcon::Any) && (context != dir->context()))
00332             continue;
00333         dw = abs(dir->size() - size);
00334         iconlist[(dw<127)?dw:127]+=dir->iconList();
00335     }
00336 
00337     QStringList iconlistResult;
00338     for (int i=0; i<128; i++) iconlistResult+=iconlist[i];
00339 
00340     return iconlistResult;
00341 }
00342 
00343 bool KIconTheme::hasContext(KIcon::Context context) const
00344 {
00345     QPtrListIterator<KIconThemeDir> dirs(mDirs);
00346     KIconThemeDir *dir;
00347 
00348     for ( ; dirs.current(); ++dirs)
00349     {
00350         dir = dirs.current();
00351         if ((context == KIcon::Any) || (context == dir->context()))
00352             return true;
00353     }
00354     return false;
00355 }
00356 
00357 KIcon KIconTheme::iconPath(const QString& name, int size, KIcon::MatchType match) const
00358 {
00359     KIcon icon;
00360     QString path;
00361     int delta = -1000, dw;
00362     KIconThemeDir *dir;
00363 
00364     dw = 1000; // shut up, gcc
00365     QPtrListIterator<KIconThemeDir> dirs(mDirs);
00366     for ( ; dirs.current(); ++dirs)
00367     {
00368         dir = dirs.current();
00369 
00370         if (match == KIcon::MatchExact)
00371         {
00372             if ((dir->type() == KIcon::Fixed) && (dir->size() != size))
00373                 continue;
00374             if ((dir->type() == KIcon::Scalable) &&
00375                 ((size < dir->minSize()) || (size > dir->maxSize())))
00376               continue;
00377             if ((dir->type() == KIcon::Threshold) &&
00378         (abs(dir->size()-size) > dir->threshold()))
00379                 continue;
00380         } else
00381         {
00382           // dw < 0 means need to scale up to get an icon of the requested size
00383           if (dir->type() == KIcon::Fixed)
00384           {
00385             dw = dir->size() - size;
00386           } else if (dir->type() == KIcon::Scalable)
00387           {
00388             if (size < dir->minSize())
00389               dw = dir->minSize() - size;
00390             else if (size > dir->maxSize())
00391               dw = dir->maxSize() - size;
00392             else
00393               dw = 0;
00394           } else if (dir->type() == KIcon::Threshold)
00395           {
00396             if (size < dir->size() - dir->threshold())
00397               dw = dir->size() - dir->threshold() - size;
00398             else if (size > dir->size() + dir->threshold())
00399               dw = dir->size() + dir->threshold() - size;
00400             else
00401               dw = 0;
00402           }
00403           /* Skip this if we've found a closer one, unless
00404              it's a downscale, and we only had upscales befores.
00405              This is to avoid scaling up unless we have to,
00406              since that looks very ugly */
00407           if ((abs(dw) >= abs(delta)) ||
00408               (delta > 0 && dw < 0))
00409             continue;
00410         }
00411 
00412         path = dir->iconPath(name);
00413         if (path.isEmpty())
00414             continue;
00415         icon.path = path;
00416         icon.size = dir->size();
00417         icon.type = dir->type();
00418     icon.threshold = dir->threshold();
00419         icon.context = dir->context();
00420 
00421         // if we got in MatchExact that far, we find no better
00422         if (match == KIcon::MatchExact)
00423             return icon;
00424     else
00425         {
00426         delta = dw;
00427         if (delta==0) return icon; // We won't find a better match anyway
00428         }
00429     }
00430     return icon;
00431 }
00432 
00433 // static
00434 QString *KIconTheme::_theme = 0L;
00435 
00436 // static
00437 QStringList *KIconTheme::_theme_list = 0L;
00438 
00439 // static
00440 QString KIconTheme::current()
00441 {
00442     // Static pointer because of unloading problems wrt DSO's.
00443     if (_theme != 0L)
00444         return *_theme;
00445 
00446     _theme = new QString();
00447     KConfig *config = KGlobal::config();
00448     KConfigGroupSaver saver(config, "Icons");
00449     *_theme = config->readEntry("Theme",defaultThemeName());
00450     if ( *_theme == QString::fromLatin1("hicolor") ) *_theme = defaultThemeName();
00451 /*    if (_theme->isEmpty())
00452     {
00453         if (QPixmap::defaultDepth() > 8)
00454             *_theme = defaultThemeName();
00455         else
00456             *_theme = QString::fromLatin1("locolor");
00457     }*/
00458     return *_theme;
00459 }
00460 
00461 // static
00462 QStringList KIconTheme::list()
00463 {
00464     // Static pointer because of unloading problems wrt DSO's.
00465     if (_theme_list != 0L)
00466         return *_theme_list;
00467 
00468     _theme_list = new QStringList();
00469     QStringList icnlibs = KGlobal::dirs()->resourceDirs("icon");
00470     QStringList::ConstIterator it;
00471     for (it=icnlibs.begin(); it!=icnlibs.end(); ++it)
00472     {
00473         QDir dir(*it);
00474         if (!dir.exists())
00475             continue;
00476         QStringList lst = dir.entryList(QDir::Dirs);
00477         QStringList::ConstIterator it2;
00478         for (it2=lst.begin(); it2!=lst.end(); ++it2)
00479         {
00480             if ((*it2 == ".") || (*it2 == "..") || (*it2).startsWith("default.") )
00481                 continue;
00482             if (!KStandardDirs::exists(*it + *it2 + "/index.desktop") && !KStandardDirs::exists(*it + *it2 + "/index.theme"))
00483                 continue;
00484         KIconTheme oink(*it2);
00485         if (!oink.isValid()) continue;
00486 
00487         if (!_theme_list->contains(*it2))
00488                 _theme_list->append(*it2);
00489         }
00490     }
00491     return *_theme_list;
00492 }
00493 
00494 // static
00495 void KIconTheme::reconfigure()
00496 {
00497     delete _theme;
00498     _theme=0L;
00499     delete _theme_list;
00500     _theme_list=0L;
00501 }
00502 
00503 // static
00504 QString KIconTheme::defaultThemeName()
00505 {
00506     return QString::fromLatin1("crystalsvg");
00507 }
00508 
00509 /*** KIconThemeDir ***/
00510 
00511 KIconThemeDir::KIconThemeDir(const QString& dir, const KConfigBase *config)
00512 {
00513     mbValid = false;
00514     mDir = dir;
00515     mSize = config->readNumEntry("Size");
00516     mMinSize = 1;    // just set the variables to something
00517     mMaxSize = 50;   // meaningful in case someone calls minSize or maxSize
00518     mType = KIcon::Fixed;
00519 
00520     if (mSize == 0)
00521         return;
00522 
00523     QString tmp = config->readEntry("Context");
00524     if (tmp == "Devices")
00525         mContext = KIcon::Device;
00526     else if (tmp == "MimeTypes")
00527         mContext = KIcon::MimeType;
00528     else if (tmp == "FileSystems")
00529         mContext = KIcon::FileSystem;
00530     else if (tmp == "Applications")
00531         mContext = KIcon::Application;
00532     else if (tmp == "Actions")
00533         mContext = KIcon::Action;
00534     else if (tmp == "Animations")
00535         mContext = KIcon::Animation;
00536     else if (tmp == "Categories")
00537         mContext = KIcon::Category;
00538     else if (tmp == "Emblems")
00539         mContext = KIcon::Emblem;
00540     else if (tmp == "Emotes")
00541         mContext = KIcon::Emote;
00542     else if (tmp == "International")
00543         mContext = KIcon::International;
00544     else if (tmp == "Places")
00545         mContext = KIcon::Place;
00546     else if (tmp == "Status")
00547         mContext = KIcon::StatusIcon;
00548     else {
00549         kdDebug(264) << "Invalid Context= line for icon theme: " << mDir << "\n";
00550         return;
00551     }
00552     tmp = config->readEntry("Type");
00553     if (tmp == "Fixed")
00554         mType = KIcon::Fixed;
00555     else if (tmp == "Scalable")
00556         mType = KIcon::Scalable;
00557     else if (tmp == "Threshold")
00558         mType = KIcon::Threshold;
00559     else {
00560         kdDebug(264) << "Invalid Type= line for icon theme: " <<  mDir << "\n";
00561         return;
00562     }
00563     if (mType == KIcon::Scalable)
00564     {
00565         mMinSize = config->readNumEntry("MinSize", mSize);
00566         mMaxSize = config->readNumEntry("MaxSize", mSize);
00567     } else if (mType == KIcon::Threshold)
00568     mThreshold = config->readNumEntry("Threshold", 2);
00569     mbValid = true;
00570 }
00571 
00572 QString KIconThemeDir::iconPath(const QString& name) const
00573 {
00574     if (!mbValid)
00575         return QString::null;
00576     QString file = mDir + "/" + name;
00577 
00578     if (access(QFile::encodeName(file), R_OK) == 0)
00579         return file;
00580 
00581     return QString::null;
00582 }
00583 
00584 QStringList KIconThemeDir::iconList() const
00585 {
00586     QDir dir(mDir);
00587 #ifdef HAVE_LIBART
00588     QStringList lst = dir.entryList("*.png;*.svg;*.svgz;*.xpm", QDir::Files);
00589 #else
00590     QStringList lst = dir.entryList("*.png;*.xpm", QDir::Files);
00591 #endif
00592     QStringList result;
00593     QStringList::ConstIterator it;
00594     for (it=lst.begin(); it!=lst.end(); ++it)
00595         result += mDir + "/" + *it;
00596     return result;
00597 }
KDE Home | KDE Accessibility Home | Description of Access Keys