contactviewer.cpp 12.2 KB
Newer Older
1 2 3
/*
    This file is part of Akonadi Contact.

4
    SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
5

6
    SPDX-License-Identifier: LGPL-2.0-or-later
7 8 9 10
*/

#include "contactviewer.h"

11
#include "contactmetadataakonadi_p.h"
Laurent Montel's avatar
Laurent Montel committed
12
#include "attributes/contactmetadataattribute_p.h"
13
#include "customfieldmanager_p.h"
14
#include "standardcontactformatter.h"
15
#include "textbrowser_p.h"
16

Laurent Montel's avatar
Laurent Montel committed
17
#include <KIOCore/kio/transferjob.h>
18 19 20 21 22
#include <collection.h>
#include <collectionfetchjob.h>
#include <entitydisplayattribute.h>
#include <item.h>
#include <itemfetchscope.h>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
23
#include <kcontacts/addressee.h>
24 25
#include <KColorScheme>
#include <KConfigGroup>
Laurent Montel's avatar
Laurent Montel committed
26
#include <KLocalizedString>
27
#include <KStringHandler>
28

29
#include <QVBoxLayout>
Laurent Montel's avatar
Laurent Montel committed
30
#include <QIcon>
31
#include <QUrlQuery>
32
#include <prison/Prison>
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
33
#include <kcontacts/vcardconverter.h>
34

35 36
using namespace Akonadi;

37
class Q_DECL_HIDDEN ContactViewer::Private
38
{
Guy Maurel's avatar
Guy Maurel committed
39 40 41
public:
    Private(ContactViewer *parent)
        : mParent(parent)
42
    {
Guy Maurel's avatar
Guy Maurel committed
43 44
        mStandardContactFormatter = new StandardContactFormatter;
        mContactFormatter = mStandardContactFormatter;
45 46 47
        KConfig config(QStringLiteral("akonadi_contactrc"));
        KConfigGroup group(&config, QStringLiteral("View"));
        mShowQRCode = group.readEntry("QRCodes", true);
48
        mQRCode = Prison::createBarcode(Prison::QRCode);
49 50 51 52
    }

    ~Private()
    {
Guy Maurel's avatar
Guy Maurel committed
53 54
        delete mStandardContactFormatter;
        delete mQRCode;
55 56
    }

Guy Maurel's avatar
Guy Maurel committed
57
    void updateView(const QVariantList &localCustomFieldDescriptions = QVariantList(), const QString &addressBookName = QString())
58
    {
Laurent Montel's avatar
Laurent Montel committed
59
        static QPixmap defaultPixmap = QIcon::fromTheme(QStringLiteral("user-identity")).pixmap(QSize(100, 100));
60
        static QPixmap defaultMapPixmap = QIcon::fromTheme(QStringLiteral("map-symbolic")).pixmap(QSize(16, 16));
61
        static QPixmap defaultSmsPixmap = QIcon::fromTheme(KContacts::Impp::serviceIcon(QStringLiteral("sms"))).pixmap(QSize(16, 16));
Guy Maurel's avatar
Guy Maurel committed
62

63
        mParent->setWindowTitle(i18nc("@title:window", "Contact %1", mCurrentContact.assembledName()));
Guy Maurel's avatar
Guy Maurel committed
64 65 66

        if (mCurrentContact.photo().isIntern()) {
            mBrowser->document()->addResource(QTextDocument::ImageResource,
Laurent Montel's avatar
Laurent Montel committed
67
                                              QUrl(QStringLiteral("contact_photo")),
Guy Maurel's avatar
Guy Maurel committed
68
                                              mCurrentContact.photo().data());
69
        } else if (!mCurrentContact.photo().url().isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
70 71 72 73
            QByteArray imageData;
            QImage image;
            KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.photo().url()), KIO::NoReload);
            QObject::connect(job, &KIO::TransferJob::data,
Laurent Montel's avatar
Laurent Montel committed
74
                             [&imageData](KIO::Job *, const QByteArray &data) {
Laurent Montel's avatar
Laurent Montel committed
75 76
                imageData.append(data);
            });
Laurent Montel's avatar
Laurent Montel committed
77 78 79
            if (job->exec()) {
                if (image.loadFromData(imageData)) {
                    mBrowser->document()->addResource(QTextDocument::ImageResource,
Laurent Montel's avatar
Laurent Montel committed
80
                                                      QUrl(QStringLiteral("contact_photo")),
Laurent Montel's avatar
Laurent Montel committed
81 82 83 84 85 86 87 88 89 90 91
                                                      image);
                } else {
                    mBrowser->document()->addResource(QTextDocument::ImageResource,
                                                      QUrl(QStringLiteral("contact_photo")),
                                                      defaultPixmap);
                }
            } else {
                mBrowser->document()->addResource(QTextDocument::ImageResource,
                                                  QUrl(QStringLiteral("contact_photo")),
                                                  defaultPixmap);
            }
Guy Maurel's avatar
Guy Maurel committed
92 93
        } else {
            mBrowser->document()->addResource(QTextDocument::ImageResource,
Laurent Montel's avatar
Laurent Montel committed
94
                                              QUrl(QStringLiteral("contact_photo")),
Guy Maurel's avatar
Guy Maurel committed
95 96 97 98 99
                                              defaultPixmap);
        }

        if (mCurrentContact.logo().isIntern()) {
            mBrowser->document()->addResource(QTextDocument::ImageResource,
Laurent Montel's avatar
Laurent Montel committed
100
                                              QUrl(QStringLiteral("contact_logo")),
Guy Maurel's avatar
Guy Maurel committed
101
                                              mCurrentContact.logo().data());
102
        } else if (!mCurrentContact.logo().url().isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
103 104 105 106
            QByteArray imageData;
            QImage image;
            KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.logo().url()), KIO::NoReload);
            QObject::connect(job, &KIO::TransferJob::data,
Laurent Montel's avatar
Laurent Montel committed
107
                             [&imageData](KIO::Job *, const QByteArray &data) {
Laurent Montel's avatar
Laurent Montel committed
108 109
                imageData.append(data);
            });
Laurent Montel's avatar
Laurent Montel committed
110 111 112 113 114 115 116
            if (job->exec()) {
                if (image.loadFromData(imageData)) {
                    mBrowser->document()->addResource(QTextDocument::ImageResource,
                                                      QUrl(QStringLiteral("contact_logo")),
                                                      image);
                }
            }
Guy Maurel's avatar
Guy Maurel committed
117 118 119
        }

        mBrowser->document()->addResource(QTextDocument::ImageResource,
Laurent Montel's avatar
Laurent Montel committed
120
                                          QUrl(QStringLiteral("map_icon")),
Laurent Montel's avatar
Laurent Montel committed
121
                                          defaultMapPixmap);
Guy Maurel's avatar
Guy Maurel committed
122 123

        mBrowser->document()->addResource(QTextDocument::ImageResource,
Laurent Montel's avatar
Laurent Montel committed
124
                                          QUrl(QStringLiteral("sms_icon")),
Laurent Montel's avatar
Laurent Montel committed
125
                                          defaultSmsPixmap);
126

127
        if (mShowQRCode) {
Guy Maurel's avatar
Guy Maurel committed
128 129 130 131 132
            KContacts::VCardConverter converter;
            KContacts::Addressee addr(mCurrentContact);
            addr.setPhoto(KContacts::Picture());
            addr.setLogo(KContacts::Picture());
            const QString data = QString::fromUtf8(converter.createVCard(addr));
Laurent Montel's avatar
Laurent Montel committed
133
            if (mQRCode) {
134 135 136 137 138
                mQRCode->setData(data);
                mBrowser->document()->addResource(QTextDocument::ImageResource,
                                                  QUrl(QStringLiteral("qrcode")),
                                                  mQRCode->toImage(QSizeF(50, 50)));
            }
Guy Maurel's avatar
Guy Maurel committed
139
        }
140

Guy Maurel's avatar
Guy Maurel committed
141
        // merge local and global custom field descriptions
Laurent Montel's avatar
Laurent Montel committed
142
        QVector<QVariantMap> customFieldDescriptions;
Sergio Martins's avatar
Sergio Martins committed
143 144
        const CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
        customFieldDescriptions.reserve(localCustomFieldDescriptions.count() + globalCustomFields.count());
Laurent Montel's avatar
Laurent Montel committed
145
        for (const QVariant &entry : qAsConst(localCustomFieldDescriptions)) {
Guy Maurel's avatar
Guy Maurel committed
146 147
            customFieldDescriptions << entry.toMap();
        }
148

Laurent Montel's avatar
Laurent Montel committed
149
        for (const CustomField &field : qAsConst(globalCustomFields)) {
Guy Maurel's avatar
Guy Maurel committed
150
            QVariantMap description;
Laurent Montel's avatar
Laurent Montel committed
151 152
            description.insert(QStringLiteral("key"), field.key());
            description.insert(QStringLiteral("title"), field.title());
153

Guy Maurel's avatar
Guy Maurel committed
154 155
            customFieldDescriptions << description;
        }
156

Guy Maurel's avatar
Guy Maurel committed
157 158
        KContacts::Addressee contact(mCurrentContact);
        if (!addressBookName.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
159
            contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("AddressBook"), addressBookName);
Guy Maurel's avatar
Guy Maurel committed
160
        }
161

Guy Maurel's avatar
Guy Maurel committed
162 163
        mContactFormatter->setContact(contact);
        mContactFormatter->setCustomFieldDescriptions(customFieldDescriptions);
164

Guy Maurel's avatar
Guy Maurel committed
165
        mBrowser->setHtml(mContactFormatter->toHtml());
166 167
    }

Guy Maurel's avatar
Guy Maurel committed
168
    void slotUrlClicked(const QUrl &url)
169
    {
170
        const QUrlQuery query(url);
Guy Maurel's avatar
Guy Maurel committed
171
        const QString urlScheme(url.scheme());
Laurent Montel's avatar
Laurent Montel committed
172 173
        if (urlScheme == QLatin1String("http")
            || urlScheme == QLatin1String("https")) {
Laurent Montel's avatar
Laurent Montel committed
174
            Q_EMIT mParent->urlClicked(url);
Guy Maurel's avatar
Guy Maurel committed
175
        } else if (urlScheme == QLatin1String("phone")) {
176
            const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
Guy Maurel's avatar
Guy Maurel committed
177 178 179

            const KContacts::PhoneNumber::List numbers = mCurrentContact.phoneNumbers();
            if (pos < numbers.count()) {
Laurent Montel's avatar
Laurent Montel committed
180
                Q_EMIT mParent->phoneNumberClicked(numbers.at(pos));
Guy Maurel's avatar
Guy Maurel committed
181 182
            }
        } else if (urlScheme == QLatin1String("sms")) {
183
            const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
Guy Maurel's avatar
Guy Maurel committed
184 185 186

            const KContacts::PhoneNumber::List numbers = mCurrentContact.phoneNumbers();
            if (pos < numbers.count()) {
Laurent Montel's avatar
Laurent Montel committed
187
                Q_EMIT mParent->smsClicked(numbers.at(pos));
Guy Maurel's avatar
Guy Maurel committed
188 189
            }
        } else if (urlScheme == QLatin1String("address")) {
190
            const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
Guy Maurel's avatar
Guy Maurel committed
191 192 193

            const KContacts::Address::List addresses = mCurrentContact.addresses();
            if (pos < addresses.count()) {
Laurent Montel's avatar
Laurent Montel committed
194
                Q_EMIT mParent->addressClicked(addresses.at(pos));
Guy Maurel's avatar
Guy Maurel committed
195 196 197 198 199 200 201
            }
        } else if (urlScheme == QLatin1String("mailto")) {
            QString name, address;

            // remove the 'mailto:' and split into name and email address
            KContacts::Addressee::parseEmailAddress(url.path(), name, address);

Laurent Montel's avatar
Laurent Montel committed
202
            Q_EMIT mParent->emailClicked(name, address);
203
        }
204 205
    }

Guy Maurel's avatar
Guy Maurel committed
206
    void slotParentCollectionFetched(KJob *job)
207
    {
Laurent Montel's avatar
Laurent Montel committed
208
        mParentCollectionFetchJob = nullptr;
209

Guy Maurel's avatar
Guy Maurel committed
210
        QString addressBookName;
211

Guy Maurel's avatar
Guy Maurel committed
212 213 214
        if (!job->error()) {
            CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob *>(job);
            if (!fetchJob->collections().isEmpty()) {
Sergio Martins's avatar
Sergio Martins committed
215
                const Collection collection = fetchJob->collections().at(0);
Guy Maurel's avatar
Guy Maurel committed
216 217
                addressBookName = collection.displayName();
            }
218 219
        }

Guy Maurel's avatar
Guy Maurel committed
220
        // load the local meta data of the item
221
        ContactMetaDataAkonadi metaData;
Guy Maurel's avatar
Guy Maurel committed
222
        metaData.load(mCurrentItem);
223

Guy Maurel's avatar
Guy Maurel committed
224
        updateView(metaData.customFieldDescriptions(), addressBookName);
225 226
    }

Laurent Montel's avatar
Laurent Montel committed
227
    QMetaObject::Connection mCollectionFetchJobConnection;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
228
    KContacts::Addressee mCurrentContact;
229
    Item mCurrentItem;
Laurent Montel's avatar
Reorder  
Laurent Montel committed
230 231
    ContactViewer *mParent = nullptr;
    TextBrowser *mBrowser = nullptr;
Laurent Montel's avatar
Laurent Montel committed
232 233 234 235
    AbstractContactFormatter *mContactFormatter = nullptr;
    AbstractContactFormatter *mStandardContactFormatter = nullptr;
    CollectionFetchJob *mParentCollectionFetchJob = nullptr;
    Prison::AbstractBarcode *mQRCode = nullptr;
Laurent Montel's avatar
Reorder  
Laurent Montel committed
236
    bool mShowQRCode = true;
237 238
};

Guy Maurel's avatar
Guy Maurel committed
239 240 241
ContactViewer::ContactViewer(QWidget *parent)
    : QWidget(parent)
    , d(new Private(this))
242
{
Guy Maurel's avatar
Guy Maurel committed
243
    QVBoxLayout *layout = new QVBoxLayout(this);
244
    layout->setContentsMargins(0, 0, 0, 0);
245

Guy Maurel's avatar
Guy Maurel committed
246
    d->mBrowser = new TextBrowser;
247

Laurent Montel's avatar
Laurent Montel committed
248
    connect(d->mBrowser, &TextBrowser::anchorClicked,
Laurent Montel's avatar
Laurent Montel committed
249 250 251
            this, [this](const QUrl &url) {
        d->slotUrlClicked(url);
    });
252

Guy Maurel's avatar
Guy Maurel committed
253
    layout->addWidget(d->mBrowser);
254

Guy Maurel's avatar
Guy Maurel committed
255 256 257 258
    // always fetch full payload for contacts
    fetchScope().fetchFullPayload();
    fetchScope().fetchAttribute<ContactMetaDataAttribute>();
    fetchScope().setAncestorRetrieval(ItemFetchScope::Parent);
259 260 261 262
}

ContactViewer::~ContactViewer()
{
Guy Maurel's avatar
Guy Maurel committed
263
    delete d;
264 265 266 267
}

Akonadi::Item ContactViewer::contact() const
{
Guy Maurel's avatar
Guy Maurel committed
268
    return ItemMonitor::item();
269 270
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
271
KContacts::Addressee ContactViewer::rawContact() const
272
{
Guy Maurel's avatar
Guy Maurel committed
273
    return d->mCurrentContact;
274 275
}

Guy Maurel's avatar
Guy Maurel committed
276
void ContactViewer::setContactFormatter(AbstractContactFormatter *formatter)
277
{
Laurent Montel's avatar
Laurent Montel committed
278
    if (formatter == nullptr) {
Guy Maurel's avatar
Guy Maurel committed
279 280 281
        d->mContactFormatter = d->mStandardContactFormatter;
    } else {
        d->mContactFormatter = formatter;
Laurent Montel's avatar
Laurent Montel committed
282
        delete d->mStandardContactFormatter;
Laurent Montel's avatar
Laurent Montel committed
283
        d->mStandardContactFormatter = nullptr;
Guy Maurel's avatar
Guy Maurel committed
284
    }
285 286
}

Guy Maurel's avatar
Guy Maurel committed
287
void ContactViewer::setContact(const Akonadi::Item &contact)
288
{
Guy Maurel's avatar
Guy Maurel committed
289
    ItemMonitor::setItem(contact);
290 291
}

Guy Maurel's avatar
Guy Maurel committed
292
void ContactViewer::setRawContact(const KContacts::Addressee &contact)
293
{
Guy Maurel's avatar
Guy Maurel committed
294
    d->mCurrentContact = contact;
295

Guy Maurel's avatar
Guy Maurel committed
296
    d->updateView();
297 298
}

Guy Maurel's avatar
Guy Maurel committed
299
void ContactViewer::itemChanged(const Item &contactItem)
300
{
Guy Maurel's avatar
Guy Maurel committed
301 302 303 304 305 306 307 308 309
    if (!contactItem.hasPayload<KContacts::Addressee>()) {
        return;
    }

    d->mCurrentItem = contactItem;
    d->mCurrentContact = contactItem.payload<KContacts::Addressee>();

    // stop any running fetch job
    if (d->mParentCollectionFetchJob) {
Laurent Montel's avatar
Laurent Montel committed
310
        disconnect(d->mCollectionFetchJobConnection);
Guy Maurel's avatar
Guy Maurel committed
311
        delete d->mParentCollectionFetchJob;
Laurent Montel's avatar
Laurent Montel committed
312
        d->mParentCollectionFetchJob = nullptr;
Guy Maurel's avatar
Guy Maurel committed
313 314 315
    }

    d->mParentCollectionFetchJob = new CollectionFetchJob(contactItem.parentCollection(), CollectionFetchJob::Base, this);
Laurent Montel's avatar
Laurent Montel committed
316
    d->mCollectionFetchJobConnection = connect(d->mParentCollectionFetchJob, &CollectionFetchJob::result, this, [this](KJob *job) {d->slotParentCollectionFetched(job);});
317 318 319 320
}

void ContactViewer::itemRemoved()
{
Guy Maurel's avatar
Guy Maurel committed
321
    d->mBrowser->clear();
322 323
}

Laurent Montel's avatar
Laurent Montel committed
324 325
void ContactViewer::updateView()
{
326
    itemChanged(d->mCurrentItem);
Laurent Montel's avatar
Laurent Montel committed
327 328
}

329 330 331 332 333 334 335 336 337 338 339 340 341
void ContactViewer::setShowQRCode(bool b)
{
    if (d->mShowQRCode != b) {
        d->mShowQRCode = b;
        updateView();
    }
}

bool ContactViewer::showQRCode() const
{
    return d->mShowQRCode;
}

342
#include "moc_contactviewer.cpp"