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

KDE's Doxygen guidelines are available online.