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"