diff --git a/src/editor/kmcomposerwin.cpp b/src/editor/kmcomposerwin.cpp index 7539b142c..8ae329e84 100644 --- a/src/editor/kmcomposerwin.cpp +++ b/src/editor/kmcomposerwin.cpp @@ -1,3501 +1,3506 @@ /* * This file is part of KMail. * Copyright (c) 2011-2017 Laurent Montel * * Copyright (c) 2009 Constantin Berzan * * Based on KMail code by: * Copyright (c) 1997 Markus Wuebben * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmcomposerwin.h" -#include "kconfigwidgets_version.h" // KMail includes -#include "job/addressvalidationjob.h" -#include "kmcomposercreatenewcomposerjob.h" #include "attachment/attachmentcontroller.h" -#include #include "attachment/attachmentview.h" #include "codec/codecaction.h" -#include -#include "kmcommands.h" +#include "custommimeheader.h" #include "editor/kmcomposereditorng.h" -#include "KPIMTextEdit/RichTextComposerControler" -#include "MessageComposer/RichTextComposerSignatures" -#include -#include -#include -#include +#include "editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.h" +#include "editor/plugininterface/kmailplugineditorinitmanagerinterface.h" +#include "editor/plugininterface/kmailplugineditormanagerinterface.h" +#include "editor/potentialphishingemail/potentialphishingemailjob.h" +#include "editor/potentialphishingemail/potentialphishingemailwarning.h" +#include "editor/warningwidgets/incorrectidentityfolderwarning.h" +#include "editor/widgets/snippetwidget.h" +#include "job/addressvalidationjob.h" +#include "job/createnewcontactjob.h" +#include "job/saveasfilejob.h" +#include "job/savedraftjob.h" +#include "kconfigwidgets_version.h" +#include "kmail_debug.h" +#include "kmcommands.h" +#include "kmcomposercreatenewcomposerjob.h" +#include "kmcomposerglobalaction.h" #include "kmcomposerupdatetemplatejob.h" -#include #include "kmkernel.h" -#include "settings/kmailsettings.h" -#include "kmmainwin.h" #include "kmmainwidget.h" +#include "kmmainwin.h" #include "mailcomposeradaptor.h" // TODO port all D-Bus stuff... -#include "messageviewer/stl_util.h" -#include "messagecomposer/util.h" -#include -#include "util.h" -#include "editor/widgets/snippetwidget.h" +#include "settings/kmailsettings.h" #include "templatesconfiguration_kfg.h" -#include "mailcommon/foldercollectionmonitor.h" -#include "mailcommon/mailkernel.h" -#include "custommimeheader.h" -#include "PimCommon/LineEditWithAutoCorrection" -#include "PimCommon/CustomToolsWidgetng" +#include "util.h" +#include "validatesendmailshortcut.h" #include "warningwidgets/attachmentmissingwarning.h" -#include "job/createnewcontactjob.h" -#include "job/savedraftjob.h" #include "warningwidgets/externaleditorwarning.h" #include "widgets/cryptostateindicatorwidget.h" -#include "validatesendmailshortcut.h" -#include "job/saveasfilejob.h" -#include "messagecomposer/followupreminderselectdatedialog.h" -#include "messagecomposer/followupremindercreatejob.h" -#include "FollowupReminder/FollowUpReminderUtil" -#include "editor/potentialphishingemail/potentialphishingemailwarning.h" -#include "kmcomposerglobalaction.h" #include "widgets/kactionmenutransport.h" -#include "pimcommon/kactionmenuchangecase.h" -#include "sonnet_version.h" +#include "widgets/statusbarlabeltoggledstate.h" -#include -#include +#include +#include +#include +#include +#include +#include -#include "KPIMTextEdit/EditorUtil" -#include "plugineditorinterface.h" -#include "editor/plugininterface/kmailplugineditormanagerinterface.h" -#include "editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.h" -#include "editor/plugininterface/kmailplugineditorinitmanagerinterface.h" -#include -#include +#include -#include -#include "SendLater/SendLaterUtil" -#include "SendLater/SendLaterDialog" -#include "SendLater/SendLaterInfo" +#include -// KDEPIM includes -#include -#include -#include -#include -#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include #ifndef QT_NO_CURSOR #include #endif -#include -#include -#include -#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include #include +#include +#include +#include #include #include -#include -#include +#include +#include +#include #include +#include +#include +#include #include -#include -#include +#include +#include #include #include -#include #include -#include -#include -#include +#include -#include "widgets/statusbarlabeltoggledstate.h" +#include +#include -// KDEPIMLIBS includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -// KDELIBS includes -#include -#include -#include -#include "kmail_debug.h" -#include -#include +#include +#include +#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +// KDE Frameworks includes +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include // Qt includes -#include -#include -#include -#include #include -#include -#include -#include -#include #include #include -#include +#include +#include #include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include // GPGME #include #include -#include -#include -#include -#include using Sonnet::DictionaryComboBox; using MailTransport::TransportManager; using MailTransport::Transport; Q_DECLARE_METATYPE(MessageComposer::Recipient::Ptr) KMail::Composer *KMail::makeComposer(const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint identity, const QString &textSelection, const QString &customTemplate) { return KMComposerWin::create(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate); } KMail::Composer *KMComposerWin::create(const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint identity, const QString &textSelection, const QString &customTemplate) { return new KMComposerWin(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate); } int KMComposerWin::s_composerNumber = 0; KMComposerWin::KMComposerWin(const KMime::Message::Ptr &aMsg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint id, const QString &textSelection, const QString &customTemplate) : KMail::Composer(QStringLiteral("kmail-composer#")) , mCursorLineLabel(nullptr) , mCursorColumnLabel(nullptr) , mStatusbarLabel(nullptr) , mDone(false) , mTextSelection(textSelection) , mCustomTemplate(customTemplate) , mSigningAndEncryptionExplicitlyDisabled(false) , mFolder(Akonadi::Collection(-1)) , mForceDisableHtml(false) , mId(id) , mContext(context) , mSignAction(nullptr) , mEncryptAction(nullptr) , mRequestMDNAction(nullptr) , mUrgentAction(nullptr) , mAllFieldsAction(nullptr) , mFromAction(nullptr) , mReplyToAction(nullptr) , mSubjectAction(nullptr) , mIdentityAction(nullptr) , mTransportAction(nullptr) , mFccAction(nullptr) , mWordWrapAction(nullptr) , mFixedFontAction(nullptr) , mAutoSpellCheckingAction(nullptr) , mDictionaryAction(nullptr) , mSnippetAction(nullptr) , mAppendSignature(nullptr) , mPrependSignature(nullptr) , mInsertSignatureAtCursorPosition(nullptr) , mCodecAction(nullptr) , mCryptoModuleAction(nullptr) , mFindText(nullptr) , mFindNextText(nullptr) , mReplaceText(nullptr) , mSelectAll(nullptr) , mDummyComposer(nullptr) , mLabelWidth(0) , mComposerBase(nullptr) , mVerifyMissingAttachment(nullptr) , mPreventFccOverwrite(false) , mCheckForForgottenAttachments(true) , mWasModified(false) , mCryptoStateIndicatorWidget(nullptr) , mSendNowByShortcutUsed(false) , mFollowUpToggleAction(nullptr) , mStatusBarLabelToggledOverrideMode(nullptr) , mStatusBarLabelSpellCheckingChangeMode(nullptr) , mPluginEditorManagerInterface(nullptr) { mGlobalAction = new KMComposerGlobalAction(this, this); mComposerBase = new MessageComposer::ComposerViewBase(this, this); mComposerBase->setIdentityManager(kmkernel->identityManager()); mPluginEditorManagerInterface = new KMailPluginEditorManagerInterface(this); mPluginEditorCheckBeforeSendManagerInterface = new KMailPluginEditorCheckBeforeSendManagerInterface(this); mPluginEditorInitManagerInterface = new KMailPluginEditorInitManagerInterface(this); connect(mComposerBase, &MessageComposer::ComposerViewBase::disableHtml, this, &KMComposerWin::disableHtml); connect(mComposerBase, &MessageComposer::ComposerViewBase::enableHtml, this, &KMComposerWin::enableHtml); connect(mComposerBase, &MessageComposer::ComposerViewBase::failed, this, &KMComposerWin::slotSendFailed); connect(mComposerBase, &MessageComposer::ComposerViewBase::sentSuccessfully, this, &KMComposerWin::slotSendSuccessful); connect(mComposerBase, &MessageComposer::ComposerViewBase::modified, this, &KMComposerWin::setModified); (void)new MailcomposerAdaptor(this); mdbusObjectPath = QLatin1String("/Composer_") + QString::number(++s_composerNumber); QDBusConnection::sessionBus().registerObject(mdbusObjectPath, this); MessageComposer::SignatureController *sigController = new MessageComposer::SignatureController(this); connect(sigController, &MessageComposer::SignatureController::enableHtml, this, &KMComposerWin::enableHtml); mComposerBase->setSignatureController(sigController); if (!kmkernel->xmlGuiInstanceName().isEmpty()) { setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2")); } mMainWidget = new QWidget(this); // splitter between the headers area and the actual editor mHeadersToEditorSplitter = new QSplitter(Qt::Vertical, mMainWidget); mHeadersToEditorSplitter->setObjectName(QStringLiteral("mHeadersToEditorSplitter")); mHeadersToEditorSplitter->setChildrenCollapsible(false); mHeadersArea = new QWidget(mHeadersToEditorSplitter); mHeadersArea->setSizePolicy(mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding); mHeadersToEditorSplitter->addWidget(mHeadersArea); const QList defaultSizes{ 0 }; mHeadersToEditorSplitter->setSizes(defaultSizes); QVBoxLayout *v = new QVBoxLayout(mMainWidget); v->setMargin(0); v->addWidget(mHeadersToEditorSplitter); KIdentityManagement::IdentityCombo *identity = new KIdentityManagement::IdentityCombo(kmkernel->identityManager(), mHeadersArea); identity->setCurrentIdentity(mId); connect(identity, &KIdentityManagement::IdentityCombo::identityDeleted, this, &KMComposerWin::slotIdentityDeleted); mComposerBase->setIdentityCombo(identity); sigController->setIdentityCombo(identity); sigController->suspend(); // we have to do identity change tracking ourselves due to the template code Sonnet::DictionaryComboBox *dictionaryCombo = new DictionaryComboBox(mHeadersArea); dictionaryCombo->setToolTip(i18n("Select the dictionary to use when spell-checking this message")); mComposerBase->setDictionary(dictionaryCombo); mFccFolder = new MailCommon::FolderRequester(mHeadersArea); mFccFolder->setNotAllowToCreateNewFolder(true); mFccFolder->setMustBeReadWrite(true); mFccFolder->setToolTip(i18n("Select the sent-mail folder where a copy of this message will be saved")); connect(mFccFolder, &MailCommon::FolderRequester::folderChanged, this, &KMComposerWin::slotFccFolderChanged); connect(mFccFolder, &MailCommon::FolderRequester::invalidFolder, this, &KMComposerWin::slotFccIsInvalid); MailTransport::TransportComboBox *transport = new MailTransport::TransportComboBox(mHeadersArea); transport->setToolTip(i18n("Select the outgoing account to use for sending this message")); mComposerBase->setTransportCombo(transport); connect(transport, QOverload::of(&MailTransport::TransportComboBox::activated), this, &KMComposerWin::slotTransportChanged); connect(transport, &MailTransport::TransportComboBox::transportRemoved, this, &KMComposerWin::slotTransportRemoved); mEdtFrom = new MessageComposer::ComposerLineEdit(false, mHeadersArea); mEdtFrom->setObjectName(QStringLiteral("fromLine")); mEdtFrom->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); mEdtFrom->setToolTip(i18n("Set the \"From:\" email address for this message")); mEdtReplyTo = new MessageComposer::ComposerLineEdit(true, mHeadersArea); mEdtReplyTo->setObjectName(QStringLiteral("replyToLine")); mEdtReplyTo->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); mEdtReplyTo->setToolTip(i18n("Set the \"Reply-To:\" email address for this message")); connect(mEdtReplyTo, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); MessageComposer::RecipientsEditor *recipientsEditor = new MessageComposer::RecipientsEditor(mHeadersArea); recipientsEditor->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); connect(recipientsEditor, &MessageComposer::RecipientsEditor::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); connect(recipientsEditor, &MessageComposer::RecipientsEditor::sizeHintChanged, this, &KMComposerWin::recipientEditorSizeHintChanged); connect(recipientsEditor, &MessageComposer::RecipientsEditor::lineAdded, this, &KMComposerWin::slotRecipientEditorLineAdded); mComposerBase->setRecipientsEditor(recipientsEditor); mEdtSubject = new PimCommon::LineEditWithAutoCorrection(mHeadersArea, QStringLiteral("kmail2rc")); mEdtSubject->setActivateLanguageMenu(false); mEdtSubject->setToolTip(i18n("Set a subject for this message")); mEdtSubject->setAutocorrection(KMKernel::self()->composerAutoCorrection()); mLblIdentity = new QLabel(i18n("&Identity:"), mHeadersArea); mDictionaryLabel = new QLabel(i18n("&Dictionary:"), mHeadersArea); mLblFcc = new QLabel(i18n("&Sent-Mail folder:"), mHeadersArea); mLblTransport = new QLabel(i18n("&Mail transport:"), mHeadersArea); mLblFrom = new QLabel(i18nc("sender address field", "&From:"), mHeadersArea); mLblReplyTo = new QLabel(i18n("&Reply to:"), mHeadersArea); mLblSubject = new QLabel(i18nc("@label:textbox Subject of email.", "S&ubject:"), mHeadersArea); mShowHeaders = KMailSettings::self()->headers(); mDone = false; mGrid = nullptr; mFixedFontAction = nullptr; // the attachment view is separated from the editor by a splitter mSplitter = new QSplitter(Qt::Vertical, mMainWidget); mSplitter->setObjectName(QStringLiteral("mSplitter")); mSplitter->setChildrenCollapsible(false); mSnippetSplitter = new QSplitter(Qt::Horizontal, mSplitter); mSnippetSplitter->setObjectName(QStringLiteral("mSnippetSplitter")); mSplitter->addWidget(mSnippetSplitter); QWidget *editorAndCryptoStateIndicators = new QWidget(mSplitter); mCryptoStateIndicatorWidget = new CryptoStateIndicatorWidget; mCryptoStateIndicatorWidget->setShowAlwaysIndicator(KMailSettings::self()->showCryptoLabelIndicator()); QVBoxLayout *vbox = new QVBoxLayout(editorAndCryptoStateIndicators); vbox->setMargin(0); mPotentialPhishingEmailWarning = new PotentialPhishingEmailWarning(this); connect(mPotentialPhishingEmailWarning, &PotentialPhishingEmailWarning::sendNow, this, &KMComposerWin::slotCheckSendNowStep2); vbox->addWidget(mPotentialPhishingEmailWarning); mAttachmentMissing = new AttachmentMissingWarning(this); connect(mAttachmentMissing, &AttachmentMissingWarning::attachMissingFile, this, &KMComposerWin::slotAttachMissingFile); connect(mAttachmentMissing, &AttachmentMissingWarning::explicitClosedMissingAttachment, this, &KMComposerWin::slotExplicitClosedMissingAttachment); vbox->addWidget(mAttachmentMissing); KMComposerEditorNg *composerEditorNg = new KMComposerEditorNg(this, mCryptoStateIndicatorWidget); mRichTextEditorwidget = new KPIMTextEdit::RichTextEditorWidget(composerEditorNg, mCryptoStateIndicatorWidget); //Don't use new connect api here. It crashs connect(composerEditorNg, SIGNAL(textChanged()), this, SLOT(slotEditorTextChanged())); //connect(editor, &KMComposerEditor::textChanged, this, &KMComposeWin::slotEditorTextChanged); mComposerBase->setEditor(composerEditorNg); mIncorrectIdentityFolderWarning = new IncorrectIdentityFolderWarning(this); vbox->addWidget(mIncorrectIdentityFolderWarning); vbox->addWidget(mCryptoStateIndicatorWidget); vbox->addWidget(mRichTextEditorwidget); mSnippetSplitter->insertWidget(0, editorAndCryptoStateIndicators); mSnippetSplitter->setOpaqueResize(true); sigController->setEditor(composerEditorNg); mHeadersToEditorSplitter->addWidget(mSplitter); composerEditorNg->setAcceptDrops(true); connect(sigController, &MessageComposer::SignatureController::signatureAdded, mComposerBase->editor()->externalComposer(), &KPIMTextEdit::RichTextExternalComposer::startExternalEditor); connect(dictionaryCombo, &Sonnet::DictionaryComboBox::dictionaryChanged, this, &KMComposerWin::slotSpellCheckingLanguage); connect(composerEditorNg, &KMComposerEditorNg::languageChanged, this, &KMComposerWin::slotDictionaryLanguageChanged); connect(composerEditorNg, &KMComposerEditorNg::spellCheckStatus, this, &KMComposerWin::slotSpellCheckingStatus); connect(composerEditorNg, &KMComposerEditorNg::insertModeChanged, this, &KMComposerWin::slotOverwriteModeChanged); connect(composerEditorNg, &KMComposerEditorNg::spellCheckingFinished, this, &KMComposerWin::slotDelayedCheckSendNow); mSnippetWidget = new SnippetWidget(composerEditorNg, actionCollection(), mSnippetSplitter); mSnippetWidget->setVisible(KMailSettings::self()->showSnippetManager()); mSnippetSplitter->addWidget(mSnippetWidget); mSnippetSplitter->setCollapsible(0, false); mSnippetSplitterCollapser = new KSplitterCollapserButton(mSnippetWidget, mSnippetSplitter); mSnippetSplitterCollapser->setVisible(KMailSettings::self()->showSnippetManager()); mSplitter->setOpaqueResize(true); setWindowTitle(i18n("Composer")); setMinimumSize(200, 200); mCustomToolsWidget = new PimCommon::CustomToolsWidgetNg(actionCollection(), this); mSplitter->addWidget(mCustomToolsWidget); connect(mCustomToolsWidget, &PimCommon::CustomToolsWidgetNg::insertText, this, &KMComposerWin::slotInsertShortUrl); MessageComposer::AttachmentModel *attachmentModel = new MessageComposer::AttachmentModel(this); KMail::AttachmentView *attachmentView = new KMail::AttachmentView(attachmentModel, mSplitter); attachmentView->hideIfEmpty(); connect(attachmentView, &KMail::AttachmentView::modified, this, &KMComposerWin::setModified); KMail::AttachmentController *attachmentController = new KMail::AttachmentController(attachmentModel, attachmentView, this); mComposerBase->setAttachmentModel(attachmentModel); mComposerBase->setAttachmentController(attachmentController); if (KMailSettings::self()->showForgottenAttachmentWarning()) { mVerifyMissingAttachment = new QTimer(this); mVerifyMissingAttachment->setSingleShot(true); mVerifyMissingAttachment->setInterval(1000 * 5); connect(mVerifyMissingAttachment, &QTimer::timeout, this, &KMComposerWin::slotVerifyMissingAttachmentTimeout); } connect(attachmentController, &KMail::AttachmentController::fileAttached, mAttachmentMissing, &AttachmentMissingWarning::slotFileAttached); mExternalEditorWarning = new ExternalEditorWarning(this); v->addWidget(mExternalEditorWarning); mPluginEditorManagerInterface->setParentWidget(this); mPluginEditorManagerInterface->setRichTextEditor(mRichTextEditorwidget->editor()); mPluginEditorManagerInterface->setActionCollection(actionCollection()); mPluginEditorCheckBeforeSendManagerInterface->setParentWidget(this); mPluginEditorInitManagerInterface->setParent(this); mPluginEditorInitManagerInterface->setRichTextEditor(composerEditorNg); setupStatusBar(attachmentView->widget()); setupActions(); setupEditor(); rethinkFields(); readConfig(); updateSignatureAndEncryptionStateIndicators(); applyMainWindowSettings(KMKernel::self()->config()->group("Composer")); connect(mEdtSubject, &PimCommon::LineEditWithAutoCorrection::textChanged, this, &KMComposerWin::slotUpdateWindowTitle); connect(identity, &KIdentityManagement::IdentityCombo::identityChanged, [this](uint val) { slotIdentityChanged(val); }); connect(kmkernel->identityManager(), QOverload::of(&KIdentityManagement::IdentityManager::changed), this, [this](uint val) { slotIdentityChanged(val); }); connect(mEdtFrom, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::collectionRemoved, this, &KMComposerWin::slotFolderRemoved); connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::slotConfigChanged); mMainWidget->resize(800, 600); setCentralWidget(mMainWidget); if (KMailSettings::self()->useHtmlMarkup()) { enableHtml(); } else { disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm); } if (KMailSettings::self()->useExternalEditor()) { composerEditorNg->setUseExternalEditor(true); composerEditorNg->setExternalEditorPath(KMailSettings::self()->externalEditor()); } const QList lstLines = recipientsEditor->lines(); for (KPIM::MultiplyingLine *line : lstLines) { slotRecipientEditorLineAdded(line); } if (aMsg) { setMessage(aMsg, lastSignState, lastEncryptState); } mComposerBase->recipientsEditor()->setFocus(); composerEditorNg->composerActions()->updateActionStates(); // set toolbar buttons to correct values mDone = true; mDummyComposer = new MessageComposer::Composer(this); mDummyComposer->globalPart()->setParentWidgetForGui(this); KConfigGroup grp(KMKernel::self()->config()->group("Composer")); setAutoSaveSettings(grp, true); } KMComposerWin::~KMComposerWin() { // When we have a collection set, store the message back to that collection. // Note that when we save the message or sent it, mFolder is set back to 0. // So this for example kicks in when opening a draft and then closing the window. if (mFolder.isValid() && mMsg && isModified()) { SaveDraftJob *saveDraftJob = new SaveDraftJob(mMsg, mFolder); saveDraftJob->start(); } delete mComposerBase; } void KMComposerWin::slotSpellCheckingLanguage(const QString &language) { mComposerBase->editor()->setSpellCheckingLanguage(language); mEdtSubject->setSpellCheckingLanguage(language); } QString KMComposerWin::dbusObjectPath() const { return mdbusObjectPath; } void KMComposerWin::slotEditorTextChanged() { const bool textIsNotEmpty = !mComposerBase->editor()->document()->isEmpty(); mFindText->setEnabled(textIsNotEmpty); mFindNextText->setEnabled(textIsNotEmpty); mReplaceText->setEnabled(textIsNotEmpty); mSelectAll->setEnabled(textIsNotEmpty); if (mVerifyMissingAttachment && !mVerifyMissingAttachment->isActive()) { mVerifyMissingAttachment->start(); } } void KMComposerWin::send(int how) { switch (how) { case 1: slotSendNow(); break; default: case 0: // TODO: find out, what the default send method is and send it this way case 2: slotSendLater(); break; } } void KMComposerWin::addAttachmentsAndSend(const QList &urls, const QString &comment, int how) { const int nbUrl = urls.count(); for (int i = 0; i < nbUrl; ++i) { mComposerBase->addAttachment(urls.at(i), comment, true); } send(how); } void KMComposerWin::addAttachment(const QUrl &url, const QString &comment) { mComposerBase->addAttachment(url, comment, false); } void KMComposerWin::addAttachment(const QString &name, KMime::Headers::contentEncoding cte, const QString &charset, const QByteArray &data, const QByteArray &mimeType) { Q_UNUSED(cte); mComposerBase->addAttachment(name, name, charset, data, mimeType); } void KMComposerWin::readConfig(bool reload /* = false */) { mEdtFrom->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode()); mComposerBase->recipientsEditor()->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode()); mEdtReplyTo->setCompletionMode((KCompletion::CompletionMode)KMailSettings::self()->completionMode()); if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) { mBodyFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); mFixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); } else { mBodyFont = KMailSettings::self()->composerFont(); mFixedFont = MessageViewer::MessageViewerSettings::self()->fixedFont(); } slotUpdateFont(); mEdtFrom->setFont(mBodyFont); mEdtReplyTo->setFont(mBodyFont); mEdtSubject->setFont(mBodyFont); if (!reload) { QSize siz = KMailSettings::self()->composerSize(); if (siz.width() < 200) { siz.setWidth(200); } if (siz.height() < 200) { siz.setHeight(200); } resize(siz); if (!KMailSettings::self()->snippetSplitterPosition().isEmpty()) { mSnippetSplitter->setSizes(KMailSettings::self()->snippetSplitterPosition()); } else { QList defaults; defaults << (int)(width() * 0.8) << (int)(width() * 0.2); mSnippetSplitter->setSizes(defaults); } } mComposerBase->identityCombo()->setCurrentIdentity(mId); qCDebug(KMAIL_LOG) << mComposerBase->identityCombo()->currentIdentityName(); const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(mId); mComposerBase->setAutoSaveInterval(KMailSettings::self()->autosaveInterval() * 1000 * 60); mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); QString fccName; if (!ident.fcc().isEmpty()) { fccName = ident.fcc(); } setFcc(fccName); } void KMComposerWin::writeConfig(void) { KMailSettings::self()->setHeaders(mShowHeaders); KMailSettings::self()->setCurrentTransport(mComposerBase->transportComboBox()->currentText()); KMailSettings::self()->setPreviousIdentity(mComposerBase->identityCombo()->currentIdentity()); KMailSettings::self()->setPreviousFcc(QString::number(mFccFolder->collection().id())); KMailSettings::self()->setPreviousDictionary(mComposerBase->dictionary()->currentDictionaryName()); KMailSettings::self()->setAutoSpellChecking(mAutoSpellCheckingAction->isChecked()); MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mFixedFontAction->isChecked()); if (!mForceDisableHtml) { KMailSettings::self()->setUseHtmlMarkup(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); } KMailSettings::self()->setComposerSize(size()); KMailSettings::self()->setShowSnippetManager(mSnippetAction->isChecked()); if (mSnippetAction->isChecked()) { KMailSettings::setSnippetSplitterPosition(mSnippetSplitter->sizes()); } // make sure config changes are written to disk, cf. bug 127538 KMKernel::self()->slotSyncConfig(); } MessageComposer::Composer *KMComposerWin::createSimpleComposer() { QList< QByteArray > charsets = mCodecAction->mimeCharsets(); if (!mOriginalPreferredCharset.isEmpty()) { charsets.insert(0, mOriginalPreferredCharset); } mComposerBase->setFrom(from()); mComposerBase->setReplyTo(replyTo()); mComposerBase->setSubject(subject()); mComposerBase->setCharsets(charsets); return mComposerBase->createSimpleComposer(); } bool KMComposerWin::canSignEncryptAttachments() const { return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat; } void KMComposerWin::slotUpdateView() { if (!mDone) { return; // otherwise called from rethinkFields during the construction // which is not the intended behavior } //This sucks awfully, but no, I cannot get an activated(int id) from // actionContainer() KToggleAction *act = ::qobject_cast(sender()); if (!act) { return; } int id; if (act == mAllFieldsAction) { id = 0; } else if (act == mIdentityAction) { id = HDR_IDENTITY; } else if (act == mTransportAction) { id = HDR_TRANSPORT; } else if (act == mFromAction) { id = HDR_FROM; } else if (act == mReplyToAction) { id = HDR_REPLY_TO; } else if (act == mSubjectAction) { id = HDR_SUBJECT; } else if (act == mFccAction) { id = HDR_FCC; } else if (act == mDictionaryAction) { id = HDR_DICTIONARY; } else { qCDebug(KMAIL_LOG) << "Something is wrong (Oh, yeah?)"; return; } bool forceAllHeaders = false; // sanders There's a bug here this logic doesn't work if no // fields are shown and then show all fields is selected. // Instead of all fields being shown none are. if (!act->isChecked()) { // hide header if (id > 0) { mShowHeaders = mShowHeaders & ~id; } else { mShowHeaders = std::abs(mShowHeaders); } } else { // show header if (id > 0) { mShowHeaders |= id; } else { mShowHeaders = -std::abs(mShowHeaders); if (mShowHeaders == 0) { forceAllHeaders = true; } } } rethinkFields(true, forceAllHeaders); } int KMComposerWin::calcColumnWidth(int which, long allShowing, int width) const { if ((allShowing & which) == 0) { return width; } QLabel *w = nullptr; if (which == HDR_IDENTITY) { w = mLblIdentity; } else if (which == HDR_DICTIONARY) { w = mDictionaryLabel; } else if (which == HDR_FCC) { w = mLblFcc; } else if (which == HDR_TRANSPORT) { w = mLblTransport; } else if (which == HDR_FROM) { w = mLblFrom; } else if (which == HDR_REPLY_TO) { w = mLblReplyTo; } else if (which == HDR_SUBJECT) { w = mLblSubject; } else { return width; } w->setBuddy(mComposerBase->editor()); // set dummy so we don't calculate width of '&' for this label. w->adjustSize(); w->show(); return qMax(width, w->sizeHint().width()); } void KMComposerWin::rethinkFields(bool fromSlot, bool forceAllHeaders) { //This sucks even more but again no ids. sorry (sven) int mask, row; long showHeaders; if ((mShowHeaders < 0) || forceAllHeaders) { showHeaders = HDR_ALL; } else { showHeaders = mShowHeaders; } for (mask = 1, mNumHeaders = 0; mask <= showHeaders; mask <<= 1) { if ((showHeaders & mask) != 0) { ++mNumHeaders; } } delete mGrid; mGrid = new QGridLayout(mHeadersArea); mGrid->setColumnStretch(0, 1); mGrid->setColumnStretch(1, 100); mGrid->setRowStretch(mNumHeaders + 1, 100); row = 0; mLabelWidth = mComposerBase->recipientsEditor()->setFirstColumnWidth(0) + 2; if (std::abs(showHeaders)&HDR_IDENTITY) { mLabelWidth = calcColumnWidth(HDR_IDENTITY, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_DICTIONARY) { mLabelWidth = calcColumnWidth(HDR_DICTIONARY, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_FCC) { mLabelWidth = calcColumnWidth(HDR_FCC, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_TRANSPORT) { mLabelWidth = calcColumnWidth(HDR_TRANSPORT, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_FROM) { mLabelWidth = calcColumnWidth(HDR_FROM, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_REPLY_TO) { mLabelWidth = calcColumnWidth(HDR_REPLY_TO, showHeaders, mLabelWidth); } if (std::abs(showHeaders)&HDR_SUBJECT) { mLabelWidth = calcColumnWidth(HDR_SUBJECT, showHeaders, mLabelWidth); } if (!fromSlot) { mAllFieldsAction->setChecked(showHeaders == HDR_ALL); } if (!fromSlot) { mIdentityAction->setChecked(std::abs(mShowHeaders)&HDR_IDENTITY); } rethinkHeaderLine(showHeaders, HDR_IDENTITY, row, mLblIdentity, mComposerBase->identityCombo()); if (!fromSlot) { mDictionaryAction->setChecked(std::abs(mShowHeaders)&HDR_DICTIONARY); } rethinkHeaderLine(showHeaders, HDR_DICTIONARY, row, mDictionaryLabel, mComposerBase->dictionary()); if (!fromSlot) { mFccAction->setChecked(std::abs(mShowHeaders)&HDR_FCC); } rethinkHeaderLine(showHeaders, HDR_FCC, row, mLblFcc, mFccFolder); if (!fromSlot) { mTransportAction->setChecked(std::abs(mShowHeaders)&HDR_TRANSPORT); } rethinkHeaderLine(showHeaders, HDR_TRANSPORT, row, mLblTransport, mComposerBase->transportComboBox()); if (!fromSlot) { mFromAction->setChecked(std::abs(mShowHeaders)&HDR_FROM); } rethinkHeaderLine(showHeaders, HDR_FROM, row, mLblFrom, mEdtFrom); QWidget *prevFocus = mEdtFrom; if (!fromSlot) { mReplyToAction->setChecked(std::abs(mShowHeaders)&HDR_REPLY_TO); } rethinkHeaderLine(showHeaders, HDR_REPLY_TO, row, mLblReplyTo, mEdtReplyTo); if (showHeaders & HDR_REPLY_TO) { (void)connectFocusMoving(prevFocus, mEdtReplyTo); } mGrid->addWidget(mComposerBase->recipientsEditor(), row, 0, 1, 2); ++row; if (showHeaders & HDR_REPLY_TO) { connect(mEdtReplyTo, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop); connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusUp, mEdtReplyTo, QOverload<>::of(&QWidget::setFocus)); } else { connect(mEdtFrom, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop); connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusUp, mEdtFrom, QOverload<>::of(&QWidget::setFocus)); } connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusDown, mEdtSubject, QOverload<>::of(&QWidget::setFocus)); connect(mEdtSubject, &PimCommon::SpellCheckLineEdit::focusUp, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusBottom); prevFocus = mComposerBase->recipientsEditor(); if (!fromSlot) { mSubjectAction->setChecked(std::abs(mShowHeaders)&HDR_SUBJECT); } rethinkHeaderLine(showHeaders, HDR_SUBJECT, row, mLblSubject, mEdtSubject); connectFocusMoving(mEdtSubject, mComposerBase->editor()); assert(row <= mNumHeaders + 1); mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height()); mIdentityAction->setEnabled(!mAllFieldsAction->isChecked()); mDictionaryAction->setEnabled(!mAllFieldsAction->isChecked()); mTransportAction->setEnabled(!mAllFieldsAction->isChecked()); mFromAction->setEnabled(!mAllFieldsAction->isChecked()); if (mReplyToAction) { mReplyToAction->setEnabled(!mAllFieldsAction->isChecked()); } mFccAction->setEnabled(!mAllFieldsAction->isChecked()); mSubjectAction->setEnabled(!mAllFieldsAction->isChecked()); mComposerBase->recipientsEditor()->setFirstColumnWidth(mLabelWidth); } QWidget *KMComposerWin::connectFocusMoving(QWidget *prev, QWidget *next) { connect(prev, SIGNAL(focusDown()), next, SLOT(setFocus())); connect(next, SIGNAL(focusUp()), prev, SLOT(setFocus())); return next; } void KMComposerWin::rethinkHeaderLine(int aValue, int aMask, int &aRow, QLabel *aLbl, QWidget *aCbx) { if (aValue & aMask) { aLbl->setBuddy(aCbx); aLbl->setFixedWidth(mLabelWidth); mGrid->addWidget(aLbl, aRow, 0); mGrid->addWidget(aCbx, aRow, 1); aCbx->show(); aLbl->show(); aRow++; } else { aLbl->hide(); aCbx->hide(); } } void KMComposerWin::slotUpdateComposer(const KIdentityManagement::Identity &ident, const KMime::Message::Ptr &msg, uint uoid, uint uoldId, bool wasModified) { mComposerBase->updateTemplate(msg); updateSignature(uoid, uoldId); updateComposerAfterIdentityChanged(ident, uoid, wasModified); } void KMComposerWin::applyTemplate(uint uoid, uint uOldId, const KIdentityManagement::Identity &ident, bool wasModified) { TemplateParser::TemplateParserJob::Mode mode; switch (mContext) { case New: mode = TemplateParser::TemplateParserJob::NewMessage; break; case Reply: mode = TemplateParser::TemplateParserJob::Reply; break; case ReplyToAll: mode = TemplateParser::TemplateParserJob::ReplyAll; break; case Forward: mode = TemplateParser::TemplateParserJob::Forward; break; case NoTemplate: updateComposerAfterIdentityChanged(ident, uoid, wasModified); return; } KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Templates"); header->fromUnicodeString(ident.templates(), "utf-8"); mMsg->setHeader(header); if (mode == TemplateParser::TemplateParserJob::NewMessage) { KMComposerUpdateTemplateJob *job = new KMComposerUpdateTemplateJob; connect(job, &KMComposerUpdateTemplateJob::updateComposer, this, &KMComposerWin::slotUpdateComposer); job->setMsg(mMsg); job->setCustomTemplate(mCustomTemplate); job->setTextSelection(mTextSelection); job->setWasModified(wasModified); job->setUoldId(uOldId); job->setUoid(uoid); job->setIdent(ident); job->setCollection(mCollectionForNewMessage); job->start(); } else { if (auto hrd = mMsg->headerByType("X-KMail-Link-Message")) { Akonadi::Item::List items; const QStringList serNums = hrd->asUnicodeString().split(QLatin1Char(',')); items.reserve(serNums.count()); for (const QString &serNumStr : serNums) { items << Akonadi::Item(serNumStr.toLongLong()); } Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(items, this); job->fetchScope().fetchFullPayload(true); job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); job->setProperty("mode", (int)mode); job->setProperty("uoid", uoid); job->setProperty("uOldid", uOldId); connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDelayedApplyTemplate); } updateComposerAfterIdentityChanged(ident, uoid, wasModified); } } void KMComposerWin::slotDelayedApplyTemplate(KJob *job) { #if 0 //FIXME template const Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); const Akonadi::Item::List items = fetchJob->items(); const TemplateParser::TemplateParserJob::Mode mode = static_cast(fetchJob->property("mode").toInt()); const uint uoid = fetchJob->property("uoid").toUInt(); const uint uOldId = fetchJob->property("uOldid").toUInt(); TemplateParser::TemplateParser parser(mMsg, mode); parser.setSelection(mTextSelection); parser.setAllowDecryption(true); parser.setWordWrap(MessageComposer::MessageComposerSettings::self()->wordWrap(), MessageComposer::MessageComposerSettings::self()->lineWrapWidth()); parser.setIdentityManager(KMKernel::self()->identityManager()); for (const Akonadi::Item &item : items) { if (!mCustomTemplate.isEmpty()) { parser.process(mCustomTemplate, MessageCore::Util::message(item)); } else { parser.processWithIdentity(uoid, MessageCore::Util::message(item)); } } mComposerBase->updateTemplate(mMsg); updateSignature(uoid, uOldId); #else qCWarning(KMAIL_LOG) << " void KMComposerWin::slotDelayedApplyTemplate(KJob *job) is not implemented after removing qtwebkit"; Q_UNUSED(job); #endif } void KMComposerWin::updateSignature(uint uoid, uint uOldId) { const KIdentityManagement::Identity &ident = kmkernel->identityManager()->identityForUoid(uoid); const KIdentityManagement::Identity &oldIdentity = kmkernel->identityManager()->identityForUoid(uOldId); mComposerBase->identityChanged(ident, oldIdentity, true); } void KMComposerWin::setCollectionForNewMessage(const Akonadi::Collection &folder) { mCollectionForNewMessage = folder; } void KMComposerWin::setQuotePrefix(uint uoid) { QString quotePrefix; if (auto hrd = mMsg->headerByType("X-KMail-QuotePrefix")) { quotePrefix = hrd->asUnicodeString(); } if (quotePrefix.isEmpty()) { // no quote prefix header, set quote prefix according in identity // TODO port templates to ComposerViewBase if (mCustomTemplate.isEmpty()) { const KIdentityManagement::Identity &identity = kmkernel->identityManager()->identityForUoidOrDefault(uoid); // Get quote prefix from template // ( custom templates don't specify custom quotes prefixes ) TemplateParser::Templates quoteTemplate( TemplateParser::TemplatesConfiguration::configIdString(identity.uoid())); quotePrefix = quoteTemplate.quoteString(); } } mComposerBase->editor()->setQuotePrefixName(MessageCore::StringUtil::formatQuotePrefix(quotePrefix, mMsg->from()->displayString())); } void KMComposerWin::setupActions(void) { KActionMenuTransport *actActionNowMenu = nullptr; KActionMenuTransport *actActionLaterMenu = nullptr; if (MessageComposer::MessageComposerSettings::self()->sendImmediate()) { //default = send now, alternative = queue QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this); actionCollection()->addAction(QStringLiteral("send_default"), action); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Return)); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNowByShortcut); actActionNowMenu = new KActionMenuTransport(this); actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); actActionNowMenu->setText(i18n("&Send Mail Via")); actActionNowMenu->setIconText(i18n("Send")); actionCollection()->addAction(QStringLiteral("send_default_via"), actActionNowMenu); action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this); actionCollection()->addAction(QStringLiteral("send_alternative"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater); actActionLaterMenu = new KActionMenuTransport(this); actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue"))); actActionLaterMenu->setText(i18n("Send &Later Via")); actActionLaterMenu->setIconText(i18nc("Queue the message for sending at a later date", "Queue")); actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionLaterMenu); } else { //default = queue, alternative = send now QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this); actionCollection()->addAction(QStringLiteral("send_default"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Return)); actActionLaterMenu = new KActionMenuTransport(this); actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue"))); actActionLaterMenu->setText(i18n("Send &Later Via")); actionCollection()->addAction(QStringLiteral("send_default_via"), actActionLaterMenu); action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this); actionCollection()->addAction(QStringLiteral("send_alternative"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNow); actActionNowMenu = new KActionMenuTransport(this); actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); actActionNowMenu->setText(i18n("&Send Mail Via")); actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionNowMenu); } connect(actActionNowMenu, &QAction::triggered, this, &KMComposerWin::slotSendNow); connect(actActionLaterMenu, &QAction::triggered, this, &KMComposerWin::slotSendLater); connect(actActionNowMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendNowVia); connect(actActionLaterMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendLaterVia); QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Draft"), this); actionCollection()->addAction(QStringLiteral("save_in_drafts"), action); KMail::Util::addQActionHelpText(action, i18n("Save email in Draft folder")); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_S)); connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveDraft); action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Template"), this); KMail::Util::addQActionHelpText(action, i18n("Save email in Template folder")); actionCollection()->addAction(QStringLiteral("save_in_templates"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveTemplate); action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this); KMail::Util::addQActionHelpText(action, i18n("Save email as text or html file")); actionCollection()->addAction(QStringLiteral("save_as_file"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveAsFile); action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this); actionCollection()->addAction(QStringLiteral("insert_file"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotInsertFile); mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Recent Text File"), this); actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction); connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &KMComposerWin::slotInsertRecentFile); connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &KMComposerWin::slotRecentListFileClear); mRecentAction->loadEntries(KMKernel::self()->config()->group(QString())); action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this); KMail::Util::addQActionHelpText(action, i18n("Open Address Book")); actionCollection()->addAction(QStringLiteral("addressbook"), action); if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) { action->setEnabled(false); } else { connect(action, &QAction::triggered, this, &KMComposerWin::slotAddressBook); } action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Composer"), this); actionCollection()->addAction(QStringLiteral("new_composer"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotNewComposer); actionCollection()->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::New)); action = new QAction(i18n("Select &Recipients..."), this); actionCollection()->addAction(QStringLiteral("select_recipients"), action); connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::selectRecipients); action = new QAction(i18n("Save &Distribution List..."), this); actionCollection()->addAction(QStringLiteral("save_distribution_list"), action); connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::saveDistributionList); KStandardAction::print(this, &KMComposerWin::slotPrint, actionCollection()); KStandardAction::printPreview(this, &KMComposerWin::slotPrintPreview, actionCollection()); KStandardAction::close(this, &KMComposerWin::slotClose, actionCollection()); KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection()); KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection()); KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection()); KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection()); KStandardAction::paste(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection()); mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection()); mFindText = KStandardAction::find(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFind, actionCollection()); mFindNextText = KStandardAction::findNext(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotFindNext, actionCollection()); mReplaceText = KStandardAction::replace(mRichTextEditorwidget, &KPIMTextEdit::RichTextEditorWidget::slotReplace, actionCollection()); actionCollection()->addAction(KStandardAction::Spelling, QStringLiteral("spellcheck"), mComposerBase->editor(), SLOT(slotCheckSpelling())); action = new QAction(i18n("Paste as Attac&hment"), this); actionCollection()->addAction(QStringLiteral("paste_att"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotPasteAsAttachment); action = new QAction(i18n("Cl&ean Spaces"), this); actionCollection()->addAction(QStringLiteral("clean_spaces"), action); connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace); mFixedFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this); actionCollection()->addAction(QStringLiteral("toggle_fixedfont"), mFixedFontAction); connect(mFixedFontAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateFont); mFixedFontAction->setChecked(MessageViewer::MessageViewerSettings::self()->useFixedFont()); //these are checkable!!! mUrgentAction = new KToggleAction( i18nc("@action:inmenu Mark the email as urgent.", "&Urgent"), this); actionCollection()->addAction(QStringLiteral("urgent"), mUrgentAction); mRequestMDNAction = new KToggleAction(i18n("&Request Disposition Notification"), this); actionCollection()->addAction(QStringLiteral("options_request_mdn"), mRequestMDNAction); mRequestMDNAction->setChecked(KMailSettings::self()->requestMDN()); //----- Message-Encoding Submenu mCodecAction = new CodecAction(CodecAction::ComposerMode, this); actionCollection()->addAction(QStringLiteral("charsets"), mCodecAction); mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this); actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction); mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap()); connect(mWordWrapAction, &KToggleAction::toggled, this, &KMComposerWin::slotWordWrapToggled); mSnippetAction = new KToggleAction(i18n("&Snippets"), this); actionCollection()->addAction(QStringLiteral("snippets"), mSnippetAction); connect(mSnippetAction, &KToggleAction::toggled, this, &KMComposerWin::slotSnippetWidgetVisibilityChanged); mSnippetAction->setChecked(KMailSettings::self()->showSnippetManager()); mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Automatic Spellchecking"), this); actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction); const bool spellChecking = KMailSettings::self()->autoSpellChecking(); const bool useKmailEditor = !KMailSettings::self()->useExternalEditor(); const bool spellCheckingEnabled = useKmailEditor && spellChecking; mAutoSpellCheckingAction->setEnabled(useKmailEditor); mAutoSpellCheckingAction->setChecked(spellCheckingEnabled); slotAutoSpellCheckingToggled(spellCheckingEnabled); connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &KMComposerWin::slotAutoSpellCheckingToggled); connect(mComposerBase->editor(), &KPIMTextEdit::RichTextEditor::checkSpellingChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled); connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::textModeChanged, this, &KMComposerWin::slotTextModeChanged); connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorClosed, this, &KMComposerWin::slotExternalEditorClosed); connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorStarted, this, &KMComposerWin::slotExternalEditorStarted); //these are checkable!!! mMarkupAction = new KToggleAction(i18n("Rich Text Editing"), this); mMarkupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); mMarkupAction->setIconText(i18n("Rich Text")); mMarkupAction->setToolTip(i18n("Toggle rich text editing mode")); actionCollection()->addAction(QStringLiteral("html"), mMarkupAction); connect(mMarkupAction, &KToggleAction::triggered, this, &KMComposerWin::slotToggleMarkup); mAllFieldsAction = new KToggleAction(i18n("&All Fields"), this); actionCollection()->addAction(QStringLiteral("show_all_fields"), mAllFieldsAction); connect(mAllFieldsAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mIdentityAction = new KToggleAction(i18n("&Identity"), this); actionCollection()->addAction(QStringLiteral("show_identity"), mIdentityAction); connect(mIdentityAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this); actionCollection()->addAction(QStringLiteral("show_dictionary"), mDictionaryAction); connect(mDictionaryAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this); actionCollection()->addAction(QStringLiteral("show_fcc"), mFccAction); connect(mFccAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mTransportAction = new KToggleAction(i18n("&Mail Transport"), this); actionCollection()->addAction(QStringLiteral("show_transport"), mTransportAction); connect(mTransportAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mFromAction = new KToggleAction(i18n("&From"), this); actionCollection()->addAction(QStringLiteral("show_from"), mFromAction); connect(mFromAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mReplyToAction = new KToggleAction(i18n("&Reply To"), this); actionCollection()->addAction(QStringLiteral("show_reply_to"), mReplyToAction); connect(mReplyToAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); mSubjectAction = new KToggleAction( i18nc("@action:inmenu Show the subject in the composer window.", "S&ubject"), this); actionCollection()->addAction(QStringLiteral("show_subject"), mSubjectAction); connect(mSubjectAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); //end of checkable mAppendSignature = new QAction(i18n("Append S&ignature"), this); actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature); connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature); mPrependSignature = new QAction(i18n("Pr&epend Signature"), this); actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature); connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature); mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this); actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition); connect(mInsertSignatureAtCursorPosition, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::insertSignatureAtCursor); mComposerBase->attachmentController()->createActions(); setStandardToolBarMenuEnabled(true); KStandardAction::keyBindings(this, &KMComposerWin::slotEditKeys, actionCollection()); KStandardAction::configureToolbars(this, &KMComposerWin::slotEditToolbars, actionCollection()); KStandardAction::preferences(kmkernel, &KMKernel::slotShowConfigurationDialog, actionCollection()); action = new QAction(i18n("&Spellchecker..."), this); action->setIconText(i18n("Spellchecker")); actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action); connect(action, &QAction::triggered, this, &KMComposerWin::slotSpellcheckConfig); mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this); mEncryptAction->setIconText(i18n("Encrypt")); actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction); mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this); mSignAction->setIconText(i18n("Sign")); actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction); const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity()); // PENDING(marc): check the uses of this member and split it into // smime/openpgp and or enc/sign, if necessary: mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty(); mLastEncryptActionState = false; mLastSignActionState = ident.pgpAutoSign(); changeCryptoAction(); connect(mEncryptAction, &KToggleAction::triggered, this, &KMComposerWin::slotEncryptToggled); connect(mSignAction, &KToggleAction::triggered, this, &KMComposerWin::slotSignToggled); QStringList listCryptoFormat; listCryptoFormat.reserve(numCryptoMessageFormats); for (int i = 0; i < numCryptoMessageFormats; ++i) { listCryptoFormat.push_back(Kleo::cryptoMessageFormatToLabel(cryptoMessageFormats[i])); } mCryptoModuleAction = new KSelectAction(i18n("&Cryptographic Message Format"), this); actionCollection()->addAction(QStringLiteral("options_select_crypto"), mCryptoModuleAction); connect(mCryptoModuleAction, QOverload::of(&KSelectAction::triggered), this, &KMComposerWin::slotCryptoModuleSelected); mCryptoModuleAction->setToolTip(i18n("Select a cryptographic format for this message")); mCryptoModuleAction->setItems(listCryptoFormat); mComposerBase->editor()->createActions(actionCollection()); mFollowUpToggleAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("Create Follow Up Reminder..."), this); actionCollection()->addAction(QStringLiteral("follow_up_mail"), mFollowUpToggleAction); connect(mFollowUpToggleAction, &KToggleAction::triggered, this, &KMComposerWin::slotFollowUpMail); mFollowUpToggleAction->setEnabled(FollowUpReminder::FollowUpReminderUtil::followupReminderAgentEnabled()); mPluginEditorManagerInterface->initializePlugins(); mPluginEditorCheckBeforeSendManagerInterface->initializePlugins(); mPluginEditorInitManagerInterface->initializePlugins(); createGUI(QStringLiteral("kmcomposerui.rc")); initializePluginActions(); connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &KMComposerWin::htmlToolBarVisibilityChanged); // In Kontact, this entry would read "Configure Kontact", but bring // up KMail's config dialog. That's sensible, though, so fix the label. QAction *configureAction = actionCollection()->action(QStringLiteral("options_configure")); if (configureAction) { configureAction->setText(i18n("Configure KMail...")); } } void KMComposerWin::initializePluginActions() { if (guiFactory()) { QHashIterator > localActionsType(mPluginEditorManagerInterface->actionsType()); while (localActionsType.hasNext()) { localActionsType.next(); QList lst = localActionsType.value(); if (!lst.isEmpty()) { const QString actionlistname = QStringLiteral("kmaileditor") + MessageComposer::PluginEditorInterface::actionXmlExtension(localActionsType.key()); Q_FOREACH (KXMLGUIClient *client, guiFactory()->clients()) { client->unplugActionList(actionlistname); client->plugActionList(actionlistname, lst); } } } } } void KMComposerWin::changeCryptoAction() { const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity()); if (!QGpgME::openpgp() && !QGpgME::smime()) { // no crypto whatsoever mEncryptAction->setEnabled(false); setEncryption(false); mSignAction->setEnabled(false); setSigning(false); } else { const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty(); const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty(); setEncryption(false); setSigning((canOpenPGPSign || canSMIMESign) && ident.pgpAutoSign()); } } void KMComposerWin::setupStatusBar(QWidget *w) { statusBar()->addWidget(w); mStatusbarLabel = new QLabel(this); mStatusbarLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); statusBar()->addPermanentWidget(mStatusbarLabel); mCursorLineLabel = new QLabel(this); mCursorLineLabel->setTextFormat(Qt::PlainText); mCursorLineLabel->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", QStringLiteral(" "))); statusBar()->addPermanentWidget(mCursorLineLabel); mCursorColumnLabel = new QLabel(i18n(" Column: %1 ", QStringLiteral(" "))); mCursorColumnLabel->setTextFormat(Qt::PlainText); statusBar()->addPermanentWidget(mCursorColumnLabel); mStatusBarLabelToggledOverrideMode = new StatusBarLabelToggledState(this); mStatusBarLabelToggledOverrideMode->setStateString(i18n("OVR"), i18n("INS")); statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode, 0); connect(mStatusBarLabelToggledOverrideMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotOverwriteModeWasChanged); mStatusBarLabelSpellCheckingChangeMode = new StatusBarLabelToggledState(this); mStatusBarLabelSpellCheckingChangeMode->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off")); statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode, 0); connect(mStatusBarLabelSpellCheckingChangeMode, &StatusBarLabelToggledState::toggleModeChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled); } void KMComposerWin::setupEditor(void) { QFontMetrics fm(mBodyFont); mComposerBase->editor()->setTabStopWidth(fm.width(QLatin1Char(' ')) * 8); slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap()); // Font setup slotUpdateFont(); connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &KMComposerWin::slotCursorPositionChanged); slotCursorPositionChanged(); } QString KMComposerWin::subject() const { return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject->toPlainText()); } QString KMComposerWin::from() const { return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom->text()); } QString KMComposerWin::replyTo() const { if (mEdtReplyTo) { return MessageComposer::Util::cleanedUpHeaderString(mEdtReplyTo->text()); } else { return QString(); } } void KMComposerWin::slotFccIsInvalid() { mIncorrectIdentityFolderWarning->fccIsInvalid(); } void KMComposerWin::setCurrentTransport(int transportId) { if (!mComposerBase->transportComboBox()->setCurrentTransport(transportId)) { mIncorrectIdentityFolderWarning->mailTransportIsInvalid(); } } void KMComposerWin::setCurrentReplyTo(const QString &replyTo) { if (mEdtReplyTo) { mEdtReplyTo->setText(replyTo); } } uint KMComposerWin::currentIdentity() const { return mComposerBase->identityCombo()->currentIdentity(); } void KMComposerWin::setMessage(const KMime::Message::Ptr &newMsg, bool lastSignState, bool lastEncryptState, bool mayAutoSign, bool allowDecryption, bool isModified) { if (!newMsg) { qCDebug(KMAIL_LOG) << "newMsg == 0!"; return; } if (lastSignState) { mLastSignActionState = true; } if (lastEncryptState) { mLastEncryptActionState = true; } mComposerBase->setMessage(newMsg, allowDecryption); mMsg = newMsg; KIdentityManagement::IdentityManager *im = KMKernel::self()->identityManager(); mEdtFrom->setText(mMsg->from()->asUnicodeString()); mEdtSubject->setText(mMsg->subject()->asUnicodeString()); // Restore the quote prefix. We can't just use the global quote prefix here, // since the prefix is different for each message, it might for example depend // on the original sender in a reply. if (auto hdr = mMsg->headerByType("X-KMail-QuotePrefix")) { mComposerBase->editor()->setQuotePrefixName(hdr->asUnicodeString()); } if (auto hrd = newMsg->headerByType("X-KMail-Identity")) { const QString identityStr = hrd->asUnicodeString(); if (!identityStr.isEmpty()) { mId = identityStr.toUInt(); } } // don't overwrite the header values with identity specific values disconnect(mComposerBase->identityCombo(), SIGNAL(identityChanged(uint)), this, SLOT(slotIdentityChanged(uint))); // load the mId into the gui, sticky or not, without emitting mComposerBase->identityCombo()->setCurrentIdentity(mId); connect(mComposerBase->identityCombo(), SIGNAL(identityChanged(uint)), this, SLOT(slotIdentityChanged(uint))); // manually load the identity's value into the fields; either the one from the // messge, where appropriate, or the one from the sticky identity. What's in // mId might have changed meanwhile, thus the save value slotIdentityChanged(mId, true /*initalChange*/); // Fixing the identitis with auto signing activated mLastSignActionState = mSignAction->isChecked(); const KIdentityManagement::Identity &ident = im->identityForUoid(mComposerBase->identityCombo()->currentIdentity()); // check for the presence of a DNT header, indicating that MDN's were requested if (auto hdr = newMsg->headerByType("Disposition-Notification-To")) { QString mdnAddr = hdr->asUnicodeString(); mRequestMDNAction->setChecked((!mdnAddr.isEmpty() && im->thatIsMe(mdnAddr)) || KMailSettings::self()->requestMDN()); } // check for presence of a priority header, indicating urgent mail: if (newMsg->headerByType("X-PRIORITY") && newMsg->headerByType("Priority")) { const QString xpriority = newMsg->headerByType("X-PRIORITY")->asUnicodeString(); const QString priority = newMsg->headerByType("Priority")->asUnicodeString(); if (xpriority == QLatin1String("2 (High)") && priority == QLatin1String("urgent")) { mUrgentAction->setChecked(true); } } if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) { mMsg->removeHeader("X-Face"); } else { QString xface = ident.xface(); if (!xface.isEmpty()) { int numNL = (xface.length() - 1) / 70; for (int i = numNL; i > 0; --i) { xface.insert(i * 70, QStringLiteral("\n\t")); } auto header = new KMime::Headers::Generic("X-Face"); header->fromUnicodeString(xface, "utf-8"); mMsg->setHeader(header); } } // if these headers are present, the state of the message should be overruled if (auto hdr = mMsg->headerByType("X-KMail-SignatureActionEnabled")) { mLastSignActionState = (hdr->as7BitString(false).contains("true")); } if (auto hdr = mMsg->headerByType("X-KMail-EncryptActionEnabled")) { mLastEncryptActionState = (hdr->as7BitString(false).contains("true")); } if (auto hdr = mMsg->headerByType("X-KMail-CryptoMessageFormat")) { mCryptoModuleAction->setCurrentItem(format2cb(static_cast( hdr->asUnicodeString().toInt()))); } mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty(); if (QGpgME::openpgp() || QGpgME::smime()) { const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty(); const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty(); setEncryption(mLastEncryptActionState); setSigning((canOpenPGPSign || canSMIMESign) && mLastSignActionState); } updateSignatureAndEncryptionStateIndicators(); QString kmailFcc; if (auto hdr = mMsg->headerByType("X-KMail-Fcc")) { kmailFcc = hdr->asUnicodeString(); } if (kmailFcc.isEmpty()) { setFcc(ident.fcc()); } else { setFcc(kmailFcc); } if (auto hdr = mMsg->headerByType("X-KMail-Dictionary")) { const QString dictionary = hdr->asUnicodeString(); if (!dictionary.isEmpty()) { #if SONNET_VERSION >= QT_VERSION_CHECK(5, 40, 0) if (!mComposerBase->dictionary()->assignByDictionnary(dictionary)) { mIncorrectIdentityFolderWarning->dictionaryInvalid(); } #else mComposerBase->dictionary()->setCurrentByDictionary(dictionary); #endif } } else { mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); } mEdtReplyTo->setText(mMsg->replyTo()->asUnicodeString()); KMime::Content *msgContent = new KMime::Content; msgContent->setContent(mMsg->encodedContent()); msgContent->parse(); MimeTreeParser::SimpleObjectTreeSource emptySource; MimeTreeParser::ObjectTreeParser otp(&emptySource); //All default are ok emptySource.setDecryptMessage(allowDecryption); otp.parseObjectTree(msgContent); bool shouldSetCharset = false; if ((mContext == Reply || mContext == ReplyToAll || mContext == Forward) && MessageComposer::MessageComposerSettings::forceReplyCharset()) { shouldSetCharset = true; } if (shouldSetCharset && !otp.plainTextContentCharset().isEmpty()) { mOriginalPreferredCharset = otp.plainTextContentCharset(); } // always set auto charset, but prefer original when composing if force reply is set. mCodecAction->setAutoCharset(); delete msgContent; if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1String("auto")) && mayAutoSign) { // // Espen 2000-05-16 // Delay the signature appending. It may start a fileseletor. // Not user friendy if this modal fileseletor opens before the // composer. // if (MessageComposer::MessageComposerSettings::self()->prependSignature()) { QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature); } else { QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature); } } else { mComposerBase->editor()->externalComposer()->startExternalEditor(); } setModified(isModified); // honor "keep reply in this folder" setting even when the identity is changed later on mPreventFccOverwrite = (!kmailFcc.isEmpty() && ident.fcc() != kmailFcc); QTimer::singleShot(0, this, &KMComposerWin::forceAutoSaveMessage); //Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in. } void KMComposerWin::setAutoSaveFileName(const QString &fileName) { mComposerBase->setAutoSaveFileName(fileName); } void KMComposerWin::setSigningAndEncryptionDisabled(bool v) { mSigningAndEncryptionExplicitlyDisabled = v; } void KMComposerWin::setFolder(const Akonadi::Collection &aFolder) { mFolder = aFolder; } void KMComposerWin::setFcc(const QString &idString) { // check if the sent-mail folder still exists Akonadi::Collection col; if (idString.isEmpty()) { col = CommonKernel->sentCollectionFolder(); } else { col = Akonadi::Collection(idString.toLongLong()); } mComposerBase->setFcc(col); mFccFolder->setCollection(col); } bool KMComposerWin::isComposerModified() const { return mComposerBase->editor()->document()->isModified() || mEdtFrom->isModified() || (mEdtReplyTo && mEdtReplyTo->isModified()) || mComposerBase->recipientsEditor()->isModified() || mEdtSubject->document()->isModified(); } bool KMComposerWin::isModified() const { return mWasModified || isComposerModified(); } void KMComposerWin::setModified(bool modified) { mWasModified = modified; changeModifiedState(modified); } void KMComposerWin::changeModifiedState(bool modified) { mComposerBase->editor()->document()->setModified(modified); if (!modified) { mEdtFrom->setModified(false); if (mEdtReplyTo) { mEdtReplyTo->setModified(false); } mComposerBase->recipientsEditor()->clearModified(); mEdtSubject->document()->setModified(false); } } bool KMComposerWin::queryClose() { if (!mComposerBase->editor()->checkExternalEditorFinished()) { return false; } if (kmkernel->shuttingDown() || qApp->isSavingSession()) { writeConfig(); return true; } if (isModified()) { const bool istemplate = (mFolder.isValid() && CommonKernel->folderIsTemplates(mFolder)); const QString savebut = (istemplate ? i18n("Re&save as Template") : i18n("&Save as Draft")); const QString savetext = (istemplate ? i18n("Resave this message in the Templates folder. " "It can then be used at a later time.") : i18n("Save this message in the Drafts folder. " "It can then be edited and sent at a later time.")); const int rc = KMessageBox::warningYesNoCancel(this, i18n("Do you want to save the message for later or discard it?"), i18n("Close Composer"), KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); if (rc == KMessageBox::Cancel) { return false; } else if (rc == KMessageBox::Yes) { // doSend will close the window. Just return false from this method if (istemplate) { slotSaveTemplate(); } else { slotSaveDraft(); } return false; } //else fall through: return true } mComposerBase->cleanupAutoSave(); if (!mMiscComposers.isEmpty()) { qCWarning(KMAIL_LOG) << "Tried to close while composer was active"; return false; } writeConfig(); return true; } MessageComposer::ComposerViewBase::MissingAttachment KMComposerWin::userForgotAttachment() { bool checkForForgottenAttachments = mCheckForForgottenAttachments && KMailSettings::self()->showForgottenAttachmentWarning(); if (!checkForForgottenAttachments) { return MessageComposer::ComposerViewBase::NoMissingAttachmentFound; } mComposerBase->setSubject(subject()); //be sure the composer knows the subject MessageComposer::ComposerViewBase::MissingAttachment missingAttachments = mComposerBase->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords()); return missingAttachments; } void KMComposerWin::forceAutoSaveMessage() { autoSaveMessage(true); } void KMComposerWin::autoSaveMessage(bool force) { if (isComposerModified() || force) { applyComposerSetting(mComposerBase); mComposerBase->saveMailSettings(); mComposerBase->autoSaveMessage(); if (!force) { mWasModified = true; changeModifiedState(false); } } else { mComposerBase->updateAutoSave(); } } bool KMComposerWin::encryptToSelf() const { return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf(); } void KMComposerWin::slotSendFailed(const QString &msg, MessageComposer::ComposerViewBase::FailedType type) { setEnabled(true); if (!msg.isEmpty()) { KMessageBox::sorry(mMainWidget, msg, (type == MessageComposer::ComposerViewBase::AutoSave) ? i18n("Autosave Message Failed") : i18n("Sending Message Failed")); } } void KMComposerWin::slotSendSuccessful() { setModified(false); mComposerBase->cleanupAutoSave(); mFolder = Akonadi::Collection(); // see dtor close(); } const KIdentityManagement::Identity &KMComposerWin::identity() const { return KMKernel::self()->identityManager()->identityForUoidOrDefault(mComposerBase->identityCombo()->currentIdentity()); } Kleo::CryptoMessageFormat KMComposerWin::cryptoMessageFormat() const { if (!mCryptoModuleAction) { return Kleo::AutoFormat; } return cb2format(mCryptoModuleAction->currentItem()); } void KMComposerWin::addAttach(KMime::Content *msgPart) { mComposerBase->addAttachmentPart(msgPart); setModified(true); } void KMComposerWin::slotAddressBook() { KRun::runCommand(QStringLiteral("kaddressbook"), window()); } void KMComposerWin::slotInsertFile() { const QUrl u = insertFile(); if (u.isEmpty()) { return; } mRecentAction->addUrl(u); // Prevent race condition updating list when multiple composers are open { QUrlQuery query; const QString encoding = MimeTreeParser::NodeHelper::encodingForName(query.queryItemValue(QStringLiteral("charset"))); QStringList urls = KMailSettings::self()->recentUrls(); QStringList encodings = KMailSettings::self()->recentEncodings(); // Prevent config file from growing without bound // Would be nicer to get this constant from KRecentFilesAction const int mMaxRecentFiles = 30; while (urls.count() > mMaxRecentFiles) { urls.removeLast(); } while (encodings.count() > mMaxRecentFiles) { encodings.removeLast(); } // sanity check if (urls.count() != encodings.count()) { urls.clear(); encodings.clear(); } urls.prepend(u.toDisplayString()); encodings.prepend(encoding); KMailSettings::self()->setRecentUrls(urls); KMailSettings::self()->setRecentEncodings(encodings); mRecentAction->saveEntries(KMKernel::self()->config()->group(QString())); } slotInsertRecentFile(u); } void KMComposerWin::slotRecentListFileClear() { KSharedConfig::Ptr config = KMKernel::self()->config(); KConfigGroup group(config, "Composer"); group.deleteEntry("recent-urls"); group.deleteEntry("recent-encodings"); mRecentAction->saveEntries(config->group(QString())); } void KMComposerWin::slotInsertRecentFile(const QUrl &u) { if (u.fileName().isEmpty()) { return; } // Get the encoding previously used when inserting this file QString encoding; const QStringList urls = KMailSettings::self()->recentUrls(); const QStringList encodings = KMailSettings::self()->recentEncodings(); const int index = urls.indexOf(u.toDisplayString()); if (index != -1) { encoding = encodings[ index ]; } else { qCDebug(KMAIL_LOG) << " encoding not found so we can't insert text"; //see InsertTextFileJob return; } MessageComposer::InsertTextFileJob *job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u); job->setEncoding(encoding); connect(job, &KJob::result, this, &KMComposerWin::slotInsertTextFile); job->start(); } bool KMComposerWin::showErrorMessage(KJob *job) { if (job->error()) { if (static_cast(job)->uiDelegate()) { static_cast(job)->uiDelegate()->showErrorMessage(); } else { qCDebug(KMAIL_LOG) << " job->errorString() :" << job->errorString(); } return true; } return false; } void KMComposerWin::slotInsertTextFile(KJob *job) { showErrorMessage(job); } void KMComposerWin::slotCryptoModuleSelected() { slotSelectCryptoModule(false); } void KMComposerWin::slotSelectCryptoModule(bool init) { if (!init) { setModified(true); } mComposerBase->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments()); mComposerBase->attachmentModel()->setSignEnabled(canSignEncryptAttachments()); } void KMComposerWin::slotUpdateFont() { if (!mFixedFontAction) { return; } mComposerBase->editor()->composerControler()->setFontForWholeText(mFixedFontAction->isChecked() ? mFixedFont : mBodyFont); } QUrl KMComposerWin::insertFile() { const KEncodingFileDialog::Result result = KEncodingFileDialog::getOpenUrlAndEncoding(QString(), QUrl(), QString(), this, i18nc("@title:window", "Insert File")); QUrl url; if (!result.URLs.isEmpty()) { url = result.URLs.constFirst(); if (url.isValid()) { MessageCore::StringUtil::setEncodingFile(url, MimeTreeParser::NodeHelper::fixEncoding(result.encoding)); } } return url; } QString KMComposerWin::smartQuote(const QString &msg) { return MessageCore::StringUtil::smartQuote(msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth()); } void KMComposerWin::insertUrls(const QMimeData *source, const QList &urlList) { QStringList urlAdded; for (const QUrl &url : urlList) { QString urlStr; if (url.scheme() == QLatin1String("mailto")) { urlStr = KEmailAddress::decodeMailtoUrl(url); } else { urlStr = url.toDisplayString(); // Workaround #346370 if (urlStr.isEmpty()) { urlStr = source->text(); } } if (!urlAdded.contains(urlStr)) { mComposerBase->editor()->composerControler()->insertLink(urlStr); urlAdded.append(urlStr); } } } bool KMComposerWin::insertFromMimeData(const QMimeData *source, bool forceAttachment) { // If this is a PNG image, either add it as an attachment or as an inline image if (source->hasHtml() && mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich) { const QString html = QString::fromUtf8(source->data(QStringLiteral("text/html"))); mComposerBase->editor()->insertHtml(html); return true; } else if (source->hasHtml() && (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Plain) && source->hasText() && !forceAttachment) { mComposerBase->editor()->insertPlainText(source->text()); return true; } else if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) { // Get the image data before showing the dialog, since that processes events which can delete // the QMimeData object behind our back const QByteArray imageData = source->data(QStringLiteral("image/png")); if (imageData.isEmpty()) { return true; } if (!forceAttachment) { if (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) { QImage image = qvariant_cast(source->imageData()); QFileInfo fi(source->text()); QMenu menu(this); const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image")); /*const QAction *addAsAttachmentAction = */ menu.addAction(i18n("Add as &Attachment")); const QAction *selectedAction = menu.exec(QCursor::pos()); if (selectedAction == addAsInlineImageAction) { // Let the textedit from kdepimlibs handle inline images mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi); return true; } else if (!selectedAction) { return true; } // else fall through } } // Ok, when we reached this point, the user wants to add the image as an attachment. // Ask for the filename first. bool ok; const QString attName = QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok); if (!ok) { return true; } addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png"); return true; } // If this is a URL list, add those files as attachments or text // but do not offer this if we are pasting plain text containing an url, e.g. from a browser const QList urlList = source->urls(); if (!urlList.isEmpty()) { //Search if it's message items. Akonadi::Item::List items; Akonadi::Collection::List collections; bool allLocalURLs = true; for (const QUrl &url : urlList) { if (!url.isLocalFile()) { allLocalURLs = false; } const Akonadi::Item item = Akonadi::Item::fromUrl(url); if (item.isValid()) { items << item; } else { const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url); if (collection.isValid()) { collections << collection; } } } if (items.isEmpty() && collections.isEmpty()) { if (allLocalURLs || forceAttachment) { for (const QUrl &url : urlList) { addAttachment(url, QString()); } } else { QMenu p; const int sizeUrl(urlList.size()); const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl)); const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl)); const QAction *selectedAction = p.exec(QCursor::pos()); if (selectedAction == addAsTextAction) { insertUrls(source, urlList); } else if (selectedAction == addAsAttachmentAction) { for (const QUrl &url : urlList) { if (url.isValid()) { addAttachment(url, QString()); } } } } return true; } else { if (!items.isEmpty()) { Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob(items, this); itemFetchJob->fetchScope().fetchFullPayload(true); itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotFetchJob); } if (!collections.isEmpty()) { qCDebug(KMAIL_LOG) << "Collection dnd not supported"; } return true; } } return false; } void KMComposerWin::slotPasteAsAttachment() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); if (insertFromMimeData(mimeData, true)) { return; } if (mimeData->hasText()) { bool ok; const QString attName = QInputDialog::getText(this, i18n("Insert clipboard text as attachment"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok); if (ok) { mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain"); } return; } } void KMComposerWin::slotFetchJob(KJob *job) { if (showErrorMessage(job)) { return; } Akonadi::ItemFetchJob *fjob = qobject_cast(job); if (!fjob) { return; } const Akonadi::Item::List items = fjob->items(); if (items.isEmpty()) { return; } if (items.first().mimeType() == KMime::Message::mimeType()) { uint identity = 0; if (items.at(0).isValid()) { const Akonadi::Collection parentCollection = items.at(0).parentCollection(); if (parentCollection.isValid()) { const QString resourceName = parentCollection.resource(); if (!resourceName.isEmpty()) { QSharedPointer fd(MailCommon::FolderSettings::forCollection(parentCollection, false)); if (fd) { identity = fd->identity(); } } } } KMCommand *command = new KMForwardAttachedCommand(this, items, identity, this); command->start(); } else { for (const Akonadi::Item &item : items) { QString attachmentName = QStringLiteral("attachment"); if (item.hasPayload()) { const KContacts::Addressee contact = item.payload(); attachmentName = contact.realName() + QLatin1String(".vcf"); //Workaround about broken kaddressbook fields. QByteArray data = item.payloadData(); KContacts::adaptIMAttributes(data); addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), data, "text/x-vcard"); } else if (item.hasPayload()) { const KContacts::ContactGroup group = item.payload(); attachmentName = group.name() + QLatin1String(".vcf"); Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob(group, this); expandJob->setProperty("groupName", attachmentName); connect(expandJob, &KJob::result, this, &KMComposerWin::slotExpandGroupResult); expandJob->start(); } else { addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1()); } } } } void KMComposerWin::slotExpandGroupResult(KJob *job) { Akonadi::ContactGroupExpandJob *expandJob = qobject_cast(job); Q_ASSERT(expandJob); const QString attachmentName = expandJob->property("groupName").toString(); KContacts::VCardConverter converter; const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0); if (!groupData.isEmpty()) { addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), groupData, "text/x-vcard"); } } void KMComposerWin::slotClose() { close(); } void KMComposerWin::slotNewComposer() { KMComposerCreateNewComposerJob *job = new KMComposerCreateNewComposerJob; job->setCollectionForNewMessage(mCollectionForNewMessage); job->setCurrentIdentity(currentIdentity()); job->start(); } void KMComposerWin::slotUpdateWindowTitle() { QString s(mEdtSubject->toPlainText()); // Remove characters that show badly in most window decorations: // newlines tend to become boxes. if (s.isEmpty()) { setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')')); } else { setWindowTitle(s.replace(QLatin1Char('\n'), QLatin1Char(' '))); } } void KMComposerWin::slotEncryptToggled(bool on) { setEncryption(on, true); updateSignatureAndEncryptionStateIndicators(); } void KMComposerWin::setEncryption(bool encrypt, bool setByUser) { bool wasModified = isModified(); if (setByUser) { setModified(true); } if (!mEncryptAction->isEnabled()) { encrypt = false; } // check if the user wants to encrypt messages to himself and if he defined // an encryption key for the current identity else if (encrypt && encryptToSelf() && !mLastIdentityHasEncryptionKey) { if (setByUser) { KMessageBox::sorry(this, i18n("

You have requested that messages be " "encrypted to yourself, but the currently selected " "identity does not define an (OpenPGP or S/MIME) " "encryption key to use for this.

" "

Please select the key(s) to use " "in the identity configuration.

" "
"), i18n("Undefined Encryption Key")); setModified(wasModified); } encrypt = false; } // make sure the mEncryptAction is in the right state mEncryptAction->setChecked(encrypt); mEncryptAction->setProperty("setByUser", setByUser); if (!setByUser) { updateSignatureAndEncryptionStateIndicators(); } // show the appropriate icon if (encrypt) { mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-encrypt"))); } else { mEncryptAction->setIcon(QIcon::fromTheme(QStringLiteral("document-decrypt"))); } if (setByUser) { // User has toggled encryption, go over all recipients Q_FOREACH (auto line, mComposerBase->recipientsEditor()->lines()) { if (encrypt) { // Encryption was enabled, update encryption status of all recipients slotRecipientAdded(qobject_cast(line)); } else { // Encryption was disabled, remove the encryption indicator auto edit = qobject_cast(line); edit->setIcon(QIcon()); auto recipient = edit->data().dynamicCast(); recipient->setEncryptionAction(Kleo::Impossible); recipient->setKey(GpgME::Key()); } } } // mark the attachments for (no) encryption if (canSignEncryptAttachments()) { mComposerBase->attachmentModel()->setEncryptSelected(encrypt); } } void KMComposerWin::slotSignToggled(bool on) { setSigning(on, true); updateSignatureAndEncryptionStateIndicators(); } void KMComposerWin::setSigning(bool sign, bool setByUser) { bool wasModified = isModified(); if (setByUser) { setModified(true); } if (!mSignAction->isEnabled()) { sign = false; } // check if the user defined a signing key for the current identity if (sign && !mLastIdentityHasSigningKey) { if (setByUser) { KMessageBox::sorry(this, i18n("

In order to be able to sign " "this message you first have to " "define the (OpenPGP or S/MIME) signing key " "to use.

" "

Please select the key to use " "in the identity configuration.

" "
"), i18n("Undefined Signing Key")); setModified(wasModified); } sign = false; } // make sure the mSignAction is in the right state mSignAction->setChecked(sign); if (!setByUser) { updateSignatureAndEncryptionStateIndicators(); } // mark the attachments for (no) signing if (canSignEncryptAttachments()) { mComposerBase->attachmentModel()->setSignSelected(sign); } } void KMComposerWin::slotWordWrapToggled(bool on) { if (on) { mComposerBase->editor()->enableWordWrap(validateLineWrapWidth()); } else { disableWordWrap(); } } int KMComposerWin::validateLineWrapWidth() { int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth(); if ((lineWrap == 0) || (lineWrap > 78)) { lineWrap = 78; } else if (lineWrap < 30) { lineWrap = 30; } return lineWrap; } void KMComposerWin::disableWordWrap() { mComposerBase->editor()->disableWordWrap(); } void KMComposerWin::forceDisableHtml() { mForceDisableHtml = true; disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); mMarkupAction->setEnabled(false); // FIXME: Remove the toggle toolbar action somehow } bool KMComposerWin::isComposing() const { return mComposerBase && mComposerBase->isComposing(); } void KMComposerWin::disableForgottenAttachmentsCheck() { mCheckForForgottenAttachments = false; } void KMComposerWin::slotPrint() { printComposer(false); } void KMComposerWin::slotPrintPreview() { printComposer(true); } void KMComposerWin::printComposer(bool preview) { MessageComposer::Composer *composer = createSimpleComposer(); mMiscComposers.append(composer); composer->setProperty("preview", preview); connect(composer, &MessageComposer::Composer::result, this, &KMComposerWin::slotPrintComposeResult); composer->start(); } void KMComposerWin::slotPrintComposeResult(KJob *job) { const bool preview = job->property("preview").toBool(); printComposeResult(job, preview); } void KMComposerWin::printComposeResult(KJob *job, bool preview) { Q_ASSERT(dynamic_cast< MessageComposer::Composer * >(job)); MessageComposer::Composer *composer = dynamic_cast< MessageComposer::Composer * >(job); Q_ASSERT(mMiscComposers.contains(composer)); mMiscComposers.removeAll(composer); if (composer->error() == MessageComposer::Composer::NoError) { Q_ASSERT(composer->resultMessages().size() == 1); Akonadi::Item printItem; printItem.setPayload(composer->resultMessages().constFirst()); Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().constFirst()), printItem); const bool isHtml = mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich; const MessageViewer::Viewer::DisplayFormatMessage format = isHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text; KMPrintCommand *command = new KMPrintCommand(this, printItem, nullptr, format, isHtml); command->setPrintPreview(preview); command->start(); } else { showErrorMessage(job); } } void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool willSendItWithoutReediting) { // TODO integrate with MDA online status if (method == MessageComposer::MessageSender::SendImmediate) { if (!MessageComposer::Util::sendMailDispatcherIsOnline()) { method = MessageComposer::MessageSender::SendLater; } } if (saveIn == MessageComposer::MessageSender::SaveInNone || willSendItWithoutReediting) { // don't save as draft or template, send immediately if (KEmailAddress::firstEmailAddress(from()).isEmpty()) { if (!(mShowHeaders & HDR_FROM)) { mShowHeaders |= HDR_FROM; rethinkFields(false); } mEdtFrom->setFocus(); KMessageBox::sorry(this, i18n("You must enter your email address in the " "From: field. You should also set your email " "address for all identities, so that you do " "not have to enter it for each message.")); return; } if (mComposerBase->to().isEmpty()) { if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) { KMessageBox::information(this, i18n("You must specify at least one receiver, " "either in the To: field or as CC or as BCC.")); return; } else { int rc = KMessageBox::questionYesNo(this, i18n("To: field is empty. " "Send message anyway?"), i18n("No To: specified"), KStandardGuiItem::yes(), KStandardGuiItem::no()); if (rc == KMessageBox::No) { return; } } } if (subject().isEmpty()) { mEdtSubject->setFocus(); int rc = KMessageBox::questionYesNo(this, i18n("You did not specify a subject. " "Send message anyway?"), i18n("No Subject Specified"), KGuiItem(i18n("S&end as Is")), KGuiItem(i18n("&Specify the Subject"))); if (rc == KMessageBox::No) { return; } } const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment(); if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment) || (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) { return; } MessageComposer::PluginEditorCheckBeforeSendParams params; params.setSubject(subject()); params.setHtmlMail(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); params.setIdentity(mComposerBase->identityCombo()->currentIdentity()); params.setHasAttachment(mComposerBase->attachmentModel()->rowCount() > 0); params.setTransportId(mComposerBase->transportComboBox()->currentTransportId()); const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(mComposerBase->identityCombo()->currentIdentity()); QString defaultDomainName; if (!ident.isNull()) { defaultDomainName = ident.defaultDomainName(); } const QString composerBaseBccTrimmed = mComposerBase->bcc().trimmed(); const QString composerBaseToTrimmed = mComposerBase->to().trimmed(); const QString composerBaseCcTrimmed = mComposerBase->cc().trimmed(); params.setBccAddresses(composerBaseBccTrimmed); params.setToAddresses(composerBaseToTrimmed); params.setCcAddresses(composerBaseCcTrimmed); params.setDefaultDomain(defaultDomainName); if (!mPluginEditorCheckBeforeSendManagerInterface->execute(params)) { return; } const QStringList recipients = {composerBaseToTrimmed, composerBaseCcTrimmed, composerBaseBccTrimmed}; setEnabled(false); // Validate the To:, CC: and BCC fields AddressValidationJob *job = new AddressValidationJob(recipients.join(QStringLiteral(", ")), this, this); job->setDefaultDomain(defaultDomainName); job->setProperty("method", static_cast(method)); job->setProperty("saveIn", static_cast(saveIn)); connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDoDelayedSend); job->start(); // we'll call send from within slotDoDelaySend } else { if (saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptAction->isChecked() && KMailSettings::self()->alwaysEncryptDrafts() && mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty()) { KMessageBox::information(this, i18n("You must specify at least one receiver " "in order to be able to encrypt a draft.")); return; } doDelayedSend(method, saveIn); } } void KMComposerWin::slotDoDelayedSend(KJob *job) { if (job->error()) { KMessageBox::error(this, job->errorText()); setEnabled(true); return; } const AddressValidationJob *validateJob = qobject_cast(job); // Abort sending if one of the recipient addresses is invalid ... if (!validateJob->isValid()) { setEnabled(true); return; } // ... otherwise continue as usual const MessageComposer::MessageSender::SendMethod method = static_cast(job->property("method").toInt()); const MessageComposer::MessageSender::SaveIn saveIn = static_cast(job->property("saveIn").toInt()); doDelayedSend(method, saveIn); } void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase *mComposerBase) { QList< QByteArray > charsets = mCodecAction->mimeCharsets(); if (!mOriginalPreferredCharset.isEmpty()) { charsets.insert(0, mOriginalPreferredCharset); } mComposerBase->setFrom(from()); mComposerBase->setReplyTo(replyTo()); mComposerBase->setSubject(subject()); mComposerBase->setCharsets(charsets); mComposerBase->setUrgent(mUrgentAction->isChecked()); mComposerBase->setMDNRequested(mRequestMDNAction->isChecked()); } void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn) { #ifndef QT_NO_CURSOR KPIM::KCursorSaver busy(KPIM::KBusyPtr::busy()); #endif applyComposerSetting(mComposerBase); if (mForceDisableHtml) { disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); } bool sign = mSignAction->isChecked(); bool encrypt = mEncryptAction->isChecked(); mComposerBase->setCryptoOptions(sign, encrypt, cryptoMessageFormat(), ((saveIn != MessageComposer::MessageSender::SaveInNone && !KMailSettings::self()->alwaysEncryptDrafts()) || mSigningAndEncryptionExplicitlyDisabled)); const int num = KMailSettings::self()->customMessageHeadersCount(); QMap customHeader; for (int ix = 0; ix < num; ++ix) { CustomMimeHeader customMimeHeader(QString::number(ix)); customMimeHeader.load(); customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue()); } QMap::const_iterator extraCustomHeader = mExtraHeaders.constBegin(); while (extraCustomHeader != mExtraHeaders.constEnd()) { customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value()); ++extraCustomHeader; } mComposerBase->setCustomHeader(customHeader); mComposerBase->send(method, saveIn, false); } void KMComposerWin::slotSendLater() { if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) { return; } if (!checkRecipientNumber()) { return; } mComposerBase->setSendLaterInfo(nullptr); if (mComposerBase->editor()->checkExternalEditorFinished()) { const bool wasRegistered = (SendLater::SendLaterUtil::sentLaterAgentWasRegistered() && SendLater::SendLaterUtil::sentLaterAgentEnabled()); if (wasRegistered) { SendLater::SendLaterInfo *info = nullptr; QPointer dlg = new SendLater::SendLaterDialog(info, this); if (dlg->exec()) { info = dlg->info(); const SendLater::SendLaterDialog::SendLaterAction action = dlg->action(); delete dlg; switch (action) { case SendLater::SendLaterDialog::Unknown: qCDebug(KMAIL_LOG) << "Sendlater action \"Unknown\": Need to fix it."; break; case SendLater::SendLaterDialog::Canceled: return; break; case SendLater::SendLaterDialog::PutInOutbox: doSend(MessageComposer::MessageSender::SendLater); break; case SendLater::SendLaterDialog::SendDeliveryAtTime: mComposerBase->setSendLaterInfo(info); if (info->isRecurrence()) { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates, true); } else { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts, true); } break; } } else { delete dlg; } } else { doSend(MessageComposer::MessageSender::SendLater); } } } void KMComposerWin::slotSaveDraft() { if (mComposerBase->editor()->checkExternalEditorFinished()) { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts); } } void KMComposerWin::slotSaveTemplate() { if (mComposerBase->editor()->checkExternalEditorFinished()) { doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates); } } void KMComposerWin::slotSendNowVia(MailTransport::Transport *transport) { if (transport) { mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); slotSendNow(); } } void KMComposerWin::slotSendLaterVia(MailTransport::Transport *transport) { if (transport) { mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); slotSendLater(); } } void KMComposerWin::sendNow(bool shortcutUsed) { if (!mComposerBase->editor()->checkExternalEditorFinished()) { return; } if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) { return; } if (!checkRecipientNumber()) { return; } mSendNowByShortcutUsed = shortcutUsed; if (KMailSettings::self()->checkSpellingBeforeSend()) { mComposerBase->editor()->forceSpellChecking(); } else { slotCheckSendNow(); } } void KMComposerWin::slotSendNowByShortcut() { sendNow(true); } void KMComposerWin::slotSendNow() { sendNow(false); } void KMComposerWin::confirmBeforeSend() { const int rc = KMessageBox::warningYesNoCancel(mMainWidget, i18n("About to send email..."), i18n("Send Confirmation"), KGuiItem(i18n("&Send Now")), KGuiItem(i18n("Send &Later"))); if (rc == KMessageBox::Yes) { doSend(MessageComposer::MessageSender::SendImmediate); } else if (rc == KMessageBox::No) { doSend(MessageComposer::MessageSender::SendLater); } } void KMComposerWin::slotCheckSendNowStep2() { if (KMailSettings::self()->confirmBeforeSend()) { confirmBeforeSend(); } else { if (mSendNowByShortcutUsed) { if (!KMailSettings::self()->checkSendDefaultActionShortcut()) { ValidateSendMailShortcut validateShortcut(actionCollection(), this); if (!validateShortcut.validate()) { return; } } if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) { confirmBeforeSend(); return; } } doSend(MessageComposer::MessageSender::SendImmediate); } } void KMComposerWin::slotDelayedCheckSendNow() { QTimer::singleShot(0, this, &KMComposerWin::slotCheckSendNow); } void KMComposerWin::slotCheckSendNow() { PotentialPhishingEmailJob *job = new PotentialPhishingEmailJob(this); KConfigGroup group(KSharedConfig::openConfig(), "PotentialPhishing"); const QStringList whiteList = group.readEntry("whiteList", QStringList()); job->setEmailWhiteList(whiteList); QStringList lst; lst << mComposerBase->to(); if (!mComposerBase->cc().isEmpty()) { lst << mComposerBase->cc().split(QLatin1Char(',')); } if (!mComposerBase->bcc().isEmpty()) { lst << mComposerBase->bcc().split(QLatin1Char(',')); } job->setPotentialPhishingEmails(lst); connect(job, &PotentialPhishingEmailJob::potentialPhishingEmailsFound, this, &KMComposerWin::slotPotentialPhishingEmailsFound); job->start(); } void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList &list) { if (list.isEmpty()) { slotCheckSendNowStep2(); } else { mPotentialPhishingEmailWarning->setPotentialPhisingEmail(list); } } bool KMComposerWin::checkRecipientNumber() const { const int thresHold = KMailSettings::self()->recipientThreshold(); if (KMailSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold) { if (KMessageBox::questionYesNo(mMainWidget, i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold), i18n("Too many recipients"), KGuiItem(i18n("&Send as Is")), KGuiItem(i18n("&Edit Recipients"))) == KMessageBox::No) { return false; } } return true; } void KMComposerWin::enableHtml() { if (mForceDisableHtml) { disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); return; } mComposerBase->editor()->activateRichText(); if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) { // Use singleshot, as we we might actually be called from a slot that wanted to disable the // toolbar (but the messagebox in disableHtml() prevented that and called us). // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled" // signal, so wait one event loop run for that. QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show); } if (!mMarkupAction->isChecked()) { mMarkupAction->setChecked(true); } mComposerBase->editor()->composerActions()->updateActionStates(); mComposerBase->editor()->composerActions()->setActionsEnabled(true); } void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation) { bool forcePlainTextMarkup = false; if (confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->composerControler()->isFormattingUsed() && !mForceDisableHtml) { int choice = KMessageBox::warningYesNoCancel(this, i18n("Turning HTML mode off " "will cause the text to lose the formatting. Are you sure?"), i18n("Lose the formatting?"), KGuiItem(i18n("Lose Formatting")), KGuiItem(i18n("Add Markup Plain Text")), KStandardGuiItem::cancel(), QStringLiteral("LoseFormattingWarning")); switch (choice) { case KMessageBox::Cancel: enableHtml(); return; case KMessageBox::No: forcePlainTextMarkup = true; break; case KMessageBox::Yes: break; } } mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup); mComposerBase->editor()->switchToPlainText(); mComposerBase->editor()->composerActions()->setActionsEnabled(false); slotUpdateFont(); if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) { // See the comment in enableHtml() why we use a singleshot timer, similar situation here. QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide); } if (mMarkupAction->isChecked()) { mMarkupAction->setChecked(false); } } void KMComposerWin::slotToggleMarkup() { htmlToolBarVisibilityChanged(mMarkupAction->isChecked()); } void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode) { if (mode == MessageComposer::RichTextComposerNg::Plain) { disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); // ### Can this happen at all? } else { enableHtml(); } } void KMComposerWin::htmlToolBarVisibilityChanged(bool visible) { if (visible) { enableHtml(); } else { disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm); } } void KMComposerWin::slotAutoSpellCheckingToggled(bool on) { mAutoSpellCheckingAction->setChecked(on); if (on != mComposerBase->editor()->checkSpellingEnabled()) { mComposerBase->editor()->setCheckSpellingEnabled(on); } if (on != mEdtSubject->checkSpellingEnabled()) { mEdtSubject->setCheckSpellingEnabled(on); } mStatusBarLabelSpellCheckingChangeMode->setToggleMode(on); } void KMComposerWin::slotSpellCheckingStatus(const QString &status) { mStatusbarLabel->setText(status); QTimer::singleShot(2000, this, &KMComposerWin::slotSpellcheckDoneClearStatus); } void KMComposerWin::slotSpellcheckDoneClearStatus() { mStatusbarLabel->clear(); } void KMComposerWin::slotIdentityChanged(uint uoid, bool initalChange) { if (mMsg == nullptr) { qCDebug(KMAIL_LOG) << "Trying to change identity but mMsg == 0!"; return; } const KIdentityManagement::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(uoid); if (ident.isNull()) { return; } bool wasModified(isModified()); Q_EMIT identityChanged(identity()); if (!ident.fullEmailAddr().isNull()) { mEdtFrom->setText(ident.fullEmailAddr()); } // make sure the From field is shown if it does not contain a valid email address if (KEmailAddress::firstEmailAddress(from()).isEmpty()) { mShowHeaders |= HDR_FROM; } if (mEdtReplyTo) { mEdtReplyTo->setText(ident.replyToAddr()); } // remove BCC of old identity and add BCC of new identity (if they differ) const KIdentityManagement::Identity &oldIdentity = KMKernel::self()->identityManager()->identityForUoidOrDefault(mId); if (ident.organization().isEmpty()) { mMsg->removeHeader(); } else { KMime::Headers::Organization *const organization = new KMime::Headers::Organization; organization->fromUnicodeString(ident.organization(), "utf-8"); mMsg->setHeader(organization); } if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) { mMsg->removeHeader("X-Face"); } else { QString xface = ident.xface(); if (!xface.isEmpty()) { int numNL = (xface.length() - 1) / 70; for (int i = numNL; i > 0; --i) { xface.insert(i * 70, QStringLiteral("\n\t")); } KMime::Headers::Generic *header = new KMime::Headers::Generic("X-Face"); header->fromUnicodeString(xface, "utf-8"); mMsg->setHeader(header); } } const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt(); const Transport *transport = TransportManager::self()->transportById(transportId, true); if (!transport) { mMsg->removeHeader("X-KMail-Transport"); mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); } else { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-Transport"); header->fromUnicodeString(QString::number(transport->id()), "utf-8"); mMsg->setHeader(header); mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); } const bool fccIsDisabled = ident.disabledFcc(); if (fccIsDisabled) { KMime::Headers::Generic *header = new KMime::Headers::Generic("X-KMail-FccDisabled"); header->fromUnicodeString(QStringLiteral("true"), "utf-8"); mMsg->setHeader(header); } else { mMsg->removeHeader("X-KMail-FccDisabled"); } mFccFolder->setEnabled(!fccIsDisabled); mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); slotSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary()); if (!mPreventFccOverwrite) { setFcc(ident.fcc()); } // if unmodified, apply new template, if one is set if (!wasModified && !(ident.templates().isEmpty() && mCustomTemplate.isEmpty()) && !initalChange) { applyTemplate(uoid, mId, ident, wasModified); } else { mComposerBase->identityChanged(ident, oldIdentity, false); mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage()); updateComposerAfterIdentityChanged(ident, uoid, wasModified); } } void KMComposerWin::updateComposerAfterIdentityChanged(const KIdentityManagement::Identity &ident, uint uoid, bool wasModified) { // disable certain actions if there is no PGP user identity set // for this profile bool bNewIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); bool bNewIdentityHasEncryptionKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); // save the state of the sign and encrypt button if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) { mLastEncryptActionState = mEncryptAction->isChecked(); setEncryption(false); } if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) { mLastSignActionState = mSignAction->isChecked(); setSigning(false); } // restore the last state of the sign and encrypt button if (bNewIdentityHasEncryptionKey && !mLastIdentityHasEncryptionKey) { setEncryption(mLastEncryptActionState); } if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) { setSigning(mLastSignActionState); } mCryptoModuleAction->setCurrentItem(format2cb( Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat()))); slotSelectCryptoModule(true); mLastIdentityHasSigningKey = bNewIdentityHasSigningKey; mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey; const KIdentityManagement::Signature sig = const_cast(ident).signature(); bool isEnabledSignature = sig.isEnabledSignature(); mAppendSignature->setEnabled(isEnabledSignature); mPrependSignature->setEnabled(isEnabledSignature); mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature); mId = uoid; changeCryptoAction(); // make sure the From and BCC fields are shown if necessary rethinkFields(false); setModified(wasModified); } void KMComposerWin::slotSpellcheckConfig() { static_cast(mComposerBase->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc")); } void KMComposerWin::slotEditToolbars() { QPointer dlg = new KEditToolBar(guiFactory(), this); connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KMComposerWin::slotUpdateToolbars); dlg->exec(); delete dlg; } void KMComposerWin::slotUpdateToolbars() { createGUI(QStringLiteral("kmcomposerui.rc")); applyMainWindowSettings(KMKernel::self()->config()->group("Composer")); } void KMComposerWin::slotEditKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsDisallowed); } void KMComposerWin::setFocusToEditor() { // The cursor position is already set by setMsg(), so we only need to set the // focus here. mComposerBase->editor()->setFocus(); } void KMComposerWin::setFocusToSubject() { mEdtSubject->setFocus(); } void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode) { KMailSettings::self()->setCompletionMode((int)mode); // sync all the lineedits to the same completion mode mEdtFrom->setCompletionMode(mode); mEdtReplyTo->setCompletionMode(mode); mComposerBase->recipientsEditor()->setCompletionMode(mode); } void KMComposerWin::slotConfigChanged() { readConfig(true /*reload*/); mComposerBase->updateAutoSave(); rethinkFields(); slotWordWrapToggled(mWordWrapAction->isChecked()); } /* * checks if the drafts-folder has been deleted * that is not nice so we set the system-drafts-folder */ void KMComposerWin::slotFolderRemoved(const Akonadi::Collection &col) { qCDebug(KMAIL_LOG) << "you killed me."; // TODO: need to handle templates here? if ((mFolder.isValid()) && (col.id() == mFolder.id())) { mFolder = CommonKernel->draftsCollectionFolder(); qCDebug(KMAIL_LOG) << "restoring drafts to" << mFolder.id(); } else if (col.id() == mFccFolder->collection().id()) { qCDebug(KMAIL_LOG) << "FCC was removed " << col.id(); mFccFolder->setCollection(CommonKernel->sentCollectionFolder()); mIncorrectIdentityFolderWarning->fccIsInvalid(); } } void KMComposerWin::slotOverwriteModeChanged() { const bool overwriteMode = mComposerBase->editor()->overwriteMode(); mComposerBase->editor()->setCursorWidth(overwriteMode ? 5 : 1); mStatusBarLabelToggledOverrideMode->setToggleMode(overwriteMode); } void KMComposerWin::slotCursorPositionChanged() { // Change Line/Column info in status bar const int line = mComposerBase->editor()->linePosition() + 1; const int col = mComposerBase->editor()->columnNumber() + 1; QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line); mCursorLineLabel->setText(temp); temp = i18n(" Column: %1 ", col); mCursorColumnLabel->setText(temp); // Show link target in status bar if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) { const QString text = mComposerBase->editor()->composerControler()->currentLinkText() + QLatin1String(" -> ") + mComposerBase->editor()->composerControler()->currentLinkUrl(); mStatusbarLabel->setText(text); } else { mStatusbarLabel->clear(); } } void KMComposerWin::recipientEditorSizeHintChanged() { QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize); } void KMComposerWin::setMaximumHeaderSize() { mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height()); } void KMComposerWin::updateSignatureAndEncryptionStateIndicators() { mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(mSignAction->isChecked(), mEncryptAction->isChecked()); } void KMComposerWin::slotDictionaryLanguageChanged(const QString &language) { mComposerBase->dictionary()->setCurrentByDictionary(language); } void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection &collection) { mComposerBase->setFcc(collection); mComposerBase->editor()->document()->setModified(true); } void KMComposerWin::slotSaveAsFile() { SaveAsFileJob *job = new SaveAsFileJob(this); job->setParentWidget(this); job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); job->setTextDocument(mComposerBase->editor()->document()); job->start(); //not necessary to delete it. It done in SaveAsFileJob } void KMComposerWin::slotAttachMissingFile() { mComposerBase->attachmentController()->showAddAttachmentFileDialog(); } void KMComposerWin::slotVerifyMissingAttachmentTimeout() { if (mComposerBase->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) { mAttachmentMissing->animatedShow(); } } void KMComposerWin::slotExplicitClosedMissingAttachment() { if (mVerifyMissingAttachment) { mVerifyMissingAttachment->stop(); delete mVerifyMissingAttachment; mVerifyMissingAttachment = nullptr; } } void KMComposerWin::addExtraCustomHeaders(const QMap &headers) { mExtraHeaders = headers; } void KMComposerWin::slotExternalEditorStarted() { mComposerBase->identityCombo()->setEnabled(false); mExternalEditorWarning->show(); } void KMComposerWin::slotExternalEditorClosed() { mComposerBase->identityCombo()->setEnabled(true); mExternalEditorWarning->hide(); } void KMComposerWin::slotInsertShortUrl(const QString &url) { mComposerBase->editor()->composerControler()->insertLink(url); } void KMComposerWin::slotTransportChanged() { mComposerBase->editor()->document()->setModified(true); } void KMComposerWin::slotFollowUpMail(bool toggled) { if (toggled) { QPointer dlg = new MessageComposer::FollowUpReminderSelectDateDialog(this); if (dlg->exec()) { mComposerBase->setFollowUpDate(dlg->selectedDate()); mComposerBase->setFollowUpCollection(dlg->collection()); } else { mFollowUpToggleAction->setChecked(false); } delete dlg; } else { mComposerBase->clearFollowUp(); } } void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b) { mSnippetWidget->setVisible(b); mSnippetSplitterCollapser->setVisible(b); } void KMComposerWin::slotOverwriteModeWasChanged(bool state) { mComposerBase->editor()->setCursorWidth(state ? 5 : 1); mComposerBase->editor()->setOverwriteMode(state); } QList KMComposerWin::customToolsList() const { return mCustomToolsWidget->actionList(); } QList KMComposerWin::pluginToolsActionListForPopupMenu() const { return mPluginEditorManagerInterface->actionsType(MessageComposer::ActionType::PopupMenu); } void KMComposerWin::slotRecipientEditorLineAdded(KPIM::MultiplyingLine *line_) { auto line = qobject_cast(line_); Q_ASSERT(line); connect(line, &MessageComposer::RecipientLineNG::countChanged, this, [this, line]() { this->slotRecipientAdded(line); }); connect(line, &MessageComposer::RecipientLineNG::iconClicked, this, [this, line]() { this->slotRecipientLineIconClicked(line); }); connect(line, &MessageComposer::RecipientLineNG::destroyed, this, &KMComposerWin::slotRecipientEditorFocusChanged, Qt::QueuedConnection); connect(line, &MessageComposer::RecipientLineNG::activeChanged, this, [this, line]() { this->slotRecipientFocusLost(line); }); slotRecipientEditorFocusChanged(); } void KMComposerWin::slotRecipientEditorFocusChanged() { // Already disabled if (mEncryptAction->property("setByUser").toBool()) { return; } // Focus changed, which basically means that user "committed" a new recipient. // If we have at least one recipient that does not have a key, disable encryption // (unless user enabled it manually), because we want to encrypt by default, // but not by force bool encrypt = false; Q_FOREACH (auto line_, mComposerBase->recipientsEditor()->lines()) { auto line = qobject_cast(line_); // There's still a lookup job running, so wait, slotKeyForMailBoxResult() // will call us if the job returns empty key if (line->property("keyLookupJob").isValid()) { return; } const auto keyStatus = static_cast(line->property("keyStatus").toInt()); if (keyStatus == NoState) { continue; } if (!line->recipient()->isEmpty() && keyStatus != KeyOk) { setEncryption(false, false); return; } encrypt = true; } if (encrypt) { setEncryption(true, false); } } void KMComposerWin::slotRecipientLineIconClicked(MessageComposer::RecipientLineNG *line) { const auto data = line->data().dynamicCast(); if (!data->key().isNull()) { QProcess::startDetached(QStringLiteral("kleopatra"), { QStringLiteral("--query"), QString::fromLatin1(data->key().primaryFingerprint()), QStringLiteral("--parent-windowid"), QString::number(winId()) }); } } void KMComposerWin::slotRecipientAdded(MessageComposer::RecipientLineNG *line) { // User has disabled encryption, don't bother checking the key... if (!mEncryptAction->isChecked() && mEncryptAction->property("setByUser").toBool()) { return; } // Same if auto-encryption is not enabled in current identity settings if (!identity().pgpAutoEncrypt() || identity().pgpEncryptionKey().isEmpty()) { return; } if (line->recipientsCount() == 0) { return; } const auto protocol = QGpgME::openpgp(); // If we don't have gnupg we can't look for keys if (!protocol) { return; } auto recipient = line->data().dynamicCast(); // check if is an already running key lookup job and if so, cancel it // this is to prevent a slower job overwriting results of the job that we // are about to start now. const auto runningJob = line->property("keyLookupJob").value >(); if (runningJob) { disconnect(runningJob.data(), &QGpgME::KeyForMailboxJob::result, this, &KMComposerWin::slotKeyForMailBoxResult); runningJob->slotCancel(); line->setProperty("keyLookupJob", QVariant()); } QGpgME::KeyForMailboxJob *job = protocol->keyForMailboxJob(); if (!job) { line->setProperty("keyStatus", NoKey); recipient->setEncryptionAction(Kleo::Impossible); return; } QString dummy, addrSpec; if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) { addrSpec = recipient->email(); } line->setProperty("keyLookupJob", QVariant::fromValue(QPointer(job))); job->setProperty("recipient", QVariant::fromValue(recipient)); job->setProperty("line", QVariant::fromValue(QPointer(line))); connect(job, &QGpgME::KeyForMailboxJob::result, this, &KMComposerWin::slotKeyForMailBoxResult); job->start(addrSpec, true); } void KMComposerWin::slotRecipientFocusLost(MessageComposer::RecipientLineNG *line) { if (mEncryptAction->property("setByUser").toBool()) { return; } // Same if auto-encryption is not enabled in current identity settings if (!identity().pgpAutoEncrypt() || identity().pgpEncryptionKey().isEmpty()) { return; } if (line->recipientsCount() == 0) { return; } if (line->property("keyLookupJob").toBool()) { return; } if (static_cast(line->property("keyStatus").toInt()) != KeyOk) { line->setProperty("keyStatus", NoKey); setEncryption(false, false); } } void KMComposerWin::slotKeyForMailBoxResult(const GpgME::KeyListResult &, const GpgME::Key &key, const GpgME::UserID &userID) { QObject *job = sender(); Q_ASSERT(job); // Check if the encryption was explicitly disabled while the job was running if (!mEncryptAction->isChecked() && mEncryptAction->property("setByUser").toBool()) { return; } auto recipient = job->property("recipient").value(); auto line = job->property("line").value >(); if (!recipient || !line) { return; } line->setProperty("keyLookupJob", QVariant()); if (key.isNull()) { recipient->setEncryptionAction(Kleo::Impossible); // no key line->setIcon(QIcon()); line->setProperty("keyStatus", InProgress); } else { recipient->setEncryptionAction(Kleo::DoIt); recipient->setKey(key); const QIcon icon = QIcon::fromTheme(QStringLiteral("gpg")); QIcon overlay; QString tooltip; switch (userID.validity()) { case GpgME::UserID::Ultimate: case GpgME::UserID::Full: overlay = QIcon::fromTheme(QStringLiteral("emblem-favorite")); tooltip = i18n("High security encryption will be used for this recipient (the encryption key is fully trusted). " "Click the icon for details."); break; case GpgME::UserID::Marginal: overlay = QIcon::fromTheme(QStringLiteral("emblem-success")); tooltip = i18n("Medium security encryption will be used for this recipient (the encryption key is marginally trusted). " "Click the icon for details."); break; case GpgME::UserID::Never: overlay = QIcon::fromTheme(QStringLiteral("emblem-error")); tooltip = i18n("Low security encryption will be used for this recipient (the encryption key is untrusted). " "Click the icon for details."); break; case GpgME::UserID::Undefined: case GpgME::UserID::Unknown: default: overlay = QIcon::fromTheme(QStringLiteral("emblem-information")); tooltip = i18n("The email to this recipient will be encrypted, but the security of the encryption is unknown " "(the encryption key could not be verified). Click the icon for details."); break; } line->setProperty("keyStatus", KeyOk); line->setIcon(KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner), tooltip); slotRecipientEditorFocusChanged(); } } void KMComposerWin::slotIdentityDeleted(uint uoid) { if (mComposerBase->identityCombo()->currentIdentity() == uoid) { mIncorrectIdentityFolderWarning->identityInvalid(); } } void KMComposerWin::slotTransportRemoved(int id, const QString &name) { Q_UNUSED(name); if (mComposerBase->transportComboBox()->currentTransportId() == id) { mIncorrectIdentityFolderWarning->mailTransportIsInvalid(); } }