File indexing completed on 2024-10-27 04:51:02
0001 /* 0002 * This file is part of KMail. 0003 * SPDX-FileCopyrightText: 2011-2024 Laurent Montel <montel@kde.org> 0004 * 0005 * SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com> 0006 * 0007 * Based on KMail code by: 0008 * SPDX-FileCopyrightText: 1997 Markus Wuebben <markus.wuebben@kde.org> 0009 * 0010 * SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 #include "kmcomposerwin.h" 0013 #include "subjectlineeditwithautocorrection.h" 0014 // KMail includes 0015 #include "attachment/attachmentcontroller.h" 0016 #include "attachment/attachmentview.h" 0017 #include "custommimeheader.h" 0018 #include "editor/kmcomposereditorng.h" 0019 #include "editor/plugininterface/kmailplugineditorcheckbeforesendmanagerinterface.h" 0020 #include "editor/plugininterface/kmailplugineditorconverttextmanagerinterface.h" 0021 #include "editor/plugininterface/kmailplugineditorinitmanagerinterface.h" 0022 #include "editor/plugininterface/kmailplugineditormanagerinterface.h" 0023 #include "editor/plugininterface/kmailplugingrammareditormanagerinterface.h" 0024 #include "editor/potentialphishingemail/potentialphishingemailjob.h" 0025 #include "editor/potentialphishingemail/potentialphishingemailwarning.h" 0026 #include "editor/warningwidgets/attachmentaddedfromexternalwarning.h" 0027 #include "editor/warningwidgets/incorrectidentityfolderwarning.h" 0028 #include "editor/warningwidgets/nearexpirywarning.h" 0029 #include "editor/warningwidgets/toomanyrecipientswarning.h" 0030 #include "job/addressvalidationjob.h" 0031 #include "job/dndfromarkjob.h" 0032 #include "job/saveasfilejob.h" 0033 #include "job/savedraftjob.h" 0034 #include "kmail_debug.h" 0035 #include "kmcommands.h" 0036 #include "kmcomposercreatenewcomposerjob.h" 0037 #include "kmcomposerglobalaction.h" 0038 #include "kmcomposerupdatetemplatejob.h" 0039 #include "kmkernel.h" 0040 #include "mailcomposeradaptor.h" // TODO port all D-Bus stuff... 0041 #include "settings/kmailsettings.h" 0042 #include "undosend/undosendmanager.h" 0043 #include "util.h" 0044 #include "validatesendmailshortcut.h" 0045 #include "warningwidgets/attachmentmissingwarning.h" 0046 #include "warningwidgets/externaleditorwarning.h" 0047 #include "widgets/cryptostateindicatorwidget.h" 0048 #include "widgets/kactionmenutransport.h" 0049 #include <templateparser/templatesconfiguration_kfg.h> 0050 0051 #include <Akonadi/ChangeRecorder> 0052 #include <Akonadi/ContactGroupExpandJob> 0053 #include <Akonadi/ItemFetchJob> 0054 #include <Akonadi/MessageFlags> 0055 #include <Akonadi/MessageStatus> 0056 #include <Akonadi/Monitor> 0057 0058 #include <KContacts/VCardConverter> 0059 0060 #include <KIdentityManagementCore/Identity> 0061 #include <KIdentityManagementCore/IdentityManager> 0062 #include <KIdentityManagementCore/Signature> 0063 #include <KIdentityManagementWidgets/IdentityCombo> 0064 0065 #include <KMime/Message> 0066 0067 #include <KPIMTextEdit/RichTextComposerActions> 0068 #include <KPIMTextEdit/RichTextComposerControler> 0069 #include <KPIMTextEdit/RichTextComposerImages> 0070 #include <KPIMTextEdit/RichTextExternalComposer> 0071 #include <TextCustomEditor/RichTextEditorWidget> 0072 0073 #include <Libkdepim/ProgressStatusBarWidget> 0074 #include <Libkdepim/StatusbarProgressWidget> 0075 0076 #include <KCursorSaver> 0077 0078 #include <Libkleo/ExpiryChecker> 0079 #include <Libkleo/KeyCache> 0080 #include <Libkleo/KeySelectionDialog> 0081 0082 #include <MailCommon/FolderCollectionMonitor> 0083 #include <MailCommon/FolderRequester> 0084 #include <MailCommon/FolderSettings> 0085 #include <MailCommon/MailKernel> 0086 #include <MailCommon/SnippetTreeView> 0087 #include <MailCommon/SnippetsManager> 0088 0089 #include <MailTransport/Transport> 0090 #include <MailTransport/TransportComboBox> 0091 #include <MailTransport/TransportManager> 0092 0093 #include <MessageComposer/AttachmentModel> 0094 #include <MessageComposer/Composer> 0095 #include <MessageComposer/ComposerLineEdit> 0096 #include <MessageComposer/ComposerViewInterface> 0097 #include <MessageComposer/ConvertSnippetVariablesJob> 0098 #include <MessageComposer/DraftStatus> 0099 #include <MessageComposer/FollowUpReminderSelectDateDialog> 0100 #include <MessageComposer/FollowupReminder> 0101 #include <MessageComposer/FollowupReminderCreateJob> 0102 #include <MessageComposer/GlobalPart> 0103 #include <MessageComposer/InfoPart> 0104 #include <MessageComposer/InsertTextFileJob> 0105 #include <MessageComposer/Kleo_Util> 0106 #include <MessageComposer/MessageComposerSettings> 0107 #include <MessageComposer/MessageHelper> 0108 #include <MessageComposer/PluginActionType> 0109 #include <MessageComposer/PluginEditorCheckBeforeSendParams> 0110 #include <MessageComposer/PluginEditorConverterBeforeConvertingData> 0111 #include <MessageComposer/PluginEditorConverterInitialData> 0112 #include <MessageComposer/PluginEditorInterface> 0113 #include <MessageComposer/RecipientsEditor> 0114 #include <MessageComposer/RichTextComposerSignatures> 0115 #include <MessageComposer/SendLaterDialog> 0116 #include <MessageComposer/SendLaterInfo> 0117 #include <MessageComposer/SendLaterUtil> 0118 #include <MessageComposer/SignatureController> 0119 #include <MessageComposer/StatusBarLabelToggledState> 0120 #include <MessageComposer/TextPart> 0121 #include <MessageComposer/Util> 0122 0123 #include <Sonnet/DictionaryComboBox> 0124 0125 #include <MessageCore/AttachmentPart> 0126 #include <MessageCore/AutocryptStorage> 0127 #include <MessageCore/MessageCoreSettings> 0128 #include <MessageCore/NodeHelper> 0129 #include <MessageCore/StringUtil> 0130 0131 #include <MessageViewer/MessageViewerSettings> 0132 #include <MessageViewer/Stl_Util> 0133 0134 #include <MimeTreeParser/NodeHelper> 0135 #include <MimeTreeParser/ObjectTreeParser> 0136 #include <MimeTreeParser/SimpleObjectTreeSource> 0137 0138 #include <PimCommon/CustomToolsPluginManager> 0139 #include <PimCommon/CustomToolsWidgetng> 0140 #include <PimCommon/KActionMenuChangeCase> 0141 #include <PimCommon/LineEditWithAutoCorrection> 0142 #include <PimCommon/PurposeMenuMessageWidget> 0143 0144 #include <TemplateParser/TemplateParserJob> 0145 #include <TemplateParser/TemplatesConfiguration> 0146 0147 #include <QGpgME/Protocol> 0148 0149 // KDE Frameworks includes 0150 #include <KActionCollection> 0151 #include <KActionMenu> 0152 #include <KConfigGroup> 0153 #include <KEditToolBar> 0154 #include <KEmailAddress> 0155 #include <KEncodingFileDialog> 0156 #include <KFileWidget> 0157 #include <KIO/JobUiDelegate> 0158 #include <KIconUtils> 0159 #include <KMessageBox> 0160 #include <KRecentDirs> 0161 #include <KRecentFilesAction> 0162 #include <KShortcutsDialog> 0163 #include <KSplitterCollapserButton> 0164 #include <KStandardShortcut> 0165 #include <KToggleAction> 0166 #include <KToolBar> 0167 #include <KXMLGUIFactory> 0168 0169 #include <QDBusConnection> 0170 // Qt includes 0171 #include <QAction> 0172 #include <QApplication> 0173 #include <QCheckBox> 0174 #include <QClipboard> 0175 #include <QFontDatabase> 0176 #include <QInputDialog> 0177 #include <QMenu> 0178 #include <QMenuBar> 0179 #include <QMimeData> 0180 #include <QPointer> 0181 #include <QShortcut> 0182 #include <QSplitter> 0183 #include <QStandardPaths> 0184 #include <QStatusBar> 0185 #include <QUrlQuery> 0186 0187 // GPGME 0188 #include <gpgme++/key.h> 0189 #include <gpgme++/keylistresult.h> 0190 #include <gpgme++/tofuinfo.h> 0191 0192 #include <KDialogJobUiDelegate> 0193 #include <KIO/CommandLauncherJob> 0194 #include <chrono> 0195 0196 using namespace std::chrono_literals; 0197 0198 using MailTransport::Transport; 0199 using MailTransport::TransportManager; 0200 using Sonnet::DictionaryComboBox; 0201 0202 Q_DECLARE_METATYPE(MessageComposer::Recipient::Ptr) 0203 0204 KMail::Composer *KMail::makeComposer(const KMime::Message::Ptr &msg, 0205 bool lastSignState, 0206 bool lastEncryptState, 0207 Composer::TemplateContext context, 0208 uint identity, 0209 const QString &textSelection, 0210 const QString &customTemplate) 0211 { 0212 return KMComposerWin::create(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate); 0213 } 0214 0215 KMail::Composer *KMComposerWin::create(const KMime::Message::Ptr &msg, 0216 bool lastSignState, 0217 bool lastEncryptState, 0218 Composer::TemplateContext context, 0219 uint identity, 0220 const QString &textSelection, 0221 const QString &customTemplate) 0222 { 0223 return new KMComposerWin(msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate); 0224 } 0225 0226 int KMComposerWin::s_composerNumber = 0; 0227 0228 KMComposerWin::KMComposerWin(const KMime::Message::Ptr &aMsg, 0229 bool lastSignState, 0230 bool lastEncryptState, 0231 Composer::TemplateContext context, 0232 uint id, 0233 const QString &textSelection, 0234 const QString &customTemplate) 0235 : KMail::Composer(QStringLiteral("kmail-composer#")) 0236 , mTextSelection(textSelection) 0237 , mCustomTemplate(customTemplate) 0238 , mFolder(Akonadi::Collection(-1)) 0239 , mId(id) 0240 , mContext(context) 0241 , mAttachmentMissing(new AttachmentMissingWarning(this)) 0242 , mExternalEditorWarning(new ExternalEditorWarning(this)) 0243 , mTooMyRecipientWarning(new TooManyRecipientsWarning(this)) 0244 , mNearExpiryWarning(new NearExpiryWarning(this)) 0245 , mCryptoStateIndicatorWidget(new CryptoStateIndicatorWidget(this)) 0246 , mPotentialPhishingEmailWarning(new PotentialPhishingEmailWarning(this)) 0247 , mIncorrectIdentityFolderWarning(new IncorrectIdentityFolderWarning(this)) 0248 , mPluginEditorManagerInterface(new KMailPluginEditorManagerInterface(this)) 0249 , mPluginEditorGrammarManagerInterface(new KMailPluginGrammarEditorManagerInterface(this)) 0250 , mAttachmentFromExternalMissing(new AttachmentAddedFromExternalWarning(this)) 0251 , mPluginEditorMessageWidget(new PimCommon::PurposeMenuMessageWidget(this)) 0252 , mKeyCache(Kleo::KeyCache::mutableInstance()) 0253 { 0254 mGlobalAction = new KMComposerGlobalAction(this, this); 0255 mComposerBase = new MessageComposer::ComposerViewBase(this, this); 0256 mComposerBase->setIdentityManager(kmkernel->identityManager()); 0257 0258 connect(mPluginEditorManagerInterface, &KMailPluginEditorManagerInterface::message, this, &KMComposerWin::slotMessage); 0259 connect(mPluginEditorManagerInterface, &KMailPluginEditorManagerInterface::insertText, this, &KMComposerWin::slotEditorPluginInsertText); 0260 mPluginEditorCheckBeforeSendManagerInterface = new KMailPluginEditorCheckBeforeSendManagerInterface(this); 0261 mPluginEditorInitManagerInterface = new KMailPluginEditorInitManagerInterface(this); 0262 mPluginEditorConvertTextManagerInterface = new KMailPluginEditorConvertTextManagerInterface(this); 0263 0264 connect(mPluginEditorManagerInterface, 0265 &KMailPluginEditorManagerInterface::errorMessage, 0266 mPluginEditorMessageWidget, 0267 &PimCommon::PurposeMenuMessageWidget::slotShareError); 0268 connect(mPluginEditorManagerInterface, 0269 &KMailPluginEditorManagerInterface::successMessage, 0270 mPluginEditorMessageWidget, 0271 &PimCommon::PurposeMenuMessageWidget::slotShareSuccess); 0272 0273 connect(mComposerBase, &MessageComposer::ComposerViewBase::disableHtml, this, &KMComposerWin::disableHtml); 0274 connect(mComposerBase, &MessageComposer::ComposerViewBase::enableHtml, this, &KMComposerWin::enableHtml); 0275 connect(mComposerBase, &MessageComposer::ComposerViewBase::failed, this, &KMComposerWin::slotSendFailed); 0276 connect(mComposerBase, &MessageComposer::ComposerViewBase::sentSuccessfully, this, &KMComposerWin::slotSendSuccessful); 0277 connect(mComposerBase, &MessageComposer::ComposerViewBase::modified, this, &KMComposerWin::setModified); 0278 0279 (void)new MailcomposerAdaptor(this); 0280 0281 connect(mComposerBase->expiryChecker().get(), 0282 &Kleo::ExpiryChecker::expiryMessage, 0283 this, 0284 [&](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) { 0285 Q_UNUSED(isNewMessage); 0286 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) { 0287 const auto plainMsg = msg.replace(QStringLiteral("<p>"), QStringLiteral(" ")) 0288 .replace(QStringLiteral("</p>"), QStringLiteral(" ")) 0289 .replace(QStringLiteral("<p align=center>"), QStringLiteral(" ")); 0290 mNearExpiryWarning->addInfo(plainMsg); 0291 mNearExpiryWarning->setWarning(info == Kleo::ExpiryChecker::OwnKeyExpired); 0292 mNearExpiryWarning->animatedShow(); 0293 } 0294 const QList<KPIM::MultiplyingLine *> lstLines = mComposerBase->recipientsEditor()->lines(); 0295 for (KPIM::MultiplyingLine *line : lstLines) { 0296 auto recipient = line->data().dynamicCast<MessageComposer::Recipient>(); 0297 if (recipient->key().primaryFingerprint() == key.primaryFingerprint()) { 0298 auto recipientLine = qobject_cast<MessageComposer::RecipientLineNG *>(line); 0299 QString iconname = QStringLiteral("emblem-warning"); 0300 if (info == Kleo::ExpiryChecker::OtherKeyExpired) { 0301 mEncryptionState.setAcceptedSolution(false); 0302 iconname = QStringLiteral("emblem-error"); 0303 const auto showCryptoIndicator = KMailSettings::self()->showCryptoLabelIndicator(); 0304 const auto hasOverride = mEncryptionState.hasOverride(); 0305 const auto encrypt = mEncryptionState.encrypt(); 0306 0307 const bool showAllIcons = showCryptoIndicator && hasOverride && encrypt; 0308 if (!showAllIcons) { 0309 recipientLine->setIcon(QIcon(), msg); 0310 return; 0311 } 0312 } 0313 0314 recipientLine->setIcon(QIcon::fromTheme(iconname), msg); 0315 return; 0316 } 0317 } 0318 }); 0319 0320 mdbusObjectPath = QLatin1StringView("/Composer_") + QString::number(++s_composerNumber); 0321 QDBusConnection::sessionBus().registerObject(mdbusObjectPath, this); 0322 0323 auto sigController = new MessageComposer::SignatureController(this); 0324 connect(sigController, &MessageComposer::SignatureController::enableHtml, this, &KMComposerWin::enableHtml); 0325 mComposerBase->setSignatureController(sigController); 0326 0327 if (!kmkernel->xmlGuiInstanceName().isEmpty()) { 0328 setComponentName(kmkernel->xmlGuiInstanceName(), i18n("KMail2")); 0329 } 0330 mMainWidget = new QWidget(this); 0331 // splitter between the headers area and the actual editor 0332 mHeadersToEditorSplitter = new QSplitter(Qt::Vertical, mMainWidget); 0333 mHeadersToEditorSplitter->setObjectName(QLatin1StringView("mHeadersToEditorSplitter")); 0334 mHeadersToEditorSplitter->setChildrenCollapsible(false); 0335 mHeadersArea = new QWidget(mHeadersToEditorSplitter); 0336 mHeadersArea->setSizePolicy(mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding); 0337 mHeadersToEditorSplitter->addWidget(mHeadersArea); 0338 const QList<int> defaultSizes{0}; 0339 mHeadersToEditorSplitter->setSizes(defaultSizes); 0340 0341 auto v = new QVBoxLayout(mMainWidget); 0342 v->setContentsMargins({}); 0343 v->addWidget(mHeadersToEditorSplitter); 0344 auto identity = new KIdentityManagementWidgets::IdentityCombo(kmkernel->identityManager(), mHeadersArea); 0345 identity->setCurrentIdentity(mId); 0346 identity->setObjectName(QLatin1StringView("identitycombo")); 0347 connect(identity, &KIdentityManagementWidgets::IdentityCombo::identityDeleted, this, &KMComposerWin::slotIdentityDeleted); 0348 connect(identity, &KIdentityManagementWidgets::IdentityCombo::invalidIdentity, this, &KMComposerWin::slotInvalidIdentity); 0349 mComposerBase->setIdentityCombo(identity); 0350 0351 sigController->setIdentityCombo(identity); 0352 sigController->suspend(); // we have to do identity change tracking ourselves due to the template code 0353 0354 auto dictionaryCombo = new DictionaryComboBox(mHeadersArea); 0355 dictionaryCombo->setToolTip(i18n("Select the dictionary to use when spell-checking this message")); 0356 mComposerBase->setDictionary(dictionaryCombo); 0357 0358 mFccFolder = new MailCommon::FolderRequester(mHeadersArea); 0359 mFccFolder->setNotAllowToCreateNewFolder(true); 0360 mFccFolder->setMustBeReadWrite(true); 0361 0362 mFccFolder->setToolTip(i18n("Select the sent-mail folder where a copy of this message will be saved")); 0363 connect(mFccFolder, &MailCommon::FolderRequester::folderChanged, this, &KMComposerWin::slotFccFolderChanged); 0364 connect(mFccFolder, &MailCommon::FolderRequester::invalidFolder, this, &KMComposerWin::slotFccIsInvalid); 0365 0366 auto transport = new MailTransport::TransportComboBox(mHeadersArea); 0367 transport->setToolTip(i18n("Select the outgoing account to use for sending this message")); 0368 mComposerBase->setTransportCombo(transport); 0369 connect(transport, &MailTransport::TransportComboBox::activated, this, &KMComposerWin::slotTransportChanged); 0370 connect(transport, &MailTransport::TransportComboBox::transportRemoved, this, &KMComposerWin::slotTransportRemoved); 0371 mEdtFrom = new MessageComposer::ComposerLineEdit(false, mHeadersArea); 0372 mEdtFrom->installEventFilter(this); 0373 mEdtFrom->setObjectName(QLatin1StringView("fromLine")); 0374 mEdtFrom->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); 0375 mEdtFrom->setToolTip(i18n("Set the \"From:\" email address for this message")); 0376 0377 auto recipientsEditor = new MessageComposer::RecipientsEditor(mHeadersArea); 0378 recipientsEditor->setRecentAddressConfig(MessageComposer::MessageComposerSettings::self()->config()); 0379 connect(recipientsEditor, &MessageComposer::RecipientsEditor::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); 0380 connect(recipientsEditor, &MessageComposer::RecipientsEditor::sizeHintChanged, this, &KMComposerWin::recipientEditorSizeHintChanged); 0381 connect(recipientsEditor, &MessageComposer::RecipientsEditor::lineAdded, this, &KMComposerWin::slotRecipientEditorLineAdded); 0382 connect(recipientsEditor, &MessageComposer::RecipientsEditor::focusInRecipientLineEdit, this, &KMComposerWin::slotRecipientEditorLineFocused); 0383 mComposerBase->setRecipientsEditor(recipientsEditor); 0384 0385 mEdtSubject = new SubjectLineEditWithAutoCorrection(mHeadersArea, QStringLiteral("kmail2rc")); 0386 mEdtSubject->installEventFilter(this); 0387 mEdtSubject->setAutocorrection(KMKernel::self()->composerAutoCorrection()); 0388 connect(mEdtSubject, &SubjectLineEditWithAutoCorrection::handleMimeData, this, [this](const QMimeData *mimeData) { 0389 insertFromMimeData(mimeData, false); 0390 }); 0391 0392 connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &KMComposerWin::slotEncryptionButtonIconUpdate); 0393 connect(&mEncryptionState, &EncryptionState::encryptChanged, this, &KMComposerWin::updateSignatureAndEncryptionStateIndicators); 0394 connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &KMComposerWin::slotEncryptionButtonIconUpdate); 0395 connect(&mEncryptionState, &EncryptionState::overrideChanged, this, &KMComposerWin::runKeyResolver); 0396 connect(&mEncryptionState, &EncryptionState::acceptedSolutionChanged, this, &KMComposerWin::slotEncryptionButtonIconUpdate); 0397 0398 mRunKeyResolverTimer = new QTimer(this); 0399 mRunKeyResolverTimer->setSingleShot(true); 0400 mRunKeyResolverTimer->setInterval(500ms); 0401 connect(mRunKeyResolverTimer, &QTimer::timeout, this, &KMComposerWin::runKeyResolver); 0402 0403 mLblIdentity = new QLabel(i18n("&Identity:"), mHeadersArea); 0404 mDictionaryLabel = new QLabel(i18n("&Dictionary:"), mHeadersArea); 0405 mLblFcc = new QLabel(i18n("&Sent-Mail folder:"), mHeadersArea); 0406 mLblTransport = new QLabel(i18n("&Mail transport:"), mHeadersArea); 0407 mLblFrom = new QLabel(i18nc("sender address field", "&From:"), mHeadersArea); 0408 mLblSubject = new QLabel(i18nc("@label:textbox Subject of email.", "S&ubject:"), mHeadersArea); 0409 mShowHeaders = KMailSettings::self()->headers(); 0410 mDone = false; 0411 // the attachment view is separated from the editor by a splitter 0412 mSplitter = new QSplitter(Qt::Vertical, mMainWidget); 0413 mSplitter->setObjectName(QLatin1StringView("mSplitter")); 0414 mSplitter->setChildrenCollapsible(false); 0415 mSnippetSplitter = new QSplitter(Qt::Horizontal, mSplitter); 0416 mSnippetSplitter->setObjectName(QLatin1StringView("mSnippetSplitter")); 0417 mSplitter->addWidget(mSnippetSplitter); 0418 0419 auto editorAndCryptoStateIndicators = new QWidget(mSplitter); 0420 0421 auto vbox = new QVBoxLayout(editorAndCryptoStateIndicators); 0422 vbox->setSpacing(0); 0423 vbox->setContentsMargins({}); 0424 0425 connect(mPotentialPhishingEmailWarning, &PotentialPhishingEmailWarning::sendNow, this, &KMComposerWin::slotCheckSendNowStep2); 0426 vbox->addWidget(mPotentialPhishingEmailWarning); 0427 0428 connect(mAttachmentMissing, &AttachmentMissingWarning::attachMissingFile, this, &KMComposerWin::slotAttachMissingFile); 0429 connect(mAttachmentMissing, &AttachmentMissingWarning::explicitClosedMissingAttachment, this, &KMComposerWin::slotExplicitClosedMissingAttachment); 0430 vbox->addWidget(mAttachmentMissing); 0431 0432 auto composerEditorNg = new KMComposerEditorNg(this, mCryptoStateIndicatorWidget); 0433 composerEditorNg->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge})); 0434 mRichTextEditorWidget = new TextCustomEditor::RichTextEditorWidget(composerEditorNg, mCryptoStateIndicatorWidget); 0435 composerEditorNg->installEventFilter(this); 0436 0437 connect(composerEditorNg, &KMComposerEditorNg::insertEmoticon, mGlobalAction, &KMComposerGlobalAction::slotInsertEmoticon); 0438 // Don't use new connect api here. It crashes 0439 connect(composerEditorNg, SIGNAL(textChanged()), this, SLOT(slotEditorTextChanged())); 0440 connect(composerEditorNg, &KMComposerEditorNg::selectionChanged, this, &KMComposerWin::slotSelectionChanged); 0441 // connect(editor, &KMComposerEditor::textChanged, this, &KMComposeWin::slotEditorTextChanged); 0442 mComposerBase->setEditor(composerEditorNg); 0443 0444 vbox->addWidget(mIncorrectIdentityFolderWarning); 0445 0446 vbox->addWidget(mPluginEditorMessageWidget); 0447 vbox->addWidget(mAttachmentFromExternalMissing); 0448 vbox->addWidget(mTooMyRecipientWarning); 0449 vbox->addWidget(mNearExpiryWarning); 0450 0451 vbox->addWidget(mCryptoStateIndicatorWidget); 0452 vbox->addWidget(mRichTextEditorWidget); 0453 0454 mSnippetSplitter->insertWidget(0, editorAndCryptoStateIndicators); 0455 mSnippetSplitter->setOpaqueResize(true); 0456 sigController->setEditor(composerEditorNg); 0457 0458 mHeadersToEditorSplitter->addWidget(mSplitter); 0459 composerEditorNg->setAcceptDrops(true); 0460 connect(sigController, 0461 &MessageComposer::SignatureController::signatureAdded, 0462 mComposerBase->editor()->externalComposer(), 0463 &KPIMTextEdit::RichTextExternalComposer::startExternalEditor); 0464 0465 connect(dictionaryCombo, &Sonnet::DictionaryComboBox::dictionaryChanged, this, &KMComposerWin::slotSpellCheckingLanguage); 0466 0467 connect(composerEditorNg, &KMComposerEditorNg::languageChanged, this, &KMComposerWin::slotDictionaryLanguageChanged); 0468 connect(composerEditorNg, &KMComposerEditorNg::spellCheckStatus, this, &KMComposerWin::slotSpellCheckingStatus); 0469 connect(composerEditorNg, &KMComposerEditorNg::insertModeChanged, this, &KMComposerWin::slotOverwriteModeChanged); 0470 connect(composerEditorNg, &KMComposerEditorNg::spellCheckingFinished, this, &KMComposerWin::slotDelayedCheckSendNow); 0471 mSnippetWidget = new MailCommon::SnippetTreeView(actionCollection(), mSnippetSplitter); 0472 connect(mSnippetWidget, &MailCommon::SnippetTreeView::insertSnippetInfo, this, &KMComposerWin::insertSnippetInfo); 0473 connect(composerEditorNg, &KMComposerEditorNg::insertSnippet, mSnippetWidget->snippetsManager(), &MailCommon::SnippetsManager::insertSnippet); 0474 mSnippetWidget->setVisible(KMailSettings::self()->showSnippetManager()); 0475 mSnippetSplitter->addWidget(mSnippetWidget); 0476 mSnippetSplitter->setCollapsible(0, false); 0477 mSnippetSplitterCollapser = new KSplitterCollapserButton(mSnippetWidget, mSnippetSplitter); 0478 mSnippetSplitterCollapser->setVisible(KMailSettings::self()->showSnippetManager()); 0479 0480 mSplitter->setOpaqueResize(true); 0481 0482 setWindowTitle(i18nc("@title:window", "Composer")); 0483 setMinimumSize(200, 200); 0484 0485 mCustomToolsWidget = new PimCommon::CustomToolsWidgetNg(this); 0486 mCustomToolsWidget->initializeView(actionCollection(), PimCommon::CustomToolsPluginManager::self()->pluginsList()); 0487 mSplitter->addWidget(mCustomToolsWidget); 0488 connect(mCustomToolsWidget, &PimCommon::CustomToolsWidgetNg::insertText, this, &KMComposerWin::slotInsertShortUrl); 0489 0490 auto attachmentModel = new MessageComposer::AttachmentModel(this); 0491 auto attachmentView = new KMail::AttachmentView(attachmentModel, mSplitter); 0492 attachmentView->hideIfEmpty(); 0493 connect(attachmentView, &KMail::AttachmentView::modified, this, &KMComposerWin::setModified); 0494 auto attachmentController = new KMail::AttachmentController(attachmentModel, attachmentView, this); 0495 0496 mComposerBase->setAttachmentModel(attachmentModel); 0497 mComposerBase->setAttachmentController(attachmentController); 0498 0499 if (KMailSettings::self()->showForgottenAttachmentWarning()) { 0500 mVerifyMissingAttachment = new QTimer(this); 0501 mVerifyMissingAttachment->setSingleShot(true); 0502 mVerifyMissingAttachment->setInterval(5s); 0503 connect(mVerifyMissingAttachment, &QTimer::timeout, this, &KMComposerWin::slotVerifyMissingAttachmentTimeout); 0504 } 0505 connect(attachmentController, &KMail::AttachmentController::fileAttached, mAttachmentMissing, &AttachmentMissingWarning::slotFileAttached); 0506 0507 v->addWidget(mExternalEditorWarning); 0508 0509 mPluginEditorManagerInterface->setParentWidget(this); 0510 mPluginEditorManagerInterface->setRichTextEditor(mRichTextEditorWidget->editor()); 0511 mPluginEditorManagerInterface->setActionCollection(actionCollection()); 0512 mPluginEditorManagerInterface->setComposerInterface(mComposerBase); 0513 0514 mPluginEditorCheckBeforeSendManagerInterface->setParentWidget(this); 0515 0516 mPluginEditorInitManagerInterface->setParentWidget(this); 0517 mPluginEditorInitManagerInterface->setRichTextEditor(composerEditorNg); 0518 0519 mPluginEditorConvertTextManagerInterface->setParentWidget(this); 0520 mPluginEditorConvertTextManagerInterface->setActionCollection(actionCollection()); 0521 mPluginEditorConvertTextManagerInterface->setRichTextEditor(composerEditorNg); 0522 0523 mPluginEditorGrammarManagerInterface->setParentWidget(this); 0524 mPluginEditorGrammarManagerInterface->setActionCollection(actionCollection()); 0525 mPluginEditorGrammarManagerInterface->setRichTextEditor(composerEditorNg); 0526 mPluginEditorGrammarManagerInterface->setCustomToolsWidget(mCustomToolsWidget); 0527 0528 setupStatusBar(attachmentView->widget()); 0529 setupActions(); 0530 setupEditor(); 0531 rethinkFields(); 0532 readConfig(); 0533 0534 updateSignatureAndEncryptionStateIndicators(); 0535 0536 applyMainWindowSettings(KMKernel::self()->config()->group(QStringLiteral("Composer"))); 0537 0538 mUpdateWindowTitleConnection = connect(mEdtSubject, &PimCommon::LineEditWithAutoCorrection::textChanged, this, &KMComposerWin::slotUpdateWindowTitle); 0539 mIdentityConnection = connect(identity, &KIdentityManagementWidgets::IdentityCombo::identityChanged, this, [this](uint val) { 0540 slotIdentityChanged(val); 0541 }); 0542 connect(kmkernel->identityManager(), qOverload<uint>(&KIdentityManagementCore::IdentityManager::changed), this, [this](uint val) { 0543 if (currentIdentity() == val) { 0544 slotIdentityChanged(val); 0545 } 0546 }); 0547 0548 connect(mEdtFrom, &MessageComposer::ComposerLineEdit::completionModeChanged, this, &KMComposerWin::slotCompletionModeChanged); 0549 connect(kmkernel->folderCollectionMonitor(), &Akonadi::Monitor::collectionRemoved, this, &KMComposerWin::slotFolderRemoved); 0550 connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::slotConfigChanged); 0551 connect(kmkernel, &KMKernel::configChanged, this, &KMComposerWin::runKeyResolver); 0552 0553 mMainWidget->resize(800, 600); 0554 setCentralWidget(mMainWidget); 0555 0556 if (KMailSettings::self()->useHtmlMarkup()) { 0557 enableHtml(); 0558 } else { 0559 disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm); 0560 } 0561 0562 if (KMailSettings::self()->useExternalEditor()) { 0563 composerEditorNg->setUseExternalEditor(true); 0564 composerEditorNg->setExternalEditorPath(KMailSettings::self()->externalEditor()); 0565 } 0566 0567 const QList<KPIM::MultiplyingLine *> lstLines = recipientsEditor->lines(); 0568 for (KPIM::MultiplyingLine *line : lstLines) { 0569 slotRecipientEditorLineAdded(line); 0570 } 0571 0572 if (aMsg) { 0573 setMessage(aMsg, lastSignState, lastEncryptState); 0574 } 0575 0576 mComposerBase->recipientsEditor()->setFocusBottom(); 0577 composerEditorNg->composerActions()->updateActionStates(); // set toolbar buttons to correct values 0578 0579 mDone = true; 0580 0581 mDummyComposer = new MessageComposer::Composer(this); 0582 mDummyComposer->globalPart()->setParentWidgetForGui(this); 0583 0584 KConfigGroup grp(KMKernel::self()->config()->group(QStringLiteral("Composer"))); 0585 setAutoSaveSettings(grp, true); 0586 connect(mComposerBase, &MessageComposer::ComposerViewBase::tooManyRecipient, this, &KMComposerWin::slotTooManyRecipients); 0587 } 0588 0589 KMComposerWin::~KMComposerWin() 0590 { 0591 disconnect(mUpdateWindowTitleConnection); 0592 // When we have a collection set, store the message back to that collection. 0593 // Note that when we save the message or sent it, mFolder is set back to 0. 0594 // So this for example kicks in when opening a draft and then closing the window. 0595 if (mFolder.isValid() && mMsg && isModified()) { 0596 auto saveDraftJob = new SaveDraftJob(mMsg, mFolder); 0597 saveDraftJob->start(); 0598 } 0599 0600 delete mComposerBase; 0601 } 0602 0603 void KMComposerWin::slotTooManyRecipients(bool b) 0604 { 0605 if (b) { 0606 mTooMyRecipientWarning->animatedShow(); 0607 } else { 0608 mTooMyRecipientWarning->animatedHide(); 0609 } 0610 } 0611 0612 void KMComposerWin::slotRecipientEditorLineFocused() 0613 { 0614 mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields); 0615 } 0616 0617 KMComposerWin::ModeType KMComposerWin::modeType() const 0618 { 0619 return mModeType; 0620 } 0621 0622 void KMComposerWin::setModeType(KMComposerWin::ModeType modeType) 0623 { 0624 mModeType = modeType; 0625 } 0626 0627 bool KMComposerWin::eventFilter(QObject *obj, QEvent *event) 0628 { 0629 if (event->type() == QEvent::FocusIn) { 0630 if (obj == mEdtSubject) { 0631 mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::SubjectField); 0632 } else if (obj == mComposerBase->recipientsEditor()) { 0633 mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields); 0634 } else if (obj == mEdtFrom) { 0635 mPluginEditorManagerInterface->setStatusBarWidgetEnabled(MessageComposer::PluginEditorInterface::ApplyOnFieldType::EmailFields); 0636 } 0637 } 0638 return KMail::Composer::eventFilter(obj, event); 0639 } 0640 0641 void KMComposerWin::insertSnippetInfo(const MailCommon::SnippetInfo &info) 0642 { 0643 { 0644 if (!info.to.isEmpty()) { 0645 const QStringList lst = KEmailAddress::splitAddressList(info.to); 0646 for (const QString &addr : lst) { 0647 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::To)) { 0648 qCWarning(KMAIL_LOG) << "Impossible to add to entry"; 0649 } 0650 } 0651 } 0652 } 0653 { 0654 if (!info.cc.isEmpty()) { 0655 const QStringList lst = KEmailAddress::splitAddressList(info.cc); 0656 for (const QString &addr : lst) { 0657 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::Cc)) { 0658 qCWarning(KMAIL_LOG) << "Impossible to add cc entry"; 0659 } 0660 } 0661 } 0662 } 0663 { 0664 if (!info.bcc.isEmpty()) { 0665 const QStringList lst = KEmailAddress::splitAddressList(info.bcc); 0666 for (const QString &addr : lst) { 0667 if (!mComposerBase->recipientsEditor()->addRecipient(addr, MessageComposer::Recipient::Bcc)) { 0668 qCWarning(KMAIL_LOG) << "Impossible to add bcc entry"; 0669 } 0670 } 0671 } 0672 } 0673 { 0674 if (!info.attachment.isEmpty()) { 0675 const QStringList lst = info.attachment.split(QLatin1Char(',')); 0676 for (const QString &attach : lst) { 0677 auto job = new MessageComposer::ConvertSnippetVariablesJob(this); 0678 job->setText(attach); 0679 auto interface = new MessageComposer::ComposerViewInterface(mComposerBase); 0680 job->setComposerViewInterface(interface); 0681 connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) { 0682 if (!str.isEmpty()) { 0683 const QUrl localUrl = QUrl::fromLocalFile(str); 0684 AttachmentInfo info; 0685 info.url = localUrl; 0686 addAttachment(QList<AttachmentInfo>() << info, false); 0687 } 0688 }); 0689 job->start(); 0690 } 0691 } 0692 } 0693 { 0694 if (!info.subject.isEmpty()) { 0695 // Convert subject 0696 auto job = new MessageComposer::ConvertSnippetVariablesJob(this); 0697 job->setText(info.subject); 0698 auto interface = new MessageComposer::ComposerViewInterface(mComposerBase); 0699 job->setComposerViewInterface(interface); 0700 connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) { 0701 if (!str.isEmpty()) { 0702 if (mComposerBase->subject().isEmpty()) { // Add subject only if we don't have subject 0703 mEdtSubject->setPlainText(str); 0704 } 0705 } 0706 }); 0707 job->start(); 0708 } 0709 } 0710 { 0711 if (!info.text.isEmpty()) { 0712 // Convert plain text 0713 auto job = new MessageComposer::ConvertSnippetVariablesJob(this); 0714 job->setText(info.text); 0715 auto interface = new MessageComposer::ComposerViewInterface(mComposerBase); 0716 job->setComposerViewInterface(interface); 0717 connect(job, &MessageComposer::ConvertSnippetVariablesJob::textConverted, this, [this](const QString &str) { 0718 mComposerBase->editor()->insertPlainText(str); 0719 }); 0720 job->start(); 0721 } 0722 } 0723 } 0724 0725 void KMComposerWin::slotSpellCheckingLanguage(const QString &language) 0726 { 0727 mComposerBase->editor()->setSpellCheckingLanguage(language); 0728 mEdtSubject->setSpellCheckingLanguage(language); 0729 } 0730 0731 QString KMComposerWin::dbusObjectPath() const 0732 { 0733 return mdbusObjectPath; 0734 } 0735 0736 void KMComposerWin::slotEditorTextChanged() 0737 { 0738 const bool textIsNotEmpty = !mComposerBase->editor()->document()->isEmpty(); 0739 mFindText->setEnabled(textIsNotEmpty); 0740 mFindNextText->setEnabled(textIsNotEmpty); 0741 mReplaceText->setEnabled(textIsNotEmpty); 0742 mSelectAll->setEnabled(textIsNotEmpty); 0743 if (mVerifyMissingAttachment && !mVerifyMissingAttachment->isActive()) { 0744 mVerifyMissingAttachment->start(); 0745 } 0746 } 0747 0748 void KMComposerWin::send(int how) 0749 { 0750 switch (how) { 0751 case 1: 0752 slotSendNow(); 0753 break; 0754 default: 0755 case 0: 0756 // TODO: find out, what the default send method is and send it this way 0757 case 2: 0758 slotSendLater(); 0759 break; 0760 } 0761 } 0762 0763 void KMComposerWin::addAttachmentsAndSend(const QList<QUrl> &urls, const QString &comment, int how) 0764 { 0765 const int nbUrl = urls.count(); 0766 for (int i = 0; i < nbUrl; ++i) { 0767 mComposerBase->addAttachment(urls.at(i), comment, true); 0768 } 0769 0770 send(how); 0771 } 0772 0773 void KMComposerWin::addAttachment(const QList<KMail::Composer::AttachmentInfo> &infos, bool showWarning) 0774 { 0775 QStringList lst; 0776 for (const AttachmentInfo &info : infos) { 0777 if (showWarning) { 0778 lst.append(info.url.toDisplayString()); 0779 } 0780 mComposerBase->addAttachment(info.url, info.comment, false); 0781 } 0782 if (showWarning) { 0783 mAttachmentFromExternalMissing->setAttachmentNames(lst); 0784 mAttachmentFromExternalMissing->animatedShow(); 0785 } 0786 } 0787 0788 void KMComposerWin::addAttachment(const QString &name, 0789 KMime::Headers::contentEncoding cte, 0790 const QString &charset, 0791 const QByteArray &data, 0792 const QByteArray &mimeType) 0793 { 0794 Q_UNUSED(cte) 0795 mComposerBase->addAttachment(name, name, charset, data, mimeType); 0796 } 0797 0798 void KMComposerWin::readConfig(bool reload) 0799 { 0800 mEdtFrom->setCompletionMode(static_cast<KCompletion::CompletionMode>(KMailSettings::self()->completionMode())); 0801 mComposerBase->recipientsEditor()->setCompletionMode(static_cast<KCompletion::CompletionMode>(KMailSettings::self()->completionMode())); 0802 mCryptoStateIndicatorWidget->setShowAlwaysIndicator(KMailSettings::self()->showCryptoLabelIndicator()); 0803 0804 if (MessageCore::MessageCoreSettings::self()->useDefaultFonts()) { 0805 mBodyFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); 0806 mFixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); 0807 } else { 0808 mBodyFont = KMailSettings::self()->composerFont(); 0809 mFixedFont = MessageViewer::MessageViewerSettings::self()->fixedFont(); 0810 } 0811 0812 slotUpdateFont(); 0813 mEdtFrom->setFont(mBodyFont); 0814 mEdtSubject->setFont(mBodyFont); 0815 0816 if (!reload) { 0817 QSize composerSize = KMailSettings::self()->composerSize(); 0818 if (composerSize.width() < 200) { 0819 composerSize.setWidth(200); 0820 } 0821 if (composerSize.height() < 200) { 0822 composerSize.setHeight(200); 0823 } 0824 resize(composerSize); 0825 0826 if (!KMailSettings::self()->snippetSplitterPosition().isEmpty()) { 0827 mSnippetSplitter->setSizes(KMailSettings::self()->snippetSplitterPosition()); 0828 } else { 0829 const QList<int> defaults{(int)(width() * 0.8), (int)(width() * 0.2)}; 0830 mSnippetSplitter->setSizes(defaults); 0831 } 0832 } 0833 0834 mComposerBase->identityCombo()->setCurrentIdentity(mId); 0835 qCDebug(KMAIL_LOG) << mComposerBase->identityCombo()->currentIdentityName(); 0836 const KIdentityManagementCore::Identity &ident = kmkernel->identityManager()->identityForUoid(mId); 0837 0838 mComposerBase->setAutoSaveInterval(KMailSettings::self()->autosaveInterval() * 1000 * 60); 0839 0840 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); 0841 0842 const QString fccName = ident.fcc(); 0843 setFcc(fccName); 0844 } 0845 0846 void KMComposerWin::writeConfig() 0847 { 0848 KMailSettings::self()->setHeaders(mShowHeaders); 0849 KMailSettings::self()->setCurrentTransport(mComposerBase->transportComboBox()->currentText()); 0850 KMailSettings::self()->setPreviousIdentity(currentIdentity()); 0851 KMailSettings::self()->setPreviousFcc(QString::number(mFccFolder->collection().id())); 0852 KMailSettings::self()->setPreviousDictionary(mComposerBase->dictionary()->currentDictionaryName()); 0853 KMailSettings::self()->setAutoSpellChecking(mAutoSpellCheckingAction->isChecked()); 0854 MessageViewer::MessageViewerSettings::self()->setUseFixedFont(mFixedFontAction->isChecked()); 0855 if (!mForceDisableHtml) { 0856 KMailSettings::self()->setUseHtmlMarkup(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); 0857 } 0858 KMailSettings::self()->setComposerSize(size()); 0859 KMailSettings::self()->setShowSnippetManager(mSnippetAction->isChecked()); 0860 0861 if (mSnippetAction->isChecked()) { 0862 KMailSettings::setSnippetSplitterPosition(mSnippetSplitter->sizes()); 0863 } 0864 0865 // make sure config changes are written to disk, cf. bug 127538 0866 KMKernel::self()->slotSyncConfig(); 0867 } 0868 0869 MessageComposer::Composer *KMComposerWin::createSimpleComposer() 0870 { 0871 mComposerBase->setFrom(from()); 0872 mComposerBase->setSubject(subject()); 0873 auto composer = new MessageComposer::Composer(); 0874 mComposerBase->fillComposer(composer); 0875 return composer; 0876 } 0877 0878 bool KMComposerWin::canSignEncryptAttachments() const 0879 { 0880 return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat; 0881 } 0882 0883 void KMComposerWin::slotUpdateView() 0884 { 0885 if (!mDone) { 0886 return; // otherwise called from rethinkFields during the construction 0887 // which is not the intended behavior 0888 } 0889 0890 // This sucks awfully, but no, I cannot get an activated(int id) from 0891 // actionContainer() 0892 auto act = ::qobject_cast<KToggleAction *>(sender()); 0893 if (!act) { 0894 return; 0895 } 0896 int id; 0897 0898 if (act == mAllFieldsAction) { 0899 id = 0; 0900 } else if (act == mIdentityAction) { 0901 id = HDR_IDENTITY; 0902 } else if (act == mTransportAction) { 0903 id = HDR_TRANSPORT; 0904 } else if (act == mFromAction) { 0905 id = HDR_FROM; 0906 } else if (act == mSubjectAction) { 0907 id = HDR_SUBJECT; 0908 } else if (act == mFccAction) { 0909 id = HDR_FCC; 0910 } else if (act == mDictionaryAction) { 0911 id = HDR_DICTIONARY; 0912 } else { 0913 qCDebug(KMAIL_LOG) << "Something is wrong (Oh, yeah?)"; 0914 return; 0915 } 0916 0917 bool forceAllHeaders = false; 0918 // sanders There's a bug here this logic doesn't work if no 0919 // fields are shown and then show all fields is selected. 0920 // Instead of all fields being shown none are. 0921 if (!act->isChecked()) { 0922 // hide header 0923 if (id > 0) { 0924 mShowHeaders = mShowHeaders & ~id; 0925 } else { 0926 mShowHeaders = std::abs(mShowHeaders); 0927 } 0928 } else { 0929 // show header 0930 if (id > 0) { 0931 mShowHeaders |= id; 0932 } else { 0933 mShowHeaders = -std::abs(mShowHeaders); 0934 if (mShowHeaders == 0) { 0935 forceAllHeaders = true; 0936 } 0937 } 0938 } 0939 rethinkFields(true, forceAllHeaders); 0940 } 0941 0942 int KMComposerWin::calcColumnWidth(int which, long allShowing, int width) const 0943 { 0944 if ((allShowing & which) == 0) { 0945 return width; 0946 } 0947 0948 QLabel *w = nullptr; 0949 if (which == HDR_IDENTITY) { 0950 w = mLblIdentity; 0951 } else if (which == HDR_DICTIONARY) { 0952 w = mDictionaryLabel; 0953 } else if (which == HDR_FCC) { 0954 w = mLblFcc; 0955 } else if (which == HDR_TRANSPORT) { 0956 w = mLblTransport; 0957 } else if (which == HDR_FROM) { 0958 w = mLblFrom; 0959 } else if (which == HDR_SUBJECT) { 0960 w = mLblSubject; 0961 } else { 0962 return width; 0963 } 0964 0965 w->setBuddy(mComposerBase->editor()); // set dummy so we don't calculate width of '&' for this label. 0966 w->adjustSize(); 0967 w->show(); 0968 return qMax(width, w->sizeHint().width()); 0969 } 0970 0971 void KMComposerWin::rethinkFields(bool fromSlot, bool forceAllHeaders) 0972 { 0973 // This sucks even more but again no ids. sorry (sven) 0974 int mask; 0975 int row; 0976 long showHeaders; 0977 0978 if ((mShowHeaders < 0) || forceAllHeaders) { 0979 showHeaders = HDR_ALL; 0980 } else { 0981 showHeaders = mShowHeaders; 0982 } 0983 0984 for (mask = 1, mNumHeaders = 0; mask <= showHeaders; mask <<= 1) { 0985 if ((showHeaders & mask) != 0) { 0986 ++mNumHeaders; 0987 } 0988 } 0989 delete mGrid; 0990 mGrid = new QGridLayout(mHeadersArea); 0991 mGrid->setColumnStretch(0, 1); 0992 mGrid->setColumnStretch(1, 100); 0993 mGrid->setRowStretch(mNumHeaders + 1, 100); 0994 0995 row = 0; 0996 0997 mLabelWidth = mComposerBase->recipientsEditor()->setFirstColumnWidth(0) + 2; 0998 if (std::abs(showHeaders) & HDR_IDENTITY) { 0999 mLabelWidth = calcColumnWidth(HDR_IDENTITY, showHeaders, mLabelWidth); 1000 } 1001 if (std::abs(showHeaders) & HDR_DICTIONARY) { 1002 mLabelWidth = calcColumnWidth(HDR_DICTIONARY, showHeaders, mLabelWidth); 1003 } 1004 if (std::abs(showHeaders) & HDR_FCC) { 1005 mLabelWidth = calcColumnWidth(HDR_FCC, showHeaders, mLabelWidth); 1006 } 1007 if (std::abs(showHeaders) & HDR_TRANSPORT) { 1008 mLabelWidth = calcColumnWidth(HDR_TRANSPORT, showHeaders, mLabelWidth); 1009 } 1010 if (std::abs(showHeaders) & HDR_FROM) { 1011 mLabelWidth = calcColumnWidth(HDR_FROM, showHeaders, mLabelWidth); 1012 } 1013 if (std::abs(showHeaders) & HDR_SUBJECT) { 1014 mLabelWidth = calcColumnWidth(HDR_SUBJECT, showHeaders, mLabelWidth); 1015 } 1016 1017 if (!fromSlot) { 1018 mAllFieldsAction->setChecked(showHeaders == HDR_ALL); 1019 } 1020 1021 if (!fromSlot) { 1022 mIdentityAction->setChecked(std::abs(mShowHeaders) & HDR_IDENTITY); 1023 } 1024 rethinkHeaderLine(showHeaders, HDR_IDENTITY, row, mLblIdentity, mComposerBase->identityCombo()); 1025 1026 if (!fromSlot) { 1027 mDictionaryAction->setChecked(std::abs(mShowHeaders) & HDR_DICTIONARY); 1028 } 1029 rethinkHeaderLine(showHeaders, HDR_DICTIONARY, row, mDictionaryLabel, mComposerBase->dictionary()); 1030 1031 if (!fromSlot) { 1032 mFccAction->setChecked(std::abs(mShowHeaders) & HDR_FCC); 1033 } 1034 rethinkHeaderLine(showHeaders, HDR_FCC, row, mLblFcc, mFccFolder); 1035 1036 if (!fromSlot) { 1037 mTransportAction->setChecked(std::abs(mShowHeaders) & HDR_TRANSPORT); 1038 } 1039 rethinkHeaderLine(showHeaders, HDR_TRANSPORT, row, mLblTransport, mComposerBase->transportComboBox()); 1040 1041 if (!fromSlot) { 1042 mFromAction->setChecked(std::abs(mShowHeaders) & HDR_FROM); 1043 } 1044 rethinkHeaderLine(showHeaders, HDR_FROM, row, mLblFrom, mEdtFrom); 1045 1046 mGrid->addWidget(mComposerBase->recipientsEditor(), row, 0, 1, 2); 1047 ++row; 1048 connect(mEdtFrom, &MessageComposer::ComposerLineEdit::focusDown, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusTop); 1049 connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusUp, mEdtFrom, qOverload<>(&QWidget::setFocus)); 1050 1051 connect(mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::focusDown, mEdtSubject, qOverload<>(&QWidget::setFocus)); 1052 connect(mEdtSubject, &PimCommon::SpellCheckLineEdit::focusUp, mComposerBase->recipientsEditor(), &KPIM::MultiplyingLineEditor::setFocusBottom); 1053 1054 if (!fromSlot) { 1055 mSubjectAction->setChecked(std::abs(mShowHeaders) & HDR_SUBJECT); 1056 } 1057 rethinkHeaderLine(showHeaders, HDR_SUBJECT, row, mLblSubject, mEdtSubject); 1058 connectFocusMoving(mEdtSubject, mComposerBase->editor()); 1059 1060 assert(row <= mNumHeaders + 1); 1061 1062 mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height()); 1063 1064 mIdentityAction->setEnabled(!mAllFieldsAction->isChecked()); 1065 mDictionaryAction->setEnabled(!mAllFieldsAction->isChecked()); 1066 mTransportAction->setEnabled(!mAllFieldsAction->isChecked()); 1067 mFromAction->setEnabled(!mAllFieldsAction->isChecked()); 1068 mFccAction->setEnabled(!mAllFieldsAction->isChecked()); 1069 mSubjectAction->setEnabled(!mAllFieldsAction->isChecked()); 1070 mComposerBase->recipientsEditor()->setFirstColumnWidth(mLabelWidth); 1071 } 1072 1073 QWidget *KMComposerWin::connectFocusMoving(QWidget *prev, QWidget *next) 1074 { 1075 connect(prev, SIGNAL(focusDown()), next, SLOT(setFocus())); 1076 connect(next, SIGNAL(focusUp()), prev, SLOT(setFocus())); 1077 1078 return next; 1079 } 1080 1081 void KMComposerWin::rethinkHeaderLine(int aValue, int aMask, int &aRow, QLabel *aLbl, QWidget *aCbx) 1082 { 1083 if (aValue & aMask) { 1084 aLbl->setBuddy(aCbx); 1085 aLbl->setFixedWidth(mLabelWidth); 1086 mGrid->addWidget(aLbl, aRow, 0); 1087 1088 mGrid->addWidget(aCbx, aRow, 1); 1089 aCbx->show(); 1090 aLbl->show(); 1091 aRow++; 1092 } else { 1093 aLbl->hide(); 1094 aCbx->hide(); 1095 } 1096 } 1097 1098 void KMComposerWin::slotUpdateComposer(const KIdentityManagementCore::Identity &ident, const KMime::Message::Ptr &msg, uint uoid, uint uoldId, bool wasModified) 1099 { 1100 mComposerBase->updateTemplate(msg); 1101 updateSignature(uoid, uoldId); 1102 updateComposerAfterIdentityChanged(ident, uoid, wasModified); 1103 } 1104 1105 void KMComposerWin::applyTemplate(uint uoid, uint uOldId, const KIdentityManagementCore::Identity &ident, bool wasModified) 1106 { 1107 TemplateParser::TemplateParserJob::Mode mode; 1108 switch (mContext) { 1109 case New: 1110 mode = TemplateParser::TemplateParserJob::NewMessage; 1111 break; 1112 case Reply: 1113 mode = TemplateParser::TemplateParserJob::Reply; 1114 break; 1115 case ReplyToAll: 1116 mode = TemplateParser::TemplateParserJob::ReplyAll; 1117 break; 1118 case Forward: 1119 mode = TemplateParser::TemplateParserJob::Forward; 1120 break; 1121 case NoTemplate: 1122 updateComposerAfterIdentityChanged(ident, uoid, wasModified); 1123 return; 1124 } 1125 1126 auto header = new KMime::Headers::Generic("X-KMail-Templates"); 1127 header->fromUnicodeString(ident.templates(), "utf-8"); 1128 mMsg->setHeader(header); 1129 1130 if (mode == TemplateParser::TemplateParserJob::NewMessage) { 1131 auto job = new KMComposerUpdateTemplateJob; 1132 connect(job, &KMComposerUpdateTemplateJob::updateComposer, this, &KMComposerWin::slotUpdateComposer); 1133 job->setMsg(mMsg); 1134 job->setCustomTemplate(mCustomTemplate); 1135 job->setTextSelection(mTextSelection); 1136 job->setWasModified(wasModified); 1137 job->setUoldId(uOldId); 1138 job->setUoid(uoid); 1139 job->setIdent(ident); 1140 job->setCollection(mCollectionForNewMessage); 1141 job->start(); 1142 } else { 1143 if (auto hrd = mMsg->headerByType("X-KMail-Link-Message")) { 1144 Akonadi::Item::List items; 1145 const QStringList serNums = hrd->asUnicodeString().split(QLatin1Char(',')); 1146 items.reserve(serNums.count()); 1147 for (const QString &serNumStr : serNums) { 1148 items << Akonadi::Item(serNumStr.toLongLong()); 1149 } 1150 1151 auto job = new Akonadi::ItemFetchJob(items, this); 1152 job->fetchScope().fetchFullPayload(true); 1153 job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); 1154 job->setProperty("mode", static_cast<int>(mode)); 1155 job->setProperty("uoid", uoid); 1156 job->setProperty("uOldid", uOldId); 1157 connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDelayedApplyTemplate); 1158 } 1159 updateComposerAfterIdentityChanged(ident, uoid, wasModified); 1160 } 1161 } 1162 1163 void KMComposerWin::slotDelayedApplyTemplate(KJob *job) 1164 { 1165 const Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job); 1166 // const Akonadi::Item::List items = fetchJob->items(); 1167 1168 // Readd ? const TemplateParser::TemplateParserJob::Mode mode = static_cast<TemplateParser::TemplateParserJob::Mode>(fetchJob->property("mode").toInt()); 1169 const uint uoid = fetchJob->property("uoid").toUInt(); 1170 const uint uOldId = fetchJob->property("uOldid").toUInt(); 1171 #if 0 // FIXME template 1172 TemplateParser::TemplateParser parser(mMsg, mode); 1173 parser.setSelection(mTextSelection); 1174 parser.setAllowDecryption(true); 1175 parser.setWordWrap(MessageComposer::MessageComposerSettings::self()->wordWrap(), MessageComposer::MessageComposerSettings::self()->lineWrapWidth()); 1176 parser.setIdentityManager(KMKernel::self()->identityManager()); 1177 for (const Akonadi::Item &item : items) { 1178 if (!mCustomTemplate.isEmpty()) { 1179 parser.process(mCustomTemplate, MessageCore::Util::message(item)); 1180 } else { 1181 parser.processWithIdentity(uoid, MessageCore::Util::message(item)); 1182 } 1183 } 1184 #else 1185 mComposerBase->updateTemplate(mMsg); 1186 updateSignature(uoid, uOldId); 1187 qCWarning(KMAIL_LOG) << " void KMComposerWin::slotDelayedApplyTemplate(KJob *job) is not implemented after removing qtwebkit"; 1188 #endif 1189 } 1190 1191 void KMComposerWin::updateSignature(uint uoid, uint uOldId) 1192 { 1193 const KIdentityManagementCore::Identity &ident = kmkernel->identityManager()->identityForUoid(uoid); 1194 const KIdentityManagementCore::Identity &oldIdentity = kmkernel->identityManager()->identityForUoid(uOldId); 1195 mComposerBase->identityChanged(ident, oldIdentity, true); 1196 } 1197 1198 void KMComposerWin::setCollectionForNewMessage(const Akonadi::Collection &folder) 1199 { 1200 mCollectionForNewMessage = folder; 1201 } 1202 1203 void KMComposerWin::setQuotePrefix(uint uoid) 1204 { 1205 QString quotePrefix; 1206 if (auto hrd = mMsg->headerByType("X-KMail-QuotePrefix")) { 1207 quotePrefix = hrd->asUnicodeString(); 1208 } 1209 if (quotePrefix.isEmpty()) { 1210 // no quote prefix header, set quote prefix according in identity 1211 // TODO port templates to ComposerViewBase 1212 1213 if (mCustomTemplate.isEmpty()) { 1214 const KIdentityManagementCore::Identity &identity = kmkernel->identityManager()->identityForUoidOrDefault(uoid); 1215 // Get quote prefix from template 1216 // ( custom templates don't specify custom quotes prefixes ) 1217 TemplateParser::Templates quoteTemplate(TemplateParser::TemplatesConfiguration::configIdString(identity.uoid())); 1218 quotePrefix = quoteTemplate.quoteString(); 1219 } 1220 } 1221 mComposerBase->editor()->setQuotePrefixName(MessageCore::StringUtil::formatQuotePrefix(quotePrefix, mMsg->from()->displayString())); 1222 } 1223 1224 void KMComposerWin::setupActions() 1225 { 1226 KActionMenuTransport *actActionNowMenu = nullptr; 1227 KActionMenuTransport *actActionLaterMenu = nullptr; 1228 1229 if (MessageComposer::MessageComposerSettings::self()->sendImmediate()) { 1230 // default = send now, alternative = queue 1231 auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this); 1232 actionCollection()->addAction(QStringLiteral("send_default"), action); 1233 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return)); 1234 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNowByShortcut); 1235 1236 actActionNowMenu = new KActionMenuTransport(this); 1237 actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); 1238 actActionNowMenu->setText(i18n("&Send Mail Via")); 1239 1240 actActionNowMenu->setIconText(i18n("Send")); 1241 actionCollection()->addAction(QStringLiteral("send_default_via"), actActionNowMenu); 1242 1243 action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this); 1244 actionCollection()->addAction(QStringLiteral("send_alternative"), action); 1245 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater); 1246 1247 actActionLaterMenu = new KActionMenuTransport(this); 1248 actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue"))); 1249 actActionLaterMenu->setText(i18n("Send &Later Via")); 1250 1251 actActionLaterMenu->setIconText(i18nc("Queue the message for sending at a later date", "Queue")); 1252 actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionLaterMenu); 1253 } else { 1254 // default = queue, alternative = send now 1255 auto action = new QAction(QIcon::fromTheme(QStringLiteral("mail-queue")), i18n("Send &Later"), this); 1256 actionCollection()->addAction(QStringLiteral("send_default"), action); 1257 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendLater); 1258 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Return)); 1259 1260 actActionLaterMenu = new KActionMenuTransport(this); 1261 actActionLaterMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-queue"))); 1262 actActionLaterMenu->setText(i18n("Send &Later Via")); 1263 actionCollection()->addAction(QStringLiteral("send_default_via"), actActionLaterMenu); 1264 1265 action = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Mail"), this); 1266 actionCollection()->addAction(QStringLiteral("send_alternative"), action); 1267 connect(action, &QAction::triggered, this, &KMComposerWin::slotSendNow); 1268 1269 actActionNowMenu = new KActionMenuTransport(this); 1270 actActionNowMenu->setIcon(QIcon::fromTheme(QStringLiteral("mail-send"))); 1271 actActionNowMenu->setText(i18n("&Send Mail Via")); 1272 actionCollection()->addAction(QStringLiteral("send_alternative_via"), actActionNowMenu); 1273 } 1274 1275 connect(actActionNowMenu, &QAction::triggered, this, &KMComposerWin::slotSendNow); 1276 connect(actActionLaterMenu, &QAction::triggered, this, &KMComposerWin::slotSendLater); 1277 connect(actActionNowMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendNowVia); 1278 connect(actActionLaterMenu, &KActionMenuTransport::transportSelected, this, &KMComposerWin::slotSendLaterVia); 1279 1280 auto action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Draft"), this); 1281 actionCollection()->addAction(QStringLiteral("save_in_drafts"), action); 1282 KMail::Util::addQActionHelpText(action, i18n("Save email in Draft folder")); 1283 actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_S)); 1284 connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveDraft); 1285 1286 action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &Template"), this); 1287 KMail::Util::addQActionHelpText(action, i18n("Save email in Template folder")); 1288 actionCollection()->addAction(QStringLiteral("save_in_templates"), action); 1289 connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveTemplate); 1290 1291 action = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save as &File"), this); 1292 KMail::Util::addQActionHelpText(action, i18n("Save email as text or html file")); 1293 actionCollection()->addAction(QStringLiteral("save_as_file"), action); 1294 connect(action, &QAction::triggered, this, &KMComposerWin::slotSaveAsFile); 1295 1296 action = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Text File..."), this); 1297 actionCollection()->addAction(QStringLiteral("insert_file"), action); 1298 connect(action, &QAction::triggered, this, &KMComposerWin::slotInsertFile); 1299 1300 mRecentAction = new KRecentFilesAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("&Insert Recent Text File"), this); 1301 actionCollection()->addAction(QStringLiteral("insert_file_recent"), mRecentAction); 1302 connect(mRecentAction, &KRecentFilesAction::urlSelected, this, &KMComposerWin::slotInsertRecentFile); 1303 connect(mRecentAction, &KRecentFilesAction::recentListCleared, this, &KMComposerWin::slotRecentListFileClear); 1304 1305 const QStringList urls = KMailSettings::self()->recentUrls(); 1306 for (const QString &url : urls) { 1307 mRecentAction->addUrl(QUrl(url)); 1308 } 1309 1310 action = new QAction(QIcon::fromTheme(QStringLiteral("x-office-address-book")), i18n("&Address Book"), this); 1311 KMail::Util::addQActionHelpText(action, i18n("Open Address Book")); 1312 actionCollection()->addAction(QStringLiteral("addressbook"), action); 1313 if (QStandardPaths::findExecutable(QStringLiteral("kaddressbook")).isEmpty()) { 1314 action->setEnabled(false); 1315 } else { 1316 connect(action, &QAction::triggered, this, &KMComposerWin::slotAddressBook); 1317 } 1318 action = new QAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("&New Composer"), this); 1319 actionCollection()->addAction(QStringLiteral("new_composer"), action); 1320 1321 connect(action, &QAction::triggered, this, &KMComposerWin::slotNewComposer); 1322 actionCollection()->setDefaultShortcuts(action, KStandardShortcut::shortcut(KStandardShortcut::New)); 1323 1324 action = new QAction(i18n("Select &Recipients..."), this); 1325 actionCollection()->addAction(QStringLiteral("select_recipients"), action); 1326 connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::selectRecipients); 1327 action = new QAction(i18n("Save &Distribution List..."), this); 1328 actionCollection()->addAction(QStringLiteral("save_distribution_list"), action); 1329 connect(action, &QAction::triggered, mComposerBase->recipientsEditor(), &MessageComposer::RecipientsEditor::saveDistributionList); 1330 1331 KStandardAction::print(this, &KMComposerWin::slotPrint, actionCollection()); 1332 KStandardAction::printPreview(this, &KMComposerWin::slotPrintPreview, actionCollection()); 1333 KStandardAction::close(this, &KMComposerWin::slotClose, actionCollection()); 1334 1335 KStandardAction::undo(mGlobalAction, &KMComposerGlobalAction::slotUndo, actionCollection()); 1336 KStandardAction::redo(mGlobalAction, &KMComposerGlobalAction::slotRedo, actionCollection()); 1337 KStandardAction::cut(mGlobalAction, &KMComposerGlobalAction::slotCut, actionCollection()); 1338 KStandardAction::copy(mGlobalAction, &KMComposerGlobalAction::slotCopy, actionCollection()); 1339 KStandardAction::paste(mGlobalAction, &KMComposerGlobalAction::slotPaste, actionCollection()); 1340 mSelectAll = KStandardAction::selectAll(mGlobalAction, &KMComposerGlobalAction::slotMarkAll, actionCollection()); 1341 1342 mFindText = KStandardAction::find(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFind, actionCollection()); 1343 mFindNextText = KStandardAction::findNext(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotFindNext, actionCollection()); 1344 1345 mReplaceText = KStandardAction::replace(mRichTextEditorWidget, &TextCustomEditor::RichTextEditorWidget::slotReplace, actionCollection()); 1346 actionCollection()->addAction(KStandardAction::Spelling, QStringLiteral("spellcheck"), mComposerBase->editor(), SLOT(slotCheckSpelling())); 1347 1348 action = new QAction(i18n("Paste as Attac&hment"), this); 1349 actionCollection()->addAction(QStringLiteral("paste_att"), action); 1350 connect(action, &QAction::triggered, this, &KMComposerWin::slotPasteAsAttachment); 1351 1352 action = new QAction(i18n("Cl&ean Spaces"), this); 1353 actionCollection()->addAction(QStringLiteral("clean_spaces"), action); 1354 connect(action, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::cleanSpace); 1355 1356 mFixedFontAction = new KToggleAction(i18n("Use Fi&xed Font"), this); 1357 actionCollection()->addAction(QStringLiteral("toggle_fixedfont"), mFixedFontAction); 1358 connect(mFixedFontAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateFont); 1359 mFixedFontAction->setChecked(MessageViewer::MessageViewerSettings::self()->useFixedFont()); 1360 1361 // these are checkable!!! 1362 mUrgentAction = new KToggleAction(i18nc("@action:inmenu Mark the email as urgent.", "&Urgent"), this); 1363 actionCollection()->addAction(QStringLiteral("urgent"), mUrgentAction); 1364 mRequestMDNAction = new KToggleAction(i18n("&Request Disposition Notification"), this); 1365 actionCollection()->addAction(QStringLiteral("options_request_mdn"), mRequestMDNAction); 1366 mRequestMDNAction->setChecked(KMailSettings::self()->requestMDN()); 1367 1368 mRequestDeliveryConfirmation = new KToggleAction(i18n("&Request Delivery Confirmation"), this); 1369 actionCollection()->addAction(QStringLiteral("options_request_delivery_confirmation"), mRequestDeliveryConfirmation); 1370 // TOOD mRequestDeliveryConfirmation->setChecked(KMailSettings::self()->requestMDN()); 1371 1372 //----- Message-Encoding Submenu 1373 mWordWrapAction = new KToggleAction(i18n("&Wordwrap"), this); 1374 actionCollection()->addAction(QStringLiteral("wordwrap"), mWordWrapAction); 1375 mWordWrapAction->setChecked(MessageComposer::MessageComposerSettings::self()->wordWrap()); 1376 connect(mWordWrapAction, &KToggleAction::toggled, this, &KMComposerWin::slotWordWrapToggled); 1377 1378 mSnippetAction = new KToggleAction(i18n("&Snippets"), this); 1379 actionCollection()->addAction(QStringLiteral("snippets"), mSnippetAction); 1380 connect(mSnippetAction, &KToggleAction::toggled, this, &KMComposerWin::slotSnippetWidgetVisibilityChanged); 1381 mSnippetAction->setChecked(KMailSettings::self()->showSnippetManager()); 1382 1383 mAutoSpellCheckingAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Automatic Spellchecking"), this); 1384 actionCollection()->addAction(QStringLiteral("options_auto_spellchecking"), mAutoSpellCheckingAction); 1385 const bool spellChecking = KMailSettings::self()->autoSpellChecking(); 1386 const bool useKmailEditor = !KMailSettings::self()->useExternalEditor(); 1387 const bool spellCheckingEnabled = useKmailEditor && spellChecking; 1388 mAutoSpellCheckingAction->setEnabled(useKmailEditor); 1389 1390 mAutoSpellCheckingAction->setChecked(spellCheckingEnabled); 1391 slotAutoSpellCheckingToggled(spellCheckingEnabled); 1392 connect(mAutoSpellCheckingAction, &KToggleAction::toggled, this, &KMComposerWin::slotAutoSpellCheckingToggled); 1393 connect(mComposerBase->editor(), &TextCustomEditor::RichTextEditor::checkSpellingChanged, this, &KMComposerWin::slotAutoSpellCheckingToggled); 1394 1395 connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::textModeChanged, this, &KMComposerWin::slotTextModeChanged); 1396 connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorClosed, this, &KMComposerWin::slotExternalEditorClosed); 1397 connect(mComposerBase->editor(), &MessageComposer::RichTextComposerNg::externalEditorStarted, this, &KMComposerWin::slotExternalEditorStarted); 1398 // these are checkable!!! 1399 mMarkupAction = new KToggleAction(i18n("Rich Text Editing"), this); 1400 mMarkupAction->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); 1401 mMarkupAction->setIconText(i18n("Rich Text")); 1402 mMarkupAction->setToolTip(i18n("Toggle rich text editing mode")); 1403 actionCollection()->addAction(QStringLiteral("html"), mMarkupAction); 1404 connect(mMarkupAction, &KToggleAction::triggered, this, &KMComposerWin::slotToggleMarkup); 1405 1406 mAllFieldsAction = new KToggleAction(i18n("&All Fields"), this); 1407 actionCollection()->addAction(QStringLiteral("show_all_fields"), mAllFieldsAction); 1408 connect(mAllFieldsAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); 1409 mIdentityAction = new KToggleAction(i18n("&Identity"), this); 1410 actionCollection()->addAction(QStringLiteral("show_identity"), mIdentityAction); 1411 connect(mIdentityAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); 1412 mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this); 1413 actionCollection()->addAction(QStringLiteral("show_dictionary"), mDictionaryAction); 1414 connect(mDictionaryAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); 1415 mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this); 1416 actionCollection()->addAction(QStringLiteral("show_fcc"), mFccAction); 1417 connect(mFccAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); 1418 mTransportAction = new KToggleAction(i18n("&Mail Transport"), this); 1419 actionCollection()->addAction(QStringLiteral("show_transport"), mTransportAction); 1420 connect(mTransportAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); 1421 mFromAction = new KToggleAction(i18n("&From"), this); 1422 actionCollection()->addAction(QStringLiteral("show_from"), mFromAction); 1423 connect(mFromAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); 1424 mSubjectAction = new KToggleAction(i18nc("@action:inmenu Show the subject in the composer window.", "S&ubject"), this); 1425 actionCollection()->addAction(QStringLiteral("show_subject"), mSubjectAction); 1426 connect(mSubjectAction, &KToggleAction::triggered, this, &KMComposerWin::slotUpdateView); 1427 // end of checkable 1428 1429 mAppendSignature = new QAction(i18n("Append S&ignature"), this); 1430 actionCollection()->addAction(QStringLiteral("append_signature"), mAppendSignature); 1431 connect(mAppendSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature); 1432 1433 mPrependSignature = new QAction(i18n("Pr&epend Signature"), this); 1434 actionCollection()->addAction(QStringLiteral("prepend_signature"), mPrependSignature); 1435 connect(mPrependSignature, &QAction::triggered, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature); 1436 1437 mInsertSignatureAtCursorPosition = new QAction(i18n("Insert Signature At C&ursor Position"), this); 1438 actionCollection()->addAction(QStringLiteral("insert_signature_at_cursor_position"), mInsertSignatureAtCursorPosition); 1439 connect(mInsertSignatureAtCursorPosition, 1440 &QAction::triggered, 1441 mComposerBase->signatureController(), 1442 &MessageComposer::SignatureController::insertSignatureAtCursor); 1443 1444 mComposerBase->attachmentController()->createActions(); 1445 1446 setStandardToolBarMenuEnabled(true); 1447 1448 KStandardAction::keyBindings(this, &KMComposerWin::slotEditKeys, actionCollection()); 1449 KStandardAction::configureToolbars(this, &KMComposerWin::slotEditToolbars, actionCollection()); 1450 KStandardAction::preferences(kmkernel, &KMKernel::slotShowConfigurationDialog, actionCollection()); 1451 1452 action = new QAction(QIcon::fromTheme(QStringLiteral("tools-check-spelling")), i18n("&Spellchecker..."), this); 1453 action->setIconText(i18n("Spellchecker")); 1454 actionCollection()->addAction(QStringLiteral("setup_spellchecker"), action); 1455 connect(action, &QAction::triggered, this, &KMComposerWin::slotSpellcheckConfig); 1456 1457 mEncryptAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-encrypt")), i18n("&Encrypt Message"), this); 1458 mEncryptAction->setIconText(i18n("Encrypt")); 1459 actionCollection()->addAction(QStringLiteral("encrypt_message"), mEncryptAction); 1460 mSignAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("document-sign")), i18n("&Sign Message"), this); 1461 mSignAction->setIconText(i18n("Sign")); 1462 actionCollection()->addAction(QStringLiteral("sign_message"), mSignAction); 1463 const auto ident = identity(); 1464 // PENDING(marc): check the uses of this member and split it into 1465 // smime/openpgp and or enc/sign, if necessary: 1466 mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); 1467 mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty(); 1468 1469 mLastEncryptActionState = false; 1470 mLastSignActionState = pgpAutoSign(); 1471 1472 changeCryptoAction(); 1473 1474 connect(&mEncryptionState, &EncryptionState::possibleEncryptChanged, mEncryptAction, &KToggleAction::setEnabled); 1475 connect(&mEncryptionState, &EncryptionState::encryptChanged, mEncryptAction, &KToggleAction::setChecked); 1476 connect(mEncryptAction, &KToggleAction::triggered, &mEncryptionState, &EncryptionState::toggleOverride); 1477 connect(mSignAction, &KToggleAction::triggered, this, &KMComposerWin::slotSignToggled); 1478 1479 QStringList listCryptoFormat; 1480 listCryptoFormat.reserve(numCryptoMessageFormats); 1481 for (int i = 0; i < numCryptoMessageFormats; ++i) { 1482 listCryptoFormat.push_back(Kleo::cryptoMessageFormatToLabel(cryptoMessageFormats[i])); 1483 } 1484 1485 mCryptoModuleAction = new KSelectAction(i18n("&Cryptographic Message Format"), this); 1486 actionCollection()->addAction(QStringLiteral("options_select_crypto"), mCryptoModuleAction); 1487 connect(mCryptoModuleAction, &KSelectAction::indexTriggered, this, &KMComposerWin::slotCryptoModuleSelected); 1488 mCryptoModuleAction->setToolTip(i18n("Select a cryptographic format for this message")); 1489 mCryptoModuleAction->setItems(listCryptoFormat); 1490 1491 mComposerBase->editor()->createActions(actionCollection()); 1492 1493 mFollowUpToggleAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("Create Follow Up Reminder..."), this); 1494 actionCollection()->addAction(QStringLiteral("follow_up_mail"), mFollowUpToggleAction); 1495 connect(mFollowUpToggleAction, &KToggleAction::triggered, this, &KMComposerWin::slotFollowUpMail); 1496 mFollowUpToggleAction->setEnabled(MessageComposer::FollowUpReminder::isAvailableAndEnabled()); 1497 1498 mPluginEditorManagerInterface->initializePlugins(); 1499 mPluginEditorCheckBeforeSendManagerInterface->initializePlugins(); 1500 mPluginEditorInitManagerInterface->initializePlugins(); 1501 mPluginEditorConvertTextManagerInterface->initializePlugins(); 1502 mPluginEditorGrammarManagerInterface->initializePlugins(); 1503 1504 mShowMenuBarAction = KStandardAction::showMenubar(this, &KMComposerWin::slotToggleMenubar, actionCollection()); 1505 mShowMenuBarAction->setChecked(KMailSettings::self()->composerShowMenuBar()); 1506 slotToggleMenubar(true); 1507 1508 mHamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection()); 1509 mHamburgerMenu->setShowMenuBarAction(mShowMenuBarAction); 1510 mHamburgerMenu->setMenuBar(menuBar()); 1511 connect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, [this]() { 1512 updateHamburgerMenu(); 1513 // Immediately disconnect. We only need to run this once, but on demand. 1514 // NOTE: The nullptr at the end disconnects all connections between 1515 // q and mHamburgerMenu's aboutToShowMenu signal. 1516 disconnect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, nullptr); 1517 }); 1518 1519 createGUI(QStringLiteral("kmcomposerui.rc")); 1520 initializePluginActions(); 1521 connect(toolBar(QStringLiteral("htmlToolBar"))->toggleViewAction(), &QAction::toggled, this, &KMComposerWin::htmlToolBarVisibilityChanged); 1522 1523 // In Kontact, this entry would read "Configure Kontact", but bring 1524 // up KMail's config dialog. That's sensible, though, so fix the label. 1525 QAction *configureAction = actionCollection()->action(QStringLiteral("options_configure")); 1526 if (configureAction) { 1527 configureAction->setText(i18n("Configure KMail...")); 1528 } 1529 } 1530 1531 void KMComposerWin::updateHamburgerMenu() 1532 { 1533 auto menu = new QMenu(this); 1534 menu->addAction(actionCollection()->action(QStringLiteral("new_composer"))); 1535 menu->addSeparator(); 1536 menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Undo))); 1537 menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Redo))); 1538 menu->addSeparator(); 1539 menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Print))); 1540 menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::PrintPreview))); 1541 menu->addSeparator(); 1542 menu->addAction(actionCollection()->action(QStringLiteral("attach_menu"))); 1543 menu->addSeparator(); 1544 menu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Close))); 1545 mHamburgerMenu->setMenu(menu); 1546 } 1547 1548 void KMComposerWin::slotToggleMenubar(bool dontShowWarning) 1549 { 1550 if (menuBar()) { 1551 if (mShowMenuBarAction->isChecked()) { 1552 menuBar()->show(); 1553 } else { 1554 if (!dontShowWarning && (!toolBar()->isVisible() || !toolBar()->actions().contains(mHamburgerMenu))) { 1555 const QString accel = mShowMenuBarAction->shortcut().toString(); 1556 KMessageBox::information(this, 1557 i18n("<qt>This will hide the menu bar completely." 1558 " You can show it again by typing %1.</qt>", 1559 accel), 1560 i18nc("@title:window", "Hide menu bar"), 1561 QStringLiteral("HideMenuBarWarning")); 1562 } 1563 menuBar()->hide(); 1564 } 1565 KMailSettings::self()->setComposerShowMenuBar(mShowMenuBarAction->isChecked()); 1566 } 1567 } 1568 1569 void KMComposerWin::initializePluginActions() 1570 { 1571 if (guiFactory()) { 1572 QHash<QString, QList<QAction *>> hashActions; 1573 QHashIterator<MessageComposer::PluginActionType::Type, QList<QAction *>> localEditorManagerActionsType(mPluginEditorManagerInterface->actionsType()); 1574 while (localEditorManagerActionsType.hasNext()) { 1575 localEditorManagerActionsType.next(); 1576 QList<QAction *> lst = localEditorManagerActionsType.value(); 1577 if (!lst.isEmpty()) { 1578 const QString actionlistname = 1579 QLatin1StringView("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorManagerActionsType.key()); 1580 hashActions.insert(actionlistname, lst); 1581 } 1582 } 1583 QHashIterator<MessageComposer::PluginActionType::Type, QList<QAction *>> localEditorConvertTextManagerActionsType( 1584 mPluginEditorConvertTextManagerInterface->actionsType()); 1585 while (localEditorConvertTextManagerActionsType.hasNext()) { 1586 localEditorConvertTextManagerActionsType.next(); 1587 QList<QAction *> lst = localEditorConvertTextManagerActionsType.value(); 1588 if (!lst.isEmpty()) { 1589 const QString actionlistname = 1590 QLatin1StringView("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(localEditorConvertTextManagerActionsType.key()); 1591 if (hashActions.contains(actionlistname)) { 1592 lst = hashActions.value(actionlistname) + lst; 1593 hashActions.remove(actionlistname); 1594 } 1595 hashActions.insert(actionlistname, lst); 1596 } 1597 } 1598 1599 const QList<KToggleAction *> customToolsWidgetActionList = mCustomToolsWidget->actionList(); 1600 const QString actionlistname = 1601 QLatin1StringView("kmaileditor") + MessageComposer::PluginActionType::actionXmlExtension(MessageComposer::PluginActionType::Tools); 1602 for (KToggleAction *act : customToolsWidgetActionList) { 1603 QList<QAction *> lst{act}; 1604 if (hashActions.contains(actionlistname)) { 1605 lst = hashActions.value(actionlistname) + lst; 1606 hashActions.remove(actionlistname); 1607 } 1608 hashActions.insert(actionlistname, lst); 1609 } 1610 1611 QHash<QString, QList<QAction *>>::const_iterator i = hashActions.constBegin(); 1612 1613 while (i != hashActions.constEnd()) { 1614 const auto lst = guiFactory()->clients(); 1615 for (KXMLGUIClient *client : lst) { 1616 client->unplugActionList(i.key()); 1617 client->plugActionList(i.key(), i.value()); 1618 } 1619 ++i; 1620 } 1621 // Initialize statusbar 1622 const QList<QWidget *> statusbarWidgetList = mPluginEditorManagerInterface->statusBarWidgetList(); 1623 for (int index = 0; index < statusbarWidgetList.count(); ++index) { 1624 statusBar()->addPermanentWidget(statusbarWidgetList.at(index), 0); 1625 } 1626 const QList<QWidget *> statusbarWidgetListConverter = mPluginEditorConvertTextManagerInterface->statusBarWidgetList(); 1627 for (int index = 0; index < statusbarWidgetListConverter.count(); ++index) { 1628 statusBar()->addPermanentWidget(statusbarWidgetListConverter.at(index), 0); 1629 } 1630 } 1631 } 1632 1633 void KMComposerWin::changeCryptoAction() 1634 { 1635 const auto ident = identity(); 1636 1637 if (!QGpgME::openpgp() && !QGpgME::smime()) { 1638 // no crypto whatsoever 1639 mEncryptAction->setEnabled(false); 1640 mEncryptionState.setPossibleEncrypt(false); 1641 mSignAction->setEnabled(false); 1642 setSigning(false); 1643 } else { 1644 const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty(); 1645 const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty(); 1646 1647 setSigning((canOpenPGPSign || canSMIMESign) && ident.pgpAutoSign()); 1648 } 1649 } 1650 1651 void KMComposerWin::setupStatusBar(QWidget *w) 1652 { 1653 statusBar()->addWidget(w); 1654 mStatusbarLabel = new QLabel(this); 1655 mStatusbarLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); 1656 statusBar()->addPermanentWidget(mStatusbarLabel); 1657 1658 mCursorLineLabel = new QLabel(this); 1659 mCursorLineLabel->setTextFormat(Qt::PlainText); 1660 mCursorLineLabel->setText(i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", QStringLiteral(" "))); 1661 statusBar()->addPermanentWidget(mCursorLineLabel); 1662 1663 mCursorColumnLabel = new QLabel(i18n(" Column: %1 ", QStringLiteral(" "))); 1664 mCursorColumnLabel->setTextFormat(Qt::PlainText); 1665 statusBar()->addPermanentWidget(mCursorColumnLabel); 1666 1667 mStatusBarLabelToggledOverrideMode = new MessageComposer::StatusBarLabelToggledState(this); 1668 mStatusBarLabelToggledOverrideMode->setStateString(i18n("OVR"), i18n("INS")); 1669 statusBar()->addPermanentWidget(mStatusBarLabelToggledOverrideMode, 0); 1670 connect(mStatusBarLabelToggledOverrideMode, 1671 &MessageComposer::StatusBarLabelToggledState::toggleModeChanged, 1672 this, 1673 &KMComposerWin::slotOverwriteModeWasChanged); 1674 1675 mStatusBarLabelSpellCheckingChangeMode = new MessageComposer::StatusBarLabelToggledState(this); 1676 mStatusBarLabelSpellCheckingChangeMode->setStateString(i18n("Spellcheck: on"), i18n("Spellcheck: off")); 1677 statusBar()->addPermanentWidget(mStatusBarLabelSpellCheckingChangeMode, 0); 1678 connect(mStatusBarLabelSpellCheckingChangeMode, 1679 &MessageComposer::StatusBarLabelToggledState::toggleModeChanged, 1680 this, 1681 &KMComposerWin::slotAutoSpellCheckingToggled); 1682 } 1683 1684 void KMComposerWin::setupEditor() 1685 { 1686 QFontMetrics fm(mBodyFont); 1687 mComposerBase->editor()->setTabStopDistance(fm.boundingRect(QLatin1Char(' ')).width() * 8); 1688 1689 slotWordWrapToggled(MessageComposer::MessageComposerSettings::self()->wordWrap()); 1690 1691 // Font setup 1692 slotUpdateFont(); 1693 1694 connect(mComposerBase->editor(), &QTextEdit::cursorPositionChanged, this, &KMComposerWin::slotCursorPositionChanged); 1695 slotCursorPositionChanged(); 1696 } 1697 1698 QString KMComposerWin::subject() const 1699 { 1700 return MessageComposer::Util::cleanedUpHeaderString(mEdtSubject->toPlainText()); 1701 } 1702 1703 QString KMComposerWin::from() const 1704 { 1705 return MessageComposer::Util::cleanedUpHeaderString(mEdtFrom->text()); 1706 } 1707 1708 void KMComposerWin::slotInvalidIdentity() 1709 { 1710 mIncorrectIdentityFolderWarning->identityInvalid(); 1711 } 1712 1713 void KMComposerWin::slotFccIsInvalid() 1714 { 1715 mIncorrectIdentityFolderWarning->fccIsInvalid(); 1716 } 1717 1718 void KMComposerWin::setCurrentTransport(int transportId) 1719 { 1720 if (!mComposerBase->transportComboBox()->setCurrentTransport(transportId)) { 1721 mIncorrectIdentityFolderWarning->mailTransportIsInvalid(); 1722 } 1723 } 1724 1725 uint KMComposerWin::currentIdentity() const 1726 { 1727 return mComposerBase->identityCombo()->currentIdentity(); 1728 } 1729 1730 void KMComposerWin::addFaceHeaders(const KIdentityManagementCore::Identity &ident, const KMime::Message::Ptr &msg) 1731 { 1732 if (!ident.isXFaceEnabled() || ident.xface().isEmpty()) { 1733 msg->removeHeader("X-Face"); 1734 } else { 1735 QString xface = ident.xface(); 1736 if (!xface.isEmpty()) { 1737 int numNL = (xface.length() - 1) / 70; 1738 for (int i = numNL; i > 0; --i) { 1739 xface.insert(i * 70, QStringLiteral("\n\t")); 1740 } 1741 auto header = new KMime::Headers::Generic("X-Face"); 1742 header->fromUnicodeString(xface, "utf-8"); 1743 msg->setHeader(header); 1744 } 1745 } 1746 1747 if (!ident.isFaceEnabled() || ident.face().isEmpty()) { 1748 msg->removeHeader("Face"); 1749 } else { 1750 QString face = ident.face(); 1751 if (!face.isEmpty()) { 1752 // The first line of data is 72 lines long to account for the 1753 // header name, the following lines are 76 lines long, like in 1754 // https://quimby.gnus.org/circus/face/ 1755 if (face.length() > 72) { 1756 int numNL = (face.length() - 73) / 76; 1757 1758 for (int i = numNL; i > 0; --i) { 1759 face.insert(72 + i * 76, QStringLiteral("\n\t")); 1760 } 1761 1762 face.insert(72, QStringLiteral("\n\t")); 1763 } 1764 1765 auto header = new KMime::Headers::Generic("Face"); 1766 header->fromUnicodeString(face, "utf-8"); 1767 msg->setHeader(header); 1768 } 1769 } 1770 } 1771 1772 void KMComposerWin::setMessage(const KMime::Message::Ptr &newMsg, 1773 bool lastSignState, 1774 bool lastEncryptState, 1775 bool mayAutoSign, 1776 bool allowDecryption, 1777 bool isModified) 1778 { 1779 if (!newMsg) { 1780 qCDebug(KMAIL_LOG) << "newMsg == 0!"; 1781 return; 1782 } 1783 1784 if (lastSignState) { 1785 mLastSignActionState = true; 1786 } 1787 1788 if (lastEncryptState) { 1789 mLastEncryptActionState = true; 1790 } 1791 1792 const auto im = KMKernel::self()->identityManager(); 1793 1794 if (auto hrd = newMsg->headerByType("X-KMail-Identity")) { 1795 const QString identityStr = hrd->asUnicodeString(); 1796 if (!identityStr.isEmpty()) { 1797 const auto ident = im->identityForUoid(identityStr.toUInt()); 1798 if (ident.isNull()) { 1799 if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) { 1800 const QString identityStrName = hrd->asUnicodeString(); 1801 const auto id = im->modifyIdentityForName(identityStrName); 1802 if (!id.isNull()) { 1803 mId = id.uoid(); 1804 } else { 1805 mId = 0; 1806 } 1807 } else { 1808 mId = 0; 1809 } 1810 } else { 1811 mId = identityStr.toUInt(); 1812 } 1813 } 1814 } else { 1815 if (auto hrd = newMsg->headerByType("X-KMail-Identity-Name")) { 1816 const QString identityStrName = hrd->asUnicodeString(); 1817 const auto id = im->modifyIdentityForName(identityStrName); 1818 if (!id.isNull()) { 1819 mId = id.uoid(); 1820 } else { 1821 mId = 0; 1822 } 1823 } else { 1824 mId = 0; 1825 } 1826 } 1827 1828 // don't overwrite the header values with identity specific values 1829 disconnect(mIdentityConnection); 1830 1831 // load the mId into the gui, sticky or not, without emitting 1832 mComposerBase->identityCombo()->setCurrentIdentity(mId); 1833 mIdentityConnection = connect(mComposerBase->identityCombo(), &KIdentityManagementWidgets::IdentityCombo::identityChanged, this, [this](uint val) { 1834 slotIdentityChanged(val); 1835 }); 1836 1837 mMsg = newMsg; 1838 1839 // manually load the identity's value into the fields; either the one from the 1840 // message, where appropriate, or the one from the sticky identity. What's in 1841 // mId might have changed meanwhile, thus the save value 1842 if (mId == 0) { 1843 mId = mComposerBase->identityCombo()->currentIdentity(); 1844 } 1845 slotIdentityChanged(mId, true /*initalChange*/); 1846 // Fixing the identities with auto signing activated 1847 mLastSignActionState = sign(); 1848 1849 // Add initial data. 1850 MessageComposer::PluginEditorConverterInitialData data; 1851 data.setMewMsg(mMsg); 1852 data.setNewMessage(mContext == TemplateContext::New); 1853 mPluginEditorConvertTextManagerInterface->setInitialData(data); 1854 1855 if (auto msgFrom = mMsg->from(false)) { 1856 mEdtFrom->setText(msgFrom->asUnicodeString()); 1857 } 1858 if (auto msgSubject = mMsg->subject(false)) { 1859 mEdtSubject->setPlainText(msgSubject->asUnicodeString()); 1860 } 1861 1862 // Restore the quote prefix. We can't just use the global quote prefix here, 1863 // since the prefix is different for each message, it might for example depend 1864 // on the original sender in a reply. 1865 if (auto hdr = mMsg->headerByType("X-KMail-QuotePrefix")) { 1866 mComposerBase->editor()->setQuotePrefixName(hdr->asUnicodeString()); 1867 } 1868 1869 // check for the presence of a DNT header, indicating that MDN's were requested 1870 if (auto hdr = newMsg->headerByType("Disposition-Notification-To")) { 1871 const QString mdnAddr = hdr->asUnicodeString(); 1872 mRequestMDNAction->setChecked((!mdnAddr.isEmpty() && im->thatIsMe(mdnAddr)) || KMailSettings::self()->requestMDN()); 1873 } 1874 if (auto hdr = newMsg->headerByType("Return-Receipt-To")) { 1875 const QString returnReceiptToAddr = hdr->asUnicodeString(); 1876 mRequestDeliveryConfirmation->setChecked((!returnReceiptToAddr.isEmpty() && im->thatIsMe(returnReceiptToAddr)) 1877 /*TODO || KMailSettings::self()->requestMDN()*/); 1878 } 1879 // check for presence of a priority header, indicating urgent mail: 1880 if (newMsg->headerByType("X-PRIORITY") && newMsg->headerByType("Priority")) { 1881 const QString xpriority = newMsg->headerByType("X-PRIORITY")->asUnicodeString(); 1882 const QString priority = newMsg->headerByType("Priority")->asUnicodeString(); 1883 if (xpriority == QLatin1StringView("2 (High)") && priority == QLatin1StringView("urgent")) { 1884 mUrgentAction->setChecked(true); 1885 } 1886 } 1887 1888 const auto &ident = identity(); 1889 1890 addFaceHeaders(ident, mMsg); 1891 1892 // if these headers are present, the state of the message should be overruled 1893 MessageComposer::DraftSignatureState signState(mMsg); 1894 if (signState.isDefined()) { 1895 mLastSignActionState = signState.signState(); 1896 } 1897 MessageComposer::DraftEncryptionState encState(mMsg); 1898 if (encState.isDefined()) { 1899 mLastEncryptActionState = encState.encryptionState(); 1900 } 1901 MessageComposer::DraftCryptoMessageFormatState formatState(mMsg); 1902 if (formatState.isDefined()) { 1903 mCryptoModuleAction->setCurrentItem(format2cb(formatState.cryptoMessageFormatState())); 1904 } 1905 1906 mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty(); 1907 mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty(); 1908 1909 if (QGpgME::openpgp() || QGpgME::smime()) { 1910 const bool canOpenPGPSign = QGpgME::openpgp() && !ident.pgpSigningKey().isEmpty(); 1911 const bool canSMIMESign = QGpgME::smime() && !ident.smimeSigningKey().isEmpty(); 1912 1913 setSigning((canOpenPGPSign || canSMIMESign) && mLastSignActionState); 1914 } else { 1915 mEncryptionState.setPossibleEncrypt(false); 1916 } 1917 updateSignatureAndEncryptionStateIndicators(); 1918 1919 // We need to set encryption/signing first before adding content. 1920 // This is important if we are in auto detect the encrypt mode. 1921 mComposerBase->setMessage(mMsg, allowDecryption); 1922 1923 QString kmailFcc; 1924 if (auto hdr = mMsg->headerByType("X-KMail-Fcc")) { 1925 kmailFcc = hdr->asUnicodeString(); 1926 } 1927 if (kmailFcc.isEmpty()) { 1928 setFcc(ident.fcc()); 1929 } else { 1930 setFcc(kmailFcc); 1931 } 1932 if (auto hdr = mMsg->headerByType("X-KMail-Dictionary")) { 1933 const QString dictionary = hdr->asUnicodeString(); 1934 if (!dictionary.isEmpty()) { 1935 if (!mComposerBase->dictionary()->assignByDictionnary(dictionary)) { 1936 mIncorrectIdentityFolderWarning->dictionaryInvalid(); 1937 } 1938 } 1939 } else { 1940 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); 1941 } 1942 1943 auto msgContent = new KMime::Content; 1944 msgContent->setContent(mMsg->encodedContent()); 1945 msgContent->parse(); 1946 MimeTreeParser::SimpleObjectTreeSource emptySource; 1947 MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok 1948 emptySource.setDecryptMessage(allowDecryption); 1949 otp.parseObjectTree(msgContent); 1950 1951 delete msgContent; 1952 1953 if ((MessageComposer::MessageComposerSettings::self()->autoTextSignature() == QLatin1StringView("auto")) && mayAutoSign) { 1954 // 1955 // Espen 2000-05-16 1956 // Delay the signature appending. It may start a fileseletor. 1957 // Not user friendly if this modal fileseletor opens before the 1958 // composer. 1959 // 1960 if (MessageComposer::MessageComposerSettings::self()->prependSignature()) { 1961 QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::prependSignature); 1962 } else { 1963 QTimer::singleShot(0, mComposerBase->signatureController(), &MessageComposer::SignatureController::appendSignature); 1964 } 1965 } else { 1966 mComposerBase->editor()->externalComposer()->startExternalEditor(); 1967 } 1968 1969 setModified(isModified); 1970 1971 // honor "keep reply in this folder" setting even when the identity is changed later on 1972 mPreventFccOverwrite = (!kmailFcc.isEmpty() && ident.fcc() != kmailFcc); 1973 QTimer::singleShot( 1974 0, 1975 this, 1976 &KMComposerWin::forceAutoSaveMessage); // Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in. 1977 } 1978 1979 void KMComposerWin::setAutoSaveFileName(const QString &fileName) 1980 { 1981 mComposerBase->setAutoSaveFileName(fileName); 1982 } 1983 1984 void KMComposerWin::setSigningAndEncryptionDisabled(bool v) 1985 { 1986 mSigningAndEncryptionExplicitlyDisabled = v; 1987 } 1988 1989 void KMComposerWin::setFolder(const Akonadi::Collection &aFolder) 1990 { 1991 mFolder = aFolder; 1992 } 1993 1994 void KMComposerWin::setFcc(const QString &idString) 1995 { 1996 // check if the sent-mail folder still exists 1997 Akonadi::Collection col; 1998 if (idString.isEmpty()) { 1999 col = CommonKernel->sentCollectionFolder(); 2000 } else { 2001 col = Akonadi::Collection(idString.toLongLong()); 2002 } 2003 if (col.isValid()) { 2004 mComposerBase->setFcc(col); 2005 mFccFolder->setCollection(col); 2006 mIncorrectIdentityFolderWarning->clearFccInvalid(); 2007 } else { 2008 mIncorrectIdentityFolderWarning->fccIsInvalid(); 2009 qCWarning(KMAIL_LOG) << "setFcc: collection invalid " << idString; 2010 } 2011 } 2012 2013 bool KMComposerWin::isComposerModified() const 2014 { 2015 return mComposerBase->editor()->document()->isModified() || mEdtFrom->isModified() || mComposerBase->recipientsEditor()->isModified() 2016 || mEdtSubject->document()->isModified(); 2017 } 2018 2019 bool KMComposerWin::isModified() const 2020 { 2021 return mWasModified || isComposerModified(); 2022 } 2023 2024 void KMComposerWin::setModified(bool modified) 2025 { 2026 mWasModified = modified; 2027 changeModifiedState(modified); 2028 } 2029 2030 void KMComposerWin::changeModifiedState(bool modified) 2031 { 2032 mComposerBase->editor()->document()->setModified(modified); 2033 if (!modified) { 2034 mEdtFrom->setModified(false); 2035 mComposerBase->recipientsEditor()->clearModified(); 2036 mEdtSubject->document()->setModified(false); 2037 } 2038 } 2039 2040 bool KMComposerWin::queryClose() 2041 { 2042 if (!mComposerBase->editor()->checkExternalEditorFinished()) { 2043 return false; 2044 } 2045 if (kmkernel->shuttingDown() || qApp->isSavingSession()) { 2046 writeConfig(); 2047 return true; 2048 } 2049 2050 if (isModified()) { 2051 const bool istemplate = (mFolder.isValid() && CommonKernel->folderIsTemplates(mFolder)); 2052 const QString savebut = (istemplate ? i18n("Re&save as Template") : i18n("&Save as Draft")); 2053 const QString savetext = (istemplate ? i18n("Resave this message in the Templates folder. " 2054 "It can then be used at a later time.") 2055 : i18n("Save this message in the Drafts folder. " 2056 "It can then be edited and sent at a later time.")); 2057 2058 const int rc = KMessageBox::warningTwoActionsCancel(this, 2059 i18n("Do you want to save the message for later or discard it?"), 2060 i18nc("@title:window", "Close Composer"), 2061 KGuiItem(savebut, QStringLiteral("document-save"), QString(), savetext), 2062 KStandardGuiItem::discard(), 2063 KStandardGuiItem::cancel()); 2064 if (rc == KMessageBox::Cancel) { 2065 return false; 2066 } else if (rc == KMessageBox::ButtonCode::PrimaryAction) { 2067 // doSend will close the window. Just return false from this method 2068 if (istemplate) { 2069 slotSaveTemplate(); 2070 } else { 2071 slotSaveDraft(); 2072 } 2073 return false; 2074 } 2075 // else fall through: return true 2076 } 2077 mComposerBase->cleanupAutoSave(); 2078 2079 if (!mMiscComposers.isEmpty()) { 2080 qCWarning(KMAIL_LOG) << "Tried to close while composer was active"; 2081 return false; 2082 } 2083 writeConfig(); 2084 return true; 2085 } 2086 2087 MessageComposer::ComposerViewBase::MissingAttachment KMComposerWin::userForgotAttachment() 2088 { 2089 const bool checkForForgottenAttachments = mCheckForForgottenAttachments && KMailSettings::self()->showForgottenAttachmentWarning(); 2090 2091 if (!checkForForgottenAttachments) { 2092 return MessageComposer::ComposerViewBase::NoMissingAttachmentFound; 2093 } 2094 2095 mComposerBase->setSubject(subject()); // be sure the composer knows the subject 2096 const MessageComposer::ComposerViewBase::MissingAttachment missingAttachments = 2097 mComposerBase->checkForMissingAttachments(KMailSettings::self()->attachmentKeywords()); 2098 2099 return missingAttachments; 2100 } 2101 2102 void KMComposerWin::forceAutoSaveMessage() 2103 { 2104 autoSaveMessage(true); 2105 } 2106 2107 void KMComposerWin::autoSaveMessage(bool force) 2108 { 2109 if (isComposerModified() || force) { 2110 applyComposerSetting(mComposerBase); 2111 mComposerBase->saveMailSettings(); 2112 mComposerBase->autoSaveMessage(); 2113 if (!force) { 2114 mWasModified = true; 2115 changeModifiedState(false); 2116 } 2117 } else { 2118 mComposerBase->updateAutoSave(); 2119 } 2120 } 2121 2122 void KMComposerWin::slotSendFailed(const QString &msg, MessageComposer::ComposerViewBase::FailedType type) 2123 { 2124 setEnabled(true); 2125 if (!msg.isEmpty()) { 2126 KMessageBox::error(mMainWidget, 2127 msg, 2128 (type == MessageComposer::ComposerViewBase::AutoSave) ? i18nc("@title:window", "Autosave Message Failed") 2129 : i18nc("@title:window", "Sending Message Failed")); 2130 } 2131 } 2132 2133 void KMComposerWin::slotSendSuccessful(Akonadi::Item::Id id) 2134 { 2135 if (id != -1) { 2136 UndoSendManager::UndoSendManagerInfo info; 2137 info.subject = MessageCore::StringUtil::quoteHtmlChars(subject()); 2138 info.index = id; 2139 info.delay = KMailSettings::self()->undoSendDelay(); 2140 info.to = MessageCore::StringUtil::quoteHtmlChars(mComposerBase->to()); 2141 2142 UndoSendManager::self()->addItem(info); 2143 } 2144 setModified(false); 2145 mComposerBase->cleanupAutoSave(); 2146 mFolder = Akonadi::Collection(); // see dtor 2147 close(); 2148 } 2149 2150 const KIdentityManagementCore::Identity &KMComposerWin::identity() const 2151 { 2152 return KMKernel::self()->identityManager()->identityForUoidOrDefault(currentIdentity()); 2153 } 2154 2155 bool KMComposerWin::pgpAutoEncrypt() const 2156 { 2157 const auto ident = identity(); 2158 if (ident.encryptionOverride()) { 2159 return ident.pgpAutoEncrypt(); 2160 } else { 2161 return MessageComposer::MessageComposerSettings::self()->cryptoAutoEncrypt(); 2162 } 2163 } 2164 2165 bool KMComposerWin::pgpAutoSign() const 2166 { 2167 const auto ident = identity(); 2168 if (ident.encryptionOverride()) { 2169 return ident.pgpAutoSign(); 2170 } else { 2171 return MessageComposer::MessageComposerSettings::self()->cryptoAutoSign(); 2172 } 2173 } 2174 2175 Kleo::CryptoMessageFormat KMComposerWin::cryptoMessageFormat() const 2176 { 2177 if (!mCryptoModuleAction) { 2178 return Kleo::AutoFormat; 2179 } 2180 return cb2format(mCryptoModuleAction->currentItem()); 2181 } 2182 2183 void KMComposerWin::addAttach(KMime::Content *msgPart) 2184 { 2185 mComposerBase->addAttachmentPart(msgPart); 2186 setModified(true); 2187 } 2188 2189 void KMComposerWin::slotAddressBook() 2190 { 2191 auto job = new KIO::CommandLauncherJob(QStringLiteral("kaddressbook"), {}, this); 2192 job->setDesktopName(QStringLiteral("org.kde.kaddressbook")); 2193 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this)); 2194 job->start(); 2195 } 2196 2197 void KMComposerWin::slotInsertFile() 2198 { 2199 const QUrl u = insertFile(); 2200 if (u.isEmpty()) { 2201 return; 2202 } 2203 2204 mRecentAction->addUrl(u); 2205 // Prevent race condition updating list when multiple composers are open 2206 { 2207 QUrlQuery query(u); 2208 const QString encoding = MimeTreeParser::NodeHelper::encodingForName(query.queryItemValue(QStringLiteral("charset"))); 2209 QStringList urls = KMailSettings::self()->recentUrls(); 2210 QStringList encodings = KMailSettings::self()->recentEncodings(); 2211 // Prevent config file from growing without bound 2212 // Would be nicer to get this constant from KRecentFilesAction 2213 const int mMaxRecentFiles = 30; 2214 while (urls.count() > mMaxRecentFiles) { 2215 urls.removeLast(); 2216 } 2217 while (encodings.count() > mMaxRecentFiles) { 2218 encodings.removeLast(); 2219 } 2220 // sanity check 2221 if (urls.count() != encodings.count()) { 2222 urls.clear(); 2223 encodings.clear(); 2224 } 2225 urls.prepend(u.toDisplayString()); 2226 encodings.prepend(encoding); 2227 KMailSettings::self()->setRecentUrls(urls); 2228 KMailSettings::self()->setRecentEncodings(encodings); 2229 KMailSettings::self()->save(); 2230 } 2231 slotInsertRecentFile(u); 2232 } 2233 2234 void KMComposerWin::slotRecentListFileClear() 2235 { 2236 KSharedConfig::Ptr config = KMKernel::self()->config(); 2237 KConfigGroup group(config, QStringLiteral("Composer")); 2238 group.deleteEntry("recent-urls"); 2239 group.deleteEntry("recent-encoding"); 2240 KMailSettings::self()->save(); 2241 } 2242 2243 void KMComposerWin::slotInsertRecentFile(const QUrl &u) 2244 { 2245 if (u.fileName().isEmpty()) { 2246 return; 2247 } 2248 2249 // Get the encoding previously used when inserting this file 2250 QString encoding; 2251 const QStringList urls = KMailSettings::self()->recentUrls(); 2252 const QStringList encodings = KMailSettings::self()->recentEncodings(); 2253 const int index = urls.indexOf(u.toDisplayString()); 2254 if (index != -1) { 2255 encoding = encodings[index]; 2256 } else { 2257 qCDebug(KMAIL_LOG) << " encoding not found so we can't insert text"; // see InsertTextFileJob 2258 return; 2259 } 2260 auto job = new MessageComposer::InsertTextFileJob(mComposerBase->editor(), u); 2261 job->setEncoding(encoding); 2262 connect(job, &KJob::result, this, &KMComposerWin::slotInsertTextFile); 2263 job->start(); 2264 } 2265 2266 bool KMComposerWin::showErrorMessage(KJob *job) 2267 { 2268 if (job->error()) { 2269 if (auto uiDelegate = static_cast<KIO::Job *>(job)->uiDelegate()) { 2270 uiDelegate->showErrorMessage(); 2271 } else { 2272 qCDebug(KMAIL_LOG) << " job->errorString() :" << job->errorString(); 2273 } 2274 return true; 2275 } 2276 return false; 2277 } 2278 2279 void KMComposerWin::slotInsertTextFile(KJob *job) 2280 { 2281 showErrorMessage(job); 2282 } 2283 2284 void KMComposerWin::slotCryptoModuleSelected() 2285 { 2286 slotSelectCryptoModule(false); 2287 } 2288 2289 void KMComposerWin::slotSelectCryptoModule(bool init) 2290 { 2291 if (!init) { 2292 setModified(true); 2293 } 2294 2295 mComposerBase->attachmentModel()->setEncryptEnabled(canSignEncryptAttachments()); 2296 mComposerBase->attachmentModel()->setSignEnabled(canSignEncryptAttachments()); 2297 } 2298 2299 void KMComposerWin::slotUpdateFont() 2300 { 2301 if (!mFixedFontAction) { 2302 return; 2303 } 2304 const QFont plaintextFont = mFixedFontAction->isChecked() ? mFixedFont : mBodyFont; 2305 mComposerBase->editor()->composerControler()->setFontForWholeText(plaintextFont); 2306 mComposerBase->editor()->setDefaultFontSize(plaintextFont.pointSize()); 2307 } 2308 2309 QUrl KMComposerWin::insertFile() 2310 { 2311 QString recentDirClass; 2312 QUrl startUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///InsertFile")), recentDirClass); 2313 2314 const KEncodingFileDialog::Result result = 2315 KEncodingFileDialog::getOpenUrlAndEncoding(QString(), startUrl, QString(), this, i18nc("@title:window", "Insert File")); 2316 QUrl url; 2317 if (!result.URLs.isEmpty()) { 2318 url = result.URLs.constFirst(); 2319 if (url.isValid()) { 2320 MessageCore::StringUtil::setEncodingFile(url, MimeTreeParser::NodeHelper::fixEncoding(result.encoding)); 2321 if (!recentDirClass.isEmpty()) { 2322 KRecentDirs::add(recentDirClass, url.path()); 2323 } 2324 } 2325 } 2326 return url; 2327 } 2328 2329 QString KMComposerWin::smartQuote(const QString &msg) 2330 { 2331 return MessageCore::StringUtil::smartQuote(msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth()); 2332 } 2333 2334 void KMComposerWin::insertUrls(const QMimeData *source, const QList<QUrl> &urlList) 2335 { 2336 QStringList urlAdded; 2337 for (const QUrl &url : urlList) { 2338 QString urlStr; 2339 if (url.scheme() == QLatin1StringView("mailto")) { 2340 urlStr = KEmailAddress::decodeMailtoUrl(url); 2341 } else { 2342 urlStr = url.toDisplayString(); 2343 // Workaround #346370 2344 if (urlStr.isEmpty()) { 2345 urlStr = source->text(); 2346 } 2347 } 2348 if (!urlAdded.contains(urlStr)) { 2349 mComposerBase->editor()->composerControler()->insertLink(urlStr); 2350 urlAdded.append(urlStr); 2351 } 2352 } 2353 } 2354 2355 bool KMComposerWin::insertFromMimeData(const QMimeData *source, bool forceAttachment) 2356 { 2357 // If this is a PNG image, either add it as an attachment or as an inline image 2358 if (source->hasHtml() && mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich) { 2359 const QString html = QString::fromUtf8(source->data(QStringLiteral("text/html"))); 2360 mComposerBase->editor()->insertHtml(html); 2361 return true; 2362 } else if (source->hasHtml() && (mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Plain) && source->hasText() 2363 && !forceAttachment) { 2364 mComposerBase->editor()->insertPlainText(source->text()); 2365 return true; 2366 } else if (source->hasImage() && source->hasFormat(QStringLiteral("image/png"))) { 2367 // Get the image data before showing the dialog, since that processes events which can delete 2368 // the QMimeData object behind our back 2369 const QByteArray imageData = source->data(QStringLiteral("image/png")); 2370 if (imageData.isEmpty()) { 2371 return true; 2372 } 2373 if (!forceAttachment) { 2374 if (mComposerBase->editor()->textMode() 2375 == MessageComposer::RichTextComposerNg::Rich /*&& mComposerBase->editor()->isEnableImageActions() Necessary ?*/) { 2376 auto image = qvariant_cast<QImage>(source->imageData()); 2377 QFileInfo fi(source->text()); 2378 2379 QMenu menu(this); 2380 const QAction *addAsInlineImageAction = menu.addAction(i18n("Add as &Inline Image")); 2381 menu.addAction(i18n("Add as &Attachment")); 2382 const QAction *selectedAction = menu.exec(QCursor::pos()); 2383 if (selectedAction == addAsInlineImageAction) { 2384 // Let the textedit from kdepimlibs handle inline images 2385 mComposerBase->editor()->composerControler()->composerImages()->insertImage(image, fi); 2386 return true; 2387 } else if (!selectedAction) { 2388 return true; 2389 } 2390 // else fall through 2391 } 2392 } 2393 // Ok, when we reached this point, the user wants to add the image as an attachment. 2394 // Ask for the filename first. 2395 bool ok; 2396 QString attName = QInputDialog::getText(this, i18n("KMail"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok); 2397 if (!ok) { 2398 return true; 2399 } 2400 attName = attName.trimmed(); 2401 if (attName.isEmpty()) { 2402 KMessageBox::error(this, i18n("Attachment name can't be empty"), i18nc("@title:window", "Invalid Attachment Name")); 2403 2404 return true; 2405 } 2406 addAttachment(attName, KMime::Headers::CEbase64, QString(), imageData, "image/png"); 2407 return true; 2408 } else { 2409 auto job = new DndFromArkJob(this); 2410 job->setComposerWin(this); 2411 if (job->extract(source)) { 2412 return true; 2413 } 2414 } 2415 2416 // If this is a URL list, add those files as attachments or text 2417 // but do not offer this if we are pasting plain text containing an url, e.g. from a browser 2418 const QList<QUrl> urlList = source->urls(); 2419 if (!urlList.isEmpty()) { 2420 // Search if it's message items. 2421 Akonadi::Item::List items; 2422 Akonadi::Collection::List collections; 2423 bool allLocalURLs = true; 2424 2425 for (const QUrl &url : urlList) { 2426 if (!url.isLocalFile()) { 2427 allLocalURLs = false; 2428 } 2429 const Akonadi::Item item = Akonadi::Item::fromUrl(url); 2430 if (item.isValid()) { 2431 items << item; 2432 } else { 2433 const Akonadi::Collection collection = Akonadi::Collection::fromUrl(url); 2434 if (collection.isValid()) { 2435 collections << collection; 2436 } 2437 } 2438 } 2439 2440 if (items.isEmpty() && collections.isEmpty()) { 2441 if (allLocalURLs || forceAttachment) { 2442 QList<AttachmentInfo> infoList; 2443 infoList.reserve(urlList.count()); 2444 for (const QUrl &url : urlList) { 2445 AttachmentInfo info; 2446 info.url = url; 2447 infoList.append(std::move(info)); 2448 } 2449 addAttachment(infoList, false); 2450 } else { 2451 QMenu p; 2452 const int sizeUrl(urlList.size()); 2453 const QAction *addAsTextAction = p.addAction(i18np("Add URL into Message", "Add URLs into Message", sizeUrl)); 2454 const QAction *addAsAttachmentAction = p.addAction(i18np("Add File as &Attachment", "Add Files as &Attachment", sizeUrl)); 2455 const QAction *selectedAction = p.exec(QCursor::pos()); 2456 2457 if (selectedAction == addAsTextAction) { 2458 insertUrls(source, urlList); 2459 } else if (selectedAction == addAsAttachmentAction) { 2460 QList<AttachmentInfo> infoList; 2461 for (const QUrl &url : urlList) { 2462 if (url.isValid()) { 2463 AttachmentInfo info; 2464 info.url = url; 2465 infoList.append(std::move(info)); 2466 } 2467 } 2468 addAttachment(infoList, false); 2469 } 2470 } 2471 return true; 2472 } else { 2473 if (!items.isEmpty()) { 2474 auto itemFetchJob = new Akonadi::ItemFetchJob(items, this); 2475 itemFetchJob->fetchScope().fetchFullPayload(true); 2476 itemFetchJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); 2477 connect(itemFetchJob, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotFetchJob); 2478 } 2479 if (!collections.isEmpty()) { 2480 qCDebug(KMAIL_LOG) << "Collection dnd not supported"; 2481 } 2482 return true; 2483 } 2484 } 2485 return false; 2486 } 2487 2488 void KMComposerWin::slotPasteAsAttachment() 2489 { 2490 const QMimeData *mimeData = QApplication::clipboard()->mimeData(); 2491 if (!mimeData) { 2492 return; 2493 } 2494 if (insertFromMimeData(mimeData, true)) { 2495 return; 2496 } 2497 if (mimeData->hasText()) { 2498 bool ok; 2499 const QString attName = 2500 QInputDialog::getText(this, i18n("Insert clipboard text as attachment"), i18n("Name of the attachment:"), QLineEdit::Normal, QString(), &ok); 2501 if (ok) { 2502 mComposerBase->addAttachment(attName, attName, QStringLiteral("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain"); 2503 } 2504 return; 2505 } 2506 } 2507 2508 void KMComposerWin::slotFetchJob(KJob *job) 2509 { 2510 if (showErrorMessage(job)) { 2511 return; 2512 } 2513 auto fjob = qobject_cast<Akonadi::ItemFetchJob *>(job); 2514 if (!fjob) { 2515 return; 2516 } 2517 const Akonadi::Item::List items = fjob->items(); 2518 2519 if (items.isEmpty()) { 2520 return; 2521 } 2522 2523 if (items.constFirst().mimeType() == KMime::Message::mimeType()) { 2524 uint identity = 0; 2525 if (items.at(0).isValid()) { 2526 const Akonadi::Collection parentCollection = items.at(0).parentCollection(); 2527 if (parentCollection.isValid()) { 2528 const QString resourceName = parentCollection.resource(); 2529 if (!resourceName.isEmpty()) { 2530 QSharedPointer<MailCommon::FolderSettings> fd(MailCommon::FolderSettings::forCollection(parentCollection, false)); 2531 if (!fd.isNull()) { 2532 identity = fd->identity(); 2533 } 2534 } 2535 } 2536 } 2537 KMCommand *command = new KMForwardAttachedCommand(this, items, identity, this); 2538 command->start(); 2539 } else { 2540 for (const Akonadi::Item &item : items) { 2541 QString attachmentName = QStringLiteral("attachment"); 2542 if (item.hasPayload<KContacts::Addressee>()) { 2543 const auto contact = item.payload<KContacts::Addressee>(); 2544 attachmentName = contact.realName() + QLatin1StringView(".vcf"); 2545 // Workaround about broken kaddressbook fields. 2546 QByteArray data = item.payloadData(); 2547 KContacts::adaptIMAttributes(data); 2548 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), data, "text/x-vcard"); 2549 } else if (item.hasPayload<KContacts::ContactGroup>()) { 2550 const auto group = item.payload<KContacts::ContactGroup>(); 2551 attachmentName = group.name() + QLatin1StringView(".vcf"); 2552 auto expandJob = new Akonadi::ContactGroupExpandJob(group, this); 2553 expandJob->setProperty("groupName", attachmentName); 2554 connect(expandJob, &KJob::result, this, &KMComposerWin::slotExpandGroupResult); 2555 expandJob->start(); 2556 } else { 2557 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1()); 2558 } 2559 } 2560 } 2561 } 2562 2563 void KMComposerWin::slotExpandGroupResult(KJob *job) 2564 { 2565 auto expandJob = qobject_cast<Akonadi::ContactGroupExpandJob *>(job); 2566 Q_ASSERT(expandJob); 2567 2568 const QString attachmentName = expandJob->property("groupName").toString(); 2569 KContacts::VCardConverter converter; 2570 const QByteArray groupData = converter.exportVCards(expandJob->contacts(), KContacts::VCardConverter::v3_0); 2571 if (!groupData.isEmpty()) { 2572 addAttachment(attachmentName, KMime::Headers::CEbase64, QString(), groupData, "text/x-vcard"); 2573 } 2574 } 2575 2576 void KMComposerWin::slotClose() 2577 { 2578 close(); 2579 } 2580 2581 void KMComposerWin::slotNewComposer() 2582 { 2583 auto job = new KMComposerCreateNewComposerJob; 2584 job->setCollectionForNewMessage(mCollectionForNewMessage); 2585 2586 job->setCurrentIdentity(currentIdentity()); 2587 job->start(); 2588 } 2589 2590 void KMComposerWin::slotUpdateWindowTitle() 2591 { 2592 QString s(mEdtSubject->toPlainText()); 2593 mComposerBase->setSubject(s); 2594 // Remove characters that show badly in most window decorations: 2595 // newlines tend to become boxes. 2596 if (s.isEmpty()) { 2597 setWindowTitle(QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')')); 2598 } else { 2599 setWindowTitle(s.replace(QLatin1Char('\n'), QLatin1Char(' '))); 2600 } 2601 } 2602 2603 void KMComposerWin::slotSignToggled(bool on) 2604 { 2605 setSigning(on, true); 2606 updateSignatureAndEncryptionStateIndicators(); 2607 } 2608 2609 void KMComposerWin::setSigning(bool sign, bool setByUser) 2610 { 2611 const bool wasModified = isModified(); 2612 if (setByUser) { 2613 setModified(true); 2614 } 2615 if (!mSignAction->isEnabled()) { 2616 sign = false; 2617 } 2618 2619 // check if the user defined a signing key for the current identity 2620 if (sign && !mLastIdentityHasSigningKey) { 2621 if (setByUser) { 2622 KMessageBox::error(this, 2623 i18n("<qt><p>In order to be able to sign " 2624 "this message you first have to " 2625 "define the (OpenPGP or S/MIME) signing key " 2626 "to use.</p>" 2627 "<p>Please select the key to use " 2628 "in the identity configuration.</p>" 2629 "</qt>"), 2630 i18nc("@title:window", "Undefined Signing Key")); 2631 setModified(wasModified); 2632 } 2633 sign = false; 2634 } 2635 2636 // make sure the mSignAction is in the right state 2637 mSignAction->setChecked(sign); 2638 2639 if (!setByUser) { 2640 updateSignatureAndEncryptionStateIndicators(); 2641 } 2642 // mark the attachments for (no) signing 2643 if (canSignEncryptAttachments()) { 2644 mComposerBase->attachmentModel()->setSignSelected(sign); 2645 } 2646 } 2647 2648 void KMComposerWin::slotWordWrapToggled(bool on) 2649 { 2650 if (on) { 2651 mComposerBase->editor()->enableWordWrap(validateLineWrapWidth()); 2652 } else { 2653 disableWordWrap(); 2654 } 2655 } 2656 2657 int KMComposerWin::validateLineWrapWidth() const 2658 { 2659 int lineWrap = MessageComposer::MessageComposerSettings::self()->lineWrapWidth(); 2660 if ((lineWrap == 0) || (lineWrap > 78)) { 2661 lineWrap = 78; 2662 } else if (lineWrap < 30) { 2663 lineWrap = 30; 2664 } 2665 return lineWrap; 2666 } 2667 2668 void KMComposerWin::disableWordWrap() 2669 { 2670 mComposerBase->editor()->disableWordWrap(); 2671 } 2672 2673 void KMComposerWin::forceDisableHtml() 2674 { 2675 mForceDisableHtml = true; 2676 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); 2677 mMarkupAction->setEnabled(false); 2678 // FIXME: Remove the toggle toolbar action somehow 2679 } 2680 2681 bool KMComposerWin::isComposing() const 2682 { 2683 return mComposerBase && mComposerBase->isComposing(); 2684 } 2685 2686 void KMComposerWin::disableForgottenAttachmentsCheck() 2687 { 2688 mCheckForForgottenAttachments = false; 2689 } 2690 2691 void KMComposerWin::slotPrint() 2692 { 2693 printComposer(false); 2694 } 2695 2696 void KMComposerWin::slotPrintPreview() 2697 { 2698 printComposer(true); 2699 } 2700 2701 void KMComposerWin::printComposer(bool preview) 2702 { 2703 auto composer = new MessageComposer::Composer(); 2704 mComposerBase->fillComposer(composer); 2705 mMiscComposers.append(composer); 2706 composer->setProperty("preview", preview); 2707 connect(composer, &MessageComposer::Composer::result, this, &KMComposerWin::slotPrintComposeResult); 2708 composer->start(); 2709 } 2710 2711 void KMComposerWin::slotPrintComposeResult(KJob *job) 2712 { 2713 const bool preview = job->property("preview").toBool(); 2714 printComposeResult(job, preview); 2715 } 2716 2717 void KMComposerWin::printComposeResult(KJob *job, bool preview) 2718 { 2719 Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job)); 2720 auto composer = qobject_cast<MessageComposer::Composer *>(job); 2721 Q_ASSERT(mMiscComposers.contains(composer)); 2722 mMiscComposers.removeAll(composer); 2723 2724 if (composer->error() == MessageComposer::Composer::NoError) { 2725 Q_ASSERT(composer->resultMessages().size() == 1); 2726 Akonadi::Item printItem; 2727 printItem.setPayload<KMime::Message::Ptr>(composer->resultMessages().constFirst()); 2728 Akonadi::MessageFlags::copyMessageFlags(*(composer->resultMessages().constFirst()), printItem); 2729 const bool isHtml = mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich; 2730 const MessageViewer::Viewer::DisplayFormatMessage format = isHtml ? MessageViewer::Viewer::Html : MessageViewer::Viewer::Text; 2731 KMPrintCommandInfo commandInfo; 2732 commandInfo.mMsg = printItem; 2733 commandInfo.mFormat = format; 2734 commandInfo.mHtmlLoadExtOverride = isHtml; 2735 commandInfo.mPrintPreview = preview; 2736 auto command = new KMPrintCommand(this, commandInfo); 2737 command->start(); 2738 } else { 2739 showErrorMessage(job); 2740 } 2741 } 2742 2743 void KMComposerWin::doSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool willSendItWithoutReediting) 2744 { 2745 if (saveIn == MessageComposer::MessageSender::SaveInNone) { 2746 const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment(); 2747 if ((forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment) 2748 || (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel)) { 2749 return; 2750 } 2751 } 2752 2753 // TODO generate new message from plugins. 2754 MessageComposer::PluginEditorConverterBeforeConvertingData data; 2755 data.setNewMessage(mContext == TemplateContext::New); 2756 mPluginEditorConvertTextManagerInterface->setDataBeforeConvertingText(data); 2757 2758 // TODO converttext if necessary 2759 2760 // TODO integrate with MDA online status 2761 if (method == MessageComposer::MessageSender::SendImmediate) { 2762 qDebug() << " VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV"; 2763 if (!MessageComposer::Util::sendMailDispatcherIsOnline()) { 2764 qDebug() << " CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"; 2765 method = MessageComposer::MessageSender::SendLater; 2766 } 2767 if (KMailSettings::self()->enabledUndoSend()) { 2768 mComposerBase->setSendLaterInfo(nullptr); 2769 const bool wasRegistered = sendLaterRegistered(); 2770 if (wasRegistered) { 2771 auto info = new MessageComposer::SendLaterInfo; 2772 info->setRecurrence(false); 2773 info->setSubject(subject()); 2774 info->setDateTime(QDateTime::currentDateTime().addSecs(KMailSettings::self()->undoSendDelay())); 2775 mComposerBase->setSendLaterInfo(info); 2776 } 2777 method = MessageComposer::MessageSender::SendLater; 2778 willSendItWithoutReediting = true; 2779 saveIn = MessageComposer::MessageSender::SaveInOutbox; 2780 } 2781 } 2782 2783 if (saveIn == MessageComposer::MessageSender::SaveInNone || willSendItWithoutReediting) { // don't save as draft or template, send immediately 2784 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) { 2785 if (!(mShowHeaders & HDR_FROM)) { 2786 mShowHeaders |= HDR_FROM; 2787 rethinkFields(false); 2788 } 2789 mEdtFrom->setFocus(); 2790 KMessageBox::error(this, 2791 i18n("You must enter your email address in the " 2792 "From: field. You should also set your email " 2793 "address for all identities, so that you do " 2794 "not have to enter it for each message.")); 2795 return; 2796 } 2797 if (mComposerBase->to().isEmpty()) { 2798 if (mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty()) { 2799 KMessageBox::information(this, 2800 i18n("You must specify at least one receiver, " 2801 "either in the To: field or as CC or as BCC.")); 2802 2803 return; 2804 } else { 2805 const int rc = KMessageBox::questionTwoActions(this, 2806 i18n("To: field is empty. " 2807 "Send message anyway?"), 2808 i18nc("@title:window", "No To: specified"), 2809 KGuiItem(i18n("S&end as Is"), QLatin1StringView("mail-send")), 2810 KGuiItem(i18n("&Specify the To field"), QLatin1StringView("edit-rename"))); 2811 if (rc == KMessageBox::ButtonCode::SecondaryAction) { 2812 return; 2813 } 2814 } 2815 } 2816 2817 if (subject().isEmpty()) { 2818 mEdtSubject->setFocus(); 2819 const int rc = KMessageBox::questionTwoActions(this, 2820 i18n("You did not specify a subject. " 2821 "Send message anyway?"), 2822 i18nc("@title:window", "No Subject Specified"), 2823 KGuiItem(i18n("S&end as Is"), QStringLiteral("mail-send")), 2824 KGuiItem(i18n("&Specify the Subject"), QStringLiteral("edit-rename"))); 2825 if (rc == KMessageBox::ButtonCode::SecondaryAction) { 2826 return; 2827 } 2828 } 2829 2830 MessageComposer::PluginEditorCheckBeforeSendParams params; 2831 params.setSubject(subject()); 2832 params.setHtmlMail(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); 2833 params.setIdentity(currentIdentity()); 2834 params.setHasAttachment(mComposerBase->attachmentModel()->rowCount() > 0); 2835 params.setTransportId(mComposerBase->transportComboBox()->currentTransportId()); 2836 const auto ident = identity(); 2837 QString defaultDomainName; 2838 if (!ident.isNull()) { 2839 defaultDomainName = ident.defaultDomainName(); 2840 } 2841 const QString composerBaseBccTrimmed = mComposerBase->bcc().trimmed(); 2842 const QString composerBaseToTrimmed = mComposerBase->to().trimmed(); 2843 const QString composerBaseCcTrimmed = mComposerBase->cc().trimmed(); 2844 params.setBccAddresses(composerBaseBccTrimmed); 2845 params.setToAddresses(composerBaseToTrimmed); 2846 params.setCcAddresses(composerBaseCcTrimmed); 2847 params.setDefaultDomain(defaultDomainName); 2848 2849 if (!mPluginEditorCheckBeforeSendManagerInterface->execute(params)) { 2850 return; 2851 } 2852 const QStringList recipients = {composerBaseToTrimmed, composerBaseCcTrimmed, composerBaseBccTrimmed}; 2853 2854 setEnabled(false); 2855 2856 // Validate the To:, CC: and BCC fields 2857 auto job = new AddressValidationJob(recipients.join(QLatin1StringView(", ")), this, this); 2858 job->setDefaultDomain(defaultDomainName); 2859 job->setProperty("method", static_cast<int>(method)); 2860 job->setProperty("saveIn", static_cast<int>(saveIn)); 2861 connect(job, &Akonadi::ItemFetchJob::result, this, &KMComposerWin::slotDoDelayedSend); 2862 job->start(); 2863 2864 // we'll call send from within slotDoDelaySend 2865 } else { 2866 if (saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptionState.encrypt() && KMailSettings::self()->alwaysEncryptDrafts() 2867 && mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty()) { 2868 KMessageBox::information(this, 2869 i18n("You must specify at least one receiver " 2870 "in order to be able to encrypt a draft.")); 2871 return; 2872 } 2873 doDelayedSend(method, saveIn); 2874 } 2875 } 2876 2877 void KMComposerWin::slotDoDelayedSend(KJob *job) 2878 { 2879 if (job->error()) { 2880 KMessageBox::error(this, job->errorText()); 2881 setEnabled(true); 2882 return; 2883 } 2884 2885 const AddressValidationJob *validateJob = qobject_cast<AddressValidationJob *>(job); 2886 2887 // Abort sending if one of the recipient addresses is invalid ... 2888 if (!validateJob->isValid()) { 2889 setEnabled(true); 2890 return; 2891 } 2892 2893 // ... otherwise continue as usual 2894 const MessageComposer::MessageSender::SendMethod method = static_cast<MessageComposer::MessageSender::SendMethod>(job->property("method").toInt()); 2895 const MessageComposer::MessageSender::SaveIn saveIn = static_cast<MessageComposer::MessageSender::SaveIn>(job->property("saveIn").toInt()); 2896 2897 doDelayedSend(method, saveIn); 2898 } 2899 2900 void KMComposerWin::applyComposerSetting(MessageComposer::ComposerViewBase *mComposerBase) 2901 { 2902 mComposerBase->setFrom(from()); 2903 mComposerBase->setSubject(subject()); 2904 mComposerBase->setUrgent(mUrgentAction->isChecked()); 2905 mComposerBase->setMDNRequested(mRequestMDNAction->isChecked()); 2906 mComposerBase->setRequestDeleveryConfirmation(mRequestDeliveryConfirmation->isChecked()); 2907 } 2908 2909 void KMComposerWin::doDelayedSend(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn) 2910 { 2911 KCursorSaver saver(Qt::WaitCursor); 2912 applyComposerSetting(mComposerBase); 2913 if (mForceDisableHtml) { 2914 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); 2915 } 2916 const bool encrypt = mEncryptionState.encrypt(); 2917 2918 mComposerBase->setCryptoOptions( 2919 sign(), 2920 encrypt, 2921 cryptoMessageFormat(), 2922 ((saveIn != MessageComposer::MessageSender::SaveInNone && !KMailSettings::self()->alwaysEncryptDrafts()) || mSigningAndEncryptionExplicitlyDisabled)); 2923 2924 const int num = KMailSettings::self()->customMessageHeadersCount(); 2925 QMap<QByteArray, QString> customHeader; 2926 for (int ix = 0; ix < num; ++ix) { 2927 CustomMimeHeader customMimeHeader(QString::number(ix)); 2928 customMimeHeader.load(); 2929 customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue()); 2930 } 2931 2932 QMap<QByteArray, QString>::const_iterator extraCustomHeader = mExtraHeaders.constBegin(); 2933 while (extraCustomHeader != mExtraHeaders.constEnd()) { 2934 customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value()); 2935 ++extraCustomHeader; 2936 } 2937 2938 mComposerBase->setCustomHeader(customHeader); 2939 mComposerBase->send(method, saveIn, false); 2940 } 2941 2942 bool KMComposerWin::sendLaterRegistered() const 2943 { 2944 return MessageComposer::SendLaterUtil::sentLaterAgentWasRegistered() && MessageComposer::SendLaterUtil::sentLaterAgentEnabled(); 2945 } 2946 2947 void KMComposerWin::slotSendLater() 2948 { 2949 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) { 2950 return; 2951 } 2952 if (!checkRecipientNumber()) { 2953 return; 2954 } 2955 mComposerBase->setSendLaterInfo(nullptr); 2956 if (mComposerBase->editor()->checkExternalEditorFinished()) { 2957 const bool wasRegistered = sendLaterRegistered(); 2958 if (wasRegistered) { 2959 MessageComposer::SendLaterInfo *info = nullptr; 2960 QPointer<MessageComposer::SendLaterDialog> dlg = new MessageComposer::SendLaterDialog(info, this); 2961 if (dlg->exec()) { 2962 info = dlg->info(); 2963 const MessageComposer::SendLaterDialog::SendLaterAction action = dlg->action(); 2964 delete dlg; 2965 switch (action) { 2966 case MessageComposer::SendLaterDialog::Unknown: 2967 qCDebug(KMAIL_LOG) << "Sendlater action \"Unknown\": Need to fix it."; 2968 break; 2969 case MessageComposer::SendLaterDialog::Canceled: 2970 return; 2971 break; 2972 case MessageComposer::SendLaterDialog::PutInOutbox: 2973 doSend(MessageComposer::MessageSender::SendLater); 2974 break; 2975 case MessageComposer::SendLaterDialog::SendDeliveryAtTime: 2976 mComposerBase->setSendLaterInfo(info); 2977 if (info->isRecurrence()) { 2978 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates, true); 2979 } else { 2980 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts, true); 2981 } 2982 break; 2983 } 2984 } else { 2985 delete dlg; 2986 } 2987 } else { 2988 doSend(MessageComposer::MessageSender::SendLater); 2989 } 2990 } 2991 } 2992 2993 void KMComposerWin::slotSaveDraft() 2994 { 2995 if (mComposerBase->editor()->checkExternalEditorFinished()) { 2996 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts); 2997 } 2998 } 2999 3000 void KMComposerWin::slotSaveTemplate() 3001 { 3002 if (mComposerBase->editor()->checkExternalEditorFinished()) { 3003 doSend(MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates); 3004 } 3005 } 3006 3007 void KMComposerWin::slotSendNowVia(MailTransport::Transport *transport) 3008 { 3009 if (transport) { 3010 mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); 3011 slotSendNow(); 3012 } 3013 } 3014 3015 void KMComposerWin::slotSendLaterVia(MailTransport::Transport *transport) 3016 { 3017 if (transport) { 3018 mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); 3019 slotSendLater(); 3020 } 3021 } 3022 3023 void KMComposerWin::sendNow(bool shortcutUsed) 3024 { 3025 if (!mComposerBase->editor()->checkExternalEditorFinished()) { 3026 return; 3027 } 3028 if (!TransportManager::self()->showTransportCreationDialog(this, TransportManager::IfNoTransportExists)) { 3029 return; 3030 } 3031 if (!checkRecipientNumber()) { 3032 return; 3033 } 3034 mSendNowByShortcutUsed = shortcutUsed; 3035 if (KMailSettings::self()->checkSpellingBeforeSend()) { 3036 mComposerBase->editor()->forceSpellChecking(); 3037 } else { 3038 slotCheckSendNow(); 3039 } 3040 } 3041 3042 void KMComposerWin::slotSendNowByShortcut() 3043 { 3044 sendNow(true); 3045 } 3046 3047 void KMComposerWin::slotSendNow() 3048 { 3049 sendNow(false); 3050 } 3051 3052 void KMComposerWin::confirmBeforeSend() 3053 { 3054 const int rc = KMessageBox::warningTwoActionsCancel(mMainWidget, 3055 i18n("About to send email..."), 3056 i18nc("@title:window", "Send Confirmation"), 3057 KGuiItem(i18n("&Send Now"), QLatin1StringView("mail-send")), 3058 KGuiItem(i18n("Send &Later"), QLatin1StringView("mail-queue"))); 3059 3060 if (rc == KMessageBox::ButtonCode::PrimaryAction) { 3061 doSend(MessageComposer::MessageSender::SendImmediate); 3062 } else if (rc == KMessageBox::ButtonCode::SecondaryAction) { 3063 doSend(MessageComposer::MessageSender::SendLater); 3064 } 3065 } 3066 3067 void KMComposerWin::slotCheckSendNowStep2() 3068 { 3069 if (KMailSettings::self()->confirmBeforeSend()) { 3070 confirmBeforeSend(); 3071 } else { 3072 if (mSendNowByShortcutUsed) { 3073 if (!KMailSettings::self()->checkSendDefaultActionShortcut()) { 3074 ValidateSendMailShortcut validateShortcut(actionCollection(), this); 3075 if (!validateShortcut.validate()) { 3076 return; 3077 } 3078 } 3079 if (KMailSettings::self()->confirmBeforeSendWhenUseShortcut()) { 3080 confirmBeforeSend(); 3081 return; 3082 } 3083 } 3084 doSend(MessageComposer::MessageSender::SendImmediate); 3085 } 3086 } 3087 3088 void KMComposerWin::slotDelayedCheckSendNow() 3089 { 3090 QTimer::singleShot(0, this, &KMComposerWin::slotCheckSendNow); 3091 } 3092 3093 void KMComposerWin::slotCheckSendNow() 3094 { 3095 QStringList lst{mComposerBase->to()}; 3096 const QString ccStr = mComposerBase->cc(); 3097 if (!ccStr.isEmpty()) { 3098 lst << KEmailAddress::splitAddressList(ccStr); 3099 } 3100 const QString bccStr = mComposerBase->bcc(); 3101 if (!bccStr.isEmpty()) { 3102 lst << KEmailAddress::splitAddressList(bccStr); 3103 } 3104 if (lst.isEmpty()) { 3105 slotCheckSendNowStep2(); 3106 } else { 3107 auto job = new PotentialPhishingEmailJob(this); 3108 KConfigGroup group(KSharedConfig::openConfig(), QStringLiteral("PotentialPhishing")); 3109 const QStringList whiteList = group.readEntry("whiteList", QStringList()); 3110 job->setEmailWhiteList(whiteList); 3111 job->setPotentialPhishingEmails(lst); 3112 connect(job, &PotentialPhishingEmailJob::potentialPhishingEmailsFound, this, &KMComposerWin::slotPotentialPhishingEmailsFound); 3113 if (!job->start()) { 3114 qCWarning(KMAIL_LOG) << "PotentialPhishingEmailJob can't start"; 3115 } 3116 } 3117 } 3118 3119 void KMComposerWin::slotPotentialPhishingEmailsFound(const QStringList &list) 3120 { 3121 if (list.isEmpty()) { 3122 slotCheckSendNowStep2(); 3123 } else { 3124 mPotentialPhishingEmailWarning->setPotentialPhisingEmail(list); 3125 } 3126 } 3127 3128 bool KMComposerWin::checkRecipientNumber() const 3129 { 3130 const int thresHold = KMailSettings::self()->recipientThreshold(); 3131 if (KMailSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold) { 3132 if (KMessageBox::questionTwoActions(mMainWidget, 3133 i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold), 3134 i18n("Too many recipients"), 3135 KGuiItem(i18n("&Send as Is")), 3136 KGuiItem(i18n("&Edit Recipients"))) 3137 == KMessageBox::ButtonCode::SecondaryAction) { 3138 return false; 3139 } 3140 } 3141 return true; 3142 } 3143 3144 void KMComposerWin::enableHtml() 3145 { 3146 if (mForceDisableHtml) { 3147 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); 3148 return; 3149 } 3150 3151 mComposerBase->editor()->activateRichText(); 3152 if (!toolBar(QStringLiteral("htmlToolBar"))->isVisible()) { 3153 // Use singleshot, as we we might actually be called from a slot that wanted to disable the 3154 // toolbar (but the messagebox in disableHtml() prevented that and called us). 3155 // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled" 3156 // signal, so wait one event loop run for that. 3157 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::show); 3158 } 3159 if (!mMarkupAction->isChecked()) { 3160 mMarkupAction->setChecked(true); 3161 } 3162 3163 mComposerBase->editor()->composerActions()->updateActionStates(); 3164 mComposerBase->editor()->composerActions()->setActionsEnabled(true); 3165 } 3166 3167 void KMComposerWin::disableHtml(MessageComposer::ComposerViewBase::Confirmation confirmation) 3168 { 3169 bool forcePlainTextMarkup = false; 3170 if (confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->composerControler()->isFormattingUsed() 3171 && !mForceDisableHtml) { 3172 int choice = KMessageBox::warningTwoActionsCancel(this, 3173 i18n("Turning HTML mode off " 3174 "will cause the text to lose the formatting. Are you sure?"), 3175 i18n("Lose the formatting?"), 3176 KGuiItem(i18n("Lose Formatting")), 3177 KGuiItem(i18n("Add Markup Plain Text")), 3178 KStandardGuiItem::cancel(), 3179 QStringLiteral("LoseFormattingWarning")); 3180 3181 switch (choice) { 3182 case KMessageBox::Cancel: 3183 enableHtml(); 3184 return; 3185 case KMessageBox::ButtonCode::SecondaryAction: 3186 forcePlainTextMarkup = true; 3187 break; 3188 case KMessageBox::ButtonCode::PrimaryAction: 3189 break; 3190 } 3191 } 3192 3193 mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup); 3194 mComposerBase->editor()->switchToPlainText(); 3195 mComposerBase->editor()->composerActions()->setActionsEnabled(false); 3196 3197 slotUpdateFont(); 3198 if (toolBar(QStringLiteral("htmlToolBar"))->isVisible()) { 3199 // See the comment in enableHtml() why we use a singleshot timer, similar situation here. 3200 QTimer::singleShot(0, toolBar(QStringLiteral("htmlToolBar")), &QWidget::hide); 3201 } 3202 if (mMarkupAction->isChecked()) { 3203 mMarkupAction->setChecked(false); 3204 } 3205 } 3206 3207 void KMComposerWin::slotToggleMarkup() 3208 { 3209 htmlToolBarVisibilityChanged(mMarkupAction->isChecked()); 3210 } 3211 3212 void KMComposerWin::slotTextModeChanged(MessageComposer::RichTextComposerNg::Mode mode) 3213 { 3214 if (mode == MessageComposer::RichTextComposerNg::Plain) { 3215 disableHtml(MessageComposer::ComposerViewBase::NoConfirmationNeeded); // ### Can this happen at all? 3216 } else { 3217 enableHtml(); 3218 } 3219 enableDisablePluginActions(mode == MessageComposer::RichTextComposerNg::Rich); 3220 } 3221 3222 void KMComposerWin::enableDisablePluginActions(bool richText) 3223 { 3224 mPluginEditorConvertTextManagerInterface->enableDisablePluginActions(richText); 3225 } 3226 3227 void KMComposerWin::htmlToolBarVisibilityChanged(bool visible) 3228 { 3229 if (visible) { 3230 enableHtml(); 3231 } else { 3232 disableHtml(MessageComposer::ComposerViewBase::LetUserConfirm); 3233 } 3234 } 3235 3236 void KMComposerWin::slotAutoSpellCheckingToggled(bool on) 3237 { 3238 mAutoSpellCheckingAction->setChecked(on); 3239 if (on != mComposerBase->editor()->checkSpellingEnabled()) { 3240 mComposerBase->editor()->setCheckSpellingEnabled(on); 3241 } 3242 if (on != mEdtSubject->checkSpellingEnabled()) { 3243 mEdtSubject->setCheckSpellingEnabled(on); 3244 } 3245 mStatusBarLabelSpellCheckingChangeMode->setToggleMode(on); 3246 } 3247 3248 void KMComposerWin::showAndActivateComposer() 3249 { 3250 show(); 3251 raise(); 3252 activateWindow(); 3253 } 3254 3255 void KMComposerWin::slotSpellCheckingStatus(const QString &status) 3256 { 3257 mStatusbarLabel->setText(status); 3258 QTimer::singleShot(2s, this, &KMComposerWin::slotSpellcheckDoneClearStatus); 3259 } 3260 3261 void KMComposerWin::slotSpellcheckDoneClearStatus() 3262 { 3263 mStatusbarLabel->clear(); 3264 } 3265 3266 void KMComposerWin::slotIdentityChanged(uint uoid, bool initialChange) 3267 { 3268 if (!mMsg) { 3269 qCDebug(KMAIL_LOG) << "Trying to change identity but mMsg == 0!"; 3270 return; 3271 } 3272 const KIdentityManagementCore::Identity &ident = KMKernel::self()->identityManager()->identityForUoid(uoid); 3273 if (ident.isNull()) { 3274 return; 3275 } 3276 const bool wasModified(isModified()); 3277 Q_EMIT identityChanged(identity()); 3278 if (!ident.fullEmailAddr().isNull()) { 3279 mEdtFrom->setText(ident.fullEmailAddr()); 3280 } 3281 3282 // make sure the From field is shown if it does not contain a valid email address 3283 if (KEmailAddress::firstEmailAddress(from()).isEmpty()) { 3284 mShowHeaders |= HDR_FROM; 3285 } 3286 3287 // remove BCC of old identity and add BCC of new identity (if they differ) 3288 const KIdentityManagementCore::Identity &oldIdentity = KMKernel::self()->identityManager()->identityForUoidOrDefault(mId); 3289 3290 if (ident.organization().isEmpty()) { 3291 mMsg->removeHeader<KMime::Headers::Organization>(); 3292 } else { 3293 auto const organization = new KMime::Headers::Organization; 3294 organization->fromUnicodeString(ident.organization(), "utf-8"); 3295 mMsg->setHeader(organization); 3296 } 3297 3298 addFaceHeaders(ident, mMsg); 3299 3300 if (initialChange) { 3301 if (auto hrd = mMsg->headerByType("X-KMail-Transport")) { 3302 const QString mailtransportStr = hrd->asUnicodeString(); 3303 if (!mailtransportStr.isEmpty()) { 3304 int transportId = mailtransportStr.toInt(); 3305 const Transport *transport = TransportManager::self()->transportById(transportId, false); /*don't return default transport */ 3306 if (transport) { 3307 auto header = new KMime::Headers::Generic("X-KMail-Transport"); 3308 header->fromUnicodeString(QString::number(transport->id()), "utf-8"); 3309 mMsg->setHeader(header); 3310 mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); 3311 } else { 3312 if (auto hrd = mMsg->headerByType("X-KMail-Transport-Name")) { 3313 const QString identityStrName = hrd->asUnicodeString(); 3314 const Transport *transport = TransportManager::self()->transportByName(identityStrName, true); 3315 if (transport) { 3316 auto header = new KMime::Headers::Generic("X-KMail-Transport"); 3317 header->fromUnicodeString(QString::number(transport->id()), "utf-8"); 3318 mMsg->setHeader(header); 3319 mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); 3320 } else { 3321 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); 3322 } 3323 } else { 3324 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); 3325 } 3326 } 3327 } 3328 } else { 3329 const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt(); 3330 const Transport *transport = TransportManager::self()->transportById(transportId, true); 3331 if (transport) { 3332 auto header = new KMime::Headers::Generic("X-KMail-Transport"); 3333 header->fromUnicodeString(QString::number(transport->id()), "utf-8"); 3334 mMsg->setHeader(header); 3335 mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); 3336 } else { 3337 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); 3338 } 3339 } 3340 } else { 3341 const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt(); 3342 const Transport *transport = TransportManager::self()->transportById(transportId, true); 3343 if (!transport) { 3344 mMsg->removeHeader("X-KMail-Transport"); 3345 mComposerBase->transportComboBox()->setCurrentTransport(TransportManager::self()->defaultTransportId()); 3346 } else { 3347 auto header = new KMime::Headers::Generic("X-KMail-Transport"); 3348 header->fromUnicodeString(QString::number(transport->id()), "utf-8"); 3349 mMsg->setHeader(header); 3350 mComposerBase->transportComboBox()->setCurrentTransport(transport->id()); 3351 } 3352 } 3353 3354 const bool fccIsDisabled = ident.disabledFcc(); 3355 if (fccIsDisabled) { 3356 auto header = new KMime::Headers::Generic("X-KMail-FccDisabled"); 3357 header->fromUnicodeString(QStringLiteral("true"), "utf-8"); 3358 mMsg->setHeader(header); 3359 } else { 3360 mMsg->removeHeader("X-KMail-FccDisabled"); 3361 } 3362 mFccFolder->setEnabled(!fccIsDisabled); 3363 3364 mComposerBase->dictionary()->setCurrentByDictionaryName(ident.dictionary()); 3365 slotSpellCheckingLanguage(mComposerBase->dictionary()->currentDictionary()); 3366 if (!mPreventFccOverwrite) { 3367 setFcc(ident.fcc()); 3368 } 3369 // if unmodified, apply new template, if one is set 3370 if (!wasModified && !(ident.templates().isEmpty() && mCustomTemplate.isEmpty()) && !initialChange) { 3371 applyTemplate(uoid, mId, ident, wasModified); 3372 } else { 3373 mComposerBase->identityChanged(ident, oldIdentity, false); 3374 mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage()); 3375 updateComposerAfterIdentityChanged(ident, uoid, wasModified); 3376 } 3377 } 3378 3379 void KMComposerWin::checkOwnKeyExpiry(const KIdentityManagementCore::Identity &ident) 3380 { 3381 mNearExpiryWarning->clearInfo(); 3382 mNearExpiryWarning->hide(); 3383 3384 if (cryptoMessageFormat() & Kleo::AnyOpenPGP) { 3385 if (!ident.pgpEncryptionKey().isEmpty()) { 3386 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpEncryptionKey().constData()); 3387 if (key.isNull() || !key.canEncrypt()) { 3388 mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint", 3389 "Your selected PGP key (%1) doesn't exist in your keyring or is not suitable for encryption.", 3390 QString::fromUtf8(ident.pgpEncryptionKey()))); 3391 mNearExpiryWarning->setWarning(true); 3392 mNearExpiryWarning->show(); 3393 } else { 3394 mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey); 3395 } 3396 } 3397 if (!ident.pgpSigningKey().isEmpty()) { 3398 if (ident.pgpSigningKey() != ident.pgpEncryptionKey()) { 3399 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpSigningKey().constData()); 3400 if (key.isNull() || !key.canSign()) { 3401 mNearExpiryWarning->addInfo(i18nc("The argument is as PGP fingerprint", 3402 "Your selected PGP signing key (%1) doesn't exist in your keyring or is not suitable for signing.", 3403 QString::fromUtf8(ident.pgpSigningKey()))); 3404 mNearExpiryWarning->setWarning(true); 3405 mNearExpiryWarning->show(); 3406 } else { 3407 mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey); 3408 } 3409 } 3410 } 3411 } 3412 3413 if (cryptoMessageFormat() & Kleo::AnySMIME) { 3414 if (!ident.smimeEncryptionKey().isEmpty()) { 3415 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeEncryptionKey().constData()); 3416 if (key.isNull() || !key.canEncrypt()) { 3417 mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint", 3418 "Your selected SMIME key (%1) doesn't exist in your keyring or is not suitable for encryption.", 3419 QString::fromUtf8(ident.smimeEncryptionKey()))); 3420 mNearExpiryWarning->setWarning(true); 3421 mNearExpiryWarning->show(); 3422 } else { 3423 mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnEncryptionKey); 3424 } 3425 } 3426 if (!ident.smimeSigningKey().isEmpty()) { 3427 if (ident.smimeSigningKey() != ident.smimeEncryptionKey()) { 3428 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeSigningKey().constData()); 3429 if (key.isNull() || !key.canSign()) { 3430 mNearExpiryWarning->addInfo(i18nc("The argument is as SMIME fingerprint", 3431 "Your selected SMIME signing key (%1) doesn't exist in your keyring or is not suitable for signing.", 3432 QString::fromUtf8(ident.smimeSigningKey()))); 3433 mNearExpiryWarning->setWarning(true); 3434 mNearExpiryWarning->show(); 3435 } else { 3436 mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::OwnSigningKey); 3437 } 3438 } 3439 } 3440 } 3441 } 3442 3443 void KMComposerWin::updateComposerAfterIdentityChanged(const KIdentityManagementCore::Identity &ident, uint uoid, bool wasModified) 3444 { 3445 // disable certain actions if there is no PGP user identity set 3446 // for this profile 3447 bool bPGPEncryptionKey = !ident.pgpEncryptionKey().isEmpty(); 3448 bool bPGPSigningKey = !ident.pgpSigningKey().isEmpty(); 3449 bool bSMIMEEncryptionKey = !ident.smimeEncryptionKey().isEmpty(); 3450 bool bSMIMESigningKey = !ident.smimeSigningKey().isEmpty(); 3451 if (cryptoMessageFormat() & Kleo::AnyOpenPGP) { 3452 if (bPGPEncryptionKey) { 3453 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpEncryptionKey().constData()); 3454 if (key.isNull() || !key.canEncrypt()) { 3455 bPGPEncryptionKey = false; 3456 } 3457 } 3458 if (bPGPSigningKey) { 3459 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.pgpSigningKey().constData()); 3460 if (key.isNull() || !key.canSign()) { 3461 bPGPSigningKey = false; 3462 } 3463 } 3464 } else { 3465 bPGPEncryptionKey = false; 3466 bPGPSigningKey = false; 3467 } 3468 3469 if (cryptoMessageFormat() & Kleo::AnySMIME) { 3470 if (bSMIMEEncryptionKey) { 3471 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeEncryptionKey().constData()); 3472 if (key.isNull() || !key.canEncrypt()) { 3473 bSMIMEEncryptionKey = false; 3474 } 3475 } 3476 if (bSMIMESigningKey) { 3477 auto const key = mKeyCache->findByKeyIDOrFingerprint(ident.smimeSigningKey().constData()); 3478 if (key.isNull() || !key.canSign()) { 3479 bSMIMESigningKey = false; 3480 } 3481 } 3482 } else { 3483 bSMIMEEncryptionKey = false; 3484 bSMIMESigningKey = false; 3485 } 3486 3487 bool bNewIdentityHasSigningKey = bPGPSigningKey || bSMIMESigningKey; 3488 bool bNewIdentityHasEncryptionKey = bPGPEncryptionKey || bSMIMEEncryptionKey; 3489 3490 if (!mKeyCache->initialized()) { 3491 // We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated. 3492 mKeyCache->startKeyListing(); 3493 connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this, &ident]() { 3494 checkOwnKeyExpiry(ident); 3495 runKeyResolver(); 3496 }); 3497 } else { 3498 checkOwnKeyExpiry(ident); 3499 } 3500 3501 // save the state of the sign and encrypt button 3502 if (!bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey) { 3503 mLastEncryptActionState = mEncryptionState.encrypt(); 3504 } 3505 3506 mSignAction->setEnabled(bNewIdentityHasSigningKey); 3507 3508 if (!bNewIdentityHasSigningKey && mLastIdentityHasSigningKey) { 3509 mLastSignActionState = sign(); 3510 setSigning(false); 3511 } 3512 // restore the last state of the sign and encrypt button 3513 if (bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey) { 3514 setSigning(mLastSignActionState); 3515 } 3516 3517 mCryptoModuleAction->setCurrentItem(format2cb(Kleo::stringToCryptoMessageFormat(ident.preferredCryptoMessageFormat()))); 3518 slotSelectCryptoModule(true); 3519 3520 mLastIdentityHasSigningKey = bNewIdentityHasSigningKey; 3521 mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey; 3522 const KIdentityManagementCore::Signature sig = const_cast<KIdentityManagementCore::Identity &>(ident).signature(); 3523 bool isEnabledSignature = sig.isEnabledSignature(); 3524 mAppendSignature->setEnabled(isEnabledSignature); 3525 mPrependSignature->setEnabled(isEnabledSignature); 3526 mInsertSignatureAtCursorPosition->setEnabled(isEnabledSignature); 3527 3528 mId = uoid; 3529 3530 mEncryptionState.setPossibleEncrypt(true); 3531 changeCryptoAction(); 3532 mEncryptionState.unsetOverride(); 3533 mEncryptionState.setPossibleEncrypt(mEncryptionState.possibleEncrypt() && bNewIdentityHasEncryptionKey); 3534 mEncryptionState.setAutoEncrypt(ident.pgpAutoEncrypt()); 3535 3536 // make sure the From and BCC fields are shown if necessary 3537 rethinkFields(false); 3538 setModified(wasModified); 3539 3540 if (ident.pgpAutoEncrypt() && mKeyCache->initialized()) { 3541 runKeyResolver(); 3542 } 3543 } 3544 3545 auto findSendersUid(const std::string &addrSpec, const std::vector<GpgME::UserID> &userIds) 3546 { 3547 return std::find_if(userIds.cbegin(), userIds.cend(), [&addrSpec](const auto &uid) { 3548 return uid.addrSpec() == addrSpec || (uid.addrSpec().empty() && std::string(uid.email()) == addrSpec) 3549 || (uid.addrSpec().empty() && (!uid.email() || !*uid.email()) && uid.name() == addrSpec); 3550 }); 3551 } 3552 3553 std::unique_ptr<Kleo::KeyResolverCore> KMComposerWin::fillKeyResolver() 3554 { 3555 const auto ident = identity(); 3556 auto keyResolverCore = std::make_unique<Kleo::KeyResolverCore>(true, sign()); 3557 3558 keyResolverCore->setMinimumValidity(GpgME::UserID::Unknown); 3559 3560 QStringList signingKeys, encryptionKeys; 3561 3562 if (cryptoMessageFormat() & Kleo::AnyOpenPGP) { 3563 if (!ident.pgpSigningKey().isEmpty()) { 3564 signingKeys.push_back(QLatin1StringView(ident.pgpSigningKey())); 3565 } 3566 if (!ident.pgpEncryptionKey().isEmpty()) { 3567 encryptionKeys.push_back(QLatin1StringView(ident.pgpEncryptionKey())); 3568 } 3569 } 3570 3571 if (cryptoMessageFormat() & Kleo::AnySMIME) { 3572 if (!ident.smimeSigningKey().isEmpty()) { 3573 signingKeys.push_back(QLatin1StringView(ident.smimeSigningKey())); 3574 } 3575 if (!ident.smimeEncryptionKey().isEmpty()) { 3576 encryptionKeys.push_back(QLatin1StringView(ident.smimeEncryptionKey())); 3577 } 3578 } 3579 3580 keyResolverCore->setSender(ident.fullEmailAddr()); 3581 keyResolverCore->setSigningKeys(signingKeys); 3582 keyResolverCore->setOverrideKeys({{GpgME::UnknownProtocol, {{keyResolverCore->normalizedSender(), encryptionKeys}}}}); 3583 3584 QStringList recipients; 3585 const auto lst = mComposerBase->recipientsEditor()->lines(); 3586 for (auto line : lst) { 3587 auto recipient = line->data().dynamicCast<MessageComposer::Recipient>(); 3588 recipients.push_back(recipient->email()); 3589 } 3590 3591 keyResolverCore->setRecipients(recipients); 3592 return keyResolverCore; 3593 } 3594 3595 void KMComposerWin::slotEncryptionButtonIconUpdate() 3596 { 3597 const auto state = mEncryptionState.encrypt(); 3598 const auto setByUser = mEncryptionState.override(); 3599 const auto acceptedSolution = mEncryptionState.acceptedSolution(); 3600 3601 auto icon = QIcon::fromTheme(QStringLiteral("document-encrypt")); 3602 QString tooltip; 3603 if (state) { 3604 tooltip = i18nc("@info:tooltip", "Encrypt"); 3605 } else { 3606 tooltip = i18nc("@info:tooltip", "Not Encrypt"); 3607 icon = QIcon::fromTheme(QStringLiteral("document-decrypt")); 3608 } 3609 3610 if (acceptedSolution) { 3611 auto overlay = QIcon::fromTheme(QStringLiteral("emblem-added")); 3612 if (state) { 3613 overlay = QIcon::fromTheme(QStringLiteral("emblem-checked")); 3614 } 3615 icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner); 3616 } else { 3617 if (state && setByUser) { 3618 auto overlay = QIcon::fromTheme(QStringLiteral("emblem-warning")); 3619 icon = KIconUtils::addOverlay(icon, overlay, Qt::BottomRightCorner); 3620 } 3621 } 3622 mEncryptAction->setIcon(icon); 3623 mEncryptAction->setToolTip(tooltip); 3624 } 3625 3626 void KMComposerWin::runKeyResolver() 3627 { 3628 const auto ident = identity(); 3629 auto keyResolverCore = fillKeyResolver(); 3630 auto result = keyResolverCore->resolve(); 3631 3632 QStringList autocryptKeys; 3633 QStringList gossipKeys; 3634 3635 if (!(result.flags & Kleo::KeyResolverCore::AllResolved)) { 3636 if (result.flags & Kleo::KeyResolverCore::OpenPGPOnly && ident.autocryptEnabled()) { 3637 bool allResolved = true; 3638 const auto storage = MessageCore::AutocryptStorage::self(); 3639 for (const auto &recipient : result.solution.encryptionKeys.keys()) { 3640 const auto key = result.solution.encryptionKeys[recipient]; 3641 if (key.size() > 0) { // There are already keys found 3642 continue; 3643 } 3644 if (recipient == keyResolverCore->normalizedSender()) { // Don't care about own key as we show warnings in another way 3645 continue; 3646 } 3647 const auto rec = storage->getRecipient(recipient.toUtf8()); 3648 GpgME::Key autocryptKey; 3649 if (rec) { 3650 const auto key = rec->gpgKey(); 3651 if (!key.isBad() && key.canEncrypt()) { 3652 autocryptKey = key; 3653 } else { 3654 const auto gossipKey = rec->gossipKey(); 3655 if (!gossipKey.isBad() && gossipKey.canEncrypt()) { 3656 gossipKeys.push_back(recipient); 3657 autocryptKey = gossipKey; 3658 } 3659 } 3660 } 3661 if (!autocryptKey.isNull()) { 3662 autocryptKeys.push_back(recipient); 3663 result.solution.encryptionKeys[recipient].push_back(autocryptKey); 3664 } else { 3665 allResolved = false; 3666 } 3667 } 3668 if (allResolved) { 3669 result.flags = Kleo::KeyResolverCore::SolutionFlags(result.flags | Kleo::KeyResolverCore::AllResolved); 3670 } 3671 } 3672 } 3673 3674 const auto lst = mComposerBase->recipientsEditor()->lines(); 3675 3676 if (lst.size() == 1) { 3677 const auto line = qobject_cast<MessageComposer::RecipientLineNG *>(lst.first()); 3678 if (line->recipientsCount() == 0) { 3679 mEncryptionState.setAcceptedSolution(false); 3680 return; 3681 } 3682 } 3683 3684 mEncryptionState.setAcceptedSolution(result.flags & Kleo::KeyResolverCore::AllResolved); 3685 3686 for (auto line_ : lst) { 3687 auto line = qobject_cast<MessageComposer::RecipientLineNG *>(line_); 3688 Q_ASSERT(line); 3689 auto recipient = line->data().dynamicCast<MessageComposer::Recipient>(); 3690 3691 QString dummy; 3692 QString addrSpec; 3693 if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) { 3694 addrSpec = recipient->email(); 3695 } 3696 3697 auto resolvedKeys = result.solution.encryptionKeys[addrSpec]; 3698 GpgME::Key key; 3699 if (resolvedKeys.size() == 0) { // no key found for recipient 3700 // Search for any key, also for not accepted ons, to at least give the user more info. 3701 key = Kleo::KeyCache::instance()->findBestByMailBox(addrSpec.toUtf8().constData(), GpgME::UnknownProtocol, Kleo::KeyCache::KeyUsage::Encrypt); 3702 key.update(); // We need tofu information for key. 3703 recipient->setKey(key); 3704 } else { // A key was found for recipient 3705 key = resolvedKeys.front(); 3706 if (recipient->key().primaryFingerprint() != key.primaryFingerprint()) { 3707 key.update(); // We need tofu information for key. 3708 recipient->setKey(key); 3709 } 3710 } 3711 3712 const bool autocryptKey = autocryptKeys.contains(addrSpec); 3713 const bool gossipKey = gossipKeys.contains(addrSpec); 3714 annotateRecipientEditorLineWithCryptoInfo(line, autocryptKey, gossipKey); 3715 3716 if (!key.isNull()) { 3717 mComposerBase->expiryChecker()->checkKey(key, Kleo::ExpiryChecker::EncryptionKey); 3718 } 3719 } 3720 } 3721 3722 void KMComposerWin::annotateRecipientEditorLineWithCryptoInfo(MessageComposer::RecipientLineNG *line, bool autocryptKey, bool gossipKey) 3723 { 3724 auto recipient = line->data().dynamicCast<MessageComposer::Recipient>(); 3725 const auto key = recipient->key(); 3726 3727 const auto showCryptoIndicator = KMailSettings::self()->showCryptoLabelIndicator(); 3728 const auto hasOverride = mEncryptionState.hasOverride(); 3729 const auto encrypt = mEncryptionState.encrypt(); 3730 3731 const bool showPositiveIcons = showCryptoIndicator && encrypt; 3732 const bool showAllIcons = showCryptoIndicator && hasOverride && encrypt; 3733 3734 QString dummy; 3735 QString addrSpec; 3736 bool invalidEmail = false; 3737 if (KEmailAddress::splitAddress(recipient->email(), dummy, addrSpec, dummy) != KEmailAddress::AddressOk) { 3738 invalidEmail = true; 3739 addrSpec = recipient->email(); 3740 } 3741 3742 if (key.isNull()) { 3743 recipient->setEncryptionAction(Kleo::Impossible); 3744 if (showAllIcons && !invalidEmail) { 3745 const auto icon = QIcon::fromTheme(QStringLiteral("emblem-error")); 3746 line->setIcon(icon, i18nc("@info:tooltip", "No key found for the recipient.")); 3747 } else { 3748 line->setIcon(QIcon()); 3749 } 3750 line->setProperty("keyStatus", invalidEmail ? InProgress : NoKey); 3751 return; 3752 } 3753 3754 KMComposerWin::CryptoKeyState keyState = KeyOk; 3755 3756 if (recipient->encryptionAction() != Kleo::DoIt) { 3757 recipient->setEncryptionAction(Kleo::DoIt); 3758 } 3759 3760 if (autocryptKey) { // We found an Autocrypt key for recipient 3761 QIcon icon = QIcon::fromTheme(QStringLiteral("emblem-success")); 3762 QString tooltip; 3763 const auto storage = MessageCore::AutocryptStorage::self(); 3764 const auto rec = storage->getRecipient(addrSpec.toUtf8()); 3765 if (gossipKey) { // We found an Autocrypt gossip key for recipient 3766 icon = QIcon::fromTheme(QStringLiteral("emblem-informations")); 3767 tooltip = i18nc("@info:tooltip", 3768 "Autocrypt gossip key is used for this recipient. We got this key from 3rd party recipients. " 3769 "This key is not verified."); 3770 } else if (rec->prefer_encrypt()) { 3771 tooltip = i18nc("@info:tooltip", 3772 "Autocrypt key is used for this recipient. " 3773 "This key is not verified. The recipient prefers encrypted replies."); 3774 } else { 3775 tooltip = i18nc("@info:tooltip", 3776 "Autocrypt key is used for this recipient. " 3777 "This key is not verified. The recipient does not prefer encrypted replies."); 3778 } 3779 if (showAllIcons) { 3780 line->setIcon(icon, tooltip); 3781 } else { 3782 line->setIcon(QIcon()); 3783 } 3784 3785 } else { 3786 QIcon icon; 3787 QString tooltip; 3788 3789 const auto uids = key.userIDs(); 3790 const auto _uid = findSendersUid(addrSpec.toStdString(), uids); 3791 GpgME::UserID uid; 3792 if (_uid == uids.cend()) { 3793 uid = key.userID(0); 3794 } else { 3795 uid = *_uid; 3796 } 3797 3798 const auto trustLevel = Kleo::trustLevel(uid); 3799 switch (trustLevel) { 3800 case Kleo::Level0: 3801 if (uid.tofuInfo().isNull()) { 3802 tooltip = i18nc("@info:tooltip", 3803 "The encryption key is not trusted. It hasn't enough validity. " 3804 "You can sign the key, if you communicated the fingerprint by another channel. " 3805 "Click the icon for details."); 3806 keyState = NoKey; 3807 } else { 3808 switch (uid.tofuInfo().validity()) { 3809 case GpgME::TofuInfo::NoHistory: 3810 tooltip = i18nc("@info:tooltip", 3811 "The encryption key is not trusted. " 3812 "It hasn't been used anywhere to guarantee it belongs to the stated person. " 3813 "By using the key will be trusted more. " 3814 "Or you can sign the key, if you communicated the fingerprint by another channel. " 3815 "Click the icon for details."); 3816 break; 3817 case GpgME::TofuInfo::Conflict: 3818 tooltip = i18nc("@info:tooltip", 3819 "The encryption key is not trusted. It has conflicting TOFU data. " 3820 "Click the icon for details."); 3821 keyState = NoKey; 3822 break; 3823 case GpgME::TofuInfo::ValidityUnknown: 3824 tooltip = i18nc("@info:tooltip", 3825 "The encryption key is not trusted. It has unknown validity in TOFU data. " 3826 "Click the icon for details."); 3827 keyState = NoKey; 3828 break; 3829 default: 3830 tooltip = i18nc("@info:tooltip", 3831 "The encryption key is not trusted. The key is marked as bad. " 3832 "Click the icon for details."); 3833 keyState = NoKey; 3834 } 3835 } 3836 break; 3837 case Kleo::Level1: 3838 tooltip = i18nc("@info:tooltip", 3839 "The encryption key is only marginally trusted and hasn't been used enough time to guarantee it belongs to the stated person. " 3840 "By using the key will be trusted more. " 3841 "Or you can sign the key, if you communicated the fingerprint by another channel. " 3842 "Click the icon for details."); 3843 break; 3844 case Kleo::Level2: 3845 if (uid.tofuInfo().isNull()) { 3846 tooltip = i18nc("@info:tooltip", 3847 "The encryption key is only marginally trusted. " 3848 "You can sign the key, if you communicated the fingerprint by another channel. " 3849 "Click the icon for details."); 3850 } else { 3851 tooltip = 3852 i18nc("@info:tooltip", 3853 "The encryption key is only marginally trusted, but has been used enough times to be very likely controlled by the stated person. " 3854 "By using the key will be trusted more. " 3855 "Or you can sign the key, if you communicated the fingerprint by another channel. " 3856 "Click the icon for details."); 3857 } 3858 break; 3859 case Kleo::Level3: 3860 tooltip = i18nc("@info:tooltip", 3861 "The encryption key is fully trusted. You can raise the security level, by signing the key. " 3862 "Click the icon for details."); 3863 break; 3864 case Kleo::Level4: 3865 tooltip = i18nc("@info:tooltip", 3866 "The encryption key is ultimately trusted or is signed by another ultimately trusted key. " 3867 "Click the icon for details."); 3868 break; 3869 default: 3870 Q_UNREACHABLE(); 3871 } 3872 3873 if (keyState == NoKey) { 3874 mEncryptionState.setAcceptedSolution(false); 3875 if (showAllIcons) { 3876 line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error")), tooltip); 3877 } else { 3878 line->setIcon(QIcon()); 3879 } 3880 } else if (trustLevel == Kleo::Level0 && encrypt) { 3881 line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")), tooltip); 3882 } else if (showPositiveIcons) { 3883 // Magically, the icon name maps precisely to each trust level 3884 // line->setIcon(QIcon::fromTheme(QStringLiteral("gpg-key-trust-level-%1").arg(trustLevel)), tooltip); 3885 line->setIcon(QIcon::fromTheme(QStringLiteral("emblem-success")), tooltip); 3886 } else { 3887 line->setIcon(QIcon()); 3888 } 3889 } 3890 3891 if (line->property("keyStatus") != keyState) { 3892 line->setProperty("keyStatus", keyState); 3893 } 3894 } 3895 3896 void KMComposerWin::slotSpellcheckConfig() 3897 { 3898 static_cast<KMComposerEditorNg *>(mComposerBase->editor())->showSpellConfigDialog(QStringLiteral("kmail2rc")); 3899 } 3900 3901 void KMComposerWin::slotEditToolbars() 3902 { 3903 QPointer<KEditToolBar> dlg = new KEditToolBar(guiFactory(), this); 3904 3905 connect(dlg.data(), &KEditToolBar::newToolBarConfig, this, &KMComposerWin::slotUpdateToolbars); 3906 3907 dlg->exec(); 3908 delete dlg; 3909 } 3910 3911 void KMComposerWin::slotUpdateToolbars() 3912 { 3913 createGUI(QStringLiteral("kmcomposerui.rc")); 3914 applyMainWindowSettings(KMKernel::self()->config()->group(QStringLiteral("Composer"))); 3915 } 3916 3917 void KMComposerWin::slotEditKeys() 3918 { 3919 KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); 3920 } 3921 3922 void KMComposerWin::setFocusToEditor() 3923 { 3924 // The cursor position is already set by setMsg(), so we only need to set the 3925 // focus here. 3926 mComposerBase->editor()->setFocus(); 3927 } 3928 3929 void KMComposerWin::setFocusToSubject() 3930 { 3931 mEdtSubject->setFocus(); 3932 } 3933 3934 void KMComposerWin::slotCompletionModeChanged(KCompletion::CompletionMode mode) 3935 { 3936 KMailSettings::self()->setCompletionMode(static_cast<int>(mode)); 3937 3938 // sync all the lineedits to the same completion mode 3939 mEdtFrom->setCompletionMode(mode); 3940 mComposerBase->recipientsEditor()->setCompletionMode(mode); 3941 } 3942 3943 void KMComposerWin::slotConfigChanged() 3944 { 3945 readConfig(true /*reload*/); 3946 mComposerBase->updateAutoSave(); 3947 rethinkFields(); 3948 slotWordWrapToggled(mWordWrapAction->isChecked()); 3949 } 3950 3951 /* 3952 * checks if the drafts-folder has been deleted 3953 * that is not nice so we set the system-drafts-folder 3954 */ 3955 void KMComposerWin::slotFolderRemoved(const Akonadi::Collection &col) 3956 { 3957 qCDebug(KMAIL_LOG) << "you killed me."; 3958 // TODO: need to handle templates here? 3959 if ((mFolder.isValid()) && (col.id() == mFolder.id())) { 3960 mFolder = CommonKernel->draftsCollectionFolder(); 3961 qCDebug(KMAIL_LOG) << "restoring drafts to" << mFolder.id(); 3962 } else if (col.id() == mFccFolder->collection().id()) { 3963 qCDebug(KMAIL_LOG) << "FCC was removed " << col.id(); 3964 mFccFolder->setCollection(CommonKernel->sentCollectionFolder()); 3965 mIncorrectIdentityFolderWarning->fccIsInvalid(); 3966 } 3967 } 3968 3969 void KMComposerWin::slotOverwriteModeChanged() 3970 { 3971 const bool overwriteMode = mComposerBase->editor()->overwriteMode(); 3972 mComposerBase->editor()->setCursorWidth(overwriteMode ? 5 : 1); 3973 mStatusBarLabelToggledOverrideMode->setToggleMode(overwriteMode); 3974 } 3975 3976 void KMComposerWin::slotCursorPositionChanged() 3977 { 3978 // Change Line/Column info in status bar 3979 const int line = mComposerBase->editor()->linePosition() + 1; 3980 const int col = mComposerBase->editor()->columnNumber() + 1; 3981 QString temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line); 3982 mCursorLineLabel->setText(temp); 3983 temp = i18n(" Column: %1 ", col); 3984 mCursorColumnLabel->setText(temp); 3985 3986 // Show link target in status bar 3987 if (mComposerBase->editor()->textCursor().charFormat().isAnchor()) { 3988 const QString text = mComposerBase->editor()->composerControler()->currentLinkText() + QLatin1StringView(" -> ") 3989 + mComposerBase->editor()->composerControler()->currentLinkUrl(); 3990 mStatusbarLabel->setText(text); 3991 } else { 3992 mStatusbarLabel->clear(); 3993 } 3994 } 3995 3996 void KMComposerWin::recipientEditorSizeHintChanged() 3997 { 3998 QTimer::singleShot(1, this, &KMComposerWin::setMaximumHeaderSize); 3999 } 4000 4001 void KMComposerWin::setMaximumHeaderSize() 4002 { 4003 mHeadersArea->setMaximumHeight(mHeadersArea->sizeHint().height()); 4004 } 4005 4006 void KMComposerWin::updateSignatureAndEncryptionStateIndicators() 4007 { 4008 mCryptoStateIndicatorWidget->updateSignatureAndEncrypionStateIndicators(sign(), mEncryptionState.encrypt()); 4009 if (sign() || mEncryptionState.encrypt()) { 4010 mComposerBase->editor()->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge})); 4011 mComposerBase->editor()->setProperty("_breeze_force_frame", true); 4012 } else { 4013 mComposerBase->editor()->setProperty("_breeze_borders_sides", QVariant{}); 4014 mComposerBase->editor()->setProperty("_breeze_force_frame", false); 4015 } 4016 } 4017 4018 void KMComposerWin::slotDictionaryLanguageChanged(const QString &language) 4019 { 4020 mComposerBase->dictionary()->setCurrentByDictionary(language); 4021 } 4022 4023 void KMComposerWin::slotFccFolderChanged(const Akonadi::Collection &collection) 4024 { 4025 mComposerBase->setFcc(collection); 4026 mComposerBase->editor()->document()->setModified(true); 4027 } 4028 4029 void KMComposerWin::slotSaveAsFile() 4030 { 4031 auto job = new SaveAsFileJob(this); 4032 job->setParentWidget(this); 4033 job->setHtmlMode(mComposerBase->editor()->textMode() == MessageComposer::RichTextComposerNg::Rich); 4034 job->setTextDocument(mComposerBase->editor()->document()); 4035 job->start(); 4036 // not necessary to delete it. It done in SaveAsFileJob 4037 } 4038 4039 void KMComposerWin::slotAttachMissingFile() 4040 { 4041 mComposerBase->attachmentController()->showAddAttachmentFileDialog(); 4042 } 4043 4044 void KMComposerWin::slotVerifyMissingAttachmentTimeout() 4045 { 4046 if (mComposerBase->hasMissingAttachments(KMailSettings::self()->attachmentKeywords())) { 4047 mAttachmentMissing->animatedShow(); 4048 } 4049 } 4050 4051 void KMComposerWin::slotExplicitClosedMissingAttachment() 4052 { 4053 if (mVerifyMissingAttachment) { 4054 mVerifyMissingAttachment->stop(); 4055 delete mVerifyMissingAttachment; 4056 mVerifyMissingAttachment = nullptr; 4057 } 4058 } 4059 4060 void KMComposerWin::addExtraCustomHeaders(const QMap<QByteArray, QString> &headers) 4061 { 4062 mExtraHeaders = headers; 4063 } 4064 4065 bool KMComposerWin::processModifyText(QKeyEvent *event) 4066 { 4067 return mPluginEditorManagerInterface->processProcessKeyEvent(event); 4068 } 4069 4070 MessageComposer::PluginEditorConvertTextInterface::ConvertTextStatus KMComposerWin::convertPlainText(MessageComposer::TextPart *textPart) 4071 { 4072 return mPluginEditorConvertTextManagerInterface->convertTextToFormat(textPart); 4073 } 4074 4075 void KMComposerWin::slotExternalEditorStarted() 4076 { 4077 mComposerBase->identityCombo()->setEnabled(false); 4078 mExternalEditorWarning->show(); 4079 } 4080 4081 void KMComposerWin::slotExternalEditorClosed() 4082 { 4083 mComposerBase->identityCombo()->setEnabled(true); 4084 mExternalEditorWarning->hide(); 4085 } 4086 4087 void KMComposerWin::slotInsertShortUrl(const QString &url) 4088 { 4089 mComposerBase->editor()->composerControler()->insertLink(url); 4090 } 4091 4092 void KMComposerWin::slotTransportChanged() 4093 { 4094 mComposerBase->editor()->document()->setModified(true); 4095 } 4096 4097 void KMComposerWin::slotFollowUpMail(bool toggled) 4098 { 4099 if (toggled) { 4100 QPointer<MessageComposer::FollowUpReminderSelectDateDialog> dlg = new MessageComposer::FollowUpReminderSelectDateDialog(this); 4101 if (dlg->exec()) { 4102 mComposerBase->setFollowUpDate(dlg->selectedDate()); 4103 mComposerBase->setFollowUpCollection(dlg->collection()); 4104 } else { 4105 mFollowUpToggleAction->setChecked(false); 4106 } 4107 delete dlg; 4108 } else { 4109 mComposerBase->clearFollowUp(); 4110 } 4111 } 4112 4113 void KMComposerWin::slotSnippetWidgetVisibilityChanged(bool b) 4114 { 4115 mSnippetWidget->setVisible(b); 4116 mSnippetSplitterCollapser->setVisible(b); 4117 } 4118 4119 void KMComposerWin::slotOverwriteModeWasChanged(bool state) 4120 { 4121 mComposerBase->editor()->setCursorWidth(state ? 5 : 1); 4122 mComposerBase->editor()->setOverwriteMode(state); 4123 } 4124 4125 QList<KToggleAction *> KMComposerWin::customToolsList() const 4126 { 4127 return mCustomToolsWidget->actionList(); 4128 } 4129 4130 QList<QAction *> KMComposerWin::pluginToolsActionListForPopupMenu() const 4131 { 4132 return mPluginEditorManagerInterface->actionsType(MessageComposer::PluginActionType::PopupMenu) 4133 + mPluginEditorConvertTextManagerInterface->actionsType(MessageComposer::PluginActionType::PopupMenu); 4134 } 4135 4136 void KMComposerWin::slotRecipientEditorLineAdded(KPIM::MultiplyingLine *line_) 4137 { 4138 auto line = qobject_cast<MessageComposer::RecipientLineNG *>(line_); 4139 Q_ASSERT(line); 4140 4141 connect(line, &MessageComposer::RecipientLineNG::countChanged, this, [this, line]() { 4142 this->slotRecipientAdded(line); 4143 }); 4144 connect(line, &MessageComposer::RecipientLineNG::iconClicked, this, [this, line]() { 4145 this->slotRecipientLineIconClicked(line); 4146 }); 4147 connect(line, &MessageComposer::RecipientLineNG::destroyed, this, &KMComposerWin::slotRecipientEditorFocusChanged, Qt::QueuedConnection); 4148 connect( 4149 line, 4150 &MessageComposer::RecipientLineNG::activeChanged, 4151 this, 4152 [this, line]() { 4153 this->slotRecipientFocusLost(line); 4154 }, 4155 Qt::QueuedConnection); 4156 4157 slotRecipientEditorFocusChanged(); 4158 } 4159 4160 bool KMComposerWin::sign() const 4161 { 4162 return mSignAction->isChecked(); 4163 } 4164 4165 void KMComposerWin::slotRecipientEditorFocusChanged() 4166 { 4167 if (!mEncryptionState.possibleEncrypt()) { 4168 return; 4169 } 4170 4171 if (mKeyCache->initialized()) { 4172 mRunKeyResolverTimer->stop(); 4173 runKeyResolver(); 4174 } 4175 } 4176 4177 void KMComposerWin::slotRecipientLineIconClicked(MessageComposer::RecipientLineNG *line) 4178 { 4179 const auto recipient = line->data().dynamicCast<MessageComposer::Recipient>(); 4180 4181 if (!recipient->key().isNull()) { 4182 const QString exec = QStandardPaths::findExecutable(QStringLiteral("kleopatra")); 4183 if (exec.isEmpty() 4184 || !QProcess::startDetached(exec, 4185 {QStringLiteral("--query"), 4186 QString::fromLatin1(recipient->key().primaryFingerprint()), 4187 QStringLiteral("--parent-windowid"), 4188 QString::number(winId())})) { 4189 qCWarning(KMAIL_LOG) << "Unable to execute kleopatra"; 4190 } 4191 return; 4192 } 4193 4194 const auto msg = i18nc( 4195 "if in your language something like " 4196 "'certificate(s)' is not possible please " 4197 "use the plural in the translation", 4198 "<qt>No valid and trusted encryption certificate was " 4199 "found for \"%1\".<br/><br/>" 4200 "Select the certificate(s) which should " 4201 "be used for this recipient. If there is no suitable certificate in the list " 4202 "you can also search for external certificates by clicking the button: " 4203 "search for external certificates.</qt>", 4204 recipient->name().isEmpty() ? recipient->email() : recipient->name()); 4205 4206 const bool opgp = containsOpenPGP(cryptoMessageFormat()); 4207 const bool x509 = containsSMIME(cryptoMessageFormat()); 4208 4209 QPointer<Kleo::KeySelectionDialog> dlg = new Kleo::KeySelectionDialog( 4210 i18n("Encryption Key Selection"), 4211 msg, 4212 recipient->email(), 4213 {}, 4214 Kleo::KeySelectionDialog::ValidEncryptionKeys | (opgp ? Kleo::KeySelectionDialog::OpenPGPKeys : 0) | (x509 ? Kleo::KeySelectionDialog::SMIMEKeys : 0), 4215 false, // multi-selection 4216 false); // "remember choice" box; 4217 4218 dlg->open(); 4219 4220 connect(dlg, &QDialog::accepted, this, [dlg, recipient, line, this]() { 4221 auto key = dlg->selectedKey(); 4222 key.update(); // We need tofu information for key. 4223 recipient->setKey(key); 4224 annotateRecipientEditorLineWithCryptoInfo(line, false, false); 4225 }); 4226 } 4227 4228 void KMComposerWin::slotRecipientAdded(MessageComposer::RecipientLineNG *line) 4229 { 4230 // Encryption is possible not possible to find encryption keys. 4231 if (!mEncryptionState.possibleEncrypt()) { 4232 return; 4233 } 4234 4235 if (line->recipientsCount() == 0) { 4236 return; 4237 } 4238 4239 if (!mKeyCache->initialized()) { 4240 if (line->property("keyLookupJob").toBool()) { 4241 return; 4242 } 4243 4244 line->setProperty("keyLookupJob", true); 4245 // We need to start key listing on our own othweise KMail will crash and we want to wait till the cache is populated. 4246 connect(mKeyCache.get(), &Kleo::KeyCache::keyListingDone, this, [this, line]() { 4247 slotRecipientAdded(line); 4248 }); 4249 return; 4250 } 4251 4252 if (mKeyCache->initialized()) { 4253 mRunKeyResolverTimer->start(); 4254 } 4255 } 4256 4257 void KMComposerWin::slotRecipientFocusLost(MessageComposer::RecipientLineNG *line) 4258 { 4259 // Not possible to find encryption keys. 4260 if (!mEncryptionState.possibleEncrypt()) { 4261 return; 4262 } 4263 4264 if (line->recipientsCount() == 0) { 4265 return; 4266 } 4267 4268 if (mKeyCache->initialized()) { 4269 mRunKeyResolverTimer->start(); 4270 } 4271 } 4272 4273 void KMComposerWin::slotIdentityDeleted(uint uoid) 4274 { 4275 if (currentIdentity() == uoid) { 4276 mIncorrectIdentityFolderWarning->identityInvalid(); 4277 } 4278 } 4279 4280 void KMComposerWin::slotTransportRemoved(int id, const QString &name) 4281 { 4282 Q_UNUSED(name) 4283 if (mComposerBase->transportComboBox()->currentTransportId() == id) { 4284 mIncorrectIdentityFolderWarning->mailTransportIsInvalid(); 4285 } 4286 } 4287 4288 void KMComposerWin::slotSelectionChanged() 4289 { 4290 Q_EMIT mPluginEditorManagerInterface->textSelectionChanged(mRichTextEditorWidget->editor()->textCursor().hasSelection()); 4291 } 4292 4293 void KMComposerWin::slotMessage(const QString &str) 4294 { 4295 KMessageBox::information(this, str, i18n("Plugin Editor Information")); 4296 } 4297 4298 void KMComposerWin::slotEditorPluginInsertText(const QString &str) 4299 { 4300 mGlobalAction->slotInsertText(str); 4301 } 4302 4303 #include "moc_kmcomposerwin.cpp"