KPeople

personsmodel.cpp
1/*
2 Persons Model
3 SPDX-FileCopyrightText: 2012 Martin Klapetek <martin.klapetek@gmail.com>
4 SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
5 SPDX-FileCopyrightText: 2013 David Edmundson <davidedmundson@kde.org>
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
25namespace KPeople
26{
27class PersonsModelPrivate : public QObject
28{
30public:
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 QList<MetaContact> metacontacts;
45
46 QList<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
71public Q_SLOTS:
72 void onMonitorInitialFetchComplete(bool success = true);
73};
74}
75
76using namespace KPeople;
77
78PersonsModel::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
101PersonsModel::~PersonsModel()
102{
103}
104
105QHash<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
116QVariant 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) {
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
142QVariant 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:
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
186int PersonsModel::columnCount(const QModelIndex &parent) const
187{
188 Q_UNUSED(parent);
189
190 return 1;
191}
192
193int 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
209{
210 Q_D(const PersonsModel);
211
212 return d->isInitialized;
213}
214
215QModelIndex 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
228QModelIndex 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
237void 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
250void 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
289void 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
316void 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
329void 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
349void 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
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
394void 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
420void 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
431void 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
444void 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
453QString 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
469QVariant 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 "moc_personsmodel.cpp"
488#include "personsmodel.moc"
static const QString PhoneNumberProperty
String property representing the preferred phone number of the contact.
static const QString NameProperty
String property representing the display name of the contact.
static const QString PictureProperty
QUrl or QPixmap property representing the contacts' avatar.
void contactRemoved(const QString &contactUri)
DataSources should emit this whenever a contact is removed and they are no longer able to supply up-t...
void contactAdded(const QString &contactUri, const KPeople::AbstractContact::Ptr &contact)
DataSources should emit this whenever a contact is added.
void contactChanged(const QString &contactUri, const KPeople::AbstractContact::Ptr &contact)
DataSources should emit this whenever a known contact changes.
void initialFetchComplete(bool success)
Notifies that the DataSource has completed it's initial fetch.
This class creates a model of all known contacts from all sources Contacts are represented as a tree ...
void modelInitialized(bool success)
Will emit when the model is finally initialized.
Q_SCRIPTABLE QVariant get(int row, int role)
Helper class to ease model access through QML.
QModelIndex indexForPersonUri(const QString &personUri) const
bool isInitialized
specifies whether the model has already been initialized
@ PhotoImageProviderUri
Provide a URL to use with QtQuick's Image.source, similar to the Photo Role.
@ PhoneNumberRole
groups QStringList
QVariant contactCustomProperty(const QModelIndex &index, const QString &key) const
Makes it possible to access custom properties that are not available to the model.
void beginInsertRows(const QModelIndex &parent, int first, int last)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
QModelIndex createIndex(int row, int column, const void *ptr) const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QHash< int, QByteArray > roleNames() const const
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
T value(const Key &key) const const
bool isNull() const const
void append(QList< T > &&value)
void removeAt(qsizetype i)
qsizetype size() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
T take(const Key &key)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
quintptr internalId() const const
bool isValid() const const
QModelIndex parent() const const
int row() const const
QList< Key > uniqueKeys() const const
QList< T > values() const const
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QVariant data(int role) const const
bool isValid() const const
int row() const const
bool isNull() const const
T * data() const const
QueuedConnection
UserRole
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isLocalFile() const const
QString toLocalFile() const const
bool canConvert() const const
QVariant fromValue(T &&value)
QUrl toUrl() const const
T value() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:03 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.