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