File indexing completed on 2024-06-23 05:18:36

0001 /*
0002     SPDX-FileCopyrightText: 2010 Casey Link <unnamedrambler@gmail.com>
0003     SPDX-FileCopyrightText: 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0004 
0005     Refactored from earlier code by:
0006     SPDX-FileCopyrightText: 2010 Volker Krause <vkrause@kde.org>
0007     SPDX-FileCopyrightText: 2004 Cornelius Schumacher <schumacher@kde.org>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "recipientseditor.h"
0013 
0014 #include "recipient.h"
0015 #include "recipientseditorsidewidget.h"
0016 
0017 #include "distributionlistdialog.h"
0018 #include "settings/messagecomposersettings.h"
0019 
0020 #include "messagecomposer_debug.h"
0021 
0022 #include <KEmailAddress>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 #include <KMime/Headers>
0026 
0027 #include <QKeyEvent>
0028 #include <QLayout>
0029 
0030 using namespace MessageComposer;
0031 using namespace KPIM;
0032 
0033 RecipientLineFactory::RecipientLineFactory(QObject *parent)
0034     : KPIM::MultiplyingLineFactory(parent)
0035 {
0036 }
0037 
0038 KPIM::MultiplyingLine *RecipientLineFactory::newLine(QWidget *p)
0039 {
0040     auto line = new RecipientLineNG(p);
0041     if (qobject_cast<RecipientsEditor *>(parent())) {
0042         connect(line,
0043                 SIGNAL(addRecipient(RecipientLineNG *, QString)),
0044                 qobject_cast<RecipientsEditor *>(parent()),
0045                 SLOT(addRecipient(RecipientLineNG *, QString)));
0046     } else {
0047         qCWarning(MESSAGECOMPOSER_LOG) << "RecipientLineFactory::newLine: We can't connect to new line" << parent();
0048     }
0049     return line;
0050 }
0051 
0052 int RecipientLineFactory::maximumRecipients()
0053 {
0054     return MessageComposer::MessageComposerSettings::self()->maximumRecipients();
0055 }
0056 
0057 class MessageComposer::RecipientsEditorPrivate
0058 {
0059 public:
0060     RecipientsEditorPrivate() = default;
0061 
0062     KConfig *mRecentAddressConfig = nullptr;
0063     RecipientsEditorSideWidget *mSideWidget = nullptr;
0064     bool mSkipTotal = false;
0065 };
0066 
0067 RecipientsEditor::RecipientsEditor(QWidget *parent)
0068     : RecipientsEditor(new RecipientLineFactory(nullptr), parent)
0069 {
0070 }
0071 
0072 RecipientsEditor::RecipientsEditor(RecipientLineFactory *lineFactory, QWidget *parent)
0073     : MultiplyingLineEditor(lineFactory, parent)
0074     , d(new MessageComposer::RecipientsEditorPrivate)
0075 {
0076     factory()->setParent(this); // HACK: can't use 'this' above since it's not yet constructed at that point
0077     d->mSideWidget = new RecipientsEditorSideWidget(this, this);
0078 
0079     layout()->addWidget(d->mSideWidget);
0080 
0081     // Install global event filter and listen for keypress events for RecipientLineEdits.
0082     // Unfortunately we can't install ourselves directly as event filter for the edits,
0083     // because the RecipientLineEdit has its own event filter installed into QApplication
0084     // and so it would eat the event before it would reach us.
0085     qApp->installEventFilter(this);
0086 
0087     connect(d->mSideWidget, &RecipientsEditorSideWidget::pickedRecipient, this, &RecipientsEditor::slotPickedRecipient);
0088     connect(d->mSideWidget, &RecipientsEditorSideWidget::saveDistributionList, this, &RecipientsEditor::saveDistributionList);
0089 
0090     connect(this, &RecipientsEditor::lineAdded, this, &RecipientsEditor::slotLineAdded);
0091     connect(this, &RecipientsEditor::lineDeleted, this, &RecipientsEditor::slotLineDeleted);
0092 
0093     addData(); // one default line
0094 }
0095 
0096 RecipientsEditor::~RecipientsEditor() = default;
0097 
0098 bool RecipientsEditor::addRecipient(const QString &recipient, Recipient::Type type)
0099 {
0100     return addData(Recipient::Ptr(new Recipient(recipient, type)), false);
0101 }
0102 
0103 void RecipientsEditor::addRecipient(RecipientLineNG *line, const QString &recipient)
0104 {
0105     addRecipient(recipient, line->recipientType());
0106 }
0107 
0108 bool RecipientsEditor::setRecipientString(const QList<KMime::Types::Mailbox> &mailboxes, Recipient::Type type)
0109 {
0110     int count = 1;
0111     for (const KMime::Types::Mailbox &mailbox : mailboxes) {
0112         if (count++ > MessageComposer::MessageComposerSettings::self()->maximumRecipients()) {
0113             KMessageBox::error(this,
0114                                i18ncp("@info:status",
0115                                       "Truncating recipients list to %2 of %1 entry.",
0116                                       "Truncating recipients list to %2 of %1 entries.",
0117                                       mailboxes.count(),
0118                                       MessageComposer::MessageComposerSettings::self()->maximumRecipients()));
0119             return true;
0120         }
0121         // Too many
0122         if (addRecipient(mailbox.prettyAddress(KMime::Types::Mailbox::QuoteWhenNecessary), type)) {
0123             return true;
0124         }
0125     }
0126     return false;
0127 }
0128 
0129 Recipient::List RecipientsEditor::recipients() const
0130 {
0131     const QList<MultiplyingLineData::Ptr> dataList = allData();
0132     Recipient::List recList;
0133     for (const MultiplyingLineData::Ptr &datum : dataList) {
0134         Recipient::Ptr rec = qSharedPointerDynamicCast<Recipient>(datum);
0135         if (rec.isNull()) {
0136             continue;
0137         }
0138         recList << rec;
0139     }
0140     return recList;
0141 }
0142 
0143 Recipient::Ptr RecipientsEditor::activeRecipient() const
0144 {
0145     return qSharedPointerDynamicCast<Recipient>(activeData());
0146 }
0147 
0148 QString RecipientsEditor::recipientString(Recipient::Type type) const
0149 {
0150     return recipientStringList(type).join(QLatin1StringView(", "));
0151 }
0152 
0153 QStringList RecipientsEditor::recipientStringList(Recipient::Type type) const
0154 {
0155     QStringList selectedRecipients;
0156     for (const Recipient::Ptr &r : recipients()) {
0157         if (r->type() == type) {
0158             selectedRecipients << r->email();
0159         }
0160     }
0161     return selectedRecipients;
0162 }
0163 
0164 void RecipientsEditor::removeRecipient(const QString &recipient, Recipient::Type type)
0165 {
0166     // search a line which matches recipient and type
0167     QListIterator<MultiplyingLine *> it(lines());
0168     MultiplyingLine *line = nullptr;
0169     while (it.hasNext()) {
0170         line = it.next();
0171         auto rec = qobject_cast<RecipientLineNG *>(line);
0172         if (rec) {
0173             if ((rec->recipient()->email() == recipient) && (rec->recipientType() == type)) {
0174                 break;
0175             }
0176         }
0177     }
0178     if (line) {
0179         line->slotPropagateDeletion();
0180     }
0181 }
0182 
0183 void RecipientsEditor::saveDistributionList()
0184 {
0185     std::unique_ptr<MessageComposer::DistributionListDialog> dlg(new MessageComposer::DistributionListDialog(this));
0186     dlg->setRecipients(recipients());
0187     dlg->exec();
0188 }
0189 
0190 void RecipientsEditor::selectRecipients()
0191 {
0192     d->mSideWidget->pickRecipient();
0193 }
0194 
0195 void RecipientsEditor::setRecentAddressConfig(KConfig *config)
0196 {
0197     d->mRecentAddressConfig = config;
0198     if (config) {
0199         const auto linesP{lines()};
0200         for (auto line : linesP) {
0201             auto rec = qobject_cast<RecipientLineNG *>(line);
0202             if (rec) {
0203                 rec->setRecentAddressConfig(config);
0204             }
0205         }
0206     }
0207 }
0208 
0209 void RecipientsEditor::slotPickedRecipient(const Recipient &rec, bool &tooManyAddress)
0210 {
0211     const Recipient::Type t = rec.type();
0212     tooManyAddress = addRecipient(rec.email(), t == Recipient::Undefined ? Recipient::To : t);
0213     mModified = true;
0214 }
0215 
0216 RecipientsPicker *RecipientsEditor::picker() const
0217 {
0218     return d->mSideWidget->picker();
0219 }
0220 
0221 void RecipientsEditor::slotLineAdded(MultiplyingLine *line)
0222 {
0223     // subtract 1 here, because we want the number of lines
0224     // before this line was added.
0225     const int count = lines().size() - 1;
0226     auto rec = qobject_cast<RecipientLineNG *>(line);
0227     if (!rec) {
0228         return;
0229     }
0230 
0231     if (d->mRecentAddressConfig) {
0232         rec->setRecentAddressConfig(d->mRecentAddressConfig);
0233     }
0234 
0235     if (count > 0) {
0236         if (count == 1) {
0237             rec->setRecipientType(Recipient::To);
0238         } else {
0239             auto last_rec = qobject_cast<RecipientLineNG *>(lines().at(lines().count() - 2));
0240             if (last_rec) {
0241                 if (last_rec->recipientType() == Recipient::ReplyTo) {
0242                     rec->setRecipientType(Recipient::To);
0243                 } else {
0244                     rec->setRecipientType(last_rec->recipientType());
0245                 }
0246             }
0247         }
0248         line->fixTabOrder(lines().constLast()->tabOut());
0249     }
0250     connect(rec, &RecipientLineNG::countChanged, this, &RecipientsEditor::slotCalculateTotal);
0251 }
0252 
0253 void RecipientsEditor::slotLineDeleted(int pos)
0254 {
0255     bool atLeastOneToLine = false;
0256     int firstCC = -1;
0257     for (int i = pos, total = lines().count(); i < total; ++i) {
0258         MultiplyingLine *line = lines().at(i);
0259         auto rec = qobject_cast<RecipientLineNG *>(line);
0260         if (rec) {
0261             if (rec->recipientType() == Recipient::To) {
0262                 atLeastOneToLine = true;
0263             } else if ((rec->recipientType() == Recipient::Cc) && (firstCC < 0)) {
0264                 firstCC = i;
0265             }
0266         }
0267     }
0268 
0269     if (!atLeastOneToLine && (firstCC >= 0)) {
0270         auto firstCCLine = qobject_cast<RecipientLineNG *>(lines().at(firstCC));
0271         if (firstCCLine) {
0272             firstCCLine->setRecipientType(Recipient::To);
0273         }
0274     }
0275 
0276     slotCalculateTotal();
0277 }
0278 
0279 bool RecipientsEditor::eventFilter(QObject *object, QEvent *event)
0280 {
0281     if (event->type() == QEvent::KeyPress && qobject_cast<RecipientLineEdit *>(object)) {
0282         auto ke = static_cast<QKeyEvent *>(event);
0283         // Treats comma or semicolon as email separator, will automatically move focus
0284         // to a new line, basically preventing user from inputting more than one
0285         // email address per line, which breaks our opportunistic crypto in composer
0286         if (ke->key() == Qt::Key_Comma || (ke->key() == Qt::Key_Semicolon && MessageComposerSettings::self()->allowSemicolonAsAddressSeparator())) {
0287             auto line = qobject_cast<RecipientLineNG *>(object->parent());
0288             const auto split = KEmailAddress::splitAddressList(line->rawData() + QLatin1StringView(", "));
0289             if (split.size() > 1) {
0290                 addRecipient(QString(), line->recipientType());
0291                 setFocusBottom();
0292                 return true;
0293             }
0294         }
0295     } else if (event->type() == QEvent::FocusIn && qobject_cast<RecipientLineEdit *>(object)) {
0296         Q_EMIT focusInRecipientLineEdit();
0297     }
0298 
0299     return false;
0300 }
0301 
0302 void RecipientsEditor::slotCalculateTotal()
0303 {
0304     // Prevent endless recursion when splitting recipient
0305     if (d->mSkipTotal) {
0306         return;
0307     }
0308     int empty = 0;
0309     const auto currentLines = lines();
0310     for (auto line : currentLines) {
0311         auto rec = qobject_cast<RecipientLineNG *>(line);
0312         if (rec) {
0313             if (rec->isEmpty()) {
0314                 ++empty;
0315             } else {
0316                 const int recipientsCount = rec->recipientsCount();
0317                 if (recipientsCount > 1) {
0318                     // Ensure we always have only one recipient per line
0319                     d->mSkipTotal = true;
0320                     Recipient::Ptr recipient = rec->recipient();
0321                     const auto split = KEmailAddress::splitAddressList(recipient->email());
0322                     bool maximumElementFound = false;
0323                     for (int i = 1 /* sic! */; i < split.count(); ++i) {
0324                         maximumElementFound = addRecipient(split[i], rec->recipientType());
0325                         if (maximumElementFound) {
0326                             break;
0327                         }
0328                     }
0329                     recipient->setEmail(split[0]);
0330                     rec->setData(recipient);
0331                     setFocusBottom(); // focus next empty entry
0332                     d->mSkipTotal = false;
0333                     if (maximumElementFound) {
0334                         d->mSideWidget->setTotal(lines().count(), lines().count());
0335                         return;
0336                     }
0337                 }
0338             }
0339         }
0340     }
0341     // We always want at least one empty line
0342     if (empty == 0) {
0343         addData({}, false);
0344     }
0345     int count = 0;
0346     const auto linesP{lines()};
0347     for (auto line : linesP) {
0348         auto rec = qobject_cast<RecipientLineNG *>(line);
0349         if (rec) {
0350             if (!rec->isEmpty()) {
0351                 count++;
0352             }
0353         }
0354     }
0355     // update the side widget
0356     d->mSideWidget->setTotal(count, lines().count());
0357 }
0358 
0359 RecipientLineNG *RecipientsEditor::activeLine() const
0360 {
0361     MultiplyingLine *line = MultiplyingLineEditor::activeLine();
0362     return qobject_cast<RecipientLineNG *>(line);
0363 }
0364 
0365 #include "moc_recipientseditor.cpp"