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

KDE's Doxygen guidelines are available online.