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 =
721 key.protocol() == GpgME::OpenPGP ? i18nc("@title:window", "OpenPGP Key Expired") : i18nc("@title:window", "S/MIME Certificate Expired");
722 } else {
723 title = key.protocol() == GpgME::OpenPGP ? i18nc("@title:window", "OpenPGP Key Expires Soon")
724 : i18nc("@title:window", "S/MIME Certificate Expires Soon");
725 }
726 if (KMessageBox::warningContinueCancel(nullptr, msg, title, KStandardGuiItem::cont(), KStandardGuiItem::cancel(), dontAskAgainName)
728 canceled = true;
729 }
730 });
731
732 QScopedPointer<KeyResolver> keyResolver(new KeyResolver(true, showKeyApprovalDialog(), id.pgpAutoEncrypt(), m_cryptoMessageFormat, expiryChecker()));
733
734 keyResolver->setAutocryptEnabled(autocryptEnabled());
735 keyResolver->setAkonadiLookupEnabled(m_akonadiLookupEnabled);
736
737 QStringList encryptToSelfKeys;
738 QStringList signKeys;
739
740 bool signSomething = m_sign;
741 bool doSignCompletely = m_sign;
742 bool encryptSomething = m_encrypt;
743 bool doEncryptCompletely = m_encrypt;
744
745 // Add encryptionkeys from id to keyResolver
746 if (!id.pgpEncryptionKey().isEmpty()) {
747 encryptToSelfKeys.push_back(QLatin1StringView(id.pgpEncryptionKey()));
748 }
749 if (!id.smimeEncryptionKey().isEmpty()) {
750 encryptToSelfKeys.push_back(QLatin1StringView(id.smimeEncryptionKey()));
751 }
752 if (canceled || keyResolver->setEncryptToSelfKeys(encryptToSelfKeys) != ResolverResult::Ok) {
753 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set encryptoToSelf keys!";
754 return {};
755 }
756
757 // Add signingkeys from id to keyResolver
758 if (!id.pgpSigningKey().isEmpty()) {
759 signKeys.push_back(QLatin1StringView(id.pgpSigningKey()));
760 }
761 if (!id.smimeSigningKey().isEmpty()) {
762 signKeys.push_back(QLatin1StringView(id.smimeSigningKey()));
763 }
764 if (canceled || keyResolver->setSigningKeys(signKeys) != ResolverResult::Ok) {
765 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to set signing keys!";
766 return {};
767 }
768
769 if (m_attachmentModel) {
770 const auto attachments = m_attachmentModel->attachments();
771 for (const MessageCore::AttachmentPart::Ptr &attachment : attachments) {
772 if (attachment->isSigned()) {
773 signSomething = true;
774 } else {
775 doEncryptCompletely = false;
776 }
777 if (attachment->isEncrypted()) {
778 encryptSomething = true;
779 } else {
780 doSignCompletely = false;
781 }
782 }
783 }
784
785 const QStringList recipients = mExpandedTo + mExpandedCc;
786 const QStringList bcc(mExpandedBcc);
787
788 keyResolver->setPrimaryRecipients(recipients);
789 keyResolver->setSecondaryRecipients(bcc);
790
791 bool result = true;
792 canceled = false;
793 signSomething = determineWhetherToSign(doSignCompletely, keyResolver.data(), signSomething, result, canceled);
794 if (!result) {
795 // TODO handle failure
796 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToSign: failed to resolve keys! oh noes";
797 if (!canceled) {
798 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
799 } else {
801 }
802 wasCanceled = canceled;
803 return {};
804 }
805
806 canceled = false;
807 encryptSomething = determineWhetherToEncrypt(doEncryptCompletely, keyResolver.data(), encryptSomething, signSomething, result, canceled);
808 if (!result) {
809 // TODO handle failure
810 qCDebug(MESSAGECOMPOSER_LOG) << "determineWhetherToEncrypt: failed to resolve keys! oh noes";
811 if (!canceled) {
812 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
813 } else {
815 }
816
817 wasCanceled = canceled;
818 return {};
819 }
820
822
823 // No encryption or signing is needed
824 if (!signSomething && !encryptSomething) {
825 auto composer = new MessageComposer::Composer;
826 if (m_cryptoMessageFormat & Kleo::OpenPGPMIMEFormat) {
827 composer->setAutocryptEnabled(autocryptEnabled());
828 if (keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat).size() > 0) {
829 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(Kleo::OpenPGPMIMEFormat)[0]);
830 }
831 }
832 composers.append(composer);
833 return composers;
834 }
835
836 canceled = false;
837 const auto kpgpResult = keyResolver->resolveAllKeys(signSomething, encryptSomething);
838 if (kpgpResult == ResolverResult::Canceled || canceled) {
839 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: one key resolution canceled by user";
840 return {};
841 } else if (kpgpResult != ResolverResult::Ok) {
842 // TODO handle failure
843 qCDebug(MESSAGECOMPOSER_LOG) << "resolveAllKeys: failed to resolve keys! oh noes";
844 Q_EMIT failed(i18n("Failed to resolve keys. Please report a bug."));
845 return {};
846 }
847
848 qCDebug(MESSAGECOMPOSER_LOG) << "done resolving keys.";
849
850 if (encryptSomething || signSomething) {
851 Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat;
852 for (unsigned int i = 0; i < numConcreteCryptoMessageFormats; ++i) {
853 concreteFormat = concreteCryptoMessageFormats[i];
854 const auto encData = keyResolver->encryptionItems(concreteFormat);
855 if (encData.empty()) {
856 continue;
857 }
858
859 if (!(concreteFormat & m_cryptoMessageFormat)) {
860 continue;
861 }
862
863 auto composer = new MessageComposer::Composer;
864
865 if (encryptSomething || autocryptEnabled()) {
866 auto end(encData.end());
868 data.reserve(encData.size());
869 for (auto it = encData.begin(); it != end; ++it) {
870 QPair<QStringList, std::vector<GpgME::Key>> p(it->recipients, it->keys);
871 data.append(p);
872 qCDebug(MESSAGECOMPOSER_LOG) << "got resolved keys for:" << it->recipients;
873 }
874 composer->setEncryptionKeys(data);
875 if (concreteFormat & Kleo::OpenPGPMIMEFormat && autocryptEnabled()) {
876 composer->setAutocryptEnabled(autocryptEnabled());
877 composer->setSenderEncryptionKey(keyResolver->encryptToSelfKeysFor(concreteFormat)[0]);
879 bool specialGnupgHome = addKeysToContext(dir.path(), data, keyResolver->useAutocrypt());
880 if (specialGnupgHome) {
881 dir.setAutoRemove(false);
882 composer->setGnupgHome(dir.path());
883 }
884 }
885 }
886
887 if (signSomething) {
888 // find signing keys for this format
889 std::vector<GpgME::Key> signingKeys = keyResolver->signingKeys(concreteFormat);
890 composer->setSigningKeys(signingKeys);
891 }
892
893 composer->setCryptoMessageFormat(concreteFormat);
894 composer->setSignAndEncrypt(signSomething, encryptSomething);
895
896 composers.append(composer);
897 }
898 } else {
899 auto composer = new MessageComposer::Composer;
900 composers.append(composer);
901 // If we canceled sign or encrypt be sure to change status in attachment.
902 markAllAttachmentsForSigning(false);
903 markAllAttachmentsForEncryption(false);
904 }
905
906 if (composers.isEmpty() && (signSomething || encryptSomething)) {
907 Q_ASSERT_X(false, "ComposerViewBase::generateCryptoMessages", "No concrete sign or encrypt method selected");
908 }
909
910 return composers;
911}
912
913void ComposerViewBase::fillGlobalPart(MessageComposer::GlobalPart *globalPart)
914{
915 globalPart->setParentWidgetForGui(m_parentWidget);
916 globalPart->setMDNRequested(m_mdnRequested);
917 globalPart->setRequestDeleveryConfirmation(m_requestDeleveryConfirmation);
918}
919
920void ComposerViewBase::fillInfoPart(MessageComposer::InfoPart *infoPart, ComposerViewBase::RecipientExpansion expansion)
921{
922 // TODO splitAddressList and expandAliases ugliness should be handled by a
923 // special AddressListEdit widget... (later: see RecipientsEditor)
924
925 if (m_fccCombo) {
926 infoPart->setFcc(QString::number(m_fccCombo->currentCollection().id()));
927 } else {
928 if (m_fccCollection.isValid()) {
929 infoPart->setFcc(QString::number(m_fccCollection.id()));
930 }
931 }
932
933 infoPart->setTransportId(m_transport->currentTransportId());
934 if (expansion == UseExpandedRecipients) {
935 infoPart->setFrom(mExpandedFrom);
936 infoPart->setTo(mExpandedTo);
937 infoPart->setCc(mExpandedCc);
938 infoPart->setBcc(mExpandedBcc);
939 infoPart->setReplyTo(mExpandedReplyTo);
940 } else {
941 infoPart->setFrom(from());
942 infoPart->setTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::To));
943 infoPart->setCc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Cc));
944 infoPart->setBcc(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::Bcc));
945 infoPart->setReplyTo(m_recipientsEditor->recipientStringList(MessageComposer::Recipient::ReplyTo));
946 }
947 infoPart->setSubject(subject());
948 infoPart->setUserAgent(QStringLiteral("KMail"));
949 infoPart->setUrgent(m_urgent);
950
951 if (auto inReplyTo = m_msg->inReplyTo(false)) {
952 infoPart->setInReplyTo(inReplyTo->asUnicodeString());
953 }
954
955 if (auto references = m_msg->references(false)) {
956 infoPart->setReferences(references->asUnicodeString());
957 }
958
960 if (auto hdr = m_msg->headerByType("X-KMail-SignatureActionEnabled")) {
961 extras << hdr;
962 }
963 if (auto hdr = m_msg->headerByType("X-KMail-EncryptActionEnabled")) {
964 extras << hdr;
965 }
966 if (auto hdr = m_msg->headerByType("X-KMail-CryptoMessageFormat")) {
967 extras << hdr;
968 }
969 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-To")) {
970 extras << hdr;
971 }
972 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-CC")) {
973 extras << hdr;
974 }
975 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-BCC")) {
976 extras << hdr;
977 }
978 if (auto hdr = m_msg->headerByType("X-KMail-UnExpanded-Reply-To")) {
979 extras << hdr;
980 }
981 if (auto hdr = m_msg->organization(false)) {
982 extras << hdr;
983 }
984 if (auto hdr = m_msg->headerByType("X-KMail-Identity")) {
985 extras << hdr;
986 }
987 if (auto hdr = m_msg->headerByType("X-KMail-Transport")) {
988 extras << hdr;
989 }
990 if (auto hdr = m_msg->headerByType("X-KMail-Fcc")) {
991 extras << hdr;
992 }
993 if (auto hdr = m_msg->headerByType("X-KMail-Drafts")) {
994 extras << hdr;
995 }
996 if (auto hdr = m_msg->headerByType("X-KMail-Templates")) {
997 extras << hdr;
998 }
999 if (auto hdr = m_msg->headerByType("X-KMail-Link-Message")) {
1000 extras << hdr;
1001 }
1002 if (auto hdr = m_msg->headerByType("X-KMail-Link-Type")) {
1003 extras << hdr;
1004 }
1005 if (auto hdr = m_msg->headerByType("X-Face")) {
1006 extras << hdr;
1007 }
1008 if (auto hdr = m_msg->headerByType("Face")) {
1009 extras << hdr;
1010 }
1011 if (auto hdr = m_msg->headerByType("X-KMail-FccDisabled")) {
1012 extras << hdr;
1013 }
1014 if (auto hdr = m_msg->headerByType("X-KMail-Identity-Name")) {
1015 extras << hdr;
1016 }
1017 if (auto hdr = m_msg->headerByType("X-KMail-Transport-Name")) {
1018 extras << hdr;
1019 }
1020
1021 infoPart->setExtraHeaders(extras);
1022}
1023
1024void ComposerViewBase::slotSendComposeResult(KJob *job)
1025{
1026 Q_ASSERT(dynamic_cast<MessageComposer::Composer *>(job));
1027 auto composer = static_cast<MessageComposer::Composer *>(job);
1028 if (composer->error() != MessageComposer::Composer::NoError) {
1029 qCDebug(MESSAGECOMPOSER_LOG) << "compose job might have error: " << job->error() << " errorString: " << job->errorString();
1030 }
1031
1032 if (composer->error() == MessageComposer::Composer::NoError) {
1033 Q_ASSERT(m_composers.contains(composer));
1034 // The messages were composed successfully.
1035 qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1036 const int numberOfMessage(composer->resultMessages().size());
1037 for (int i = 0; i < numberOfMessage; ++i) {
1038 if (mSaveIn == MessageComposer::MessageSender::SaveInNone) {
1039 queueMessage(composer->resultMessages().at(i), composer);
1040 } else {
1041 saveMessage(composer->resultMessages().at(i), mSaveIn);
1042 }
1043 }
1044 saveRecentAddresses(composer->resultMessages().at(0));
1045 } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1046 // The job warned the user about something, and the user chose to return
1047 // to the message. Nothing to do.
1048 qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1049 Q_EMIT failed(i18n("Job cancelled by the user"));
1050 } else {
1051 qCDebug(MESSAGECOMPOSER_LOG) << "other Error." << composer->error();
1052 QString msg;
1053 if (composer->error() == MessageComposer::Composer::BugError) {
1054 msg = i18n("Could not compose message: %1 \n Please report this bug.", job->errorString());
1055 } else {
1056 msg = i18n("Could not compose message: %1", job->errorString());
1057 }
1058 Q_EMIT failed(msg);
1059 }
1060
1061 if (!composer->gnupgHome().isEmpty()) {
1062 QDir dir(composer->gnupgHome());
1063 dir.removeRecursively();
1064 }
1065
1066 m_composers.removeAll(composer);
1067}
1068
1069void ComposerViewBase::saveRecentAddresses(const KMime::Message::Ptr &msg)
1070{
1071 KConfig *config = MessageComposer::MessageComposerSettings::self()->config();
1072 if (auto to = msg->to(false)) {
1073 const auto toAddresses = to->mailboxes();
1074 for (const auto &address : toAddresses) {
1075 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1076 }
1077 }
1078 if (auto cc = msg->cc(false)) {
1079 const auto ccAddresses = cc->mailboxes();
1080 for (const auto &address : ccAddresses) {
1081 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1082 }
1083 }
1084 if (auto bcc = msg->bcc(false)) {
1085 const auto bccAddresses = bcc->mailboxes();
1086 for (const auto &address : bccAddresses) {
1087 PimCommon::RecentAddresses::self(config)->add(address.prettyAddress());
1088 }
1089 }
1090}
1091
1092void ComposerViewBase::queueMessage(const KMime::Message::Ptr &message, MessageComposer::Composer *composer)
1093{
1094 const MessageComposer::InfoPart *infoPart = composer->infoPart();
1095 auto qjob = new Akonadi::MessageQueueJob(this);
1096 qjob->setMessage(message);
1097 qjob->transportAttribute().setTransportId(infoPart->transportId());
1098 if (mSendMethod == MessageComposer::MessageSender::SendLater) {
1099 qjob->dispatchModeAttribute().setDispatchMode(Akonadi::DispatchModeAttribute::Manual);
1100 }
1101
1102 if (message->hasHeader("X-KMail-FccDisabled")) {
1103 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::Delete);
1104 } else if (!infoPart->fcc().isEmpty()) {
1105 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToCollection);
1106
1107 const Akonadi::Collection sentCollection(infoPart->fcc().toLongLong());
1108 qjob->sentBehaviourAttribute().setMoveToCollection(sentCollection);
1109 } else {
1110 qjob->sentBehaviourAttribute().setSentBehaviour(Akonadi::SentBehaviourAttribute::MoveToDefaultSentCollection);
1111 }
1112
1113 MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById(infoPart->transportId());
1114 if (transport && transport->specifySenderOverwriteAddress()) {
1115 qjob->addressAttribute().setFrom(
1117 } else {
1118 qjob->addressAttribute().setFrom(KEmailAddress::extractEmailAddress(KEmailAddress::normalizeAddressesAndEncodeIdn(infoPart->from())));
1119 }
1120 // if this header is not empty, it contains the real recipient of the message, either the primary or one of the
1121 // secondary recipients. so we set that to the transport job, while leaving the message itself alone.
1122 if (KMime::Headers::Base *realTo = message->headerByType("X-KMail-EncBccRecipients")) {
1123 qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(realTo->asUnicodeString().split(QLatin1Char('%'))));
1124 message->removeHeader("X-KMail-EncBccRecipients");
1125 message->assemble();
1126 qCDebug(MESSAGECOMPOSER_LOG) << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to();
1127 } else {
1128 qjob->addressAttribute().setTo(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->to()));
1129 qjob->addressAttribute().setCc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->cc()));
1130 qjob->addressAttribute().setBcc(MessageComposer::Util::cleanUpEmailListAndEncoding(infoPart->bcc()));
1131 }
1132 if (m_requestDeleveryConfirmation) {
1133 qjob->addressAttribute().setDeliveryStatusNotification(true);
1134 }
1135 MessageComposer::Util::addSendReplyForwardAction(message, qjob);
1137
1138 MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1139 message->assemble();
1140 connect(qjob, &Akonadi::MessageQueueJob::result, this, &ComposerViewBase::slotQueueResult);
1141 m_pendingQueueJobs++;
1142 qjob->start();
1143
1144 qCDebug(MESSAGECOMPOSER_LOG) << "Queued a message.";
1145}
1146
1147void ComposerViewBase::slotQueueResult(KJob *job)
1148{
1149 m_pendingQueueJobs--;
1150 auto qjob = static_cast<Akonadi::MessageQueueJob *>(job);
1151 qCDebug(MESSAGECOMPOSER_LOG) << "mPendingQueueJobs" << m_pendingQueueJobs;
1152 Q_ASSERT(m_pendingQueueJobs >= 0);
1153
1154 if (job->error()) {
1155 qCDebug(MESSAGECOMPOSER_LOG) << "Failed to queue a message:" << job->errorString();
1156 // There is not much we can do now, since all the MessageQueueJobs have been
1157 // started. So just wait for them to finish.
1158 // TODO show a message box or something
1159 const QString msg = i18n("There were problems trying to queue the message for sending: %1", job->errorString());
1160
1161 if (m_pendingQueueJobs == 0) {
1162 Q_EMIT failed(msg);
1163 return;
1164 }
1165 }
1166
1167 if (m_pendingQueueJobs == 0) {
1168 addFollowupReminder(qjob->message()->messageID(false)->asUnicodeString());
1170 }
1171}
1172
1173void ComposerViewBase::initAutoSave()
1174{
1175 qCDebug(MESSAGECOMPOSER_LOG) << "initialising autosave";
1176
1177 // Ensure that the autosave directory exists.
1179 if (!dataDirectory.exists(QStringLiteral("autosave"))) {
1180 qCDebug(MESSAGECOMPOSER_LOG) << "Creating autosave directory.";
1181 dataDirectory.mkdir(QStringLiteral("autosave"));
1182 }
1183
1184 // Construct a file name
1185 if (m_autoSaveUUID.isEmpty()) {
1186 m_autoSaveUUID = QUuid::createUuid().toString();
1187 }
1188
1190}
1191
1192Akonadi::Collection ComposerViewBase::followUpCollection() const
1193{
1194 return mFollowUpCollection;
1195}
1196
1197void ComposerViewBase::setFollowUpCollection(const Akonadi::Collection &followUpCollection)
1198{
1199 mFollowUpCollection = followUpCollection;
1200}
1201
1202QDate ComposerViewBase::followUpDate() const
1203{
1204 return mFollowUpDate;
1205}
1206
1207void ComposerViewBase::setFollowUpDate(const QDate &followUpDate)
1208{
1209 mFollowUpDate = followUpDate;
1210}
1211
1212Sonnet::DictionaryComboBox *ComposerViewBase::dictionary() const
1213{
1214 return m_dictionary;
1215}
1216
1217void ComposerViewBase::setDictionary(Sonnet::DictionaryComboBox *dictionary)
1218{
1219 m_dictionary = dictionary;
1220}
1221
1223{
1224 if (m_autoSaveInterval == 0) {
1225 delete m_autoSaveTimer;
1226 m_autoSaveTimer = nullptr;
1227 } else {
1228 if (!m_autoSaveTimer) {
1229 m_autoSaveTimer = new QTimer(this);
1230 if (m_parentWidget) {
1231 connect(m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()));
1232 } else {
1234 }
1235 }
1236 m_autoSaveTimer->start(m_autoSaveInterval);
1237 }
1238}
1239
1241{
1242 delete m_autoSaveTimer;
1243 m_autoSaveTimer = nullptr;
1244 if (!m_autoSaveUUID.isEmpty()) {
1245 qCDebug(MESSAGECOMPOSER_LOG) << "deleting autosave files" << m_autoSaveUUID;
1246
1247 // Delete the autosave files
1249
1250 // Filter out only this composer window's autosave files
1251 const QStringList autoSaveFilter{m_autoSaveUUID + QLatin1StringView("*")};
1252 autoSaveDir.setNameFilters(autoSaveFilter);
1253
1254 // Return the files to be removed
1255 const QStringList autoSaveFiles = autoSaveDir.entryList();
1256 qCDebug(MESSAGECOMPOSER_LOG) << "There are" << autoSaveFiles.count() << "to be deleted.";
1257
1258 // Delete each file
1259 for (const QString &file : autoSaveFiles) {
1260 autoSaveDir.remove(file);
1261 }
1262 m_autoSaveUUID.clear();
1263 }
1264}
1265
1266//-----------------------------------------------------------------------------
1268{
1269 qCDebug(MESSAGECOMPOSER_LOG) << "Autosaving message";
1270
1271 if (m_autoSaveTimer) {
1272 m_autoSaveTimer->stop();
1273 }
1274
1275 if (!m_composers.isEmpty()) {
1276 // This may happen if e.g. the autosave timer calls applyChanges.
1277 qCDebug(MESSAGECOMPOSER_LOG) << "Autosave: Called while composer active; ignoring. Number of composer " << m_composers.count();
1278 return;
1279 }
1280
1281 auto composer = new Composer();
1282 fillComposer(composer);
1283 composer->setAutoSave(true);
1284 composer->setAutocryptEnabled(autocryptEnabled());
1285 m_composers.append(composer);
1286 connect(composer, &MessageComposer::Composer::result, this, &ComposerViewBase::slotAutoSaveComposeResult);
1287 composer->start();
1288}
1289
1291{
1292 m_autoSaveUUID = fileName;
1293
1294 Q_EMIT modified(true);
1295}
1296
1297void ComposerViewBase::slotAutoSaveComposeResult(KJob *job)
1298{
1300
1301 Q_ASSERT(dynamic_cast<Composer *>(job));
1302 auto composer = static_cast<Composer *>(job);
1303
1304 if (composer->error() == Composer::NoError) {
1305 Q_ASSERT(m_composers.contains(composer));
1306
1307 // The messages were composed successfully. Only save the first message, there should
1308 // only be one anyway, since crypto is disabled.
1309 qCDebug(MESSAGECOMPOSER_LOG) << "NoError.";
1310 writeAutoSaveToDisk(composer->resultMessages().constFirst());
1311 Q_ASSERT(composer->resultMessages().size() == 1);
1312
1313 if (m_autoSaveInterval > 0) {
1315 }
1316 } else if (composer->error() == MessageComposer::Composer::UserCancelledError) {
1317 // The job warned the user about something, and the user chose to return
1318 // to the message. Nothing to do.
1319 qCDebug(MESSAGECOMPOSER_LOG) << "UserCancelledError.";
1320 Q_EMIT failed(i18n("Job cancelled by the user"), AutoSave);
1321 } else {
1322 qCDebug(MESSAGECOMPOSER_LOG) << "other Error.";
1323 Q_EMIT failed(i18n("Could not autosave message: %1", job->errorString()), AutoSave);
1324 }
1325
1326 m_composers.removeAll(composer);
1327}
1328
1329void ComposerViewBase::writeAutoSaveToDisk(const KMime::Message::Ptr &message)
1330{
1332 QDir().mkpath(autosavePath);
1333 const QString filename = autosavePath + m_autoSaveUUID;
1334 QSaveFile file(filename);
1336 qCDebug(MESSAGECOMPOSER_LOG) << "Writing message to disk as" << filename;
1337
1338 if (file.open(QIODevice::WriteOnly)) {
1339 file.setPermissions(QFile::ReadUser | QFile::WriteUser);
1340
1341 if (file.write(message->encodedContent()) != static_cast<qint64>(message->encodedContent().size())) {
1342 errorMessage = i18n("Could not write all data to file.");
1343 } else {
1344 if (!file.commit()) {
1345 errorMessage = i18n("Could not finalize the file.");
1346 }
1347 }
1348 } else {
1349 errorMessage = i18n("Could not open file.");
1350 }
1351
1352 if (!errorMessage.isEmpty()) {
1353 qCWarning(MESSAGECOMPOSER_LOG) << "Auto saving failed:" << errorMessage << file.errorString() << " m_autoSaveUUID" << m_autoSaveUUID;
1354 if (!m_autoSaveErrorShown) {
1355 KMessageBox::error(m_parentWidget,
1356 i18n("Autosaving the message as %1 failed.\n"
1357 "%2\n"
1358 "Reason: %3",
1359 filename,
1360 errorMessage,
1361 file.errorString()),
1362 i18nc("@title:window", "Autosaving Message Failed"));
1363
1364 // Error dialog shown, hide the errors the next time
1365 m_autoSaveErrorShown = true;
1366 }
1367 } else {
1368 // No error occurred, the next error should be shown again
1369 m_autoSaveErrorShown = false;
1370 }
1371 file.commit();
1372 message->clear();
1373}
1374
1375void ComposerViewBase::saveMessage(const KMime::Message::Ptr &message, MessageComposer::MessageSender::SaveIn saveIn)
1376{
1377 Akonadi::Collection target;
1378 const auto identity = currentIdentity();
1379 message->date()->setDateTime(QDateTime::currentDateTime());
1380 if (!identity.isNull()) {
1381 if (auto header = message->headerByType("X-KMail-Fcc")) {
1382 const int sentCollectionId = header->asUnicodeString().toInt();
1383 if (identity.fcc() == QString::number(sentCollectionId)) {
1384 message->removeHeader("X-KMail-Fcc");
1385 }
1386 }
1387 }
1388 MessageComposer::Util::addCustomHeaders(message, m_customHeader);
1389
1390 message->assemble();
1391
1392 Akonadi::Item item;
1393 item.setMimeType(QStringLiteral("message/rfc822"));
1394 item.setPayload(message);
1396
1397 if (!identity.isNull()) { // we have a valid identity
1398 switch (saveIn) {
1399 case MessageComposer::MessageSender::SaveInTemplates:
1400 if (!identity.templates().isEmpty()) { // the user has specified a custom templates collection
1401 target = Akonadi::Collection(identity.templates().toLongLong());
1402 }
1403 break;
1404 case MessageComposer::MessageSender::SaveInDrafts:
1405 if (!identity.drafts().isEmpty()) { // the user has specified a custom drafts collection
1406 target = Akonadi::Collection(identity.drafts().toLongLong());
1407 }
1408 break;
1409 case MessageComposer::MessageSender::SaveInOutbox: // We don't define save outbox in identity
1411 break;
1412 case MessageComposer::MessageSender::SaveInNone:
1413 break;
1414 }
1415
1416 auto saveMessageJob = new Akonadi::CollectionFetchJob(target, Akonadi::CollectionFetchJob::Base);
1417 saveMessageJob->setProperty("Akonadi::Item", QVariant::fromValue(item));
1418 QObject::connect(saveMessageJob, &Akonadi::CollectionFetchJob::result, this, &ComposerViewBase::slotSaveMessage);
1419 } else {
1420 // preinitialize with the default collections
1421 target = defaultSpecialTarget();
1422 auto create = new Akonadi::ItemCreateJob(item, target, this);
1423 connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1424 ++m_pendingQueueJobs;
1425 }
1426}
1427
1428void ComposerViewBase::slotSaveMessage(KJob *job)
1429{
1430 Akonadi::Collection target;
1431 auto item = job->property("Akonadi::Item").value<Akonadi::Item>();
1432 if (job->error()) {
1433 target = defaultSpecialTarget();
1434 } else {
1436 if (fetchJob->collections().isEmpty()) {
1437 target = defaultSpecialTarget();
1438 } else {
1439 target = fetchJob->collections().at(0);
1440 }
1441 }
1442 auto create = new Akonadi::ItemCreateJob(item, target, this);
1443 connect(create, &Akonadi::ItemCreateJob::result, this, &ComposerViewBase::slotCreateItemResult);
1444 ++m_pendingQueueJobs;
1445}
1446
1447Akonadi::Collection ComposerViewBase::defaultSpecialTarget() const
1448{
1449 Akonadi::Collection target;
1450 switch (mSaveIn) {
1451 case MessageComposer::MessageSender::SaveInNone:
1452 break;
1453 case MessageComposer::MessageSender::SaveInDrafts:
1455 break;
1456 case MessageComposer::MessageSender::SaveInTemplates:
1458 break;
1459 case MessageComposer::MessageSender::SaveInOutbox:
1461 break;
1462 }
1463
1464 return target;
1465}
1466
1467void ComposerViewBase::slotCreateItemResult(KJob *job)
1468{
1469 --m_pendingQueueJobs;
1470 qCDebug(MESSAGECOMPOSER_LOG) << "mPendingCreateItemJobs" << m_pendingQueueJobs;
1471 Q_ASSERT(m_pendingQueueJobs >= 0);
1472
1473 if (job->error()) {
1474 qCWarning(MESSAGECOMPOSER_LOG) << "Failed to save a message:" << job->errorString();
1475 Q_EMIT failed(i18n("Failed to save the message: %1", job->errorString()));
1476 return;
1477 }
1478
1479 Akonadi::Item::Id id = -1;
1480 if (mSendLaterInfo) {
1481 auto createJob = static_cast<Akonadi::ItemCreateJob *>(job);
1482 const Akonadi::Item item = createJob->item();
1483 if (item.isValid()) {
1484 id = item.id();
1485 addSendLaterItem(item);
1486 }
1487 }
1488
1489 if (m_pendingQueueJobs == 0) {
1491 }
1492}
1493
1494void ComposerViewBase::addAttachment(const QUrl &url, const QString &comment, bool sync)
1495{
1496 Q_UNUSED(comment)
1497 qCDebug(MESSAGECOMPOSER_LOG) << "adding attachment with url:" << url;
1498 if (sync) {
1499 m_attachmentController->addAttachmentUrlSync(url);
1500 } else {
1501 m_attachmentController->addAttachment(url);
1502 }
1503}
1504
1505void ComposerViewBase::addAttachment(const QString &name, const QString &filename, const QString &charset, const QByteArray &data, const QByteArray &mimeType)
1506{
1508 if (!data.isEmpty()) {
1509 attachment->setName(name);
1510 attachment->setFileName(filename);
1511 attachment->setData(data);
1512 attachment->setCharset(charset.toLatin1());
1513 attachment->setMimeType(mimeType);
1514 // TODO what about the other fields?
1515
1516 m_attachmentController->addAttachment(attachment);
1517 }
1518}
1519
1520void ComposerViewBase::addAttachmentPart(KMime::Content *partToAttach)
1521{
1523 if (partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType(false)->mimeType() == "message/rfc822") {
1524 // if it is a digest or a full message, use the encodedContent() of the attachment,
1525 // which already has the proper headers
1526 part->setData(partToAttach->encodedContent());
1527 } else {
1528 part->setData(partToAttach->decodedContent());
1529 }
1530 part->setMimeType(partToAttach->contentType(false)->mimeType());
1531 if (auto cd = partToAttach->contentDescription(false)) {
1532 part->setDescription(cd->asUnicodeString());
1533 }
1534 if (auto ct = partToAttach->contentType(false)) {
1535 if (ct->hasParameter("name")) {
1536 part->setName(ct->parameter("name"));
1537 }
1538 }
1539 if (auto cd = partToAttach->contentDisposition(false)) {
1540 part->setFileName(cd->filename());
1541 part->setInline(cd->disposition() == KMime::Headers::CDinline);
1542 }
1543 if (part->name().isEmpty() && !part->fileName().isEmpty()) {
1544 part->setName(part->fileName());
1545 }
1546 if (part->fileName().isEmpty() && !part->name().isEmpty()) {
1547 part->setFileName(part->name());
1548 }
1549 m_attachmentController->addAttachment(part);
1550}
1551
1552void ComposerViewBase::fillComposer(MessageComposer::Composer *composer)
1553{
1554 fillComposer(composer, UseUnExpandedRecipients, false);
1555}
1556
1557void ComposerViewBase::fillComposer(MessageComposer::Composer *composer, ComposerViewBase::RecipientExpansion expansion, bool autoresize)
1558{
1559 fillGlobalPart(composer->globalPart());
1560 m_editor->fillComposerTextPart(composer->textPart());
1561 fillInfoPart(composer->infoPart(), expansion);
1562 if (m_attachmentModel) {
1563 composer->addAttachmentParts(m_attachmentModel->attachments(), autoresize);
1564 }
1565}
1566
1567//-----------------------------------------------------------------------------
1569{
1570 if (m_recipientsEditor) {
1571 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::To));
1572 }
1573 return {};
1574}
1575
1576//-----------------------------------------------------------------------------
1577QString ComposerViewBase::cc() const
1578{
1579 if (m_recipientsEditor) {
1580 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Cc));
1581 }
1582 return {};
1583}
1584
1585//-----------------------------------------------------------------------------
1586QString ComposerViewBase::bcc() const
1587{
1588 if (m_recipientsEditor) {
1589 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::Bcc));
1590 }
1591 return {};
1592}
1593
1594QString ComposerViewBase::from() const
1595{
1596 return MessageComposer::Util::cleanedUpHeaderString(m_from);
1597}
1598
1599QString ComposerViewBase::replyTo() const
1600{
1601 if (m_recipientsEditor) {
1602 return MessageComposer::Util::cleanedUpHeaderString(m_recipientsEditor->recipientString(MessageComposer::Recipient::ReplyTo));
1603 }
1604 return {};
1605}
1606
1607QString ComposerViewBase::subject() const
1608{
1609 return MessageComposer::Util::cleanedUpHeaderString(m_subject);
1610}
1611
1612const KIdentityManagementCore::Identity &ComposerViewBase::currentIdentity() const
1613{
1614 return m_identMan->identityForUoidOrDefault(m_identityCombo->currentIdentity());
1615}
1616
1617bool ComposerViewBase::autocryptEnabled() const
1618{
1619 return currentIdentity().autocryptEnabled();
1620}
1621
1622void ComposerViewBase::setParentWidgetForGui(QWidget *w)
1623{
1624 m_parentWidget = w;
1625}
1626
1627void ComposerViewBase::setAttachmentController(MessageComposer::AttachmentControllerBase *controller)
1628{
1629 m_attachmentController = controller;
1630}
1631
1632MessageComposer::AttachmentControllerBase *ComposerViewBase::attachmentController()
1633{
1634 return m_attachmentController;
1635}
1636
1638{
1639 m_attachmentModel = model;
1640}
1641
1642MessageComposer::AttachmentModel *ComposerViewBase::attachmentModel()
1643{
1644 return m_attachmentModel;
1645}
1646
1647void ComposerViewBase::setRecipientsEditor(MessageComposer::RecipientsEditor *recEditor)
1648{
1649 m_recipientsEditor = recEditor;
1650}
1651
1652MessageComposer::RecipientsEditor *ComposerViewBase::recipientsEditor()
1653{
1654 return m_recipientsEditor;
1655}
1656
1657void ComposerViewBase::setSignatureController(MessageComposer::SignatureController *sigController)
1658{
1659 m_signatureController = sigController;
1660}
1661
1662MessageComposer::SignatureController *ComposerViewBase::signatureController()
1663{
1664 return m_signatureController;
1665}
1666
1667void ComposerViewBase::setIdentityCombo(KIdentityManagementWidgets::IdentityCombo *identCombo)
1668{
1669 m_identityCombo = identCombo;
1670}
1671
1672KIdentityManagementWidgets::IdentityCombo *ComposerViewBase::identityCombo()
1673{
1674 return m_identityCombo;
1675}
1676
1677void ComposerViewBase::updateRecipients(const KIdentityManagementCore::Identity &ident,
1678 const KIdentityManagementCore::Identity &oldIdent,
1679 MessageComposer::Recipient::Type type)
1680{
1681 QString oldIdentList;
1682 QString newIdentList;
1683 switch (type) {
1684 case MessageComposer::Recipient::Bcc: {
1685 oldIdentList = oldIdent.bcc();
1686 newIdentList = ident.bcc();
1687 break;
1688 }
1689 case MessageComposer::Recipient::Cc: {
1690 oldIdentList = oldIdent.cc();
1691 newIdentList = ident.cc();
1692 break;
1693 }
1694 case MessageComposer::Recipient::ReplyTo: {
1695 oldIdentList = oldIdent.replyToAddr();
1696 newIdentList = ident.replyToAddr();
1697 break;
1698 }
1699 case MessageComposer::Recipient::To:
1700 case MessageComposer::Recipient::Undefined:
1701 return;
1702 }
1703
1704 if (oldIdentList != newIdentList) {
1705 const auto oldRecipients = KMime::Types::Mailbox::listFromUnicodeString(oldIdentList);
1706 for (const KMime::Types::Mailbox &recipient : oldRecipients) {
1707 m_recipientsEditor->removeRecipient(recipient.prettyAddress(), type);
1708 }
1709
1710 const auto newRecipients = KMime::Types::Mailbox::listFromUnicodeString(newIdentList);
1711 for (const KMime::Types::Mailbox &recipient : newRecipients) {
1712 m_recipientsEditor->addRecipient(recipient.prettyAddress(), type);
1713 }
1714 m_recipientsEditor->setFocusBottom();
1715 }
1716}
1717
1718void ComposerViewBase::identityChanged(const KIdentityManagementCore::Identity &ident, const KIdentityManagementCore::Identity &oldIdent, bool msgCleared)
1719{
1720 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Bcc);
1721 updateRecipients(ident, oldIdent, MessageComposer::Recipient::Cc);
1722 updateRecipients(ident, oldIdent, MessageComposer::Recipient::ReplyTo);
1723
1724 KIdentityManagementCore::Signature oldSig = const_cast<KIdentityManagementCore::Identity &>(oldIdent).signature();
1725 KIdentityManagementCore::Signature newSig = const_cast<KIdentityManagementCore::Identity &>(ident).signature();
1726 // replace existing signatures
1727 const bool replaced = editor()->composerSignature()->replaceSignature(oldSig, newSig);
1728 // Just append the signature if there was no old signature
1729 if (!replaced && (msgCleared || oldSig.rawText().isEmpty())) {
1730 signatureController()->applySignature(newSig);
1731 }
1732 const QString vcardFileName = ident.vCardFile();
1733 attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty());
1734 attachmentController()->setAttachOwnVcard(ident.attachVcard());
1735
1736 m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage());
1737}
1738
1739void ComposerViewBase::setEditor(MessageComposer::RichTextComposerNg *editor)
1740{
1741 m_editor = editor;
1742 m_editor->document()->setModified(false);
1743}
1744
1745MessageComposer::RichTextComposerNg *ComposerViewBase::editor() const
1746{
1747 return m_editor;
1748}
1749
1750void ComposerViewBase::setTransportCombo(MailTransport::TransportComboBox *transpCombo)
1751{
1752 m_transport = transpCombo;
1753}
1754
1755MailTransport::TransportComboBox *ComposerViewBase::transportComboBox() const
1756{
1757 return m_transport;
1758}
1759
1760void ComposerViewBase::setIdentityManager(KIdentityManagementCore::IdentityManager *identMan)
1761{
1762 m_identMan = identMan;
1763}
1764
1765KIdentityManagementCore::IdentityManager *ComposerViewBase::identityManager()
1766{
1767 return m_identMan;
1768}
1769
1770void ComposerViewBase::setFcc(const Akonadi::Collection &fccCollection)
1771{
1772 if (m_fccCombo) {
1773 m_fccCombo->setDefaultCollection(fccCollection);
1774 } else {
1775 m_fccCollection = fccCollection;
1776 }
1777 auto const checkFccCollectionJob = new Akonadi::CollectionFetchJob(fccCollection, Akonadi::CollectionFetchJob::Base);
1778 connect(checkFccCollectionJob, &KJob::result, this, &ComposerViewBase::slotFccCollectionCheckResult);
1779}
1780
1781void ComposerViewBase::slotFccCollectionCheckResult(KJob *job)
1782{
1783 if (job->error()) {
1784 qCWarning(MESSAGECOMPOSER_LOG) << " void ComposerViewBase::slotFccCollectionCheckResult(KJob *job) error " << job->errorString();
1786 if (m_fccCombo) {
1787 m_fccCombo->setDefaultCollection(sentMailCol);
1788 } else {
1789 m_fccCollection = sentMailCol;
1790 }
1791 }
1792}
1793
1794void ComposerViewBase::setFccCombo(Akonadi::CollectionComboBox *fcc)
1795{
1796 m_fccCombo = fcc;
1797}
1798
1799Akonadi::CollectionComboBox *ComposerViewBase::fccCombo() const
1800{
1801 return m_fccCombo;
1802}
1803
1805{
1806 m_from = from;
1807}
1808
1809void ComposerViewBase::setSubject(const QString &subject)
1810{
1811 m_subject = subject;
1812 if (mSendLaterInfo) {
1813 mSendLaterInfo->setSubject(m_subject);
1814 mSendLaterInfo->setTo(to());
1815 }
1816}
1817
1818void ComposerViewBase::setAutoSaveInterval(int interval)
1819{
1820 m_autoSaveInterval = interval;
1821}
1822
1823void ComposerViewBase::setCryptoOptions(bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts)
1824{
1825 m_sign = sign;
1826 m_encrypt = encrypt;
1827 m_cryptoMessageFormat = format;
1828 m_neverEncrypt = neverEncryptDrafts;
1829}
1830
1831void ComposerViewBase::setMDNRequested(bool mdnRequested)
1832{
1833 m_mdnRequested = mdnRequested;
1834}
1835
1836void ComposerViewBase::setUrgent(bool urgent)
1837{
1838 m_urgent = urgent;
1839}
1840
1841int ComposerViewBase::autoSaveInterval() const
1842{
1843 return m_autoSaveInterval;
1844}
1845
1846//-----------------------------------------------------------------------------
1847void ComposerViewBase::collectImages(KMime::Content *root)
1848{
1849 if (KMime::Content *n = Util::findTypeInMessage(root, "multipart", "alternative")) {
1850 KMime::Content *parentnode = n->parent();
1851 if (parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related") {
1852 const auto nodes = parentnode->contents();
1853 for (auto node : nodes) {
1854 if (node->contentType()->isImage()) {
1855 qCDebug(MESSAGECOMPOSER_LOG) << "found image in multipart/related : " << node->contentType()->name();
1856 QImage img;
1857 img.loadFromData(node->decodedContent());
1858 m_editor->composerControler()->composerImages()->loadImage(
1859 img,
1860 QString::fromLatin1(QByteArray(QByteArrayLiteral("cid:") + node->contentID()->identifier())),
1861 node->contentType()->name());
1862 }
1863 }
1864 }
1865 }
1866}
1867
1868//-----------------------------------------------------------------------------
1869bool ComposerViewBase::inlineSigningEncryptionSelected() const
1870{
1871 if (!m_sign && !m_encrypt) {
1872 return false;
1873 }
1874 return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat;
1875}
1876
1877bool ComposerViewBase::hasMissingAttachments(const QStringList &attachmentKeywords)
1878{
1879 if (attachmentKeywords.isEmpty()) {
1880 return false;
1881 }
1882 if (m_attachmentModel && m_attachmentModel->rowCount() > 0) {
1883 return false;
1884 }
1885
1886 return MessageComposer::Util::hasMissingAttachments(attachmentKeywords, m_editor->document(), subject());
1887}
1888
1889ComposerViewBase::MissingAttachment ComposerViewBase::checkForMissingAttachments(const QStringList &attachmentKeywords)
1890{
1891 if (!hasMissingAttachments(attachmentKeywords)) {
1892 return NoMissingAttachmentFound;
1893 }
1894 const int rc = KMessageBox::warningTwoActionsCancel(m_editor,
1895
1896 i18n("The message you have composed seems to refer to an "
1897 "attached file but you have not attached anything.\n"
1898 "Do you want to attach a file to your message?"),
1899 i18nc("@title:window", "File Attachment Reminder"),
1900 KGuiItem(i18nc("@action:button", "&Attach File..."), QLatin1StringView("mail-attachment")),
1901 KGuiItem(i18nc("@action:button", "&Send as Is"), QLatin1StringView("mail-send")));
1902 if (rc == KMessageBox::Cancel) {
1903 return FoundMissingAttachmentAndCancel;
1904 }
1905 if (rc == KMessageBox::ButtonCode::PrimaryAction) {
1906 m_attachmentController->showAddAttachmentFileDialog();
1907 return FoundMissingAttachmentAndAddedAttachment;
1908 }
1909
1910 return FoundMissingAttachmentAndSending;
1911}
1912
1913void ComposerViewBase::markAllAttachmentsForSigning(bool sign)
1914{
1915 if (m_attachmentModel) {
1916 const auto attachments = m_attachmentModel->attachments();
1917 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1918 attachment->setSigned(sign);
1919 }
1920 }
1921}
1922
1923void ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt)
1924{
1925 if (m_attachmentModel) {
1926 const auto attachments = m_attachmentModel->attachments();
1927 for (MessageCore::AttachmentPart::Ptr attachment : attachments) {
1928 attachment->setEncrypted(encrypt);
1929 }
1930 }
1931}
1932
1933bool ComposerViewBase::determineWhetherToSign(bool doSignCompletely, KeyResolver *keyResolver, bool signSomething, bool &result, bool &canceled)
1934{
1935 bool sign = false;
1936 switch (keyResolver->checkSigningPreferences(signSomething)) {
1937 case Kleo::DoIt:
1938 if (!signSomething) {
1939 markAllAttachmentsForSigning(true);
1940 return true;
1941 }
1942 sign = true;
1943 break;
1944 case Kleo::DontDoIt:
1945 sign = false;
1946 break;
1947 case Kleo::AskOpportunistic:
1948 assert(0);
1949 case Kleo::Ask: {
1950 // the user wants to be asked or has to be asked
1952 const QString msg = i18n(
1953 "Examination of the recipient's signing preferences "
1954 "yielded that you be asked whether or not to sign "
1955 "this message.\n"
1956 "Sign this message?");
1957 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1958 msg,
1959 i18nc("@title:window", "Sign Message?"),
1960 KGuiItem(i18nc("to sign", "&Sign")),
1961 KGuiItem(i18nc("@action:button", "Do &Not Sign")))) {
1963 result = false;
1964 canceled = true;
1965 return false;
1966 case KMessageBox::ButtonCode::PrimaryAction:
1967 markAllAttachmentsForSigning(true);
1968 return true;
1969 case KMessageBox::ButtonCode::SecondaryAction:
1970 markAllAttachmentsForSigning(false);
1971 return false;
1972 default:
1973 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
1974 return false;
1975 }
1976 break;
1977 }
1978 case Kleo::Conflict: {
1979 // warn the user that there are conflicting signing preferences
1981 const QString msg = i18n(
1982 "There are conflicting signing preferences "
1983 "for these recipients.\n"
1984 "Sign this message?");
1985 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
1986 msg,
1987 i18nc("@title:window", "Sign Message?"),
1988 KGuiItem(i18nc("to sign", "&Sign")),
1989 KGuiItem(i18nc("@action:button", "Do &Not Sign")))) {
1991 result = false;
1992 canceled = true;
1993 return false;
1994 case KMessageBox::ButtonCode::PrimaryAction:
1995 markAllAttachmentsForSigning(true);
1996 return true;
1997 case KMessageBox::ButtonCode::SecondaryAction:
1998 markAllAttachmentsForSigning(false);
1999 return false;
2000 default:
2001 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2002 return false;
2003 }
2004 break;
2005 }
2006 case Kleo::Impossible: {
2008 const QString msg = i18n(
2009 "You have requested to sign this message, "
2010 "but no valid signing keys have been configured "
2011 "for this identity.");
2012 if (KMessageBox::warningContinueCancel(m_parentWidget,
2013 msg,
2014 i18nc("@title:window", "Send Unsigned?"),
2015 KGuiItem(i18nc("@action:button", "Send &Unsigned")))
2017 result = false;
2018 return false;
2019 } else {
2020 markAllAttachmentsForSigning(false);
2021 return false;
2022 }
2023 }
2024 }
2025
2026 if (!sign || !doSignCompletely) {
2027 if (cryptoWarningUnsigned(currentIdentity())) {
2029 const QString msg = sign && !doSignCompletely ? i18n(
2030 "Some parts of this message will not be signed.\n"
2031 "Sending only partially signed messages might violate site policy.\n"
2032 "Sign all parts instead?") // oh, I hate this...
2033 : i18n(
2034 "This message will not be signed.\n"
2035 "Sending unsigned message might violate site policy.\n"
2036 "Sign message instead?"); // oh, I hate this...
2037 const QString buttonText = sign && !doSignCompletely ? i18n("&Sign All Parts") : i18n("&Sign");
2038 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2039 msg,
2040 i18nc("@title:window", "Unsigned-Message Warning"),
2041 KGuiItem(buttonText),
2042 KGuiItem(i18nc("@action:button", "Send &As Is")))) {
2044 result = false;
2045 canceled = true;
2046 return false;
2047 case KMessageBox::ButtonCode::PrimaryAction:
2048 markAllAttachmentsForSigning(true);
2049 return true;
2050 case KMessageBox::ButtonCode::SecondaryAction:
2051 return sign || doSignCompletely;
2052 default:
2053 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2054 return false;
2055 }
2056 }
2057 }
2058 return sign || doSignCompletely;
2059}
2060
2061bool ComposerViewBase::determineWhetherToEncrypt(bool doEncryptCompletely,
2062 KeyResolver *keyResolver,
2063 bool encryptSomething,
2064 bool signSomething,
2065 bool &result,
2066 bool &canceled)
2067{
2068 bool encrypt = false;
2069 bool opportunistic = false;
2070 switch (keyResolver->checkEncryptionPreferences(encryptSomething)) {
2071 case Kleo::DoIt:
2072 if (!encryptSomething) {
2073 markAllAttachmentsForEncryption(true);
2074 return true;
2075 }
2076 encrypt = true;
2077 break;
2078 case Kleo::DontDoIt:
2079 encrypt = false;
2080 break;
2081 case Kleo::AskOpportunistic:
2082 opportunistic = true;
2083 // fall through...
2084 [[fallthrough]];
2085 case Kleo::Ask: {
2086 // the user wants to be asked or has to be asked
2088 const QString msg = opportunistic ? i18n(
2089 "Valid trusted encryption keys were found for all recipients.\n"
2090 "Encrypt this message?")
2091 : i18n(
2092 "Examination of the recipient's encryption preferences "
2093 "yielded that you be asked whether or not to encrypt "
2094 "this message.\n"
2095 "Encrypt this message?");
2096 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2097 msg,
2098 i18n("Encrypt Message?"),
2099 KGuiItem(signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt")),
2100 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2102 result = false;
2103 canceled = true;
2104 return false;
2105 case KMessageBox::ButtonCode::PrimaryAction:
2106 markAllAttachmentsForEncryption(true);
2107 return true;
2108 case KMessageBox::ButtonCode::SecondaryAction:
2109 markAllAttachmentsForEncryption(false);
2110 return false;
2111 default:
2112 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2113 return false;
2114 }
2115 break;
2116 }
2117 case Kleo::Conflict: {
2118 // warn the user that there are conflicting encryption preferences
2120 const QString msg = i18n(
2121 "There are conflicting encryption preferences "
2122 "for these recipients.\n"
2123 "Encrypt this message?");
2125
2126 m_parentWidget,
2127 msg,
2128 i18n("Encrypt Message?"),
2129 KGuiItem(i18nc("@action:button", "&Encrypt")),
2130 KGuiItem(i18nc("@action:button", "Do &Not Encrypt")))) {
2132 result = false;
2133 canceled = true;
2134 return false;
2135 case KMessageBox::ButtonCode::PrimaryAction:
2136 markAllAttachmentsForEncryption(true);
2137 return true;
2138 case KMessageBox::ButtonCode::SecondaryAction:
2139 markAllAttachmentsForEncryption(false);
2140 return false;
2141 default:
2142 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2143 return false;
2144 }
2145 break;
2146 }
2147 case Kleo::Impossible: {
2149 const QString msg = i18n(
2150 "You have requested to encrypt this message, "
2151 "and to encrypt a copy to yourself, "
2152 "but no valid trusted encryption keys have been "
2153 "configured for this identity.");
2154 if (KMessageBox::warningContinueCancel(m_parentWidget,
2155 msg,
2156 i18nc("@title:window", "Send Unencrypted?"),
2157 KGuiItem(i18nc("@action:button", "Send &Unencrypted")))
2159 result = false;
2160 return false;
2161 } else {
2162 markAllAttachmentsForEncryption(false);
2163 return false;
2164 }
2165 }
2166 }
2167
2168 if (!encrypt || !doEncryptCompletely) {
2169 if (cryptoWarningUnencrypted(currentIdentity())) {
2171 const QString msg = !doEncryptCompletely ? i18n(
2172 "Some parts of this message will not be encrypted.\n"
2173 "Sending only partially encrypted messages might violate "
2174 "site policy and/or leak sensitive information.\n"
2175 "Encrypt all parts instead?") // oh, I hate this...
2176 : i18n(
2177 "This message will not be encrypted.\n"
2178 "Sending unencrypted messages might violate site policy and/or "
2179 "leak sensitive information.\n"
2180 "Encrypt messages instead?"); // oh, I hate this...
2181 const QString buttonText = !doEncryptCompletely ? i18n("&Encrypt All Parts") : i18n("&Encrypt");
2182 switch (KMessageBox::warningTwoActionsCancel(m_parentWidget,
2183 msg,
2184 i18nc("@title:window", "Unencrypted Message Warning"),
2185 KGuiItem(buttonText),
2186 KGuiItem(signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")))) {
2188 result = false;
2189 canceled = true;
2190 return false;
2191 case KMessageBox::ButtonCode::PrimaryAction:
2192 markAllAttachmentsForEncryption(true);
2193 return true;
2194 case KMessageBox::ButtonCode::SecondaryAction:
2195 return encrypt || doEncryptCompletely;
2196 default:
2197 qCWarning(MESSAGECOMPOSER_LOG) << "Unhandled MessageBox response";
2198 return false;
2199 }
2200 }
2201 }
2202
2203 return encrypt || doEncryptCompletely;
2204}
2205
2206void ComposerViewBase::setSendLaterInfo(SendLaterInfo *info)
2207{
2208 mSendLaterInfo.reset(info);
2209}
2210
2211SendLaterInfo *ComposerViewBase::sendLaterInfo() const
2212{
2213 return mSendLaterInfo.get();
2214}
2215
2216void ComposerViewBase::addFollowupReminder(const QString &messageId)
2217{
2218 if (!messageId.isEmpty()) {
2219 if (mFollowUpDate.isValid()) {
2221 job->setSubject(m_subject);
2222 job->setMessageId(messageId);
2223 job->setTo(mExpandedReplyTo.isEmpty() ? mExpandedTo.join(QLatin1Char(',')) : mExpandedReplyTo.join(QLatin1Char(',')));
2224 job->setFollowUpReminderDate(mFollowUpDate);
2225 job->setCollectionToDo(mFollowUpCollection);
2226 job->start();
2227 }
2228 }
2229}
2230
2231void ComposerViewBase::addSendLaterItem(const Akonadi::Item &item)
2232{
2233 mSendLaterInfo->setItemId(item.id());
2234
2235 auto job = new MessageComposer::SendLaterCreateJob(*mSendLaterInfo, this);
2236 job->start();
2237}
2238
2239bool ComposerViewBase::requestDeleveryConfirmation() const
2240{
2241 return m_requestDeleveryConfirmation;
2242}
2243
2244void ComposerViewBase::setRequestDeleveryConfirmation(bool requestDeleveryConfirmation)
2245{
2246 m_requestDeleveryConfirmation = requestDeleveryConfirmation;
2247}
2248
2249KMime::Message::Ptr ComposerViewBase::msg() const
2250{
2251 return m_msg;
2252}
2253
2254std::shared_ptr<Kleo::ExpiryChecker> ComposerViewBase::expiryChecker()
2255{
2256 if (!mExpiryChecker) {
2257 mExpiryChecker.reset(new Kleo::ExpiryChecker{Kleo::ExpiryCheckerSettings{encryptOwnKeyNearExpiryWarningThresholdInDays(),
2258 encryptKeyNearExpiryWarningThresholdInDays(),
2259 encryptRootCertNearExpiryWarningThresholdInDays(),
2260 encryptChainCertNearExpiryWarningThresholdInDays()}});
2261 }
2262 return mExpiryChecker;
2263}
2264
2265#include "moc_composerviewbase.cpp"
Akonadi::Collection currentCollection() const
void setDefaultCollection(const Collection &collection)
Collection::List collections() const
bool isValid() const
void setMimeType(const QString &mimeType)
Id id() const
bool isValid() const
void setPayload(const T &p)
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
const Headers::ContentType * contentType() const
Content * parent()
QByteArray decodedContent() const
void setContent(const QByteArray &s)
const Headers::ContentDisposition * contentDisposition() const
QList< Content * > attachments()
QByteArray encodedContent(bool useCrLf=false) const
QList< Content * > contents()
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
A class to resolve signing/encryption keys w.r.t.
Kleo::Action checkEncryptionPreferences(bool encryptionRequested) const
Determine whether to encrypt or not, depending on the per-recipient encryption preferences,...
Kleo::Action checkSigningPreferences(bool signingRequested) const
Determine whether to sign or not, depending on the per-recipient signing preferences,...
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)
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)
QAction * create(StandardAction id, const Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
KGuiItem cont()
KGuiItem cancel()
const QList< QKeySequence > & end()
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 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 Mon Nov 18 2024 12:07:24 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.