standardcontactformatter.cpp 14.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
    This file is part of Akonadi Contact.

    Copyright (c) 2010 Tobias Koenig <tokoe@kde.org>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "standardcontactformatter.h"

24
#include <item.h>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
25
#include <kcontacts/addressee.h>
26
27
#include <KColorScheme>
#include <KConfigGroup>
Laurent Montel's avatar
Laurent Montel committed
28

Laurent Montel's avatar
Laurent Montel committed
29
#include <KLocalizedString>
30
#include <KStringHandler>
31

Laurent Montel's avatar
Laurent Montel committed
32
#include <QRegularExpression>
Laurent Montel's avatar
Laurent Montel committed
33
#include <QSet>
Laurent Montel's avatar
Laurent Montel committed
34
#include <QLocale>
35
36
37

using namespace Akonadi;

38
class Q_DECL_HIDDEN StandardContactFormatter::Private
39
40
{
public:
Laurent Montel's avatar
Laurent Montel committed
41
42
43
44
    Private()
        : displayQRcode(true)
    {
    }
45

Laurent Montel's avatar
Laurent Montel committed
46
    bool displayQRcode;
47
48
};

49
StandardContactFormatter::StandardContactFormatter()
Laurent Montel's avatar
Laurent Montel committed
50
    : d(new Private())
51
52
53
54
55
{
}

StandardContactFormatter::~StandardContactFormatter()
{
Laurent Montel's avatar
Laurent Montel committed
56
    delete d;
57
58
}

Laurent Montel's avatar
Laurent Montel committed
59
static int contactAge(const QDate &date)
60
{
Laurent Montel's avatar
Laurent Montel committed
61
62
63
64
65
66
    QDate now = QDate::currentDate();
    int age = now.year() - date.year();
    if (date > now.addYears(-age)) {
        age--;
    }
    return age;
67
68
}

Laurent Montel's avatar
Laurent Montel committed
69
QString StandardContactFormatter::toHtml(HtmlForm form) const
70
{
Laurent Montel's avatar
Laurent Montel committed
71
72
73
74
    KContacts::Addressee rawContact;
    const Akonadi::Item localItem = item();
    if (localItem.isValid() && localItem.hasPayload<KContacts::Addressee>()) {
        rawContact = localItem.payload<KContacts::Addressee>();
75
    } else {
Laurent Montel's avatar
Laurent Montel committed
76
        rawContact = contact();
77
    }
78

Laurent Montel's avatar
Laurent Montel committed
79
80
81
    if (rawContact.isEmpty()) {
        return QString();
    }
82

Laurent Montel's avatar
Laurent Montel committed
83
84
85
86
87
88
    // We'll be building a table to display the vCard in.
    // Each row of the table will be built using one of these strings for its HTML.

    // single data item:
    //  %1 is the item name
    //  %2 is the item value
89
    QString rowFmtStr1 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
90
91
92
93
94
        "<tr valign=\"top\">"
        "<td align=\"right\" valign=\"top\" width=\"30%\"><b><font color=\"grey\">%1</font></b></td>\n"
        "<td colspan=\"2\" align=\"left\" valign=\"top\" width=\"70%\"><font>%2</font></td>\n"
        "</tr>\n"
        );
Laurent Montel's avatar
Laurent Montel committed
95
96
97
98
99

    // data item plus additional icon(s):
    //  %1 is the item name
    //  %2 is the item value
    //  %3 is the icon(s), each as a HTML <a><img> tag
100
    QString rowFmtStr2 = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
101
102
103
104
105
106
        "<tr valign=\"top\">"
        "<td align=\"right\" valign=\"top\" width=\"30%\"><b><font color=\"grey\">%1</font></b></td>\n"
        "<td align=\"left\" valign=\"top\"><font>%2</font></td>\n"
        "<td align=\"left\" valign=\"top\">%3</td>\n"
        "</tr>\n"
        );
Laurent Montel's avatar
Laurent Montel committed
107
108
109
110
111
112
113
114
115
116
117

    // Build the table's rows here
    QString dynamicPart;

    // Birthday
    const QDate date = rawContact.birthday().date();
    const int years = contactAge(date);

    if (date.isValid()) {
        dynamicPart += rowFmtStr1
                       .arg(KContacts::Addressee::birthdayLabel())
Laurent Montel's avatar
Laurent Montel committed
118
119
                       .arg(QLocale().toString(date)
                            +QLatin1String("&nbsp;&nbsp;") + i18np("(One year old)", "(%1 years old)", years));
Laurent Montel's avatar
Laurent Montel committed
120
    }
121

Laurent Montel's avatar
Laurent Montel committed
122
123
    // Phone Numbers
    int counter = 0;
Laurent Montel's avatar
Laurent Montel committed
124
125

    for (const KContacts::PhoneNumber &number : rawContact.phoneNumbers()) {
Laurent Montel's avatar
Laurent Montel committed
126
        QString dispLabel = number.typeLabel().replace(QLatin1Char(' '), QStringLiteral("&nbsp;"));
127
        QString dispValue = QStringLiteral("<a href=\"phone:?index=%1\">%2</a>").arg(counter).arg(number.number().toHtmlEscaped());
Laurent Montel's avatar
Laurent Montel committed
128
        if (number.type() & KContacts::PhoneNumber::Cell) {
129
            QString dispIcon = QStringLiteral("<a href=\"sms:?index=%1\" title=\"%2\"><img src=\"sms_icon\" align=\"top\"/>")
Laurent Montel's avatar
Laurent Montel committed
130
131
132
133
134
135
136
137
138
139
140
                               .arg(counter)
                               .arg(i18nc("@info:tooltip", "Send SMS"));
            dynamicPart += rowFmtStr2
                           .arg(dispLabel)
                           .arg(dispValue)
                           .arg(dispIcon);
        } else {
            dynamicPart += rowFmtStr1
                           .arg(dispLabel)
                           .arg(dispValue);
        }
141

Laurent Montel's avatar
Laurent Montel committed
142
        ++counter;
143
    }
144

Laurent Montel's avatar
Laurent Montel committed
145
    // EMails
Laurent Montel's avatar
Laurent Montel committed
146
    for (const QString &email : rawContact.emails()) {
Laurent Montel's avatar
Laurent Montel committed
147
        const QString type = i18nc("a contact's email address", "Email");
148

Laurent Montel's avatar
Laurent Montel committed
149
        const QString fullEmail = QString::fromLatin1(QUrl::toPercentEncoding(rawContact.fullEmail(email)));
150

Laurent Montel's avatar
Laurent Montel committed
151
        dynamicPart += rowFmtStr1.arg(type)
152
                       .arg(QStringLiteral("<a href=\"mailto:%1\">%2</a>")
Laurent Montel's avatar
Laurent Montel committed
153
                            .arg(fullEmail, email));
154
155
    }

Laurent Montel's avatar
Laurent Montel committed
156
157
    // Homepage
    if (rawContact.url().isValid()) {
Laurent Montel's avatar
Laurent Montel committed
158
        QString url = rawContact.url().url().url();
Laurent Montel's avatar
Laurent Montel committed
159
160
        if (!url.startsWith(QLatin1String("http://")) && !url.startsWith(QLatin1String("https://"))) {
            url = QLatin1String("http://") + url;
161
        }
162

Laurent Montel's avatar
Laurent Montel committed
163
164
165
166
167
        url = KStringHandler::tagUrls(url.toHtmlEscaped());
        dynamicPart += rowFmtStr1.arg(i18n("Homepage")).arg(url);
    }

    // Blog Feed
168
    const QString blog = rawContact.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("BlogFeed"));
Laurent Montel's avatar
Laurent Montel committed
169
170
171
172
173
174
    if (!blog.isEmpty()) {
        dynamicPart += rowFmtStr1.arg(i18n("Blog Feed")).arg(KStringHandler::tagUrls(blog.toHtmlEscaped()));
    }

    // Addresses
    counter = 0;
Laurent Montel's avatar
Laurent Montel committed
175
    for (const KContacts::Address &address : rawContact.addresses()) {
Laurent Montel's avatar
Laurent Montel committed
176
177
178
179
        QString formattedAddress;

        if (address.label().isEmpty()) {
            formattedAddress = address.formattedAddress().trimmed().toHtmlEscaped();
180
        } else {
Laurent Montel's avatar
Laurent Montel committed
181
182
183
            formattedAddress = address.label().toHtmlEscaped();
        }

Laurent Montel's avatar
Laurent Montel committed
184
        formattedAddress.replace(QRegularExpression(QStringLiteral("\n+")), QStringLiteral("<br>"));
Laurent Montel's avatar
Laurent Montel committed
185

186
        const QString url = QStringLiteral("<a href=\"address:?index=%1\" title=\"%2\"><img src=\"map_icon\" alt=\"%2\"/></a>")
Laurent Montel's avatar
Laurent Montel committed
187
188
189
190
191
192
193
194
195
196
197
198
199
                            .arg(counter)
                            .arg(i18nc("@info:tooltip", "Show address on map"));
        counter++;

        dynamicPart += rowFmtStr2
                       .arg(KContacts::Address::typeLabel(address.type()))
                       .arg(formattedAddress)
                       .arg(url);
    }

    // Note
    QString notes;
    if (!rawContact.note().isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
200
        notes = rowFmtStr1.arg(i18n("Notes")).arg(rawContact.note().toHtmlEscaped().replace(QLatin1Char('\n'), QLatin1String("<br>")));
Laurent Montel's avatar
Laurent Montel committed
201
202
203
204
205
206
    }

    // Custom Data
    QString customData;
    static QMap<QString, QString> titleMap;
    if (titleMap.isEmpty()) {
207
208
209
210
211
212
213
214
215
        titleMap.insert(QStringLiteral("Department"), i18n("Department"));
        titleMap.insert(QStringLiteral("Profession"), i18n("Profession"));
        titleMap.insert(QStringLiteral("AssistantsName"), i18n("Assistant's Name"));
        titleMap.insert(QStringLiteral("ManagersName"), i18n("Manager's Name"));
        titleMap.insert(QStringLiteral("SpousesName"), i18nc("Wife/Husband/...", "Partner's Name"));
        titleMap.insert(QStringLiteral("Office"), i18n("Office"));
        titleMap.insert(QStringLiteral("IMAddress"), i18n("IM Address"));
        titleMap.insert(QStringLiteral("Anniversary"), i18n("Anniversary"));
        titleMap.insert(QStringLiteral("AddressBook"), i18n("Address Book"));
Laurent Montel's avatar
Laurent Montel committed
216
        const QMap<QString, QString> tmpTitleMap = titleMap;
217

Laurent Montel's avatar
Laurent Montel committed
218
        QMap<QString, QString>::const_iterator iterator = tmpTitleMap.constBegin();
219
        while (iterator != tmpTitleMap.constEnd()) {
Laurent Montel's avatar
Laurent Montel committed
220
            titleMap.insert(iterator.key().toUpper(), iterator.value());
221
222
            ++iterator;
        }
Laurent Montel's avatar
Laurent Montel committed
223
224
225
226
    }

    static QSet<QString> blacklistedKeys;
    if (blacklistedKeys.isEmpty()) {
227
228
229
230
231
232
233
        blacklistedKeys.insert(QStringLiteral("CRYPTOPROTOPREF"));
        blacklistedKeys.insert(QStringLiteral("OPENPGPFP"));
        blacklistedKeys.insert(QStringLiteral("SMIMEFP"));
        blacklistedKeys.insert(QStringLiteral("CRYPTOSIGNPREF"));
        blacklistedKeys.insert(QStringLiteral("CRYPTOENCRYPTPREF"));
        blacklistedKeys.insert(QStringLiteral("MailPreferedFormatting"));
        blacklistedKeys.insert(QStringLiteral("MailAllowToRemoteContent"));
234
235
        blacklistedKeys.insert(QStringLiteral("MAILPREFEREDFORMATTING"));
        blacklistedKeys.insert(QStringLiteral("MAILALLOWTOREMOTECONTENT"));
Laurent Montel's avatar
Laurent Montel committed
236
237
238
239
    }

    if (!rawContact.customs().empty()) {
        const QStringList customs = rawContact.customs();
Laurent Montel's avatar
Laurent Montel committed
240
        for (QString custom : customs) {
Laurent Montel's avatar
Laurent Montel committed
241
            if (custom.startsWith(QLatin1String("KADDRESSBOOK-"))) {
Laurent Montel's avatar
Laurent Montel committed
242
243
                custom.remove(QStringLiteral("KADDRESSBOOK-X-"));
                custom.remove(QStringLiteral("KADDRESSBOOK-"));
Laurent Montel's avatar
Laurent Montel committed
244
245
246
247
248
249
250
251
252

                int pos = custom.indexOf(QLatin1Char(':'));
                QString key = custom.left(pos);
                QString value = custom.mid(pos + 1);

                // convert anniversary correctly
                if (key == QLatin1String("Anniversary") || key == QLatin1String("ANNIVERSARY")) {
                    const QDateTime dateTime = QDateTime::fromString(value, Qt::ISODate);
                    value = QLocale().toString(dateTime.date());
253
                } else if (key == QLatin1String("BlogFeed") || key == QLatin1String("BLOGFEED")) {      // blog is handled separated
Laurent Montel's avatar
Laurent Montel committed
254
255
256
257
258
259
260
261
262
263
                    continue;
                } else if (blacklistedKeys.contains(key)) {
                    continue;
                }

                // check whether we have a mapping for the title
                const QMap<QString, QString>::ConstIterator keyIt = titleMap.constFind(key);
                bool needToEscape = true;
                if (keyIt != titleMap.constEnd()) {
                    key = keyIt.value();
264
                } else {
Laurent Montel's avatar
Laurent Montel committed
265
                    // check whether it is a custom local field
Laurent Montel's avatar
Laurent Montel committed
266
                    for (const QVariantMap &description : customFieldDescriptions()) {
267
268
269
                        if (description.value(QStringLiteral("key")).toString() == key) {
                            key = description.value(QStringLiteral("title")).toString();
                            const QString descriptionType = description.value(QStringLiteral("type")).toString();
Laurent Montel's avatar
Laurent Montel committed
270
271
272
273
274
275
276
277
278
279
280
                            if (descriptionType == QLatin1String("boolean")) {
                                if (value == QLatin1String("true")) {
                                    value = i18nc("Boolean value", "yes");
                                } else {
                                    value = i18nc("Boolean value", "no");
                                }
                            } else if (descriptionType == QLatin1String("date")) {
                                const QDate date = QDate::fromString(value, Qt::ISODate);
                                value = QLocale().toString(date, QLocale::ShortFormat);
                            } else if (descriptionType == QLatin1String("time")) {
                                const QTime time = QTime::fromString(value, Qt::ISODate);
281
                                value = QLocale().toString(time);
Laurent Montel's avatar
Laurent Montel committed
282
283
                            } else if (descriptionType == QLatin1String("datetime")) {
                                const QDateTime dateTime = QDateTime::fromString(value, Qt::ISODate);
284
                                value = QLocale().toString(dateTime, QLocale::ShortFormat);
Laurent Montel's avatar
Laurent Montel committed
285
286
287
288
289
290
291
292
                            } else if (descriptionType == QLatin1String("url")) {
                                value = KStringHandler::tagUrls(value.toHtmlEscaped());
                                needToEscape = false;
                            }

                            break;
                        }
                    }
293
                }
Laurent Montel's avatar
Laurent Montel committed
294
295
296
297
                if (needToEscape) {
                    value = value.toHtmlEscaped();
                }
                customData += rowFmtStr1.arg(key).arg(value);
298
299
300
            }
        }
    }
Laurent Montel's avatar
Laurent Montel committed
301
302
303
304
305
306
307

    // Assemble all parts
    QString role = rawContact.title();
    if (role.isEmpty()) {
        role = rawContact.role();
    }
    if (role.isEmpty()) {
308
        role = rawContact.custom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("X-Profession"));
Laurent Montel's avatar
Laurent Montel committed
309
310
    }

311
    QString strAddr = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
312
313
314
315
        "<div align=\"center\">"
        "<table cellpadding=\"3\" cellspacing=\"1\">"
        "<tr>"
        "<td align=\"right\" valign=\"top\" width=\"30%\" rowspan=\"3\">"
Volker Krause's avatar
Volker Krause committed
316
        "<img src=\"%1\" width=\"100\" vspace=\"1\"/>"                   // image
Laurent Montel's avatar
Laurent Montel committed
317
318
319
320
321
322
323
324
325
        "</td>"
        "<td colspan=\"2\" align=\"left\" width=\"70%\"><font size=\"+2\"><b>%2</b></font></td>"                   // name
        "</tr>"
        "<tr>"
        "<td colspan=\"2\" align=\"left\" width=\"70%\">%3</td>"                    // role
        "</tr>"
        "<tr>"
        "<td colspan=\"2\" align=\"left\" width=\"70%\">%4</td>"                    // organization
        "</tr>")
326
                      .arg(QStringLiteral("contact_photo"))
Laurent Montel's avatar
Laurent Montel committed
327
328
329
330
331
332
333
                      .arg(rawContact.realName().toHtmlEscaped())
                      .arg(role.toHtmlEscaped())
                      .arg(rawContact.organization().toHtmlEscaped());

    strAddr.append(dynamicPart);
    strAddr.append(notes);
    strAddr.append(customData);
334
    strAddr.append(QLatin1String("</table>"));
Tobias Koenig's avatar
Tobias Koenig committed
335

Laurent Montel's avatar
Laurent Montel committed
336
    if (d->displayQRcode) {
Laurent Montel's avatar
Laurent Montel committed
337
        KConfig config(QStringLiteral("akonadi_contactrc"));
Laurent Montel's avatar
Laurent Montel committed
338
        KConfigGroup group(&config, QStringLiteral("View"));
Laurent Montel's avatar
Laurent Montel committed
339
340
341
        if (group.readEntry("QRCodes", true)) {
            strAddr.append(QString::fromLatin1(
                               "<p align=\"center\">"
Volker Krause's avatar
Volker Krause committed
342
                               "<img src=\"%1\" vspace=\"1\"/>"
Laurent Montel's avatar
Laurent Montel committed
343
                               "</p>"
Laurent Montel's avatar
Laurent Montel committed
344
                               )
Laurent Montel's avatar
Laurent Montel committed
345
                           .arg(QStringLiteral("qrcode")));
Laurent Montel's avatar
Laurent Montel committed
346
        }
347
    }
Tobias Koenig's avatar
Tobias Koenig committed
348

349
    strAddr.append(QLatin1String("</div>\n"));
Laurent Montel's avatar
Laurent Montel committed
350
351
352
353
354

    if (form == EmbeddableForm) {
        return strAddr;
    }

355
    const QString document = QStringLiteral(
Laurent Montel's avatar
Laurent Montel committed
356
357
358
359
360
361
362
363
364
365
        "<html>"
        "<head>"
        " <style type=\"text/css\">"
        "  a {text-decoration:none; color:%1}"
        " </style>"
        "</head>"
        "<body text=\"%1\" bgcolor=\"%2\">"                          // text and background color
        "%3"                          // contact part
        "</body>"
        "</html>")
Laurent Montel's avatar
Laurent Montel committed
366
367
368
369
370
                             .arg(KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name())
                             .arg(KColorScheme(QPalette::Active, KColorScheme::View).background().color().name())
                             .arg(strAddr);

    return document;
371
}
Laurent Montel's avatar
Minor    
Laurent Montel committed
372

Laurent Montel's avatar
Laurent Montel committed
373
void StandardContactFormatter::setDisplayQRCode(bool show)
374
{
Laurent Montel's avatar
Laurent Montel committed
375
    d->displayQRcode = show;
376
377
378
379
}

bool StandardContactFormatter::displayQRCode() const
{
Laurent Montel's avatar
Laurent Montel committed
380
    return d->displayQRcode;
381
}