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

KDE's Doxygen guidelines are available online.