Messagelib

grantleeheaderformatter.cpp
1 /*
2  SPDX-FileCopyrightText: 2013-2021 Laurent Montel <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "grantleeheaderformatter.h"
8 #include "config-messageviewer.h"
9 #include "headerstyle_util.h"
10 #include "settings/messageviewersettings.h"
11 #include "utils/iconnamecache.h"
12 
13 #include <MessageCore/StringUtil>
14 #include <MimeTreeParser/NodeHelper>
15 
16 #include <KMime/DateFormatter>
17 #include <KMime/KMimeMessage>
18 
19 #include <KColorScheme>
20 #include <KIconLoader>
21 #include <KLocalizedString>
22 
23 #include <grantlee/engine.h>
24 #include <grantlee/metatype.h>
25 
26 using namespace MessageCore;
27 
28 using namespace MessageViewer;
29 
30 Q_DECLARE_METATYPE(const KMime::Headers::Generics::AddressList *)
31 Q_DECLARE_METATYPE(QSharedPointer<KMime::Headers::Generics::AddressList>)
32 Q_DECLARE_METATYPE(const KMime::Headers::Generics::MailboxList *)
33 Q_DECLARE_METATYPE(QSharedPointer<KMime::Headers::Generics::MailboxList>)
34 Q_DECLARE_METATYPE(QDateTime)
35 
36 // Read-only introspection of KMime::Headers::Generics::AddressList object.
37 namespace Grantlee
38 {
39 template<>
40 inline QVariant TypeAccessor<const KMime::Headers::Generics::AddressList *>::lookUp(const KMime::Headers::Generics::AddressList *const object,
41  const QString &property)
42 {
43  if (property == QLatin1String("nameOnly")) {
44  return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayNameOnly);
45  } else if (property == QLatin1String("isSet")) {
46  return !object->asUnicodeString().isEmpty();
47  } else if (property == QLatin1String("fullAddress")) {
48  return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayFullAddress);
49  } else if (property == QLatin1String("str")) {
50  return object->asUnicodeString();
51  } else if (property.startsWith(QLatin1String("expandable"))) {
52  const auto &name = property.mid(10);
54  MessageCore::StringUtil::DisplayFullAddress,
55  QString(),
56  MessageCore::StringUtil::ShowLink,
57  MessageCore::StringUtil::ExpandableAddresses,
58  QStringLiteral("Full") + name + QStringLiteral("AddressList"));
59  return val;
60  }
61  return {};
62 }
63 }
64 
66 if (property == QLatin1String("nameOnly")) {
67  return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayNameOnly);
68 } else if (property == QLatin1String("isSet")) {
69  return !object->asUnicodeString().isEmpty();
70 } else if (property == QLatin1String("fullAddress")) {
71  return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayFullAddress);
72 } else if (property == QLatin1String("str")) {
73  return object->asUnicodeString();
74 } else if (property.startsWith(QLatin1String("expandable"))) {
75  const auto &name = property.mid(10);
76  const QString val = MessageCore::StringUtil::emailAddrAsAnchor(object.data(),
77  MessageCore::StringUtil::DisplayFullAddress,
78  QString(),
79  MessageCore::StringUtil::ShowLink,
80  MessageCore::StringUtil::ExpandableAddresses,
81  QStringLiteral("Full") + name + QStringLiteral("AddressList"));
82  return val;
83 }
84 GRANTLEE_END_LOOKUP
85 
86 // Read-only introspection of KMime::Headers::Generics::MailboxList object.
87 namespace Grantlee
88 {
89 template<>
90 inline QVariant TypeAccessor<const KMime::Headers::Generics::MailboxList *>::lookUp(const KMime::Headers::Generics::MailboxList *const object,
91  const QString &property)
92 {
93  if (property == QLatin1String("nameOnly")) {
94  return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayNameOnly);
95  } else if (property == QLatin1String("isSet")) {
96  return !object->asUnicodeString().isEmpty();
97  } else if (property == QLatin1String("fullAddress")) {
98  return StringUtil::emailAddrAsAnchor(object, StringUtil::DisplayFullAddress);
99  } else if (property == QLatin1String("str")) {
100  return object->asUnicodeString();
101  } else if (property.startsWith(QLatin1String("expandable"))) {
102  const auto &name = property.mid(10);
104  MessageCore::StringUtil::DisplayFullAddress,
105  QString(),
106  MessageCore::StringUtil::ShowLink,
107  MessageCore::StringUtil::ExpandableAddresses,
108  QStringLiteral("Full") + name + QStringLiteral("AddressList"));
109  return val;
110  }
111  return {};
112 }
113 }
114 
116 if (property == QLatin1String("nameOnly")) {
117  return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayNameOnly);
118 } else if (property == QLatin1String("isSet")) {
119  return !object->asUnicodeString().isEmpty();
120 } else if (property == QLatin1String("fullAddress")) {
121  return StringUtil::emailAddrAsAnchor(object.data(), StringUtil::DisplayFullAddress);
122 } else if (property == QLatin1String("str")) {
123  return object->asUnicodeString();
124 } else if (property.startsWith(QLatin1String("expandable"))) {
125  const auto &name = property.mid(10);
126  const QString val = MessageCore::StringUtil::emailAddrAsAnchor(object.data(),
127  MessageCore::StringUtil::DisplayFullAddress,
128  QString(),
129  MessageCore::StringUtil::ShowLink,
130  MessageCore::StringUtil::ExpandableAddresses,
131  QStringLiteral("Full") + name + QStringLiteral("AddressList"));
132  return val;
133 }
134 GRANTLEE_END_LOOKUP
135 
136 namespace Grantlee
137 {
138 template<> inline QVariant TypeAccessor<QDateTime &>::lookUp(const QDateTime &object, const QString &property)
139 {
141  if (property == QLatin1String("str")) {
142  return HeaderStyleUtil::dateStr(object);
143  } else if (property == QLatin1String("short")) {
145  } else if (property == QLatin1String("long")) {
147  } else if (property == QLatin1String("fancylong")) {
149  } else if (property == QLatin1String("fancyshort")) {
151  } else if (property == QLatin1String("localelong")) {
153  } else {
154  return {};
155  }
156 
157  return HeaderStyleUtil::strToHtml(HeaderStyleUtil::dateString(object, dateFormat));
158 }
159 }
160 
161 class Q_DECL_HIDDEN HeaderFormatter
162 {
163 public:
164  virtual ~HeaderFormatter() = default;
165 
166  virtual QVariant format(KMime::Message *message, MimeTreeParser::NodeHelper *nodeHelper) = 0;
167  virtual QString i18nName() = 0;
168 };
169 
170 class DefaultHeaderFormatter : public HeaderFormatter
171 {
172 public:
173  DefaultHeaderFormatter(const QByteArray &h)
174  : header(h)
175  {
176  }
177 
178  QString i18nName() override
179  {
180  if (header == "list-id") {
181  return i18n("List-Id:");
182  } else {
183  return {};
184  }
185  }
186 
187  QVariant format(KMime::Message *message, MimeTreeParser::NodeHelper *nodeHelper) override
188  {
189  return nodeHelper->mailHeaderAsBase(header.constData(), message)->asUnicodeString();
190  }
191 
192 private:
193  QByteArray header;
194 };
195 
196 class SubjectFormatter : public HeaderFormatter
197 {
198 public:
199  QString i18nName() override
200  {
201  return i18n("Subject:");
202  }
203 
204  QVariant format(KMime::Message *message, MimeTreeParser::NodeHelper *nodeHelper) override
205  {
207  // TODO: somehow, we need to get settings from format method.
208  // if (showEmoticons) {
209  // flags |= KTextToHTML::ReplaceSmileys;
210  //}
211  auto subjectStr = nodeHelper->mailHeaderAsBase("subject", message)->asUnicodeString();
212 
213  return HeaderStyleUtil::strToHtml(subjectStr, flags);
214  }
215 };
216 
217 class DateFormatter : public HeaderFormatter
218 {
219 public:
220  QString i18nName() override
221  {
222  return i18n("Date:");
223  }
224 
225  QVariant format(KMime::Message *message, MimeTreeParser::NodeHelper *nodeHelper) override
226  {
227  const auto value = nodeHelper->dateHeader(message);
228  return value;
229  }
230 };
231 
232 class AddressHeaderFormatter : public HeaderFormatter
233 {
234 public:
235  AddressHeaderFormatter(const QByteArray &h)
236  : header(h)
237  {
238  }
239 
240  QString i18nName() override
241  {
242  if (header == "to") {
243  return i18n("To:");
244  } else if (header == "reply-To") {
245  return i18n("Reply To:");
246  } else if (header == "replyFrom") {
247  return i18n("Reply From:");
248  } else if (header == "cc") {
249  return i18n("CC:");
250  } else if (header == "bcc") {
251  return i18n("BCC:");
252  } else if (header == "from") {
253  return i18n("From:");
254  } else if (header == "sender") {
255  return i18n("Sender:");
256  } else if (header == "resent-From") {
257  return i18n("resent from:");
258  } else if (header == "resent-To") {
259  return i18n("resent to:");
260  } else {
261  return {};
262  }
263  }
264 
265  QVariant format(KMime::Message *message, MimeTreeParser::NodeHelper *nodeHelper) override
266  {
267  const auto value = nodeHelper->mailHeaderAsAddressList(header.constData(), message);
268  return QVariant::fromValue(value);
269  }
270 
271 protected:
272  QByteArray header;
273 };
274 
275 class MessageViewer::GrantleeHeaderFormatter::GrantleeHeaderFormatterPrivate
276 {
277 public:
278  GrantleeHeaderFormatterPrivate()
279  {
280  Grantlee::registerMetaType<const KMime::Headers::Generics::AddressList *>();
281  Grantlee::registerMetaType<const KMime::Headers::Generics::MailboxList *>();
282  Grantlee::registerMetaType<QSharedPointer<KMime::Headers::Generics::MailboxList>>();
283  Grantlee::registerMetaType<QSharedPointer<KMime::Headers::Generics::AddressList>>();
284  Grantlee::registerMetaType<QDateTime>();
286  engine = new Grantlee::Engine;
287  templateLoader = QSharedPointer<Grantlee::FileSystemTemplateLoader>(new Grantlee::FileSystemTemplateLoader);
288  engine->addTemplateLoader(templateLoader);
289 
290  QVector<QByteArray> addressHeaders;
291  addressHeaders << "to"
292  << "reply-To"
293  << "reply-From"
294  << "cc"
295  << "bcc"
296  << "from"
297  << "sender"
298  << "resent-From"
299  << "resent-To";
300 
301  for (const auto &header : std::as_const(addressHeaders)) {
302  registerHeaderFormatter(header, QSharedPointer<HeaderFormatter>(new AddressHeaderFormatter(header)));
303  }
304 
305  registerHeaderFormatter("subject", QSharedPointer<HeaderFormatter>(new SubjectFormatter()));
306  registerHeaderFormatter("date", QSharedPointer<HeaderFormatter>(new DateFormatter()));
307  }
308 
309  ~GrantleeHeaderFormatterPrivate()
310  {
311  delete engine;
312  }
313 
314  void registerHeaderFormatter(const QByteArray &header, QSharedPointer<HeaderFormatter> formatter)
315  {
316  headerFormatter[header] = formatter;
317  }
318 
321  Grantlee::Engine *engine = nullptr;
322  MessageViewer::HeaderStyleUtil headerStyleUtil;
323  int iconSize;
324 };
325 
326 GrantleeHeaderFormatter::GrantleeHeaderFormatter()
327  : d(new GrantleeHeaderFormatter::GrantleeHeaderFormatterPrivate)
328 {
329 }
330 
331 GrantleeHeaderFormatter::~GrantleeHeaderFormatter() = default;
332 
333 QString GrantleeHeaderFormatter::toHtml(const GrantleeHeaderFormatter::GrantleeHeaderFormatterSettings &settings) const
334 {
336  if (!settings.theme.isValid()) {
337  errorMessage = i18n("Grantlee theme \"%1\" is not valid.", settings.theme.name());
338  return errorMessage;
339  }
340  d->templateLoader->setTemplateDirs(QStringList() << settings.theme.absolutePath());
341  Grantlee::Template headerTemplate = d->engine->loadByName(settings.theme.themeFilename());
342  if (headerTemplate->error()) {
343  errorMessage = headerTemplate->errorString();
344  return errorMessage;
345  }
346  return format(settings.theme.absolutePath(),
347  headerTemplate,
348  settings.theme.displayExtraVariables(),
349  settings.isPrinting,
350  settings.style,
351  settings.message,
352  settings.showEmoticons);
353 }
354 
355 QString GrantleeHeaderFormatter::toHtml(const QStringList &displayExtraHeaders,
356  const QString &absolutPath,
357  const QString &filename,
358  const MessageViewer::HeaderStyle *style,
359  KMime::Message *message,
360  bool isPrinting) const
361 {
362  d->templateLoader->setTemplateDirs(QStringList() << absolutPath);
363  Grantlee::Template headerTemplate = d->engine->loadByName(filename);
364  if (headerTemplate->error()) {
365  return headerTemplate->errorString();
366  }
367  return format(absolutPath, headerTemplate, displayExtraHeaders, isPrinting, style, message);
368 }
369 
370 QString GrantleeHeaderFormatter::format(const QString &absolutePath,
371  const Grantlee::Template &headerTemplate,
372  const QStringList &displayExtraHeaders,
373  bool isPrinting,
374  const MessageViewer::HeaderStyle *style,
375  KMime::Message *message,
376  bool showEmoticons) const
377 {
378  QVariantHash headerObject;
379  const auto nodeHelper = style->nodeHelper();
380 
381  // However, the direction of the message subject within the header is
382  // determined according to the contents of the subject itself. Since
383  // the "Re:" and "Fwd:" prefixes would always cause the subject to be
384  // considered left-to-right, they are ignored when determining its
385  // direction.
386  const QString absoluteThemePath = QUrl::fromLocalFile(absolutePath + QLatin1Char('/')).url();
387  headerObject.insert(QStringLiteral("absoluteThemePath"), absoluteThemePath);
388  headerObject.insert(QStringLiteral("applicationDir"), QApplication::isRightToLeft() ? QStringLiteral("rtl") : QStringLiteral("ltr"));
389 
390  // TODO: use correct subject from nodeHelper->mailHeader
391  headerObject.insert(QStringLiteral("subjectDir"), d->headerStyleUtil.subjectDirectionString(message));
392 
393  QVector<QByteArray> defaultHeaders;
394  defaultHeaders << "to"
395  << "reply-To"
396  << "reply-From"
397  << "cc"
398  << "bcc"
399  << "from"
400  << "sender"
401  << "resent-From"
402  << "resent-To"
403  << "subject"
404  << "organization"
405  << "list-id"
406  << "date";
407 
408  for (const auto &header : std::as_const(defaultHeaders)) {
410  if (d->headerFormatter.contains(header)) {
411  formatter = d->headerFormatter.value(header);
412  } else {
413  formatter = QSharedPointer<HeaderFormatter>(new DefaultHeaderFormatter(header));
414  }
415  const auto i18nName = formatter->i18nName();
416  const auto objectName = QString::fromUtf8(header).remove(QLatin1Char('-'));
417  if (nodeHelper->hasMailHeader(header.constData(), message)) {
418  const auto value = formatter->format(message, nodeHelper);
419  headerObject.insert(objectName, value);
420  }
421  if (!i18nName.isEmpty()) {
422  headerObject.insert(objectName + QStringLiteral("i18n"), i18nName);
423  }
424  }
425 
426  if (!nodeHelper->hasMailHeader("subject", message)) {
427  headerObject.insert(QStringLiteral("subject"), i18n("No Subject"));
428  }
429 
430  const QString spamHtml = d->headerStyleUtil.spamStatus(message);
431  if (!spamHtml.isEmpty()) {
432  headerObject.insert(QStringLiteral("spamstatusi18n"), i18n("Spam Status:"));
433  headerObject.insert(QStringLiteral("spamHTML"), spamHtml);
434  }
435 
436  if (!style->vCardName().isEmpty()) {
437  headerObject.insert(QStringLiteral("vcardname"), style->vCardName());
438  }
439 
440  if (isPrinting) {
441  // provide a bit more left padding when printing
442  // kolab/issue3254 (printed mail cut at the left side)
443  // Use it just for testing if we are in printing mode
444  headerObject.insert(QStringLiteral("isprinting"), i18n("Printing mode"));
445  headerObject.insert(QStringLiteral("printmode"), QStringLiteral("printmode"));
446  } else {
447  headerObject.insert(QStringLiteral("screenmode"), QStringLiteral("screenmode"));
448  }
449 
450  // colors depend on if it is encapsulated or not
451  QColor fontColor(Qt::white);
452  QString linkColor = QStringLiteral("white");
454  QColor activeColorDark = activeColor.darker(130);
455  // reverse colors for encapsulated
456  if (!style->isTopLevel()) {
457  activeColorDark = activeColor.darker(50);
458  fontColor = QColor(Qt::black);
459  linkColor = QStringLiteral("black");
460  }
461 
462  // 3D borders
463  headerObject.insert(QStringLiteral("activecolordark"), activeColorDark.name());
464  headerObject.insert(QStringLiteral("fontcolor"), fontColor.name());
465  headerObject.insert(QStringLiteral("linkcolor"), linkColor);
466 
467  MessageViewer::HeaderStyleUtil::xfaceSettings xface = d->headerStyleUtil.xface(style, message);
468  if (!xface.photoURL.isEmpty()) {
469  headerObject.insert(QStringLiteral("photowidth"), xface.photoWidth);
470  headerObject.insert(QStringLiteral("photoheight"), xface.photoHeight);
471  headerObject.insert(QStringLiteral("photourl"), xface.photoURL);
472  }
473 
474  for (QString header : std::as_const(displayExtraHeaders)) {
475  const QByteArray baHeader = header.toLocal8Bit();
476  if (auto hrd = message->headerByType(baHeader.constData())) {
477  // Grantlee doesn't support '-' in variable name => remove it.
478  header.remove(QLatin1Char('-'));
479  headerObject.insert(header, hrd->asUnicodeString());
480  }
481  }
482 
483  headerObject.insert(QStringLiteral("vcardi18n"), i18n("[vcard]"));
484  headerObject.insert(QStringLiteral("readOnlyMessage"), style->readOnlyMessage());
485 
486  const QString attachmentHtml = style->attachmentHtml();
487  const bool messageHasAttachment = KMime::hasAttachment(message) && !attachmentHtml.isEmpty();
488  headerObject.insert(QStringLiteral("hasAttachment"), messageHasAttachment);
489  headerObject.insert(QStringLiteral("attachmentHtml"), attachmentHtml);
490  headerObject.insert(QStringLiteral("attachmentI18n"), i18n("Attachments:"));
491 
492  if (messageHasAttachment) {
493  const QString iconPath = MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral("mail-attachment"), KIconLoader::Toolbar);
494  const QString html =
495  QStringLiteral("<img height=\"%2\" width=\"%2\" src=\"%1\"></a>").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize));
496  headerObject.insert(QStringLiteral("attachmentIcon"), html);
497  }
498 
499  const bool messageIsSigned = KMime::isSigned(message);
500  headerObject.insert(QStringLiteral("messageIsSigned"), messageIsSigned);
501  if (messageIsSigned) {
502  const QString iconPath = MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral("mail-signed"), KIconLoader::Toolbar);
503  const QString html =
504  QStringLiteral("<img height=\"%2\" width=\"%2\" src=\"%1\"></a>").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize));
505  headerObject.insert(QStringLiteral("signedIcon"), html);
506  }
507 
508  const bool messageIsEncrypted = KMime::isEncrypted(message);
509  headerObject.insert(QStringLiteral("messageIsEncrypted"), messageIsEncrypted);
510  if (messageIsEncrypted) {
511  const QString iconPath = MessageViewer::IconNameCache::instance()->iconPath(QStringLiteral("mail-encrypted"), KIconLoader::Toolbar);
512  const QString html =
513  QStringLiteral("<img height=\"%2\" width=\"%2\" src=\"%1\"></a>").arg(QUrl::fromLocalFile(iconPath).url(), QString::number(d->iconSize));
514  headerObject.insert(QStringLiteral("encryptedIcon"), html);
515  }
516 
517  const bool messageHasSecurityInfo = messageIsEncrypted || messageIsSigned;
518  headerObject.insert(QStringLiteral("messageHasSecurityInfo"), messageHasSecurityInfo);
519  headerObject.insert(QStringLiteral("messageHasSecurityInfoI18n"), i18n("Security:"));
520 
521  QVariantHash mapping;
522  mapping.insert(QStringLiteral("header"), headerObject);
523  Grantlee::Context context(mapping);
524 
525  return headerTemplate->render(&context);
526 }
Same as ShortDate for dates a week or more ago.
QString url(QUrl::FormattingOptions options) const const
bool isRightToLeft()
int currentSize(KIconLoader::Group group) const
QColor darker(int factor) const const
QBrush background(BackgroundRole=NormalBackground) const
This class encapsulates the visual appearance of message headers.
Definition: headerstyle.h:46
QString name() const const
QByteArray & insert(int i, char ch)
QString & remove(int position, int n)
const QLatin1String name
Locale Long date format, e.g.
virtual QString asUnicodeString() const =0
const QColor & color() const const
QString number(int n, int base)
QString fromUtf8(const char *str, int size)
QString & insert(int position, QChar ch)
The HeaderStyleUtil class.
QString objectName() const const
bool isEmpty() const const
const char * constData() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
The GrantleeHeaderFormatter class.
static KIconLoader * global()
QLatin1String mid(int start) const const
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
Headers::Base * headerByType(const char *type) const
QVariant fromValue(const T &value)
QString i18n(const char *text, const TYPE &arg...)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
MESSAGECORE_EXPORT QString emailAddrAsAnchor(const QVector< KMime::Types::Mailbox > &mailboxList, Display display=DisplayNameOnly, const QString &cssStyle=QString(), Link link=ShowLink, AddressMode expandable=FullAddresses, const QString &fieldName=QString(), int collapseNumber=4)
Same as the above, only for Mailbox::List types.
Locale Short date format, e.g.
Same as LongDate for dates a week or more ago.
QByteArray & remove(int pos, int len)
QUrl fromLocalFile(const QString &localFile)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Nov 30 2021 23:05:46 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.