Messagelib

composerviewbase.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
3 SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "composerviewbase.h"
9
10#include "attachment/attachmentcontrollerbase.h"
11#include "attachment/attachmentmodel.h"
12#include "composer-ng/richtextcomposerng.h"
13#include "composer-ng/richtextcomposersignatures.h"
14#include "composer.h"
15#include "composer/keyresolver.h"
16#include "composer/signaturecontroller.h"
17#include "draftstatus/draftstatus.h"
18#include "imagescaling/imagescalingutils.h"
19#include "job/emailaddressresolvejob.h"
20#include "part/globalpart.h"
21#include "part/infopart.h"
22#include "utils/kleo_util.h"
23#include "utils/util.h"
24#include "utils/util_p.h"
25#include <KPIMTextEdit/RichTextComposerControler>
26#include <KPIMTextEdit/RichTextComposerImages>
27
28#include "sendlater/sendlatercreatejob.h"
29#include "sendlater/sendlaterinfo.h"
30
31#include <PimCommonAkonadi/RecentAddresses>
32
33#include "settings/messagecomposersettings.h"
34#include <MessageComposer/RecipientsEditor>
35
36#include <KCursorSaver>
37#include <KIdentityManagementCore/Identity>
38#include <MimeTreeParser/ObjectTreeParser>
39#include <MimeTreeParser/SimpleObjectTreeSource>
40#include <Sonnet/DictionaryComboBox>
41
42#include <MessageCore/AutocryptStorage>
43#include <MessageCore/StringUtil>
44
45#include <Akonadi/MessageQueueJob>
46#include <MailTransport/TransportComboBox>
47#include <MailTransport/TransportManager>
48
49#include <Akonadi/CollectionComboBox>
50#include <Akonadi/CollectionFetchJob>
51#include <Akonadi/ItemCreateJob>
52#include <Akonadi/MessageFlags>
53#include <Akonadi/SpecialMailCollections>
54
55#include <KEmailAddress>
56#include <KIdentityManagementCore/IdentityManager>
57#include <KIdentityManagementWidgets/IdentityCombo>
58
59#include "messagecomposer_debug.h"
60
61#include <Libkleo/ExpiryChecker>
62#include <Libkleo/ExpiryCheckerSettings>
63
64#include <QGpgME/ExportJob>
65#include <QGpgME/ImportJob>
66#include <QGpgME/Protocol>
67#include <gpgme++/context.h>
68#include <gpgme++/importresult.h>
69
70#include <KLocalizedString>
71#include <KMessageBox>
72#include <QSaveFile>
73
74#include "followupreminder/followupremindercreatejob.h"
75#include <QDir>
76#include <QStandardPaths>
77#include <QTemporaryDir>
78#include <QTimer>
79#include <QUuid>
80
81using namespace MessageComposer;
82
83ComposerViewBase::ComposerViewBase(QObject *parent, QWidget *parentGui)
84 : QObject(parent)
85 , m_msg(KMime::Message::Ptr(new KMime::Message))
86 , m_parentWidget(parentGui)
87 , m_cryptoMessageFormat(Kleo::AutoFormat)
88 , m_autoSaveInterval(60000) // default of 1 min
89{
90 initAutoSave();
91}
92
93ComposerViewBase::~ComposerViewBase() = default;
94
96{
97 return !m_composers.isEmpty();
98}
99
100void ComposerViewBase::setMessage(const KMime::Message::Ptr &msg, bool allowDecryption)
101{
102 if (m_attachmentModel) {
103 const auto attachments{m_attachmentModel->attachments()};
104 for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
105 if (!m_attachmentModel->removeAttachment(attachment)) {
106 qCWarning(MESSAGECOMPOSER_LOG) << "Attachment not found.";
107 }
108 }
109 }
110 m_msg = msg;
111 if (m_recipientsEditor) {
112 m_recipientsEditor->clear();
113 bool resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->to()->mailboxes(), MessageComposer::Recipient::To);
114 if (!resultTooManyRecipients) {
115 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->cc()->mailboxes(), MessageComposer::Recipient::Cc);
116 }
117 if (!resultTooManyRecipients) {
118 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->bcc()->mailboxes(), MessageComposer::Recipient::Bcc);
119 }
120 if (!resultTooManyRecipients) {
121 resultTooManyRecipients = m_recipientsEditor->setRecipientString(m_msg->replyTo()->mailboxes(), MessageComposer::Recipient::ReplyTo);
122 }
123 m_recipientsEditor->setFocusBottom();
124
125 if (!resultTooManyRecipients) {
126 // If we are loading from a draft, load unexpanded aliases as well
127 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-To")) {
128 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
129 for (const QString &addr : spl) {
130 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::To)) {
131 resultTooManyRecipients = true;
132 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
133 break;
134 }
135 }
136 }
137 }
138 if (!resultTooManyRecipients) {
139 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
140 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
141 for (const QString &addr : spl) {
142 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Cc)) {
143 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
144 resultTooManyRecipients = true;
145 break;
146 }
147 }
148 }
149 }
150 if (!resultTooManyRecipients) {
151 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
152 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
153 for (const QString &addr : spl) {
154 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::Bcc)) {
155 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
156 resultTooManyRecipients = true;
157 break;
158 }
159 }
160 }
161 }
162 if (!resultTooManyRecipients) {
163 if (auto hrd = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
164 const QStringList spl = hrd->asUnicodeString().split(QLatin1Char(','));
165 for (const QString &addr : spl) {
166 if (m_recipientsEditor->addRecipient(addr, MessageComposer::Recipient::ReplyTo)) {
167 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to add recipient.";
168 resultTooManyRecipients = true;
169 break;
170 }
171 }
172 }
173 }
174 Q_EMIT tooManyRecipient(resultTooManyRecipients);
175 }
176 // First, we copy the message and then parse it to the object tree parser.
177 // The otp gets the message text out of it, in textualContent(), and also decrypts
178 // the message if necessary.
179 KMime::Content msgContent;
180 msgContent.setContent(m_msg->encodedContent());
181 msgContent.parse();
183 MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
184 emptySource.setDecryptMessage(allowDecryption);
185 otp.parseObjectTree(&msgContent);
186
187 // Load the attachments
188 const auto attachmentsOfExtraContents{otp.nodeHelper()->attachmentsOfExtraContents()};
189 for (const auto &att : attachmentsOfExtraContents) {
190 addAttachmentPart(att);
191 }
192 const auto attachments{msgContent.attachments()};
193 for (const auto &att : attachments) {
194 addAttachmentPart(att);
195 }
196
197 int transportId = -1;
198 if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
199 transportId = hdr->asUnicodeString().toInt();
200 }
201
202 if (m_transport) {
204 if (transport) {
205 if (!m_transport->setCurrentTransport(transport->id())) {
206 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to find transport id" << transport->id();
207 }
208 }
209 }
210
211 // Set the HTML text and collect HTML images
212 QString htmlContent = otp.htmlContent();
213 if (htmlContent.isEmpty()) {
214 m_editor->setPlainText(otp.plainTextContent());
215 } else {
216 // Bug 372085 <div id="name"> is replaced in qtextedit by <a id="name">... => break url
217 htmlContent.replace(QRegularExpression(QStringLiteral("<div\\s*id=\".*\">")), QStringLiteral("<div>"));
218 m_editor->setHtml(htmlContent);
219 Q_EMIT enableHtml();
220 collectImages(m_msg.data());
221 }
222
223 if (auto hdr = m_msg->headerByType("X-KMail-CursorPos")) {
224 m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toUInt());
225 }
226}
227
228void ComposerViewBase::updateTemplate(const KMime::Message::Ptr &msg)
229{
230 // First, we copy the message and then parse it to the object tree parser.
231 // The otp gets the message text out of it, in textualContent(), and also decrypts
232 // the message if necessary.
233 KMime::Content msgContent;
234 msgContent.setContent(msg->encodedContent());
235 msgContent.parse();
237 MimeTreeParser::ObjectTreeParser otp(&emptySource); // All default are ok
238 otp.parseObjectTree(&msgContent);
239 // Set the HTML text and collect HTML images
240 if (!otp.htmlContent().isEmpty()) {
241 m_editor->setHtml(otp.htmlContent());
242 Q_EMIT enableHtml();
243 collectImages(msg.data());
244 } else {
245 m_editor->setPlainText(otp.plainTextContent());
246 }
247
248 if (auto hdr = msg->headerByType("X-KMail-CursorPos")) {
249 m_editor->setCursorPositionFromStart(hdr->asUnicodeString().toInt());
250 }
251}
252
253void ComposerViewBase::saveMailSettings()
254{
255 const auto identity = currentIdentity();
256 auto header = new KMime::Headers::Generic("X-KMail-Transport");
257 header->fromUnicodeString(QString::number(m_transport->currentTransportId()));
258 m_msg->setHeader(header);
259
260 header = new KMime::Headers::Generic("X-KMail-Transport-Name");
261 header->fromUnicodeString(m_transport->currentText());
262 m_msg->setHeader(header);
263
264 header = new KMime::Headers::Generic("X-KMail-Fcc");
265 header->fromUnicodeString(QString::number(m_fccCollection.id()));
266 m_msg->setHeader(header);
267
268 header = new KMime::Headers::Generic("X-KMail-Identity");
269 header->fromUnicodeString(QString::number(identity.uoid()));
270 m_msg->setHeader(header);
271
272 header = new KMime::Headers::Generic("X-KMail-Identity-Name");
273 header->fromUnicodeString(identity.identityName());
274 m_msg->setHeader(header);
275
276 header = new KMime::Headers::Generic("X-KMail-Dictionary");
277 header->fromUnicodeString(m_dictionary->currentDictionary());
278 m_msg->setHeader(header);
279
280 // Save the quote prefix which is used for this message. Each message can have
281 // a different quote prefix, for example depending on the original sender.
282 if (m_editor->quotePrefixName().isEmpty()) {
283 m_msg->removeHeader("X-KMail-QuotePrefix");
284 } else {
285 header = new KMime::Headers::Generic("X-KMail-QuotePrefix");
286 header->fromUnicodeString(m_editor->quotePrefixName());
287 m_msg->setHeader(header);
288 }
289
290 if (m_editor->composerControler()->isFormattingUsed()) {
291 qCDebug(MESSAGECOMPOSER_LOG) << "HTML mode";
292 header = new KMime::Headers::Generic("X-KMail-Markup");
293 header->fromUnicodeString(QStringLiteral("true"));
294 m_msg->setHeader(header);
295 } else {
296 m_msg->removeHeader("X-KMail-Markup");
297 qCDebug(MESSAGECOMPOSER_LOG) << "Plain text";
298 }
299}
300
301void ComposerViewBase::clearFollowUp()
302{
303 mFollowUpDate = QDate();
304 mFollowUpCollection = Akonadi::Collection();
305}
306
307void ComposerViewBase::send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher)
308{
309 mSendMethod = method;
310 mSaveIn = saveIn;
311
313 const auto identity = currentIdentity();
314
315 if (identity.attachVcard() && m_attachmentController->attachOwnVcard()) {
316 const QString vcardFileName = identity.vCardFile();
317 if (!vcardFileName.isEmpty()) {
318 m_attachmentController->addAttachmentUrlSync(QUrl::fromLocalFile(vcardFileName));
319 }
320 }
321 saveMailSettings();
322
323 if (m_editor->composerControler()->isFormattingUsed() && inlineSigningEncryptionSelected()) {
324 const QString keepBtnText =
325 m_encrypt ? m_sign ? i18n("&Keep markup, do not sign/encrypt") : i18n("&Keep markup, do not encrypt") : i18n("&Keep markup, do not sign");
326 const QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n("Encrypt (delete markup)") : i18n("Sign (delete markup)");
327 int ret = KMessageBox::warningTwoActionsCancel(m_parentWidget,
328 i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
329 "<p>do you want to delete your markup?</p></qt>"),
330 i18nc("@title:window", "Sign/Encrypt Message?"),
331 KGuiItem(yesBtnText),
332 KGuiItem(keepBtnText));
333 if (KMessageBox::Cancel == ret) {
334 return;
335 }
336 if (KMessageBox::ButtonCode::SecondaryAction == ret) {
337 m_encrypt = false;
338 m_sign = false;
339 } else {
340 Q_EMIT disableHtml(NoConfirmationNeeded);
341 }
342 }
343
344 if (m_neverEncrypt && saveIn != MessageComposer::MessageSender::SaveInNone) {
345 // we can't use the state of the mail itself, to remember the
346 // signing and encryption state, so let's add a header instead
347 DraftSignatureState(m_msg).setState(m_sign);
348 DraftEncryptionState(m_msg).setState(m_encrypt);
349 DraftCryptoMessageFormatState(m_msg).setState(m_cryptoMessageFormat);
350 } else {
351 removeDraftCryptoHeaders(m_msg);
352 }
353
354 if (mSendMethod == MessageComposer::MessageSender::SendImmediate && checkMailDispatcher) {
355 if (!MessageComposer::Util::sendMailDispatcherIsOnline(m_parentWidget)) {
356 qCWarning(MESSAGECOMPOSER_LOG) << "Impossible to set sendmaildispatcher online. Please verify it";
357 return;
358 }
359 }
360
361 readyForSending();
362}
363
364void ComposerViewBase::setCustomHeader(const QMap<QByteArray, QString> &customHeader)
365{
366 m_customHeader = customHeader;
367}
368
369void ComposerViewBase::readyForSending()
370{
371 qCDebug(MESSAGECOMPOSER_LOG) << "Entering readyForSending";
372 if (!m_msg) {
373 qCDebug(MESSAGECOMPOSER_LOG) << "m_msg == 0!";
374 return;
375 }
376
377 if (!m_composers.isEmpty()) {
378 // This may happen if e.g. the autosave timer calls applyChanges.
379 qCDebug(MESSAGECOMPOSER_LOG) << "ready for sending: Called while composer active; ignoring. Number of composer " << m_composers.count();
380 return;
381 }
382
383 // first, expand all addresses
384 auto job = new MessageComposer::EmailAddressResolveJob(this);
385 const auto identity = currentIdentity();
386 if (!identity.isNull()) {
387 job->setDefaultDomainName(identity.defaultDomainName());
388 }
389 job->setFrom(from());
390 job->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
391 job->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
392 job->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
393 job->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
394
395 connect(job, &MessageComposer::EmailAddressResolveJob::result, this, &ComposerViewBase::slotEmailAddressResolved);
396 job->start();
397}
398
399void ComposerViewBase::slotEmailAddressResolved(KJob *job)
400{
401 if (job->error()) {
402 qCWarning(MESSAGECOMPOSER_LOG) << "An error occurred while resolving the email addresses:" << job->errorString();
403 // This error could be caused by a broken search infrastructure, so we ignore it for now
404 // to not block sending emails completely.
405 }
406
407 bool autoresizeImage = MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled();
408
410 if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
411 mExpandedFrom = resolveJob->expandedFrom();
412 mExpandedTo = resolveJob->expandedTo();
413 mExpandedCc = resolveJob->expandedCc();
414 mExpandedBcc = resolveJob->expandedBcc();
415 mExpandedReplyTo = resolveJob->expandedReplyTo();
416 if (autoresizeImage) {
417 const QStringList listEmails = QStringList() << mExpandedFrom << mExpandedTo << mExpandedCc << mExpandedBcc << mExpandedReplyTo;
418 MessageComposer::Utils resizeUtils;
419 autoresizeImage = resizeUtils.filterRecipients(listEmails);
420 }
421 } else { // saved to draft, so keep the old values, not very nice.
422 mExpandedFrom = from();
423 const auto recipients{m_recipientsEditor->recipients()};
424 for (const MessageComposer::Recipient::Ptr &r : recipients) {
425 switch (r->type()) {
426 case MessageComposer::Recipient::To:
427 mExpandedTo << r->email();
428 break;
429 case MessageComposer::Recipient::Cc:
430 mExpandedCc << r->email();
431 break;
432 case MessageComposer::Recipient::Bcc:
433 mExpandedBcc << r->email();
434 break;
435 case MessageComposer::Recipient::ReplyTo:
436 mExpandedReplyTo << r->email();
437 break;
438 case MessageComposer::Recipient::Undefined:
439 Q_ASSERT(!"Unknown recipient type!");
440 break;
441 }
442 }
443 QStringList unExpandedTo;
444 QStringList unExpandedCc;
445 QStringList unExpandedBcc;
446 QStringList unExpandedReplyTo;
447 const auto expandedToLst{resolveJob->expandedTo()};
448 for (const QString &exp : expandedToLst) {
449 if (!mExpandedTo.contains(exp)) { // this address was expanded, so save it explicitly
450 unExpandedTo << exp;
451 }
452 }
453 const auto expandedCcLst{resolveJob->expandedCc()};
454 for (const QString &exp : expandedCcLst) {
455 if (!mExpandedCc.contains(exp)) {
456 unExpandedCc << exp;
457 }
458 }
459 const auto expandedBCcLst{resolveJob->expandedBcc()};
460 for (const QString &exp : expandedBCcLst) {
461 if (!mExpandedBcc.contains(exp)) { // this address was expanded, so save it explicitly
462 unExpandedBcc << exp;
463 }
464 }
465 const auto expandedReplyLst{resolveJob->expandedReplyTo()};
466 for (const QString &exp : expandedReplyLst) {
467 if (!mExpandedReplyTo.contains(exp)) { // this address was expanded, so save it explicitly
468 unExpandedReplyTo << exp;
469 }
470 }
471 auto header = new KMime::Headers::Generic("X-KMail-UnExpanded-To");
472 header->from7BitString(unExpandedTo.join(QLatin1StringView(", ")).toLatin1());
473 m_msg->setHeader(header);
474 header = new KMime::Headers::Generic("X-KMail-UnExpanded-CC");
475 header->from7BitString(unExpandedCc.join(QLatin1StringView(", ")).toLatin1());
476 m_msg->setHeader(header);
477 header = new KMime::Headers::Generic("X-KMail-UnExpanded-BCC");
478 header->from7BitString(unExpandedBcc.join(QLatin1StringView(", ")).toLatin1());
479 m_msg->setHeader(header);
480 header = new KMime::Headers::Generic("X-KMail-UnExpanded-Reply-To");
481 header->from7BitString(unExpandedReplyTo.join(QLatin1StringView(", ")).toLatin1());
482 m_msg->setHeader(header);
483 autoresizeImage = false;
484 }
485
486 Q_ASSERT(m_composers.isEmpty()); // composers should be empty. The caller of this function
487 // checks for emptiness before calling it
488 // so just ensure it actually is empty
489 // and document it
490 // we first figure out if we need to create multiple messages with different crypto formats
491 // if so, we create a composer per format
492 // if we aren't signing or encrypting, this just returns a single empty message
493 if (m_neverEncrypt && mSaveIn != MessageComposer::MessageSender::SaveInNone && !mSendLaterInfo) {
494 auto composer = new MessageComposer::Composer;
495 composer->setNoCrypto(true);
496 m_composers.append(composer);
497 } else {
498 bool wasCanceled = false;
499 m_composers = generateCryptoMessages(wasCanceled);
500 if (wasCanceled) {
501 return;
502 }
503 }
504
505 if (m_composers.isEmpty()) {
506 Q_EMIT failed(i18n("It was not possible to create a message composer."));
507 return;
508 }
509
510 if (autoresizeImage) {
511 if (MessageComposer::MessageComposerSettings::self()->askBeforeResizing()) {
512 if (m_attachmentModel) {
513 MessageComposer::Utils resizeUtils;
514 if (resizeUtils.containsImage(m_attachmentModel->attachments())) {
515 const int rc = KMessageBox::warningTwoActions(m_parentWidget,
516 i18n("Do you want to resize images?"),
517 i18nc("@title:window", "Auto Resize Images"),
518 KGuiItem(i18nc("@action:button", "Auto Resize")),
519 KGuiItem(i18nc("@action:button", "Do Not Resize")));
520 if (rc == KMessageBox::ButtonCode::PrimaryAction) {
521 autoresizeImage = true;
522 } else {
523 autoresizeImage = false;
524 }
525 } else {
526 autoresizeImage = false;
527 }
528 }
529 }
530 }
531 // Compose each message and prepare it for queueing, sending, or storing
532
533 // working copy in case composers instantly emit result
534 const auto composers = m_composers;
535 for (MessageComposer::Composer *composer : composers) {
536 fillComposer(composer, UseExpandedRecipients, autoresizeImage);
537 connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotSendComposeResult);
538 composer->start();
539 qCDebug(MESSAGECOMPOSER_LOG) << "Started a composer for sending!";
540 }
541}
542
543namespace
544{
545// helper methods for reading encryption settings
546
547inline Kleo::chrono::days encryptOwnKeyNearExpiryWarningThresholdInDays()
548{
549 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
550 return Kleo::chrono::days{-1};
551 }
552 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnOwnEncrKeyNearExpiryThresholdDays();
553 return Kleo::chrono::days{qMax(1, num)};
554}
555
556inline Kleo::chrono::days encryptKeyNearExpiryWarningThresholdInDays()
557{
558 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
559 return Kleo::chrono::days{-1};
560 }
561 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
562 return Kleo::chrono::days{qMax(1, num)};
563}
564
565inline Kleo::chrono::days encryptRootCertNearExpiryWarningThresholdInDays()
566{
567 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
568 return Kleo::chrono::days{-1};
569 }
570 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
571 return Kleo::chrono::days{qMax(1, num)};
572}
573
574inline Kleo::chrono::days encryptChainCertNearExpiryWarningThresholdInDays()
575{
576 if (!MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire()) {
577 return Kleo::chrono::days{-1};
578 }
579 const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
580 return Kleo::chrono::days{qMax(1, num)};
581}
582
583inline bool showKeyApprovalDialog()
584{
585 return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval();
586}
587
588inline bool cryptoWarningUnsigned(const KIdentityManagementCore::Identity &identity)
589{
590 if (identity.encryptionOverride()) {
591 return identity.warnNotSign();
592 }
593 return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned();
594}
595
596inline bool cryptoWarningUnencrypted(const KIdentityManagementCore::Identity &identity)
597{
598 if (identity.encryptionOverride()) {
599 return identity.warnNotEncrypt();
600 }
601 return MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted();
602}
603} // nameless namespace
604
605bool ComposerViewBase::addKeysToContext(const QString &gnupgHome,
606 const QList<QPair<QStringList, std::vector<GpgME::Key>>> &data,
607 const std::map<QByteArray, QString> &autocryptMap)
608{
609 bool needSpecialContext = false;
610
611 for (const auto &p : data) {
612 for (const auto &k : p.second) {
613 const auto it = autocryptMap.find(k.primaryFingerprint());
614 if (it != autocryptMap.end()) {
615 needSpecialContext = true;
616 break;
617 }
618 }
619 if (needSpecialContext) {
620 break;
621 }
622 }
623
624 if (!needSpecialContext) {
625 return false;
626 }
627 const QGpgME::Protocol *proto(QGpgME::openpgp());
628
629 const auto storage = MessageCore::AutocryptStorage::self();
630 QEventLoop loop;
631 int runningJobs = 0;
632 for (const auto &p : data) {
633 for (const auto &k : p.second) {
634 const auto it = autocryptMap.find(k.primaryFingerprint());
635 if (it == autocryptMap.end()) {
636 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "via Export/Import";
637 auto exportJob = proto->publicKeyExportJob(false);
638 connect(exportJob,
639 &QGpgME::ExportJob::result,
640 this,
641 [&gnupgHome, &proto, &runningJobs, &loop, &k](const GpgME::Error &result,
642 const QByteArray &keyData,
643 const QString &auditLogAsHtml,
644 const GpgME::Error &auditLogError) {
645 Q_UNUSED(auditLogAsHtml);
646 Q_UNUSED(auditLogError);
647 if (result) {
648 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to export " << k.primaryFingerprint() << result.asString();
649 --runningJobs;
650 if (runningJobs < 1) {
651 loop.quit();
652 }
653 }
654
655 auto importJob = proto->importJob();
656 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
657 importJob->exec(keyData);
658 importJob->deleteLater();
659 --runningJobs;
660 if (runningJobs < 1) {
661 loop.quit();
662 }
663 });
664 QStringList patterns;
665 patterns << QString::fromUtf8(k.primaryFingerprint());
666 runningJobs++;
667 exportJob->start(patterns);
668 exportJob->setExportFlags(GpgME::Context::ExportMinimal);
669 } else {
670 qCDebug(MESSAGECOMPOSER_LOG) << "Adding " << k.primaryFingerprint() << "from Autocrypt storage";
671 const auto recipient = storage->getRecipient(it->second.toUtf8());
672 auto key = recipient->gpgKey();
673 auto keydata = recipient->gpgKeydata();
674 if (QByteArray(key.primaryFingerprint()) != QByteArray(k.primaryFingerprint())) {
675 qCDebug(MESSAGECOMPOSER_LOG) << "Using gossipkey";
676 keydata = recipient->gossipKeydata();
677 }
678 auto importJob = proto->importJob();
679 QGpgME::Job::context(importJob)->setEngineHomeDirectory(gnupgHome.toUtf8().constData());
680 const auto result = importJob->exec(keydata);
681 importJob->deleteLater();
682 }
683 }
684 }
685 loop.exec();
686 return true;
687}
688
689void ComposerViewBase::setAkonadiLookupEnabled(bool akonadiLookupEnabled)
690{
691 m_akonadiLookupEnabled = akonadiLookupEnabled;
692}
693
694QList<MessageComposer::Composer *> ComposerViewBase::generateCryptoMessages(bool &wasCanceled)
695{
696 const auto id = currentIdentity();
697
698 bool canceled = false;
699
700 qCDebug(MESSAGECOMPOSER_LOG) << "filling crypto info";
701 connect(expiryChecker().get(),
702 &Kleo::ExpiryChecker::expiryMessage,
703 this,
704 [&canceled](const GpgME::Key &key, QString msg, Kleo::ExpiryChecker::ExpiryInformation info, bool isNewMessage) {
705 if (!isNewMessage) {
706 return;
707 }
708
709 if (canceled) {
710 return;
711 }
712 QString title;
713 QString dontAskAgainName;
714 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OwnKeyNearExpiry) {
715 dontAskAgainName = QStringLiteral("own key expires soon warning");
716 } else {
717 dontAskAgainName = QStringLiteral("other encryption key near expiry warning");
718 }
719 if (info == Kleo::ExpiryChecker::OwnKeyExpired || info == Kleo::ExpiryChecker::OtherKeyExpired) {
720 title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expired") : i18n("S/MIME Certificate Expired");
721 } else {
722 title = key.protocol() == GpgME::OpenPGP ? i18n("OpenPGP Key Expires Soon") : i18n("S/MIME Certificate Expires Soon");
723 }
724 if (KMessageBox::warningContinueCancel(nullptr, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dontAskAgainName)
726 canceled = true;
727 }
728 });
729
731 new Kleo::KeyResolver(true, showKeyApprovalDialog(), id.pgpAutoEncrypt(), m_cryptoMessageFormat, expiryChecker()));
732
733 keyResolver->setAutocryptEnabled(autocryptEnabled());
734 keyResolver->setAkonadiLookupEnabled(m_akonadiLookupEnabled);
735
736 QStringList encryptToSelfKeys;
737 QStringList signKeys;
738
739 bool signSomething = m_sign;
740 bool doSignCompletely = m_sign;
741 bool encryptSomething = m_encrypt;
742 bool doEncryptCompletely = m_encrypt;
743
744 // Add encryptionkeys from id to keyResolver
745 if (!id.pgpEncryptionKey().isEmpty()) {
746 encryptToSelfKeys.push_back(QLatin1StringView(id.pgpEncryptionKey()));
747 }
748 if (!id.smimeEncryptionKey().isEmpty()) {
749 encryptToSelfKeys.push_back(QLatin1StringView(id.smimeEncryptionKey()));
750 }
751 if (canceled || keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != Kleo::Ok) {
752 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
753 return {};
754 }
755
756 // Add signingkeys from id to keyResolver
757 if (!id.pgpSigningKey().isEmpty()) {
758 signKeys.push_back(QLatin1StringView(id.pgpSigningKey()));
759 }
760 if (!id.smimeSigningKey().isEmpty()) {
761 signKeys.push_back(QLatin1StringView(id.smimeSigningKey()));
762 }
763 if (canceled || keyResolver->setSigningKeys(signKeys) != Kleo::Ok) {
764 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
765 return {};
766 }
767
768 if (m_attachmentModel) {
769 const auto attachments = m_attachmentModel->attachments();
770 for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
771 if (attachment->isSigned()) {
772 signSomething = true;
773 } else {
774 doEncryptCompletely = false;
775 }
776 if (attachment->isEncrypted()) {
777 encryptSomething = true;
778 } else {
779 doSignCompletely = false;
780 }
781 }
782 }
783
784 const QStringList recipients = mExpandedTo + mExpandedCc;
785 const QStringList bcc(mExpandedBcc);
786
787 keyResolver->setPrimaryRecipients(recipients);
788 keyResolver->setSecondaryRecipients(bcc);
789
790 bool result = true;
791 canceled = false;
792 signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
793 if (!result) {
794 // TODO handle failure
795 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
796 if (!canceled) {
797 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
798 } else {
800 }
801 wasCanceled = canceled;
802 return {};
803 }
804
805 canceled = false;
806 encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
807 if (!result) {
808 // TODO handle failure
809 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
810 if (!canceled) {
811 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
812 } else {
814 }
815
816 wasCanceled = canceled;
817 return {};
818 }
819
821
822 // No encryption or signing is needed
823 if (!signSomething && !encryptSomething) {
824 auto composer = new MessageComposer::Composer;
825 if (m_cryptoMessageFormat & Kleo::OpenPGPMIMEFormat) {
826 composer->setAutocryptEnabled(autocryptEnabled());
827 if (keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat).size() > 0) {
828 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat)[0]);
829 }
830 }
831 composers.append(composer);
832 return composers;
833 }
834
835 canceled = false;
836 const Kleo::Result kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
837 if (kpgpResult == Kleo::Canceled || canceled) {
838 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
839 return {};
840 } else if (kpgpResult != Kleo::Ok) {
841 // TODO handle failure
842 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
843 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
844 return {};
845 }
846
847 qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys.";
848
849 if (encryptSomething || signSomething) {
850 Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
851 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
852 concreteFormat = concreteCryptoMessageFormats[i];
853 const auto encData = keyResolver->encryptionItems(concreteFormat);
854 if (encData.empty()) {
855 continue;
856 }
857
858 if (!(concreteFormat & m_cryptoMessageFormat)) {
859 continue;
860 }
861
862 auto composer = new MessageComposer::Composer;
863
864 if (encryptSomething || autocryptEnabled()) {
865 auto end(encData.end());
867 data.reserve(encData.size());
868 for (auto it = encData.begin(); it != end; ++it) {
869 QPair<QStringList, std::vector<GpgME::Key>> p(it->recipients, it->keys);
870 data.append(p);
871 qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
872 }
873 composer->setEncryptionKeys(data);
874 if (concreteFormat & Kleo::OpenPGPMIMEFormat && autocryptEnabled()) {
875 composer->setAutocryptEnabled(autocryptEnabled());
876 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(concreteFormat)[0]);
878 bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver->useAutocrypt());
879 if (specialGnupgHome) {
880 dir.setAutoRemove(false);
881 composer->setGnupgHome(dir.path());
882 }
883 }
884 }
885
886 if (signSomething) {
887 // find signing keys for this format
888 std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
889 composer->setSigningKeys(signingKeys);
890 }
891
892 composer->setCryptoMessageFormat(concreteFormat);
893 composer->setSignAndEncrypt(signSomething, encryptSomething);
894
895 composers.append(composer);
896 }
897 } else {
898 auto composer = new MessageComposer::Composer;
899 composers.append(composer);
900 // If we canceled sign or encrypt be sure to change status in attachment.
901 markAllAttachmentsForSigning(false);
902 markAllAttachmentsForEncryption(false);
903 }
904
905 if (composers.isEmpty() && (signSomething || encryptSomething)) {
906 Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
907 }
908
909 return composers;
910}
911
912void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
913{
914 globalPart->setParentWidgetForGui(m_parentWidget);
915 globalPart->setMDNRequested(m_mdnRequested);
916 globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
917}
918
919void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
920{
921 // TODO splitAddressList and expandAliases ugliness should be handled by a
922 // special AddressListEdit widget... (later: see RecipientsEditor)
923
924 if (m_fccCombo) {
925 infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
926 } else {
927 if (m_fccCollection.isValid()) {
928 infoPart->setFcc(QString::number(m_fccCollection.id()));
929 }
930 }
931
932 infoPart->setTransportId(m_transport->currentTransportId());
933 if (expansion == UseExpandedRecipients) {
934 infoPart->setFrom(mExpandedFrom);
935 infoPart->setTo(mExpandedTo);
936 infoPart->setCc(mExpandedCc);
937 infoPart->setBcc(mExpandedBcc);
938 infoPart->setReplyTo(mExpandedReplyTo);
939 } else {
940 infoPart->setFrom(from());
941 infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
942 infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
943 infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
944 infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
945 }
946 infoPart->setSubject(subject());
947 infoPart->setUserAgent(QStringLiteral("KMail"));
948 infoPart->setUrgent(m_urgent);
949
950 if (auto inReplyTo = m_msg->inReplyTo(false)) {
951 infoPart->setInReplyTo(inReplyTo->asUnicodeString());
952 }
953
954 if (auto references = m_msg->references(false)) {
955 infoPart->setReferences(references->asUnicodeString());
956 }
957
959 if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
960 extras << hdr;
961 }
962 if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
963 extras << hdr;
964 }
965 if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
966 extras << hdr;
967 }
968 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
969 extras << hdr;
970 }
971 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
972 extras << hdr;
973 }
974 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
975 extras << hdr;
976 }
977 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
978 extras << hdr;
979 }
980 if (auto hdr = m_msg->organization(false)) {
981 extras << hdr;
982 }
983 if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
984 extras << hdr;
985 }
986 if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
987 extras << hdr;
988 }
989 if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
990 extras << hdr;
991 }
992 if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
993 extras << hdr;
994 }
995 if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
996 extras << hdr;
997 }
998 if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
999 extras << hdr;
1000 }
1001 if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
1002 extras << hdr;
1003 }
1004 if (auto hdr = m_msg->headerByType("X-Face")) {
1005 extras << hdr;
1006 }
1007 if (auto hdr = m_msg->headerByType("Face")) {
1008 extras << hdr;
1009 }
1010 if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
1011 extras << hdr;
1012 }
1013 if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
1014 extras << hdr;
1015 }
1016 if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
1017 extras << hdr;
1018 }
1019
1020 infoPart->setExtraHeaders(extras);
1021}
1022
1023void ComposerViewBase::slotSendComposeResult(KJob *job)
1024{
1025 Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
1026 auto composer = static_cast<MessageComposer::Composer *>(job);
1027 if (composer->error() != MessageComposer::Composer::NoError) {
1028 qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
1029 }
1030
1031 if (composer->error() == MessageComposer::Composer::NoError) {
1032 Q_ASSERT(m_composers.contains(composer));
1033 // The messages were composed successfully.
1034 qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1035 const int numberOfMessage(composer->resultMessages().size());
1036 for (int i = 0; i < numberOfMessage; ++i) {
1037 if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
1038 queueMessage(composer->resultMessages().at(i), composer);
1039 } else {
1040 saveMessage(composer->resultMessages().at(i), mSaveIn);
1041 }
1042 }
1043 saveRecentAddresses(composer->resultMessages().at(0));
1044 } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1045 // The job warned the user about something, and the user chose to return
1046 // to the message. Nothing to do.
1047 qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1048 Q_EMIT failed(i18n("Job cancelled by the user"));
1049 } else {
1050 qCDebug(MESSAGECOMPOSER_LOG) << "other Error." << composer->error();
1051 QString msg;
1052 if (composer->error() == MessageComposer::Composer::BugError) {
1053 msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
1054 } else {
1055 msg = i18n("Could not compose message: %1", job->errorString());
1056 }
1057 Q_EMIT failed(msg);
1058 }
1059
1060 if (!composer->gnupgHome().isEmpty()) {
1061 QDir dir(composer->gnupgHome());
1062 dir.removeRecursively();
1063 }
1064
1065 m_composers.removeAll(composer);
1066}
1067
1068void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
1069{
1070 KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
1071 if (auto to = msg->to(false)) {
1072 const auto toAddresses = to->mailboxes();
1073 for (const auto &address : toAddresses) {
1074 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1075 }
1076 }
1077 if (auto cc = msg->cc(false)) {
1078 const auto ccAddresses = cc->mailboxes();
1079 for (const auto &address : ccAddresses) {
1080 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1081 }
1082 }
1083 if (auto bcc = msg->bcc(false)) {
1084 const auto bccAddresses = bcc->mailboxes();
1085 for (const auto &address : bccAddresses) {
1086 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1087 }
1088 }
1089}
1090
1091void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
1092{
1093 const MessageComposer::InfoPart *infoPart = composer->infoPart();
1094 auto qjob = new Akonadi::MessageQueueJob(this);
1095 qjob->setMessage(message);
1096 qjob->transportAttribute().setTransportId(infoPart->transportId());
1097 if (mSendMethod == MessageComposer::MessageSender::SendLater) {
1098 qjob->dispatchModeAttribute().setDispatchMode(Akonadi::DispatchModeAttribute::Manual);
1099 }
1100
1101 if (message->hasHeader("X-KMail-FccDisabled")) {
1102 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete);
1103 } else if (!infoPart->fcc().isEmpty()) {
1104 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection);
1105
1106 const Akonadi::Collection sentCollection(infoPart->fcc().toLongLong());
1107 qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
1108 } else {
1109 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToDefaultSentCollection);
1110 }
1111
1112 MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(infoPart->transportId());
1113 if (transport && transport->specifySenderOverwriteAddress()) {
1114 qjob->addressAttribute().setFrom(
1116 } else {
1117 qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from())));
1118 }
1119 // if this header is not empty, it contains the real recipient of the message, either the primary or one of the
1120 // secondary recipients. so we set that to the transport job, while leaving the message itself alone.
1121 if (KMime::Headers::Base *realTo = message->headerByType("X-KMail-EncBccRecipients")) {
1122 qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(realTo->asUnicodeString().split(QLatin1Char('%'))));
1123 message->removeHeader("X-KMail-EncBccRecipients");
1124 message->assemble();
1125 qCDebug(MESSAGECOMPOSER_LOG) << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to();
1126 } else {
1127 qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->to()));
1128 qjob->addressAttribute().setCc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->cc()));
1129 qjob->addressAttribute().setBcc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->bcc()));
1130 }
1131 if (m_requestDeleveryConfirmation) {
1132 qjob->addressAttribute().setDeliveryStatusNotification(true);
1133 }
1134 MessageComposer::Util::addSendReplyForwardAction(message, qjob);
1136
1137 MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1138 message->assemble();
1139 connect(qjob, &Akonadi::MessageQueueJob::result, this, &ComposerViewBase::slotQueueResult);
1140 m_pendingQueueJobs++;
1141 qjob->start();
1142
1143 qCDebug(MESSAGECOMPOSER_LOG) << "Queued a message.";
1144}
1145
1146void ComposerViewBase::slotQueueResult(KJob *job)
1147{
1148 m_pendingQueueJobs--;
1149 auto qjob = static_cast<Akonadi::MessageQueueJob *>(job);
1150 qCDebug(MESSAGECOMPOSER_LOG) << "mPendingQueueJobs" << m_pendingQueueJobs;
1151 Q_ASSERT(m_pendingQueueJobs >= 0);
1152
1153 if (job->error()) {
1154 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to queue a message:" << job->errorString();
1155 // There is not much we can do now, since all the MessageQueueJobs have been
1156 // started. So just wait for them to finish.
1157 // TODO show a message box or something
1158 const QString msg = i18n("There were problems trying to queue the message for sending: %1", job->errorString());
1159
1160 if (m_pendingQueueJobs == 0) {
1161 Q_EMIT failed(msg);
1162 return;
1163 }
1164 }
1165
1166 if (m_pendingQueueJobs == 0) {
1167 addFollowupReminder(qjob->message()->messageID(false)->asUnicodeString());
1169 }
1170}
1171
1172void ComposerViewBase::initAutoSave()
1173{
1174 qCDebug(MESSAGECOMPOSER_LOG) << "initialising autosave";
1175
1176 // Ensure that the autosave directory exists.
1178 if (!dataDirectory.exists(QStringLiteral("autosave"))) {
1179 qCDebug(MESSAGECOMPOSER_LOG) << "Creating autosave directory.";
1180 dataDirectory.mkdir(QStringLiteral("autosave"));
1181 }
1182
1183 // Construct a file name
1184 if (m_autoSaveUUID.isEmpty()) {
1185 m_autoSaveUUID = QUuid::createUuid().toString();
1186 }
1187
1189}
1190
1191Akonadi::Collection ComposerViewBase::followUpCollection() const
1192{
1193 return mFollowUpCollection;
1194}
1195
1196void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
1197{
1198 mFollowUpCollection = followUpCollection;
1199}
1200
1201QDate ComposerViewBase::followUpDate() const
1202{
1203 return mFollowUpDate;
1204}
1205
1206void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
1207{
1208 mFollowUpDate = followUpDate;
1209}
1210
1211Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
1212{
1213 return m_dictionary;
1214}
1215
1216void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
1217{
1218 m_dictionary = dictionary;
1219}
1220
1222{
1223 if (m_autoSaveInterval == 0) {
1224 delete m_autoSaveTimer;
1225 m_autoSaveTimer = nullptr;
1226 } else {
1227 if (!m_autoSaveTimer) {
1228 m_autoSaveTimer = new QTimer(this);
1229 if (m_parentWidget) {
1230 connect(m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()));
1231 } else {
1233 }
1234 }
1235 m_autoSaveTimer->start(m_autoSaveInterval);
1236 }
1237}
1238
1240{
1241 delete m_autoSaveTimer;
1242 m_autoSaveTimer = nullptr;
1243 if (!m_autoSaveUUID.isEmpty()) {
1244 qCDebug(MESSAGECOMPOSER_LOG) << "deleting autosave files" << m_autoSaveUUID;
1245
1246 // Delete the autosave files
1248
1249 // Filter out only this composer window's autosave files
1250 const QStringList autoSaveFilter{m_autoSaveUUID + QLatin1StringView("*")};
1251 autoSaveDir.setNameFilters(autoSaveFilter);
1252
1253 // Return the files to be removed
1254 const QStringList autoSaveFiles = autoSaveDir.entryList();
1255 qCDebug(MESSAGECOMPOSER_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
1256
1257 // Delete each file
1258 for (const QString &file : autoSaveFiles) {
1259 autoSaveDir.remove(file);
1260 }
1261 m_autoSaveUUID.clear();
1262 }
1263}
1264
1265//-----------------------------------------------------------------------------
1267{
1268 qCDebug(MESSAGECOMPOSER_LOG) << "Autosaving message";
1269
1270 if (m_autoSaveTimer) {
1271 m_autoSaveTimer->stop();
1272 }
1273
1274 if (!m_composers.isEmpty()) {
1275 // This may happen if e.g. the autosave timer calls applyChanges.
1276 qCDebug(MESSAGECOMPOSER_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
1277 return;
1278 }
1279
1280 auto composer = new Composer();
1281 fillComposer(composer);
1282 composer->setAutoSave(true);
1283 composer->setAutocryptEnabled(autocryptEnabled());
1284 m_composers.append(composer);
1285 connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
1286 composer->start();
1287}
1288
1290{
1291 m_autoSaveUUID = fileName;
1292
1293 Q_EMIT modified(true);
1294}
1295
1296void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
1297{
1299
1300 Q_ASSERT(dynamic_cast<Composer *>(job));
1301 auto composer = static_cast<Composer *>(job);
1302
1303 if (composer->error() == Composer::NoError) {
1304 Q_ASSERT(m_composers.contains(composer));
1305
1306 // The messages were composed successfully. Only save the first message, there should
1307 // only be one anyway, since crypto is disabled.
1308 qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1309 writeAutoSaveToDisk(composer->resultMessages().constFirst());
1310 Q_ASSERT(composer->resultMessages().size() == 1);
1311
1312 if (m_autoSaveInterval > 0) {
1314 }
1315 } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1316 // The job warned the user about something, and the user chose to return
1317 // to the message. Nothing to do.
1318 qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1319 Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
1320 } else {
1321 qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
1322 Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
1323 }
1324
1325 m_composers.removeAll(composer);
1326}
1327
1328void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
1329{
1331 QDir().mkpath(autosavePath);
1332 const QString filename = autosavePath + m_autoSaveUUID;
1333 QSaveFile file(filename);
1335 qCDebug(MESSAGECOMPOSER_LOG) << "Writing message to disk as" << filename;
1336
1337 if (file.open(QIODevice::WriteOnly)) {
1338 file.setPermissions(QFile::ReadUser | QFile::WriteUser);
1339
1340 if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
1341 errorMessage = i18n("Could not write all data to file.");
1342 } else {
1343 if (!file.commit()) {
1344 errorMessage = i18n("Could not finalize the file.");
1345 }
1346 }
1347 } else {
1348 errorMessage = i18n("Could not open file.");
1349 }
1350
1351 if (!errorMessage.isEmpty()) {
1352 qCWarning(MESSAGECOMPOSER_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << " m_autoSaveUUID" << m_autoSaveUUID;
1353 if (!m_autoSaveErrorShown) {
1354 KMessageBox::error(m_parentWidget,
1355 i18n("Autosaving the message as %1 failed.\n"
1356 "%2\n"
1357 "Reason: %3",
1358 filename,
1359 errorMessage,
1360 file.errorString()),
1361 i18nc("@title:window", "Autosaving Message Failed"));
1362
1363 // Error dialog shown, hide the errors the next time
1364 m_autoSaveErrorShown = true;
1365 }
1366 } else {
1367 // No error occurred, the next error should be shown again
1368 m_autoSaveErrorShown = false;
1369 }
1370 file.commit();
1371 message->clear();
1372}
1373
1374void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn)
1375{
1376 Akonadi::Collection target;
1377 const auto identity = currentIdentity();
1378 message->date()->setDateTime(QDateTime::currentDateTime());
1379 if (!identity.isNull()) {
1380 if (auto header = message->headerByType("X-KMail-Fcc")) {
1381 const int sentCollectionId = header->asUnicodeString().toInt();
1382 if (identity.fcc() == QString::number(sentCollectionId)) {
1383 message->removeHeader("X-KMail-Fcc");
1384 }
1385 }
1386 }
1387 MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1388
1389 message->assemble();
1390
1391 Akonadi::Item item;
1392 item.setMimeType(QStringLiteral("message/rfc822"));
1393 item.setPayload(message);
1395
1396 if (!identity.isNull()) { // we have a valid identity
1397 switch (saveIn) {
1398 case MessageComposer::MessageSender::SaveInTemplates:
1399 if (!identity.templates().isEmpty()) { // the user has specified a custom templates collection
1400 target = Akonadi::Collection(identity.templates().toLongLong());
1401 }
1402 break;
1403 case MessageComposer::MessageSender::SaveInDrafts:
1404 if (!identity.drafts().isEmpty()) { // the user has specified a custom drafts collection
1405 target = Akonadi::Collection(identity.drafts().toLongLong());
1406 }
1407 break;
1408 case MessageComposer::MessageSender::SaveInOutbox: // We don't define save outbox in identity
1410 break;
1411 case MessageComposer::MessageSender::SaveInNone:
1412 break;
1413 }
1414
1415 auto saveMessageJob = new Akonadi::CollectionFetchJob(target, Akonadi::CollectionFetchJob::Base);
1416 saveMessageJob->setProperty("Akonadi::Item", QVariant::fromValue(item));
1417 QObject::connect(saveMessageJob, &Akonadi::CollectionFetchJob::result, this, &ComposerViewBase::slotSaveMessage);
1418 } else {
1419 // preinitialize with the default collections
1420 target = defaultSpecialTarget();
1421 auto create = new Akonadi::ItemCreateJob(item, target, this);
1422 connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1423 ++m_pendingQueueJobs;
1424 }
1425}
1426
1427void ComposerViewBase::slotSaveMessage(KJob *job)
1428{
1429 Akonadi::Collection target;
1430 auto item = job->property("Akonadi::Item").value<Akonadi::Item>();
1431 if (job->error()) {
1432 target = defaultSpecialTarget();
1433 } else {
1435 if (fetchJob->collections().isEmpty()) {
1436 target = defaultSpecialTarget();
1437 } else {
1438 target = fetchJob->collections().at(0);
1439 }
1440 }
1441 auto create = new Akonadi::ItemCreateJob(item, target, this);
1442 connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1443 ++m_pendingQueueJobs;
1444}
1445
1446Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
1447{
1448 Akonadi::Collection target;
1449 switch (mSaveIn) {
1450 case MessageComposer::MessageSender::SaveInNone:
1451 break;
1452 case MessageComposer::MessageSender::SaveInDrafts:
1454 break;
1455 case MessageComposer::MessageSender::SaveInTemplates:
1457 break;
1458 case MessageComposer::MessageSender::SaveInOutbox:
1460 break;
1461 }
1462
1463 return target;
1464}
1465
1466void ComposerViewBase::slotCreateItemResult(KJob *job)
1467{
1468 --m_pendingQueueJobs;
1469 qCDebug(MESSAGECOMPOSER_LOG) << "mPendingCreateItemJobs" << m_pendingQueueJobs;
1470 Q_ASSERT(m_pendingQueueJobs >= 0);
1471
1472 if (job->error()) {
1473 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to save a message:" << job->errorString();
1474 Q_EMIT failed(i18n("Failed to save the message: %1", job->errorString()));
1475 return;
1476 }
1477
1478 Akonadi::Item::Id id = -1;
1479 if (mSendLaterInfo) {
1480 auto createJob = static_cast<Akonadi::ItemCreateJob *>(job);
1481 const Akonadi::Item item = createJob->item();
1482 if (item.isValid()) {
1483 id = item.id();
1484 addSendLaterItem(item);
1485 }
1486 }
1487
1488 if (m_pendingQueueJobs == 0) {
1490 }
1491}
1492
1493void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
1494{
1495 Q_UNUSED(comment)
1496 qCDebug(MESSAGECOMPOSER_LOG) << "adding attachment with url:" << url;
1497 if (sync) {
1498 m_attachmentController->addAttachmentUrlSync(url);
1499 } else {
1500 m_attachmentController->addAttachment(url);
1501 }
1502}
1503
1504void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
1505{
1507 if (!data.isEmpty()) {
1508 attachment->setName(name);
1509 attachment->setFileName(filename);
1510 attachment->setData(data);
1511 attachment->setCharset(charset.toLatin1());
1512 attachment->setMimeType(mimeType);
1513 // TODO what about the other fields?
1514
1515 m_attachmentController->addAttachment(attachment);
1516 }
1517}
1518
1519void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
1520{
1522 if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
1523 // if it is a digest or a full message, use the encodedContent() of the attachment,
1524 // which already has the proper headers
1525 part->setData(partToAttach->encodedContent());
1526 } else {
1527 part->setData(partToAttach->decodedContent());
1528 }
1529 part->setMimeType(partToAttach->contentType(false)->mimeType());
1530 if (auto cd = partToAttach->contentDescription(false)) {
1531 part->setDescription(cd->asUnicodeString());
1532 }
1533 if (auto ct = partToAttach->contentType(false)) {
1534 if (ct->hasParameter("name")) {
1535 part->setName(ct->parameter("name"));
1536 }
1537 }
1538 if (auto cd = partToAttach->contentDisposition(false)) {
1539 part->setFileName(cd->filename());
1540 part->setInline(cd->disposition() == KMime::Headers::CDinline);
1541 }
1542 if (part->name().isEmpty() && !part->fileName().isEmpty()) {
1543 part->setName(part->fileName());
1544 }
1545 if (part->fileName().isEmpty() && !part->name().isEmpty()) {
1546 part->setFileName(part->name());
1547 }
1548 m_attachmentController->addAttachment(part);
1549}
1550
1551void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
1552{
1553 fillComposer(composer, UseUnExpandedRecipients, false);
1554}
1555
1556void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
1557{
1558 fillGlobalPart(composer->globalPart());
1559 m_editor->fillComposerTextPart(composer->textPart());
1560 fillInfoPart(composer->infoPart(), expansion);
1561 if (m_attachmentModel) {
1562 composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
1563 }
1564}
1565
1566//-----------------------------------------------------------------------------
1568{
1569 if (m_recipientsEditor) {
1570 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
1571 }
1572 return {};
1573}
1574
1575//-----------------------------------------------------------------------------
1576QString ComposerViewBase::cc() const
1577{
1578 if (m_recipientsEditor) {
1579 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
1580 }
1581 return {};
1582}
1583
1584//-----------------------------------------------------------------------------
1585QString ComposerViewBase::bcc() const
1586{
1587 if (m_recipientsEditor) {
1588 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
1589 }
1590 return {};
1591}
1592
1593QString ComposerViewBase::from() const
1594{
1595 return MessageComposer::Util::cleanedUpHeaderString(m_from);
1596}
1597
1598QString ComposerViewBase::replyTo() const
1599{
1600 if (m_recipientsEditor) {
1601 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
1602 }
1603 return {};
1604}
1605
1606QString ComposerViewBase::subject() const
1607{
1608 return MessageComposer::Util::cleanedUpHeaderString(m_subject);
1609}
1610
1611const KIdentityManagementCore::Identity &ComposerViewBase::currentIdentity() const
1612{
1613 return m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
1614}
1615
1616bool ComposerViewBase::autocryptEnabled() const
1617{
1618 return currentIdentity().autocryptEnabled();
1619}
1620
1621void ComposerViewBase::setParentWidgetForGui(QWidget *w)
1622{
1623 m_parentWidget = w;
1624}
1625
1626void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
1627{
1628 m_attachmentController = controller;
1629}
1630
1631MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
1632{
1633 return m_attachmentController;
1634}
1635
1637{
1638 m_attachmentModel = model;
1639}
1640
1641MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
1642{
1643 return m_attachmentModel;
1644}
1645
1646void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
1647{
1648 m_recipientsEditor = recEditor;
1649}
1650
1651MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
1652{
1653 return m_recipientsEditor;
1654}
1655
1656void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
1657{
1658 m_signatureController = sigController;
1659}
1660
1661MessageComposer::SignatureController *ComposerViewBase::signatureController()
1662{
1663 return m_signatureController;
1664}
1665
1666void ComposerViewBase::setIdentityCombo(KIdentityManagementWidgets::IdentityCombo *identCombo)
1667{
1668 m_identityCombo = identCombo;
1669}
1670
1671KIdentityManagementWidgets::IdentityCombo *ComposerViewBase::identityCombo()
1672{
1673 return m_identityCombo;
1674}
1675
1676void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident,
1677 const KIdentityManagementCore::Identity &oldIdent,
1678 MessageComposer::Recipient::Type type)
1679{
1680 QString oldIdentList;
1681 QString newIdentList;
1682 switch (type) {
1683 case MessageComposer::Recipient::Bcc: {
1684 oldIdentList = oldIdent.bcc();
1685 newIdentList = ident.bcc();
1686 break;
1687 }
1688 case MessageComposer::Recipient::Cc: {
1689 oldIdentList = oldIdent.cc();
1690 newIdentList = ident.cc();
1691 break;
1692 }
1693 case MessageComposer::Recipient::ReplyTo: {
1694 oldIdentList = oldIdent.replyToAddr();
1695 newIdentList = ident.replyToAddr();
1696 break;
1697 }
1698 case MessageComposer::Recipient::To:
1699 case MessageComposer::Recipient::Undefined:
1700 return;
1701 }
1702
1703 if (oldIdentList != newIdentList) {
1704 const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
1705 for (const KMime::Types::Mailbox &recipient : oldRecipients) {
1706 m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
1707 }
1708
1709 const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
1710 for (const KMime::Types::Mailbox &recipient : newRecipients) {
1711 m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
1712 }
1713 m_recipientsEditor->setFocusBottom();
1714 }
1715}
1716
1717void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
1718{
1719 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
1720 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
1721 updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
1722
1723 KIdentityManagementCore::Signature oldSig = const_cast<KIdentityManagementCore::Identity &>(oldIdent).signature();
1724 KIdentityManagementCore::Signature newSig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
1725 // replace existing signatures
1726 const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
1727 // Just append the signature if there was no old signature
1728 if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
1729 signatureController()->applySignature(newSig);
1730 }
1731 const QString vcardFileName = ident.vCardFile();
1732 attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
1733 attachmentController()->setAttachOwnVcard(ident.attachVcard());
1734
1735 m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
1736}
1737
1738void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
1739{
1740 m_editor = editor;
1741 m_editor->document()->setModified(false);
1742}
1743
1744MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
1745{
1746 return m_editor;
1747}
1748
1749void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
1750{
1751 m_transport = transpCombo;
1752}
1753
1754MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
1755{
1756 return m_transport;
1757}
1758
1759void ComposerViewBase::setIdentityManager(KIdentityManagementCore::IdentityManager *identMan)
1760{
1761 m_identMan = identMan;
1762}
1763
1764KIdentityManagementCore::IdentityManager *ComposerViewBase::identityManager()
1765{
1766 return m_identMan;
1767}
1768
1769void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
1770{
1771 if (m_fccCombo) {
1772 m_fccCombo->setDefaultCollection(fccCollection);
1773 } else {
1774 m_fccCollection = fccCollection;
1775 }
1776 auto const checkFccCollectionJob = new Akonadi::CollectionFetchJob(fccCollection, Akonadi::CollectionFetchJob::Base);
1777 connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
1778}
1779
1780void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
1781{
1782 if (job->error()) {
1783 qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
1785 if (m_fccCombo) {
1786 m_fccCombo->setDefaultCollection(sentMailCol);
1787 } else {
1788 m_fccCollection = sentMailCol;
1789 }
1790 }
1791}
1792
1793void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
1794{
1795 m_fccCombo = fcc;
1796}
1797
1798Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
1799{
1800 return m_fccCombo;
1801}
1802
1804{
1805 m_from = from;
1806}
1807
1808void ComposerViewBase::setSubject(const QString &subject)
1809{
1810 m_subject = subject;
1811 if (mSendLaterInfo) {
1812 mSendLaterInfo->setSubject(m_subject);
1813 mSendLaterInfo->setTo(to());
1814 }
1815}
1816
1817void ComposerViewBase::setAutoSaveInterval(int interval)
1818{
1819 m_autoSaveInterval = interval;
1820}
1821
1822void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
1823{
1824 m_sign = sign;
1825 m_encrypt = encrypt;
1826 m_cryptoMessageFormat = format;
1827 m_neverEncrypt = neverEncryptDrafts;
1828}
1829
1830void ComposerViewBase::setMDNRequested(bool mdnRequested)
1831{
1832 m_mdnRequested = mdnRequested;
1833}
1834
1835void ComposerViewBase::setUrgent(bool urgent)
1836{
1837 m_urgent = urgent;
1838}
1839
1840int ComposerViewBase::autoSaveInterval() const
1841{
1842 return m_autoSaveInterval;
1843}
1844
1845//-----------------------------------------------------------------------------
1846void ComposerViewBase::collectImages(KMime::Content *root)
1847{
1848 if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
1849 KMime::Content *parentnode = n->parent();
1850 if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
1851 const auto nodes = parentnode->contents();
1852 for (auto node : nodes) {
1853 if (node->contentType()->isImage()) {
1854 qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
1855 QImage img;
1856 img.loadFromData(node->decodedContent());
1857 m_editor->composerControler()->composerImages()->loadImage(
1858 img,
1859 QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
1860 node->contentType()->name());
1861 }
1862 }
1863 }
1864 }
1865}
1866
1867//-----------------------------------------------------------------------------
1868bool ComposerViewBase::inlineSigningEncryptionSelected() const
1869{
1870 if (!m_sign && !m_encrypt) {
1871 return false;
1872 }
1873 return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
1874}
1875
1876bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
1877{
1878 if (attachmentKeywords.isEmpty()) {
1879 return false;
1880 }
1881 if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
1882 return false;
1883 }
1884
1885 return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
1886}
1887
1888ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
1889{
1890 if (!hasMissingAttachments(attachmentKeywords)) {
1891 return NoMissingAttachmentFound;
1892 }
1893 const int rc = KMessageBox::warningTwoActionsCancel(m_editor,
1894
1895 i18n("The message you have composed seems to refer to an "
1896 "attached file but you have not attached anything.\n"
1897 "Do you want to attach a file to your message?"),
1898 i18nc("@title:window", "File Attachment Reminder"),
1899 KGuiItem(i18nc("@action:button", "&Attach File..."), QLatin1StringView("mail-attachment")),
1900 KGuiItem(i18nc("@action:button", "&Send as Is"), QLatin1StringView("mail-send")));
1901 if (rc == KMessageBox::Cancel) {
1902 return FoundMissingAttachmentAndCancel;
1903 }
1904 if (rc == KMessageBox::ButtonCode::PrimaryAction) {
1905 m_attachmentController->showAddAttachmentFileDialog();
1906 return FoundMissingAttachmentAndAddedAttachment;
1907 }
1908
1909 return FoundMissingAttachmentAndSending;
1910}
1911
1912void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
1913{
1914 if (m_attachmentModel) {
1915 const auto attachments = m_attachmentModel->attachments();
1916 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1917 attachment->setSigned(sign);
1918 }
1919 }
1920}
1921
1922void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
1923{
1924 if (m_attachmentModel) {
1925 const auto attachments = m_attachmentModel->attachments();
1926 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1927 attachment->setEncrypted(encrypt);
1928 }
1929 }
1930}
1931
1932bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, Kleo::KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
1933{
1934 bool sign = false;
1935 switch (keyResolver->checkSigningPreferences(signSomething)) {
1936 case Kleo::DoIt:
1937 if (!signSomething) {
1938 markAllAttachmentsForSigning(true);
1939 return true;
1940 }
1941 sign = true;
1942 break;
1943 case Kleo::DontDoIt:
1944 sign = false;
1945 break;
1946 case Kleo::AskOpportunistic:
1947 assert(0);
1948 case Kleo::Ask: {
1949 // the user wants to be asked or has to be asked
1951 const QString msg = i18n(
1952 "Examination of the recipient's signing preferences "
1953 "yielded that you be asked whether or not to sign "
1954 "this message.\n"
1955 "Sign this message?");
1956 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1957 msg,
1958 i18nc("@title:window", "Sign Message?"),
1959 KGuiItem(i18nc("to sign", "&Sign")),
1960 KGuiItem(i18nc("@action:button", "Do &Not Sign")))) {
1962 result = false;
1963 canceled = true;
1964 return false;
1965 case KMessageBox::ButtonCode::PrimaryAction:
1966 markAllAttachmentsForSigning(true);
1967 return true;
1968 case KMessageBox::ButtonCode::SecondaryAction:
1969 markAllAttachmentsForSigning(false);
1970 return false;
1971 default:
1972 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1973 return false;
1974 }
1975 break;
1976 }
1977 case Kleo::Conflict: {
1978 // warn the user that there are conflicting signing preferences
1980 const QString msg = i18n(
1981 "There are conflicting signing preferences "
1982 "for these recipients.\n"
1983 "Sign this message?");
1984 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1985 msg,
1986 i18nc("@title:window", "Sign Message?"),
1987 KGuiItem(i18nc("to sign", "&Sign")),
1988 KGuiItem(i18nc("@action:button", "Do &Not Sign")))) {
1990 result = false;
1991 canceled = true;
1992 return false;
1993 case KMessageBox::ButtonCode::PrimaryAction:
1994 markAllAttachmentsForSigning(true);
1995 return true;
1996 case KMessageBox::ButtonCode::SecondaryAction:
1997 markAllAttachmentsForSigning(false);
1998 return false;
1999 default:
2000 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2001 return false;
2002 }
2003 break;
2004 }
2005 case Kleo::Impossible: {
2007 const QString msg = i18n(
2008 "You have requested to sign this message, "
2009 "but no valid signing keys have been configured "
2010 "for this identity.");
2011 if (KMessageBox::warningContinueCancel(m_parentWidget,
2012 msg,
2013 i18nc("@title:window", "Send Unsigned?"),
2014 KGuiItem(i18nc("@action:button", "Send &Unsigned")))
2016 result = false;
2017 return false;
2018 } else {
2019 markAllAttachmentsForSigning(false);
2020 return false;
2021 }
2022 }
2023 }
2024
2025 if (!sign || !doSignCompletely) {
2026 if (cryptoWarningUnsigned(currentIdentity())) {
2028 const QString msg = sign && !doSignCompletely ? i18n(
2029 "Some parts of this message will not be signed.\n"
2030 "Sending only partially signed messages might violate site policy.\n"
2031 "Sign all parts instead?") // oh, I hate this...
2032 : i18n(
2033 "This message will not be signed.\n"
2034 "Sending unsigned message might violate site policy.\n"
2035 "Sign message instead?"); // oh, I hate this...
2036 const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign");
2037 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2038 msg,
2039 i18nc("@title:window", "Unsigned-Message Warning"),
2040 KGuiItem(buttonText),
2041 KGuiItem(i18nc("@action:button", "Send &As Is")))) {
2043 result = false;
2044 canceled = true;
2045 return false;
2046 case KMessageBox::ButtonCode::PrimaryAction:
2047 markAllAttachmentsForSigning(true);
2048 return true;
2049 case KMessageBox::ButtonCode::SecondaryAction:
2050 return sign || doSignCompletely;
2051 default:
2052 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2053 return false;
2054 }
2055 }
2056 }
2057 return sign || doSignCompletely;
2058}
2059
2060bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely,
2061 Kleo::KeyResolver *keyResolver,
2062 bool encryptSomething,
2063 bool signSomething,
2064 bool &result,
2065 bool &canceled)
2066{
2067 bool encrypt = false;
2068 bool opportunistic = false;
2069 switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
2070 case Kleo::DoIt:
2071 if (!encryptSomething) {
2072 markAllAttachmentsForEncryption(true);
2073 return true;
2074 }
2075 encrypt = true;
2076 break;
2077 case Kleo::DontDoIt:
2078 encrypt = false;
2079 break;
2080 case Kleo::AskOpportunistic:
2081 opportunistic = true;
2082 // fall through...
2083 [[fallthrough]];
2084 case Kleo::Ask: {
2085 // the user wants to be asked or has to be asked
2087 const QString msg = opportunistic ? i18n(
2088 "Valid trusted encryption keys were found for all recipients.\n"
2089 "Encrypt this message?")
2090 : i18n(
2091 "Examination of the recipient's encryption preferences "
2092 "yielded that you be asked whether or not to encrypt "
2093 "this message.\n"
2094 "Encrypt this message?");
2095 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2096 msg,
2097 i18n("Encrypt Message?"),
2098 KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")),
2099 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2101 result = false;
2102 canceled = true;
2103 return false;
2104 case KMessageBox::ButtonCode::PrimaryAction:
2105 markAllAttachmentsForEncryption(true);
2106 return true;
2107 case KMessageBox::ButtonCode::SecondaryAction:
2108 markAllAttachmentsForEncryption(false);
2109 return false;
2110 default:
2111 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2112 return false;
2113 }
2114 break;
2115 }
2116 case Kleo::Conflict: {
2117 // warn the user that there are conflicting encryption preferences
2119 const QString msg = i18n(
2120 "There are conflicting encryption preferences "
2121 "for these recipients.\n"
2122 "Encrypt this message?");
2124
2125 m_parentWidget,
2126 msg,
2127 i18n("Encrypt Message?"),
2128 KGuiItem(i18nc("@action:button", "&Encrypt")),
2129 KGuiItem(i18nc("@action:button", "Do &Not Encrypt")))) {
2131 result = false;
2132 canceled = true;
2133 return false;
2134 case KMessageBox::ButtonCode::PrimaryAction:
2135 markAllAttachmentsForEncryption(true);
2136 return true;
2137 case KMessageBox::ButtonCode::SecondaryAction:
2138 markAllAttachmentsForEncryption(false);
2139 return false;
2140 default:
2141 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2142 return false;
2143 }
2144 break;
2145 }
2146 case Kleo::Impossible: {
2148 const QString msg = i18n(
2149 "You have requested to encrypt this message, "
2150 "and to encrypt a copy to yourself, "
2151 "but no valid trusted encryption keys have been "
2152 "configured for this identity.");
2153 if (KMessageBox::warningContinueCancel(m_parentWidget,
2154 msg,
2155 i18nc("@title:window", "Send Unencrypted?"),
2156 KGuiItem(i18nc("@action:button", "Send &Unencrypted")))
2158 result = false;
2159 return false;
2160 } else {
2161 markAllAttachmentsForEncryption(false);
2162 return false;
2163 }
2164 }
2165 }
2166
2167 if (!encrypt || !doEncryptCompletely) {
2168 if (cryptoWarningUnencrypted(currentIdentity())) {
2170 const QString msg = !doEncryptCompletely ? i18n(
2171 "Some parts of this message will not be encrypted.\n"
2172 "Sending only partially encrypted messages might violate "
2173 "site policy and/or leak sensitive information.\n"
2174 "Encrypt all parts instead?") // oh, I hate this...
2175 : i18n(
2176 "This message will not be encrypted.\n"
2177 "Sending unencrypted messages might violate site policy and/or "
2178 "leak sensitive information.\n"
2179 "Encrypt messages instead?"); // oh, I hate this...
2180 const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt");
2181 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2182 msg,
2183 i18nc("@title:window", "Unencrypted Message Warning"),
2184 KGuiItem(buttonText),
2185 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2187 result = false;
2188 canceled = true;
2189 return false;
2190 case KMessageBox::ButtonCode::PrimaryAction:
2191 markAllAttachmentsForEncryption(true);
2192 return true;
2193 case KMessageBox::ButtonCode::SecondaryAction:
2194 return encrypt || doEncryptCompletely;
2195 default:
2196 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2197 return false;
2198 }
2199 }
2200 }
2201
2202 return encrypt || doEncryptCompletely;
2203}
2204
2205void ComposerViewBase::setSendLaterInfo(SendLaterInfo *info)
2206{
2207 mSendLaterInfo.reset(info);
2208}
2209
2210SendLaterInfo *ComposerViewBase::sendLaterInfo() const
2211{
2212 return mSendLaterInfo.get();
2213}
2214
2215void ComposerViewBase::addFollowupReminder(const QString &messageId)
2216{
2217 if (!messageId.isEmpty()) {
2218 if (mFollowUpDate.isValid()) {
2220 job->setSubject(m_subject);
2221 job->setMessageId(messageId);
2222 job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
2223 job->setFollowUpReminderDate(mFollowUpDate);
2224 job->setCollectionToDo(mFollowUpCollection);
2225 job->start();
2226 }
2227 }
2228}
2229
2230void ComposerViewBase::addSendLaterItem(const Akonadi::Item &item)
2231{
2232 mSendLaterInfo->setItemId(item.id());
2233
2234 auto job = new MessageComposer::SendLaterCreateJob(*mSendLaterInfo, this);
2235 job->start();
2236}
2237
2238bool ComposerViewBase::requestDeleveryConfirmation() const
2239{
2240 return m_requestDeleveryConfirmation;
2241}
2242
2243void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
2244{
2245 m_requestDeleveryConfirmation = requestDeleveryConfirmation;
2246}
2247
2248KMime::Message::Ptr ComposerViewBase::msg() const
2249{
2250 return m_msg;
2251}
2252
2253std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
2254{
2255 if (!mExpiryChecker) {
2256 mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
2257 encryptKeyNearExpiryWarningThresholdInDays(),
2258 encryptRootCertNearExpiryWarningThresholdInDays(),
2259 encryptChainCertNearExpiryWarningThresholdInDays()}});
2260 }
2261 return mExpiryChecker;
2262}
2263
2264#include "moc_composerviewbase.cpp"
Akonadi::Collection currentCollection() const
void setDefaultCollection(const Collection &collection)
Collection::List collections() const
bool isValid() const
void setPayload(const T &p)
void setMimeType(const QString &mimeType)
Id id() const
bool isValid() const
static SpecialMailCollections * self()
Akonadi::Collection defaultCollection(Type type) const
const Identity & identityForUoidOrDefault(uint uoid) const
QString autocorrectionLanguage() const
QString rawText(bool *ok=nullptr, QString *errorMessage=nullptr) const
KIdentityManagementCore::Identity::Id currentIdentity() const
virtual QString errorString() const
int error() const
void result(KJob *job)
virtual Q_SCRIPTABLE void start()=0
QList< Content * > attachments() const
const Headers::ContentType * contentType() const
Content * parent()
QByteArray decodedContent() const
void setContent(const QByteArray &s)
const Headers::ContentDisposition * contentDisposition() const
QList< Content * > contents() const
QByteArray encodedContent(bool useCrLf=false) const
const Headers::ContentDescription * contentDescription() const
QByteArray subType() const
QByteArray mimeType() const
static QList< Mailbox > listFromUnicodeString(QStringView s)
bool setCurrentTransport(int transportId)
Transport * transportById(Transport::Id id, bool def=true) const
static TransportManager * self()
void addAttachment(const MessageCore::AttachmentPart::Ptr &part)
sets sign, encrypt, shows properties dialog if so configured
The AttachmentModel class.
void addAttachment(const QUrl &url, const QString &comment, bool sync)
Add the given attachment to the message.
void setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts=false)
The following are various settings the user can modify when composing a message.
void setFrom(const QString &from)
Widgets for editing differ in client classes, so values are set before sending.
QString to() const
Header fields in recipients editor.
void send(MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher=true)
Send the message with the specified method, saving it in the specified folder.
void setMessage(const KMime::Message::Ptr &newMsg, bool allowDecryption)
Set the message to be opened in the composer window, and set the internal data structures to keep tra...
void setAttachmentModel(MessageComposer::AttachmentModel *model)
The following are for setting the various options and widgets in the composer.
bool isComposing() const
Returns true if there is at least one composer job running.
void failed(const QString &errorMessage, MessageComposer::ComposerViewBase::FailedType type=Sending)
Message sending failed with given error message.
void sentSuccessfully(Akonadi::Item::Id id)
Message sending completed successfully.
void updateAutoSave()
Enables/disables autosaving depending on the value of the autosave interval.
void cleanupAutoSave()
Stop autosaving and delete the autosaved message.
ComposerViewBase::MissingAttachment checkForMissingAttachments(const QStringList &attachmentKeywords)
Check if the mail has references to attachments, but no attachments are added to it.
void disableHtml(MessageComposer::ComposerViewBase::Confirmation)
Enabling or disabling HTML in the editor is affected by various client options, so when that would ot...
void setAutoSaveFileName(const QString &fileName)
Sets the filename to use when autosaving something.
void modified(bool isModified)
The composer was modified.
void autoSaveMessage()
Save the message.
The Composer class.
Definition composer.h:35
void setAutoSave(bool isAutoSave)
Sets if this message being composed is an auto-saved message if so, might need different handling,...
Definition composer.cpp:673
A job to resolve nicknames, distribution lists and email addresses for queued emails.
QStringList expandedReplyTo() const
Returns the expanded Reply-To field.
QString expandedFrom() const
Returns the expanded From field.
QStringList expandedCc() const
Returns the expanded CC field.
QStringList expandedBcc() const
Returns the expanded Bcc field.
QStringList expandedTo() const
Returns the expanded To field.
The GlobalPart class.
Definition globalpart.h:20
The InfoPart class contains the message header.
Definition infopart.h:22
QStringList cc
Carbon copy: The email address and optionally the name of the secondary recipients.
Definition infopart.h:32
QString from
The email address and optionally the name of the author of the mail.
Definition infopart.h:26
QStringList to
The email address and optionally the name of the primary recipients.
Definition infopart.h:29
QString fcc
The name of a file, to which a copy of the sent message should be appended.
Definition infopart.h:45
QStringList bcc
Blind Carbon copy: The email address and optionally the name of the secondary recipients.
Definition infopart.h:36
The RecipientsEditor class.
void removeRecipient(const QString &recipient, Recipient::Type type)
Removes the recipient provided it can be found and has the given type.
bool addRecipient(const QString &recipient, Recipient::Type type)
Adds a recipient (or multiple recipients) to one line of the editor.
The RichTextComposerNg class.
Send later information.
The SignatureController class Controls signature (the footer thing, not the crypto thing) operations ...
void applySignature(const KIdentityManagementCore::Signature &signature)
Adds the given signature to the editor, taking user preferences into account.
A class that encapsulates an attachment.
QSharedPointer< AttachmentPart > Ptr
Defines a pointer to an attachment object.
QList< KMime::Content * > attachmentsOfExtraContents() const
Returns a list of attachments of attached extra content nodes.
Parses messages and generates HTML display code out of them.
void parseObjectTree(KMime::Content *node, bool parseOnlySingleNode=false)
Parse beginning at a given node and recursively parsing the children of that node and it's next sibli...
QString plainTextContent() const
The text of the message, ie.
QString htmlContent() const
Similar to plainTextContent(), but returns the HTML source of the first text/html MIME part.
void add(const QString &entry)
static RecentAddresses * self(KConfig *config=nullptr)
QString currentDictionary() const
KCODECS_EXPORT QByteArray extractEmailAddress(const QByteArray &address)
KCODECS_EXPORT QString normalizeAddressesAndEncodeIdn(const QString &str)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_MIME_EXPORT void copyMessageFlags(KMime::Message &from, Akonadi::Item &to)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
QAction * create(GameStandardAction id, const QObject *recvr, const char *slot, QObject *parent)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
PostalAddress address(const QVariant &location)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
ButtonCode warningTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ButtonCode warningTwoActionsCancel(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
KIOCORE_EXPORT QString dir(const QString &fileClass)
KGuiItem cont()
KGuiItem cancel()
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
void removePrivateHeaderFields(const KMime::Message::Ptr &message, bool cleanUpHeader)
Removes all private header fields (e.g.
const char * constData() const const
bool isEmpty() const const
bool isValid(int year, int month, int day)
QDateTime currentDateTime()
QStringList entryList(Filters filters, SortFlags sort) const const
bool mkpath(const QString &dirPath) const const
bool remove(const QString &fileName)
void setNameFilters(const QStringList &nameFilters)
int exec(ProcessEventsFlags flags)
void quit()
bool loadFromData(QByteArrayView data, const char *format)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool contains(const AT &value) const const
qsizetype count() const const
bool isEmpty() const const
void push_back(parameter_type value)
qsizetype removeAll(const AT &t)
void reserve(qsizetype size)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
T * data() const const
QString writableLocation(StandardLocation type)
void clear()
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QByteArray toLatin1() const const
qlonglong toLongLong(bool *ok, int base) const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
WaitCursor
void setHtml(const QString &text)
void setPlainText(const QString &text)
void start()
void stop()
void timeout()
QUrl fromLocalFile(const QString &localFile)
QUuid createUuid()
QString toString(StringFormat mode) const const
QVariant fromValue(T &&value)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:54:19 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.