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() = default;
61 
62  KConfig *mRecentAddressConfig = nullptr;
63  RecipientsEditorSideWidget *mSideWidget = nullptr;
64  bool mSkipTotal = false;
65 };
66 
67 RecipientsEditor::RecipientsEditor(QWidget *parent)
68  : RecipientsEditor(new RecipientLineFactory(nullptr), parent)
69 {
70 }
71 
72 RecipientsEditor::RecipientsEditor(RecipientLineFactory *lineFactory, QWidget *parent)
73  : MultiplyingLineEditor(lineFactory, parent)
74  , d(new MessageComposer::RecipientsEditorPrivate)
75 {
76  factory()->setParent(this); // HACK: can't use 'this' above since it's not yet constructed at that point
77  d->mSideWidget = new RecipientsEditorSideWidget(this, this);
78 
79  layout()->addWidget(d->mSideWidget);
80 
81  // Install global event filter and listen for keypress events for RecipientLineEdits.
82  // Unfortunately we can't install ourselves directly as event filter for the edits,
83  // because the RecipientLineEdit has its own event filter installed into QApplication
84  // and so it would eat the event before it would reach us.
85  qApp->installEventFilter(this);
86 
87  connect(d->mSideWidget, &RecipientsEditorSideWidget::pickedRecipient, this, &RecipientsEditor::slotPickedRecipient);
88  connect(d->mSideWidget, &RecipientsEditorSideWidget::saveDistributionList, this, &RecipientsEditor::saveDistributionList);
89 
90  connect(this, &RecipientsEditor::lineAdded, this, &RecipientsEditor::slotLineAdded);
91  connect(this, &RecipientsEditor::lineDeleted, this, &RecipientsEditor::slotLineDeleted);
92 
93  addData(); // one default line
94 }
95 
96 RecipientsEditor::~RecipientsEditor() = default;
97 
98 bool RecipientsEditor::addRecipient(const QString &recipient, Recipient::Type type)
99 {
100  return addData(Recipient::Ptr(new Recipient(recipient, type)), false);
101 }
102 
103 void RecipientsEditor::addRecipient(RecipientLineNG *line, const QString &recipient)
104 {
105  addRecipient(recipient, line->recipientType());
106 }
107 
108 bool RecipientsEditor::setRecipientString(const QVector<KMime::Types::Mailbox> &mailboxes, Recipient::Type type)
109 {
110  int count = 1;
111  for (const KMime::Types::Mailbox &mailbox : mailboxes) {
112  if (count++ > MessageComposer::MessageComposerSettings::self()->maximumRecipients()) {
113  KMessageBox::sorry(this,
114  i18ncp("@info:status",
115  "Truncating recipients list to %2 of %1 entry.",
116  "Truncating recipients list to %2 of %1 entries.",
117  mailboxes.count(),
118  MessageComposer::MessageComposerSettings::self()->maximumRecipients()));
119  return true;
120  }
121  // Too many
122  if (addRecipient(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), type)) {
123  return true;
124  }
125  }
126  return false;
127 }
128 
129 Recipient::List RecipientsEditor::recipients() const
130 {
131  const QVector<MultiplyingLineData::Ptr> dataList = allData();
132  Recipient::List recList;
133  for (const MultiplyingLineData::Ptr &datum : dataList) {
134  Recipient::Ptr rec = qSharedPointerDynamicCast<Recipient>(datum);
135  if (!rec) {
136  continue;
137  }
138  recList << rec;
139  }
140  return recList;
141 }
142 
143 Recipient::Ptr RecipientsEditor::activeRecipient() const
144 {
145  return qSharedPointerDynamicCast<Recipient>(activeData());
146 }
147 
148 QString RecipientsEditor::recipientString(Recipient::Type type) const
149 {
150  return recipientStringList(type).join(QLatin1String(", "));
151 }
152 
153 QStringList RecipientsEditor::recipientStringList(Recipient::Type type) const
154 {
155  QStringList selectedRecipients;
156  for (const Recipient::Ptr &r : recipients()) {
157  if (r->type() == type) {
158  selectedRecipients << r->email();
159  }
160  }
161  return selectedRecipients;
162 }
163 
164 void RecipientsEditor::removeRecipient(const QString &recipient, Recipient::Type type)
165 {
166  // search a line which matches recipient and type
168  MultiplyingLine *line = nullptr;
169  while (it.hasNext()) {
170  line = it.next();
171  auto rec = qobject_cast<RecipientLineNG *>(line);
172  if (rec) {
173  if ((rec->recipient()->email() == recipient) && (rec->recipientType() == type)) {
174  break;
175  }
176  }
177  }
178  if (line) {
179  line->slotPropagateDeletion();
180  }
181 }
182 
183 void RecipientsEditor::saveDistributionList()
184 {
185  std::unique_ptr<MessageComposer::DistributionListDialog> dlg(new MessageComposer::DistributionListDialog(this));
186  dlg->setRecipients(recipients());
187  dlg->exec();
188 }
189 
190 void RecipientsEditor::selectRecipients()
191 {
192  d->mSideWidget->pickRecipient();
193 }
194 
196 {
197  d->mRecentAddressConfig = config;
198  if (config) {
199  const auto linesP{lines()};
200  for (auto line : linesP) {
201  auto rec = qobject_cast<RecipientLineNG *>(line);
202  if (rec) {
203  rec->setRecentAddressConfig(config);
204  }
205  }
206  }
207 }
208 
209 void RecipientsEditor::slotPickedRecipient(const Recipient &rec, bool &tooManyAddress)
210 {
211  const Recipient::Type t = rec.type();
212  tooManyAddress = addRecipient(rec.email(), t == Recipient::Undefined ? Recipient::To : t);
213  mModified = true;
214 }
215 
216 RecipientsPicker *RecipientsEditor::picker() const
217 {
218  return d->mSideWidget->picker();
219 }
220 
221 void RecipientsEditor::slotLineAdded(MultiplyingLine *line)
222 {
223  // subtract 1 here, because we want the number of lines
224  // before this line was added.
225  const int count = lines().size() - 1;
226  auto rec = qobject_cast<RecipientLineNG *>(line);
227  if (!rec) {
228  return;
229  }
230 
231  if (d->mRecentAddressConfig) {
232  rec->setRecentAddressConfig(d->mRecentAddressConfig);
233  }
234 
235  if (count > 0) {
236  if (count == 1) {
237  auto last_rec = qobject_cast<RecipientLineNG *>(lines().constFirst());
238  if (last_rec && (last_rec->recipientType() == Recipient::Bcc || last_rec->recipientType() == Recipient::ReplyTo)) {
239  rec->setRecipientType(Recipient::To);
240  } else {
241  rec->setRecipientType(Recipient::Cc);
242  }
243  } else {
244  auto last_rec = qobject_cast<RecipientLineNG *>(lines().at(lines().count() - 2));
245  if (last_rec) {
246  if (last_rec->recipientType() == Recipient::ReplyTo) {
247  rec->setRecipientType(Recipient::To);
248  } else {
249  rec->setRecipientType(last_rec->recipientType());
250  }
251  }
252  }
253  line->fixTabOrder(lines().constLast()->tabOut());
254  }
255  connect(rec, &RecipientLineNG::countChanged, this, &RecipientsEditor::slotCalculateTotal);
256 }
257 
258 void RecipientsEditor::slotLineDeleted(int pos)
259 {
260  bool atLeastOneToLine = false;
261  int firstCC = -1;
262  for (int i = pos, total = lines().count(); i < total; ++i) {
263  MultiplyingLine *line = lines().at(i);
264  auto rec = qobject_cast<RecipientLineNG *>(line);
265  if (rec) {
266  if (rec->recipientType() == Recipient::To) {
267  atLeastOneToLine = true;
268  } else if ((rec->recipientType() == Recipient::Cc) && (firstCC < 0)) {
269  firstCC = i;
270  }
271  }
272  }
273 
274  if (!atLeastOneToLine && (firstCC >= 0)) {
275  auto firstCCLine = qobject_cast<RecipientLineNG *>(lines().at(firstCC));
276  if (firstCCLine) {
277  firstCCLine->setRecipientType(Recipient::To);
278  }
279  }
280 
281  slotCalculateTotal();
282 }
283 
284 bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
285 {
286  if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit *>(object)) {
287  auto ke = static_cast<QKeyEvent *>(event);
288  // Treats comma or semicolon as email separator, will automatically move focus
289  // to a new line, basically preventing user from inputting more than one
290  // email address per line, which breaks our opportunistic crypto in composer
291  if (ke->key() == Qt::Key_Comma || (ke->key() == Qt::Key_Semicolon && MessageComposerSettings::self()->allowSemicolonAsAddressSeparator())) {
292  auto line = qobject_cast<RecipientLineNG *>(object->parent());
293  const auto split = KEmailAddress::splitAddressList(line->rawData() + QLatin1String(", "));
294  if (split.size() > 1) {
295  addRecipient(QString(), line->recipientType());
296  setFocusBottom();
297  return true;
298  }
299  }
300  } else if (event->type() == QEvent::FocusIn && qobject_cast<RecipientLineEdit *>(object)) {
301  Q_EMIT focusInRecipientLineEdit();
302  }
303 
304  return false;
305 }
306 
307 void RecipientsEditor::slotCalculateTotal()
308 {
309  // Prevent endless recursion when splitting recipient
310  if (d->mSkipTotal) {
311  return;
312  }
313  int empty = 0;
314  const auto currentLines = lines();
315  for (auto line : currentLines) {
316  auto rec = qobject_cast<RecipientLineNG *>(line);
317  if (rec) {
318  if (rec->isEmpty()) {
319  ++empty;
320  } else {
321  const int recipientsCount = rec->recipientsCount();
322  if (recipientsCount > 1) {
323  // Ensure we always have only one recipient per line
324  d->mSkipTotal = true;
325  Recipient::Ptr recipient = rec->recipient();
326  const auto split = KEmailAddress::splitAddressList(recipient->email());
327  bool maximumElementFound = false;
328  for (int i = 1 /* sic! */; i < split.count(); ++i) {
329  maximumElementFound = addRecipient(split[i], rec->recipientType());
330  if (maximumElementFound) {
331  break;
332  }
333  }
334  recipient->setEmail(split[0]);
335  rec->setData(recipient);
336  setFocusBottom(); // focus next empty entry
337  d->mSkipTotal = false;
338  if (maximumElementFound) {
339  return;
340  }
341  }
342  }
343  }
344  }
345  // We always want at least one empty line
346  if (empty == 0) {
347  addData({}, false);
348  }
349  int count = 0;
350  const auto linesP{lines()};
351  for (auto line : linesP) {
352  auto rec = qobject_cast<RecipientLineNG *>(line);
353  if (rec) {
354  if (!rec->isEmpty()) {
355  count++;
356  }
357  }
358  }
359  // update the side widget
360  d->mSideWidget->setTotal(count, lines().count());
361 }
362 
363 RecipientLineNG *RecipientsEditor::activeLine() const
364 {
365  MultiplyingLine *line = MultiplyingLineEditor::activeLine();
366  return qobject_cast<RecipientLineNG *>(line);
367 }
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.
PartitionTable::TableType type
void setRecentAddressConfig(KConfig *config)
Sets the config file used for storing recent addresses.
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
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 Sun Dec 5 2021 23:04:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.