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"