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"