• Skip to content
  • Skip to link menu
KDE API Documentation - personsmodel.cpp Source File (KPeople)
  • KDE Home
  • Contact Us
 

KPeople

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

KDE's Doxygen guidelines are available online.

KPeople

Skip menu "KPeople"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • File List

Class Picker

Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal