File indexing completed on 2024-06-23 05:18:36

0001 /*
0002     SPDX-FileCopyrightText: 2010 Volker Krause <vkrause@kde.org>
0003     This file was part of KMail.
0004     SPDX-FileCopyrightText: 2005 Cornelius Schumacher <schumacher@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "distributionlistdialog.h"
0010 
0011 #include <Akonadi/CollectionDialog>
0012 #include <Akonadi/ContactGroupSearchJob>
0013 #include <Akonadi/ContactSearchJob>
0014 #include <Akonadi/ItemCreateJob>
0015 #include <KEmailAddress>
0016 
0017 #include "messagecomposer_debug.h"
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <QInputDialog>
0021 #include <QLineEdit>
0022 
0023 #include <KConfigGroup>
0024 #include <KSharedConfig>
0025 #include <QDialogButtonBox>
0026 #include <QHBoxLayout>
0027 #include <QHeaderView>
0028 #include <QLabel>
0029 #include <QPushButton>
0030 #include <QTreeWidget>
0031 #include <QTreeWidgetItem>
0032 #include <QVBoxLayout>
0033 
0034 using namespace MessageComposer;
0035 
0036 namespace MessageComposer
0037 {
0038 class DistributionListItem : public QTreeWidgetItem
0039 {
0040 public:
0041     explicit DistributionListItem(QTreeWidget *tree)
0042         : QTreeWidgetItem(tree)
0043     {
0044         setFlags(flags() | Qt::ItemIsUserCheckable);
0045     }
0046 
0047     void setAddressee(const KContacts::Addressee &a, const QString &email)
0048     {
0049         init(a, email);
0050     }
0051 
0052     void init(const KContacts::Addressee &a, const QString &email)
0053     {
0054         mAddressee = a;
0055         mEmail = email;
0056         mId = -1;
0057         setText(0, mAddressee.realName());
0058         setText(1, mEmail);
0059     }
0060 
0061     [[nodiscard]] KContacts::Addressee addressee() const
0062     {
0063         return mAddressee;
0064     }
0065 
0066     [[nodiscard]] QString email() const
0067     {
0068         return mEmail;
0069     }
0070 
0071     [[nodiscard]] bool isTransient() const
0072     {
0073         return mId == -1;
0074     }
0075 
0076     void setId(Akonadi::Item::Id id)
0077     {
0078         mId = id;
0079     }
0080 
0081     [[nodiscard]] Akonadi::Item::Id id() const
0082     {
0083         return mId;
0084     }
0085 
0086 private:
0087     KContacts::Addressee mAddressee;
0088     QString mEmail;
0089     Akonadi::Item::Id mId = -1;
0090 };
0091 }
0092 
0093 DistributionListDialog::DistributionListDialog(QWidget *parent)
0094     : QDialog(parent)
0095 {
0096     setWindowTitle(i18nc("@title:window", "Save Distribution List"));
0097     setModal(false);
0098 
0099     auto mainLayout = new QVBoxLayout(this);
0100 
0101     auto topFrame = new QWidget(this);
0102     mainLayout->addWidget(topFrame);
0103 
0104     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
0105     mUser1Button = new QPushButton(this);
0106     buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
0107     mUser1Button->setText(i18nc("@action:button", "Save List"));
0108     mUser1Button->setEnabled(false);
0109     mUser1Button->setDefault(true);
0110     connect(buttonBox, &QDialogButtonBox::accepted, this, &DistributionListDialog::accept);
0111     connect(buttonBox, &QDialogButtonBox::rejected, this, &DistributionListDialog::reject);
0112 
0113     mainLayout->addWidget(buttonBox);
0114 
0115     auto topLayout = new QVBoxLayout(topFrame);
0116     topLayout->setContentsMargins({});
0117 
0118     auto titleLayout = new QHBoxLayout;
0119     topLayout->addLayout(titleLayout);
0120 
0121     auto label = new QLabel(i18nc("@label:textbox Name of the distribution list.", "&Name:"), topFrame);
0122     titleLayout->addWidget(label);
0123 
0124     mTitleEdit = new QLineEdit(topFrame);
0125     mTitleEdit->setPlaceholderText(i18n("Name of Distribution List"));
0126     titleLayout->addWidget(mTitleEdit);
0127     mTitleEdit->setFocus();
0128     mTitleEdit->setClearButtonEnabled(true);
0129     label->setBuddy(mTitleEdit);
0130 
0131     mRecipientsList = new QTreeWidget(topFrame);
0132     mRecipientsList->setHeaderLabels(QStringList() << i18nc("@title:column Name of the recipient", "Name")
0133                                                    << i18nc("@title:column Email of the recipient", "Email"));
0134     mRecipientsList->setRootIsDecorated(false);
0135     mRecipientsList->header()->setSectionsMovable(false);
0136     topLayout->addWidget(mRecipientsList);
0137     connect(mUser1Button, &QPushButton::clicked, this, &DistributionListDialog::slotUser1);
0138     connect(mTitleEdit, &QLineEdit::textChanged, this, &DistributionListDialog::slotTitleChanged);
0139     readConfig();
0140 }
0141 
0142 DistributionListDialog::~DistributionListDialog()
0143 {
0144     writeConfig();
0145 }
0146 
0147 // This starts one ContactSearchJob for each of the specified recipients.
0148 void DistributionListDialog::setRecipients(const Recipient::List &recipients)
0149 {
0150     Recipient::List::ConstIterator end(recipients.constEnd());
0151     for (Recipient::List::ConstIterator it = recipients.constBegin(); it != end; ++it) {
0152         const QStringList emails = KEmailAddress::splitAddressList((*it)->email());
0153         QStringList::ConstIterator end2(emails.constEnd());
0154         for (QStringList::ConstIterator it2 = emails.constBegin(); it2 != end2; ++it2) {
0155             QString name;
0156             QString email;
0157             KContacts::Addressee::parseEmailAddress(*it2, name, email);
0158             if (!email.isEmpty()) {
0159                 auto job = new Akonadi::ContactSearchJob(this);
0160                 job->setQuery(Akonadi::ContactSearchJob::Email, email.toLower(), Akonadi::ContactSearchJob::ExactMatch);
0161                 job->setProperty("name", name);
0162                 job->setProperty("email", email);
0163                 connect(job, &Akonadi::ContactSearchJob::result, this, &DistributionListDialog::slotDelayedSetRecipients);
0164             }
0165         }
0166     }
0167 }
0168 
0169 // This result slot will be called once for each of the original recipients.
0170 // There could potentially be more than one Akonadi item returned per
0171 // recipient, in the case where email addresses are duplicated between contacts.
0172 void DistributionListDialog::slotDelayedSetRecipients(KJob *job)
0173 {
0174     const Akonadi::ContactSearchJob *searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
0175     const Akonadi::Item::List akItems = searchJob->items();
0176 
0177     const QString email = searchJob->property("email").toString();
0178     QString name = searchJob->property("name").toString();
0179     if (name.isEmpty()) {
0180         const int index = email.indexOf(QLatin1Char('@'));
0181         if (index != -1) {
0182             name = email.left(index);
0183         } else {
0184             name = email;
0185         }
0186     }
0187 
0188     if (akItems.isEmpty()) {
0189         KContacts::Addressee contact;
0190         contact.setNameFromString(name);
0191         contact.addEmail(KContacts::Email(email));
0192 
0193         auto item = new DistributionListItem(mRecipientsList);
0194         item->setAddressee(contact, email);
0195         item->setCheckState(0, Qt::Checked);
0196     } else {
0197         bool isFirst = true;
0198         for (const Akonadi::Item &akItem : std::as_const(akItems)) {
0199             if (akItem.hasPayload<KContacts::Addressee>()) {
0200                 const auto contact = akItem.payload<KContacts::Addressee>();
0201 
0202                 auto item = new DistributionListItem(mRecipientsList);
0203                 item->setAddressee(contact, email);
0204 
0205                 // Need to record the Akonadi ID of the contact, so that
0206                 // it can be added as a reference later.  Setting an ID
0207                 // makes the item non-transient.
0208                 item->setId(akItem.id());
0209 
0210                 // If there were multiple contacts returned for an email address,
0211                 // then check the first one and uncheck any subsequent ones.
0212                 if (isFirst) {
0213                     item->setCheckState(0, Qt::Checked);
0214                     isFirst = false;
0215                 } else {
0216                     // Need this to create an unchecked item, as otherwise the
0217                     // item will have no checkbox at all.
0218                     item->setCheckState(0, Qt::Unchecked);
0219                 }
0220             }
0221         }
0222     }
0223 }
0224 
0225 void DistributionListDialog::slotUser1()
0226 {
0227     bool isEmpty = true;
0228     const int numberOfTopLevel(mRecipientsList->topLevelItemCount());
0229     for (int i = 0; i < numberOfTopLevel; ++i) {
0230         auto item = static_cast<DistributionListItem *>(mRecipientsList->topLevelItem(i));
0231         if (item && item->checkState(0) == Qt::Checked) {
0232             isEmpty = false;
0233             break;
0234         }
0235     }
0236 
0237     if (isEmpty) {
0238         KMessageBox::information(this,
0239                                  i18nc("@info",
0240                                        "There are no recipients in your list. "
0241                                        "First select some recipients, "
0242                                        "then try again."));
0243         return;
0244     }
0245 
0246     QString name = mTitleEdit->text();
0247 
0248     if (name.isEmpty()) {
0249         bool ok = false;
0250         name = QInputDialog::getText(this,
0251                                      i18nc("@title:window", "New Distribution List"),
0252                                      i18nc("@label:textbox", "Please enter name:"),
0253                                      QLineEdit::Normal,
0254                                      QString(),
0255                                      &ok);
0256         if (!ok || name.isEmpty()) {
0257             return;
0258         }
0259     }
0260 
0261     auto job = new Akonadi::ContactGroupSearchJob();
0262     job->setQuery(Akonadi::ContactGroupSearchJob::Name, name);
0263     job->setProperty("name", name);
0264     connect(job, &Akonadi::ContactSearchJob::result, this, &DistributionListDialog::slotDelayedUser1);
0265 }
0266 
0267 void DistributionListDialog::slotDelayedUser1(KJob *job)
0268 {
0269     const Akonadi::ContactGroupSearchJob *searchJob = qobject_cast<Akonadi::ContactGroupSearchJob *>(job);
0270     const QString name = searchJob->property("name").toString();
0271 
0272     if (!searchJob->contactGroups().isEmpty()) {
0273         qDebug() << " searchJob->contactGroups()" << searchJob->contactGroups().count();
0274         KMessageBox::information(this,
0275                                  xi18nc("@info",
0276                                         "<para>Distribution list with the given name <resource>%1</resource> "
0277                                         "already exists. Please select a different name.</para>",
0278                                         name));
0279         return;
0280     }
0281 
0282     QPointer<Akonadi::CollectionDialog> dlg = new Akonadi::CollectionDialog(Akonadi::CollectionDialog::KeepTreeExpanded, nullptr, this);
0283     dlg->setMimeTypeFilter(QStringList() << KContacts::Addressee::mimeType() << KContacts::ContactGroup::mimeType());
0284     dlg->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
0285     dlg->setWindowTitle(i18nc("@title:window", "Select Address Book"));
0286     dlg->setDescription(i18n("Select the address book folder to store the contact group in:"));
0287     if (dlg->exec()) {
0288         const Akonadi::Collection targetCollection = dlg->selectedCollection();
0289         delete dlg;
0290 
0291         KContacts::ContactGroup group(name);
0292         const int numberOfTopLevel(mRecipientsList->topLevelItemCount());
0293         for (int i = 0; i < numberOfTopLevel; ++i) {
0294             auto item = static_cast<DistributionListItem *>(mRecipientsList->topLevelItem(i));
0295             if (item && item->checkState(0) == Qt::Checked) {
0296                 qCDebug(MESSAGECOMPOSER_LOG) << item->addressee().fullEmail() << item->addressee().uid();
0297                 if (item->isTransient()) {
0298                     group.append(KContacts::ContactGroup::Data(item->addressee().realName(), item->email()));
0299                 } else {
0300                     KContacts::ContactGroup::ContactReference reference(QString::number(item->id()));
0301                     if (item->email() != item->addressee().preferredEmail()) {
0302                         reference.setPreferredEmail(item->email());
0303                     }
0304                     group.append(reference);
0305                 }
0306             }
0307         }
0308 
0309         Akonadi::Item groupItem(KContacts::ContactGroup::mimeType());
0310         groupItem.setPayload<KContacts::ContactGroup>(group);
0311 
0312         Akonadi::Job *createJob = new Akonadi::ItemCreateJob(groupItem, targetCollection);
0313         connect(createJob, &Akonadi::ItemCreateJob::result, this, &DistributionListDialog::slotContactGroupCreateJobResult);
0314     }
0315 
0316     delete dlg;
0317 }
0318 
0319 void DistributionListDialog::slotContactGroupCreateJobResult(KJob *job)
0320 {
0321     if (job->error()) {
0322         KMessageBox::information(this, i18n("Unable to create distribution list: %1", job->errorString()));
0323         qCWarning(MESSAGECOMPOSER_LOG) << "Unable to create distribution list:" << job->errorText();
0324     } else {
0325         accept();
0326     }
0327 }
0328 
0329 void DistributionListDialog::slotTitleChanged(const QString &text)
0330 {
0331     mUser1Button->setEnabled(!text.trimmed().isEmpty());
0332 }
0333 
0334 namespace
0335 {
0336 static const char myDistributionListDialogGroupName[] = "DistributionListDialog";
0337 }
0338 
0339 void DistributionListDialog::readConfig()
0340 {
0341     KSharedConfig::Ptr cfg = KSharedConfig::openStateConfig();
0342     KConfigGroup group(cfg, QLatin1StringView(myDistributionListDialogGroupName));
0343     const QSize size = group.readEntry("Size", QSize());
0344     if (!size.isEmpty()) {
0345         resize(size);
0346     }
0347     mRecipientsList->header()->restoreState(group.readEntry("Header", QByteArray()));
0348 }
0349 
0350 void DistributionListDialog::writeConfig()
0351 {
0352     KSharedConfig::Ptr cfg = KSharedConfig::openStateConfig();
0353     KConfigGroup group(cfg, QLatin1StringView(myDistributionListDialogGroupName));
0354     group.writeEntry("Size", size());
0355     group.writeEntry("Header", mRecipientsList->header()->saveState());
0356 }
0357 
0358 #include "moc_distributionlistdialog.cpp"