Akonadi Contacts

contactgroupmodel.cpp
1/*
2 This file is part of Akonadi Contact.
3
4 SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "contactgroupmodel_p.h"
10
11#include <Akonadi/ItemFetchJob>
12#include <Akonadi/ItemFetchScope>
13#include <KContacts/Addressee>
14
15#include <KIconEngine>
16#include <KIconLoader>
17#include <KLocalizedString>
18#include <QIcon>
19
20using namespace Akonadi;
21
22struct GroupMember {
25 KContacts::Addressee referencedContact;
26 bool isReference = false;
27 bool loadingError = false;
28};
29
30class Akonadi::ContactGroupModelPrivate
31{
32public:
33 ContactGroupModelPrivate(ContactGroupModel *parent)
34 : mParent(parent)
35 {
36 }
37
38 void resolveContactReference(const KContacts::ContactGroup::ContactReference &reference, int row)
39 {
40 Item item;
41 if (!reference.gid().isEmpty()) {
42 item.setGid(reference.gid());
43 } else {
44 item.setId(reference.uid().toLongLong());
45 }
46 auto job = new ItemFetchJob(item, mParent);
47 job->setProperty("row", row);
48 job->fetchScope().fetchFullPayload();
49
50 mParent->connect(job, &ItemFetchJob::result, mParent, [this](KJob *job) {
51 itemFetched(job);
52 });
53 }
54
55 void itemFetched(KJob *job)
56 {
57 const int row = job->property("row").toInt();
58
59 if (job->error()) {
60 mMembers[row].loadingError = true;
61 Q_EMIT mParent->dataChanged(mParent->index(row, 0, QModelIndex()), mParent->index(row, 1, QModelIndex()));
62 return;
63 }
64
65 auto fetchJob = qobject_cast<ItemFetchJob *>(job);
66
67 if (fetchJob->items().count() != 1) {
68 mMembers[row].loadingError = true;
69 Q_EMIT mParent->dataChanged(mParent->index(row, 0, QModelIndex()), mParent->index(row, 1, QModelIndex()));
70 return;
71 }
72
73 const Item item = fetchJob->items().at(0);
74 const auto contact = item.payload<KContacts::Addressee>();
75
76 GroupMember &member = mMembers[row];
77 member.referencedContact = contact;
78 Q_EMIT mParent->dataChanged(mParent->index(row, 0, QModelIndex()), mParent->index(row, 1, QModelIndex()));
79 }
80
81 void normalizeMemberList()
82 {
83 // check whether a normalization is needed or not
84 bool needsNormalization = false;
85 if (mMembers.isEmpty()) {
86 needsNormalization = true;
87 } else {
88 for (int i = 0; i < mMembers.count(); ++i) {
89 const GroupMember &member = mMembers[i];
90 if (!member.isReference && !(i == mMembers.count() - 1)) {
91 if (member.data.name().isEmpty() && member.data.email().isEmpty()) {
92 needsNormalization = true;
93 break;
94 }
95 }
96 }
97
98 const GroupMember &member = mMembers.last();
99 if (member.isReference || !(member.data.name().isEmpty() && member.data.email().isEmpty())) {
100 needsNormalization = true;
101 }
102 }
103
104 // if not, avoid to update the model and view
105 if (!needsNormalization) {
106 return;
107 }
108
109 bool foundEmpty = false;
110
111 // add an empty line at the end
112 mParent->beginInsertRows(QModelIndex(), mMembers.count(), mMembers.count());
113 GroupMember member;
114 member.isReference = false;
115 mMembers.append(member);
116 mParent->endInsertRows();
117
118 // remove all empty lines first except the last line
119 do {
120 foundEmpty = false;
121 for (int i = 0, total = mMembers.count(); i < total; ++i) {
122 const GroupMember &member = mMembers[i];
123 if (!member.isReference && !(i == mMembers.count() - 1)) {
124 if (member.data.name().isEmpty() && member.data.email().isEmpty()) {
125 mParent->beginRemoveRows(QModelIndex(), i, i);
126 mMembers.remove(i);
127 mParent->endRemoveRows();
128 foundEmpty = true;
129 break;
130 }
131 }
132 }
133 } while (foundEmpty);
134 }
135
136 ContactGroupModel *const mParent;
137 QList<GroupMember> mMembers;
139 QString mLastErrorMessage;
140};
141
142ContactGroupModel::ContactGroupModel(QObject *parent)
143 : QAbstractItemModel(parent)
144 , d(new ContactGroupModelPrivate(this))
145{
146}
147
148ContactGroupModel::~ContactGroupModel() = default;
149
150void ContactGroupModel::loadContactGroup(const KContacts::ContactGroup &contactGroup)
151{
152 Q_EMIT layoutAboutToBeChanged();
153
154 d->mMembers.clear();
155 d->mGroup = contactGroup;
156
157 for (int i = 0; i < d->mGroup.dataCount(); ++i) {
158 const KContacts::ContactGroup::Data data = d->mGroup.data(i);
159 GroupMember member;
160 member.isReference = false;
161 member.data = data;
162
163 d->mMembers.append(member);
164 }
165
166 for (int i = 0; i < d->mGroup.contactReferenceCount(); ++i) {
167 const KContacts::ContactGroup::ContactReference reference = d->mGroup.contactReference(i);
168 GroupMember member;
169 member.isReference = true;
170 member.reference = reference;
171
172 d->mMembers.append(member);
173
174 d->resolveContactReference(reference, d->mMembers.count() - 1);
175 }
176
177 d->normalizeMemberList();
178
179 Q_EMIT layoutChanged();
180}
181
182bool ContactGroupModel::storeContactGroup(KContacts::ContactGroup &group) const
183{
185 group.removeAllContactData();
186
187 for (int i = 0; i < d->mMembers.count(); ++i) {
188 const GroupMember &member = d->mMembers[i];
189 if (member.isReference) {
190 group.append(member.reference);
191 } else {
192 if (i != (d->mMembers.count() - 1)) {
193 if (member.data.email().isEmpty()) {
194 d->mLastErrorMessage = i18n("The member with name <b>%1</b> is missing an email address", member.data.name());
195 return false;
196 }
197 group.append(member.data);
198 }
199 }
200 }
201
202 return true;
203}
204
205QString ContactGroupModel::lastErrorMessage() const
206{
207 return d->mLastErrorMessage;
208}
209
210QModelIndex ContactGroupModel::index(int row, int col, const QModelIndex &index) const
211{
212 Q_UNUSED(index)
213 return createIndex(row, col);
214}
215
216QModelIndex ContactGroupModel::parent(const QModelIndex &index) const
217{
218 Q_UNUSED(index)
219 return {};
220}
221
222QVariant ContactGroupModel::data(const QModelIndex &index, int role) const
223{
224 if (!index.isValid()) {
225 return {};
226 }
227
228 if (index.row() < 0 || index.row() >= d->mMembers.count()) {
229 return {};
230 }
231
232 if (index.column() < 0 || index.column() > 1) {
233 return {};
234 }
235
236 const GroupMember &member = d->mMembers[index.row()];
237
238 if (role == Qt::DisplayRole) {
239 if (member.loadingError) {
240 if (index.column() == 0) {
241 return i18n("Contact does not exist any more");
242 } else {
243 return QString();
244 }
245 }
246
247 if (member.isReference) {
248 if (index.column() == 0) {
249 return member.referencedContact.realName();
250 } else {
251 if (!member.reference.preferredEmail().isEmpty()) {
252 return member.reference.preferredEmail();
253 } else {
254 return member.referencedContact.preferredEmail();
255 }
256 }
257 } else {
258 if (index.column() == 0) {
259 return member.data.name();
260 } else {
261 return member.data.email();
262 }
263 }
264 }
265
266 if (role == Qt::DecorationRole) {
267 if (index.column() == 1) {
268 return {};
269 }
270
271 if (member.loadingError) {
272 return QIcon::fromTheme(QStringLiteral("emblem-important"));
273 }
274
275 if (index.row() == (d->mMembers.count() - 1)) {
276 return QIcon::fromTheme(QStringLiteral("contact-new"));
277 }
278
279 if (member.isReference) {
280 return QIcon(new KIconEngine(QStringLiteral("x-office-contact"), KIconLoader::global(), QStringList() << QStringLiteral("emblem-symbolic-link")));
281 } else {
282 return QIcon::fromTheme(QStringLiteral("x-office-contact"));
283 }
284 }
285
286 if (role == Qt::EditRole) {
287 if (member.isReference) {
288 if (index.column() == 0) {
289 return member.referencedContact.realName();
290 } else {
291 if (!member.reference.preferredEmail().isEmpty()) {
292 return member.reference.preferredEmail();
293 } else {
294 return member.referencedContact.preferredEmail();
295 }
296 }
297 } else {
298 if (index.column() == 0) {
299 return member.data.name();
300 } else {
301 return member.data.email();
302 }
303 }
304 }
305
306 if (role == IsReferenceRole) {
307 return member.isReference;
308 }
309
310 if (role == AllEmailsRole) {
311 if (member.isReference) {
312 return member.referencedContact.emails();
313 } else {
314 return QStringList();
315 }
316 }
317
318 return {};
319}
320
321bool ContactGroupModel::setData(const QModelIndex &index, const QVariant &value, int role)
322{
323 if (!index.isValid()) {
324 return false;
325 }
326
327 if (index.row() < 0 || index.row() >= d->mMembers.count()) {
328 return false;
329 }
330
331 if (index.column() < 0 || index.column() > 1) {
332 return false;
333 }
334
335 GroupMember &member = d->mMembers[index.row()];
336
337 if (role == Qt::EditRole) {
338 if (member.isReference) {
339 if (index.column() == 0) {
340 member.reference.setUid(QString::number(value.toLongLong()));
341 d->resolveContactReference(member.reference, index.row());
342 }
343 if (index.column() == 1) {
344 const QString email = value.toString();
345 if (email != member.referencedContact.preferredEmail()) {
346 member.reference.setPreferredEmail(email);
347 } else {
348 member.reference.setPreferredEmail(QString());
349 }
350 }
351 } else {
352 if (index.column() == 0) {
353 member.data.setName(value.toString());
354 } else {
355 member.data.setEmail(value.toString());
356 }
357 }
358
359 d->normalizeMemberList();
360
361 return true;
362 }
363
364 if (role == IsReferenceRole) {
365 if ((value.toBool() == true) && !member.isReference) {
366 member.isReference = true;
367 }
368 if ((value.toBool() == false) && member.isReference) {
369 member.isReference = false;
370 member.data.setName(member.referencedContact.realName());
371 member.data.setEmail(member.referencedContact.preferredEmail());
372 }
373
374 return true;
375 }
376
377 return false;
378}
379
380QVariant ContactGroupModel::headerData(int section, Qt::Orientation orientation, int role) const
381{
382 if (section < 0 || section > 1) {
383 return {};
384 }
385
386 if (orientation != Qt::Horizontal) {
387 return {};
388 }
389
390 if (role != Qt::DisplayRole) {
391 return {};
392 }
393
394 if (section == 0) {
395 return i18nc("contact's name", "Name");
396 } else {
397 return i18nc("contact's email address", "EMail");
398 }
399}
400
401Qt::ItemFlags ContactGroupModel::flags(const QModelIndex &index) const
402{
403 if (!index.isValid() || index.row() < 0 || index.row() >= d->mMembers.count()) {
404 return Qt::ItemIsEnabled;
405 }
406
407 if (d->mMembers[index.row()].loadingError) {
408 return {Qt::ItemIsEnabled};
409 }
410
411 Qt::ItemFlags parentFlags = QAbstractItemModel::flags(index);
412 return parentFlags | Qt::ItemIsEnabled | Qt::ItemIsEditable;
413}
414
415int ContactGroupModel::columnCount(const QModelIndex &parent) const
416{
417 if (!parent.isValid()) {
418 return 2;
419 } else {
420 return 0;
421 }
422}
423
424int ContactGroupModel::rowCount(const QModelIndex &parent) const
425{
426 if (!parent.isValid()) {
427 return d->mMembers.count();
428 } else {
429 return 0;
430 }
431}
432
433bool ContactGroupModel::removeRows(int row, int count, const QModelIndex &parent)
434{
435 if (parent.isValid()) {
436 return false;
437 }
438
439 beginRemoveRows(QModelIndex(), row, row + count - 1);
440 for (int i = 0; i < count; ++i) {
441 d->mMembers.remove(row);
442 }
443 endRemoveRows();
444
445 return true;
446}
447
448GroupFilterModel::GroupFilterModel(QObject *parent)
449 : QSortFilterProxyModel(parent)
450{
451 setFilterCaseSensitivity(Qt::CaseInsensitive);
452 setFilterKeyColumn(-1);
453}
454
455bool GroupFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
456{
457 if (sourceRow == sourceModel()->rowCount() - 1) {
458 return true;
459 }
460
461 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
462}
463
464bool GroupFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
465{
466 if (left.row() == sourceModel()->rowCount() - 1) {
467 return true;
468 }
469
470 if (right.row() == sourceModel()->rowCount() - 1) {
471 return false;
472 }
473
474 return QSortFilterProxyModel::lessThan(left, right);
475}
476
477#include "moc_contactgroupmodel_p.cpp"
QStringList emails() const
QString preferredEmail() const
QString realName() const
void setPreferredEmail(const QString &email)
void setName(const QString &name)
void setEmail(const QString &email)
void append(const ContactGroupReference &reference)
static KIconLoader * global()
int error() const
void result(KJob *job)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
A widget for editing the display name of a contact.
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
QIcon fromTheme(const QString &name)
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
T & last()
void remove(qsizetype i, qsizetype n)
int column() const const
bool isValid() const const
int row() const const
QVariant property(const char *name) const const
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const const
virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
qlonglong toLongLong(bool *ok, int base) const const
CaseInsensitive
DisplayRole
typedef ItemFlags
Orientation
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
bool toBool() const const
int toInt(bool *ok) const const
qlonglong toLongLong(bool *ok) const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:20 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.