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"