00001 /**************************************************************************** 00002 ** 00003 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). 00004 ** Contact: Qt Software Information (qt-info@nokia.com) 00005 ** 00006 ** This file is part of the QtGui module of the Qt Toolkit. 00007 ** 00008 ** Commercial Usage 00009 ** Licensees holding valid Qt Commercial licenses may use this file in 00010 ** accordance with the Qt Commercial License Agreement provided with the 00011 ** Software or, alternatively, in accordance with the terms contained in 00012 ** a written agreement between you and Nokia. 00013 ** 00014 ** GNU Lesser General Public License Usage 00015 ** Alternatively, this file may be used under the terms of the GNU Lesser 00016 ** General Public License version 2.1 as published by the Free Software 00017 ** Foundation and appearing in the file LICENSE.LGPL included in the 00018 ** packaging of this file. Please review the following information to 00019 ** ensure the GNU Lesser General Public License version 2.1 requirements 00020 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 00021 ** 00022 ** In addition, as a special exception, Nokia gives you certain 00023 ** additional rights. These rights are described in the Nokia Qt LGPL 00024 ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this 00025 ** package. 00026 ** 00027 ** GNU General Public License Usage 00028 ** Alternatively, this file may be used under the terms of the GNU 00029 ** General Public License version 3.0 as published by the Free Software 00030 ** Foundation and appearing in the file LICENSE.GPL included in the 00031 ** packaging of this file. Please review the following information to 00032 ** ensure the GNU General Public License version 3.0 requirements will be 00033 ** met: http://www.gnu.org/copyleft/gpl.html. 00034 ** 00035 ** If you are unsure which license is appropriate for your use, please 00036 ** contact the sales department at qt-sales@nokia.com. 00037 ** 00038 ****************************************************************************/ 00039 00040 00041 #include "qticonloader.h" 00042 #include <QtGui/QPixmapCache> 00043 00044 #include <QtCore/QList> 00045 #include <QtCore/QHash> 00046 #include <QtCore/QDir> 00047 #include <QtCore/QString> 00048 #include <QtCore/QLibrary> 00049 #include <QtCore/QSettings> 00050 #include <QtCore/QTextStream> 00051 00052 #ifdef Q_WS_X11 00053 00054 extern "C" { 00055 struct GConfClient; 00056 struct GError; 00057 typedef void (*Ptr_g_type_init)(); 00058 typedef GConfClient* (*Ptr_gconf_client_get_default)(); 00059 typedef char* (*Ptr_gconf_client_get_string)(GConfClient*, const char*, GError **); 00060 typedef void (*Ptr_g_object_unref)(void *); 00061 typedef void (*Ptr_g_error_free)(GError *); 00062 typedef void (*Ptr_g_free)(void*); 00063 static Ptr_g_type_init p_g_type_init = 0; 00064 static Ptr_gconf_client_get_default p_gconf_client_get_default = 0; 00065 static Ptr_gconf_client_get_string p_gconf_client_get_string = 0; 00066 static Ptr_g_object_unref p_g_object_unref = 0; 00067 static Ptr_g_error_free p_g_error_free = 0; 00068 static Ptr_g_free p_g_free = 0; 00069 } 00070 00071 00072 static int kdeVersion() 00073 { 00074 static int version = qgetenv("KDE_SESSION_VERSION").toInt(); 00075 return version; 00076 } 00077 00078 static QString kdeHome() 00079 { 00080 static QString kdeHomePath; 00081 if (kdeHomePath.isEmpty()) { 00082 kdeHomePath = QFile::decodeName(qgetenv("KDEHOME")); 00083 if (kdeHomePath.isEmpty()) { 00084 int kdeSessionVersion = kdeVersion(); 00085 QDir homeDir(QDir::homePath()); 00086 QString kdeConfDir(QLatin1String("/.kde")); 00087 if (4 == kdeSessionVersion && homeDir.exists(QLatin1String(".kde4"))) 00088 kdeConfDir = QLatin1String("/.kde4"); 00089 kdeHomePath = QDir::homePath() + kdeConfDir; 00090 } 00091 } 00092 return kdeHomePath; 00093 } 00094 00095 00096 static QString systemThemeName() 00097 { 00098 QString themeName; 00099 #ifdef Q_WS_X11 00100 // If we are running GNOME we resolve and use GConf. In all other 00101 // cases we currently use the KDE icon theme 00102 if (qgetenv("DESKTOP_SESSION") == "gnome" || 00103 !qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) { 00104 00105 if (themeName.isEmpty()) { 00106 // Resolve glib and gconf 00107 00108 p_g_type_init = (Ptr_g_type_init)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_type_init"); 00109 p_gconf_client_get_default = (Ptr_gconf_client_get_default)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_default"); 00110 p_gconf_client_get_string = (Ptr_gconf_client_get_string)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_string"); 00111 p_g_object_unref = (Ptr_g_object_unref)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_object_unref"); 00112 p_g_error_free = (Ptr_g_error_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_error_free"); 00113 p_g_free = (Ptr_g_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_free"); 00114 00115 if (p_g_type_init && p_gconf_client_get_default && 00116 p_gconf_client_get_string && p_g_object_unref && 00117 p_g_error_free && p_g_free) { 00118 00119 p_g_type_init(); 00120 GConfClient* client = p_gconf_client_get_default(); 00121 GError *err = 0; 00122 00123 char *str = p_gconf_client_get_string(client, "/desktop/gnome/interface/icon_theme", &err); 00124 if (!err) { 00125 themeName = QString::fromUtf8(str); 00126 p_g_free(str); 00127 } 00128 00129 p_g_object_unref(client); 00130 if (err) 00131 p_g_error_free (err); 00132 00133 } 00134 if (themeName.isEmpty()) 00135 themeName = QLatin1String("gnome"); 00136 } 00137 00138 if (!themeName.isEmpty()) 00139 return themeName; 00140 } 00141 00142 QStringList kdeDirs = QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':')); 00143 00144 bool kde4 = kdeVersion() >= 4; 00145 QString defaultPath = kde4 ? 00146 QLatin1String("/usr/share/icons/default.kde4") : 00147 QLatin1String("/usr/share/icons/default.kde"); 00148 00149 QFileInfo fileInfo(defaultPath); 00150 QDir dir(fileInfo.canonicalFilePath()); 00151 QString kdeDefault = kde4 ? 00152 QString::fromLatin1("oxygen") : 00153 QString::fromLatin1("crystalsvg"); 00154 QString defaultTheme = fileInfo.exists() ? dir.dirName() : kdeDefault; 00155 00156 QSettings settings(kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat); 00157 settings.beginGroup(QLatin1String("Icons")); 00158 themeName = settings.value(QLatin1String("Theme"), defaultTheme).toString(); 00159 00160 #endif 00161 return themeName; 00162 } 00163 00164 struct QIconDirInfo 00165 { 00166 enum Type { Fixed, Scalable, Threshold }; 00167 QIconDirInfo(const QString &_path = QString()) : 00168 path(_path), 00169 size(0), 00170 maxSize(0), 00171 minSize(0), 00172 threshold(0), 00173 type(Threshold) {} 00174 QString path; 00175 short size; 00176 short maxSize; 00177 short minSize; 00178 short threshold; 00179 Type type : 4; 00180 }; 00181 00182 class QIconLoaderEngineEntry 00183 { 00184 public: 00185 virtual ~QIconLoaderEngineEntry() {} 00186 QString filename; 00187 QIconDirInfo dir; 00188 static int count; 00189 }; 00190 00191 struct ScalableEntry : public QIconLoaderEngineEntry 00192 { 00193 QIcon svgIcon; 00194 }; 00195 00196 struct PixmapEntry : public QIconLoaderEngineEntry 00197 { 00198 QPixmap basePixmap; 00199 }; 00200 00201 typedef QList<QIconLoaderEngineEntry*> QThemeIconEntries; 00202 00203 class QIconTheme 00204 { 00205 public: 00206 QIconTheme(const QString &name); 00207 QIconTheme() : m_valid(false) {}; 00208 QStringList parents() { return m_parents; } 00209 QList <QIconDirInfo> keyList() { return m_keyList; } 00210 QString contentDir() { return m_contentDir; } 00211 bool isValid() { return m_valid; } 00212 00213 private: 00214 QString m_contentDir; 00215 QList <QIconDirInfo> m_keyList; 00216 QStringList m_parents; 00217 bool m_valid; 00218 }; 00219 00220 00221 class QtIconLoaderImplementation 00222 { 00223 public: 00224 QtIconLoaderImplementation(); 00225 QIcon loadIcon(const QString &name); 00226 00227 private: 00228 QString themeName() const { return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; } 00229 00230 QThemeIconEntries findIconHelper(const QString &themeName, 00231 const QString &iconName, 00232 QStringList &visited) const; 00233 00234 uint m_themeKey; 00235 bool m_supportsSvg; 00236 mutable QString m_userTheme; 00237 mutable QString m_systemTheme; 00238 mutable QStringList m_iconDirs; 00239 mutable QHash <QString, QIconTheme> themeList; 00240 }; 00241 00242 Q_GLOBAL_STATIC(QtIconLoaderImplementation, iconLoaderInstance) 00243 00244 /*! 00245 00246 Returns the standard icon for the given icon /a name 00247 as specified in the freedesktop icon spec 00248 http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html 00249 00250 /a fallback is an optional argument to specify the icon to be used if 00251 no icon is found on the platform. This is particularily useful for 00252 crossplatform code. 00253 00254 */ 00255 QIcon QtIconLoaderImplementation::loadIcon(const QString &name) 00256 { 00257 QIcon icon; 00258 QString theme = themeName(); 00259 if (!theme.isEmpty()) { 00260 QStringList visited; 00261 QThemeIconEntries entries = findIconHelper(theme, name, visited); 00262 for (int i = 0; i < entries.size() ; ++i) { 00263 int size = entries.at(i)->dir.size; 00264 icon.addFile(entries.at(i)->filename, 00265 QSize(size, size), 00266 QIcon::Normal, QIcon::Off); 00267 } 00268 } 00269 return icon; 00270 } 00271 00272 QtIconLoaderImplementation::QtIconLoaderImplementation() 00273 { 00274 m_systemTheme = systemThemeName(); 00275 00276 /* QFactoryLoader iconFactoryLoader(QIconEngineFactoryInterfaceV2_iid, 00277 QLatin1String("/iconengines"), 00278 Qt::CaseInsensitive); 00279 if (iconFactoryLoader.keys().contains(QLatin1String("svg"))) 00280 */ 00281 m_supportsSvg = false; 00282 } 00283 00284 static QString fallbackTheme() 00285 { 00286 QString defaultTheme = systemThemeName(); 00287 if (defaultTheme.isEmpty()) 00288 defaultTheme = QLatin1String("hicolor"); 00289 return defaultTheme; 00290 } 00291 00292 static QStringList themeSearchPaths() 00293 { 00294 QStringList iconDirs; 00295 if (iconDirs.isEmpty()) { 00296 00297 #if defined(Q_WS_X11) 00298 00299 QString xdgDirString = QFile::decodeName(getenv("XDG_DATA_DIRS")); 00300 if (xdgDirString.isEmpty()) 00301 xdgDirString = QLatin1String("/usr/local/share/:/usr/share/"); 00302 00303 QStringList xdgDirs = xdgDirString.split(QLatin1Char(':')); 00304 00305 for (int i = 0 ; i < xdgDirs.size() ; ++i) { 00306 QDir dir(xdgDirs[i]); 00307 if (dir.exists()) 00308 iconDirs.append(dir.path() + 00309 QLatin1String("/icons")); 00310 } 00311 00312 if (kdeVersion() != 0) { 00313 00314 iconDirs << QLatin1Char(':') + 00315 kdeHome() + QLatin1String("/share/icons"); 00316 QStringList kdeDirs = 00317 QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':')); 00318 00319 for (int i = 0 ; i< kdeDirs.count() ; ++i) { 00320 QDir dir(QLatin1Char(':') + kdeDirs.at(i) + 00321 QLatin1String("/share/icons")); 00322 if (dir.exists()) 00323 iconDirs.append(dir.path()); 00324 } 00325 } 00326 00327 // Add home directory first in search path 00328 QDir homeDir(QDir::homePath() + QLatin1String("/.icons")); 00329 if (homeDir.exists()) 00330 iconDirs.prepend(homeDir.path()); 00331 00332 #elif defined(Q_WS_WIN) 00333 m_iconDirs.append(qApp->applicationDirPath() + 00334 QLatin1String("/icons")); 00335 #elif defined(Q_WS_MAC) 00336 m_iconDirs.append(qApp->applicationDirPath() + 00337 QLatin1String("/../Resources/icons")); 00338 #endif 00339 } 00340 return iconDirs; 00341 } 00342 00343 00344 QIconTheme::QIconTheme(const QString &themeName) 00345 : m_valid(false) 00346 { 00347 QFile themeIndex; 00348 00349 QList <QIconDirInfo> keyList; 00350 QStringList iconDirs = themeSearchPaths(); 00351 for ( int i = 0 ; i < iconDirs.size() ; ++i) { 00352 QDir iconDir(iconDirs[i]); 00353 QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; 00354 themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); 00355 if (themeIndex.exists()) { 00356 m_contentDir = themeDir; 00357 m_valid = true; 00358 break; 00359 } 00360 } 00361 00362 if (themeIndex.exists()) { 00363 const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); 00364 QStringListIterator keyIterator(indexReader.allKeys()); 00365 while (keyIterator.hasNext()) { 00366 00367 const QString key = keyIterator.next(); 00368 if (key.endsWith(QLatin1String("/Size"))) { 00369 // Note the QSettings ini-format does not accept 00370 // slashes in key names, hence we have to cheat 00371 if (int size = indexReader.value(key).toInt()) { 00372 QString directoryKey = key.left(key.size() - 5); 00373 QIconDirInfo dirInfo(directoryKey); 00374 dirInfo.size = size; 00375 QString type = indexReader.value(directoryKey + 00376 QLatin1String("/Type") 00377 ).toString(); 00378 00379 if (type == QLatin1String("Fixed")) 00380 dirInfo.type = QIconDirInfo::Fixed; 00381 else if (type == QLatin1String("Scalable")) 00382 dirInfo.type = QIconDirInfo::Scalable; 00383 else 00384 dirInfo.type = QIconDirInfo::Threshold; 00385 00386 dirInfo.threshold = indexReader.value(directoryKey + 00387 QLatin1String("/Threshold"), 00388 2).toInt(); 00389 00390 dirInfo.minSize = indexReader.value(directoryKey + 00391 QLatin1String("/MinSize"), 00392 size).toInt(); 00393 00394 dirInfo.maxSize = indexReader.value(directoryKey + 00395 QLatin1String("/MaxSize"), 00396 size).toInt(); 00397 m_keyList.append(dirInfo); 00398 } 00399 } 00400 } 00401 00402 // Parent themes provide fallbacks for missing icons 00403 m_parents = indexReader.value( 00404 QLatin1String("Icon Theme/Inherits")).toStringList(); 00405 00406 // Ensure a default platform fallback for all themes 00407 if (m_parents.isEmpty()) 00408 m_parents.append(fallbackTheme()); 00409 00410 // Ensure that all themes fall back to hicolor 00411 if (!m_parents.isEmpty()) 00412 m_parents.append(QLatin1String("hicolor")); 00413 } 00414 } 00415 00416 00417 QThemeIconEntries QtIconLoaderImplementation::findIconHelper(const QString &themeName, 00418 const QString &iconName, 00419 QStringList &visited) const 00420 { 00421 QThemeIconEntries entries; 00422 Q_ASSERT(!themeName.isEmpty()); 00423 00424 QPixmap pixmap; 00425 00426 // Used to protect against potential recursions 00427 visited << themeName; 00428 00429 QIconTheme theme = themeList.value(themeName); 00430 if (!theme.isValid()) { 00431 theme = QIconTheme(themeName); 00432 if (!theme.isValid()) 00433 theme = fallbackTheme(); 00434 00435 themeList.insert(themeName, theme); 00436 } 00437 00438 QString contentDir = theme.contentDir() + QLatin1Char('/'); 00439 QList<QIconDirInfo> subDirs = theme.keyList(); 00440 00441 const QString svgext(QLatin1String(".svg")); 00442 const QString pngext(QLatin1String(".png")); 00443 00444 // Add all relevant files 00445 for (int i = 0; i < subDirs.size() ; ++i) { 00446 const QIconDirInfo &dirInfo = subDirs.at(i); 00447 QString subdir = dirInfo.path; 00448 QDir currentDir(contentDir + subdir); 00449 00450 if (dirInfo.type == QIconDirInfo::Scalable && m_supportsSvg && 00451 currentDir.exists(iconName + svgext)) { 00452 ScalableEntry *iconEntry = new ScalableEntry; 00453 iconEntry->dir = dirInfo; 00454 iconEntry->filename = currentDir.filePath(iconName + svgext); 00455 entries.append(iconEntry); 00456 00457 } else if (currentDir.exists(iconName + pngext)) { 00458 PixmapEntry *iconEntry = new PixmapEntry; 00459 iconEntry->dir = dirInfo; 00460 iconEntry->filename = currentDir.filePath(iconName + pngext); 00461 // Notice we ensure that pixmap entries allways come before 00462 // scalable to preserve search order afterwards 00463 entries.prepend(iconEntry); 00464 } 00465 } 00466 00467 if (entries.isEmpty()) { 00468 const QStringList parents = theme.parents(); 00469 // Search recursively through inherited themes 00470 for (int i = 0 ; i < parents.size() ; ++i) { 00471 00472 const QString parentTheme = parents.at(i).trimmed(); 00473 00474 if (!visited.contains(parentTheme)) // guard against recursion 00475 entries = findIconHelper(parentTheme, iconName, visited); 00476 00477 if (!entries.isEmpty()) // success 00478 break; 00479 } 00480 } 00481 return entries; 00482 } 00483 00484 #endif //Q_WS_X11 00485 00486 QIcon QtIconLoader::icon(const QString &name, const QIcon &fallback) 00487 { 00488 QIcon icon; 00489 #ifdef Q_WS_X11 00490 icon = iconLoaderInstance()->loadIcon(name); 00491 #endif //Q_WS_X11 00492 if (icon.isNull()) 00493 icon = fallback; 00494 Q_UNUSED(name); 00495 return icon; 00496 }