Akonadi Contacts

standardcontactformatter.cpp
1 /*
2  This file is part of Akonadi Contact.
3 
4  Copyright (c) 2010 Tobias Koenig <[email protected]>
5 
6  This library is free software; you can redistribute it and/or modify it
7  under the terms of the GNU Library General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or (at your
9  option) any later version.
10 
11  This library is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14  License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to the
18  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  02110-1301, USA.
20 */
21 
22 #include "standardcontactformatter.h"
23 
24 #include <item.h>
25 #include <kcontacts/addressee.h>
26 #include <KColorScheme>
27 #include <KConfigGroup>
28 
29 #include <KLocalizedString>
30 #include <KStringHandler>
31 
32 #include <QRegularExpression>
33 #include <QSet>
34 #include <QLocale>
35 
36 using namespace Akonadi;
37 
38 class Q_DECL_HIDDEN StandardContactFormatter::Private
39 {
40 public:
41  Private()
42  : displayQRcode(true)
43  {
44  }
45 
46  bool displayQRcode;
47 };
48 
50  : d(new Private())
51 {
52 }
53 
55 {
56  delete d;
57 }
58 
59 static int contactAge(const QDate &date)
60 {
61  QDate now = QDate::currentDate();
62  int age = now.year() - date.year();
63  if (date > now.addYears(-age)) {
64  age--;
65  }
66  return age;
67 }
68 
70 {
71  KContacts::Addressee rawContact;
72  const Akonadi::Item localItem = item();
73  if (localItem.isValid() && localItem.hasPayload<KContacts::Addressee>()) {
74  rawContact = localItem.payload<KContacts::Addressee>();
75  } else {
76  rawContact = contact();
77  }
78 
79  if (rawContact.isEmpty()) {
80  return QString();
81  }
82 
83  // We'll be building a table to display the vCard in.
84  // Each row of the table will be built using one of these strings for its HTML.
85 
86  // single data item:
87  // %1 is the item name
88  // %2 is the item value
89  QString rowFmtStr1 = QStringLiteral(
90  "<tr valign=\"top\">"
91  "<td align=\"right\" valign=\"top\" width=\"30%\"><b><font color=\"grey\">%1</font></b></td>\n"
92  "<td colspan=\"2\" align=\"left\" valign=\"top\" width=\"70%\"><font>%2</font></td>\n"
93  "</tr>\n"
94  );
95 
96  // data item plus additional icon(s):
97  // %1 is the item name
98  // %2 is the item value
99  // %3 is the icon(s), each as a HTML <a><img> tag
100  QString rowFmtStr2 = QStringLiteral(
101  "<tr valign=\"top\">"
102  "<td align=\"right\" valign=\"top\" width=\"30%\"><b><font color=\"grey\">%1</font></b></td>\n"
103  "<td align=\"left\" valign=\"top\"><font>%2</font></td>\n"
104  "<td align=\"left\" valign=\"top\">%3</td>\n"
105  "</tr>\n"
106  );
107 
108  // Build the table's rows here
109  QString dynamicPart;
110 
111  // Birthday
112  const QDate date = rawContact.birthday().date();
113  const int years = contactAge(date);
114 
115  if (date.isValid()) {
116  dynamicPart += rowFmtStr1
118  .arg(QLocale().toString(date)
119  +QLatin1String("&nbsp;&nbsp;") + i18np("(One year old)", "(%1 years old)", years));
120  }
121 
122  // Phone Numbers
123  int counter = 0;
124 
125  const auto phoneNumbers = rawContact.phoneNumbers();
126  for (const KContacts::PhoneNumber &number : phoneNumbers) {
127  QString dispLabel = number.typeLabel().replace(QLatin1Char(' '), QStringLiteral("&nbsp;"));
128  QString dispValue = QStringLiteral("<a href=\"phone:?index=%1\">%2</a>").arg(counter).arg(number.number().toHtmlEscaped());
129  if (number.type() & KContacts::PhoneNumber::Cell) {
130  QString dispIcon = QStringLiteral("<a href=\"sms:?index=%1\" title=\"%2\"><img src=\"sms_icon\" align=\"top\"/>")
131  .arg(counter)
132  .arg(i18nc("@info:tooltip", "Send SMS"));
133  dynamicPart += rowFmtStr2
134  .arg(dispLabel, dispValue, dispIcon);
135  } else {
136  dynamicPart += rowFmtStr1
137  .arg(dispLabel, dispValue);
138  }
139 
140  ++counter;
141  }
142 
143  // EMails
144  for (const QString &email : rawContact.emails()) {
145  const QString type = i18nc("a contact's email address", "Email");
146 
147  const QString fullEmail = QString::fromLatin1(QUrl::toPercentEncoding(rawContact.fullEmail(email)));
148 
149  dynamicPart += rowFmtStr1.arg(type)
150  .arg(QStringLiteral("<a href=\"mailto:%1\">%2</a>")
151  .arg(fullEmail, email));
152  }
153 
154  // Homepage
155  if (rawContact.url().isValid()) {
156  QString url = rawContact.url().url().url();
157  if (!url.startsWith(QLatin1String("http://")) && !url.startsWith(QLatin1String("https://"))) {
158  url = QLatin1String("http://") + url;
159  }
160 
162  dynamicPart += rowFmtStr1.arg(i18n("Homepage"), url);
163  }
164 
165  // Blog Feed
166  const QString blog = rawContact.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("BlogFeed"));
167  if (!blog.isEmpty()) {
168  dynamicPart += rowFmtStr1.arg(i18n("Blog Feed")).arg(KStringHandler::tagUrls(blog.toHtmlEscaped()));
169  }
170 
171  // Addresses
172  counter = 0;
173  const auto addresses = rawContact.addresses();
174  for (const KContacts::Address &address : addresses) {
175  QString formattedAddress;
176 
177  if (address.label().isEmpty()) {
178  formattedAddress = address.formattedAddress().trimmed().toHtmlEscaped();
179  } else {
180  formattedAddress = address.label().toHtmlEscaped();
181  }
182 
183  formattedAddress.replace(QRegularExpression(QStringLiteral("\n+")), QStringLiteral("<br>"));
184 
185  const QString url = QStringLiteral("<a href=\"address:?index=%1\" title=\"%2\"><img src=\"map_icon\" alt=\"%2\"/></a>")
186  .arg(counter)
187  .arg(i18nc("@info:tooltip", "Show address on map"));
188  counter++;
189 
190  dynamicPart += rowFmtStr2
191  .arg(KContacts::Address::typeLabel(address.type()), formattedAddress, url);
192  }
193 
194  // Note
195  QString notes;
196  if (!rawContact.note().isEmpty()) {
197  notes = rowFmtStr1.arg(i18n("Notes")).arg(rawContact.note().toHtmlEscaped().replace(QLatin1Char('\n'), QLatin1String("<br>")));
198  }
199 
200  // Custom Data
201  QString customData;
202  static QMap<QString, QString> titleMap;
203  if (titleMap.isEmpty()) {
204  titleMap.insert(QStringLiteral("Department"), i18n("Department"));
205  titleMap.insert(QStringLiteral("Profession"), i18n("Profession"));
206  titleMap.insert(QStringLiteral("AssistantsName"), i18n("Assistant's Name"));
207  titleMap.insert(QStringLiteral("ManagersName"), i18n("Manager's Name"));
208  titleMap.insert(QStringLiteral("SpousesName"), i18nc("Wife/Husband/...", "Partner's Name"));
209  titleMap.insert(QStringLiteral("Office"), i18n("Office"));
210  titleMap.insert(QStringLiteral("IMAddress"), i18n("IM Address"));
211  titleMap.insert(QStringLiteral("Anniversary"), i18n("Anniversary"));
212  titleMap.insert(QStringLiteral("AddressBook"), i18n("Address Book"));
213  const QMap<QString, QString> tmpTitleMap = titleMap;
214 
215  QMap<QString, QString>::const_iterator iterator = tmpTitleMap.constBegin();
216  while (iterator != tmpTitleMap.constEnd()) {
217  titleMap.insert(iterator.key().toUpper(), iterator.value());
218  ++iterator;
219  }
220  }
221 
222  static QSet<QString> blacklistedKeys;
223  if (blacklistedKeys.isEmpty()) {
224  blacklistedKeys.insert(QStringLiteral("CRYPTOPROTOPREF"));
225  blacklistedKeys.insert(QStringLiteral("OPENPGPFP"));
226  blacklistedKeys.insert(QStringLiteral("SMIMEFP"));
227  blacklistedKeys.insert(QStringLiteral("CRYPTOSIGNPREF"));
228  blacklistedKeys.insert(QStringLiteral("CRYPTOENCRYPTPREF"));
229  blacklistedKeys.insert(QStringLiteral("MailPreferedFormatting"));
230  blacklistedKeys.insert(QStringLiteral("MailAllowToRemoteContent"));
231  blacklistedKeys.insert(QStringLiteral("MAILPREFEREDFORMATTING"));
232  blacklistedKeys.insert(QStringLiteral("MAILALLOWTOREMOTECONTENT"));
233  }
234 
235  if (!rawContact.customs().empty()) {
236  const QStringList customs = rawContact.customs();
237  for (QString custom : customs) {
238  if (custom.startsWith(QLatin1String("KADDRESSBOOK-"))) {
239  custom.remove(QStringLiteral("KADDRESSBOOK-X-"));
240  custom.remove(QStringLiteral("KADDRESSBOOK-"));
241 
242  int pos = custom.indexOf(QLatin1Char(':'));
243  QString key = custom.left(pos);
244  QString value = custom.mid(pos + 1);
245 
246  // convert anniversary correctly
247  if (key == QLatin1String("Anniversary") || key == QLatin1String("ANNIVERSARY")) {
248  const QDateTime dateTime = QDateTime::fromString(value, Qt::ISODate);
249  value = QLocale().toString(dateTime.date());
250  } else if (key == QLatin1String("BlogFeed") || key == QLatin1String("BLOGFEED")) { // blog is handled separated
251  continue;
252  } else if (blacklistedKeys.contains(key)) {
253  continue;
254  }
255 
256  // check whether we have a mapping for the title
257  const QMap<QString, QString>::ConstIterator keyIt = titleMap.constFind(key);
258  bool needToEscape = true;
259  if (keyIt != titleMap.constEnd()) {
260  key = keyIt.value();
261  } else {
262  // check whether it is a custom local field
263  for (const QVariantMap &description : customFieldDescriptions()) {
264  if (description.value(QStringLiteral("key")).toString() == key) {
265  key = description.value(QStringLiteral("title")).toString();
266  const QString descriptionType = description.value(QStringLiteral("type")).toString();
267  if (descriptionType == QLatin1String("boolean")) {
268  if (value == QLatin1String("true")) {
269  value = i18nc("Boolean value", "yes");
270  } else {
271  value = i18nc("Boolean value", "no");
272  }
273  } else if (descriptionType == QLatin1String("date")) {
274  const QDate date = QDate::fromString(value, Qt::ISODate);
275  value = QLocale().toString(date, QLocale::ShortFormat);
276  } else if (descriptionType == QLatin1String("time")) {
277  const QTime time = QTime::fromString(value, Qt::ISODate);
278  value = QLocale().toString(time);
279  } else if (descriptionType == QLatin1String("datetime")) {
280  const QDateTime dateTime = QDateTime::fromString(value, Qt::ISODate);
281  value = QLocale().toString(dateTime, QLocale::ShortFormat);
282  } else if (descriptionType == QLatin1String("url")) {
283  value = KStringHandler::tagUrls(value.toHtmlEscaped());
284  needToEscape = false;
285  }
286 
287  break;
288  }
289  }
290  }
291  if (needToEscape) {
292  value = value.toHtmlEscaped();
293  }
294  customData += rowFmtStr1.arg(key, value);
295  }
296  }
297  }
298 
299  // Assemble all parts
300  QString role = rawContact.title();
301  if (role.isEmpty()) {
302  role = rawContact.role();
303  }
304  if (role.isEmpty()) {
305  role = rawContact.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Profession"));
306  }
307 
308  QString strAddr = QStringLiteral(
309  "<div align=\"center\">"
310  "<table cellpadding=\"3\" cellspacing=\"1\">"
311  "<tr>"
312  "<td align=\"right\" valign=\"top\" width=\"30%\" rowspan=\"3\">"
313  "<img src=\"%1\" width=\"100\" vspace=\"1\"/>" // image
314  "</td>"
315  "<td colspan=\"2\" align=\"left\" width=\"70%\"><font size=\"+2\"><b>%2</b></font></td>" // name
316  "</tr>"
317  "<tr>"
318  "<td colspan=\"2\" align=\"left\" width=\"70%\">%3</td>" // role
319  "</tr>"
320  "<tr>"
321  "<td colspan=\"2\" align=\"left\" width=\"70%\">%4</td>" // organization
322  "</tr>")
323  .arg(QStringLiteral("contact_photo"))
324  .arg(rawContact.realName().toHtmlEscaped())
325  .arg(role.toHtmlEscaped())
326  .arg(rawContact.organization().toHtmlEscaped());
327 
328  strAddr.append(dynamicPart);
329  strAddr.append(notes);
330  strAddr.append(customData);
331  strAddr.append(QLatin1String("</table>"));
332 
333  if (d->displayQRcode) {
334  KConfig config(QStringLiteral("akonadi_contactrc"));
335  KConfigGroup group(&config, QStringLiteral("View"));
336  if (group.readEntry("QRCodes", true)) {
337  strAddr.append(QString::fromLatin1(
338  "<p align=\"center\">"
339  "<img src=\"%1\" vspace=\"1\"/>"
340  "</p>"
341  )
342  .arg(QStringLiteral("qrcode")));
343  }
344  }
345 
346  strAddr.append(QLatin1String("</div>\n"));
347 
348  if (form == EmbeddableForm) {
349  return strAddr;
350  }
351 
352  const QString document = QStringLiteral(
353  "<html>"
354  "<head>"
355  " <style type=\"text/css\">"
356  " a {text-decoration:none; color:%1}"
357  " </style>"
358  "</head>"
359  "<body text=\"%1\" bgcolor=\"%2\">" // text and background color
360  "%3" // contact part
361  "</body>"
362  "</html>")
363  .arg(KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name())
364  .arg(KColorScheme(QPalette::Active, KColorScheme::View).background().color().name())
365  .arg(strAddr);
366 
367  return document;
368 }
369 
370 void StandardContactFormatter::setDisplayQRCode(bool show)
371 {
372  d->displayQRcode = show;
373 }
374 
375 bool StandardContactFormatter::displayQRCode() const
376 {
377  return d->displayQRcode;
378 }
QString custom(const QString &app, const QString &name) const
QString & append(QChar ch)
QVector< QVariantMap > customFieldDescriptions() const
Returns the custom field descriptions that will be used.
HtmlForm
Describes the form of the HTML that is created.
QString toString(qlonglong i) const const
QStringList emails() const
QString organization() const
QString role() const
Creates a div HTML element that can be embedded.
QMap::const_iterator constBegin() const const
QMap::const_iterator constFind(const Key &key) const const
QTime fromString(const QString &string, Qt::DateFormat format)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QSet::iterator insert(const T &value)
ResourceLocatorUrl url() const
QString fullEmail(const QString &email=QString()) const
QString title() const
QDate fromString(const QString &string, Qt::DateFormat format)
QString number(int n, int base)
bool empty() const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isEmpty() const const
QMap::const_iterator constEnd() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isValid() const const
bool isEmpty() const
QDate addYears(int nyears) const const
int indexOf(QStringView str, int from) const const
QDateTime birthday() const
KContacts::Addressee contact() const
Returns the contact that will be formatted.
QString realName() const
QString toHtmlEscaped() const const
A class that formats a contact as HTML code.
~StandardContactFormatter() override
Destroys the standard contact formatter.
QDateTime fromString(const QString &string, Qt::DateFormat format)
bool contains(const T &value) const const
const Key key(const T &value, const Key &defaultKey) const const
QString i18n(const char *text, const TYPE &arg...)
QString & replace(int position, int n, QChar after)
PhoneNumber::List phoneNumbers() const
QString typeLabel() const
StandardContactFormatter()
Creates a new standard contact formatter.
QString mid(int position, int n) const const
QDate date() const const
QString toHtml(HtmlForm form=SelfcontainedForm) const override
Returns the contact formatted as HTML.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QStringList customs() const
QDate currentDate()
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
bool isEmpty() const const
QString left(int n) const const
QString fromLatin1(const char *str, int size)
static QString birthdayLabel()
QMap::iterator insert(const Key &key, const T &value)
bool isEmpty() const const
Akonadi::Item item() const
Returns the item who&#39;s payload will be formatted.
Address::List addresses() const
int year() const const
T readEntry(const QString &key, const T &aDefault) const
QString note() const
const T value(const Key &key, const T &defaultValue) const const
KCOREADDONS_EXPORT QString tagUrls(const QString &text)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Wed Aug 5 2020 23:05:58 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.