Messagelib

maintextjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Constantin Berzan <[email protected]>
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 "part/globalpart.h"
11 #include "job/multipartjob.h"
12 #include "job/singlepartjob.h"
13 #include "part/textpart.h"
14 #include "utils/util.h"
15 
16 #include <QTextCodec>
17 
18 #include <KCharsets>
19 #include "messagecomposer_debug.h"
20 #include <KLocalizedString>
21 #include <KMessageBox>
22 
23 #include <kmime/kmime_content.h>
24 
25 using namespace MessageComposer;
26 
27 class MessageComposer::MainTextJobPrivate : public ContentJobBasePrivate
28 {
29 public:
30  MainTextJobPrivate(MainTextJob *qq)
31  : ContentJobBasePrivate(qq)
32  {
33  }
34 
35  bool chooseSourcePlainText();
36  bool chooseCharsetAndEncode();
37  bool chooseCharset();
38  bool encodeTexts();
39  SinglepartJob *createPlainTextJob();
40  SinglepartJob *createHtmlJob();
41  SinglepartJob *createImageJob(const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image);
42 
43  TextPart *textPart = nullptr;
44  QByteArray chosenCharset;
45  QString sourcePlainText;
46  QByteArray encodedPlainText;
47  QByteArray encodedHtml;
48 
49  Q_DECLARE_PUBLIC(MainTextJob)
50 };
51 
52 bool MainTextJobPrivate::chooseSourcePlainText()
53 {
54  Q_Q(MainTextJob);
55  Q_ASSERT(textPart);
56  if (textPart->isWordWrappingEnabled()) {
57  sourcePlainText = textPart->wrappedPlainText();
58  if (sourcePlainText.isEmpty()
59  && !textPart->cleanPlainText().isEmpty()) {
60  q->setError(JobBase::BugError);
61  q->setErrorText(i18n("Asked to use word wrapping, but not given wrapped plain text."));
62  return false;
63  }
64  } else {
65  sourcePlainText = textPart->cleanPlainText();
66  if (sourcePlainText.isEmpty()
67  && !textPart->wrappedPlainText().isEmpty()) {
68  q->setError(JobBase::BugError);
69  q->setErrorText(i18n("Asked not to use word wrapping, but not given clean plain text."));
70  return false;
71  }
72  }
73  return true;
74 }
75 
76 bool MainTextJobPrivate::chooseCharsetAndEncode()
77 {
78  Q_Q(MainTextJob);
79 
80  const QVector<QByteArray> charsets = q->globalPart()->charsets(true);
81  if (charsets.isEmpty()) {
82  q->setError(JobBase::BugError);
83  q->setErrorText(i18n("No charsets were available for encoding. Please check your configuration and make sure it contains at least one charset for sending."));
84  return false;
85  }
86 
87  Q_ASSERT(textPart);
88  QString toTry = sourcePlainText;
89  if (textPart->isHtmlUsed()) {
90  toTry = textPart->cleanHtml();
91  }
92  chosenCharset = MessageComposer::Util::selectCharset(charsets, toTry);
93  if (!chosenCharset.isEmpty()) {
94  // Good, found a charset that encodes the data without loss.
95  return encodeTexts();
96  } else {
97  // No good charset was found.
98  if (q->globalPart()->isGuiEnabled() && textPart->warnBadCharset()) {
99  // Warn the user and give them a chance to go back.
100  int result = KMessageBox::warningYesNo(
101  q->globalPart()->parentWidgetForGui(),
102  i18n("Encoding the message with %1 will lose some characters.\n"
103  "Do you want to continue?", QString::fromLatin1(charsets.first())),
104  i18n("Some Characters Will Be Lost"),
105  KGuiItem(i18n("Lose Characters")),
106  KGuiItem(i18n("Change Encoding"))
107  );
108  if (result == KMessageBox::No) {
109  q->setError(JobBase::UserCancelledError);
110  q->setErrorText(i18n("User decided to change the encoding."));
111  return false;
112  } else {
113  chosenCharset = charsets.first();
114  return encodeTexts();
115  }
116  } else if (textPart->warnBadCharset()) {
117  // Should warn user but no Gui available.
118  qCDebug(MESSAGECOMPOSER_LOG) << "warnBadCharset but Gui is disabled.";
119  q->setError(JobBase::UserError);
120  q->setErrorText(i18n("The selected encoding (%1) cannot fully encode the message.",
121  QString::fromLatin1(charsets.first())));
122  return false;
123  } else {
124  // OK to go ahead with a bad charset.
125  chosenCharset = charsets.first();
126  return encodeTexts();
127 
128  // FIXME: This is based on the assumption that QTextCodec will replace
129  // unknown characters with '?' or some other meaningful thing. The code in
130  // QTextCodec indeed uses '?', but this behaviour is not documented.
131  }
132  }
133 
134  // Should not reach here.
135  Q_ASSERT(false);
136  return false;
137 }
138 
139 bool MainTextJobPrivate::encodeTexts()
140 {
141  Q_Q(MainTextJob);
143  if (!codec) {
144  qCCritical(MESSAGECOMPOSER_LOG) << "Could not get text codec for charset" << chosenCharset;
145  q->setError(JobBase::BugError);
146  q->setErrorText(i18n("Could not get text codec for charset \"%1\".", QString::fromLatin1(chosenCharset)));
147  return false;
148  }
149  encodedPlainText = codec->fromUnicode(sourcePlainText);
150  if (!textPart->cleanHtml().isEmpty()) {
151  encodedHtml = codec->fromUnicode(textPart->cleanHtml());
152  }
153  qCDebug(MESSAGECOMPOSER_LOG) << "Done.";
154  return true;
155 }
156 
157 SinglepartJob *MainTextJobPrivate::createPlainTextJob()
158 {
159  SinglepartJob *cjob = new SinglepartJob; // No parent.
160  cjob->contentType()->setMimeType("text/plain");
161  cjob->contentType()->setCharset(chosenCharset);
162  cjob->setData(encodedPlainText);
163  // TODO standard recommends Content-ID.
164  return cjob;
165 }
166 
167 SinglepartJob *MainTextJobPrivate::createHtmlJob()
168 {
169  SinglepartJob *cjob = new SinglepartJob; // No parent.
170  cjob->contentType()->setMimeType("text/html");
171  cjob->contentType()->setCharset(chosenCharset);
172  const QByteArray data = KPIMTextEdit::RichTextComposerImages::imageNamesToContentIds(encodedHtml,
173  textPart->embeddedImages());
174  cjob->setData(data);
175  // TODO standard recommends Content-ID.
176  return cjob;
177 }
178 
179 SinglepartJob *MainTextJobPrivate::createImageJob(const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image)
180 {
181  Q_Q(MainTextJob);
182 
183  // The image is a PNG encoded with base64.
184  SinglepartJob *cjob = new SinglepartJob; // No parent.
185  cjob->contentType()->setMimeType("image/png");
186  const QByteArray charset = MessageComposer::Util::selectCharset(q->globalPart()->charsets(true), image->imageName);
187  Q_ASSERT(!charset.isEmpty());
188  cjob->contentType()->setName(image->imageName, charset);
189  cjob->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
190  cjob->contentTransferEncoding()->setDecoded(false); // It is already encoded.
191  cjob->contentID()->setIdentifier(image->contentID.toLatin1());
192  qCDebug(MESSAGECOMPOSER_LOG) << "cid" << cjob->contentID()->identifier();
193  cjob->setData(image->image);
194  return cjob;
195 }
196 
197 MainTextJob::MainTextJob(TextPart *textPart, QObject *parent)
198  : ContentJobBase(*new MainTextJobPrivate(this), parent)
199 {
200  Q_D(MainTextJob);
201  d->textPart = textPart;
202 }
203 
204 MainTextJob::~MainTextJob()
205 {
206 }
207 
208 TextPart *MainTextJob::textPart() const
209 {
210  Q_D(const MainTextJob);
211  return d->textPart;
212 }
213 
214 void MainTextJob::setTextPart(TextPart *part)
215 {
216  Q_D(MainTextJob);
217  d->textPart = part;
218 }
219 
220 void MainTextJob::doStart()
221 {
222  Q_D(MainTextJob);
223  Q_ASSERT(d->textPart);
224 
225  // Word wrapping.
226  if (!d->chooseSourcePlainText()) {
227  // chooseSourcePlainText has set an error.
228  Q_ASSERT(error());
229  emitResult();
230  return;
231  }
232 
233  // Charset.
234  if (!d->chooseCharsetAndEncode()) {
235  // chooseCharsetAndEncode has set an error.
236  Q_ASSERT(error());
237  emitResult();
238  return;
239  }
240 
241  // Assemble the Content.
242  SinglepartJob *plainJob = d->createPlainTextJob();
243  if (d->encodedHtml.isEmpty()) {
244  qCDebug(MESSAGECOMPOSER_LOG) << "Making text/plain";
245  // Content is text/plain.
246  appendSubjob(plainJob);
247  } else {
248  MultipartJob *alternativeJob = new MultipartJob;
249  alternativeJob->setMultipartSubtype("alternative");
250  alternativeJob->appendSubjob(plainJob); // text/plain first.
251  alternativeJob->appendSubjob(d->createHtmlJob()); // text/html second.
252  if (!d->textPart->hasEmbeddedImages()) {
253  qCDebug(MESSAGECOMPOSER_LOG) << "Have no images. Making multipart/alternative.";
254  // Content is multipart/alternative.
255  appendSubjob(alternativeJob);
256  } else {
257  qCDebug(MESSAGECOMPOSER_LOG) << "Have related images. Making multipart/related.";
258  // Content is multipart/related with a multipart/alternative sub-Content.
259  MultipartJob *multipartJob = new MultipartJob;
260  multipartJob->setMultipartSubtype("related");
261  multipartJob->appendSubjob(alternativeJob);
262  const auto embeddedImages = d->textPart->embeddedImages();
263  for (const QSharedPointer<KPIMTextEdit::EmbeddedImage> &image : embeddedImages) {
264  multipartJob->appendSubjob(d->createImageJob(image));
265  }
266  appendSubjob(multipartJob);
267  }
268  }
269  ContentJobBase::doStart();
270 }
271 
272 void MainTextJob::process()
273 {
274  Q_D(MainTextJob);
275  // The content has been created by our subjob.
276  Q_ASSERT(d->subjobContents.count() == 1);
277  d->resultContent = d->subjobContents.constFirst();
278  emitResult();
279 }
QTextCodec * codecForName(const QString &name) const
QByteArray fromUnicode(const QString &str) const const
void setMimeType(const QByteArray &mimeType)
void setIdentifier(const QByteArray &id)
bool isEmpty() const const
void setCharset(const QByteArray &s)
T & first()
void setName(const QString &s, const QByteArray &cs)
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
KCharsets * charsets()
static KCharsets * charsets()
QString i18n(const char *text, const TYPE &arg...)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
bool appendSubjob(ContentJobBase *job)
This is meant to be used instead of KCompositeJob::addSubjob(), making it possible to add subjobs fro...
void setDecoded(bool isDecoded=true)
bool isEmpty() const const
void setEncoding(contentEncoding e)
QString fromLatin1(const char *str, int size)
The TextPart class.
Definition: textpart.h:20
The MainTextJob class.
Definition: maintextjob.h:19
The MultipartJob class.
Definition: multipartjob.h:19
The SinglepartJob class.
Definition: singlepartjob.h:29
ButtonCode warningYesNo(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Options(Notify|Dangerous))
The ContentJobBase class.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Sep 24 2020 23:14:20 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.