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