Messagelib

distributionlistdialog.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Volker Krause <vkrause@kde.org>
3 This file was part of KMail.
4 SPDX-FileCopyrightText: 2005 Cornelius Schumacher <schumacher@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "distributionlistdialog.h"
10
11#include <Akonadi/CollectionDialog>
12#include <Akonadi/ContactGroupSearchJob>
13#include <Akonadi/ContactSearchJob>
14#include <Akonadi/ItemCreateJob>
15#include <KEmailAddress>
16
17#include "messagecomposer_debug.h"
18#include <KLocalizedString>
19#include <KMessageBox>
20#include <QInputDialog>
21#include <QLineEdit>
22
23#include <KConfigGroup>
24#include <KSharedConfig>
25#include <QDialogButtonBox>
26#include <QHBoxLayout>
27#include <QHeaderView>
28#include <QLabel>
29#include <QPushButton>
30#include <QTreeWidget>
31#include <QTreeWidgetItem>
32#include <QVBoxLayout>
33
34using namespace MessageComposer;
35
36namespace MessageComposer
37{
38class DistributionListItem : public QTreeWidgetItem
39{
40public:
41 explicit DistributionListItem(QTreeWidget *tree)
42 : QTreeWidgetItem(tree)
43 {
45 }
46
47 void setAddressee(const KContacts::Addressee &a, const QString &email)
48 {
49 init(a, email);
50 }
51
52 void init(const KContacts::Addressee &a, const QString &email)
53 {
54 mAddressee = a;
55 mEmail = email;
56 mId = -1;
57 setText(0, mAddressee.realName());
58 setText(1, mEmail);
59 }
60
61 [[nodiscard]] KContacts::Addressee addressee() const
62 {
63 return mAddressee;
64 }
65
66 [[nodiscard]] QString email() const
67 {
68 return mEmail;
69 }
70
71 [[nodiscard]] bool isTransient() const
72 {
73 return mId == -1;
74 }
75
76 void setId(Akonadi::Item::Id id)
77 {
78 mId = id;
79 }
80
81 [[nodiscard]] Akonadi::Item::Id id() const
82 {
83 return mId;
84 }
85
86private:
87 KContacts::Addressee mAddressee;
88 QString mEmail;
89 Akonadi::Item::Id mId = -1;
90};
91}
92
93DistributionListDialog::DistributionListDialog(QWidget *parent)
94 : QDialog(parent)
95{
96 setWindowTitle(i18nc("@title:window", "Save Distribution List"));
97 setModal(false);
98
99 auto mainLayout = new QVBoxLayout(this);
100
101 auto topFrame = new QWidget(this);
102 mainLayout->addWidget(topFrame);
103
104 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
105 mUser1Button = new QPushButton(this);
106 buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
107 mUser1Button->setText(i18nc("@action:button", "Save List"));
108 mUser1Button->setEnabled(false);
109 mUser1Button->setDefault(true);
110 connect(buttonBox, &QDialogButtonBox::accepted, this, &DistributionListDialog::accept);
111 connect(buttonBox, &QDialogButtonBox::rejected, this, &DistributionListDialog::reject);
112
113 mainLayout->addWidget(buttonBox);
114
115 auto topLayout = new QVBoxLayout(topFrame);
116 topLayout->setContentsMargins({});
117
118 auto titleLayout = new QHBoxLayout;
119 topLayout->addLayout(titleLayout);
120
121 auto label = new QLabel(i18nc("@label:textbox Name of the distribution list.", "&Name:"), topFrame);
122 titleLayout->addWidget(label);
123
124 mTitleEdit = new QLineEdit(topFrame);
125 mTitleEdit->setPlaceholderText(i18nc("@info:placeholder", "Name of Distribution List"));
126 titleLayout->addWidget(mTitleEdit);
127 mTitleEdit->setFocus();
128 mTitleEdit->setClearButtonEnabled(true);
129 label->setBuddy(mTitleEdit);
130
131 mRecipientsList = new QTreeWidget(topFrame);
132 mRecipientsList->setHeaderLabels(QStringList() << i18nc("@title:column Name of the recipient", "Name")
133 << i18nc("@title:column Email of the recipient", "Email"));
134 mRecipientsList->setRootIsDecorated(false);
135 mRecipientsList->header()->setSectionsMovable(false);
136 topLayout->addWidget(mRecipientsList);
137 connect(mUser1Button, &QPushButton::clicked, this, &DistributionListDialog::slotUser1);
138 connect(mTitleEdit, &QLineEdit::textChanged, this, &DistributionListDialog::slotTitleChanged);
139 readConfig();
140}
141
142DistributionListDialog::~DistributionListDialog()
143{
144 writeConfig();
145}
146
147// This starts one ContactSearchJob for each of the specified recipients.
148void DistributionListDialog::setRecipients(const Recipient::List &recipients)
149{
151 for (Recipient::List::ConstIterator it = recipients.constBegin(); it != end; ++it) {
152 const QStringList emails = KEmailAddress::splitAddressList((*it)->email());
154 for (QStringList::ConstIterator it2 = emails.constBegin(); it2 != end2; ++it2) {
156 QString email;
158 if (!email.isEmpty()) {
159 auto job = new Akonadi::ContactSearchJob(this);
161 job->setProperty("name", name);
162 job->setProperty("email", email);
163 connect(job, &Akonadi::ContactSearchJob::result, this, &DistributionListDialog::slotDelayedSetRecipients);
164 }
165 }
166 }
167}
168
169// This result slot will be called once for each of the original recipients.
170// There could potentially be more than one Akonadi item returned per
171// recipient, in the case where email addresses are duplicated between contacts.
172void DistributionListDialog::slotDelayedSetRecipients(KJob *job)
173{
175 const Akonadi::Item::List akItems = searchJob->items();
176
177 const QString email = searchJob->property("email").toString();
178 QString name = searchJob->property("name").toString();
179 if (name.isEmpty()) {
180 const int index = email.indexOf(QLatin1Char('@'));
181 if (index != -1) {
182 name = email.left(index);
183 } else {
184 name = email;
185 }
186 }
187
188 if (akItems.isEmpty()) {
189 KContacts::Addressee contact;
190 contact.setNameFromString(name);
191 contact.addEmail(KContacts::Email(email));
192
193 auto item = new DistributionListItem(mRecipientsList);
194 item->setAddressee(contact, email);
195 item->setCheckState(0, Qt::Checked);
196 } else {
197 bool isFirst = true;
198 for (const Akonadi::Item &akItem : std::as_const(akItems)) {
199 if (akItem.hasPayload<KContacts::Addressee>()) {
200 const auto contact = akItem.payload<KContacts::Addressee>();
201
202 auto item = new DistributionListItem(mRecipientsList);
203 item->setAddressee(contact, email);
204
205 // Need to record the Akonadi ID of the contact, so that
206 // it can be added as a reference later. Setting an ID
207 // makes the item non-transient.
208 item->setId(akItem.id());
209
210 // If there were multiple contacts returned for an email address,
211 // then check the first one and uncheck any subsequent ones.
212 if (isFirst) {
213 item->setCheckState(0, Qt::Checked);
214 isFirst = false;
215 } else {
216 // Need this to create an unchecked item, as otherwise the
217 // item will have no checkbox at all.
218 item->setCheckState(0, Qt::Unchecked);
219 }
220 }
221 }
222 }
223}
224
225void DistributionListDialog::slotUser1()
226{
227 bool isEmpty = true;
228 const int numberOfTopLevel(mRecipientsList->topLevelItemCount());
229 for (int i = 0; i < numberOfTopLevel; ++i) {
230 auto item = static_cast<DistributionListItem *>(mRecipientsList->topLevelItem(i));
231 if (item && item->checkState(0) == Qt::Checked) {
232 isEmpty = false;
233 break;
234 }
235 }
236
237 if (isEmpty) {
239 i18nc("@info",
240 "There are no recipients in your list. "
241 "First select some recipients, "
242 "then try again."));
243 return;
244 }
245
246 QString name = mTitleEdit->text();
247
248 if (name.isEmpty()) {
249 bool ok = false;
251 i18nc("@title:window", "New Distribution List"),
252 i18nc("@label:textbox", "Please enter name:"),
254 QString(),
255 &ok);
256 if (!ok || name.isEmpty()) {
257 return;
258 }
259 }
260
261 auto job = new Akonadi::ContactGroupSearchJob();
262 job->setQuery(Akonadi::ContactGroupSearchJob::Name, name);
263 job->setProperty("name", name);
264 connect(job, &Akonadi::ContactSearchJob::result, this, &DistributionListDialog::slotDelayedUser1);
265}
266
267void DistributionListDialog::slotDelayedUser1(KJob *job)
268{
270 const QString name = searchJob->property("name").toString();
271
272 if (!searchJob->contactGroups().isEmpty()) {
273 qDebug() << " searchJob->contactGroups()" << searchJob->contactGroups().count();
275 xi18nc("@info",
276 "<para>Distribution list with the given name <resource>%1</resource> "
277 "already exists. Please select a different name.</para>",
278 name));
279 return;
280 }
281
282 QPointer<Akonadi::CollectionDialog> dlg = new Akonadi::CollectionDialog(Akonadi::CollectionDialog::KeepTreeExpanded, nullptr, this);
284 dlg->setAccessRightsFilter(Akonadi::Collection::CanCreateItem);
285 dlg->setWindowTitle(i18nc("@title:window", "Select Address Book"));
286 dlg->setDescription(i18n("Select the address book folder to store the contact group in:"));
287 if (dlg->exec()) {
288 const Akonadi::Collection targetCollection = dlg->selectedCollection();
289 delete dlg;
290
291 KContacts::ContactGroup group(name);
292 const int numberOfTopLevel(mRecipientsList->topLevelItemCount());
293 for (int i = 0; i < numberOfTopLevel; ++i) {
294 auto item = static_cast<DistributionListItem *>(mRecipientsList->topLevelItem(i));
295 if (item && item->checkState(0) == Qt::Checked) {
296 qCDebug(MESSAGECOMPOSER_LOG) << item->addressee().fullEmail() << item->addressee().uid();
297 if (item->isTransient()) {
298 group.append(KContacts::ContactGroup::Data(item->addressee().realName(), item->email()));
299 } else {
301 if (item->email() != item->addressee().preferredEmail()) {
302 reference.setPreferredEmail(item->email());
303 }
304 group.append(reference);
305 }
306 }
307 }
308
310 groupItem.setPayload<KContacts::ContactGroup>(group);
311
312 Akonadi::Job *createJob = new Akonadi::ItemCreateJob(groupItem, targetCollection);
313 connect(createJob, &Akonadi::ItemCreateJob::result, this, &DistributionListDialog::slotContactGroupCreateJobResult);
314 }
315
316 delete dlg;
317}
318
319void DistributionListDialog::slotContactGroupCreateJobResult(KJob *job)
320{
321 if (job->error()) {
322 KMessageBox::information(this, i18n("Unable to create distribution list: %1", job->errorString()));
323 qCWarning(MESSAGECOMPOSER_LOG) << "Unable to create distribution list:" << job->errorText();
324 } else {
325 accept();
326 }
327}
328
329void DistributionListDialog::slotTitleChanged(const QString &text)
330{
331 mUser1Button->setEnabled(!text.trimmed().isEmpty());
332}
333
334namespace
335{
336static const char myDistributionListDialogGroupName[] = "DistributionListDialog";
337}
338
339void DistributionListDialog::readConfig()
340{
341 KSharedConfig::Ptr cfg = KSharedConfig::openStateConfig();
342 KConfigGroup group(cfg, QLatin1StringView(myDistributionListDialogGroupName));
343 const QSize size = group.readEntry("Size", QSize());
344 if (!size.isEmpty()) {
345 resize(size);
346 }
347 mRecipientsList->header()->restoreState(group.readEntry("Header", QByteArray()));
348}
349
350void DistributionListDialog::writeConfig()
351{
352 KSharedConfig::Ptr cfg = KSharedConfig::openStateConfig();
353 KConfigGroup group(cfg, QLatin1StringView(myDistributionListDialogGroupName));
354 group.writeEntry("Size", size());
355 group.writeEntry("Header", mRecipientsList->header()->saveState());
356}
357
358#include "moc_distributionlistdialog.cpp"
KContacts::ContactGroup::List contactGroups() const
void setNameFromString(const QString &s)
void addEmail(const Email &email)
static QString mimeType()
static void parseEmailAddress(const QString &rawEmail, QString &fullName, QString &email)
QString preferredEmail() const
QString realName() const
QString fullEmail(const QString &email=QString()) const
QString uid() const
static QString mimeType()
virtual QString errorString() const
int error() const
void result(KJob *job)
QString errorText() const
constexpr bool isEmpty() const
static KSharedConfig::Ptr openStateConfig(const QString &fileName=QString())
KCODECS_EXPORT QStringList splitAddressList(const QString &aStr)
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
QString name(StandardAction id)
QString label(StandardShortcut id)
const QList< QKeySequence > & end()
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
void clicked(bool checked)
void addLayout(QLayout *layout, int stretch)
virtual void accept()
bool restoreState(const QByteArray &state)
QByteArray saveState() const const
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
void textChanged(const QString &text)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
bool setProperty(const char *name, QVariant &&value)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
QString number(double n, char format, int precision)
QString toLower() const const
ItemIsUserCheckable
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QHeaderView * header() const const
QTreeWidgetItem * topLevelItem(int index) const const
Qt::ItemFlags flags() const const
void setFlags(Qt::ItemFlags flags)
void setText(int column, const QString &text)
void setEnabled(bool)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:07:25 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.