File indexing completed on 2025-01-05 04:47:11

0001 /*
0002     SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "standardactionmanager.h"
0008 
0009 #include "actionstatemanager_p.h"
0010 #include "agentfilterproxymodel.h"
0011 #include "agentinstancecreatejob.h"
0012 #include "agentmanager.h"
0013 #include "agenttypedialog.h"
0014 #include "collectioncreatejob.h"
0015 #include "collectiondeletejob.h"
0016 #include "collectiondialog.h"
0017 #include "collectionpropertiesdialog.h"
0018 #include "collectionpropertiespage.h"
0019 #include "collectionutils.h"
0020 #include "entitydeletedattribute.h"
0021 #include "entitytreemodel.h"
0022 #include "favoritecollectionsmodel.h"
0023 #include "itemdeletejob.h"
0024 #include "pastehelper_p.h"
0025 #include "recentcollectionaction_p.h"
0026 #include "renamefavoritedialog_p.h"
0027 #include "specialcollectionattribute.h"
0028 #include "subscriptiondialog.h"
0029 #include "trashjob.h"
0030 #include "trashrestorejob.h"
0031 
0032 #include <KActionCollection>
0033 #include <KActionMenu>
0034 #include <KConfig>
0035 #include <KConfigGroup>
0036 #include <KLocalizedString>
0037 #include <KMessageBox>
0038 #include <KToggleAction>
0039 #include <QAction>
0040 #include <QIcon>
0041 #include <QMenu>
0042 
0043 #include <QApplication>
0044 #include <QClipboard>
0045 #include <QInputDialog>
0046 #include <QItemSelectionModel>
0047 #include <QMimeData>
0048 #include <QPointer>
0049 #include <QRegularExpression>
0050 #include <QTimer>
0051 
0052 #include <KLazyLocalizedString>
0053 
0054 using namespace Akonadi;
0055 
0056 /// @cond PRIVATE
0057 
0058 enum ActionType {
0059     NormalAction,
0060     ActionWithAlternative, // Normal action, but with an alternative state
0061     ActionAlternative, // Alternative state of the ActionWithAlternative
0062     MenuAction,
0063     ToggleAction
0064 };
0065 
0066 struct StandardActionData { // NOLINT(clang-analyzer-optin.performance.Padding) FIXME
0067     const char *name;
0068     const KLazyLocalizedString label;
0069     const KLazyLocalizedString iconLabel;
0070     const char *icon;
0071     const char *altIcon;
0072     int shortcut;
0073     const char *slot;
0074     ActionType actionType;
0075 };
0076 
0077 static const StandardActionData standardActionData[] = {
0078     {"akonadi_collection_create", kli18n("&New Folder..."), kli18n("New"), "folder-new", nullptr, 0, SLOT(slotCreateCollection()), NormalAction},
0079     {"akonadi_collection_copy", KLazyLocalizedString(), KLazyLocalizedString(), "edit-copy", nullptr, 0, SLOT(slotCopyCollections()), NormalAction},
0080     {"akonadi_collection_delete", kli18n("&Delete Folder"), kli18n("Delete"), "edit-delete", nullptr, 0, SLOT(slotDeleteCollection()), NormalAction},
0081     {"akonadi_collection_sync",
0082      kli18n("&Synchronize Folder"),
0083      kli18n("Synchronize"),
0084      "view-refresh",
0085      nullptr,
0086      Qt::Key_F5,
0087      SLOT(slotSynchronizeCollection()),
0088      NormalAction},
0089     {"akonadi_collection_properties",
0090      kli18n("Folder &Properties"),
0091      kli18n("Properties"),
0092      "configure",
0093      nullptr,
0094      0,
0095      SLOT(slotCollectionProperties()),
0096      NormalAction},
0097     {"akonadi_item_copy", KLazyLocalizedString(), KLazyLocalizedString(), "edit-copy", nullptr, 0, SLOT(slotCopyItems()), NormalAction},
0098     {"akonadi_paste", kli18n("&Paste"), kli18n("Paste"), "edit-paste", nullptr, Qt::CTRL | Qt::Key_V, SLOT(slotPaste()), NormalAction},
0099     {"akonadi_item_delete", KLazyLocalizedString(), KLazyLocalizedString(), "edit-delete", nullptr, 0, SLOT(slotDeleteItems()), NormalAction},
0100     {"akonadi_manage_local_subscriptions",
0101      kli18n("Manage Local &Subscriptions..."),
0102      kli18n("Manage Local Subscriptions"),
0103      "folder-bookmarks",
0104      nullptr,
0105      0,
0106      SLOT(slotLocalSubscription()),
0107      NormalAction},
0108     {"akonadi_collection_add_to_favorites",
0109      kli18n("Add to Favorite Folders"),
0110      kli18n("Add to Favorite"),
0111      "bookmark-new",
0112      nullptr,
0113      0,
0114      SLOT(slotAddToFavorites()),
0115      NormalAction},
0116     {"akonadi_collection_remove_from_favorites",
0117      kli18n("Remove from Favorite Folders"),
0118      kli18n("Remove from Favorite"),
0119      "edit-delete",
0120      nullptr,
0121      0,
0122      SLOT(slotRemoveFromFavorites()),
0123      NormalAction},
0124     {"akonadi_collection_rename_favorite", kli18n("Rename Favorite..."), kli18n("Rename"), "edit-rename", nullptr, 0, SLOT(slotRenameFavorite()), NormalAction},
0125     {"akonadi_collection_copy_to_menu",
0126      kli18n("Copy Folder To..."),
0127      kli18n("Copy To"),
0128      "edit-copy",
0129      nullptr,
0130      0,
0131      SLOT(slotCopyCollectionTo(QAction *)),
0132      MenuAction},
0133     {"akonadi_item_copy_to_menu", kli18n("Copy Item To..."), kli18n("Copy To"), "edit-copy", nullptr, 0, SLOT(slotCopyItemTo(QAction *)), MenuAction},
0134     {"akonadi_item_move_to_menu", kli18n("Move Item To..."), kli18n("Move To"), "edit-move", "go-jump", 0, SLOT(slotMoveItemTo(QAction *)), MenuAction},
0135     {"akonadi_collection_move_to_menu",
0136      kli18n("Move Folder To..."),
0137      kli18n("Move To"),
0138      "edit-move",
0139      "go-jump",
0140      0,
0141      SLOT(slotMoveCollectionTo(QAction *)),
0142      MenuAction},
0143     {"akonadi_item_cut", kli18n("&Cut Item"), kli18n("Cut"), "edit-cut", nullptr, Qt::CTRL | Qt::Key_X, SLOT(slotCutItems()), NormalAction},
0144     {"akonadi_collection_cut", kli18n("&Cut Folder"), kli18n("Cut"), "edit-cut", nullptr, Qt::CTRL | Qt::Key_X, SLOT(slotCutCollections()), NormalAction},
0145     {"akonadi_resource_create", kli18n("Create Resource"), KLazyLocalizedString(), "folder-new", nullptr, 0, SLOT(slotCreateResource()), NormalAction},
0146     {"akonadi_resource_delete", kli18n("Delete Resource"), KLazyLocalizedString(), "edit-delete", nullptr, 0, SLOT(slotDeleteResource()), NormalAction},
0147     {"akonadi_resource_properties",
0148      kli18n("&Resource Properties"),
0149      kli18n("Properties"),
0150      "configure",
0151      nullptr,
0152      0,
0153      SLOT(slotResourceProperties()),
0154      NormalAction},
0155     {"akonadi_resource_synchronize",
0156      kli18n("Synchronize Resource"),
0157      kli18n("Synchronize"),
0158      "view-refresh",
0159      nullptr,
0160      0,
0161      SLOT(slotSynchronizeResource()),
0162      NormalAction},
0163     {"akonadi_work_offline", kli18n("Work Offline"), KLazyLocalizedString(), "user-offline", nullptr, 0, SLOT(slotToggleWorkOffline(bool)), ToggleAction},
0164     {"akonadi_collection_copy_to_dialog", kli18n("Copy Folder To..."), kli18n("Copy To"), "edit-copy", nullptr, 0, SLOT(slotCopyCollectionTo()), NormalAction},
0165     {"akonadi_collection_move_to_dialog",
0166      kli18n("Move Folder To..."),
0167      kli18n("Move To"),
0168      "edit-move",
0169      "go-jump",
0170      0,
0171      SLOT(slotMoveCollectionTo()),
0172      NormalAction},
0173     {"akonadi_item_copy_to_dialog", kli18n("Copy Item To..."), kli18n("Copy To"), "edit-copy", nullptr, 0, SLOT(slotCopyItemTo()), NormalAction},
0174     {"akonadi_item_move_to_dialog", kli18n("Move Item To..."), kli18n("Move To"), "edit-move", "go-jump", 0, SLOT(slotMoveItemTo()), NormalAction},
0175     {"akonadi_collection_sync_recursive",
0176      kli18n("&Synchronize Folder Recursively"),
0177      kli18n("Synchronize Recursively"),
0178      "view-refresh",
0179      nullptr,
0180      Qt::CTRL | Qt::Key_F5,
0181      SLOT(slotSynchronizeCollectionRecursive()),
0182      NormalAction},
0183     {"akonadi_move_collection_to_trash",
0184      kli18n("&Move Folder To Trash"),
0185      kli18n("Move Folder To Trash"),
0186      "edit-delete",
0187      nullptr,
0188      0,
0189      SLOT(slotMoveCollectionToTrash()),
0190      NormalAction},
0191     {"akonadi_move_item_to_trash",
0192      kli18n("&Move Item To Trash"),
0193      kli18n("Move Item To Trash"),
0194      "edit-delete",
0195      nullptr,
0196      0,
0197      SLOT(slotMoveItemToTrash()),
0198      NormalAction},
0199     {"akonadi_restore_collection_from_trash",
0200      kli18n("&Restore Folder From Trash"),
0201      kli18n("Restore Folder From Trash"),
0202      "view-refresh",
0203      nullptr,
0204      0,
0205      SLOT(slotRestoreCollectionFromTrash()),
0206      NormalAction},
0207     {"akonadi_restore_item_from_trash",
0208      kli18n("&Restore Item From Trash"),
0209      kli18n("Restore Item From Trash"),
0210      "view-refresh",
0211      nullptr,
0212      0,
0213      SLOT(slotRestoreItemFromTrash()),
0214      NormalAction},
0215     {"akonadi_collection_trash_restore",
0216      kli18n("&Restore Folder From Trash"),
0217      kli18n("Restore Folder From Trash"),
0218      "edit-delete",
0219      nullptr,
0220      0,
0221      SLOT(slotTrashRestoreCollection()),
0222      ActionWithAlternative},
0223     {nullptr, kli18n("&Restore Collection From Trash"), kli18n("Restore Collection From Trash"), "view-refresh", nullptr, 0, nullptr, ActionAlternative},
0224     {"akonadi_item_trash_restore",
0225      kli18n("&Restore Item From Trash"),
0226      kli18n("Restore Item From Trash"),
0227      "edit-delete",
0228      nullptr,
0229      0,
0230      SLOT(slotTrashRestoreItem()),
0231      ActionWithAlternative},
0232     {nullptr, kli18n("&Restore Item From Trash"), kli18n("Restore Item From Trash"), "view-refresh", nullptr, 0, nullptr, ActionAlternative},
0233     {"akonadi_collection_sync_favorite_folders",
0234      kli18n("&Synchronize Favorite Folders"),
0235      kli18n("Synchronize Favorite Folders"),
0236      "view-refresh",
0237      nullptr,
0238      Qt::CTRL | Qt::SHIFT | Qt::Key_L,
0239      SLOT(slotSynchronizeFavoriteCollections()),
0240      NormalAction},
0241     {"akonadi_resource_synchronize_collectiontree",
0242      kli18n("Synchronize Folder Tree"),
0243      kli18n("Synchronize"),
0244      "view-refresh",
0245      nullptr,
0246      0,
0247      SLOT(slotSynchronizeCollectionTree()),
0248      NormalAction},
0249 
0250 };
0251 static const int numStandardActionData = sizeof standardActionData / sizeof *standardActionData;
0252 
0253 static QIcon standardActionDataIcon(const StandardActionData &data)
0254 {
0255     if (data.altIcon) {
0256         return QIcon::fromTheme(QString::fromLatin1(data.icon), QIcon::fromTheme(QString::fromLatin1(data.altIcon)));
0257     }
0258     return QIcon::fromTheme(QString::fromLatin1(data.icon));
0259 }
0260 
0261 static_assert(numStandardActionData == StandardActionManager::LastType, "StandardActionData table does not match StandardActionManager types");
0262 
0263 static bool canCreateCollection(const Akonadi::Collection &collection)
0264 {
0265     return !!(collection.rights() & Akonadi::Collection::CanCreateCollection);
0266 }
0267 
0268 static void setWorkOffline(bool offline)
0269 {
0270     KConfig config(QStringLiteral("akonadikderc"));
0271     KConfigGroup group(&config, QStringLiteral("Actions"));
0272 
0273     group.writeEntry("WorkOffline", offline);
0274 }
0275 
0276 static bool workOffline()
0277 {
0278     KConfig config(QStringLiteral("akonadikderc"));
0279     const KConfigGroup group(&config, QStringLiteral("Actions"));
0280 
0281     return group.readEntry("WorkOffline", false);
0282 }
0283 
0284 static QModelIndexList safeSelectedRows(QItemSelectionModel *selectionModel)
0285 {
0286     QModelIndexList selectedRows = selectionModel->selectedRows();
0287     if (!selectedRows.isEmpty()) {
0288         return selectedRows;
0289     }
0290 
0291     // 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)
0292     const auto selection = selectionModel->selection();
0293     for (const auto &range : selection) {
0294         if (!range.isValid() || range.isEmpty()) {
0295             continue;
0296         }
0297         const QModelIndex parent = range.parent();
0298         for (int row = range.top(); row <= range.bottom(); ++row) {
0299             const QModelIndex index = range.model()->index(row, range.left(), parent);
0300             const Qt::ItemFlags flags = range.model()->flags(index);
0301             if ((flags & Qt::ItemIsSelectable) && (flags & Qt::ItemIsEnabled)) {
0302                 selectedRows.push_back(index);
0303             }
0304         }
0305     }
0306 
0307     return selectedRows;
0308 }
0309 
0310 /**
0311  * @internal
0312  */
0313 class Akonadi::StandardActionManagerPrivate
0314 {
0315 public:
0316     explicit StandardActionManagerPrivate(StandardActionManager *parent)
0317         : q(parent)
0318         , actionCollection(nullptr)
0319         , parentWidget(nullptr)
0320         , collectionSelectionModel(nullptr)
0321         , itemSelectionModel(nullptr)
0322         , favoritesModel(nullptr)
0323         , favoriteSelectionModel(nullptr)
0324         , insideSelectionSlot(false)
0325     {
0326         actions.fill(nullptr, StandardActionManager::LastType);
0327 
0328         pluralLabels.insert(StandardActionManager::CopyCollections, ki18np("&Copy Folder", "&Copy %1 Folders"));
0329         pluralLabels.insert(StandardActionManager::CopyItems, ki18np("&Copy Item", "&Copy %1 Items"));
0330         pluralLabels.insert(StandardActionManager::CutItems, ki18ncp("@action", "&Cut Item", "&Cut %1 Items"));
0331         pluralLabels.insert(StandardActionManager::CutCollections, ki18ncp("@action", "&Cut Folder", "&Cut %1 Folders"));
0332         pluralLabels.insert(StandardActionManager::DeleteItems, ki18np("&Delete Item", "&Delete %1 Items"));
0333         pluralLabels.insert(StandardActionManager::DeleteCollections, ki18ncp("@action", "&Delete Folder", "&Delete %1 Folders"));
0334         pluralLabels.insert(StandardActionManager::SynchronizeCollections, ki18ncp("@action", "&Synchronize Folder", "&Synchronize %1 Folders"));
0335         pluralLabels.insert(StandardActionManager::DeleteResources, ki18np("&Delete Resource", "&Delete %1 Resources"));
0336         pluralLabels.insert(StandardActionManager::SynchronizeResources, ki18np("&Synchronize Resource", "&Synchronize %1 Resources"));
0337 
0338         pluralIconLabels.insert(StandardActionManager::CopyCollections, ki18np("Copy Folder", "Copy %1 Folders"));
0339         pluralIconLabels.insert(StandardActionManager::CopyItems, ki18np("Copy Item", "Copy %1 Items"));
0340         pluralIconLabels.insert(StandardActionManager::CutItems, ki18np("Cut Item", "Cut %1 Items"));
0341         pluralIconLabels.insert(StandardActionManager::CutCollections, ki18np("Cut Folder", "Cut %1 Folders"));
0342         pluralIconLabels.insert(StandardActionManager::DeleteItems, ki18np("Delete Item", "Delete %1 Items"));
0343         pluralIconLabels.insert(StandardActionManager::DeleteCollections, ki18np("Delete Folder", "Delete %1 Folders"));
0344         pluralIconLabels.insert(StandardActionManager::SynchronizeCollections, ki18np("Synchronize Folder", "Synchronize %1 Folders"));
0345         pluralIconLabels.insert(StandardActionManager::DeleteResources, ki18ncp("@action", "Delete Resource", "Delete %1 Resources"));
0346         pluralIconLabels.insert(StandardActionManager::SynchronizeResources, ki18ncp("@action", "Synchronize Resource", "Synchronize %1 Resources"));
0347 
0348         setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle, i18nc("@title:window", "New Folder"));
0349         setContextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText, i18nc("@label:textbox name of Akonadi folder", "Name"));
0350         setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText, ki18n("Could not create folder: %1"));
0351         setContextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Folder Creation Failed"));
0352 
0353         setContextText(
0354             StandardActionManager::DeleteCollections,
0355             StandardActionManager::MessageBoxText,
0356             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?"));
0357         setContextText(StandardActionManager::DeleteCollections,
0358                        StandardActionManager::MessageBoxTitle,
0359                        ki18ncp("@title:window", "Delete Folder?", "Delete Folders?"));
0360         setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText, ki18n("Could not delete folder: %1"));
0361         setContextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Folder Deletion Failed"));
0362 
0363         setContextText(StandardActionManager::CollectionProperties, StandardActionManager::DialogTitle, ki18nc("@title:window", "Properties of Folder %1"));
0364 
0365         setContextText(StandardActionManager::DeleteItems,
0366                        StandardActionManager::MessageBoxText,
0367                        ki18np("Do you really want to delete the selected item?", "Do you really want to delete %1 items?"));
0368         setContextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle, ki18ncp("@title:window", "Delete Item?", "Delete Items?"));
0369         setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText, ki18n("Could not delete item: %1"));
0370         setContextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Item Deletion Failed"));
0371 
0372         setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogTitle, i18nc("@title:window", "Rename Favorite"));
0373         setContextText(StandardActionManager::RenameFavoriteCollection, StandardActionManager::DialogText, i18nc("@label:textbox name of the folder", "Name:"));
0374 
0375         setContextText(StandardActionManager::CreateResource, StandardActionManager::DialogTitle, i18nc("@title:window", "New Resource"));
0376         setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText, ki18n("Could not create resource: %1"));
0377         setContextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Resource Creation Failed"));
0378 
0379         setContextText(StandardActionManager::DeleteResources,
0380                        StandardActionManager::MessageBoxText,
0381                        ki18np("Do you really want to delete this resource?", "Do you really want to delete %1 resources?"));
0382         setContextText(StandardActionManager::DeleteResources,
0383                        StandardActionManager::MessageBoxTitle,
0384                        ki18ncp("@title:window", "Delete Resource?", "Delete Resources?"));
0385 
0386         setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageText, ki18n("Could not paste data: %1"));
0387         setContextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle, i18nc("@title:window", "Paste Failed"));
0388 
0389         mDelayedUpdateTimer.setSingleShot(true);
0390         QObject::connect(&mDelayedUpdateTimer, &QTimer::timeout, q, [this]() {
0391             updateActions();
0392         });
0393 
0394         qRegisterMetaType<Akonadi::Item::List>("Akonadi::Item::List");
0395     }
0396 
0397     void enableAction(int type, bool enable) // private slot, called by ActionStateManager
0398     {
0399         enableAction(static_cast<StandardActionManager::Type>(type), enable);
0400     }
0401 
0402     void enableAction(StandardActionManager::Type type, bool enable)
0403     {
0404         Q_ASSERT(type < StandardActionManager::LastType);
0405         if (actions[type]) {
0406             actions[type]->setEnabled(enable);
0407         }
0408 
0409         // Update the action menu
0410         auto actionMenu = qobject_cast<KActionMenu *>(actions[type]);
0411         if (actionMenu) {
0412             // get rid of the submenus, they are re-created in enableAction. clear() is not enough, doesn't remove the submenu object instances.
0413             QMenu *menu = actionMenu->menu();
0414             // Not necessary to delete and recreate menu when it was not created
0415             if (menu->property("actionType").isValid() && menu->isEmpty()) {
0416                 return;
0417             }
0418             mRecentCollectionsMenu.remove(type);
0419             delete menu;
0420             menu = new QMenu();
0421 
0422             menu->setProperty("actionType", static_cast<int>(type));
0423             q->connect(menu, &QMenu::aboutToShow, q, [this]() {
0424                 aboutToShowMenu();
0425             });
0426             q->connect(menu, SIGNAL(triggered(QAction *)), standardActionData[type].slot); // clazy:exclude=old-style-connect
0427             actionMenu->setMenu(menu);
0428         }
0429     }
0430 
0431     void aboutToShowMenu()
0432     {
0433         auto menu = qobject_cast<QMenu *>(q->sender());
0434         if (!menu) {
0435             return;
0436         }
0437 
0438         if (!menu->isEmpty()) {
0439             return;
0440         }
0441         // collect all selected collections
0442         const Akonadi::Collection::List selectedCollectionsList = selectedCollections();
0443         const StandardActionManager::Type type = static_cast<StandardActionManager::Type>(menu->property("actionType").toInt());
0444 
0445         QPointer<RecentCollectionAction> recentCollection = new RecentCollectionAction(type, selectedCollectionsList, collectionSelectionModel->model(), menu);
0446         mRecentCollectionsMenu.insert(type, recentCollection);
0447         const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
0448         fillFoldersMenu(selectedCollectionsList, mimeTypes, type, menu, collectionSelectionModel->model(), QModelIndex());
0449     }
0450 
0451     void createActionFolderMenu(QMenu *menu, StandardActionManager::Type type)
0452     {
0453         if (type == StandardActionManager::CopyCollectionToMenu || type == StandardActionManager::CopyItemToMenu
0454             || type == StandardActionManager::MoveItemToMenu || type == StandardActionManager::MoveCollectionToMenu) {
0455             new RecentCollectionAction(type, Akonadi::Collection::List(), collectionSelectionModel->model(), menu);
0456             Collection::List selectedCollectionsList = selectedCollections();
0457             const QSet<QString> mimeTypes = mimeTypesOfSelection(type);
0458             fillFoldersMenu(selectedCollectionsList, mimeTypes, type, menu, collectionSelectionModel->model(), QModelIndex());
0459         }
0460     }
0461 
0462     void updateAlternatingAction(int type) // private slot, called by ActionStateManager
0463     {
0464         updateAlternatingAction(static_cast<StandardActionManager::Type>(type));
0465     }
0466 
0467     void updateAlternatingAction(StandardActionManager::Type type)
0468     {
0469         Q_ASSERT(type < StandardActionManager::LastType);
0470         if (!actions[type]) {
0471             return;
0472         }
0473 
0474         /*
0475          * The same action is stored at the ActionWithAlternative indexes as well as the corresponding ActionAlternative indexes in the actions array.
0476          * The following simply changes the standardActionData
0477          */
0478         if ((standardActionData[type].actionType == ActionWithAlternative) || (standardActionData[type].actionType == ActionAlternative)) {
0479             actions[type]->setText(standardActionData[type].label.toString());
0480             actions[type]->setIcon(standardActionDataIcon(standardActionData[type]));
0481 
0482             if (pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) {
0483                 actions[type]->setText(pluralLabels.value(type).subs(1).toString());
0484             } else if (!standardActionData[type].label.isEmpty()) {
0485                 actions[type]->setText(standardActionData[type].label.toString());
0486             }
0487             if (pluralIconLabels.contains(type) && !pluralIconLabels.value(type).isEmpty()) {
0488                 actions[type]->setIconText(pluralIconLabels.value(type).subs(1).toString());
0489             } else if (!standardActionData[type].iconLabel.isEmpty()) {
0490                 actions[type]->setIconText(standardActionData[type].iconLabel.toString());
0491             }
0492             if (standardActionData[type].icon) {
0493                 actions[type]->setIcon(standardActionDataIcon(standardActionData[type]));
0494             }
0495 
0496             // actions[type]->setShortcut( standardActionData[type].shortcut );
0497 
0498             /*if ( standardActionData[type].slot ) {
0499               switch ( standardActionData[type].actionType ) {
0500               case NormalAction:
0501               case ActionWithAlternative:
0502                   connect( action, SIGNAL(triggered()), standardActionData[type].slot );
0503                   break;
0504               }
0505             }*/
0506         }
0507     }
0508 
0509     void updatePluralLabel(int type, int count) // private slot, called by ActionStateManager
0510     {
0511         updatePluralLabel(static_cast<StandardActionManager::Type>(type), count);
0512     }
0513 
0514     void updatePluralLabel(StandardActionManager::Type type, int count) // private slot, called by ActionStateManager
0515     {
0516         Q_ASSERT(type < StandardActionManager::LastType);
0517         if (actions[type] && pluralLabels.contains(type) && !pluralLabels.value(type).isEmpty()) {
0518             actions[type]->setText(pluralLabels.value(type).subs(qMax(count, 1)).toString());
0519         }
0520     }
0521 
0522     bool isFavoriteCollection(const Akonadi::Collection &collection) const // private slot, called by ActionStateManager
0523     {
0524         if (!favoritesModel) {
0525             return false;
0526         }
0527 
0528         return favoritesModel->collectionIds().contains(collection.id());
0529     }
0530 
0531     void encodeToClipboard(QItemSelectionModel *selectionModel, bool cut = false)
0532     {
0533         Q_ASSERT(selectionModel);
0534         if (safeSelectedRows(selectionModel).isEmpty()) {
0535             return;
0536         }
0537 
0538 #ifndef QT_NO_CLIPBOARD
0539         auto model = const_cast<QAbstractItemModel *>(selectionModel->model());
0540         QMimeData *mimeData = selectionModel->model()->mimeData(safeSelectedRows(selectionModel));
0541         model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole);
0542         markCutAction(mimeData, cut);
0543         QApplication::clipboard()->setMimeData(mimeData);
0544         if (cut) {
0545             const auto rows = safeSelectedRows(selectionModel);
0546             for (const auto &index : rows) {
0547                 model->setData(index, true, EntityTreeModel::PendingCutRole);
0548             }
0549         }
0550 #endif
0551     }
0552 
0553     static Akonadi::Collection::List collectionsForIndexes(const QModelIndexList &list)
0554     {
0555         Akonadi::Collection::List collectionList;
0556         for (const QModelIndex &index : list) {
0557             auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
0558             if (!collection.isValid()) {
0559                 continue;
0560             }
0561 
0562             const auto parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>();
0563             collection.setParentCollection(parentCollection);
0564 
0565             collectionList << std::move(collection);
0566         }
0567         return collectionList;
0568     }
0569 
0570     void delayedUpdateActions()
0571     {
0572         // Compress changes (e.g. when deleting many rows, do this only once)
0573         mDelayedUpdateTimer.start(0);
0574     }
0575 
0576     void updateActions()
0577     {
0578         // favorite collections
0579         Collection::List selectedFavoriteCollectionsList;
0580         if (favoriteSelectionModel) {
0581             const QModelIndexList rows = safeSelectedRows(favoriteSelectionModel);
0582             selectedFavoriteCollectionsList = collectionsForIndexes(rows);
0583         }
0584 
0585         // collect all selected collections
0586         Collection::List selectedCollectionsList;
0587         if (collectionSelectionModel) {
0588             const QModelIndexList rows = safeSelectedRows(collectionSelectionModel);
0589             selectedCollectionsList = collectionsForIndexes(rows);
0590         }
0591 
0592         // collect all selected items
0593         Item::List selectedItems;
0594         if (itemSelectionModel) {
0595             const QModelIndexList rows = safeSelectedRows(itemSelectionModel);
0596             for (const QModelIndex &index : rows) {
0597                 Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
0598                 if (!item.isValid()) {
0599                     continue;
0600                 }
0601 
0602                 const auto parentCollection = index.data(EntityTreeModel::ParentCollectionRole).value<Collection>();
0603                 item.setParentCollection(parentCollection);
0604 
0605                 selectedItems << item;
0606             }
0607         }
0608 
0609         mActionStateManager.updateState(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems);
0610         if (favoritesModel) {
0611             enableAction(StandardActionManager::SynchronizeFavoriteCollections, (favoritesModel->rowCount() > 0));
0612         }
0613         Q_EMIT q->selectionsChanged(selectedCollectionsList, selectedFavoriteCollectionsList, selectedItems);
0614         Q_EMIT q->actionStateUpdated();
0615     }
0616 
0617 #ifndef QT_NO_CLIPBOARD
0618     void clipboardChanged(QClipboard::Mode mode)
0619     {
0620         if (mode == QClipboard::Clipboard) {
0621             updateActions();
0622         }
0623     }
0624 #endif
0625 
0626     QItemSelection mapToEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
0627     {
0628         const auto proxy = qobject_cast<const QAbstractProxyModel *>(model);
0629         if (proxy) {
0630             return mapToEntityTreeModel(proxy->sourceModel(), proxy->mapSelectionToSource(selection));
0631         } else {
0632             return selection;
0633         }
0634     }
0635 
0636     QItemSelection mapFromEntityTreeModel(const QAbstractItemModel *model, const QItemSelection &selection) const
0637     {
0638         const auto proxy = qobject_cast<const QAbstractProxyModel *>(model);
0639         if (proxy) {
0640             const QItemSelection select = mapFromEntityTreeModel(proxy->sourceModel(), selection);
0641             return proxy->mapSelectionFromSource(select);
0642         } else {
0643             return selection;
0644         }
0645     }
0646 
0647     // RAII class for setting insideSelectionSlot to true on entering, and false on exiting, the two slots below.
0648     class InsideSelectionSlotBlocker
0649     {
0650     public:
0651         explicit InsideSelectionSlotBlocker(StandardActionManagerPrivate *p)
0652             : _p(p)
0653         {
0654             Q_ASSERT(!p->insideSelectionSlot);
0655             p->insideSelectionSlot = true;
0656         }
0657 
0658         ~InsideSelectionSlotBlocker()
0659         {
0660             Q_ASSERT(_p->insideSelectionSlot);
0661             _p->insideSelectionSlot = false;
0662         }
0663 
0664     private:
0665         Q_DISABLE_COPY(InsideSelectionSlotBlocker)
0666         StandardActionManagerPrivate *const _p;
0667     };
0668 
0669     void collectionSelectionChanged()
0670     {
0671         if (insideSelectionSlot) {
0672             return;
0673         }
0674         InsideSelectionSlotBlocker block(this);
0675         if (favoriteSelectionModel) {
0676             QItemSelection selection = collectionSelectionModel->selection();
0677             selection = mapToEntityTreeModel(collectionSelectionModel->model(), selection);
0678             selection = mapFromEntityTreeModel(favoriteSelectionModel->model(), selection);
0679 
0680             favoriteSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
0681         }
0682 
0683         updateActions();
0684     }
0685 
0686     void favoriteSelectionChanged()
0687     {
0688         if (insideSelectionSlot) {
0689             return;
0690         }
0691         QItemSelection selection = favoriteSelectionModel->selection();
0692         if (selection.isEmpty()) {
0693             return;
0694         }
0695 
0696         selection = mapToEntityTreeModel(favoriteSelectionModel->model(), selection);
0697         selection = mapFromEntityTreeModel(collectionSelectionModel->model(), selection);
0698 
0699         InsideSelectionSlotBlocker block(this);
0700         collectionSelectionModel->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0701 
0702         // Also set the current index. This will trigger KMMainWidget::slotFolderChanged in kmail, which we want.
0703         if (!selection.indexes().isEmpty()) {
0704             collectionSelectionModel->setCurrentIndex(selection.indexes().first(), QItemSelectionModel::NoUpdate);
0705         }
0706 
0707         updateActions();
0708     }
0709 
0710     void slotCreateCollection()
0711     {
0712         Q_ASSERT(collectionSelectionModel);
0713         if (collectionSelectionModel->selection().indexes().isEmpty()) {
0714             return;
0715         }
0716 
0717         const QModelIndex index = collectionSelectionModel->selection().indexes().at(0);
0718         Q_ASSERT(index.isValid());
0719         const auto parentCollection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
0720         Q_ASSERT(parentCollection.isValid());
0721 
0722         if (!canCreateCollection(parentCollection)) {
0723             return;
0724         }
0725 
0726         bool ok = false;
0727         QString name = QInputDialog::getText(parentWidget,
0728                                              contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogTitle),
0729                                              contextText(StandardActionManager::CreateCollection, StandardActionManager::DialogText),
0730                                              {},
0731                                              {},
0732                                              &ok);
0733         name = name.trimmed();
0734         if (name.isEmpty() || !ok) {
0735             return;
0736         }
0737 
0738         if (name.contains(QLatin1Char('/'))) {
0739             KMessageBox::error(parentWidget, i18n("We can not add \"/\" in folder name."), i18nc("@title:window", "Create New Folder Error"));
0740             return;
0741         }
0742         if (name.startsWith(QLatin1Char('.')) || name.endsWith(QLatin1Char('.'))) {
0743             KMessageBox::error(parentWidget, i18n("We can not add \".\" at begin or end of folder name."), i18nc("@title:window", "Create New Folder Error"));
0744             return;
0745         }
0746 
0747         Collection collection;
0748         collection.setName(name);
0749         collection.setParentCollection(parentCollection);
0750         if (actions[StandardActionManager::CreateCollection]) {
0751             const QStringList mts = actions[StandardActionManager::CreateCollection]->property("ContentMimeTypes").toStringList();
0752             if (!mts.isEmpty()) {
0753                 collection.setContentMimeTypes(mts);
0754             }
0755         }
0756         if (parentCollection.contentMimeTypes().contains(Collection::virtualMimeType())) {
0757             collection.setVirtual(true);
0758             collection.setContentMimeTypes(collection.contentMimeTypes() << Collection::virtualMimeType());
0759         }
0760 
0761         auto job = new CollectionCreateJob(collection);
0762         q->connect(job, &KJob::result, q, [this](KJob *job) {
0763             collectionCreationResult(job);
0764         });
0765     }
0766 
0767     void slotCopyCollections()
0768     {
0769         encodeToClipboard(collectionSelectionModel);
0770     }
0771 
0772     void slotCutCollections()
0773     {
0774         encodeToClipboard(collectionSelectionModel, true);
0775     }
0776 
0777     Collection::List selectedCollections()
0778     {
0779         Collection::List collections;
0780 
0781         Q_ASSERT(collectionSelectionModel);
0782         const QModelIndexList indexes = safeSelectedRows(collectionSelectionModel);
0783         collections.reserve(indexes.count());
0784 
0785         for (const QModelIndex &index : indexes) {
0786             Q_ASSERT(index.isValid());
0787             const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
0788             Q_ASSERT(collection.isValid());
0789 
0790             collections << collection;
0791         }
0792 
0793         return collections;
0794     }
0795 
0796     void slotDeleteCollection()
0797     {
0798         const Collection::List collections = selectedCollections();
0799         if (collections.isEmpty()) {
0800             return;
0801         }
0802 
0803         const QString collectionName = collections.first().name();
0804         const QString text = contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxText, collections.count(), collectionName);
0805 
0806         if (KMessageBox::questionTwoActions(
0807                 parentWidget,
0808                 text,
0809                 contextText(StandardActionManager::DeleteCollections, StandardActionManager::MessageBoxTitle, collections.count(), collectionName),
0810                 KStandardGuiItem::del(),
0811                 KStandardGuiItem::cancel(),
0812                 QString(),
0813                 KMessageBox::Dangerous)
0814             != KMessageBox::ButtonCode::PrimaryAction) {
0815             return;
0816         }
0817 
0818         for (const Collection &collection : collections) {
0819             auto job = new CollectionDeleteJob(collection, q);
0820             q->connect(job, &CollectionDeleteJob::result, q, [this](KJob *job) {
0821                 collectionDeletionResult(job);
0822             });
0823         }
0824     }
0825 
0826     void slotMoveCollectionToTrash()
0827     {
0828         const Collection::List collections = selectedCollections();
0829         if (collections.isEmpty()) {
0830             return;
0831         }
0832 
0833         for (const Collection &collection : collections) {
0834             auto job = new TrashJob(collection, q);
0835             q->connect(job, &TrashJob::result, q, [this](KJob *job) {
0836                 moveCollectionToTrashResult(job);
0837             });
0838         }
0839     }
0840 
0841     void slotRestoreCollectionFromTrash()
0842     {
0843         const Collection::List collections = selectedCollections();
0844         if (collections.isEmpty()) {
0845             return;
0846         }
0847 
0848         for (const Collection &collection : collections) {
0849             auto job = new TrashRestoreJob(collection, q);
0850             q->connect(job, &TrashRestoreJob::result, q, [this](KJob *job) {
0851                 moveCollectionToTrashResult(job);
0852             });
0853         }
0854     }
0855 
0856     Item::List selectedItems() const
0857     {
0858         Item::List items;
0859 
0860         Q_ASSERT(itemSelectionModel);
0861         const QModelIndexList indexes = safeSelectedRows(itemSelectionModel);
0862         items.reserve(indexes.count());
0863 
0864         for (const QModelIndex &index : indexes) {
0865             Q_ASSERT(index.isValid());
0866             const Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
0867             Q_ASSERT(item.isValid());
0868 
0869             items << item;
0870         }
0871 
0872         return items;
0873     }
0874 
0875     void slotMoveItemToTrash()
0876     {
0877         const Item::List items = selectedItems();
0878         if (items.isEmpty()) {
0879             return;
0880         }
0881 
0882         auto job = new TrashJob(items, q);
0883         q->connect(job, &TrashJob::result, q, [this](KJob *job) {
0884             moveItemToTrashResult(job);
0885         });
0886     }
0887 
0888     void slotRestoreItemFromTrash()
0889     {
0890         const Item::List items = selectedItems();
0891         if (items.isEmpty()) {
0892             return;
0893         }
0894 
0895         auto job = new TrashRestoreJob(items, q);
0896         q->connect(job, &TrashRestoreJob::result, q, [this](KJob *job) {
0897             moveItemToTrashResult(job);
0898         });
0899     }
0900 
0901     void slotTrashRestoreCollection()
0902     {
0903         const Collection::List collections = selectedCollections();
0904         if (collections.isEmpty()) {
0905             return;
0906         }
0907 
0908         bool collectionsAreInTrash = false;
0909         for (const Collection &collection : collections) {
0910             if (collection.hasAttribute<EntityDeletedAttribute>()) {
0911                 collectionsAreInTrash = true;
0912                 break;
0913             }
0914         }
0915 
0916         if (collectionsAreInTrash) {
0917             slotRestoreCollectionFromTrash();
0918         } else {
0919             slotMoveCollectionToTrash();
0920         }
0921     }
0922 
0923     void slotTrashRestoreItem()
0924     {
0925         const Item::List items = selectedItems();
0926         if (items.isEmpty()) {
0927             return;
0928         }
0929 
0930         bool itemsAreInTrash = false;
0931         for (const Item &item : items) {
0932             if (item.hasAttribute<EntityDeletedAttribute>()) {
0933                 itemsAreInTrash = true;
0934                 break;
0935             }
0936         }
0937 
0938         if (itemsAreInTrash) {
0939             slotRestoreItemFromTrash();
0940         } else {
0941             slotMoveItemToTrash();
0942         }
0943     }
0944 
0945     void slotSynchronizeCollection()
0946     {
0947         Q_ASSERT(collectionSelectionModel);
0948         const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
0949         if (list.isEmpty()) {
0950             return;
0951         }
0952 
0953         const Collection::List collections = selectedCollections();
0954         if (collections.isEmpty()) {
0955             return;
0956         }
0957 
0958         for (const Collection &collection : collections) {
0959             if (!testAndSetOnlineResources(collection)) {
0960                 break;
0961             }
0962             AgentManager::self()->synchronizeCollection(collection, false);
0963         }
0964     }
0965 
0966     bool testAndSetOnlineResources(const Akonadi::Collection &collection)
0967     {
0968         // Shortcut for the Search resource, which is a virtual resource and thus
0969         // is always online (but AgentManager does not know about it, so it returns
0970         // an invalid AgentInstance, which is "offline").
0971         //
0972         // FIXME: AgentManager should return a valid AgentInstance even
0973         // for virtual resources, which would be always online.
0974         if (collection.resource() == QLatin1StringView("akonadi_search_resource")) {
0975             return true;
0976         }
0977 
0978         Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
0979         if (!instance.isOnline()) {
0980             if (KMessageBox::questionTwoActions(
0981                     parentWidget,
0982                     i18n("Before syncing folder \"%1\" it is necessary to have the resource online. Do you want to make it online?", collection.displayName()),
0983                     i18n("Account \"%1\" is offline", instance.name()),
0984                     KGuiItem(i18nc("@action:button", "Go Online"), QIcon::fromTheme(QStringLiteral("user-online"))),
0985                     KStandardGuiItem::cancel())
0986                 != KMessageBox::ButtonCode::PrimaryAction) {
0987                 return false;
0988             }
0989             instance.setIsOnline(true);
0990         }
0991         return true;
0992     }
0993 
0994     void slotSynchronizeCollectionRecursive()
0995     {
0996         Q_ASSERT(collectionSelectionModel);
0997         const QModelIndexList list = safeSelectedRows(collectionSelectionModel);
0998         if (list.isEmpty()) {
0999             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);
1060         model->setData(QModelIndex(), false, EntityTreeModel::PendingCutRole);
1061         QApplication::clipboard()->clear();
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 
1083         QMetaObject::invokeMethod(
1084             q,
1085             [this, items] {
1086                 slotDeleteItemsDeferred(items);
1087             },
1088             Qt::QueuedConnection);
1089     }
1090 
1091     void slotDeleteItemsDeferred(const Akonadi::Item::List &items)
1092     {
1093         Q_ASSERT(itemSelectionModel);
1094 
1095         if (KMessageBox::questionTwoActions(parentWidget,
1096                                             contextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxText, items.count(), QString()),
1097                                             contextText(StandardActionManager::DeleteItems, StandardActionManager::MessageBoxTitle, items.count(), QString()),
1098                                             KStandardGuiItem::del(),
1099                                             KStandardGuiItem::cancel(),
1100                                             QString(),
1101                                             KMessageBox::Dangerous)
1102             != KMessageBox::ButtonCode::PrimaryAction) {
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 
1172         QPointer<RenameFavoriteDialog> dlg(
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     {
1269         QPointer<Akonadi::AgentTypeDialog> dlg(new Akonadi::AgentTypeDialog(parentWidget));
1270         dlg->setWindowTitle(contextText(StandardActionManager::CreateResource, StandardActionManager::DialogTitle));
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 
1302         if (KMessageBox::questionTwoActions(
1303                 parentWidget,
1304                 contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxText, instances.count(), instances.first().name()),
1305                 contextText(StandardActionManager::DeleteResources, StandardActionManager::MessageBoxTitle, instances.count(), instances.first().name()),
1306                 KStandardGuiItem::del(),
1307                 KStandardGuiItem::cancel(),
1308                 QString(),
1309                 KMessageBox::Dangerous)
1310             != KMessageBox::ButtonCode::PrimaryAction) {
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 
1349         AgentInstance::List instances = AgentManager::self()->instances();
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 
1362         if (type == StandardActionManager::CopyItemToMenu || type == StandardActionManager::MoveItemToMenu) {
1363             dlg->setAccessRightsFilter(Collection::CanCreateItem);
1364         } else if (type == StandardActionManager::CopyCollectionToMenu || type == StandardActionManager::MoveCollectionToMenu) {
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     {
1407         QMapIterator<StandardActionManager::Type, QPointer<RecentCollectionAction>> item(mRecentCollectionsMenu);
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,
1420                                contextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageText, job->errorString()),
1421                                contextText(StandardActionManager::CreateCollection, StandardActionManager::ErrorMessageTitle));
1422         }
1423     }
1424 
1425     void collectionDeletionResult(KJob *job) const
1426     {
1427         if (job->error()) {
1428             KMessageBox::error(parentWidget,
1429                                contextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageText, job->errorString()),
1430                                contextText(StandardActionManager::DeleteCollections, StandardActionManager::ErrorMessageTitle));
1431         }
1432     }
1433 
1434     void moveCollectionToTrashResult(KJob *job) const
1435     {
1436         if (job->error()) {
1437             KMessageBox::error(parentWidget,
1438                                contextText(StandardActionManager::MoveCollectionsToTrash, StandardActionManager::ErrorMessageText, job->errorString()),
1439                                contextText(StandardActionManager::MoveCollectionsToTrash, StandardActionManager::ErrorMessageTitle));
1440         }
1441     }
1442 
1443     void moveItemToTrashResult(KJob *job) const
1444     {
1445         if (job->error()) {
1446             KMessageBox::error(parentWidget,
1447                                contextText(StandardActionManager::MoveItemsToTrash, StandardActionManager::ErrorMessageText, job->errorString()),
1448                                contextText(StandardActionManager::MoveItemsToTrash, StandardActionManager::ErrorMessageTitle));
1449         }
1450     }
1451 
1452     void itemDeletionResult(KJob *job) const
1453     {
1454         if (job->error()) {
1455             KMessageBox::error(parentWidget,
1456                                contextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageText, job->errorString()),
1457                                contextText(StandardActionManager::DeleteItems, StandardActionManager::ErrorMessageTitle));
1458         }
1459     }
1460 
1461     void resourceCreationResult(KJob *job) const
1462     {
1463         if (job->error()) {
1464             KMessageBox::error(parentWidget,
1465                                contextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageText, job->errorString()),
1466                                contextText(StandardActionManager::CreateResource, StandardActionManager::ErrorMessageTitle));
1467         }
1468     }
1469 
1470     void pasteResult(KJob *job) const
1471     {
1472         if (job->error()) {
1473             KMessageBox::error(parentWidget,
1474                                contextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageText, job->errorString()),
1475                                contextText(StandardActionManager::Paste, StandardActionManager::ErrorMessageTitle));
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;
1485         QSet<QString> mimeTypes;
1486 
1487         const bool isItemAction = (type == StandardActionManager::CopyItemToMenu || type == StandardActionManager::MoveItemToMenu);
1488         const bool isCollectionAction = (type == StandardActionManager::CopyCollectionToMenu || type == StandardActionManager::MoveCollectionToMenu);
1489 
1490         if (isItemAction) {
1491             list = safeSelectedRows(itemSelectionModel);
1492             mimeTypes.reserve(list.count());
1493             for (const QModelIndex &index : std::as_const(list)) {
1494                 mimeTypes << index.data(EntityTreeModel::MimeTypeRole).toString();
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 
1521         const bool isItemAction = (type == StandardActionManager::CopyItemToMenu || type == StandardActionManager::MoveItemToMenu);
1522         const bool isCollectionAction = (type == StandardActionManager::CopyCollectionToMenu || type == StandardActionManager::MoveCollectionToMenu);
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,
1546                          StandardActionManager::Type type,
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);
1575                 const bool moveAction = (type == StandardActionManager::MoveCollectionToMenu || type == StandardActionManager::MoveItemToMenu);
1576                 popup->setObjectName(QLatin1StringView("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 
1671     QString contextText(StandardActionManager::Type type, StandardActionManager::TextContext context) const
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     QList<QAction *> actions;
1711     QHash<StandardActionManager::Type, KLocalizedString> pluralLabels;
1712     QHash<StandardActionManager::Type, KLocalizedString> pluralIconLabels;
1713     QTimer mDelayedUpdateTimer;
1714 
1715     struct ContextTextEntry {
1716         QString text;
1717         KLocalizedString localizedText;
1718         bool isLocalized;
1719     };
1720 
1721     using ContextTexts = QHash<StandardActionManager::TextContext, ContextTextEntry>;
1722     QHash<StandardActionManager::Type, ContextTexts> contextTexts;
1723 
1724     ActionStateManager mActionStateManager;
1725 
1726     QStringList mMimeTypeFilter;
1727     QStringList mCapabilityFilter;
1728     QStringList mCollectionPropertiesPageNames;
1729     QMap<StandardActionManager::Type, QPointer<RecentCollectionAction>> mRecentCollectionsMenu;
1730 };
1731 
1732 /// @endcond
1733 
1734 StandardActionManager::StandardActionManager(KActionCollection *actionCollection, QWidget *parent)
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 
1748 StandardActionManager::~StandardActionManager() = default;
1749 
1750 void StandardActionManager::setCollectionSelectionModel(QItemSelectionModel *selectionModel)
1751 {
1752     d->collectionSelectionModel = selectionModel;
1753     connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() {
1754         d->collectionSelectionChanged();
1755     });
1756 
1757     d->checkModelsConsistency();
1758 }
1759 
1760 void StandardActionManager::setItemSelectionModel(QItemSelectionModel *selectionModel)
1761 {
1762     d->itemSelectionModel = selectionModel;
1763     connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() {
1764         d->delayedUpdateActions();
1765     });
1766 }
1767 
1768 void StandardActionManager::setFavoriteCollectionsModel(FavoriteCollectionsModel *favoritesModel)
1769 {
1770     d->favoritesModel = favoritesModel;
1771     d->checkModelsConsistency();
1772 }
1773 
1774 void StandardActionManager::setFavoriteSelectionModel(QItemSelectionModel *selectionModel)
1775 {
1776     d->favoriteSelectionModel = selectionModel;
1777     connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [this]() {
1778         d->favoriteSelectionChanged();
1779     });
1780     d->checkModelsConsistency();
1781 }
1782 
1783 QAction *StandardActionManager::createAction(Type type)
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     } else if (!standardActionData[type].label.isEmpty()) {
1813         action->setText(standardActionData[type].label.toString());
1814     }
1815     if (d->pluralIconLabels.contains(type) && !d->pluralIconLabels.value(type).isEmpty()) {
1816         action->setIconText(d->pluralIconLabels.value(type).subs(1).toString());
1817     } else if (!standardActionData[type].iconLabel.isEmpty()) {
1818         action->setIconText(standardActionData[type].iconLabel.toString());
1819     }
1820 
1821     if (standardActionData[type].icon) {
1822         action->setIcon(standardActionDataIcon(standardActionData[type]));
1823     }
1824     if (d->actionCollection) {
1825         d->actionCollection->setDefaultShortcut(action, QKeySequence(standardActionData[type].shortcut));
1826     } else {
1827         action->setShortcut(standardActionData[type].shortcut);
1828     }
1829 
1830     if (standardActionData[type].slot) {
1831         switch (standardActionData[type].actionType) {
1832         case NormalAction:
1833         case ActionWithAlternative:
1834             connect(action, SIGNAL(triggered()), standardActionData[type].slot); // clazy:exclude=old-style-connect
1835             break;
1836         case MenuAction: {
1837             auto actionMenu = qobject_cast<KActionMenu *>(action);
1838             connect(actionMenu->menu(), SIGNAL(triggered(QAction *)), standardActionData[type].slot); // clazy:exclude=old-style-connect
1839             break;
1840         }
1841         case ToggleAction: {
1842             connect(action, SIGNAL(triggered(bool)), standardActionData[type].slot); // clazy:exclude=old-style-connect
1843             break;
1844         }
1845         case ActionAlternative:
1846             Q_ASSERT(0);
1847         }
1848     }
1849 
1850     if (type == ToggleWorkOffline) {
1851         // inititalize the action state with information from config file
1852         disconnect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot); // clazy:exclude=old-style-connect
1853         action->setChecked(workOffline());
1854         connect(action, SIGNAL(triggered(bool)), this, standardActionData[type].slot); // clazy:exclude=old-style-connect
1855 
1856         // TODO: find a way to check for updates to the config file
1857     }
1858 
1859     Q_ASSERT(standardActionData[type].name);
1860     Q_ASSERT(d->actionCollection);
1861     d->actionCollection->addAction(QString::fromLatin1(standardActionData[type].name), action);
1862     d->actions[type] = action;
1863     if ((standardActionData[type].actionType == ActionWithAlternative) && (standardActionData[type + 1].actionType == ActionAlternative)) {
1864         createAction(static_cast<Type>(type + 1)); // ensure that alternative actions are initialized when not created by createAllActions
1865     }
1866     d->updateActions();
1867     return action;
1868 }
1869 
1870 void StandardActionManager::createAllActions()
1871 {
1872     for (uint i = 0; i < LastType; ++i) {
1873         createAction(static_cast<Type>(i));
1874     }
1875 }
1876 
1877 QAction *StandardActionManager::action(Type type) const
1878 {
1879     Q_ASSERT(type < LastType);
1880     return d->actions[type];
1881 }
1882 
1883 void StandardActionManager::setActionText(Type type, const KLocalizedString &text)
1884 {
1885     Q_ASSERT(type < LastType);
1886     d->pluralLabels.insert(type, text);
1887     d->updateActions();
1888 }
1889 
1890 void StandardActionManager::interceptAction(Type type, bool intercept)
1891 {
1892     Q_ASSERT(type < LastType);
1893 
1894     const QAction *action = d->actions[type];
1895 
1896     if (!action) {
1897         return;
1898     }
1899 
1900     if (intercept) {
1901         disconnect(action, SIGNAL(triggered()), this, standardActionData[type].slot); // clazy:exclude=old-style-connect
1902     } else {
1903         connect(action, SIGNAL(triggered()), standardActionData[type].slot); // clazy:exclude=old-style-connect
1904     }
1905 }
1906 
1907 Akonadi::Collection::List StandardActionManager::selectedCollections() const
1908 {
1909     Collection::List collections;
1910 
1911     if (!d->collectionSelectionModel) {
1912         return collections;
1913     }
1914 
1915     const QModelIndexList lst = safeSelectedRows(d->collectionSelectionModel);
1916     for (const QModelIndex &index : lst) {
1917         const auto collection = index.data(EntityTreeModel::CollectionRole).value<Collection>();
1918         if (collection.isValid()) {
1919             collections << collection;
1920         }
1921     }
1922 
1923     return collections;
1924 }
1925 
1926 Item::List StandardActionManager::selectedItems() const
1927 {
1928     Item::List items;
1929 
1930     if (!d->itemSelectionModel) {
1931         return items;
1932     }
1933     const QModelIndexList lst = safeSelectedRows(d->itemSelectionModel);
1934     for (const QModelIndex &index : lst) {
1935         const Item item = index.data(EntityTreeModel::ItemRole).value<Item>();
1936         if (item.isValid()) {
1937             items << item;
1938         }
1939     }
1940 
1941     return items;
1942 }
1943 
1944 void StandardActionManager::setContextText(Type type, TextContext context, const QString &text)
1945 {
1946     d->setContextText(type, context, text);
1947 }
1948 
1949 void StandardActionManager::setContextText(Type type, TextContext context, const KLocalizedString &text)
1950 {
1951     d->setContextText(type, context, text);
1952 }
1953 
1954 void StandardActionManager::setMimeTypeFilter(const QStringList &mimeTypes)
1955 {
1956     d->mMimeTypeFilter = mimeTypes;
1957 }
1958 
1959 void StandardActionManager::setCapabilityFilter(const QStringList &capabilities)
1960 {
1961     d->mCapabilityFilter = capabilities;
1962 }
1963 
1964 void StandardActionManager::setCollectionPropertiesPageNames(const QStringList &names)
1965 {
1966     d->mCollectionPropertiesPageNames = names;
1967 }
1968 
1969 void StandardActionManager::createActionFolderMenu(QMenu *menu, Type type)
1970 {
1971     d->createActionFolderMenu(menu, type);
1972 }
1973 
1974 void StandardActionManager::addRecentCollection(Akonadi::Collection::Id id) const
1975 {
1976     RecentCollectionAction::addRecentCollection(id);
1977 }
1978 
1979 #include "moc_standardactionmanager.cpp"