KPeople

personsmodel.cpp
1 /*
2  Persons Model
3  SPDX-FileCopyrightText: 2012 Martin Klapetek <[email protected]>
4  SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <[email protected]>
5  SPDX-FileCopyrightText: 2013 David Edmundson <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.1-or-later
8 */
9 
10 #include "personsmodel.h"
11 
12 #include "backends/abstractcontact.h"
13 #include "backends/basepersonsdatasource.h"
14 #include "imageprovideruri_p.h"
15 #include "metacontact_p.h"
16 #include "personmanager_p.h"
17 #include "personpluginmanager.h"
18 
19 #include "kpeople_debug.h"
20 
21 #include <QPixmap>
22 #include <QStandardPaths>
23 #include <QUrl>
24 
25 namespace KPeople
26 {
27 class PersonsModelPrivate : public QObject
28 {
29  Q_OBJECT
30 public:
31  PersonsModelPrivate(PersonsModel *qq)
32  : q(qq)
33  {
34  }
35  PersonsModel *const q;
36 
37  // NOTE This is the opposite way round to the return value from contactMapping() for easier lookups
38  QHash<QString /*contactUri*/, QString /*PersonUri*/> contactToPersons;
39 
40  // hash of person objects indexed by ID
41  QHash<QString /*Person ID*/, QPersistentModelIndex /*Row*/> personIndex;
42 
43  // a list so we have an order in the model
44  QVector<MetaContact> metacontacts;
45 
46  QVector<AllContactsMonitorPtr> m_sourceMonitors;
47 
48  int initialFetchesDoneCount = 0;
49 
50  bool isInitialized = false;
51  bool hasError = false;
52 
53  // methods that manipulate the model
54  void addPerson(const MetaContact &mc);
55  void removePerson(const QString &id);
56  void personChanged(const QString &personUri);
57  QString personUriForContact(const QString &contactUri) const;
58  QVariant dataForContact(const QString &personUri, const AbstractContact::Ptr &contact, int role) const;
59 
60  // SLOTS
61  void onContactsFetched();
62  // update when a resource signals a contact has changed
63  void onContactAdded(const QString &contactUri, const AbstractContact::Ptr &contact);
64  void onContactChanged(const QString &contactUri, const AbstractContact::Ptr &contact);
65  void onContactRemoved(const QString &contactUri);
66 
67  // update on metadata changes
68  void onAddContactToPerson(const QString &contactUri, const QString &newPersonUri);
69  void onRemoveContactsFromPerson(const QString &contactUri);
70 
71 public Q_SLOTS:
72  void onMonitorInitialFetchComplete(bool success = true);
73 };
74 }
75 
76 using namespace KPeople;
77 
78 PersonsModel::PersonsModel(QObject *parent)
79  : QAbstractItemModel(parent)
80  , d_ptr(new PersonsModelPrivate(this))
81 {
83  const auto listPlugins = PersonPluginManager::dataSourcePlugins();
84  for (BasePersonsDataSource *dataSource : listPlugins) {
85  const AllContactsMonitorPtr monitor = dataSource->allContactsMonitor();
86  if (monitor->isInitialFetchComplete()) {
87  QMetaObject::invokeMethod(d, "onMonitorInitialFetchComplete", Qt::QueuedConnection, Q_ARG(bool, monitor->initialFetchSuccess()));
88  } else {
89  connect(monitor.data(), &AllContactsMonitor::initialFetchComplete, d, &PersonsModelPrivate::onMonitorInitialFetchComplete);
90  }
91  d->m_sourceMonitors << monitor;
92  }
93  d->onContactsFetched();
94 
95  connect(PersonManager::instance(), &PersonManager::contactAddedToPerson, d, &PersonsModelPrivate::onAddContactToPerson);
96  connect(PersonManager::instance(), &PersonManager::contactRemovedFromPerson, d, &PersonsModelPrivate::onRemoveContactsFromPerson);
97 
98  initResources();
99 }
100 
101 PersonsModel::~PersonsModel()
102 {
103 }
104 
105 QHash<int, QByteArray> PersonsModel::roleNames() const
106 {
108  roles.insert(PersonUriRole, "personUri");
109  roles.insert(PersonVCardRole, "personVCard");
110  roles.insert(ContactsVCardRole, "contactsVCard");
111  roles.insert(PhoneNumberRole, "phoneNumber");
112  roles.insert(PhotoImageProviderUri, "photoImageProviderUri");
113  return roles;
114 }
115 
116 QVariant PersonsModel::data(const QModelIndex &index, int role) const
117 {
118  Q_D(const PersonsModel);
119 
120  // optimization - if we don't cover this role, ignore it
121  if (role < Qt::UserRole && role != Qt::DisplayRole && role != Qt::DecorationRole) {
122  return QVariant();
123  }
124 
125  if (index.row() < 0 || index.row() >= rowCount(index.parent())) {
126  return QVariant();
127  }
128 
129  if (index.parent().isValid()) {
130  if (role == ContactsVCardRole) {
131  return QVariant::fromValue<AbstractContact::List>(AbstractContact::List());
132  }
133  const MetaContact &mc = d->metacontacts.at(index.parent().row());
134 
135  return d->dataForContact(mc.id(), mc.contacts().at(index.row()), role);
136  } else {
137  const MetaContact &mc = d->metacontacts.at(index.row());
138  return d->dataForContact(mc.id(), mc.personAddressee(), role);
139  }
140 }
141 
142 QVariant PersonsModelPrivate::dataForContact(const QString &personUri, const AbstractContact::Ptr &person, int role) const
143 {
144  switch (role) {
145  case PersonsModel::FormattedNameRole:
146  return person->customProperty(AbstractContact::NameProperty);
147  case PersonsModel::PhotoRole: {
148  QVariant pic = person->customProperty(AbstractContact::PictureProperty);
149  if (pic.canConvert<QImage>()) {
150  QImage avatar = pic.value<QImage>();
151  if (!avatar.isNull()) {
152  return avatar;
153  }
154  } else if (pic.canConvert<QPixmap>()) {
155  QPixmap avatar = pic.value<QPixmap>();
156  if (!avatar.isNull()) {
157  return avatar;
158  }
159  } else if (pic.canConvert<QUrl>() && pic.toUrl().isLocalFile()) {
160  QPixmap avatar = QPixmap(pic.toUrl().toLocalFile());
161  if (!avatar.isNull()) {
162  return avatar;
163  }
164  }
165 
166  // If none of the above were valid images,
167  // return the generic one
168  return QPixmap(QStringLiteral(":/org.kde.kpeople/pixmaps/dummy_avatar.png"));
169  }
170  case PersonsModel::PersonUriRole:
171  return personUri;
172  case PersonsModel::PersonVCardRole:
173  return QVariant::fromValue<AbstractContact::Ptr>(person);
174  case PersonsModel::ContactsVCardRole:
175  return QVariant::fromValue<AbstractContact::List>(metacontacts[personIndex[personUri].row()].contacts());
176  case PersonsModel::GroupsRole:
177  return person->customProperty(QStringLiteral("all-groups"));
179  return person->customProperty(AbstractContact::PhoneNumberProperty);
181  return ::photoImageProviderUri(personUri);
182  }
183  return QVariant();
184 }
185 
186 int PersonsModel::columnCount(const QModelIndex &parent) const
187 {
188  Q_UNUSED(parent);
189 
190  return 1;
191 }
192 
193 int PersonsModel::rowCount(const QModelIndex &parent) const
194 {
195  Q_D(const PersonsModel);
196 
197  if (!parent.isValid()) {
198  return d->metacontacts.size();
199  }
200 
201  if (parent.isValid() && !parent.parent().isValid()) {
202  return d->metacontacts.at(parent.row()).contacts().count();
203  }
204 
205  return 0;
206 }
207 
208 bool PersonsModel::isInitialized() const
209 {
210  Q_D(const PersonsModel);
211 
212  return d->isInitialized;
213 }
214 
215 QModelIndex PersonsModel::index(int row, int column, const QModelIndex &parent) const
216 {
217  if (row < 0 || column < 0 || row >= rowCount(parent)) {
218  return QModelIndex();
219  }
220  // top level items have internalId -1. Anything >=0 is the row of the top level item
221  if (!parent.isValid()) {
222  return createIndex(row, column, -1);
223  }
224 
225  return createIndex(row, column, parent.row());
226 }
227 
228 QModelIndex PersonsModel::parent(const QModelIndex &childIndex) const
229 {
230  if (childIndex.internalId() == -1 || !childIndex.isValid()) {
231  return QModelIndex();
232  }
233 
234  return index(childIndex.internalId(), 0, QModelIndex());
235 }
236 
237 void PersonsModelPrivate::onMonitorInitialFetchComplete(bool success)
238 {
239  initialFetchesDoneCount++;
240  if (!success) {
241  hasError = true;
242  }
243  Q_ASSERT(initialFetchesDoneCount <= m_sourceMonitors.count());
244  if (initialFetchesDoneCount == m_sourceMonitors.count()) {
245  isInitialized = true;
246  Q_EMIT q->modelInitialized(!hasError);
247  }
248 }
249 
250 void PersonsModelPrivate::onContactsFetched()
251 {
253 
254  // fetch all already loaded contacts from plugins
255  for (const AllContactsMonitorPtr &contactWatcher : std::as_const(m_sourceMonitors)) {
256  addresseeMap.insert(contactWatcher->contacts());
257  }
258 
259  // add metacontacts
260  const QMultiHash<QString, QString> contactMapping = PersonManager::instance()->allPersons();
261 
262  for (const QString &key : contactMapping.uniqueKeys()) {
264  for (const QString &contact : contactMapping.values(key)) {
265  contactToPersons[contact] = key;
266  AbstractContact::Ptr ptr = addresseeMap.take(contact);
267  if (ptr) {
268  contacts[contact] = ptr;
269  }
270  }
271  if (!contacts.isEmpty()) {
272  addPerson(MetaContact(key, contacts));
273  }
274  }
275 
276  // add remaining contacts
278  for (i = addresseeMap.constBegin(); i != addresseeMap.constEnd(); ++i) {
279  addPerson(MetaContact(i.key(), i.value()));
280  }
281 
282  for (const AllContactsMonitorPtr &monitor : std::as_const(m_sourceMonitors)) {
283  connect(monitor.data(), &AllContactsMonitor::contactAdded, this, &PersonsModelPrivate::onContactAdded);
284  connect(monitor.data(), &AllContactsMonitor::contactChanged, this, &PersonsModelPrivate::onContactChanged);
285  connect(monitor.data(), &AllContactsMonitor::contactRemoved, this, &PersonsModelPrivate::onContactRemoved);
286  }
287 }
288 
289 void PersonsModelPrivate::onContactAdded(const QString &contactUri, const AbstractContact::Ptr &contact)
290 {
291  const QString &personUri = personUriForContact(contactUri);
292 
294  if (pidx != personIndex.constEnd()) {
295  int personRow = pidx->row();
296  MetaContact &mc = metacontacts[personRow];
297 
298  // if the MC object already contains this object, we want to update the row, not do an insert
299  if (mc.contactUris().contains(contactUri)) {
300  qCWarning(KPEOPLE_LOG) << "Source emitted contactAdded for a contact we already know about " << contactUri;
301  onContactChanged(contactUri, contact);
302  } else {
303  int newContactPos = mc.contacts().size();
304  q->beginInsertRows(q->index(personRow), newContactPos, newContactPos);
305  mc.insertContact(contactUri, contact);
306  q->endInsertRows();
307  personChanged(personUri);
308  }
309  } else { // new contact -> new person
311  map[contactUri] = contact;
312  addPerson(MetaContact(personUri, map));
313  }
314 }
315 
316 void PersonsModelPrivate::onContactChanged(const QString &contactUri, const AbstractContact::Ptr &contact)
317 {
318  const QString &personUri = personUriForContact(contactUri);
319  int personRow = personIndex[personUri].row();
320  int contactRow = metacontacts[personRow].updateContact(contactUri, contact);
321 
322  const QModelIndex contactIndex = q->index(contactRow, 0, q->index(personRow));
323 
324  Q_EMIT q->dataChanged(contactIndex, contactIndex);
325 
326  personChanged(personUri);
327 }
328 
329 void PersonsModelPrivate::onContactRemoved(const QString &contactUri)
330 {
331  const QString &personUri = personUriForContact(contactUri);
332 
333  int personRow = personIndex[personUri].row();
334 
335  MetaContact &mc = metacontacts[personRow];
336  int contactPosition = mc.contactUris().indexOf(contactUri);
337  q->beginRemoveRows(q->index(personRow, 0), contactPosition, contactPosition);
338  mc.removeContact(contactUri);
339  q->endRemoveRows();
340 
341  // if MC object is now invalid remove the person from the list
342  if (!mc.isValid()) {
343  removePerson(personUri);
344  } else {
345  personChanged(personUri);
346  }
347 }
348 
349 void PersonsModelPrivate::onAddContactToPerson(const QString &contactUri, const QString &newPersonUri)
350 {
351  const QString oldPersonUri = personUriForContact(contactUri);
352 
353  contactToPersons.insert(contactUri, newPersonUri);
354 
355  int oldPersonRow = personIndex[oldPersonUri].row();
356 
357  if (oldPersonRow < 0) {
358  return;
359  }
360 
361  MetaContact &oldMc = metacontacts[oldPersonRow];
362 
363  // get contact already in the model, remove it from the previous contact
364  int contactPosition = oldMc.contactUris().indexOf(contactUri);
365  const AbstractContact::Ptr contact = oldMc.contacts().at(contactPosition);
366 
367  q->beginRemoveRows(q->index(oldPersonRow), contactPosition, contactPosition);
368  oldMc.removeContact(contactUri);
369  q->endRemoveRows();
370 
371  if (!oldMc.isValid()) {
372  removePerson(oldPersonUri);
373  } else {
374  personChanged(oldPersonUri);
375  }
376 
377  // if the new person is already in the model, add the contact to it
378  QHash<QString, QPersistentModelIndex>::const_iterator pidx = personIndex.constFind(newPersonUri);
379  if (pidx != personIndex.constEnd()) {
380  int newPersonRow = pidx->row();
381  MetaContact &newMc = metacontacts[newPersonRow];
382  int newContactPos = newMc.contacts().size();
383  q->beginInsertRows(q->index(newPersonRow), newContactPos, newContactPos);
384  newMc.insertContact(contactUri, contact);
385  q->endInsertRows();
386  personChanged(newPersonUri);
387  } else { // if the person is not in the model, create a new person and insert it
389  contacts[contactUri] = contact;
390  addPerson(MetaContact(newPersonUri, contacts));
391  }
392 }
393 
394 void PersonsModelPrivate::onRemoveContactsFromPerson(const QString &contactUri)
395 {
396  const QString personUri = personUriForContact(contactUri);
397  int personRow = personIndex[personUri].row();
398  MetaContact &mc = metacontacts[personRow];
399 
400  const AbstractContact::Ptr &contact = mc.contact(contactUri);
401  const int index = mc.contactUris().indexOf(contactUri);
402 
403  q->beginRemoveRows(personIndex[personUri], index, index);
404  mc.removeContact(contactUri);
405  q->endRemoveRows();
406  contactToPersons.remove(contactUri);
407 
408  // if we don't want the person object anymore
409  if (!mc.isValid()) {
410  removePerson(personUri);
411  } else {
412  personChanged(personUri);
413  }
414 
415  // now re-insert as a new contact
416  // we know it's not part of a metacontact anymore so reinsert as a contact
417  addPerson(MetaContact(contactUri, contact));
418 }
419 
420 void PersonsModelPrivate::addPerson(const KPeople::MetaContact &mc)
421 {
422  const QString &id = mc.id();
423 
424  int row = metacontacts.size();
425  q->beginInsertRows(QModelIndex(), row, row);
426  metacontacts.append(mc);
427  personIndex[id] = q->index(row);
428  q->endInsertRows();
429 }
430 
431 void PersonsModelPrivate::removePerson(const QString &id)
432 {
433  QPersistentModelIndex index = personIndex.value(id);
434  if (!index.isValid()) { // item not found
435  return;
436  }
437 
438  q->beginRemoveRows(QModelIndex(), index.row(), index.row());
439  personIndex.remove(id);
440  metacontacts.removeAt(index.row());
441  q->endRemoveRows();
442 }
443 
444 void PersonsModelPrivate::personChanged(const QString &personUri)
445 {
446  int row = personIndex[personUri].row();
447  if (row >= 0) {
448  const QModelIndex personIndex = q->index(row);
449  Q_EMIT q->dataChanged(personIndex, personIndex);
450  }
451 }
452 
453 QString PersonsModelPrivate::personUriForContact(const QString &contactUri) const
454 {
455  QHash<QString, QString>::const_iterator it = contactToPersons.constFind(contactUri);
456  if (it != contactToPersons.constEnd()) {
457  return *it;
458  } else {
459  return contactUri;
460  }
461 }
462 
464 {
465  Q_D(const PersonsModel);
466  return d->personIndex.value(personUri);
467 }
468 
469 QVariant PersonsModel::get(int row, int role)
470 {
471  return index(row, 0).data(role);
472 }
473 
475 {
476  Q_D(const PersonsModel);
477  if (index.parent().isValid()) {
478  const MetaContact &mc = d->metacontacts.at(index.parent().row());
479 
480  return mc.contacts().at(index.row())->customProperty(key);
481  } else {
482  const MetaContact &mc = d->metacontacts.at(index.row());
483  return mc.personAddressee()->customProperty(key);
484  }
485 }
486 
487 #include "personsmodel.moc"
Q_OBJECTQ_OBJECT
QMap::const_iterator constBegin() const const
quintptr internalId() const const
T * data() const const
@ PhotoImageProviderUri
Provide a URL to use with QtQuick's Image.source, similar to the Photo Role.
Definition: personsmodel.h:48
This class creates a model of all known contacts from all sources Contacts are represented as a tree ...
Definition: personsmodel.h:33
UserRole
QUrl toUrl() const const
T take(const Key &key)
int size() const const
Q_SLOTSQ_SLOTS
T value() const const
void contactChanged(const QString &contactUri, const KPeople::AbstractContact::Ptr &contact)
DataSources should emit this whenever a known contact changes.
@ PhoneNumberRole
groups QStringList
Definition: personsmodel.h:47
QList< T > values(const Key &key) const const
bool isValid() const const
QHash::iterator insert(const Key &key, const T &value)
static const QString NameProperty
String property representing the display name of the contact.
QMap::iterator insert(const Key &key, const T &value)
static const QString PictureProperty
QUrl or QPixmap property representing the contacts' avatar.
virtual QHash< int, QByteArray > roleNames() const const
QModelIndex createIndex(int row, int column, void *ptr) const const
int size() const const
void initialFetchComplete(bool success)
Notifies that the DataSource has completed it's initial fetch.
bool hasError()
QMap::const_iterator constEnd() const const
bool isNull() const const
void contactRemoved(const QString &contactUri)
DataSources should emit this whenever a contact is removed and they are no longer able to supply up-t...
QueuedConnection
QString toLocalFile() const const
QHash::const_iterator constFind(const Key &key) const const
bool isValid() const const
int row() const const
static const QString PhoneNumberProperty
String property representing the preferred phone number of the contact.
Q_SCRIPTABLE QVariant get(int row, int role)
Helper class to ease model access through QML.
bool isNull() const const
bool canConvert(int targetTypeId) const const
void contactAdded(const QString &contactUri, const KPeople::AbstractContact::Ptr &contact)
DataSources should emit this whenever a contact is added.
QVariant data(int role) const const
bool isLocalFile() const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QList< Key > uniqueKeys() const const
bool isInitialized
specifies whether the model has already been initialized
Definition: personsmodel.h:37
QModelIndex parent() const const
QFuture< void > map(Sequence &sequence, MapFunctor function)
QObject * parent() const const
QModelIndex indexForPersonUri(const QString &personUri) const
int row() const const
Q_D(Todo)
QVariant contactCustomProperty(const QModelIndex &index, const QString &key) const
Makes it possible to access custom properties that are not available to the model.
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sun Aug 14 2022 04:09:08 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.