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 "recipientline.h"
16 #include "recipientseditorsidewidget.h"
17 
18 #include "settings/messagecomposersettings.h"
19 #include "distributionlistdialog.h"
20 
21 #include "messagecomposer_debug.h"
22 
23 #include <KMime/Headers>
24 #include <KLocalizedString>
25 #include <KMessageBox>
26 #include <KEmailAddress>
27 
28 #include <QLayout>
29 #include <QKeyEvent>
30 
31 using namespace MessageComposer;
32 using namespace KPIM;
33 
34 RecipientLineFactory::RecipientLineFactory(QObject *parent)
35  : KPIM::MultiplyingLineFactory(parent)
36 {
37 }
38 
39 KPIM::MultiplyingLine *RecipientLineFactory::newLine(QWidget *p)
40 {
41  RecipientLineNG *line = new RecipientLineNG(p);
42  if (qobject_cast<RecipientsEditor *>(parent())) {
43  connect(line, SIGNAL(addRecipient(RecipientLineNG*,QString)), qobject_cast<RecipientsEditor *>(parent()), SLOT(addRecipient(RecipientLineNG*,QString)));
44  } else {
45  qCWarning(MESSAGECOMPOSER_LOG) << "RecipientLineFactory::newLine: We can't connect to new line" << parent();
46  }
47  return line;
48 }
49 
50 int RecipientLineFactory::maximumRecipients()
51 {
52  return MessageComposer::MessageComposerSettings::self()->maximumRecipients();
53 }
54 
55 class MessageComposer::RecipientsEditorPrivate
56 {
57 public:
58  RecipientsEditorPrivate()
59  {
60  }
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()
97 {
98  delete d;
99 }
100 
101 bool RecipientsEditor::addRecipient(const QString &recipient, Recipient::Type type)
102 {
103  return addData(Recipient::Ptr(new Recipient(recipient, type)));
104 }
105 
106 void RecipientsEditor::addRecipient(RecipientLineNG *line, const QString &recipient)
107 {
108  addRecipient(recipient, line->recipientType());
109 }
110 
111 void RecipientsEditor::setRecipientString(const QVector< KMime::Types::Mailbox > &mailboxes, Recipient::Type type)
112 {
113  int count = 1;
114 
115  for (const KMime::Types::Mailbox &mailbox : mailboxes) {
116  if (count++ > MessageComposer::MessageComposerSettings::self()->maximumRecipients()) {
117  KMessageBox::sorry(this,
118  i18ncp("@info:status",
119  "Truncating recipients list to %2 of %1 entry.",
120  "Truncating recipients list to %2 of %1 entries.",
121  mailboxes.count(),
122  MessageComposer::MessageComposerSettings::self()->maximumRecipients()));
123  break;
124  }
125  addRecipient(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), type);
126  }
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();
172  if (rec) {
173  if ((rec->recipient()->email() == recipient)
174  && (rec->recipientType() == type)) {
175  break;
176  }
177  }
178  }
179  if (line) {
180  line->slotPropagateDeletion();
181  }
182 }
183 
184 void RecipientsEditor::saveDistributionList()
185 {
186  std::unique_ptr<MessageComposer::DistributionListDialog> dlg(new MessageComposer::DistributionListDialog(this));
187  dlg->setRecipients(recipients());
188  dlg->exec();
189 }
190 
191 void RecipientsEditor::selectRecipients()
192 {
193  d->mSideWidget->pickRecipient();
194 }
195 
197 {
198  d->mRecentAddressConfig = config;
199  if (config) {
200  MultiplyingLine *line;
201  foreach (line, lines()) {
203  if (rec) {
204  rec->setRecentAddressConfig(config);
205  }
206  }
207  }
208 }
209 
210 void MessageComposer::RecipientsEditor::slotPickedRecipient(const Recipient &rec, bool &tooManyAddress)
211 {
212  Recipient::Type t = rec.type();
213  tooManyAddress = addRecipient(rec.email(), t == Recipient::Undefined ? Recipient::To : t);
214  mModified = true;
215 }
216 
217 RecipientsPicker *RecipientsEditor::picker() const
218 {
219  return d->mSideWidget->picker();
220 }
221 
222 void RecipientsEditor::slotLineAdded(MultiplyingLine *line)
223 {
224  // subtract 1 here, because we want the number of lines
225  // before this line was added.
226  int count = lines().size() - 1;
228  if (!rec) {
229  return;
230  }
231 
232  if (d->mRecentAddressConfig) {
233  rec->setRecentAddressConfig(d->mRecentAddressConfig);
234  }
235 
236  if (count > 0) {
237  if (count == 1) {
238  RecipientLineNG *last_rec = qobject_cast<RecipientLineNG *>(lines().constFirst());
239  if (last_rec && (last_rec->recipientType() == Recipient::Bcc || last_rec->recipientType() == Recipient::ReplyTo)) {
240  rec->setRecipientType(Recipient::To);
241  } else {
242  rec->setRecipientType(Recipient::Cc);
243  }
244  } else {
245  RecipientLineNG *last_rec = qobject_cast<RecipientLineNG *>(lines().at(lines().count() - 2));
246  if (last_rec) {
247  if (last_rec->recipientType() == Recipient::ReplyTo) {
248  rec->setRecipientType(Recipient::To);
249  } else {
250  rec->setRecipientType(last_rec->recipientType());
251  }
252  }
253  }
254  line->fixTabOrder(lines().constLast()->tabOut());
255  }
256  connect(rec, &RecipientLineNG::countChanged, this, &RecipientsEditor::slotCalculateTotal);
257 }
258 
259 void RecipientsEditor::slotLineDeleted(int pos)
260 {
261  bool atLeastOneToLine = false;
262  int firstCC = -1;
263  for (int i = pos, total = lines().count(); i < total; ++i) {
264  MultiplyingLine *line = lines().at(i);
266  if (rec) {
267  if (rec->recipientType() == Recipient::To) {
268  atLeastOneToLine = true;
269  } else if ((rec->recipientType() == Recipient::Cc) && (firstCC < 0)) {
270  firstCC = i;
271  }
272  }
273  }
274 
275  if (!atLeastOneToLine && (firstCC >= 0)) {
276  RecipientLineNG *firstCCLine = qobject_cast<RecipientLineNG *>(lines().at(firstCC));
277  if (firstCCLine) {
278  firstCCLine->setRecipientType(Recipient::To);
279  }
280  }
281 
282  slotCalculateTotal();
283 }
284 
285 bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
286 {
287  if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit *>(object)) {
288  auto ke = static_cast<QKeyEvent *>(event);
289  // Treats comma or semicolon as email separator, will automatically move focus
290  // to a new line, basically preventing user from inputting more than one
291  // email address per line, which breaks our opportunistic crypto in composer
292  if (ke->key() == Qt::Key_Comma || (
293  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  }
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  MultiplyingLine *line = nullptr;
315  foreach (line, lines()) {
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();
348  }
349  int count = 0;
350  foreach (line, lines()) {
352  if (rec) {
353  if (!rec->isEmpty()) {
354  count++;
355  }
356  }
357  }
358  // update the side widget
359  d->mSideWidget->setTotal(count, lines().count());
360 }
361 
362 RecipientLineNG *RecipientsEditor::activeLine() const
363 {
364  MultiplyingLine *line = MultiplyingLineEditor::activeLine();
365  return qobject_cast<RecipientLineNG *>(line);
366 }
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
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-2020 The KDE developers.
Generated on Sat Jul 4 2020 23:13:33 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.