~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

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