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

KDE's Doxygen guidelines are available online.