Akonadi Contacts

contactviewer.cpp
1/*
2 This file is part of Akonadi Contact.
3
4 SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "contactviewer.h"
10
11#include "attributes/contactmetadataattribute_p.h"
12#include "contactmetadataakonadi_p.h"
13#include "customfieldmanager_p.h"
14#include "standardcontactformatter.h"
15#include "textbrowser_p.h"
16
17#include <Akonadi/Collection>
18#include <Akonadi/CollectionFetchJob>
19#include <Akonadi/EntityDisplayAttribute>
20#include <Akonadi/Item>
21#include <Akonadi/ItemFetchScope>
22#include <KConfigGroup>
23#include <KContacts/Addressee>
24#include <KLocalizedString>
25#include <kio/transferjob.h>
26
27#include <KConfig>
28#include <KContacts/VCardConverter>
29#include <Prison/Barcode>
30#include <QGuiApplication>
31#include <QIcon>
32#include <QScreen>
33#include <QUrlQuery>
34#include <QVBoxLayout>
35
36using namespace Akonadi;
37using namespace Akonadi;
38
39class Akonadi::ContactViewerPrivate
40{
41public:
42 explicit ContactViewerPrivate(ContactViewer *parent)
43 : mParent(parent)
44 , mQRCode(Prison::Barcode::create(Prison::QRCode))
45 {
46 mStandardContactFormatter = new StandardContactFormatter;
47 mContactFormatter = mStandardContactFormatter;
48 KConfig config(QStringLiteral("akonadi_contactrc"));
49 KConfigGroup group(&config, QStringLiteral("View"));
50 mShowQRCode = group.readEntry("QRCodes", true);
51 }
52
53 ~ContactViewerPrivate()
54 {
55 delete mStandardContactFormatter;
56 }
57
58 void updateView(const QVariantList &localCustomFieldDescriptions = QVariantList(), const QString &addressBookName = QString())
59 {
60 static QPixmap defaultPixmap = QIcon::fromTheme(QStringLiteral("user-identity")).pixmap(QSize(100, 100));
61 static QPixmap defaultMapPixmap = QIcon::fromTheme(QStringLiteral("map-symbolic")).pixmap(QSize(16, 16));
62 static QPixmap defaultSmsPixmap = QIcon::fromTheme(QStringLiteral("message-new")).pixmap(QSize(16, 16));
63
64 mParent->setWindowTitle(i18nc("@title:window", "Contact %1", mCurrentContact.assembledName()));
65
66 if (mCurrentContact.photo().isIntern()) {
67 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), mCurrentContact.photo().data());
68 } else if (!mCurrentContact.photo().url().isEmpty()) {
69 QByteArray imageData;
70 QImage image;
71 KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.photo().url()), KIO::NoReload);
72 QObject::connect(job, &KIO::TransferJob::data, job, [&imageData](KIO::Job *, const QByteArray &data) {
73 imageData.append(data);
74 });
75 if (job->exec()) {
76 if (image.loadFromData(imageData)) {
77 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), image);
78 } else {
79 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
80 }
81 } else {
82 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
83 }
84 } else {
85 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
86 }
87
88 if (mCurrentContact.logo().isIntern()) {
89 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_logo")), mCurrentContact.logo().data());
90 } else if (!mCurrentContact.logo().url().isEmpty()) {
91 QByteArray imageData;
92 QImage image;
93 KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.logo().url()), KIO::NoReload);
94 QObject::connect(job, &KIO::TransferJob::data, job, [&imageData](KIO::Job *, const QByteArray &data) {
95 imageData.append(data);
96 });
97 if (job->exec()) {
98 if (image.loadFromData(imageData)) {
99 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_logo")), image);
100 }
101 }
102 }
103
104 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("map_icon")), defaultMapPixmap);
105
106 mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("sms_icon")), defaultSmsPixmap);
107
108 if (mShowQRCode) {
110 KContacts::Addressee addr(mCurrentContact);
111 addr.setPhoto(KContacts::Picture());
112 addr.setLogo(KContacts::Picture());
113 const QString data = QString::fromUtf8(converter.createVCard(addr));
114 if (mQRCode) {
115 mQRCode->setData(data);
116 mBrowser->document()->addResource(QTextDocument::ImageResource,
117 QUrl(QStringLiteral("qrcode")),
118 mQRCode->toImage(mQRCode->preferredSize(QGuiApplication::primaryScreen()->devicePixelRatio()).toSize()));
119 }
120 }
121
122 // merge local and global custom field descriptions
123 QList<QVariantMap> customFieldDescriptions;
124 const CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
125 customFieldDescriptions.reserve(localCustomFieldDescriptions.count() + globalCustomFields.count());
126 for (const QVariant &entry : std::as_const(localCustomFieldDescriptions)) {
127 customFieldDescriptions << entry.toMap();
128 }
129
130 for (const CustomField &field : std::as_const(globalCustomFields)) {
131 QVariantMap description;
132 description.insert(QStringLiteral("key"), field.key());
133 description.insert(QStringLiteral("title"), field.title());
134
135 customFieldDescriptions << description;
136 }
137
138 KContacts::Addressee contact(mCurrentContact);
139 if (!addressBookName.isEmpty()) {
140 contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("AddressBook"), addressBookName);
141 }
142
143 mContactFormatter->setContact(contact);
144 mContactFormatter->setCustomFieldDescriptions(customFieldDescriptions);
145
146 mBrowser->setHtml(mContactFormatter->toHtml());
147 }
148
149 void slotUrlClicked(const QUrl &url)
150 {
151 const QUrlQuery query(url);
152 const QString urlScheme(url.scheme());
153 if (urlScheme == QLatin1StringView("http") || urlScheme == QLatin1StringView("https") || urlScheme == QLatin1StringView("tel")
154 || urlScheme == QLatin1StringView("sms") || urlScheme == QLatin1StringView("geo") || urlScheme == QLatin1StringView("mailto")) {
155 Q_EMIT mParent->urlClicked(url);
156 } else if (urlScheme == QLatin1StringView("address")) {
157 const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
158
159 const KContacts::Address::List addresses = mCurrentContact.addresses();
160 if (pos < addresses.count()) {
161 Q_EMIT mParent->addressClicked(addresses.at(pos));
162 }
163 }
164 }
165
166 void slotParentCollectionFetched(KJob *job)
167 {
168 mParentCollectionFetchJob = nullptr;
169
170 QString addressBookName;
171
172 if (!job->error()) {
173 auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
174 if (!fetchJob->collections().isEmpty()) {
175 const Collection collection = fetchJob->collections().at(0);
176 addressBookName = collection.displayName();
177 }
178 }
179
180 // load the local meta data of the item
181 Akonadi::ContactMetaDataAkonadi metaData;
182 metaData.load(mCurrentItem);
183
184 updateView(metaData.customFieldDescriptions(), addressBookName);
185 }
186
187 QMetaObject::Connection mCollectionFetchJobConnection;
188 KContacts::Addressee mCurrentContact;
189 Item mCurrentItem;
190 ContactViewer *const mParent;
191 TextBrowser *mBrowser = nullptr;
192 AbstractContactFormatter *mContactFormatter = nullptr;
193 AbstractContactFormatter *mStandardContactFormatter = nullptr;
194 CollectionFetchJob *mParentCollectionFetchJob = nullptr;
195 std::optional<Prison::Barcode> mQRCode;
196 bool mShowQRCode = true;
197};
198
200 : QWidget(parent)
201 , d(new ContactViewerPrivate(this))
202{
203 auto layout = new QVBoxLayout(this);
205
206 d->mBrowser = new TextBrowser;
207
208 connect(d->mBrowser, &TextBrowser::anchorClicked, this, [this](const QUrl &url) {
209 d->slotUrlClicked(url);
210 });
211
212 layout->addWidget(d->mBrowser);
213
214 // always fetch full payload for contacts
216 fetchScope().fetchAttribute<ContactMetaDataAttribute>();
218}
219
221
226
228{
229 return d->mCurrentContact;
230}
231
233{
234 if (formatter == nullptr) {
235 d->mContactFormatter = d->mStandardContactFormatter;
236 } else {
237 d->mContactFormatter = formatter;
238 delete d->mStandardContactFormatter;
239 d->mStandardContactFormatter = nullptr;
240 }
241}
242
247
249{
250 d->mCurrentContact = contact;
251
252 d->updateView();
253}
254
255void ContactViewer::itemChanged(const Item &contactItem)
256{
257 if (!contactItem.hasPayload<KContacts::Addressee>()) {
258 return;
259 }
260
261 d->mCurrentItem = contactItem;
262 d->mCurrentContact = contactItem.payload<KContacts::Addressee>();
263
264 // stop any running fetch job
265 if (d->mParentCollectionFetchJob) {
266 disconnect(d->mCollectionFetchJobConnection);
267 delete d->mParentCollectionFetchJob;
268 d->mParentCollectionFetchJob = nullptr;
269 }
270
271 d->mParentCollectionFetchJob = new CollectionFetchJob(contactItem.parentCollection(), CollectionFetchJob::Base, this);
272 d->mCollectionFetchJobConnection = connect(d->mParentCollectionFetchJob, &CollectionFetchJob::result, this, [this](KJob *job) {
273 d->slotParentCollectionFetched(job);
274 });
275}
276
277void ContactViewer::itemRemoved()
278{
279 d->mBrowser->clear();
280}
281
283{
284 itemChanged(d->mCurrentItem);
285}
286
288{
289 if (d->mShowQRCode != b) {
290 d->mShowQRCode = b;
291 updateView();
292 }
293}
294
295bool ContactViewer::showQRCode() const
296{
297 return d->mShowQRCode;
298}
299
300#include "moc_contactviewer.cpp"
The interface for all contact formatters.
void setContact(const KContacts::Addressee &contact)
Sets the contact that will be formatted.
virtual QString toHtml(HtmlForm form=SelfcontainedForm) const =0
This method must be reimplemented to return the contact formatted as HTML according to the requested ...
void setCustomFieldDescriptions(const QList< QVariantMap > &descriptions)
Sets the custom field descriptions that will be used.
QString displayName() const
A viewer component for contacts in Akonadi.
KContacts::Addressee rawContact() const
Returns the raw contact that is currently displayed.
void addressClicked(const KContacts::Address &address)
This signal is emitted whenever the user has clicked on an address in the viewer.
Akonadi::Item contact() const
Returns the contact that is currently displayed.
void urlClicked(const QUrl &url)
This signal is emitted whenever the user has clicked on a url (e.g.
void setContactFormatter(Akonadi::AbstractContactFormatter *formatter)
Sets the contact formatter that should be used for formatting the contact.
ContactViewer(QWidget *parent=nullptr)
Creates a new contact viewer.
void setRawContact(const KContacts::Addressee &contact)
Sets the raw contact object that shall be displayed in the viewer.
~ContactViewer() override
Destroys the contact viewer.
void setContact(const Akonadi::Item &contact)
Sets the contact that shall be displayed in the viewer.
void setAncestorRetrieval(AncestorRetrieval ancestorDepth)
void fetchAttribute(bool fetch=true)
void fetchFullPayload(bool fetch=true)
void setItem(const Item &item)
ItemFetchScope & fetchScope()
Collection & parentCollection()
bool hasPayload() const
T payload() const
A class that formats a contact as HTML code.
Picture photo() const
Picture logo() const
QString assembledName() const
Address::List addresses() const
QImage data() const
bool isIntern() const
QString url() const
QByteArray createVCard(const Addressee &addr, Version version=v3_0) const
void data(KIO::Job *job, const QByteArray &data)
bool exec()
int error() const
void result(KJob *job)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
std::optional< QSqlQuery > query(const QString &queryStatement)
A widget for editing the display name of a contact.
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
QAction * create(StandardAction id, const Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
QByteArray & append(QByteArrayView data)
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
QIcon fromTheme(const QString &name)
bool loadFromData(QByteArrayView data, const char *format)
void addWidget(QWidget *w)
void setContentsMargins(const QMargins &margins)
const_reference at(qsizetype i) const const
qsizetype count() const const
void reserve(qsizetype size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QString scheme() const const
QLayout * layout() const const
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:05:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.