/*
    Copyright (c) 2008 Volker Krause <vkrause@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) 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 "standardactionmanager.h"

#include "actionstatemanager_p.h"
#include "agentfilterproxymodel.h"
#include "agentinstancecreatejob.h"
#include "agentmanager.h"
#include "agenttypedialog.h"
#include "collectioncreatejob.h"
#include "collectiondeletejob.h"
#include "collectiondialog.h"
#include "collectionmodel.h"
#include "collectionutils_p.h"
#include "entitytreemodel.h"
#include "favoritecollectionsmodel.h"
#include "itemdeletejob.h"
#include "itemmodel.h"
#include "metatypes.h"
#include "pastehelper_p.h"
#include "specialcollectionattribute_p.h"
#include "collectionpropertiesdialog.h"
#include "subscriptiondialog_p.h"
#include "renamefavoritedialog.h"
#include "trashjob.h"
#include "trashrestorejob.h"
#include "entitydeletedattribute.h"
#include "recentcollectionaction_p.h"

#include <KIcon>
#include <KAction>
#include <KActionCollection>
#include <KActionMenu>
#include <KDebug>
#include <KInputDialog>
#include <KLocalizedString>
#include <KMenu>
#include <KMessageBox>
#include <KToggleAction>

#include <QtCore/QMimeData>
#include <QApplication>
#include <QClipboard>
#include <QItemSelectionModel>
#include <QPointer>
#include <QWeakPointer>

#include <boost/static_assert.hpp>

using namespace Akonadi;

//@cond PRIVATE

enum ActionType
{
    NormalAction,
    ActionWithAlternative, //Normal action, but with an alternative state
    ActionAlternative, //Alternative state of the ActionWithAlternative
    MenuAction,
    ToggleAction
};

static const struct {
    const char *name;
    const char *label;
    const char *iconLabel;
    const char *icon;
    int shortcut;
    const char *slot;
    ActionType actionType;
} standardActionData[] = {
    { "akonadi_collection_create", I18N_NOOP("&New Folder..."), I18N_NOOP("New"), "folder-new", 0, SLOT(slotCreateCollection()), NormalAction },
    { "akonadi_collection_copy", 0, 0, "edit-copy", 0, SLOT(slotCopyCollections()), NormalAction },
    { "akonadi_collection_delete", I18N_NOOP("&Delete Folder"), I18N_NOOP("Delete"), "edit-delete", 0, SLOT(slotDeleteCollection()), NormalAction },
    { "akonadi_collection_sync", I18N_NOOP("&Synchronize Folder"), I18N_NOOP("Synchronize"), "view-refresh", Qt::Key_F5, SLOT(slotSynchronizeCollection()), NormalAction },
    { "akonadi_collection_properties", I18N_NOOP("Folder &Properties"), I18N_NOOP("Properties"), "configure", 0, SLOT(slotCollectionProperties()), NormalAction },
    { "akonadi_item_copy", 0, 0, "edit-copy", 0, SLOT(slotCopyItems()), NormalAction },
    { "akonadi_paste", I18N_NOOP("&Paste"), I18N_NOOP("Paste"), "edit-paste", Qt::CTRL + Qt::Key_V, SLOT(slotPaste()), NormalAction },
    { "akonadi_item_delete", 0, 0, "edit-delete", Qt::Key_Delete, SLOT(slotDeleteItems()), NormalAction },
    { "akonadi_manage_local_subscriptions", I18N_NOOP("Manage Local &Subscriptions..."), I18N_NOOP("Manage Local Subscriptions"), "folder-bookmarks", 0, SLOT(slotLocalSubscription()), NormalAction },
    { "akonadi_collection_add_to_favorites", I18N_NOOP("Add to Favorite Folders"), I18N_NOOP("Add to Favorite"), "bookmark-new", 0, SLOT(slotAddToFavorites()), NormalAction },
    { "akonadi_collection_remove_from_favorites", I18N_NOOP("Remove from Favorite Folders"), I18N_NOOP("Remove from Favorite"), "edit-delete", 0, SLOT(slotRemoveFromFavorites()), NormalAction },
    { "akonadi_collection_rename_favorite", I18N_NOOP("Rename Favorite..."), I18N_NOOP("Rename"), "edit-rename", 0, SLOT(slotRenameFavorite()), NormalAction },
    { "akonadi_collection_copy_to_menu", I18N_NOOP("Copy Folder To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyCollectionTo(QAction*)), MenuAction },
    { "akonadi_item_copy_to_menu", I18N_NOOP("Copy Item To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyItemTo(QAction*)), MenuAction },
    { "akonadi_item_move_to_menu", I18N_NOOP("Move Item To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveItemTo(QAction*)), MenuAction },
    { "akonadi_collection_move_to_menu", I18N_NOOP("Move Folder To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveCollectionTo(QAction*)), MenuAction },
    { "akonadi_item_cut", I18N_NOOP("&Cut Item"), I18N_NOOP("Cut"), "edit-cut", Qt::CTRL + Qt::Key_X, SLOT(slotCutItems()), NormalAction },
    { "akonadi_collection_cut", I18N_NOOP("&Cut Folder"), I18N_NOOP("Cut"), "edit-cut", Qt::CTRL + Qt::Key_X, SLOT(slotCutCollections()), NormalAction },
    { "akonadi_resource_create", I18N_NOOP("Create Resource"), 0, "folder-new", 0, SLOT(slotCreateResource()), NormalAction },
    { "akonadi_resource_delete", I18N_NOOP("Delete Resource"), 0, "edit-delete", 0, SLOT(slotDeleteResource()), NormalAction },
    { "akonadi_resource_properties", I18N_NOOP("&Resource Properties"), I18N_NOOP("Properties"), "configure", 0, SLOT(slotResourceProperties()), NormalAction },
    { "akonadi_resource_synchronize", I18N_NOOP("Synchronize Resource"), I18N_NOOP("Synchronize"), "view-refresh", 0, SLOT(slotSynchronizeResource()), NormalAction },
    { "akonadi_work_offline", I18N_NOOP("Work Offline"), 0, "user-offline", 0, SLOT(slotToggleWorkOffline(bool)), ToggleAction },
    { "akonadi_collection_copy_to_dialog", I18N_NOOP("Copy Folder To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyCollectionTo()), NormalAction },
    { "akonadi_collection_move_to_dialog", I18N_NOOP("Move Folder To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveCollectionTo()), NormalAction },
    { "akonadi_item_copy_to_dialog", I18N_NOOP("Copy Item To..."), I18N_NOOP("Copy To"), "edit-copy", 0, SLOT(slotCopyItemTo()), NormalAction },
    { "akonadi_item_move_to_dialog", I18N_NOOP("Move Item To..."), I18N_NOOP("Move To"), "go-jump", 0, SLOT(slotMoveItemTo()), NormalAction },
    { "akonadi_collection_sync_recursive", I18N_NOOP("&Synchronize Folder Recursively"), I18N_NOOP("Synchronize Recursively"), "view-refresh", Qt::CTRL + Qt::Key_F5, SLOT(slotSynchronizeCollectionRecursive()), NormalAction },
    { "akonadi_move_collection_to_trash", I18N_NOOP("&Move Folder To Trash"), I18N_NOOP("Move Folder To Trash"), "user-trash", 0, SLOT(slotMoveCollectionToTrash()), NormalAction },
    { "akonadi_move_item_to_trash", I18N_NOOP("&Move Item To Trash"), I18N_NOOP("Move Item To Trash"), "user-trash", 0, SLOT(slotMoveItemToTrash()), NormalAction },
    { "akonadi_restore_collection_from_trash", I18N_NOOP("&Restore Folder From Trash"), I18N_NOOP("Restore Folder From Trash"), "view-refresh", 0, SLOT(slotRestoreCollectionFromTrash()), NormalAction },
    { "akonadi_restore_item_from_trash", I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "view-refresh", 0, SLOT(slotRestoreItemFromTrash()), NormalAction },
    { "akonadi_collection_trash_restore", I18N_NOOP("&Restore Folder From Trash"), I18N_NOOP("Restore Folder From Trash"), "user-trash", 0, SLOT(slotTrashRestoreCollection()), ActionWithAlternative },
    { 0, I18N_NOOP("&Restore Collection From Trash"), I18N_NOOP("Restore Collection From Trash"), "view-refresh", 0, 0, ActionAlternative },
    { "akonadi_item_trash_restore", I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "user-trash", 0, SLOT(slotTrashRestoreItem()), ActionWithAlternative },
    { 0, I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "view-refresh", 0, 0, ActionAlternative },
    { "akonadi_collection_sync_favorite_folders", I18N_NOOP("&Synchronize Favorite Folders"), I18N_NOOP("Synchronize Favorite Folders"), "view-refresh", Qt::CTRL + Qt::SHIFT + Qt::Key_L , SLOT(slotSynchronizeFavoriteCollections()), NormalAction }

};
static const int numStandardActionData = sizeof standardActionData / sizeof * standardActionData;

BOOST_STATIC_ASSERT(numStandardActionData == StandardActionManager::LastType);

static bool canCreateCollection(const Akonadi::Collection &collection)
{
    if (!(collection.rights() & Akonadi::Collection::CanCreateCollection)) {
        return false;
    }

    if (!collection.contentMimeTypes().contains(Akonadi::Collection::mimeType()) &&
        !collection.contentMimeTypes().contains(Akonadi::Collection::virtualMimeType())) {
        return false;
    }

    return true;
}

/*
static inline bool isRootCollection( const Akonadi::Collection &collection )
{
  return (collection == Akonadi::Collection::root());
}
*/

static void setWorkOffline(bool offline)
{
    KConfig config(QLatin1String("akonadikderc"));
    KConfigGroup group(&config, QLatin1String("Actions"));

    group.writeEntry("WorkOffline", offline);
}

static bool workOffline()
{
    KConfig config(QLatin1String("akonadikderc"));
    const KConfigGroup group(&config, QLatin1String("Actions"));

    return group.readEntry("WorkOffline", false);
}

static QModelIndexList safeSelectedRows(QItemSelectionModel *selectionModel)
{
    QModelIndexList selectedRows = selectionModel->selectedRows();
    if (!selectedRows.isEmpty()) {
        return selectedRows;
    }

    // try harder for selected rows that don't span the full row for some reason (e.g. due to buggy column adding proxy models etc)
    foreach (const QItemSelectionRange &range, selectionModel->selection()) {
        if (!range.isValid() || range.isEmpty()) {
            continue;
        }
        const QModelIndex parent = range.parent();
        for (int row = range.top(); row <= range.bottom(); ++row) {
            const QModelIndex index = range.model()->index(row, range.left(), parent);
            const Qt::ItemFlags flags = range.model()->flags(index);
            if ((flags &Qt::ItemIsSelectable) && (flags &Qt::ItemIsEnabled)) {
                selectedRows.push_back(index);
            }
        }
    }

    return selectedRows;
}

/**
 * @internal
 */
class StandardActionManager::Private
{
public:
    Private(StandardActionManager *parent)
        : q(parent)
        , actionCollection(0)
        , parentWidget(0)
        , collectionSelectionModel(0)
        , itemSelectionModel(0)
        , favoritesModel(0)
        , favoriteSelectionModel(0)
        , insideSelectionSlot(false)
    {
        actions.fill(0, StandardActionManager::LastType);

        pluralLabels.insert(StandardActionManager::CopyCollections,
                            ki18np("&Copy Folder", "&Copy %1 Folders"));
        pluralLabels.insert(StandardActionManager::CopyItems,
                            ki18np("&Copy Item", "&Copy %1 Items"));
        pluralLabels.insert(StandardActionManager::CutItems,
                            ki18np("&Cut Item", "&Cut %1 Items"));
        pluralLabels.insert(StandardActionManager::CutCollections,
                            ki18np("&Cut Folder", "&Cut %1 Folders"));
        pluralLabels.insert(StandardActionManager::DeleteItems,
                            ki18np("&Delete Item", "&Delete %1 Items"));
        pluralLabels.insert(StandardActionManager::DeleteCollections,
                            ki18np("&Delete Folder", "&Delete %1 Folders"));
        pluralLabels.insert(StandardActionManager::SynchronizeCollections,
                            ki18np("&Synchronize Folder", "&Synchronize %1 Folders"));
        pluralLabels.insert(StandardActionManager::DeleteResources,
                            ki18np("&Delete Resource", "&Delete %1 Resources"));
        pluralLabels.insert(StandardActionManager::SynchronizeResources,
                            ki18np("&Synchronize Resource", "&Synchronize %1 Resources"));

        pluralIconLabels.insert(StandardActionManager::CopyCollections,
                                ki18np("Copy Folder", "Copy %1 Folders"));
        pluralIconLabels.insert(StandardActionManager::CopyItems,
                                ki18np("Copy Item", "Copy %1 Items"));
        pluralIconLabels.insert(StandardActionManager::CutItems,
                                ki18np("Cut Item", "Cut %1 Items"));
        pluralIconLabels.insert(StandardActionManager::CutCollections,
                                ki18np("Cut Folder", "Cut %1 Folders"));
        pluralIconLabels.insert(StandardActionManager::DeleteItems,
                                ki18np("Delete Item", "Delete %1 Items"));
        pluralIconLabels.insert(StandardActionManager::DeleteCollections,
                                ki18np("Delete Folder", "Delete %1 Folders"));
        pluralIconLabels.insert(StandardActionManager::SynchronizeCollections,
                                ki18np("Synchronize Folder", "Synchronize %1 Folders"));
        pluralIconLabels.insert(StandardActionManager::DeleteResources,
                                ki18np("Delete Resource", "Delete %1 Resources"));
        pluralIconLabels.insert(StandardActionManager::SynchronizeResources,
                                ki18np("Synchronize Resource", "Synchronize %1 Resources"));

        setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle,
                       i18nc("@title:window", "New Folder"));
        setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText,
                       i18nc("@label:textbox name of a thing", "Name"));
        setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText,
                       ki18n("Could not create folder: %1"));
        setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle,
                       i18n("Folder creation failed"));

        setContextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText,
                       ki18np("Do you really want to delete this folder and all its sub-folders?",
                              "Do you really want to delete %1 folders and all their sub-folders?"));
        setContextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxTitle,
                       ki18ncp("@title:window", "Delete folder?", "Delete folders?"));
        setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText,
                       ki18n("Could not delete folder: %1"));
        setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle,
                       i18n("Folder deletion failed"));

        setContextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle,
                       ki18nc("@title:window", "Properties of Folder %1"));

        setContextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxText,
                       ki18np("Do you really want to delete the selected item?",
                              "Do you really want to delete %1 items?"));
        setContextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle,
                       ki18ncp("@title:window", "Delete item?", "Delete items?"));
        setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText,
                       ki18n("Could not delete item: %1"));
        setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle,
                       i18n("Item deletion failed"));

        setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogTitle,
                       i18nc("@title:window", "Rename Favorite"));
        setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogText,
                       i18nc("@label:textbox name of the folder", "Name:"));

        setContextText(StandardActionManager::CreateResource, StandardActionManager::DialogTitle,
                       i18nc("@title:window", "New Resource"));
        setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText,
                       ki18n("Could not create resource: %1"));
        setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle,
                       i18n("Resource creation failed"));

        setContextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxText,
                       ki18np("Do you really want to delete this resource?",
                              "Do you really want to delete %1 resources?"));
        setContextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxTitle,
                       ki18ncp("@title:window", "Delete Resource?", "Delete Resources?"));

        setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageText,
                       ki18n("Could not paste data: %1"));
        setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle,
                       i18n("Paste failed"));

        qRegisterMetaType<Akonadi::Item::List>("Akonadi::Item::List");
    }
    void enableAction(int type, bool enable)
    {
        enableAction(static_cast<StandardActionManager::Type>(type), enable);
    }

    void enableAction(StandardActionManager::Type type, bool enable)
    {
        Q_ASSERT(type < StandardActionManager::LastType);
        if (actions[type]) {
            actions[type]->setEnabled(enable);
        }

        // Update the action menu
        KActionMenu *actionMenu = qobject_cast<KActionMenu *>(actions[type]);
        if (actionMenu) {
            //get rid of the submenus, they are re-created in enableAction. clear() is not enough, doesn't remove the submenu object instances.
            KMenu *menu = actionMenu->menu();
            //Not necessary to delete and recreate menu when it was not created
            if (menu->property("actionType").isValid() && menu->isEmpty()) {
                return;
            }
            mRecentCollectionsMenu.remove(type);
            delete menu;
            menu = new KMenu();

            menu->setProperty("actionType", static_cast<int>(type));
            q->connect(menu, SIGNAL(aboutToShow()), SLOT(aboutToShowMenu()));
            q->connect(menu, SIGNAL(triggered(QAction*)), standardActionData[type].slot);
            actionMenu->setMenu(menu);
        }
    }

    void aboutToShowMenu()
    {
        QMenu *menu = qobject_cast<QMenu *>(q->sender());
        if (!menu) {
            return;
        }

        if (!menu->isEmpty()) {
            return;
        }
        // collect all selected collections
        const Akonadi::Collection::List selectedCollectionsList = selectedCollections();
        const StandardActionManager::Type type = static_cast<StandardActionManager::Type>(menu->property("actionType").toInt());

        QWeakPointer<RecentCollectionAction> recentCollection = new RecentCollectionAction(type, selectedCollectionsList, collectionSelectionModel->model(), menu);
        mRecentCollectionsMenu.insert(type, recentCollection);
        const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
        fillFoldersMenu(selectedCollectionsList,
                        mimeTypes,
                        type,
                        menu,
                        collectionSelectionModel->model(),
                        QModelIndex());
    }

    void createActionFolderMenu(QMenu *menu, StandardActionManager::Type type)
    {
        if (type == CopyCollectionToMenu ||
            type == CopyItemToMenu ||
            type == MoveItemToMenu ||
            type == MoveCollectionToMenu)
        {
            new RecentCollectionAction(type, Akonadi::Collection::List(), collectionSelectionModel->model(), menu);
            Collection::List selectedCollectionsList = selectedCollections();
            const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
            fillFoldersMenu(selectedCollectionsList,
                            mimeTypes,
                            type,
                            menu,
                            collectionSelectionModel->model(),
                            QModelIndex());
        }
    }

    void updateAlternatingAction(int type)
    {
        updateAlternatingAction(static_cast<StandardActionManager::Type>(type));
    }

    void updateAlternatingAction(StandardActionManager::Type type)
    {
        Q_ASSERT(type < StandardActionManager::LastType);
        if (!actions[type]) {
            return;
        }

        /*
         * The same action is stored at the ActionWithAlternative indexes as well as the corresponding ActionAlternative indexes in the actions array.
         * The following simply changes the standardActionData
         */
        if ((standardActionData[type].actionType == ActionWithAlternative) || (standardActionData[type].actionType == ActionAlternative)) {
            actions[type]->setText(i18n(standardActionData[type].label));
            actions[type]->setIcon(KIcon(QString::fromLatin1(standardActionData[type].icon)));

            if (pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) {
                actions[type]->setText(pluralLabels.value(type).subs(1).toString());
            } else if (standardActionData[type].label) {
                actions[type]->setText(i18n(standardActionData[type].label));
            }

            if (pluralIconLabels.contains(type) && !pluralIconLabels.value(type).isEmpty()) {
                actions[type]->setIconText(pluralIconLabels.value(type).subs(1).toString());
            } else if (standardActionData[type].iconLabel) {
                actions[type]->setIconText(i18n(standardActionData[type].iconLabel));
            }

            if (standardActionData[type].icon) {
                actions[type]->setIcon(KIcon(QString::fromLatin1(standardActionData[type].icon)));
            }

            //actions[type]->setShortcut( standardActionData[type].shortcut );

            /*if ( standardActionData[type].slot ) {
              switch ( standardActionData[type].actionType ) {
              case NormalAction:
              case ActionWithAlternative:
                  connect( action, SIGNAL(triggered()), standardActionData[type].slot );
                  break;
              }
            }*/
        }
    }

    void updatePluralLabel(int type, int count)
    {
        updatePluralLabel(static_cast<StandardActionManager::Type>(type), count);
    }

    void updatePluralLabel(StandardActionManager::Type type, int count)
    {
        Q_ASSERT(type < StandardActionManager::LastType);
        if (actions[type] && pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) {
            actions[type]->setText(pluralLabels.value(type).subs(qMax(count, 1)).toString());
        }
    }

    bool isFavoriteCollection(const Akonadi::Collection &collection)
    {
        if (!favoritesModel) {
            return false;
        }

        return favoritesModel->collectionIds().contains(collection.id());
    }

    void encodeToClipboard(QItemSelectionModel *selectionModel, bool cut = false)
    {
        Q_ASSERT(selectionModel);
        if (safeSelectedRows(selectionModel).count() <= 0) {
            return;
        }

#ifndef QT_NO_CLIPBOARD
        QAbstractItemModel *model = const_cast<QAbstractItemModel *>(selectionModel->model());
        QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel));
        model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole);
        markCutAction(mimeData, cut);
        QApplication::clipboard()->setMimeData(mimeData);

        foreach (const QModelIndex &index, safeSelectedRows(selectionModel)) {
            model->setData(index, true, EntityTreeModel::PendingCutRole);
        }
#endif
    }
    void updateActions()
    {
        // collect all selected collections
        Collection::List selectedCollectionsList;
        if (collectionSelectionModel) {
            const QModelIndexList rows = safeSelectedRows(collectionSelectionModel);
            foreach (const QModelIndex &index, rows) {
                Collection collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
                if (!collection.isValid()) {
                    continue;
                }

                const Collection parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>();
                collection.setParentCollection(parentCollection);

                selectedCollectionsList << collection;
            }
        }

        // collect all selected items
        Item::List selectedItems;
        if (itemSelectionModel) {
            const QModelIndexList rows = safeSelectedRows(itemSelectionModel);
            foreach (const QModelIndex &index, rows) {
                Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
                if (!item.isValid()) {
                    continue;
                }

                const Collection parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>();
                item.setParentCollection(parentCollection);

                selectedItems << item;
            }
        }

        mActionStateManager.updateState(selectedCollectionsList, selectedItems);
        if (favoritesModel) {
            enableAction(StandardActionManager::SynchronizeFavoriteCollections, (favoritesModel->rowCount() > 0));
        }
        emit q->actionStateUpdated();
    }

#ifndef QT_NO_CLIPBOARD
    void clipboardChanged(QClipboard::Mode mode)
    {
        if (mode == QClipboard::Clipboard) {
            updateActions();
        }
    }
#endif

    QItemSelection mapToEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
    {
        const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model);
        if (proxy) {
            return mapToEntityTreeModel(proxy->sourceModel(), proxy->mapSelectionToSource(selection));
        } else {
            return selection;
        }
    }

    QItemSelection mapFromEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
    {
        const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model);
        if (proxy) {
            const QItemSelection select = mapFromEntityTreeModel(proxy->sourceModel(), selection);
            return proxy->mapSelectionFromSource(select);
        } else {
            return selection;
        }
    }

    // RAII class for setting insideSelectionSlot to true on entering, and false on exiting, the two slots below.
    class InsideSelectionSlotBlocker {
    public:
        InsideSelectionSlotBlocker(Private *p)
            : _p(p)
        {
            Q_ASSERT(!p->insideSelectionSlot);
            p->insideSelectionSlot = true;
        }

        ~InsideSelectionSlotBlocker()
        {
            Q_ASSERT(_p->insideSelectionSlot);
            _p->insideSelectionSlot = false;
        }
    private:
        Private *_p;
    };

    void collectionSelectionChanged()
    {
        if (insideSelectionSlot) {
            return;
        }
        InsideSelectionSlotBlocker block(this);
        QItemSelection selection = collectionSelectionModel->selection();
        selection = mapToEntityTreeModel(collectionSelectionModel->model(), selection);
        selection = mapFromEntityTreeModel(favoritesModel, selection);

        if (favoriteSelectionModel) {
            favoriteSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
        }

        updateActions();
    }

    void favoriteSelectionChanged()
    {
        if (insideSelectionSlot) {
            return;
        }
        QItemSelection selection = favoriteSelectionModel->selection();
        if (selection.isEmpty()) {
            return;
        }

        selection = mapToEntityTreeModel(favoritesModel, selection);
        selection = mapFromEntityTreeModel(collectionSelectionModel->model(), selection);

        InsideSelectionSlotBlocker block(this);
        collectionSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);

        // Also set the current index. This will trigger KMMainWidget::slotFolderChanged in kmail, which we want.
        collectionSelectionModel->setCurrentIndex(selection.indexes().first(), QItemSelectionModel::NoUpdate);

        updateActions();
    }

    void slotCreateCollection()
    {
        Q_ASSERT(collectionSelectionModel);
        if (collectionSelectionModel->selection().indexes().isEmpty()) {
            return;
        }

        const QModelIndex index = collectionSelectionModel->selection().indexes().at(0);
        Q_ASSERT(index.isValid());
        const Collection parentCollection = index.data(CollectionModel::CollectionRole).value<Collection>();
        Q_ASSERT(parentCollection.isValid());

        if (!canCreateCollection(parentCollection)) {
            return;
        }

        QString name = KInputDialog::getText(contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle),
                                             contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText),
                                             QString(), 0, parentWidget);
        name = name.trimmed();
        if (name.isEmpty()) {
            return;
        }

        if (name.contains(QLatin1Char('/'))) {
            KMessageBox::error(parentWidget,
                               i18n("We can not add \"/\" in folder name."),
                               i18n("Create new folder error"));
            return;
        }
        if (name.startsWith(QLatin1Char('.')) ||
            name.endsWith(QLatin1Char('.'))) {
            KMessageBox::error(parentWidget,
                               i18n("We can not add \".\" at begin or end of folder name."),
                               i18n("Create new folder error"));
            return;
        }

        Collection collection;
        collection.setName(name);
        collection.setParentCollection(parentCollection);
        if (actions[StandardActionManager::CreateCollection]) {
            const QStringList mts = actions[StandardActionManager::CreateCollection]->property("ContentMimeTypes").toStringList();
            if (!mts.isEmpty()) {
                collection.setContentMimeTypes(mts);
            }
        }
        if (parentCollection.contentMimeTypes().contains(Collection::virtualMimeType())) {
            collection.setVirtual(true);
            collection.setContentMimeTypes(collection.contentMimeTypes()
                                           << Collection::virtualMimeType());
        }

        CollectionCreateJob *job = new CollectionCreateJob(collection);
        q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionCreationResult(KJob*)));
    }

    void slotCopyCollections()
    {
        encodeToClipboard(collectionSelectionModel);
    }

    void slotCutCollections()
    {
        encodeToClipboard(collectionSelectionModel, true);
    }

    Collection::List selectedCollections()
    {
        Collection::List collections;

        Q_ASSERT(collectionSelectionModel);

        foreach (const QModelIndex &index, safeSelectedRows(collectionSelectionModel)) {
            Q_ASSERT(index.isValid());
            const Collection collection = index.data(CollectionModel::CollectionRole).value<Collection>();
            Q_ASSERT(collection.isValid());

            collections << collection;
        }

        return collections;
    }

    void slotDeleteCollection()
    {
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }

        const QString collectionName = collections.first().name();
        const QString text = contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText,
                                         collections.count(), collectionName);

        if (KMessageBox::questionYesNo(parentWidget, text,
                                       contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxTitle, collections.count(), collectionName),
                                       KStandardGuiItem::del(), KStandardGuiItem::cancel(),
                                       QString(), KMessageBox::Dangerous) != KMessageBox::Yes) {
            return;
        }

        foreach (const Collection &collection, collections) {
            CollectionDeleteJob *job = new CollectionDeleteJob(collection, q);
            q->connect(job, SIGNAL(result(KJob*)), q, SLOT(collectionDeletionResult(KJob*)));
        }
    }

    void slotMoveCollectionToTrash()
    {
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }

        foreach (const Collection &collection, collections) {
            TrashJob *job = new TrashJob(collection, q);
            q->connect(job, SIGNAL(result(KJob*)), q, SLOT(moveCollectionToTrashResult(KJob*)));
        }
    }

    void slotRestoreCollectionFromTrash()
    {
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }

        foreach (const Collection &collection, collections) {
            TrashRestoreJob *job = new TrashRestoreJob(collection, q);
            q->connect(job, SIGNAL(result(KJob*)), q, SLOT(moveCollectionToTrashResult(KJob*)));
        }
    }

    Item::List selectedItems() const
    {
        Item::List items;

        Q_ASSERT(itemSelectionModel);

        foreach (const QModelIndex &index, safeSelectedRows(itemSelectionModel)) {
            Q_ASSERT(index.isValid());
            const Item item = index.data(ItemModel::ItemRole).value<Item>();
            Q_ASSERT(item.isValid());

            items << item;
        }

        return items;
    }

    void slotMoveItemToTrash()
    {
        const Item::List items = selectedItems();
        if (items.isEmpty()) {
            return;
        }

        TrashJob *job = new TrashJob(items, q);
        q->connect(job, SIGNAL(result(KJob*)), q, SLOT(moveItemToTrashResult(KJob*)));
    }

    void slotRestoreItemFromTrash()
    {
        const Item::List items = selectedItems();
        if (items.isEmpty()) {
            return;
        }

        TrashRestoreJob *job = new TrashRestoreJob(items, q);
        q->connect(job, SIGNAL(result(KJob*)), q, SLOT(moveItemToTrashResult(KJob*)));
    }

    void slotTrashRestoreCollection()
    {
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }

        bool collectionsAreInTrash = false;
        foreach (const Collection &collection, collections) {
            if (collection.hasAttribute<EntityDeletedAttribute>()) {
                collectionsAreInTrash = true;
                break;
            }
        }

        if (collectionsAreInTrash) {
            slotRestoreCollectionFromTrash();
        } else {
            slotMoveCollectionToTrash();
        }
    }

    void slotTrashRestoreItem()
    {
        const Item::List items = selectedItems();
        if (items.isEmpty()) {
            return;
        }

        bool itemsAreInTrash = false;
        foreach (const Item &item, items) {
            if (item.hasAttribute<EntityDeletedAttribute>()) {
                itemsAreInTrash = true;
                break;
            }
        }

        if (itemsAreInTrash) {
            slotRestoreItemFromTrash();
        } else {
            slotMoveItemToTrash();
        }
    }

    void slotSynchronizeCollection()
    {
        Q_ASSERT(collectionSelectionModel);
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }

        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }

        foreach (const Collection &collection, collections) {
            if (!testAndSetOnlineResources(collection)) {
                break;
            }
            AgentManager::self()->synchronizeCollection(collection, false);
        }
    }

    bool testAndSetOnlineResources(const Akonadi::Collection &collection)
    {
        // Shortcut for the Search resource, which is a virtual resource and thus
        // is awlays online (but AgentManager does not know about it, so it returns
        // an invalid AgentInstance, which is "offline").
        //
        // FIXME: AgentManager should return a valid AgentInstance even
        // for virtual resources, which would be always online.
        if (collection.resource() == QLatin1String("akonadi_search_resource")) {
            return true;
        }

        Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
        if (!instance.isOnline()) {
            if (KMessageBox::questionYesNo(parentWidget,
                                           i18n("Before syncing folder \"%1\" it is necessary to have the resource online. Do you want to make it online?", collection.displayName()),
                                           i18n("Account \"%1\" is offline", instance.name()),
                                           KGuiItem(i18nc("@action:button", "Go Online")), KStandardGuiItem::cancel()) != KMessageBox::Yes) {
                return false;
            }
            instance.setIsOnline(true);
        }
        return true;
    }

    void slotSynchronizeCollectionRecursive()
    {
        Q_ASSERT(collectionSelectionModel);
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }

        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }

        foreach (const Collection &collection, collections) {
            if (!testAndSetOnlineResources(collection)) {
                break;
            }
            AgentManager::self()->synchronizeCollection(collection, true);
        }
    }

    void slotCollectionProperties()
    {
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }

        const QModelIndex index = list.first();
        Q_ASSERT(index.isValid());

        const Collection collection = index.data(CollectionModel::CollectionRole).value<Collection>();
        Q_ASSERT(collection.isValid());

        CollectionPropertiesDialog *dlg = new CollectionPropertiesDialog(collection, mCollectionPropertiesPageNames, parentWidget);
        dlg->setCaption(contextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, collection.displayName()));
        dlg->show();
    }

    void slotCopyItems()
    {
        encodeToClipboard(itemSelectionModel);
    }

    void slotCutItems()
    {
        encodeToClipboard(itemSelectionModel, true);
    }

    void slotPaste()
    {
        Q_ASSERT(collectionSelectionModel);

        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }

        const QModelIndex index = list.first();
        Q_ASSERT(index.isValid());

#ifndef QT_NO_CLIPBOARD
        // TODO: Copy or move? We can't seem to cut yet
        QAbstractItemModel *model = const_cast<QAbstractItemModel *>(collectionSelectionModel->model());
        const QMimeData *mimeData = QApplication::clipboard()->mimeData();
        model->dropMimeData(mimeData, isCutAction(mimeData) ? Qt::MoveAction : Qt::CopyAction, -1, -1, index);
        model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole);
        QApplication::clipboard()->clear();
#endif
    }

    void slotDeleteItems()
    {
        Q_ASSERT(itemSelectionModel);

        Item::List items;
        foreach (const QModelIndex &index, safeSelectedRows(itemSelectionModel)) {
            bool ok;
            const qlonglong id = index.data(ItemModel::IdRole).toLongLong(&ok);
            Q_ASSERT(ok);
            items << Item(id);
        }

        if (items.isEmpty()) {
            return;
        }

        QMetaObject::invokeMethod(q, "slotDeleteItemsDeferred",
                                  Qt::QueuedConnection,
                                  Q_ARG(Akonadi::Item::List, items));
    }

    void slotDeleteItemsDeferred(const Akonadi::Item::List &items)
    {
        Q_ASSERT(itemSelectionModel);

        if (KMessageBox::questionYesNo(parentWidget,
                                       contextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxText, items.count(), QString()),
                                       contextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle, items.count(), QString()),
                                       KStandardGuiItem::del(), KStandardGuiItem::cancel(),
                                       QString(), KMessageBox::Dangerous) != KMessageBox::Yes) {
            return;
        }

        ItemDeleteJob *job = new ItemDeleteJob(items, q);
        q->connect(job, SIGNAL(result(KJob*)), q, SLOT(itemDeletionResult(KJob*)));
    }

    void slotLocalSubscription()
    {
        SubscriptionDialog *dlg = new SubscriptionDialog(mMimeTypeFilter, parentWidget);
        dlg->showHiddenCollection(true);
        dlg->show();
    }

    void slotAddToFavorites()
    {
        Q_ASSERT(collectionSelectionModel);
        Q_ASSERT(favoritesModel);
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }

        foreach (const QModelIndex &index, list) {
            Q_ASSERT(index.isValid());
            const Collection collection = index.data(CollectionModel::CollectionRole).value<Collection>();
            Q_ASSERT(collection.isValid());

            favoritesModel->addCollection(collection);
        }

        updateActions();
    }

    void slotRemoveFromFavorites()
    {
        Q_ASSERT(collectionSelectionModel);
        Q_ASSERT(favoritesModel);
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }

        foreach (const QModelIndex &index, list) {
            Q_ASSERT(index.isValid());
            const Collection collection = index.data(CollectionModel::CollectionRole).value<Collection>();
            Q_ASSERT(collection.isValid());

            favoritesModel->removeCollection(collection);
        }

        updateActions();
    }

    void slotRenameFavorite()
    {
        Q_ASSERT(collectionSelectionModel);
        Q_ASSERT(favoritesModel);
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }
        const QModelIndex index = list.first();
        Q_ASSERT(index.isValid());
        const Collection collection = index.data(CollectionModel::CollectionRole).value<Collection>();
        Q_ASSERT(collection.isValid());

        QPointer<RenameFavoriteDialog> dlg(new RenameFavoriteDialog(contextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogTitle), contextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogText) , favoritesModel->favoriteLabel(collection), collection.displayName(), parentWidget));
        if (dlg->exec() == QDialog::Accepted && dlg != 0) {
            favoritesModel->setFavoriteLabel(collection, dlg->newName());
        }
        delete dlg;
    }

    void slotSynchronizeFavoriteCollections()
    {
        Q_ASSERT(favoritesModel);
        foreach (const Collection &collection, favoritesModel->collections()) {
            // there might be virtual collections in favorites which cannot be checked
            // so let's be safe here, agentmanager asserts otherwise
            if (!collection.resource().isEmpty()) {
                AgentManager::self()->synchronizeCollection(collection, false);
            }
        }
    }

    void slotCopyCollectionTo()
    {
        pasteTo(collectionSelectionModel, collectionSelectionModel->model(), CopyCollectionToMenu, Qt::CopyAction);
    }

    void slotCopyItemTo()
    {
        pasteTo(itemSelectionModel, collectionSelectionModel->model(), CopyItemToMenu, Qt::CopyAction);
    }

    void slotMoveCollectionTo()
    {
        pasteTo(collectionSelectionModel, collectionSelectionModel->model(), MoveCollectionToMenu, Qt::MoveAction);
    }

    void slotMoveItemTo()
    {
        pasteTo(itemSelectionModel, collectionSelectionModel->model(), MoveItemToMenu, Qt::MoveAction);
    }

    void slotCopyCollectionTo(QAction *action)
    {
        pasteTo(collectionSelectionModel, action, Qt::CopyAction);
    }

    void slotCopyItemTo(QAction *action)
    {
        pasteTo(itemSelectionModel, action, Qt::CopyAction);
    }

    void slotMoveCollectionTo(QAction *action)
    {
        pasteTo(collectionSelectionModel, action, Qt::MoveAction);
    }

    void slotMoveItemTo(QAction *action)
    {
        pasteTo(itemSelectionModel, action, Qt::MoveAction);
    }

    AgentInstance::List selectedAgentInstances() const
    {
        AgentInstance::List instances;

        Q_ASSERT(collectionSelectionModel);
        if (collectionSelectionModel->selection().indexes().isEmpty()) {
            return instances;
        }

        foreach (const QModelIndex &index, collectionSelectionModel->selection().indexes()) {
            Q_ASSERT(index.isValid());
            const Collection collection = index.data(CollectionModel::CollectionRole).value<Collection>();
            Q_ASSERT(collection.isValid());

            if (collection.isValid()) {
                const QString identifier = collection.resource();
                instances << AgentManager::self()->instance(identifier);
            }
        }

        return instances;
    }

    AgentInstance selectedAgentInstance() const
    {
        const AgentInstance::List instances = selectedAgentInstances();

        if (instances.isEmpty()) {
            return AgentInstance();
        }

        return instances.first();
    }

    void slotCreateResource()
    {
        QPointer<Akonadi::AgentTypeDialog> dlg(new Akonadi::AgentTypeDialog(parentWidget));
        dlg->setCaption(contextText(StandardActionManager::CreateResource, StandardActionManager::DialogTitle));

        foreach (const QString &mimeType, mMimeTypeFilter) {
            dlg->agentFilterProxyModel()->addMimeTypeFilter(mimeType);
        }

        foreach (const QString &capability, mCapabilityFilter) {
            dlg->agentFilterProxyModel()->addCapabilityFilter(capability);
        }

        if (dlg->exec() == QDialog::Accepted && dlg != 0) {
            const AgentType agentType = dlg->agentType();

            if (agentType.isValid()) {
                AgentInstanceCreateJob *job = new AgentInstanceCreateJob(agentType, q);
                q->connect(job, SIGNAL(result(KJob*)), SLOT(resourceCreationResult(KJob*)));
                job->configure(parentWidget);
                job->start();
            }
        }
        delete dlg;
    }

    void slotDeleteResource()
    {
        const AgentInstance::List instances = selectedAgentInstances();
        if (instances.isEmpty()) {
            return;
        }

        if (KMessageBox::questionYesNo(parentWidget,
                                       contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxText, instances.count(), instances.first().name()),
                                       contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxTitle, instances.count(), instances.first().name()),
                                       KStandardGuiItem::del(), KStandardGuiItem::cancel(),
                                       QString(), KMessageBox::Dangerous) != KMessageBox::Yes) {
            return;
        }

        foreach (const AgentInstance &instance, instances) {
            AgentManager::self()->removeInstance(instance);
        }
    }

    void slotSynchronizeResource()
    {
        const AgentInstance::List instances = selectedAgentInstances();
        if (instances.isEmpty()) {
            return;
        }

        foreach (AgentInstance instance, instances) {    //krazy:exclude=foreach
            instance.synchronize();
        }
    }

    void slotResourceProperties()
    {
        AgentInstance instance = selectedAgentInstance();
        if (!instance.isValid()) {
            return;
        }

        instance.configure(parentWidget);
    }

    void slotToggleWorkOffline(bool offline)
    {
        setWorkOffline(offline);

        AgentInstance::List instances = AgentManager::self()->instances();
        foreach (AgentInstance instance, instances) {    //krazy:exclude=foreach
            instance.setIsOnline(!offline);
        }
    }

    void pasteTo(QItemSelectionModel *selectionModel, const QAbstractItemModel *model, StandardActionManager::Type type, Qt::DropAction dropAction)
    {
        const QSet<QString> mimeTypes = mimeTypesOfSelection(type);

        QPointer<CollectionDialog> dlg(new CollectionDialog(const_cast<QAbstractItemModel *>(model)));
        dlg->setMimeTypeFilter(mimeTypes.toList());

        if (type == CopyItemToMenu || type == MoveItemToMenu) {
            dlg->setAccessRightsFilter(Collection::CanCreateItem);
        } else if (type == CopyCollectionToMenu || type == MoveCollectionToMenu) {
            dlg->setAccessRightsFilter(Collection::CanCreateCollection);
        }

        if (dlg->exec() == QDialog::Accepted && dlg != 0) {
            const QModelIndex index = EntityTreeModel::modelIndexForCollection(collectionSelectionModel->model(), dlg->selectedCollection());
            if (!index.isValid()) {
                return;
            }

            const QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel));

            QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model());
            model->dropMimeData(mimeData, dropAction, -1, -1, index);
        }
        delete dlg;
    }

    void pasteTo(QItemSelectionModel *selectionModel, QAction *action, Qt::DropAction dropAction)
    {
        Q_ASSERT(selectionModel);
        Q_ASSERT(action);

        if (safeSelectedRows(selectionModel).count() <= 0) {
            return;
        }

        const QMimeData *mimeData = selectionModel->model()->mimeData(selectionModel->selectedRows());

        const QModelIndex index = action->data().value<QModelIndex>();
        Q_ASSERT(index.isValid());

        QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model());
        const Collection collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
        addRecentCollection(collection.id());
        model->dropMimeData(mimeData, dropAction, -1, -1, index);
    }

    void addRecentCollection(Akonadi::Collection::Id id)
    {
        QMapIterator<StandardActionManager::Type, QWeakPointer<RecentCollectionAction> > item(mRecentCollectionsMenu);
        while (item.hasNext()) {
            item.next();
            if (item.value().data()) {
                item.value().data()->addRecentCollection(item.key(),id);
            }
        }
    }

    void collectionCreationResult(KJob *job)
    {
        if (job->error()) {
            KMessageBox::error(parentWidget,
                               contextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText, job->errorString()),
                               contextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle));
        }
    }

    void collectionDeletionResult(KJob *job)
    {
        if (job->error()) {
            KMessageBox::error(parentWidget,
                               contextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText, job->errorString()),
                               contextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle));
        }
    }

    void moveCollectionToTrashResult(KJob *job)
    {
        if (job->error()) {
            KMessageBox::error(parentWidget,
                               contextText(StandardActionManager::MoveCollectionsToTrash, StandardActionManager::ErrorMessageText, job->errorString()),
                               contextText(StandardActionManager::MoveCollectionsToTrash, StandardActionManager::ErrorMessageTitle));
        }
    }

    void moveItemToTrashResult(KJob *job)
    {
        if (job->error()) {
            KMessageBox::error(parentWidget,
                               contextText(StandardActionManager::MoveItemsToTrash, StandardActionManager::ErrorMessageText, job->errorString()),
                               contextText(StandardActionManager::MoveItemsToTrash, StandardActionManager::ErrorMessageTitle));
        }
    }

    void itemDeletionResult(KJob *job)
    {
        if (job->error()) {
            KMessageBox::error(parentWidget,
                               contextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText, job->errorString()),
                               contextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle));
        }
    }

    void resourceCreationResult(KJob *job)
    {
        if (job->error()) {
            KMessageBox::error(parentWidget,
                               contextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText, job->errorString()),
                               contextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle));
        }
    }

    void pasteResult(KJob *job)
    {
        if (job->error()) {
            KMessageBox::error(parentWidget,
                               contextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageText, job->errorString()),
                               contextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle));
        }
    }

    /**
     * Returns a set of mime types of the entities that are currently selected.
     */
    QSet<QString> mimeTypesOfSelection(StandardActionManager::Type type) const
    {
        QModelIndexList list;
        QSet<QString> mimeTypes;

        const bool isItemAction = (type == CopyItemToMenu || type == MoveItemToMenu);
        const bool isCollectionAction = (type == CopyCollectionToMenu || type == MoveCollectionToMenu);

        if (isItemAction) {
            list = safeSelectedRows(itemSelectionModel);
            foreach (const QModelIndex &index, list) {
                mimeTypes << index.data(EntityTreeModel::MimeTypeRole).toString();
            }
        }

        if (isCollectionAction) {
            list = safeSelectedRows(collectionSelectionModel);
            foreach (const QModelIndex &index, list) {
                const Collection collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();

                // The mimetypes that the selected collection can possibly contain
                mimeTypes = AgentManager::self()->instance(collection.resource()).type().mimeTypes().toSet();
            }
        }

        return mimeTypes;
    }

    /**
     * Returns whether items with the given @p mimeTypes can be written to the given @p collection.
     */
    bool isWritableTargetCollectionForMimeTypes(const Collection &collection, const QSet<QString> &mimeTypes, StandardActionManager::Type type) const
    {
        if (collection.isVirtual()) {
            return false;
        }

        const bool isItemAction = (type == CopyItemToMenu || type == MoveItemToMenu);
        const bool isCollectionAction = (type == CopyCollectionToMenu || type == MoveCollectionToMenu);

        const bool canContainRequiredMimeTypes = !collection.contentMimeTypes().toSet().intersect(mimeTypes).isEmpty();
        const bool canCreateNewItems = (collection.rights() & Collection::CanCreateItem);

        const bool canCreateNewCollections = (collection.rights() & Collection::CanCreateCollection);
        const bool canContainCollections = collection.contentMimeTypes().contains(Collection::mimeType()) || collection.contentMimeTypes().contains(Collection::virtualMimeType());
        const bool resourceAllowsRequiredMimeTypes = AgentManager::self()->instance(collection.resource()).type().mimeTypes().toSet().contains(mimeTypes);

        const bool isReadOnlyForItems = (isItemAction && (!canCreateNewItems || !canContainRequiredMimeTypes));
        const bool isReadOnlyForCollections = (isCollectionAction && (!canCreateNewCollections || !canContainCollections || !resourceAllowsRequiredMimeTypes));

        return !(CollectionUtils::isStructural(collection) || isReadOnlyForItems || isReadOnlyForCollections);
    }

    void fillFoldersMenu(const Akonadi::Collection::List &selectedCollectionsList, const QSet<QString> &mimeTypes,  StandardActionManager::Type type, QMenu *menu,
                         const QAbstractItemModel *model, QModelIndex parentIndex)
    {
        const int rowCount = model->rowCount(parentIndex);

        for (int row = 0; row < rowCount; ++row) {
            const QModelIndex index = model->index(row, 0, parentIndex);
            const Collection collection = model->data(index, CollectionModel::CollectionRole).value<Collection>();

            if (collection.isVirtual()) {
                continue;
            }

            const bool readOnly = !isWritableTargetCollectionForMimeTypes( collection, mimeTypes, type );
            const bool collectionIsSelected = selectedCollectionsList.contains( collection );
            if (type == MoveCollectionToMenu && collectionIsSelected) {
                continue;
            }

            QString label = model->data(index).toString();
            label.replace(QLatin1String("&"), QLatin1String("&&"));

            const QIcon icon = model->data(index, Qt::DecorationRole).value<QIcon>();

            if (model->rowCount(index) > 0) {
                // new level
                QMenu *popup = new QMenu(menu);
                const bool moveAction = (type == MoveCollectionToMenu || type == MoveItemToMenu);
                popup->setObjectName(QString::fromUtf8("subMenu"));
                popup->setTitle(label);
                popup->setIcon(icon);

                fillFoldersMenu(selectedCollectionsList, mimeTypes, type, popup, model, index);

                if (!(type == CopyCollectionToMenu && collectionIsSelected)) {
                    if ( !readOnly ) {
                        popup->addSeparator();

                        QAction *action = popup->addAction( moveAction ? i18n( "Move to This Folder" ) : i18n( "Copy to This Folder" ) );
                        action->setData( QVariant::fromValue<QModelIndex>( index ) );
                    }
               }

                menu->addMenu(popup);

            } else {
                // insert an item
                QAction *action = menu->addAction(icon, label);
                action->setData(QVariant::fromValue<QModelIndex>(index));
                action->setEnabled(!readOnly && !collectionIsSelected);
            }
        }
    }

    void checkModelsConsistency()
    {
        if (favoritesModel == 0 || favoriteSelectionModel == 0) {
            // No need to check when the favorite collections feature is not used
            return;
        }

        // find the base ETM of the favourites view
        const QAbstractItemModel *favModel = favoritesModel;
        while (const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(favModel)) {
            favModel = proxy->sourceModel();
        }

        // Check that the collection selection model maps to the same
        // EntityTreeModel than favoritesModel
        if (collectionSelectionModel != 0) {
            const QAbstractItemModel *model = collectionSelectionModel->model();
            while (const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model)) {
                model = proxy->sourceModel();
            }

            Q_ASSERT(model == favModel);
        }

        // Check that the favorite selection model maps to favoritesModel
        const QAbstractItemModel *model = favoriteSelectionModel->model();
        while (const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model)) {
            model = proxy->sourceModel();
        }
        Q_ASSERT(model == favModel);
    }

    void markCutAction(QMimeData *mimeData, bool cut) const
    {
        if (!cut) {
            return;
        }

        const QByteArray cutSelectionData = "1"; //krazy:exclude=doublequote_chars
        mimeData->setData(QLatin1String("application/x-kde.akonadi-cutselection"), cutSelectionData);
    }

    bool isCutAction(const QMimeData *mimeData) const
    {
        const QByteArray data = mimeData->data(QLatin1String("application/x-kde.akonadi-cutselection"));
        if (data.isEmpty()) {
            return false;
        } else {
            return (data.at(0) == '1');   // true if 1
        }
    }

    void setContextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const QString &data)
    {
        ContextTextEntry entry;
        entry.text = data;

        contextTexts[type].insert(context, entry);
    }

    void setContextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const KLocalizedString &data)
    {
        ContextTextEntry entry;
        entry.localizedText = data;

        contextTexts[type].insert(context, entry);
    }

    QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context) const
    {
        return contextTexts[type].value(context).text;
    }

    QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const QString &value) const
    {
        KLocalizedString text = contextTexts[type].value(context).localizedText;
        if (text.isEmpty()) {
            return contextTexts[type].value(context).text;
        }

        return text.subs(value).toString();
    }

    QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context, int count, const QString &value) const
    {
        KLocalizedString text = contextTexts[type].value(context).localizedText;
        if (text.isEmpty()) {
            return contextTexts[type].value(context).text;
        }

        const QString str = text.subs(count).toString();
        const int argCount = str.count(QRegExp(QLatin1String("%[0-9]")));
        if (argCount > 0) {
            return text.subs(count).subs(value).toString();
        } else {
            return text.subs(count).toString();
        }
    }

    StandardActionManager *q;
    KActionCollection *actionCollection;
    QWidget *parentWidget;
    QItemSelectionModel *collectionSelectionModel;
    QItemSelectionModel *itemSelectionModel;
    FavoriteCollectionsModel *favoritesModel;
    QItemSelectionModel *favoriteSelectionModel;
    bool insideSelectionSlot;
    QVector<KAction *> actions;
    QHash<StandardActionManager::Type, KLocalizedString> pluralLabels;
    QHash<StandardActionManager::Type, KLocalizedString> pluralIconLabels;

    struct ContextTextEntry
    {
        QString text;
        KLocalizedString localizedText;
        bool isLocalized;
    };

    typedef QHash<StandardActionManager::TextContext, ContextTextEntry> ContextTexts;
    QHash<StandardActionManager::Type, ContextTexts> contextTexts;

    ActionStateManager mActionStateManager;

    QStringList mMimeTypeFilter;
    QStringList mCapabilityFilter;
    QStringList mCollectionPropertiesPageNames;
    QMap<StandardActionManager::Type, QWeakPointer<RecentCollectionAction> > mRecentCollectionsMenu;
};

//@endcond

StandardActionManager::StandardActionManager(KActionCollection *actionCollection, QWidget *parent)
    : QObject(parent)
    , d(new Private(this))
{
    d->parentWidget = parent;
    d->actionCollection = actionCollection;
    d->mActionStateManager.setReceiver(this);
#ifndef QT_NO_CLIPBOARD
    connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), SLOT(clipboardChanged(QClipboard::Mode)));
#endif
}

StandardActionManager::~ StandardActionManager()
{
    delete d;
}

void StandardActionManager::setCollectionSelectionModel(QItemSelectionModel *selectionModel)
{
    d->collectionSelectionModel = selectionModel;
    connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            SLOT(collectionSelectionChanged()));

    d->checkModelsConsistency();
}

void StandardActionManager::setItemSelectionModel(QItemSelectionModel *selectionModel)
{
    d->itemSelectionModel = selectionModel;
    connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            SLOT(updateActions()));
}

void StandardActionManager::setFavoriteCollectionsModel(FavoriteCollectionsModel *favoritesModel)
{
    d->favoritesModel = favoritesModel;
    d->checkModelsConsistency();
}

void StandardActionManager::setFavoriteSelectionModel(QItemSelectionModel *selectionModel)
{
    d->favoriteSelectionModel = selectionModel;
    connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            SLOT(favoriteSelectionChanged()));
    d->checkModelsConsistency();
}

KAction *StandardActionManager::createAction(Type type)
{
    Q_ASSERT(type < LastType);
    if (d->actions[type]) {
        return d->actions[type];
    }
    KAction *action = 0;
    switch (standardActionData[type].actionType) {
    case NormalAction:
    case ActionWithAlternative:
        action = new KAction(d->parentWidget);
        break;
    case ActionAlternative:
        d->actions[type] = d->actions[type - 1];
        Q_ASSERT(d->actions[type]);
        if ((LastType > type + 1) && (standardActionData[type + 1].actionType == ActionAlternative)) {
            createAction(static_cast<Type>(type + 1)); //ensure that alternative actions are initialized when not created by createAllActions
        }
        return d->actions[type];
    case MenuAction:
        action = new KActionMenu(d->parentWidget);
        break;
    case ToggleAction:
        action = new KToggleAction(d->parentWidget);
        break;
    }

    if (d->pluralLabels.contains(type) && !d->pluralLabels.value(type).isEmpty()) {
        action->setText(d->pluralLabels.value(type).subs(1).toString());
    } else if (standardActionData[type].label) {
        action->setText(i18n(standardActionData[type].label));
    }

    if (d->pluralIconLabels.contains(type) && !d->pluralIconLabels.value(type).isEmpty()) {
        action->setIconText(d->pluralIconLabels.value(type).subs(1).toString());
    } else if (standardActionData[type].iconLabel) {
        action->setIconText(i18n(standardActionData[type].iconLabel));
    }

    if (standardActionData[type].icon) {
        action->setIcon(KIcon(QString::fromLatin1(standardActionData[type].icon)));
    }

    action->setShortcut(standardActionData[type].shortcut);

    if (standardActionData[type].slot) {
        switch (standardActionData[type].actionType) {
        case NormalAction:
        case ActionWithAlternative:
            connect(action, SIGNAL(triggered()), standardActionData[type].slot);
            break;
        case MenuAction: {
            KActionMenu *actionMenu = qobject_cast<KActionMenu *>(action);
            connect(actionMenu->menu(), SIGNAL(triggered(QAction*)), standardActionData[type].slot);
            break;
        }
        case ToggleAction: {
            connect(action, SIGNAL(triggered(bool)), standardActionData[type].slot);
            break;
        }
        case ActionAlternative:
            Q_ASSERT(0);
        }
    }

    if (type == ToggleWorkOffline) {
        // inititalize the action state with information from config file
        disconnect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot);
        action->setChecked(workOffline());
        connect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot);

        //TODO: find a way to check for updates to the config file
    }

    Q_ASSERT(standardActionData[type].name);
    d->actionCollection->addAction(QString::fromLatin1(standardActionData[type].name), action);
    d->actions[type] = action;
    if ((standardActionData[type].actionType == ActionWithAlternative) && (standardActionData[type + 1].actionType == ActionAlternative)) {
        createAction(static_cast<Type>(type + 1)); //ensure that alternative actions are initialized when not created by createAllActions
    }
    d->updateActions();
    return action;
}

void StandardActionManager::createAllActions()
{
    for (uint i = 0; i < LastType; ++i) {
        createAction((Type)i);
    }
}

KAction *StandardActionManager::action(Type type) const
{
    Q_ASSERT(type < LastType);
    return d->actions[type];
}

void StandardActionManager::setActionText(Type type, const KLocalizedString &text)
{
    Q_ASSERT(type < LastType);
    d->pluralLabels.insert(type, text);
    d->updateActions();
}

void StandardActionManager::interceptAction(Type type, bool intercept)
{
    Q_ASSERT(type < LastType);

    const KAction *action = d->actions[type];

    if (!action) {
        return;
    }

    if (intercept) {
        disconnect(action, SIGNAL(triggered()), this, standardActionData[type].slot);
    } else {
        connect(action, SIGNAL(triggered()), standardActionData[type].slot);
    }
}

Akonadi::Collection::List StandardActionManager::selectedCollections() const
{
    Collection::List collections;

    if (!d->collectionSelectionModel) {
        return collections;
    }

    foreach (const QModelIndex &index, safeSelectedRows(d->collectionSelectionModel)) {
        const Collection collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
        if (collection.isValid()) {
            collections << collection;
        }
    }

    return collections;
}

Item::List StandardActionManager::selectedItems() const
{
    Item::List items;

    if (!d->itemSelectionModel) {
        return items;
    }

    foreach (const QModelIndex &index, safeSelectedRows(d->itemSelectionModel)) {
        const Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
        if (item.isValid()) {
            items << item;
        }
    }

    return items;
}

void StandardActionManager::setContextText(Type type, TextContext context, const QString &text)
{
    d->setContextText(type, context, text);
}

void StandardActionManager::setContextText(Type type, TextContext context, const KLocalizedString &text)
{
    d->setContextText(type, context, text);
}

void StandardActionManager::setMimeTypeFilter(const QStringList &mimeTypes)
{
    d->mMimeTypeFilter = mimeTypes;
}

void StandardActionManager::setCapabilityFilter(const QStringList &capabilities)
{
    d->mCapabilityFilter = capabilities;
}

void StandardActionManager::setCollectionPropertiesPageNames(const QStringList &names)
{
    d->mCollectionPropertiesPageNames = names;
}

void StandardActionManager::createActionFolderMenu(QMenu *menu, Type type)
{
    d->createActionFolderMenu(menu, type);
}

#include "moc_standardactionmanager.cpp"
