not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / kcontrol / input / xcursor / thememodel.cpp
blob89ce02b6ef00d4312f406b9a1afba46ac6292005
1 /*
2 * Copyright © 2005-2007 Fredrik Höglund <fredrik@kde.org>
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License version 2 as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; see the file COPYING. If not, write to
15 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
20 #include <KLocale>
21 #include <KConfig>
22 #include <KConfigGroup>
23 #include <QStringList>
24 #include <QDir>
26 #include "thememodel.h"
27 #include "thememodel.moc"
28 #include "xcursortheme.h"
29 #include "legacytheme.h"
31 #include <X11/Xlib.h>
32 #include <X11/Xcursor/Xcursor.h>
34 // Check for older version
35 #if !defined(XCURSOR_LIB_MAJOR) && defined(XCURSOR_MAJOR)
36 # define XCURSOR_LIB_MAJOR XCURSOR_MAJOR
37 # define XCURSOR_LIB_MINOR XCURSOR_MINOR
38 #endif
42 CursorThemeModel::CursorThemeModel(QObject *parent)
43 : QAbstractTableModel(parent)
45 insertThemes();
48 CursorThemeModel::~CursorThemeModel()
50 qDeleteAll(list);
51 list.clear();
55 QVariant CursorThemeModel::headerData(int section, Qt::Orientation orientation, int role) const
57 // Only provide text for the headers
58 if (role != Qt::DisplayRole)
59 return QVariant();
61 // Horizontal header labels
62 if (orientation == Qt::Horizontal)
64 switch (section)
66 case NameColumn:
67 return i18n("Name");
69 case DescColumn:
70 return i18n("Description");
72 default: return QVariant();
76 // Numbered vertical header lables
77 return QString(section);
81 QVariant CursorThemeModel::data(const QModelIndex &index, int role) const
83 if (!index.isValid() || index.row() < 0 || index.row() >= list.count())
84 return QVariant();
86 const CursorTheme *theme = list.at(index.row());
88 // Text label
89 if (role == Qt::DisplayRole)
91 switch (index.column())
93 case NameColumn:
94 return theme->title();
96 case DescColumn:
97 return theme->description();
99 default: return QVariant();
103 // Description for the first name column
104 if (role == CursorTheme::DisplayDetailRole && index.column() == NameColumn)
105 return theme->description();
107 // Icon for the name column
108 if (role == Qt::DecorationRole && index.column() == NameColumn)
109 return theme->icon();
111 return QVariant();
115 void CursorThemeModel::sort(int column, Qt::SortOrder order)
117 Q_UNUSED(column);
118 Q_UNUSED(order);
120 // Sorting of the model isn't implemented, as the KCM currently uses
121 // a sorting proxy model.
125 const CursorTheme *CursorThemeModel::theme(const QModelIndex &index)
127 if (!index.isValid())
128 return NULL;
130 if (index.row() < 0 || index.row() >= list.count())
131 return NULL;
133 return list.at(index.row());
137 QModelIndex CursorThemeModel::findIndex(const QString &name)
139 uint hash = qHash(name);
141 for (int i = 0; i < list.count(); i++)
143 const CursorTheme *theme = list.at(i);
144 if (theme->hash() == hash)
145 return index(i, 0);
148 return QModelIndex();
152 QModelIndex CursorThemeModel::defaultIndex()
154 return findIndex(defaultName);
158 const QStringList CursorThemeModel::searchPaths()
160 if (!baseDirs.isEmpty())
161 return baseDirs;
163 #if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1
164 // These are the default paths Xcursor will scan for cursor themes
165 QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons");
167 // If XCURSOR_PATH is set, use that instead of the default path
168 char *xcursorPath = std::getenv("XCURSOR_PATH");
169 if (xcursorPath)
170 path = xcursorPath;
171 #else
172 // Get the search path from Xcursor
173 QString path = XcursorLibraryPath();
174 #endif
176 // Separate the paths
177 baseDirs = path.split(':', QString::SkipEmptyParts);
179 // Remove duplicates
180 QMutableStringListIterator i(baseDirs);
181 while (i.hasNext())
183 const QString path = i.next();
184 QMutableStringListIterator j(i);
185 while (j.hasNext())
186 if (j.next() == path)
187 j.remove();
190 // Expand all occurrences of ~/ to the home dir
191 baseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/');
192 return baseDirs;
196 bool CursorThemeModel::hasTheme(const QString &name) const
198 const uint hash = qHash(name);
200 foreach (const CursorTheme *theme, list)
201 if (theme->hash() == hash)
202 return true;
204 return false;
208 bool CursorThemeModel::isCursorTheme(const QString &theme, const int depth)
210 // Prevent infinite recursion
211 if (depth > 10)
212 return false;
214 // Search each icon theme directory for 'theme'
215 foreach (const QString &baseDir, searchPaths())
217 QDir dir(baseDir);
218 if (!dir.exists() || !dir.cd(theme))
219 continue;
221 // If there's a cursors subdir, we'll assume this is a cursor theme
222 if (dir.exists("cursors"))
223 return true;
225 // If the theme doesn't have an index.theme file, it can't inherit any themes.
226 if (!dir.exists("index.theme"))
227 continue;
229 // Open the index.theme file, so we can get the list of inherited themes
230 KConfig config(dir.path() + "/index.theme", KConfig::NoGlobals);
231 KConfigGroup cg(&config, "Icon Theme");
233 // Recurse through the list of inherited themes, to check if one of them
234 // is a cursor theme.
235 QStringList inherits = cg.readEntry("Inherits", QStringList());
236 foreach (const QString &inherit, inherits)
238 // Avoid possible DoS
239 if (inherit == theme)
240 continue;
242 if (isCursorTheme(inherit, depth + 1))
243 return true;
247 return false;
251 bool CursorThemeModel::handleDefault(const QDir &themeDir)
253 QFileInfo info(themeDir.path());
255 // If "default" is a symlink
256 if (info.isSymLink())
258 QFileInfo target(info.symLinkTarget());
259 if (target.exists() && (target.isDir() || target.isSymLink()))
260 defaultName = target.fileName();
262 return true;
265 // If there's no cursors subdir, or if it's empty
266 if (!themeDir.exists("cursors") || QDir(themeDir.path() + "/cursors")
267 .entryList(QDir::Files | QDir::NoDotAndDotDot ).isEmpty())
269 if (themeDir.exists("index.theme"))
271 XCursorTheme theme(themeDir);
272 if (!theme.inherits().isEmpty())
273 defaultName = theme.inherits().at(0);
275 return true;
278 defaultName = QLatin1String("default");
279 return false;
283 void CursorThemeModel::processThemeDir(const QDir &themeDir)
285 bool haveCursors = themeDir.exists("cursors");
287 // Special case handling of "default", since it's usually either a
288 // symlink to another theme, or an empty theme that inherits another
289 // theme.
290 if (defaultName.isNull() && themeDir.dirName() == "default")
292 if (handleDefault(themeDir))
293 return;
296 // If the directory doesn't have a cursors subdir and lacks an
297 // index.theme file it can't be a cursor theme.
298 if (!themeDir.exists("index.theme") && !haveCursors)
299 return;
301 // Create a cursor theme object for the theme dir
302 XCursorTheme *theme = new XCursorTheme(themeDir);
304 // Skip this theme if it's hidden.
305 if (theme->isHidden()) {
306 delete theme;
307 return;
310 // If there's no cursors subdirectory we'll do a recursive scan
311 // to check if the theme inherits a theme with one.
312 if (!haveCursors)
314 bool foundCursorTheme = false;
316 foreach (const QString &name, theme->inherits())
317 if (foundCursorTheme = isCursorTheme(name))
318 break;
320 if (!foundCursorTheme) {
321 delete theme;
322 return;
326 // Append the theme to the list
327 list.append(theme);
331 void CursorThemeModel::insertThemes()
333 // Scan each base dir for Xcursor themes and add them to the list.
334 foreach (const QString &baseDir, searchPaths())
336 QDir dir(baseDir);
337 if (!dir.exists())
338 continue;
340 // Process each subdir in the directory
341 foreach (const QString &name, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
343 // Don't process the theme if a theme with the same name already exists
344 // in the list. Xcursor will pick the first one it finds in that case,
345 // and since we use the same search order, the one Xcursor picks should
346 // be the one already in the list.
347 if (hasTheme(name) || !dir.cd(name))
348 continue;
350 processThemeDir(dir);
351 dir.cdUp(); // Return to the base dir
355 // Insert 'special' themes here
356 CursorTheme *legacy = new LegacyTheme();
357 list.append(legacy);
359 // The theme Xcursor will end up using if no theme is configured
360 if (defaultName.isNull() || !hasTheme(defaultName))
361 defaultName = legacy->name();
365 bool CursorThemeModel::addTheme(const QDir &dir)
367 XCursorTheme *theme = new XCursorTheme(dir);
369 // Don't add the theme to the list if it's hidden
370 if (theme->isHidden()) {
371 delete theme;
372 return false;
375 // ### If the theme is hidden, the user will probably find it strange that it
376 // doesn't appear in the list view. There also won't be a way for the user
377 // to delete the theme using the KCM. Perhaps a warning about this should
378 // be issued, and the user be given a chance to undo the installation.
380 // If an item with the same name already exists in the list,
381 // we'll remove it before inserting the new one.
382 for (int i = 0; i < list.count(); i++)
384 if (list.at(i)->hash() == theme->hash()) {
385 removeTheme(index(i, 0));
386 break;
390 // Append the theme to the list
391 beginInsertRows(QModelIndex(), rowCount(), rowCount());
392 list.append(theme);
393 endInsertRows();
395 return true;
399 void CursorThemeModel::removeTheme(const QModelIndex &index)
401 if (!index.isValid())
402 return;
404 beginRemoveRows(QModelIndex(), index.row(), index.row());
405 delete list.takeAt(index.row());
406 endRemoveRows();