Akonadi Contacts

contactviewer.cpp
1 /*
2  This file is part of Akonadi Contact.
3 
4  SPDX-FileCopyrightText: 2009 Tobias Koenig <[email protected]>
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 <KColorScheme>
18 #include <KConfigGroup>
19 #include <KIOCore/kio/transferjob.h>
20 #include <KLocalizedString>
21 #include <collection.h>
22 #include <collectionfetchjob.h>
23 #include <entitydisplayattribute.h>
24 #include <item.h>
25 #include <itemfetchscope.h>
26 #include <kcontacts/addressee.h>
27 
28 #include <QGuiApplication>
29 #include <QIcon>
30 #include <QScreen>
31 #include <QUrlQuery>
32 #include <QVBoxLayout>
33 #include <kcontacts/vcardconverter.h>
34 #include <prison/Prison>
35 
36 using namespace Akonadi;
37 
38 class Q_DECL_HIDDEN ContactViewer::Private
39 {
40 public:
41  Private(ContactViewer *parent)
42  : mParent(parent)
43  {
44  mStandardContactFormatter = new StandardContactFormatter;
45  mContactFormatter = mStandardContactFormatter;
46  KConfig config(QStringLiteral("akonadi_contactrc"));
47  KConfigGroup group(&config, QStringLiteral("View"));
48  mShowQRCode = group.readEntry("QRCodes", true);
50  }
51 
52  ~Private()
53  {
54  delete mStandardContactFormatter;
55  delete mQRCode;
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(KContacts::Impp::serviceIcon(QStringLiteral("sms"))).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, [&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, [&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) {
109  KContacts::VCardConverter converter;
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  QVector<QVariantMap> customFieldDescriptions;
124  const CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
125  customFieldDescriptions.reserve(localCustomFieldDescriptions.count() + globalCustomFields.count());
126  for (const QVariant &entry : qAsConst(localCustomFieldDescriptions)) {
127  customFieldDescriptions << entry.toMap();
128  }
129 
130  for (const CustomField &field : qAsConst(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 == QLatin1String("http") || urlScheme == QLatin1String("https")) {
154  Q_EMIT mParent->urlClicked(url);
155  } else if (urlScheme == QLatin1String("phone")) {
156  const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
157 
158  const KContacts::PhoneNumber::List numbers = mCurrentContact.phoneNumbers();
159  if (pos < numbers.count()) {
160  Q_EMIT mParent->phoneNumberClicked(numbers.at(pos));
161  }
162  } else if (urlScheme == QLatin1String("sms")) {
163  const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
164 
165  const KContacts::PhoneNumber::List numbers = mCurrentContact.phoneNumbers();
166  if (pos < numbers.count()) {
167  Q_EMIT mParent->smsClicked(numbers.at(pos));
168  }
169  } else if (urlScheme == QLatin1String("address")) {
170  const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
171 
172  const KContacts::Address::List addresses = mCurrentContact.addresses();
173  if (pos < addresses.count()) {
174  Q_EMIT mParent->addressClicked(addresses.at(pos));
175  }
176  } else if (urlScheme == QLatin1String("mailto")) {
178 
179  // remove the 'mailto:' and split into name and email address
180  KContacts::Addressee::parseEmailAddress(url.path(), name, address);
181 
182  Q_EMIT mParent->emailClicked(name, address);
183  }
184  }
185 
186  void slotParentCollectionFetched(KJob *job)
187  {
188  mParentCollectionFetchJob = nullptr;
189 
190  QString addressBookName;
191 
192  if (!job->error()) {
193  auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
194  if (!fetchJob->collections().isEmpty()) {
195  const Collection collection = fetchJob->collections().at(0);
196  addressBookName = collection.displayName();
197  }
198  }
199 
200  // load the local meta data of the item
201  ContactMetaDataAkonadi metaData;
202  metaData.load(mCurrentItem);
203 
204  updateView(metaData.customFieldDescriptions(), addressBookName);
205  }
206 
207  QMetaObject::Connection mCollectionFetchJobConnection;
208  KContacts::Addressee mCurrentContact;
209  Item mCurrentItem;
210  ContactViewer *const mParent;
211  TextBrowser *mBrowser = nullptr;
212  AbstractContactFormatter *mContactFormatter = nullptr;
213  AbstractContactFormatter *mStandardContactFormatter = nullptr;
214  CollectionFetchJob *mParentCollectionFetchJob = nullptr;
215  Prison::AbstractBarcode *mQRCode = nullptr;
216  bool mShowQRCode = true;
217 };
218 
220  : QWidget(parent)
221  , d(new Private(this))
222 {
223  auto layout = new QVBoxLayout(this);
224  layout->setContentsMargins(0, 0, 0, 0);
225 
226  d->mBrowser = new TextBrowser;
227 
228  connect(d->mBrowser, &TextBrowser::anchorClicked, this, [this](const QUrl &url) {
229  d->slotUrlClicked(url);
230  });
231 
232  layout->addWidget(d->mBrowser);
233 
234  // always fetch full payload for contacts
238 }
239 
241 {
242  delete d;
243 }
244 
246 {
247  return ItemMonitor::item();
248 }
249 
251 {
252  return d->mCurrentContact;
253 }
254 
256 {
257  if (formatter == nullptr) {
258  d->mContactFormatter = d->mStandardContactFormatter;
259  } else {
260  d->mContactFormatter = formatter;
261  delete d->mStandardContactFormatter;
262  d->mStandardContactFormatter = nullptr;
263  }
264 }
265 
267 {
268  ItemMonitor::setItem(contact);
269 }
270 
272 {
273  d->mCurrentContact = contact;
274 
275  d->updateView();
276 }
277 
278 void ContactViewer::itemChanged(const Item &contactItem)
279 {
280  if (!contactItem.hasPayload<KContacts::Addressee>()) {
281  return;
282  }
283 
284  d->mCurrentItem = contactItem;
285  d->mCurrentContact = contactItem.payload<KContacts::Addressee>();
286 
287  // stop any running fetch job
288  if (d->mParentCollectionFetchJob) {
289  disconnect(d->mCollectionFetchJobConnection);
290  delete d->mParentCollectionFetchJob;
291  d->mParentCollectionFetchJob = nullptr;
292  }
293 
294  d->mParentCollectionFetchJob = new CollectionFetchJob(contactItem.parentCollection(), CollectionFetchJob::Base, this);
295  d->mCollectionFetchJobConnection = connect(d->mParentCollectionFetchJob, &CollectionFetchJob::result, this, [this](KJob *job) {
296  d->slotParentCollectionFetched(job);
297  });
298 }
299 
300 void ContactViewer::itemRemoved()
301 {
302  d->mBrowser->clear();
303 }
304 
306 {
307  itemChanged(d->mCurrentItem);
308 }
309 
311 {
312  if (d->mShowQRCode != b) {
313  d->mShowQRCode = b;
314  updateView();
315  }
316 }
317 
318 bool ContactViewer::showQRCode() const
319 {
320  return d->mShowQRCode;
321 }
322 
323 #include "moc_contactviewer.cpp"
QLayout * layout() const const
void fetchAttribute(const QByteArray &type, bool fetch=true)
bool loadFromData(const uchar *data, int len, const char *format)
void setContentsMargins(int left, int top, int right, int bottom)
QString name(const QVariant &location)
Akonadi::Item contact() const
Returns the contact that is currently displayed.
QString displayName() const
std::optional< QSqlQuery > query(const QString &queryStatement)
KContacts::Addressee rawContact() const
Returns the raw contact that is currently displayed.
static void parseEmailAddress(const QString &rawEmail, QString &fullName, QString &email)
Collection parentCollection() const
ItemFetchScope & fetchScope()
bool exec()
void insert(int i, T &&value)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void fetchFullPayload(bool fetch=true)
T payload() const
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
KSharedConfigPtr config()
ContactViewer(QWidget *parent=nullptr)
Creates a new contact viewer.
QString fromUtf8(const char *str, int size)
PostalAddress address(const QVariant &location)
QScreen * primaryScreen()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
~ContactViewer() override
Destroys the contact viewer.
void setContact(const Akonadi::Item &contact)
Sets the contact that shall be displayed in the viewer.
void setRawContact(const KContacts::Addressee &contact)
Sets the raw contact object that shall be displayed in the viewer.
QString path(QUrl::ComponentFormattingOptions options) const const
PRISON_EXPORT Prison::AbstractBarcode * createBarcode(BarcodeType type)
void setContactFormatter(AbstractContactFormatter *formatter)
Sets the contact formatter that should be used for formatting the contact.
QString scheme() const const
Item item() const
void addWidget(QWidget *w)
QByteArray & append(char ch)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
void reserve(int size)
A class that formats a contact as HTML code.
void setAncestorRetrieval(AncestorRetrieval ancestorDepth)
const T & at(int i) const const
void data(KIO::Job *job, const QByteArray &data)
A viewer component for contacts in Akonadi.
Definition: contactviewer.h:61
void setItem(const Item &item)
int count(const T &value) const const
QIcon fromTheme(const QString &name)
void result(KJob *job)
bool hasPayload() const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
The interface for all contact formatters.
int error() const
QString serviceIcon() const
QByteArray createVCard(const Addressee &addr, Version version=v3_0) const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Fri Jun 18 2021 23:08:56 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.