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"
23 #include <kdesktopfileactions.h>
26 #include <kauthorized.h>
27 #include <kconfiggroup.h>
28 #include <kdesktopfile.h>
31 #include <kstandarddirs.h>
33 #include <KServiceTypeTrader>
36 #include <QtDBus/QtDBus>
38 static bool KIOSKAuthorizedAction(const KConfigGroup
& cfg
)
40 if ( !cfg
.hasKey( "X-KDE-AuthorizeAction") ) {
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())) {
53 // This helper class stores the .desktop-file actions and the servicemenus
54 // in order to support X-KDE-Priority and X-KDE-Submenu.
58 ServiceList
& selectList( const QString
& priority
, const QString
& submenuName
);
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") {
72 } else if (priority
== "Important") {
75 } else if (priority
== "TopLevel") {
76 return userToplevelSubmenus
[submenuName
];
77 } else if (priority
== "Important") {
78 return userPrioritySubmenus
[submenuName
];
80 return userSubmenus
[submenuName
];
87 KonqMenuActionsPrivate::KonqMenuActionsPrivate()
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
,
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
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
);
120 int KonqMenuActionsPrivate::insertServices(const ServiceList
& list
,
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();
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
);
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());
168 KonqMenuActions::KonqMenuActions()
169 : d(new KonqMenuActionsPrivate
)
173 void KonqMenuActions::setItems(const KFileItemList
& items
)
175 Q_ASSERT(!items
.isEmpty());
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());
205 void KonqMenuActions::setReadOnly(bool 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
;
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
)) {
267 if ( cfg
.hasKey( "X-KDE-ShowIfRunning" ) ) {
268 const QString app
= cfg
.readEntry( "X-KDE-ShowIfRunning" );
269 if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( app
) )
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);
279 int pos
= interface
.lastIndexOf( QLatin1Char( '.' ) );
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() )
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())
300 } else if (protocol
!= d
->m_url
.protocol())
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() ) )
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.
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
)
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
;
329 const QStringList excludeTypes
= cfg
.readEntry( "ExcludeServiceTypes" , QStringList() );
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
;
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?
347 *it
== "all/allfiles") {
348 checkTheMimetypes
= true;
351 // if we have a mimetype, see if we have an exact or a type globbed match
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
) {
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
) ) {
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
;