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()
55 QVariant
CursorThemeModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
57 // Only provide text for the headers
58 if (role
!= Qt::DisplayRole
)
61 // Horizontal header labels
62 if (orientation
== Qt::Horizontal
)
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())
86 const CursorTheme
*theme
= list
.at(index
.row());
89 if (role
== Qt::DisplayRole
)
91 switch (index
.column())
94 return theme
->title();
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();
115 void CursorThemeModel::sort(int column
, Qt::SortOrder 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())
130 if (index
.row() < 0 || index
.row() >= list
.count())
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
)
148 return QModelIndex();
152 QModelIndex
CursorThemeModel::defaultIndex()
154 return findIndex(defaultName
);
158 const QStringList
CursorThemeModel::searchPaths()
160 if (!baseDirs
.isEmpty())
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");
172 // Get the search path from Xcursor
173 QString path
= XcursorLibraryPath();
176 // Separate the paths
177 baseDirs
= path
.split(':', QString::SkipEmptyParts
);
180 QMutableStringListIterator
i(baseDirs
);
183 const QString path
= i
.next();
184 QMutableStringListIterator
j(i
);
186 if (j
.next() == path
)
190 // Expand all occurrences of ~/ to the home dir
191 baseDirs
.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/');
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
)
208 bool CursorThemeModel::isCursorTheme(const QString
&theme
, const int depth
)
210 // Prevent infinite recursion
214 // Search each icon theme directory for 'theme'
215 foreach (const QString
&baseDir
, searchPaths())
218 if (!dir
.exists() || !dir
.cd(theme
))
221 // If there's a cursors subdir, we'll assume this is a cursor theme
222 if (dir
.exists("cursors"))
225 // If the theme doesn't have an index.theme file, it can't inherit any themes.
226 if (!dir
.exists("index.theme"))
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
)
242 if (isCursorTheme(inherit
, depth
+ 1))
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();
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);
278 defaultName
= QLatin1String("default");
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
290 if (defaultName
.isNull() && themeDir
.dirName() == "default")
292 if (handleDefault(themeDir
))
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
)
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()) {
310 // If there's no cursors subdirectory we'll do a recursive scan
311 // to check if the theme inherits a theme with one.
314 bool foundCursorTheme
= false;
316 foreach (const QString
&name
, theme
->inherits())
317 if (foundCursorTheme
= isCursorTheme(name
))
320 if (!foundCursorTheme
) {
326 // Append the theme to the list
331 void CursorThemeModel::insertThemes()
333 // Scan each base dir for Xcursor themes and add them to the list.
334 foreach (const QString
&baseDir
, searchPaths())
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
))
350 processThemeDir(dir
);
351 dir
.cdUp(); // Return to the base dir
355 // Insert 'special' themes here
356 CursorTheme
*legacy
= new LegacyTheme();
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()) {
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));
390 // Append the theme to the list
391 beginInsertRows(QModelIndex(), rowCount(), rowCount());
399 void CursorThemeModel::removeTheme(const QModelIndex
&index
)
401 if (!index
.isValid())
404 beginRemoveRows(QModelIndex(), index
.row(), index
.row());
405 delete list
.takeAt(index
.row());