File indexing completed on 2024-11-24 04:39:27
0001 /* 0002 This file is part of Akonadi Contact. 0003 0004 SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "contactgroupmodel_p.h" 0010 0011 #include <Akonadi/ItemFetchJob> 0012 #include <Akonadi/ItemFetchScope> 0013 #include <KContacts/Addressee> 0014 0015 #include <KIconEngine> 0016 #include <KIconLoader> 0017 #include <KLocalizedString> 0018 #include <QIcon> 0019 0020 using namespace Akonadi; 0021 0022 struct GroupMember { 0023 KContacts::ContactGroup::ContactReference reference; 0024 KContacts::ContactGroup::Data data; 0025 KContacts::Addressee referencedContact; 0026 bool isReference = false; 0027 bool loadingError = false; 0028 }; 0029 0030 class Akonadi::ContactGroupModelPrivate 0031 { 0032 public: 0033 ContactGroupModelPrivate(ContactGroupModel *parent) 0034 : mParent(parent) 0035 { 0036 } 0037 0038 void resolveContactReference(const KContacts::ContactGroup::ContactReference &reference, int row) 0039 { 0040 Item item; 0041 if (!reference.gid().isEmpty()) { 0042 item.setGid(reference.gid()); 0043 } else { 0044 item.setId(reference.uid().toLongLong()); 0045 } 0046 auto job = new ItemFetchJob(item, mParent); 0047 job->setProperty("row", row); 0048 job->fetchScope().fetchFullPayload(); 0049 0050 mParent->connect(job, &ItemFetchJob::result, mParent, [this](KJob *job) { 0051 itemFetched(job); 0052 }); 0053 } 0054 0055 void itemFetched(KJob *job) 0056 { 0057 const int row = job->property("row").toInt(); 0058 0059 if (job->error()) { 0060 mMembers[row].loadingError = true; 0061 Q_EMIT mParent->dataChanged(mParent->index(row, 0, QModelIndex()), mParent->index(row, 1, QModelIndex())); 0062 return; 0063 } 0064 0065 auto fetchJob = qobject_cast<ItemFetchJob *>(job); 0066 0067 if (fetchJob->items().count() != 1) { 0068 mMembers[row].loadingError = true; 0069 Q_EMIT mParent->dataChanged(mParent->index(row, 0, QModelIndex()), mParent->index(row, 1, QModelIndex())); 0070 return; 0071 } 0072 0073 const Item item = fetchJob->items().at(0); 0074 const auto contact = item.payload<KContacts::Addressee>(); 0075 0076 GroupMember &member = mMembers[row]; 0077 member.referencedContact = contact; 0078 Q_EMIT mParent->dataChanged(mParent->index(row, 0, QModelIndex()), mParent->index(row, 1, QModelIndex())); 0079 } 0080 0081 void normalizeMemberList() 0082 { 0083 // check whether a normalization is needed or not 0084 bool needsNormalization = false; 0085 if (mMembers.isEmpty()) { 0086 needsNormalization = true; 0087 } else { 0088 for (int i = 0; i < mMembers.count(); ++i) { 0089 const GroupMember &member = mMembers[i]; 0090 if (!member.isReference && !(i == mMembers.count() - 1)) { 0091 if (member.data.name().isEmpty() && member.data.email().isEmpty()) { 0092 needsNormalization = true; 0093 break; 0094 } 0095 } 0096 } 0097 0098 const GroupMember &member = mMembers.last(); 0099 if (member.isReference || !(member.data.name().isEmpty() && member.data.email().isEmpty())) { 0100 needsNormalization = true; 0101 } 0102 } 0103 0104 // if not, avoid to update the model and view 0105 if (!needsNormalization) { 0106 return; 0107 } 0108 0109 bool foundEmpty = false; 0110 0111 // add an empty line at the end 0112 mParent->beginInsertRows(QModelIndex(), mMembers.count(), mMembers.count()); 0113 GroupMember member; 0114 member.isReference = false; 0115 mMembers.append(member); 0116 mParent->endInsertRows(); 0117 0118 // remove all empty lines first except the last line 0119 do { 0120 foundEmpty = false; 0121 for (int i = 0, total = mMembers.count(); i < total; ++i) { 0122 const GroupMember &member = mMembers[i]; 0123 if (!member.isReference && !(i == mMembers.count() - 1)) { 0124 if (member.data.name().isEmpty() && member.data.email().isEmpty()) { 0125 mParent->beginRemoveRows(QModelIndex(), i, i); 0126 mMembers.remove(i); 0127 mParent->endRemoveRows(); 0128 foundEmpty = true; 0129 break; 0130 } 0131 } 0132 } 0133 } while (foundEmpty); 0134 } 0135 0136 ContactGroupModel *const mParent; 0137 QList<GroupMember> mMembers; 0138 KContacts::ContactGroup mGroup; 0139 QString mLastErrorMessage; 0140 }; 0141 0142 ContactGroupModel::ContactGroupModel(QObject *parent) 0143 : QAbstractItemModel(parent) 0144 , d(new ContactGroupModelPrivate(this)) 0145 { 0146 } 0147 0148 ContactGroupModel::~ContactGroupModel() = default; 0149 0150 void ContactGroupModel::loadContactGroup(const KContacts::ContactGroup &contactGroup) 0151 { 0152 Q_EMIT layoutAboutToBeChanged(); 0153 0154 d->mMembers.clear(); 0155 d->mGroup = contactGroup; 0156 0157 for (int i = 0; i < d->mGroup.dataCount(); ++i) { 0158 const KContacts::ContactGroup::Data data = d->mGroup.data(i); 0159 GroupMember member; 0160 member.isReference = false; 0161 member.data = data; 0162 0163 d->mMembers.append(member); 0164 } 0165 0166 for (int i = 0; i < d->mGroup.contactReferenceCount(); ++i) { 0167 const KContacts::ContactGroup::ContactReference reference = d->mGroup.contactReference(i); 0168 GroupMember member; 0169 member.isReference = true; 0170 member.reference = reference; 0171 0172 d->mMembers.append(member); 0173 0174 d->resolveContactReference(reference, d->mMembers.count() - 1); 0175 } 0176 0177 d->normalizeMemberList(); 0178 0179 Q_EMIT layoutChanged(); 0180 } 0181 0182 bool ContactGroupModel::storeContactGroup(KContacts::ContactGroup &group) const 0183 { 0184 group.removeAllContactReferences(); 0185 group.removeAllContactData(); 0186 0187 for (int i = 0; i < d->mMembers.count(); ++i) { 0188 const GroupMember &member = d->mMembers[i]; 0189 if (member.isReference) { 0190 group.append(member.reference); 0191 } else { 0192 if (i != (d->mMembers.count() - 1)) { 0193 if (member.data.email().isEmpty()) { 0194 d->mLastErrorMessage = i18n("The member with name <b>%1</b> is missing an email address", member.data.name()); 0195 return false; 0196 } 0197 group.append(member.data); 0198 } 0199 } 0200 } 0201 0202 return true; 0203 } 0204 0205 QString ContactGroupModel::lastErrorMessage() const 0206 { 0207 return d->mLastErrorMessage; 0208 } 0209 0210 QModelIndex ContactGroupModel::index(int row, int col, const QModelIndex &index) const 0211 { 0212 Q_UNUSED(index) 0213 return createIndex(row, col); 0214 } 0215 0216 QModelIndex ContactGroupModel::parent(const QModelIndex &index) const 0217 { 0218 Q_UNUSED(index) 0219 return {}; 0220 } 0221 0222 QVariant ContactGroupModel::data(const QModelIndex &index, int role) const 0223 { 0224 if (!index.isValid()) { 0225 return {}; 0226 } 0227 0228 if (index.row() < 0 || index.row() >= d->mMembers.count()) { 0229 return {}; 0230 } 0231 0232 if (index.column() < 0 || index.column() > 1) { 0233 return {}; 0234 } 0235 0236 const GroupMember &member = d->mMembers[index.row()]; 0237 0238 if (role == Qt::DisplayRole) { 0239 if (member.loadingError) { 0240 if (index.column() == 0) { 0241 return i18n("Contact does not exist any more"); 0242 } else { 0243 return QString(); 0244 } 0245 } 0246 0247 if (member.isReference) { 0248 if (index.column() == 0) { 0249 return member.referencedContact.realName(); 0250 } else { 0251 if (!member.reference.preferredEmail().isEmpty()) { 0252 return member.reference.preferredEmail(); 0253 } else { 0254 return member.referencedContact.preferredEmail(); 0255 } 0256 } 0257 } else { 0258 if (index.column() == 0) { 0259 return member.data.name(); 0260 } else { 0261 return member.data.email(); 0262 } 0263 } 0264 } 0265 0266 if (role == Qt::DecorationRole) { 0267 if (index.column() == 1) { 0268 return {}; 0269 } 0270 0271 if (member.loadingError) { 0272 return QIcon::fromTheme(QStringLiteral("emblem-important")); 0273 } 0274 0275 if (index.row() == (d->mMembers.count() - 1)) { 0276 return QIcon::fromTheme(QStringLiteral("contact-new")); 0277 } 0278 0279 if (member.isReference) { 0280 return QIcon(new KIconEngine(QStringLiteral("x-office-contact"), KIconLoader::global(), QStringList() << QStringLiteral("emblem-symbolic-link"))); 0281 } else { 0282 return QIcon::fromTheme(QStringLiteral("x-office-contact")); 0283 } 0284 } 0285 0286 if (role == Qt::EditRole) { 0287 if (member.isReference) { 0288 if (index.column() == 0) { 0289 return member.referencedContact.realName(); 0290 } else { 0291 if (!member.reference.preferredEmail().isEmpty()) { 0292 return member.reference.preferredEmail(); 0293 } else { 0294 return member.referencedContact.preferredEmail(); 0295 } 0296 } 0297 } else { 0298 if (index.column() == 0) { 0299 return member.data.name(); 0300 } else { 0301 return member.data.email(); 0302 } 0303 } 0304 } 0305 0306 if (role == IsReferenceRole) { 0307 return member.isReference; 0308 } 0309 0310 if (role == AllEmailsRole) { 0311 if (member.isReference) { 0312 return member.referencedContact.emails(); 0313 } else { 0314 return QStringList(); 0315 } 0316 } 0317 0318 return {}; 0319 } 0320 0321 bool ContactGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) 0322 { 0323 if (!index.isValid()) { 0324 return false; 0325 } 0326 0327 if (index.row() < 0 || index.row() >= d->mMembers.count()) { 0328 return false; 0329 } 0330 0331 if (index.column() < 0 || index.column() > 1) { 0332 return false; 0333 } 0334 0335 GroupMember &member = d->mMembers[index.row()]; 0336 0337 if (role == Qt::EditRole) { 0338 if (member.isReference) { 0339 if (index.column() == 0) { 0340 member.reference.setUid(QString::number(value.toLongLong())); 0341 d->resolveContactReference(member.reference, index.row()); 0342 } 0343 if (index.column() == 1) { 0344 const QString email = value.toString(); 0345 if (email != member.referencedContact.preferredEmail()) { 0346 member.reference.setPreferredEmail(email); 0347 } else { 0348 member.reference.setPreferredEmail(QString()); 0349 } 0350 } 0351 } else { 0352 if (index.column() == 0) { 0353 member.data.setName(value.toString()); 0354 } else { 0355 member.data.setEmail(value.toString()); 0356 } 0357 } 0358 0359 d->normalizeMemberList(); 0360 0361 return true; 0362 } 0363 0364 if (role == IsReferenceRole) { 0365 if ((value.toBool() == true) && !member.isReference) { 0366 member.isReference = true; 0367 } 0368 if ((value.toBool() == false) && member.isReference) { 0369 member.isReference = false; 0370 member.data.setName(member.referencedContact.realName()); 0371 member.data.setEmail(member.referencedContact.preferredEmail()); 0372 } 0373 0374 return true; 0375 } 0376 0377 return false; 0378 } 0379 0380 QVariant ContactGroupModel::headerData(int section, Qt::Orientation orientation, int role) const 0381 { 0382 if (section < 0 || section > 1) { 0383 return {}; 0384 } 0385 0386 if (orientation != Qt::Horizontal) { 0387 return {}; 0388 } 0389 0390 if (role != Qt::DisplayRole) { 0391 return {}; 0392 } 0393 0394 if (section == 0) { 0395 return i18nc("contact's name", "Name"); 0396 } else { 0397 return i18nc("contact's email address", "EMail"); 0398 } 0399 } 0400 0401 Qt::ItemFlags ContactGroupModel::flags(const QModelIndex &index) const 0402 { 0403 if (!index.isValid() || index.row() < 0 || index.row() >= d->mMembers.count()) { 0404 return Qt::ItemIsEnabled; 0405 } 0406 0407 if (d->mMembers[index.row()].loadingError) { 0408 return {Qt::ItemIsEnabled}; 0409 } 0410 0411 Qt::ItemFlags parentFlags = QAbstractItemModel::flags(index); 0412 return parentFlags | Qt::ItemIsEnabled | Qt::ItemIsEditable; 0413 } 0414 0415 int ContactGroupModel::columnCount(const QModelIndex &parent) const 0416 { 0417 if (!parent.isValid()) { 0418 return 2; 0419 } else { 0420 return 0; 0421 } 0422 } 0423 0424 int ContactGroupModel::rowCount(const QModelIndex &parent) const 0425 { 0426 if (!parent.isValid()) { 0427 return d->mMembers.count(); 0428 } else { 0429 return 0; 0430 } 0431 } 0432 0433 bool ContactGroupModel::removeRows(int row, int count, const QModelIndex &parent) 0434 { 0435 if (parent.isValid()) { 0436 return false; 0437 } 0438 0439 beginRemoveRows(QModelIndex(), row, row + count - 1); 0440 for (int i = 0; i < count; ++i) { 0441 d->mMembers.remove(row); 0442 } 0443 endRemoveRows(); 0444 0445 return true; 0446 } 0447 0448 GroupFilterModel::GroupFilterModel(QObject *parent) 0449 : QSortFilterProxyModel(parent) 0450 { 0451 setFilterCaseSensitivity(Qt::CaseInsensitive); 0452 setFilterKeyColumn(-1); 0453 } 0454 0455 bool GroupFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 0456 { 0457 if (sourceRow == sourceModel()->rowCount() - 1) { 0458 return true; 0459 } 0460 0461 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); 0462 } 0463 0464 bool GroupFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const 0465 { 0466 if (left.row() == sourceModel()->rowCount() - 1) { 0467 return true; 0468 } 0469 0470 if (right.row() == sourceModel()->rowCount() - 1) { 0471 return false; 0472 } 0473 0474 return QSortFilterProxyModel::lessThan(left, right); 0475 } 0476 0477 #include "moc_contactgroupmodel_p.cpp"