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

KDE's Doxygen guidelines are available online.