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 <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 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
30 #include <prison/Prison>
31 #else
32 #include <Prison/Prison>
33 #endif
34 #include <QGuiApplication>
35 #include <QIcon>
36 #include <QScreen>
37 #include <QUrlQuery>
38 #include <QVBoxLayout>
39 
40 using namespace Akonadi;
41 
42 class Akonadi::ContactViewerPrivate
43 {
44 public:
45  explicit ContactViewerPrivate(ContactViewer *parent)
46  : mParent(parent)
47  , mQRCode(Prison::createBarcode(Prison::QRCode))
48  {
49  mStandardContactFormatter = new StandardContactFormatter;
50  mContactFormatter = mStandardContactFormatter;
51  KConfig config(QStringLiteral("akonadi_contactrc"));
52  KConfigGroup group(&config, QStringLiteral("View"));
53  mShowQRCode = group.readEntry("QRCodes", true);
54  }
55 
56  ~ContactViewerPrivate()
57  {
58  delete mStandardContactFormatter;
59  delete mQRCode;
60  }
61 
62  void updateView(const QVariantList &localCustomFieldDescriptions = QVariantList(), const QString &addressBookName = QString())
63  {
64  static QPixmap defaultPixmap = QIcon::fromTheme(QStringLiteral("user-identity")).pixmap(QSize(100, 100));
65  static QPixmap defaultMapPixmap = QIcon::fromTheme(QStringLiteral("map-symbolic")).pixmap(QSize(16, 16));
66  static QPixmap defaultSmsPixmap = QIcon::fromTheme(QStringLiteral("message-new")).pixmap(QSize(16, 16));
67 
68  mParent->setWindowTitle(i18nc("@title:window", "Contact %1", mCurrentContact.assembledName()));
69 
70  if (mCurrentContact.photo().isIntern()) {
71  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), mCurrentContact.photo().data());
72  } else if (!mCurrentContact.photo().url().isEmpty()) {
73  QByteArray imageData;
74  QImage image;
75  KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.photo().url()), KIO::NoReload);
76  QObject::connect(job, &KIO::TransferJob::data, [&imageData](KIO::Job *, const QByteArray &data) {
77  imageData.append(data);
78  });
79  if (job->exec()) {
80  if (image.loadFromData(imageData)) {
81  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), image);
82  } else {
83  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
84  }
85  } else {
86  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
87  }
88  } else {
89  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_photo")), defaultPixmap);
90  }
91 
92  if (mCurrentContact.logo().isIntern()) {
93  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_logo")), mCurrentContact.logo().data());
94  } else if (!mCurrentContact.logo().url().isEmpty()) {
95  QByteArray imageData;
96  QImage image;
97  KIO::TransferJob *job = KIO::get(QUrl(mCurrentContact.logo().url()), KIO::NoReload);
98  QObject::connect(job, &KIO::TransferJob::data, [&imageData](KIO::Job *, const QByteArray &data) {
99  imageData.append(data);
100  });
101  if (job->exec()) {
102  if (image.loadFromData(imageData)) {
103  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("contact_logo")), image);
104  }
105  }
106  }
107 
108  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("map_icon")), defaultMapPixmap);
109 
110  mBrowser->document()->addResource(QTextDocument::ImageResource, QUrl(QStringLiteral("sms_icon")), defaultSmsPixmap);
111 
112  if (mShowQRCode) {
113  KContacts::VCardConverter converter;
114  KContacts::Addressee addr(mCurrentContact);
115  addr.setPhoto(KContacts::Picture());
116  addr.setLogo(KContacts::Picture());
117  const QString data = QString::fromUtf8(converter.createVCard(addr));
118  if (mQRCode) {
119  mQRCode->setData(data);
120  mBrowser->document()->addResource(QTextDocument::ImageResource,
121  QUrl(QStringLiteral("qrcode")),
122  mQRCode->toImage(mQRCode->preferredSize(QGuiApplication::primaryScreen()->devicePixelRatio()).toSize()));
123  }
124  }
125 
126  // merge local and global custom field descriptions
127  QVector<QVariantMap> customFieldDescriptions;
128  const CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
129  customFieldDescriptions.reserve(localCustomFieldDescriptions.count() + globalCustomFields.count());
130  for (const QVariant &entry : std::as_const(localCustomFieldDescriptions)) {
131  customFieldDescriptions << entry.toMap();
132  }
133 
134  for (const CustomField &field : std::as_const(globalCustomFields)) {
135  QVariantMap description;
136  description.insert(QStringLiteral("key"), field.key());
137  description.insert(QStringLiteral("title"), field.title());
138 
139  customFieldDescriptions << description;
140  }
141 
142  KContacts::Addressee contact(mCurrentContact);
143  if (!addressBookName.isEmpty()) {
144  contact.insertCustom(QStringLiteral("KADDRESSBOOK"), QStringLiteral("AddressBook"), addressBookName);
145  }
146 
147  mContactFormatter->setContact(contact);
148  mContactFormatter->setCustomFieldDescriptions(customFieldDescriptions);
149 
150  mBrowser->setHtml(mContactFormatter->toHtml());
151  }
152 
153  void slotUrlClicked(const QUrl &url)
154  {
155  const QUrlQuery query(url);
156  const QString urlScheme(url.scheme());
157  if (urlScheme == QLatin1String("http") || urlScheme == QLatin1String("https") || urlScheme == QLatin1String("tel")
158  || (urlScheme == QLatin1String("sms") && !query.hasQueryItem(QLatin1String("index"))) || urlScheme == QLatin1String("geo")
159  || urlScheme == QLatin1String("mailto")) {
160  Q_EMIT mParent->urlClicked(url);
161  } else if (urlScheme == QLatin1String("phone")) {
162  const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
163 
164  const KContacts::PhoneNumber::List numbers = mCurrentContact.phoneNumbers();
165  if (pos < numbers.count()) {
166  Q_EMIT mParent->phoneNumberClicked(numbers.at(pos));
167  }
168  } else if (urlScheme == QLatin1String("sms")) {
169  const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
170 
171  const KContacts::PhoneNumber::List numbers = mCurrentContact.phoneNumbers();
172  if (pos < numbers.count()) {
173  Q_EMIT mParent->smsClicked(numbers.at(pos));
174  }
175  } else if (urlScheme == QLatin1String("address")) {
176  const int pos = query.queryItemValue(QStringLiteral("index")).toInt();
177 
178  const KContacts::Address::List addresses = mCurrentContact.addresses();
179  if (pos < addresses.count()) {
180  Q_EMIT mParent->addressClicked(addresses.at(pos));
181  }
182  } else if (urlScheme == QLatin1String("mailto")) {
183  QString name;
185 
186  // remove the 'mailto:' and split into name and email address
187  KContacts::Addressee::parseEmailAddress(url.path(), name, address);
188 
189  Q_EMIT mParent->emailClicked(name, address);
190  }
191  }
192 
193  void slotParentCollectionFetched(KJob *job)
194  {
195  mParentCollectionFetchJob = nullptr;
196 
197  QString addressBookName;
198 
199  if (!job->error()) {
200  auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
201  if (!fetchJob->collections().isEmpty()) {
202  const Collection collection = fetchJob->collections().at(0);
203  addressBookName = collection.displayName();
204  }
205  }
206 
207  // load the local meta data of the item
208  ContactMetaDataAkonadi metaData;
209  metaData.load(mCurrentItem);
210 
211  updateView(metaData.customFieldDescriptions(), addressBookName);
212  }
213 
214  QMetaObject::Connection mCollectionFetchJobConnection;
215  KContacts::Addressee mCurrentContact;
216  Item mCurrentItem;
217  ContactViewer *const mParent;
218  TextBrowser *mBrowser = nullptr;
219  AbstractContactFormatter *mContactFormatter = nullptr;
220  AbstractContactFormatter *mStandardContactFormatter = nullptr;
221  CollectionFetchJob *mParentCollectionFetchJob = nullptr;
222  Prison::AbstractBarcode *const mQRCode;
223  bool mShowQRCode = true;
224 };
225 
227  : QWidget(parent)
228  , d(new ContactViewerPrivate(this))
229 {
230  auto layout = new QVBoxLayout(this);
232 
233  d->mBrowser = new TextBrowser;
234 
235  connect(d->mBrowser, &TextBrowser::anchorClicked, this, [this](const QUrl &url) {
236  d->slotUrlClicked(url);
237  });
238 
239  layout->addWidget(d->mBrowser);
240 
241  // always fetch full payload for contacts
243  fetchScope().fetchAttribute<ContactMetaDataAttribute>();
245 }
246 
248 
250 {
251  return ItemMonitor::item();
252 }
253 
255 {
256  return d->mCurrentContact;
257 }
258 
260 {
261  if (formatter == nullptr) {
262  d->mContactFormatter = d->mStandardContactFormatter;
263  } else {
264  d->mContactFormatter = formatter;
265  delete d->mStandardContactFormatter;
266  d->mStandardContactFormatter = nullptr;
267  }
268 }
269 
271 {
273 }
274 
276 {
277  d->mCurrentContact = contact;
278 
279  d->updateView();
280 }
281 
282 void ContactViewer::itemChanged(const Item &contactItem)
283 {
284  if (!contactItem.hasPayload<KContacts::Addressee>()) {
285  return;
286  }
287 
288  d->mCurrentItem = contactItem;
289  d->mCurrentContact = contactItem.payload<KContacts::Addressee>();
290 
291  // stop any running fetch job
292  if (d->mParentCollectionFetchJob) {
293  disconnect(d->mCollectionFetchJobConnection);
294  delete d->mParentCollectionFetchJob;
295  d->mParentCollectionFetchJob = nullptr;
296  }
297 
298  d->mParentCollectionFetchJob = new CollectionFetchJob(contactItem.parentCollection(), CollectionFetchJob::Base, this);
299  d->mCollectionFetchJobConnection = connect(d->mParentCollectionFetchJob, &CollectionFetchJob::result, this, [this](KJob *job) {
300  d->slotParentCollectionFetched(job);
301  });
302 }
303 
304 void ContactViewer::itemRemoved()
305 {
306  d->mBrowser->clear();
307 }
308 
310 {
311  itemChanged(d->mCurrentItem);
312 }
313 
315 {
316  if (d->mShowQRCode != b) {
317  d->mShowQRCode = b;
318  updateView();
319  }
320 }
321 
322 bool ContactViewer::showQRCode() const
323 {
324  return d->mShowQRCode;
325 }
326 
327 #include "moc_contactviewer.cpp"
std::optional< QSqlQuery > query(const QString &queryStatement)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QString fromUtf8(const char *str, int size)
void result(KJob *job)
QByteArray & append(char ch)
QString scheme() const const
void insert(int i, T &&value)
QLayout * layout() const const
QString displayName() const
void fetchAttribute(bool fetch=true)
QIcon fromTheme(const QString &name)
void setContactFormatter(AbstractContactFormatter *formatter)
Sets the contact formatter that should be used for formatting the contact.
void setContact(const Akonadi::Item &contact)
Sets the contact that shall be displayed in the viewer.
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
void fetchFullPayload(bool fetch=true)
bool loadFromData(const uchar *data, int len, const char *format)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool hasPayload() const
static void parseEmailAddress(const QString &rawEmail, QString &fullName, QString &email)
PRISON_EXPORT Prison::AbstractBarcode * createBarcode(BarcodeType type)
QByteArray createVCard(const Addressee &addr, Version version=v3_0) const
const T & at(int i) const const
A class that formats a contact as HTML code.
void setRawContact(const KContacts::Addressee &contact)
Sets the raw contact object that shall be displayed in the viewer.
void setItem(const Item &item)
PostalAddress address(const QVariant &location)
A viewer component for contacts in Akonadi.
Definition: contactviewer.h:64
KSharedConfigPtr config()
void reserve(int size)
~ContactViewer() override
Destroys the contact viewer.
KContacts::Addressee rawContact() const
Returns the raw contact that is currently displayed.
Collection & parentCollection()
QString path(QUrl::ComponentFormattingOptions options) const const
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
ContactViewer(QWidget *parent=nullptr)
Creates a new contact viewer.
void addWidget(QWidget *w)
const char * name(StandardAction id)
void setAncestorRetrieval(AncestorRetrieval ancestorDepth)
void setContentsMargins(int left, int top, int right, int bottom)
ItemFetchScope & fetchScope()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
int count(const T &value) const const
Akonadi::Item contact() const
Returns the contact that is currently displayed.
int error() const
T payload() const
void data(KIO::Job *job, const QByteArray &data)
The interface for all contact formatters.
bool exec()
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Apr 1 2023 04:09:04 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.