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

« back to all changes in this revision

Viewing changes to plasma/desktop/applets/kickoff/core/applicationmodel.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 2007 Pino Toscano <pino@kde.org>
 
3
    Copyright 2007 Robert Knight <robertknight@gmail.com>
 
4
 
 
5
    This library is free software; you can redistribute it and/or
 
6
    modify it under the terms of the GNU Library General Public
 
7
    License as published by the Free Software Foundation; either
 
8
    version 2 of the License, or (at your option) any later version.
 
9
 
 
10
    This library is distributed in the hope that it will be useful,
 
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
    Library General Public License for more details.
 
14
 
 
15
    You should have received a copy of the GNU Library General Public License
 
16
    along with this library; see the file COPYING.LIB.  If not, write to
 
17
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
18
    Boston, MA 02110-1301, USA.
 
19
*/
 
20
 
 
21
// Own
 
22
#include "applicationmodel.h"
 
23
 
 
24
// Qt
 
25
#include <QtCore/QtAlgorithms>
 
26
#include <QtCore/QList>
 
27
#include <QtGui/QLabel>
 
28
#include <QtGui/QLayout>
 
29
#include <QtGui/QCheckBox>
 
30
 
 
31
// KDE
 
32
#include <kauthorized.h>
 
33
#include <khistorycombobox.h>
 
34
#include <kdesktopfile.h>
 
35
#include <klineedit.h>
 
36
#include <klocale.h>
 
37
#include <kiconloader.h>
 
38
#include <krun.h>
 
39
#include <kstandarddirs.h>
 
40
#include <kstringhandler.h>
 
41
#include <kmimetypetrader.h>
 
42
#include <kurlcompletion.h>
 
43
#include <kurlrequester.h>
 
44
#include <kmimetype.h>
 
45
#include <kservicegroup.h>
 
46
#include <ksycoca.h>
 
47
#include <kdebug.h>
 
48
 
 
49
#include <assert.h>
 
50
#include <stdlib.h>
 
51
#include <kbuildsycocaprogressdialog.h>
 
52
#include <kconfiggroup.h>
 
53
#include "kickoffadaptor.h"
 
54
// Local
 
55
#include "core/models.h"
 
56
 
 
57
template <> inline
 
58
void KConfigGroup::writeEntry(const char *pKey,
 
59
                              const KGlobalSettings::Completion& aValue,
 
60
                              KConfigBase::WriteConfigFlags flags)
 
61
{
 
62
    writeEntry(pKey, int(aValue), flags);
 
63
}
 
64
 
 
65
namespace Kickoff
 
66
{
 
67
 
 
68
class AppNode
 
69
{
 
70
public:
 
71
    AppNode()
 
72
        : parent(0),
 
73
          fetched(false),
 
74
          isDir(false),
 
75
          isSeparator(false),
 
76
          subTitleMandatory(false)
 
77
    {
 
78
    }
 
79
 
 
80
    ~AppNode()
 
81
    {
 
82
        qDeleteAll(children);
 
83
    }
 
84
 
 
85
    QList<AppNode*> children;
 
86
 
 
87
    QIcon icon;
 
88
    QString iconName;
 
89
    QString genericName;
 
90
    QString appName;
 
91
    QString relPath;
 
92
    QString desktopEntry;
 
93
 
 
94
    AppNode *parent;
 
95
    DisplayOrder displayOrder;
 
96
    bool fetched : 1;
 
97
    bool isDir : 1;
 
98
    bool isSeparator : 1;
 
99
    bool subTitleMandatory : 1;
 
100
};
 
101
 
 
102
class ApplicationModelPrivate
 
103
{
 
104
public:
 
105
    ApplicationModelPrivate(ApplicationModel *qq, bool _allowSeparators)
 
106
            : q(qq),
 
107
              root(new AppNode()),
 
108
              duplicatePolicy(ApplicationModel::ShowDuplicatesPolicy),
 
109
              systemApplicationPolicy(ApplicationModel::ShowSystemOnlyPolicy),
 
110
              primaryNamePolicy(ApplicationModel::GenericNamePrimary),
 
111
              displayOrder(NameAfterDescription),
 
112
              allowSeparators(_allowSeparators)
 
113
    {
 
114
        systemApplications = Kickoff::systemApplicationList();
 
115
        reloadTimer = new QTimer(qq);
 
116
        reloadTimer->setSingleShot(true);
 
117
        QObject::connect(reloadTimer, SIGNAL(timeout()), qq, SLOT(delayedReloadMenu()));
 
118
    }
 
119
 
 
120
    ~ApplicationModelPrivate()
 
121
    {
 
122
        delete root;
 
123
    }
 
124
 
 
125
    void fillNode(const QString &relPath, AppNode *node);
 
126
    static QHash<QString, QString> iconNameMap();
 
127
 
 
128
    ApplicationModel *q;
 
129
    AppNode *root;
 
130
    ApplicationModel::DuplicatePolicy duplicatePolicy;
 
131
    ApplicationModel::SystemApplicationPolicy systemApplicationPolicy;
 
132
    ApplicationModel::PrimaryNamePolicy primaryNamePolicy;
 
133
    QStringList systemApplications;
 
134
    DisplayOrder displayOrder;
 
135
    bool allowSeparators;
 
136
    QTimer *reloadTimer;
 
137
};
 
138
 
 
139
void ApplicationModelPrivate::fillNode(const QString &_relPath, AppNode *node)
 
140
{
 
141
    KServiceGroup::Ptr root = KServiceGroup::group(_relPath);
 
142
 
 
143
    if (!root || !root->isValid()) {
 
144
        return;
 
145
    }
 
146
 
 
147
    const KServiceGroup::List list = root->entries(true /* sorted */,
 
148
                                                   true /* exclude no display entries */,
 
149
                                                   allowSeparators /* allow separators */,
 
150
                                                   primaryNamePolicy == ApplicationModel::GenericNamePrimary /* sort by generic name */);
 
151
 
 
152
    // application name <-> service map for detecting duplicate entries
 
153
    QHash<QString, KService::Ptr> existingServices;
 
154
 
 
155
    // generic name <-> node mapping to determinate duplicate generic names
 
156
    QHash<QString,QList<AppNode*> > genericNames;
 
157
 
 
158
    for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) {
 
159
        QString icon;
 
160
        QString appName;
 
161
        QString genericName;
 
162
        QString relPath = _relPath;
 
163
        QString desktopEntry;
 
164
        bool isDir = false;
 
165
        bool isSeparator = false;
 
166
        const KSycocaEntry::Ptr p = (*it);
 
167
 
 
168
        if (p->isType(KST_KService)) {
 
169
            const KService::Ptr service = KService::Ptr::staticCast(p);
 
170
 
 
171
            if (service->noDisplay()) {
 
172
                continue;
 
173
            }
 
174
 
 
175
            icon = service->icon();
 
176
            appName = service->name();
 
177
            genericName = service->genericName();
 
178
            desktopEntry = service->entryPath();
 
179
 
 
180
            // check for duplicates (eg. KDE 3 and KDE 4 versions of application
 
181
            // both present)
 
182
            if (duplicatePolicy == ApplicationModel::ShowLatestOnlyPolicy &&
 
183
                existingServices.contains(appName)) {
 
184
                if (Kickoff::isLaterVersion(existingServices[appName], service)) {
 
185
                    continue;
 
186
                } else {
 
187
                    // find and remove the existing entry with the same name
 
188
                    for (int i = node->children.count() - 1; i >= 0; --i) {
 
189
                        AppNode *app = node->children.at(i);
 
190
                        if (app->appName == appName &&
 
191
                            app->genericName == genericName &&
 
192
                            app->iconName == icon) {
 
193
                            app = node->children.takeAt(i);
 
194
                            const QString s = app->genericName.toLower();
 
195
                            if (genericNames.contains(s)) {
 
196
                                QList<AppNode*> list = genericNames[s];
 
197
                                for (int j = list.count() - 1; j >= 0; --j) {
 
198
                                    if(list.at(j) == app) {
 
199
                                        list.takeAt(j);
 
200
                                    }
 
201
                                }
 
202
                                genericNames[s] = list;
 
203
                            }
 
204
                            delete app;
 
205
                        }
 
206
                    }
 
207
                }
 
208
            }
 
209
 
 
210
            if (systemApplicationPolicy == ApplicationModel::ShowSystemOnlyPolicy &&
 
211
                    systemApplications.contains(service->desktopEntryName())) {
 
212
                // don't duplicate applications that are configured to appear in the System tab
 
213
                // in the Applications tab
 
214
                continue;
 
215
            }
 
216
 
 
217
            existingServices[appName] = service;
 
218
        } else if (p->isType(KST_KServiceGroup)) {
 
219
            const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr::staticCast(p);
 
220
 
 
221
            if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) {
 
222
                continue;
 
223
            }
 
224
 
 
225
            kDebug(250) << "Service group" << serviceGroup->entryPath() << serviceGroup->icon()
 
226
            << serviceGroup->relPath() << serviceGroup->directoryEntryPath();
 
227
 
 
228
            icon = serviceGroup->icon();
 
229
            if (iconNameMap().contains(icon)) {
 
230
                icon = iconNameMap().value(icon);
 
231
            }
 
232
 
 
233
            desktopEntry = serviceGroup->entryPath();
 
234
            genericName = serviceGroup->caption();
 
235
            relPath = serviceGroup->relPath();
 
236
            appName = serviceGroup->comment();
 
237
            isDir = true;
 
238
        } else if (p->isType(KST_KServiceSeparator)) {
 
239
            isSeparator = true;
 
240
        } else {
 
241
            kWarning(250) << "KServiceGroup: Unexpected object in list!";
 
242
            continue;
 
243
        }
 
244
 
 
245
        AppNode *newnode = new AppNode();
 
246
        newnode->iconName = icon;
 
247
        newnode->icon = KIcon(icon);
 
248
        newnode->appName = appName;
 
249
        newnode->genericName = genericName;
 
250
        newnode->relPath = relPath;
 
251
        newnode->desktopEntry = desktopEntry;
 
252
        newnode->isDir = isDir;
 
253
        newnode->isSeparator = isSeparator;
 
254
        newnode->parent = node;
 
255
        node->children.append(newnode);
 
256
 
 
257
        if (p->isType(KST_KService)) {
 
258
            const QString s = genericName.toLower();
 
259
            QList<AppNode*> list = genericNames.value(s);
 
260
            list.append(newnode);
 
261
            genericNames[s] = list;
 
262
        }
 
263
    }
 
264
 
 
265
    // set the subTitleMandatory field for nodes that do not provide a unique generic
 
266
    // name what may help us on display to show in such cases also the subtitle to
 
267
    // provide a hint to the user what the duplicate entries are about.
 
268
    foreach (const QList<AppNode*> &list, genericNames) {
 
269
        if (list.count() > 1) {
 
270
            foreach (AppNode* n, list) {
 
271
                n->subTitleMandatory = true;
 
272
            }
 
273
        }
 
274
    }
 
275
}
 
276
 
 
277
ApplicationModel::ApplicationModel(QObject *parent, bool allowSeparators)
 
278
  : KickoffAbstractModel(parent),
 
279
    d(new ApplicationModelPrivate(this, allowSeparators))
 
280
{
 
281
    QDBusConnection dbus = QDBusConnection::sessionBus();
 
282
    (void)new KickoffAdaptor(this);
 
283
    QDBusConnection::sessionBus().registerObject("/kickoff", this);
 
284
    dbus.connect(QString(), "/kickoff", "org.kde.plasma", "reloadMenu", this, SLOT(reloadMenu()));
 
285
    connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), this, SLOT(checkSycocaChange(QStringList)));
 
286
}
 
287
 
 
288
ApplicationModel::~ApplicationModel()
 
289
{
 
290
    delete d;
 
291
}
 
292
 
 
293
bool ApplicationModel::canFetchMore(const QModelIndex &parent) const
 
294
{
 
295
    if (!parent.isValid())
 
296
        return false;
 
297
 
 
298
    AppNode *node = static_cast<AppNode*>(parent.internalPointer());
 
299
    return node->isDir && !node->fetched;
 
300
}
 
301
 
 
302
void ApplicationModel::setNameDisplayOrder(DisplayOrder displayOrder) 
 
303
{
 
304
    d->displayOrder = displayOrder;
 
305
}
 
306
 
 
307
DisplayOrder ApplicationModel::nameDisplayOrder() const
 
308
{
 
309
   return d->displayOrder;
 
310
}
 
311
 
 
312
int ApplicationModel::columnCount(const QModelIndex &parent) const
 
313
{
 
314
    Q_UNUSED(parent)
 
315
    return 1;
 
316
}
 
317
 
 
318
bool ApplicationModel::nameAfterDescription(const QModelIndex &index) const
 
319
{
 
320
    AppNode *node = static_cast<AppNode*>(index.internalPointer());
 
321
    if (node->isDir) {
 
322
        return true;
 
323
    }
 
324
 
 
325
    QModelIndex parent = index.parent();
 
326
    while (parent.parent().isValid()) {
 
327
        parent = parent.parent();
 
328
    }
 
329
 
 
330
    if (parent.isValid()) {
 
331
        // nasty little hack to always makes games show their unique name
 
332
        // there is no such thing as a "generic name" for a game in practice
 
333
        // though this is apparently quite true for all other kinds of apps
 
334
        AppNode *node = static_cast<AppNode*>(parent.internalPointer());
 
335
        if (node->isDir && node->genericName == i18n("Games")) {
 
336
            return false;
 
337
        }
 
338
    }
 
339
 
 
340
    return d->displayOrder == NameAfterDescription;
 
341
}
 
342
 
 
343
QVariant ApplicationModel::data(const QModelIndex &index, int role) const
 
344
{
 
345
    if (!index.isValid()) {
 
346
        return QVariant();
 
347
    }
 
348
 
 
349
    AppNode *node = static_cast<AppNode*>(index.internalPointer());
 
350
 
 
351
    switch (role) {
 
352
    case Qt::DisplayRole:
 
353
      if (nameAfterDescription(index) && !node->genericName.isEmpty()) {
 
354
            return node->genericName;
 
355
        } else {
 
356
            return node->appName;
 
357
        }
 
358
        break;
 
359
    case Kickoff::SubTitleRole:
 
360
      if (!nameAfterDescription(index) && !node->genericName.isEmpty()) {
 
361
            return node->genericName;
 
362
        } else {
 
363
            return node->appName;
 
364
        }
 
365
        break;
 
366
    case Kickoff::UrlRole:
 
367
        if (node->isDir) {
 
368
            return QString::fromLatin1("applications://%1").arg(node->desktopEntry);
 
369
        } else {
 
370
            return node->desktopEntry;
 
371
        }
 
372
        break;
 
373
    case Kickoff::SubTitleMandatoryRole:
 
374
        return nameAfterDescription(index) && node->subTitleMandatory;
 
375
        break;
 
376
    case Kickoff::SeparatorRole:
 
377
        return node->isSeparator;
 
378
        break;
 
379
    case Qt::DecorationRole:
 
380
        return node->icon;
 
381
        break;
 
382
    case Kickoff::RelPathRole:
 
383
        return node->relPath;
 
384
        break;
 
385
    case Kickoff::IconNameRole:
 
386
        return node->iconName;
 
387
        break;
 
388
    default:
 
389
        ;
 
390
    }
 
391
    return QVariant();
 
392
}
 
393
 
 
394
void ApplicationModel::fetchMore(const QModelIndex &parent)
 
395
{
 
396
    if (!parent.isValid()) {
 
397
        return;
 
398
    }
 
399
 
 
400
    AppNode *node = static_cast<AppNode*>(parent.internalPointer());
 
401
    if (!node->isDir) {
 
402
        return;
 
403
    }
 
404
 
 
405
    emit layoutAboutToBeChanged();
 
406
    d->fillNode(node->relPath, node);
 
407
    node->fetched = true;
 
408
    emit layoutChanged();
 
409
}
 
410
 
 
411
bool ApplicationModel::hasChildren(const QModelIndex &parent) const
 
412
{
 
413
    if (!parent.isValid()) {
 
414
        return true;
 
415
    }
 
416
 
 
417
    AppNode *node = static_cast<AppNode*>(parent.internalPointer());
 
418
    return node->isDir;
 
419
}
 
420
 
 
421
QVariant ApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const
 
422
{
 
423
    if (orientation != Qt::Horizontal || section != 0) {
 
424
        return QVariant();
 
425
    }
 
426
 
 
427
    switch (role) {
 
428
    case Qt::DisplayRole:
 
429
        return i18n("All Applications");
 
430
        break;
 
431
    default:
 
432
        return QVariant();
 
433
    }
 
434
}
 
435
 
 
436
QModelIndex ApplicationModel::index(int row, int column, const QModelIndex &parent) const
 
437
{
 
438
    if (row < 0 || column != 0)
 
439
        return QModelIndex();
 
440
 
 
441
    AppNode *node = d->root;
 
442
    if (parent.isValid())
 
443
        node = static_cast<AppNode*>(parent.internalPointer());
 
444
 
 
445
    if (row >= node->children.count())
 
446
        return QModelIndex();
 
447
    else
 
448
        return createIndex(row, 0, node->children.at(row));
 
449
}
 
450
 
 
451
QModelIndex ApplicationModel::parent(const QModelIndex &index) const
 
452
{
 
453
    if (!index.isValid()) {
 
454
        return QModelIndex();
 
455
    }
 
456
 
 
457
    AppNode *node = static_cast<AppNode*>(index.internalPointer());
 
458
    if (node->parent->parent) {
 
459
        int id = node->parent->parent->children.indexOf(node->parent);
 
460
 
 
461
        if (id >= 0 && id < node->parent->parent->children.count()) {
 
462
            return createIndex(id, 0, node->parent);
 
463
        }
 
464
    }
 
465
 
 
466
    return QModelIndex();
 
467
}
 
468
 
 
469
int ApplicationModel::rowCount(const QModelIndex &parent) const
 
470
{
 
471
    if (!parent.isValid()) {
 
472
        return d->root->children.count();
 
473
    }
 
474
 
 
475
    AppNode *node = static_cast<AppNode*>(parent.internalPointer());
 
476
    return node->children.count();
 
477
}
 
478
 
 
479
void ApplicationModel::setDuplicatePolicy(DuplicatePolicy policy)
 
480
{
 
481
    if (d->duplicatePolicy != policy) {
 
482
        d->duplicatePolicy = policy;
 
483
        reloadMenu();
 
484
    }
 
485
}
 
486
 
 
487
void ApplicationModel::setSystemApplicationPolicy(SystemApplicationPolicy policy)
 
488
{
 
489
    if (d->systemApplicationPolicy != policy) {
 
490
        d->systemApplicationPolicy = policy;
 
491
        reloadMenu();
 
492
    }
 
493
}
 
494
 
 
495
void ApplicationModel::setPrimaryNamePolicy(PrimaryNamePolicy policy)
 
496
{
 
497
    if (policy != d->primaryNamePolicy) {
 
498
        d->primaryNamePolicy = policy;
 
499
        reloadMenu();
 
500
    }
 
501
}
 
502
 
 
503
ApplicationModel::PrimaryNamePolicy ApplicationModel::primaryNamePolicy() const
 
504
{
 
505
    return d->primaryNamePolicy;
 
506
}
 
507
 
 
508
void ApplicationModel::delayedReloadMenu()
 
509
{
 
510
    if (!d->reloadTimer->isActive()) {
 
511
        d->reloadTimer->start(200);
 
512
    }
 
513
}
 
514
 
 
515
void ApplicationModel::reloadMenu()
 
516
{
 
517
    delete d->root;
 
518
    d->root = new AppNode();
 
519
    d->fillNode(QString(), d->root);
 
520
    reset();
 
521
}
 
522
 
 
523
void ApplicationModel::checkSycocaChange(const QStringList &changes)
 
524
{
 
525
    if (changes.contains("services") || changes.contains("apps")) {
 
526
        reloadMenu();
 
527
    }
 
528
}
 
529
 
 
530
ApplicationModel::DuplicatePolicy ApplicationModel::duplicatePolicy() const
 
531
{
 
532
    return d->duplicatePolicy;
 
533
}
 
534
 
 
535
ApplicationModel::SystemApplicationPolicy ApplicationModel::systemApplicationPolicy() const
 
536
{
 
537
    return d->systemApplicationPolicy;
 
538
}
 
539
 
 
540
/**
 
541
 * FIXME This is a temporary workaround to map the icon names found
 
542
 * in the desktop directory files (from /usr/share/desktop-directories)
 
543
 * into the Oxygen icon names.  (Only applies if the Gnome menu files
 
544
 * are also installed)
 
545
 *
 
546
 * This list was compiled from Kubuntu 7.04 with the gnome-menus
 
547
 * package present.
 
548
 *
 
549
 * This needs to be discussed on kde-core-devel and fixed
 
550
 */
 
551
QHash<QString, QString> ApplicationModelPrivate::iconNameMap()
 
552
{
 
553
    static QHash<QString, QString> map;
 
554
    if (map.isEmpty()) {
 
555
        map.insert("gnome-util", "applications-accessories");
 
556
        // accessibility Oxygen icon was missing when this list was compiled
 
557
        map.insert("accessibility-directory", "applications-other");
 
558
        map.insert("gnome-devel", "applications-development");
 
559
        map.insert("package_edutainment", "applications-education");
 
560
        map.insert("gnome-joystick", "applications-games");
 
561
        map.insert("gnome-graphics", "applications-graphics");
 
562
        map.insert("gnome-globe", "applications-internet");
 
563
        map.insert("gnome-multimedia", "applications-multimedia");
 
564
        map.insert("gnome-applications", "applications-office");
 
565
        map.insert("gnome-system", "applications-system");
 
566
    }
 
567
    return map;
 
568
}
 
569
 
 
570
} // namespace Kickoff
 
571
 
 
572
 
 
573
#include "applicationmodel.moc"