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 QList<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::error(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 QList<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.isNull()) {
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  rec->setRecipientType(Recipient::To);
238  } else {
239  auto last_rec = qobject_cast<RecipientLineNG *>(lines().at(lines().count() - 2));
240  if (last_rec) {
241  if (last_rec->recipientType() == Recipient::ReplyTo) {
242  rec->setRecipientType(Recipient::To);
243  } else {
244  rec->setRecipientType(last_rec->recipientType());
245  }
246  }
247  }
248  line->fixTabOrder(lines().constLast()->tabOut());
249  }
250  connect(rec, &RecipientLineNG::countChanged, this, &RecipientsEditor::slotCalculateTotal);
251 }
252 
253 void RecipientsEditor::slotLineDeleted(int pos)
254 {
255  bool atLeastOneToLine = false;
256  int firstCC = -1;
257  for (int i = pos, total = lines().count(); i < total; ++i) {
258  MultiplyingLine *line = lines().at(i);
259  auto rec = qobject_cast<RecipientLineNG *>(line);
260  if (rec) {
261  if (rec->recipientType() == Recipient::To) {
262  atLeastOneToLine = true;
263  } else if ((rec->recipientType() == Recipient::Cc) && (firstCC < 0)) {
264  firstCC = i;
265  }
266  }
267  }
268 
269  if (!atLeastOneToLine && (firstCC >= 0)) {
270  auto firstCCLine = qobject_cast<RecipientLineNG *>(lines().at(firstCC));
271  if (firstCCLine) {
272  firstCCLine->setRecipientType(Recipient::To);
273  }
274  }
275 
276  slotCalculateTotal();
277 }
278 
279 bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
280 {
281  if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit *>(object)) {
282  auto ke = static_cast<QKeyEvent *>(event);
283  // Treats comma or semicolon as email separator, will automatically move focus
284  // to a new line, basically preventing user from inputting more than one
285  // email address per line, which breaks our opportunistic crypto in composer
286  if (ke->key() == Qt::Key_Comma || (ke->key() == Qt::Key_Semicolon && MessageComposerSettings::self()->allowSemicolonAsAddressSeparator())) {
287  auto line = qobject_cast<RecipientLineNG *>(object->parent());
288  const auto split = KEmailAddress::splitAddressList(line->rawData() + QLatin1String(", "));
289  if (split.size() > 1) {
290  addRecipient(QString(), line->recipientType());
291  setFocusBottom();
292  return true;
293  }
294  }
295  } else if (event->type() == QEvent::FocusIn && qobject_cast<RecipientLineEdit *>(object)) {
296  Q_EMIT focusInRecipientLineEdit();
297  }
298 
299  return false;
300 }
301 
302 void RecipientsEditor::slotCalculateTotal()
303 {
304  // Prevent endless recursion when splitting recipient
305  if (d->mSkipTotal) {
306  return;
307  }
308  int empty = 0;
309  const auto currentLines = lines();
310  for (auto line : currentLines) {
311  auto rec = qobject_cast<RecipientLineNG *>(line);
312  if (rec) {
313  if (rec->isEmpty()) {
314  ++empty;
315  } else {
316  const int recipientsCount = rec->recipientsCount();
317  if (recipientsCount > 1) {
318  // Ensure we always have only one recipient per line
319  d->mSkipTotal = true;
320  Recipient::Ptr recipient = rec->recipient();
321  const auto split = KEmailAddress::splitAddressList(recipient->email());
322  bool maximumElementFound = false;
323  for (int i = 1 /* sic! */; i < split.count(); ++i) {
324  maximumElementFound = addRecipient(split[i], rec->recipientType());
325  if (maximumElementFound) {
326  break;
327  }
328  }
329  recipient->setEmail(split[0]);
330  rec->setData(recipient);
331  setFocusBottom(); // focus next empty entry
332  d->mSkipTotal = false;
333  if (maximumElementFound) {
334  d->mSideWidget->setTotal(lines().count(), lines().count());
335  return;
336  }
337  }
338  }
339  }
340  }
341  // We always want at least one empty line
342  if (empty == 0) {
343  addData({}, false);
344  }
345  int count = 0;
346  const auto linesP{lines()};
347  for (auto line : linesP) {
348  auto rec = qobject_cast<RecipientLineNG *>(line);
349  if (rec) {
350  if (!rec->isEmpty()) {
351  count++;
352  }
353  }
354  }
355  // update the side widget
356  d->mSideWidget->setTotal(count, lines().count());
357 }
358 
359 RecipientLineNG *RecipientsEditor::activeLine() const
360 {
361  MultiplyingLine *line = MultiplyingLineEditor::activeLine();
362  return qobject_cast<RecipientLineNG *>(line);
363 }
364 
365 #include "moc_recipientseditor.cpp"
void setParent(QWidget *parent)
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
bool isNull() const const
Q_EMITQ_EMIT
The RecipientsEditor class.
virtual void fixTabOrder(QWidget *previous)=0
QLayout * layout() const const
virtual bool event(QEvent *event) override
bool hasNext() const const
void setRecentAddressConfig(KConfig *config)
Sets the config file used for storing recent addresses.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
KCODECS_EXPORT QStringList splitAddressList(const QString &aStr)
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
const T & next()
The Recipient class.
Definition: recipient.h:27
MultiplyingLineData::Ptr activeData() const
void removeRecipient(const QString &recipient, Recipient::Type type)
Removes the recipient provided it can be found and has the given type.
bool addRecipient(const QString &recipient, Recipient::Type type)
Adds a recipient (or multiple recipients) to one line of the editor.
QString join(const QString &separator) const const
Key_Comma
KSharedConfigPtr config()
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
void addWidget(QWidget *w)
The RecipientLineNG class.
Definition: recipientline.h:54
QList< MultiplyingLineData::Ptr > allData() const
The RecipientLineFactory class.
QObject * parent() const const
bool addData(const MultiplyingLineData::Ptr &data=MultiplyingLineData::Ptr(), bool showDialogBox=true)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Sep 22 2023 04:02:56 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.