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"