qticonloader.cpp

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 }
Generated on Sun May 8 08:05:22 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3