Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / apps / lib / konq / konq_menuactions.cpp
bloba786b1d9da5f793d8ea01e17b81892cd3291a99b
1 /* This file is part of the KDE project
2 Copyright (C) 1998-2007 David Faure <faure@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #include "konq_menuactions.h"
21 #include "konq_menuactions_p.h"
22 #include <kdebug.h>
23 #include <kdesktopfileactions.h>
24 #include <kmenu.h>
25 #include <klocale.h>
26 #include <kauthorized.h>
27 #include <kconfiggroup.h>
28 #include <kdesktopfile.h>
29 #include <kglobal.h>
30 #include <kicon.h>
31 #include <kstandarddirs.h>
32 #include <KService>
33 #include <KServiceTypeTrader>
34 #include <QFile>
36 #include <QtDBus/QtDBus>
38 static bool KIOSKAuthorizedAction(const KConfigGroup& cfg)
40 if ( !cfg.hasKey( "X-KDE-AuthorizeAction") ) {
41 return true;
43 const QStringList list = cfg.readEntry("X-KDE-AuthorizeAction", QStringList() );
44 for(QStringList::ConstIterator it = list.begin();
45 it != list.end(); ++it) {
46 if (!KAuthorized::authorize((*it).trimmed())) {
47 return false;
50 return true;
53 // This helper class stores the .desktop-file actions and the servicemenus
54 // in order to support X-KDE-Priority and X-KDE-Submenu.
55 class PopupServices
57 public:
58 ServiceList& selectList( const QString& priority, const QString& submenuName );
60 ServiceList builtin;
61 ServiceList user, userToplevel, userPriority;
62 QMap<QString, ServiceList> userSubmenus, userToplevelSubmenus, userPrioritySubmenus;
65 ServiceList& PopupServices::selectList( const QString& priority, const QString& submenuName )
67 // we use the categories .desktop entry to define submenus
68 // if none is defined, we just pop it in the main menu
69 if (submenuName.isEmpty()) {
70 if (priority == "TopLevel") {
71 return userToplevel;
72 } else if (priority == "Important") {
73 return userPriority;
75 } else if (priority == "TopLevel") {
76 return userToplevelSubmenus[submenuName];
77 } else if (priority == "Important") {
78 return userPrioritySubmenus[submenuName];
79 } else {
80 return userSubmenus[submenuName];
82 return user;
85 ////
87 KonqMenuActionsPrivate::KonqMenuActionsPrivate()
88 : QObject(),
89 m_isDirectory(false),
90 m_readOnly(false),
91 m_executeServiceActionGroup(static_cast<QWidget *>(0)),
92 m_ownActions(static_cast<QWidget *>(0))
94 QObject::connect(&m_executeServiceActionGroup, SIGNAL(triggered(QAction*)),
95 this, SLOT(slotExecuteService(QAction*)));
98 int KonqMenuActionsPrivate::insertServicesSubmenus(const QMap<QString, ServiceList>& submenus,
99 QMenu* menu,
100 bool isBuiltin)
102 int count = 0;
103 QMap<QString, ServiceList>::ConstIterator it;
104 for (it = submenus.begin(); it != submenus.end(); ++it) {
105 if (it.value().isEmpty()) {
106 //avoid empty sub-menus
107 continue;
110 QMenu* actionSubmenu = new KMenu(menu);
111 actionSubmenu->setTitle( it.key() );
112 menu->menuAction()->setObjectName("services_submenu"); // for the unittest
113 menu->addMenu(actionSubmenu);
114 count += insertServices(it.value(), actionSubmenu, isBuiltin);
117 return count;
120 int KonqMenuActionsPrivate::insertServices(const ServiceList& list,
121 QMenu* menu,
122 bool isBuiltin)
124 int count = 0;
125 ServiceList::const_iterator it = list.begin();
126 for( ; it != list.end(); ++it ) {
127 if ((*it).isSeparator()) {
128 const QList<QAction*> actions = menu->actions();
129 if (!actions.isEmpty() && !actions.last()->isSeparator()) {
130 menu->addSeparator();
132 continue;
135 if (isBuiltin || !(*it).noDisplay()) {
136 QAction* act = new QAction(&m_ownActions);
137 act->setObjectName("menuaction"); // for the unittest
138 QString text = (*it).text();
139 text.replace('&',"&&");
140 act->setText( text );
141 if ( !(*it).icon().isEmpty() ) {
142 act->setIcon( KIcon((*it).icon()) );
144 // act->setData(...);
145 m_executeServiceActionGroup.addAction(act);
147 menu->addAction(act); // Add to toplevel menu
149 m_mapPopupServices.insert(act, *it);
150 ++count;
154 return count;
157 void KonqMenuActionsPrivate::slotExecuteService(QAction* act)
159 QMap<QAction *,KServiceAction>::Iterator it = m_mapPopupServices.find(act);
160 Q_ASSERT(it != m_mapPopupServices.end());
161 if (it != m_mapPopupServices.end()) {
162 KDesktopFileActions::executeService(m_urlList, it.value());
166 ////
168 KonqMenuActions::KonqMenuActions()
169 : d(new KonqMenuActionsPrivate)
173 void KonqMenuActions::setItems(const KFileItemList& items)
175 Q_ASSERT(!items.isEmpty());
176 d->m_items = items;
177 d->m_mimeType = items.first().mimetype();
178 d->m_mimeGroup = d->m_mimeType.left(d->m_mimeType.indexOf('/'));
179 d->m_isDirectory = items.first().isDir();
180 d->m_urlList = items.urlList();
181 if (items.count() > 1) {
182 KFileItemList::const_iterator kit = items.begin();
183 const KFileItemList::const_iterator kend = items.end();
184 for ( ; kit != kend; ++kit ) {
185 const QString itemMimeType = (*kit).mimetype();
186 if (d->m_mimeType != itemMimeType) {
187 d->m_mimeType.clear();
188 if (d->m_mimeGroup != itemMimeType.left(itemMimeType.indexOf('/')))
189 d->m_mimeGroup.clear(); // mimetype groups are different as well!
191 if (d->m_isDirectory && !(*kit).isDir())
192 d->m_isDirectory = false;
195 if (d->m_url.isEmpty())
196 d->m_url = d->m_urlList.first();
199 void KonqMenuActions::setUrl(const KUrl& url)
201 Q_ASSERT(!url.isEmpty());
202 d->m_url = url;
205 void KonqMenuActions::setReadOnly(bool ro)
207 d->m_readOnly = ro;
210 int KonqMenuActions::addActionsTo(QMenu* mainMenu)
212 d->m_mapPopupServices.clear();
213 const bool isLocal = d->m_url.isLocalFile();
214 const bool isSingleLocal = d->m_urlList.count() == 1 && isLocal;
216 PopupServices s;
218 // 1 - Look for builtin and user-defined services
219 if (isSingleLocal && d->m_mimeType == "application/x-desktop") // .desktop file
221 // get builtin services, like mount/unmount
222 s.builtin = KDesktopFileActions::builtinServices( d->m_url );
223 const QString path = d->m_url.path();
224 KDesktopFile desktopFile(path);
225 KConfigGroup cfg = desktopFile.desktopGroup();
226 const QString priority = cfg.readEntry("X-KDE-Priority");
227 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
228 if ( cfg.readEntry("Type") == "Link" ) {
229 d->m_url = cfg.readEntry("URL");
230 // TODO: Do we want to make all the actions apply on the target
231 // of the .desktop file instead of the .desktop file itself?
233 ServiceList& list = s.selectList( priority, submenuName );
234 list = KDesktopFileActions::userDefinedServices( path, desktopFile, d->m_url.isLocalFile() );
237 // 2 - Look for "servicesmenus" bindings (konqueror-specific user-defined services)
239 // first check the .directory if this is a directory
240 if (d->m_isDirectory && isSingleLocal) {
241 QString dotDirectoryFile = d->m_url.path( KUrl::AddTrailingSlash ).append(".directory");
242 if (QFile::exists(dotDirectoryFile)) {
243 KDesktopFile desktopFile( dotDirectoryFile );
244 const KConfigGroup cfg = desktopFile.desktopGroup();
246 if (KIOSKAuthorizedAction(cfg)) {
247 const QString priority = cfg.readEntry("X-KDE-Priority");
248 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
249 ServiceList& list = s.selectList( priority, submenuName );
250 list += KDesktopFileActions::userDefinedServices( dotDirectoryFile, desktopFile, true );
255 const KMimeType::Ptr mimeTypePtr = d->m_mimeType.isEmpty() ? KMimeType::Ptr() : KMimeType::mimeType(d->m_mimeType);
256 const KService::List entries = KServiceTypeTrader::self()->query( "KonqPopupMenu/Plugin");
257 KService::List::const_iterator eEnd = entries.end();
258 for (KService::List::const_iterator it2 = entries.begin(); it2 != eEnd; it2++ ) {
259 QString file = KStandardDirs::locate("services", (*it2)->entryPath());
260 KDesktopFile desktopFile( file );
261 const KConfigGroup cfg = desktopFile.desktopGroup();
263 if (!KIOSKAuthorizedAction(cfg)) {
264 continue;
267 if ( cfg.hasKey( "X-KDE-ShowIfRunning" ) ) {
268 const QString app = cfg.readEntry( "X-KDE-ShowIfRunning" );
269 if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( app ) )
270 continue;
272 if ( cfg.hasKey( "X-KDE-ShowIfDBusCall" ) ) {
273 QString calldata = cfg.readEntry( "X-KDE-ShowIfDBusCall" );
274 QStringList parts = calldata.split(' ');
275 const QString &app = parts.at(0);
276 const QString &obj = parts.at(1);
277 QString interface = parts.at(2);
278 QString method;
279 int pos = interface.lastIndexOf( QLatin1Char( '.' ) );
280 if ( pos != -1 ) {
281 method = interface.mid(pos + 1);
282 interface.truncate(pos);
285 //if ( !QDBus::sessionBus().busService()->nameHasOwner( app ) )
286 // continue; //app does not exist so cannot send call
288 QDBusMessage reply = QDBusInterface( app, obj, interface ).
289 call( method, d->m_urlList.toStringList() );
290 if ( reply.arguments().count() < 1 || reply.arguments().at(0).type() != QVariant::Bool || !reply.arguments().at(0).toBool() )
291 continue;
294 if ( cfg.hasKey( "X-KDE-Protocol" ) ) {
295 const QString protocol = cfg.readEntry( "X-KDE-Protocol" );
296 if (protocol.startsWith('!')) {
297 const QString excludedProtocol = protocol.mid(1);
298 if (excludedProtocol == d->m_url.protocol())
299 continue;
300 } else if (protocol != d->m_url.protocol())
301 continue;
303 else if ( cfg.hasKey( "X-KDE-Protocols" ) ) {
304 const QStringList protocols = cfg.readEntry( "X-KDE-Protocols", QStringList() );
305 if ( !protocols.contains( d->m_url.protocol() ) )
306 continue;
308 else if ( d->m_url.protocol() == "trash" || d->m_url.url().startsWith( "system:/trash" ) ) {
309 // Require servicemenus for the trash to ask for protocol=trash explicitly.
310 // Trashed files aren't supposed to be available for actions.
311 // One might want a servicemenu for trash.desktop itself though.
312 continue;
315 if ( cfg.hasKey( "X-KDE-Require" ) ) {
316 const QStringList capabilities = cfg.readEntry( "X-KDE-Require" , QStringList() );
317 if ( capabilities.contains( "Write" ) && d->m_readOnly )
318 continue;
320 if ( cfg.hasKey( "Actions" ) || cfg.hasKey( "X-KDE-GetActionMenu") ) {
321 // Like KService, we support ServiceTypes, X-KDE-ServiceTypes, and MimeType.
322 QStringList types = cfg.readEntry("ServiceTypes", QStringList());
323 types += cfg.readEntry("X-KDE-ServiceTypes", QStringList());
324 types += cfg.readXdgListEntry("MimeType");
325 kDebug() << file << types;
327 if (types.isEmpty())
328 continue;
329 const QStringList excludeTypes = cfg.readEntry( "ExcludeServiceTypes" , QStringList() );
330 bool ok = false;
332 // check for exact matches or a typeglob'd mimetype if we have a mimetype
333 for (QStringList::ConstIterator it = types.begin();
334 it != types.end() && !ok;
335 ++it)
337 // first check if we have an all mimetype
338 bool checkTheMimetypes = false;
339 if (*it == "all/all" ||
340 *it == "allfiles" /*compat with KDE up to 3.0.3*/) {
341 checkTheMimetypes = true;
344 // next, do we match all files?
345 if (!ok &&
346 !d->m_isDirectory &&
347 *it == "all/allfiles") {
348 checkTheMimetypes = true;
351 // if we have a mimetype, see if we have an exact or a type globbed match
352 if (!ok &&
353 (mimeTypePtr && mimeTypePtr->is(*it)) ||
354 (!d->m_mimeGroup.isEmpty() &&
355 ((*it).right(1) == "*" &&
356 (*it).left((*it).indexOf('/')) == d->m_mimeGroup))) {
357 checkTheMimetypes = true;
360 if (checkTheMimetypes) {
361 ok = true;
362 for (QStringList::ConstIterator itex = excludeTypes.begin(); itex != excludeTypes.end(); ++itex)
364 if( ((*itex).endsWith('*') && (*itex).left((*itex).indexOf('/')) == d->m_mimeGroup) ||
365 ((*itex) == d->m_mimeType) ) {
366 ok = false;
367 break;
373 if ( ok ) {
374 const QString priority = cfg.readEntry("X-KDE-Priority");
375 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
377 ServiceList& list = s.selectList( priority, submenuName );
378 list += KDesktopFileActions::userDefinedServices( *(*it2), d->m_url.isLocalFile(), d->m_urlList );
385 QMenu* actionMenu = mainMenu;
386 int userItemCount = 0;
387 if (s.user.count() + s.userSubmenus.count() +
388 s.userPriority.count() + s.userPrioritySubmenus.count() > 1)
390 // we have more than one item, so let's make a submenu
391 actionMenu = new KMenu(i18nc("@title:menu", "Actions"), mainMenu);
392 actionMenu->menuAction()->setObjectName("actions_submenu"); // for the unittest
393 mainMenu->addMenu(actionMenu);
396 userItemCount += d->insertServicesSubmenus(s.userPrioritySubmenus, actionMenu, false);
397 userItemCount += d->insertServices(s.userPriority, actionMenu, false);
399 // see if we need to put a separator between our priority items and our regular items
400 if (userItemCount > 0 &&
401 (s.user.count() > 0 ||
402 s.userSubmenus.count() > 0 ||
403 s.builtin.count() > 0) &&
404 !actionMenu->actions().last()->isSeparator()) {
405 actionMenu->addSeparator();
407 userItemCount += d->insertServicesSubmenus(s.userSubmenus, actionMenu, false);
408 userItemCount += d->insertServices(s.user, actionMenu, false);
409 userItemCount += d->insertServices(s.builtin, mainMenu, true);
410 userItemCount += d->insertServicesSubmenus(s.userToplevelSubmenus, mainMenu, false);
411 userItemCount += d->insertServices(s.userToplevel, mainMenu, false);
412 return userItemCount;