recipientseditor.cpp 12 KB
Newer Older
1
/*
2
3
    SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com>
    SPDX-FileCopyrightText: 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
4
5

    Refactored from earlier code by:
6
7
8
9
    SPDX-FileCopyrightText: 2010 Volker Krause <vkrause@kde.org>
    SPDX-FileCopyrightText: 2004 Cornelius Schumacher <schumacher@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
10
11
12
13
14
15
16
*/

#include "recipientseditor.h"

#include "recipient.h"
#include "recipientseditorsidewidget.h"

Laurent Montel's avatar
Laurent Montel committed
17
#include "distributionlistdialog.h"
Laurent Montel's avatar
Laurent Montel committed
18
#include "settings/messagecomposersettings.h"
19
20
21

#include "messagecomposer_debug.h"

Laurent Montel's avatar
Laurent Montel committed
22
#include <KEmailAddress>
23
24
#include <KLocalizedString>
#include <KMessageBox>
Laurent Montel's avatar
Laurent Montel committed
25
#include <KMime/Headers>
26

27
#include <QKeyEvent>
Laurent Montel's avatar
Laurent Montel committed
28
#include <QLayout>
29
30
31
32
33
34
35
36
37
38
39

using namespace MessageComposer;
using namespace KPIM;

RecipientLineFactory::RecipientLineFactory(QObject *parent)
    : KPIM::MultiplyingLineFactory(parent)
{
}

KPIM::MultiplyingLine *RecipientLineFactory::newLine(QWidget *p)
{
Laurent Montel's avatar
Laurent Montel committed
40
    auto line = new RecipientLineNG(p);
41
    if (qobject_cast<RecipientsEditor *>(parent())) {
Laurent Montel's avatar
Laurent Montel committed
42
43
44
45
        connect(line,
                SIGNAL(addRecipient(RecipientLineNG *, QString)),
                qobject_cast<RecipientsEditor *>(parent()),
                SLOT(addRecipient(RecipientLineNG *, QString)));
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    } else {
        qCWarning(MESSAGECOMPOSER_LOG) << "RecipientLineFactory::newLine: We can't connect to new line" << parent();
    }
    return line;
}

int RecipientLineFactory::maximumRecipients()
{
    return MessageComposer::MessageComposerSettings::self()->maximumRecipients();
}

class MessageComposer::RecipientsEditorPrivate
{
public:
    RecipientsEditorPrivate()
    {
    }
Laurent Montel's avatar
Laurent Montel committed
63

Laurent Montel's avatar
Laurent Montel committed
64
65
66
    KConfig *mRecentAddressConfig = nullptr;
    RecipientsEditorSideWidget *mSideWidget = nullptr;
    bool mSkipTotal = false;
67
68
69
};

RecipientsEditor::RecipientsEditor(QWidget *parent)
Laurent Montel's avatar
Laurent Montel committed
70
    : RecipientsEditor(new RecipientLineFactory(nullptr), parent)
71
72
73
74
{
}

RecipientsEditor::RecipientsEditor(RecipientLineFactory *lineFactory, QWidget *parent)
Laurent Montel's avatar
Laurent Montel committed
75
76
    : MultiplyingLineEditor(lineFactory, parent)
    , d(new MessageComposer::RecipientsEditorPrivate)
77
{
Laurent Montel's avatar
Laurent Montel committed
78
    factory()->setParent(this); // HACK: can't use 'this' above since it's not yet constructed at that point
79
80
81
82
    d->mSideWidget = new RecipientsEditorSideWidget(this, this);

    layout()->addWidget(d->mSideWidget);

83
84
85
86
87
88
    // Install global event filter and listen for keypress events for RecipientLineEdits.
    // Unfortunately we can't install ourselves directly as event filter for the edits,
    // because the RecipientLineEdit has its own event filter installed into QApplication
    // and so it would eat the event before it would reach us.
    qApp->installEventFilter(this);

89
90
91
92
93
94
    connect(d->mSideWidget, &RecipientsEditorSideWidget::pickedRecipient, this, &RecipientsEditor::slotPickedRecipient);
    connect(d->mSideWidget, &RecipientsEditorSideWidget::saveDistributionList, this, &RecipientsEditor::saveDistributionList);

    connect(this, &RecipientsEditor::lineAdded, this, &RecipientsEditor::slotLineAdded);
    connect(this, &RecipientsEditor::lineDeleted, this, &RecipientsEditor::slotLineDeleted);

Yuri Chornoivan's avatar
Yuri Chornoivan committed
95
    addData(); // one default line
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
}

RecipientsEditor::~RecipientsEditor()
{
    delete d;
}

bool RecipientsEditor::addRecipient(const QString &recipient, Recipient::Type type)
{
    return addData(Recipient::Ptr(new Recipient(recipient, type)));
}

void RecipientsEditor::addRecipient(RecipientLineNG *line, const QString &recipient)
{
    addRecipient(recipient, line->recipientType());
}

Laurent Montel's avatar
Laurent Montel committed
113
void RecipientsEditor::setRecipientString(const QVector<KMime::Types::Mailbox> &mailboxes, Recipient::Type type)
114
115
116
{
    int count = 1;

Laurent Montel's avatar
Laurent Montel committed
117
    for (const KMime::Types::Mailbox &mailbox : mailboxes) {
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
        if (count++ > MessageComposer::MessageComposerSettings::self()->maximumRecipients()) {
            KMessageBox::sorry(this,
                               i18ncp("@info:status",
                                      "Truncating recipients list to %2 of %1 entry.",
                                      "Truncating recipients list to %2 of %1 entries.",
                                      mailboxes.count(),
                                      MessageComposer::MessageComposerSettings::self()->maximumRecipients()));
            break;
        }
        addRecipient(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), type);
    }
}

Recipient::List RecipientsEditor::recipients() const
{
Laurent Montel's avatar
Laurent Montel committed
133
    const QVector<MultiplyingLineData::Ptr> dataList = allData();
134
    Recipient::List recList;
Laurent Montel's avatar
Laurent Montel committed
135
    for (const MultiplyingLineData::Ptr &datum : dataList) {
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
        Recipient::Ptr rec = qSharedPointerDynamicCast<Recipient>(datum);
        if (!rec) {
            continue;
        }
        recList << rec;
    }
    return recList;
}

Recipient::Ptr RecipientsEditor::activeRecipient() const
{
    return qSharedPointerDynamicCast<Recipient>(activeData());
}

QString RecipientsEditor::recipientString(Recipient::Type type) const
{
152
    return recipientStringList(type).join(QLatin1String(", "));
153
154
155
156
157
}

QStringList RecipientsEditor::recipientStringList(Recipient::Type type) const
{
    QStringList selectedRecipients;
Laurent Montel's avatar
Laurent Montel committed
158
    for (const Recipient::Ptr &r : recipients()) {
159
160
161
162
163
164
165
166
167
168
169
        if (r->type() == type) {
            selectedRecipients << r->email();
        }
    }
    return selectedRecipients;
}

void RecipientsEditor::removeRecipient(const QString &recipient, Recipient::Type type)
{
    // search a line which matches recipient and type
    QListIterator<MultiplyingLine *> it(lines());
Laurent Montel's avatar
Laurent Montel committed
170
    MultiplyingLine *line = nullptr;
171
172
    while (it.hasNext()) {
        line = it.next();
Laurent Montel's avatar
Laurent Montel committed
173
        auto rec = qobject_cast<RecipientLineNG *>(line);
174
        if (rec) {
Laurent Montel's avatar
Laurent Montel committed
175
            if ((rec->recipient()->email() == recipient) && (rec->recipientType() == type)) {
176
177
178
179
180
181
182
183
184
185
186
                break;
            }
        }
    }
    if (line) {
        line->slotPropagateDeletion();
    }
}

void RecipientsEditor::saveDistributionList()
{
Laurent Montel's avatar
Laurent Montel committed
187
    std::unique_ptr<MessageComposer::DistributionListDialog> dlg(new MessageComposer::DistributionListDialog(this));
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    dlg->setRecipients(recipients());
    dlg->exec();
}

void RecipientsEditor::selectRecipients()
{
    d->mSideWidget->pickRecipient();
}

void MessageComposer::RecipientsEditor::setRecentAddressConfig(KConfig *config)
{
    d->mRecentAddressConfig = config;
    if (config) {
        MultiplyingLine *line;
        foreach (line, lines()) {
Laurent Montel's avatar
Laurent Montel committed
203
            auto rec = qobject_cast<RecipientLineNG *>(line);
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
            if (rec) {
                rec->setRecentAddressConfig(config);
            }
        }
    }
}

void MessageComposer::RecipientsEditor::slotPickedRecipient(const Recipient &rec, bool &tooManyAddress)
{
    Recipient::Type t = rec.type();
    tooManyAddress = addRecipient(rec.email(), t == Recipient::Undefined ? Recipient::To : t);
    mModified = true;
}

RecipientsPicker *RecipientsEditor::picker() const
{
    return d->mSideWidget->picker();
}

void RecipientsEditor::slotLineAdded(MultiplyingLine *line)
{
    // subtract 1 here, because we want the number of lines
    // before this line was added.
    int count = lines().size() - 1;
Laurent Montel's avatar
Laurent Montel committed
228
    auto rec = qobject_cast<RecipientLineNG *>(line);
229
230
231
232
233
234
235
236
237
238
    if (!rec) {
        return;
    }

    if (d->mRecentAddressConfig) {
        rec->setRecentAddressConfig(d->mRecentAddressConfig);
    }

    if (count > 0) {
        if (count == 1) {
Laurent Montel's avatar
Laurent Montel committed
239
            auto last_rec = qobject_cast<RecipientLineNG *>(lines().constFirst());
240
            if (last_rec && (last_rec->recipientType() == Recipient::Bcc || last_rec->recipientType() == Recipient::ReplyTo)) {
241
242
243
244
245
                rec->setRecipientType(Recipient::To);
            } else {
                rec->setRecipientType(Recipient::Cc);
            }
        } else {
Laurent Montel's avatar
Laurent Montel committed
246
            auto last_rec = qobject_cast<RecipientLineNG *>(lines().at(lines().count() - 2));
247
            if (last_rec) {
248
249
250
251
252
                if (last_rec->recipientType() == Recipient::ReplyTo) {
                    rec->setRecipientType(Recipient::To);
                } else {
                    rec->setRecipientType(last_rec->recipientType());
                }
253
254
            }
        }
Laurent Montel's avatar
Laurent Montel committed
255
        line->fixTabOrder(lines().constLast()->tabOut());
256
257
258
259
260
261
262
263
    }
    connect(rec, &RecipientLineNG::countChanged, this, &RecipientsEditor::slotCalculateTotal);
}

void RecipientsEditor::slotLineDeleted(int pos)
{
    bool atLeastOneToLine = false;
    int firstCC = -1;
Laurent Montel's avatar
Laurent Montel committed
264
    for (int i = pos, total = lines().count(); i < total; ++i) {
265
        MultiplyingLine *line = lines().at(i);
Laurent Montel's avatar
Laurent Montel committed
266
        auto rec = qobject_cast<RecipientLineNG *>(line);
267
268
269
270
271
272
273
274
275
276
        if (rec) {
            if (rec->recipientType() == Recipient::To) {
                atLeastOneToLine = true;
            } else if ((rec->recipientType() == Recipient::Cc) && (firstCC < 0)) {
                firstCC = i;
            }
        }
    }

    if (!atLeastOneToLine && (firstCC >= 0)) {
Laurent Montel's avatar
Laurent Montel committed
277
        auto firstCCLine = qobject_cast<RecipientLineNG *>(lines().at(firstCC));
278
279
280
281
282
283
284
285
        if (firstCCLine) {
            firstCCLine->setRecipientType(Recipient::To);
        }
    }

    slotCalculateTotal();
}

286
287
bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
{
Laurent Montel's avatar
Laurent Montel committed
288
289
    if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit *>(object)) {
        auto ke = static_cast<QKeyEvent *>(event);
290
291
292
        // Treats comma or semicolon as email separator, will automatically move focus
        // to a new line, basically preventing user from inputting more than one
        // email address per line, which breaks our opportunistic crypto in composer
Laurent Montel's avatar
Laurent Montel committed
293
        if (ke->key() == Qt::Key_Comma || (ke->key() == Qt::Key_Semicolon && MessageComposerSettings::self()->allowSemicolonAsAddressSeparator())) {
Laurent Montel's avatar
Laurent Montel committed
294
            auto line = qobject_cast<RecipientLineNG *>(object->parent());
295
296
297
298
299
300
301
            const auto split = KEmailAddress::splitAddressList(line->rawData() + QLatin1String(", "));
            if (split.size() > 1) {
                addRecipient(QString(), line->recipientType());
                setFocusBottom();
                return true;
            }
        }
302
303
    } else if (event->type() == QEvent::FocusIn && qobject_cast<RecipientLineEdit *>(object)) {
        Q_EMIT focusInRecipientLineEdit();
304
305
306
307
308
    }

    return false;
}

309
310
void RecipientsEditor::slotCalculateTotal()
{
311
312
313
314
    // Prevent endless recursion when splitting recipient
    if (d->mSkipTotal) {
        return;
    }
315
316
    int empty = 0;
    MultiplyingLine *line = nullptr;
317
    foreach (line, lines()) {
Laurent Montel's avatar
Laurent Montel committed
318
        auto rec = qobject_cast<RecipientLineNG *>(line);
319
320
321
322
        if (rec) {
            if (rec->isEmpty()) {
                ++empty;
            } else {
323
324
325
326
327
328
                const int recipientsCount = rec->recipientsCount();
                if (recipientsCount > 1) {
                    // Ensure we always have only one recipient per line
                    d->mSkipTotal = true;
                    Recipient::Ptr recipient = rec->recipient();
                    const auto split = KEmailAddress::splitAddressList(recipient->email());
329
                    bool maximumElementFound = false;
330
                    for (int i = 1 /* sic! */; i < split.count(); ++i) {
331
332
333
334
                        maximumElementFound = addRecipient(split[i], rec->recipientType());
                        if (maximumElementFound) {
                            break;
                        }
335
336
337
338
339
                    }
                    recipient->setEmail(split[0]);
                    rec->setData(recipient);
                    setFocusBottom(); // focus next empty entry
                    d->mSkipTotal = false;
340
341
342
                    if (maximumElementFound) {
                        return;
                    }
343
                }
344
345
346
347
348
349
350
            }
        }
    }
    // We always want at least one empty line
    if (empty == 0) {
        addData();
    }
351
352
    int count = 0;
    foreach (line, lines()) {
Laurent Montel's avatar
Laurent Montel committed
353
        auto rec = qobject_cast<RecipientLineNG *>(line);
354
355
356
357
358
359
        if (rec) {
            if (!rec->isEmpty()) {
                count++;
            }
        }
    }
360
361
362
363
364
365
366
    // update the side widget
    d->mSideWidget->setTotal(count, lines().count());
}

RecipientLineNG *RecipientsEditor::activeLine() const
{
    MultiplyingLine *line = MultiplyingLineEditor::activeLine();
Laurent Montel's avatar
Laurent Montel committed
367
    return qobject_cast<RecipientLineNG *>(line);
368
}