Akonadi

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

KDE's Doxygen guidelines are available online.