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"