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
);
75 subTitleMandatory(false)
84 QList
<AppNode
*> children
;
95 bool subTitleMandatory
;
98 class ApplicationModelPrivate
101 ApplicationModelPrivate(ApplicationModel
*qq
)
104 duplicatePolicy(ApplicationModel::ShowDuplicatesPolicy
),
105 systemApplicationPolicy(ApplicationModel::ShowSystemOnlyPolicy
),
106 primaryNamePolicy(ApplicationModel::GenericNamePrimary
)
108 systemApplications
= Kickoff::systemApplicationList();
111 ~ApplicationModelPrivate()
116 void fillNode(const QString
&relPath
, AppNode
*node
);
117 static QHash
<QString
, QString
> iconNameMap();
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()) {
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
) {
150 QString relPath
= _relPath
;
151 QString desktopEntry
;
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()) {
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
169 if (duplicatePolicy
== ApplicationModel::ShowLatestOnlyPolicy
&&
170 existingServices
.contains(appName
)) {
171 if (Kickoff::isLaterVersion(existingServices
[appName
], service
)) {
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
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)
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();
209 } else if (p
->isType(KST_KServiceSeparator
)) {
210 // TODO: implement seaparators
212 kWarning(250) << "KServiceGroup: Unexpected object in list!";
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
);
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()
264 bool ApplicationModel::canFetchMore(const QModelIndex
&parent
) const
266 if (!parent
.isValid())
269 AppNode
*node
= static_cast<AppNode
*>(parent
.internalPointer());
270 return node
->isDir
&& !node
->fetched
;
273 int ApplicationModel::columnCount(const QModelIndex
&parent
) const
279 QVariant
ApplicationModel::data(const QModelIndex
&index
, int role
) const
281 if (!index
.isValid())
284 AppNode
*node
= static_cast<AppNode
*>(index
.internalPointer());
287 case Qt::DisplayRole
:
288 if (!node
->genericName
.isEmpty()) {
289 return node
->genericName
;
291 return node
->appName
;
294 case Kickoff::SubTitleRole
:
295 if (!node
->genericName
.isEmpty()) {
296 return node
->appName
;
299 case Kickoff::UrlRole
:
300 return node
->desktopEntry
;
302 case Kickoff::SubTitleMandatoryRole
:
303 return node
->subTitleMandatory
;
305 case Qt::DecorationRole
:
314 void ApplicationModel::fetchMore(const QModelIndex
&parent
)
316 if (!parent
.isValid()) {
320 AppNode
*node
= static_cast<AppNode
*>(parent
.internalPointer());
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()) {
337 AppNode
*node
= static_cast<AppNode
*>(parent
.internalPointer());
341 QVariant
ApplicationModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
343 if (orientation
!= Qt::Horizontal
|| section
!= 0) {
348 case Qt::DisplayRole
:
349 return i18n("Known Applications");
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();
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
;
407 void ApplicationModel::setSystemApplicationPolicy(SystemApplicationPolicy policy
)
409 if (d
->systemApplicationPolicy
!= policy
) {
410 d
->systemApplicationPolicy
= policy
;
415 void ApplicationModel::setPrimaryNamePolicy(PrimaryNamePolicy policy
)
417 if (policy
!= d
->primaryNamePolicy
) {
418 d
->primaryNamePolicy
= policy
;
423 ApplicationModel::PrimaryNamePolicy
ApplicationModel::primaryNamePolicy() const
425 return d
->primaryNamePolicy
;
428 void ApplicationModel::reloadMenu()
431 d
->root
= new AppNode();
432 d
->fillNode(QString(), d
->root
);
436 void ApplicationModel::checkSycocaChange()
438 if (KSycoca::self()->isChanged("services")) {
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
462 * This needs to be discussed on kde-core-devel and fixed
464 QHash
<QString
, QString
> ApplicationModelPrivate::iconNameMap()
466 static QHash
<QString
, QString
> map
;
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");
483 } // namespace Kickoff
486 #include "applicationmodel.moc"