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

« back to all changes in this revision

Viewing changes to kcontrol/input/xcursor/themepage.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 © 2003-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
#include <config-X11.h>
 
20
 
 
21
#include <KConfig>
 
22
#include <KLocale>
 
23
#include <KStandardDirs>
 
24
 
 
25
#include <KGlobalSettings>
 
26
#include <KToolInvocation>
 
27
#include <KMessageBox>
 
28
#include <KUrlRequesterDialog>
 
29
#include <KIO/Job>
 
30
#include <KIO/DeleteJob>
 
31
#include <KIO/NetAccess>
 
32
#include <knewstuff3/downloaddialog.h>
 
33
 
 
34
 
 
35
#include <KTar>
 
36
 
 
37
#include <klauncher_iface.h>
 
38
#include "../../krdb/krdb.h"
 
39
 
 
40
#include <QWidget>
 
41
#include <QPushButton>
 
42
#include <QDir>
 
43
#include <QX11Info>
 
44
 
 
45
#include "themepage.h"
 
46
#include "themepage.moc"
 
47
 
 
48
#include "thememodel.h"
 
49
#include "itemdelegate.h"
 
50
#include "sortproxymodel.h"
 
51
#include "cursortheme.h"
 
52
 
 
53
#include <X11/Xlib.h>
 
54
#include <X11/Xcursor/Xcursor.h>
 
55
 
 
56
#ifdef HAVE_XFIXES
 
57
#  include <X11/extensions/Xfixes.h>
 
58
#endif
 
59
 
 
60
 
 
61
ThemePage::ThemePage(QWidget *parent)
 
62
    : QWidget(parent)
 
63
{
 
64
    setupUi(this);
 
65
    installKnsButton->setIcon( KIcon("get-hot-new-stuff") );
 
66
 
 
67
    model = new CursorThemeModel(this);
 
68
 
 
69
    proxy = new SortProxyModel(this);
 
70
    proxy->setSourceModel(model);
 
71
    proxy->setFilterCaseSensitivity(Qt::CaseSensitive);
 
72
    proxy->sort(NameColumn, Qt::AscendingOrder);
 
73
 
 
74
    int size = style()->pixelMetric(QStyle::PM_LargeIconSize);
 
75
 
 
76
    view->setModel(proxy);
 
77
    view->setItemDelegate(new ItemDelegate(this));
 
78
    view->setIconSize(QSize(size, size));
 
79
    view->setSelectionMode( QAbstractItemView::SingleSelection );
 
80
 
 
81
    // Make sure we find out about selection changes
 
82
    connect(view->selectionModel(),
 
83
            SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
 
84
            SLOT(currentChanged(const QModelIndex &, const QModelIndex &)));
 
85
 
 
86
    // Disable the install button if we can't install new themes to ~/.icons,
 
87
    // or Xcursor isn't set up to look for cursor themes there.
 
88
    if (!model->searchPaths().contains(QDir::homePath() + "/.icons") || !iconsIsWritable()) {
 
89
            installButton->setEnabled(false);
 
90
            installKnsButton->setEnabled(false);
 
91
    }
 
92
 
 
93
    connect(installKnsButton, SIGNAL(clicked()), SLOT(getNewClicked()));
 
94
    connect(installButton, SIGNAL(clicked()), SLOT(installClicked()));
 
95
    connect(removeButton,  SIGNAL(clicked()), SLOT(removeClicked()));
 
96
}
 
97
 
 
98
 
 
99
ThemePage::~ThemePage()
 
100
{
 
101
}
 
102
 
 
103
 
 
104
bool ThemePage::iconsIsWritable() const
 
105
{
 
106
    const QFileInfo icons = QFileInfo(QDir::homePath() + "/.icons");
 
107
    const QFileInfo home  = QFileInfo(QDir::homePath());
 
108
 
 
109
    return ((icons.exists() && icons.isDir() && icons.isWritable()) ||
 
110
            (!icons.exists() && home.isWritable()));
 
111
}
 
112
 
 
113
 
 
114
bool ThemePage::haveXfixes()
 
115
{
 
116
    bool result = false;
 
117
 
 
118
#ifdef HAVE_XFIXES
 
119
    int event_base, error_base;
 
120
    if (XFixesQueryExtension(QX11Info::display(), &event_base, &error_base))
 
121
    {
 
122
        int major, minor;
 
123
        XFixesQueryVersion(QX11Info::display(), &major, &minor);
 
124
        result = (major >= 2);
 
125
    }
 
126
#endif
 
127
 
 
128
    return result;
 
129
}
 
130
 
 
131
 
 
132
bool ThemePage::applyTheme(const CursorTheme *theme)
 
133
{
 
134
    // Require the Xcursor version that shipped with X11R6.9 or greater, since
 
135
    // in previous versions the Xfixes code wasn't enabled due to a bug in the
 
136
    // build system (freedesktop bug #975).
 
137
#if HAVE_XFIXES && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105
 
138
    if (!haveXfixes())
 
139
        return false;
 
140
 
 
141
    QByteArray themeName = QFile::encodeName(theme->name());
 
142
 
 
143
    // Set up the proper launch environment for newly started apps
 
144
    KToolInvocation::klauncher()->setLaunchEnv("XCURSOR_THEME", themeName);
 
145
 
 
146
    // Update the Xcursor X resources
 
147
    runRdb(0);
 
148
 
 
149
    // Notify all applications that the cursor theme has changed
 
150
    KGlobalSettings::self()->emitChange(KGlobalSettings::CursorChanged);
 
151
 
 
152
    // Reload the standard cursors
 
153
    QStringList names;
 
154
 
 
155
    // Qt cursors
 
156
    names << "left_ptr"       << "up_arrow"      << "cross"      << "wait"
 
157
          << "left_ptr_watch" << "ibeam"         << "size_ver"   << "size_hor"
 
158
          << "size_bdiag"     << "size_fdiag"    << "size_all"   << "split_v"
 
159
          << "split_h"        << "pointing_hand" << "openhand"
 
160
          << "closedhand"     << "forbidden"     << "whats_this";
 
161
 
 
162
    // X core cursors
 
163
    names << "X_cursor"            << "right_ptr"           << "hand1"
 
164
          << "hand2"               << "watch"               << "xterm"
 
165
          << "crosshair"           << "left_ptr_watch"      << "center_ptr"
 
166
          << "sb_h_double_arrow"   << "sb_v_double_arrow"   << "fleur"
 
167
          << "top_left_corner"     << "top_side"            << "top_right_corner"
 
168
          << "right_side"          << "bottom_right_corner" << "bottom_side"
 
169
          << "bottom_left_corner"  << "left_side"           << "question_arrow"
 
170
          << "pirate";
 
171
 
 
172
    foreach (const QString &name, names)
 
173
    {
 
174
        QCursor cursor = theme->loadCursor(name);
 
175
        XFixesChangeCursorByName(x11Info().display(), cursor.handle(), QFile::encodeName(name));
 
176
    }
 
177
 
 
178
    return true;
 
179
#else
 
180
    Q_UNUSED(theme)
 
181
    return false;
 
182
#endif
 
183
}
 
184
 
 
185
 
 
186
void ThemePage::save()
 
187
{
 
188
    if (appliedIndex == view->currentIndex() || !view->currentIndex().isValid())
 
189
        return;
 
190
 
 
191
    const CursorTheme *theme = proxy->theme(view->currentIndex());
 
192
 
 
193
    KConfig config("kcminputrc");
 
194
    KConfigGroup c(&config, "Mouse");
 
195
    c.writeEntry("cursorTheme", theme->name());
 
196
    c.sync();
 
197
 
 
198
    if (!applyTheme(theme))
 
199
    {
 
200
        KMessageBox::information(this,
 
201
                                 i18n("You have to restart KDE for these changes to take effect."),
 
202
                                 i18n("Cursor Settings Changed"), "CursorSettingsChanged");
 
203
    }
 
204
 
 
205
    appliedIndex = view->currentIndex();
 
206
}
 
207
 
 
208
 
 
209
void ThemePage::load()
 
210
{
 
211
    view->selectionModel()->clear();
 
212
    // Get the name of the theme libXcursor currently uses
 
213
    QString currentTheme = XcursorGetTheme(x11Info().display());
 
214
 
 
215
    // Get the name of the theme KDE is configured to use
 
216
    KConfig c("kcminputrc");
 
217
    KConfigGroup cg(&c, "Mouse");
 
218
    currentTheme = cg.readEntry("cursorTheme", currentTheme);
 
219
 
 
220
    // Find the theme in the listview
 
221
    if (!currentTheme.isEmpty())
 
222
        appliedIndex = proxy->findIndex(currentTheme);
 
223
    else
 
224
        appliedIndex = proxy->defaultIndex();
 
225
 
 
226
    // Disable the listview and the buttons if we're in kiosk mode
 
227
    if (cg.isEntryImmutable("cursorTheme"))
 
228
    {
 
229
        view->setEnabled(false);
 
230
        installButton->setEnabled(false);
 
231
        removeButton->setEnabled(false);
 
232
    }
 
233
 
 
234
    const CursorTheme *theme = proxy->theme(appliedIndex);
 
235
 
 
236
    if (appliedIndex.isValid())
 
237
    {
 
238
        // Select the current theme
 
239
        selectRow(appliedIndex);
 
240
        view->scrollTo(appliedIndex, QListView::PositionAtCenter);
 
241
 
 
242
        // Update the preview widget as well
 
243
        preview->setTheme(theme);
 
244
    }
 
245
 
 
246
    if (!theme || !theme->isWritable())
 
247
        removeButton->setEnabled(false);
 
248
}
 
249
 
 
250
 
 
251
void ThemePage::defaults()
 
252
{
 
253
    view->selectionModel()->clear();
 
254
    QModelIndex defaultIndex = proxy->findIndex("Oxygen_Black");
 
255
    view->setCurrentIndex(defaultIndex);
 
256
}
 
257
 
 
258
 
 
259
void ThemePage::selectRow(int row) const
 
260
{
 
261
    // Create a selection that stretches across all columns
 
262
    QModelIndex from = proxy->index(row, 0);
 
263
    QModelIndex to   = proxy->index(row, model->columnCount() - 1);
 
264
    QItemSelection selection(from, to);
 
265
 
 
266
    view->selectionModel()->select(selection, QItemSelectionModel::Select);
 
267
}
 
268
 
 
269
 
 
270
void ThemePage::currentChanged(const QModelIndex &current, const QModelIndex &previous)
 
271
{
 
272
    Q_UNUSED(previous)
 
273
 
 
274
    if (current.isValid())
 
275
    {
 
276
        const CursorTheme *theme = proxy->theme(current);
 
277
        preview->setTheme(theme);
 
278
        removeButton->setEnabled(theme->isWritable());
 
279
    } else
 
280
        preview->setTheme(NULL);
 
281
 
 
282
    emit changed(appliedIndex != current);
 
283
}
 
284
 
 
285
void ThemePage::getNewClicked()
 
286
{
 
287
    KNS3::DownloadDialog dialog("xcursor.knsrc", this);
 
288
    if (dialog.exec()) {
 
289
        KNS3::Entry::List list = dialog.changedEntries();
 
290
        if (list.count() > 0)
 
291
            model->refreshList();
 
292
    }
 
293
}
 
294
 
 
295
void ThemePage::installClicked()
 
296
{
 
297
    // Get the URL for the theme we're going to install
 
298
    KUrl url = KUrlRequesterDialog::getUrl(QString(), this, i18n("Drag or Type Theme URL"));
 
299
 
 
300
    if (url.isEmpty())
 
301
        return;
 
302
 
 
303
    QString tempFile;
 
304
    if (!KIO::NetAccess::download(url, tempFile, this))
 
305
    {
 
306
        QString text;
 
307
 
 
308
        if (url.isLocalFile())
 
309
            text = i18n("Unable to find the cursor theme archive %1.",
 
310
                        url.prettyUrl());
 
311
        else
 
312
            text = i18n("Unable to download the cursor theme archive; "
 
313
                        "please check that the address %1 is correct.",
 
314
                        url.prettyUrl());
 
315
 
 
316
        KMessageBox::sorry(this, text);
 
317
        return;
 
318
    }
 
319
 
 
320
    if (!installThemes(tempFile))
 
321
        KMessageBox::error(this, i18n("The file %1 does not appear to be a valid "
 
322
                                      "cursor theme archive.", url.fileName()));
 
323
 
 
324
    KIO::NetAccess::removeTempFile(tempFile);
 
325
}
 
326
 
 
327
 
 
328
void ThemePage::removeClicked()
 
329
{
 
330
    // We don't have to check if the current index is valid, since
 
331
    // the remove button will be disabled when there's no selection.
 
332
    const CursorTheme *theme = proxy->theme(view->currentIndex());
 
333
 
 
334
    // Don't let the user delete the currently configured theme
 
335
    if (view->currentIndex() == appliedIndex) {
 
336
        KMessageBox::sorry(this, i18n("<qt>You cannot delete the theme you are currently "
 
337
                "using.<br />You have to switch to another theme first.</qt>"));
 
338
        return;
 
339
    }
 
340
 
 
341
    // Get confirmation from the user
 
342
    QString question = i18n("<qt>Are you sure you want to remove the "
 
343
            "<i>%1</i> cursor theme?<br />"
 
344
            "This will delete all the files installed by this theme.</qt>",
 
345
            theme->title());
 
346
 
 
347
    int answer = KMessageBox::warningContinueCancel(this, question,
 
348
            i18n("Confirmation"), KStandardGuiItem::del());
 
349
 
 
350
    if (answer != KMessageBox::Continue)
 
351
        return;
 
352
 
 
353
    // Delete the theme from the harddrive
 
354
    KIO::del(KUrl(theme->path())); // async
 
355
 
 
356
    // Remove the theme from the model
 
357
    proxy->removeTheme(view->currentIndex());
 
358
 
 
359
    // TODO:
 
360
    //  Since it's possible to substitute cursors in a system theme by adding a local
 
361
    //  theme with the same name, we shouldn't remove the theme from the list if it's
 
362
    //  still available elsewhere. We could add a
 
363
    //  bool CursorThemeModel::tryAddTheme(const QString &name), and call that, but
 
364
    //  since KIO::del() is an asynchronos operation, the theme we're deleting will be
 
365
    //  readded to the list again before KIO has removed it.
 
366
}
 
367
 
 
368
 
 
369
bool ThemePage::installThemes(const QString &file)
 
370
{
 
371
    KTar archive(file);
 
372
 
 
373
    if (!archive.open(QIODevice::ReadOnly))
 
374
        return false;
 
375
 
 
376
    const KArchiveDirectory *archiveDir = archive.directory();
 
377
    QStringList themeDirs;
 
378
 
 
379
    // Extract the dir names of the cursor themes in the archive, and
 
380
    // append them to themeDirs
 
381
    foreach(const QString &name, archiveDir->entries())
 
382
    {
 
383
        const KArchiveEntry *entry = archiveDir->entry(name);
 
384
        if (entry->isDirectory() && entry->name().toLower() != "default")
 
385
        {
 
386
            const KArchiveDirectory *dir = static_cast<const KArchiveDirectory *>(entry);
 
387
            if (dir->entry("index.theme") && dir->entry("cursors"))
 
388
                themeDirs << dir->name();
 
389
        }
 
390
    }
 
391
 
 
392
    if (themeDirs.isEmpty())
 
393
        return false;
 
394
 
 
395
    // The directory we'll install the themes to
 
396
    QString destDir = QDir::homePath() + "/.icons/";
 
397
    KStandardDirs::makeDir(destDir); // Make sure the directory exists
 
398
 
 
399
    // Process each cursor theme in the archive
 
400
    foreach (const QString &dirName, themeDirs)
 
401
    {
 
402
        QDir dest(destDir + dirName);
 
403
        if (dest.exists())
 
404
        {
 
405
            QString question = i18n("A theme named %1 already exists in your icon "
 
406
                    "theme folder. Do you want replace it with this one?", dirName);
 
407
 
 
408
            int answer = KMessageBox::warningContinueCancel(this, question,
 
409
                                i18n("Overwrite Theme?"),
 
410
                                KStandardGuiItem::overwrite());
 
411
 
 
412
            if (answer != KMessageBox::Continue)
 
413
                continue;
 
414
 
 
415
            // ### If the theme that's being replaced is the current theme, it
 
416
            //     will cause cursor inconsistencies in newly started apps.
 
417
        }
 
418
 
 
419
        // ### Should we check if a theme with the same name exists in a global theme dir?
 
420
        //     If that's the case it will effectively replace it, even though the global theme
 
421
        //     won't be deleted. Checking for this situation is easy, since the global theme
 
422
        //     will be in the listview. Maybe this should never be allowed since it might
 
423
        //     result in strange side effects (from the average users point of view). OTOH
 
424
        //     a user might want to do this 'upgrade' a global theme.
 
425
 
 
426
        const KArchiveDirectory *dir = static_cast<const KArchiveDirectory*>
 
427
                        (archiveDir->entry(dirName));
 
428
        dir->copyTo(dest.path());
 
429
        model->addTheme(dest);
 
430
    }
 
431
 
 
432
    archive.close();
 
433
    return true;
 
434
}
 
435