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

0001 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0002 // SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
0003 
0004 // SPDX-License-Identifier: LGPL-2.0-or-later
0005 
0006 #include "contactmanager.h"
0007 
0008 #include "colorproxymodel.h"
0009 #include "contactcollectionmodel.h"
0010 #include "contactconfig.h"
0011 #include "globalcontactmodel.h"
0012 #include "merkuro_contact_debug.h"
0013 #include "sortedcollectionproxymodel.h"
0014 #include <Akonadi/AgentManager>
0015 #include <Akonadi/Collection>
0016 #include <Akonadi/CollectionColorAttribute>
0017 #include <Akonadi/CollectionDeleteJob>
0018 #include <Akonadi/CollectionModifyJob>
0019 #include <Akonadi/CollectionPropertiesDialog>
0020 #include <Akonadi/CollectionStatistics>
0021 #include <Akonadi/CollectionUtils>
0022 #include <Akonadi/ContactsFilterProxyModel>
0023 #include <Akonadi/ContactsTreeModel>
0024 #include <Akonadi/ETMViewStateSaver>
0025 #include <Akonadi/EmailAddressSelectionModel>
0026 #include <Akonadi/EntityMimeTypeFilterModel>
0027 #include <Akonadi/EntityRightsFilterModel>
0028 #include <Akonadi/ItemDeleteJob>
0029 #include <Akonadi/ItemFetchJob>
0030 #include <Akonadi/ItemFetchScope>
0031 #include <Akonadi/Monitor>
0032 #include <Akonadi/SelectionProxyModel>
0033 #include <KCheckableProxyModel>
0034 #include <KConfigGroup>
0035 #include <KContacts/Addressee>
0036 #include <KContacts/ContactGroup>
0037 #include <KDescendantsProxyModel>
0038 #include <KLocalizedString>
0039 #include <KSelectionProxyModel>
0040 #include <KSharedConfig>
0041 #include <QBuffer>
0042 #include <QItemSelectionModel>
0043 #include <QPointer>
0044 
0045 ContactManager::ContactManager(QObject *parent)
0046     : QObject(parent)
0047 {
0048     // Sidebar collection model
0049     m_collectionTree = new Akonadi::EntityMimeTypeFilterModel(this);
0050     m_collectionTree->setDynamicSortFilter(true);
0051     m_collectionTree->setSortCaseSensitivity(Qt::CaseInsensitive);
0052     m_collectionTree->setSourceModel(GlobalContactModel::instance()->model());
0053     m_collectionTree->addMimeTypeInclusionFilter(Akonadi::Collection::mimeType());
0054     m_collectionTree->setHeaderGroup(Akonadi::EntityTreeModel::CollectionTreeHeaders);
0055 
0056     m_collectionSelectionModel = new QItemSelectionModel(m_collectionTree);
0057     m_checkableProxyModel = new ContactCollectionModel(this);
0058     m_checkableProxyModel->setSelectionModel(m_collectionSelectionModel);
0059     m_checkableProxyModel->setSourceModel(m_collectionTree);
0060 
0061     auto contactConfig = new ContactConfig(this);
0062     contactConfig->lastUsedAddressBookCollection();
0063 
0064     auto sortedModel = new SortedCollectionProxModel(this);
0065     sortedModel->setObjectName(QLatin1StringView("Sort collection"));
0066     sortedModel->setSourceModel(m_checkableProxyModel);
0067     sortedModel->addMimeTypeFilter(KContacts::Addressee::mimeType());
0068     sortedModel->addMimeTypeFilter(KContacts::ContactGroup::mimeType());
0069     sortedModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0070     sortedModel->sort(0, Qt::AscendingOrder);
0071 
0072     m_colorProxy = new ColorProxyModel(this);
0073     m_colorProxy->setSourceModel(sortedModel);
0074     m_colorProxy->setObjectName(QLatin1StringView("Show contact colors"));
0075     m_colorProxy->setDynamicSortFilter(true);
0076     m_colorProxy->setStandardCollectionId(contactConfig->lastUsedAddressBookCollection());
0077     connect(contactConfig, &ContactConfig::lastUsedAddressBookCollectionChanged, this, [this, contactConfig]() {
0078         m_colorProxy->setStandardCollectionId(contactConfig->lastUsedAddressBookCollection());
0079     });
0080 
0081     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kalendarrc"));
0082     m_collectionSelectionModelStateSaver = new Akonadi::ETMViewStateSaver(this);
0083     KConfigGroup selectionGroup = config->group(QStringLiteral("ContactCollectionSelection"));
0084     m_collectionSelectionModelStateSaver->setView(nullptr);
0085     m_collectionSelectionModelStateSaver->setSelectionModel(m_checkableProxyModel->selectionModel());
0086     m_collectionSelectionModelStateSaver->restoreState(selectionGroup);
0087     connect(m_checkableProxyModel->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &, const QItemSelection &) {
0088         saveState();
0089     });
0090 
0091     // List of contacts for the main contact view
0092     auto selectionProxyModel = new Akonadi::SelectionProxyModel(m_checkableProxyModel->selectionModel(), this);
0093     selectionProxyModel->setSourceModel(GlobalContactModel::instance()->model());
0094     selectionProxyModel->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection);
0095 
0096     auto flatModel = new KDescendantsProxyModel(this);
0097     flatModel->setSourceModel(selectionProxyModel);
0098 
0099     auto entityMimeTypeFilterModel = new Akonadi::EntityMimeTypeFilterModel(this);
0100     entityMimeTypeFilterModel->setSourceModel(flatModel);
0101     entityMimeTypeFilterModel->addMimeTypeExclusionFilter(Akonadi::Collection::mimeType());
0102     entityMimeTypeFilterModel->setHeaderGroup(Akonadi::EntityTreeModel::ItemListHeaders);
0103 
0104     m_filteredContacts = new QSortFilterProxyModel(this);
0105     m_filteredContacts->setSourceModel(entityMimeTypeFilterModel);
0106     m_filteredContacts->setSortLocaleAware(true);
0107     m_filteredContacts->setSortCaseSensitivity(Qt::CaseInsensitive);
0108     m_filteredContacts->setFilterCaseSensitivity(Qt::CaseInsensitive);
0109     m_filteredContacts->sort(0);
0110 }
0111 
0112 ContactManager::~ContactManager()
0113 {
0114     saveState();
0115 }
0116 
0117 void ContactManager::saveState() const
0118 {
0119     Akonadi::ETMViewStateSaver treeStateSaver;
0120     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kalendarrc"));
0121     KConfigGroup group = config->group(QStringLiteral("ContactCollectionSelection"));
0122     treeStateSaver.setView(nullptr);
0123     treeStateSaver.setSelectionModel(m_checkableProxyModel->selectionModel());
0124     treeStateSaver.saveState(group);
0125 }
0126 
0127 QAbstractItemModel *ContactManager::contactCollections() const
0128 {
0129     return m_colorProxy;
0130 }
0131 
0132 QAbstractItemModel *ContactManager::filteredContacts() const
0133 {
0134     return m_filteredContacts;
0135 }
0136 
0137 Akonadi::Item ContactManager::getItem(qint64 itemId)
0138 {
0139     Akonadi::Item item(itemId);
0140 
0141     return item;
0142 }
0143 
0144 QUrl ContactManager::decorationToUrl(QVariant decoration)
0145 {
0146     if (!decoration.canConvert<QImage>()) {
0147         return {};
0148     }
0149 
0150     auto imgDecoration = decoration.value<QImage>();
0151     QByteArray byteArray;
0152     QBuffer buffer(&byteArray);
0153     buffer.open(QIODevice::WriteOnly);
0154     imgDecoration.save(&buffer, "png");
0155     const QString base64 = QString::fromUtf8(byteArray.toBase64());
0156     return QUrl(QLatin1StringView("data:image/png;base64,") + base64);
0157 }
0158 
0159 void ContactManager::deleteItem(const Akonadi::Item &item)
0160 {
0161     new Akonadi::ItemDeleteJob(item);
0162 }
0163 
0164 void ContactManager::updateAllCollections()
0165 {
0166     const auto collections = contactCollections();
0167     for (int i = 0, count = collections->rowCount(); i < count; i++) {
0168         auto collection = collections->data(collections->index(i, 0), Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0169         Akonadi::AgentManager::self()->synchronizeCollection(collection, true);
0170     }
0171 }
0172 
0173 void ContactManager::updateCollection(const Akonadi::Collection &collection)
0174 {
0175     Akonadi::AgentManager::self()->synchronizeCollection(collection, false);
0176 }
0177 
0178 void ContactManager::deleteCollection(const Akonadi::Collection &collection)
0179 {
0180     const bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root();
0181 
0182     if (!isTopLevel) {
0183         // deletes contents
0184         auto job = new Akonadi::CollectionDeleteJob(collection, this);
0185         connect(job, &Akonadi::CollectionDeleteJob::result, this, [](KJob *job) {
0186             if (job->error()) {
0187                 qCWarning(MERKURO_LOG) << "Error occurred deleting collection: " << job->errorString();
0188             }
0189         });
0190         return;
0191     }
0192     // deletes the agent, not the contents
0193     const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource());
0194     if (instance.isValid()) {
0195         Akonadi::AgentManager::self()->removeInstance(instance);
0196     }
0197 }
0198 
0199 void ContactManager::editCollection(const Akonadi::Collection &collection)
0200 {
0201     // TODO: Reimplement this dialog in QML
0202     QPointer<Akonadi::CollectionPropertiesDialog> dlg = new Akonadi::CollectionPropertiesDialog(collection);
0203     dlg->setWindowTitle(i18nc("@title:window", "Properties of Address Book %1", collection.name()));
0204     dlg->show();
0205 }
0206 
0207 QVariantMap ContactManager::getCollectionDetails(const Akonadi::Collection &collection)
0208 {
0209     QVariantMap collectionDetails;
0210 
0211     collectionDetails[QLatin1StringView("id")] = collection.id();
0212     collectionDetails[QLatin1StringView("name")] = collection.name();
0213     collectionDetails[QLatin1StringView("displayName")] = collection.displayName();
0214     collectionDetails[QLatin1StringView("color")] = m_colorProxy->color(collection.id());
0215     collectionDetails[QLatin1StringView("count")] = collection.statistics().count();
0216     collectionDetails[QLatin1StringView("isResource")] = Akonadi::CollectionUtils::isResource(collection);
0217     collectionDetails[QLatin1StringView("resource")] = collection.resource();
0218     collectionDetails[QLatin1StringView("readOnly")] = collection.rights().testFlag(Akonadi::Collection::ReadOnly);
0219     collectionDetails[QLatin1StringView("canChange")] = collection.rights().testFlag(Akonadi::Collection::CanChangeCollection);
0220     collectionDetails[QLatin1StringView("canCreate")] = collection.rights().testFlag(Akonadi::Collection::CanCreateCollection);
0221     collectionDetails[QLatin1StringView("canDelete")] =
0222         collection.rights().testFlag(Akonadi::Collection::CanDeleteCollection) && !Akonadi::CollectionUtils::isResource(collection);
0223 
0224     return collectionDetails;
0225 }
0226 
0227 void ContactManager::setCollectionColor(Akonadi::Collection collection, const QColor &color)
0228 {
0229     auto colorAttr = collection.attribute<Akonadi::CollectionColorAttribute>(Akonadi::Collection::AddIfMissing);
0230     colorAttr->setColor(color);
0231     auto modifyJob = new Akonadi::CollectionModifyJob(collection);
0232     connect(modifyJob, &Akonadi::CollectionModifyJob::result, this, [this, collection, color](KJob *job) {
0233         if (job->error()) {
0234             qCWarning(MERKURO_LOG) << "Error occurred modifying collection color: " << job->errorString();
0235         } else {
0236             m_colorProxy->setColor(collection.id(), color);
0237         }
0238     });
0239 }
0240 
0241 #include "moc_contactmanager.cpp"