1 /*
2   This file is part of KMail, the KDE mail client.
3   SPDX-FileCopyrightText: 2002 Don Sanders <sanders@kde.org>
4   SPDX-FileCopyrightText: 2009-2021 Laurent Montel <montel@kde.org>
5 
6   Based on the work of Stefan Taferner <taferner@kde.org>
7 
8   SPDX-License-Identifier: GPL-2.0-only
9 */
10 
11 // KMail includes
12 #include "kmmainwidget.h"
13 #include "editor/composer.h"
14 #include "job/composenewmessagejob.h"
15 #include "kmcommands.h"
16 #include "kmmainwin.h"
17 #include "kmreadermainwin.h"
18 #include "searchdialog/searchwindow.h"
19 #include "undostack.h"
20 #include "util.h"
21 #include "widgets/vacationscriptindicatorwidget.h"
22 #include "widgets/zoomlabelwidget.h"
23 #include <MailCommon/FolderSelectionDialog>
24 #include <PimCommonAkonadi/MailUtil>
25 #include <TemplateParser/CustomTemplatesMenu>
26 
27 #include "dialog/archivefolderdialog.h"
28 #include "foldershortcutactionmanager.h"
29 #include "job/markallmessagesasreadinfolderandsubfolderjob.h"
30 #include "job/removeduplicatemessageinfolderandsubfolderjob.h"
31 #include "manageshowcollectionproperties.h"
32 #include "plugininterface/kmailplugininterface.h"
33 #include "settings/kmailsettings.h"
34 #include "sieveimapinterface/kmsieveimappasswordprovider.h"
35 #include "tag/tagactionmanager.h"
36 #include "widgets/collectionpane.h"
37 #include "widgets/kactionmenuaccount.h"
38 #include "widgets/kactionmenutransport.h"
39 #include <KPIMTextEdit/TextToSpeech>
40 #include <KSieveUi/SieveDebugDialog>
41 #include <MailCommon/FolderTreeView>
42 #include <MailCommon/MailKernel>
43 #include <MailCommon/MailUtil>
44 #include <MailCommon/SearchRuleStatus>
45 
46 #include "collectionpage/collectionmailinglistpage.h"
47 #include "collectionpage/collectionquotapage.h"
48 #include "collectionpage/collectionshortcutpage.h"
49 #include "collectionpage/collectiontemplatespage.h"
50 #include "collectionpage/collectionviewpage.h"
51 #include "folderarchive/folderarchivemanager.h"
52 #include "folderarchive/folderarchiveutil.h"
53 #include "job/createnewcontactjob.h"
54 #include "tag/tagselectdialog.h"
55 #include <Akonadi/CollectionMaintenancePage>
56 
57 #include "mailcommonsettings_base.h"
58 #include <Akonadi/AgentConfigurationDialog>
59 #include <MailCommon/CollectionExpiryPage>
60 #include <MailCommon/CollectionGeneralPage>
61 #include <MailCommon/ExpireCollectionAttribute>
62 #include <MailCommon/FavoriteCollectionOrderProxyModel>
63 #include <MailCommon/FavoriteCollectionWidget>
64 #include <MailCommon/FilterManager>
65 #include <MailCommon/MailFilter>
66 #include <PimCommon/PimUtil>
67 #include <PimCommonAkonadi/CollectionAclPage>
68 
69 #include "kmail-version.h"
70 
71 #include "messageviewer/config-messageviewer.h"
72 #include <MessageViewer/HeaderStyle>
73 #include <MessageViewer/HeaderStylePlugin>
74 #include <MessageViewer/MessageViewerSettings>
75 #include <MessageViewer/Viewer>
76 
77 #include <MessageViewer/AttachmentStrategy>
78 #include <MessageViewer/MessageViewerCheckBeforeDeletingPluginManager>
79 
80 #include <KCursorSaver>
81 
82 #include <MessageComposer/MessageHelper>
83 #include <MessageComposer/MessageSender>
84 
85 #include <MessageCore/MailingList>
86 #include <MessageCore/MessageCoreSettings>
87 
88 #include "dialog/kmknotify.h"
89 #include "widgets/displaymessageformatactionmenu.h"
90 
91 #include "kmlaunchexternalcomponent.h"
92 #include <KSieveUi/VacationManager>
93 
94 #include <Libkdepim/ProgressManager>
95 #include <PimCommon/BroadcastStatus>
96 
97 #include <Akonadi/AgentInstance>
98 #include <Akonadi/AgentManager>
99 #include <Akonadi/AgentType>
100 #include <Akonadi/AttributeFactory>
101 #include <Akonadi/CachePolicy>
102 #include <Akonadi/ChangeRecorder>
103 #include <Akonadi/CollectionAttributesSynchronizationJob>
104 #include <Akonadi/CollectionDialog>
105 #include <Akonadi/CollectionFetchJob>
106 #include <Akonadi/CollectionFetchScope>
107 #include <Akonadi/CollectionPropertiesDialog>
108 #include <Akonadi/CollectionStatistics>
109 #include <Akonadi/Contact/ContactSearchJob>
110 #include <Akonadi/ControlGui>
111 #include <Akonadi/ETMViewStateSaver>
112 #include <Akonadi/EntityDisplayAttribute>
113 #include <Akonadi/EntityListView>
114 #include <Akonadi/EntityMimeTypeFilterModel>
115 #include <Akonadi/EntityTreeModel>
116 #include <Akonadi/FavoriteCollectionsModel>
117 #include <Akonadi/ItemFetchJob>
118 #include <Akonadi/ItemFetchScope>
119 #include <Akonadi/ItemModifyJob>
120 #include <Akonadi/KMime/MessageFlags>
121 #include <Akonadi/Session>
122 #include <Akonadi/StandardActionManager>
123 
124 #include <KEmailAddress>
125 #include <KIdentityManagement/Identity>
126 #include <KIdentityManagement/IdentityManager>
127 #include <KMime/HeaderParsing>
128 #include <KMime/KMimeMessage>
129 #include <KMime/MDN>
130 #include <KSieveUi/ManageSieveScriptsDialog>
131 #include <KSieveUi/Util>
132 #include <MailTransport/Transport>
133 #include <MailTransport/TransportManager>
134 
135 #include <PimCommon/LogActivitiesManager>
136 
137 #include "kmail_debug.h"
138 
139 #include <KAcceleratorManager>
140 #include <KActionMenu>
141 #include <KCharsets>
142 #include <KMessageBox>
143 #include <KStandardShortcut>
144 #include <KWindowSystem>
145 
146 #include <KConfigGroup>
147 #include <KNotification>
148 #include <KRecentFilesMenu>
149 #include <KStandardAction>
150 #include <KStringHandler>
151 #include <KToggleAction>
152 #include <KXMLGUIFactory>
153 
154 #include <QAction>
155 #include <QByteArray>
156 #include <QHeaderView>
157 #include <QList>
158 #include <QMenu>
159 #include <QProcess>
160 #include <QSplitter>
161 #include <QStatusBar>
162 #include <QVBoxLayout>
163 #include <WebEngineViewer/WebHitTestResult>
164 
165 #include <QDBusConnection>
166 #include <QDBusInterface>
167 #include <QDBusReply>
168 #include <QStandardPaths>
169 
170 #include <PimCommonAkonadi/ManageServerSideSubscriptionJob>
171 #include <job/removecollectionjob.h>
172 #include <job/removeduplicatemailjob.h>
173 
174 #include <MessageViewer/DKIMViewerMenu>
175 #include <MessageViewer/DKIMWidgetInfo>
176 #include <MessageViewer/RemoteContentMenu>
177 
178 #include "historyswitchfolder/collectionswitchertreeviewmanager.h"
179 #include "plugininterface/kmailplugincheckbeforedeletingmanagerinterface.h"
180 
181 #ifdef WITH_KUSERFEEDBACK
182 #include <KUserFeedback/NotificationPopup>
183 #include <KUserFeedback/Provider>
184 #endif
185 
186 using namespace std::chrono_literals;
187 #include <chrono>
188 
189 using namespace KMime;
190 using namespace Akonadi;
191 using namespace MailCommon;
192 using KMail::SearchWindow;
193 using KMime::Types::AddrSpecList;
194 using KPIM::ProgressManager;
195 using MessageViewer::AttachmentStrategy;
196 using PimCommon::BroadcastStatus;
197 
198 static KMMainWidget *myMainWidget = nullptr;
199 //-----------------------------------------------------------------------------
KMMainWidget(QWidget * parent,KXMLGUIClient * aGUIClient,KActionCollection * actionCollection,const KSharedConfig::Ptr & config)200 KMMainWidget::KMMainWidget(QWidget *parent, KXMLGUIClient *aGUIClient, KActionCollection *actionCollection, const KSharedConfig::Ptr &config)
201     : QWidget(parent)
202     , mLaunchExternalComponent(new KMLaunchExternalComponent(this, this))
203     , mManageShowCollectionProperties(new ManageShowCollectionProperties(this, this))
204     , mCollectionSwitcherTreeViewManager(new CollectionSwitcherTreeViewManager(this))
205 {
206     // must be the first line of the constructor:
207     mStartupDone = false;
208     mWasEverShown = false;
209     mReaderWindowActive = true;
210     mReaderWindowBelow = true;
211     mFolderHtmlLoadExtPreference = false;
212     mDestructed = false;
213     mActionCollection = actionCollection;
214     mTopLayout = new QVBoxLayout(this);
215     mTopLayout->setContentsMargins({});
216     mConfig = config;
217     mGUIClient = aGUIClient;
218     mFolderTreeWidget = nullptr;
219     Akonadi::ControlGui::widgetNeedsAkonadi(this);
220     mFavoritesModel = nullptr;
221     mSievePasswordProvider = new KMSieveImapPasswordProvider(this);
222     mVacationManager = new KSieveUi::VacationManager(mSievePasswordProvider, this);
223     connect(mVacationManager,
224             &KSieveUi::VacationManager::updateVacationScriptStatus,
225             this,
226             qOverload<bool, const QString &>(&KMMainWidget::updateVacationScriptStatus));
227 
228 #ifdef WITH_KUSERFEEDBACK
229     auto userFeedBackNotificationPopup = new KUserFeedback::NotificationPopup(this);
230     userFeedBackNotificationPopup->setFeedbackProvider(kmkernel->userFeedbackProvider());
231 #endif
232 
233     mToolbarActionSeparator = new QAction(this);
234     mToolbarActionSeparator->setSeparator(true);
235 
236     KMailPluginInterface::self()->setActionCollection(mActionCollection);
237     KMailPluginInterface::self()->initializePlugins();
238     KMailPluginInterface::self()->setMainWidget(this);
239     mPluginCheckBeforeDeletingManagerInterface = new KMailPluginCheckBeforeDeletingManagerInterface(this);
240     mPluginCheckBeforeDeletingManagerInterface->setParentWidget(this);
241     mPluginCheckBeforeDeletingManagerInterface->setActionCollection(mActionCollection);
242     mPluginCheckBeforeDeletingManagerInterface->initializePlugins();
243 
244     myMainWidget = this;
245 
246     readPreConfig();
247     createWidgets();
248     setupActions();
249 
250     readConfig();
251 
252     if (!kmkernel->isOffline()) { // kmail is set to online mode, make sure the agents are also online
253         kmkernel->setAccountStatus(true);
254     }
255 
256     QTimer::singleShot(0, this, &KMMainWidget::slotShowStartupFolder);
257 
258     connect(kmkernel, &KMKernel::startCheckMail, this, &KMMainWidget::slotStartCheckMail);
259 
260     connect(kmkernel, &KMKernel::endCheckMail, this, &KMMainWidget::slotEndCheckMail);
261 
262     connect(kmkernel, &KMKernel::configChanged, this, &KMMainWidget::slotConfigChanged);
263 
264     connect(kmkernel, &KMKernel::onlineStatusChanged, this, &KMMainWidget::slotUpdateOnlineStatus);
265 
266     connect(mTagActionManager, &KMail::TagActionManager::tagActionTriggered, this, &KMMainWidget::slotUpdateMessageTagList);
267 
268     connect(mTagActionManager, &KMail::TagActionManager::tagMoreActionClicked, this, &KMMainWidget::slotSelectMoreMessageTagList);
269 
270     kmkernel->toggleSystemTray();
271 
272     {
273         // make sure the pages are registered only once, since there can be multiple instances of KMMainWidget
274         static bool pagesRegistered = false;
275 
276         if (!pagesRegistered) {
277             Akonadi::CollectionPropertiesDialog::registerPage(new PimCommon::CollectionAclPageFactory);
278             Akonadi::CollectionPropertiesDialog::registerPage(new MailCommon::CollectionGeneralPageFactory);
279             Akonadi::CollectionPropertiesDialog::registerPage(new CollectionMaintenancePageFactory);
280             Akonadi::CollectionPropertiesDialog::registerPage(new CollectionQuotaPageFactory);
281             Akonadi::CollectionPropertiesDialog::registerPage(new CollectionTemplatesPageFactory);
282             Akonadi::CollectionPropertiesDialog::registerPage(new MailCommon::CollectionExpiryPageFactory);
283             Akonadi::CollectionPropertiesDialog::registerPage(new CollectionViewPageFactory);
284             Akonadi::CollectionPropertiesDialog::registerPage(new CollectionMailingListPageFactory);
285             Akonadi::CollectionPropertiesDialog::registerPage(new CollectionShortcutPageFactory);
286 
287             pagesRegistered = true;
288         }
289     }
290 
291     auto mainWin = qobject_cast<KMainWindow *>(window());
292     mCurrentStatusBar = mainWin ? mainWin->statusBar() : nullptr;
293     mVacationScriptIndicator = new KMail::VacationScriptIndicatorWidget(mCurrentStatusBar);
294     mVacationScriptIndicator->hide();
295 
296     mZoomLabelIndicator = new ZoomLabelWidget(mCurrentStatusBar);
297     if (mMsgView) {
298         setZoomChanged(mMsgView->viewer()->webViewZoomFactor());
299     }
300     connect(mVacationScriptIndicator, &KMail::VacationScriptIndicatorWidget::clicked, this, &KMMainWidget::slotEditVacation);
301     if (KSieveUi::Util::checkOutOfOfficeOnStartup()) {
302         QTimer::singleShot(0, this, &KMMainWidget::slotCheckVacation);
303     }
304 
305     connect(mFolderTreeWidget->folderTreeView()->model(), &QAbstractItemModel::modelReset, this, &KMMainWidget::restoreCollectionFolderViewConfig);
306     restoreCollectionFolderViewConfig();
307 
308     if (kmkernel->firstStart()) {
309         const QStringList listOfMailerFound = MailCommon::Util::foundMailer();
310         if (!listOfMailerFound.isEmpty()) {
311             const int answer = KMessageBox::questionYesNoList(this,
312                                                               i18n("Another mailer was found on system. Do you want to import data from it?"),
313                                                               listOfMailerFound,
314                                                               QString(),
315                                                               KGuiItem(i18nc("@action:button", "Import"), QStringLiteral("document-import")),
316                                                               KGuiItem(i18nc("@action:button", "Do Not Import"), QStringLiteral("dialog-cancel")));
317             if (answer == KMessageBox::Yes) {
318                 const QString path = QStandardPaths::findExecutable(QStringLiteral("akonadiimportwizard"));
319                 if (path.isEmpty() || !QProcess::startDetached(path, QStringList())) {
320                     KMessageBox::error(this,
321                                        i18n("Could not start the import wizard. "
322                                             "Please check your installation."),
323                                        i18n("Unable to start import wizard"));
324                 }
325             } else {
326                 mLaunchExternalComponent->slotAccountWizard();
327             }
328         } else {
329             mLaunchExternalComponent->slotAccountWizard();
330         }
331     }
332     // must be the last line of the constructor:
333     mStartupDone = true;
334 
335     mCheckMailTimer.setInterval(3s);
336     mCheckMailTimer.setSingleShot(true);
337     connect(&mCheckMailTimer, &QTimer::timeout, this, &KMMainWidget::slotUpdateActionsAfterMailChecking);
338 
339     setupUnifiedMailboxChecker();
340     mCollectionSwitcherTreeViewManager->setParentWidget(this);
341     connect(mCollectionSwitcherTreeViewManager, &CollectionSwitcherTreeViewManager::switchToFolder, this, &KMMainWidget::slotHistorySwitchFolder);
342 }
343 
dkimWidgetInfo() const344 QWidget *KMMainWidget::dkimWidgetInfo() const
345 {
346     if (mMsgView) {
347         return mMsgView->viewer()->dkimWidgetInfo();
348     }
349     return nullptr;
350 }
351 
restoreCollectionFolderViewConfig()352 void KMMainWidget::restoreCollectionFolderViewConfig()
353 {
354     auto saver = new ETMViewStateSaver;
355     saver->setView(mFolderTreeWidget->folderTreeView());
356     const KConfigGroup cfg(KMKernel::self()->config(), "CollectionFolderView");
357     mFolderTreeWidget->restoreHeaderState(cfg.readEntry("HeaderState", QByteArray()));
358     saver->restoreState(cfg);
359     // Restore startup folder
360 
361     Akonadi::Collection::Id id = -1;
362     if (mCurrentCollection.isValid()) {
363         id = mCurrentCollection.id();
364     }
365 
366     if (id == -1) {
367         if (KMailSettings::self()->startSpecificFolderAtStartup()) {
368             Akonadi::Collection::Id startupFolder = KMailSettings::self()->startupFolder();
369             if (startupFolder > 0) {
370                 saver->restoreCurrentItem(QStringLiteral("c%1").arg(startupFolder));
371             }
372         }
373     } else {
374         saver->restoreCurrentItem(QStringLiteral("c%1").arg(id));
375     }
376 }
377 
378 //-----------------------------------------------------------------------------
379 // The kernel may have already been deleted when this method is called,
380 // perform all cleanup that requires the kernel in destruct()
~KMMainWidget()381 KMMainWidget::~KMMainWidget()
382 {
383     myMainWidget = nullptr;
384     qDeleteAll(mFilterCommands);
385     destruct();
386 }
387 
388 //-----------------------------------------------------------------------------
389 // This method performs all cleanup that requires the kernel to exist.
destruct()390 void KMMainWidget::destruct()
391 {
392     if (mDestructed) {
393         return;
394     }
395     if (mSearchWin) {
396         mSearchWin->close();
397     }
398     disconnect(mFolderTreeWidget->folderTreeView()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KMMainWidget::updateFolderMenu);
399     writeConfig(false); /* don't force kmkernel sync when close BUG: 289287 */
400     writeFolderConfig();
401     deleteWidgets();
402     clearCurrentFolder();
403     delete mMoveOrCopyToDialog;
404     delete mSelectFromAllFoldersDialog;
405     delete mSievePasswordProvider;
406 
407     // clang-format off
408     disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)), this, nullptr);
409     disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), this, nullptr);
410     disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(collectionChanged(Akonadi::Collection,QSet<QByteArray>)), this, nullptr);
411     disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)), this, nullptr);
412     // clang-format on
413     disconnect(kmkernel->folderCollectionMonitor(), SIGNAL(itemRemoved(Akonadi::Item)), this, nullptr);
414 
415     mDestructed = true;
416 }
417 
clearCurrentFolder()418 void KMMainWidget::clearCurrentFolder()
419 {
420     mCurrentFolderSettings.clear();
421     mCurrentCollection = Akonadi::Collection();
422 }
423 
slotStartCheckMail()424 void KMMainWidget::slotStartCheckMail()
425 {
426     if (mCheckMailTimer.isActive()) {
427         mCheckMailTimer.stop();
428     }
429 }
430 
slotEndCheckMail()431 void KMMainWidget::slotEndCheckMail()
432 {
433     if (!mCheckMailTimer.isActive()) {
434         mCheckMailTimer.start();
435     }
436 }
437 
slotUpdateActionsAfterMailChecking()438 void KMMainWidget::slotUpdateActionsAfterMailChecking()
439 {
440     const bool sendOnAll = KMailSettings::self()->sendOnCheck() == KMailSettings::EnumSendOnCheck::SendOnAllChecks;
441     const bool sendOnManual = KMailSettings::self()->sendOnCheck() == KMailSettings::EnumSendOnCheck::SendOnManualChecks;
442     if (!kmkernel->isOffline() && (sendOnAll || sendOnManual)) {
443         slotSendQueued();
444     }
445     // update folder menus in case some mail got filtered to trash/current folder
446     // and we can enable "empty trash/move all to trash" action etc.
447     updateFolderMenu();
448 }
449 
setCurrentCollection(const Akonadi::Collection & col)450 void KMMainWidget::setCurrentCollection(const Akonadi::Collection &col)
451 {
452     mCurrentCollection = col;
453     if (mCurrentFolderSettings) {
454         mCurrentFolderSettings->setCollection(col);
455     }
456 }
457 
slotCollectionFetched(int collectionId)458 void KMMainWidget::slotCollectionFetched(int collectionId)
459 {
460     // Called when a collection is fetched for the first time by the ETM.
461     // This is the right time to update the caption (which still says "Loading...")
462     // and to update the actions that depend on the number of mails in the folder.
463     if (collectionId == mCurrentCollection.id()) {
464         setCurrentCollection(CommonKernel->collectionFromId(mCurrentCollection.id()));
465         updateMessageActions();
466         updateFolderMenu();
467     }
468     // We call this for any collection, it could be one of our parents...
469     if (mCurrentCollection.isValid()) {
470         Q_EMIT captionChangeRequest(fullCollectionPath());
471     }
472 }
473 
fullCollectionPath() const474 QString KMMainWidget::fullCollectionPath() const
475 {
476     if (mCurrentCollection.isValid()) {
477         return MailCommon::Util::fullCollectionPath(mCurrentCollection);
478     }
479     return {};
480 }
481 
482 // Connected to the currentChanged signals from the folderTreeView and favorites view.
slotFolderChanged(const Akonadi::Collection & collection)483 void KMMainWidget::slotFolderChanged(const Akonadi::Collection &collection)
484 {
485     if (mCurrentCollection == collection) {
486         return;
487     }
488     mCollectionSwitcherTreeViewManager->addHistory(collection, MailCommon::Util::fullCollectionPath(collection));
489     slotHistorySwitchFolder(collection);
490 }
491 
slotHistorySwitchFolder(const Akonadi::Collection & collection)492 void KMMainWidget::slotHistorySwitchFolder(const Akonadi::Collection &collection)
493 {
494     if (mCurrentCollection == collection) {
495         return;
496     }
497     if (mFolderTreeWidget) {
498         mFolderTreeWidget->selectCollectionFolder(collection, false); // Don't expand treewidget
499     }
500     // Store previous collection
501     if (mGoToFirstUnreadMessageInSelectedFolder) {
502         // the default action has been overridden from outside
503         mPreSelectionMode = MessageList::Core::PreSelectFirstUnreadCentered;
504     } else {
505         // use the default action
506         switch (KMailSettings::self()->actionEnterFolder()) {
507         case KMailSettings::EnumActionEnterFolder::SelectFirstUnread:
508             mPreSelectionMode = MessageList::Core::PreSelectFirstUnreadCentered;
509             break;
510         case KMailSettings::EnumActionEnterFolder::SelectLastSelected:
511             mPreSelectionMode = MessageList::Core::PreSelectLastSelected;
512             break;
513         case KMailSettings::EnumActionEnterFolder::SelectNewest:
514             mPreSelectionMode = MessageList::Core::PreSelectNewestCentered;
515             break;
516         case KMailSettings::EnumActionEnterFolder::SelectOldest:
517             mPreSelectionMode = MessageList::Core::PreSelectOldestCentered;
518             break;
519         default:
520             mPreSelectionMode = MessageList::Core::PreSelectNone;
521             break;
522         }
523     }
524 
525     mGoToFirstUnreadMessageInSelectedFolder = false;
526     KCursorSaver saver(Qt::WaitCursor);
527 
528     if (mMsgView) {
529         mMsgView->clear(true);
530     }
531     const bool newFolder = mCurrentCollection != collection;
532 
533     // Delete any pending timer, if needed it will be recreated below
534     delete mShowBusySplashTimer;
535     mShowBusySplashTimer = nullptr;
536     if (newFolder) {
537         // We're changing folder: write configuration for the old one
538         writeFolderConfig();
539     }
540 
541     mCurrentFolderSettings = FolderSettings::forCollection(collection);
542     mCurrentCollection = collection;
543 
544     readFolderConfig();
545     if (mMsgView) {
546         assignLoadExternalReference();
547     }
548 
549     if (!mCurrentFolderSettings->isValid() && (mMessagePane->count() < 2)) {
550         slotIntro();
551     } else {
552         if (mMessagePane->isHidden()) {
553             mMessagePane->show();
554         }
555     }
556 
557     // The message pane uses the selection model of the folder view to load the correct aggregation model and theme
558     //  settings. At this point the selection model hasn't been updated yet to the user's new choice, so it would load
559     //  the old folder settings instead.
560     QTimer::singleShot(0, this, &KMMainWidget::slotShowSelectedFolderInPane);
561     if (collection.cachePolicy().syncOnDemand()) {
562         AgentManager::self()->synchronizeCollection(collection, false);
563     }
564     mMsgActions->setCurrentMessage(Akonadi::Item());
565     Q_EMIT captionChangeRequest(MailCommon::Util::fullCollectionPath(collection));
566 }
567 
slotShowSelectedFolderInPane()568 void KMMainWidget::slotShowSelectedFolderInPane()
569 {
570     if (mCurrentCollection.isValid()) {
571         const QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(KMKernel::self()->entityTreeModel(), mCurrentCollection);
572         if (idx.isValid()) {
573             mMessagePane->setCurrentFolder(mCurrentCollection, idx, false, mPreSelectionMode);
574         }
575     }
576     updateMessageActions();
577     updateFolderMenu();
578 }
579 
580 //-----------------------------------------------------------------------------
readPreConfig()581 void KMMainWidget::readPreConfig()
582 {
583     mLongFolderList = KMailSettings::self()->folderList() == KMailSettings::EnumFolderList::longlist;
584     mReaderWindowActive = KMailSettings::self()->readerWindowMode() != KMailSettings::EnumReaderWindowMode::hide;
585     mReaderWindowBelow = KMailSettings::self()->readerWindowMode() == KMailSettings::EnumReaderWindowMode::below;
586 
587     mHtmlGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlMail();
588     mHtmlLoadExtGlobalSetting = MessageViewer::MessageViewerSettings::self()->htmlLoadExternal();
589 
590     mEnableFavoriteFolderView =
591         (KMKernel::self()->mailCommonSettings()->favoriteCollectionViewMode() != MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::HiddenMode);
592     mEnableFolderQuickSearch = KMailSettings::self()->enableFolderQuickSearch();
593 }
594 
updateDisplayFormatMessage()595 void KMMainWidget::updateDisplayFormatMessage()
596 {
597     readFolderConfig();
598     updateHtmlMenuEntry();
599     if (mMsgView) {
600         mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference);
601         mMsgView->update(true);
602     }
603 }
604 
605 //-----------------------------------------------------------------------------
readFolderConfig()606 void KMMainWidget::readFolderConfig()
607 {
608     if (!mCurrentCollection.isValid()) {
609         return;
610     }
611     KSharedConfig::Ptr config = KMKernel::self()->config();
612     KConfigGroup group(config, MailCommon::FolderSettings::configGroupName(mCurrentCollection));
613     if (group.hasKey("htmlMailOverride")) {
614         const bool useHtml = group.readEntry("htmlMailOverride", false);
615         mFolderDisplayFormatPreference = useHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text;
616         group.deleteEntry("htmlMailOverride");
617         group.sync();
618     } else {
619         mFolderDisplayFormatPreference = static_cast<MessageViewer::Viewer::DisplayFormatMessage>(
620             group.readEntry("displayFormatOverride", static_cast<int>(MessageViewer::Viewer::UseGlobalSetting)));
621     }
622     mFolderHtmlLoadExtPreference = group.readEntry("htmlLoadExternalOverride", false);
623 }
624 
625 //-----------------------------------------------------------------------------
writeFolderConfig()626 void KMMainWidget::writeFolderConfig()
627 {
628     if (mCurrentFolderSettings) {
629         mCurrentFolderSettings->setFolderHtmlLoadExtPreference(mFolderHtmlLoadExtPreference);
630         mCurrentFolderSettings->setFormatMessage(mFolderDisplayFormatPreference);
631         mCurrentFolderSettings->writeConfig();
632     }
633 }
634 
635 //-----------------------------------------------------------------------------
layoutSplitters()636 void KMMainWidget::layoutSplitters()
637 {
638     // This function can only be called when the old splitters are already deleted
639     Q_ASSERT(!mSplitter1);
640     Q_ASSERT(!mSplitter2);
641 
642     // For some reason, this is necessary here so that the copy action still
643     // works after changing the folder layout.
644     if (mMsgView) {
645         disconnect(mMsgView->copyAction(), &QAction::triggered, mMsgView, &KMReaderWin::slotCopySelectedText);
646     }
647 
648     // If long folder list is enabled, the splitters are:
649     // Splitter 1: FolderView vs (HeaderAndSearch vs MessageViewer)
650     // Splitter 2: HeaderAndSearch vs MessageViewer
651     //
652     // If long folder list is disabled, the splitters are:
653     // Splitter 1: (FolderView vs HeaderAndSearch) vs MessageViewer
654     // Splitter 2: FolderView vs HeaderAndSearch
655 
656     // The folder view is both the folder tree and the favorite folder view, if
657     // enabled
658 
659     const bool readerWindowAtSide = !mReaderWindowBelow && mReaderWindowActive;
660     const bool readerWindowBelow = mReaderWindowBelow && mReaderWindowActive;
661 
662     mSplitter1 = new QSplitter(this);
663     mSplitter2 = new QSplitter(mSplitter1);
664 
665     QWidget *folderTreeWidget = mFolderTreeWidget;
666     if (mFavoriteCollectionsView) {
667         mFolderViewSplitter = new QSplitter(Qt::Vertical);
668         mFolderViewSplitter->setChildrenCollapsible(false);
669         mFolderViewSplitter->addWidget(mFavoriteCollectionsView);
670         mFolderViewSplitter->addWidget(mFolderTreeWidget);
671         folderTreeWidget = mFolderViewSplitter;
672     }
673 
674     if (mLongFolderList) {
675         // add folder tree
676         mSplitter1->setOrientation(Qt::Horizontal);
677         mSplitter1->addWidget(folderTreeWidget);
678 
679         // and the rest to the right
680         mSplitter1->addWidget(mSplitter2);
681 
682         // add the message list to the right or below
683         if (readerWindowAtSide) {
684             mSplitter2->setOrientation(Qt::Horizontal);
685         } else {
686             mSplitter2->setOrientation(Qt::Vertical);
687         }
688         mSplitter2->addWidget(mMessagePane);
689 
690         // add the preview window, if there is one
691         if (mMsgView) {
692             mSplitter2->addWidget(mMsgView);
693         }
694     } else { // short folder list
695         if (mReaderWindowBelow) {
696             mSplitter1->setOrientation(Qt::Vertical);
697             mSplitter2->setOrientation(Qt::Horizontal);
698         } else { // at side or none
699             mSplitter1->setOrientation(Qt::Horizontal);
700             mSplitter2->setOrientation(Qt::Vertical);
701         }
702 
703         mSplitter1->addWidget(mSplitter2);
704 
705         // add folder tree
706         mSplitter2->addWidget(folderTreeWidget);
707         // add message list to splitter 2
708         mSplitter2->addWidget(mMessagePane);
709 
710         // add the preview window, if there is one
711         if (mMsgView) {
712             mSplitter1->addWidget(mMsgView);
713         }
714     }
715 
716     //
717     // Set splitter properties
718     //
719     mSplitter1->setObjectName(QStringLiteral("splitter1"));
720     mSplitter1->setChildrenCollapsible(false);
721     mSplitter2->setObjectName(QStringLiteral("splitter2"));
722     mSplitter2->setChildrenCollapsible(false);
723 
724     //
725     // Set the stretch factors
726     //
727     mSplitter1->setStretchFactor(0, 0);
728     mSplitter2->setStretchFactor(0, 0);
729     mSplitter1->setStretchFactor(1, 1);
730     mSplitter2->setStretchFactor(1, 1);
731 
732     if (mFavoriteCollectionsView) {
733         mFolderViewSplitter->setStretchFactor(0, 0);
734         mFolderViewSplitter->setStretchFactor(1, 1);
735     }
736 
737     // Because the reader windows's width increases a tiny bit after each
738     // restart in short folder list mode with message window at side, disable
739     // the stretching as a workaround here
740     if (readerWindowAtSide && !mLongFolderList) {
741         mSplitter1->setStretchFactor(0, 1);
742         mSplitter1->setStretchFactor(1, 0);
743     }
744 
745     //
746     // Set the sizes of the splitters to the values stored in the config
747     //
748     QList<int> splitter1Sizes;
749     QList<int> splitter2Sizes;
750 
751     const int folderViewWidth = KMailSettings::self()->folderViewWidth();
752     int ftHeight = KMailSettings::self()->folderTreeHeight();
753     int headerHeight = KMailSettings::self()->searchAndHeaderHeight();
754     const int messageViewerWidth = KMailSettings::self()->readerWindowWidth();
755     int headerWidth = KMailSettings::self()->searchAndHeaderWidth();
756     int messageViewerHeight = KMailSettings::self()->readerWindowHeight();
757 
758     int ffvHeight = mFolderViewSplitter ? KMKernel::self()->mailCommonSettings()->favoriteCollectionViewHeight() : 0;
759 
760     // If the message viewer was hidden before, make sure it is not zero height
761     if (messageViewerHeight < 10 && readerWindowBelow) {
762         headerHeight /= 2;
763         messageViewerHeight = headerHeight;
764     }
765 
766     if (mLongFolderList) {
767         if (!readerWindowAtSide) {
768             splitter1Sizes << folderViewWidth << headerWidth;
769             splitter2Sizes << headerHeight << messageViewerHeight;
770         } else {
771             splitter1Sizes << folderViewWidth << (headerWidth + messageViewerWidth);
772             splitter2Sizes << headerWidth << messageViewerWidth;
773         }
774     } else {
775         if (!readerWindowAtSide) {
776             splitter1Sizes << headerHeight << messageViewerHeight;
777             splitter2Sizes << folderViewWidth << headerWidth;
778         } else {
779             splitter1Sizes << headerWidth << messageViewerWidth;
780             splitter2Sizes << ftHeight + ffvHeight << messageViewerHeight;
781         }
782     }
783 
784     mSplitter1->setSizes(splitter1Sizes);
785     mSplitter2->setSizes(splitter2Sizes);
786 
787     if (mFolderViewSplitter) {
788         QList<int> splitterSizes;
789         splitterSizes << ffvHeight << ftHeight;
790         mFolderViewSplitter->setSizes(splitterSizes);
791     }
792 
793     //
794     // Now add the splitters to the main layout
795     //
796     mTopLayout->addWidget(mSplitter1);
797 
798     // Make sure the focus is on the view, and not on the quick search line edit, because otherwise
799     // shortcuts like + or j go to the wrong place.
800     // This would normally be done in the message list itself, but apparently something resets the focus
801     // again, probably all the reparenting we do here.
802     mMessagePane->focusView();
803 
804     // By default hide th unread and size columns on first run.
805     if (kmkernel->firstStart()) {
806         mFolderTreeWidget->folderTreeView()->hideColumn(1);
807         mFolderTreeWidget->folderTreeView()->hideColumn(3);
808         mFolderTreeWidget->folderTreeView()->header()->resizeSection(0, static_cast<int>(folderViewWidth * 0.8));
809     }
810 
811     // Make the copy action work, see disconnect comment above
812     if (mMsgView) {
813         connect(mMsgView->copyAction(), &QAction::triggered, mMsgView, &KMReaderWin::slotCopySelectedText);
814     }
815 }
816 
817 //-----------------------------------------------------------------------------
refreshFavoriteFoldersViewProperties()818 void KMMainWidget::refreshFavoriteFoldersViewProperties()
819 {
820     if (mFavoriteCollectionsView) {
821         if (KMKernel::self()->mailCommonSettings()->favoriteCollectionViewMode() == MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::IconMode) {
822             mFavoriteCollectionsView->changeViewMode(QListView::IconMode);
823         } else if (KMKernel::self()->mailCommonSettings()->favoriteCollectionViewMode()
824                    == MailCommon::MailCommonSettings::EnumFavoriteCollectionViewMode::ListMode) {
825             mFavoriteCollectionsView->changeViewMode(QListView::ListMode);
826         } else {
827             Q_ASSERT(false); // we should never get here in hidden mode
828         }
829         mFavoriteCollectionsView->setDropActionMenuEnabled(kmkernel->showPopupAfterDnD());
830         mFavoriteCollectionsView->setWordWrap(true);
831         mFavoriteCollectionsView->updateMode();
832     }
833 }
834 
835 //-----------------------------------------------------------------------------
readConfig()836 void KMMainWidget::readConfig()
837 {
838     const bool oldLongFolderList = mLongFolderList;
839     const bool oldReaderWindowActive = mReaderWindowActive;
840     const bool oldReaderWindowBelow = mReaderWindowBelow;
841     const bool oldFavoriteFolderView = mEnableFavoriteFolderView;
842     const bool oldFolderQuickSearch = mEnableFolderQuickSearch;
843 
844     // on startup, the layout is always new and we need to relayout the widgets
845     bool layoutChanged = !mStartupDone;
846 
847     if (mStartupDone) {
848         readPreConfig();
849 
850         layoutChanged = (oldLongFolderList != mLongFolderList) || (oldReaderWindowActive != mReaderWindowActive) || (oldReaderWindowBelow != mReaderWindowBelow)
851             || (oldFavoriteFolderView != mEnableFavoriteFolderView);
852 
853         if (layoutChanged) {
854             deleteWidgets();
855             createWidgets();
856             restoreCollectionFolderViewConfig();
857             Q_EMIT recreateGui();
858         } else if (oldFolderQuickSearch != mEnableFolderQuickSearch) {
859             if (mEnableFolderQuickSearch) {
860                 mFolderTreeWidget->filterFolderLineEdit()->show();
861             } else {
862                 mFolderTreeWidget->filterFolderLineEdit()->hide();
863             }
864         }
865     }
866 
867     {
868         // Read the config of the folder views and the header
869         if (mMsgView) {
870             mMsgView->readConfig();
871         }
872         mMessagePane->reloadGlobalConfiguration();
873         mFolderTreeWidget->readConfig();
874         if (mFavoriteCollectionsView) {
875             mFavoriteCollectionsView->readConfig();
876         }
877         refreshFavoriteFoldersViewProperties();
878     }
879 
880     {
881         // area for config group "General"
882         if (!mStartupDone) {
883             // check mail on startup
884             // do it after building the kmmainwin, so that the progressdialog is available
885             QTimer::singleShot(0, this, &KMMainWidget::slotCheckMailOnStartup);
886         }
887     }
888 
889     if (layoutChanged) {
890         layoutSplitters();
891     }
892 
893     updateMessageMenu();
894     updateFileMenu();
895     kmkernel->toggleSystemTray();
896     mAccountActionMenu->setAccountOrder(KMKernel::self()->mailCommonSettings()->order());
897 
898     connect(Akonadi::AgentManager::self(), &AgentManager::instanceAdded, this, &KMMainWidget::updateFileMenu);
899     connect(Akonadi::AgentManager::self(), &AgentManager::instanceRemoved, this, &KMMainWidget::updateFileMenu);
900 }
901 
902 //-----------------------------------------------------------------------------
writeConfig(bool force)903 void KMMainWidget::writeConfig(bool force)
904 {
905     // Don't save the sizes of all the widgets when we were never shown.
906     // This can happen in Kontact, where the KMail plugin is automatically
907     // loaded, but not necessarily shown.
908     // This prevents invalid sizes from being saved
909     if (mWasEverShown) {
910         // The height of the header widget can be 0, this happens when the user
911         // did not switch to the header widget onced and the "Welcome to KMail"
912         // HTML widget was shown the whole time
913         int headersHeight = mMessagePane->height();
914         if (headersHeight == 0) {
915             headersHeight = height() / 2;
916         }
917 
918         KMailSettings::self()->setSearchAndHeaderHeight(headersHeight);
919         KMailSettings::self()->setSearchAndHeaderWidth(mMessagePane->width());
920         if (mFavoriteCollectionsView) {
921             KMKernel::self()->mailCommonSettings()->setFavoriteCollectionViewHeight(mFavoriteCollectionsView->height());
922             KMailSettings::self()->setFolderTreeHeight(mFolderTreeWidget->height());
923             if (!mLongFolderList) {
924                 KMailSettings::self()->setFolderViewHeight(mFolderViewSplitter->height());
925             }
926         } else if (!mLongFolderList && mFolderTreeWidget) {
927             KMailSettings::self()->setFolderTreeHeight(mFolderTreeWidget->height());
928         }
929         if (mFolderTreeWidget) {
930             KMailSettings::self()->setFolderViewWidth(mFolderTreeWidget->width());
931             KSharedConfig::Ptr config = KMKernel::self()->config();
932             KConfigGroup group(config, "CollectionFolderView");
933 
934             ETMViewStateSaver saver;
935             saver.setView(mFolderTreeWidget->folderTreeView());
936             saver.saveState(group);
937 
938             group.writeEntry("HeaderState", mFolderTreeWidget->folderTreeView()->header()->saveState());
939             // Work around from startup folder
940             group.deleteEntry("Selection");
941 #if 0
942             if (!KMailSettings::self()->startSpecificFolderAtStartup()) {
943                 group.deleteEntry("Current");
944             }
945 #endif
946             group.sync();
947         }
948 
949         if (mMsgView) {
950             if (!mReaderWindowBelow) {
951                 KMailSettings::self()->setReaderWindowWidth(mMsgView->width());
952             }
953             mMsgView->viewer()->writeConfig(force);
954             KMailSettings::self()->setReaderWindowHeight(mMsgView->height());
955         }
956     }
957 }
958 
writeReaderConfig()959 void KMMainWidget::writeReaderConfig()
960 {
961     if (mWasEverShown) {
962         if (mMsgView) {
963             mMsgView->viewer()->writeConfig();
964         }
965     }
966 }
967 
messageView() const968 KMReaderWin *KMMainWidget::messageView() const
969 {
970     return mMsgView;
971 }
972 
messageListPane() const973 CollectionPane *KMMainWidget::messageListPane() const
974 {
975     return mMessagePane;
976 }
977 
currentCollection() const978 Collection KMMainWidget::currentCollection() const
979 {
980     return mCurrentCollection;
981 }
982 
983 //-----------------------------------------------------------------------------
deleteWidgets()984 void KMMainWidget::deleteWidgets()
985 {
986     // Simply delete the top splitter, which always is mSplitter1, regardless
987     // of the layout. This deletes all children.
988     // akonadi action manager is created in createWidgets(), parented to this
989     //  so not autocleaned up.
990     delete mAkonadiStandardActionManager;
991     mAkonadiStandardActionManager = nullptr;
992     delete mSplitter1;
993     mMsgView = nullptr;
994     mFolderViewSplitter = nullptr;
995     mFavoriteCollectionsView = nullptr;
996     mFolderTreeWidget = nullptr;
997     mSplitter1 = nullptr;
998     mSplitter2 = nullptr;
999     mFavoritesModel = nullptr;
1000 }
1001 
1002 //-----------------------------------------------------------------------------
createWidgets()1003 void KMMainWidget::createWidgets()
1004 {
1005     // Note that all widgets we create in this function have the parent 'this'.
1006     // They will be properly reparented in layoutSplitters()
1007 
1008     //
1009     // Create the folder tree
1010     //
1011     FolderTreeWidget::TreeViewOptions opt = FolderTreeWidget::ShowUnreadCount;
1012     opt |= FolderTreeWidget::UseLineEditForFiltering;
1013     opt |= FolderTreeWidget::ShowCollectionStatisticAnimation;
1014     opt |= FolderTreeWidget::DontKeyFilter;
1015     mFolderTreeWidget = new FolderTreeWidget(this, mGUIClient, opt);
1016 
1017     connect(mFolderTreeWidget->folderTreeView(),
1018             qOverload<const Akonadi::Collection &>(&EntityTreeView::currentChanged),
1019             this,
1020             &KMMainWidget::slotFolderChanged);
1021 
1022     connect(mFolderTreeWidget->folderTreeView()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KMMainWidget::updateFolderMenu);
1023 
1024     connect(mFolderTreeWidget->folderTreeView(), &FolderTreeView::newTabRequested, this, &KMMainWidget::slotCreateNewTab);
1025 
1026     //
1027     // Create the message pane
1028     //
1029     mMessagePane = new CollectionPane(!KMailSettings::self()->startSpecificFolderAtStartup(),
1030                                       KMKernel::self()->entityTreeModel(),
1031                                       mFolderTreeWidget->folderTreeView()->selectionModel(),
1032                                       this);
1033     connect(KMKernel::self()->entityTreeModel(), &Akonadi::EntityTreeModel::collectionFetched, this, &KMMainWidget::slotCollectionFetched);
1034 
1035     mMessagePane->setXmlGuiClient(mGUIClient);
1036     connect(mMessagePane, &MessageList::Pane::messageSelected, this, &KMMainWidget::slotMessageSelected);
1037     connect(mMessagePane, &MessageList::Pane::selectionChanged, this, &KMMainWidget::startUpdateMessageActionsTimer);
1038     connect(mMessagePane, &CollectionPane::currentTabChanged, this, &KMMainWidget::refreshMessageListSelection);
1039     connect(mMessagePane, &MessageList::Pane::messageActivated, this, &KMMainWidget::slotMessageActivated);
1040     connect(mMessagePane, &MessageList::Pane::messageStatusChangeRequest, this, &KMMainWidget::slotMessageStatusChangeRequest);
1041 
1042     connect(mMessagePane, &MessageList::Pane::statusMessage, this, &KMMainWidget::showMessageActivities);
1043 
1044     connect(mMessagePane, &MessageList::Pane::forceLostFocus, this, &KMMainWidget::slotSetFocusToViewer);
1045 
1046     //
1047     // Create the reader window
1048     //
1049     if (mReaderWindowActive) {
1050         mMsgView = new KMReaderWin(this, this, actionCollection());
1051         if (mMsgActions) {
1052             mMsgActions->setMessageView(mMsgView);
1053         }
1054         connect(mMsgView->viewer(), &MessageViewer::Viewer::displayPopupMenu, this, &KMMainWidget::slotMessagePopup);
1055         connect(mMsgView->viewer(), &MessageViewer::Viewer::moveMessageToTrash, this, &KMMainWidget::slotMoveMessageToTrash);
1056         connect(mMsgView->viewer(), &MessageViewer::Viewer::pageIsScrolledToBottom, this, &KMMainWidget::slotPageIsScrolledToBottom);
1057         connect(mMsgView->viewer(), &MessageViewer::Viewer::replyMessageTo, this, &KMMainWidget::slotReplyMessageTo);
1058         connect(mMsgView->viewer(), &MessageViewer::Viewer::showStatusBarMessage, this, &KMMainWidget::setShowStatusBarMessage);
1059         connect(mMsgView->viewer(), &MessageViewer::Viewer::zoomChanged, this, &KMMainWidget::setZoomChanged);
1060         if (mShowIntroductionAction) {
1061             mShowIntroductionAction->setEnabled(true);
1062         }
1063     } else {
1064         if (mMsgActions) {
1065             mMsgActions->setMessageView(nullptr);
1066         }
1067         if (mShowIntroductionAction) {
1068             mShowIntroductionAction->setEnabled(false);
1069         }
1070     }
1071     if (!KMailSettings::self()->enableFolderQuickSearch()) {
1072         mFolderTreeWidget->filterFolderLineEdit()->hide();
1073     }
1074 
1075     //
1076     // Create the favorite folder view
1077     //
1078     mAkonadiStandardActionManager = new Akonadi::StandardMailActionManager(mGUIClient->actionCollection(), this);
1079     connect(mAkonadiStandardActionManager, &Akonadi::StandardMailActionManager::actionStateUpdated, this, &KMMainWidget::slotAkonadiStandardActionUpdated);
1080 
1081     mAkonadiStandardActionManager->setCollectionSelectionModel(mFolderTreeWidget->folderTreeView()->selectionModel());
1082     mAkonadiStandardActionManager->setItemSelectionModel(mMessagePane->currentItemSelectionModel());
1083 
1084     if (mEnableFavoriteFolderView) {
1085         mFavoriteCollectionsView = new FavoriteCollectionWidget(KMKernel::self()->mailCommonSettings(), mGUIClient, this);
1086         refreshFavoriteFoldersViewProperties();
1087         connect(mFavoriteCollectionsView, qOverload<const Akonadi::Collection &>(&EntityListView::currentChanged), this, &KMMainWidget::slotFolderChanged);
1088         connect(mFavoriteCollectionsView, &FavoriteCollectionWidget::newTabRequested, this, &KMMainWidget::slotCreateNewTab);
1089         mFavoritesModel = new Akonadi::FavoriteCollectionsModel(mFolderTreeWidget->folderTreeWidgetProxyModel(),
1090                                                                 KMKernel::self()->config()->group("FavoriteCollections"),
1091                                                                 mFavoriteCollectionsView);
1092 
1093         auto orderProxy = new MailCommon::FavoriteCollectionOrderProxyModel(this);
1094         orderProxy->setOrderConfig(KMKernel::self()->config()->group("FavoriteCollectionsOrder"));
1095         orderProxy->setSourceModel(mFavoritesModel);
1096         orderProxy->sort(0, Qt::AscendingOrder);
1097 
1098         mFavoriteCollectionsView->setModel(orderProxy);
1099 
1100         mAkonadiStandardActionManager->setFavoriteCollectionsModel(mFavoritesModel);
1101         mAkonadiStandardActionManager->setFavoriteSelectionModel(mFavoriteCollectionsView->selectionModel());
1102     }
1103 
1104     // Don't use mMailActionManager->createAllActions() to save memory by not
1105     // creating actions that doesn't make sense.
1106     const auto standardActions = {
1107         StandardActionManager::CreateCollection,
1108         StandardActionManager::CopyCollections,
1109         StandardActionManager::DeleteCollections,
1110         StandardActionManager::SynchronizeCollections,
1111         StandardActionManager::CollectionProperties,
1112         StandardActionManager::CopyItems,
1113         StandardActionManager::Paste,
1114         StandardActionManager::DeleteItems,
1115         StandardActionManager::ManageLocalSubscriptions,
1116         StandardActionManager::CopyCollectionToMenu,
1117         StandardActionManager::CopyItemToMenu,
1118         StandardActionManager::MoveItemToMenu,
1119         StandardActionManager::MoveCollectionToMenu,
1120         StandardActionManager::CutItems,
1121         StandardActionManager::CutCollections,
1122         StandardActionManager::CreateResource,
1123         StandardActionManager::DeleteResources,
1124         StandardActionManager::ResourceProperties,
1125         StandardActionManager::SynchronizeResources,
1126         StandardActionManager::ToggleWorkOffline,
1127         StandardActionManager::SynchronizeCollectionsRecursive,
1128     };
1129 
1130     for (StandardActionManager::Type standardAction : standardActions) {
1131         mAkonadiStandardActionManager->createAction(standardAction);
1132     }
1133 
1134     if (mEnableFavoriteFolderView) {
1135         const auto favoriteActions = {
1136             StandardActionManager::AddToFavoriteCollections,
1137             StandardActionManager::RemoveFromFavoriteCollections,
1138             StandardActionManager::RenameFavoriteCollection,
1139             StandardActionManager::SynchronizeFavoriteCollections,
1140         };
1141         for (StandardActionManager::Type favoriteAction : favoriteActions) {
1142             mAkonadiStandardActionManager->createAction(favoriteAction);
1143         }
1144     }
1145 
1146     const auto mailActions = {StandardMailActionManager::MarkAllMailAsRead,
1147                               StandardMailActionManager::MoveToTrash,
1148                               StandardMailActionManager::MoveAllToTrash,
1149                               StandardMailActionManager::RemoveDuplicates,
1150                               StandardMailActionManager::EmptyAllTrash,
1151                               StandardMailActionManager::MarkMailAsRead,
1152                               StandardMailActionManager::MarkMailAsUnread,
1153                               StandardMailActionManager::MarkMailAsImportant,
1154                               StandardMailActionManager::MarkMailAsActionItem};
1155 
1156     for (StandardMailActionManager::Type mailAction : mailActions) {
1157         mAkonadiStandardActionManager->createAction(mailAction);
1158     }
1159 
1160     mAkonadiStandardActionManager->interceptAction(Akonadi::StandardActionManager::CollectionProperties);
1161     connect(mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CollectionProperties),
1162             &QAction::triggered,
1163             mManageShowCollectionProperties,
1164             &ManageShowCollectionProperties::slotCollectionProperties);
1165 
1166     //
1167     // Create all kinds of actions
1168     //
1169     mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::RemoveDuplicates)->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Asterisk));
1170     mAkonadiStandardActionManager->interceptAction(Akonadi::StandardMailActionManager::RemoveDuplicates);
1171     connect(mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::RemoveDuplicates),
1172             &QAction::triggered,
1173             this,
1174             &KMMainWidget::slotRemoveDuplicates);
1175 
1176     mCollectionProperties = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CollectionProperties);
1177     connect(kmkernel->folderCollectionMonitor(), &Monitor::collectionRemoved, this, &KMMainWidget::slotCollectionRemoved);
1178     connect(kmkernel->folderCollectionMonitor(), &Monitor::itemAdded, this, &KMMainWidget::slotItemAdded);
1179     connect(kmkernel->folderCollectionMonitor(), &Monitor::itemRemoved, this, &KMMainWidget::slotItemRemoved);
1180     connect(kmkernel->folderCollectionMonitor(), &Monitor::itemMoved, this, &KMMainWidget::slotItemMoved);
1181     connect(kmkernel->folderCollectionMonitor(),
1182             qOverload<const Akonadi::Collection &, const QSet<QByteArray> &>(&ChangeRecorder::collectionChanged),
1183             this,
1184             &KMMainWidget::slotCollectionChanged);
1185 
1186     connect(kmkernel->folderCollectionMonitor(), &Monitor::collectionStatisticsChanged, this, &KMMainWidget::slotCollectionStatisticsChanged);
1187 }
1188 
updateMoveAction(const Akonadi::CollectionStatistics & statistic)1189 void KMMainWidget::updateMoveAction(const Akonadi::CollectionStatistics &statistic)
1190 {
1191     const bool hasUnreadMails = (statistic.unreadCount() > 0);
1192     updateMoveAction(hasUnreadMails);
1193 }
1194 
updateMoveAction(bool hasUnreadMails)1195 void KMMainWidget::updateMoveAction(bool hasUnreadMails)
1196 {
1197     const bool enable_goto_unread = hasUnreadMails || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllFolders)
1198         || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllMarkedFolders);
1199     actionCollection()->action(QStringLiteral("go_next_unread_message"))->setEnabled(enable_goto_unread);
1200     actionCollection()->action(QStringLiteral("go_prev_unread_message"))->setEnabled(enable_goto_unread);
1201     if (mAkonadiStandardActionManager && mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkAllMailAsRead)) {
1202         mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MarkAllMailAsRead)->setEnabled(hasUnreadMails);
1203     }
1204 }
1205 
updateAllToTrashAction(qint64 statistics)1206 void KMMainWidget::updateAllToTrashAction(qint64 statistics)
1207 {
1208     if (mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)) {
1209         const bool folderWithContent = mCurrentFolderSettings && !mCurrentFolderSettings->isStructural();
1210         mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)
1211             ->setEnabled(folderWithContent && (statistics > 0) && mCurrentFolderSettings->canDeleteMessages());
1212     }
1213 }
1214 
slotCollectionStatisticsChanged(Akonadi::Collection::Id id,const Akonadi::CollectionStatistics & statistic)1215 void KMMainWidget::slotCollectionStatisticsChanged(Akonadi::Collection::Id id, const Akonadi::CollectionStatistics &statistic)
1216 {
1217     if (id == CommonKernel->outboxCollectionFolder().id()) {
1218         const bool enableAction = (statistic.count() > 0);
1219         mSendQueued->setEnabled(enableAction);
1220         mSendActionMenu->setEnabled(enableAction);
1221     } else if (id == mCurrentCollection.id()) {
1222         updateMoveAction(statistic);
1223         updateAllToTrashAction(statistic.count());
1224         setCurrentCollection(CommonKernel->collectionFromId(mCurrentCollection.id()));
1225     }
1226 }
1227 
slotCreateNewTab(bool preferNewTab)1228 void KMMainWidget::slotCreateNewTab(bool preferNewTab)
1229 {
1230     mMessagePane->setPreferEmptyTab(preferNewTab);
1231 }
1232 
slotCollectionChanged(const Akonadi::Collection & collection,const QSet<QByteArray> & set)1233 void KMMainWidget::slotCollectionChanged(const Akonadi::Collection &collection, const QSet<QByteArray> &set)
1234 {
1235     if ((collection == mCurrentCollection) && (set.contains("MESSAGEFOLDER") || set.contains("expirationcollectionattribute"))) {
1236         if (set.contains("MESSAGEFOLDER")) {
1237             mMessagePane->resetModelStorage();
1238         } else {
1239             setCurrentCollection(collection);
1240         }
1241     } else if (set.contains("ENTITYDISPLAY") || set.contains("NAME")) {
1242         const QModelIndex idx = Akonadi::EntityTreeModel::modelIndexForCollection(KMKernel::self()->collectionModel(), collection);
1243         if (idx.isValid()) {
1244             const QString text = idx.data().toString();
1245             const auto icon = idx.data(Qt::DecorationRole).value<QIcon>();
1246             mMessagePane->updateTabIconText(collection, text, icon);
1247         }
1248     }
1249 }
1250 
slotItemAdded(const Akonadi::Item & msg,const Akonadi::Collection & col)1251 void KMMainWidget::slotItemAdded(const Akonadi::Item &msg, const Akonadi::Collection &col)
1252 {
1253     Q_UNUSED(msg)
1254     if (col.isValid()) {
1255         if (col == CommonKernel->outboxCollectionFolder()) {
1256             startUpdateMessageActionsTimer();
1257         }
1258     }
1259 }
1260 
slotItemRemoved(const Akonadi::Item & item)1261 void KMMainWidget::slotItemRemoved(const Akonadi::Item &item)
1262 {
1263     if (item.isValid() && item.parentCollection().isValid() && (item.parentCollection() == CommonKernel->outboxCollectionFolder())) {
1264         startUpdateMessageActionsTimer();
1265     }
1266 }
1267 
slotItemMoved(const Akonadi::Item & item,const Akonadi::Collection & from,const Akonadi::Collection & to)1268 void KMMainWidget::slotItemMoved(const Akonadi::Item &item, const Akonadi::Collection &from, const Akonadi::Collection &to)
1269 {
1270     if (item.isValid() && ((from.id() == CommonKernel->outboxCollectionFolder().id()) || to.id() == CommonKernel->outboxCollectionFolder().id())) {
1271         startUpdateMessageActionsTimer();
1272     }
1273 }
1274 
1275 //-------------------------------------------------------------------------
slotFocusQuickSearch()1276 void KMMainWidget::slotFocusQuickSearch()
1277 {
1278     const QString text = mMsgView ? mMsgView->copyText() : QString();
1279     mMessagePane->focusQuickSearch(text);
1280 }
1281 
1282 //-------------------------------------------------------------------------
showSearchDialog()1283 bool KMMainWidget::showSearchDialog()
1284 {
1285     if (!mSearchWin) {
1286         mSearchWin = new SearchWindow(this, mCurrentCollection);
1287         mSearchWin->setModal(false);
1288         mSearchWin->setObjectName(QStringLiteral("Search"));
1289     } else {
1290         mSearchWin->activateFolder(mCurrentCollection);
1291     }
1292 
1293     mSearchWin->show();
1294     KWindowSystem::activateWindow(mSearchWin->winId());
1295     return true;
1296 }
1297 
1298 //-----------------------------------------------------------------------------
slotFilter()1299 void KMMainWidget::slotFilter()
1300 {
1301     FilterIf->openFilterDialog(true);
1302 }
1303 
slotManageSieveScripts()1304 void KMMainWidget::slotManageSieveScripts()
1305 {
1306     if (!kmkernel->askToGoOnline()) {
1307         return;
1308     }
1309     if (mManageSieveDialog) {
1310         return;
1311     }
1312 
1313     mManageSieveDialog = new KSieveUi::ManageSieveScriptsDialog(mSievePasswordProvider);
1314     connect(mManageSieveDialog.data(), &KSieveUi::ManageSieveScriptsDialog::finished, this, &KMMainWidget::slotCheckVacation);
1315     mManageSieveDialog->show();
1316 }
1317 
1318 //-----------------------------------------------------------------------------
slotCheckMail()1319 void KMMainWidget::slotCheckMail()
1320 {
1321     kmkernel->checkMail();
1322 }
1323 
1324 //-----------------------------------------------------------------------------
slotCheckMailOnStartup()1325 void KMMainWidget::slotCheckMailOnStartup()
1326 {
1327     kmkernel->checkMailOnStartup();
1328 }
1329 
slotCompose()1330 void KMMainWidget::slotCompose()
1331 {
1332     auto job = new ComposeNewMessageJob;
1333     job->setFolderSettings(mCurrentFolderSettings);
1334     job->setCurrentCollection(mCurrentCollection);
1335     job->start();
1336 }
1337 
1338 //-----------------------------------------------------------------------------
1339 // TODO: do we want the list sorted alphabetically?
slotShowNewFromTemplate()1340 void KMMainWidget::slotShowNewFromTemplate()
1341 {
1342     if (mCurrentFolderSettings) {
1343         const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoidOrDefault(mCurrentFolderSettings->identity());
1344         mTemplateFolder = CommonKernel->collectionFromId(ident.templates().toLongLong());
1345     }
1346 
1347     if (!mTemplateFolder.isValid()) {
1348         mTemplateFolder = CommonKernel->templatesCollectionFolder();
1349     }
1350     if (!mTemplateFolder.isValid()) {
1351         qCWarning(KMAIL_LOG) << "Template folder not found";
1352         return;
1353     }
1354 
1355     mTemplateMenu->menu()->clear();
1356 
1357     auto job = new Akonadi::ItemFetchJob(mTemplateFolder);
1358     job->fetchScope().setAncestorRetrieval(ItemFetchScope::Parent);
1359     job->fetchScope().fetchFullPayload();
1360     connect(job, &Akonadi::ItemFetchJob::result, this, &KMMainWidget::slotDelayedShowNewFromTemplate);
1361 }
1362 
slotDelayedShowNewFromTemplate(KJob * job)1363 void KMMainWidget::slotDelayedShowNewFromTemplate(KJob *job)
1364 {
1365     auto fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
1366 
1367     const Akonadi::Item::List items = fetchJob->items();
1368     const int numberOfItems = items.count();
1369     for (int idx = 0; idx < numberOfItems; ++idx) {
1370         KMime::Message::Ptr msg = MessageComposer::Util::message(items.at(idx));
1371         if (msg) {
1372             QString subj = msg->subject()->asUnicodeString();
1373             if (subj.isEmpty()) {
1374                 subj = i18n("No Subject");
1375             }
1376 
1377             QAction *templateAction = mTemplateMenu->menu()->addAction(KStringHandler::rsqueeze(subj.replace(QLatin1Char('&'), QStringLiteral("&&"))));
1378             QVariant var;
1379             var.setValue(items.at(idx));
1380             templateAction->setData(var);
1381         }
1382     }
1383 
1384     // If there are no templates available, add a menu entry which informs
1385     // the user about this.
1386     if (mTemplateMenu->menu()->actions().isEmpty()) {
1387         QAction *noAction = mTemplateMenu->menu()->addAction(i18n("(no templates)"));
1388         noAction->setEnabled(false);
1389     }
1390 }
1391 
1392 //-----------------------------------------------------------------------------
slotNewFromTemplate(QAction * action)1393 void KMMainWidget::slotNewFromTemplate(QAction *action)
1394 {
1395     if (!mTemplateFolder.isValid()) {
1396         return;
1397     }
1398     const auto item = action->data().value<Akonadi::Item>();
1399     newFromTemplate(item);
1400 }
1401 
1402 //-----------------------------------------------------------------------------
newFromTemplate(const Akonadi::Item & msg)1403 void KMMainWidget::newFromTemplate(const Akonadi::Item &msg)
1404 {
1405     if (!msg.isValid()) {
1406         return;
1407     }
1408     auto command = new KMUseTemplateCommand(this, msg);
1409     command->start();
1410 }
1411 
1412 //-----------------------------------------------------------------------------
slotPostToML()1413 void KMMainWidget::slotPostToML()
1414 {
1415     if (mCurrentFolderSettings && mCurrentFolderSettings->isMailingListEnabled()) {
1416         if (KMail::Util::mailingListPost(mCurrentFolderSettings, mCurrentCollection)) {
1417             return;
1418         }
1419     }
1420     slotCompose();
1421 }
1422 
slotExpireFolder()1423 void KMMainWidget::slotExpireFolder()
1424 {
1425     if (!mCurrentFolderSettings) {
1426         return;
1427     }
1428     const MailCommon::ExpireCollectionAttribute *attr = mCurrentCollection.attribute<MailCommon::ExpireCollectionAttribute>();
1429     if (attr) {
1430         bool canBeExpired = true;
1431         if (!attr->isAutoExpire()) {
1432             canBeExpired = false;
1433         } else if (attr->unreadExpireUnits() == MailCommon::ExpireCollectionAttribute::ExpireNever
1434                    && attr->readExpireUnits() == MailCommon::ExpireCollectionAttribute::ExpireNever) {
1435             canBeExpired = false;
1436         }
1437 
1438         if (!canBeExpired) {
1439             const QString message = i18n("This folder does not have any expiry options set");
1440             KMessageBox::information(this, message);
1441             return;
1442         }
1443 
1444         if (KMailSettings::self()->warnBeforeExpire()) {
1445             const QString message = i18n("<qt>Are you sure you want to expire the folder <b>%1</b>?</qt>", mCurrentFolderSettings->name().toHtmlEscaped());
1446             if (KMessageBox::warningContinueCancel(this, message, i18n("Expire Folder"), KGuiItem(i18n("&Expire"))) != KMessageBox::Continue) {
1447                 return;
1448             }
1449         }
1450 
1451         MailCommon::Util::expireOldMessages(mCurrentCollection, true /*immediate*/);
1452     }
1453 }
1454 
1455 //-----------------------------------------------------------------------------
slotEmptyFolder()1456 void KMMainWidget::slotEmptyFolder()
1457 {
1458     if (!mCurrentCollection.isValid()) {
1459         return;
1460     }
1461     const bool isTrash = CommonKernel->folderIsTrash(mCurrentCollection);
1462     const QString title = (isTrash) ? i18n("Empty Trash") : i18n("Move to Trash");
1463     const QString text = (isTrash) ? i18n("Are you sure you want to empty the trash folder?")
1464                                    : i18n(
1465                                        "<qt>Are you sure you want to move all messages from "
1466                                        "folder <b>%1</b> to the trash?</qt>",
1467                                        mCurrentCollection.name().toHtmlEscaped());
1468     const QString icon = (isTrash) ? QStringLiteral("edit-delete-shred") : QStringLiteral("edit-delete");
1469 
1470     if (KMessageBox::warningContinueCancel(this, text, title, KGuiItem(title, icon)) != KMessageBox::Continue) {
1471         return;
1472     }
1473     KCursorSaver saver(Qt::WaitCursor);
1474     slotSelectAllMessages();
1475     if (isTrash) {
1476         /* Don't ask for confirmation again when deleting, the user has already
1477         confirmed. */
1478         deleteSelectedMessages(false);
1479     } else {
1480         slotTrashSelectedMessages();
1481     }
1482 
1483     if (mMsgView) {
1484         mMsgView->clearCache();
1485     }
1486 
1487     if (!isTrash) {
1488         const QString str = i18n("Moved all messages to the trash");
1489         showMessageActivities(str);
1490     }
1491 
1492     updateMessageActions();
1493 
1494     // Disable empty trash/move all to trash action - we've just deleted/moved
1495     // all folder contents.
1496     mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)->setEnabled(false);
1497 }
1498 
1499 //-----------------------------------------------------------------------------
slotArchiveFolder()1500 void KMMainWidget::slotArchiveFolder()
1501 {
1502     if (mCurrentCollection.isValid()) {
1503         KMail::ArchiveFolderDialog archiveDialog(this);
1504         archiveDialog.setFolder(mCurrentCollection);
1505         archiveDialog.exec();
1506     }
1507 }
1508 
1509 //-----------------------------------------------------------------------------
slotRemoveFolder()1510 void KMMainWidget::slotRemoveFolder()
1511 {
1512     if (!mCurrentFolderSettings) {
1513         return;
1514     }
1515     if (!mCurrentFolderSettings->isValid()) {
1516         return;
1517     }
1518     if (mCurrentFolderSettings->isSystemFolder()) {
1519         return;
1520     }
1521     if (mCurrentFolderSettings->isReadOnly()) {
1522         return;
1523     }
1524 
1525     auto job = new RemoveCollectionJob(this);
1526     connect(job, &RemoveCollectionJob::clearCurrentFolder, this, &KMMainWidget::slotClearCurrentFolder);
1527     job->setMainWidget(this);
1528     job->setCurrentFolder(mCurrentCollection);
1529     job->start();
1530 }
1531 
slotClearCurrentFolder()1532 void KMMainWidget::slotClearCurrentFolder()
1533 {
1534     clearCurrentFolder();
1535 }
1536 
1537 //-----------------------------------------------------------------------------
slotExpireAll()1538 void KMMainWidget::slotExpireAll()
1539 {
1540     if (KMailSettings::self()->warnBeforeExpire()) {
1541         const int ret = KMessageBox::warningContinueCancel(KMainWindow::memberList().constFirst(),
1542                                                            i18n("Are you sure you want to expire all old messages?"),
1543                                                            i18n("Expire Old Messages?"),
1544                                                            KGuiItem(i18n("Expire")));
1545         if (ret != KMessageBox::Continue) {
1546             return;
1547         }
1548     }
1549 
1550     kmkernel->expireAllFoldersNow();
1551 }
1552 
assignLoadExternalReference()1553 void KMMainWidget::assignLoadExternalReference()
1554 {
1555     if (mFolderHtmlLoadExtPreference) {
1556         mMsgView->setHtmlLoadExtDefault(mFolderHtmlLoadExtPreference);
1557     } else {
1558         mMsgView->setHtmlLoadExtDefault(mHtmlLoadExtGlobalSetting);
1559     }
1560     mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference);
1561 }
1562 
1563 //-----------------------------------------------------------------------------
slotOverrideHtmlLoadExt()1564 void KMMainWidget::slotOverrideHtmlLoadExt()
1565 {
1566     if (mHtmlLoadExtGlobalSetting == mFolderHtmlLoadExtPreference) {
1567         int result = KMessageBox::warningContinueCancel(this,
1568                                                         // the warning text is taken from configuredialog.cpp:
1569                                                         i18n("Loading external references in html mail will make you more vulnerable to "
1570                                                              "\"spam\" and may increase the likelihood that your system will be "
1571                                                              "compromised by other present and anticipated security exploits."),
1572                                                         i18n("Security Warning"),
1573                                                         KGuiItem(i18n("Load External References")),
1574                                                         KStandardGuiItem::cancel(),
1575                                                         QStringLiteral("OverrideHtmlLoadExtWarning"),
1576                                                         KMessageBox::Option());
1577         if (result == KMessageBox::Cancel) {
1578             mPreferHtmlLoadExtAction->setChecked(false);
1579             return;
1580         }
1581     }
1582     mFolderHtmlLoadExtPreference = !mFolderHtmlLoadExtPreference;
1583 
1584     if (mMsgView) {
1585         assignLoadExternalReference();
1586         mMsgView->update(true);
1587     }
1588 }
1589 
1590 //-----------------------------------------------------------------------------
slotForwardInlineMsg()1591 void KMMainWidget::slotForwardInlineMsg()
1592 {
1593     if (!mCurrentFolderSettings) {
1594         return;
1595     }
1596 
1597     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
1598     if (selectedMessages.isEmpty()) {
1599         return;
1600     }
1601     const QString text = mMsgView ? mMsgView->copyText() : QString();
1602     auto command = new KMForwardCommand(this, selectedMessages, mCurrentFolderSettings->identity(), QString(), text);
1603 
1604     command->start();
1605 }
1606 
1607 //-----------------------------------------------------------------------------
slotForwardAttachedMessage()1608 void KMMainWidget::slotForwardAttachedMessage()
1609 {
1610     if (!mCurrentFolderSettings) {
1611         return;
1612     }
1613 
1614     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
1615     if (selectedMessages.isEmpty()) {
1616         return;
1617     }
1618     auto command = new KMForwardAttachedCommand(this, selectedMessages, mCurrentFolderSettings->identity());
1619 
1620     command->start();
1621 }
1622 
1623 //-----------------------------------------------------------------------------
slotUseTemplate()1624 void KMMainWidget::slotUseTemplate()
1625 {
1626     newFromTemplate(mMessagePane->currentItem());
1627 }
1628 
1629 //-----------------------------------------------------------------------------
1630 // Message moving and permanent deletion
1631 //
1632 
moveMessageSelected(MessageList::Core::MessageItemSetReference ref,const Akonadi::Collection & dest,bool confirmOnDeletion)1633 void KMMainWidget::moveMessageSelected(MessageList::Core::MessageItemSetReference ref, const Akonadi::Collection &dest, bool confirmOnDeletion)
1634 {
1635     Akonadi::Item::List selectMsg = mMessagePane->itemListFromPersistentSet(ref);
1636     selectMsg = mPluginCheckBeforeDeletingManagerInterface->confirmBeforeDeleting(selectMsg);
1637     if (selectMsg.isEmpty()) {
1638         mMessagePane->deletePersistentSet(ref);
1639         return;
1640     }
1641     // If this is a deletion, ask for confirmation
1642     if (confirmOnDeletion) {
1643         const int selectedMessageCount = selectMsg.count();
1644         int ret = KMessageBox::warningContinueCancel(this,
1645                                                      i18np("<qt>Do you really want to delete the selected message?<br />"
1646                                                            "Once deleted, it cannot be restored.</qt>",
1647                                                            "<qt>Do you really want to delete the %1 selected messages?<br />"
1648                                                            "Once deleted, they cannot be restored.</qt>",
1649                                                            selectedMessageCount),
1650                                                      selectedMessageCount > 1 ? i18n("Delete Messages") : i18n("Delete Message"),
1651                                                      KStandardGuiItem::del(),
1652                                                      KStandardGuiItem::cancel());
1653         if (ret == KMessageBox::Cancel) {
1654             mMessagePane->deletePersistentSet(ref);
1655             return; // user canceled the action
1656         }
1657     }
1658     mMessagePane->markMessageItemsAsAboutToBeRemoved(ref, true);
1659     // And stuff them into a KMMoveCommand :)
1660     auto command = new KMMoveCommand(dest, selectMsg, ref);
1661     QObject::connect(command, &KMMoveCommand::moveDone, this, &KMMainWidget::slotMoveMessagesCompleted);
1662     command->start();
1663 
1664     if (dest.isValid()) {
1665         showMessageActivities(i18n("Moving messages..."));
1666     } else {
1667         showMessageActivities(i18n("Deleting messages..."));
1668     }
1669 }
1670 
slotMoveMessagesCompleted(KMMoveCommand * command)1671 void KMMainWidget::slotMoveMessagesCompleted(KMMoveCommand *command)
1672 {
1673     Q_ASSERT(command);
1674     mMessagePane->markMessageItemsAsAboutToBeRemoved(command->refSet(), false);
1675     mMessagePane->deletePersistentSet(command->refSet());
1676     // Bleah :D
1677     const bool moveWasReallyADelete = !command->destFolder().isValid();
1678 
1679     QString str;
1680     if (command->result() == KMCommand::OK) {
1681         if (moveWasReallyADelete) {
1682             str = i18n("Messages deleted successfully.");
1683         } else {
1684             str = i18n("Messages moved successfully.");
1685         }
1686     } else {
1687         if (moveWasReallyADelete) {
1688             if (command->result() == KMCommand::Failed) {
1689                 str = i18n("Deleting messages failed.");
1690             } else {
1691                 str = i18n("Deleting messages canceled.");
1692             }
1693         } else {
1694             if (command->result() == KMCommand::Failed) {
1695                 str = i18n("Moving messages failed.");
1696             } else {
1697                 str = i18n("Moving messages canceled.");
1698             }
1699         }
1700     }
1701     showMessageActivities(str);
1702     // The command will autodelete itself and will also kill the set.
1703 }
1704 
slotDeleteMessages()1705 void KMMainWidget::slotDeleteMessages()
1706 {
1707     deleteSelectedMessages(true && !KMailSettings::self()->deleteMessageWithoutConfirmation());
1708 }
1709 
currentSelection() const1710 Akonadi::Item::List KMMainWidget::currentSelection() const
1711 {
1712     Akonadi::Item::List selectMsg;
1713     MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet();
1714     if (ref != -1) {
1715         selectMsg = mMessagePane->itemListFromPersistentSet(ref);
1716     }
1717     return selectMsg;
1718 }
1719 
deleteSelectedMessages(bool confirmDelete)1720 void KMMainWidget::deleteSelectedMessages(bool confirmDelete)
1721 {
1722     // Create a persistent message set from the current selection
1723     MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet();
1724     if (ref != -1) {
1725         moveMessageSelected(ref, Akonadi::Collection(), confirmDelete);
1726     }
1727 }
1728 
slotDeleteThread(bool confirmDelete)1729 void KMMainWidget::slotDeleteThread(bool confirmDelete)
1730 {
1731     // Create a persistent set from the current thread.
1732     MessageList::Core::MessageItemSetReference ref = mMessagePane->currentThreadAsPersistentSet();
1733     if (ref != -1) {
1734         moveMessageSelected(ref, Akonadi::Collection(), confirmDelete);
1735     }
1736 }
1737 
moveOrCopyToDialog()1738 FolderSelectionDialog *KMMainWidget::moveOrCopyToDialog()
1739 {
1740     if (!mMoveOrCopyToDialog) {
1741         FolderSelectionDialog::SelectionFolderOption options = FolderSelectionDialog::HideVirtualFolder;
1742         mMoveOrCopyToDialog = new FolderSelectionDialog(this, options);
1743         mMoveOrCopyToDialog->setModal(true);
1744     }
1745     return mMoveOrCopyToDialog;
1746 }
1747 
selectFromAllFoldersDialog()1748 FolderSelectionDialog *KMMainWidget::selectFromAllFoldersDialog()
1749 {
1750     if (!mSelectFromAllFoldersDialog) {
1751         FolderSelectionDialog::SelectionFolderOptions options = FolderSelectionDialog::None;
1752         options |= FolderSelectionDialog::NotAllowToCreateNewFolder;
1753 
1754         mSelectFromAllFoldersDialog = new FolderSelectionDialog(this, options);
1755         mSelectFromAllFoldersDialog->setModal(true);
1756     }
1757     return mSelectFromAllFoldersDialog;
1758 }
1759 
slotMoveSelectedMessageToFolder()1760 void KMMainWidget::slotMoveSelectedMessageToFolder()
1761 {
1762     QPointer<MailCommon::FolderSelectionDialog> dialog(moveOrCopyToDialog());
1763     dialog->setWindowTitle(i18nc("@title:window", "Move Messages to Folder"));
1764     if (dialog->exec() && dialog) {
1765         const Akonadi::Collection dest = dialog->selectedCollection();
1766         if (dest.isValid()) {
1767             moveSelectedMessagesToFolder(dest);
1768         }
1769     }
1770 }
1771 
moveSelectedMessagesToFolder(const Akonadi::Collection & dest)1772 void KMMainWidget::moveSelectedMessagesToFolder(const Akonadi::Collection &dest)
1773 {
1774     MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet();
1775     if (ref != -1) {
1776         // Need to verify if dest == src ??? akonadi do it for us.
1777         moveMessageSelected(ref, dest, false);
1778     }
1779 }
1780 
copyMessageSelected(const Akonadi::Item::List & selectMsg,const Akonadi::Collection & dest)1781 void KMMainWidget::copyMessageSelected(const Akonadi::Item::List &selectMsg, const Akonadi::Collection &dest)
1782 {
1783     if (selectMsg.isEmpty()) {
1784         return;
1785     }
1786     // And stuff them into a KMCopyCommand :)
1787     auto command = new KMCopyCommand(dest, selectMsg);
1788     QObject::connect(command, &KMCommand::completed, this, &KMMainWidget::slotCopyMessagesCompleted);
1789     command->start();
1790     showMessageActivities(i18n("Copying messages..."));
1791 }
1792 
slotCopyMessagesCompleted(KMCommand * command)1793 void KMMainWidget::slotCopyMessagesCompleted(KMCommand *command)
1794 {
1795     Q_ASSERT(command);
1796     QString str;
1797     if (command->result() == KMCommand::OK) {
1798         str = i18n("Messages copied successfully.");
1799     } else {
1800         if (command->result() == KMCommand::Failed) {
1801             str = i18n("Copying messages failed.");
1802         } else {
1803             str = i18n("Copying messages canceled.");
1804         }
1805     }
1806     showMessageActivities(str);
1807     // The command will autodelete itself and will also kill the set.
1808 }
1809 
slotCopySelectedMessagesToFolder()1810 void KMMainWidget::slotCopySelectedMessagesToFolder()
1811 {
1812     QPointer<MailCommon::FolderSelectionDialog> dialog(moveOrCopyToDialog());
1813     dialog->setWindowTitle(i18nc("@title:window", "Copy Messages to Folder"));
1814 
1815     if (dialog->exec() && dialog) {
1816         const Akonadi::Collection dest = dialog->selectedCollection();
1817         if (dest.isValid()) {
1818             copySelectedMessagesToFolder(dest);
1819         }
1820     }
1821 }
1822 
copySelectedMessagesToFolder(const Akonadi::Collection & dest)1823 void KMMainWidget::copySelectedMessagesToFolder(const Akonadi::Collection &dest)
1824 {
1825     const Akonadi::Item::List lstMsg = mMessagePane->selectionAsMessageItemList();
1826     if (!lstMsg.isEmpty()) {
1827         copyMessageSelected(lstMsg, dest);
1828     }
1829 }
1830 
1831 //-----------------------------------------------------------------------------
1832 // Message trashing
1833 //
trashMessageSelected(MessageList::Core::MessageItemSetReference ref)1834 void KMMainWidget::trashMessageSelected(MessageList::Core::MessageItemSetReference ref)
1835 {
1836     if (!mCurrentCollection.isValid()) {
1837         return;
1838     }
1839 
1840     Akonadi::Item::List select = mMessagePane->itemListFromPersistentSet(ref);
1841 
1842     select = mPluginCheckBeforeDeletingManagerInterface->confirmBeforeDeleting(select);
1843     if (select.isEmpty()) {
1844         mMessagePane->deletePersistentSet(ref);
1845         return;
1846     }
1847 
1848     mMessagePane->markMessageItemsAsAboutToBeRemoved(ref, true);
1849 
1850     // FIXME: Why we don't use KMMoveCommand( trashFolder(), selectedMessages ); ?
1851     // And stuff them into a KMTrashMsgCommand :)
1852     auto command = new KMTrashMsgCommand(mCurrentCollection, select, ref);
1853 
1854     QObject::connect(command, &KMTrashMsgCommand::moveDone, this, &KMMainWidget::slotTrashMessagesCompleted);
1855     command->start();
1856     switch (command->operation()) {
1857     case KMTrashMsgCommand::MoveToTrash:
1858         showMessageActivities(i18n("Moving messages to trash..."));
1859         break;
1860     case KMTrashMsgCommand::Delete:
1861         showMessageActivities(i18n("Deleting messages..."));
1862         break;
1863     case KMTrashMsgCommand::Both:
1864     case KMTrashMsgCommand::Unknown:
1865         showMessageActivities(i18n("Deleting and moving messages to trash..."));
1866         break;
1867     }
1868 }
1869 
slotTrashMessagesCompleted(KMTrashMsgCommand * command)1870 void KMMainWidget::slotTrashMessagesCompleted(KMTrashMsgCommand *command)
1871 {
1872     Q_ASSERT(command);
1873     mMessagePane->markMessageItemsAsAboutToBeRemoved(command->refSet(), false);
1874     mMessagePane->deletePersistentSet(command->refSet());
1875     if (command->result() == KMCommand::OK) {
1876         switch (command->operation()) {
1877         case KMTrashMsgCommand::MoveToTrash:
1878             showMessageActivities(i18n("Messages moved to trash successfully."));
1879             break;
1880         case KMTrashMsgCommand::Delete:
1881             showMessageActivities(i18n("Messages deleted successfully."));
1882             break;
1883         case KMTrashMsgCommand::Both:
1884         case KMTrashMsgCommand::Unknown:
1885             showMessageActivities(i18n("Messages moved to trash or deleted successfully"));
1886             break;
1887         }
1888     } else if (command->result() == KMCommand::Failed) {
1889         switch (command->operation()) {
1890         case KMTrashMsgCommand::MoveToTrash:
1891             showMessageActivities(i18n("Moving messages to trash failed."));
1892             break;
1893         case KMTrashMsgCommand::Delete:
1894             showMessageActivities(i18n("Deleting messages failed."));
1895             break;
1896         case KMTrashMsgCommand::Both:
1897         case KMTrashMsgCommand::Unknown:
1898             showMessageActivities(i18n("Deleting or moving messages to trash failed."));
1899             break;
1900         }
1901     } else {
1902         switch (command->operation()) {
1903         case KMTrashMsgCommand::MoveToTrash:
1904             showMessageActivities(i18n("Moving messages to trash canceled."));
1905             break;
1906         case KMTrashMsgCommand::Delete:
1907             showMessageActivities(i18n("Deleting messages canceled."));
1908             break;
1909         case KMTrashMsgCommand::Both:
1910         case KMTrashMsgCommand::Unknown:
1911             showMessageActivities(i18n("Deleting or moving messages to trash canceled."));
1912             break;
1913         }
1914     }
1915 
1916     // The command will autodelete itself and will also kill the set.
1917 }
1918 
slotTrashSelectedMessages()1919 void KMMainWidget::slotTrashSelectedMessages()
1920 {
1921     MessageList::Core::MessageItemSetReference ref = mMessagePane->selectionAsPersistentSet();
1922     if (ref != -1) {
1923         trashMessageSelected(ref);
1924     }
1925 }
1926 
slotTrashThread()1927 void KMMainWidget::slotTrashThread()
1928 {
1929     MessageList::Core::MessageItemSetReference ref = mMessagePane->currentThreadAsPersistentSet();
1930     if (ref != -1) {
1931         trashMessageSelected(ref);
1932     }
1933 }
1934 
1935 //-----------------------------------------------------------------------------
1936 // Message tag setting for messages
1937 //
1938 // FIXME: The "selection" version of these functions is in MessageActions.
1939 //        We should probably move everything there....
toggleMessageSetTag(const Akonadi::Item::List & select,const Akonadi::Tag & tag)1940 void KMMainWidget::toggleMessageSetTag(const Akonadi::Item::List &select, const Akonadi::Tag &tag)
1941 {
1942     if (select.isEmpty()) {
1943         return;
1944     }
1945     auto command = new KMSetTagCommand(Akonadi::Tag::List() << tag, select, KMSetTagCommand::Toggle);
1946     command->start();
1947 }
1948 
slotSelectMoreMessageTagList()1949 void KMMainWidget::slotSelectMoreMessageTagList()
1950 {
1951     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
1952     if (selectedMessages.isEmpty()) {
1953         return;
1954     }
1955 
1956     QPointer<TagSelectDialog> dlg = new TagSelectDialog(this, selectedMessages.count(), selectedMessages.first());
1957     dlg->setActionCollection(QList<KActionCollection *>{actionCollection()});
1958     if (dlg->exec()) {
1959         const Akonadi::Tag::List lst = dlg->selectedTag();
1960         auto command = new KMSetTagCommand(lst, selectedMessages, KMSetTagCommand::CleanExistingAndAddNew);
1961         command->start();
1962     }
1963     delete dlg;
1964 }
1965 
slotUpdateMessageTagList(const Akonadi::Tag & tag)1966 void KMMainWidget::slotUpdateMessageTagList(const Akonadi::Tag &tag)
1967 {
1968     // Create a persistent set from the current thread.
1969     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
1970     if (selectedMessages.isEmpty()) {
1971         return;
1972     }
1973     toggleMessageSetTag(selectedMessages, tag);
1974 }
1975 
refreshMessageListSelection()1976 void KMMainWidget::refreshMessageListSelection()
1977 {
1978     mAkonadiStandardActionManager->setItemSelectionModel(mMessagePane->currentItemSelectionModel());
1979     slotMessageSelected(mMessagePane->currentItem());
1980     Q_EMIT captionChangeRequest(MailCommon::Util::fullCollectionPath(mMessagePane->currentFolder()));
1981 }
1982 
1983 //-----------------------------------------------------------------------------
1984 // Status setting for threads
1985 //
1986 // FIXME: The "selection" version of these functions is in MessageActions.
1987 //        We should probably move everything there....
setMessageSetStatus(const Akonadi::Item::List & select,Akonadi::MessageStatus status,bool toggle)1988 void KMMainWidget::setMessageSetStatus(const Akonadi::Item::List &select, Akonadi::MessageStatus status, bool toggle)
1989 {
1990     auto command = new KMSetStatusCommand(status, select, toggle);
1991     command->start();
1992 }
1993 
setCurrentThreadStatus(Akonadi::MessageStatus status,bool toggle)1994 void KMMainWidget::setCurrentThreadStatus(Akonadi::MessageStatus status, bool toggle)
1995 {
1996     const Akonadi::Item::List select = mMessagePane->currentThreadAsMessageList();
1997     if (select.isEmpty()) {
1998         return;
1999     }
2000     setMessageSetStatus(select, status, toggle);
2001 }
2002 
slotSetThreadStatusUnread()2003 void KMMainWidget::slotSetThreadStatusUnread()
2004 {
2005     setCurrentThreadStatus(MessageStatus::statusRead(), true);
2006 }
2007 
slotSetThreadStatusImportant()2008 void KMMainWidget::slotSetThreadStatusImportant()
2009 {
2010     setCurrentThreadStatus(MessageStatus::statusImportant(), true);
2011 }
2012 
slotSetThreadStatusRead()2013 void KMMainWidget::slotSetThreadStatusRead()
2014 {
2015     setCurrentThreadStatus(MessageStatus::statusRead(), false);
2016 }
2017 
slotSetThreadStatusToAct()2018 void KMMainWidget::slotSetThreadStatusToAct()
2019 {
2020     setCurrentThreadStatus(MessageStatus::statusToAct(), true);
2021 }
2022 
slotSetThreadStatusWatched()2023 void KMMainWidget::slotSetThreadStatusWatched()
2024 {
2025     setCurrentThreadStatus(MessageStatus::statusWatched(), true);
2026     if (mWatchThreadAction->isChecked()) {
2027         mIgnoreThreadAction->setChecked(false);
2028     }
2029 }
2030 
slotSetThreadStatusIgnored()2031 void KMMainWidget::slotSetThreadStatusIgnored()
2032 {
2033     setCurrentThreadStatus(MessageStatus::statusIgnored(), true);
2034     if (mIgnoreThreadAction->isChecked()) {
2035         mWatchThreadAction->setChecked(false);
2036     }
2037 }
2038 
2039 //-----------------------------------------------------------------------------
slotRedirectMessage()2040 void KMMainWidget::slotRedirectMessage()
2041 {
2042     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
2043     if (selectedMessages.isEmpty()) {
2044         return;
2045     }
2046 
2047     auto command = new KMRedirectCommand(this, selectedMessages);
2048     command->start();
2049 }
2050 
slotNewMessageToRecipients()2051 void KMMainWidget::slotNewMessageToRecipients()
2052 {
2053     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
2054     if (selectedMessages.count() != 1) {
2055         return;
2056     }
2057 
2058     auto *job = new ComposeNewMessageJob;
2059     job->setFolderSettings(mCurrentFolderSettings);
2060     job->setCurrentCollection(mCurrentCollection);
2061     job->setRecipientsFromMessage(selectedMessages.constFirst());
2062     job->start();
2063 }
2064 
2065 //-----------------------------------------------------------------------------
slotCustomReplyToMsg(const QString & tmpl)2066 void KMMainWidget::slotCustomReplyToMsg(const QString &tmpl)
2067 {
2068     const Akonadi::Item msg = mMessagePane->currentItem();
2069     if (!msg.isValid()) {
2070         return;
2071     }
2072 
2073     const QString text = mMsgView ? mMsgView->copyText() : QString();
2074 
2075     qCDebug(KMAIL_LOG) << "Reply with template:" << tmpl;
2076 
2077     auto command = new KMReplyCommand(this, msg, MessageComposer::ReplySmart, text, false, tmpl);
2078     command->start();
2079 }
2080 
2081 //-----------------------------------------------------------------------------
slotCustomReplyAllToMsg(const QString & tmpl)2082 void KMMainWidget::slotCustomReplyAllToMsg(const QString &tmpl)
2083 {
2084     const Akonadi::Item msg = mMessagePane->currentItem();
2085     if (!msg.isValid()) {
2086         return;
2087     }
2088 
2089     const QString text = mMsgView ? mMsgView->copyText() : QString();
2090 
2091     qCDebug(KMAIL_LOG) << "Reply to All with template:" << tmpl;
2092 
2093     auto command = new KMReplyCommand(this, msg, MessageComposer::ReplyAll, text, false, tmpl);
2094     command->setReplyAsHtml(messageView()->htmlMail());
2095 
2096     command->start();
2097 }
2098 
2099 //-----------------------------------------------------------------------------
slotCustomForwardMsg(const QString & tmpl)2100 void KMMainWidget::slotCustomForwardMsg(const QString &tmpl)
2101 {
2102     if (!mCurrentFolderSettings) {
2103         return;
2104     }
2105 
2106     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
2107     if (selectedMessages.isEmpty()) {
2108         return;
2109     }
2110     const QString text = mMsgView ? mMsgView->copyText() : QString();
2111     qCDebug(KMAIL_LOG) << "Forward with template:" << tmpl;
2112     auto command = new KMForwardCommand(this, selectedMessages, mCurrentFolderSettings->identity(), tmpl, text);
2113 
2114     command->start();
2115 }
2116 
openFilterDialog(const QByteArray & field,const QString & value)2117 void KMMainWidget::openFilterDialog(const QByteArray &field, const QString &value)
2118 {
2119     FilterIf->openFilterDialog(false);
2120     FilterIf->createFilter(field, value);
2121 }
2122 
2123 //-----------------------------------------------------------------------------
slotSubjectFilter()2124 void KMMainWidget::slotSubjectFilter()
2125 {
2126     const KMime::Message::Ptr msg = mMessagePane->currentMessage();
2127     if (!msg) {
2128         return;
2129     }
2130 
2131     openFilterDialog("Subject", msg->subject()->asUnicodeString());
2132 }
2133 
2134 //-----------------------------------------------------------------------------
slotFromFilter()2135 void KMMainWidget::slotFromFilter()
2136 {
2137     KMime::Message::Ptr msg = mMessagePane->currentMessage();
2138     if (!msg) {
2139         return;
2140     }
2141 
2142     AddrSpecList al = MessageHelper::extractAddrSpecs(msg, "From");
2143     if (al.empty()) {
2144         openFilterDialog("From", msg->from()->asUnicodeString());
2145     } else {
2146         openFilterDialog("From", al.front().asString());
2147     }
2148 }
2149 
2150 //-----------------------------------------------------------------------------
slotToFilter()2151 void KMMainWidget::slotToFilter()
2152 {
2153     KMime::Message::Ptr msg = mMessagePane->currentMessage();
2154     if (!msg) {
2155         return;
2156     }
2157     openFilterDialog("To", msg->to()->asUnicodeString());
2158 }
2159 
slotCcFilter()2160 void KMMainWidget::slotCcFilter()
2161 {
2162     KMime::Message::Ptr msg = mMessagePane->currentMessage();
2163     if (!msg) {
2164         return;
2165     }
2166     openFilterDialog("Cc", msg->cc()->asUnicodeString());
2167 }
2168 
2169 //-----------------------------------------------------------------------------
slotUndo()2170 void KMMainWidget::slotUndo()
2171 {
2172     kmkernel->undoStack()->undo();
2173     updateMessageActions();
2174     updateFolderMenu();
2175 }
2176 
2177 //-----------------------------------------------------------------------------
slotJumpToFolder()2178 void KMMainWidget::slotJumpToFolder()
2179 {
2180     QPointer<MailCommon::FolderSelectionDialog> dialog(selectFromAllFoldersDialog());
2181     dialog->setWindowTitle(i18nc("@title:window", "Jump to Folder"));
2182     if (dialog->exec() && dialog) {
2183         Akonadi::Collection collection = dialog->selectedCollection();
2184         if (collection.isValid()) {
2185             slotSelectCollectionFolder(collection);
2186         }
2187     }
2188 }
2189 
slotSelectCollectionFolder(const Akonadi::Collection & col)2190 void KMMainWidget::slotSelectCollectionFolder(const Akonadi::Collection &col)
2191 {
2192     if (mFolderTreeWidget) {
2193         mFolderTreeWidget->selectCollectionFolder(col);
2194         slotFolderChanged(col); // call it explicitly in case the collection is filtered out in the foldertreeview
2195     }
2196 }
2197 
slotApplyFilters()2198 void KMMainWidget::slotApplyFilters()
2199 {
2200     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
2201     if (selectedMessages.isEmpty()) {
2202         return;
2203     }
2204     applyFilters(selectedMessages);
2205 }
2206 
applyFilterOnCollection(bool recursive)2207 Akonadi::Collection::List KMMainWidget::applyFilterOnCollection(bool recursive)
2208 {
2209     Akonadi::Collection::List cols;
2210     if (recursive) {
2211         cols = KMKernel::self()->subfolders(mCurrentCollection);
2212     } else {
2213         cols << mCurrentCollection;
2214     }
2215     return cols;
2216 }
2217 
slotApplyFiltersOnFolder(bool recursive)2218 void KMMainWidget::slotApplyFiltersOnFolder(bool recursive)
2219 {
2220     if (mCurrentCollection.isValid()) {
2221         const Akonadi::Collection::List cols = applyFilterOnCollection(recursive);
2222         applyFilters(cols);
2223     }
2224 }
2225 
slotApplyFilterOnFolder(bool recursive)2226 void KMMainWidget::slotApplyFilterOnFolder(bool recursive)
2227 {
2228     if (mCurrentCollection.isValid()) {
2229         const Akonadi::Collection::List cols = applyFilterOnCollection(recursive);
2230         auto action = qobject_cast<QAction *>(sender());
2231         applyFilter(cols, action->property("filter_id").toString());
2232     }
2233 }
2234 
applyFilters(const Akonadi::Item::List & selectedMessages)2235 void KMMainWidget::applyFilters(const Akonadi::Item::List &selectedMessages)
2236 {
2237     KCursorSaver saver(Qt::WaitCursor);
2238 
2239     MailCommon::FilterManager::instance()->filter(selectedMessages);
2240 }
2241 
applyFilters(const Akonadi::Collection::List & selectedCols)2242 void KMMainWidget::applyFilters(const Akonadi::Collection::List &selectedCols)
2243 {
2244     KCursorSaver saver(Qt::WaitCursor);
2245     MailCommon::FilterManager::instance()->filter(selectedCols);
2246 }
2247 
applyFilter(const Akonadi::Collection::List & selectedCols,const QString & filter)2248 void KMMainWidget::applyFilter(const Akonadi::Collection::List &selectedCols, const QString &filter)
2249 {
2250     KCursorSaver saver(Qt::WaitCursor);
2251     MailCommon::FilterManager::instance()->filter(selectedCols, {filter});
2252 }
2253 
2254 //-----------------------------------------------------------------------------
slotCheckVacation()2255 void KMMainWidget::slotCheckVacation()
2256 {
2257     updateVacationScriptStatus(false);
2258     if (!kmkernel->askToGoOnline()) {
2259         return;
2260     }
2261 
2262     mVacationManager->checkVacation();
2263 }
2264 
slotEditCurrentVacation()2265 void KMMainWidget::slotEditCurrentVacation()
2266 {
2267     slotEditVacation(QString());
2268 }
2269 
slotEditVacation(const QString & serverName)2270 void KMMainWidget::slotEditVacation(const QString &serverName)
2271 {
2272     if (!kmkernel->askToGoOnline()) {
2273         return;
2274     }
2275 
2276     mVacationManager->slotEditVacation(serverName);
2277 }
2278 
2279 //-----------------------------------------------------------------------------
slotDebugSieve()2280 void KMMainWidget::slotDebugSieve()
2281 {
2282     if (kmkernel->allowToDebug()) {
2283         QPointer<KSieveUi::SieveDebugDialog> mSieveDebugDialog = new KSieveUi::SieveDebugDialog(mSievePasswordProvider, this);
2284         mSieveDebugDialog->exec();
2285         delete mSieveDebugDialog;
2286     }
2287 }
2288 
slotConfigChanged()2289 void KMMainWidget::slotConfigChanged()
2290 {
2291     readConfig();
2292     mMsgActions->setupForwardActions(actionCollection());
2293     mMsgActions->setupForwardingActionsList(mGUIClient);
2294 }
2295 
2296 //-----------------------------------------------------------------------------
slotSaveMsg()2297 void KMMainWidget::slotSaveMsg()
2298 {
2299     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
2300     if (selectedMessages.isEmpty()) {
2301         return;
2302     }
2303     auto saveCommand = new KMSaveMsgCommand(this, selectedMessages);
2304     saveCommand->start();
2305 }
2306 
2307 //-----------------------------------------------------------------------------
slotOpenMsg()2308 void KMMainWidget::slotOpenMsg()
2309 {
2310     auto openCommand = new KMOpenMsgCommand(this, QUrl(), overrideEncoding(), this);
2311 
2312     openCommand->start();
2313 }
2314 
2315 //-----------------------------------------------------------------------------
slotSaveAttachments()2316 void KMMainWidget::slotSaveAttachments()
2317 {
2318     const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
2319     if (selectedMessages.isEmpty()) {
2320         return;
2321     }
2322     if (!mMsgView) {
2323         return;
2324     }
2325     // Avoid re-downloading in the common case that only one message is selected, and the message
2326     // is also displayed in the viewer. For this, create a dummy item without a parent collection / item id,
2327     // so that KMCommand doesn't download it.
2328     KMSaveAttachmentsCommand *saveCommand = nullptr;
2329     if (mMsgView && selectedMessages.size() == 1 && mMsgView->messageItem().hasPayload<KMime::Message::Ptr>()
2330         && selectedMessages.first().id() == mMsgView->messageItem().id()) {
2331         Akonadi::Item dummyItem;
2332         dummyItem.setPayload<KMime::Message::Ptr>(mMsgView->messageItem().payload<KMime::Message::Ptr>());
2333         saveCommand = new KMSaveAttachmentsCommand(this, dummyItem, mMsgView->viewer());
2334     } else {
2335         saveCommand = new KMSaveAttachmentsCommand(this, selectedMessages, mMsgView->viewer());
2336     }
2337 
2338     saveCommand->start();
2339 }
2340 
slotOnlineStatus()2341 void KMMainWidget::slotOnlineStatus()
2342 {
2343     // KMKernel will emit a signal when we toggle the network state that is caught by
2344     // KMMainWidget::slotUpdateOnlineStatus to update our GUI
2345     if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Online) {
2346         // if online; then toggle and set it offline.
2347         kmkernel->stopNetworkJobs();
2348     } else {
2349         kmkernel->resumeNetworkJobs();
2350         slotCheckVacation();
2351     }
2352 }
2353 
slotUpdateOnlineStatus(KMailSettings::EnumNetworkState::type)2354 void KMMainWidget::slotUpdateOnlineStatus(KMailSettings::EnumNetworkState::type)
2355 {
2356     if (!mAkonadiStandardActionManager) {
2357         return;
2358     }
2359     QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::ToggleWorkOffline);
2360     if (KMailSettings::self()->networkState() == KMailSettings::EnumNetworkState::Online) {
2361         action->setText(i18n("Work Offline"));
2362         action->setIcon(QIcon::fromTheme(QStringLiteral("user-offline")));
2363     } else {
2364         action->setText(i18n("Work Online"));
2365         action->setIcon(QIcon::fromTheme(QStringLiteral("user-online")));
2366     }
2367 }
2368 
2369 //-----------------------------------------------------------------------------
slotSendQueued()2370 void KMMainWidget::slotSendQueued()
2371 {
2372     if (kmkernel->msgSender()) {
2373         if (!kmkernel->msgSender()->sendQueued()) {
2374             KNotification::event(QStringLiteral("sent-mail-error"),
2375                                  i18n("Send Email"),
2376                                  i18n("Impossible to send email"),
2377                                  QStringLiteral("kmail"),
2378                                  this,
2379                                  KNotification::CloseOnTimeout);
2380         }
2381     }
2382 }
2383 
2384 //-----------------------------------------------------------------------------
slotSendQueuedVia(MailTransport::Transport * transport)2385 void KMMainWidget::slotSendQueuedVia(MailTransport::Transport *transport)
2386 {
2387     if (transport) {
2388         if (kmkernel->msgSender()) {
2389             if (!kmkernel->msgSender()->sendQueued(transport->id())) {
2390                 KNotification::event(QStringLiteral("sent-mail-error"),
2391                                      i18n("Send Email"),
2392                                      i18n("Impossible to send email"),
2393                                      QStringLiteral("kmail"),
2394                                      this,
2395                                      KNotification::CloseOnTimeout);
2396             }
2397         }
2398     }
2399 }
2400 
2401 //-----------------------------------------------------------------------------
slotShowBusySplash()2402 void KMMainWidget::slotShowBusySplash()
2403 {
2404     if (mReaderWindowActive) {
2405         mMsgView->displayBusyPage();
2406     }
2407 }
2408 
showOfflinePage()2409 void KMMainWidget::showOfflinePage()
2410 {
2411     if (!mReaderWindowActive) {
2412         return;
2413     }
2414 
2415     mMsgView->displayOfflinePage();
2416 }
2417 
showResourceOfflinePage()2418 void KMMainWidget::showResourceOfflinePage()
2419 {
2420     if (!mReaderWindowActive) {
2421         return;
2422     }
2423 
2424     mMsgView->displayResourceOfflinePage();
2425 }
2426 
slotFocusOnNextMessage()2427 void KMMainWidget::slotFocusOnNextMessage()
2428 {
2429     mMessagePane->focusNextMessageItem(MessageList::Core::MessageTypeAny, true, false);
2430 }
2431 
slotFocusOnPrevMessage()2432 void KMMainWidget::slotFocusOnPrevMessage()
2433 {
2434     mMessagePane->focusPreviousMessageItem(MessageList::Core::MessageTypeAny, true, false);
2435 }
2436 
slotSelectFirstMessage()2437 void KMMainWidget::slotSelectFirstMessage()
2438 {
2439     mMessagePane->selectFirstMessageItem(MessageList::Core::MessageTypeAny, true);
2440 }
2441 
slotSelectLastMessage()2442 void KMMainWidget::slotSelectLastMessage()
2443 {
2444     mMessagePane->selectLastMessageItem(MessageList::Core::MessageTypeAny, true);
2445 }
2446 
slotSelectFocusedMessage()2447 void KMMainWidget::slotSelectFocusedMessage()
2448 {
2449     mMessagePane->selectFocusedMessageItem(true);
2450 }
2451 
slotSelectNextMessage()2452 void KMMainWidget::slotSelectNextMessage()
2453 {
2454     mMessagePane->selectNextMessageItem(MessageList::Core::MessageTypeAny, MessageList::Core::ClearExistingSelection, true, false);
2455 }
2456 
slotExtendSelectionToNextMessage()2457 void KMMainWidget::slotExtendSelectionToNextMessage()
2458 {
2459     mMessagePane->selectNextMessageItem(MessageList::Core::MessageTypeAny,
2460                                         MessageList::Core::GrowOrShrinkExistingSelection,
2461                                         true, // center item
2462                                         false // don't loop in folder
2463     );
2464 }
2465 
slotSelectNextUnreadMessage()2466 void KMMainWidget::slotSelectNextUnreadMessage()
2467 {
2468     // The looping logic is: "Don't loop" just never loops, "Loop in current folder"
2469     // loops just in current folder, "Loop in all folders" loops in the current folder
2470     // first and then after confirmation jumps to the next folder.
2471     // A bad point here is that if you answer "No, and don't ask me again" to the confirmation
2472     // dialog then you have "Loop in current folder" and "Loop in all folders" that do
2473     // the same thing and no way to get the old behaviour. However, after a consultation on #kontact,
2474     // for bug-to-bug backward compatibility, the masters decided to keep it b0rken :D
2475     // If nobody complains, it stays like it is: if you complain enough maybe the masters will
2476     // decide to reconsider :)
2477     if (!mMessagePane->selectNextMessageItem(MessageList::Core::MessageTypeUnreadOnly,
2478                                              MessageList::Core::ClearExistingSelection,
2479                                              true, // center item
2480                                              KMailSettings::self()->loopOnGotoUnread() != KMailSettings::EnumLoopOnGotoUnread::DontLoop)) {
2481         // no next unread message was found in the current folder
2482         if ((KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllFolders)
2483             || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllMarkedFolders)) {
2484             mGoToFirstUnreadMessageInSelectedFolder = true;
2485             mFolderTreeWidget->folderTreeView()->selectNextUnreadFolder(true);
2486             mGoToFirstUnreadMessageInSelectedFolder = false;
2487         }
2488     }
2489 }
2490 
slotSelectPreviousMessage()2491 void KMMainWidget::slotSelectPreviousMessage()
2492 {
2493     mMessagePane->selectPreviousMessageItem(MessageList::Core::MessageTypeAny, MessageList::Core::ClearExistingSelection, true, false);
2494 }
2495 
slotExtendSelectionToPreviousMessage()2496 void KMMainWidget::slotExtendSelectionToPreviousMessage()
2497 {
2498     mMessagePane->selectPreviousMessageItem(MessageList::Core::MessageTypeAny,
2499                                             MessageList::Core::GrowOrShrinkExistingSelection,
2500                                             true, // center item
2501                                             false // don't loop in folder
2502     );
2503 }
2504 
slotSelectPreviousUnreadMessage()2505 void KMMainWidget::slotSelectPreviousUnreadMessage()
2506 {
2507     if (!mMessagePane->selectPreviousMessageItem(MessageList::Core::MessageTypeUnreadOnly,
2508                                                  MessageList::Core::ClearExistingSelection,
2509                                                  true, // center item
2510                                                  KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInCurrentFolder)) {
2511         // no next unread message was found in the current folder
2512         if ((KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllFolders)
2513             || (KMailSettings::self()->loopOnGotoUnread() == KMailSettings::EnumLoopOnGotoUnread::LoopInAllMarkedFolders)) {
2514             mGoToFirstUnreadMessageInSelectedFolder = true;
2515             mFolderTreeWidget->folderTreeView()->selectPrevUnreadFolder();
2516             mGoToFirstUnreadMessageInSelectedFolder = false;
2517         }
2518     }
2519 }
2520 
slotDisplayCurrentMessage()2521 void KMMainWidget::slotDisplayCurrentMessage()
2522 {
2523     if (mMessagePane->currentItem().isValid() && !mMessagePane->searchEditHasFocus()) {
2524         slotMessageActivated(mMessagePane->currentItem());
2525     }
2526 }
2527 
2528 // Called by double-clicked or 'Enter' in the messagelist -> pop up reader window
slotMessageActivated(const Akonadi::Item & msg)2529 void KMMainWidget::slotMessageActivated(const Akonadi::Item &msg)
2530 {
2531     if (!mCurrentCollection.isValid() || !msg.isValid()) {
2532         return;
2533     }
2534 
2535     if (CommonKernel->folderIsDraftOrOutbox(mCurrentCollection)) {
2536         mMsgActions->setCurrentMessage(msg);
2537         mMsgActions->editCurrentMessage();
2538         return;
2539     }
2540 
2541     if (CommonKernel->folderIsTemplates(mCurrentCollection)) {
2542         slotUseTemplate();
2543         return;
2544     }
2545 
2546     KMReaderMainWin *win = nullptr;
2547     if (!mMsgView) {
2548         win = new KMReaderMainWin(mFolderDisplayFormatPreference, mFolderHtmlLoadExtPreference);
2549     }
2550     // Try to fetch the mail, even in offline mode, it might be cached
2551     auto cmd = new KMFetchMessageCommand(this, msg, win ? win->viewer() : mMsgView->viewer(), win);
2552     connect(cmd, &KMCommand::completed, this, &KMMainWidget::slotItemsFetchedForActivation);
2553     cmd->start();
2554 }
2555 
slotItemsFetchedForActivation(KMCommand * command)2556 void KMMainWidget::slotItemsFetchedForActivation(KMCommand *command)
2557 {
2558     KMCommand::Result result = command->result();
2559     if (result != KMCommand::OK) {
2560         qCDebug(KMAIL_LOG) << "slotItemsFetchedForActivation result:" << result;
2561         return;
2562     }
2563 
2564     auto fetchCmd = qobject_cast<KMFetchMessageCommand *>(command);
2565     const Item msg = fetchCmd->item();
2566     KMReaderMainWin *win = fetchCmd->readerMainWin();
2567 
2568     if (!win) {
2569         win = new KMReaderMainWin(mFolderDisplayFormatPreference, mFolderHtmlLoadExtPreference);
2570     }
2571     if (mMsgView && mMsgView->viewer()) {
2572         win->viewer()->setWebViewZoomFactor(mMsgView->viewer()->webViewZoomFactor());
2573         win->viewer()->setHtmlLoadExtOverride(mMsgView->viewer()->htmlLoadExtOverride());
2574         win->viewer()->setDisplayFormatMessageOverwrite(mMsgView->viewer()->displayFormatMessageOverwrite());
2575     }
2576     const bool useFixedFont = mMsgView ? mMsgView->isFixedFont() : MessageViewer::MessageViewerSettings::self()->useFixedFont();
2577     win->setUseFixedFont(useFixedFont);
2578     win->showMessage(overrideEncoding(), msg, CommonKernel->collectionFromId(msg.parentCollection().id()));
2579     win->show();
2580 }
2581 
slotMessageStatusChangeRequest(const Akonadi::Item & item,const Akonadi::MessageStatus & set,const Akonadi::MessageStatus & clear)2582 void KMMainWidget::slotMessageStatusChangeRequest(const Akonadi::Item &item, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &clear)
2583 {
2584     if (!item.isValid()) {
2585         return;
2586     }
2587 
2588     if (clear.toQInt32() != Akonadi::MessageStatus().toQInt32()) {
2589         auto command = new KMSetStatusCommand(clear, Akonadi::Item::List() << item, true);
2590         command->start();
2591     }
2592 
2593     if (set.toQInt32() != Akonadi::MessageStatus().toQInt32()) {
2594         auto command = new KMSetStatusCommand(set, Akonadi::Item::List() << item, false);
2595         command->start();
2596     }
2597 }
2598 
2599 //-----------------------------------------------------------------------------
slotSelectAllMessages()2600 void KMMainWidget::slotSelectAllMessages()
2601 {
2602     mMessagePane->selectAll();
2603     updateMessageActions();
2604 }
2605 
slotMessagePopup(const Akonadi::Item & msg,const WebEngineViewer::WebHitTestResult & result,const QPoint & aPoint)2606 void KMMainWidget::slotMessagePopup(const Akonadi::Item &msg, const WebEngineViewer::WebHitTestResult &result, const QPoint &aPoint)
2607 {
2608     QUrl aUrl = result.linkUrl();
2609     QUrl imageUrl = result.imageUrl();
2610     updateMessageMenu();
2611 
2612     const QString email = KEmailAddress::firstEmailAddress(aUrl.path()).toLower();
2613     if (aUrl.scheme() == QLatin1String("mailto") && !email.isEmpty()) {
2614         auto job = new Akonadi::ContactSearchJob(this);
2615         job->setLimit(1);
2616         job->setQuery(Akonadi::ContactSearchJob::Email, email, Akonadi::ContactSearchJob::ExactMatch);
2617         job->setProperty("msg", QVariant::fromValue(msg));
2618         job->setProperty("point", aPoint);
2619         job->setProperty("imageUrl", imageUrl);
2620         job->setProperty("url", aUrl);
2621         job->setProperty("webhitresult", QVariant::fromValue(result));
2622         connect(job, &Akonadi::ContactSearchJob::result, this, &KMMainWidget::slotContactSearchJobForMessagePopupDone);
2623     } else {
2624         showMessagePopup(msg, aUrl, imageUrl, aPoint, false, false, result);
2625     }
2626 }
2627 
slotContactSearchJobForMessagePopupDone(KJob * job)2628 void KMMainWidget::slotContactSearchJobForMessagePopupDone(KJob *job)
2629 {
2630     const Akonadi::ContactSearchJob *searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
2631     const bool contactAlreadyExists = !searchJob->contacts().isEmpty();
2632 
2633     const Akonadi::Item::List listContact = searchJob->items();
2634     const bool uniqueContactFound = (listContact.count() == 1);
2635     if (uniqueContactFound) {
2636         mMsgView->setContactItem(listContact.first(), searchJob->contacts().at(0));
2637     } else {
2638         mMsgView->clearContactItem();
2639     }
2640     const auto msg = job->property("msg").value<Akonadi::Item>();
2641     const QPoint aPoint = job->property("point").toPoint();
2642     const QUrl imageUrl = job->property("imageUrl").toUrl();
2643     const QUrl url = job->property("url").toUrl();
2644     const auto result = job->property("webhitresult").value<WebEngineViewer::WebHitTestResult>();
2645 
2646     showMessagePopup(msg, url, imageUrl, aPoint, contactAlreadyExists, uniqueContactFound, result);
2647 }
2648 
showMessagePopup(const Akonadi::Item & msg,const QUrl & url,const QUrl & imageUrl,const QPoint & aPoint,bool contactAlreadyExists,bool uniqueContactFound,const WebEngineViewer::WebHitTestResult & result)2649 void KMMainWidget::showMessagePopup(const Akonadi::Item &msg,
2650                                     const QUrl &url,
2651                                     const QUrl &imageUrl,
2652                                     const QPoint &aPoint,
2653                                     bool contactAlreadyExists,
2654                                     bool uniqueContactFound,
2655                                     const WebEngineViewer::WebHitTestResult &result)
2656 {
2657     QMenu menu(this);
2658     bool urlMenuAdded = false;
2659 
2660     if (!url.isEmpty()) {
2661         if (url.scheme() == QLatin1String("mailto")) {
2662             // popup on a mailto URL
2663             menu.addAction(mMsgView->mailToComposeAction());
2664             menu.addAction(mMsgView->mailToReplyAction());
2665             menu.addAction(mMsgView->mailToForwardAction());
2666 
2667             menu.addSeparator();
2668 
2669             if (contactAlreadyExists) {
2670                 if (uniqueContactFound) {
2671                     menu.addAction(mMsgView->editContactAction());
2672                 } else {
2673                     menu.addAction(mMsgView->openAddrBookAction());
2674                 }
2675             } else {
2676                 menu.addAction(mMsgView->addAddrBookAction());
2677                 menu.addAction(mMsgView->addToExistingContactAction());
2678             }
2679             menu.addSeparator();
2680             menu.addMenu(mMsgView->viewHtmlOption());
2681             menu.addSeparator();
2682             menu.addAction(mMsgView->copyURLAction());
2683             urlMenuAdded = true;
2684         } else if (url.scheme() != QLatin1String("attachment")) {
2685             // popup on a not-mailto URL
2686             menu.addAction(mMsgView->urlOpenAction());
2687             menu.addAction(mMsgView->addBookmarksAction());
2688             menu.addAction(mMsgView->urlSaveAsAction());
2689             menu.addAction(mMsgView->copyURLAction());
2690             menu.addSeparator();
2691             menu.addAction(mMsgView->shareServiceUrlMenu());
2692             menu.addActions(mMsgView->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedUrl));
2693             if (!imageUrl.isEmpty()) {
2694                 menu.addSeparator();
2695                 menu.addAction(mMsgView->copyImageLocation());
2696                 menu.addAction(mMsgView->downloadImageToDiskAction());
2697                 menu.addAction(mMsgView->shareImage());
2698             }
2699             urlMenuAdded = true;
2700         }
2701         qCDebug(KMAIL_LOG) << "URL is:" << url;
2702     }
2703     const QString selectedText = mMsgView ? mMsgView->copyText() : QString();
2704     if (mMsgView && !selectedText.isEmpty()) {
2705         if (urlMenuAdded) {
2706             menu.addSeparator();
2707         }
2708         menu.addAction(mMsgActions->replyMenu());
2709         menu.addSeparator();
2710 
2711         menu.addAction(mMsgView->copyAction());
2712         menu.addAction(mMsgView->selectAllAction());
2713         menu.addSeparator();
2714         mMsgActions->addWebShortcutsMenu(&menu, selectedText);
2715         menu.addSeparator();
2716         menu.addActions(mMsgView->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedSelection));
2717         if (KPIMTextEdit::TextToSpeech::self()->isReady()) {
2718             menu.addSeparator();
2719             menu.addAction(mMsgView->speakTextAction());
2720         }
2721         menu.addSeparator();
2722         menu.addAction(mMsgView->shareTextAction());
2723     } else if (!urlMenuAdded) {
2724         // popup somewhere else (i.e., not a URL) on the message
2725         if (!mMessagePane->currentMessage()) {
2726             // no messages
2727             return;
2728         }
2729         Akonadi::Collection parentCol = msg.parentCollection();
2730         if (parentCol.isValid() && CommonKernel->folderIsTemplates(parentCol)) {
2731             menu.addAction(mMsgActions->newMessageFromTemplateAction());
2732         } else {
2733             menu.addAction(mMsgActions->replyMenu());
2734             menu.addAction(mMsgActions->forwardMenu());
2735         }
2736         if (parentCol.isValid() && CommonKernel->folderIsSentMailFolder(parentCol)) {
2737             menu.addAction(mMsgActions->sendAgainAction());
2738         } else {
2739             menu.addAction(mMsgActions->editAsNewAction());
2740         }
2741         menu.addAction(mailingListActionMenu());
2742         menu.addSeparator();
2743 
2744         menu.addAction(mCopyActionMenu);
2745         menu.addAction(mMoveActionMenu);
2746 
2747         menu.addSeparator();
2748 
2749         menu.addAction(mMsgActions->messageStatusMenu());
2750         menu.addSeparator();
2751         if (mMsgView) {
2752             if (!imageUrl.isEmpty()) {
2753                 menu.addSeparator();
2754                 menu.addAction(mMsgView->copyImageLocation());
2755                 menu.addAction(mMsgView->downloadImageToDiskAction());
2756                 menu.addAction(mMsgView->shareImage());
2757                 menu.addSeparator();
2758             }
2759             menu.addSeparator();
2760             menu.addAction(mMsgActions->printPreviewAction());
2761             menu.addAction(mMsgActions->printAction());
2762             menu.addSeparator();
2763         }
2764         menu.addAction(mSaveAsAction);
2765         menu.addSeparator();
2766         menu.addAction(mSaveAttachmentsAction);
2767         menu.addSeparator();
2768         menu.addAction(mMsgActions->exportToPdfAction());
2769         menu.addSeparator();
2770         if (parentCol.isValid() && CommonKernel->folderIsTrash(parentCol)) {
2771             menu.addAction(mDeleteAction);
2772         } else {
2773             menu.addAction(akonadiStandardAction(Akonadi::StandardMailActionManager::MoveToTrash));
2774         }
2775         menu.addSeparator();
2776 
2777         if (mMsgView) {
2778             if (mMsgView->dkimViewerMenu()) {
2779                 menu.addMenu(mMsgView->dkimViewerMenu()->menu());
2780                 menu.addSeparator();
2781             }
2782             if (mMsgView->remoteContentMenu()) {
2783                 menu.addMenu(mMsgView->remoteContentMenu());
2784                 menu.addSeparator();
2785             }
2786             menu.addActions(mPluginCheckBeforeDeletingManagerInterface->actions());
2787             menu.addSeparator();
2788 
2789             menu.addActions(mMsgView->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedMessage));
2790             menu.addSeparator();
2791         }
2792         menu.addAction(mMsgActions->addFollowupReminderAction());
2793         if (kmkernel->allowToDebug()) {
2794             menu.addSeparator();
2795             menu.addAction(mMsgActions->debugAkonadiSearchAction());
2796             menu.addSeparator();
2797             menu.addAction(mMsgView->developmentToolsAction());
2798         }
2799     }
2800     if (mMsgView) {
2801         const QList<QAction *> interceptorUrlActions = mMsgView->interceptorUrlActions(result);
2802         if (!interceptorUrlActions.isEmpty()) {
2803             menu.addSeparator();
2804             menu.addActions(interceptorUrlActions);
2805         }
2806     }
2807 
2808     KAcceleratorManager::manage(&menu);
2809     menu.exec(aPoint, nullptr);
2810 }
2811 
setZoomChanged(qreal zoomFactor)2812 void KMMainWidget::setZoomChanged(qreal zoomFactor)
2813 {
2814     if (mZoomLabelIndicator) {
2815         mZoomLabelIndicator->setZoom(zoomFactor);
2816     }
2817 }
2818 
setupActions()2819 void KMMainWidget::setupActions()
2820 {
2821     KMailPluginInterface::self()->setParentWidget(this);
2822     KMailPluginInterface::self()->createPluginInterface();
2823     mMsgActions = new KMail::MessageActions(actionCollection(), this);
2824     mMsgActions->fillAkonadiStandardAction(mAkonadiStandardActionManager);
2825     mMsgActions->setMessageView(mMsgView);
2826 
2827     //----- File Menu
2828     mSaveAsAction = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save &As..."), this);
2829     actionCollection()->addAction(QStringLiteral("file_save_as"), mSaveAsAction);
2830     connect(mSaveAsAction, &QAction::triggered, this, &KMMainWidget::slotSaveMsg);
2831     actionCollection()->setDefaultShortcut(mSaveAsAction, KStandardShortcut::save().first());
2832 
2833     mOpenAction = KStandardAction::open(this, &KMMainWidget::slotOpenMsg, actionCollection());
2834 
2835     mOpenRecentMenu = new KRecentFilesMenu(this);
2836     actionCollection()->addAction(QStringLiteral("kmail_file_open_recent"), mOpenRecentMenu->menuAction());
2837     connect(mOpenRecentMenu, &KRecentFilesMenu::urlTriggered, this, &KMMainWidget::slotOpenRecentMessage);
2838     {
2839         auto action = new QAction(i18n("&Expire All Folders"), this);
2840         actionCollection()->addAction(QStringLiteral("expire_all_folders"), action);
2841         connect(action, &QAction::triggered, this, &KMMainWidget::slotExpireAll);
2842     }
2843     {
2844         auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-receive")), i18n("Check &Mail"), this);
2845         actionCollection()->addAction(QStringLiteral("check_mail"), action);
2846         connect(action, &QAction::triggered, this, &KMMainWidget::slotCheckMail);
2847         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_L));
2848     }
2849 
2850     mAccountActionMenu = new KActionMenuAccount(this);
2851     mAccountActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-receive")));
2852     mAccountActionMenu->setText(i18n("Check Mail In"));
2853 
2854     mAccountActionMenu->setIconText(i18n("Check Mail"));
2855     mAccountActionMenu->setToolTip(i18n("Check Mail"));
2856     actionCollection()->addAction(QStringLiteral("check_mail_in"), mAccountActionMenu);
2857     connect(mAccountActionMenu, &KActionMenu::triggered, this, &KMMainWidget::slotCheckMail);
2858 
2859     mSendQueued = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Queued Messages"), this);
2860     actionCollection()->addAction(QStringLiteral("send_queued"), mSendQueued);
2861     connect(mSendQueued, &QAction::triggered, this, &KMMainWidget::slotSendQueued);
2862     {
2863         QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::ToggleWorkOffline);
2864         mAkonadiStandardActionManager->interceptAction(Akonadi::StandardActionManager::ToggleWorkOffline);
2865         action->setCheckable(false);
2866         connect(action, &QAction::triggered, this, &KMMainWidget::slotOnlineStatus);
2867         action->setText(i18n("Online status (unknown)"));
2868     }
2869 
2870     mSendActionMenu = new KActionMenuTransport(this);
2871     mSendActionMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send")));
2872     mSendActionMenu->setText(i18n("Send Queued Messages Via"));
2873     actionCollection()->addAction(QStringLiteral("send_queued_via"), mSendActionMenu);
2874 
2875     connect(mSendActionMenu, &KActionMenuTransport::transportSelected, this, &KMMainWidget::slotSendQueuedVia);
2876 
2877     //----- Tools menu
2878     if (parent()->inherits("KMMainWin")) {
2879         auto action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this);
2880         actionCollection()->addAction(QStringLiteral("addressbook"), action);
2881         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotRunAddressBook);
2882         if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) {
2883             action->setEnabled(false);
2884         }
2885     }
2886 
2887     {
2888         auto action = new QAction(QIcon::fromTheme(QStringLiteral("pgp-keys")), i18n("Certificate Manager"), this);
2889         actionCollection()->addAction(QStringLiteral("tools_start_certman"), action);
2890         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotStartCertManager);
2891         // disable action if no certman binary is around
2892         if (QStandardPaths::findExecutable(QStringLiteral("kleopatra")).isEmpty()) {
2893             action->setEnabled(false);
2894         }
2895     }
2896 
2897     {
2898         auto action = new QAction(QIcon::fromTheme(QStringLiteral("document-import")), i18n("&Import Messages..."), this);
2899         actionCollection()->addAction(QStringLiteral("import"), action);
2900         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotImport);
2901         if (QStandardPaths::findExecutable(QStringLiteral("akonadiimportwizard")).isEmpty()) {
2902             action->setEnabled(false);
2903         }
2904     }
2905 
2906     {
2907         if (kmkernel->allowToDebug()) {
2908             auto action = new QAction(i18n("&Debug Sieve..."), this);
2909             actionCollection()->addAction(QStringLiteral("tools_debug_sieve"), action);
2910             connect(action, &QAction::triggered, this, &KMMainWidget::slotDebugSieve);
2911         }
2912     }
2913 
2914     {
2915         auto action = new QAction(i18n("Filter &Log Viewer..."), this);
2916         actionCollection()->addAction(QStringLiteral("filter_log_viewer"), action);
2917         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotFilterLogViewer);
2918     }
2919     {
2920         auto action = new QAction(i18n("&Import from another Email Client..."), this);
2921         actionCollection()->addAction(QStringLiteral("importWizard"), action);
2922         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotImportWizard);
2923     }
2924     if (KSieveUi::Util::allowOutOfOfficeSettings()) {
2925         auto action = new QAction(i18n("Edit \"Out of Office\" Replies..."), this);
2926         actionCollection()->addAction(QStringLiteral("tools_edit_vacation"), action);
2927         connect(action, &QAction::triggered, this, &KMMainWidget::slotEditCurrentVacation);
2928     }
2929 
2930     {
2931         auto action = new QAction(i18n("&Configure Automatic Archiving..."), this);
2932         actionCollection()->addAction(QStringLiteral("tools_automatic_archiving"), action);
2933         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotConfigureAutomaticArchiving);
2934     }
2935 
2936     {
2937         auto action = new QAction(i18n("Delayed Messages..."), this);
2938         actionCollection()->addAction(QStringLiteral("message_delayed"), action);
2939         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotConfigureSendLater);
2940     }
2941 
2942     {
2943         auto action = new QAction(i18n("Followup Reminder Messages..."), this);
2944         actionCollection()->addAction(QStringLiteral("followup_reminder_messages"), action);
2945         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotConfigureFollowupReminder);
2946     }
2947 
2948     // Disable the standard action delete key sortcut.
2949     QAction *const standardDelAction = akonadiStandardAction(Akonadi::StandardActionManager::DeleteItems);
2950     standardDelAction->setShortcut(QKeySequence());
2951 
2952     //----- Edit Menu
2953 
2954     mDeleteAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action Hard delete, bypassing trash", "&Delete"), this);
2955     actionCollection()->addAction(QStringLiteral("delete"), mDeleteAction);
2956     connect(mDeleteAction, &QAction::triggered, this, &KMMainWidget::slotDeleteMessages);
2957     actionCollection()->setDefaultShortcut(mDeleteAction, QKeySequence(Qt::SHIFT | Qt::Key_Delete));
2958 
2959     mTrashThreadAction = new QAction(i18n("M&ove Thread to Trash"), this);
2960     actionCollection()->addAction(QStringLiteral("move_thread_to_trash"), mTrashThreadAction);
2961     actionCollection()->setDefaultShortcut(mTrashThreadAction, QKeySequence(Qt::CTRL | Qt::Key_Delete));
2962     mTrashThreadAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
2963     KMail::Util::addQActionHelpText(mTrashThreadAction, i18n("Move thread to trashcan"));
2964     connect(mTrashThreadAction, &QAction::triggered, this, &KMMainWidget::slotTrashThread);
2965 
2966     mDeleteThreadAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete-shred")), i18n("Delete T&hread"), this);
2967     actionCollection()->addAction(QStringLiteral("delete_thread"), mDeleteThreadAction);
2968     // Don't use new connect api.
2969     connect(mDeleteThreadAction, &QAction::triggered, this, &KMMainWidget::slotDeleteThread);
2970     actionCollection()->setDefaultShortcut(mDeleteThreadAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Delete));
2971 
2972     mSearchMessages = new QAction(QIcon::fromTheme(QStringLiteral("edit-find-mail")), i18n("&Find Messages..."), this);
2973     actionCollection()->addAction(QStringLiteral("search_messages"), mSearchMessages);
2974     connect(mSearchMessages, &QAction::triggered, this, &KMMainWidget::slotRequestFullSearchFromQuickSearch);
2975     actionCollection()->setDefaultShortcut(mSearchMessages, QKeySequence(Qt::Key_S));
2976 
2977     mSelectAllMessages = new QAction(i18n("Select &All Messages"), this);
2978     actionCollection()->addAction(QStringLiteral("mark_all_messages"), mSelectAllMessages);
2979     connect(mSelectAllMessages, &QAction::triggered, this, &KMMainWidget::slotSelectAllMessages);
2980     actionCollection()->setDefaultShortcut(mSelectAllMessages, QKeySequence(Qt::CTRL | Qt::Key_A));
2981 
2982     //----- Folder Menu
2983 
2984     mFolderMailingListPropertiesAction = new QAction(i18n("&Mailing List Management..."), this);
2985     actionCollection()->addAction(QStringLiteral("folder_mailinglist_properties"), mFolderMailingListPropertiesAction);
2986     connect(mFolderMailingListPropertiesAction,
2987             &QAction::triggered,
2988             mManageShowCollectionProperties,
2989             &ManageShowCollectionProperties::slotFolderMailingListProperties);
2990     // mFolderMailingListPropertiesAction->setIcon(QIcon::fromTheme("document-properties-mailing-list"));
2991 
2992     mShowFolderShortcutDialogAction = new QAction(QIcon::fromTheme(QStringLiteral("configure-shortcuts")), i18n("&Assign Shortcut..."), this);
2993     actionCollection()->addAction(QStringLiteral("folder_shortcut_command"), mShowFolderShortcutDialogAction);
2994     connect(mShowFolderShortcutDialogAction,
2995             &QAction::triggered,
2996             mManageShowCollectionProperties,
2997             &ManageShowCollectionProperties::slotShowFolderShortcutDialog);
2998     // FIXME: this action is not currently enabled in the rc file, but even if
2999     // it were there is inconsistency between the action name and action.
3000     // "Expiration Settings" implies that this will lead to a settings dialog
3001     // and it should be followed by a "...", but slotExpireFolder() performs
3002     // an immediate expiry.
3003     //
3004     // TODO: expire action should be disabled if there is no content or if
3005     // the folder can't delete messages.
3006     //
3007     // Leaving the action here for the moment, it and the "Expire" option in the
3008     // folder popup menu should be combined or at least made consistent.  Same for
3009     // slotExpireFolder() and FolderViewItem::slotShowExpiryProperties().
3010     mExpireFolderAction = new QAction(i18n("&Expiration Settings"), this);
3011     actionCollection()->addAction(QStringLiteral("expire"), mExpireFolderAction);
3012     connect(mExpireFolderAction, &QAction::triggered, this, &KMMainWidget::slotExpireFolder);
3013 
3014     mAkonadiStandardActionManager->interceptAction(Akonadi::StandardMailActionManager::MoveToTrash);
3015     connect(mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveToTrash),
3016             &QAction::triggered,
3017             this,
3018             &KMMainWidget::slotTrashSelectedMessages);
3019 
3020     mAkonadiStandardActionManager->interceptAction(Akonadi::StandardMailActionManager::MoveAllToTrash);
3021     connect(mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash),
3022             &QAction::triggered,
3023             this,
3024             &KMMainWidget::slotEmptyFolder);
3025 
3026     mAkonadiStandardActionManager->interceptAction(Akonadi::StandardActionManager::DeleteCollections);
3027     connect(mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections),
3028             &QAction::triggered,
3029             this,
3030             &KMMainWidget::slotRemoveFolder);
3031 
3032     // ### PORT ME: Add this to the context menu. Not possible right now because
3033     //              the context menu uses XMLGUI, and that would add the entry to
3034     //              all collection context menus
3035     mArchiveFolderAction = new QAction(i18n("&Archive Folder..."), this);
3036     actionCollection()->addAction(QStringLiteral("archive_folder"), mArchiveFolderAction);
3037     connect(mArchiveFolderAction, &QAction::triggered, this, &KMMainWidget::slotArchiveFolder);
3038 
3039     mDisplayMessageFormatMenu = new DisplayMessageFormatActionMenu(this);
3040     connect(mDisplayMessageFormatMenu, &DisplayMessageFormatActionMenu::changeDisplayMessageFormat, this, &KMMainWidget::slotChangeDisplayMessageFormat);
3041     actionCollection()->addAction(QStringLiteral("display_format_message"), mDisplayMessageFormatMenu);
3042 
3043     mPreferHtmlLoadExtAction = new KToggleAction(i18n("Load E&xternal References"), this);
3044     actionCollection()->addAction(QStringLiteral("prefer_html_external_refs"), mPreferHtmlLoadExtAction);
3045     connect(mPreferHtmlLoadExtAction, &KToggleAction::triggered, this, &KMMainWidget::slotOverrideHtmlLoadExt);
3046 
3047     {
3048         QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyCollections);
3049         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_C));
3050     }
3051     {
3052         QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::Paste);
3053         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_V));
3054     }
3055     {
3056         QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyItems);
3057         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_C));
3058     }
3059     {
3060         QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CutItems);
3061         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_X));
3062     }
3063 
3064     {
3065         QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyItemToMenu);
3066         action->setText(i18n("Copy Message To..."));
3067         action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::MoveItemToMenu);
3068         action->setText(i18n("Move Message To..."));
3069     }
3070 
3071     //----- Message Menu
3072     {
3073         auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Message..."), this);
3074         actionCollection()->addAction(QStringLiteral("new_message"), action);
3075         action->setIconText(i18nc("@action:intoolbar New Empty Message", "New"));
3076         connect(action, &QAction::triggered, this, &KMMainWidget::slotCompose);
3077         // do not set a New shortcut if kmail is a component
3078         if (kmkernel->xmlGuiInstanceName().isEmpty()) {
3079             actionCollection()->setDefaultShortcut(action, KStandardShortcut::openNew().first());
3080         }
3081     }
3082 
3083     mTemplateMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Message From &Template"), actionCollection());
3084     mTemplateMenu->setPopupMode(QToolButton::DelayedPopup);
3085     actionCollection()->addAction(QStringLiteral("new_from_template"), mTemplateMenu);
3086     connect(mTemplateMenu->menu(), &QMenu::aboutToShow, this, &KMMainWidget::slotShowNewFromTemplate);
3087     connect(mTemplateMenu->menu(), &QMenu::triggered, this, &KMMainWidget::slotNewFromTemplate);
3088 
3089     mMessageNewList = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new-list")), i18n("New Message t&o Mailing-List..."), this);
3090     actionCollection()->addAction(QStringLiteral("post_message"), mMessageNewList);
3091     connect(mMessageNewList, &QAction::triggered, this, &KMMainWidget::slotPostToML);
3092     actionCollection()->setDefaultShortcut(mMessageNewList, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_N));
3093 
3094     //----- Create filter actions
3095     mFilterMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("&Create Filter"), this);
3096     actionCollection()->addAction(QStringLiteral("create_filter"), mFilterMenu);
3097     connect(mFilterMenu, &QAction::triggered, this, &KMMainWidget::slotFilter);
3098     {
3099         auto action = new QAction(i18n("Filter on &Subject..."), this);
3100         actionCollection()->addAction(QStringLiteral("subject_filter"), action);
3101         connect(action, &QAction::triggered, this, &KMMainWidget::slotSubjectFilter);
3102         mFilterMenu->addAction(action);
3103     }
3104 
3105     {
3106         auto action = new QAction(i18n("Filter on &From..."), this);
3107         actionCollection()->addAction(QStringLiteral("from_filter"), action);
3108         connect(action, &QAction::triggered, this, &KMMainWidget::slotFromFilter);
3109         mFilterMenu->addAction(action);
3110     }
3111     {
3112         auto action = new QAction(i18n("Filter on &To..."), this);
3113         actionCollection()->addAction(QStringLiteral("to_filter"), action);
3114         connect(action, &QAction::triggered, this, &KMMainWidget::slotToFilter);
3115         mFilterMenu->addAction(action);
3116     }
3117     {
3118         auto action = new QAction(i18n("Filter on &Cc..."), this);
3119         actionCollection()->addAction(QStringLiteral("cc_filter"), action);
3120         connect(action, &QAction::triggered, this, &KMMainWidget::slotCcFilter);
3121         mFilterMenu->addAction(action);
3122     }
3123     mFilterMenu->addAction(mMsgActions->listFilterAction());
3124 
3125     //----- "Mark Thread" submenu
3126     mThreadStatusMenu = new KActionMenu(i18n("Mark &Thread"), this);
3127     actionCollection()->addAction(QStringLiteral("thread_status"), mThreadStatusMenu);
3128 
3129     mMarkThreadAsReadAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-mark-read")), i18n("Mark Thread as &Read"), this);
3130     actionCollection()->addAction(QStringLiteral("thread_read"), mMarkThreadAsReadAction);
3131     connect(mMarkThreadAsReadAction, &QAction::triggered, this, &KMMainWidget::slotSetThreadStatusRead);
3132     KMail::Util::addQActionHelpText(mMarkThreadAsReadAction, i18n("Mark all messages in the selected thread as read"));
3133     mThreadStatusMenu->addAction(mMarkThreadAsReadAction);
3134 
3135     mMarkThreadAsUnreadAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-mark-unread")), i18n("Mark Thread as &Unread"), this);
3136     actionCollection()->addAction(QStringLiteral("thread_unread"), mMarkThreadAsUnreadAction);
3137     connect(mMarkThreadAsUnreadAction, &QAction::triggered, this, &KMMainWidget::slotSetThreadStatusUnread);
3138     KMail::Util::addQActionHelpText(mMarkThreadAsUnreadAction, i18n("Mark all messages in the selected thread as unread"));
3139     mThreadStatusMenu->addAction(mMarkThreadAsUnreadAction);
3140 
3141     mThreadStatusMenu->addSeparator();
3142 
3143     //----- "Mark Thread" toggle actions
3144     mToggleThreadImportantAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-mark-important")), i18n("Mark Thread as &Important"), this);
3145     actionCollection()->addAction(QStringLiteral("thread_flag"), mToggleThreadImportantAction);
3146     connect(mToggleThreadImportantAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusImportant);
3147     mToggleThreadImportantAction->setCheckedState(KGuiItem(i18n("Remove &Important Thread Mark")));
3148     mThreadStatusMenu->addAction(mToggleThreadImportantAction);
3149 
3150     mToggleThreadToActAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-mark-task")), i18n("Mark Thread as &Action Item"), this);
3151     actionCollection()->addAction(QStringLiteral("thread_toact"), mToggleThreadToActAction);
3152     connect(mToggleThreadToActAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusToAct);
3153     mToggleThreadToActAction->setCheckedState(KGuiItem(i18n("Remove &Action Item Thread Mark")));
3154     mThreadStatusMenu->addAction(mToggleThreadToActAction);
3155 
3156     //------- "Watch and ignore thread" actions
3157     mWatchThreadAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-thread-watch")), i18n("&Watch Thread"), this);
3158     actionCollection()->addAction(QStringLiteral("thread_watched"), mWatchThreadAction);
3159     connect(mWatchThreadAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusWatched);
3160 
3161     mIgnoreThreadAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("mail-thread-ignored")), i18n("&Ignore Thread"), this);
3162     actionCollection()->addAction(QStringLiteral("thread_ignored"), mIgnoreThreadAction);
3163     connect(mIgnoreThreadAction, &KToggleAction::triggered, this, &KMMainWidget::slotSetThreadStatusIgnored);
3164 
3165     mThreadStatusMenu->addSeparator();
3166     mThreadStatusMenu->addAction(mWatchThreadAction);
3167     mThreadStatusMenu->addAction(mIgnoreThreadAction);
3168 
3169     mSaveAttachmentsAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Save A&ttachments..."), this);
3170     actionCollection()->addAction(QStringLiteral("file_save_attachments"), mSaveAttachmentsAction);
3171     connect(mSaveAttachmentsAction, &QAction::triggered, this, &KMMainWidget::slotSaveAttachments);
3172 
3173     mMoveActionMenu = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::MoveItemToMenu);
3174 
3175     mCopyActionMenu = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyItemToMenu);
3176 
3177     mCopyDecryptedActionMenu = new KActionMenu(i18n("Copy Decrypted To..."), this);
3178     actionCollection()->addAction(QStringLiteral("copy_decrypted_to_menu"), mCopyDecryptedActionMenu);
3179     connect(mCopyDecryptedActionMenu->menu(), &QMenu::triggered, this, &KMMainWidget::slotCopyDecryptedTo);
3180 
3181     mApplyAllFiltersAction = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Appl&y All Filters"), this);
3182     actionCollection()->addAction(QStringLiteral("apply_filters"), mApplyAllFiltersAction);
3183     connect(mApplyAllFiltersAction, &QAction::triggered, this, &KMMainWidget::slotApplyFilters);
3184     actionCollection()->setDefaultShortcut(mApplyAllFiltersAction, QKeySequence(Qt::CTRL | Qt::Key_J));
3185 
3186     mApplyFilterActionsMenu = new KActionMenu(i18n("A&pply Filter"), this);
3187     actionCollection()->addAction(QStringLiteral("apply_filter_actions"), mApplyFilterActionsMenu);
3188 
3189     {
3190         auto action = new QAction(i18nc("View->", "&Expand Thread / Group"), this);
3191         actionCollection()->addAction(QStringLiteral("expand_thread"), action);
3192         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Period));
3193         KMail::Util::addQActionHelpText(action, i18n("Expand the current thread or group"));
3194         connect(action, &QAction::triggered, this, &KMMainWidget::slotExpandThread);
3195     }
3196     {
3197         auto action = new QAction(i18nc("View->", "&Collapse Thread / Group"), this);
3198         actionCollection()->addAction(QStringLiteral("collapse_thread"), action);
3199         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Comma));
3200         KMail::Util::addQActionHelpText(action, i18n("Collapse the current thread or group"));
3201         connect(action, &QAction::triggered, this, &KMMainWidget::slotCollapseThread);
3202     }
3203     {
3204         auto action = new QAction(i18nc("View->", "Ex&pand All Threads"), this);
3205         actionCollection()->addAction(QStringLiteral("expand_all_threads"), action);
3206         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Period));
3207         KMail::Util::addQActionHelpText(action, i18n("Expand all threads in the current folder"));
3208         connect(action, &QAction::triggered, this, &KMMainWidget::slotExpandAllThreads);
3209     }
3210     {
3211         auto action = new QAction(i18nc("View->", "C&ollapse All Threads"), this);
3212         actionCollection()->addAction(QStringLiteral("collapse_all_threads"), action);
3213         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Comma));
3214         KMail::Util::addQActionHelpText(action, i18n("Collapse all threads in the current folder"));
3215         connect(action, &QAction::triggered, this, &KMMainWidget::slotCollapseAllThreads);
3216     }
3217 
3218     auto dukeOfMonmoth = new QAction(i18n("&Display Message"), this);
3219     actionCollection()->addAction(QStringLiteral("display_message"), dukeOfMonmoth);
3220     connect(dukeOfMonmoth, &QAction::triggered, this, &KMMainWidget::slotDisplayCurrentMessage);
3221     actionCollection()->setDefaultShortcuts(dukeOfMonmoth, QList<QKeySequence>{QKeySequence(Qt::Key_Enter), QKeySequence(Qt::Key_Return)});
3222 
3223     //----- Go Menu
3224     {
3225         auto action = new QAction(i18n("&Next Message"), this);
3226         actionCollection()->addAction(QStringLiteral("go_next_message"), action);
3227         actionCollection()->setDefaultShortcuts(action, QList<QKeySequence>{QKeySequence(Qt::Key_N), QKeySequence(Qt::Key_Right)});
3228         KMail::Util::addQActionHelpText(action, i18n("Go to the next message"));
3229         connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectNextMessage);
3230     }
3231     {
3232         auto action = new QAction(i18n("Next &Unread Message"), this);
3233         actionCollection()->addAction(QStringLiteral("go_next_unread_message"), action);
3234         actionCollection()->setDefaultShortcuts(action, QList<QKeySequence>{QKeySequence(Qt::Key_Plus), QKeySequence(Qt::Key_Plus | Qt::KeypadModifier)});
3235         if (QApplication::isRightToLeft()) {
3236             action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
3237         } else {
3238             action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
3239         }
3240         action->setIconText(i18nc("@action:inmenu Goto next unread message", "Next"));
3241         KMail::Util::addQActionHelpText(action, i18n("Go to the next unread message"));
3242         connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectNextUnreadMessage);
3243     }
3244     {
3245         auto action = new QAction(i18n("&Previous Message"), this);
3246         actionCollection()->addAction(QStringLiteral("go_prev_message"), action);
3247         KMail::Util::addQActionHelpText(action, i18n("Go to the previous message"));
3248         actionCollection()->setDefaultShortcuts(action, QList<QKeySequence>{QKeySequence(Qt::Key_P), QKeySequence(Qt::Key_Left)});
3249         connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectPreviousMessage);
3250     }
3251     {
3252         auto action = new QAction(i18n("Previous Unread &Message"), this);
3253         actionCollection()->addAction(QStringLiteral("go_prev_unread_message"), action);
3254         actionCollection()->setDefaultShortcuts(action, QList<QKeySequence>{QKeySequence(Qt::Key_Minus), QKeySequence(Qt::Key_Minus | Qt::KeypadModifier)});
3255         if (QApplication::isRightToLeft()) {
3256             action->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
3257         } else {
3258             action->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
3259         }
3260         action->setIconText(i18nc("@action:inmenu Goto previous unread message.", "Previous"));
3261         KMail::Util::addQActionHelpText(action, i18n("Go to the previous unread message"));
3262         connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectPreviousUnreadMessage);
3263     }
3264     {
3265         auto action = new QAction(i18n("Next Unread &Folder"), this);
3266         actionCollection()->addAction(QStringLiteral("go_next_unread_folder"), action);
3267         connect(action, &QAction::triggered, this, &KMMainWidget::slotNextUnreadFolder);
3268         actionCollection()->setDefaultShortcuts(
3269             action,
3270             QList<QKeySequence>{QKeySequence(Qt::ALT | Qt::Key_Plus), QKeySequence(Qt::ALT | Qt::Key_Plus | Qt::KeypadModifier)});
3271         KMail::Util::addQActionHelpText(action, i18n("Go to the next folder with unread messages"));
3272     }
3273     {
3274         auto action = new QAction(i18n("Previous Unread F&older"), this);
3275         actionCollection()->addAction(QStringLiteral("go_prev_unread_folder"), action);
3276         actionCollection()->setDefaultShortcuts(
3277             action,
3278             QList<QKeySequence>{QKeySequence(Qt::ALT | Qt::Key_Minus), QKeySequence(Qt::ALT | Qt::Key_Minus | Qt::KeypadModifier)});
3279         KMail::Util::addQActionHelpText(action, i18n("Go to the previous folder with unread messages"));
3280         connect(action, &QAction::triggered, this, &KMMainWidget::slotPrevUnreadFolder);
3281     }
3282     {
3283         auto action = new QAction(i18nc("Go->", "Next Unread &Text"), this);
3284         actionCollection()->addAction(QStringLiteral("go_next_unread_text"), action);
3285         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Space));
3286         KMail::Util::addQActionHelpText(action, i18n("Go to the next unread text"));
3287         action->setWhatsThis(
3288             i18n("Scroll down current message. "
3289                  "If at end of current message, "
3290                  "go to next unread message."));
3291         connect(action, &QAction::triggered, this, &KMMainWidget::slotReadOn);
3292     }
3293 
3294     //----- Settings Menu
3295     {
3296         auto action = new QAction(QIcon::fromTheme(QStringLiteral("dialog-filters")), i18n("Configure &Filters..."), this);
3297         action->setMenuRole(QAction::NoRole); // do not move to application menu on OS X
3298         actionCollection()->addAction(QStringLiteral("filter"), action);
3299         connect(action, &QAction::triggered, this, &KMMainWidget::slotFilter);
3300     }
3301     {
3302         auto action = new QAction(i18n("Manage &Sieve Scripts..."), this);
3303         actionCollection()->addAction(QStringLiteral("sieveFilters"), action);
3304         connect(action, &QAction::triggered, this, &KMMainWidget::slotManageSieveScripts);
3305     }
3306     {
3307         auto action = new QAction(QIcon::fromTheme(QStringLiteral("list-resource-add")), i18n("&Add Account..."), this);
3308         actionCollection()->addAction(QStringLiteral("accountWizard"), action);
3309         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotAccountWizard);
3310     }
3311     {
3312         mShowIntroductionAction = new QAction(QIcon::fromTheme(QStringLiteral("kmail")), i18n("KMail &Introduction"), this);
3313         actionCollection()->addAction(QStringLiteral("help_kmail_welcomepage"), mShowIntroductionAction);
3314         KMail::Util::addQActionHelpText(mShowIntroductionAction, i18n("Display KMail's Welcome Page"));
3315         connect(mShowIntroductionAction, &QAction::triggered, this, &KMMainWidget::slotIntro);
3316         mShowIntroductionAction->setEnabled(mMsgView != nullptr);
3317     }
3318 
3319     // ----- Standard Actions
3320     {
3321         auto action = new QAction(QIcon::fromTheme(QStringLiteral("preferences-desktop-notification")), i18n("Configure &Notifications..."), this);
3322         action->setMenuRole(QAction::NoRole); // do not move to application menu on OS X
3323         actionCollection()->addAction(QStringLiteral("kmail_configure_notifications"), action);
3324         connect(action, &QAction::triggered, this, &KMMainWidget::slotEditNotifications);
3325     }
3326 
3327     {
3328         auto action = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("&Configure KMail..."), this);
3329         action->setMenuRole(QAction::PreferencesRole); // this one should move to the application menu on OS X
3330         actionCollection()->addAction(QStringLiteral("kmail_configure_kmail"), action);
3331         connect(action, &QAction::triggered, kmkernel, &KMKernel::slotShowConfigurationDialog);
3332     }
3333 
3334     {
3335         mExpireConfigAction = new QAction(i18n("Expire..."), this);
3336         actionCollection()->addAction(QStringLiteral("expire_settings"), mExpireConfigAction);
3337         connect(mExpireConfigAction, &QAction::triggered, mManageShowCollectionProperties, &ManageShowCollectionProperties::slotShowExpiryProperties);
3338     }
3339 
3340     {
3341         auto action = new QAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add Favorite Folder..."), this);
3342         actionCollection()->addAction(QStringLiteral("add_favorite_folder"), action);
3343         connect(action, &QAction::triggered, this, &KMMainWidget::slotAddFavoriteFolder);
3344     }
3345 
3346     {
3347         mServerSideSubscription = new QAction(QIcon::fromTheme(QStringLiteral("folder-bookmarks")), i18n("Serverside Subscription..."), this);
3348         actionCollection()->addAction(QStringLiteral("serverside_subscription"), mServerSideSubscription);
3349         connect(mServerSideSubscription, &QAction::triggered, this, &KMMainWidget::slotServerSideSubscription);
3350     }
3351 
3352     {
3353         mApplyAllFiltersFolderAction = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Apply All Filters"), this);
3354         actionCollection()->addAction(QStringLiteral("apply_filters_folder"), mApplyAllFiltersFolderAction);
3355         connect(mApplyAllFiltersFolderAction, &QAction::triggered, this, [this] {
3356             slotApplyFiltersOnFolder(/* recursive */ false);
3357         });
3358     }
3359 
3360     {
3361         mApplyAllFiltersFolderRecursiveAction = new QAction(QIcon::fromTheme(QStringLiteral("view-filter")), i18n("Apply All Filters"), this);
3362         actionCollection()->addAction(QStringLiteral("apply_filters_folder_recursive"), mApplyAllFiltersFolderRecursiveAction);
3363         connect(mApplyAllFiltersFolderRecursiveAction, &QAction::triggered, this, [this] {
3364             slotApplyFiltersOnFolder(/* recursive */ true);
3365         });
3366     }
3367 
3368     {
3369         mApplyFilterFolderActionsMenu = new KActionMenu(i18n("Apply Filters on Folder"), this);
3370         actionCollection()->addAction(QStringLiteral("apply_filters_on_folder_actions"), mApplyFilterFolderActionsMenu);
3371     }
3372 
3373     {
3374         mApplyFilterFolderRecursiveActionsMenu = new KActionMenu(i18n("Apply Filters on Folder and all its Subfolders"), this);
3375         actionCollection()->addAction(QStringLiteral("apply_filters_on_folder_recursive_actions"), mApplyFilterFolderRecursiveActionsMenu);
3376     }
3377 
3378     {
3379         auto action = new QAction(QIcon::fromTheme(QStringLiteral("kontact")), i18n("Import/Export KMail Data..."), this);
3380         actionCollection()->addAction(QStringLiteral("kmail_export_data"), action);
3381         connect(action, &QAction::triggered, mLaunchExternalComponent, &KMLaunchExternalComponent::slotExportData);
3382     }
3383 
3384     {
3385         auto action = new QAction(QIcon::fromTheme(QStringLiteral("contact-new")), i18n("New AddressBook Contact..."), this);
3386         actionCollection()->addAction(QStringLiteral("kmail_new_addressbook_contact"), action);
3387         connect(action, &QAction::triggered, this, &KMMainWidget::slotCreateAddressBookContact);
3388     }
3389 
3390     QAction *act = actionCollection()->addAction(KStandardAction::Undo, QStringLiteral("kmail_undo"));
3391     connect(act, &QAction::triggered, this, &KMMainWidget::slotUndo);
3392 
3393     menutimer = new QTimer(this);
3394     menutimer->setObjectName(QStringLiteral("menutimer"));
3395     menutimer->setSingleShot(true);
3396     connect(menutimer, &QTimer::timeout, this, &KMMainWidget::updateMessageActionsDelayed);
3397     connect(kmkernel->undoStack(), &KMail::UndoStack::undoStackChanged, this, &KMMainWidget::slotUpdateUndo);
3398 
3399     updateMessageActions();
3400     updateFolderMenu();
3401     mTagActionManager = new KMail::TagActionManager(this, actionCollection(), mMsgActions, mGUIClient);
3402     mFolderShortcutActionManager = new KMail::FolderShortcutActionManager(this, actionCollection());
3403 
3404     {
3405         auto action = new QAction(i18n("Copy Message to Folder"), this);
3406         actionCollection()->addAction(QStringLiteral("copy_message_to_folder"), action);
3407         connect(action, &QAction::triggered, this, &KMMainWidget::slotCopySelectedMessagesToFolder);
3408         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_C));
3409     }
3410     {
3411         auto action = new QAction(QIcon::fromTheme(QStringLiteral("go-jump")), i18n("Jump to Folder..."), this);
3412         actionCollection()->addAction(QStringLiteral("jump_to_folder"), action);
3413         connect(action, &QAction::triggered, this, &KMMainWidget::slotJumpToFolder);
3414         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_J));
3415     }
3416     {
3417         auto action = new QAction(i18n("Abort Current Operation"), this);
3418         actionCollection()->addAction(QStringLiteral("cancel"), action);
3419         connect(action, &QAction::triggered, ProgressManager::instance(), &KPIM::ProgressManager::slotAbortAll);
3420         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Escape));
3421     }
3422     {
3423         auto action = new QAction(i18n("Focus on Next Folder"), this);
3424         actionCollection()->addAction(QStringLiteral("inc_current_folder"), action);
3425         connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusNextFolder);
3426         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Right));
3427     }
3428     {
3429         auto action = new QAction(i18n("Focus on Previous Folder"), this);
3430         actionCollection()->addAction(QStringLiteral("dec_current_folder"), action);
3431         connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusPrevFolder);
3432         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Left));
3433     }
3434     {
3435         auto action = new QAction(i18n("Select Folder with Focus"), this);
3436         actionCollection()->addAction(QStringLiteral("select_current_folder"), action);
3437 
3438         connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotSelectFocusFolder);
3439         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Space));
3440     }
3441     {
3442         auto action = new QAction(i18n("Focus on First Folder"), this);
3443         actionCollection()->addAction(QStringLiteral("focus_first_folder"), action);
3444         connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusFirstFolder);
3445         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Home));
3446     }
3447     {
3448         auto action = new QAction(i18n("Focus on Last Folder"), this);
3449         actionCollection()->addAction(QStringLiteral("focus_last_folder"), action);
3450         connect(action, &QAction::triggered, mFolderTreeWidget->folderTreeView(), &FolderTreeView::slotFocusLastFolder);
3451         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_End));
3452     }
3453     {
3454         auto action = new QAction(i18n("Focus on Next Message"), this);
3455         actionCollection()->addAction(QStringLiteral("inc_current_message"), action);
3456         connect(action, &QAction::triggered, this, &KMMainWidget::slotFocusOnNextMessage);
3457         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::Key_Right));
3458     }
3459     {
3460         auto action = new QAction(i18n("Focus on Previous Message"), this);
3461         actionCollection()->addAction(QStringLiteral("dec_current_message"), action);
3462         connect(action, &QAction::triggered, this, &KMMainWidget::slotFocusOnPrevMessage);
3463         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::Key_Left));
3464     }
3465     {
3466         auto action = new QAction(i18n("Select First Message"), this);
3467         actionCollection()->addAction(QStringLiteral("select_first_message"), action);
3468         connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectFirstMessage);
3469         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::Key_Home));
3470     }
3471     {
3472         auto action = new QAction(i18n("Select Last Message"), this);
3473         actionCollection()->addAction(QStringLiteral("select_last_message"), action);
3474         connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectLastMessage);
3475         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::Key_End));
3476     }
3477     {
3478         auto action = new QAction(i18n("Select Message with Focus"), this);
3479         actionCollection()->addAction(QStringLiteral("select_current_message"), action);
3480         connect(action, &QAction::triggered, this, &KMMainWidget::slotSelectFocusedMessage);
3481         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::ALT | Qt::Key_Space));
3482     }
3483 
3484     {
3485         mQuickSearchAction = new QAction(i18n("Set Focus to Quick Search"), this);
3486         // If change shortcut change Panel::setQuickSearchClickMessage(...) message
3487         actionCollection()->setDefaultShortcut(mQuickSearchAction, QKeySequence(Qt::ALT | Qt::Key_Q));
3488         actionCollection()->addAction(QStringLiteral("focus_to_quickseach"), mQuickSearchAction);
3489         connect(mQuickSearchAction, &QAction::triggered, this, &KMMainWidget::slotFocusQuickSearch);
3490         updateQuickSearchLineText();
3491     }
3492     {
3493         auto action = new QAction(i18n("Extend Selection to Previous Message"), this);
3494         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT | Qt::Key_Left));
3495         actionCollection()->addAction(QStringLiteral("previous_message"), action);
3496         connect(action, &QAction::triggered, this, &KMMainWidget::slotExtendSelectionToPreviousMessage);
3497     }
3498     {
3499         auto action = new QAction(i18n("Extend Selection to Next Message"), this);
3500         actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::SHIFT | Qt::Key_Right));
3501         actionCollection()->addAction(QStringLiteral("next_message"), action);
3502         connect(action, &QAction::triggered, this, &KMMainWidget::slotExtendSelectionToNextMessage);
3503     }
3504 
3505     {
3506         mMoveMsgToFolderAction = new QAction(i18n("Move Message to Folder"), this);
3507         actionCollection()->setDefaultShortcut(mMoveMsgToFolderAction, QKeySequence(Qt::Key_M));
3508         actionCollection()->addAction(QStringLiteral("move_message_to_folder"), mMoveMsgToFolderAction);
3509         connect(mMoveMsgToFolderAction, &QAction::triggered, this, &KMMainWidget::slotMoveSelectedMessageToFolder);
3510     }
3511 
3512     mArchiveAction = new QAction(i18nc("@action", "Archive"), this);
3513     actionCollection()->addAction(QStringLiteral("archive_mails"), mArchiveAction);
3514     connect(mArchiveAction, &QAction::triggered, this, &KMMainWidget::slotArchiveMails);
3515 
3516     mMarkAllMessageAsReadAndInAllSubFolder = new QAction(i18n("Mark All Messages As Read in This Folder and All its Subfolder"), this);
3517     mMarkAllMessageAsReadAndInAllSubFolder->setIcon(QIcon::fromTheme(QStringLiteral("mail-mark-read")));
3518     actionCollection()->addAction(QStringLiteral("markallmessagereadcurentfolderandsubfolder"), mMarkAllMessageAsReadAndInAllSubFolder);
3519     connect(mMarkAllMessageAsReadAndInAllSubFolder, &KToggleAction::triggered, this, &KMMainWidget::slotMarkAllMessageAsReadInCurrentFolderAndSubfolder);
3520 
3521     mRemoveDuplicateRecursiveAction = new QAction(i18n("Remove Duplicates in This Folder and All its Subfolder"), this);
3522     actionCollection()->addAction(QStringLiteral("remove_duplicate_recursive"), mRemoveDuplicateRecursiveAction);
3523     connect(mRemoveDuplicateRecursiveAction, &KToggleAction::triggered, this, &KMMainWidget::slotRemoveDuplicateRecursive);
3524 
3525     mAccountSettings = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Account &Settings"), this);
3526     actionCollection()->addAction(QStringLiteral("resource_settings"), mAccountSettings);
3527     connect(mAccountSettings, &QAction::triggered, this, &KMMainWidget::slotAccountSettings);
3528 
3529     mRestartAccountSettings = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Restart Account"), this);
3530     actionCollection()->addAction(QStringLiteral("resource_restart"), mRestartAccountSettings);
3531     connect(mRestartAccountSettings, &QAction::triggered, this, &KMMainWidget::slotRestartAccount);
3532     {
3533         QList<QAction *> listActions;
3534         auto act = new QAction(i18n("Previous Selected Folder"), this); // TODO fix me i18n
3535         actionCollection()->setDefaultShortcut(act, QKeySequence(Qt::CTRL | Qt::Key_Tab));
3536         actionCollection()->addAction(QStringLiteral("previous_folder"), act);
3537         listActions.append(act);
3538 
3539         connect(act, &QAction::triggered, this, &KMMainWidget::undoSwitchFolder);
3540 
3541         act = new QAction(i18n("Next Selected Folder"), this); // TODO fix me i18n
3542         actionCollection()->addAction(QStringLiteral("next_folder"), act);
3543         actionCollection()->setDefaultShortcut(act, QKeySequence(Qt::SHIFT | Qt::Key_Tab | Qt::CTRL));
3544         connect(act, &QAction::triggered, this, &KMMainWidget::redoSwitchFolder);
3545         listActions.append(act);
3546 
3547         mCollectionSwitcherTreeViewManager->addActions(listActions);
3548     }
3549 }
3550 
redoSwitchFolder()3551 void KMMainWidget::redoSwitchFolder()
3552 {
3553     mCollectionSwitcherTreeViewManager->selectBackward();
3554 }
3555 
undoSwitchFolder()3556 void KMMainWidget::undoSwitchFolder()
3557 {
3558     mCollectionSwitcherTreeViewManager->selectForward();
3559 }
3560 
slotAddFavoriteFolder()3561 void KMMainWidget::slotAddFavoriteFolder()
3562 {
3563     if (!mFavoritesModel) {
3564         return;
3565     }
3566     QPointer<MailCommon::FolderSelectionDialog> dialog(selectFromAllFoldersDialog());
3567     dialog->setWindowTitle(i18nc("@title:window", "Add Favorite Folder"));
3568     if (dialog->exec() && dialog) {
3569         const Akonadi::Collection collection = dialog->selectedCollection();
3570         if (collection.isValid()) {
3571             mFavoritesModel->addCollection(collection);
3572         }
3573     }
3574 }
3575 
3576 //-----------------------------------------------------------------------------
slotEditNotifications()3577 void KMMainWidget::slotEditNotifications()
3578 {
3579     QPointer<KMail::KMKnotify> notifyDlg = new KMail::KMKnotify(this);
3580     notifyDlg->exec();
3581     delete notifyDlg;
3582 }
3583 
3584 //-----------------------------------------------------------------------------
slotReadOn()3585 void KMMainWidget::slotReadOn()
3586 {
3587     if (!mMsgView) {
3588         return;
3589     }
3590     mMsgView->viewer()->atBottom();
3591 }
3592 
slotPageIsScrolledToBottom(bool isAtBottom)3593 void KMMainWidget::slotPageIsScrolledToBottom(bool isAtBottom)
3594 {
3595     if (isAtBottom) {
3596         slotSelectNextUnreadMessage();
3597     } else {
3598         if (mMsgView) {
3599             mMsgView->viewer()->slotJumpDown();
3600         }
3601     }
3602 }
3603 
slotNextUnreadFolder()3604 void KMMainWidget::slotNextUnreadFolder()
3605 {
3606     if (!mFolderTreeWidget) {
3607         return;
3608     }
3609     mGoToFirstUnreadMessageInSelectedFolder = true;
3610     mFolderTreeWidget->folderTreeView()->selectNextUnreadFolder();
3611     mGoToFirstUnreadMessageInSelectedFolder = false;
3612 }
3613 
slotPrevUnreadFolder()3614 void KMMainWidget::slotPrevUnreadFolder()
3615 {
3616     if (!mFolderTreeWidget) {
3617         return;
3618     }
3619     mGoToFirstUnreadMessageInSelectedFolder = true;
3620     mFolderTreeWidget->folderTreeView()->selectPrevUnreadFolder();
3621     mGoToFirstUnreadMessageInSelectedFolder = false;
3622 }
3623 
slotExpandThread()3624 void KMMainWidget::slotExpandThread()
3625 {
3626     mMessagePane->setCurrentThreadExpanded(true);
3627 }
3628 
slotCollapseThread()3629 void KMMainWidget::slotCollapseThread()
3630 {
3631     mMessagePane->setCurrentThreadExpanded(false);
3632 }
3633 
slotExpandAllThreads()3634 void KMMainWidget::slotExpandAllThreads()
3635 {
3636     // TODO: Make this asynchronous ? (if there is enough demand)
3637     KCursorSaver saver(Qt::WaitCursor);
3638     mMessagePane->setAllThreadsExpanded(true);
3639 }
3640 
slotCollapseAllThreads()3641 void KMMainWidget::slotCollapseAllThreads()
3642 {
3643     KCursorSaver saver(Qt::WaitCursor);
3644     mMessagePane->setAllThreadsExpanded(false);
3645 }
3646 
3647 //-----------------------------------------------------------------------------
updateMessageMenu()3648 void KMMainWidget::updateMessageMenu()
3649 {
3650     updateMessageActions();
3651 }
3652 
startUpdateMessageActionsTimer()3653 void KMMainWidget::startUpdateMessageActionsTimer()
3654 {
3655     // FIXME: This delay effectively CAN make the actions to be in an incoherent state
3656     //        Maybe we should mark actions as "dirty" here and check it in every action handler...
3657     updateMessageActions(true);
3658 
3659     menutimer->stop();
3660     menutimer->start(500ms);
3661 }
3662 
updateMessageActions(bool fast)3663 void KMMainWidget::updateMessageActions(bool fast)
3664 {
3665     Akonadi::Item::List selectedItems;
3666     Akonadi::Item::List selectedVisibleItems;
3667     bool allSelectedBelongToSameThread = false;
3668     if (mCurrentFolderSettings && mCurrentFolderSettings->isValid()
3669         && mMessagePane->getSelectionStats(selectedItems, selectedVisibleItems, &allSelectedBelongToSameThread)) {
3670         mMsgActions->setCurrentMessage(mMessagePane->currentItem(), selectedVisibleItems);
3671     } else {
3672         mMsgActions->setCurrentMessage(Akonadi::Item());
3673     }
3674 
3675     if (!fast) {
3676         updateMessageActionsDelayed();
3677     }
3678 }
3679 
updateMessageActionsDelayed()3680 void KMMainWidget::updateMessageActionsDelayed()
3681 {
3682     int count;
3683     Akonadi::Item::List selectedItems;
3684     Akonadi::Item::List selectedVisibleItems;
3685     bool allSelectedBelongToSameThread = false;
3686     Akonadi::Item currentMessage;
3687     bool currentFolderSettingsIsValid = mCurrentFolderSettings && mCurrentFolderSettings->isValid();
3688     if (currentFolderSettingsIsValid && mMessagePane->getSelectionStats(selectedItems, selectedVisibleItems, &allSelectedBelongToSameThread)) {
3689         count = selectedItems.count();
3690 
3691         currentMessage = mMessagePane->currentItem();
3692     } else {
3693         count = 0;
3694         currentMessage = Akonadi::Item();
3695     }
3696 
3697     mApplyFilterActionsMenu->setEnabled(currentFolderSettingsIsValid);
3698     mApplyFilterFolderRecursiveActionsMenu->setEnabled(currentFolderSettingsIsValid);
3699 
3700     //
3701     // Here we have:
3702     //
3703     // - A list of selected messages stored in selectedSernums.
3704     //   The selected messages might contain some invisible ones as a selected
3705     //   collapsed node "includes" all the children in the selection.
3706     // - A list of selected AND visible messages stored in selectedVisibleSernums.
3707     //   This list does not contain children of selected and collapsed nodes.
3708     //
3709     // Now, actions can operate on:
3710     // - Any set of messages
3711     //     These are called "mass actions" and are enabled whenever we have a message selected.
3712     //     In fact we should differentiate between actions that operate on visible selection
3713     //     and those that operate on the selection as a whole (without considering visibility)...
3714     // - A single thread
3715     //     These are called "thread actions" and are enabled whenever all the selected messages belong
3716     //     to the same thread. If the selection doesn't cover the whole thread then the action
3717     //     will act on the whole thread anyway (thus will silently extend the selection)
3718     // - A single message
3719     //     And we have two sub-cases:
3720     //     - The selection must contain exactly one message
3721     //       These actions can't ignore the hidden messages and thus must be disabled if
3722     //       the selection contains any.
3723     //     - The selection must contain exactly one visible message
3724     //       These actions will ignore the hidden message and thus can be enabled if
3725     //       the selection contains any.
3726     //
3727 
3728     const bool readOnly = currentFolderSettingsIsValid && (mCurrentFolderSettings->rights() & Akonadi::Collection::ReadOnly);
3729     // can we apply strictly single message actions ? (this is false if the whole selection contains more than one message)
3730     const bool single_actions = count == 1;
3731     // can we apply loosely single message actions ? (this is false if the VISIBLE selection contains more than one message)
3732     const bool singleVisibleMessageSelected = selectedVisibleItems.count() == 1;
3733     // can we apply "mass" actions to the selection ? (this is actually always true if the selection is non-empty)
3734     const bool mass_actions = count >= 1;
3735     // does the selection identify a single thread ?
3736     const bool thread_actions = mass_actions && allSelectedBelongToSameThread && mMessagePane->isThreaded();
3737     // can we apply flags to the selected messages ?
3738     const bool flags_available = KMailSettings::self()->allowLocalFlags() || !(currentFolderSettingsIsValid ? readOnly : true);
3739 
3740     mThreadStatusMenu->setEnabled(thread_actions);
3741     // these need to be handled individually, the user might have them
3742     // in the toolbar
3743     mWatchThreadAction->setEnabled(thread_actions && flags_available);
3744     mIgnoreThreadAction->setEnabled(thread_actions && flags_available);
3745     mMarkThreadAsReadAction->setEnabled(thread_actions);
3746     mMarkThreadAsUnreadAction->setEnabled(thread_actions);
3747     mToggleThreadToActAction->setEnabled(thread_actions && flags_available);
3748     mToggleThreadImportantAction->setEnabled(thread_actions && flags_available);
3749     bool canDeleteMessages = currentFolderSettingsIsValid && (mCurrentFolderSettings->rights() & Akonadi::Collection::CanDeleteItem);
3750 
3751     mTrashThreadAction->setEnabled(thread_actions && canDeleteMessages);
3752     mDeleteThreadAction->setEnabled(thread_actions && canDeleteMessages);
3753     if (messageView() && messageView()->viewer() && messageView()->viewer()->headerStylePlugin()) {
3754         messageView()->viewer()->headerStylePlugin()->headerStyle()->setReadOnlyMessage(!canDeleteMessages);
3755     }
3756 
3757     if (currentMessage.isValid()) {
3758         MessageStatus status;
3759         status.setStatusFromFlags(currentMessage.flags());
3760         mTagActionManager->updateActionStates(count, mMessagePane->currentItem());
3761         if (thread_actions) {
3762             mToggleThreadToActAction->setChecked(status.isToAct());
3763             mToggleThreadImportantAction->setChecked(status.isImportant());
3764             mWatchThreadAction->setChecked(status.isWatched());
3765             mIgnoreThreadAction->setChecked(status.isIgnored());
3766         }
3767     }
3768 
3769     mMoveActionMenu->setEnabled(mass_actions && canDeleteMessages);
3770     if (mMoveMsgToFolderAction) {
3771         mMoveMsgToFolderAction->setEnabled(mass_actions && canDeleteMessages);
3772     }
3773     // mCopyActionMenu->setEnabled( mass_actions );
3774 
3775     mDeleteAction->setEnabled(mass_actions && canDeleteMessages);
3776 
3777     mExpireConfigAction->setEnabled(canDeleteMessages && !MailCommon::Util::isVirtualCollection(mCurrentCollection));
3778 
3779     if (mMsgView) {
3780         mMsgView->findInMessageAction()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection));
3781     }
3782     mMsgActions->forwardInlineAction()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection));
3783     mMsgActions->forwardAttachedAction()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection));
3784     mMsgActions->forwardMenu()->setEnabled(mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection));
3785 
3786     mMsgActions->editAsNewAction()->setEnabled(single_actions);
3787     mMsgActions->newMessageFromTemplateAction()->setEnabled(single_actions && CommonKernel->folderIsTemplates(mCurrentCollection));
3788     filterMenu()->setEnabled(single_actions);
3789     mMsgActions->redirectAction()->setEnabled(/*single_actions &&*/ mass_actions && !CommonKernel->folderIsTemplates(mCurrentCollection));
3790     mMsgActions->newToRecipientsAction()->setEnabled(single_actions);
3791 
3792     if (auto *menuCustom = mMsgActions->customTemplatesMenu()) {
3793         menuCustom->forwardActionMenu()->setEnabled(mass_actions);
3794         menuCustom->replyActionMenu()->setEnabled(single_actions);
3795         menuCustom->replyAllActionMenu()->setEnabled(single_actions);
3796     }
3797 
3798     // "Print" will act on the current message: it will ignore any hidden selection
3799     mMsgActions->printAction()->setEnabled(singleVisibleMessageSelected && mMsgView);
3800     // "Print preview" will act on the current message: it will ignore any hidden selection
3801     if (QAction *printPreviewAction = mMsgActions->printPreviewAction()) {
3802         printPreviewAction->setEnabled(singleVisibleMessageSelected && mMsgView);
3803     }
3804 
3805     // "View Source" will act on the current message: it will ignore any hidden selection
3806     if (mMsgView) {
3807         mMsgView->viewSourceAction()->setEnabled(singleVisibleMessageSelected);
3808         mMsgView->selectAllAction()->setEnabled(count);
3809     }
3810     MessageStatus status;
3811     status.setStatusFromFlags(currentMessage.flags());
3812 
3813     QList<QAction *> actionList;
3814     bool statusSendAgain = single_actions
3815         && ((currentMessage.isValid() && status.isSent()) || (currentMessage.isValid() && CommonKernel->folderIsSentMailFolder(mCurrentCollection)));
3816     if (statusSendAgain) {
3817         actionList << mMsgActions->sendAgainAction();
3818     } else if (single_actions) {
3819         actionList << mMsgActions->editAsNewAction();
3820     }
3821     actionList << mSaveAttachmentsAction;
3822     if (mCurrentCollection.isValid() && FolderArchive::FolderArchiveUtil::resourceSupportArchiving(mCurrentCollection.resource())) {
3823         actionList << mArchiveAction;
3824     }
3825     mGUIClient->unplugActionList(QStringLiteral("messagelist_actionlist"));
3826     mGUIClient->plugActionList(QStringLiteral("messagelist_actionlist"), actionList);
3827     mMsgActions->sendAgainAction()->setEnabled(statusSendAgain);
3828 
3829     if (currentFolderSettingsIsValid) {
3830         updateMoveAction(mCurrentFolderSettings->statistics());
3831     } else {
3832         updateMoveAction(false);
3833     }
3834 
3835     const auto col = CommonKernel->collectionFromId(CommonKernel->outboxCollectionFolder().id());
3836     const bool nbMsgOutboxCollectionIsNotNull = (col.statistics().count() > 0);
3837 
3838     mSendQueued->setEnabled(nbMsgOutboxCollectionIsNotNull);
3839     mSendActionMenu->setEnabled(nbMsgOutboxCollectionIsNotNull);
3840 
3841     const bool newPostToMailingList = mCurrentFolderSettings && mCurrentFolderSettings->isMailingListEnabled();
3842     mMessageNewList->setEnabled(newPostToMailingList);
3843 
3844     slotUpdateOnlineStatus(static_cast<GlobalSettingsBase::EnumNetworkState::type>(KMailSettings::self()->networkState()));
3845     if (QAction *act = action(QStringLiteral("kmail_undo"))) {
3846         act->setEnabled(kmkernel->undoStack() && !kmkernel->undoStack()->isEmpty());
3847     }
3848 
3849     // Enable / disable all filters.
3850     for (QAction *filterAction : std::as_const(mFilterMenuActions)) {
3851         filterAction->setEnabled(count > 0);
3852     }
3853 
3854     mApplyAllFiltersAction->setEnabled(count);
3855     mApplyFilterActionsMenu->setEnabled(count);
3856     mSelectAllMessages->setEnabled(count);
3857 
3858     if (currentMessage.hasFlag(Akonadi::MessageFlags::Encrypted) || count > 1) {
3859         mCopyDecryptedActionMenu->setVisible(true);
3860         mCopyDecryptedActionMenu->menu()->clear();
3861         mAkonadiStandardActionManager->standardActionManager()->createActionFolderMenu(mCopyDecryptedActionMenu->menu(),
3862                                                                                        Akonadi::StandardActionManager::CopyItemToMenu);
3863     } else {
3864         mCopyDecryptedActionMenu->setVisible(false);
3865     }
3866 }
3867 
slotAkonadiStandardActionUpdated()3868 void KMMainWidget::slotAkonadiStandardActionUpdated()
3869 {
3870     if (mCollectionProperties) {
3871         if (mCurrentCollection.isValid()) {
3872             const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(mCurrentCollection.resource());
3873 
3874             mCollectionProperties->setEnabled(!mCurrentFolderSettings->isStructural() && (instance.status() != Akonadi::AgentInstance::Broken));
3875         } else {
3876             mCollectionProperties->setEnabled(false);
3877         }
3878         QList<QAction *> collectionProperties;
3879         if (mCollectionProperties->isEnabled()) {
3880             collectionProperties << mCollectionProperties;
3881         }
3882         mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_collectionproperties_actionlist"));
3883         mGUIClient->plugActionList(QStringLiteral("akonadi_collection_collectionproperties_actionlist"), collectionProperties);
3884     }
3885 
3886     const bool folderWithContent = mCurrentFolderSettings && !mCurrentFolderSettings->isStructural();
3887 
3888     if (QAction *act = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections)) {
3889         act->setEnabled(mCurrentFolderSettings && (mCurrentCollection.rights() & Collection::CanDeleteCollection) && !mCurrentFolderSettings->isSystemFolder()
3890                         && folderWithContent);
3891     }
3892 
3893     updateMoveAllToTrash();
3894 
3895     QList<QAction *> addToFavorite;
3896     QAction *actionAddToFavoriteCollections = akonadiStandardAction(Akonadi::StandardActionManager::AddToFavoriteCollections);
3897     if (actionAddToFavoriteCollections) {
3898         if (mEnableFavoriteFolderView && actionAddToFavoriteCollections->isEnabled()) {
3899             addToFavorite << actionAddToFavoriteCollections;
3900         }
3901         mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_add_to_favorites_actionlist"));
3902         mGUIClient->plugActionList(QStringLiteral("akonadi_collection_add_to_favorites_actionlist"), addToFavorite);
3903     }
3904 
3905     QList<QAction *> syncActionList;
3906     QAction *actionSync = akonadiStandardAction(Akonadi::StandardActionManager::SynchronizeCollections);
3907     if (actionSync && actionSync->isEnabled()) {
3908         syncActionList << actionSync;
3909     }
3910     actionSync = akonadiStandardAction(Akonadi::StandardActionManager::SynchronizeCollectionsRecursive);
3911     if (actionSync && actionSync->isEnabled()) {
3912         syncActionList << actionSync;
3913     }
3914     mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_sync_actionlist"));
3915     mGUIClient->plugActionList(QStringLiteral("akonadi_collection_sync_actionlist"), syncActionList);
3916 
3917     QList<QAction *> actionList;
3918 
3919     QAction *action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CreateCollection);
3920     if (action && action->isEnabled()) {
3921         actionList << action;
3922     }
3923 
3924     action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::MoveCollectionToMenu);
3925     if (action && action->isEnabled()) {
3926         actionList << action;
3927     }
3928 
3929     action = mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::CopyCollectionToMenu);
3930     if (action && action->isEnabled()) {
3931         actionList << action;
3932     }
3933     mGUIClient->unplugActionList(QStringLiteral("akonadi_collection_move_copy_menu_actionlist"));
3934     mGUIClient->plugActionList(QStringLiteral("akonadi_collection_move_copy_menu_actionlist"), actionList);
3935 }
3936 
updateHtmlMenuEntry()3937 void KMMainWidget::updateHtmlMenuEntry()
3938 {
3939     if (mDisplayMessageFormatMenu && mPreferHtmlLoadExtAction) {
3940         // the visual ones only make sense if we are showing a message list
3941         const bool enabledAction = (mFolderTreeWidget && mFolderTreeWidget->folderTreeView()->currentFolder().isValid());
3942 
3943         mDisplayMessageFormatMenu->setEnabled(enabledAction);
3944         const bool isEnabled = (mFolderTreeWidget && mFolderTreeWidget->folderTreeView()->currentFolder().isValid());
3945         const bool useHtml = (mFolderDisplayFormatPreference == MessageViewer::Viewer::Html
3946                               || (mHtmlGlobalSetting && mFolderDisplayFormatPreference == MessageViewer::Viewer::UseGlobalSetting));
3947         mPreferHtmlLoadExtAction->setEnabled(isEnabled && useHtml);
3948 
3949         mDisplayMessageFormatMenu->setDisplayMessageFormat(mFolderDisplayFormatPreference);
3950 
3951         if (mFolderHtmlLoadExtPreference) {
3952             mPreferHtmlLoadExtAction->setChecked(true);
3953         } else {
3954             mPreferHtmlLoadExtAction->setChecked(mHtmlLoadExtGlobalSetting);
3955         }
3956     }
3957 }
3958 
3959 //-----------------------------------------------------------------------------
updateFolderMenu()3960 void KMMainWidget::updateFolderMenu()
3961 {
3962     if (!CommonKernel->outboxCollectionFolder().isValid()) {
3963         QTimer::singleShot(1s, this, &KMMainWidget::updateFolderMenu);
3964         return;
3965     }
3966 
3967     const bool folderWithContent = mCurrentFolderSettings && !mCurrentFolderSettings->isStructural() && mCurrentFolderSettings->isValid();
3968     mFolderMailingListPropertiesAction->setEnabled(folderWithContent && !mCurrentFolderSettings->isSystemFolder());
3969 
3970     QList<QAction *> actionlist;
3971     if (mCurrentCollection.id() == CommonKernel->outboxCollectionFolder().id() && (mCurrentCollection).statistics().count() > 0) {
3972         qCDebug(KMAIL_LOG) << "Enabling send queued";
3973         mSendQueued->setEnabled(true);
3974         actionlist << mSendQueued;
3975     }
3976     //   if ( mCurrentCollection.id() != CommonKernel->trashCollectionFolder().id() ) {
3977     //     actionlist << mTrashAction;
3978     //   }
3979     mGUIClient->unplugActionList(QStringLiteral("outbox_folder_actionlist"));
3980     mGUIClient->plugActionList(QStringLiteral("outbox_folder_actionlist"), actionlist);
3981     actionlist.clear();
3982 
3983     const bool isASearchFolder = mCurrentCollection.resource() == QLatin1String("akonadi_search_resource");
3984     if (isASearchFolder) {
3985         mAkonadiStandardActionManager->action(Akonadi::StandardActionManager::DeleteCollections)->setText(i18n("&Delete Search"));
3986     }
3987 
3988     mArchiveFolderAction->setEnabled(mCurrentFolderSettings && folderWithContent);
3989 
3990     const bool isInTrashFolder = (mCurrentFolderSettings && CommonKernel->folderIsTrash(mCurrentCollection));
3991     QAction *moveToTrash = akonadiStandardAction(Akonadi::StandardMailActionManager::MoveToTrash);
3992     updateMoveAllToTrash();
3993 
3994     KMail::Util::setActionTrashOrDelete(moveToTrash, isInTrashFolder);
3995 
3996     mTrashThreadAction->setIcon(isInTrashFolder ? QIcon::fromTheme(QStringLiteral("edit-delete-shred")) : QIcon::fromTheme(QStringLiteral("edit-delete")));
3997     mTrashThreadAction->setText(isInTrashFolder ? i18n("Delete T&hread") : i18n("M&ove Thread to Trash"));
3998 
3999     mSearchMessages->setText((mCurrentCollection.resource() == QLatin1String("akonadi_search_resource")) ? i18n("Edit Search...") : i18n("&Find Messages..."));
4000 
4001     mExpireConfigAction->setEnabled(mCurrentFolderSettings && !mCurrentFolderSettings->isStructural() && mCurrentFolderSettings->canDeleteMessages()
4002                                     && folderWithContent && !MailCommon::Util::isVirtualCollection(mCurrentCollection));
4003 
4004     updateHtmlMenuEntry();
4005 
4006     mShowFolderShortcutDialogAction->setEnabled(folderWithContent);
4007     actionlist << akonadiStandardAction(Akonadi::StandardActionManager::ManageLocalSubscriptions);
4008     bool imapFolderIsOnline = false;
4009     if (mCurrentFolderSettings && PimCommon::MailUtil::isImapFolder(mCurrentCollection, imapFolderIsOnline)) {
4010         if (imapFolderIsOnline) {
4011             actionlist << mServerSideSubscription;
4012         }
4013     }
4014     if (mCurrentCollection.parentCollection() != Akonadi::Collection::root()) {
4015         mGUIClient->unplugActionList(QStringLiteral("resource_settings"));
4016         mGUIClient->unplugActionList(QStringLiteral("resource_restart"));
4017     } else {
4018         mGUIClient->plugActionList(QStringLiteral("resource_settings"), {mAccountSettings});
4019         mGUIClient->plugActionList(QStringLiteral("resource_restart"), {mRestartAccountSettings});
4020     }
4021 
4022     mGUIClient->unplugActionList(QStringLiteral("collectionview_actionlist"));
4023     mGUIClient->plugActionList(QStringLiteral("collectionview_actionlist"), actionlist);
4024 
4025     const bool folderIsValid = folderWithContent;
4026     mApplyAllFiltersFolderAction->setEnabled(folderIsValid);
4027     mApplyFilterFolderActionsMenu->setEnabled(folderIsValid);
4028     mApplyFilterFolderRecursiveActionsMenu->setEnabled(folderIsValid);
4029     for (auto a : std::as_const(mFilterFolderMenuActions)) {
4030         a->setEnabled(folderIsValid);
4031     }
4032     for (auto a : std::as_const(mFilterFolderMenuRecursiveActions)) {
4033         a->setEnabled(folderIsValid);
4034     }
4035     if (mCurrentCollection.resource() == QLatin1String("akonadi_unifiedmailbox_agent")) {
4036         mAccountSettings->setText(i18n("Configure Unified Mailbox"));
4037     } else {
4038         mAccountSettings->setText(i18n("Account &Settings"));
4039     }
4040 }
4041 
updateMoveAllToTrash()4042 void KMMainWidget::updateMoveAllToTrash()
4043 {
4044     if (QAction *act = mAkonadiStandardActionManager->action(Akonadi::StandardMailActionManager::MoveAllToTrash)) {
4045         const bool folderWithContent = mCurrentFolderSettings && !mCurrentFolderSettings->isStructural();
4046         act->setEnabled(folderWithContent && (mCurrentFolderSettings->count() > 0) && mCurrentFolderSettings->canDeleteMessages());
4047         act->setText((mCurrentFolderSettings && CommonKernel->folderIsTrash(mCurrentCollection)) ? i18n("E&mpty Trash") : i18n("&Move All Messages to Trash"));
4048     }
4049 }
4050 
4051 //-----------------------------------------------------------------------------
slotIntro()4052 void KMMainWidget::slotIntro()
4053 {
4054     if (!mMsgView) {
4055         return;
4056     }
4057 
4058     mMsgView->clear(true);
4059 
4060     // hide widgets that are in the way:
4061     if (mMessagePane && mLongFolderList) {
4062         mMessagePane->hide();
4063     }
4064     mMsgView->displayAboutPage();
4065 
4066     clearCurrentFolder();
4067 }
4068 
slotShowStartupFolder()4069 void KMMainWidget::slotShowStartupFolder()
4070 {
4071     connect(MailCommon::FilterManager::instance(), &FilterManager::filtersChanged, this, [this]() {
4072         initializeFilterActions(true);
4073     });
4074     // Plug various action lists. This can't be done in the constructor, as that is called before
4075     // the main window or Kontact calls createGUI().
4076     // This function however is called with a single shot timer.
4077     checkAkonadiServerManagerState();
4078     mFolderShortcutActionManager->createActions();
4079     mTagActionManager->createActions();
4080     messageActions()->setupForwardingActionsList(mGUIClient);
4081     initializePluginActions();
4082 
4083     const QString newFeaturesMD5 = KMReaderWin::newFeaturesMD5();
4084     if (kmkernel->firstStart() || KMailSettings::self()->previousNewFeaturesMD5() != newFeaturesMD5) {
4085         KMailSettings::self()->setPreviousNewFeaturesMD5(newFeaturesMD5);
4086         slotIntro();
4087     }
4088 }
4089 
checkAkonadiServerManagerState()4090 void KMMainWidget::checkAkonadiServerManagerState()
4091 {
4092     Akonadi::ServerManager::State state = Akonadi::ServerManager::self()->state();
4093     if (state == Akonadi::ServerManager::Running) {
4094         initializeFilterActions(true);
4095     } else {
4096         connect(Akonadi::ServerManager::self(), &ServerManager::stateChanged, this, &KMMainWidget::slotServerStateChanged);
4097     }
4098 }
4099 
slotServerStateChanged(Akonadi::ServerManager::State state)4100 void KMMainWidget::slotServerStateChanged(Akonadi::ServerManager::State state)
4101 {
4102     if (state == Akonadi::ServerManager::Running) {
4103         initializeFilterActions(true);
4104         disconnect(Akonadi::ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)));
4105     }
4106 }
4107 
actionCollections() const4108 QList<KActionCollection *> KMMainWidget::actionCollections() const
4109 {
4110     return QList<KActionCollection *>() << actionCollection();
4111 }
4112 
4113 //-----------------------------------------------------------------------------
slotUpdateUndo()4114 void KMMainWidget::slotUpdateUndo()
4115 {
4116     if (actionCollection()->action(QStringLiteral("kmail_undo"))) {
4117         QAction *act = actionCollection()->action(QStringLiteral("kmail_undo"));
4118         act->setEnabled(!kmkernel->undoStack()->isEmpty());
4119         const QString infoStr = kmkernel->undoStack()->undoInfo();
4120         if (infoStr.isEmpty()) {
4121             act->setText(i18n("&Undo"));
4122         } else {
4123             act->setText(i18n("&Undo: \"%1\"", kmkernel->undoStack()->undoInfo()));
4124         }
4125     }
4126 }
4127 
4128 //-----------------------------------------------------------------------------
clearFilterActions()4129 void KMMainWidget::clearFilterActions()
4130 {
4131     if (mGUIClient->factory()) {
4132         if (!mFilterTBarActions.isEmpty()) {
4133             mGUIClient->unplugActionList(QStringLiteral("toolbar_filter_actions"));
4134         }
4135         if (!mFilterMenuActions.isEmpty()) {
4136             mGUIClient->unplugActionList(QStringLiteral("menu_filter_actions"));
4137         }
4138         if (!mFilterFolderMenuActions.isEmpty()) {
4139             mGUIClient->unplugActionList(QStringLiteral("menu_filter_folder_actions"));
4140         }
4141         if (!mFilterFolderMenuRecursiveActions.isEmpty()) {
4142             mGUIClient->unplugActionList(QStringLiteral("menu_filter_folder_recursive_actions"));
4143         }
4144     }
4145 
4146     for (QAction *a : std::as_const(mFilterMenuActions)) {
4147         actionCollection()->removeAction(a);
4148     }
4149     for (QAction *a : std::as_const(mFilterFolderMenuActions)) {
4150         actionCollection()->removeAction(a);
4151     }
4152     for (QAction *a : std::as_const(mFilterFolderMenuRecursiveActions)) {
4153         actionCollection()->removeAction(a);
4154     }
4155 
4156     mApplyFilterActionsMenu->menu()->clear();
4157     mApplyFilterFolderActionsMenu->menu()->clear();
4158     mApplyFilterFolderRecursiveActionsMenu->menu()->clear();
4159     mFilterTBarActions.clear();
4160     mFilterMenuActions.clear();
4161     mFilterFolderMenuActions.clear();
4162     mFilterFolderMenuRecursiveActions.clear();
4163 
4164     qDeleteAll(mFilterCommands);
4165     mFilterCommands.clear();
4166 }
4167 
clearPluginActions()4168 void KMMainWidget::clearPluginActions()
4169 {
4170     KMailPluginInterface::self()->clearPluginActions(QStringLiteral("kmail"), mGUIClient);
4171 }
4172 
initializePluginActions()4173 void KMMainWidget::initializePluginActions()
4174 {
4175     KMailPluginInterface::self()->initializePluginActions(QStringLiteral("kmail"), mGUIClient);
4176 }
4177 
filterToAction(MailCommon::MailFilter * filter)4178 QAction *KMMainWidget::filterToAction(MailCommon::MailFilter *filter)
4179 {
4180     const QString displayText = i18n("Filter %1", filter->name());
4181     QString icon = filter->icon();
4182     if (icon.isEmpty()) {
4183         icon = QStringLiteral("system-run");
4184     }
4185     auto filterAction = new QAction(QIcon::fromTheme(icon), displayText, actionCollection());
4186     filterAction->setProperty("filter_id", filter->identifier());
4187     filterAction->setIconText(filter->toolbarName());
4188 
4189     // The shortcut configuration is done in the filter dialog.
4190     // The shortcut set in the shortcut dialog would not be saved back to
4191     // the filter settings correctly.
4192     actionCollection()->setShortcutsConfigurable(filterAction, false);
4193 
4194     return filterAction;
4195 }
4196 
4197 //-----------------------------------------------------------------------------
initializeFilterActions(bool clearFilter)4198 void KMMainWidget::initializeFilterActions(bool clearFilter)
4199 {
4200     if (clearFilter) {
4201         clearFilterActions();
4202     }
4203     mApplyFilterActionsMenu->menu()->addAction(mApplyAllFiltersAction);
4204     mApplyFilterFolderActionsMenu->menu()->addAction(mApplyAllFiltersFolderAction);
4205     mApplyFilterFolderRecursiveActionsMenu->menu()->addAction(mApplyAllFiltersFolderRecursiveAction);
4206     bool addedSeparator = false;
4207 
4208     const QVector<MailFilter *> lstFilters = MailCommon::FilterManager::instance()->filters();
4209     for (MailFilter *filter : lstFilters) {
4210         if (!filter->isEmpty() && filter->configureShortcut() && filter->isEnabled()) {
4211             QString filterName = QStringLiteral("Filter %1").arg(filter->name());
4212             filterName.replace(QLatin1Char(' '), QLatin1Char('_'));
4213             if (action(filterName)) {
4214                 continue;
4215             }
4216 
4217             if (!addedSeparator) {
4218                 QAction *a = mApplyFilterActionsMenu->menu()->addSeparator();
4219                 mFilterMenuActions.append(a);
4220                 a = mApplyFilterFolderActionsMenu->menu()->addSeparator();
4221                 mFilterFolderMenuActions.append(a);
4222                 a = mApplyFilterFolderRecursiveActionsMenu->menu()->addSeparator();
4223                 mFilterFolderMenuRecursiveActions.append(a);
4224                 addedSeparator = true;
4225             }
4226 
4227             auto filterCommand = new KMMetaFilterActionCommand(filter->identifier(), this);
4228             mFilterCommands.append(filterCommand);
4229 
4230             auto filterAction = filterToAction(filter);
4231             actionCollection()->addAction(filterName, filterAction);
4232             connect(filterAction, &QAction::triggered, filterCommand, &KMMetaFilterActionCommand::start);
4233             actionCollection()->setDefaultShortcut(filterAction, filter->shortcut());
4234             mApplyFilterActionsMenu->menu()->addAction(filterAction);
4235             mFilterMenuActions.append(filterAction);
4236             if (filter->configureToolbar()) {
4237                 mFilterTBarActions.append(filterAction);
4238             }
4239 
4240             filterAction = filterToAction(filter);
4241             actionCollection()->addAction(filterName + QStringLiteral("___folder"), filterAction);
4242             connect(filterAction, &QAction::triggered, this, [this] {
4243                 slotApplyFilterOnFolder(/* recursive */ false);
4244             });
4245             mApplyFilterFolderActionsMenu->menu()->addAction(filterAction);
4246             mFilterFolderMenuActions.append(filterAction);
4247 
4248             filterAction = filterToAction(filter);
4249             actionCollection()->addAction(filterName + QStringLiteral("___folder_recursive"), filterAction);
4250             connect(filterAction, &QAction::triggered, this, [this] {
4251                 slotApplyFilterOnFolder(/* recursive */ true);
4252             });
4253             mApplyFilterFolderRecursiveActionsMenu->menu()->addAction(filterAction);
4254             mFilterFolderMenuRecursiveActions.append(filterAction);
4255         }
4256     }
4257 
4258     if (mGUIClient->factory()) {
4259         if (!mFilterMenuActions.isEmpty()) {
4260             mGUIClient->plugActionList(QStringLiteral("menu_filter_actions"), mFilterMenuActions);
4261         }
4262         if (!mFilterTBarActions.isEmpty()) {
4263             mFilterTBarActions.prepend(mToolbarActionSeparator);
4264             mGUIClient->plugActionList(QStringLiteral("toolbar_filter_actions"), mFilterTBarActions);
4265         }
4266         if (!mFilterFolderMenuActions.isEmpty()) {
4267             mGUIClient->plugActionList(QStringLiteral("menu_filter_folder_actions"), mFilterFolderMenuActions);
4268         }
4269         if (!mFilterFolderMenuRecursiveActions.isEmpty()) {
4270             mGUIClient->plugActionList(QStringLiteral("menu_filter_folder_recursive_actions"), mFilterFolderMenuRecursiveActions);
4271         }
4272     }
4273 
4274     // Our filters have changed, now enable/disable them
4275     updateMessageActions();
4276 }
4277 
updateFileMenu()4278 void KMMainWidget::updateFileMenu()
4279 {
4280     const bool isEmpty = MailCommon::Util::agentInstances().isEmpty();
4281     actionCollection()->action(QStringLiteral("check_mail"))->setEnabled(!isEmpty);
4282     actionCollection()->action(QStringLiteral("check_mail_in"))->setEnabled(!isEmpty);
4283 }
4284 
4285 //-----------------------------------------------------------------------------
mainWidgetList()4286 const KMMainWidget *KMMainWidget::mainWidgetList()
4287 {
4288     // better safe than sorry; check whether the global static has already been destroyed
4289     if (!myMainWidget) {
4290         return nullptr;
4291     }
4292     return myMainWidget;
4293 }
4294 
currentFolder() const4295 QSharedPointer<FolderSettings> KMMainWidget::currentFolder() const
4296 {
4297     return mCurrentFolderSettings;
4298 }
4299 
action(const QString & name)4300 QAction *KMMainWidget::action(const QString &name)
4301 {
4302     return mActionCollection->action(name);
4303 }
4304 
filterMenu() const4305 KActionMenu *KMMainWidget::filterMenu() const
4306 {
4307     return mFilterMenu;
4308 }
4309 
mailingListActionMenu() const4310 KActionMenu *KMMainWidget::mailingListActionMenu() const
4311 {
4312     return mMsgActions->mailingListActionMenu();
4313 }
4314 
sendQueuedAction() const4315 QAction *KMMainWidget::sendQueuedAction() const
4316 {
4317     return mSendQueued;
4318 }
4319 
sendQueueViaMenu() const4320 KActionMenuTransport *KMMainWidget::sendQueueViaMenu() const
4321 {
4322     return mSendActionMenu;
4323 }
4324 
messageActions() const4325 KMail::MessageActions *KMMainWidget::messageActions() const
4326 {
4327     return mMsgActions;
4328 }
4329 
4330 //-----------------------------------------------------------------------------
overrideEncoding() const4331 QString KMMainWidget::overrideEncoding() const
4332 {
4333     if (mMsgView) {
4334         return mMsgView->overrideEncoding();
4335     } else {
4336         return MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding();
4337     }
4338 }
4339 
showEvent(QShowEvent * event)4340 void KMMainWidget::showEvent(QShowEvent *event)
4341 {
4342     QWidget::showEvent(event);
4343     mWasEverShown = true;
4344 }
4345 
actionCollection() const4346 KActionCollection *KMMainWidget::actionCollection() const
4347 {
4348     return mActionCollection;
4349 }
4350 
slotRequestFullSearchFromQuickSearch()4351 void KMMainWidget::slotRequestFullSearchFromQuickSearch()
4352 {
4353     // First, open the search window. If we are currently on a search folder,
4354     // the search associated with that will be loaded.
4355     if (!showSearchDialog()) {
4356         return;
4357     }
4358 
4359     assert(mSearchWin);
4360 
4361     // Now we look at the current state of the quick search, and if there's
4362     // something in there, we add the criteria to the existing search for
4363     // the search folder, if applicable, or make a new one from it.
4364     SearchPattern pattern;
4365     const QString searchString = mMessagePane->currentFilterSearchString();
4366     if (!searchString.isEmpty()) {
4367         MessageList::Core::QuickSearchLine::SearchOptions options = mMessagePane->currentOptions();
4368         QByteArray searchStringVal;
4369         if (options & MessageList::Core::QuickSearchLine::SearchEveryWhere) {
4370             searchStringVal = QByteArrayLiteral("<message>");
4371         } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstSubject) {
4372             searchStringVal = QByteArrayLiteral("subject");
4373         } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstBody) {
4374             searchStringVal = QByteArrayLiteral("<body>");
4375         } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstFrom) {
4376             searchStringVal = QByteArrayLiteral("from");
4377         } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstBcc) {
4378             searchStringVal = QByteArrayLiteral("bcc");
4379         } else if (options & MessageList::Core::QuickSearchLine::SearchAgainstTo) {
4380             searchStringVal = QByteArrayLiteral("to");
4381         } else {
4382             searchStringVal = QByteArrayLiteral("<message>");
4383         }
4384         pattern.append(SearchRule::createInstance(searchStringVal, SearchRule::FuncContains, searchString));
4385         const QVector<MessageStatus> statusList = mMessagePane->currentFilterStatus();
4386         for (MessageStatus status : statusList) {
4387             if (status.hasAttachment()) {
4388                 pattern.append(SearchRule::createInstance(searchStringVal, SearchRule::FuncHasAttachment, searchString));
4389                 status.setHasAttachment(false);
4390             }
4391             if (!status.isOfUnknownStatus()) {
4392                 pattern.append(SearchRule::Ptr(new SearchRuleStatus(status)));
4393             }
4394         }
4395     }
4396     if (!pattern.isEmpty()) {
4397         mSearchWin->addRulesToSearchPattern(pattern);
4398     }
4399 }
4400 
updateVacationScriptStatus(bool active,const QString & serverName)4401 void KMMainWidget::updateVacationScriptStatus(bool active, const QString &serverName)
4402 {
4403     mVacationScriptIndicator->setVacationScriptActive(active, serverName);
4404     mVacationIndicatorActive = mVacationScriptIndicator->hasVacationScriptActive();
4405 }
4406 
vacationScriptIndicator() const4407 QWidget *KMMainWidget::vacationScriptIndicator() const
4408 {
4409     return mVacationScriptIndicator;
4410 }
4411 
zoomLabelIndicator() const4412 QWidget *KMMainWidget::zoomLabelIndicator() const
4413 {
4414     return mZoomLabelIndicator;
4415 }
4416 
folderTreeView() const4417 FolderTreeView *KMMainWidget::folderTreeView() const
4418 {
4419     return mFolderTreeWidget->folderTreeView();
4420 }
4421 
guiClient() const4422 KXMLGUIClient *KMMainWidget::guiClient() const
4423 {
4424     return mGUIClient;
4425 }
4426 
tagActionManager() const4427 KMail::TagActionManager *KMMainWidget::tagActionManager() const
4428 {
4429     return mTagActionManager;
4430 }
4431 
folderShortcutActionManager() const4432 KMail::FolderShortcutActionManager *KMMainWidget::folderShortcutActionManager() const
4433 {
4434     return mFolderShortcutActionManager;
4435 }
4436 
slotMessageSelected(const Akonadi::Item & item)4437 void KMMainWidget::slotMessageSelected(const Akonadi::Item &item)
4438 {
4439     delete mShowBusySplashTimer;
4440     mShowBusySplashTimer = nullptr;
4441     if (mMsgView) {
4442         // The current selection was cleared, so we'll remove the previously
4443         // selected message from the preview pane
4444         if (!item.isValid()) {
4445             mMsgView->clear();
4446         } else {
4447             mShowBusySplashTimer = new QTimer(this);
4448             mShowBusySplashTimer->setSingleShot(true);
4449             connect(mShowBusySplashTimer, &QTimer::timeout, this, &KMMainWidget::slotShowBusySplash);
4450             mShowBusySplashTimer->start(1s);
4451 
4452             Akonadi::ItemFetchJob *itemFetchJob = mMsgView->viewer()->createFetchJob(item);
4453             if (mCurrentCollection.isValid()) {
4454                 const QString resource = mCurrentCollection.resource();
4455                 itemFetchJob->setProperty("_resource", QVariant::fromValue(resource));
4456                 connect(itemFetchJob, &ItemFetchJob::itemsReceived, this, &KMMainWidget::itemsReceived);
4457                 connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMMainWidget::itemsFetchDone);
4458             }
4459         }
4460     }
4461 }
4462 
itemsReceived(const Akonadi::Item::List & list)4463 void KMMainWidget::itemsReceived(const Akonadi::Item::List &list)
4464 {
4465     qDebug() << " list count  " << list.count();
4466     // Q_ASSERT(list.size() == 1);
4467     delete mShowBusySplashTimer;
4468     mShowBusySplashTimer = nullptr;
4469 
4470     if (!mMsgView) {
4471         return;
4472     }
4473 
4474     Item item = list.first();
4475 
4476     if (mMessagePane) {
4477         mMessagePane->show();
4478 
4479         if (mMessagePane->currentItem() != item) {
4480             // The user has selected another email already, so don't render this one.
4481             // Mark it as read, though, if the user settings say so.
4482             if (MessageViewer::MessageViewerSettings::self()->delayedMarkAsRead() && MessageViewer::MessageViewerSettings::self()->delayedMarkTime() == 0) {
4483                 item.setFlag(Akonadi::MessageFlags::Seen);
4484                 auto modifyJob = new Akonadi::ItemModifyJob(item, this);
4485                 modifyJob->disableRevisionCheck();
4486                 modifyJob->setIgnorePayload(true);
4487             }
4488             return;
4489         }
4490     }
4491 
4492     Akonadi::Item copyItem(item);
4493     if (mCurrentCollection.isValid()) {
4494         copyItem.setParentCollection(mCurrentCollection);
4495     }
4496 
4497     mMsgView->setMessage(copyItem);
4498     assignLoadExternalReference();
4499     mMsgView->setDecryptMessageOverwrite(false);
4500     mMsgActions->setCurrentMessage(copyItem);
4501 }
4502 
itemsFetchDone(KJob * job)4503 void KMMainWidget::itemsFetchDone(KJob *job)
4504 {
4505     delete mShowBusySplashTimer;
4506     mShowBusySplashTimer = nullptr;
4507     if (job->error()) {
4508         // Unfortunately job->error() is Job::Unknown in many cases.
4509         // (see JobPrivate::handleResponse in akonadi/job.cpp)
4510         // So we show the "offline" page after checking the resource status.
4511         qCDebug(KMAIL_LOG) << job->error() << job->errorString();
4512 
4513         const QString resource = job->property("_resource").toString();
4514         const Akonadi::AgentInstance agentInstance = Akonadi::AgentManager::self()->instance(resource);
4515         if (!agentInstance.isOnline()) {
4516             // The resource is offline
4517             mMessagePane->show();
4518             if (mMsgView) {
4519                 mMsgView->viewer()->enableMessageDisplay();
4520                 mMsgView->clear(true);
4521                 if (kmkernel->isOffline()) {
4522                     showOfflinePage();
4523                 } else {
4524                     showResourceOfflinePage();
4525                 }
4526             }
4527         } else {
4528             // Some other error
4529             showMessageActivities(job->errorString());
4530         }
4531     }
4532 }
4533 
akonadiStandardAction(Akonadi::StandardActionManager::Type type)4534 QAction *KMMainWidget::akonadiStandardAction(Akonadi::StandardActionManager::Type type)
4535 {
4536     return mAkonadiStandardActionManager->action(type);
4537 }
4538 
akonadiStandardAction(Akonadi::StandardMailActionManager::Type type)4539 QAction *KMMainWidget::akonadiStandardAction(Akonadi::StandardMailActionManager::Type type)
4540 {
4541     return mAkonadiStandardActionManager->action(type);
4542 }
4543 
standardMailActionManager() const4544 StandardMailActionManager *KMMainWidget::standardMailActionManager() const
4545 {
4546     return mAkonadiStandardActionManager;
4547 }
4548 
slotRemoveDuplicates()4549 void KMMainWidget::slotRemoveDuplicates()
4550 {
4551     auto job = new RemoveDuplicateMailJob(mFolderTreeWidget->folderTreeView()->selectionModel(), this, this);
4552     job->start();
4553 }
4554 
slotServerSideSubscription()4555 void KMMainWidget::slotServerSideSubscription()
4556 {
4557     if (!mCurrentCollection.isValid()) {
4558         return;
4559     }
4560     auto job = new PimCommon::ManageServerSideSubscriptionJob(this);
4561     job->setCurrentCollection(mCurrentCollection);
4562     job->setParentWidget(this);
4563     job->start();
4564 }
4565 
slotAccountSettings()4566 void KMMainWidget::slotAccountSettings()
4567 {
4568     if (!mCurrentCollection.isValid() || mCurrentCollection.parentCollection() != Akonadi::Collection::root()) {
4569         return;
4570     }
4571 
4572     auto instance = Akonadi::AgentManager::self()->instance(mCurrentCollection.resource());
4573     if (!instance.isValid()) {
4574         return;
4575     }
4576 
4577     QPointer<AgentConfigurationDialog> dlg = new AgentConfigurationDialog(instance, this);
4578     dlg->exec();
4579     delete dlg;
4580 }
4581 
slotRestartAccount()4582 void KMMainWidget::slotRestartAccount()
4583 {
4584     if (!mCurrentCollection.isValid() || mCurrentCollection.parentCollection() != Akonadi::Collection::root()) {
4585         return;
4586     }
4587 
4588     auto instance = Akonadi::AgentManager::self()->instance(mCurrentCollection.resource());
4589     if (!instance.isValid()) {
4590         return;
4591     }
4592 
4593     instance.restart();
4594 }
4595 
savePaneSelection()4596 void KMMainWidget::savePaneSelection()
4597 {
4598     if (mMessagePane) {
4599         mMessagePane->saveCurrentSelection();
4600     }
4601 }
4602 
updatePaneTagComboBox()4603 void KMMainWidget::updatePaneTagComboBox()
4604 {
4605     if (mMessagePane) {
4606         mMessagePane->updateTagComboBox();
4607     }
4608 }
4609 
slotCreateAddressBookContact()4610 void KMMainWidget::slotCreateAddressBookContact()
4611 {
4612     auto job = new CreateNewContactJob(this, this);
4613     job->start();
4614 }
4615 
slotOpenRecentMessage(const QUrl & url)4616 void KMMainWidget::slotOpenRecentMessage(const QUrl &url)
4617 {
4618     auto openCommand = new KMOpenMsgCommand(this, url, overrideEncoding(), this);
4619     openCommand->start();
4620 }
4621 
addRecentFile(const QUrl & url)4622 void KMMainWidget::addRecentFile(const QUrl &url)
4623 {
4624     mOpenRecentMenu->addUrl(url);
4625 }
4626 
slotMoveMessageToTrash()4627 void KMMainWidget::slotMoveMessageToTrash()
4628 {
4629     if (messageView() && messageView()->viewer() && mCurrentCollection.isValid()) {
4630         auto command = new KMTrashMsgCommand(mCurrentCollection, messageView()->viewer()->messageItem(), -1);
4631         command->start();
4632     }
4633 }
4634 
slotArchiveMails()4635 void KMMainWidget::slotArchiveMails()
4636 {
4637     if (mCurrentCollection.isValid()) {
4638         const Akonadi::Item::List selectedMessages = mMessagePane->selectionAsMessageItemList();
4639         KMKernel::self()->folderArchiveManager()->setArchiveItems(selectedMessages, mCurrentCollection.resource());
4640     }
4641 }
4642 
updateQuickSearchLineText()4643 void KMMainWidget::updateQuickSearchLineText()
4644 {
4645     // If change change shortcut
4646     mMessagePane->setQuickSearchClickMessage(
4647         i18nc("Show shortcut for focus quick search. Don't change it", "Search... <%1>", mQuickSearchAction->shortcut().toString()));
4648 }
4649 
slotChangeDisplayMessageFormat(MessageViewer::Viewer::DisplayFormatMessage format)4650 void KMMainWidget::slotChangeDisplayMessageFormat(MessageViewer::Viewer::DisplayFormatMessage format)
4651 {
4652     if (format == MessageViewer::Viewer::Html) {
4653         const int result = KMessageBox::warningContinueCancel(this,
4654                                                               // the warning text is taken from configuredialog.cpp:
4655                                                               i18n("Use of HTML in mail will make you more vulnerable to "
4656                                                                    "\"spam\" and may increase the likelihood that your system will be "
4657                                                                    "compromised by other present and anticipated security exploits."),
4658                                                               i18n("Security Warning"),
4659                                                               KGuiItem(i18n("Use HTML")),
4660                                                               KStandardGuiItem::cancel(),
4661                                                               QStringLiteral("OverrideHtmlWarning"),
4662                                                               KMessageBox::Option());
4663         if (result == KMessageBox::Cancel) {
4664             mDisplayMessageFormatMenu->setDisplayMessageFormat(MessageViewer::Viewer::Text);
4665             return;
4666         }
4667     }
4668     mFolderDisplayFormatPreference = format;
4669 
4670     // Update mPrefererHtmlLoadExtAction
4671     const bool useHtml = (mFolderDisplayFormatPreference == MessageViewer::Viewer::Html
4672                           || (mHtmlGlobalSetting && mFolderDisplayFormatPreference == MessageViewer::Viewer::UseGlobalSetting));
4673     mPreferHtmlLoadExtAction->setEnabled(useHtml);
4674 
4675     if (mMsgView) {
4676         mMsgView->setDisplayFormatMessageOverwrite(mFolderDisplayFormatPreference);
4677         mMsgView->update(true);
4678     }
4679     writeFolderConfig();
4680 }
4681 
populateMessageListStatusFilterCombo()4682 void KMMainWidget::populateMessageListStatusFilterCombo()
4683 {
4684     mMessagePane->populateStatusFilterCombo();
4685 }
4686 
slotCollectionRemoved(const Akonadi::Collection & col)4687 void KMMainWidget::slotCollectionRemoved(const Akonadi::Collection &col)
4688 {
4689     if (mFavoritesModel) {
4690         mFavoritesModel->removeCollection(col);
4691     }
4692 }
4693 
slotMarkAllMessageAsReadInCurrentFolderAndSubfolder()4694 void KMMainWidget::slotMarkAllMessageAsReadInCurrentFolderAndSubfolder()
4695 {
4696     if (mCurrentCollection.isValid()) {
4697         auto job = new MarkAllMessagesAsReadInFolderAndSubFolderJob(this);
4698         job->setTopLevelCollection(mCurrentCollection);
4699         job->start();
4700     }
4701 }
4702 
slotRemoveDuplicateRecursive()4703 void KMMainWidget::slotRemoveDuplicateRecursive()
4704 {
4705     if (mCurrentCollection.isValid()) {
4706         auto job = new RemoveDuplicateMessageInFolderAndSubFolderJob(this, this);
4707         job->setTopLevelCollection(mCurrentCollection);
4708         job->start();
4709     }
4710 }
4711 
slotUpdateConfig()4712 void KMMainWidget::slotUpdateConfig()
4713 {
4714     updateDisplayFormatMessage();
4715 }
4716 
printCurrentMessage(bool preview)4717 void KMMainWidget::printCurrentMessage(bool preview)
4718 {
4719     bool result = false;
4720     if (messageView() && messageView()->viewer()) {
4721         if (MessageViewer::MessageViewerSettings::self()->printSelectedText()) {
4722             result = messageView()->printSelectedText(preview);
4723         }
4724     }
4725     if (!result) {
4726         const bool useFixedFont = MessageViewer::MessageViewerSettings::self()->useFixedFont();
4727         const QString overrideEncoding = MessageCore::MessageCoreSettings::self()->overrideCharacterEncoding();
4728 
4729         const Akonadi::Item currentItem = messageView()->viewer()->messageItem();
4730 
4731         KMPrintCommandInfo commandInfo;
4732         commandInfo.mMsg = currentItem;
4733         commandInfo.mHeaderStylePlugin = messageView()->viewer()->headerStylePlugin();
4734         commandInfo.mFormat = messageView()->viewer()->displayFormatMessageOverwrite();
4735         commandInfo.mHtmlLoadExtOverride = messageView()->viewer()->htmlLoadExternal();
4736         commandInfo.mPrintPreview = preview;
4737         commandInfo.mUseFixedFont = useFixedFont;
4738         commandInfo.mOverrideFont = overrideEncoding;
4739         commandInfo.mShowSignatureDetails =
4740             messageView()->viewer()->showSignatureDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails();
4741         commandInfo.mShowEncryptionDetails =
4742             messageView()->viewer()->showEncryptionDetails() || MessageViewer::MessageViewerSettings::self()->alwaysShowEncryptionSignatureDetails();
4743 
4744         auto command = new KMPrintCommand(this, commandInfo);
4745         command->start();
4746     }
4747 }
4748 
slotRedirectCurrentMessage()4749 void KMMainWidget::slotRedirectCurrentMessage()
4750 {
4751     if (messageView() && messageView()->viewer()) {
4752         const Akonadi::Item currentItem = messageView()->viewer()->messageItem();
4753         if (!currentItem.hasPayload<KMime::Message::Ptr>()) {
4754             return;
4755         }
4756         auto command = new KMRedirectCommand(this, currentItem);
4757         command->start();
4758     }
4759 }
4760 
replyMessageTo(const Akonadi::Item & item,bool replyToAll)4761 void KMMainWidget::replyMessageTo(const Akonadi::Item &item, bool replyToAll)
4762 {
4763     auto command = new KMReplyCommand(this, item, replyToAll ? MessageComposer::ReplyAll : MessageComposer::ReplyAuthor);
4764     command->setReplyAsHtml(messageView()->htmlMail());
4765     command->start();
4766 }
4767 
slotReplyMessageTo(const KMime::Message::Ptr & message,bool replyToAll)4768 void KMMainWidget::slotReplyMessageTo(const KMime::Message::Ptr &message, bool replyToAll)
4769 {
4770     Akonadi::Item item;
4771 
4772     item.setPayload<KMime::Message::Ptr>(message);
4773     item.setMimeType(KMime::Message::mimeType());
4774     replyMessageTo(item, replyToAll);
4775 }
4776 
showMessageActivities(const QString & str)4777 void KMMainWidget::showMessageActivities(const QString &str)
4778 {
4779     BroadcastStatus::instance()->setStatusMsg(str);
4780     PimCommon::LogActivitiesManager::self()->appendLog(str);
4781 }
4782 
slotCopyDecryptedTo(QAction * action)4783 void KMMainWidget::slotCopyDecryptedTo(QAction *action)
4784 {
4785     if (action) {
4786         const auto index = action->data().toModelIndex();
4787         const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
4788 
4789         auto command = new KMCopyDecryptedCommand(collection, mMessagePane->selectionAsMessageItemList());
4790         command->start();
4791     }
4792 }
4793 
slotSetFocusToViewer()4794 void KMMainWidget::slotSetFocusToViewer()
4795 {
4796     if (messageView() && messageView()->viewer()) {
4797         messageView()->viewer()->setFocus();
4798     }
4799 }
4800 
setShowStatusBarMessage(const QString & msg)4801 void KMMainWidget::setShowStatusBarMessage(const QString &msg)
4802 {
4803     if (mCurrentStatusBar) {
4804         mCurrentStatusBar->showMessage(msg);
4805     }
4806 }
4807 
setupUnifiedMailboxChecker()4808 void KMMainWidget::setupUnifiedMailboxChecker()
4809 {
4810     if (!KMailSettings::self()->askEnableUnifiedMailboxes()) {
4811         return;
4812     }
4813 
4814     const auto ask = [this]() {
4815         if (!KMailSettings::self()->askEnableUnifiedMailboxes()) {
4816             return;
4817         }
4818 
4819         if (kmkernel->accounts().count() <= 1) {
4820             return;
4821         }
4822 
4823         KMailSettings::self()->setAskEnableUnifiedMailboxes(false);
4824 
4825         const auto service = Akonadi::ServerManager::self()->agentServiceName(Akonadi::ServerManager::Agent, QStringLiteral("akonadi_unifiedmailbox_agent"));
4826         QDBusInterface iface(service, QStringLiteral("/"), QStringLiteral("org.freedesktop.Akonadi.UnifiedMailboxAgent"), QDBusConnection::sessionBus(), this);
4827         if (!iface.isValid()) {
4828             return;
4829         }
4830 
4831         QDBusReply<bool> reply = iface.call(QStringLiteral("enabledAgent"));
4832         if (!reply.isValid() || bool(reply)) {
4833             return;
4834         }
4835 
4836         const auto answer = KMessageBox::questionYesNo(
4837             this,
4838             i18n("You have more than one email account set up.\nDo you want to enable the Unified Mailbox feature to "
4839                  "show unified content of your inbox, sent and drafts folders?\n"
4840                  "You can configure unified mailboxes, create custom ones or\ndisable the feature completely in KMail's Plugin settings."),
4841             i18n("Enable Unified Mailboxes?"),
4842             KGuiItem(i18n("Enable Unified Mailboxes"), QStringLiteral("dialog-ok")),
4843             KGuiItem(i18n("Cancel"), QStringLiteral("dialog-cancel")));
4844         if (answer == KMessageBox::Yes) {
4845             iface.call(QStringLiteral("setEnableAgent"), true);
4846         }
4847     };
4848 
4849     connect(kmkernel, &KMKernel::incomingAccountsChanged, this, ask);
4850 
4851     // Wait for a bit before asking so we at least have the window on screen
4852     QTimer::singleShot(500ms, this, ask);
4853 }
4854