Messagelib

distributionlistdialog.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Volker Krause <[email protected]>
3  This file was part of KMail.
4  SPDX-FileCopyrightText: 2005 Cornelius Schumacher <[email protected]>
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 
34 using namespace MessageComposer;
35 
36 namespace MessageComposer
37 {
38 class DistributionListItem : public QTreeWidgetItem
39 {
40 public:
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  Q_REQUIRED_RESULT KContacts::Addressee addressee() const
62  {
63  return mAddressee;
64  }
65 
66  Q_REQUIRED_RESULT QString email() const
67  {
68  return mEmail;
69  }
70 
71  Q_REQUIRED_RESULT bool isTransient() const
72  {
73  return mId == -1;
74  }
75 
76  void setId(Akonadi::Item::Id id)
77  {
78  mId = id;
79  }
80 
81  Q_REQUIRED_RESULT Akonadi::Item::Id id() const
82  {
83  return mId;
84  }
85 
86 private:
87  KContacts::Addressee mAddressee;
88  QString mEmail;
89  Akonadi::Item::Id mId = -1;
90 };
91 }
92 
93 DistributionListDialog::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(i18n("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 
142 DistributionListDialog::~DistributionListDialog()
143 {
144  writeConfig();
145 }
146 
147 // This starts one ContactSearchJob for each of the specified recipients.
148 void DistributionListDialog::setRecipients(const Recipient::List &recipients)
149 {
150  Recipient::List::ConstIterator end(recipients.constEnd());
151  for (Recipient::List::ConstIterator it = recipients.constBegin(); it != end; ++it) {
152  const QStringList emails = KEmailAddress::splitAddressList((*it)->email());
153  QStringList::ConstIterator end2(emails.constEnd());
154  for (QStringList::ConstIterator it2 = emails.constBegin(); it2 != end2; ++it2) {
155  QString name;
156  QString email;
157  KContacts::Addressee::parseEmailAddress(*it2, name, 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.
172 void DistributionListDialog::slotDelayedSetRecipients(KJob *job)
173 {
174  const Akonadi::ContactSearchJob *searchJob = qobject_cast<Akonadi::ContactSearchJob *>(job);
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 
225 void 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 
267 void DistributionListDialog::slotDelayedUser1(KJob *job)
268 {
269  const Akonadi::ContactGroupSearchJob *searchJob = qobject_cast<Akonadi::ContactGroupSearchJob *>(job);
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 
319 void 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 
329 void DistributionListDialog::slotTitleChanged(const QString &text)
330 {
331  mUser1Button->setEnabled(!text.trimmed().isEmpty());
332 }
333 
334 namespace
335 {
336 static const char myDistributionListDialogGroupName[] = "DistributionListDialog";
337 }
338 
339 void DistributionListDialog::readConfig()
340 {
341  KSharedConfig::Ptr cfg = KSharedConfig::openStateConfig();
342  KConfigGroup group(cfg, 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 
350 void DistributionListDialog::writeConfig()
351 {
352  KSharedConfig::Ptr cfg = KSharedConfig::openStateConfig();
353  KConfigGroup group(cfg, myDistributionListDialogGroupName);
354  group.writeEntry("Size", size());
355  group.writeEntry("Header", mRecipientsList->header()->saveState());
356 }
void setFlags(Qt::ItemFlags flags)
bool isEmpty() const const
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
QString number(int n, int base)
void result(KJob *job)
QString preferredEmail() const
QCA_EXPORT void init()
bool isEmpty() const const
QString realName() const
void clicked(bool checked)
void readConfig()
QVector::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
KCODECS_EXPORT QStringList splitAddressList(const QString &aStr)
ItemIsUserCheckable
static void parseEmailAddress(const QString &rawEmail, QString &fullName, QString &email)
QString i18n(const char *text, const TYPE &arg...)
constexpr bool isEmpty() const
void textChanged(const QString &text)
bool isEmpty() const const
QString errorText() const
void information(QWidget *parent, const QString &text, const QString &caption=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
static KSharedConfig::Ptr openStateConfig(const QString &fileName=QString())
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 setText(int column, const QString &text)
static QString mimeType()
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
typedef ConstIterator
QString label(StandardShortcut id)
QString uid() const
bool setProperty(const char *name, const QVariant &value)
QString toLower() const const
QList::const_iterator constEnd() const const
void setNameFromString(const QString &s)
QString fullEmail(const QString &email=QString()) const
QString left(int n) const const
const char * name(StandardAction id)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void addEmail(const Email &email)
int count(const T &value) const const
KContacts::ContactGroup::List contactGroups() const
void addLayout(QLayout *layout, int stretch)
QVector::const_iterator constBegin() const const
virtual QString errorString() const
int error() const
const QList< QKeySequence > & end()
QString & append(QChar ch)
Qt::ItemFlags flags() const const
static QString mimeType()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 19 2022 03:53:25 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.