File indexing completed on 2024-11-24 04:50:42

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