File indexing completed on 2024-11-24 04:39:30

0001 /*
0002     This file is part of Contact Editor.
0003 
0004     SPDX-FileCopyrightText: 2016 eyeOS S.L.U., a Telefonica company, sales@eyeos.com
0005     SPDX-FileCopyrightText: 2016-2020 Laurent Montel <montel.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "customfieldslistwidget.h"
0011 #include "../utils/utils.h"
0012 
0013 #include "customfieldmanager_p.h"
0014 #include "customfieldslistdelegate.h"
0015 #include <KContacts/Addressee>
0016 #include <QSortFilterProxyModel>
0017 #include <QTreeView>
0018 #include <QVBoxLayout>
0019 
0020 using namespace Akonadi;
0021 
0022 CustomFieldsListWidget::CustomFieldsListWidget(QWidget *parent)
0023     : QWidget(parent)
0024     , mCustomFieldList(new QTreeView(this))
0025     , mModel(new CustomFieldsModel(this))
0026 {
0027     auto topLayout = new QVBoxLayout(this);
0028     mCustomFieldList->setObjectName(QLatin1StringView("customfieldlist"));
0029     mCustomFieldList->setSortingEnabled(true);
0030     mCustomFieldList->setRootIsDecorated(false);
0031     auto customFieldDelegate = new Akonadi::CustomFieldsListDelegate(mCustomFieldList, this);
0032     mCustomFieldList->setItemDelegate(customFieldDelegate);
0033     topLayout->addWidget(mCustomFieldList);
0034 
0035     auto proxyModel = new QSortFilterProxyModel(this);
0036     proxyModel->setDynamicSortFilter(true);
0037     proxyModel->setSourceModel(mModel);
0038     mCustomFieldList->setModel(proxyModel);
0039     mCustomFieldList->setColumnHidden(2, true); // hide the 'key' column
0040 }
0041 
0042 CustomFieldsListWidget::~CustomFieldsListWidget() = default;
0043 
0044 void CustomFieldsListWidget::loadContact(const KContacts::Addressee &contact)
0045 {
0046     CustomField::List externalCustomFields;
0047 
0048     CustomField::List globalCustomFields = CustomFieldManager::globalCustomFieldDescriptions();
0049 
0050     const QStringList customs = contact.customs();
0051     for (const QString &custom : customs) {
0052         QString app;
0053         QString name;
0054         QString value;
0055         Akonadi::Utils::splitCustomField(custom, app, name, value);
0056 
0057         // skip all well-known fields that have separated editor widgets
0058         if (custom.startsWith(QLatin1StringView("messaging/"))) { // IM addresses
0059             continue;
0060         }
0061 
0062         if (app == QLatin1StringView("KADDRESSBOOK")) {
0063             static QSet<QString> blacklist;
0064             if (blacklist.isEmpty()) {
0065                 blacklist << QStringLiteral("BlogFeed") << QStringLiteral("X-IMAddress") << QStringLiteral("X-Profession") << QStringLiteral("X-Office")
0066                           << QStringLiteral("X-ManagersName") << QStringLiteral("X-AssistantsName") << QStringLiteral("X-Anniversary")
0067                           << QStringLiteral("X-SpousesName") << QStringLiteral("MailPreferedFormatting") << QStringLiteral("MailAllowToRemoteContent")
0068                           << QStringLiteral("CRYPTOPROTOPREF") << QStringLiteral("OPENPGPFP") << QStringLiteral("SMIMEFP") << QStringLiteral("CRYPTOSIGNPREF")
0069                           << QStringLiteral("CRYPTOENCRYPTPREF");
0070             }
0071             QSet<QString> upperCaseBlacklist;
0072             for (const QString &blacklistEntry : std::as_const(blacklist)) {
0073                 upperCaseBlacklist << blacklistEntry.toUpper();
0074             }
0075             blacklist.unite(upperCaseBlacklist);
0076             if (blacklist.contains(name)) { // several KAddressBook specific fields
0077                 continue;
0078             }
0079         }
0080 
0081         // check whether it correspond to a local custom field
0082         bool isLocalCustomField = false;
0083         const int localCustomFieldsCount(mLocalCustomFields.count());
0084         for (int i = 0; i < localCustomFieldsCount; ++i) {
0085             if (mLocalCustomFields[i].key() == name) {
0086                 mLocalCustomFields[i].setValue(value);
0087                 isLocalCustomField = true;
0088                 break;
0089             }
0090         }
0091 
0092         // check whether it correspond to a global custom field
0093         bool isGlobalCustomField = false;
0094         const int globalCustomFieldsCount(globalCustomFields.count());
0095         for (int i = 0; i < globalCustomFieldsCount; ++i) {
0096             if (globalCustomFields[i].key() == name) {
0097                 globalCustomFields[i].setValue(value);
0098                 isGlobalCustomField = true;
0099                 break;
0100             }
0101         }
0102 
0103         // if not local and not global it must be external
0104         if (!isLocalCustomField && !isGlobalCustomField) {
0105             if (app == QLatin1StringView("KADDRESSBOOK")) {
0106                 // however if it starts with our prefix it might be that this is an outdated
0107                 // global custom field, in this case treat it as local field of type text
0108                 CustomField customField(name, name, CustomField::TextType, CustomField::LocalScope);
0109                 customField.setValue(value);
0110 
0111                 mLocalCustomFields << customField;
0112             } else {
0113                 // it is really an external custom field
0114                 const QString key = app + QLatin1Char('-') + name;
0115                 CustomField customField(key, key, CustomField::TextType, CustomField::ExternalScope);
0116                 customField.setValue(value);
0117 
0118                 externalCustomFields << customField;
0119             }
0120         }
0121     }
0122 
0123     mModel->setCustomFields(CustomField::List() << mLocalCustomFields << globalCustomFields << externalCustomFields);
0124 }
0125 
0126 void CustomFieldsListWidget::storeContact(KContacts::Addressee &contact) const
0127 {
0128     const CustomField::List customFields = mModel->customFields();
0129     for (const CustomField &customField : customFields) {
0130         // write back values for local and global scope, leave external untouched
0131         if (customField.scope() != CustomField::ExternalScope) {
0132             if (!customField.value().isEmpty()) {
0133                 contact.insertCustom(QStringLiteral("KADDRESSBOOK"), customField.key(), customField.value());
0134             } else {
0135                 contact.removeCustom(QStringLiteral("KADDRESSBOOK"), customField.key());
0136             }
0137         }
0138     }
0139 
0140     // Now remove all fields that were available in loadContact (these are stored in mLocalCustomFields)
0141     // but are not part of customFields now, which means they have been removed or renamed by the user
0142     // in the editor dialog.
0143     for (const CustomField &oldCustomField : std::as_const(mLocalCustomFields)) {
0144         if (oldCustomField.scope() != CustomField::ExternalScope) {
0145             bool fieldStillExists = false;
0146             for (const CustomField &newCustomField : std::as_const(customFields)) {
0147                 if (newCustomField.scope() != CustomField::ExternalScope) {
0148                     if (newCustomField.key() == oldCustomField.key()) {
0149                         fieldStillExists = true;
0150                         break;
0151                     }
0152                 }
0153             }
0154 
0155             if (!fieldStillExists) {
0156                 contact.removeCustom(QStringLiteral("KADDRESSBOOK"), oldCustomField.key());
0157             }
0158         }
0159     }
0160 
0161     // And store the global custom fields descriptions as well
0162     CustomField::List globalCustomFields;
0163     for (const CustomField &customField : std::as_const(customFields)) {
0164         if (customField.scope() == CustomField::GlobalScope) {
0165             globalCustomFields << customField;
0166         }
0167     }
0168 
0169     CustomFieldManager::setGlobalCustomFieldDescriptions(globalCustomFields);
0170 }
0171 
0172 void CustomFieldsListWidget::setReadOnly(bool readOnly)
0173 {
0174     mCustomFieldList->setEnabled(!readOnly);
0175 }
0176 
0177 void CustomFieldsListWidget::slotAddNewField(const CustomField &field)
0178 {
0179     const int lastRow = mModel->rowCount();
0180     mModel->insertRow(lastRow);
0181     mModel->setData(mModel->index(lastRow, 2), field.key(), Qt::EditRole);
0182     mModel->setData(mModel->index(lastRow, 0), field.title(), Qt::EditRole);
0183     mModel->setData(mModel->index(lastRow, 0), field.type(), CustomFieldsModel::TypeRole);
0184     mModel->setData(mModel->index(lastRow, 0), field.scope(), CustomFieldsModel::ScopeRole);
0185 }
0186 
0187 void CustomFieldsListWidget::setLocalCustomFieldDescriptions(const QVariantList &descriptions)
0188 {
0189     mLocalCustomFields.clear();
0190     mLocalCustomFields.reserve(descriptions.count());
0191     for (const QVariant &description : descriptions) {
0192         mLocalCustomFields.append(CustomField::fromVariantMap(description.toMap(), CustomField::LocalScope));
0193     }
0194 }
0195 
0196 QVariantList CustomFieldsListWidget::localCustomFieldDescriptions() const
0197 {
0198     const CustomField::List customFields = mModel->customFields();
0199 
0200     QVariantList descriptions;
0201     for (const CustomField &field : customFields) {
0202         if (field.scope() == CustomField::LocalScope) {
0203             descriptions.append(field.toVariantMap());
0204         }
0205     }
0206 
0207     return descriptions;
0208 }
0209 
0210 #include "moc_customfieldslistwidget.cpp"