File indexing completed on 2024-05-12 05:20:44

0001 /*
0002     This file is part of KMail, the KDE mail client.
0003     SPDX-FileCopyrightText: 2002 Don Sanders <sanders@kde.org>
0004     SPDX-FileCopyrightText: 2011-2024 Laurent Montel <montel@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only
0007 */
0008 
0009 //
0010 // A toplevel KMainWindow derived class for displaying
0011 // single messages or single message parts.
0012 //
0013 // Could be extended to include support for normal main window
0014 // widgets like a toolbar.
0015 
0016 #include "kmreadermainwin.h"
0017 #include "historyclosedreader/historyclosedreadermanager.h"
0018 #include "job/composenewmessagejob.h"
0019 #include "kmmainwidget.h"
0020 #include "kmreaderwin.h"
0021 #include "widgets/zoomlabelwidget.h"
0022 
0023 #include "kmcommands.h"
0024 #include "messageactions.h"
0025 #include "util.h"
0026 #include <KAcceleratorManager>
0027 #include <KActionMenu>
0028 #include <KEditToolBar>
0029 #include <KLocalizedString>
0030 #include <KMessageBox>
0031 #include <KStandardAction>
0032 #include <KStandardShortcut>
0033 #include <KToolBar>
0034 #include <MailCommon/FolderSettings>
0035 #include <MailCommon/MailKernel>
0036 #include <MessageViewer/HeaderStyle>
0037 #include <MessageViewer/HeaderStylePlugin>
0038 #include <MessageViewer/MessageViewerSettings>
0039 #include <QAction>
0040 #include <QMenu>
0041 #include <QMenuBar>
0042 #include <QStatusBar>
0043 #include <TemplateParser/CustomTemplatesMenu>
0044 
0045 #include "tag/tagactionmanager.h"
0046 #include "tag/tagselectdialog.h"
0047 #include <Akonadi/ContactSearchJob>
0048 #include <KActionCollection>
0049 #include <KEmailAddress>
0050 #include <KMime/KMimeMessage>
0051 #include <WebEngineViewer/WebHitTestResult>
0052 
0053 #include <Akonadi/ItemCopyJob>
0054 #include <Akonadi/ItemCreateJob>
0055 #include <Akonadi/ItemMoveJob>
0056 #include <Akonadi/MessageFlags>
0057 #include <MailCommon/MailUtil>
0058 #include <MessageViewer/DKIMViewerMenu>
0059 #include <MessageViewer/DKIMWidgetInfo>
0060 #include <MessageViewer/RemoteContentMenu>
0061 using namespace MailCommon;
0062 
0063 KMReaderMainWin::KMReaderMainWin(MessageViewer::Viewer::DisplayFormatMessage format, bool htmlLoadExtDefault, const QString &name)
0064     : KMail::SecondaryWindow(!name.isEmpty() ? name : QStringLiteral("readerwindow#"))
0065 {
0066     mReaderWin = new KMReaderWin(this, this, actionCollection());
0067     mReaderWin->setDisplayFormatMessageOverwrite(format);
0068     mReaderWin->setHtmlLoadExtDefault(htmlLoadExtDefault);
0069     mReaderWin->setDecryptMessageOverwrite(true);
0070     initKMReaderMainWin();
0071 }
0072 
0073 KMReaderMainWin::KMReaderMainWin(const QString &name)
0074     : KMail::SecondaryWindow(!name.isEmpty() ? name : QStringLiteral("readerwindow#"))
0075 {
0076     mReaderWin = new KMReaderWin(this, this, actionCollection());
0077     initKMReaderMainWin();
0078 }
0079 
0080 KMReaderMainWin::KMReaderMainWin(KMime::Content *aMsgPart, MessageViewer::Viewer::DisplayFormatMessage format, const QString &encoding, const QString &name)
0081     : KMail::SecondaryWindow(!name.isEmpty() ? name : QStringLiteral("readerwindow#"))
0082 {
0083     mReaderWin = new KMReaderWin(this, this, actionCollection());
0084     mReaderWin->setOverrideEncoding(encoding);
0085     mReaderWin->setDisplayFormatMessageOverwrite(format);
0086     mReaderWin->setMsgPart(aMsgPart);
0087     initKMReaderMainWin();
0088 }
0089 
0090 void KMReaderMainWin::initKMReaderMainWin()
0091 {
0092     setCentralWidget(mReaderWin);
0093     setupAccel();
0094     setupGUI(Keys | StatusBar | Create, QStringLiteral("kmreadermainwin.rc"));
0095     mMsgActions->setupForwardingActionsList(this);
0096     applyMainWindowSettings(KMKernel::self()->config()->group(QStringLiteral("Separate Reader Window")));
0097     mZoomLabelIndicator = new ZoomLabelWidget(statusBar());
0098     statusBar()->addPermanentWidget(mZoomLabelIndicator);
0099     setZoomChanged(mReaderWin->viewer()->webViewZoomFactor());
0100     statusBar()->addPermanentWidget(mReaderWin->viewer()->dkimWidgetInfo());
0101     if (!mReaderWin->messageItem().isValid()) {
0102         menuBar()->hide();
0103         toolBar(QStringLiteral("mainToolBar"))->hide();
0104     } else {
0105         slotToggleMenubar(true);
0106     }
0107     connect(kmkernel, &KMKernel::configChanged, this, &KMReaderMainWin::slotConfigChanged);
0108     connect(mReaderWin, &KMReaderWin::showStatusBarMessage, this, &KMReaderMainWin::slotShowMessageStatusBar);
0109     connect(mReaderWin, &KMReaderWin::zoomChanged, this, &KMReaderMainWin::setZoomChanged);
0110     connect(mReaderWin, &KMReaderWin::showPreviousMessage, this, &KMReaderMainWin::showPreviousMessage);
0111     connect(mReaderWin, &KMReaderWin::showNextMessage, this, &KMReaderMainWin::showNextMessage);
0112 }
0113 
0114 KMReaderMainWin::~KMReaderMainWin()
0115 {
0116     KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("kmail2rc"))->group(QStringLiteral("Separate Reader Window")));
0117     saveMainWindowSettings(grp);
0118     if (mMsg.isValid()) {
0119         HistoryClosedReaderInfo info;
0120         info.setItem(mMsg.id());
0121         KMime::Message::Ptr message = MessageComposer::Util::message(mMsg);
0122         if (message) {
0123             if (auto subject = message->subject(false)) {
0124                 info.setSubject(subject->asUnicodeString());
0125             }
0126         }
0127         HistoryClosedReaderManager::self()->addInfo(std::move(info));
0128     }
0129 }
0130 
0131 void KMReaderMainWin::setZoomChanged(qreal zoomFactor)
0132 {
0133     if (mZoomLabelIndicator) {
0134         mZoomLabelIndicator->setZoom(zoomFactor);
0135     }
0136 }
0137 
0138 void KMReaderMainWin::slotShowMessageStatusBar(const QString &msg)
0139 {
0140     statusBar()->showMessage(msg);
0141 }
0142 
0143 void KMReaderMainWin::setUseFixedFont(bool useFixedFont)
0144 {
0145     mReaderWin->setUseFixedFont(useFixedFont);
0146 }
0147 
0148 MessageViewer::Viewer *KMReaderMainWin::viewer() const
0149 {
0150     return mReaderWin->viewer();
0151 }
0152 
0153 void KMReaderMainWin::showMessage(const QString &encoding, const Akonadi::Item &msg, const Akonadi::Collection &parentCollection)
0154 {
0155     mParentCollection = parentCollection;
0156     mReaderWin->setOverrideEncoding(encoding);
0157     mReaderWin->setMessage(msg, MimeTreeParser::Force);
0158     KMime::Message::Ptr message = MessageComposer::Util::message(msg);
0159     QString caption;
0160     if (message) {
0161         if (auto subject = message->subject(false)) {
0162             caption = subject->asUnicodeString();
0163         }
0164     }
0165     if (mParentCollection.isValid()) {
0166         caption += QLatin1StringView(" - ");
0167         caption += MailCommon::Util::fullCollectionPath(mParentCollection);
0168     }
0169     if (!caption.isEmpty()) {
0170         setCaption(caption);
0171     }
0172     mMsg = msg;
0173     mMsgActions->setCurrentMessage(msg);
0174     mAkonadiStandardActionManager->setItems({mMsg});
0175 
0176     const bool canChange = mParentCollection.isValid() ? static_cast<bool>(mParentCollection.rights() & Akonadi::Collection::CanDeleteItem) : false;
0177     mTrashAction->setEnabled(canChange);
0178 
0179     if (mReaderWin->viewer() && mReaderWin->viewer()->headerStylePlugin() && mReaderWin->viewer()->headerStylePlugin()->headerStyle()) {
0180         mReaderWin->viewer()->headerStylePlugin()->headerStyle()->setReadOnlyMessage(!canChange);
0181     }
0182 
0183     const bool isInTrashFolder = mParentCollection.isValid() ? CommonKernel->folderIsTrash(mParentCollection) : false;
0184     QAction *moveToTrash = actionCollection()->action(QStringLiteral("move_to_trash"));
0185     KMail::Util::setActionTrashOrDelete(moveToTrash, isInTrashFolder);
0186     updateActions();
0187 }
0188 
0189 void KMReaderMainWin::updateButtons()
0190 {
0191     if (mListMessage.count() <= 1) {
0192         return;
0193     }
0194     mReaderWin->updateShowMultiMessagesButton((mCurrentMessageIndex > 0), (mCurrentMessageIndex < (mListMessage.count() - 1)));
0195 }
0196 
0197 void KMReaderMainWin::showNextMessage()
0198 {
0199     if (mCurrentMessageIndex >= (mListMessage.count() - 1)) {
0200         return;
0201     }
0202     mCurrentMessageIndex++;
0203     initializeMessage(mListMessage.at(mCurrentMessageIndex));
0204     updateButtons();
0205 }
0206 
0207 void KMReaderMainWin::showPreviousMessage()
0208 {
0209     if (mCurrentMessageIndex <= 0) {
0210         return;
0211     }
0212     mCurrentMessageIndex--;
0213     initializeMessage(mListMessage.at(mCurrentMessageIndex));
0214     updateButtons();
0215 }
0216 
0217 void KMReaderMainWin::showMessage(const QString &encoding, const QList<KMime::Message::Ptr> &message)
0218 {
0219     if (message.isEmpty()) {
0220         return;
0221     }
0222 
0223     mListMessage = message;
0224     mReaderWin->setOverrideEncoding(encoding);
0225     mCurrentMessageIndex = 0;
0226     initializeMessage(mListMessage.at(mCurrentMessageIndex));
0227     mReaderWin->hasMultiMessages(message.count() > 1);
0228     mAkonadiStandardActionManager->setItems({});
0229     updateButtons();
0230 }
0231 
0232 void KMReaderMainWin::initializeMessage(const KMime::Message::Ptr &message)
0233 {
0234     Akonadi::Item item;
0235     item.setPayload<KMime::Message::Ptr>(message);
0236     Akonadi::MessageFlags::copyMessageFlags(*message, item);
0237     item.setMimeType(KMime::Message::mimeType());
0238 
0239     mMsg = item;
0240     mMsgActions->setCurrentMessage(item);
0241 
0242     mReaderWin->setMessage(message);
0243     if (auto subject = message->subject(false)) {
0244         setCaption(subject->asUnicodeString());
0245     }
0246     mTrashAction->setEnabled(false);
0247     mAkonadiStandardActionManager->setItems({mMsg});
0248     updateActions();
0249 }
0250 
0251 void KMReaderMainWin::showMessage(const QString &encoding, const KMime::Message::Ptr &message)
0252 {
0253     if (!message) {
0254         return;
0255     }
0256     const QList<KMime::Message::Ptr> lst{message};
0257     showMessage(encoding, lst);
0258 }
0259 
0260 void KMReaderMainWin::updateActions()
0261 {
0262     if (mHideMenuBarAction->isChecked()) {
0263         menuBar()->show();
0264     }
0265     toolBar(QStringLiteral("mainToolBar"))->show();
0266     if (mMsg.isValid()) {
0267         mTagActionManager->updateActionStates(1, mMsg);
0268     }
0269 }
0270 
0271 void KMReaderMainWin::slotReplyOrForwardFinished()
0272 {
0273     if (MessageViewer::MessageViewerSettings::self()->closeAfterReplyOrForward()) {
0274         close();
0275     }
0276 }
0277 
0278 void KMReaderMainWin::slotSelectMoreMessageTagList()
0279 {
0280     const Akonadi::Item::List selectedMessages = {mMsg};
0281     if (selectedMessages.isEmpty()) {
0282         return;
0283     }
0284 
0285     QPointer<TagSelectDialog> dlg = new TagSelectDialog(this, selectedMessages.count(), selectedMessages.first());
0286     dlg->setActionCollection(QList<KActionCollection *>{actionCollection()});
0287     if (dlg->exec()) {
0288         const Akonadi::Tag::List lst = dlg->selectedTag();
0289         KMCommand *command = new KMSetTagCommand(lst, selectedMessages, KMSetTagCommand::CleanExistingAndAddNew);
0290         command->start();
0291     }
0292     delete dlg;
0293 }
0294 
0295 void KMReaderMainWin::slotUpdateMessageTagList(const Akonadi::Tag &tag)
0296 {
0297     // Create a persistent set from the current thread.
0298     const Akonadi::Item::List selectedMessages = {mMsg};
0299     if (selectedMessages.isEmpty()) {
0300         return;
0301     }
0302     toggleMessageSetTag(selectedMessages, tag);
0303 }
0304 
0305 void KMReaderMainWin::toggleMessageSetTag(const Akonadi::Item::List &select, const Akonadi::Tag &tag)
0306 {
0307     if (select.isEmpty()) {
0308         return;
0309     }
0310     KMCommand *command = new KMSetTagCommand(Akonadi::Tag::List() << tag, select, KMSetTagCommand::Toggle);
0311     command->start();
0312 }
0313 
0314 Akonadi::Collection KMReaderMainWin::parentCollection() const
0315 {
0316     if (mParentCollection.isValid()) {
0317         return mParentCollection;
0318     } else {
0319         return mMsg.parentCollection();
0320     }
0321 }
0322 
0323 void KMReaderMainWin::slotTrashMessage()
0324 {
0325     if (!mMsg.isValid()) {
0326         return;
0327     }
0328     auto command = new KMTrashMsgCommand(parentCollection(), mMsg, -1);
0329     command->start();
0330     close();
0331 }
0332 
0333 void KMReaderMainWin::slotForwardInlineMsg()
0334 {
0335     if (!mReaderWin->messageItem().hasPayload<KMime::Message::Ptr>()) {
0336         return;
0337     }
0338     KMCommand *command = nullptr;
0339     const Akonadi::Collection parentCol = mReaderWin->messageItem().parentCollection();
0340     if (parentCol.isValid()) {
0341         QSharedPointer<FolderSettings> fd = FolderSettings::forCollection(parentCol, false);
0342         if (!fd.isNull()) {
0343             command = new KMForwardCommand(this, mReaderWin->messageItem(), fd->identity(), QString(), mReaderWin->copyText());
0344         } else {
0345             command = new KMForwardCommand(this, mReaderWin->messageItem(), 0, QString(), mReaderWin->copyText());
0346         }
0347     } else {
0348         command = new KMForwardCommand(this, mReaderWin->messageItem(), 0, QString(), mReaderWin->copyText());
0349     }
0350     connect(command, &KMTrashMsgCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished);
0351     command->start();
0352 }
0353 
0354 void KMReaderMainWin::slotForwardAttachedMessage()
0355 {
0356     if (!mReaderWin->messageItem().hasPayload<KMime::Message::Ptr>()) {
0357         return;
0358     }
0359     KMCommand *command = nullptr;
0360     const Akonadi::Collection parentCol = mReaderWin->messageItem().parentCollection();
0361     if (parentCol.isValid()) {
0362         QSharedPointer<FolderSettings> fd = FolderSettings::forCollection(parentCol, false);
0363         if (!fd.isNull()) {
0364             command = new KMForwardAttachedCommand(this, mReaderWin->messageItem(), fd->identity());
0365         } else {
0366             command = new KMForwardAttachedCommand(this, mReaderWin->messageItem());
0367         }
0368     } else {
0369         command = new KMForwardAttachedCommand(this, mReaderWin->messageItem());
0370     }
0371 
0372     connect(command, &KMForwardAttachedCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished);
0373     command->start();
0374 }
0375 
0376 void KMReaderMainWin::slotNewMessageToRecipients()
0377 {
0378     auto job = new ComposeNewMessageJob;
0379 
0380     const Akonadi::Collection parentCol = mReaderWin->messageItem().parentCollection();
0381     if (parentCol.isValid()) {
0382         job->setCurrentCollection(parentCol);
0383 
0384         QSharedPointer<FolderSettings> fd = FolderSettings::forCollection(parentCol, false);
0385         if (!fd.isNull()) {
0386             job->setFolderSettings(fd);
0387         }
0388     }
0389 
0390     job->setRecipientsFromMessage(mReaderWin->messageItem());
0391     job->start();
0392 }
0393 
0394 void KMReaderMainWin::slotRedirectMessage()
0395 {
0396     const Akonadi::Item currentItem = mReaderWin->messageItem();
0397     if (!currentItem.hasPayload<KMime::Message::Ptr>()) {
0398         return;
0399     }
0400     auto command = new KMRedirectCommand(this, currentItem);
0401     connect(command, &KMRedirectCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished);
0402     command->start();
0403 }
0404 
0405 void KMReaderMainWin::slotCustomReplyToMsg(const QString &tmpl)
0406 {
0407     const Akonadi::Item currentItem = mReaderWin->messageItem();
0408     if (!currentItem.hasPayload<KMime::Message::Ptr>()) {
0409         return;
0410     }
0411     auto command = new KMReplyCommand(this, currentItem, MessageComposer::ReplySmart, mReaderWin->copyText(), false, tmpl);
0412     command->setReplyAsHtml(mReaderWin->htmlMail());
0413     connect(command, &KMReplyCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished);
0414     command->start();
0415 }
0416 
0417 void KMReaderMainWin::slotCustomReplyAllToMsg(const QString &tmpl)
0418 {
0419     const Akonadi::Item currentItem = mReaderWin->messageItem();
0420     if (!currentItem.hasPayload<KMime::Message::Ptr>()) {
0421         return;
0422     }
0423     auto command = new KMReplyCommand(this, currentItem, MessageComposer::ReplyAll, mReaderWin->copyText(), false, tmpl);
0424     command->setReplyAsHtml(mReaderWin->htmlMail());
0425     connect(command, &KMReplyCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished);
0426 
0427     command->start();
0428 }
0429 
0430 void KMReaderMainWin::slotCustomForwardMsg(const QString &tmpl)
0431 {
0432     const Akonadi::Item currentItem = mReaderWin->messageItem();
0433     if (!currentItem.hasPayload<KMime::Message::Ptr>()) {
0434         return;
0435     }
0436     auto command = new KMForwardCommand(this, currentItem, 0, tmpl, mReaderWin->copyText());
0437     connect(command, &KMForwardCommand::completed, this, &KMReaderMainWin::slotReplyOrForwardFinished);
0438 
0439     command->start();
0440 }
0441 
0442 void KMReaderMainWin::slotConfigChanged()
0443 {
0444     // readConfig();
0445     mMsgActions->setupForwardActions(actionCollection());
0446     mMsgActions->setupForwardingActionsList(this);
0447 }
0448 
0449 void KMReaderMainWin::initializeAkonadiStandardAction()
0450 {
0451     const auto mailActions = {Akonadi::StandardMailActionManager::MarkAllMailAsRead,
0452                               Akonadi::StandardMailActionManager::MarkMailAsRead,
0453                               Akonadi::StandardMailActionManager::MarkMailAsUnread,
0454                               Akonadi::StandardMailActionManager::MarkMailAsImportant,
0455                               Akonadi::StandardMailActionManager::MarkMailAsActionItem};
0456 
0457     for (const Akonadi::StandardMailActionManager::Type mailAction : mailActions) {
0458         QAction *act = mAkonadiStandardActionManager->createAction(mailAction);
0459         mAkonadiStandardActionManager->interceptAction(mailAction);
0460         connect(act, &QAction::triggered, this, &KMReaderMainWin::slotMarkMailAs);
0461     }
0462 }
0463 
0464 void KMReaderMainWin::slotMarkMailAs()
0465 {
0466     const QAction *action = qobject_cast<QAction *>(sender());
0467     Q_ASSERT(action);
0468 
0469     const QByteArray typeStr = action->data().toByteArray();
0470 
0471     mAkonadiStandardActionManager->markItemsAs(typeStr, {mMsgActions->currentItem()}, false);
0472 }
0473 
0474 void KMReaderMainWin::setupAccel()
0475 {
0476     if (!kmkernel->xmlGuiInstanceName().isEmpty()) {
0477         setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2"));
0478     }
0479     mMsgActions = new KMail::MessageActions(actionCollection(), this);
0480     mAkonadiStandardActionManager = new Akonadi::StandardMailActionManager(actionCollection(), this);
0481     initializeAkonadiStandardAction();
0482     mMsgActions->fillAkonadiStandardAction(mAkonadiStandardActionManager);
0483     mMsgActions->setMessageView(mReaderWin);
0484     connect(mMsgActions, &KMail::MessageActions::replyActionFinished, this, &KMReaderMainWin::slotReplyOrForwardFinished);
0485 
0486     //----- File Menu
0487 
0488     mSaveAtmAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Save A&ttachments..."), actionCollection());
0489     connect(mSaveAtmAction, &QAction::triggered, mReaderWin->viewer(), &MessageViewer::Viewer::slotAttachmentSaveAll);
0490 
0491     mTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Move to Trash"), this);
0492     mTrashAction->setIconText(i18nc("@action:intoolbar Move to Trash", "Trash"));
0493     KMail::Util::addQActionHelpText(mTrashAction, i18n("Move message to trashcan"));
0494     actionCollection()->addAction(QStringLiteral("move_to_trash"), mTrashAction);
0495     actionCollection()->setDefaultShortcut(mTrashAction, QKeySequence(Qt::Key_Delete));
0496     connect(mTrashAction, &QAction::triggered, this, &KMReaderMainWin::slotTrashMessage);
0497 
0498     QAction *closeAction = KStandardAction::close(this, &KMReaderMainWin::close, actionCollection());
0499     QList<QKeySequence> closeShortcut = closeAction->shortcuts();
0500     closeAction->setShortcuts(closeShortcut << QKeySequence(Qt::Key_Escape));
0501 
0502     mTagActionManager = new KMail::TagActionManager(this, actionCollection(), mMsgActions, this);
0503     connect(mTagActionManager, &KMail::TagActionManager::tagActionTriggered, this, &KMReaderMainWin::slotUpdateMessageTagList);
0504 
0505     connect(mTagActionManager, &KMail::TagActionManager::tagMoreActionClicked, this, &KMReaderMainWin::slotSelectMoreMessageTagList);
0506 
0507     mTagActionManager->createActions();
0508     if (mReaderWin->messageItem().isValid()) {
0509         mTagActionManager->updateActionStates(1, mReaderWin->messageItem());
0510     }
0511     mHideMenuBarAction = KStandardAction::showMenubar(this, &KMReaderMainWin::slotToggleMenubar, actionCollection());
0512     mHideMenuBarAction->setChecked(KMailSettings::self()->readerShowMenuBar());
0513 
0514     //----- Message Menu
0515     connect(mReaderWin->viewer(), &MessageViewer::Viewer::displayPopupMenu, this, &KMReaderMainWin::slotMessagePopup);
0516 
0517     connect(mReaderWin->viewer(), &MessageViewer::Viewer::itemRemoved, this, &QWidget::close);
0518 
0519     setStandardToolBarMenuEnabled(true);
0520     KStandardAction::configureToolbars(this, &KMReaderMainWin::slotEditToolbars, actionCollection());
0521     connect(mReaderWin->viewer(), &MessageViewer::Viewer::moveMessageToTrash, this, &KMReaderMainWin::slotTrashMessage);
0522 }
0523 
0524 void KMReaderMainWin::slotToggleMenubar(bool dontShowWarning)
0525 {
0526     if (!mReaderWin->messageItem().isValid()) {
0527         return;
0528     }
0529     if (menuBar()) {
0530         if (mHideMenuBarAction->isChecked()) {
0531             menuBar()->show();
0532         } else {
0533             if (!dontShowWarning) {
0534                 const QString accel = mHideMenuBarAction->shortcut().toString();
0535                 KMessageBox::information(this,
0536                                          i18n("<qt>This will hide the menu bar completely."
0537                                               " You can show it again by typing %1.</qt>",
0538                                               accel),
0539                                          i18n("Hide menu bar"),
0540                                          QStringLiteral("HideMenuBarWarning"));
0541             }
0542             menuBar()->hide();
0543         }
0544         KMailSettings::self()->setReaderShowMenuBar(mHideMenuBarAction->isChecked());
0545     }
0546 }
0547 
0548 QAction *KMReaderMainWin::copyActionMenu(QMenu *menu)
0549 {
0550     KMMainWidget *mainwin = kmkernel->getKMMainWidget();
0551     if (mainwin) {
0552         Akonadi::StandardActionManager *manager = mainwin->standardMailActionManager()->standardActionManager();
0553         const auto mainWinAction = manager->action(Akonadi::StandardActionManager::CopyItemToMenu);
0554         auto action = new KActionMenu(menu);
0555         action->setIcon(mainWinAction->icon());
0556         action->setText(mainWinAction->text());
0557         manager->createActionFolderMenu(action->menu(), Akonadi::StandardActionManager::CopyItemToMenu);
0558         connect(action->menu(), &QMenu::triggered, this, &KMReaderMainWin::slotCopyItem);
0559         return action;
0560     }
0561     return nullptr;
0562 }
0563 
0564 QAction *KMReaderMainWin::moveActionMenu(QMenu *menu)
0565 {
0566     KMMainWidget *mainwin = kmkernel->getKMMainWidget();
0567     if (mainwin) {
0568         Akonadi::StandardActionManager *manager = mainwin->standardMailActionManager()->standardActionManager();
0569         const auto mainWinAction = manager->action(Akonadi::StandardActionManager::MoveItemToMenu);
0570         auto action = new KActionMenu(menu);
0571         action->setIcon(mainWinAction->icon());
0572         action->setText(mainWinAction->text());
0573         manager->createActionFolderMenu(action->menu(), Akonadi::StandardActionManager::MoveItemToMenu);
0574         connect(action->menu(), &QMenu::triggered, this, &KMReaderMainWin::slotMoveItem);
0575         return action;
0576     }
0577     return nullptr;
0578 }
0579 
0580 void KMReaderMainWin::slotMoveItem(QAction *action)
0581 {
0582     if (action) {
0583         const QModelIndex index = action->data().toModelIndex();
0584         const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0585         copyOrMoveItem(collection, true);
0586     }
0587 }
0588 
0589 void KMReaderMainWin::copyOrMoveItem(const Akonadi::Collection &collection, bool move)
0590 {
0591     if (mMsg.isValid()) {
0592         if (move) {
0593             auto job = new Akonadi::ItemMoveJob(mMsg, collection, this);
0594             connect(job, &KJob::result, this, &KMReaderMainWin::slotCopyMoveResult);
0595         } else {
0596             auto job = new Akonadi::ItemCopyJob(mMsg, collection, this);
0597             connect(job, &KJob::result, this, &KMReaderMainWin::slotCopyMoveResult);
0598         }
0599     } else {
0600         auto job = new Akonadi::ItemCreateJob(mMsg, collection, this);
0601         connect(job, &KJob::result, this, &KMReaderMainWin::slotCopyMoveResult);
0602     }
0603 
0604     KMMainWidget *mainwin = kmkernel->getKMMainWidget();
0605     if (mainwin) {
0606         mainwin->standardMailActionManager()->standardActionManager()->addRecentCollection(collection.id());
0607     }
0608 }
0609 
0610 void KMReaderMainWin::slotCopyItem(QAction *action)
0611 {
0612     if (action) {
0613         const QModelIndex index = action->data().toModelIndex();
0614         const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0615         copyOrMoveItem(collection, false);
0616     }
0617 }
0618 
0619 void KMReaderMainWin::slotCopyMoveResult(KJob *job)
0620 {
0621     if (job->error()) {
0622         KMessageBox::error(this, i18n("Cannot copy item. %1", job->errorString()));
0623     }
0624 }
0625 
0626 void KMReaderMainWin::slotMessagePopup(const Akonadi::Item &aMsg, const WebEngineViewer::WebHitTestResult &result, const QPoint &aPoint)
0627 {
0628     QUrl aUrl = result.linkUrl();
0629     QUrl imageUrl = result.imageUrl();
0630     mMsg = aMsg;
0631 
0632     const QString email = KEmailAddress::firstEmailAddress(aUrl.path()).toLower();
0633     if (aUrl.scheme() == QLatin1StringView("mailto") && !email.isEmpty()) {
0634         auto job = new Akonadi::ContactSearchJob(this);
0635         job->setLimit(1);
0636         job->setQuery(Akonadi::ContactSearchJob::Email, email, Akonadi::ContactSearchJob::ExactMatch);
0637         job->setProperty("msg", QVariant::fromValue(mMsg));
0638         job->setProperty("point", aPoint);
0639         job->setProperty("imageUrl", imageUrl);
0640         job->setProperty("url", aUrl);
0641         job->setProperty("webhitresult", QVariant::fromValue(result));
0642         connect(job, &Akonadi::ItemCopyJob::result, this, &KMReaderMainWin::slotContactSearchJobForMessagePopupDone);
0643     } else {
0644         showMessagePopup(mMsg, aUrl, imageUrl, aPoint, false, false, result);
0645     }
0646 }
0647 
0648 void KMReaderMainWin::slotContactSearchJobForMessagePopupDone(KJob *job)
0649 {
0650     const Akonadi::ContactSearchJob *searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
0651     const bool contactAlreadyExists = !searchJob->contacts().isEmpty();
0652 
0653     const Akonadi::Item::List listContact = searchJob->items();
0654     const bool uniqueContactFound = (listContact.count() == 1);
0655     if (uniqueContactFound) {
0656         mReaderWin->setContactItem(listContact.first(), searchJob->contacts().at(0));
0657     } else {
0658         mReaderWin->clearContactItem();
0659     }
0660 
0661     const auto msg = job->property("msg").value<Akonadi::Item>();
0662     const QPoint aPoint = job->property("point").toPoint();
0663     const QUrl imageUrl = job->property("imageUrl").toUrl();
0664     const QUrl url = job->property("url").toUrl();
0665     const auto result = job->property("webhitresult").value<WebEngineViewer::WebHitTestResult>();
0666 
0667     showMessagePopup(msg, url, imageUrl, aPoint, contactAlreadyExists, uniqueContactFound, result);
0668 }
0669 
0670 void KMReaderMainWin::showMessagePopup(const Akonadi::Item &msg,
0671                                        const QUrl &url,
0672                                        const QUrl &imageUrl,
0673                                        const QPoint &aPoint,
0674                                        bool contactAlreadyExists,
0675                                        bool uniqueContactFound,
0676                                        const WebEngineViewer::WebHitTestResult &result)
0677 {
0678     QMenu *menu = nullptr;
0679 
0680     bool urlMenuAdded = false;
0681     bool copyAdded = false;
0682     const bool messageHasPayload = msg.hasPayload<KMime::Message::Ptr>();
0683     if (!url.isEmpty()) {
0684         if (url.scheme() == QLatin1StringView("mailto")) {
0685             // popup on a mailto URL
0686             menu = new QMenu(this);
0687             menu->addAction(mReaderWin->mailToComposeAction());
0688             if (messageHasPayload) {
0689                 menu->addAction(mReaderWin->mailToReplyAction());
0690                 menu->addAction(mReaderWin->mailToForwardAction());
0691                 menu->addSeparator();
0692             }
0693 
0694             if (contactAlreadyExists) {
0695                 if (uniqueContactFound) {
0696                     menu->addAction(mReaderWin->editContactAction());
0697                 } else {
0698                     menu->addAction(mReaderWin->openAddrBookAction());
0699                 }
0700             } else {
0701                 menu->addAction(mReaderWin->addAddrBookAction());
0702                 menu->addAction(mReaderWin->addToExistingContactAction());
0703             }
0704             menu->addSeparator();
0705             menu->addMenu(mReaderWin->viewHtmlOption());
0706             menu->addSeparator();
0707             menu->addAction(mReaderWin->copyURLAction());
0708             copyAdded = true;
0709             urlMenuAdded = true;
0710         } else if (url.scheme() != QLatin1StringView("attachment")) {
0711             // popup on a not-mailto URL
0712             menu = new QMenu(this);
0713             menu->addAction(mReaderWin->urlOpenAction());
0714             menu->addAction(mReaderWin->addUrlToBookmarkAction());
0715             menu->addAction(mReaderWin->urlSaveAsAction());
0716             menu->addAction(mReaderWin->copyURLAction());
0717             menu->addSeparator();
0718             menu->addAction(mReaderWin->shareServiceUrlMenu());
0719             menu->addSeparator();
0720             menu->addActions(mReaderWin->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedUrl));
0721             if (!imageUrl.isEmpty()) {
0722                 menu->addSeparator();
0723                 mReaderWin->addImageMenuActions(menu);
0724             }
0725             urlMenuAdded = true;
0726         }
0727     }
0728     const QString selectedText(mReaderWin->copyText());
0729     if (!selectedText.isEmpty()) {
0730         if (!menu) {
0731             menu = new QMenu(this);
0732         }
0733         if (urlMenuAdded) {
0734             menu->addSeparator();
0735         }
0736         if (messageHasPayload) {
0737             menu->addAction(mMsgActions->replyMenu());
0738             menu->addSeparator();
0739             menu->addAction(mMsgActions->mailingListActionMenu());
0740             menu->addSeparator();
0741         }
0742         if (!copyAdded) {
0743             menu->addAction(mReaderWin->copyAction());
0744         }
0745         menu->addAction(mReaderWin->selectAllAction());
0746         menu->addSeparator();
0747         mMsgActions->addWebShortcutsMenu(menu, selectedText);
0748         menu->addSeparator();
0749         menu->addActions(mReaderWin->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedSelection));
0750 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT
0751         menu->addSeparator();
0752         menu->addAction(mReaderWin->speakTextAction());
0753 #endif
0754         menu->addSeparator();
0755         menu->addAction(mReaderWin->shareTextAction());
0756     } else if (!urlMenuAdded) {
0757         if (!menu) {
0758             menu = new QMenu(this);
0759         }
0760 
0761         // popup somewhere else (i.e., not a URL) on the message
0762         if (messageHasPayload) {
0763             bool replyForwardMenu = false;
0764             Akonadi::Collection col = parentCollection();
0765             if (col.isValid()) {
0766                 if (!(CommonKernel->folderIsSentMailFolder(col) || CommonKernel->folderIsDrafts(col) || CommonKernel->folderIsTemplates(col))) {
0767                     replyForwardMenu = true;
0768                 }
0769             } else if (messageHasPayload) {
0770                 replyForwardMenu = true;
0771             }
0772 
0773             if (replyForwardMenu) {
0774                 // add the reply and forward actions only if we are not in a sent-mail,
0775                 // templates or drafts folder
0776                 menu->addAction(mMsgActions->replyMenu());
0777                 menu->addAction(mMsgActions->forwardMenu());
0778                 menu->addSeparator();
0779             } else if (col.isValid() && CommonKernel->folderIsTemplates(col)) {
0780                 menu->addAction(mMsgActions->newMessageFromTemplateAction());
0781             }
0782 
0783             if (col.isValid() && CommonKernel->folderIsSentMailFolder(col)) {
0784                 menu->addAction(mMsgActions->sendAgainAction());
0785                 menu->addSeparator();
0786             }
0787 
0788             menu->addAction(copyActionMenu(menu));
0789             if (col.isValid()) {
0790                 menu->addAction(moveActionMenu(menu));
0791             }
0792             menu->addSeparator();
0793             menu->addAction(mMsgActions->mailingListActionMenu());
0794 
0795             menu->addSeparator();
0796             if (!imageUrl.isEmpty()) {
0797                 menu->addSeparator();
0798                 mReaderWin->addImageMenuActions(menu);
0799                 menu->addSeparator();
0800             }
0801 
0802             menu->addAction(mMsgActions->printPreviewAction());
0803             menu->addAction(mMsgActions->printAction());
0804             menu->addSeparator();
0805             menu->addAction(mReaderWin->saveAsAction());
0806             menu->addSeparator();
0807             menu->addAction(mMsgActions->exportToPdfAction());
0808             menu->addSeparator();
0809             menu->addAction(mSaveAtmAction);
0810             if (msg.isValid()) {
0811                 if (mReaderWin->dkimViewerMenu()) {
0812                     menu->addSeparator();
0813                     menu->addMenu(mReaderWin->dkimViewerMenu()->menu());
0814                 }
0815                 menu->addSeparator();
0816                 if (mReaderWin->remoteContentMenu()) {
0817                     menu->addMenu(mReaderWin->remoteContentMenu());
0818                     menu->addSeparator();
0819                 }
0820                 menu->addActions(mReaderWin->viewerPluginActionList(MessageViewer::ViewerPluginInterface::NeedMessage));
0821             }
0822         } else {
0823             menu->addAction(mReaderWin->toggleFixFontAction());
0824             if (!mReaderWin->mimePartTreeIsEmpty()) {
0825                 menu->addAction(mReaderWin->toggleMimePartTreeAction());
0826             }
0827         }
0828         if (msg.isValid()) {
0829             menu->addSeparator();
0830             menu->addAction(mMsgActions->addFollowupReminderAction());
0831         }
0832         if (kmkernel->allowToDebug()) {
0833             menu->addSeparator();
0834             menu->addAction(mMsgActions->debugAkonadiSearchAction());
0835             menu->addSeparator();
0836             menu->addAction(mReaderWin->developmentToolsAction());
0837         }
0838     }
0839     const QList<QAction *> interceptorUrlActions = mReaderWin->interceptorUrlActions(result);
0840     if (!interceptorUrlActions.isEmpty()) {
0841         menu->addSeparator();
0842         menu->addActions(interceptorUrlActions);
0843     }
0844 
0845     if (menu) {
0846         KAcceleratorManager::manage(menu);
0847         menu->exec(aPoint, nullptr);
0848         delete menu;
0849     }
0850 }
0851 
0852 void KMReaderMainWin::showAndActivateWindow()
0853 {
0854     show();
0855     raise();
0856     activateWindow();
0857 }
0858 
0859 void KMReaderMainWin::slotEditToolbars()
0860 {
0861     KConfigGroup grp(KMKernel::self()->config(), QStringLiteral("ReaderWindow"));
0862     saveMainWindowSettings(grp);
0863     QPointer<KEditToolBar> dlg = new KEditToolBar(guiFactory(), this);
0864     connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KMReaderMainWin::slotUpdateToolbars);
0865     dlg->exec();
0866     delete dlg;
0867 }
0868 
0869 void KMReaderMainWin::slotUpdateToolbars()
0870 {
0871     createGUI(QStringLiteral("kmreadermainwin.rc"));
0872     applyMainWindowSettings(KConfigGroup(KMKernel::self()->config(), QStringLiteral("ReaderWindow")));
0873 }
0874 
0875 #include "moc_kmreadermainwin.cpp"