File indexing completed on 2025-01-05 04:58:20

0001 /*
0002   SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 
0006 */
0007 
0008 #include "blacklistbalooemailcompletionwidget.h"
0009 #include "blacklistbalooemaillist.h"
0010 #include "blacklistbalooemailsearchjob.h"
0011 #include "blacklistbalooemailutil.h"
0012 #include "blacklistbalooemailwarning.h"
0013 #include <KLineEditEventHandler>
0014 
0015 #include "pimcommonakonadi_debug.h"
0016 #include <KConfigGroup>
0017 #include <KListWidgetSearchLine>
0018 #include <KLocalizedString>
0019 #include <KSharedConfig>
0020 #include <QHBoxLayout>
0021 #include <QLabel>
0022 #include <QLineEdit>
0023 #include <QMenu>
0024 #include <QPushButton>
0025 #include <QVBoxLayout>
0026 
0027 using namespace PimCommon;
0028 BlackListBalooEmailCompletionWidget::BlackListBalooEmailCompletionWidget(QWidget *parent)
0029     : QWidget(parent)
0030     , mNumberOfEmailsFound(new QLabel(this))
0031     , mSearchLineEdit(new QLineEdit(this))
0032     , mExcludeDomainLineEdit(new QLineEdit(this))
0033     , mEmailList(new BlackListBalooEmailList(this))
0034     , mSearchButton(new QPushButton(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Search"), this))
0035     , mSelectButton(new QPushButton(i18n("&Select"), this))
0036     , mUnselectButton(new QPushButton(i18n("&Unselect"), this))
0037     , mShowAllBlackListedEmails(new QPushButton(i18n("Show Blacklisted Emails"), this))
0038     , mMoreResult(new QLabel(i18n("<qt><a href=\"more_result\">More result...</a></qt>"), this))
0039     , mBlackListWarning(new BlackListBalooEmailWarning(this))
0040     , mExcludeEmailFromRegularExpressionLineEdit(new QLineEdit(this))
0041 {
0042     auto mainLayout = new QVBoxLayout(this);
0043 
0044     auto searchLayout = new QHBoxLayout;
0045     mainLayout->addLayout(searchLayout);
0046 
0047     auto lab = new QLabel(i18n("Search email:"), this);
0048     lab->setObjectName(QLatin1StringView("search_label"));
0049     searchLayout->addWidget(lab);
0050 
0051     mSearchLineEdit->setPlaceholderText(i18n("Research is done from 3 characters"));
0052     mSearchLineEdit->setFocus();
0053     mSearchLineEdit->setClearButtonEnabled(true);
0054     KLineEditEventHandler::catchReturnKey(mSearchLineEdit);
0055     mSearchLineEdit->setObjectName(QLatin1StringView("search_lineedit"));
0056     connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &BlackListBalooEmailCompletionWidget::slotCheckIfUpdateBlackListIsNeeded);
0057     searchLayout->addWidget(mSearchLineEdit);
0058 
0059     mSearchButton->setObjectName(QLatin1StringView("search_button"));
0060     connect(mSearchButton, &QAbstractButton::clicked, this, &BlackListBalooEmailCompletionWidget::slotCheckIfUpdateBlackListIsNeeded);
0061     mSearchButton->setEnabled(false);
0062     searchLayout->addWidget(mSearchButton);
0063 
0064     mShowAllBlackListedEmails->setObjectName(QLatin1StringView("show_blacklisted_email_button"));
0065     connect(mShowAllBlackListedEmails, &QAbstractButton::clicked, this, &BlackListBalooEmailCompletionWidget::slotShowAllBlacklistedEmail);
0066     searchLayout->addWidget(mShowAllBlackListedEmails);
0067 
0068     mEmailList->setObjectName(QLatin1StringView("email_list"));
0069     mainLayout->addWidget(mEmailList);
0070 
0071     mBlackListWarning->setObjectName(QLatin1StringView("backlistwarning"));
0072     connect(mBlackListWarning, &BlackListBalooEmailWarning::newSearch, this, &BlackListBalooEmailCompletionWidget::slotSearch);
0073     connect(mBlackListWarning, &BlackListBalooEmailWarning::saveChanges, this, &BlackListBalooEmailCompletionWidget::slotSaveChanges);
0074     mainLayout->addWidget(mBlackListWarning);
0075 
0076     auto searchLineLayout = new QHBoxLayout;
0077     mainLayout->addLayout(searchLineLayout);
0078 
0079     auto selectElementLayout = new QHBoxLayout;
0080     searchLineLayout->addLayout(selectElementLayout);
0081 
0082     mSelectButton->setObjectName(QLatin1StringView("select_email"));
0083     connect(mSelectButton, &QAbstractButton::clicked, this, &BlackListBalooEmailCompletionWidget::slotSelectEmails);
0084     selectElementLayout->addWidget(mSelectButton);
0085 
0086     mUnselectButton->setObjectName(QLatin1StringView("unselect_email"));
0087     connect(mUnselectButton, &QAbstractButton::clicked, this, &BlackListBalooEmailCompletionWidget::slotUnselectEmails);
0088     selectElementLayout->addWidget(mUnselectButton);
0089 
0090     mMoreResult->setObjectName(QLatin1StringView("moreresultlabel"));
0091     selectElementLayout->addWidget(mMoreResult);
0092 
0093     mMoreResult->setContextMenuPolicy(Qt::NoContextMenu);
0094     connect(mMoreResult, &QLabel::linkActivated, this, &BlackListBalooEmailCompletionWidget::slotLinkClicked);
0095     mMoreResult->setVisible(false);
0096     selectElementLayout->addStretch(1);
0097 
0098     connect(mSearchLineEdit, &QLineEdit::textChanged, this, &BlackListBalooEmailCompletionWidget::slotSearchLineEditChanged);
0099 
0100     mSearchInResultLineEdit = new KListWidgetSearchLine(this, mEmailList);
0101     mSearchInResultLineEdit->setObjectName(QLatin1StringView("searchinresultlineedit"));
0102     mSearchInResultLineEdit->setClearButtonEnabled(true);
0103     mSearchInResultLineEdit->setPlaceholderText(i18n("Search in result..."));
0104     KLineEditEventHandler::catchReturnKey(mSearchInResultLineEdit);
0105 
0106     searchLineLayout->addStretch(0);
0107     mNumberOfEmailsFound->setObjectName(QLatin1StringView("numberofemailsfound"));
0108 
0109     searchLineLayout->addWidget(mNumberOfEmailsFound, 1);
0110     searchLineLayout->addWidget(mSearchInResultLineEdit);
0111 
0112     auto excludeDomainLayout = new QHBoxLayout;
0113     excludeDomainLayout->setContentsMargins({});
0114     mainLayout->addLayout(excludeDomainLayout);
0115 
0116     auto excludeDomainLabel = new QLabel(i18n("Exclude domain names:"), this);
0117     excludeDomainLabel->setObjectName(QLatin1StringView("domain_label"));
0118     excludeDomainLayout->addWidget(excludeDomainLabel);
0119 
0120     excludeDomainLayout->addWidget(mExcludeDomainLineEdit);
0121     mExcludeDomainLineEdit->setObjectName(QLatin1StringView("domain_lineedit"));
0122     mExcludeDomainLineEdit->setClearButtonEnabled(true);
0123     KLineEditEventHandler::catchReturnKey(mExcludeDomainLineEdit);
0124     mExcludeDomainLineEdit->setPlaceholderText(i18n("Separate domain with \'%1\'", QLatin1Char(',')));
0125 
0126     auto excludeEmailRegularExpressionLayout = new QHBoxLayout;
0127     excludeEmailRegularExpressionLayout->setContentsMargins({});
0128     mainLayout->addLayout(excludeEmailRegularExpressionLayout);
0129 
0130     auto excludeEmailRegularExpressionLabel = new QLabel(i18n("Exclude email with Regular Expression:"), this);
0131     excludeEmailRegularExpressionLabel->setObjectName(QLatin1StringView("email_regularexpression_label"));
0132     excludeEmailRegularExpressionLayout->addWidget(excludeEmailRegularExpressionLabel);
0133 
0134     excludeEmailRegularExpressionLayout->addWidget(mExcludeEmailFromRegularExpressionLineEdit);
0135     mExcludeEmailFromRegularExpressionLineEdit->setObjectName(QLatin1StringView("exclude_email_lineedit"));
0136     mExcludeEmailFromRegularExpressionLineEdit->setClearButtonEnabled(true);
0137     KLineEditEventHandler::catchReturnKey(mExcludeEmailFromRegularExpressionLineEdit);
0138     mExcludeEmailFromRegularExpressionLineEdit->setPlaceholderText(i18n("Separate regular expression with \'%1\'", QLatin1Char(',')));
0139 
0140     connect(mEmailList, &QListWidget::itemSelectionChanged, this, &BlackListBalooEmailCompletionWidget::slotSelectionChanged);
0141     slotSelectionChanged();
0142     connect(mEmailList, &QListWidget::customContextMenuRequested, this, &BlackListBalooEmailCompletionWidget::slotCustomContextMenuRequested);
0143 }
0144 
0145 BlackListBalooEmailCompletionWidget::~BlackListBalooEmailCompletionWidget() = default;
0146 
0147 void BlackListBalooEmailCompletionWidget::slotSelectionChanged()
0148 {
0149     const bool selectionIsNotEmpty = !mEmailList->selectedItems().isEmpty();
0150     mSelectButton->setEnabled(selectionIsNotEmpty);
0151     mUnselectButton->setEnabled(selectionIsNotEmpty);
0152 }
0153 
0154 void BlackListBalooEmailCompletionWidget::slotCustomContextMenuRequested(const QPoint &pos)
0155 {
0156     const bool selectionIsNotEmpty = !mEmailList->selectedItems().isEmpty();
0157     if (selectionIsNotEmpty) {
0158         QMenu menu(this);
0159         menu.addAction(i18n("Select"), this, &BlackListBalooEmailCompletionWidget::slotSelectEmails);
0160         menu.addSeparator();
0161         menu.addAction(i18n("Unselect"), this, &BlackListBalooEmailCompletionWidget::slotUnselectEmails);
0162         menu.exec(mapToGlobal(pos));
0163     }
0164 }
0165 
0166 void BlackListBalooEmailCompletionWidget::load()
0167 {
0168     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kpimbalooblacklist"));
0169     KConfigGroup group(config, QStringLiteral("AddressLineEdit"));
0170 
0171     const QStringList lstExcludeEmailsRegularExpressions = group.readEntry("ExcludeEmailsRegexp", QStringList());
0172     mEmailList->setExcludeEmailsRegularExpressions(lstExcludeEmailsRegularExpressions);
0173     mExcludeEmailFromRegularExpressionLineEdit->setText(lstExcludeEmailsRegularExpressions.join(QLatin1Char(',')));
0174     mOriginalExcludeEmailRegexp = lstExcludeEmailsRegularExpressions;
0175 
0176     const QStringList lstExcludeDomains = group.readEntry("ExcludeDomain", QStringList());
0177     mEmailList->setExcludeDomains(lstExcludeDomains);
0178     mExcludeDomainLineEdit->setText(lstExcludeDomains.join(QLatin1Char(',')));
0179     mOriginalExcludeDomain = lstExcludeDomains;
0180     slotSelectionChanged();
0181 }
0182 
0183 void BlackListBalooEmailCompletionWidget::save()
0184 {
0185     const QString excludeEmailsRegexp = mExcludeEmailFromRegularExpressionLineEdit->text().remove(QLatin1Char(' '));
0186     const QStringList newExcludeEmailsRegExp = excludeEmailsRegexp.split(QLatin1Char(','), Qt::SkipEmptyParts);
0187     bool needToSave = (mOriginalExcludeEmailRegexp != newExcludeEmailsRegExp);
0188 
0189     const QString excludeDomains = mExcludeDomainLineEdit->text().remove(QLatin1Char(' '));
0190     const QStringList newExcludeDomain = excludeDomains.split(QLatin1Char(','), Qt::SkipEmptyParts);
0191     needToSave |= (mOriginalExcludeDomain != newExcludeDomain);
0192 
0193     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kpimbalooblacklist"));
0194     KConfigGroup group(config, QStringLiteral("AddressLineEdit"));
0195     const QHash<QString, bool> result = mEmailList->blackListItemChanged();
0196     if (!result.isEmpty()) {
0197         needToSave = true;
0198         QStringList blackList = group.readEntry("BalooBackList", QStringList());
0199         PimCommon::BlackListBalooEmailUtil util;
0200         util.initialBlackList(blackList);
0201         util.setNewBlackList(result);
0202         blackList = util.createNewBlackList();
0203         group.writeEntry("BalooBackList", blackList);
0204     }
0205     if (needToSave) {
0206         group.writeEntry("ExcludeEmailsRegexp", newExcludeEmailsRegExp);
0207         mEmailList->setExcludeEmailsRegularExpressions(newExcludeEmailsRegExp);
0208         mOriginalExcludeEmailRegexp = newExcludeEmailsRegExp;
0209 
0210         group.writeEntry("ExcludeDomain", newExcludeDomain);
0211         mEmailList->setExcludeDomains(newExcludeDomain);
0212         mOriginalExcludeDomain = newExcludeDomain;
0213         group.sync();
0214     }
0215     config->reparseConfiguration();
0216 }
0217 
0218 void BlackListBalooEmailCompletionWidget::slotSaveChanges()
0219 {
0220     // TODO avoid to save a lot.
0221     const QHash<QString, bool> result = mEmailList->blackListItemChanged();
0222     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kpimbalooblacklist"));
0223     KConfigGroup group(config, QStringLiteral("AddressLineEdit"));
0224     QStringList blackList = group.readEntry("BalooBackList", QStringList());
0225     PimCommon::BlackListBalooEmailUtil util;
0226     util.initialBlackList(blackList);
0227     util.setNewBlackList(result);
0228     blackList = util.createNewBlackList();
0229     group.writeEntry("BalooBackList", blackList);
0230     group.sync();
0231     mEmailList->setEmailBlackList(blackList);
0232     slotSearch();
0233 }
0234 
0235 void BlackListBalooEmailCompletionWidget::slotUnselectEmails()
0236 {
0237     const QList<QListWidgetItem *> lstSelected = mEmailList->selectedItems();
0238     for (QListWidgetItem *item : lstSelected) {
0239         item->setCheckState(Qt::Unchecked);
0240     }
0241 }
0242 
0243 void BlackListBalooEmailCompletionWidget::slotSelectEmails()
0244 {
0245     const QList<QListWidgetItem *> lstSelected = mEmailList->selectedItems();
0246     for (QListWidgetItem *item : lstSelected) {
0247         item->setCheckState(Qt::Checked);
0248     }
0249 }
0250 
0251 void BlackListBalooEmailCompletionWidget::slotSearchLineEditChanged(const QString &text)
0252 {
0253     mSearchButton->setEnabled(text.trimmed().length() > 2);
0254     hideMoreResultAndChangeLimit();
0255 }
0256 
0257 void BlackListBalooEmailCompletionWidget::hideMoreResultAndChangeLimit()
0258 {
0259     mMoreResult->setVisible(false);
0260     mLimit = 500;
0261 }
0262 
0263 void BlackListBalooEmailCompletionWidget::slotSearch()
0264 {
0265     const QString searchEmail = mSearchLineEdit->text().trimmed();
0266     if (searchEmail.length() > 2) {
0267         mSearchInResultLineEdit->clear();
0268         auto job = new PimCommon::BlackListBalooEmailSearchJob(this);
0269         job->setSearchEmail(searchEmail);
0270         job->setLimit(mLimit);
0271         connect(job, &BlackListBalooEmailSearchJob::emailsFound, this, &BlackListBalooEmailCompletionWidget::slotEmailFound);
0272         if (!job->start()) {
0273             qCWarning(PIMCOMMONAKONADI_LOG) << "Impossible to start search job";
0274         }
0275     }
0276 }
0277 
0278 void BlackListBalooEmailCompletionWidget::slotEmailFound(const QStringList &list)
0279 {
0280     const int numberOfEmails = mEmailList->setEmailFound(list);
0281     mMoreResult->setVisible(numberOfEmails == mLimit);
0282     mEmailList->scrollToBottom();
0283     if (numberOfEmails == 0) {
0284         mNumberOfEmailsFound->setText(i18n("No email found."));
0285     } else {
0286         mNumberOfEmailsFound->setText(i18np("1 email found", "%1 emails found", numberOfEmails));
0287     }
0288 }
0289 
0290 void BlackListBalooEmailCompletionWidget::setEmailBlackList(const QStringList &list)
0291 {
0292     mEmailList->setEmailBlackList(list);
0293 }
0294 
0295 void BlackListBalooEmailCompletionWidget::slotCheckIfUpdateBlackListIsNeeded()
0296 {
0297     const QHash<QString, bool> result = mEmailList->blackListItemChanged();
0298     if (result.isEmpty()) {
0299         slotSearch();
0300     } else {
0301         mBlackListWarning->animatedShow();
0302     }
0303 }
0304 
0305 void BlackListBalooEmailCompletionWidget::slotLinkClicked(const QString &link)
0306 {
0307     if (link == QLatin1StringView("more_result")) {
0308         mLimit += 200;
0309         slotSearch();
0310     }
0311 }
0312 
0313 void BlackListBalooEmailCompletionWidget::slotShowAllBlacklistedEmail()
0314 {
0315     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kpimbalooblacklist"));
0316     KConfigGroup group(config, QStringLiteral("AddressLineEdit"));
0317     const QStringList balooBlackList = group.readEntry("BalooBackList", QStringList());
0318     slotEmailFound(balooBlackList);
0319 }
0320 
0321 #include "moc_blacklistbalooemailcompletionwidget.cpp"