1 /****************************************************************************
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: Qt Software Information (qt-info@nokia.com)
6 ** This file is part of the QtGui module of the Qt Toolkit.
9 ** Licensees holding valid Qt Commercial licenses may use this file in
10 ** accordance with the Qt Commercial License Agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and Nokia.
14 ** GNU Lesser General Public License Usage
15 ** Alternatively, this file may be used under the terms of the GNU Lesser
16 ** General Public License version 2.1 as published by the Free Software
17 ** Foundation and appearing in the file LICENSE.LGPL included in the
18 ** packaging of this file. Please review the following information to
19 ** ensure the GNU Lesser General Public License version 2.1 requirements
20 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22 ** In addition, as a special exception, Nokia gives you certain
23 ** additional rights. These rights are described in the Nokia Qt LGPL
24 ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
27 ** GNU General Public License Usage
28 ** Alternatively, this file may be used under the terms of the GNU
29 ** General Public License version 3.0 as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL included in the
31 ** packaging of this file. Please review the following information to
32 ** ensure the GNU General Public License version 3.0 requirements will be
33 ** met: http://www.gnu.org/copyleft/gpl.html.
35 ** If you are unsure which license is appropriate for your use, please
36 ** contact the sales department at qt-sales@nokia.com.
38 ****************************************************************************/
41 #include "qticonloader.h"
42 #include <QtGui/QPixmapCache>
44 #include <QtCore/QList>
45 #include <QtCore/QHash>
46 #include <QtCore/QDir>
47 #include <QtCore/QString>
48 #include <QtCore/QLibrary>
49 #include <QtCore/QSettings>
50 #include <QtCore/QTextStream>
57 QIconTheme(QHash <int, QString> dirList, QStringList parents) :
58 _dirList(dirList), _parents(parents), _valid(true){ }
59 QIconTheme() : _valid(false){ }
60 QHash <int, QString> dirList() {return _dirList;}
61 QStringList parents() {return _parents;}
62 bool isValid() {return _valid;}
65 QHash <int, QString> _dirList;
70 class QtIconLoaderImplementation
73 QtIconLoaderImplementation();
74 QPixmap findIcon(int size, const QString &name) const;
77 QIconTheme parseIndexFile(const QString &themeName) const;
78 void lookupIconTheme() const;
79 QPixmap findIconHelper(int size,
80 const QString &themeName,
81 const QString &iconName,
82 QStringList &visited) const;
83 mutable QString themeName;
84 mutable QStringList iconDirs;
85 mutable QHash <QString, QIconTheme> themeList;
88 Q_GLOBAL_STATIC(QtIconLoaderImplementation, iconLoaderInstance)
93 Returns the standard icon for the given icon /a name
94 as specified in the freedesktop icon spec
95 http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
97 /a fallback is an optional argument to specify the icon to be used if
98 no icon is found on the platform. This is particularily useful for
102 QIcon QtIconLoader::icon(const QString &name)
107 #if QT_VERSION < 0x040600
108 QString pngExtension(QLatin1String(".png"));
109 QList<int> iconSizes;
110 iconSizes << 16 << 22 << 24 << 32 << 48;
111 Q_FOREACH (int size, iconSizes) {
112 icon.addPixmap(iconLoaderInstance()->findIcon(size, name));
115 icon = QIcon::fromTheme(name);
118 icon = QIcon(QString(":/images/%1.png").arg(name));
119 if (!icon.isNull()) {
120 icon.addPixmap(QString(":/images/%1_active.png").arg(name), QIcon::Active);
121 icon.addPixmap(QString(":/images/%1_selected.png").arg(name), QIcon::Selected);
130 QtIconLoaderImplementation::QtIconLoaderImplementation()
138 typedef void (*Ptr_g_type_init)();
139 typedef GConfClient* (*Ptr_gconf_client_get_default)();
140 typedef char* (*Ptr_gconf_client_get_string)(GConfClient*, const char*, GError **);
141 typedef void (*Ptr_g_object_unref)(void *);
142 typedef void (*Ptr_g_error_free)(GError *);
143 typedef void (*Ptr_g_free)(void*);
144 static Ptr_g_type_init p_g_type_init = 0;
145 static Ptr_gconf_client_get_default p_gconf_client_get_default = 0;
146 static Ptr_gconf_client_get_string p_gconf_client_get_string = 0;
147 static Ptr_g_object_unref p_g_object_unref = 0;
148 static Ptr_g_error_free p_g_error_free = 0;
149 static Ptr_g_free p_g_free = 0;
153 static int kdeVersion()
155 static int version = qgetenv("KDE_SESSION_VERSION").toInt();
159 static QString kdeHome()
161 static QString kdeHomePath;
162 if (kdeHomePath.isEmpty()) {
163 kdeHomePath = QFile::decodeName(qgetenv("KDEHOME"));
164 if (kdeHomePath.isEmpty()) {
165 int kdeSessionVersion = kdeVersion();
166 QDir homeDir(QDir::homePath());
167 QString kdeConfDir(QLatin1String("/.kde"));
168 if (4 == kdeSessionVersion && homeDir.exists(QLatin1String(".kde4")))
169 kdeConfDir = QLatin1String("/.kde4");
170 kdeHomePath = QDir::homePath() + kdeConfDir;
176 void QtIconLoaderImplementation::lookupIconTheme() const
180 QString dataDirs = QFile::decodeName(getenv("XDG_DATA_DIRS"));
181 if (dataDirs.isEmpty())
182 dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
184 dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
185 iconDirs = dataDirs.split(QLatin1Char(':'));
187 // If we are running GNOME we resolve and use GConf. In all other
188 // cases we currently use the KDE icon theme
190 if (qgetenv("DESKTOP_SESSION") == "gnome" ||
191 !qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) {
193 if (themeName.isEmpty()) {
194 // Resolve glib and gconf
196 p_g_type_init = (Ptr_g_type_init)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_type_init");
197 p_gconf_client_get_default = (Ptr_gconf_client_get_default)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_default");
198 p_gconf_client_get_string = (Ptr_gconf_client_get_string)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_string");
199 p_g_object_unref = (Ptr_g_object_unref)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_object_unref");
200 p_g_error_free = (Ptr_g_error_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_error_free");
201 p_g_free = (Ptr_g_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_free");
203 if (p_g_type_init && p_gconf_client_get_default &&
204 p_gconf_client_get_string && p_g_object_unref &&
205 p_g_error_free && p_g_free) {
208 GConfClient* client = p_gconf_client_get_default();
211 char *str = p_gconf_client_get_string(client, "/desktop/gnome/interface/icon_theme", &err);
213 themeName = QString::fromUtf8(str);
217 p_g_object_unref(client);
219 p_g_error_free (err);
221 if (themeName.isEmpty())
222 themeName = QLatin1String("gnome");
225 if (!themeName.isEmpty())
230 if (dataDirs.isEmpty())
231 dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
233 dataDirs += QLatin1Char(':') + kdeHome() + QLatin1String("/share");
234 dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
235 QStringList kdeDirs = QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':'));
236 Q_FOREACH (const QString dirName, kdeDirs)
237 dataDirs.append(QLatin1Char(':') + dirName + QLatin1String("/share"));
238 iconDirs = dataDirs.split(QLatin1Char(':'));
240 QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
241 QDir dir(fileInfo.canonicalFilePath());
242 QString kdeDefault = kdeVersion() >= 4 ? QString::fromLatin1("oxygen") : QString::fromLatin1("crystalsvg");
243 QString defaultTheme = fileInfo.exists() ? dir.dirName() : kdeDefault;
244 QSettings settings(kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat);
245 settings.beginGroup(QLatin1String("Icons"));
246 themeName = settings.value(QLatin1String("Theme"), defaultTheme).toString();
250 QIconTheme QtIconLoaderImplementation::parseIndexFile(const QString &themeName) const
255 QHash <int, QString> dirList;
257 for ( int i = 0 ; i < iconDirs.size() && !themeIndex.exists() ; ++i) {
258 const QString &contentDir = QLatin1String(iconDirs[i].startsWith(QDir::homePath()) ? "/.icons/" : "/icons/");
259 themeIndex.setFileName(iconDirs[i] + contentDir + themeName + QLatin1String("/index.theme"));
262 if (themeIndex.exists()) {
263 QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
264 Q_FOREACH (const QString &key, indexReader.allKeys()) {
265 if (key.endsWith("/Size")) {
266 if (int size = indexReader.value(key).toInt())
267 dirList.insertMulti(size, key.left(key.size() - 5));
271 // Parent themes provide fallbacks for missing icons
272 // parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toString().split(QLatin1Char(','));
273 parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toStringList();
278 if (kdeVersion() >= 3) {
279 QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
280 QDir dir(fileInfo.canonicalFilePath());
281 QString defaultKDETheme = dir.exists() ? dir.dirName() : kdeVersion() == 3 ?
282 QString::fromLatin1("crystalsvg") : QString::fromLatin1("oxygen");
283 if (!parents.contains(defaultKDETheme) && themeName != defaultKDETheme)
284 parents.append(defaultKDETheme);
285 } else if (parents.isEmpty() && themeName != QLatin1String("hicolor")) {
286 parents.append(QLatin1String("hicolor"));
289 theme = QIconTheme(dirList, parents);
293 QPixmap QtIconLoaderImplementation::findIconHelper(int size, const QString &themeName,
294 const QString &iconName, QStringList &visited) const
298 if (!themeName.isEmpty()) {
299 visited << themeName;
300 QIconTheme theme = themeList.value(themeName);
302 if (!theme.isValid()) {
303 theme = parseIndexFile(themeName);
304 themeList.insert(themeName, theme);
307 if (!theme.isValid())
310 QList <QString> subDirs = theme.dirList().values(size);
312 for ( int i = 0 ; i < iconDirs.size() ; ++i) {
313 for ( int j = 0 ; j < subDirs.size() ; ++j) {
314 QString contentDir = (iconDirs[i].startsWith(QDir::homePath())) ?
315 QLatin1String("/.icons/") : QLatin1String("/icons/");
316 QString fileName = iconDirs[i] + contentDir + themeName + QLatin1Char('/') + subDirs[j] + QLatin1Char('/') + iconName;
318 QString svgExtension(QLatin1String(".svg"));
319 QString svgFilename = fileName + svgExtension;
320 QFile svgFile(svgFilename);
321 if (false && svgFile.exists()) {
322 // qDebug() << "Found svg";
323 pixmap.load(svgFilename);
325 QString pngExtension(QLatin1String(".png"));
326 QString pngFilename = fileName + pngExtension;
327 QFile pngFile(pngFilename);
328 if (pngFile.exists()) {
329 // qDebug() << "Found png";
330 pixmap.load(pngFilename);
334 if (!pixmap.isNull())
339 if (pixmap.isNull()) {
340 QStringList parents = theme.parents();
341 //search recursively through inherited themes
342 for (int i = 0 ; pixmap.isNull() && i < parents.size() ; ++i) {
343 QString parentTheme = parents[i].trimmed();
344 if (!visited.contains(parentTheme)) //guard against endless recursion
345 pixmap = findIconHelper(size, parentTheme, iconName, visited);
352 QPixmap QtIconLoaderImplementation::findIcon(int size, const QString &name) const
355 QString pixmapName = QLatin1String("$qt") + name + QString::number(size);
356 if (QPixmapCache::find(pixmapName, pixmap))
359 if (!themeName.isEmpty()) {
361 pixmap = findIconHelper(size, themeName, name, visited);
363 QPixmapCache::insert(pixmapName, pixmap);