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

0001 /*
0002   SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "addresseelineedit_p.h"
0008 #include "addresseelineedit.h"
0009 #include "addresseelineeditmanager.h"
0010 #include "addressline/completionorder/completionordereditor.h"
0011 #include "kmailcompletion.h"
0012 #include "pimcommonakonadi_debug.h"
0013 
0014 #include <Akonadi/CollectionFetchJob>
0015 #include <Akonadi/ContactGroupSearchJob>
0016 #include <Akonadi/ContactSearchJob>
0017 #include <Akonadi/ItemFetchScope>
0018 #include <Akonadi/Job>
0019 #include <Akonadi/Session>
0020 #include <KColorScheme>
0021 #include <KCompletionBox>
0022 #include <KConfigGroup>
0023 #include <KLocalizedString>
0024 #include <QApplication>
0025 #include <QMap>
0026 #include <QTimer>
0027 #include <QToolButton>
0028 #include <config-akonadi-search.h>
0029 #if !DISABLE_AKONADI_SEARCH
0030 #include <PIM/contactcompleter.h>
0031 #endif
0032 
0033 #include "addressline/addresslineedit/baloocompletionemail.h"
0034 #include <Akonadi/ContactSearchJob>
0035 #include <chrono>
0036 
0037 using namespace std::chrono_literals;
0038 
0039 using namespace PimCommon;
0040 AddresseeLineEditPrivate::AddresseeLineEditPrivate(PimCommon::AddresseeLineEdit *qq, bool enableCompletion)
0041     : QObject(qq)
0042     , q(qq)
0043     , mDelayedQueryTimer(new QTimer(this))
0044     , mUseCompletion(enableCompletion)
0045 {
0046     mDelayedQueryTimer->setSingleShot(true);
0047     connect(mDelayedQueryTimer, &QTimer::timeout, this, &AddresseeLineEditPrivate::slotTriggerDelayedQueries);
0048 }
0049 
0050 AddresseeLineEditPrivate::~AddresseeLineEditPrivate()
0051 {
0052     if (AddresseeLineEditManager::self()->addressLineEdit() == q) {
0053         AddresseeLineEditManager::self()->stopLDAPLookup();
0054     }
0055 }
0056 
0057 void AddresseeLineEditPrivate::restartTime(const QString &searchString)
0058 {
0059     if (useCompletion()) {
0060         AddresseeLineEditManager::self()->restartLdap(searchString, q);
0061     }
0062 }
0063 
0064 static const QString s_completionItemIndentString = QStringLiteral("     ");
0065 
0066 class SourceWithWeight
0067 {
0068 public:
0069     int weight; // the weight of the source
0070     int index; // index into AddresseeLineEditStatic::self()->completionSources
0071     QString sourceName; // the name of the source, e.g. "LDAP Server"
0072 
0073     bool operator<(const SourceWithWeight &other) const
0074     {
0075         if (weight > other.weight) {
0076             return true;
0077         }
0078 
0079         if (weight < other.weight) {
0080             return false;
0081         }
0082 
0083         return sourceName < other.sourceName;
0084     }
0085 };
0086 
0087 void AddresseeLineEditPrivate::init()
0088 {
0089     if (mToolButton) {
0090         return;
0091     }
0092     mToolButton = new QToolButton(q);
0093     mToolButton->setVisible(false);
0094     mToolButton->setCursor(Qt::ArrowCursor);
0095     const int size = q->sizeHint().height() - 5;
0096     mToolButton->setFixedSize(size, size);
0097     int padding = (q->sizeHint().height() - size) / 2;
0098     mToolButton->move(2, padding);
0099     mToolButton->setStyleSheet(QStringLiteral("QToolButton { border: none; }"));
0100     connect(mToolButton, &QToolButton::clicked, q, &AddresseeLineEdit::iconClicked);
0101 
0102     if (!AddresseeLineEditManager::self()) {
0103         AddresseeLineEditManager::self()->completion()->setOrder(KCompletion::Weighted);
0104         AddresseeLineEditManager::self()->completion()->setIgnoreCase(true);
0105     }
0106 
0107     if (mUseCompletion) {
0108         AddresseeLineEditManager::self()->initializeLdap();
0109         AddresseeLineEditManager::self()->setBalooCompletionSource(q->addCompletionSource(i18nc("@title:group", "Contacts found in your data"), -1));
0110         AddresseeLineEditManager::self()->updateLDAPWeights();
0111 
0112         if (!mCompletionInitialized) {
0113             q->setCompletionObject(AddresseeLineEditManager::self()->completion(), false);
0114             connect(q, &KLineEdit::completion, this, &AddresseeLineEditPrivate::slotCompletion);
0115             connect(q, &AddresseeLineEdit::returnKeyPressed, this, &AddresseeLineEditPrivate::slotReturnPressed);
0116 
0117             KCompletionBox *box = q->completionBox();
0118             connect(box, &KCompletionBox::textActivated, this, &AddresseeLineEditPrivate::slotPopupCompletion);
0119             connect(box, &KCompletionBox::userCancelled, this, &AddresseeLineEditPrivate::slotUserCancelled);
0120             connect(AddresseeLineEditManager::self()->ldapTimer(), &QTimer::timeout, this, &AddresseeLineEditPrivate::slotStartLDAPLookup);
0121             connect(AddresseeLineEditManager::self()->ldapSearch(),
0122                     qOverload<const KLDAPWidgets::LdapResult::List &>(&KLDAPWidgets::LdapClientSearch::searchData),
0123                     this,
0124                     &AddresseeLineEditPrivate::slotLDAPSearchData);
0125 
0126             mCompletionInitialized = true;
0127         }
0128     }
0129     connect(q, &AddresseeLineEdit::textCompleted, q, &AddresseeLineEdit::slotEditingFinished);
0130     connect(q, &AddresseeLineEdit::editingFinished, q, &AddresseeLineEdit::slotEditingFinished);
0131 }
0132 
0133 void AddresseeLineEditPrivate::setIcon(const QIcon &icon, const QString &tooltip)
0134 {
0135     if (icon.isNull()) {
0136         mToolButton->setVisible(false);
0137         q->setStyleSheet(QString());
0138     } else {
0139         mToolButton->setIcon(icon);
0140         mToolButton->setToolTip(tooltip);
0141         const int padding = mToolButton->width() - q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
0142         q->setStyleSheet(QStringLiteral("QLineEdit { padding-left: %1px }").arg(padding));
0143         mToolButton->setVisible(true);
0144     }
0145 }
0146 
0147 void AddresseeLineEditPrivate::searchInBaloo()
0148 {
0149 #if !DISABLE_AKONADI_SEARCH
0150     const QString trimmedString = mSearchString.trimmed();
0151     Akonadi::Search::PIM::ContactCompleter com(trimmedString, 20);
0152     const QStringList listEmail = AddresseeLineEditManager::self()->cleanupEmailList(com.complete());
0153     for (const QString &email : listEmail) {
0154         addCompletionItem(email, 1, AddresseeLineEditManager::self()->balooCompletionSource());
0155     }
0156     doCompletion(mLastSearchMode);
0157 #endif
0158 }
0159 
0160 void AddresseeLineEditPrivate::setCompletedItems(const QStringList &items, bool autoSuggest)
0161 {
0162     KCompletionBox *completionBox = q->completionBox();
0163 
0164     if (!items.isEmpty() && !(items.count() == 1 && mSearchString == items.first())) {
0165         completionBox->clear();
0166         const int numberOfItems(items.count());
0167         for (int i = 0; i < numberOfItems; ++i) {
0168             auto item = new QListWidgetItem(items.at(i), completionBox);
0169             if (!items.at(i).startsWith(s_completionItemIndentString)) {
0170                 item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
0171                 item->setBackground(AddresseeLineEditManager::self()->alternateColor());
0172             }
0173             completionBox->addItem(item);
0174         }
0175         if (!completionBox->isVisible()) {
0176             if (!mSearchString.isEmpty()) {
0177                 completionBox->setCancelledText(mSearchString);
0178             }
0179             completionBox->popup();
0180             // we have to install the event filter after popup(), since that
0181             // calls show(), and that's where KCompletionBox installs its filter.
0182             // We want to be first, though, so do it now.
0183             if (AddresseeLineEditManager::self()->completion()->order() == KCompletion::Weighted) {
0184                 qApp->installEventFilter(q);
0185             }
0186         }
0187 
0188         QListWidgetItem *item = completionBox->item(1);
0189         if (item) {
0190             completionBox->blockSignals(true);
0191             completionBox->setCurrentItem(item);
0192             item->setSelected(true);
0193             completionBox->blockSignals(false);
0194         }
0195 
0196         if (autoSuggest) {
0197             const int index = items.first().indexOf(mSearchString);
0198             const QString newText = items.first().mid(index);
0199             q->callSetUserSelection(false);
0200             q->callSetCompletedText(newText, true);
0201         }
0202     } else {
0203         if (completionBox && completionBox->isVisible()) {
0204             completionBox->hide();
0205             completionBox->setItems(QStringList());
0206         }
0207     }
0208 }
0209 
0210 void AddresseeLineEditPrivate::addCompletionItem(const QString &string, int weight, int completionItemSource, const QStringList *keyWords)
0211 {
0212     // Check if there is an exact match for item already, and use the
0213     // maximum weight if so. Since there's no way to get the information
0214     // from KCompletion, we have to keep our own QMap.
0215     // We also update the source since the item should always be shown from the source with the highest weight
0216 
0217     AddresseeLineEditManager::CompletionItemsMap::iterator it = AddresseeLineEditManager::self()->completionItemMap.find(string);
0218     if (it != AddresseeLineEditManager::self()->completionItemMap.end()) {
0219         weight = qMax((*it).first, weight);
0220         (*it).first = weight;
0221         (*it).second = completionItemSource;
0222     } else {
0223         AddresseeLineEditManager::self()->completionItemMap.insert(string, qMakePair(weight, completionItemSource));
0224     }
0225 
0226     AddresseeLineEditManager::self()->completion()->addItem(string, weight);
0227     if (keyWords && !keyWords->isEmpty()) {
0228         AddresseeLineEditManager::self()->completion()->addItemWithKeys(string, weight, keyWords); // see kmailcompletion.cpp
0229     }
0230 }
0231 
0232 const QStringList PimCommon::AddresseeLineEditPrivate::adjustedCompletionItems(bool fullSearch)
0233 {
0234     QStringList items = fullSearch ? AddresseeLineEditManager::self()->completion()->allMatches(mSearchString)
0235                                    : AddresseeLineEditManager::self()->completion()->substringCompletion(mSearchString);
0236 
0237     // force items to be sorted by email
0238     items.sort();
0239 
0240     // For weighted mode, the algorithm is the following:
0241     // In the first loop, we add each item to its section (there is one section per completion source)
0242     // We also add spaces in front of the items.
0243     // The sections are appended to the items list.
0244     // In the second loop, we then walk through the sections and add all the items in there to the
0245     // sorted item list, which is the final result.
0246     //
0247     // The algo for non-weighted mode is different.
0248 
0249     int lastSourceIndex = -1;
0250 
0251     // Maps indices of the items list, which are section headers/source items,
0252     // to a QStringList which are the items of that section/source.
0253     QMap<int, QStringList> sections;
0254     QStringList sortedItems;
0255     for (QStringList::Iterator it = items.begin(); it != items.end(); ++it) {
0256         AddresseeLineEditManager::CompletionItemsMap::const_iterator cit = AddresseeLineEditManager::self()->completionItemMap.constFind(*it);
0257         if (cit == AddresseeLineEditManager::self()->completionItemMap.constEnd()) {
0258             continue;
0259         }
0260 
0261         const int index = (*cit).second;
0262 
0263         if (AddresseeLineEditManager::self()->completion()->order() == KCompletion::Weighted) {
0264             if (lastSourceIndex == -1 || lastSourceIndex != index) {
0265                 const QString sourceLabel(AddresseeLineEditManager::self()->completionSources.at(index));
0266                 if (sections.find(index) == sections.end()) {
0267                     it = items.insert(it, sourceLabel);
0268                     ++it; // skip new item
0269                 }
0270                 lastSourceIndex = index;
0271             }
0272 
0273             (*it).prepend(s_completionItemIndentString);
0274             // remove preferred email sort <blank> added in  addContact()
0275             (*it).replace(QLatin1StringView("  <"), QStringLiteral(" <"));
0276         }
0277         sections[index].append(*it);
0278 
0279         if (AddresseeLineEditManager::self()->completion()->order() == KCompletion::Sorted) {
0280             sortedItems.append(*it);
0281         }
0282     }
0283 
0284     if (AddresseeLineEditManager::self()->completion()->order() == KCompletion::Weighted) {
0285         // Sort the sections
0286         QList<SourceWithWeight> sourcesAndWeights;
0287         const int numberOfCompletionSources(AddresseeLineEditManager::self()->completionSources.count());
0288         sourcesAndWeights.reserve(numberOfCompletionSources);
0289         for (int i = 0; i < numberOfCompletionSources; ++i) {
0290             SourceWithWeight sww;
0291             sww.sourceName = AddresseeLineEditManager::self()->completionSources.at(i);
0292             sww.weight = AddresseeLineEditManager::self()->completionSourceWeights[sww.sourceName];
0293             sww.index = i;
0294             sourcesAndWeights.append(sww);
0295         }
0296 
0297         std::sort(sourcesAndWeights.begin(), sourcesAndWeights.end());
0298         // Add the sections and their items to the final sortedItems result list
0299         const int numberOfSources(sourcesAndWeights.size());
0300         for (int i = 0; i < numberOfSources; ++i) {
0301             const SourceWithWeight source = sourcesAndWeights.at(i);
0302             const QStringList sectionItems = sections[source.index];
0303             if (!sectionItems.isEmpty()) {
0304                 sortedItems.append(source.sourceName);
0305                 for (const QString &itemInSection : sectionItems) {
0306                     sortedItems.append(itemInSection);
0307                 }
0308             }
0309         }
0310     } else {
0311         sortedItems.sort();
0312     }
0313 
0314     return sortedItems;
0315 }
0316 
0317 void AddresseeLineEditPrivate::updateSearchString()
0318 {
0319     mSearchString = q->text();
0320 
0321     int n = -1;
0322     bool inQuote = false;
0323     const int searchStringLength = mSearchString.length();
0324     for (int i = 0; i < searchStringLength; ++i) {
0325         const QChar searchChar = mSearchString.at(i);
0326         if (searchChar == QLatin1Char('"')) {
0327             inQuote = !inQuote;
0328         }
0329 
0330         if (searchChar == QLatin1Char('\\') && (i + 1) < searchStringLength && mSearchString.at(i + 1) == QLatin1Char('"')) {
0331             ++i;
0332         }
0333 
0334         if (inQuote) {
0335             continue;
0336         }
0337 
0338         if (i < searchStringLength && (searchChar == QLatin1Char(',') || (mUseSemicolonAsSeparator && searchChar == QLatin1Char(';')))) {
0339             n = i;
0340         }
0341     }
0342 
0343     if (n >= 0) {
0344         ++n; // Go past the ","
0345 
0346         const int len = mSearchString.length();
0347 
0348         // Increment past any whitespace...
0349         while (n < len && mSearchString.at(n).isSpace()) {
0350             ++n;
0351         }
0352 
0353         mPreviousAddresses = mSearchString.left(n);
0354         mSearchString = mSearchString.mid(n).trimmed();
0355     } else {
0356         mPreviousAddresses.clear();
0357     }
0358 }
0359 
0360 void AddresseeLineEditPrivate::slotTriggerDelayedQueries()
0361 {
0362     const QString strSearch = mSearchString.trimmed();
0363     if (strSearch.size() <= 2) {
0364         return;
0365     }
0366 
0367     if (mEnableBalooSearch) {
0368         searchInBaloo();
0369     }
0370 
0371     // We send a contactsearch job through akonadi.
0372     // This not only searches baloo but also servers if remote search is enabled
0373     if (mEnableAkonadiSearch) {
0374         akonadiPerformSearch();
0375     }
0376 }
0377 
0378 void AddresseeLineEditPrivate::startSearches()
0379 {
0380     if (!mDelayedQueryTimer->isActive()) {
0381         mDelayedQueryTimer->start(50ms);
0382     }
0383 }
0384 
0385 void AddresseeLineEditPrivate::akonadiPerformSearch()
0386 {
0387     qCDebug(PIMCOMMONAKONADI_LOG) << "searching akonadi with:" << mSearchString;
0388 
0389     // first, kill all job still in flight, they are no longer current
0390     const auto akonadiJobsInFlight = AddresseeLineEditManager::self()->akonadiJobsInFlight;
0391     for (const QPointer<Akonadi::Job> &job : akonadiJobsInFlight) {
0392         if (!job.isNull()) {
0393             job.data()->kill();
0394         }
0395     }
0396     AddresseeLineEditManager::self()->akonadiJobsInFlight.clear();
0397 
0398     // now start new jobs
0399     auto contactJob = new Akonadi::ContactSearchJob(AddresseeLineEditManager::self()->akonadiSession());
0400     contactJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0401     contactJob->setQuery(Akonadi::ContactSearchJob::NameOrEmail, mSearchString, Akonadi::ContactSearchJob::ContainsWordBoundaryMatch);
0402     connect(contactJob, &Akonadi::ItemSearchJob::itemsReceived, this, &AddresseeLineEditPrivate::slotAkonadiHandleItems);
0403     connect(contactJob, &KJob::result, this, &AddresseeLineEditPrivate::slotAkonadiSearchResult);
0404 
0405     auto groupJob = new Akonadi::ContactGroupSearchJob(AddresseeLineEditManager::self()->akonadiSession());
0406     groupJob->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
0407     groupJob->setQuery(Akonadi::ContactGroupSearchJob::Name, mSearchString, Akonadi::ContactGroupSearchJob::ContainsMatch);
0408     connect(contactJob, &Akonadi::ItemSearchJob::itemsReceived, this, &AddresseeLineEditPrivate::slotAkonadiHandleItems);
0409     connect(groupJob, &KJob::result, this, &AddresseeLineEditPrivate::slotAkonadiSearchResult);
0410 
0411     AddresseeLineEditManager::self()->akonadiJobsInFlight.append(contactJob);
0412     AddresseeLineEditManager::self()->akonadiJobsInFlight.append(groupJob);
0413     akonadiHandlePending();
0414 }
0415 
0416 void AddresseeLineEditPrivate::akonadiHandlePending()
0417 {
0418     qCDebug(PIMCOMMONAKONADI_LOG) << "Pending items: " << AddresseeLineEditManager::self()->akonadiPendingItems.size();
0419     Akonadi::Item::List::iterator it = AddresseeLineEditManager::self()->akonadiPendingItems.begin();
0420     while (it != AddresseeLineEditManager::self()->akonadiPendingItems.end()) {
0421         const Akonadi::Item item = *it;
0422 
0423         const AddresseeLineEditManager::collectionInfo sourceIndex =
0424             AddresseeLineEditManager::self()->akonadiCollectionToCompletionSourceMap.value(item.parentCollection().id(),
0425                                                                                            AddresseeLineEditManager::collectionInfo());
0426         if (sourceIndex.index >= 0) {
0427             qCDebug(PIMCOMMONAKONADI_LOG) << "identified collection: " << AddresseeLineEditManager::self()->completionSources[sourceIndex.index];
0428             if (sourceIndex.enabled) {
0429                 q->addItem(item, 1, sourceIndex.index);
0430             }
0431 
0432             // remove from the pending
0433             it = AddresseeLineEditManager::self()->akonadiPendingItems.erase(it);
0434         } else {
0435             ++it;
0436         }
0437     }
0438 }
0439 
0440 void AddresseeLineEditPrivate::doCompletion(bool ctrlT)
0441 {
0442     mLastSearchMode = ctrlT;
0443 
0444     const KCompletion::CompletionMode mode = q->completionMode();
0445 
0446     if (mode == KCompletion::CompletionNone) {
0447         return;
0448     }
0449 
0450     AddresseeLineEditManager::self()->completion()->setOrder(KCompletion::Weighted);
0451 
0452     // cursor at end of string - or Ctrl+T pressed for substring completion?
0453     if (ctrlT) {
0454         const QStringList completions = adjustedCompletionItems(false);
0455 
0456         if (completions.count() == 1) {
0457             q->setText(mPreviousAddresses + completions.first().trimmed());
0458         }
0459 
0460         // Make sure the completion popup is closed if no matching items were found
0461         setCompletedItems(completions, true);
0462 
0463         q->cursorAtEnd();
0464         q->setCompletionMode(mode); // set back to previous mode
0465         return;
0466     }
0467 
0468     switch (mode) {
0469     case KCompletion::CompletionPopupAuto:
0470         if (mSearchString.isEmpty()) {
0471             break;
0472         }
0473         // else: fall-through to the CompletionPopup case
0474         [[fallthrough]];
0475 
0476     case KCompletion::CompletionPopup: {
0477         const QStringList items = adjustedCompletionItems(false);
0478         setCompletedItems(items, false);
0479         break;
0480     }
0481 
0482     case KCompletion::CompletionShell: {
0483         const QString match = AddresseeLineEditManager::self()->completion()->makeCompletion(mSearchString);
0484         if (!match.isNull() && match != mSearchString) {
0485             q->setText(mPreviousAddresses + match);
0486             q->setModified(true);
0487             q->cursorAtEnd();
0488         }
0489         break;
0490     }
0491 
0492     case KCompletion::CompletionMan: // Short-Auto in fact
0493     case KCompletion::CompletionAuto:
0494         // force autoSuggest in KLineEdit::keyPressed or setCompletedText will have no effect
0495         q->setCompletionMode(q->completionMode());
0496 
0497         if (!mSearchString.isEmpty()) {
0498             // if only our \" is left, remove it since user has not typed it either
0499             if (mSearchExtended && mSearchString == QLatin1StringView("\"")) {
0500                 mSearchExtended = false;
0501                 mSearchString.clear();
0502                 q->setText(mPreviousAddresses);
0503                 break;
0504             }
0505 
0506             QString match = AddresseeLineEditManager::self()->completion()->makeCompletion(mSearchString);
0507 
0508             if (!match.isEmpty()) {
0509                 if (match != mSearchString) {
0510                     const QString adds = mPreviousAddresses + match;
0511                     q->callSetCompletedText(adds);
0512                 }
0513             } else {
0514                 if (!mSearchString.startsWith(QLatin1Char('\"'))) {
0515                     // try with quoted text, if user has not type one already
0516                     match = AddresseeLineEditManager::self()->completion()->makeCompletion(QLatin1StringView("\"") + mSearchString);
0517                     if (!match.isEmpty() && match != mSearchString) {
0518                         mSearchString = QLatin1StringView("\"") + mSearchString;
0519                         mSearchExtended = true;
0520                         q->setText(mPreviousAddresses + mSearchString);
0521                         q->callSetCompletedText(mPreviousAddresses + match);
0522                     }
0523                 } else if (mSearchExtended) {
0524                     // our added \" does not work anymore, remove it
0525                     mSearchString.remove(0, 1);
0526                     mSearchExtended = false;
0527                     q->setText(mPreviousAddresses + mSearchString);
0528                     // now try again
0529                     match = AddresseeLineEditManager::self()->completion()->makeCompletion(mSearchString);
0530                     if (!match.isEmpty() && match != mSearchString) {
0531                         const QString adds = mPreviousAddresses + match;
0532                         q->setCompletedText(adds);
0533                     }
0534                 }
0535             }
0536         }
0537         break;
0538 
0539     case KCompletion::CompletionNone:
0540         break;
0541     }
0542 }
0543 
0544 void AddresseeLineEditPrivate::slotCompletion()
0545 {
0546     // Called by KLineEdit's keyPressEvent for CompletionModes
0547     // Auto,Popup -> new text, update search string.
0548     // not called for CompletionShell, this is been taken care of
0549     // in AddresseeLineEdit::keyPressEvent
0550 
0551     updateSearchString();
0552     if (q->completionBox()) {
0553         q->completionBox()->setCancelledText(mSearchString);
0554     }
0555 
0556     startSearches();
0557     doCompletion(false);
0558 }
0559 
0560 void AddresseeLineEditPrivate::slotPopupCompletion(const QString &completion)
0561 {
0562     QString c = completion.trimmed();
0563     if (c.endsWith(QLatin1Char(')'))) {
0564         c = completion.mid(0, completion.lastIndexOf(QLatin1StringView(" ("))).trimmed();
0565     }
0566     q->setText(mPreviousAddresses + c);
0567     q->cursorAtEnd();
0568     updateSearchString();
0569     q->emitTextCompleted();
0570 }
0571 
0572 void AddresseeLineEditPrivate::slotReturnPressed(const QString &)
0573 {
0574     if (!q->completionBox()->selectedItems().isEmpty()) {
0575         slotPopupCompletion(q->completionBox()->selectedItems().constFirst()->text());
0576     }
0577 }
0578 
0579 void AddresseeLineEditPrivate::slotStartLDAPLookup()
0580 {
0581     if (AddresseeLineEditManager::self()->isOnline()) {
0582         const KCompletion::CompletionMode mode = q->completionMode();
0583         if (mode == KCompletion::CompletionNone) {
0584             return;
0585         }
0586         if (!AddresseeLineEditManager::self()->ldapSearch()->isAvailable()) {
0587             return;
0588         }
0589         if (AddresseeLineEditManager::self()->addressLineEdit() != q) {
0590             return;
0591         }
0592         AddresseeLineEditManager::self()->startLoadingLDAPEntries();
0593     }
0594 }
0595 
0596 void AddresseeLineEditPrivate::slotLDAPSearchData(const KLDAPWidgets::LdapResult::List &results)
0597 {
0598     if (results.isEmpty() || AddresseeLineEditManager::self()->addressLineEdit() != q) {
0599         return;
0600     }
0601 
0602     for (const KLDAPWidgets::LdapResult &result : results) {
0603         KContacts::Addressee contact;
0604         contact.setNameFromString(result.name);
0605         contact.setEmails(result.email);
0606         QString ou;
0607 
0608         if (AddresseeLineEditManager::self()->showOU()) {
0609             const int depth = result.dn.depth();
0610             for (int i = 0; i < depth; ++i) {
0611                 const QString rdnStr = result.dn.rdnString(i);
0612                 if (rdnStr.startsWith(QLatin1StringView("ou="), Qt::CaseInsensitive)) {
0613                     ou = rdnStr.mid(3);
0614                     break;
0615                 }
0616             }
0617         }
0618 
0619         if (!AddresseeLineEditManager::self()->isLdapClientToCompletionSourceMapContains(result.clientNumber)) {
0620             AddresseeLineEditManager::self()->updateLDAPWeights(); // we got results from a new source, so update the completion sources
0621         }
0622 
0623         q->addContact(contact, result.completionWeight, AddresseeLineEditManager::self()->ldapClientToCompletionSourceValue(result.clientNumber), ou);
0624     }
0625 
0626     if ((q->hasFocus() || q->completionBox()->hasFocus()) && q->completionMode() != KCompletion::CompletionNone
0627         && q->completionMode() != KCompletion::CompletionShell) {
0628         q->setText(mPreviousAddresses + mSearchString);
0629         // only complete again if the user didn't change the selection while
0630         // we were waiting; otherwise the completion box will be closed
0631         const QListWidgetItem *current = q->completionBox()->currentItem();
0632         if (!current || mSearchString.trimmed() != current->text().trimmed()) {
0633             doCompletion(mLastSearchMode);
0634         }
0635     }
0636 }
0637 
0638 void AddresseeLineEditPrivate::slotEditCompletionOrder()
0639 {
0640     if (mUseCompletion) {
0641         init(); // for AddresseeLineEditStatic::self()->ldapSearch
0642         QPointer<CompletionOrderEditor> dlg = new CompletionOrderEditor(AddresseeLineEditManager::self()->ldapSearch(), nullptr);
0643         if (dlg->exec()) {
0644             AddresseeLineEditManager::self()->updateCompletionOrder();
0645         }
0646         delete dlg;
0647     }
0648 }
0649 
0650 KLDAPWidgets::LdapClientSearch *AddresseeLineEditPrivate::ldapSearch()
0651 {
0652     init(); // for AddresseeLineEditStatic::self()->ldapSearch
0653     return AddresseeLineEditManager::self()->ldapSearch();
0654 }
0655 
0656 void AddresseeLineEditPrivate::slotUserCancelled(const QString &cancelText)
0657 {
0658     if (AddresseeLineEditManager::self()->addressLineEdit() == q) {
0659         AddresseeLineEditManager::self()->stopLDAPLookup();
0660     }
0661 
0662     q->callUserCancelled(mPreviousAddresses + cancelText); // in KLineEdit
0663 }
0664 
0665 void AddresseeLineEditPrivate::slotAkonadiHandleItems(const Akonadi::Item::List &items)
0666 {
0667     /* We have to fetch the collections of the items, so that
0668        the source name can be correctly labeled.*/
0669     for (const Akonadi::Item &item : items) {
0670         // check the local cache of collections
0671         const Akonadi::Collection::Id colId = item.parentCollection().id();
0672         const AddresseeLineEditManager::collectionInfo sourceIndex =
0673             AddresseeLineEditManager::self()->akonadiCollectionToCompletionSourceMap.value(colId, AddresseeLineEditManager::collectionInfo());
0674         if (sourceIndex.index == -1) {
0675             qCDebug(PIMCOMMONAKONADI_LOG) << "Fetching New collection: " << colId;
0676             // the collection isn't there, start the fetch job.
0677             auto collectionJob =
0678                 new Akonadi::CollectionFetchJob(item.parentCollection(), Akonadi::CollectionFetchJob::Base, AddresseeLineEditManager::self()->akonadiSession());
0679             connect(collectionJob, &Akonadi::CollectionFetchJob::collectionsReceived, this, &AddresseeLineEditPrivate::slotAkonadiCollectionsReceived);
0680             /* we don't want to start multiple fetch jobs for the same collection,
0681             so insert the collection with an index value of -2 */
0682             AddresseeLineEditManager::collectionInfo info;
0683             info.index = -2;
0684             AddresseeLineEditManager::self()->akonadiCollectionToCompletionSourceMap.insert(colId, info);
0685             AddresseeLineEditManager::self()->akonadiPendingItems.append(item);
0686         } else if (sourceIndex.index == -2) {
0687             /* fetch job already started, don't need to start another one,
0688             so just append the item as pending */
0689             AddresseeLineEditManager::self()->akonadiPendingItems.append(item);
0690         } else {
0691             if (sourceIndex.enabled) {
0692                 q->addItem(item, 1, sourceIndex.index);
0693             }
0694         }
0695     }
0696 
0697     if (!items.isEmpty()) {
0698         const QListWidgetItem *current = q->completionBox()->currentItem();
0699         if (!current || mSearchString.trimmed() != current->text().trimmed()) {
0700             doCompletion(mLastSearchMode);
0701         }
0702     }
0703 }
0704 
0705 void AddresseeLineEditPrivate::slotAkonadiSearchResult(KJob *job)
0706 {
0707     if (job->error()) {
0708         qCWarning(PIMCOMMONAKONADI_LOG) << "Akonadi search job failed: " << job->errorString();
0709     }
0710     const int index = AddresseeLineEditManager::self()->akonadiJobsInFlight.indexOf(qobject_cast<Akonadi::Job *>(job));
0711     if (index != -1) {
0712         AddresseeLineEditManager::self()->akonadiJobsInFlight.remove(index);
0713     }
0714 }
0715 
0716 void AddresseeLineEditPrivate::slotAkonadiCollectionsReceived(const Akonadi::Collection::List &collections)
0717 {
0718     KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("kpimcompletionorder"));
0719     KConfigGroup groupCompletionWeights(config, QStringLiteral("CompletionWeights"));
0720     KConfigGroup groupCompletionEnabled(config, QStringLiteral("CompletionEnabled"));
0721     for (const Akonadi::Collection &collection : collections) {
0722         if (collection.isValid()) {
0723             const QString sourceString = collection.displayName();
0724             const Akonadi::Collection::Id colId = collection.id();
0725             const int weight = groupCompletionWeights.readEntry(QString::number(colId), 1);
0726             const int index = q->addCompletionSource(sourceString, weight);
0727             AddresseeLineEditManager::collectionInfo info(index, groupCompletionEnabled.readEntry(QString::number(colId), true));
0728             qCDebug(PIMCOMMONAKONADI_LOG) << "\treceived: " << sourceString << "index: " << index << " enabled: " << info.enabled;
0729             AddresseeLineEditManager::self()->akonadiCollectionToCompletionSourceMap.insert(colId, info);
0730         }
0731     }
0732 
0733     // now that we have added the new collections, recheck our list of pending contacts
0734     akonadiHandlePending();
0735     // do completion
0736     const QListWidgetItem *current = q->completionBox()->currentItem();
0737     if (!current || mSearchString.trimmed() != current->text().trimmed()) {
0738         doCompletion(mLastSearchMode);
0739     }
0740 }
0741 
0742 void AddresseeLineEditPrivate::slotToggleExpandGroups(bool checked)
0743 {
0744     AddresseeLineEditManager::self()->setAutoGroupExpand(checked);
0745 }
0746 
0747 void AddresseeLineEditPrivate::slotShowOUChanged(bool checked)
0748 {
0749     AddresseeLineEditManager::self()->setShowOU(checked);
0750 }
0751 
0752 void AddresseeLineEditPrivate::updateBalooBlackList()
0753 {
0754     AddresseeLineEditManager::self()->loadBalooBlackList();
0755     q->removeCompletionSource(i18nc("@title:group", "Contacts found in your data"));
0756     AddresseeLineEditManager::self()->setBalooCompletionSource(q->addCompletionSource(i18nc("@title:group", "Contacts found in your data"), -1));
0757 }
0758 
0759 void AddresseeLineEditPrivate::updateCompletionOrder()
0760 {
0761     AddresseeLineEditManager::self()->updateCompletionOrder();
0762 }
0763 
0764 bool AddresseeLineEditPrivate::canDeleteLineEdit() const
0765 {
0766     return mCanDeleteLineEdit;
0767 }
0768 
0769 void AddresseeLineEditPrivate::setCanDeleteLineEdit(bool inprogressToConfigureCompletion)
0770 {
0771     mCanDeleteLineEdit = inprogressToConfigureCompletion;
0772 }
0773 
0774 KConfig *AddresseeLineEditPrivate::recentAddressConfig() const
0775 {
0776     return mRecentAddressConfig;
0777 }
0778 
0779 bool AddresseeLineEditPrivate::showRecentAddresses() const
0780 {
0781     return mShowRecentAddresses;
0782 }
0783 
0784 void AddresseeLineEditPrivate::setRecentAddressConfig(KConfig *config)
0785 {
0786     mRecentAddressConfig = config;
0787 }
0788 
0789 KContacts::ContactGroup::List AddresseeLineEditPrivate::groups() const
0790 {
0791     return mGroups;
0792 }
0793 
0794 void AddresseeLineEditPrivate::setGroups(const KContacts::ContactGroup::List &groups)
0795 {
0796     mGroups = groups;
0797 }
0798 
0799 QList<KJob *> AddresseeLineEditPrivate::mightBeGroupJobs() const
0800 {
0801     return mMightBeGroupJobs;
0802 }
0803 
0804 void AddresseeLineEditPrivate::setMightBeGroupJobs(const QList<KJob *> &mightBeGroupJobs)
0805 {
0806     mMightBeGroupJobs = mightBeGroupJobs;
0807 }
0808 
0809 bool AddresseeLineEditPrivate::autoGroupExpand() const
0810 {
0811     return AddresseeLineEditManager::self()->autoGroupExpand();
0812 }
0813 
0814 void AddresseeLineEditPrivate::setAutoGroupExpand(bool autoGroupExpand)
0815 {
0816     AddresseeLineEditManager::self()->setAutoGroupExpand(autoGroupExpand);
0817 }
0818 
0819 void AddresseeLineEditPrivate::setExpandIntern(bool b)
0820 {
0821     mExpandIntern = b;
0822 }
0823 
0824 bool AddresseeLineEditPrivate::expandIntern() const
0825 {
0826     return mExpandIntern;
0827 }
0828 
0829 bool AddresseeLineEditPrivate::useSemicolonAsSeparator() const
0830 {
0831     return mUseSemicolonAsSeparator;
0832 }
0833 
0834 void AddresseeLineEditPrivate::setUseSemicolonAsSeparator(bool useSemicolonAsSeparator)
0835 {
0836     mUseSemicolonAsSeparator = useSemicolonAsSeparator;
0837 }
0838 
0839 bool AddresseeLineEditPrivate::enableBalooSearch() const
0840 {
0841     return mEnableBalooSearch;
0842 }
0843 
0844 void AddresseeLineEditPrivate::setEnableBalooSearch(bool enableBalooSearch)
0845 {
0846     mEnableBalooSearch = enableBalooSearch;
0847 }
0848 
0849 bool AddresseeLineEditPrivate::enableAkonadiSearch() const
0850 {
0851     return mEnableAkonadiSearch;
0852 }
0853 
0854 void AddresseeLineEditPrivate::setEnableAkonadiSearch(bool enableAkonadiSearch)
0855 {
0856     mEnableAkonadiSearch = enableAkonadiSearch;
0857 }
0858 
0859 QString AddresseeLineEditPrivate::searchString() const
0860 {
0861     return mSearchString;
0862 }
0863 
0864 void AddresseeLineEditPrivate::setSearchString(const QString &searchString)
0865 {
0866     mSearchString = searchString;
0867 }
0868 
0869 bool AddresseeLineEditPrivate::searchExtended() const
0870 {
0871     return mSearchExtended;
0872 }
0873 
0874 void AddresseeLineEditPrivate::setSearchExtended(bool searchExtended)
0875 {
0876     mSearchExtended = searchExtended;
0877 }
0878 
0879 bool AddresseeLineEditPrivate::smartPaste() const
0880 {
0881     return mSmartPaste;
0882 }
0883 
0884 void AddresseeLineEditPrivate::setSmartPaste(bool smartPaste)
0885 {
0886     mSmartPaste = smartPaste;
0887 }
0888 
0889 bool AddresseeLineEditPrivate::completionInitialized() const
0890 {
0891     return mCompletionInitialized;
0892 }
0893 
0894 bool AddresseeLineEditPrivate::useCompletion() const
0895 {
0896     return mUseCompletion;
0897 }
0898 
0899 void AddresseeLineEditPrivate::setUseCompletion(bool useCompletion)
0900 {
0901     mUseCompletion = useCompletion;
0902 }
0903 
0904 bool AddresseeLineEditPrivate::showOU() const
0905 {
0906     return AddresseeLineEditManager::self()->showOU();
0907 }
0908 
0909 void AddresseeLineEditPrivate::removeCompletionSource(const QString &source)
0910 {
0911     AddresseeLineEditManager::self()->removeCompletionSource(source);
0912 }
0913 
0914 int AddresseeLineEditPrivate::addCompletionSource(const QString &source, int weight)
0915 {
0916     return AddresseeLineEditManager::self()->addCompletionSource(source, weight);
0917 }
0918 
0919 void AddresseeLineEditPrivate::mightBeGroupJobsClear()
0920 {
0921     mMightBeGroupJobs.clear();
0922 }
0923 
0924 bool AddresseeLineEditPrivate::groupsIsEmpty() const
0925 {
0926     return mGroups.isEmpty();
0927 }
0928 
0929 void AddresseeLineEditPrivate::setShowRecentAddresses(bool b)
0930 {
0931     mShowRecentAddresses = b;
0932 }
0933 
0934 void AddresseeLineEditPrivate::groupsClear()
0935 {
0936     mGroups.clear();
0937 }
0938 
0939 void AddresseeLineEditPrivate::addGroups(const KContacts::ContactGroup::List &lst)
0940 {
0941     mGroups << lst;
0942 }
0943 
0944 void AddresseeLineEditPrivate::mightBeGroupJobsRemoveOne(Akonadi::ContactGroupSearchJob *search)
0945 {
0946     mMightBeGroupJobs.removeOne(search);
0947 }
0948 
0949 void AddresseeLineEditPrivate::mightBeGroupJobsAdd(Akonadi::ContactGroupSearchJob *job)
0950 {
0951     mMightBeGroupJobs.append(job);
0952 }
0953 
0954 #include "moc_addresseelineedit_p.cpp"