standardactionmanager.cpp 72.8 KB
Newer Older
1
/*
2
    SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
3

4
    SPDX-License-Identifier: LGPL-2.0-or-later
5
6
7
*/

#include "standardactionmanager.h"
8

9
#include "actionstatemanager_p.h"
10
11
#include "agentfilterproxymodel.h"
#include "agentinstancecreatejob.h"
12
#include "agentmanager.h"
13
#include "agenttypedialog.h"
14
15
#include "collectioncreatejob.h"
#include "collectiondeletejob.h"
16
#include "collectiondialog.h"
17
#include "collectionpropertiespage.h"
18
#include "collectionutils.h"
19
#include "entitytreemodel.h"
20
#include "favoritecollectionsmodel.h"
21
#include "itemdeletejob.h"
22
#include "pastehelper_p.h"
23
#include "specialcollectionattribute.h"
24
#include "collectionpropertiesdialog.h"
25
#include "subscriptiondialog.h"
26
#include "renamefavoritedialog_p.h"
Christian Mollekopf's avatar
Christian Mollekopf committed
27
28
29
#include "trashjob.h"
#include "trashrestorejob.h"
#include "entitydeletedattribute.h"
30
#include "recentcollectionaction_p.h"
31

32
#include <QIcon>
Laurent Montel's avatar
Laurent Montel committed
33
#include <QAction>
34
#include <KActionCollection>
Kevin Ottens's avatar
Kevin Ottens committed
35
#include <KActionMenu>
36
#include <KLocalizedString>
37
#include <KConfigGroup>
Laurent Montel's avatar
Laurent Montel committed
38
#include <QMenu>
39
#include <KMessageBox>
40
#include <KToggleAction>
41

Daniel Vrátil's avatar
Daniel Vrátil committed
42
#include <QMimeData>
43
44
45
#include <QApplication>
#include <QClipboard>
#include <QItemSelectionModel>
Kevin Krammer's avatar
Kevin Krammer committed
46
#include <QPointer>
Laurent Montel's avatar
Laurent Montel committed
47
#include <QInputDialog>
48
#include <QRegularExpression>
49
#include <QTimer>
50
51
52

using namespace Akonadi;

53
54
//@cond PRIVATE

Guy Maurel's avatar
Guy Maurel committed
55
enum ActionType {
Guy Maurel's avatar
Guy Maurel committed
56
57
58
59
60
    NormalAction,
    ActionWithAlternative, //Normal action, but with an alternative state
    ActionAlternative, //Alternative state of the ActionWithAlternative
    MenuAction,
    ToggleAction
61
62
};

Daniel Vrátil's avatar
Daniel Vrátil committed
63
static const struct { // NOLINT(clang-analyzer-optin.performance.Padding) FIXME
Guy Maurel's avatar
Guy Maurel committed
64
65
66
67
68
69
70
    const char *name;
    const char *label;
    const char *iconLabel;
    const char *icon;
    int shortcut;
    const char *slot;
    ActionType actionType;
71
} standardActionData[] = {
Guy Maurel's avatar
Guy Maurel committed
72
    { "akonadi_collection_create", I18N_NOOP("&New Folder..."), I18N_NOOP("New"), "folder-new", 0, SLOT(slotCreateCollection()), NormalAction },
Laurent Montel's avatar
Laurent Montel committed
73
    { "akonadi_collection_copy", nullptr, nullptr, "edit-copy", 0, SLOT(slotCopyCollections()), NormalAction },
Guy Maurel's avatar
Guy Maurel committed
74
75
76
    { "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 },
Laurent Montel's avatar
Laurent Montel committed
77
    { "akonadi_item_copy", nullptr, nullptr, "edit-copy", 0, SLOT(slotCopyItems()), NormalAction },
Guy Maurel's avatar
Guy Maurel committed
78
    { "akonadi_paste", I18N_NOOP("&Paste"), I18N_NOOP("Paste"), "edit-paste", Qt::CTRL + Qt::Key_V, SLOT(slotPaste()), NormalAction },
Laurent Montel's avatar
Laurent Montel committed
79
    { "akonadi_item_delete", nullptr, nullptr, "edit-delete", 0, SLOT(slotDeleteItems()), NormalAction },
Guy Maurel's avatar
Guy Maurel committed
80
81
82
83
84
85
86
87
88
89
    { "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 },
Laurent Montel's avatar
Laurent Montel committed
90
91
    { "akonadi_resource_create", I18N_NOOP("Create Resource"), nullptr, "folder-new", 0, SLOT(slotCreateResource()), NormalAction },
    { "akonadi_resource_delete", I18N_NOOP("Delete Resource"), nullptr, "edit-delete", 0, SLOT(slotDeleteResource()), NormalAction },
Guy Maurel's avatar
Guy Maurel committed
92
93
    { "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 },
Laurent Montel's avatar
Laurent Montel committed
94
    { "akonadi_work_offline", I18N_NOOP("Work Offline"), nullptr, "user-offline", 0, SLOT(slotToggleWorkOffline(bool)), ToggleAction },
Guy Maurel's avatar
Guy Maurel committed
95
96
97
98
99
    { "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 },
Laurent Montel's avatar
Laurent Montel committed
100
101
    { "akonadi_move_collection_to_trash", I18N_NOOP("&Move Folder To Trash"), I18N_NOOP("Move Folder To Trash"), "edit-delete", 0, SLOT(slotMoveCollectionToTrash()), NormalAction },
    { "akonadi_move_item_to_trash", I18N_NOOP("&Move Item To Trash"), I18N_NOOP("Move Item To Trash"), "edit-delete", 0, SLOT(slotMoveItemToTrash()), NormalAction },
Guy Maurel's avatar
Guy Maurel committed
102
103
    { "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 },
Laurent Montel's avatar
Laurent Montel committed
104
    { "akonadi_collection_trash_restore", I18N_NOOP("&Restore Folder From Trash"), I18N_NOOP("Restore Folder From Trash"), "edit-delete", 0, SLOT(slotTrashRestoreCollection()), ActionWithAlternative },
Laurent Montel's avatar
Laurent Montel committed
105
    { nullptr, I18N_NOOP("&Restore Collection From Trash"), I18N_NOOP("Restore Collection From Trash"), "view-refresh", 0, nullptr, ActionAlternative },
Laurent Montel's avatar
Laurent Montel committed
106
    { "akonadi_item_trash_restore", I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "edit-delete", 0, SLOT(slotTrashRestoreItem()), ActionWithAlternative },
Laurent Montel's avatar
Laurent Montel committed
107
    { nullptr, I18N_NOOP("&Restore Item From Trash"), I18N_NOOP("Restore Item From Trash"), "view-refresh", 0, nullptr, ActionAlternative },
Laurent Montel's avatar
Laurent Montel committed
108
    { "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 },
109
    { "akonadi_resource_synchronize_collectiontree", I18N_NOOP("Synchronize Folder Tree"), I18N_NOOP("Synchronize"), "view-refresh", 0, SLOT(slotSynchronizeCollectionTree()), NormalAction }
110

111
};
Guy Maurel's avatar
Guy Maurel committed
112
static const int numStandardActionData = sizeof standardActionData / sizeof * standardActionData;
113

114
115
static_assert(numStandardActionData == StandardActionManager::LastType,
              "StandardActionData table does not match StandardActionManager types");
116

Guy Maurel's avatar
Guy Maurel committed
117
static bool canCreateCollection(const Akonadi::Collection &collection)
Tobias Koenig's avatar
Tobias Koenig committed
118
{
119
    return !!(collection.rights() & Akonadi::Collection::CanCreateCollection);
Tobias Koenig's avatar
Tobias Koenig committed
120
121
}

Guy Maurel's avatar
Guy Maurel committed
122
static void setWorkOffline(bool offline)
123
{
124
125
    KConfig config(QStringLiteral("akonadikderc"));
    KConfigGroup group(&config, QStringLiteral("Actions"));
126

Guy Maurel's avatar
Guy Maurel committed
127
    group.writeEntry("WorkOffline", offline);
128
129
130
131
}

static bool workOffline()
{
132
133
    KConfig config(QStringLiteral("akonadikderc"));
    const KConfigGroup group(&config, QStringLiteral("Actions"));
134

Guy Maurel's avatar
Guy Maurel committed
135
    return group.readEntry("WorkOffline", false);
136
137
}

Guy Maurel's avatar
Guy Maurel committed
138
static QModelIndexList safeSelectedRows(QItemSelectionModel *selectionModel)
139
{
Guy Maurel's avatar
Guy Maurel committed
140
141
142
143
    QModelIndexList selectedRows = selectionModel->selectedRows();
    if (!selectedRows.isEmpty()) {
        return selectedRows;
    }
144

Guy Maurel's avatar
Guy Maurel committed
145
    // 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)
Daniel Vrátil's avatar
Daniel Vrátil committed
146
147
    const auto selection = selectionModel->selection();
    for (const auto &range : selection) {
Guy Maurel's avatar
Guy Maurel committed
148
149
150
151
152
153
154
        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);
Guy Maurel's avatar
Guy Maurel committed
155
            if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) {
Guy Maurel's avatar
Guy Maurel committed
156
157
158
                selectedRows.push_back(index);
            }
        }
159
160
    }

Guy Maurel's avatar
Guy Maurel committed
161
    return selectedRows;
162
163
}

Tobias Koenig's avatar
Tobias Koenig committed
164
165
166
/**
 * @internal
 */
Laurent Montel's avatar
Laurent Montel committed
167
class Q_DECL_HIDDEN StandardActionManager::Private
168
{
Guy Maurel's avatar
Guy Maurel committed
169
public:
170
    explicit Private(StandardActionManager *parent)
Guy Maurel's avatar
Guy Maurel committed
171
        : q(parent)
Laurent Montel's avatar
Laurent Montel committed
172
173
174
175
176
177
        , actionCollection(nullptr)
        , parentWidget(nullptr)
        , collectionSelectionModel(nullptr)
        , itemSelectionModel(nullptr)
        , favoritesModel(nullptr)
        , favoriteSelectionModel(nullptr)
Guy Maurel's avatar
Guy Maurel committed
178
179
        , insideSelectionSlot(false)
    {
Laurent Montel's avatar
Laurent Montel committed
180
        actions.fill(nullptr, StandardActionManager::LastType);
Guy Maurel's avatar
Guy Maurel committed
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

        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,
223
                       i18nc("@label:textbox name of Akonadi folder", "Name"));
Guy Maurel's avatar
Guy Maurel committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
        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"));

275
276
277
        mDelayedUpdateTimer.setSingleShot(true);
        connect(&mDelayedUpdateTimer, &QTimer::timeout, q, [this]() { updateActions(); });

Guy Maurel's avatar
Guy Maurel committed
278
279
        qRegisterMetaType<Akonadi::Item::List>("Akonadi::Item::List");
    }
280
281

    void enableAction(int type, bool enable) // private slot, called by ActionStateManager
Guy Maurel's avatar
Guy Maurel committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
    {
        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.
Laurent Montel's avatar
Laurent Montel committed
297
            QMenu *menu = actionMenu->menu();
Guy Maurel's avatar
Guy Maurel committed
298
299
300
301
302
303
            //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;
Laurent Montel's avatar
Laurent Montel committed
304
            menu = new QMenu();
Guy Maurel's avatar
Guy Maurel committed
305
306

            menu->setProperty("actionType", static_cast<int>(type));
Daniel Vrátil's avatar
Daniel Vrátil committed
307
308
            q->connect(menu, &QMenu::aboutToShow, q, [this]() { aboutToShowMenu(); });
            q->connect(menu, SIGNAL(triggered(QAction*)), standardActionData[type].slot); // clazy:exclude=old-style-connect
Guy Maurel's avatar
Guy Maurel committed
309
310
            actionMenu->setMenu(menu);
        }
311
    }
312

313
314
    void aboutToShowMenu()
    {
Guy Maurel's avatar
Guy Maurel committed
315
316
317
318
        QMenu *menu = qobject_cast<QMenu *>(q->sender());
        if (!menu) {
            return;
        }
319

Guy Maurel's avatar
Guy Maurel committed
320
321
322
323
324
325
        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());
326

327
        QPointer<RecentCollectionAction> recentCollection = new RecentCollectionAction(type, selectedCollectionsList, collectionSelectionModel->model(), menu);
Guy Maurel's avatar
Guy Maurel committed
328
329
330
331
332
333
334
335
        mRecentCollectionsMenu.insert(type, recentCollection);
        const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
        fillFoldersMenu(selectedCollectionsList,
                        mimeTypes,
                        type,
                        menu,
                        collectionSelectionModel->model(),
                        QModelIndex());
336
337
    }

338
339
    void createActionFolderMenu(QMenu *menu, StandardActionManager::Type type)
    {
Guy Maurel's avatar
Guy Maurel committed
340
        if (type == CopyCollectionToMenu ||
Laurent Montel's avatar
Laurent Montel committed
341
342
343
                type == CopyItemToMenu ||
                type == MoveItemToMenu ||
                type == MoveCollectionToMenu) {
344

345
            new RecentCollectionAction(type, Akonadi::Collection::List(), collectionSelectionModel->model(), menu);
Guy Maurel's avatar
Guy Maurel committed
346
347
348
349
350
351
352
353
354
            Collection::List selectedCollectionsList = selectedCollections();
            const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
            fillFoldersMenu(selectedCollectionsList,
                            mimeTypes,
                            type,
                            menu,
                            collectionSelectionModel->model(),
                            QModelIndex());
        }
355
356
    }

357
    void updateAlternatingAction(int type) // private slot, called by ActionStateManager
Christian Mollekopf's avatar
Christian Mollekopf committed
358
    {
Guy Maurel's avatar
Guy Maurel committed
359
        updateAlternatingAction(static_cast<StandardActionManager::Type>(type));
Christian Mollekopf's avatar
Christian Mollekopf committed
360
361
    }

Guy Maurel's avatar
Guy Maurel committed
362
    void updateAlternatingAction(StandardActionManager::Type type)
Christian Mollekopf's avatar
Christian Mollekopf committed
363
    {
Guy Maurel's avatar
Guy Maurel committed
364
365
366
367
        Q_ASSERT(type < StandardActionManager::LastType);
        if (!actions[type]) {
            return;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
368

Guy Maurel's avatar
Guy Maurel committed
369
370
371
372
373
374
        /*
         * 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));
375
            actions[type]->setIcon(QIcon::fromTheme(QString::fromLatin1(standardActionData[type].icon)));
Guy Maurel's avatar
Guy Maurel committed
376
377
378
379
380
381
382
383
384
385
386
387
388
389

            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) {
390
                actions[type]->setIcon(QIcon::fromTheme(QString::fromLatin1(standardActionData[type].icon)));
Guy Maurel's avatar
Guy Maurel committed
391
392
393
394
395
396
397
398
399
400
401
402
403
            }

            //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;
              }
            }*/
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
404
405
    }

406
    void updatePluralLabel(int type, int count) // private slot, called by ActionStateManager
407
    {
Guy Maurel's avatar
Guy Maurel committed
408
        updatePluralLabel(static_cast<StandardActionManager::Type>(type), count);
409
410
    }

411
    void updatePluralLabel(StandardActionManager::Type type, int count) // private slot, called by ActionStateManager
412
    {
Guy Maurel's avatar
Guy Maurel committed
413
414
415
416
        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());
        }
417
418
    }

419
    bool isFavoriteCollection(const Akonadi::Collection &collection) const // private slot, called by ActionStateManager
420
    {
Guy Maurel's avatar
Guy Maurel committed
421
422
423
        if (!favoritesModel) {
            return false;
        }
424

Guy Maurel's avatar
Guy Maurel committed
425
        return favoritesModel->collectionIds().contains(collection.id());
426
427
    }

Guy Maurel's avatar
Guy Maurel committed
428
    void encodeToClipboard(QItemSelectionModel *selectionModel, bool cut = false)
429
    {
Guy Maurel's avatar
Guy Maurel committed
430
        Q_ASSERT(selectionModel);
431
        if (safeSelectedRows(selectionModel).isEmpty()) {
Guy Maurel's avatar
Guy Maurel committed
432
433
            return;
        }
Tobias Koenig's avatar
Tobias Koenig committed
434

435
#ifndef QT_NO_CLIPBOARD
436
        QAbstractItemModel *model = const_cast<QAbstractItemModel *>(selectionModel->model());
Guy Maurel's avatar
Guy Maurel committed
437
        QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel));
Laurent Montel's avatar
Laurent Montel committed
438
        model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole);
Guy Maurel's avatar
Guy Maurel committed
439
440
        markCutAction(mimeData, cut);
        QApplication::clipboard()->setMimeData(mimeData);
441
442
443
444
445
        if (cut) {
            const auto rows = safeSelectedRows(selectionModel);
            for (const auto &index : rows) {
                model->setData(index, true, EntityTreeModel::PendingCutRole);
            }
Guy Maurel's avatar
Guy Maurel committed
446
        }
447
#endif
448
    }
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466

    static Akonadi::Collection::List collectionsForIndexes(const QModelIndexList& list)
    {
        Akonadi::Collection::List collectionList;
        for (const QModelIndex &index : list) {
            Collection collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
            if (!collection.isValid()) {
                continue;
            }

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

            collectionList << std::move(collection);
        }
        return collectionList;
    }

467
468
469
470
471
472
    void delayedUpdateActions()
    {
        // Compress changes (e.g. when deleting many rows, do this only once)
        mDelayedUpdateTimer.start(0);
    }

473
474
    void updateActions()
    {
475
476
477
478
479
480
481
        // favorite collections
        Collection::List selectedFavoriteCollectionsList;
        if (favoriteSelectionModel) {
            const QModelIndexList rows = safeSelectedRows(favoriteSelectionModel);
            selectedFavoriteCollectionsList = collectionsForIndexes(rows);
        }

Guy Maurel's avatar
Guy Maurel committed
482
483
484
485
        // collect all selected collections
        Collection::List selectedCollectionsList;
        if (collectionSelectionModel) {
            const QModelIndexList rows = safeSelectedRows(collectionSelectionModel);
486
            selectedCollectionsList = collectionsForIndexes(rows);
487
        }
488

Guy Maurel's avatar
Guy Maurel committed
489
490
491
492
        // collect all selected items
        Item::List selectedItems;
        if (itemSelectionModel) {
            const QModelIndexList rows = safeSelectedRows(itemSelectionModel);
Laurent Montel's avatar
Laurent Montel committed
493
            for (const QModelIndex &index : rows) {
Guy Maurel's avatar
Guy Maurel committed
494
495
496
497
498
499
500
501
502
503
                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;
            }
504
        }
505

506
        mActionStateManager.updateState(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems);
Guy Maurel's avatar
Guy Maurel committed
507
508
509
        if (favoritesModel) {
            enableAction(StandardActionManager::SynchronizeFavoriteCollections, (favoritesModel->rowCount() > 0));
        }
510
        Q_EMIT q->selectionsChanged(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems);
511
        Q_EMIT q->actionStateUpdated();
512
513
    }

514
#ifndef QT_NO_CLIPBOARD
Guy Maurel's avatar
Guy Maurel committed
515
    void clipboardChanged(QClipboard::Mode mode)
516
    {
Guy Maurel's avatar
Guy Maurel committed
517
518
519
        if (mode == QClipboard::Clipboard) {
            updateActions();
        }
520
    }
521
#endif
522

Guy Maurel's avatar
Guy Maurel committed
523
    QItemSelection mapToEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
524
    {
Guy Maurel's avatar
Guy Maurel committed
525
526
527
528
529
530
        const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model);
        if (proxy) {
            return mapToEntityTreeModel(proxy->sourceModel(), proxy->mapSelectionToSource(selection));
        } else {
            return selection;
        }
531
532
    }

Guy Maurel's avatar
Guy Maurel committed
533
    QItemSelection mapFromEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
534
    {
Guy Maurel's avatar
Guy Maurel committed
535
536
537
538
539
540
541
        const QAbstractProxyModel *proxy = qobject_cast<const QAbstractProxyModel *>(model);
        if (proxy) {
            const QItemSelection select = mapFromEntityTreeModel(proxy->sourceModel(), selection);
            return proxy->mapSelectionFromSource(select);
        } else {
            return selection;
        }
542
543
    }

544
    // RAII class for setting insideSelectionSlot to true on entering, and false on exiting, the two slots below.
Laurent Montel's avatar
Laurent Montel committed
545
546
    class InsideSelectionSlotBlocker
    {
547
    public:
548
        explicit InsideSelectionSlotBlocker(Private *p)
Guy Maurel's avatar
Guy Maurel committed
549
550
551
552
553
554
555
556
557
558
559
            : _p(p)
        {
            Q_ASSERT(!p->insideSelectionSlot);
            p->insideSelectionSlot = true;
        }

        ~InsideSelectionSlotBlocker()
        {
            Q_ASSERT(_p->insideSelectionSlot);
            _p->insideSelectionSlot = false;
        }
560
    private:
561
        Q_DISABLE_COPY(InsideSelectionSlotBlocker)
Guy Maurel's avatar
Guy Maurel committed
562
        Private *_p;
563
564
    };

565
566
    void collectionSelectionChanged()
    {
Guy Maurel's avatar
Guy Maurel committed
567
568
569
570
571
        if (insideSelectionSlot) {
            return;
        }
        InsideSelectionSlotBlocker block(this);
        if (favoriteSelectionModel) {
572
573
574
575
            QItemSelection selection = collectionSelectionModel->selection();
            selection = mapToEntityTreeModel(collectionSelectionModel->model(), selection);
            selection = mapFromEntityTreeModel(favoriteSelectionModel->model(), selection);

Guy Maurel's avatar
Guy Maurel committed
576
577
            favoriteSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
        }
578

Guy Maurel's avatar
Guy Maurel committed
579
        updateActions();
580
581
582
583
    }

    void favoriteSelectionChanged()
    {
Guy Maurel's avatar
Guy Maurel committed
584
585
586
587
588
589
590
        if (insideSelectionSlot) {
            return;
        }
        QItemSelection selection = favoriteSelectionModel->selection();
        if (selection.isEmpty()) {
            return;
        }
Tobias Koenig's avatar
Tobias Koenig committed
591

592
        selection = mapToEntityTreeModel(favoriteSelectionModel->model(), selection);
Guy Maurel's avatar
Guy Maurel committed
593
        selection = mapFromEntityTreeModel(collectionSelectionModel->model(), selection);
Tobias Koenig's avatar
Tobias Koenig committed
594

Guy Maurel's avatar
Guy Maurel committed
595
596
        InsideSelectionSlotBlocker block(this);
        collectionSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
597

Guy Maurel's avatar
Guy Maurel committed
598
        // Also set the current index. This will trigger KMMainWidget::slotFolderChanged in kmail, which we want.
599
600
601
        if (!selection.indexes().isEmpty()) {
            collectionSelectionModel->setCurrentIndex(selection.indexes().first(), QItemSelectionModel::NoUpdate);
        }
602

Guy Maurel's avatar
Guy Maurel committed
603
        updateActions();
604
605
    }

606
607
    void slotCreateCollection()
    {
Guy Maurel's avatar
Guy Maurel committed
608
609
610
611
        Q_ASSERT(collectionSelectionModel);
        if (collectionSelectionModel->selection().indexes().isEmpty()) {
            return;
        }
612

Guy Maurel's avatar
Guy Maurel committed
613
614
        const QModelIndex index = collectionSelectionModel->selection().indexes().at(0);
        Q_ASSERT(index.isValid());
615
        const Collection parentCollection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
Guy Maurel's avatar
Guy Maurel committed
616
        Q_ASSERT(parentCollection.isValid());
Tobias Koenig's avatar
Tobias Koenig committed
617

Guy Maurel's avatar
Guy Maurel committed
618
619
620
        if (!canCreateCollection(parentCollection)) {
            return;
        }
Tobias Koenig's avatar
Tobias Koenig committed
621

Laurent Montel's avatar
Laurent Montel committed
622
623
        QString name = QInputDialog::getText(parentWidget, contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle),
                                             contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText));
Guy Maurel's avatar
Guy Maurel committed
624
625
626
627
        name = name.trimmed();
        if (name.isEmpty()) {
            return;
        }
628

Guy Maurel's avatar
Guy Maurel committed
629
630
631
632
633
634
635
        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('.')) ||
Laurent Montel's avatar
Laurent Montel committed
636
                name.endsWith(QLatin1Char('.'))) {
Guy Maurel's avatar
Guy Maurel committed
637
638
639
640
641
            KMessageBox::error(parentWidget,
                               i18n("We can not add \".\" at begin or end of folder name."),
                               i18n("Create new folder error"));
            return;
        }
642

Guy Maurel's avatar
Guy Maurel committed
643
644
645
646
647
648
649
650
651
        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);
            }
        }
652
653
654
655
656
657
        if (parentCollection.contentMimeTypes().contains(Collection::virtualMimeType())) {
            collection.setVirtual(true);
            collection.setContentMimeTypes(collection.contentMimeTypes()
                                           << Collection::virtualMimeType());
        }

Guy Maurel's avatar
Guy Maurel committed
658
        CollectionCreateJob *job = new CollectionCreateJob(collection);
Daniel Vrátil's avatar
Daniel Vrátil committed
659
        q->connect(job, &KJob::result, q, [this](KJob *job) { collectionCreationResult(job); });
660
661
    }

662
    void slotCopyCollections()
663
    {
Guy Maurel's avatar
Guy Maurel committed
664
        encodeToClipboard(collectionSelectionModel);
665
666
667
668
    }

    void slotCutCollections()
    {
Guy Maurel's avatar
Guy Maurel committed
669
        encodeToClipboard(collectionSelectionModel, true);
670
671
    }

672
    Collection::List selectedCollections()
673
    {
Guy Maurel's avatar
Guy Maurel committed
674
        Collection::List collections;
675

Guy Maurel's avatar
Guy Maurel committed
676
        Q_ASSERT(collectionSelectionModel);
Sergio Martins's avatar
Sergio Martins committed
677
678
        const QModelIndexList indexes = safeSelectedRows(collectionSelectionModel);
        collections.reserve(indexes.count());
679

680
        for (const QModelIndex &index : indexes) {
Guy Maurel's avatar
Guy Maurel committed
681
            Q_ASSERT(index.isValid());
682
            const Collection collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
Guy Maurel's avatar
Guy Maurel committed
683
            Q_ASSERT(collection.isValid());
684

Guy Maurel's avatar
Guy Maurel committed
685
686
            collections << collection;
        }
687

Guy Maurel's avatar
Guy Maurel committed
688
        return collections;
689
690
691
692
    }

    void slotDeleteCollection()
    {
Guy Maurel's avatar
Guy Maurel committed
693
694
695
696
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }
697

Guy Maurel's avatar
Guy Maurel committed
698
699
700
        const QString collectionName = collections.first().name();
        const QString text = contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText,
                                         collections.count(), collectionName);
701

Guy Maurel's avatar
Guy Maurel committed
702
703
704
705
706
707
        if (KMessageBox::questionYesNo(parentWidget, text,
                                       contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxTitle, collections.count(), collectionName),
                                       KStandardGuiItem::del(), KStandardGuiItem::cancel(),
                                       QString(), KMessageBox::Dangerous) != KMessageBox::Yes) {
            return;
        }
Tobias Koenig's avatar
Tobias Koenig committed
708

Laurent Montel's avatar
Laurent Montel committed
709
        for (const Collection &collection : collections) {
Guy Maurel's avatar
Guy Maurel committed
710
            CollectionDeleteJob *job = new CollectionDeleteJob(collection, q);
Laurent Montel's avatar
Laurent Montel committed
711
            q->connect(job, &CollectionDeleteJob::result, q, [this](KJob* job) { collectionDeletionResult(job); });
Guy Maurel's avatar
Guy Maurel committed
712
        }
713
714
    }

Christian Mollekopf's avatar
Christian Mollekopf committed
715
716
    void slotMoveCollectionToTrash()
    {
Guy Maurel's avatar
Guy Maurel committed
717
718
719
720
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
721

Laurent Montel's avatar
Laurent Montel committed
722
        for (const Collection &collection : collections) {
Guy Maurel's avatar
Guy Maurel committed
723
            TrashJob *job = new TrashJob(collection, q);
Laurent Montel's avatar
Laurent Montel committed
724
            q->connect(job, &TrashJob::result, q, [this](KJob *job) { moveCollectionToTrashResult(job);});
Guy Maurel's avatar
Guy Maurel committed
725
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
726
727
728
729
    }

    void slotRestoreCollectionFromTrash()
    {
Guy Maurel's avatar
Guy Maurel committed
730
731
732
733
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
734

735
        for (const Collection &collection : collections) {
Guy Maurel's avatar
Guy Maurel committed
736
            TrashRestoreJob *job = new TrashRestoreJob(collection, q);
Laurent Montel's avatar
Laurent Montel committed
737
            q->connect(job, &TrashRestoreJob::result, q, [this](KJob*job) {moveCollectionToTrashResult(job);});
Guy Maurel's avatar
Guy Maurel committed
738
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
739
740
741
742
    }

    Item::List selectedItems() const
    {
Guy Maurel's avatar
Guy Maurel committed
743
        Item::List items;
Christian Mollekopf's avatar
Christian Mollekopf committed
744

Guy Maurel's avatar
Guy Maurel committed
745
        Q_ASSERT(itemSelectionModel);
Sergio Martins's avatar
Sergio Martins committed
746
747
        const QModelIndexList indexes = safeSelectedRows(itemSelectionModel);
        items.reserve(indexes.count());
Christian Mollekopf's avatar
Christian Mollekopf committed
748

Laurent Montel's avatar
Laurent Montel committed
749
        for (const QModelIndex &index : indexes) {
Guy Maurel's avatar
Guy Maurel committed
750
            Q_ASSERT(index.isValid());
751
            const Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
Guy Maurel's avatar
Guy Maurel committed
752
            Q_ASSERT(item.isValid());
Christian Mollekopf's avatar
Christian Mollekopf committed
753

Guy Maurel's avatar
Guy Maurel committed
754
755
            items << item;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
756

Guy Maurel's avatar
Guy Maurel committed
757
        return items;
Christian Mollekopf's avatar
Christian Mollekopf committed
758
759
760
761
    }

    void slotMoveItemToTrash()
    {
Guy Maurel's avatar
Guy Maurel committed
762
763
764
765
        const Item::List items = selectedItems();
        if (items.isEmpty()) {
            return;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
766

Guy Maurel's avatar
Guy Maurel committed
767
        TrashJob *job = new TrashJob(items, q);
Laurent Montel's avatar
Laurent Montel committed
768
        q->connect(job, &TrashJob::result, q, [this](KJob *job) {moveItemToTrashResult(job); });
Christian Mollekopf's avatar
Christian Mollekopf committed
769
770
771
772
    }

    void slotRestoreItemFromTrash()
    {
Guy Maurel's avatar
Guy Maurel committed
773
774
775
776
        const Item::List items = selectedItems();
        if (items.isEmpty()) {
            return;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
777

Guy Maurel's avatar
Guy Maurel committed
778
        TrashRestoreJob *job = new TrashRestoreJob(items, q);
Laurent Montel's avatar
Laurent Montel committed
779
        q->connect(job, &TrashRestoreJob::result, q, [this](KJob *job) {moveItemToTrashResult(job);});
Christian Mollekopf's avatar
Christian Mollekopf committed
780
781
782
783
    }

    void slotTrashRestoreCollection()
    {
Guy Maurel's avatar
Guy Maurel committed
784
785
786
787
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
788

Guy Maurel's avatar
Guy Maurel committed
789
        bool collectionsAreInTrash = false;
790
        for (const Collection &collection : collections) {
Guy Maurel's avatar
Guy Maurel committed
791
792
793
794
            if (collection.hasAttribute<EntityDeletedAttribute>()) {
                collectionsAreInTrash = true;
                break;
            }
Christian Mollekopf's avatar
Christian Mollekopf committed
795
796
        }

Guy Maurel's avatar
Guy Maurel committed
797
798
799
800
801
        if (collectionsAreInTrash) {
            slotRestoreCollectionFromTrash();
        } else {
            slotMoveCollectionToTrash();
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
802
803
804
805
    }

    void slotTrashRestoreItem()
    {
Guy Maurel's avatar
Guy Maurel committed
806
807
808
809
        const Item::List items = selectedItems();
        if (items.isEmpty()) {
            return;
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
810

Guy Maurel's avatar
Guy Maurel committed
811
        bool itemsAreInTrash = false;
812
        for (const Item &item : items) {
Guy Maurel's avatar
Guy Maurel committed
813
814
815
816
            if (item.hasAttribute<EntityDeletedAttribute>()) {
                itemsAreInTrash = true;
                break;
            }
Christian Mollekopf's avatar
Christian Mollekopf committed
817
818
        }

Guy Maurel's avatar
Guy Maurel committed
819
820
821
822
823
        if (itemsAreInTrash) {
            slotRestoreItemFromTrash();
        } else {
            slotMoveItemToTrash();
        }
Christian Mollekopf's avatar
Christian Mollekopf committed
824
825
    }

826
827
    void slotSynchronizeCollection()
    {
Guy Maurel's avatar
Guy Maurel committed
828
829
830
831
832
        Q_ASSERT(collectionSelectionModel);
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }
833

Guy Maurel's avatar
Guy Maurel committed
834
835
836
837
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }
838

839
        for (const Collection &collection : collections) {
Guy Maurel's avatar
Guy Maurel committed
840
841
842
843
844
            if (!testAndSetOnlineResources(collection)) {
                break;
            }
            AgentManager::self()->synchronizeCollection(collection, false);
        }
845
846
    }

Guy Maurel's avatar
Guy Maurel committed
847
    bool testAndSetOnlineResources(const Akonadi::Collection &collection)
848
    {
Guy Maurel's avatar
Guy Maurel committed
849
        // Shortcut for the Search resource, which is a virtual resource and thus
Laurent Montel's avatar
Laurent Montel committed
850
        // is always online (but AgentManager does not know about it, so it returns
Guy Maurel's avatar
Guy Maurel committed
851
852
853
854
        // an invalid AgentInstance, which is "offline").
        //
        // FIXME: AgentManager should return a valid AgentInstance even
        // for virtual resources, which would be always online.
855
        if (collection.resource() == QLatin1String("akonadi_search_resource")) {
Guy Maurel's avatar
Guy Maurel committed
856
857
            return true;
        }
858

Guy Maurel's avatar
Guy Maurel committed
859
860
861
862
863
864
865
866
867
868
869
        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;
870
871
    }

872
873
    void slotSynchronizeCollectionRecursive()
    {
Guy Maurel's avatar
Guy Maurel committed
874
875
876
877
878
        Q_ASSERT(collectionSelectionModel);
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }
879

Guy Maurel's avatar
Guy Maurel committed
880
881
882
883
        const Collection::List collections = selectedCollections();
        if (collections.isEmpty()) {
            return;
        }
884

885
        for (const Collection &collection : collections) {
Guy Maurel's avatar
Guy Maurel committed
886
887
888
889
890
            if (!testAndSetOnlineResources(collection)) {
                break;
            }
            AgentManager::self()->synchronizeCollection(collection, true);
        }
891
892
    }

893
    void slotCollectionProperties() const
894
    {
Guy Maurel's avatar
Guy Maurel committed
895
896
897
898
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }
899

Guy Maurel's avatar
Guy Maurel committed
900
901
        const QModelIndex index = list.first();
        Q_ASSERT(index.isValid());
902

903
        const Collection collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
Guy Maurel's avatar
Guy Maurel committed
904
        Q_ASSERT(collection.isValid());
905

Guy Maurel's avatar
Guy Maurel committed
906
        CollectionPropertiesDialog *dlg = new CollectionPropertiesDialog(collection, mCollectionPropertiesPageNames, parentWidget);
Laurent Montel's avatar
Laurent Montel committed
907
        dlg->setWindowTitle(contextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, collection.displayName()));
Guy Maurel's avatar
Guy Maurel committed
908
        dlg->show();
909
910
    }

911
912
    void slotCopyItems()
    {
Guy Maurel's avatar
Guy Maurel committed
913
        encodeToClipboard(itemSelectionModel);
914
915
916
917
    }

    void slotCutItems()
    {
Guy Maurel's avatar
Guy Maurel committed
918
        encodeToClipboard(itemSelectionModel, true);
919
920
    }

921
922
    void slotPaste()
    {
Guy Maurel's avatar
Guy Maurel committed
923
        Q_ASSERT(collectionSelectionModel);
924

Guy Maurel's avatar
Guy Maurel committed
925
926
927
928
        const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
        if (list.isEmpty()) {
            return;
        }
929

Guy Maurel's avatar
Guy Maurel committed
930
931
        const QModelIndex index = list.first();
        Q_ASSERT(index.isValid());
932

933
#ifndef QT_NO_CLIPBOARD
Guy Maurel's avatar
Guy Maurel committed
934
935
936
937
938
939
        // 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();
940
#endif
941
942
    }

Volker Krause's avatar
Volker Krause committed
943
944
    void slotDeleteItems()
    {
Guy Maurel's avatar
Guy Maurel committed
945
        Q_ASSERT(itemSelectionModel);
Volker Krause's avatar
Volker Krause committed
946

Guy Maurel's avatar
Guy Maurel committed
947
        Item::List items;
Sergio Martins's avatar
Sergio Martins committed
948
949
        const QModelIndexList indexes = safeSelectedRows(itemSelectionModel);
        items.reserve(indexes.count());
Laurent Montel's avatar
Laurent Montel committed
950
        for (const QModelIndex &index : indexes) {
Guy Maurel's avatar
Guy Maurel committed
951
            bool ok;
952
            const qlonglong id = index.data(EntityTreeModel::ItemIdRole).toLongLong(&ok);
Guy Maurel's avatar
Guy Maurel committed
953
954
955
            Q_ASSERT(ok);
            items << Item(id);
        }
Tobias Koenig's avatar
Tobias Koenig committed
956

Guy Maurel's avatar
Guy Maurel committed
957
958
959
        if (items.isEmpty()) {
            return;
        }
960

Laurent Montel's avatar
Laurent Montel committed
961
        QMetaObject::invokeMethod(q, [this, items] {slotDeleteItemsDeferred(items); }, Qt::QueuedConnection);
962
963
    }

Stephen Kelly's avatar
Stephen Kelly committed
964
    void slotDeleteItemsDeferred(const Akonadi::Item::List &items)
965
    {
Guy Maurel's avatar
Guy Maurel committed
966
        Q_ASSERT(itemSelectionModel);
967

Guy Maurel's avatar
Guy Maurel committed
968
969
970
971
972
973
974
        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;
        }
975

Guy Maurel's avatar
Guy Maurel committed
976
        ItemDeleteJob *job = new ItemDeleteJob(items, q);
Laurent Montel's avatar
Laurent Montel committed
977
        q->connect(job, &ItemDeleteJob::result, q, [this](KJob*job) {itemDeletionResult(job);});
Volker Krause's avatar
Volker Krause committed
978
979
    }

980
    void slotLocalSubscription() const
981
    {
Guy Maurel's avatar
Guy Maurel committed
982
983
984
        SubscriptionDialog *dlg = new SubscriptionDialog(mMimeTypeFilter, parentWidget);
        dlg->showHiddenCollection(true);
        dlg->show();
985
986
    }

987
988
    void slotAddToFavorites()
    {
Guy Maurel's avatar
Guy Maurel committed
989
990
991
992
993