Akonadi

standardactionmanager.cpp
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#include "agentconfigurationdialog.h"
11#include "agentfilterproxymodel.h"
12#include "agentinstancecreatejob.h"
13#include "agentmanager.h"
14#include "agenttypedialog.h"
15#include "collectioncreatejob.h"
16#include "collectiondeletejob.h"
17#include "collectiondialog.h"
18#include "collectionpropertiesdialog.h"
19#include "collectionpropertiespage.h"
20#include "collectionutils.h"
21#include "entitydeletedattribute.h"
22#include "entitytreemodel.h"
23#include "favoritecollectionsmodel.h"
24#include "itemdeletejob.h"
25#include "pastehelper_p.h"
26#include "recentcollectionaction_p.h"
27#include "renamefavoritedialog_p.h"
28#include "specialcollectionattribute.h"
29#include "subscriptiondialog.h"
30#include "trashjob.h"
31#include "trashrestorejob.h"
32
33#include <KActionCollection>
34#include <KActionMenu>
35#include <KConfig>
36#include <KConfigGroup>
37#include <KLocalizedString>
38#include <KMessageBox>
39#include <KToggleAction>
40#include <QAction>
41#include <QIcon>
42#include <QMenu>
43
44#include <QApplication>
45#include <QClipboard>
46#include <QInputDialog>
47#include <QItemSelectionModel>
48#include <QMimeData>
49#include <QPointer>
50#include <QRegularExpression>
51#include <QTimer>
52
53#include <KLazyLocalizedString>
54
55using namespace Akonadi;
56
57/// @cond PRIVATE
58
59enum ActionType {
60 NormalAction,
61 ActionWithAlternative, // Normal action, but with an alternative state
62 ActionAlternative, // Alternative state of the ActionWithAlternative
63 MenuAction,
64 ToggleAction
65};
66
67struct StandardActionData { // NOLINT(clang-analyzer-optin.performance.Padding) FIXME
68 const char *name;
69 const KLazyLocalizedString label;
70 const KLazyLocalizedString iconLabel;
71 const char *icon;
72 const char *altIcon;
73 QKeySequence shortcut;
74 const char *slot;
75 ActionType actionType;
76};
77
78static const StandardActionData standardActionData[] = {
79 {"akonadi_collection_create", kli18n("&New Folder..."), kli18n("New"), "folder-new", nullptr, QKeySequence(), SLOT(slotCreateCollection()), NormalAction},
80 {"akonadi_collection_copy",
83 "edit-copy",
84 nullptr,
86 SLOT(slotCopyCollections()),
87 NormalAction},
88 {"akonadi_collection_delete",
89 kli18n("&Delete Folder"),
90 kli18n("Delete"),
91 "edit-delete",
92 nullptr,
94 SLOT(slotDeleteCollection()),
95 NormalAction},
96 {"akonadi_collection_sync",
97 kli18n("&Synchronize Folder"),
98 kli18n("Synchronize"),
99 "view-refresh",
100 nullptr,
102 SLOT(slotSynchronizeCollection()),
103 NormalAction},
104 {"akonadi_collection_properties",
105 kli18n("Folder &Properties"),
106 kli18n("Properties"),
107 "configure",
108 nullptr,
109 QKeySequence(),
110 SLOT(slotCollectionProperties()),
111 NormalAction},
112 {"akonadi_item_copy", KLazyLocalizedString(), KLazyLocalizedString(), "edit-copy", nullptr, 0, SLOT(slotCopyItems()), NormalAction},
113 {"akonadi_paste", kli18n("&Paste"), kli18n("Paste"), "edit-paste", nullptr, Qt::CTRL | Qt::Key_V, SLOT(slotPaste()), NormalAction},
114 {"akonadi_item_delete", KLazyLocalizedString(), KLazyLocalizedString(), "edit-delete", nullptr, 0, SLOT(slotDeleteItems()), NormalAction},
115 {"akonadi_manage_local_subscriptions",
116 kli18n("Manage Local &Subscriptions..."),
117 kli18n("Manage Local Subscriptions"),
118 "folder-bookmarks",
119 nullptr,
120 QKeySequence(),
121 SLOT(slotLocalSubscription()),
122 NormalAction},
123 {"akonadi_collection_add_to_favorites",
124 kli18n("Add to Favorite Folders"),
125 kli18n("Add to Favorite"),
126 "bookmark-new",
127 nullptr,
128 QKeySequence(),
129 SLOT(slotAddToFavorites()),
130 NormalAction},
131 {"akonadi_collection_remove_from_favorites",
132 kli18n("Remove from Favorite Folders"),
133 kli18n("Remove from Favorite"),
134 "edit-delete",
135 nullptr,
136 QKeySequence(),
137 SLOT(slotRemoveFromFavorites()),
138 NormalAction},
139 {"akonadi_collection_rename_favorite",
140 kli18n("Rename Favorite..."),
141 kli18n("Rename"),
142 "edit-rename",
143 nullptr,
144 QKeySequence(),
145 SLOT(slotRenameFavorite()),
146 NormalAction},
147 {"akonadi_collection_copy_to_menu",
148 kli18n("Copy Folder To..."),
149 kli18n("Copy To"),
150 "edit-copy",
151 nullptr,
152 QKeySequence(),
153 SLOT(slotCopyCollectionTo(QAction *)),
154 MenuAction},
155 {"akonadi_item_copy_to_menu",
156 kli18n("Copy Item To..."),
157 kli18n("Copy To"),
158 "edit-copy",
159 nullptr,
160 QKeySequence(),
161 SLOT(slotCopyItemTo(QAction *)),
162 MenuAction},
163 {"akonadi_item_move_to_menu",
164 kli18n("Move Item To..."),
165 kli18n("Move To"),
166 "edit-move",
167 "go-jump",
168 QKeySequence(),
169 SLOT(slotMoveItemTo(QAction *)),
170 MenuAction},
171 {"akonadi_collection_move_to_menu",
172 kli18n("Move Folder To..."),
173 kli18n("Move To"),
174 "edit-move",
175 "go-jump",
176 QKeySequence(),
177 SLOT(slotMoveCollectionTo(QAction *)),
178 MenuAction},
179 {"akonadi_item_cut", kli18n("&Cut Item"), kli18n("Cut"), "edit-cut", nullptr, Qt::CTRL | Qt::Key_X, SLOT(slotCutItems()), NormalAction},
180 {"akonadi_collection_cut", kli18n("&Cut Folder"), kli18n("Cut"), "edit-cut", nullptr, Qt::CTRL | Qt::Key_X, SLOT(slotCutCollections()), NormalAction},
181 {"akonadi_resource_create",
182 kli18n("Create Resource"),
184 "folder-new",
185 nullptr,
186 QKeySequence(),
187 SLOT(slotCreateResource()),
188 NormalAction},
189 {"akonadi_resource_delete",
190 kli18n("Delete Resource"),
192 "edit-delete",
193 nullptr,
194 QKeySequence(),
195 SLOT(slotDeleteResource()),
196 NormalAction},
197 {"akonadi_resource_properties",
198 kli18n("&Resource Properties"),
199 kli18n("Properties"),
200 "configure",
201 nullptr,
202 QKeySequence(),
203 SLOT(slotResourceProperties()),
204 NormalAction},
205 {"akonadi_resource_synchronize",
206 kli18n("Synchronize Resource"),
207 kli18n("Synchronize"),
208 "view-refresh",
209 nullptr,
210 QKeySequence(),
211 SLOT(slotSynchronizeResource()),
212 NormalAction},
213 {"akonadi_work_offline",
214 kli18n("Work Offline"),
216 "user-offline",
217 nullptr,
218 QKeySequence(),
219 SLOT(slotToggleWorkOffline(bool)),
220 ToggleAction},
221 {"akonadi_collection_copy_to_dialog",
222 kli18n("Copy Folder To..."),
223 kli18n("Copy To"),
224 "edit-copy",
225 nullptr,
226 QKeySequence(),
227 SLOT(slotCopyCollectionTo()),
228 NormalAction},
229 {"akonadi_collection_move_to_dialog",
230 kli18n("Move Folder To..."),
231 kli18n("Move To"),
232 "edit-move",
233 "go-jump",
234 QKeySequence(),
235 SLOT(slotMoveCollectionTo()),
236 NormalAction},
237 {"akonadi_item_copy_to_dialog", kli18n("Copy Item To..."), kli18n("Copy To"), "edit-copy", nullptr, QKeySequence(), SLOT(slotCopyItemTo()), NormalAction},
238 {"akonadi_item_move_to_dialog", kli18n("Move Item To..."), kli18n("Move To"), "edit-move", "go-jump", QKeySequence(), SLOT(slotMoveItemTo()), NormalAction},
239 {"akonadi_collection_sync_recursive",
240 kli18n("&Synchronize Folder Recursively"),
241 kli18n("Synchronize Recursively"),
242 "view-refresh",
243 nullptr,
245 SLOT(slotSynchronizeCollectionRecursive()),
246 NormalAction},
247 {"akonadi_move_collection_to_trash",
248 kli18n("&Move Folder To Trash"),
249 kli18n("Move Folder To Trash"),
250 "edit-delete",
251 nullptr,
252 QKeySequence(),
253 SLOT(slotMoveCollectionToTrash()),
254 NormalAction},
255 {"akonadi_move_item_to_trash",
256 kli18n("&Move Item To Trash"),
257 kli18n("Move Item To Trash"),
258 "edit-delete",
259 nullptr,
260 QKeySequence(),
261 SLOT(slotMoveItemToTrash()),
262 NormalAction},
263 {"akonadi_restore_collection_from_trash",
264 kli18n("&Restore Folder From Trash"),
265 kli18n("Restore Folder From Trash"),
266 "view-refresh",
267 nullptr,
268 QKeySequence(),
269 SLOT(slotRestoreCollectionFromTrash()),
270 NormalAction},
271 {"akonadi_restore_item_from_trash",
272 kli18n("&Restore Item From Trash"),
273 kli18n("Restore Item From Trash"),
274 "view-refresh",
275 nullptr,
276 QKeySequence(),
277 SLOT(slotRestoreItemFromTrash()),
278 NormalAction},
279 {"akonadi_collection_trash_restore",
280 kli18n("&Restore Folder From Trash"),
281 kli18n("Restore Folder From Trash"),
282 "edit-delete",
283 nullptr,
284 QKeySequence(),
285 SLOT(slotTrashRestoreCollection()),
286 ActionWithAlternative},
287 {nullptr,
288 kli18n("&Restore Collection From Trash"),
289 kli18n("Restore Collection From Trash"),
290 "view-refresh",
291 nullptr,
292 QKeySequence(),
293 nullptr,
294 ActionAlternative},
295 {"akonadi_item_trash_restore",
296 kli18n("&Restore Item From Trash"),
297 kli18n("Restore Item From Trash"),
298 "edit-delete",
299 nullptr,
300 0,
301 SLOT(slotTrashRestoreItem()),
302 ActionWithAlternative},
303 {nullptr, kli18n("&Restore Item From Trash"), kli18n("Restore Item From Trash"), "view-refresh", nullptr, QKeySequence(), nullptr, ActionAlternative},
304 {"akonadi_collection_sync_favorite_folders",
305 kli18n("&Synchronize Favorite Folders"),
306 kli18n("Synchronize Favorite Folders"),
307 "view-refresh",
308 nullptr,
310 SLOT(slotSynchronizeFavoriteCollections()),
311 NormalAction},
312 {"akonadi_resource_synchronize_collectiontree",
313 kli18n("Synchronize Folder Tree"),
314 kli18n("Synchronize"),
315 "view-refresh",
316 nullptr,
317 QKeySequence(),
318 SLOT(slotSynchronizeCollectionTree()),
319 NormalAction},
320
321};
322static const int numStandardActionData = sizeof standardActionData / sizeof *standardActionData;
323
324static QIcon standardActionDataIcon(const StandardActionData &data)
325{
326 if (data.altIcon) {
328 }
329 return QIcon::fromTheme(QString::fromLatin1(data.icon));
330}
331
332static_assert(numStandardActionData == StandardActionManager::LastType, "StandardActionData table does not match StandardActionManager types");
333
334static bool canCreateCollection(const Akonadi::Collection &collection)
335{
336 return !!(collection.rights() & Akonadi::Collection::CanCreateCollection);
337}
338
339static void setWorkOffline(bool offline)
340{
341 KConfig config(QStringLiteral("akonadikderc"));
342 KConfigGroup group(&config, QStringLiteral("Actions"));
343
344 group.writeEntry("WorkOffline", offline);
345}
346
347static bool workOffline()
348{
349 KConfig config(QStringLiteral("akonadikderc"));
350 const KConfigGroup group(&config, QStringLiteral("Actions"));
351
352 return group.readEntry("WorkOffline", false);
353}
354
355static QModelIndexList safeSelectedRows(QItemSelectionModel *selectionModel)
356{
357 QModelIndexList selectedRows = selectionModel->selectedRows();
358 if (!selectedRows.isEmpty()) {
359 return selectedRows;
360 }
361
362 // 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)
363 const auto selection = selectionModel->selection();
364 for (const auto &range : selection) {
365 if (!range.isValid() || range.isEmpty()) {
366 continue;
367 }
368 const QModelIndex parent = range.parent();
369 for (int row = range.top(); row <= range.bottom(); ++row) {
370 const QModelIndex index = range.model()->index(row, range.left(), parent);
371 const Qt::ItemFlags flags = range.model()->flags(index);
372 if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) {
373 selectedRows.push_back(index);
374 }
375 }
376 }
377
378 return selectedRows;
379}
380
381/**
382 * @internal
383 */
384class Akonadi::StandardActionManagerPrivate
385{
386public:
387 explicit StandardActionManagerPrivate(StandardActionManager *parent)
388 : q(parent)
389 , actionCollection(nullptr)
390 , parentWidget(nullptr)
391 , collectionSelectionModel(nullptr)
392 , itemSelectionModel(nullptr)
393 , favoritesModel(nullptr)
394 , favoriteSelectionModel(nullptr)
395 , insideSelectionSlot(false)
396 {
397 actions.fill(nullptr, StandardActionManager::LastType);
398
399 pluralLabels.insert(StandardActionManager::CopyCollections, ki18np("&Copy Folder", "&Copy %1 Folders"));
400 pluralLabels.insert(StandardActionManager::CopyItems, ki18np("&Copy Item", "&Copy %1 Items"));
401 pluralLabels.insert(StandardActionManager::CutItems, ki18ncp("@action", "&Cut Item", "&Cut %1 Items"));
402 pluralLabels.insert(StandardActionManager::CutCollections, ki18ncp("@action", "&Cut Folder", "&Cut %1 Folders"));
403 pluralLabels.insert(StandardActionManager::DeleteItems, ki18np("&Delete Item", "&Delete %1 Items"));
404 pluralLabels.insert(StandardActionManager::DeleteCollections, ki18ncp("@action", "&Delete Folder", "&Delete %1 Folders"));
405 pluralLabels.insert(StandardActionManager::SynchronizeCollections, ki18ncp("@action", "&Synchronize Folder", "&Synchronize %1 Folders"));
406 pluralLabels.insert(StandardActionManager::DeleteResources, ki18np("&Delete Resource", "&Delete %1 Resources"));
407 pluralLabels.insert(StandardActionManager::SynchronizeResources, ki18np("&Synchronize Resource", "&Synchronize %1 Resources"));
408
409 pluralIconLabels.insert(StandardActionManager::CopyCollections, ki18np("Copy Folder", "Copy %1 Folders"));
410 pluralIconLabels.insert(StandardActionManager::CopyItems, ki18np("Copy Item", "Copy %1 Items"));
411 pluralIconLabels.insert(StandardActionManager::CutItems, ki18np("Cut Item", "Cut %1 Items"));
412 pluralIconLabels.insert(StandardActionManager::CutCollections, ki18np("Cut Folder", "Cut %1 Folders"));
413 pluralIconLabels.insert(StandardActionManager::DeleteItems, ki18np("Delete Item", "Delete %1 Items"));
414 pluralIconLabels.insert(StandardActionManager::DeleteCollections, ki18np("Delete Folder", "Delete %1 Folders"));
415 pluralIconLabels.insert(StandardActionManager::SynchronizeCollections, ki18np("Synchronize Folder", "Synchronize %1 Folders"));
416 pluralIconLabels.insert(StandardActionManager::DeleteResources, ki18ncp("@action", "Delete Resource", "Delete %1 Resources"));
417 pluralIconLabels.insert(StandardActionManager::SynchronizeResources, ki18ncp("@action", "Synchronize Resource", "Synchronize %1 Resources"));
418
419 setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle, i18nc("@title:window", "New Folder"));
420 setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText, i18nc("@label:textbox name of Akonadi folder", "Name"));
421 setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText, ki18n("Could not create folder: %1"));
422 setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Folder Creation Failed"));
423
424 setContextText(
427 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?"));
430 ki18ncp("@title:window", "Delete Folder?", "Delete Folders?"));
431 setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText, ki18n("Could not delete folder: %1"));
432 setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Folder Deletion Failed"));
433
434 setContextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, ki18nc("@title:window", "Properties of Folder %1"));
435
438 ki18np("Do you really want to delete the selected item?", "Do you really want to delete %1 items?"));
439 setContextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete Item?", "Delete Items?"));
440 setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText, ki18n("Could not delete item: %1"));
441 setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Item Deletion Failed"));
442
443 setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogTitle, i18nc("@title:window", "Rename Favorite"));
444 setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogText, i18nc("@label:textbox name of the folder", "Name:"));
445
446 setContextText(StandardActionManager::CreateResource, StandardActionManager::DialogTitle, i18nc("@title:window", "New Resource"));
447 setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText, ki18n("Could not create resource: %1"));
448 setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Resource Creation Failed"));
449
452 ki18np("Do you really want to delete this resource?", "Do you really want to delete %1 resources?"));
455 ki18ncp("@title:window", "Delete Resource?", "Delete Resources?"));
456
457 setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageText, ki18n("Could not paste data: %1"));
458 setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Paste Failed"));
459
460 mDelayedUpdateTimer.setSingleShot(true);
461 QObject::connect(&mDelayedUpdateTimer, &QTimer::timeout, q, [this]() {
462 updateActions();
463 });
464
465 qRegisterMetaType<Akonadi::Item::List>("Akonadi::Item::List");
466 }
467
468 void enableAction(int type, bool enable) // private slot, called by ActionStateManager
469 {
470 enableAction(static_cast<StandardActionManager::Type>(type), enable);
471 }
472
473 void enableAction(StandardActionManager::Type type, bool enable)
474 {
475 Q_ASSERT(type < StandardActionManager::LastType);
476 if (actions[type]) {
477 actions[type]->setEnabled(enable);
478 }
479
480 // Update the action menu
481 auto actionMenu = qobject_cast<KActionMenu *>(actions[type]);
482 if (actionMenu) {
483 // get rid of the submenus, they are re-created in enableAction. clear() is not enough, doesn't remove the submenu object instances.
484 QMenu *menu = actionMenu->menu();
485 // Not necessary to delete and recreate menu when it was not created
486 if (menu->property("actionType").isValid() && menu->isEmpty()) {
487 return;
488 }
489 mRecentCollectionsMenu.remove(type);
490 delete menu;
491 menu = new QMenu();
492
493 menu->setProperty("actionType", static_cast<int>(type));
494 q->connect(menu, &QMenu::aboutToShow, q, [this]() {
495 aboutToShowMenu();
496 });
497 q->connect(menu, SIGNAL(triggered(QAction *)), standardActionData[type].slot); // clazy:exclude=old-style-connect
498 actionMenu->setMenu(menu);
499 }
500 }
501
502 void aboutToShowMenu()
503 {
504 auto menu = qobject_cast<QMenu *>(q->sender());
505 if (!menu) {
506 return;
507 }
508
509 if (!menu->isEmpty()) {
510 return;
511 }
512 // collect all selected collections
513 const Akonadi::Collection::List selectedCollectionsList = selectedCollections();
514 const StandardActionManager::Type type = static_cast<StandardActionManager::Type>(menu->property("actionType").toInt());
515
516 QPointer<RecentCollectionAction> recentCollection = new RecentCollectionAction(type, selectedCollectionsList, collectionSelectionModel->model(), menu);
517 mRecentCollectionsMenu.insert(type, recentCollection);
518 const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
519 fillFoldersMenu(selectedCollectionsList, mimeTypes, type, menu, collectionSelectionModel->model(), QModelIndex());
520 }
521
522 void createActionFolderMenu(QMenu *menu, StandardActionManager::Type type)
523 {
526 new RecentCollectionAction(type, Akonadi::Collection::List(), collectionSelectionModel->model(), menu);
527 Collection::List selectedCollectionsList = selectedCollections();
528 const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
529 fillFoldersMenu(selectedCollectionsList, mimeTypes, type, menu, collectionSelectionModel->model(), QModelIndex());
530 }
531 }
532
533 void updateAlternatingAction(int type) // private slot, called by ActionStateManager
534 {
535 updateAlternatingAction(static_cast<StandardActionManager::Type>(type));
536 }
537
538 void updateAlternatingAction(StandardActionManager::Type type)
539 {
540 Q_ASSERT(type < StandardActionManager::LastType);
541 if (!actions[type]) {
542 return;
543 }
544
545 /*
546 * The same action is stored at the ActionWithAlternative indexes as well as the corresponding ActionAlternative indexes in the actions array.
547 * The following simply changes the standardActionData
548 */
549 if ((standardActionData[type].actionType == ActionWithAlternative) || (standardActionData[type].actionType == ActionAlternative)) {
550 actions[type]->setText(standardActionData[type].label.toString());
551 actions[type]->setIcon(standardActionDataIcon(standardActionData[type]));
552
553 if (pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) {
554 actions[type]->setText(pluralLabels.value(type).subs(1).toString());
555 } else if (!standardActionData[type].label.isEmpty()) {
556 actions[type]->setText(standardActionData[type].label.toString());
557 }
558 if (pluralIconLabels.contains(type) && !pluralIconLabels.value(type).isEmpty()) {
559 actions[type]->setIconText(pluralIconLabels.value(type).subs(1).toString());
560 } else if (!standardActionData[type].iconLabel.isEmpty()) {
561 actions[type]->setIconText(standardActionData[type].iconLabel.toString());
562 }
563 if (standardActionData[type].icon) {
564 actions[type]->setIcon(standardActionDataIcon(standardActionData[type]));
565 }
566
567 // actions[type]->setShortcut( standardActionData[type].shortcut );
568
569 /*if ( standardActionData[type].slot ) {
570 switch ( standardActionData[type].actionType ) {
571 case NormalAction:
572 case ActionWithAlternative:
573 connect( action, SIGNAL(triggered()), standardActionData[type].slot );
574 break;
575 }
576 }*/
577 }
578 }
579
580 void updatePluralLabel(int type, int count) // private slot, called by ActionStateManager
581 {
582 updatePluralLabel(static_cast<StandardActionManager::Type>(type), count);
583 }
584
585 void updatePluralLabel(StandardActionManager::Type type, int count) // private slot, called by ActionStateManager
586 {
587 Q_ASSERT(type < StandardActionManager::LastType);
588 if (actions[type] && pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) {
589 actions[type]->setText(pluralLabels.value(type).subs(qMax(count, 1)).toString());
590 }
591 }
592
593 bool isFavoriteCollection(const Akonadi::Collection &collection) const // private slot, called by ActionStateManager
594 {
595 if (!favoritesModel) {
596 return false;
597 }
598
599 return favoritesModel->collectionIds().contains(collection.id());
600 }
601
602 void encodeToClipboard(QItemSelectionModel *selectionModel, bool cut = false)
603 {
604 Q_ASSERT(selectionModel);
605 if (safeSelectedRows(selectionModel).isEmpty()) {
606 return;
607 }
608
609#ifndef QT_NO_CLIPBOARD
610 auto model = const_cast<QAbstractItemModel *>(selectionModel->model());
611 QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel));
612 model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole);
613 markCutAction(mimeData, cut);
615 if (cut) {
616 const auto rows = safeSelectedRows(selectionModel);
617 for (const auto &index : rows) {
618 model->setData(index, true, EntityTreeModel::PendingCutRole);
619 }
620 }
621#endif
622 }
623
624 static Akonadi::Collection::List collectionsForIndexes(const QModelIndexList &list)
625 {
626 Akonadi::Collection::List collectionList;
627 for (const QModelIndex &index : list) {
628 auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
629 if (!collection.isValid()) {
630 continue;
631 }
632
633 const auto parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>();
634 collection.setParentCollection(parentCollection);
635
636 collectionList << std::move(collection);
637 }
638 return collectionList;
639 }
640
641 void delayedUpdateActions()
642 {
643 // Compress changes (e.g. when deleting many rows, do this only once)
644 mDelayedUpdateTimer.start(0);
645 }
646
647 void updateActions()
648 {
649 // favorite collections
650 Collection::List selectedFavoriteCollectionsList;
651 if (favoriteSelectionModel) {
652 const QModelIndexList rows = safeSelectedRows(favoriteSelectionModel);
653 selectedFavoriteCollectionsList = collectionsForIndexes(rows);
654 }
655
656 // collect all selected collections
657 Collection::List selectedCollectionsList;
658 if (collectionSelectionModel) {
659 const QModelIndexList rows = safeSelectedRows(collectionSelectionModel);
660 selectedCollectionsList = collectionsForIndexes(rows);
661 }
662
663 // collect all selected items
664 Item::List selectedItems;
665 if (itemSelectionModel) {
666 const QModelIndexList rows = safeSelectedRows(itemSelectionModel);
667 for (const QModelIndex &index : rows) {
668 Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
669 if (!item.isValid()) {
670 continue;
671 }
672
673 const auto parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>();
674 item.setParentCollection(parentCollection);
675
676 selectedItems << item;
677 }
678 }
679
680 mActionStateManager.updateState(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems);
681 if (favoritesModel) {
682 enableAction(StandardActionManager::SynchronizeFavoriteCollections, (favoritesModel->rowCount() > 0));
683 }
684 Q_EMIT q->selectionsChanged(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems);
685 Q_EMIT q->actionStateUpdated();
686 }
687
688#ifndef QT_NO_CLIPBOARD
689 void clipboardChanged(QClipboard::Mode mode)
690 {
691 if (mode == QClipboard::Clipboard) {
692 updateActions();
693 }
694 }
695#endif
696
697 QItemSelection mapToEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
698 {
699 const auto proxy = qobject_cast<const QAbstractProxyModel *>(model);
700 if (proxy) {
701 return mapToEntityTreeModel(proxy->sourceModel(), proxy->mapSelectionToSource(selection));
702 } else {
703 return selection;
704 }
705 }
706
707 QItemSelection mapFromEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
708 {
709 const auto proxy = qobject_cast<const QAbstractProxyModel *>(model);
710 if (proxy) {
711 const QItemSelection select = mapFromEntityTreeModel(proxy->sourceModel(), selection);
712 return proxy->mapSelectionFromSource(select);
713 } else {
714 return selection;
715 }
716 }
717
718 // RAII class for setting insideSelectionSlot to true on entering, and false on exiting, the two slots below.
719 class InsideSelectionSlotBlocker
720 {
721 public:
722 explicit InsideSelectionSlotBlocker(StandardActionManagerPrivate *p)
723 : _p(p)
724 {
725 Q_ASSERT(!p->insideSelectionSlot);
726 p->insideSelectionSlot = true;
727 }
728
729 ~InsideSelectionSlotBlocker()
730 {
731 Q_ASSERT(_p->insideSelectionSlot);
732 _p->insideSelectionSlot = false;
733 }
734
735 private:
736 Q_DISABLE_COPY(InsideSelectionSlotBlocker)
737 StandardActionManagerPrivate *const _p;
738 };
739
740 void collectionSelectionChanged()
741 {
742 if (insideSelectionSlot) {
743 return;
744 }
745 InsideSelectionSlotBlocker block(this);
746 if (favoriteSelectionModel) {
747 QItemSelection selection = collectionSelectionModel->selection();
748 selection = mapToEntityTreeModel(collectionSelectionModel->model(), selection);
749 selection = mapFromEntityTreeModel(favoriteSelectionModel->model(), selection);
750
751 favoriteSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
752 }
753
754 updateActions();
755 }
756
757 void favoriteSelectionChanged()
758 {
759 if (insideSelectionSlot) {
760 return;
761 }
762 QItemSelection selection = favoriteSelectionModel->selection();
763 if (selection.isEmpty()) {
764 return;
765 }
766
767 selection = mapToEntityTreeModel(favoriteSelectionModel->model(), selection);
768 selection = mapFromEntityTreeModel(collectionSelectionModel->model(), selection);
769
770 InsideSelectionSlotBlocker block(this);
771 collectionSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
772
773 // Also set the current index. This will trigger KMMainWidget::slotFolderChanged in kmail, which we want.
774 if (!selection.indexes().isEmpty()) {
775 collectionSelectionModel->setCurrentIndex(selection.indexes().first(), QItemSelectionModel::NoUpdate);
776 }
777
778 updateActions();
779 }
780
781 void slotCreateCollection()
782 {
783 Q_ASSERT(collectionSelectionModel);
784 if (collectionSelectionModel->selection().indexes().isEmpty()) {
785 return;
786 }
787
788 const QModelIndex index = collectionSelectionModel->selection().indexes().at(0);
789 Q_ASSERT(index.isValid());
790 const auto parentCollection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
791 Q_ASSERT(parentCollection.isValid());
792
793 if (!canCreateCollection(parentCollection)) {
794 return;
795 }
796
797 bool ok = false;
798 QString name = QInputDialog::getText(parentWidget,
801 {},
802 {},
803 &ok);
804 name = name.trimmed();
805 if (name.isEmpty() || !ok) {
806 return;
807 }
808
809 if (name.contains(QLatin1Char('/'))) {
810 KMessageBox::error(parentWidget, i18n("We can not add \"/\" in folder name."), i18nc("@title:window", "Create New Folder Error"));
811 return;
812 }
813 if (name.startsWith(QLatin1Char('.')) || name.endsWith(QLatin1Char('.'))) {
814 KMessageBox::error(parentWidget, i18n("We can not add \".\" at begin or end of folder name."), i18nc("@title:window", "Create New Folder Error"));
815 return;
816 }
817
818 Collection collection;
819 collection.setName(name);
820 collection.setParentCollection(parentCollection);
822 const QStringList mts = actions[StandardActionManager::CreateCollection]->property("ContentMimeTypes").toStringList();
823 if (!mts.isEmpty()) {
824 collection.setContentMimeTypes(mts);
825 }
826 }
827 if (parentCollection.contentMimeTypes().contains(Collection::virtualMimeType())) {
828 collection.setVirtual(true);
829 collection.setContentMimeTypes(collection.contentMimeTypes() << Collection::virtualMimeType());
830 }
831
832 auto job = new CollectionCreateJob(collection);
833 q->connect(job, &KJob::result, q, [this](KJob *job) {
834 collectionCreationResult(job);
835 });
836 }
837
838 void slotCopyCollections()
839 {
840 encodeToClipboard(collectionSelectionModel);
841 }
842
843 void slotCutCollections()
844 {
845 encodeToClipboard(collectionSelectionModel, true);
846 }
847
848 Collection::List selectedCollections()
849 {
850 Collection::List collections;
851
852 Q_ASSERT(collectionSelectionModel);
853 const QModelIndexList indexes = safeSelectedRows(collectionSelectionModel);
854 collections.reserve(indexes.count());
855
856 for (const QModelIndex &index : indexes) {
857 Q_ASSERT(index.isValid());
858 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
859 Q_ASSERT(collection.isValid());
860
861 collections << collection;
862 }
863
864 return collections;
865 }
866
867 void slotDeleteCollection()
868 {
869 const Collection::List collections = selectedCollections();
870 if (collections.isEmpty()) {
871 return;
872 }
873
874 const QString collectionName = collections.first().name();
875 const QString text = contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText, collections.count(), collectionName);
876
878 parentWidget,
879 text,
883 QString(),
885 != KMessageBox::ButtonCode::PrimaryAction) {
886 return;
887 }
888
889 for (const Collection &collection : collections) {
890 auto job = new CollectionDeleteJob(collection, q);
891 q->connect(job, &CollectionDeleteJob::result, q, [this](KJob *job) {
892 collectionDeletionResult(job);
893 });
894 }
895 }
896
897 void slotMoveCollectionToTrash()
898 {
899 const Collection::List collections = selectedCollections();
900 if (collections.isEmpty()) {
901 return;
902 }
903
904 for (const Collection &collection : collections) {
905 auto job = new TrashJob(collection, q);
906 q->connect(job, &TrashJob::result, q, [this](KJob *job) {
907 moveCollectionToTrashResult(job);
908 });
909 }
910 }
911
912 void slotRestoreCollectionFromTrash()
913 {
914 const Collection::List collections = selectedCollections();
915 if (collections.isEmpty()) {
916 return;
917 }
918
919 for (const Collection &collection : collections) {
920 auto job = new TrashRestoreJob(collection, q);
921 q->connect(job, &TrashRestoreJob::result, q, [this](KJob *job) {
922 moveCollectionToTrashResult(job);
923 });
924 }
925 }
926
927 Item::List selectedItems() const
928 {
929 Item::List items;
930
931 Q_ASSERT(itemSelectionModel);
932 const QModelIndexList indexes = safeSelectedRows(itemSelectionModel);
933 items.reserve(indexes.count());
934
935 for (const QModelIndex &index : indexes) {
936 Q_ASSERT(index.isValid());
937 const Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
938 Q_ASSERT(item.isValid());
939
940 items << item;
941 }
942
943 return items;
944 }
945
946 void slotMoveItemToTrash()
947 {
948 const Item::List items = selectedItems();
949 if (items.isEmpty()) {
950 return;
951 }
952
953 auto job = new TrashJob(items, q);
954 q->connect(job, &TrashJob::result, q, [this](KJob *job) {
955 moveItemToTrashResult(job);
956 });
957 }
958
959 void slotRestoreItemFromTrash()
960 {
961 const Item::List items = selectedItems();
962 if (items.isEmpty()) {
963 return;
964 }
965
966 auto job = new TrashRestoreJob(items, q);
967 q->connect(job, &TrashRestoreJob::result, q, [this](KJob *job) {
968 moveItemToTrashResult(job);
969 });
970 }
971
972 void slotTrashRestoreCollection()
973 {
974 const Collection::List collections = selectedCollections();
975 if (collections.isEmpty()) {
976 return;
977 }
978
979 bool collectionsAreInTrash = false;
980 for (const Collection &collection : collections) {
981 if (collection.hasAttribute<EntityDeletedAttribute>()) {
982 collectionsAreInTrash = true;
983 break;
984 }
985 }
986
987 if (collectionsAreInTrash) {
988 slotRestoreCollectionFromTrash();
989 } else {
990 slotMoveCollectionToTrash();
991 }
992 }
993
994 void slotTrashRestoreItem()
995 {
996 const Item::List items = selectedItems();
997 if (items.isEmpty()) {
998 return;
999 }
1000
1001 bool itemsAreInTrash = false;
1002 for (const Item &item : items) {
1003 if (item.hasAttribute<EntityDeletedAttribute>()) {
1004 itemsAreInTrash = true;
1005 break;
1006 }
1007 }
1008
1009 if (itemsAreInTrash) {
1010 slotRestoreItemFromTrash();
1011 } else {
1012 slotMoveItemToTrash();
1013 }
1014 }
1015
1016 void slotSynchronizeCollection()
1017 {
1018 Q_ASSERT(collectionSelectionModel);
1019 const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
1020 if (list.isEmpty()) {
1021 return;
1022 }
1023
1024 const Collection::List collections = selectedCollections();
1025 if (collections.isEmpty()) {
1026 return;
1027 }
1028
1029 for (const Collection &collection : collections) {
1030 if (!testAndSetOnlineResources(collection)) {
1031 break;
1032 }
1033 AgentManager::self()->synchronizeCollection(collection, false);
1034 }
1035 }
1036
1037 bool testAndSetOnlineResources(const Akonadi::Collection &collection)
1038 {
1039 // Shortcut for the Search resource, which is a virtual resource and thus
1040 // is always online (but AgentManager does not know about it, so it returns
1041 // an invalid AgentInstance, which is "offline").
1042 //
1043 // FIXME: AgentManager should return a valid AgentInstance even
1044 // for virtual resources, which would be always online.
1045 if (collection.resource() == QLatin1StringView("akonadi_search_resource")) {
1046 return true;
1047 }
1048
1049 Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
1050 if (!instance.isOnline()) {
1052 parentWidget,
1053 i18n("Before syncing folder \"%1\" it is necessary to have the resource online. Do you want to make it online?", collection.displayName()),
1054 i18n("Account \"%1\" is offline", instance.name()),
1055 KGuiItem(i18nc("@action:button", "Go Online"), QIcon::fromTheme(QStringLiteral("user-online"))),
1057 != KMessageBox::ButtonCode::PrimaryAction) {
1058 return false;
1059 }
1060 instance.setIsOnline(true);
1061 }
1062 return true;
1063 }
1064
1065 void slotSynchronizeCollectionRecursive()
1066 {
1067 Q_ASSERT(collectionSelectionModel);
1068 const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
1069 if (list.isEmpty()) {
1070 return;
1071 }
1072
1073 const Collection::List collections = selectedCollections();
1074 if (collections.isEmpty()) {
1075 return;
1076 }
1077
1078 for (const Collection &collection : collections) {
1079 if (!testAndSetOnlineResources(collection)) {
1080 break;
1081 }
1082 AgentManager::self()->synchronizeCollection(collection, true);
1083 }
1084 }
1085
1086 void slotCollectionProperties() const
1087 {
1088 const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
1089 if (list.isEmpty()) {
1090 return;
1091 }
1092
1093 const QModelIndex index = list.first();
1094 Q_ASSERT(index.isValid());
1095
1096 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1097 Q_ASSERT(collection.isValid());
1098
1099 auto dlg = new CollectionPropertiesDialog(collection, mCollectionPropertiesPageNames, parentWidget);
1100 dlg->setWindowTitle(contextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, collection.displayName()));
1101 dlg->show();
1102 }
1103
1104 void slotCopyItems()
1105 {
1106 encodeToClipboard(itemSelectionModel);
1107 }
1108
1109 void slotCutItems()
1110 {
1111 encodeToClipboard(itemSelectionModel, true);
1112 }
1113
1114 void slotPaste()
1115 {
1116 Q_ASSERT(collectionSelectionModel);
1117
1118 const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
1119 if (list.isEmpty()) {
1120 return;
1121 }
1122
1123 const QModelIndex index = list.first();
1124 Q_ASSERT(index.isValid());
1125
1126#ifndef QT_NO_CLIPBOARD
1127 // TODO: Copy or move? We can't seem to cut yet
1128 auto model = const_cast<QAbstractItemModel *>(collectionSelectionModel->model());
1129 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
1130 model->dropMimeData(mimeData, isCutAction(mimeData) ? Qt::MoveAction : Qt::CopyAction, -1, -1, index);
1131 model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole);
1133#endif
1134 }
1135
1136 void slotDeleteItems()
1137 {
1138 Q_ASSERT(itemSelectionModel);
1139
1140 Item::List items;
1141 const QModelIndexList indexes = safeSelectedRows(itemSelectionModel);
1142 items.reserve(indexes.count());
1143 for (const QModelIndex &index : indexes) {
1144 bool ok;
1145 const qlonglong id = index.data(EntityTreeModel::ItemIdRole).toLongLong(&ok);
1146 Q_ASSERT(ok);
1147 items << Item(id);
1148 }
1149
1150 if (items.isEmpty()) {
1151 return;
1152 }
1153
1155 q,
1156 [this, items] {
1157 slotDeleteItemsDeferred(items);
1158 },
1160 }
1161
1162 void slotDeleteItemsDeferred(const Akonadi::Item::List &items)
1163 {
1164 Q_ASSERT(itemSelectionModel);
1165
1166 if (KMessageBox::questionTwoActions(parentWidget,
1171 QString(),
1173 != KMessageBox::ButtonCode::PrimaryAction) {
1174 return;
1175 }
1176
1177 auto job = new ItemDeleteJob(items, q);
1178 q->connect(job, &ItemDeleteJob::result, q, [this](KJob *job) {
1179 itemDeletionResult(job);
1180 });
1181 }
1182
1183 void slotLocalSubscription() const
1184 {
1185 auto dlg = new SubscriptionDialog(mMimeTypeFilter, parentWidget);
1186 dlg->showHiddenCollection(true);
1187 dlg->show();
1188 }
1189
1190 void slotAddToFavorites()
1191 {
1192 Q_ASSERT(collectionSelectionModel);
1193 Q_ASSERT(favoritesModel);
1194 const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
1195 if (list.isEmpty()) {
1196 return;
1197 }
1198
1199 for (const QModelIndex &index : list) {
1200 Q_ASSERT(index.isValid());
1201 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1202 Q_ASSERT(collection.isValid());
1203
1204 favoritesModel->addCollection(collection);
1205 }
1206
1207 updateActions();
1208 }
1209
1210 void slotRemoveFromFavorites()
1211 {
1212 Q_ASSERT(favoriteSelectionModel);
1213 Q_ASSERT(favoritesModel);
1214 const QModelIndexList list = safeSelectedRows(favoriteSelectionModel);
1215 if (list.isEmpty()) {
1216 return;
1217 }
1218
1219 for (const QModelIndex &index : list) {
1220 Q_ASSERT(index.isValid());
1221 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1222 Q_ASSERT(collection.isValid());
1223
1224 favoritesModel->removeCollection(collection);
1225 }
1226
1227 updateActions();
1228 }
1229
1230 void slotRenameFavorite()
1231 {
1232 Q_ASSERT(favoriteSelectionModel);
1233 Q_ASSERT(favoritesModel);
1234 const QModelIndexList list = safeSelectedRows(favoriteSelectionModel);
1235 if (list.isEmpty()) {
1236 return;
1237 }
1238 const QModelIndex index = list.first();
1239 Q_ASSERT(index.isValid());
1240 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1241 Q_ASSERT(collection.isValid());
1242
1243 QPointer<RenameFavoriteDialog> dlg(
1244 new RenameFavoriteDialog(favoritesModel->favoriteLabel(collection), favoritesModel->defaultFavoriteLabel(collection), parentWidget));
1245 if (dlg->exec() == QDialog::Accepted) {
1246 favoritesModel->setFavoriteLabel(collection, dlg->newName());
1247 }
1248 delete dlg;
1249 }
1250
1251 void slotSynchronizeFavoriteCollections()
1252 {
1253 Q_ASSERT(favoritesModel);
1254 const auto collections = favoritesModel->collections();
1255 for (const auto &collection : collections) {
1256 // there might be virtual collections in favorites which cannot be checked
1257 // so let's be safe here, agentmanager asserts otherwise
1258 if (!collection.resource().isEmpty()) {
1259 AgentManager::self()->synchronizeCollection(collection, false);
1260 }
1261 }
1262 }
1263
1264 void slotCopyCollectionTo()
1265 {
1266 pasteTo(collectionSelectionModel, collectionSelectionModel->model(), StandardActionManager::CopyCollectionToMenu, Qt::CopyAction);
1267 }
1268
1269 void slotCopyItemTo()
1270 {
1271 pasteTo(itemSelectionModel, collectionSelectionModel->model(), StandardActionManager::CopyItemToMenu, Qt::CopyAction);
1272 }
1273
1274 void slotMoveCollectionTo()
1275 {
1276 pasteTo(collectionSelectionModel, collectionSelectionModel->model(), StandardActionManager::MoveCollectionToMenu, Qt::MoveAction);
1277 }
1278
1279 void slotMoveItemTo()
1280 {
1281 pasteTo(itemSelectionModel, collectionSelectionModel->model(), StandardActionManager::MoveItemToMenu, Qt::MoveAction);
1282 }
1283
1284 void slotCopyCollectionTo(QAction *action)
1285 {
1286 pasteTo(collectionSelectionModel, action, Qt::CopyAction);
1287 }
1288
1289 void slotCopyItemTo(QAction *action)
1290 {
1291 pasteTo(itemSelectionModel, action, Qt::CopyAction);
1292 }
1293
1294 void slotMoveCollectionTo(QAction *action)
1295 {
1296 pasteTo(collectionSelectionModel, action, Qt::MoveAction);
1297 }
1298
1299 void slotMoveItemTo(QAction *action)
1300 {
1301 pasteTo(itemSelectionModel, action, Qt::MoveAction);
1302 }
1303
1304 AgentInstance::List selectedAgentInstances() const
1305 {
1306 AgentInstance::List instances;
1307
1308 Q_ASSERT(collectionSelectionModel);
1309 if (collectionSelectionModel->selection().indexes().isEmpty()) {
1310 return instances;
1311 }
1312 const QModelIndexList lstIndexes = collectionSelectionModel->selection().indexes();
1313 for (const QModelIndex &index : lstIndexes) {
1314 Q_ASSERT(index.isValid());
1315 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1316 Q_ASSERT(collection.isValid());
1317
1318 if (collection.isValid()) {
1319 const QString identifier = collection.resource();
1320 instances << AgentManager::self()->instance(identifier);
1321 }
1322 }
1323
1324 return instances;
1325 }
1326
1327 AgentInstance selectedAgentInstance() const
1328 {
1329 const AgentInstance::List instances = selectedAgentInstances();
1330
1331 if (instances.isEmpty()) {
1332 return AgentInstance();
1333 }
1334
1335 return instances.first();
1336 }
1337
1338 void slotCreateResource()
1339 {
1340 QPointer<Akonadi::AgentTypeDialog> dlg(new Akonadi::AgentTypeDialog(parentWidget));
1342
1343 for (const QString &mimeType : std::as_const(mMimeTypeFilter)) {
1344 dlg->agentFilterProxyModel()->addMimeTypeFilter(mimeType);
1345 }
1346
1347 for (const QString &capability : std::as_const(mCapabilityFilter)) {
1348 dlg->agentFilterProxyModel()->addCapabilityFilter(capability);
1349 }
1350
1351 if (dlg->exec() == QDialog::Accepted) {
1352 const AgentType agentType = dlg->agentType();
1353
1354 if (agentType.isValid()) {
1355 auto job = new AgentInstanceCreateJob(agentType, q);
1356 q->connect(job, &KJob::result, q, [this, job](KJob *) {
1357 if (job->error()) {
1358 resourceCreationResult(job);
1359 return;
1360 }
1361
1362 auto configureDialog = new Akonadi::AgentConfigurationDialog(job->instance(), parentWidget);
1363 configureDialog->setAttribute(Qt::WA_DeleteOnClose);
1364 q->connect(configureDialog, &QDialog::rejected, q, [instance = job->instance()] {
1365 Akonadi::AgentManager::self()->removeInstance(instance);
1366 });
1367 configureDialog->show();
1368 });
1369 job->start();
1370 }
1371 }
1372 delete dlg;
1373 }
1374
1375 void slotDeleteResource() const
1376 {
1377 const AgentInstance::List instances = selectedAgentInstances();
1378 if (instances.isEmpty()) {
1379 return;
1380 }
1381
1383 parentWidget,
1384 contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxText, instances.count(), instances.first().name()),
1385 contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxTitle, instances.count(), instances.first().name()),
1388 QString(),
1390 != KMessageBox::ButtonCode::PrimaryAction) {
1391 return;
1392 }
1393
1394 for (const AgentInstance &instance : instances) {
1395 AgentManager::self()->removeInstance(instance);
1396 }
1397 }
1398
1399 void slotSynchronizeResource() const
1400 {
1401 AgentInstance::List instances = selectedAgentInstances();
1402 for (AgentInstance &instance : instances) {
1403 instance.synchronize();
1404 }
1405 }
1406
1407 void slotSynchronizeCollectionTree() const
1408 {
1409 AgentInstance::List instances = selectedAgentInstances();
1410 for (AgentInstance &instance : instances) {
1411 instance.synchronizeCollectionTree();
1412 }
1413 }
1414
1415 void slotResourceProperties() const
1416 {
1417 AgentInstance instance = selectedAgentInstance();
1418 if (!instance.isValid()) {
1419 return;
1420 }
1421
1422 auto configureDialog = new Akonadi::AgentConfigurationDialog(instance, parentWidget);
1423 configureDialog->setAttribute(Qt::WA_DeleteOnClose);
1424 configureDialog->show();
1425 }
1426
1427 void slotToggleWorkOffline(bool offline)
1428 {
1429 setWorkOffline(offline);
1430
1431 AgentInstance::List instances = AgentManager::self()->instances();
1432 for (AgentInstance &instance : instances) {
1433 instance.setIsOnline(!offline);
1434 }
1435 }
1436
1437 void pasteTo(QItemSelectionModel *selectionModel, const QAbstractItemModel *model, StandardActionManager::Type type, Qt::DropAction dropAction)
1438 {
1439 const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
1440
1441 QPointer<CollectionDialog> dlg(new CollectionDialog(const_cast<QAbstractItemModel *>(model)));
1442 dlg->setMimeTypeFilter(mimeTypes.values());
1443
1445 dlg->setAccessRightsFilter(Collection::CanCreateItem);
1447 dlg->setAccessRightsFilter(Collection::CanCreateCollection);
1448 }
1449
1450 if (dlg->exec() == QDialog::Accepted && dlg != nullptr) {
1451 const QModelIndex index = EntityTreeModel::modelIndexForCollection(collectionSelectionModel->model(), dlg->selectedCollection());
1452 if (!index.isValid()) {
1453 delete dlg;
1454 return;
1455 }
1456
1457 const QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel));
1458
1459 auto model = const_cast<QAbstractItemModel *>(index.model());
1460 model->dropMimeData(mimeData, dropAction, -1, -1, index);
1461 delete mimeData;
1462 }
1463 delete dlg;
1464 }
1465
1466 void pasteTo(QItemSelectionModel *selectionModel, QAction *action, Qt::DropAction dropAction)
1467 {
1468 Q_ASSERT(selectionModel);
1469 Q_ASSERT(action);
1470
1471 if (safeSelectedRows(selectionModel).count() <= 0) {
1472 return;
1473 }
1474
1475 const QMimeData *mimeData = selectionModel->model()->mimeData(selectionModel->selectedRows());
1476
1477 const QModelIndex index = action->data().toModelIndex();
1478 Q_ASSERT(index.isValid());
1479
1480 auto model = const_cast<QAbstractItemModel *>(index.model());
1481 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1482 addRecentCollection(collection.id());
1483 model->dropMimeData(mimeData, dropAction, -1, -1, index);
1484 delete mimeData;
1485 }
1486
1487 void addRecentCollection(Akonadi::Collection::Id id) const
1488 {
1489 QMapIterator<StandardActionManager::Type, QPointer<RecentCollectionAction>> item(mRecentCollectionsMenu);
1490 while (item.hasNext()) {
1491 item.next();
1492 if (item.value().data()) {
1493 item.value().data()->addRecentCollection(item.key(), id);
1494 }
1495 }
1496 }
1497
1498 void collectionCreationResult(KJob *job) const
1499 {
1500 if (job->error()) {
1501 KMessageBox::error(parentWidget,
1504 }
1505 }
1506
1507 void collectionDeletionResult(KJob *job) const
1508 {
1509 if (job->error()) {
1510 KMessageBox::error(parentWidget,
1513 }
1514 }
1515
1516 void moveCollectionToTrashResult(KJob *job) const
1517 {
1518 if (job->error()) {
1519 KMessageBox::error(parentWidget,
1522 }
1523 }
1524
1525 void moveItemToTrashResult(KJob *job) const
1526 {
1527 if (job->error()) {
1528 KMessageBox::error(parentWidget,
1531 }
1532 }
1533
1534 void itemDeletionResult(KJob *job) const
1535 {
1536 if (job->error()) {
1537 KMessageBox::error(parentWidget,
1540 }
1541 }
1542
1543 void resourceCreationResult(KJob *job) const
1544 {
1545 if (job->error()) {
1546 KMessageBox::error(parentWidget,
1549 }
1550 }
1551
1552 void pasteResult(KJob *job) const
1553 {
1554 if (job->error()) {
1555 KMessageBox::error(parentWidget,
1558 }
1559 }
1560
1561 /**
1562 * Returns a set of mime types of the entities that are currently selected.
1563 */
1564 QSet<QString> mimeTypesOfSelection(StandardActionManager::Type type) const
1565 {
1566 QModelIndexList list;
1567 QSet<QString> mimeTypes;
1568
1571
1572 if (isItemAction) {
1573 list = safeSelectedRows(itemSelectionModel);
1574 mimeTypes.reserve(list.count());
1575 for (const QModelIndex &index : std::as_const(list)) {
1576 mimeTypes << index.data(EntityTreeModel::MimeTypeRole).toString();
1577 }
1578 }
1579
1580 if (isCollectionAction) {
1581 list = safeSelectedRows(collectionSelectionModel);
1582 for (const QModelIndex &index : std::as_const(list)) {
1583 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1584
1585 // The mimetypes that the selected collection can possibly contain
1586 const auto mimeTypesResult = AgentManager::self()->instance(collection.resource()).type().mimeTypes();
1587 mimeTypes = QSet<QString>(mimeTypesResult.begin(), mimeTypesResult.end());
1588 }
1589 }
1590
1591 return mimeTypes;
1592 }
1593
1594 /**
1595 * Returns whether items with the given @p mimeTypes can be written to the given @p collection.
1596 */
1597 bool isWritableTargetCollectionForMimeTypes(const Collection &collection, const QSet<QString> &mimeTypes, StandardActionManager::Type type) const
1598 {
1599 if (collection.isVirtual()) {
1600 return false;
1601 }
1602
1605
1606 const auto contentMimeTypesList{collection.contentMimeTypes()};
1607 const QSet<QString> contentMimeTypesSet = QSet<QString>(contentMimeTypesList.cbegin(), contentMimeTypesList.cend());
1608
1609 const bool canContainRequiredMimeTypes = contentMimeTypesSet.intersects(mimeTypes);
1610 const bool canCreateNewItems = (collection.rights() & Collection::CanCreateItem);
1611
1612 const bool canCreateNewCollections = (collection.rights() & Collection::CanCreateCollection);
1613 const bool canContainCollections =
1614 collection.contentMimeTypes().contains(Collection::mimeType()) || collection.contentMimeTypes().contains(Collection::virtualMimeType());
1615
1616 const auto mimeTypesList{AgentManager::self()->instance(collection.resource()).type().mimeTypes()};
1617 const QSet<QString> mimeTypesListSet = QSet<QString>(mimeTypesList.cbegin(), mimeTypesList.cend());
1618
1619 const bool resourceAllowsRequiredMimeTypes = mimeTypesListSet.contains(mimeTypes);
1620 const bool isReadOnlyForItems = (isItemAction && (!canCreateNewItems || !canContainRequiredMimeTypes));
1621 const bool isReadOnlyForCollections = (isCollectionAction && (!canCreateNewCollections || !canContainCollections || !resourceAllowsRequiredMimeTypes));
1622
1623 return !(CollectionUtils::isStructural(collection) || isReadOnlyForItems || isReadOnlyForCollections);
1624 }
1625
1626 void fillFoldersMenu(const Akonadi::Collection::List &selectedCollectionsList,
1627 const QSet<QString> &mimeTypes,
1629 QMenu *menu,
1630 const QAbstractItemModel *model,
1631 const QModelIndex &parentIndex)
1632 {
1633 const int rowCount = model->rowCount(parentIndex);
1634
1635 for (int row = 0; row < rowCount; ++row) {
1636 const QModelIndex index = model->index(row, 0, parentIndex);
1637 const auto collection = model->data(index, EntityTreeModel::CollectionRole).value<Collection>();
1638
1639 if (collection.isVirtual()) {
1640 continue;
1641 }
1642
1643 const bool readOnly = !isWritableTargetCollectionForMimeTypes(collection, mimeTypes, type);
1644 const bool collectionIsSelected = selectedCollectionsList.contains(collection);
1645 if (type == StandardActionManager::MoveCollectionToMenu && collectionIsSelected) {
1646 continue;
1647 }
1648
1649 QString label = model->data(index).toString();
1650 label.replace(QLatin1Char('&'), QStringLiteral("&&"));
1651
1652 const auto icon = model->data(index, Qt::DecorationRole).value<QIcon>();
1653
1654 if (model->rowCount(index) > 0) {
1655 // new level
1656 auto popup = new QMenu(menu);
1658 popup->setObjectName(QLatin1StringView("subMenu"));
1659 popup->setTitle(label);
1660 popup->setIcon(icon);
1661
1662 fillFoldersMenu(selectedCollectionsList, mimeTypes, type, popup, model, index);
1663 if (!(type == StandardActionManager::CopyCollectionToMenu && collectionIsSelected)) {
1664 if (!readOnly) {
1665 popup->addSeparator();
1666
1667 QAction *action = popup->addAction(moveAction ? i18n("Move to This Folder") : i18n("Copy to This Folder"));
1669 }
1670 }
1671
1672 if (!popup->isEmpty()) {
1673 menu->addMenu(popup);
1674 }
1675
1676 } else {
1677 // insert an item
1678 QAction *action = menu->addAction(icon, label);
1680 action->setEnabled(!readOnly && !collectionIsSelected);
1681 }
1682 }
1683 }
1684
1685 void checkModelsConsistency() const
1686 {
1687 if (favoritesModel == nullptr || favoriteSelectionModel == nullptr) {
1688 // No need to check when the favorite collections feature is not used
1689 return;
1690 }
1691
1692 // find the base ETM of the favourites view
1693 const QAbstractItemModel *favModel = favoritesModel;
1694 while (const auto proxy = qobject_cast<const QAbstractProxyModel *>(favModel)) {
1695 favModel = proxy->sourceModel();
1696 }
1697
1698 // Check that the collection selection model maps to the same
1699 // EntityTreeModel than favoritesModel
1700 if (collectionSelectionModel != nullptr) {
1701 const QAbstractItemModel *model = collectionSelectionModel->model();
1702 while (const auto proxy = qobject_cast<const QAbstractProxyModel *>(model)) {
1703 model = proxy->sourceModel();
1704 }
1705
1706 Q_ASSERT(model == favModel);
1707 }
1708
1709 // Check that the favorite selection model maps to favoritesModel
1710 const QAbstractItemModel *model = favoriteSelectionModel->model();
1711 while (const auto proxy = qobject_cast<const QAbstractProxyModel *>(model)) {
1712 model = proxy->sourceModel();
1713 }
1714 Q_ASSERT(model == favModel);
1715 }
1716
1717 void markCutAction(QMimeData *mimeData, bool cut) const
1718 {
1719 if (!cut) {
1720 return;
1721 }
1722
1723 const QByteArray cutSelectionData = "1"; // krazy:exclude=doublequote_chars
1724 mimeData->setData(QStringLiteral("application/x-kde.akonadi-cutselection"), cutSelectionData);
1725 }
1726
1727 bool isCutAction(const QMimeData *mimeData) const
1728 {
1729 const QByteArray data = mimeData->data(QStringLiteral("application/x-kde.akonadi-cutselection"));
1730 if (data.isEmpty()) {
1731 return false;
1732 } else {
1733 return (data.at(0) == '1'); // true if 1
1734 }
1735 }
1736
1737 void setContextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const QString &data)
1738 {
1739 ContextTextEntry entry;
1740 entry.text = data;
1741
1742 contextTexts[type].insert(context, entry);
1743 }
1744
1745 void setContextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const KLocalizedString &data)
1746 {
1747 ContextTextEntry entry;
1748 entry.localizedText = data;
1749
1750 contextTexts[type].insert(context, entry);
1751 }
1752
1753 QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context) const
1754 {
1755 return contextTexts[type].value(context).text;
1756 }
1757
1758 QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context, const QString &value) const
1759 {
1760 KLocalizedString text = contextTexts[type].value(context).localizedText;
1761 if (text.isEmpty()) {
1762 return contextTexts[type].value(context).text;
1763 }
1764
1765 return text.subs(value).toString();
1766 }
1767
1768 QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context, int count, const QString &value) const
1769 {
1770 KLocalizedString text = contextTexts[type].value(context).localizedText;
1771 if (text.isEmpty()) {
1772 return contextTexts[type].value(context).text;
1773 }
1774
1775 const QString str = text.subs(count).toString();
1776 const int argCount = str.count(QRegularExpression(QStringLiteral("%[0-9]")));
1777 if (argCount > 0) {
1778 return text.subs(count).subs(value).toString();
1779 } else {
1780 return text.subs(count).toString();
1781 }
1782 }
1783
1784 StandardActionManager *const q;
1785 KActionCollection *actionCollection;
1786 QWidget *parentWidget;
1787 QItemSelectionModel *collectionSelectionModel;
1788 QItemSelectionModel *itemSelectionModel;
1789 FavoriteCollectionsModel *favoritesModel;
1790 QItemSelectionModel *favoriteSelectionModel;
1791 bool insideSelectionSlot;
1792 QList<QAction *> actions;
1793 QHash<StandardActionManager::Type, KLocalizedString> pluralLabels;
1794 QHash<StandardActionManager::Type, KLocalizedString> pluralIconLabels;
1795 QTimer mDelayedUpdateTimer;
1796
1797 struct ContextTextEntry {
1798 QString text;
1799 KLocalizedString localizedText;
1800 bool isLocalized;
1801 };
1802
1803 using ContextTexts = QHash<StandardActionManager::TextContext, ContextTextEntry>;
1804 QHash<StandardActionManager::Type, ContextTexts> contextTexts;
1805
1806 ActionStateManager mActionStateManager;
1807
1808 QStringList mMimeTypeFilter;
1809 QStringList mCapabilityFilter;
1810 QStringList mCollectionPropertiesPageNames;
1811 QMap<StandardActionManager::Type, QPointer<RecentCollectionAction>> mRecentCollectionsMenu;
1812};
1813
1814/// @endcond
1815
1817 : QObject(parent)
1818 , d(new StandardActionManagerPrivate(this))
1819{
1820 d->parentWidget = parent;
1821 d->actionCollection = actionCollection;
1822 d->mActionStateManager.setReceiver(this);
1823#ifndef QT_NO_CLIPBOARD
1824 connect(QApplication::clipboard(), &QClipboard::changed, this, [this](auto mode) {
1825 d->clipboardChanged(mode);
1826 });
1827#endif
1828}
1829
1831
1833{
1834 d->collectionSelectionModel = selectionModel;
1835 connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() {
1836 d->collectionSelectionChanged();
1837 });
1838
1839 d->checkModelsConsistency();
1840}
1841
1843{
1844 d->itemSelectionModel = selectionModel;
1845 connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() {
1846 d->delayedUpdateActions();
1847 });
1848}
1849
1851{
1852 d->favoritesModel = favoritesModel;
1853 d->checkModelsConsistency();
1854}
1855
1857{
1858 d->favoriteSelectionModel = selectionModel;
1859 connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() {
1860 d->favoriteSelectionChanged();
1861 });
1862 d->checkModelsConsistency();
1863}
1864
1866{
1867 Q_ASSERT(type < LastType);
1868 if (d->actions[type]) {
1869 return d->actions[type];
1870 }
1871 QAction *action = nullptr;
1872 switch (standardActionData[type].actionType) {
1873 case NormalAction:
1874 case ActionWithAlternative:
1875 action = new QAction(d->parentWidget);
1876 break;
1877 case ActionAlternative:
1878 d->actions[type] = d->actions[type - 1];
1879 Q_ASSERT(d->actions[type]);
1880 if ((LastType > type + 1) && (standardActionData[type + 1].actionType == ActionAlternative)) {
1881 createAction(static_cast<Type>(type + 1)); // ensure that alternative actions are initialized when not created by createAllActions
1882 }
1883 return d->actions[type];
1884 case MenuAction:
1885 action = new KActionMenu(d->parentWidget);
1886 break;
1887 case ToggleAction:
1888 action = new KToggleAction(d->parentWidget);
1889 break;
1890 }
1891
1892 if (d->pluralLabels.contains(type) && !d->pluralLabels.value(type).isEmpty()) {
1893 action->setText(d->pluralLabels.value(type).subs(1).toString());
1894 } else if (!standardActionData[type].label.isEmpty()) {
1895 action->setText(standardActionData[type].label.toString());
1896 }
1897 if (d->pluralIconLabels.contains(type) && !d->pluralIconLabels.value(type).isEmpty()) {
1898 action->setIconText(d->pluralIconLabels.value(type).subs(1).toString());
1899 } else if (!standardActionData[type].iconLabel.isEmpty()) {
1900 action->setIconText(standardActionData[type].iconLabel.toString());
1901 }
1902
1903 if (standardActionData[type].icon) {
1904 action->setIcon(standardActionDataIcon(standardActionData[type]));
1905 }
1906 if (d->actionCollection) {
1907 d->actionCollection->setDefaultShortcut(action, QKeySequence(standardActionData[type].shortcut));
1908 } else {
1909 action->setShortcut(standardActionData[type].shortcut);
1910 }
1911
1912 if (standardActionData[type].slot) {
1913 switch (standardActionData[type].actionType) {
1914 case NormalAction:
1915 case ActionWithAlternative:
1916 connect(action, SIGNAL(triggered()), standardActionData[type].slot); // clazy:exclude=old-style-connect
1917 break;
1918 case MenuAction: {
1919 auto actionMenu = qobject_cast<KActionMenu *>(action);
1920 connect(actionMenu->menu(), SIGNAL(triggered(QAction *)), standardActionData[type].slot); // clazy:exclude=old-style-connect
1921 break;
1922 }
1923 case ToggleAction: {
1924 connect(action, SIGNAL(triggered(bool)), standardActionData[type].slot); // clazy:exclude=old-style-connect
1925 break;
1926 }
1927 case ActionAlternative:
1928 Q_ASSERT(0);
1929 }
1930 }
1931
1932 if (type == ToggleWorkOffline) {
1933 // inititalize the action state with information from config file
1934 disconnect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot); // clazy:exclude=old-style-connect
1935 action->setChecked(workOffline());
1936 connect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot); // clazy:exclude=old-style-connect
1937
1938 // TODO: find a way to check for updates to the config file
1939 }
1940
1941 Q_ASSERT(standardActionData[type].name);
1942 Q_ASSERT(d->actionCollection);
1943 d->actionCollection->addAction(QString::fromLatin1(standardActionData[type].name), action);
1944 d->actions[type] = action;
1945 if ((standardActionData[type].actionType == ActionWithAlternative) && (standardActionData[type + 1].actionType == ActionAlternative)) {
1946 createAction(static_cast<Type>(type + 1)); // ensure that alternative actions are initialized when not created by createAllActions
1947 }
1948 d->updateActions();
1949 return action;
1950}
1951
1953{
1954 for (uint i = 0; i < LastType; ++i) {
1955 createAction(static_cast<Type>(i));
1956 }
1957}
1958
1960{
1961 Q_ASSERT(type < LastType);
1962 return d->actions[type];
1963}
1964
1966{
1967 Q_ASSERT(type < LastType);
1968 d->pluralLabels.insert(type, text);
1969 d->updateActions();
1970}
1971
1973{
1974 Q_ASSERT(type < LastType);
1975
1976 const QAction *action = d->actions[type];
1977
1978 if (!action) {
1979 return;
1980 }
1981
1982 if (intercept) {
1983 disconnect(action, SIGNAL(triggered()), this, standardActionData[type].slot); // clazy:exclude=old-style-connect
1984 } else {
1985 connect(action, SIGNAL(triggered()), standardActionData[type].slot); // clazy:exclude=old-style-connect
1986 }
1987}
1988
1990{
1991 Collection::List collections;
1992
1993 if (!d->collectionSelectionModel) {
1994 return collections;
1995 }
1996
1997 const QModelIndexList lst = safeSelectedRows(d->collectionSelectionModel);
1998 for (const QModelIndex &index : lst) {
1999 const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
2000 if (collection.isValid()) {
2001 collections << collection;
2002 }
2003 }
2004
2005 return collections;
2006}
2007
2009{
2010 Item::List items;
2011
2012 if (!d->itemSelectionModel) {
2013 return items;
2014 }
2015 const QModelIndexList lst = safeSelectedRows(d->itemSelectionModel);
2016 for (const QModelIndex &index : lst) {
2017 const Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
2018 if (item.isValid()) {
2019 items << item;
2020 }
2021 }
2022
2023 return items;
2024}
2025
2027{
2028 d->setContextText(type, context, text);
2029}
2030
2032{
2033 d->setContextText(type, context, text);
2034}
2035
2037{
2038 d->mMimeTypeFilter = mimeTypes;
2039}
2040
2042{
2043 d->mCapabilityFilter = capabilities;
2044}
2045
2047{
2048 d->mCollectionPropertiesPageNames = names;
2049}
2050
2052{
2053 d->createActionFolderMenu(menu, type);
2054}
2055
2057{
2058 RecentCollectionAction::addRecentCollection(id);
2059}
2060
2061#include "moc_standardactionmanager.cpp"
void synchronize()
Triggers the agent instance to start synchronization.
void setIsOnline(bool online)
Sets online status of the agent instance.
void synchronizeCollectionTree()
Triggers a synchronization of the collection tree by the given agent instance.
static AgentManager * self()
Returns the global instance of the agent manager.
AgentInstance instance(const QString &identifier) const
Returns the agent instance with the given identifier or an invalid agent instance if the identifier d...
Represents a collection of PIM items.
Definition collection.h:62
void setVirtual(bool isVirtual)
Sets whether the collection is virtual or not.
qint64 Id
Describes the unique id type.
Definition collection.h:82
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
static QString mimeType()
Returns the mimetype used for collections.
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
static QString virtualMimeType()
Returns the mimetype used for virtual collections.
void setName(const QString &name)
Sets the i18n'ed name of the collection.
@ CanCreateItem
Can create new items in this collection.
Definition collection.h:95
@ CanCreateCollection
Can create new subcollections in this collection.
Definition collection.h:98
void setContentMimeTypes(const QStringList &types)
Sets the list of possible content mime types.
QList< Collection > List
Describes a list of collections.
Definition collection.h:87
static QModelIndex modelIndexForCollection(const QAbstractItemModel *model, const Collection &collection)
Returns a QModelIndex in model which points to collection.
@ ParentCollectionRole
The parent collection of the entity.
@ CollectionRole
The collection.
@ PendingCutRole
Used to indicate items which are to be cut.
@ MimeTypeRole
The mimetype of the entity.
A model that lists a set of favorite collections.
Represents a PIM item stored in Akonadi storage.
Definition item.h:100
bool isValid() const
Returns whether the item is valid.
Definition item.cpp:88
QList< Item > List
Describes a list of items.
Definition item.h:110
void setActionText(Type type, const KLocalizedString &text)
Sets the label of the action type to text, which is used during updating the action state and substit...
Akonadi::Item::List selectedItems() const
Returns the list of items that are currently selected.
void setCapabilityFilter(const QStringList &capabilities)
Sets the capability filter that will be used when creating new resources.
void setFavoriteCollectionsModel(FavoriteCollectionsModel *favoritesModel)
Sets the favorite collections model based on which the collection relatedactions should operate.
void setMimeTypeFilter(const QStringList &mimeTypes)
Sets the mime type filter that will be used when creating new resources.
Type
Describes the supported actions.
@ CreateResource
Creates a new resource.
@ CreateCollection
Creates an collection.
@ SynchronizeResources
Synchronizes the selected resources.
@ DeleteItems
Deletes the selected items.
@ SynchronizeCollections
Synchronizes collections.
@ CopyItemToMenu
Menu allowing to quickly copy an item into a collection.
@ RenameFavoriteCollection
Rename the collection of the favorite collections model.
@ SynchronizeFavoriteCollections
Synchronize favorite collections.
@ CutItems
Cuts the selected items.
@ DeleteCollections
Deletes the selected collections.
@ ToggleWorkOffline
Toggles the work offline state of all resources.
@ CutCollections
Cuts the selected collections.
@ CopyCollectionToMenu
Menu allowing to quickly copy a collection into another collection.
@ MoveItemToMenu
Menu allowing to move item into a collection.
@ MoveItemsToTrash
Moves the selected items to trash and marks them as deleted, needs EntityDeletedAttribute.
@ CopyCollections
Copies the selected collections.
@ Paste
Paste collections or items.
@ CollectionProperties
Provides collection properties.
@ DeleteResources
Deletes the selected resources.
@ CopyItems
Copies the selected items.
@ MoveCollectionToMenu
Menu allowing to move a collection into another collection.
@ MoveCollectionsToTrash
Moves the selected collection to trash and marks it as deleted, needs EntityDeletedAttribute.
void addRecentCollection(Akonadi::Collection::Id id) const
Add a collection to the global recent collection list.
~StandardActionManager() override
Destroys the standard action manager.
void setContextText(Type type, TextContext context, const QString &text)
Sets the text of the action type for the given context.
void interceptAction(Type type, bool intercept=true)
Sets whether the default implementation for the given action type shall be executed when the action i...
void setFavoriteSelectionModel(QItemSelectionModel *selectionModel)
Sets the favorite collection selection model based on which the favorite collection related actions s...
void setCollectionSelectionModel(QItemSelectionModel *selectionModel)
Sets the collection selection model based on which the collection related actions should operate.
Akonadi::Collection::List selectedCollections() const
Returns the list of collections that are currently selected.
QAction * action(Type type) const
Returns the action of the given type, 0 if it has not been created (yet).
void setItemSelectionModel(QItemSelectionModel *selectionModel)
Sets the item selection model based on which the item related actions should operate.
QAction * createAction(Type type)
Creates the action of the given type and adds it to the action collection specified in the constructo...
StandardActionManager(KActionCollection *actionCollection, QWidget *parent=nullptr)
Creates a new standard action manager.
void setCollectionPropertiesPageNames(const QStringList &names)
Sets the page names of the config pages that will be used by the built-in collection properties dialo...
TextContext
Describes the text context that can be customized.
@ MessageBoxText
The text of a message box.
@ ErrorMessageText
The text of an error message.
@ MessageBoxTitle
The window title of a message box.
@ DialogTitle
The window title of a dialog.
@ ErrorMessageTitle
The window title of an error message.
void createActionFolderMenu(QMenu *menu, Type type)
Create a popup menu.
void createAllActions()
Convenience method to create all standard actions.
virtual QString errorString() const
int error() const
void result(KJob *job)
QString toString() const
bool isEmpty() const
KLocalizedString subs(const KLocalizedString &a, int fieldWidth=0, QChar fillChar=QLatin1Char(' ')) const
KLocalizedString KI18N_EXPORT ki18np(const char *singular, const char *plural)
KLocalizedString KI18N_EXPORT ki18n(const char *text)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
KLocalizedString KI18N_EXPORT ki18ncp(const char *context, const char *singular, const char *plural)
KLocalizedString KI18N_EXPORT ki18nc(const char *context, const char *text)
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
char * toString(const EngineQuery &query)
QString name(const QVariant &location)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem cancel()
KGuiItem del()
QString label(StandardShortcut id)
const QList< QKeySequence > & shortcut(StandardShortcut id)
virtual QVariant data(const QModelIndex &index, int role) const const=0
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual QMimeData * mimeData(const QModelIndexList &indexes) const const
virtual int rowCount(const QModelIndex &parent) const const=0
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
QVariant data() const const
void setEnabled(bool)
void setData(const QVariant &data)
char at(qsizetype i) const const
bool isEmpty() const const
void changed(QClipboard::Mode mode)
void clear(Mode mode)
const QMimeData * mimeData(Mode mode) const const
void setMimeData(QMimeData *src, Mode mode)
void rejected()
QClipboard * clipboard()
QIcon fromTheme(const QString &name)
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
QModelIndexList indexes() const const
QAbstractItemModel * model()
QModelIndexList selectedRows(int column) const const
const QItemSelection selection() const const
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
bool contains(const AT &value) const const
qsizetype count() const const
T & first()
bool isEmpty() const const
void reserve(qsizetype size)
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
void aboutToShow()
QAction * addMenu(QMenu *menu)
bool isEmpty() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QByteArray data(const QString &mimeType) const const
void setData(const QString &mimeType, const QByteArray &data)
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
QObject(QObject *parent)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
bool setProperty(const char *name, QVariant &&value)
bool contains(const QSet< T > &other) const const
bool intersects(const QSet< T > &other) const const
void reserve(qsizetype size)
QList< T > values() const const
qsizetype count() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QueuedConnection
MoveAction
DecorationRole
typedef ItemFlags
WA_DeleteOnClose
void timeout()
QVariant fromValue(T &&value)
bool isValid() const const
qlonglong toLongLong(bool *ok) const const
QModelIndex toModelIndex() const const
QString toString() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:52:15 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.