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.
22
#include <KConfigGroup>
23
#include <QStringList>
26
#include "thememodel.h"
27
#include "thememodel.moc"
28
#include "xcursortheme.h"
29
#include "legacytheme.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
42
CursorThemeModel::CursorThemeModel(QObject *parent)
43
: QAbstractTableModel(parent)
48
CursorThemeModel::~CursorThemeModel()
54
void CursorThemeModel::refreshList()
63
QVariant CursorThemeModel::headerData(int section, Qt::Orientation orientation, int role) const
65
// Only provide text for the headers
66
if (role != Qt::DisplayRole)
69
// Horizontal header labels
70
if (orientation == Qt::Horizontal)
78
return i18n("Description");
80
default: return QVariant();
84
// Numbered vertical header lables
85
return QString(section);
89
QVariant CursorThemeModel::data(const QModelIndex &index, int role) const
91
if (!index.isValid() || index.row() < 0 || index.row() >= list.count())
94
const CursorTheme *theme = list.at(index.row());
97
if (role == Qt::DisplayRole)
99
switch (index.column())
102
return theme->title();
105
return theme->description();
107
default: return QVariant();
111
// Description for the first name column
112
if (role == CursorTheme::DisplayDetailRole && index.column() == NameColumn)
113
return theme->description();
115
// Icon for the name column
116
if (role == Qt::DecorationRole && index.column() == NameColumn)
117
return theme->icon();
123
void CursorThemeModel::sort(int column, Qt::SortOrder order)
128
// Sorting of the model isn't implemented, as the KCM currently uses
129
// a sorting proxy model.
133
const CursorTheme *CursorThemeModel::theme(const QModelIndex &index)
135
if (!index.isValid())
138
if (index.row() < 0 || index.row() >= list.count())
141
return list.at(index.row());
145
QModelIndex CursorThemeModel::findIndex(const QString &name)
147
uint hash = qHash(name);
149
for (int i = 0; i < list.count(); i++)
151
const CursorTheme *theme = list.at(i);
152
if (theme->hash() == hash)
156
return QModelIndex();
160
QModelIndex CursorThemeModel::defaultIndex()
162
return findIndex(defaultName);
166
const QStringList CursorThemeModel::searchPaths()
168
if (!baseDirs.isEmpty())
171
#if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1
172
// These are the default paths Xcursor will scan for cursor themes
173
QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons");
175
// If XCURSOR_PATH is set, use that instead of the default path
176
char *xcursorPath = std::getenv("XCURSOR_PATH");
180
// Get the search path from Xcursor
181
QString path = XcursorLibraryPath();
184
// Separate the paths
185
baseDirs = path.split(':', QString::SkipEmptyParts);
188
QMutableStringListIterator i(baseDirs);
191
const QString path = i.next();
192
QMutableStringListIterator j(i);
194
if (j.next() == path)
198
// Expand all occurrences of ~/ to the home dir
199
baseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/');
204
bool CursorThemeModel::hasTheme(const QString &name) const
206
const uint hash = qHash(name);
208
foreach (const CursorTheme *theme, list)
209
if (theme->hash() == hash)
216
bool CursorThemeModel::isCursorTheme(const QString &theme, const int depth)
218
// Prevent infinite recursion
222
// Search each icon theme directory for 'theme'
223
foreach (const QString &baseDir, searchPaths())
226
if (!dir.exists() || !dir.cd(theme))
229
// If there's a cursors subdir, we'll assume this is a cursor theme
230
if (dir.exists("cursors"))
233
// If the theme doesn't have an index.theme file, it can't inherit any themes.
234
if (!dir.exists("index.theme"))
237
// Open the index.theme file, so we can get the list of inherited themes
238
KConfig config(dir.path() + "/index.theme", KConfig::NoGlobals);
239
KConfigGroup cg(&config, "Icon Theme");
241
// Recurse through the list of inherited themes, to check if one of them
242
// is a cursor theme.
243
QStringList inherits = cg.readEntry("Inherits", QStringList());
244
foreach (const QString &inherit, inherits)
246
// Avoid possible DoS
247
if (inherit == theme)
250
if (isCursorTheme(inherit, depth + 1))
259
bool CursorThemeModel::handleDefault(const QDir &themeDir)
261
QFileInfo info(themeDir.path());
263
// If "default" is a symlink
264
if (info.isSymLink())
266
QFileInfo target(info.symLinkTarget());
267
if (target.exists() && (target.isDir() || target.isSymLink()))
268
defaultName = target.fileName();
273
// If there's no cursors subdir, or if it's empty
274
if (!themeDir.exists("cursors") || QDir(themeDir.path() + "/cursors")
275
.entryList(QDir::Files | QDir::NoDotAndDotDot ).isEmpty())
277
if (themeDir.exists("index.theme"))
279
XCursorTheme theme(themeDir);
280
if (!theme.inherits().isEmpty())
281
defaultName = theme.inherits().at(0);
286
defaultName = QLatin1String("default");
291
void CursorThemeModel::processThemeDir(const QDir &themeDir)
293
bool haveCursors = themeDir.exists("cursors");
295
// Special case handling of "default", since it's usually either a
296
// symlink to another theme, or an empty theme that inherits another
298
if (defaultName.isNull() && themeDir.dirName() == "default")
300
if (handleDefault(themeDir))
304
// If the directory doesn't have a cursors subdir and lacks an
305
// index.theme file it can't be a cursor theme.
306
if (!themeDir.exists("index.theme") && !haveCursors)
309
// Create a cursor theme object for the theme dir
310
XCursorTheme *theme = new XCursorTheme(themeDir);
312
// Skip this theme if it's hidden.
313
if (theme->isHidden()) {
318
// If there's no cursors subdirectory we'll do a recursive scan
319
// to check if the theme inherits a theme with one.
322
bool foundCursorTheme = false;
324
foreach (const QString &name, theme->inherits())
325
if ((foundCursorTheme = isCursorTheme(name)))
328
if (!foundCursorTheme) {
334
// Append the theme to the list
335
beginInsertRows(QModelIndex(), list.size(), list.size());
341
void CursorThemeModel::insertThemes()
343
// Scan each base dir for Xcursor themes and add them to the list.
344
foreach (const QString &baseDir, searchPaths())
350
// Process each subdir in the directory
351
foreach (const QString &name, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
353
// Don't process the theme if a theme with the same name already exists
354
// in the list. Xcursor will pick the first one it finds in that case,
355
// and since we use the same search order, the one Xcursor picks should
356
// be the one already in the list.
357
if (hasTheme(name) || !dir.cd(name))
360
processThemeDir(dir);
361
dir.cdUp(); // Return to the base dir
365
// Insert 'special' themes here
366
CursorTheme *legacy = new LegacyTheme();
369
// The theme Xcursor will end up using if no theme is configured
370
if (defaultName.isNull() || !hasTheme(defaultName))
371
defaultName = legacy->name();
375
bool CursorThemeModel::addTheme(const QDir &dir)
377
XCursorTheme *theme = new XCursorTheme(dir);
379
// Don't add the theme to the list if it's hidden
380
if (theme->isHidden()) {
385
// ### If the theme is hidden, the user will probably find it strange that it
386
// doesn't appear in the list view. There also won't be a way for the user
387
// to delete the theme using the KCM. Perhaps a warning about this should
388
// be issued, and the user be given a chance to undo the installation.
390
// If an item with the same name already exists in the list,
391
// we'll remove it before inserting the new one.
392
for (int i = 0; i < list.count(); i++)
394
if (list.at(i)->hash() == theme->hash()) {
395
removeTheme(index(i, 0));
400
// Append the theme to the list
401
beginInsertRows(QModelIndex(), rowCount(), rowCount());
409
void CursorThemeModel::removeTheme(const QModelIndex &index)
411
if (!index.isValid())
414
beginRemoveRows(QModelIndex(), index.row(), index.row());
415
delete list.takeAt(index.row());