add more spacing
[personal-kdebase.git] / workspace / plasma / applets / kickoff / core / applicationmodel.cpp
blob7d7551707296712af79437d8046e6770e4e7f5d5
1 /*
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.
21 // Own
22 #include "applicationmodel.h"
24 // Qt
25 #include <QtCore/QtAlgorithms>
26 #include <QtCore/QList>
27 #include <QtGui/QLabel>
28 #include <QtGui/QLayout>
29 #include <QtGui/QCheckBox>
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>
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"
57 template <> inline
58 void KConfigGroup::writeEntry(const char *pKey,
59 const KGlobalSettings::Completion& aValue,
60 KConfigBase::WriteConfigFlags flags)
62 writeEntry(pKey, int(aValue), flags);
65 namespace Kickoff
68 class AppNode
70 public:
71 AppNode()
72 : parent(0),
73 fetched(false),
74 isDir(false),
75 subTitleMandatory(false)
79 ~AppNode()
81 qDeleteAll(children);
84 QList<AppNode*> children;
86 QIcon icon;
87 QString genericName;
88 QString appName;
89 QString relPath;
90 QString desktopEntry;
92 AppNode *parent;
93 bool fetched;
94 bool isDir;
95 bool subTitleMandatory;
98 class ApplicationModelPrivate
100 public:
101 ApplicationModelPrivate(ApplicationModel *qq)
102 : q(qq),
103 root(new AppNode()),
104 duplicatePolicy(ApplicationModel::ShowDuplicatesPolicy),
105 systemApplicationPolicy(ApplicationModel::ShowSystemOnlyPolicy),
106 primaryNamePolicy(ApplicationModel::GenericNamePrimary)
108 systemApplications = Kickoff::systemApplicationList();
111 ~ApplicationModelPrivate()
113 delete root;
116 void fillNode(const QString &relPath, AppNode *node);
117 static QHash<QString, QString> iconNameMap();
119 ApplicationModel *q;
120 AppNode *root;
121 ApplicationModel::DuplicatePolicy duplicatePolicy;
122 ApplicationModel::SystemApplicationPolicy systemApplicationPolicy;
123 ApplicationModel::PrimaryNamePolicy primaryNamePolicy;
124 QStringList systemApplications;
127 void ApplicationModelPrivate::fillNode(const QString &_relPath, AppNode *node)
129 KServiceGroup::Ptr root = KServiceGroup::group(_relPath);
131 if (!root || !root->isValid()) {
132 return;
135 const KServiceGroup::List list = root->entries(true /* sorted */,
136 true /* exclude no display entries */,
137 false /* allow separators */,
138 primaryNamePolicy == ApplicationModel::GenericNamePrimary /* sort by generic name */);
140 // application name <-> service map for detecting duplicate entries
141 QHash<QString, KService::Ptr> existingServices;
143 // generic name <-> node mapping to determinate duplicate generic names
144 QHash<QString,QList<AppNode*> > genericNames;
146 for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) {
147 QString icon;
148 QString appName;
149 QString genericName;
150 QString relPath = _relPath;
151 QString desktopEntry;
152 bool isDir = false;
153 const KSycocaEntry::Ptr p = (*it);
155 if (p->isType(KST_KService)) {
156 const KService::Ptr service = KService::Ptr::staticCast(p);
158 if (service->noDisplay()) {
159 continue;
162 icon = service->icon();
163 appName = service->name();
164 genericName = service->genericName();
165 desktopEntry = service->entryPath();
167 // check for duplicates (eg. KDE 3 and KDE 4 versions of application
168 // both present)
169 if (duplicatePolicy == ApplicationModel::ShowLatestOnlyPolicy &&
170 existingServices.contains(appName)) {
171 if (Kickoff::isLaterVersion(existingServices[appName], service)) {
172 continue;
173 } else {
174 // find and remove the existing entry with the same name
175 for (int i = 0 ; i < node->children.count() ; i++) {
176 if (node->children[i]->appName == appName) {
177 delete node->children.takeAt(i);
183 if (systemApplicationPolicy == ApplicationModel::ShowSystemOnlyPolicy &&
184 systemApplications.contains(service->desktopEntryName())) {
185 // don't duplicate applications that are configured to appear in the System tab
186 // in the Applications tab
187 continue;
190 existingServices[appName] = service;
191 } else if (p->isType(KST_KServiceGroup)) {
192 const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr::staticCast(p);
194 if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0)
195 continue;
197 kDebug(250) << "Service group" << serviceGroup->entryPath() << serviceGroup->icon()
198 << serviceGroup->relPath() << serviceGroup->directoryEntryPath();
200 icon = serviceGroup->icon();
201 if (iconNameMap().contains(icon)) {
202 icon = iconNameMap().value(icon);
205 genericName = serviceGroup->caption();
206 relPath = serviceGroup->relPath();
207 appName = serviceGroup->comment();
208 isDir = true;
209 } else if (p->isType(KST_KServiceSeparator)) {
210 // TODO: implement seaparators
211 } else {
212 kWarning(250) << "KServiceGroup: Unexpected object in list!";
213 continue;
216 AppNode *newnode = new AppNode();
217 newnode->icon = KIcon(icon);
218 newnode->appName = appName;
219 newnode->genericName = genericName;
220 newnode->relPath = relPath;
221 newnode->desktopEntry = desktopEntry;
222 newnode->isDir = isDir;
223 newnode->parent = node;
224 node->children.append(newnode);
226 if (p->isType(KST_KService)) {
227 const QString s = genericName.toLower();
228 if (genericNames.contains(s)) {
229 genericNames[s].append(newnode);
230 } else {
231 genericNames[s] = QList<AppNode*>() << newnode;
236 // set the subTitleMandatory field for nodes that do not provide a unique generic
237 // name what may help us on display to show in such cases also the subtitle to
238 // provide a hint to the user what the duplicate entries are about.
239 foreach (QList<AppNode*> list, genericNames.values()) {
240 if (list.count() >= 2) {
241 foreach (AppNode* n, list) {
242 n->subTitleMandatory = true;
248 ApplicationModel::ApplicationModel(QObject *parent)
249 : KickoffAbstractModel(parent), d(new ApplicationModelPrivate(this))
251 QDBusConnection dbus = QDBusConnection::sessionBus();
252 (void)new KickoffAdaptor(this);
253 QDBusConnection::sessionBus().registerObject("/kickoff", this);
254 dbus.connect(QString(), "/kickoff", "org.kde.plasma", "reloadMenu", this, SLOT(reloadMenu()));
255 connect(KSycoca::self(), SIGNAL(databaseChanged()), this, SLOT(checkSycocaChange()));
256 d->fillNode(QString(), d->root);
259 ApplicationModel::~ApplicationModel()
261 delete d;
264 bool ApplicationModel::canFetchMore(const QModelIndex &parent) const
266 if (!parent.isValid())
267 return false;
269 AppNode *node = static_cast<AppNode*>(parent.internalPointer());
270 return node->isDir && !node->fetched;
273 int ApplicationModel::columnCount(const QModelIndex &parent) const
275 Q_UNUSED(parent)
276 return 1;
279 QVariant ApplicationModel::data(const QModelIndex &index, int role) const
281 if (!index.isValid())
282 return QVariant();
284 AppNode *node = static_cast<AppNode*>(index.internalPointer());
286 switch (role) {
287 case Qt::DisplayRole:
288 if (!node->genericName.isEmpty()) {
289 return node->genericName;
290 } else {
291 return node->appName;
293 break;
294 case Kickoff::SubTitleRole:
295 if (!node->genericName.isEmpty()) {
296 return node->appName;
298 break;
299 case Kickoff::UrlRole:
300 return node->desktopEntry;
301 break;
302 case Kickoff::SubTitleMandatoryRole:
303 return node->subTitleMandatory;
304 break;
305 case Qt::DecorationRole:
306 return node->icon;
307 break;
308 default:
311 return QVariant();
314 void ApplicationModel::fetchMore(const QModelIndex &parent)
316 if (!parent.isValid()) {
317 return;
320 AppNode *node = static_cast<AppNode*>(parent.internalPointer());
321 if (!node->isDir) {
322 return;
325 emit layoutAboutToBeChanged();
326 d->fillNode(node->relPath, node);
327 node->fetched = true;
328 emit layoutChanged();
331 bool ApplicationModel::hasChildren(const QModelIndex &parent) const
333 if (!parent.isValid()) {
334 return true;
337 AppNode *node = static_cast<AppNode*>(parent.internalPointer());
338 return node->isDir;
341 QVariant ApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const
343 if (orientation != Qt::Horizontal || section != 0) {
344 return QVariant();
347 switch (role) {
348 case Qt::DisplayRole:
349 return i18n("Known Applications");
350 break;
351 default:
352 return QVariant();
356 QModelIndex ApplicationModel::index(int row, int column, const QModelIndex &parent) const
358 if (row < 0 || column != 0)
359 return QModelIndex();
361 AppNode *node = d->root;
362 if (parent.isValid())
363 node = static_cast<AppNode*>(parent.internalPointer());
365 if (row >= node->children.count())
366 return QModelIndex();
367 else
368 return createIndex(row, 0, node->children.at(row));
371 QModelIndex ApplicationModel::parent(const QModelIndex &index) const
373 if (!index.isValid()) {
374 return QModelIndex();
377 AppNode *node = static_cast<AppNode*>(index.internalPointer());
378 if (node->parent->parent) {
379 int id = node->parent->parent->children.indexOf(node->parent);
381 if (id >= 0 && id < node->parent->parent->children.count()) {
382 return createIndex(id, 0, node->parent);
386 return QModelIndex();
389 int ApplicationModel::rowCount(const QModelIndex &parent) const
391 if (!parent.isValid()) {
392 return d->root->children.count();
395 AppNode *node = static_cast<AppNode*>(parent.internalPointer());
396 return node->children.count();
399 void ApplicationModel::setDuplicatePolicy(DuplicatePolicy policy)
401 if (d->duplicatePolicy != policy) {
402 d->duplicatePolicy = policy;
403 reloadMenu();
407 void ApplicationModel::setSystemApplicationPolicy(SystemApplicationPolicy policy)
409 if (d->systemApplicationPolicy != policy) {
410 d->systemApplicationPolicy = policy;
411 reloadMenu();
415 void ApplicationModel::setPrimaryNamePolicy(PrimaryNamePolicy policy)
417 if (policy != d->primaryNamePolicy) {
418 d->primaryNamePolicy = policy;
419 reloadMenu();
423 ApplicationModel::PrimaryNamePolicy ApplicationModel::primaryNamePolicy() const
425 return d->primaryNamePolicy;
428 void ApplicationModel::reloadMenu()
430 delete d->root;
431 d->root = new AppNode();
432 d->fillNode(QString(), d->root);
433 reset();
436 void ApplicationModel::checkSycocaChange()
438 if (KSycoca::self()->isChanged("services")) {
439 reloadMenu();
443 ApplicationModel::DuplicatePolicy ApplicationModel::duplicatePolicy() const
445 return d->duplicatePolicy;
448 ApplicationModel::SystemApplicationPolicy ApplicationModel::systemApplicationPolicy() const
450 return d->systemApplicationPolicy;
454 * FIXME This is a temporary workaround to map the icon names found
455 * in the desktop directory files (from /usr/share/desktop-directories)
456 * into the Oxygen icon names. (Only applies if the Gnome menu files
457 * are also installed)
459 * This list was compiled from Kubuntu 7.04 with the gnome-menus
460 * package present.
462 * This needs to be discussed on kde-core-devel and fixed
464 QHash<QString, QString> ApplicationModelPrivate::iconNameMap()
466 static QHash<QString, QString> map;
467 if (map.isEmpty()) {
468 map.insert("gnome-util", "applications-accessories");
469 // accessibility Oxygen icon was missing when this list was compiled
470 map.insert("accessibility-directory", "applications-other");
471 map.insert("gnome-devel", "applications-development");
472 map.insert("package_edutainment", "applications-education");
473 map.insert("gnome-joystick", "applications-games");
474 map.insert("gnome-graphics", "applications-graphics");
475 map.insert("gnome-globe", "applications-internet");
476 map.insert("gnome-multimedia", "applications-multimedia");
477 map.insert("gnome-applications", "applications-office");
478 map.insert("gnome-system", "applications-system");
480 return map;
483 } // namespace Kickoff
486 #include "applicationmodel.moc"