File indexing completed on 2024-11-24 04:39:35

0001 /*
0002     This file is part of Akonadi Contact.
0003 
0004     SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "contactviewer.h"
0010 
0011 #include "attributes/contactmetadataattribute_p.h"
0012 #include "contactmetadataakonadi_p.h"
0013 #include "customfieldmanager_p.h"
0014 #include "standardcontactformatter.h"
0015 #include "textbrowser_p.h"
0016 
0017 #include <Akonadi/Collection>
0018 #include <Akonadi/CollectionFetchJob>
0019 #include <Akonadi/EntityDisplayAttribute>
0020 #include <Akonadi/Item>
0021 #include <Akonadi/ItemFetchScope>
0022 #include <KConfigGroup>
0023 #include <KContacts/Addressee>
0024 #include <KLocalizedString>
0025 #include <kio/transferjob.h>
0026 
0027 #include <KConfig>
0028 #include <KContacts/VCardConverter>
0029 #include <Prison/Barcode>
0030 #include <QGuiApplication>
0031 #include <QIcon>
0032 #include <QScreen>
0033 #include <QUrlQuery>
0034 #include <QVBoxLayout>
0035 
0036 using namespace Akonadi;
0037 using namespace Akonadi;
0038 
0039 class Akonadi::ContactViewerPrivate
0040 {
0041 public:
0042     explicit ContactViewerPrivate(ContactViewer *parent)
0043         : mParent(parent)
0044         , mQRCode(Prison::Barcode::create(Prison::QRCode))
0045     {
0046         mStandardContactFormatter = new StandardContactFormatter;
0047         mContactFormatter = mStandardContactFormatter;
0048         KConfig config(QStringLiteral("akonadi_contactrc"));
0049         KConfigGroup group(&config, QStringLiteral("View"));
0050         mShowQRCode = group.readEntry("QRCodes", true);
0051     }
0052 
0053     ~ContactViewerPrivate()
0054     {
0055         delete mStandardContactFormatter;
0056     }
0057 
0058     void updateView(const QVariantList &localCustomFieldDescriptions = QVariantList(), const QString &addressBookName = QString())
0059     {
0060         static QPixmap defaultPixmap = QIcon::fromTheme(QStringLiteral("user-identity")).pixmap(QSize(100, 100));
0061         static QPixmap defaultMapPixmap = QIcon::fromTheme(QStringLiteral("map-symbolic")).pixmap(QSize(16, 16));
0062         static QPixmap defaultSmsPixmap = QIcon::fromTheme(QStringLiteral("message-new")).pixmap(QSize(16, 16));
0063 
0064         mParent->setWindowTitle(i18nc("@title:window", "Contact %1", mCurrentContact.assembledName()));
0065 
0066         if (mCurrentContact.photo().isIntern()) {
0067             mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), mCurrentContact.photo().data());
0068         } else if (!mCurrentContact.photo().url().isEmpty()) {
0069             QByteArray imageData;
0070             QImage image;
0071             KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.photo().url()), KIO::NoReload);
0072             QObject::connect(job, &KIO::TransferJob::data, job, [&imageData](KIO::Job *, const QByteArray &data) {
0073                 imageData.append(data);
0074             });
0075             if (job->exec()) {
0076                 if (image.loadFromData(imageData)) {
0077                     mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), image);
0078                 } else {
0079                     mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
0080                 }
0081             } else {
0082                 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
0083             }
0084         } else {
0085             mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
0086         }
0087 
0088         if (mCurrentContact.logo().isIntern()) {
0089             mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_logo")), mCurrentContact.logo().data());
0090         } else if (!mCurrentContact.logo().url().isEmpty()) {
0091             QByteArray imageData;
0092             QImage image;
0093             KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.logo().url()), KIO::NoReload);
0094             QObject::connect(job, &KIO::TransferJob::data, job, [&imageData](KIO::Job *, const QByteArray &data) {
0095                 imageData.append(data);
0096             });
0097             if (job->exec()) {
0098                 if (image.loadFromData(imageData)) {
0099                     mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_logo")), image);
0100                 }
0101             }
0102         }
0103 
0104         mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("map_icon")), defaultMapPixmap);
0105 
0106         mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("sms_icon")), defaultSmsPixmap);
0107 
0108         if (mShowQRCode) {
0109             KContacts::VCardConverter converter;
0110             KContacts::Addressee addr(mCurrentContact);
0111             addr.setPhoto(KContacts::Picture());
0112             addr.setLogo(KContacts::Picture());
0113             const QString data = QString::fromUtf8(converter.createVCard(addr));
0114             if (mQRCode) {
0115                 mQRCode->setData(data);
0116                 mBrowser->document()->addResource(QTextDocument::ImageResource,
0117                                                   QUrl(QStringLiteral("qrcode")),
0118                                                   mQRCode->toImage(mQRCode->preferredSize(QGuiApplication::primaryScreen()->devicePixelRatio()).toSize()));
0119             }
0120         }
0121 
0122         // merge local and global custom field descriptions
0123         QList<QVariantMap> customFieldDescriptions;
0124         const CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
0125         customFieldDescriptions.reserve(localCustomFieldDescriptions.count() + globalCustomFields.count());
0126         for (const QVariant &entry : std::as_const(localCustomFieldDescriptions)) {
0127             customFieldDescriptions << entry.toMap();
0128         }
0129 
0130         for (const CustomField &field : std::as_const(globalCustomFields)) {
0131             QVariantMap description;
0132             description.insert(QStringLiteral("key"), field.key());
0133             description.insert(QStringLiteral("title"), field.title());
0134 
0135             customFieldDescriptions << description;
0136         }
0137 
0138         KContacts::Addressee contact(mCurrentContact);
0139         if (!addressBookName.isEmpty()) {
0140             contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("AddressBook"), addressBookName);
0141         }
0142 
0143         mContactFormatter->setContact(contact);
0144         mContactFormatter->setCustomFieldDescriptions(customFieldDescriptions);
0145 
0146         mBrowser->setHtml(mContactFormatter->toHtml());
0147     }
0148 
0149     void slotUrlClicked(const QUrl &url)
0150     {
0151         const QUrlQuery query(url);
0152         const QString urlScheme(url.scheme());
0153         if (urlScheme == QLatin1StringView("http") || urlScheme == QLatin1StringView("https") || urlScheme == QLatin1StringView("tel")
0154             || urlScheme == QLatin1StringView("sms") || urlScheme == QLatin1StringView("geo") || urlScheme == QLatin1StringView("mailto")) {
0155             Q_EMIT mParent->urlClicked(url);
0156         } else if (urlScheme == QLatin1StringView("address")) {
0157             const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
0158 
0159             const KContacts::Address::List addresses = mCurrentContact.addresses();
0160             if (pos < addresses.count()) {
0161                 Q_EMIT mParent->addressClicked(addresses.at(pos));
0162             }
0163         }
0164     }
0165 
0166     void slotParentCollectionFetched(KJob *job)
0167     {
0168         mParentCollectionFetchJob = nullptr;
0169 
0170         QString addressBookName;
0171 
0172         if (!job->error()) {
0173             auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
0174             if (!fetchJob->collections().isEmpty()) {
0175                 const Collection collection = fetchJob->collections().at(0);
0176                 addressBookName = collection.displayName();
0177             }
0178         }
0179 
0180         // load the local meta data of the item
0181         Akonadi::ContactMetaDataAkonadi metaData;
0182         metaData.load(mCurrentItem);
0183 
0184         updateView(metaData.customFieldDescriptions(), addressBookName);
0185     }
0186 
0187     QMetaObject::Connection mCollectionFetchJobConnection;
0188     KContacts::Addressee mCurrentContact;
0189     Item mCurrentItem;
0190     ContactViewer *const mParent;
0191     TextBrowser *mBrowser = nullptr;
0192     AbstractContactFormatter *mContactFormatter = nullptr;
0193     AbstractContactFormatter *mStandardContactFormatter = nullptr;
0194     CollectionFetchJob *mParentCollectionFetchJob = nullptr;
0195     std::optional<Prison::Barcode> mQRCode;
0196     bool mShowQRCode = true;
0197 };
0198 
0199 ContactViewer::ContactViewer(QWidget *parent)
0200     : QWidget(parent)
0201     , d(new ContactViewerPrivate(this))
0202 {
0203     auto layout = new QVBoxLayout(this);
0204     layout->setContentsMargins({});
0205 
0206     d->mBrowser = new TextBrowser;
0207 
0208     connect(d->mBrowser, &TextBrowser::anchorClicked, this, [this](const QUrl &url) {
0209         d->slotUrlClicked(url);
0210     });
0211 
0212     layout->addWidget(d->mBrowser);
0213 
0214     // always fetch full payload for contacts
0215     fetchScope().fetchFullPayload();
0216     fetchScope().fetchAttribute<ContactMetaDataAttribute>();
0217     fetchScope().setAncestorRetrieval(ItemFetchScope::Parent);
0218 }
0219 
0220 ContactViewer::~ContactViewer() = default;
0221 
0222 Akonadi::Item ContactViewer::contact() const
0223 {
0224     return ItemMonitor::item();
0225 }
0226 
0227 KContacts::Addressee ContactViewer::rawContact() const
0228 {
0229     return d->mCurrentContact;
0230 }
0231 
0232 void ContactViewer::setContactFormatter(AbstractContactFormatter *formatter)
0233 {
0234     if (formatter == nullptr) {
0235         d->mContactFormatter = d->mStandardContactFormatter;
0236     } else {
0237         d->mContactFormatter = formatter;
0238         delete d->mStandardContactFormatter;
0239         d->mStandardContactFormatter = nullptr;
0240     }
0241 }
0242 
0243 void ContactViewer::setContact(const Akonadi::Item &contact)
0244 {
0245     ItemMonitor::setItem(contact);
0246 }
0247 
0248 void ContactViewer::setRawContact(const KContacts::Addressee &contact)
0249 {
0250     d->mCurrentContact = contact;
0251 
0252     d->updateView();
0253 }
0254 
0255 void ContactViewer::itemChanged(const Item &contactItem)
0256 {
0257     if (!contactItem.hasPayload<KContacts::Addressee>()) {
0258         return;
0259     }
0260 
0261     d->mCurrentItem = contactItem;
0262     d->mCurrentContact = contactItem.payload<KContacts::Addressee>();
0263 
0264     // stop any running fetch job
0265     if (d->mParentCollectionFetchJob) {
0266         disconnect(d->mCollectionFetchJobConnection);
0267         delete d->mParentCollectionFetchJob;
0268         d->mParentCollectionFetchJob = nullptr;
0269     }
0270 
0271     d->mParentCollectionFetchJob = new CollectionFetchJob(contactItem.parentCollection(), CollectionFetchJob::Base, this);
0272     d->mCollectionFetchJobConnection = connect(d->mParentCollectionFetchJob, &CollectionFetchJob::result, this, [this](KJob *job) {
0273         d->slotParentCollectionFetched(job);
0274     });
0275 }
0276 
0277 void ContactViewer::itemRemoved()
0278 {
0279     d->mBrowser->clear();
0280 }
0281 
0282 void ContactViewer::updateView()
0283 {
0284     itemChanged(d->mCurrentItem);
0285 }
0286 
0287 void ContactViewer::setShowQRCode(bool b)
0288 {
0289     if (d->mShowQRCode != b) {
0290         d->mShowQRCode = b;
0291         updateView();
0292     }
0293 }
0294 
0295 bool ContactViewer::showQRCode() const
0296 {
0297     return d->mShowQRCode;
0298 }
0299 
0300 #include "moc_contactviewer.cpp"