Messagelib

maintextjob.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "job/maintextjob.h"
8
9#include "contentjobbase_p.h"
10#include "job/multipartjob.h"
11#include "job/singlepartjob.h"
12#include "part/globalpart.h"
13#include "part/textpart.h"
14#include "utils/util.h"
15
16#include <QStringEncoder>
17
18#include "messagecomposer_debug.h"
19#include <KLocalizedString>
20
21#include <KMime/Content>
22
23using namespace MessageComposer;
24
25class MessageComposer::MainTextJobPrivate : public ContentJobBasePrivate
26{
27public:
28 MainTextJobPrivate(MainTextJob *qq)
29 : ContentJobBasePrivate(qq)
30 {
31 }
32
33 [[nodiscard]] bool chooseSourcePlainText();
34 [[nodiscard]] bool chooseCharsetAndEncode();
35 [[nodiscard]] bool chooseCharset();
36 [[nodiscard]] bool encodeTexts();
37 [[nodiscard]] SinglepartJob *createPlainTextJob();
38 [[nodiscard]] SinglepartJob *createHtmlJob();
39 [[nodiscard]] SinglepartJob *createImageJob(const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image);
40
41 TextPart *textPart = nullptr;
42 QByteArray chosenCharset;
43 QString sourcePlainText;
44 QByteArray encodedPlainText;
45 QByteArray encodedHtml;
46
47 Q_DECLARE_PUBLIC(MainTextJob)
48};
49
50bool MainTextJobPrivate::chooseSourcePlainText()
51{
52 Q_Q(MainTextJob);
53 Q_ASSERT(textPart);
54 if (textPart->isWordWrappingEnabled()) {
55 sourcePlainText = textPart->wrappedPlainText();
56 if (sourcePlainText.isEmpty() && !textPart->cleanPlainText().isEmpty()) {
57 q->setError(JobBase::BugError);
58 q->setErrorText(i18n("Asked to use word wrapping, but not given wrapped plain text."));
59 return false;
60 }
61 } else {
62 sourcePlainText = textPart->cleanPlainText();
63 if (sourcePlainText.isEmpty() && !textPart->wrappedPlainText().isEmpty()) {
64 q->setError(JobBase::BugError);
65 q->setErrorText(i18n("Asked not to use word wrapping, but not given clean plain text."));
66 return false;
67 }
68 }
69 return true;
70}
71
72bool MainTextJobPrivate::chooseCharsetAndEncode()
73{
74 Q_Q(MainTextJob);
75
76 Q_ASSERT(textPart);
77 QString toTry = sourcePlainText;
78 if (textPart->isHtmlUsed()) {
79 toTry = textPart->cleanHtml();
80 }
81 chosenCharset = "utf-8";
82 return encodeTexts();
83}
84
85bool MainTextJobPrivate::encodeTexts()
86{
87 Q_Q(MainTextJob);
88 QStringEncoder codec(chosenCharset.constData());
89 if (!codec.isValid()) {
90 qCCritical(MESSAGECOMPOSER_LOG) << "Could not get text codec for charset" << chosenCharset;
91 q->setError(JobBase::BugError);
92 q->setErrorText(i18n("Could not get text codec for charset \"%1\".", QString::fromLatin1(chosenCharset)));
93 return false;
94 }
95 encodedPlainText = codec.encode(sourcePlainText);
96 if (!textPart->cleanHtml().isEmpty()) {
97 encodedHtml = codec.encode(textPart->cleanHtml());
98 }
99 qCDebug(MESSAGECOMPOSER_LOG) << "Done.";
100 return true;
101}
102
103SinglepartJob *MainTextJobPrivate::createPlainTextJob()
104{
105 auto cjob = new SinglepartJob; // No parent.
106 cjob->contentType()->setMimeType("text/plain");
107 cjob->contentType()->setCharset(chosenCharset);
108 cjob->setData(encodedPlainText);
109 // TODO standard recommends Content-ID.
110 return cjob;
111}
112
113SinglepartJob *MainTextJobPrivate::createHtmlJob()
114{
115 auto cjob = new SinglepartJob; // No parent.
116 cjob->contentType()->setMimeType("text/html");
117 cjob->contentType()->setCharset(chosenCharset);
118 const QByteArray data = KPIMTextEdit::RichTextComposerImages::imageNamesToContentIds(encodedHtml, textPart->embeddedImages());
119 cjob->setData(data);
120 // TODO standard recommends Content-ID.
121 return cjob;
122}
123
124SinglepartJob *MainTextJobPrivate::createImageJob(const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image)
125{
126 Q_Q(MainTextJob);
127
128 // The image is a PNG encoded with base64.
129 auto cjob = new SinglepartJob; // No parent.
130 cjob->contentType()->setMimeType("image/png");
131 cjob->contentType()->setName(image->imageName);
132 cjob->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
133 cjob->setDataIsEncoded(true); // It is already encoded.
134 cjob->contentID()->setIdentifier(image->contentID.toLatin1());
135 qCDebug(MESSAGECOMPOSER_LOG) << "cid" << cjob->contentID()->identifier();
136 cjob->setData(image->image);
137 return cjob;
138}
139
140MainTextJob::MainTextJob(TextPart *textPart, QObject *parent)
141 : ContentJobBase(*new MainTextJobPrivate(this), parent)
142{
144 d->textPart = textPart;
145}
146
147MainTextJob::~MainTextJob() = default;
148
149TextPart *MainTextJob::textPart() const
150{
151 Q_D(const MainTextJob);
152 return d->textPart;
153}
154
155void MainTextJob::setTextPart(TextPart *part)
156{
158 d->textPart = part;
159}
160
161void MainTextJob::doStart()
162{
164 Q_ASSERT(d->textPart);
165
166 // Word wrapping.
167 if (!d->chooseSourcePlainText()) {
168 // chooseSourcePlainText has set an error.
169 Q_ASSERT(error());
170 emitResult();
171 return;
172 }
173
174 // Charset.
175 if (!d->chooseCharsetAndEncode()) {
176 // chooseCharsetAndEncode has set an error.
177 Q_ASSERT(error());
178 emitResult();
179 return;
180 }
181
182 // Assemble the Content.
183 SinglepartJob *plainJob = d->createPlainTextJob();
184 if (d->encodedHtml.isEmpty()) {
185 qCDebug(MESSAGECOMPOSER_LOG) << "Making text/plain";
186 // Content is text/plain.
187 appendSubjob(plainJob);
188 } else {
189 auto alternativeJob = new MultipartJob;
190 alternativeJob->setMultipartSubtype("alternative");
191 alternativeJob->appendSubjob(plainJob); // text/plain first.
192 alternativeJob->appendSubjob(d->createHtmlJob()); // text/html second.
193 if (!d->textPart->hasEmbeddedImages()) {
194 qCDebug(MESSAGECOMPOSER_LOG) << "Have no images. Making multipart/alternative.";
195 // Content is multipart/alternative.
196 appendSubjob(alternativeJob);
197 } else {
198 qCDebug(MESSAGECOMPOSER_LOG) << "Have related images. Making multipart/related.";
199 // Content is multipart/related with a multipart/alternative sub-Content.
200 auto multipartJob = new MultipartJob;
201 multipartJob->setMultipartSubtype("related");
202 multipartJob->appendSubjob(alternativeJob);
203 const auto embeddedImages = d->textPart->embeddedImages();
204 for (const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image : embeddedImages) {
205 multipartJob->appendSubjob(d->createImageJob(image));
206 }
207 appendSubjob(multipartJob);
208 }
209 }
211}
212
213void MainTextJob::process()
214{
216 // The content has been created by our subjob.
217 Q_ASSERT(d->subjobContents.count() == 1);
218 d->resultContent = d->subjobContents.constFirst();
219 emitResult();
220}
221
222#include "moc_maintextjob.cpp"
void emitResult()
int error() const
void setMimeType(const QByteArray &mimeType)
The ContentJobBase class.
virtual void doStart()
Reimplement to do additional stuff before processing children, such as adding more subjobs.
bool appendSubjob(ContentJobBase *job)
This is meant to be used instead of KCompositeJob::addSubjob(), making it possible to add subjobs fro...
The MainTextJob class.
Definition maintextjob.h:20
The MultipartJob class.
The SinglepartJob class.
The TextPart class.
Definition textpart.h:21
bool isWordWrappingEnabled
True iff the text is word wrapped. By default: true.
Definition textpart.h:25
QString i18n(const char *text, const TYPE &arg...)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
const char * constData() const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
Q_D(Todo)
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.