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

KDE's Doxygen guidelines are available online.