Messagelib

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

KDE's Doxygen guidelines are available online.