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"