The code for handling "open with" action/submenus and servicemenus (RMB / 
Actions / ...)
is currently in libkonq (and used by dolphin and konqueror), but I have 
received a number
of requests for moving that code up to kdelibs.
It is already used by the plasma "Locations" runner AFAIK, which won't have to 
link to libkonq
anymore, and at least "Open With <default file manager>" will be used in 
KFileDialog,
and I'm sure there are more potential users of "open with" and servicemenus.

The attached patch adds two classes to kdelibs:
* KFileItemListProperties (which stores common properties for a list of items,
 and determines which capabilities are available, like all-readable, 
all-writable, etc.). Unittest included.
* KFileItemActions - the class which implements "Open with" and servicemenu 
actions.

The first one belongs obviously to kio (could even have been part of 
KFileItemList
if had a d pointer... that's for kde5); I wasn't exactly sure about the second 
one,
could have been for libkfile too. OTOH the goal of this move is do generalize
the use of those actions outside of file managers, so libkio is probably better.

Thanks for reviewing the patch (especially the API, which I already cleaned up
compared to what was in libkonq).

Of course I also ported the libkonq classes to those new classes (can't remove
them, for BC reasons, but they are now mere wrappers).

-- 
David Faure, fa...@kde.org, sponsored by Qt Software @ Nokia to work on KDE,
Konqueror (http://www.konqueror.org), and KOffice (http://www.koffice.org).
Index: kio/kfileitemactions.h
===================================================================
--- kio/kfileitemactions.h	(revision 0)
+++ kio/kfileitemactions.h	(revision 0)
@@ -0,0 +1,111 @@
+/* This file is part of the KDE project
+   Copyright (C) 1998-2009 David Faure <fa...@kde.org>
+
+   This library is free software; you can redistribute it and/or modify
+   it under the terms of the GNU Library General Public License as published
+   by the Free Software Foundation; either version 2 of the License or
+   ( at your option ) version 3 or, at the discretion of KDE e.V.
+   ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFILEITEMACTIONS_H
+#define KFILEITEMACTIONS_H
+
+#include <kfileitem.h>
+#include <kio/kio_export.h>
+
+class KFileItemListProperties;
+class KAction;
+class QMenu;
+class KFileItemActionsPrivate;
+
+/**
+ * This class creates and handles the actions for a url (or urls) in a popupmenu.
+ *
+ * This includes:
+ * @li "open with <application>" actions, but also
+ * @li builtin services like mount/unmount for old-style device desktop files
+ * @li user-defined actions for a .desktop file, defined in the file itself (see the desktop entry standard)
+ * @li servicemenus actions, defined in .desktop files and selected based on the mimetype of the url
+ *
+ * @since 4.3
+ */
+class KIO_EXPORT KFileItemActions
+{
+public:
+    /**
+     * Creates a KFileItemActions instance.
+     * Note that this instance must stay alive for at least as long as the popupmenu;
+     * it has the slots for the actions created by addOpenWithActionsTo/addServiceActionsTo.
+     */
+    KFileItemActions();
+
+    /**
+     * Destructor
+     */
+    ~KFileItemActions();
+
+    /**
+     * Sets all the data for the next instance of the popupmenu.
+     * @see KFileItemListProperties
+     */
+    void setItemListProperties(const KFileItemListProperties& itemList);
+
+    /**
+     * Set the parent widget for any dialogs being shown.
+     *
+     * This should normally be your mainwindow, not a popup menu,
+     * so that it still exists even after the popup is closed
+     * (e.g. error message from KRun) and so that QAction::setStatusTip
+     * can find a statusbar, too.
+     */
+    void setParentWidget(QWidget* widget);
+
+    /**
+     * Generate the "Open With <Application>" actions, and adds them to the @p menu.
+     * All actions are created as children of the menu.
+     * @param menu the QMenu where to add actions
+     * @param traderConstraint this constraint allows to exclude the current application
+     * from the "open with" list. Example: "DesktopEntryName != 'kfmclient'".
+     */
+    void addOpenWithActionsTo(QMenu* menu, const QString& traderConstraint);
+
+    /**
+     * Returns an action for the preferred application only.
+     * @param traderConstraint this constraint allows to exclude the current application
+     * from the "open with" list. Example: "DesktopEntryName != 'kfmclient'".
+     * @return the action - or 0 if no application was found.
+     */
+    KAction* preferredOpenWithAction(const QString& traderConstraint);
+
+    /**
+     * Generate the user-defined actions and submenus, and adds them to the @p menu.
+     * User-defined actions include:
+     * - builtin services like mount/unmount for old-style device desktop files
+     * - user-defined actions for a .desktop file, defined in the file itself (see the desktop entry standard)
+     * - servicemenus actions, defined in .desktop files and selected based on the mimetype of the url
+     *
+     * When KFileItemListProperties::supportsWriting() is false, actions that modify the files are not shown.
+     * This is controlled by Require=Write in the servicemenu desktop files.
+     *
+     * All actions are created as children of the menu.
+     * @return the number of actions added
+     */
+    int addServiceActionsTo(QMenu* menu);
+
+private:
+    KFileItemActionsPrivate* const d;
+};
+
+#endif /* KFILEITEMACTIONS_H */
+
Index: kio/kfileitemlistproperties.h
===================================================================
--- kio/kfileitemlistproperties.h	(revision 0)
+++ kio/kfileitemlistproperties.h	(revision 0)
@@ -0,0 +1,139 @@
+/* This file is part of the KDE project
+   Copyright (C) 2008 by Peter Penz <peter.p...@gmx.at>
+   Copyright (C) 2008 by George Goldberg <grundleb...@googlemail.com>
+   Copyright     2009 David Faure <fa...@kde.org>
+
+   This library is free software; you can redistribute it and/or modify
+   it under the terms of the GNU Library General Public License as published
+   by the Free Software Foundation; either version 2 of the License or
+   ( at your option ) version 3 or, at the discretion of KDE e.V.
+   ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KFILEITEMLISTPROPERTIES_H
+#define KFILEITEMLISTPROPERTIES_H
+
+#include <kio/kio_export.h>
+
+#include <kurl.h>
+#include <QtCore/QSharedDataPointer>
+
+class KFileItemListPropertiesPrivate;
+class KFileItemList;
+
+/**
+ * @brief Provides information about the common properties of a group of
+ *        KFileItem objects.
+ *
+ * Given a list of KFileItems, this class can determine (and cache) the common
+ * mimetype for all items, whether all items are directories, whether all items
+ * are readable, writable, etc.
+ * As soon as one file item does not support a specific capability (read, write etc.),
+ * it is marked as unsupported for all items.
+ *
+ * This class is implicitly shared, which means it can be used as a value and
+ * copied around at almost no cost.
+ *
+ * @since 4.3
+ */
+class KIO_EXPORT KFileItemListProperties
+{
+public:
+    /**
+     * @brief Default constructor. Use setItems to specify the items.
+     */
+    KFileItemListProperties();
+    /**
+     * @brief Constructor that takes a KFileItemList and sets the capabilities
+     *        supported by all the FileItems as true.
+     * @param items The list of items that are to have their supported
+     *              capabilities checked.
+     */
+    KFileItemListProperties(const KFileItemList& items);
+    /**
+     * @brief Copy constructor
+     */
+    KFileItemListProperties(const KFileItemListProperties&);
+    /**
+     * @brief Destructor
+     */
+    virtual ~KFileItemListProperties();
+    /**
+     * @brief Assignment operator
+     */
+    KFileItemListProperties& operator=(const KFileItemListProperties& other);
+    /**
+     * Sets the items that are to have their supported capabilities checked.
+     */
+    void setItems(const KFileItemList& items);
+
+    /**
+     * @brief Check if reading capability is supported
+     * @return true if all the FileItems can be read, otherwise false.
+     */
+    bool supportsReading() const;
+    /**
+     * @brief Check if deleting capability is supported
+     * @return true if all the FileItems can be deleted, otherwise false.
+     */
+    bool supportsDeleting() const;
+    /**
+     * @brief Check if writing capability is supported
+     * (file managers use this mostly for directories)
+     * @return true if all the FileItems can be written to, otherwise false.
+     */
+    bool supportsWriting() const;
+    /**
+     * @brief Check if moving capability is supported
+     * @return true if all the FileItems can be moved, otherwise false.
+     */
+    bool supportsMoving() const;
+    /**
+     * @brief Check if files are local
+     * @return true if all the FileItems are local, otherwise there is one or more
+     *         remote file, so false.
+     */
+    bool isLocal() const;
+
+    /**
+     * List of fileitems passed to the constructor or to setItems().
+     */
+    KFileItemList items() const;
+
+    /**
+     * List of urls, gathered from the fileitems
+     */
+    KUrl::List urlList() const;
+
+    /**
+     * @return true if all items are directories
+     */
+    bool isDirectory() const;
+
+    /**
+     * @return the mimetype of all items, if they all have the same, otherwise empty
+     */
+    QString mimeType() const;
+
+    /**
+     * @return the mimetype group (e.g. "text") of all items, if they all have the same, otherwise empty
+     */
+    QString mimeGroup() const;
+
+private:
+    /** @brief d-pointer */
+    QSharedDataPointer<KFileItemListPropertiesPrivate> d;
+};
+
+#endif /* KFILEITEMLISTPROPERTIES_H */
+
Index: kio/kfileitem.h
===================================================================
--- kio/kfileitem.h	(revision 940408)
+++ kio/kfileitem.h	(working copy)
@@ -624,6 +624,8 @@
   /// @return the list of target URLs that those items represent
   /// @since 4.2
   KUrl::List targetUrlList() const;
+
+  // TODO KDE-5 add d pointer here so that we can merge KFileItemListProperties into KFileItemList
 };
 
 KIO_EXPORT QDataStream & operator<< ( QDataStream & s, const KFileItem & a );
Index: kio/kfileitemactions.cpp
===================================================================
--- kio/kfileitemactions.cpp	(revision 0)
+++ kio/kfileitemactions.cpp	(revision 0)
@@ -0,0 +1,583 @@
+/* This file is part of the KDE project
+   Copyright (C) 1998-2009 David Faure <fa...@kde.org>
+
+   This library is free software; you can redistribute it and/or modify
+   it under the terms of the GNU Library General Public License as published
+   by the Free Software Foundation; either version 2 of the License or
+   ( at your option ) version 3 or, at the discretion of KDE e.V.
+   ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+#include "kfileitemactions.h"
+#include "kfileitemactions_p.h"
+#include <kaction.h>
+#include <krun.h>
+#include <kmimetypetrader.h>
+#include <kdebug.h>
+#include <kdesktopfileactions.h>
+#include <kmenu.h>
+#include <klocale.h>
+#include <kauthorized.h>
+#include <kconfiggroup.h>
+#include <kdesktopfile.h>
+#include <kglobal.h>
+#include <kicon.h>
+#include <kstandarddirs.h>
+#include <kservice.h>
+#include <kservicetypetrader.h>
+#include <QFile>
+
+#include <QtDBus/QtDBus>
+
+static bool KIOSKAuthorizedAction(const KConfigGroup& cfg)
+{
+    if ( !cfg.hasKey( "X-KDE-AuthorizeAction") ) {
+        return true;
+    }
+    const QStringList list = cfg.readEntry("X-KDE-AuthorizeAction", QStringList() );
+    for(QStringList::ConstIterator it = list.constBegin();
+        it != list.constEnd(); ++it) {
+        if (!KAuthorized::authorize((*it).trimmed())) {
+            return false;
+        }
+    }
+    return true;
+}
+
+// This helper class stores the .desktop-file actions and the servicemenus
+// in order to support X-KDE-Priority and X-KDE-Submenu.
+namespace KIO {
+class PopupServices
+{
+public:
+    ServiceList& selectList( const QString& priority, const QString& submenuName );
+
+    ServiceList builtin;
+    ServiceList user, userToplevel, userPriority;
+    QMap<QString, ServiceList> userSubmenus, userToplevelSubmenus, userPrioritySubmenus;
+};
+
+ServiceList& PopupServices::selectList( const QString& priority, const QString& submenuName )
+{
+    // we use the categories .desktop entry to define submenus
+    // if none is defined, we just pop it in the main menu
+    if (submenuName.isEmpty()) {
+        if (priority == "TopLevel") {
+            return userToplevel;
+        } else if (priority == "Important") {
+            return userPriority;
+        }
+    } else if (priority == "TopLevel") {
+        return userToplevelSubmenus[submenuName];
+    } else if (priority == "Important") {
+        return userPrioritySubmenus[submenuName];
+    } else {
+        return userSubmenus[submenuName];
+    }
+    return user;
+}
+} // namespace
+
+////
+
+KFileItemActionsPrivate::KFileItemActionsPrivate()
+    : QObject(),
+      m_executeServiceActionGroup(static_cast<QWidget *>(0)),
+      m_runApplicationActionGroup(static_cast<QWidget *>(0)),
+      m_parentWidget(0)
+{
+    QObject::connect(&m_executeServiceActionGroup, SIGNAL(triggered(QAction*)),
+                     this, SLOT(slotExecuteService(QAction*)));
+    QObject::connect(&m_runApplicationActionGroup, SIGNAL(triggered(QAction*)),
+                     this, SLOT(slotRunApplication(QAction*)));
+}
+
+KFileItemActionsPrivate::~KFileItemActionsPrivate()
+{
+    qDeleteAll(m_ownActions);
+}
+
+int KFileItemActionsPrivate::insertServicesSubmenus(const QMap<QString, ServiceList>& submenus,
+                                                   QMenu* menu,
+                                                   bool isBuiltin)
+{
+    int count = 0;
+    QMap<QString, ServiceList>::ConstIterator it;
+    for (it = submenus.begin(); it != submenus.end(); ++it) {
+        if (it.value().isEmpty()) {
+            //avoid empty sub-menus
+            continue;
+        }
+
+        QMenu* actionSubmenu = new KMenu(menu);
+        actionSubmenu->setTitle( it.key() );
+        actionSubmenu->menuAction()->setObjectName("services_submenu"); // for the unittest
+        menu->addMenu(actionSubmenu);
+        count += insertServices(it.value(), actionSubmenu, isBuiltin);
+    }
+
+    return count;
+}
+
+int KFileItemActionsPrivate::insertServices(const ServiceList& list,
+                                           QMenu* menu,
+                                           bool isBuiltin)
+{
+    int count = 0;
+    ServiceList::const_iterator it = list.begin();
+    for( ; it != list.end(); ++it ) {
+        if ((*it).isSeparator()) {
+            const QList<QAction*> actions = menu->actions();
+            if (!actions.isEmpty() && !actions.last()->isSeparator()) {
+                menu->addSeparator();
+            }
+            continue;
+        }
+
+        if (isBuiltin || !(*it).noDisplay()) {
+            KAction *act = new KAction(m_parentWidget);
+            m_ownActions.append(act);
+            act->setObjectName("menuaction"); // for the unittest
+            QString text = (*it).text();
+            text.replace('&',"&&");
+            act->setText( text );
+            if ( !(*it).icon().isEmpty() ) {
+                act->setIcon( KIcon((*it).icon()) );
+            }
+            act->setData(QVariant::fromValue(*it));
+            m_executeServiceActionGroup.addAction(act);
+
+            menu->addAction(act); // Add to toplevel menu
+            ++count;
+        }
+    }
+
+    return count;
+}
+
+void KFileItemActionsPrivate::slotExecuteService(QAction* act)
+{
+    KServiceAction serviceAction = act->data().value<KServiceAction>();
+    KDesktopFileActions::executeService(m_props.urlList(), serviceAction);
+}
+
+////
+
+KFileItemActions::KFileItemActions()
+    : d(new KFileItemActionsPrivate)
+{
+}
+
+
+KFileItemActions::~KFileItemActions()
+{
+    delete d;
+}
+
+void KFileItemActions::setItemListProperties(const KFileItemListProperties& itemListProperties)
+{
+    d->m_props = itemListProperties;
+}
+
+int KFileItemActions::addServiceActionsTo(QMenu* mainMenu)
+{
+    const KFileItemList items = d->m_props.items();
+    const KFileItem firstItem = items.first();
+    const QString protocol = firstItem.url().protocol(); // assumed to be the same for all items
+    const bool isLocal = firstItem.url().isLocalFile();
+    const bool isSingleLocal = items.count() == 1 && isLocal;
+    const KUrl::List urlList = d->m_props.urlList();
+
+    KIO::PopupServices s;
+
+    // 1 - Look for builtin and user-defined services
+    if (isSingleLocal && d->m_props.mimeType() == "application/x-desktop") // .desktop file
+    {
+        // get builtin services, like mount/unmount
+        s.builtin = KDesktopFileActions::builtinServices(firstItem.url());
+        const QString path = firstItem.url().path();
+        KDesktopFile desktopFile(path);
+        KConfigGroup cfg = desktopFile.desktopGroup();
+        const QString priority = cfg.readEntry("X-KDE-Priority");
+        const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
+#if 0
+        if ( cfg.readEntry("Type") == "Link" ) {
+           d->m_url = cfg.readEntry("URL");
+           // TODO: Do we want to make all the actions apply on the target
+           // of the .desktop file instead of the .desktop file itself?
+        }
+#endif
+        ServiceList& list = s.selectList(priority, submenuName);
+        list = KDesktopFileActions::userDefinedServices(path, desktopFile, true /*isLocal*/);
+    }
+
+    // 2 - Look for "servicemenus" bindings (user-defined services)
+
+    // first check the .directory if this is a directory
+    if (d->m_props.isDirectory() && isSingleLocal) {
+        QString dotDirectoryFile = firstItem.url().path(KUrl::AddTrailingSlash).append(".directory");
+        if (QFile::exists(dotDirectoryFile)) {
+            const KDesktopFile desktopFile(  dotDirectoryFile );
+            const KConfigGroup cfg = desktopFile.desktopGroup();
+
+            if (KIOSKAuthorizedAction(cfg)) {
+                const QString priority = cfg.readEntry("X-KDE-Priority");
+                const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
+                ServiceList& list = s.selectList( priority, submenuName );
+                list += KDesktopFileActions::userDefinedServices( dotDirectoryFile, desktopFile, true );
+            }
+        }
+    }
+
+    const KConfig config( "kservicemenurc", KConfig::NoGlobals );
+    const KConfigGroup showGroup = config.group( "Show" );
+
+    const QString commonMimeType = d->m_props.mimeType();
+    const QString commonMimeGroup = d->m_props.mimeGroup();
+    const KMimeType::Ptr mimeTypePtr = commonMimeType.isEmpty() ? KMimeType::Ptr() : KMimeType::mimeType(commonMimeType);
+    const KService::List entries = KServiceTypeTrader::self()->query( "KonqPopupMenu/Plugin");
+    KService::List::const_iterator eEnd = entries.end();
+    for (KService::List::const_iterator it2 = entries.begin(); it2 != eEnd; ++it2 ) {
+        QString file = KStandardDirs::locate("services", (*it2)->entryPath());
+        KDesktopFile desktopFile( file );
+        const KConfigGroup cfg = desktopFile.desktopGroup();
+
+        if (!KIOSKAuthorizedAction(cfg)) {
+            continue;
+        }
+
+        if ( cfg.hasKey( "X-KDE-ShowIfRunning" ) ) {
+            const QString app = cfg.readEntry( "X-KDE-ShowIfRunning" );
+            if ( QDBusConnection::sessionBus().interface()->isServiceRegistered( app ) )
+                continue;
+        }
+        if ( cfg.hasKey( "X-KDE-ShowIfDBusCall" ) ) {
+            QString calldata = cfg.readEntry( "X-KDE-ShowIfDBusCall" );
+            QStringList parts = calldata.split(' ');
+            const QString &app = parts.at(0);
+            const QString &obj = parts.at(1);
+            QString interface = parts.at(2);
+            QString method;
+            int pos = interface.lastIndexOf( QLatin1Char( '.' ) );
+            if ( pos != -1 ) {
+                method = interface.mid(pos + 1);
+                interface.truncate(pos);
+            }
+
+            //if ( !QDBus::sessionBus().busService()->nameHasOwner( app ) )
+            //    continue; //app does not exist so cannot send call
+
+            QDBusMessage reply = QDBusInterface( app, obj, interface ).
+                                 call( method, urlList.toStringList() );
+            if ( reply.arguments().count() < 1 || reply.arguments().at(0).type() != QVariant::Bool || !reply.arguments().at(0).toBool() )
+                continue;
+
+        }
+        if ( cfg.hasKey( "X-KDE-Protocol" ) ) {
+            const QString protocol = cfg.readEntry( "X-KDE-Protocol" );
+            if (protocol.startsWith('!')) {
+                const QString excludedProtocol = protocol.mid(1);
+                if (excludedProtocol == protocol)
+                    continue;
+            } else if (protocol != protocol)
+                continue;
+        }
+        else if ( cfg.hasKey( "X-KDE-Protocols" ) ) {
+            const QStringList protocols = cfg.readEntry("X-KDE-Protocols", QStringList());
+            if (!protocols.contains(protocol))
+                continue;
+        }
+        else if (protocol == "trash") {
+            // Require servicemenus for the trash to ask for protocol=trash explicitly.
+            // Trashed files aren't supposed to be available for actions.
+            // One might want a servicemenu for trash.desktop itself though.
+            continue;
+        }
+
+        if ( cfg.hasKey( "X-KDE-Require" ) ) {
+            const QStringList capabilities = cfg.readEntry( "X-KDE-Require" , QStringList() );
+            if (capabilities.contains("Write") && !d->m_props.supportsWriting())
+                continue;
+        }
+        if ( cfg.hasKey( "Actions" ) || cfg.hasKey( "X-KDE-GetActionMenu") ) {
+            // Like KService, we support ServiceTypes, X-KDE-ServiceTypes, and MimeType.
+            QStringList types = cfg.readEntry("ServiceTypes", QStringList());
+            types += cfg.readEntry("X-KDE-ServiceTypes", QStringList());
+            types += cfg.readXdgListEntry("MimeType");
+            //kDebug() << file << types;
+
+            if (types.isEmpty())
+                continue;
+            const QStringList excludeTypes = cfg.readEntry( "ExcludeServiceTypes" , QStringList() );
+            bool ok = false;
+
+            // check for exact matches or a typeglob'd mimetype if we have a mimetype
+            for (QStringList::ConstIterator it = types.constBegin();
+                 it != types.constEnd() && !ok;
+                 ++it)
+            {
+                // first check if we have an all mimetype
+                bool checkTheMimetypes = false;
+                if (*it == "all/all" ||
+                    *it == "allfiles" /*compat with KDE up to 3.0.3*/) {
+                    checkTheMimetypes = true;
+                }
+
+                // next, do we match all files?
+                if (!ok &&
+                    !d->m_props.isDirectory() &&
+                    *it == "all/allfiles") {
+                    checkTheMimetypes = true;
+                }
+
+                // if we have a mimetype, see if we have an exact or a type globbed match
+                if (!ok && (
+                    (mimeTypePtr && mimeTypePtr->is(*it)) ||
+                    (!commonMimeGroup.isEmpty() &&
+                     ((*it).right(1) == "*" &&
+                      (*it).left((*it).indexOf('/')) == commonMimeGroup)))) {
+                    checkTheMimetypes = true;
+                }
+
+                if (checkTheMimetypes) {
+                    ok = true;
+                    for (QStringList::ConstIterator itex = excludeTypes.constBegin(); itex != excludeTypes.constEnd(); ++itex)
+                    {
+                        if( ((*itex).endsWith('*') && (*itex).left((*itex).indexOf('/')) == commonMimeGroup) ||
+                            ((*itex) == commonMimeType) ) {
+                            ok = false;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if ( ok ) {
+                const QString priority = cfg.readEntry("X-KDE-Priority");
+                const QString submenuName = cfg.readEntry( "X-KDE-Submenu" );
+
+                ServiceList& list = s.selectList( priority, submenuName );
+                const ServiceList userServices = KDesktopFileActions::userDefinedServices(*(*it2), isLocal, urlList);
+                foreach ( const KServiceAction& action, userServices ) {
+                    if ( showGroup.readEntry( action.name(), true) ) {
+                        list += action;
+                    }
+                }
+            }
+        }
+    }
+
+
+
+    QMenu* actionMenu = mainMenu;
+    int userItemCount = 0;
+    if (s.user.count() + s.userSubmenus.count() +
+        s.userPriority.count() + s.userPrioritySubmenus.count() > 1)
+    {
+        // we have more than one item, so let's make a submenu
+        actionMenu = new KMenu(i18nc("@title:menu", "&Actions"), mainMenu);
+        actionMenu->menuAction()->setObjectName("actions_submenu"); // for the unittest
+        mainMenu->addMenu(actionMenu);
+    }
+
+    userItemCount += d->insertServicesSubmenus(s.userPrioritySubmenus, actionMenu, false);
+    userItemCount += d->insertServices(s.userPriority, actionMenu, false);
+
+    // see if we need to put a separator between our priority items and our regular items
+    if (userItemCount > 0 &&
+        (s.user.count() > 0 ||
+         s.userSubmenus.count() > 0 ||
+         s.builtin.count() > 0) &&
+        !actionMenu->actions().last()->isSeparator()) {
+        actionMenu->addSeparator();
+    }
+    userItemCount += d->insertServicesSubmenus(s.userSubmenus, actionMenu, false);
+    userItemCount += d->insertServices(s.user, actionMenu, false);
+    userItemCount += d->insertServices(s.builtin, mainMenu, true);
+    userItemCount += d->insertServicesSubmenus(s.userToplevelSubmenus, mainMenu, false);
+    userItemCount += d->insertServices(s.userToplevel, mainMenu, false);
+    return userItemCount;
+}
+
+
+KService::List KFileItemActionsPrivate::associatedApplications(const QString& traderConstraint)
+{
+    if (!KAuthorized::authorizeKAction("openwith"))
+        return KService::List();
+
+    const KFileItemList items = m_props.items();
+    QStringList mimeTypeList;
+    KFileItemList::const_iterator kit = items.constBegin();
+    const KFileItemList::const_iterator kend = items.constEnd();
+    for ( ; kit != kend; ++kit ) {
+        if (!mimeTypeList.contains((*kit).mimetype()))
+            mimeTypeList << (*kit).mimetype();
+    }
+
+    QString constraint = traderConstraint;
+    const QString subConstraint = " and '%1' in ServiceTypes";
+
+    QStringList::ConstIterator it = mimeTypeList.constBegin();
+    const QStringList::ConstIterator end = mimeTypeList.constEnd();
+    Q_ASSERT( it != end );
+    QString firstMimeType = *it;
+    ++it;
+    for (; it != end ; ++it) {
+        constraint += subConstraint.arg(*it);
+    }
+
+    KService::List offers = KMimeTypeTrader::self()->query(firstMimeType, "Application", constraint);
+
+    QSet<QString> seenTexts;
+    for (KService::List::iterator it = offers.begin(); it != offers.end(); ) {
+        bool skipThisEntry = false;
+        // The offer list from the KTrader returns duplicate
+        // application entries (kde3 and kde4). Although this is a configuration
+        // problem, duplicated entries just will be skipped here.
+        const KService::Ptr service = (*it);
+        const QString appName(service->name());
+        if (!seenTexts.contains(appName)) {
+            seenTexts.insert(appName);
+        } else {
+            skipThisEntry = true;
+        }
+
+        if (!skipThisEntry) {
+            // Skip OnlyShowIn=Foo and NotShowIn=KDE entries,
+            // but still offer NoDisplay=true entries, that's the
+            // whole point of such desktop files. This is why we don't
+            // use service->noDisplay() here.
+            const QString onlyShowIn = service->property("OnlyShowIn", QVariant::String).toString();
+            if ( !onlyShowIn.isEmpty() ) {
+                const QStringList aList = onlyShowIn.split(';', QString::SkipEmptyParts);
+                if (!aList.contains("KDE"))
+                    skipThisEntry = true;
+            }
+            const QString notShowIn = service->property("NotShowIn", QVariant::String).toString();
+            if ( !notShowIn.isEmpty() ) {
+                const QStringList aList = notShowIn.split(';', QString::SkipEmptyParts);
+                if (aList.contains("KDE"))
+                    skipThisEntry = true;
+            }
+        }
+
+        if (skipThisEntry) {
+            it = offers.erase(it);
+        } else {
+            ++it;
+        }
+    }
+    return offers;
+}
+
+void KFileItemActions::addOpenWithActionsTo(QMenu* topMenu, const QString& traderConstraint)
+{
+    const KService::List offers = d->associatedApplications(traderConstraint);
+
+    //// Ok, we have everything, now insert
+
+    const KFileItemList items = d->m_props.items();
+    const KFileItem firstItem = items.first();
+    const bool isLocal = firstItem.url().isLocalFile();
+    // "Open With..." for folders is really not very useful, especially for remote folders.
+    // (media:/something, or trash:/, or ftp://...)
+    if ( !d->m_props.isDirectory() || isLocal ) {
+        if ( !topMenu->actions().isEmpty() )
+            topMenu->addSeparator();
+
+        if ( !offers.isEmpty() ) {
+            QMenu* menu = topMenu;
+
+            if ( offers.count() > 1 ) { // submenu 'open with'
+                menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu);
+                menu->menuAction()->setObjectName("openWith_submenu"); // for the unittest
+                topMenu->addMenu(menu);
+            }
+            //kDebug() << offers.count() << "offers" << topMenu << menu;
+
+            KService::List::ConstIterator it = offers.constBegin();
+            for( ; it != offers.constEnd(); it++ ) {
+                KAction* act = d->createAppAction(*it,
+                                                  // no submenu -> prefix single offer
+                                                  menu == topMenu);
+                menu->addAction(act);
+            }
+
+            QString openWithActionName;
+            if ( menu != topMenu ) { // submenu
+                menu->addSeparator();
+                openWithActionName = i18nc("@action:inmenu Open With", "&Other...");
+            } else {
+                openWithActionName = i18nc("@title:menu", "&Open With...");
+            }
+            KAction *openWithAct = new KAction(d->m_parentWidget);
+            d->m_ownActions.append(openWithAct);
+            openWithAct->setText( openWithActionName );
+            QObject::connect(openWithAct, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
+            menu->addAction(openWithAct);
+        }
+        else // no app offers -> Open With...
+        {
+            KAction *act = new KAction(d->m_parentWidget);
+            d->m_ownActions.append(act);
+            act->setText(i18nc("@title:menu", "&Open With..."));
+            QObject::connect(act, SIGNAL(triggered()), d, SLOT(slotOpenWithDialog()));
+            topMenu->addAction(act);
+        }
+
+    }
+}
+
+void KFileItemActionsPrivate::slotRunApplication(QAction* act)
+{
+    // Is it an application, from one of the "Open With" actions
+    KService::Ptr app = act->data().value<KService::Ptr>();
+    Q_ASSERT(app);
+    if (app) {
+        KRun::run(*app, m_props.urlList(), m_parentWidget);
+    }
+}
+
+void KFileItemActionsPrivate::slotOpenWithDialog()
+{
+    // The item 'Other...' or 'Open With...' has been selected
+    KRun::displayOpenWithDialog(m_props.urlList(), m_parentWidget);
+}
+
+KAction* KFileItemActionsPrivate::createAppAction(const KService::Ptr& service, bool singleOffer)
+{
+    QString actionName(service->name().replace('&', "&&"));
+    if (singleOffer)
+        actionName = i18n("Open &with %1", actionName);
+
+    KAction *act = new KAction(m_parentWidget);
+    m_ownActions.append(act);
+    act->setIcon(KIcon(service->icon()));
+    act->setText(actionName);
+    act->setData(QVariant::fromValue(service));
+    m_runApplicationActionGroup.addAction(act);
+    return act;
+}
+
+KAction* KFileItemActions::preferredOpenWithAction(const QString& traderConstraint)
+{
+    const KService::List offers = d->associatedApplications(traderConstraint);
+    if (offers.isEmpty())
+        return 0;
+    return d->createAppAction(offers.first(), true);
+}
+
+void KFileItemActions::setParentWidget(QWidget* widget)
+{
+    d->m_parentWidget = widget;
+}
Index: kio/kfileitemlistproperties.cpp
===================================================================
--- kio/kfileitemlistproperties.cpp	(revision 0)
+++ kio/kfileitemlistproperties.cpp	(revision 0)
@@ -0,0 +1,183 @@
+/* This file is part of the KDE project
+   Copyright (C) 2008 by Peter Penz <peter.p...@gmx.at>
+   Copyright (C) 2008 by George Goldberg <grundleb...@googlemail.com>
+   Copyright     2009 David Faure <fa...@kde.org>
+
+   This library is free software; you can redistribute it and/or modify
+   it under the terms of the GNU Library General Public License as published
+   by the Free Software Foundation; either version 2 of the License or
+   ( at your option ) version 3 or, at the discretion of KDE e.V.
+   ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.
+*/
+
+#include "kfileitemlistproperties.h"
+
+#include <kfileitem.h>
+#include <kprotocolmanager.h>
+
+#include <QFileInfo>
+
+class KFileItemListPropertiesPrivate : public QSharedData
+{
+public:
+    KFileItemListPropertiesPrivate()
+        : m_isDirectory(false),
+          m_supportsReading(false),
+          m_supportsDeleting(false),
+          m_supportsWriting(false),
+          m_supportsMoving(false),
+          m_isLocal(true)
+    { }
+    void setItems(const KFileItemList& items);
+
+    KFileItemList m_items;
+    KUrl::List m_urlList;
+    QString m_mimeType;
+    QString m_mimeGroup;
+    bool m_isDirectory : 1;
+    bool m_supportsReading : 1;
+    bool m_supportsDeleting : 1;
+    bool m_supportsWriting : 1;
+    bool m_supportsMoving : 1;
+    bool m_isLocal : 1;
+};
+
+
+KFileItemListProperties::KFileItemListProperties()
+    : d(new KFileItemListPropertiesPrivate)
+{
+}
+
+KFileItemListProperties::KFileItemListProperties(const KFileItemList& items)
+    : d(new KFileItemListPropertiesPrivate)
+{
+    setItems(items);
+}
+
+void KFileItemListProperties::setItems(const KFileItemList& items)
+{
+    d->setItems(items);
+}
+
+void KFileItemListPropertiesPrivate::setItems(const KFileItemList& items)
+{
+    const bool initialValue = !items.isEmpty();
+    m_items = items;
+    m_urlList = items.targetUrlList();
+    m_supportsReading = initialValue;
+    m_supportsDeleting = initialValue;
+    m_supportsWriting = initialValue;
+    m_supportsMoving = initialValue;
+    m_isDirectory = initialValue;
+    m_isLocal = true;
+    if (!items.isEmpty()) {
+        m_mimeType = items.first().mimetype();
+        m_mimeGroup = m_mimeType.left(m_mimeType.indexOf('/'));
+    }
+
+    QFileInfo parentDirInfo;
+    foreach (const KFileItem &item, items) {
+        const KUrl url = item.url();
+        m_isLocal = m_isLocal && url.isLocalFile();
+        m_supportsReading  = m_supportsReading  && KProtocolManager::supportsReading(url);
+        m_supportsDeleting = m_supportsDeleting && KProtocolManager::supportsDeleting(url);
+        m_supportsWriting  = m_supportsWriting  && KProtocolManager::supportsWriting(url) && item.isWritable();
+        m_supportsMoving   = m_supportsMoving   && KProtocolManager::supportsMoving(url);
+
+        // For local files we can do better: check if we have write permission in parent directory
+        if (m_isLocal && (m_supportsDeleting || m_supportsMoving)) {
+            const QString directory = url.directory();
+            if (parentDirInfo.filePath() != directory) {
+                parentDirInfo.setFile(directory);
+            }
+            if (!parentDirInfo.isWritable()) {
+                m_supportsDeleting = false;
+                m_supportsMoving = false;
+            }
+        }
+        const QString itemMimeType = item.mimetype();
+        // Determine if common mimetype among all items
+        if (m_mimeType != itemMimeType) {
+            m_mimeType.clear();
+            if (m_mimeGroup != itemMimeType.left(itemMimeType.indexOf('/')))
+                m_mimeGroup.clear(); // mimetype groups are different as well!
+        }
+        if (m_isDirectory && !item.isDir())
+            m_isDirectory = false;
+    }
+}
+
+KFileItemListProperties::KFileItemListProperties(const KFileItemListProperties& other)
+    : d(other.d)
+{ }
+
+
+KFileItemListProperties& KFileItemListProperties::operator=(const KFileItemListProperties& other)
+{
+    d = other.d;
+    return *this;
+}
+
+KFileItemListProperties::~KFileItemListProperties()
+{
+}
+
+bool KFileItemListProperties::supportsReading() const
+{
+    return d->m_supportsReading;
+}
+
+bool KFileItemListProperties::supportsDeleting() const
+{
+    return d->m_supportsDeleting;
+}
+
+bool KFileItemListProperties::supportsWriting() const
+{
+    return d->m_supportsWriting;
+}
+
+bool KFileItemListProperties::supportsMoving() const
+{
+    return d->m_supportsMoving && d->m_supportsDeleting;
+}
+
+bool KFileItemListProperties::isLocal() const
+{
+    return d->m_isLocal;
+}
+
+KFileItemList KFileItemListProperties::items() const
+{
+    return d->m_items;
+}
+
+KUrl::List KFileItemListProperties::urlList() const
+{
+    return d->m_urlList;
+}
+
+bool KFileItemListProperties::isDirectory() const
+{
+    return d->m_isDirectory;
+}
+
+QString KFileItemListProperties::mimeType() const
+{
+    return d->m_mimeType;
+}
+
+QString KFileItemListProperties::mimeGroup() const
+{
+    return d->m_mimeGroup;
+}
Index: tests/kfileitemtest.cpp
===================================================================
--- tests/kfileitemtest.cpp	(revision 940408)
+++ tests/kfileitemtest.cpp	(working copy)
@@ -18,6 +18,7 @@
 */
 
 #include "kfileitemtest.h"
+#include <kfileitemlistproperties.h>
 #include <qtest_kde.h>
 #include "kfileitemtest.moc"
 #include <kfileitem.h>
@@ -274,3 +275,77 @@
     QFETCH(QString, expectedFileName);
     QCOMPARE(KIO::encodeFileName(text), expectedFileName);
 }
+
+void KFileItemTest::testListProperties_data()
+{
+    QTest::addColumn<QString>("itemDescriptions");
+    QTest::addColumn<bool>("expectedReading");
+    QTest::addColumn<bool>("expectedDeleting");
+    QTest::addColumn<bool>("expectedIsLocal");
+    QTest::addColumn<bool>("expectedIsDirectory");
+    QTest::addColumn<QString>("expectedMimeType");
+    QTest::addColumn<QString>("expectedMimeGroup");
+
+    QTest::newRow("one file") << "f" << true << true << true << false << "text/plain" << "text";
+    QTest::newRow("one dir") << "d" << true << true << true << true << "inode/directory" << "inode";
+    QTest::newRow("root dir") << "/" << true << false << true << true << "inode/directory" << "inode";
+    QTest::newRow("file+dir") << "fd" << true << true << true << false << "" << "";
+    QTest::newRow("two dirs") << "dd" << true << true << true << true << "inode/directory" << "inode";
+    QTest::newRow("dir+root dir") << "d/" << true << false << true << true << "inode/directory" << "inode";
+    QTest::newRow("two (text+html) files") << "ff" << true << true << true << false << "" << "text";
+    QTest::newRow("three (text+html+empty) files") << "fff" << true << true << true << false << "" << "";
+    QTest::newRow("http url") << "h" << true << true /*says kio_http...*/
+                              << false << false << "application/octet-stream" << "application";
+    QTest::newRow("2 http urls") << "hh" << true << true /*says kio_http...*/
+                              << false << false << "application/octet-stream" << "application";
+}
+
+void KFileItemTest::testListProperties()
+{
+    QFETCH(QString, itemDescriptions);
+    QFETCH(bool, expectedReading);
+    QFETCH(bool, expectedDeleting);
+    QFETCH(bool, expectedIsLocal);
+    QFETCH(bool, expectedIsDirectory);
+    QFETCH(QString, expectedMimeType);
+    QFETCH(QString, expectedMimeGroup);
+
+    KTempDir tempDir;
+    QDir baseDir(tempDir.name());
+    KFileItemList items;
+    for (int i = 0; i < itemDescriptions.size(); ++i) {
+        QString fileName = tempDir.name() + "file" + QString::number(i);
+        switch(itemDescriptions[i].toLatin1()) {
+        case 'f':
+        {
+            if (i==1) // 2nd file is html
+                fileName += ".html";
+            QFile file(fileName);
+            QVERIFY(file.open(QIODevice::WriteOnly));
+            if (i!=2) // 3rd file is empty
+                file.write("Hello");
+            items << KFileItem(KUrl(fileName), QString(), KFileItem::Unknown);
+        }
+            break;
+        case 'd':
+            QVERIFY(baseDir.mkdir(fileName));
+            items << KFileItem(KUrl(fileName), QString(), KFileItem::Unknown);
+            break;
+        case '/':
+            items << KFileItem(KUrl("/"), QString(), KFileItem::Unknown);
+            break;
+        case 'h':
+            items << KFileItem(KUrl("http://www.kde.org";), QString(), KFileItem::Unknown);
+            break;
+        default:
+            QVERIFY(false);
+        }
+    }
+    KFileItemListProperties props(items);
+    QCOMPARE(props.supportsReading(), expectedReading);
+    QCOMPARE(props.supportsDeleting(), expectedDeleting);
+    QCOMPARE(props.isLocal(), expectedIsLocal);
+    QCOMPARE(props.isDirectory(), expectedIsDirectory);
+    QCOMPARE(props.mimeType(), expectedMimeType);
+    QCOMPARE(props.mimeGroup(), expectedMimeGroup);
+}
Index: tests/kfileitemtest.h
===================================================================
--- tests/kfileitemtest.h	(revision 940408)
+++ tests/kfileitemtest.h	(working copy)
@@ -40,6 +40,10 @@
     void testDecodeFileName();
     void testEncodeFileName_data();
     void testEncodeFileName();
+
+    // KFileItemListProperties tests
+    void testListProperties_data();
+    void testListProperties();
 };
 
 
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt	(revision 940408)
+++ CMakeLists.txt	(working copy)
@@ -79,6 +79,8 @@
   kio/kdynamicjobtracker.cpp
   kio/kemailsettings.cpp
   kio/kfileitem.cpp
+  kio/kfileitemlistproperties.cpp
+  kio/kfileitemactions.cpp
   kio/kfileitemdelegate.cpp
   kio/kfilemetainfo.cpp
   kio/kfilemetainfoitem.cpp
@@ -358,6 +360,8 @@
   kio/kdirwatch.h
   kio/kemailsettings.h
   kio/kfileitem.h
+  kio/kfileitemlistproperties.h
+  kio/kfileitemactions.h
   kio/kfileitemdelegate.h
   kio/kfilemetainfo.h
   kio/kfilemetainfoitem.h
_______________________________________________
Plasma-devel mailing list
Plasma-devel@kde.org
https://mail.kde.org/mailman/listinfo/plasma-devel

Reply via email to