~ubuntu-branches/ubuntu/gutsy/kdebase-workspace/gutsy-backports

« back to all changes in this revision

Viewing changes to kcontrol/input/xcursor/thememodel.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jonathan Riddell
  • Date: 2007-09-05 20:45:14 UTC
  • Revision ID: james.westby@ubuntu.com-20070905204514-632hhspl0nvrc84i
Tags: upstream-3.93.0
ImportĀ upstreamĀ versionĀ 3.93.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright Ā© 2005-2007 Fredrik Hƶglund <fredrik@kde.org>
 
3
 *
 
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.
 
7
 *
 
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.
 
12
 *
 
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.
 
17
 */
 
18
 
 
19
 
 
20
#include <KLocale>
 
21
#include <KConfig>
 
22
 
 
23
#include <QPixmap>
 
24
#include <QList>
 
25
#include <QStringList>
 
26
#include <QDir>
 
27
#include <QSettings>
 
28
#include <QX11Info>
 
29
 
 
30
#include "thememodel.h"
 
31
#include "thememodel.moc"
 
32
#include "xcursortheme.h"
 
33
#include "legacytheme.h"
 
34
 
 
35
#include <X11/Xlib.h>
 
36
#include <X11/Xcursor/Xcursor.h>
 
37
 
 
38
// Check for older version
 
39
#if !defined(XCURSOR_LIB_MAJOR) && defined(XCURSOR_MAJOR)
 
40
#  define XCURSOR_LIB_MAJOR     XCURSOR_MAJOR
 
41
#  define XCURSOR_LIB_MINOR     XCURSOR_MINOR
 
42
#endif
 
43
 
 
44
 
 
45
 
 
46
CursorThemeModel::CursorThemeModel(QObject *parent)
 
47
    : QAbstractTableModel(parent)
 
48
{
 
49
    insertThemes();
 
50
}
 
51
 
 
52
 
 
53
QVariant CursorThemeModel::headerData(int section, Qt::Orientation orientation, int role) const
 
54
{
 
55
    // Only provide text for the headers
 
56
    if (role != Qt::DisplayRole)
 
57
        return QVariant();
 
58
 
 
59
    // Horizontal header labels
 
60
    if (orientation == Qt::Horizontal)
 
61
    {
 
62
        switch (section)
 
63
        {
 
64
            case NameColumn:
 
65
                return i18n("Name");
 
66
 
 
67
            case DescColumn:
 
68
                return i18n("Description");
 
69
 
 
70
            default: return QVariant();
 
71
        }
 
72
    }
 
73
 
 
74
    // Numbered vertical header lables
 
75
    return QString(section);
 
76
}
 
77
 
 
78
 
 
79
QVariant CursorThemeModel::data(const QModelIndex &index, int role) const
 
80
{
 
81
    if (!index.isValid() || index.row() < 0 || index.row() >= list.count())
 
82
        return QVariant();
 
83
 
 
84
    const CursorTheme *theme = list.at(index.row());
 
85
 
 
86
    // Text label
 
87
    if (role == Qt::DisplayRole)
 
88
    {
 
89
        switch (index.column())
 
90
        {
 
91
            case NameColumn:
 
92
                return theme->title();
 
93
 
 
94
            case DescColumn:
 
95
                return theme->description();
 
96
 
 
97
            default: return QVariant();
 
98
        }
 
99
    }
 
100
 
 
101
    // Description for the first name column
 
102
    if (role == CursorTheme::DisplayDetailRole && index.column() == NameColumn)
 
103
        return theme->description();
 
104
 
 
105
    // Icon for the name column
 
106
    if (role == Qt::DecorationRole && index.column() == NameColumn)
 
107
        return theme->icon();
 
108
 
 
109
    return QVariant();
 
110
}
 
111
 
 
112
 
 
113
void CursorThemeModel::sort(int column, Qt::SortOrder order)
 
114
{
 
115
    Q_UNUSED(column);
 
116
    Q_UNUSED(order);
 
117
 
 
118
    // Sorting of the model isn't implemented, as the KCM currently uses
 
119
    // a sorting proxy model.
 
120
}
 
121
 
 
122
 
 
123
const CursorTheme *CursorThemeModel::theme(const QModelIndex &index)
 
124
{
 
125
    if (!index.isValid())
 
126
        return NULL;
 
127
 
 
128
    if (index.row() < 0 || index.row() >= list.count())
 
129
        return NULL;
 
130
 
 
131
    return list.at(index.row());
 
132
}
 
133
 
 
134
 
 
135
QModelIndex CursorThemeModel::findIndex(const QString &name)
 
136
{
 
137
    uint hash = qHash(name);
 
138
 
 
139
    for (int i = 0; i < list.count(); i++)
 
140
    {
 
141
        const CursorTheme *theme = list.at(i);
 
142
        if (theme->hash() == hash)
 
143
            return index(i, 0);
 
144
    }
 
145
 
 
146
    return QModelIndex();
 
147
}
 
148
 
 
149
 
 
150
QModelIndex CursorThemeModel::defaultIndex()
 
151
{
 
152
    return findIndex(defaultName);
 
153
}
 
154
 
 
155
 
 
156
const QStringList CursorThemeModel::searchPaths()
 
157
{
 
158
    if (!baseDirs.isEmpty())
 
159
        return baseDirs;
 
160
 
 
161
#if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1
 
162
    // These are the default paths Xcursor will scan for cursor themes
 
163
    QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons");
 
164
 
 
165
    // If XCURSOR_PATH is set, use that instead of the default path
 
166
    char *xcursorPath = std::getenv("XCURSOR_PATH");
 
167
    if (xcursorPath)
 
168
        path = xcursorPath;
 
169
#else
 
170
    // Get the search path from Xcursor
 
171
    QString path = XcursorLibraryPath();
 
172
#endif
 
173
 
 
174
    // Separate the paths
 
175
    baseDirs = path.split(':', QString::SkipEmptyParts);
 
176
 
 
177
    // Remove duplicates
 
178
    QMutableStringListIterator i(baseDirs);
 
179
    while (i.hasNext())
 
180
    {
 
181
        const QString path = i.next();
 
182
        QMutableStringListIterator j(i);
 
183
        while (j.hasNext())
 
184
            if (j.next() == path)
 
185
                j.remove();
 
186
    }
 
187
 
 
188
    // Expand all occurrences of ~/ to the home dir
 
189
    baseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/');
 
190
    return baseDirs;
 
191
}
 
192
 
 
193
 
 
194
bool CursorThemeModel::hasTheme(const QString &name) const
 
195
{
 
196
    const uint hash = qHash(name);
 
197
 
 
198
    foreach (const CursorTheme *theme, list)
 
199
        if (theme->hash() == hash)
 
200
            return true;
 
201
 
 
202
    return false;
 
203
}
 
204
 
 
205
 
 
206
bool CursorThemeModel::isCursorTheme(const QString &theme, const int depth)
 
207
{
 
208
    // Prevent infinite recursion
 
209
    if (depth > 10)
 
210
        return false;
 
211
 
 
212
    // Search each icon theme directory for 'theme'
 
213
    foreach (const QString &baseDir, searchPaths())
 
214
    {
 
215
        QDir dir(baseDir);
 
216
        if (!dir.exists() || !dir.cd(theme))
 
217
            continue;
 
218
 
 
219
        // If there's a cursors subdir, we'll assume this is a cursor theme
 
220
        if (dir.exists("cursors"))
 
221
            return true;
 
222
 
 
223
        // If the theme doesn't have an index.theme file, it can't inherit any themes.
 
224
        if (!dir.exists("index.theme"))
 
225
            continue;
 
226
 
 
227
        // Open the index.theme file, so we can get the list of inherited themes
 
228
        KConfig config(dir.path() + "/index.theme", KConfig::NoGlobals);
 
229
        KConfigGroup cg(&config, "Icon Theme");
 
230
 
 
231
        // Recurse through the list of inherited themes, to check if one of them
 
232
        // is a cursor theme.
 
233
        QStringList inherits = cg.readEntry("Inherits", QStringList());
 
234
        foreach (const QString &inherit, inherits)
 
235
        {
 
236
            // Avoid possible DoS
 
237
            if (inherit == theme)
 
238
                continue;
 
239
 
 
240
            if (isCursorTheme(inherit, depth + 1))
 
241
                return true;
 
242
        }
 
243
    }
 
244
 
 
245
    return false;
 
246
}
 
247
 
 
248
 
 
249
bool CursorThemeModel::handleDefault(const QDir &themeDir)
 
250
{
 
251
    QFileInfo info(themeDir.path());
 
252
 
 
253
    // If "default" is a symlink
 
254
    if (info.isSymLink())
 
255
    {
 
256
        QFileInfo target(info.symLinkTarget());
 
257
        if (target.exists() && (target.isDir() || target.isSymLink()))
 
258
            defaultName = target.fileName();
 
259
 
 
260
        return true;
 
261
    }
 
262
 
 
263
    // If there's no cursors subdir, or if it's empty
 
264
    if (!themeDir.exists("cursors") || QDir(themeDir.path() + "/cursors")
 
265
          .entryList(QDir::Files | QDir::NoDotAndDotDot ).isEmpty())
 
266
    {
 
267
        if (themeDir.exists("index.theme"))
 
268
        {
 
269
            XCursorTheme theme(themeDir);
 
270
            if (!theme.inherits().isEmpty())
 
271
                defaultName = theme.inherits().at(0);
 
272
        }
 
273
        return true;
 
274
    }
 
275
 
 
276
    defaultName = QLatin1String("default");
 
277
    return false;
 
278
}
 
279
 
 
280
 
 
281
void CursorThemeModel::processThemeDir(const QDir &themeDir)
 
282
{
 
283
    bool haveCursors = themeDir.exists("cursors");
 
284
 
 
285
    // Special case handling of "default", since it's usually either a
 
286
    // symlink to another theme, or an empty theme that inherits another
 
287
    // theme.
 
288
    if (defaultName.isNull() && themeDir.dirName() == "default")
 
289
    {
 
290
        if (handleDefault(themeDir))
 
291
            return;
 
292
    }
 
293
 
 
294
    // If the directory doesn't have a cursors subdir and lacks an
 
295
    // index.theme file it can't be a cursor theme.
 
296
    if (!themeDir.exists("index.theme") && !haveCursors)
 
297
        return;
 
298
 
 
299
    // Create a cursor theme object for the theme dir
 
300
    XCursorTheme *theme = new XCursorTheme(themeDir);
 
301
 
 
302
    // Skip this theme if it's hidden.
 
303
    if (theme->isHidden()) {
 
304
        delete theme;
 
305
        return;
 
306
    }
 
307
 
 
308
    // If there's no cursors subdirectory we'll do a recursive scan
 
309
    // to check if the theme inherits a theme with one.
 
310
    if (!haveCursors)
 
311
    {
 
312
        bool foundCursorTheme = false;
 
313
 
 
314
        foreach (const QString &name, theme->inherits())
 
315
            if (foundCursorTheme = isCursorTheme(name))
 
316
                break;
 
317
 
 
318
        if (!foundCursorTheme) {
 
319
            delete theme;
 
320
            return;
 
321
        }
 
322
    }
 
323
 
 
324
    // Append the theme to the list
 
325
    list.append(theme);
 
326
}
 
327
 
 
328
 
 
329
void CursorThemeModel::insertThemes()
 
330
{
 
331
    // Scan each base dir for Xcursor themes and add them to the list.
 
332
    foreach (const QString &baseDir, searchPaths())
 
333
    {
 
334
        QDir dir(baseDir);
 
335
        if (!dir.exists())
 
336
            continue;
 
337
 
 
338
        // Process each subdir in the directory
 
339
        foreach (const QString &name, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
 
340
        {
 
341
            // Don't process the theme if a theme with the same name already exists
 
342
            // in the list. Xcursor will pick the first one it finds in that case,
 
343
            // and since we use the same search order, the one Xcursor picks should
 
344
            // be the one already in the list.
 
345
            if (hasTheme(name) || !dir.cd(name))
 
346
                continue;
 
347
 
 
348
            processThemeDir(dir);
 
349
            dir.cdUp(); // Return to the base dir
 
350
        }
 
351
    }
 
352
 
 
353
    // Insert 'special' themes here
 
354
    CursorTheme *legacy = new LegacyTheme();
 
355
    list.append(legacy);
 
356
 
 
357
    // The theme Xcursor will end up using if no theme is configured
 
358
    if (defaultName.isNull() || !hasTheme(defaultName))
 
359
        defaultName = legacy->name();
 
360
}
 
361
 
 
362
 
 
363
bool CursorThemeModel::addTheme(const QDir &dir)
 
364
{
 
365
    XCursorTheme *theme = new XCursorTheme(dir);
 
366
 
 
367
    // Don't add the theme to the list if it's hidden
 
368
    if (theme->isHidden()) {
 
369
        delete theme;
 
370
        return false; 
 
371
    }
 
372
 
 
373
    // ### If the theme is hidden, the user will probably find it strange that it
 
374
    //     doesn't appear in the list view. There also won't be a way for the user
 
375
    //     to delete the theme using the KCM. Perhaps a warning about this should
 
376
    //     be issued, and the user be given a chance to undo the installation.
 
377
 
 
378
    // If an item with the same name already exists in the list,
 
379
    // we'll remove it before inserting the new one.
 
380
    for (int i = 0; i < list.count(); i++)
 
381
    {
 
382
        if (list.at(i)->hash() == theme->hash()) {
 
383
            removeTheme(index(i, 0));
 
384
            break;
 
385
        }
 
386
    }
 
387
 
 
388
    // Append the theme to the list
 
389
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
 
390
    list.append(theme);
 
391
    endInsertRows();
 
392
 
 
393
    return true;
 
394
}
 
395
 
 
396
 
 
397
void CursorThemeModel::removeTheme(const QModelIndex &index)
 
398
{
 
399
    if (!index.isValid())
 
400
        return;
 
401
 
 
402
    beginRemoveRows(QModelIndex(), index.row(), index.row());
 
403
    delete list.takeAt(index.row());
 
404
    endRemoveRows();
 
405
}
 
406