2
Copyright 2007 Pino Toscano <pino@kde.org>
3
Copyright 2007 Robert Knight <robertknight@gmail.com>
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.
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.
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.
22
#include "applicationmodel.h"
25
#include <QtCore/QtAlgorithms>
26
#include <QtCore/QList>
27
#include <QtGui/QLabel>
28
#include <QtGui/QLayout>
29
#include <QtGui/QCheckBox>
32
#include <kauthorized.h>
33
#include <khistorycombobox.h>
34
#include <kdesktopfile.h>
35
#include <klineedit.h>
37
#include <kiconloader.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>
51
#include <kbuildsycocaprogressdialog.h>
52
#include <kconfiggroup.h>
53
#include "kickoffadaptor.h"
55
#include "core/models.h"
58
void KConfigGroup::writeEntry(const char *pKey,
59
const KGlobalSettings::Completion& aValue,
60
KConfigBase::WriteConfigFlags flags)
62
writeEntry(pKey, int(aValue), flags);
76
subTitleMandatory(false)
85
QList<AppNode*> children;
95
DisplayOrder displayOrder;
99
bool subTitleMandatory : 1;
102
class ApplicationModelPrivate
105
ApplicationModelPrivate(ApplicationModel *qq, bool _allowSeparators)
108
duplicatePolicy(ApplicationModel::ShowDuplicatesPolicy),
109
systemApplicationPolicy(ApplicationModel::ShowSystemOnlyPolicy),
110
primaryNamePolicy(ApplicationModel::GenericNamePrimary),
111
displayOrder(NameAfterDescription),
112
allowSeparators(_allowSeparators)
114
systemApplications = Kickoff::systemApplicationList();
115
reloadTimer = new QTimer(qq);
116
reloadTimer->setSingleShot(true);
117
QObject::connect(reloadTimer, SIGNAL(timeout()), qq, SLOT(delayedReloadMenu()));
120
~ApplicationModelPrivate()
125
void fillNode(const QString &relPath, AppNode *node);
126
static QHash<QString, QString> iconNameMap();
130
ApplicationModel::DuplicatePolicy duplicatePolicy;
131
ApplicationModel::SystemApplicationPolicy systemApplicationPolicy;
132
ApplicationModel::PrimaryNamePolicy primaryNamePolicy;
133
QStringList systemApplications;
134
DisplayOrder displayOrder;
135
bool allowSeparators;
139
void ApplicationModelPrivate::fillNode(const QString &_relPath, AppNode *node)
141
KServiceGroup::Ptr root = KServiceGroup::group(_relPath);
143
if (!root || !root->isValid()) {
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 */);
152
// application name <-> service map for detecting duplicate entries
153
QHash<QString, KService::Ptr> existingServices;
155
// generic name <-> node mapping to determinate duplicate generic names
156
QHash<QString,QList<AppNode*> > genericNames;
158
for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) {
162
QString relPath = _relPath;
163
QString desktopEntry;
165
bool isSeparator = false;
166
const KSycocaEntry::Ptr p = (*it);
168
if (p->isType(KST_KService)) {
169
const KService::Ptr service = KService::Ptr::staticCast(p);
171
if (service->noDisplay()) {
175
icon = service->icon();
176
appName = service->name();
177
genericName = service->genericName();
178
desktopEntry = service->entryPath();
180
// check for duplicates (eg. KDE 3 and KDE 4 versions of application
182
if (duplicatePolicy == ApplicationModel::ShowLatestOnlyPolicy &&
183
existingServices.contains(appName)) {
184
if (Kickoff::isLaterVersion(existingServices[appName], service)) {
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) {
202
genericNames[s] = list;
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
217
existingServices[appName] = service;
218
} else if (p->isType(KST_KServiceGroup)) {
219
const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr::staticCast(p);
221
if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0) {
225
kDebug(250) << "Service group" << serviceGroup->entryPath() << serviceGroup->icon()
226
<< serviceGroup->relPath() << serviceGroup->directoryEntryPath();
228
icon = serviceGroup->icon();
229
if (iconNameMap().contains(icon)) {
230
icon = iconNameMap().value(icon);
233
desktopEntry = serviceGroup->entryPath();
234
genericName = serviceGroup->caption();
235
relPath = serviceGroup->relPath();
236
appName = serviceGroup->comment();
238
} else if (p->isType(KST_KServiceSeparator)) {
241
kWarning(250) << "KServiceGroup: Unexpected object in list!";
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);
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;
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;
277
ApplicationModel::ApplicationModel(QObject *parent, bool allowSeparators)
278
: KickoffAbstractModel(parent),
279
d(new ApplicationModelPrivate(this, allowSeparators))
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)));
288
ApplicationModel::~ApplicationModel()
293
bool ApplicationModel::canFetchMore(const QModelIndex &parent) const
295
if (!parent.isValid())
298
AppNode *node = static_cast<AppNode*>(parent.internalPointer());
299
return node->isDir && !node->fetched;
302
void ApplicationModel::setNameDisplayOrder(DisplayOrder displayOrder)
304
d->displayOrder = displayOrder;
307
DisplayOrder ApplicationModel::nameDisplayOrder() const
309
return d->displayOrder;
312
int ApplicationModel::columnCount(const QModelIndex &parent) const
318
bool ApplicationModel::nameAfterDescription(const QModelIndex &index) const
320
AppNode *node = static_cast<AppNode*>(index.internalPointer());
325
QModelIndex parent = index.parent();
326
while (parent.parent().isValid()) {
327
parent = parent.parent();
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")) {
340
return d->displayOrder == NameAfterDescription;
343
QVariant ApplicationModel::data(const QModelIndex &index, int role) const
345
if (!index.isValid()) {
349
AppNode *node = static_cast<AppNode*>(index.internalPointer());
352
case Qt::DisplayRole:
353
if (nameAfterDescription(index) && !node->genericName.isEmpty()) {
354
return node->genericName;
356
return node->appName;
359
case Kickoff::SubTitleRole:
360
if (!nameAfterDescription(index) && !node->genericName.isEmpty()) {
361
return node->genericName;
363
return node->appName;
366
case Kickoff::UrlRole:
368
return QString::fromLatin1("applications://%1").arg(node->desktopEntry);
370
return node->desktopEntry;
373
case Kickoff::SubTitleMandatoryRole:
374
return nameAfterDescription(index) && node->subTitleMandatory;
376
case Kickoff::SeparatorRole:
377
return node->isSeparator;
379
case Qt::DecorationRole:
382
case Kickoff::RelPathRole:
383
return node->relPath;
385
case Kickoff::IconNameRole:
386
return node->iconName;
394
void ApplicationModel::fetchMore(const QModelIndex &parent)
396
if (!parent.isValid()) {
400
AppNode *node = static_cast<AppNode*>(parent.internalPointer());
405
emit layoutAboutToBeChanged();
406
d->fillNode(node->relPath, node);
407
node->fetched = true;
408
emit layoutChanged();
411
bool ApplicationModel::hasChildren(const QModelIndex &parent) const
413
if (!parent.isValid()) {
417
AppNode *node = static_cast<AppNode*>(parent.internalPointer());
421
QVariant ApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const
423
if (orientation != Qt::Horizontal || section != 0) {
428
case Qt::DisplayRole:
429
return i18n("All Applications");
436
QModelIndex ApplicationModel::index(int row, int column, const QModelIndex &parent) const
438
if (row < 0 || column != 0)
439
return QModelIndex();
441
AppNode *node = d->root;
442
if (parent.isValid())
443
node = static_cast<AppNode*>(parent.internalPointer());
445
if (row >= node->children.count())
446
return QModelIndex();
448
return createIndex(row, 0, node->children.at(row));
451
QModelIndex ApplicationModel::parent(const QModelIndex &index) const
453
if (!index.isValid()) {
454
return QModelIndex();
457
AppNode *node = static_cast<AppNode*>(index.internalPointer());
458
if (node->parent->parent) {
459
int id = node->parent->parent->children.indexOf(node->parent);
461
if (id >= 0 && id < node->parent->parent->children.count()) {
462
return createIndex(id, 0, node->parent);
466
return QModelIndex();
469
int ApplicationModel::rowCount(const QModelIndex &parent) const
471
if (!parent.isValid()) {
472
return d->root->children.count();
475
AppNode *node = static_cast<AppNode*>(parent.internalPointer());
476
return node->children.count();
479
void ApplicationModel::setDuplicatePolicy(DuplicatePolicy policy)
481
if (d->duplicatePolicy != policy) {
482
d->duplicatePolicy = policy;
487
void ApplicationModel::setSystemApplicationPolicy(SystemApplicationPolicy policy)
489
if (d->systemApplicationPolicy != policy) {
490
d->systemApplicationPolicy = policy;
495
void ApplicationModel::setPrimaryNamePolicy(PrimaryNamePolicy policy)
497
if (policy != d->primaryNamePolicy) {
498
d->primaryNamePolicy = policy;
503
ApplicationModel::PrimaryNamePolicy ApplicationModel::primaryNamePolicy() const
505
return d->primaryNamePolicy;
508
void ApplicationModel::delayedReloadMenu()
510
if (!d->reloadTimer->isActive()) {
511
d->reloadTimer->start(200);
515
void ApplicationModel::reloadMenu()
518
d->root = new AppNode();
519
d->fillNode(QString(), d->root);
523
void ApplicationModel::checkSycocaChange(const QStringList &changes)
525
if (changes.contains("services") || changes.contains("apps")) {
530
ApplicationModel::DuplicatePolicy ApplicationModel::duplicatePolicy() const
532
return d->duplicatePolicy;
535
ApplicationModel::SystemApplicationPolicy ApplicationModel::systemApplicationPolicy() const
537
return d->systemApplicationPolicy;
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)
546
* This list was compiled from Kubuntu 7.04 with the gnome-menus
549
* This needs to be discussed on kde-core-devel and fixed
551
QHash<QString, QString> ApplicationModelPrivate::iconNameMap()
553
static QHash<QString, QString> map;
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");
570
} // namespace Kickoff
573
#include "applicationmodel.moc"