Messagelib

recipientseditor.cpp
1 /*
2  SPDX-FileCopyrightText: 2010 Casey Link <[email protected]>
3  SPDX-FileCopyrightText: 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <[email protected]>
4 
5  Refactored from earlier code by:
6  SPDX-FileCopyrightText: 2010 Volker Krause <[email protected]>
7  SPDX-FileCopyrightText: 2004 Cornelius Schumacher <[email protected]>
8 
9  SPDX-License-Identifier: LGPL-2.0-or-later
10 */
11 
12 #include "recipientseditor.h"
13 
14 #include "recipient.h"
15 #include "recipientseditorsidewidget.h"
16 
17 #include "distributionlistdialog.h"
18 #include "settings/messagecomposersettings.h"
19 
20 #include "messagecomposer_debug.h"
21 
22 #include <KEmailAddress>
23 #include <KLocalizedString>
24 #include <KMessageBox>
25 #include <KMime/Headers>
26 
27 #include <QKeyEvent>
28 #include <QLayout>
29 
30 using namespace MessageComposer;
31 using namespace KPIM;
32 
33 RecipientLineFactory::RecipientLineFactory(QObject *parent)
34  : KPIM::MultiplyingLineFactory(parent)
35 {
36 }
37 
38 KPIM::MultiplyingLine *RecipientLineFactory::newLine(QWidget *p)
39 {
40  auto line = new RecipientLineNG(p);
41  if (qobject_cast<RecipientsEditor *>(parent())) {
42  connect(line,
43  SIGNAL(addRecipient(RecipientLineNG *, QString)),
44  qobject_cast<RecipientsEditor *>(parent()),
45  SLOT(addRecipient(RecipientLineNG *, QString)));
46  } else {
47  qCWarning(MESSAGECOMPOSER_LOG) << "RecipientLineFactory::newLine: We can't connect to new line" << parent();
48  }
49  return line;
50 }
51 
52 int RecipientLineFactory::maximumRecipients()
53 {
54  return MessageComposer::MessageComposerSettings::self()->maximumRecipients();
55 }
56 
57 class MessageComposer::RecipientsEditorPrivate
58 {
59 public:
60  RecipientsEditorPrivate()
61  {
62  }
63 
64  KConfig *mRecentAddressConfig = nullptr;
65  RecipientsEditorSideWidget *mSideWidget = nullptr;
66  bool mSkipTotal = false;
67 };
68 
69 RecipientsEditor::RecipientsEditor(QWidget *parent)
70  : RecipientsEditor(new RecipientLineFactory(nullptr), parent)
71 {
72 }
73 
74 RecipientsEditor::RecipientsEditor(RecipientLineFactory *lineFactory, QWidget *parent)
75  : MultiplyingLineEditor(lineFactory, parent)
76  , d(new MessageComposer::RecipientsEditorPrivate)
77 {
78  factory()->setParent(this); // HACK: can't use 'this' above since it's not yet constructed at that point
79  d->mSideWidget = new RecipientsEditorSideWidget(this, this);
80 
81  layout()->addWidget(d->mSideWidget);
82 
83  // Install global event filter and listen for keypress events for RecipientLineEdits.
84  // Unfortunately we can't install ourselves directly as event filter for the edits,
85  // because the RecipientLineEdit has its own event filter installed into QApplication
86  // and so it would eat the event before it would reach us.
87  qApp->installEventFilter(this);
88 
89  connect(d->mSideWidget, &RecipientsEditorSideWidget::pickedRecipient, this, &RecipientsEditor::slotPickedRecipient);
90  connect(d->mSideWidget, &RecipientsEditorSideWidget::saveDistributionList, this, &RecipientsEditor::saveDistributionList);
91 
92  connect(this, &RecipientsEditor::lineAdded, this, &RecipientsEditor::slotLineAdded);
93  connect(this, &RecipientsEditor::lineDeleted, this, &RecipientsEditor::slotLineDeleted);
94 
95  addData(); // one default line
96 }
97 
98 RecipientsEditor::~RecipientsEditor()
99 {
100  delete d;
101 }
102 
103 bool RecipientsEditor::addRecipient(const QString &recipient, Recipient::Type type)
104 {
105  return addData(Recipient::Ptr(new Recipient(recipient, type)));
106 }
107 
108 void RecipientsEditor::addRecipient(RecipientLineNG *line, const QString &recipient)
109 {
110  addRecipient(recipient, line->recipientType());
111 }
112 
113 void RecipientsEditor::setRecipientString(const QVector<KMime::Types::Mailbox> &mailboxes, Recipient::Type type)
114 {
115  int count = 1;
116 
117  for (const KMime::Types::Mailbox &mailbox : mailboxes) {
118  if (count++ > MessageComposer::MessageComposerSettings::self()->maximumRecipients()) {
119  KMessageBox::sorry(this,
120  i18ncp("@info:status",
121  "Truncating recipients list to %2 of %1 entry.",
122  "Truncating recipients list to %2 of %1 entries.",
123  mailboxes.count(),
124  MessageComposer::MessageComposerSettings::self()->maximumRecipients()));
125  break;
126  }
127  addRecipient(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), type);
128  }
129 }
130 
131 Recipient::List RecipientsEditor::recipients() const
132 {
133  const QVector<MultiplyingLineData::Ptr> dataList = allData();
134  Recipient::List recList;
135  for (const MultiplyingLineData::Ptr &datum : dataList) {
136  Recipient::Ptr rec = qSharedPointerDynamicCast<Recipient>(datum);
137  if (!rec) {
138  continue;
139  }
140  recList << rec;
141  }
142  return recList;
143 }
144 
145 Recipient::Ptr RecipientsEditor::activeRecipient() const
146 {
147  return qSharedPointerDynamicCast<Recipient>(activeData());
148 }
149 
150 QString RecipientsEditor::recipientString(Recipient::Type type) const
151 {
152  return recipientStringList(type).join(QLatin1String(", "));
153 }
154 
155 QStringList RecipientsEditor::recipientStringList(Recipient::Type type) const
156 {
157  QStringList selectedRecipients;
158  for (const Recipient::Ptr &r : recipients()) {
159  if (r->type() == type) {
160  selectedRecipients << r->email();
161  }
162  }
163  return selectedRecipients;
164 }
165 
166 void RecipientsEditor::removeRecipient(const QString &recipient, Recipient::Type type)
167 {
168  // search a line which matches recipient and type
170  MultiplyingLine *line = nullptr;
171  while (it.hasNext()) {
172  line = it.next();
173  auto rec = qobject_cast<RecipientLineNG *>(line);
174  if (rec) {
175  if ((rec->recipient()->email() == recipient) && (rec->recipientType() == type)) {
176  break;
177  }
178  }
179  }
180  if (line) {
181  line->slotPropagateDeletion();
182  }
183 }
184 
185 void RecipientsEditor::saveDistributionList()
186 {
187  std::unique_ptr<MessageComposer::DistributionListDialog> dlg(new MessageComposer::DistributionListDialog(this));
188  dlg->setRecipients(recipients());
189  dlg->exec();
190 }
191 
192 void RecipientsEditor::selectRecipients()
193 {
194  d->mSideWidget->pickRecipient();
195 }
196 
198 {
199  d->mRecentAddressConfig = config;
200  if (config) {
201  MultiplyingLine *line;
202  foreach (line, lines()) {
203  auto rec = qobject_cast<RecipientLineNG *>(line);
204  if (rec) {
205  rec->setRecentAddressConfig(config);
206  }
207  }
208  }
209 }
210 
211 void MessageComposer::RecipientsEditor::slotPickedRecipient(const Recipient &rec, bool &tooManyAddress)
212 {
213  const Recipient::Type t = rec.type();
214  tooManyAddress = addRecipient(rec.email(), t == Recipient::Undefined ? Recipient::To : t);
215  mModified = true;
216 }
217 
218 RecipientsPicker *RecipientsEditor::picker() const
219 {
220  return d->mSideWidget->picker();
221 }
222 
223 void RecipientsEditor::slotLineAdded(MultiplyingLine *line)
224 {
225  // subtract 1 here, because we want the number of lines
226  // before this line was added.
227  const int count = lines().size() - 1;
228  auto rec = qobject_cast<RecipientLineNG *>(line);
229  if (!rec) {
230  return;
231  }
232 
233  if (d->mRecentAddressConfig) {
234  rec->setRecentAddressConfig(d->mRecentAddressConfig);
235  }
236 
237  if (count > 0) {
238  if (count == 1) {
239  auto last_rec = qobject_cast<RecipientLineNG *>(lines().constFirst());
240  if (last_rec && (last_rec->recipientType() == Recipient::Bcc || last_rec->recipientType() == Recipient::ReplyTo)) {
241  rec->setRecipientType(Recipient::To);
242  } else {
243  rec->setRecipientType(Recipient::Cc);
244  }
245  } else {
246  auto last_rec = qobject_cast<RecipientLineNG *>(lines().at(lines().count() - 2));
247  if (last_rec) {
248  if (last_rec->recipientType() == Recipient::ReplyTo) {
249  rec->setRecipientType(Recipient::To);
250  } else {
251  rec->setRecipientType(last_rec->recipientType());
252  }
253  }
254  }
255  line->fixTabOrder(lines().constLast()->tabOut());
256  }
257  connect(rec, &RecipientLineNG::countChanged, this, &RecipientsEditor::slotCalculateTotal);
258 }
259 
260 void RecipientsEditor::slotLineDeleted(int pos)
261 {
262  bool atLeastOneToLine = false;
263  int firstCC = -1;
264  for (int i = pos, total = lines().count(); i < total; ++i) {
265  MultiplyingLine *line = lines().at(i);
266  auto rec = qobject_cast<RecipientLineNG *>(line);
267  if (rec) {
268  if (rec->recipientType() == Recipient::To) {
269  atLeastOneToLine = true;
270  } else if ((rec->recipientType() == Recipient::Cc) && (firstCC < 0)) {
271  firstCC = i;
272  }
273  }
274  }
275 
276  if (!atLeastOneToLine && (firstCC >= 0)) {
277  auto firstCCLine = qobject_cast<RecipientLineNG *>(lines().at(firstCC));
278  if (firstCCLine) {
279  firstCCLine->setRecipientType(Recipient::To);
280  }
281  }
282 
283  slotCalculateTotal();
284 }
285 
286 bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
287 {
288  if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit *>(object)) {
289  auto ke = static_cast<QKeyEvent *>(event);
290  // Treats comma or semicolon as email separator, will automatically move focus
291  // to a new line, basically preventing user from inputting more than one
292  // email address per line, which breaks our opportunistic crypto in composer
293  if (ke->key() == Qt::Key_Comma || (ke->key() == Qt::Key_Semicolon && MessageComposerSettings::self()->allowSemicolonAsAddressSeparator())) {
294  auto line = qobject_cast<RecipientLineNG *>(object->parent());
295  const auto split = KEmailAddress::splitAddressList(line->rawData() + QLatin1String(", "));
296  if (split.size() > 1) {
297  addRecipient(QString(), line->recipientType());
298  setFocusBottom();
299  return true;
300  }
301  }
302  } else if (event->type() == QEvent::FocusIn && qobject_cast<RecipientLineEdit *>(object)) {
303  Q_EMIT focusInRecipientLineEdit();
304  }
305 
306  return false;
307 }
308 
309 void RecipientsEditor::slotCalculateTotal()
310 {
311  // Prevent endless recursion when splitting recipient
312  if (d->mSkipTotal) {
313  return;
314  }
315  int empty = 0;
316  MultiplyingLine *line = nullptr;
317  foreach (line, lines()) {
318  auto rec = qobject_cast<RecipientLineNG *>(line);
319  if (rec) {
320  if (rec->isEmpty()) {
321  ++empty;
322  } else {
323  const int recipientsCount = rec->recipientsCount();
324  if (recipientsCount > 1) {
325  // Ensure we always have only one recipient per line
326  d->mSkipTotal = true;
327  Recipient::Ptr recipient = rec->recipient();
328  const auto split = KEmailAddress::splitAddressList(recipient->email());
329  bool maximumElementFound = false;
330  for (int i = 1 /* sic! */; i < split.count(); ++i) {
331  maximumElementFound = addRecipient(split[i], rec->recipientType());
332  if (maximumElementFound) {
333  break;
334  }
335  }
336  recipient->setEmail(split[0]);
337  rec->setData(recipient);
338  setFocusBottom(); // focus next empty entry
339  d->mSkipTotal = false;
340  if (maximumElementFound) {
341  return;
342  }
343  }
344  }
345  }
346  }
347  // We always want at least one empty line
348  if (empty == 0) {
349  addData();
350  }
351  int count = 0;
352  foreach (line, lines()) {
353  auto rec = qobject_cast<RecipientLineNG *>(line);
354  if (rec) {
355  if (!rec->isEmpty()) {
356  count++;
357  }
358  }
359  }
360  // update the side widget
361  d->mSideWidget->setTotal(count, lines().count());
362 }
363 
364 RecipientLineNG *RecipientsEditor::activeLine() const
365 {
366  MultiplyingLine *line = MultiplyingLineEditor::activeLine();
367  return qobject_cast<RecipientLineNG *>(line);
368 }
QLayout * layout() const const
QEvent::Type type() const const
const T & next()
The RecipientLineNG class.
Definition: recipientline.h:54
void setParent(QWidget *parent)
The RecipientLineFactory class.
void setRecentAddressConfig(KConfig *config)
Sets the config file used for storing recent addresses.
bool addRecipient(const QString &recipient, Recipient::Type type)
Adds a recipient (or multiple recipients) to one line of the editor.
virtual void fixTabOrder(QWidget *previous)=0
QPoint pos() const const
The RecipientLineEdit class.
Definition: recipientline.h:36
void addWidget(QWidget *w)
KCODECS_EXPORT QStringList splitAddressList(const QString &aStr)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
void removeRecipient(const QString &recipient, Recipient::Type type)
Removes the recipient provided it can be found and has the given type.
The Recipient class.
Definition: recipient.h:27
The RecipientsEditor class.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
void setRecentAddressConfig(KConfig *config)
Sets the config file used for storing recent addresses.
virtual bool event(QEvent *event) override
Key_Comma
Q_EMITQ_EMIT
void sorry(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
bool hasNext() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Jun 21 2021 23:14:00 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.