• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

libkonq

konq_menuactions.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002    Copyright (C) 1998-2007 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017    Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "konq_menuactions.h"
00021 #include "konq_menuactions_p.h"
00022 #include <kaction.h>
00023 #include <krun.h>
00024 #include <kmimetypetrader.h>
00025 #include <kdebug.h>
00026 #include <kdesktopfileactions.h>
00027 #include <kmenu.h>
00028 #include <klocale.h>
00029 #include <kauthorized.h>
00030 #include <kconfiggroup.h>
00031 #include <kdesktopfile.h>
00032 #include <kglobal.h>
00033 #include <kicon.h>
00034 #include <kstandarddirs.h>
00035 #include <KService>
00036 #include <KServiceTypeTrader>
00037 #include <QFile>
00038 
00039 #include <QtDBus/QtDBus>
00040 
00041 static bool KIOSKAuthorizedAction(const KConfigGroup& cfg)
00042 {
00043     if ( !cfg.hasKey( "X-KDE-AuthorizeAction") ) {
00044         return true;
00045     }
00046     const QStringList list = cfg.readEntry("X-KDE-AuthorizeAction", QStringList() );
00047     for(QStringList::ConstIterator it = list.constBegin();
00048         it != list.constEnd(); ++it) {
00049         if (!KAuthorized::authorize((*it).trimmed())) {
00050             return false;
00051         }
00052     }
00053     return true;
00054 }
00055 
00056 // This helper class stores the .desktop-file actions and the servicemenus
00057 // in order to support X-KDE-Priority and X-KDE-Submenu.
00058 class PopupServices
00059 {
00060 public:
00061     ServiceList& selectList( const QString& priority, const QString& submenuName );
00062 
00063     ServiceList builtin;
00064     ServiceList user, userToplevel, userPriority;
00065     QMap<QString, ServiceList> userSubmenus, userToplevelSubmenus, userPrioritySubmenus;
00066 };
00067 
00068 ServiceList& PopupServices::selectList( const QString& priority, const QString& submenuName )
00069 {
00070     // we use the categories .desktop entry to define submenus
00071     // if none is defined, we just pop it in the main menu
00072     if (submenuName.isEmpty()) {
00073         if (priority == "TopLevel") {
00074             return userToplevel;
00075         } else if (priority == "Important") {
00076             return userPriority;
00077         }
00078     } else if (priority == "TopLevel") {
00079         return userToplevelSubmenus[submenuName];
00080     } else if (priority == "Important") {
00081         return userPrioritySubmenus[submenuName];
00082     } else {
00083         return userSubmenus[submenuName];
00084     }
00085     return user;
00086 }
00087 
00089 
00090 KonqMenuActionsPrivate::KonqMenuActionsPrivate()
00091     : QObject(),
00092       m_executeServiceActionGroup(static_cast<QWidget *>(0)),
00093       m_runApplicationActionGroup(static_cast<QWidget *>(0)),
00094       m_ownActions(static_cast<QWidget *>(0))
00095 {
00096     QObject::connect(&m_executeServiceActionGroup, SIGNAL(triggered(QAction*)),
00097                      this, SLOT(slotExecuteService(QAction*)));
00098     QObject::connect(&m_runApplicationActionGroup, SIGNAL(triggered(QAction*)),
00099                      this, SLOT(slotRunApplication(QAction*)));
00100 }
00101 
00102 int KonqMenuActionsPrivate::insertServicesSubmenus(const QMap<QString, ServiceList>& submenus,
00103                                                    QMenu* menu,
00104                                                    bool isBuiltin)
00105 {
00106     int count = 0;
00107     QMap<QString, ServiceList>::ConstIterator it;
00108     for (it = submenus.begin(); it != submenus.end(); ++it) {
00109         if (it.value().isEmpty()) {
00110             //avoid empty sub-menus
00111             continue;
00112         }
00113 
00114         QMenu* actionSubmenu = new KMenu(menu);
00115         actionSubmenu->setTitle( it.key() );
00116         actionSubmenu->menuAction()->setObjectName("services_submenu"); // for the unittest
00117         menu->addMenu(actionSubmenu);
00118         count += insertServices(it.value(), actionSubmenu, isBuiltin);
00119     }
00120 
00121     return count;
00122 }
00123 
00124 int KonqMenuActionsPrivate::insertServices(const ServiceList& list,
00125                                            QMenu* menu,
00126                                            bool isBuiltin)
00127 {
00128     int count = 0;
00129     ServiceList::const_iterator it = list.begin();
00130     for( ; it != list.end(); ++it ) {
00131         if ((*it).isSeparator()) {
00132             const QList<QAction*> actions = menu->actions();
00133             if (!actions.isEmpty() && !actions.last()->isSeparator()) {
00134                 menu->addSeparator();
00135             }
00136             continue;
00137         }
00138 
00139         if (isBuiltin || !(*it).noDisplay()) {
00140             QAction* act = new QAction(&m_ownActions);
00141             act->setObjectName("menuaction"); // for the unittest
00142             QString text = (*it).text();
00143             text.replace('&',"&&");
00144             act->setText( text );
00145             if ( !(*it).icon().isEmpty() ) {
00146                 act->setIcon( KIcon((*it).icon()) );
00147             }
00148             act->setData(QVariant::fromValue(*it));
00149             m_executeServiceActionGroup.addAction(act);
00150 
00151             menu->addAction(act); // Add to toplevel menu
00152             ++count;
00153         }
00154     }
00155 
00156     return count;
00157 }
00158 
00159 void KonqMenuActionsPrivate::slotExecuteService(QAction* act)
00160 {
00161     KServiceAction serviceAction = act->data().value<KServiceAction>();
00162     KDesktopFileActions::executeService(m_info.urlList(), serviceAction);
00163 }
00164 
00166 
00167 KonqMenuActions::KonqMenuActions()
00168     : d(new KonqMenuActionsPrivate)
00169 {
00170 }
00171 
00172 
00173 KonqMenuActions::~KonqMenuActions()
00174 {
00175     delete d;
00176 }
00177 
00178 void KonqMenuActions::setPopupMenuInfo(const KonqPopupMenuInformation& info)
00179 {
00180     d->m_info = info;
00181 }
00182 
00183 int KonqMenuActions::addActionsTo(QMenu* mainMenu)
00184 {
00185     const KFileItemList items = d->m_info.items();
00186     const KFileItem firstItem = items.first();
00187     const QString protocol = firstItem.url().protocol(); // assumed to be the same for all items
00188     const bool isLocal = firstItem.url().isLocalFile();
00189     const bool isSingleLocal = items.count() == 1 && isLocal;
00190     const KUrl::List urlList = d->m_info.urlList();
00191 
00192     PopupServices s;
00193 
00194     // 1 - Look for builtin and user-defined services
00195     if (isSingleLocal && d->m_info.mimeType() == "application/x-desktop") // .desktop file
00196     {
00197         // get builtin services, like mount/unmount
00198         s.builtin = KDesktopFileActions::builtinServices(firstItem.url());
00199         const QString path = firstItem.url().path();
00200         KDesktopFile desktopFile(path);
00201         KConfigGroup cfg = desktopFile.desktopGroup();
00202         const QString priority = cfg.readEntry("X-KDE-Priority");
00203         const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
00204 #if 0
00205         if ( cfg.readEntry("Type") == "Link" ) {
00206            d->m_url = cfg.readEntry("URL");
00207            // TODO: Do we want to make all the actions apply on the target
00208            // of the .desktop file instead of the .desktop file itself?
00209         }
00210 #endif
00211         ServiceList& list = s.selectList(priority, submenuName);
00212         list = KDesktopFileActions::userDefinedServices(path, desktopFile, true /*isLocal*/);
00213     }
00214 
00215     // 2 - Look for "servicesmenus" bindings (konqueror-specific user-defined services)
00216 
00217     // first check the .directory if this is a directory
00218     if (d->m_info.isDirectory() && isSingleLocal) {
00219         QString dotDirectoryFile = firstItem.url().path(KUrl::AddTrailingSlash).append(".directory");
00220         if (QFile::exists(dotDirectoryFile)) {
00221             const KDesktopFile desktopFile(  dotDirectoryFile );
00222             const KConfigGroup cfg = desktopFile.desktopGroup();
00223 
00224             if (KIOSKAuthorizedAction(cfg)) {
00225                 const QString priority = cfg.readEntry("X-KDE-Priority");
00226                 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
00227                 ServiceList& list = s.selectList( priority, submenuName );
00228                 list += KDesktopFileActions::userDefinedServices( dotDirectoryFile, desktopFile, true );
00229             }
00230         }
00231     }
00232 
00233     const QString commonMimeType = d->m_info.mimeType();
00234     const QString commonMimeGroup = d->m_info.mimeGroup();
00235     const KMimeType::Ptr mimeTypePtr = commonMimeType.isEmpty() ? KMimeType::Ptr() : KMimeType::mimeType(commonMimeType);
00236     const KService::List entries = KServiceTypeTrader::self()->query( "KonqPopupMenu/Plugin");
00237     KService::List::const_iterator eEnd = entries.end();
00238     for (KService::List::const_iterator it2 = entries.begin(); it2 != eEnd; ++it2 ) {
00239         QString file = KStandardDirs::locate("services", (*it2)->entryPath());
00240         KDesktopFile desktopFile( file );
00241         const KConfigGroup cfg = desktopFile.desktopGroup();
00242 
00243         if (!KIOSKAuthorizedAction(cfg)) {
00244             continue;
00245         }
00246 
00247         if ( cfg.hasKey( "X-KDE-ShowIfRunning" ) ) {
00248             const QString app = cfg.readEntry( "X-KDE-ShowIfRunning" );
00249             if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( app ) )
00250                 continue;
00251         }
00252         if ( cfg.hasKey( "X-KDE-ShowIfDBusCall" ) ) {
00253             QString calldata = cfg.readEntry( "X-KDE-ShowIfDBusCall" );
00254             QStringList parts = calldata.split(' ');
00255             const QString &app = parts.at(0);
00256             const QString &obj = parts.at(1);
00257             QString interface = parts.at(2);
00258             QString method;
00259             int pos = interface.lastIndexOf( QLatin1Char( '.' ) );
00260             if ( pos != -1 ) {
00261                 method = interface.mid(pos + 1);
00262                 interface.truncate(pos);
00263             }
00264 
00265             //if ( !QDBus::sessionBus().busService()->nameHasOwner( app ) )
00266             //    continue; //app does not exist so cannot send call
00267 
00268             QDBusMessage reply = QDBusInterface( app, obj, interface ).
00269                                  call( method, urlList.toStringList() );
00270             if ( reply.arguments().count() < 1 || reply.arguments().at(0).type() != QVariant::Bool || !reply.arguments().at(0).toBool() )
00271                 continue;
00272 
00273         }
00274         if ( cfg.hasKey( "X-KDE-Protocol" ) ) {
00275             const QString protocol = cfg.readEntry( "X-KDE-Protocol" );
00276             if (protocol.startsWith('!')) {
00277                 const QString excludedProtocol = protocol.mid(1);
00278                 if (excludedProtocol == protocol)
00279                     continue;
00280             } else if (protocol != protocol)
00281                 continue;
00282         }
00283         else if ( cfg.hasKey( "X-KDE-Protocols" ) ) {
00284             const QStringList protocols = cfg.readEntry("X-KDE-Protocols", QStringList());
00285             if (!protocols.contains(protocol))
00286                 continue;
00287         }
00288         else if (protocol == "trash") {
00289             // Require servicemenus for the trash to ask for protocol=trash explicitly.
00290             // Trashed files aren't supposed to be available for actions.
00291             // One might want a servicemenu for trash.desktop itself though.
00292             continue;
00293         }
00294 
00295         if ( cfg.hasKey( "X-KDE-Require" ) ) {
00296             const QStringList capabilities = cfg.readEntry( "X-KDE-Require" , QStringList() );
00297             if (capabilities.contains("Write") && !d->m_info.capabilities().supportsWriting())
00298                 continue;
00299         }
00300         if ( cfg.hasKey( "Actions" ) || cfg.hasKey( "X-KDE-GetActionMenu") ) {
00301             // Like KService, we support ServiceTypes, X-KDE-ServiceTypes, and MimeType.
00302             QStringList types = cfg.readEntry("ServiceTypes", QStringList());
00303             types += cfg.readEntry("X-KDE-ServiceTypes", QStringList());
00304             types += cfg.readXdgListEntry("MimeType");
00305             //kDebug() << file << types;
00306 
00307             if (types.isEmpty())
00308                 continue;
00309             const QStringList excludeTypes = cfg.readEntry( "ExcludeServiceTypes" , QStringList() );
00310             bool ok = false;
00311 
00312             // check for exact matches or a typeglob'd mimetype if we have a mimetype
00313             for (QStringList::ConstIterator it = types.constBegin();
00314                  it != types.constEnd() && !ok;
00315                  ++it)
00316             {
00317                 // first check if we have an all mimetype
00318                 bool checkTheMimetypes = false;
00319                 if (*it == "all/all" ||
00320                     *it == "allfiles" /*compat with KDE up to 3.0.3*/) {
00321                     checkTheMimetypes = true;
00322                 }
00323 
00324                 // next, do we match all files?
00325                 if (!ok &&
00326                     !d->m_info.isDirectory() &&
00327                     *it == "all/allfiles") {
00328                     checkTheMimetypes = true;
00329                 }
00330 
00331                 // if we have a mimetype, see if we have an exact or a type globbed match
00332                 if (!ok && (
00333                     (mimeTypePtr && mimeTypePtr->is(*it)) ||
00334                     (!commonMimeGroup.isEmpty() &&
00335                      ((*it).right(1) == "*" &&
00336                       (*it).left((*it).indexOf('/')) == commonMimeGroup)))) {
00337                     checkTheMimetypes = true;
00338                 }
00339 
00340                 if (checkTheMimetypes) {
00341                     ok = true;
00342                     for (QStringList::ConstIterator itex = excludeTypes.constBegin(); itex != excludeTypes.constEnd(); ++itex)
00343                     {
00344                         if( ((*itex).endsWith('*') && (*itex).left((*itex).indexOf('/')) == commonMimeGroup) ||
00345                             ((*itex) == commonMimeType) ) {
00346                             ok = false;
00347                             break;
00348                         }
00349                     }
00350                 }
00351             }
00352 
00353             if ( ok ) {
00354                 const QString priority = cfg.readEntry("X-KDE-Priority");
00355                 const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
00356 
00357                 ServiceList& list = s.selectList( priority, submenuName );
00358                 list += KDesktopFileActions::userDefinedServices(*(*it2), isLocal, urlList);
00359             }
00360         }
00361     }
00362 
00363 
00364 
00365     QMenu* actionMenu = mainMenu;
00366     int userItemCount = 0;
00367     if (s.user.count() + s.userSubmenus.count() +
00368         s.userPriority.count() + s.userPrioritySubmenus.count() > 1)
00369     {
00370         // we have more than one item, so let's make a submenu
00371         actionMenu = new KMenu(i18nc("@title:menu", "&Actions"), mainMenu);
00372         actionMenu->menuAction()->setObjectName("actions_submenu"); // for the unittest
00373         mainMenu->addMenu(actionMenu);
00374     }
00375 
00376     userItemCount += d->insertServicesSubmenus(s.userPrioritySubmenus, actionMenu, false);
00377     userItemCount += d->insertServices(s.userPriority, actionMenu, false);
00378 
00379     // see if we need to put a separator between our priority items and our regular items
00380     if (userItemCount > 0 &&
00381         (s.user.count() > 0 ||
00382          s.userSubmenus.count() > 0 ||
00383          s.builtin.count() > 0) &&
00384         !actionMenu->actions().last()->isSeparator()) {
00385         actionMenu->addSeparator();
00386     }
00387     userItemCount += d->insertServicesSubmenus(s.userSubmenus, actionMenu, false);
00388     userItemCount += d->insertServices(s.user, actionMenu, false);
00389     userItemCount += d->insertServices(s.builtin, mainMenu, true);
00390     userItemCount += d->insertServicesSubmenus(s.userToplevelSubmenus, mainMenu, false);
00391     userItemCount += d->insertServices(s.userToplevel, mainMenu, false);
00392     return userItemCount;
00393 }
00394 
00395 void KonqMenuActions::addOpenWithActionsTo(QMenu* topMenu, const QString& traderConstraint)
00396 {
00397     if (!KAuthorized::authorizeKAction("openwith"))
00398         return;
00399 
00400     const KFileItemList items = d->m_info.items();
00401     QStringList mimeTypeList;
00402     KFileItemList::const_iterator kit = items.constBegin();
00403     const KFileItemList::const_iterator kend = items.constEnd();
00404     for ( ; kit != kend; ++kit ) {
00405         if (!mimeTypeList.contains((*kit).mimetype()))
00406             mimeTypeList << (*kit).mimetype();
00407     }
00408 
00409     QString constraint = traderConstraint;
00410     const QString subConstraint = " and '%1' in ServiceTypes";
00411 
00412     QStringList::ConstIterator it = mimeTypeList.constBegin();
00413     const QStringList::ConstIterator end = mimeTypeList.constEnd();
00414     Q_ASSERT( it != end );
00415     QString firstMimeType = *it;
00416     ++it;
00417     for (; it != end ; ++it) {
00418         constraint += subConstraint.arg(*it);
00419     }
00420 
00421     KService::List offers = KMimeTypeTrader::self()->query(firstMimeType, "Application", constraint);
00422 
00423     QSet<QString> seenTexts;
00424     for (KService::List::iterator it = offers.begin(); it != offers.end(); ) {
00425         // The offer list from the KTrader returns duplicate
00426         // application entries (kde3 and kde4). Although this is a configuration
00427         // problem, duplicated entries just will be skipped here.
00428         const QString appName((*it)->name());
00429         if (!seenTexts.contains(appName)) {
00430             seenTexts.insert(appName);
00431             ++it;
00432         } else {
00433             it = offers.erase(it);
00434         }
00435     }
00436 
00437 
00439 
00440     const KFileItem firstItem = items.first();
00441     const bool isLocal = firstItem.url().isLocalFile();
00442     // "Open With..." for folders is really not very useful, especially for remote folders.
00443     // (media:/something, or trash:/, or ftp://...)
00444     if ( !d->m_info.isDirectory() || isLocal ) {
00445         if ( !topMenu->actions().isEmpty() )
00446             topMenu->addSeparator();
00447 
00448         if ( !offers.isEmpty() ) {
00449             QMenu* menu = topMenu;
00450 
00451             if ( offers.count() > 1 ) { // submenu 'open with'
00452                 // TODO i18nc("@title:menu", "Open With")
00453                 menu = new QMenu(i18n("&Open With"), topMenu);
00454                 menu->menuAction()->setObjectName("openWith_submenu"); // for the unittest
00455                 topMenu->addMenu(menu);
00456             }
00457             //kDebug() << offers.count() << "offers" << topMenu << menu;
00458 
00459             KService::List::ConstIterator it = offers.constBegin();
00460             for( ; it != offers.constEnd(); it++ ) {
00461                 KService::Ptr service = (*it);
00462 
00463                 // Skip OnlyShowIn=Foo and NotShowIn=KDE entries,
00464                 // but still offer NoDisplay=true entries, that's the
00465                 // whole point of such desktop files. This is why we don't
00466                 // use service->noDisplay() here.
00467                 const QString onlyShowIn = service->property("OnlyShowIn", QVariant::String).toString();
00468                 if ( !onlyShowIn.isEmpty() ) {
00469                     const QStringList aList = onlyShowIn.split(';', QString::SkipEmptyParts);
00470                     if (!aList.contains("KDE"))
00471                         continue;
00472                 }
00473                 const QString notShowIn = service->property("NotShowIn", QVariant::String).toString();
00474                 if ( !notShowIn.isEmpty() ) {
00475                     const QStringList aList = notShowIn.split(';', QString::SkipEmptyParts);
00476                     if (aList.contains("KDE"))
00477                         continue;
00478                 }
00479 
00480                 QString actionName(service->name().replace('&', "&&"));
00481                 if (menu == topMenu) // no submenu -> prefix single offer
00482                     actionName = i18n("Open &with %1", actionName);
00483 
00484                 KAction* act = d->m_ownActions.addAction("openwith");
00485                 act->setIcon(KIcon(service->icon()));
00486                 act->setText(actionName);
00487                 act->setData(QVariant::fromValue(service));
00488                 d->m_runApplicationActionGroup.addAction(act);
00489                 menu->addAction(act);
00490             }
00491 
00492             QString openWithActionName;
00493             if ( menu != topMenu ) { // submenu
00494                 menu->addSeparator();
00495                 // TODO i18nc("@action:inmenu Open With", "&Other...")
00496                 openWithActionName = i18n("&Other...");
00497             } else {
00498                 // TODO i18nc("@title:menu", "Open With...")
00499                 openWithActionName = i18n("&Open With...");
00500             }
00501             QAction *openWithAct = d->m_ownActions.addAction( "openwith_browse" );
00502             openWithAct->setText( openWithActionName );
00503             QObject::connect(openWithAct, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
00504             menu->addAction(openWithAct);
00505         }
00506         else // no app offers -> Open With...
00507         {
00508             KAction* act = d->m_ownActions.addAction( "openwith_browse" );
00509             // TODO i18nc("@title:menu", "Open With...")
00510             act->setText( i18n( "&Open With..." ) );
00511             QObject::connect(act, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
00512             topMenu->addAction(act);
00513         }
00514 
00515     }
00516 }
00517 
00518 void KonqMenuActionsPrivate::slotRunApplication(QAction* act)
00519 {
00520     // Is it an application, from one of the "Open With" actions
00521     KService::Ptr app = act->data().value<KService::Ptr>();
00522     Q_ASSERT(app);
00523     if (app) {
00524         KRun::run(*app, m_info.urlList(), m_info.parentWidget());
00525     }
00526 }
00527 
00528 void KonqMenuActionsPrivate::slotOpenWithDialog()
00529 {
00530     // The item 'Other...' or 'Open With...' has been selected
00531     KRun::displayOpenWithDialog(m_info.urlList(), m_info.parentWidget());
00532 }

libkonq

Skip menu "libkonq"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • Konsole
  • Libraries
  •   libkonq
Generated for API Reference by doxygen 1.5.7
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal