Akonadi Contacts

contactgroupmodel.cpp
1 /*
2  This file is part of Akonadi Contact.
3 
4  SPDX-FileCopyrightText: 2009 Tobias Koenig <[email protected]>
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 
20 using namespace Akonadi;
21 
22 struct GroupMember {
25  KContacts::Addressee referencedContact;
26  bool isReference = false;
27  bool loadingError = false;
28 };
29 
30 class Akonadi::ContactGroupModelPrivate
31 {
32 public:
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  QVector<GroupMember> mMembers;
139  QString mLastErrorMessage;
140 };
141 
142 ContactGroupModel::ContactGroupModel(QObject *parent)
143  : QAbstractItemModel(parent)
144  , d(new ContactGroupModelPrivate(this))
145 {
146 }
147 
148 ContactGroupModel::~ContactGroupModel() = default;
149 
150 void 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 
182 bool 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 
205 QString ContactGroupModel::lastErrorMessage() const
206 {
207  return d->mLastErrorMessage;
208 }
209 
210 QModelIndex ContactGroupModel::index(int row, int col, const QModelIndex &index) const
211 {
212  Q_UNUSED(index)
213  return createIndex(row, col);
214 }
215 
216 QModelIndex ContactGroupModel::parent(const QModelIndex &index) const
217 {
218  Q_UNUSED(index)
219  return {};
220 }
221 
222 QVariant 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 
321 bool 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 
380 QVariant 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 
401 Qt::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 
415 int ContactGroupModel::columnCount(const QModelIndex &parent) const
416 {
417  if (!parent.isValid()) {
418  return 2;
419  } else {
420  return 0;
421  }
422 }
423 
424 int ContactGroupModel::rowCount(const QModelIndex &parent) const
425 {
426  if (!parent.isValid()) {
427  return d->mMembers.count();
428  } else {
429  return 0;
430  }
431 }
432 
433 bool 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 
448 GroupFilterModel::GroupFilterModel(QObject *parent)
449  : QSortFilterProxyModel(parent)
450 {
451  setFilterCaseSensitivity(Qt::CaseInsensitive);
452  setFilterKeyColumn(-1);
453 }
454 
455 bool 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 
464 bool 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"
QTextStream & right(QTextStream &stream)
DisplayRole
QString number(int n, int base)
CaseInsensitive
void result(KJob *job)
int column() const const
QTextStream & left(QTextStream &stream)
QIcon fromTheme(const QString &name)
qlonglong toLongLong(bool *ok, int base) const const
void setGid(const QString &gid)
qlonglong toLongLong(bool *ok) const const
typedef ItemFlags
QString i18n(const char *text, const TYPE &arg...)
Orientation
bool isEmpty() const const
static KIconLoader * global()
int toInt(bool *ok) const const
virtual Qt::ItemFlags flags(const QModelIndex &index) const const
bool isValid() const const
virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const const
bool toBool() const const
int row() const const
void append(const ContactGroupReference &reference)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const const
int error() const
T payload() const
void setId(Id identifier)
QString toString() const const
QVariant property(const char *name) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Apr 1 2023 04:09:04 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.