35 #include <Akonadi/Contact/ContactSearchJob>
36 #include <Akonadi/Contact/ContactGroupSearchJob>
37 #include <Akonadi/CollectionFetchJob>
38 #include <Akonadi/EntityDisplayAttribute>
39 #include <Akonadi/ItemFetchScope>
40 #include <Akonadi/RecursiveItemFetchJob>
41 #include <Akonadi/Session>
42 #include <Akonadi/Job>
44 #include <Nepomuk2/Query/Query>
45 #include <Nepomuk2/Query/QueryServiceClient>
46 #include <Nepomuk2/Query/Result>
47 #include <Nepomuk2/Resource>
48 #include <Nepomuk2/Vocabulary/NCO>
49 #include <Nepomuk2/ResourceManager>
51 #include <KPIMUtils/Email>
53 #include <KLDAP/LdapServer>
57 #include <KConfigGroup>
58 #include <KCompletionBox>
61 #include <KStandardDirs>
62 #include <KStandardShortcut>
65 #include <solid/networking.h>
67 #include <QApplication>
75 #include <QMouseEvent>
78 #include <QtDBus/QDBusConnection>
86 class AddresseeLineEditStatic
90 AddresseeLineEditStatic()
95 nepomukSearchClient( 0 ),
96 akonadiSession(
new Akonadi::Session(
"contactsCompletionSession") ),
97 nepomukCompletionSource( 0 ),
98 contactsListed(
false )
100 KConfig config( QLatin1String(
"kpimcompletionorder" ) );
101 const KConfigGroup group( &config, QLatin1String(
"General" ) );
102 useNepomukCompletion = group.readEntry(
"UseNepomuk",
true );
105 ~AddresseeLineEditStatic()
110 delete nepomukSearchClient;
113 void slotEditCompletionOrder()
116 if( editor.exec() ) {
121 void updateLDAPWeights()
125 ldapSearch->updateCompletionWeights();
128 const int sourceIndex =
129 addCompletionSource( i18n(
"LDAP server: %1" ,client->
server().host()),
132 ldapClientToCompletionSourceMap.insert( clientIndex, sourceIndex );
138 int addCompletionSource(
const QString &source,
int weight )
140 QMap<QString,int>::iterator it = completionSourceWeights.find( source );
141 if ( it == completionSourceWeights.end() ) {
142 completionSourceWeights.insert( source, weight );
144 completionSourceWeights[source] = weight;
147 const int sourceIndex = completionSources.indexOf( source );
148 if ( sourceIndex == -1 ) {
149 completionSources.append( source );
150 return completionSources.size() - 1;
156 void removeCompletionSource(
const QString &source )
158 QMap<QString,int>::iterator it = completionSourceWeights.find( source );
159 if ( it != completionSourceWeights.end() ) {
160 completionSourceWeights.remove(source);
167 QStringList completionSources;
174 QMap<QString, int> completionSourceWeights;
178 QMap<int, int> ldapClientToCompletionSourceMap;
180 QMap<Akonadi::Collection::Id, int> akonadiCollectionToCompletionSourceMap;
182 Akonadi::Item::List akonadiPendingItems;
183 Nepomuk2::Query::QueryServiceClient* nepomukSearchClient;
184 Akonadi::Session *akonadiSession;
185 QVector<QWeakPointer<Akonadi::Job> > akonadiJobsInFlight;
186 bool useNepomukCompletion;
187 int nepomukCompletionSource;
191 K_GLOBAL_STATIC( AddresseeLineEditStatic, s_static )
196 static int s_count = 0;
197 QString name( QLatin1String(
"KPIM::AddresseeLineEdit") );
199 name += QLatin1Char(
'-');
200 name += QString::number( s_count );
212 class SourceWithWeight
219 bool operator< (
const SourceWithWeight &other )
const
221 if ( weight > other.weight ) {
225 if ( weight < other.weight ) {
229 return sourceName < other.sourceName;
233 class AddresseeLineEdit::Private
238 m_useCompletion( enableCompletion ),
239 m_completionInitialized( false ),
240 m_smartPaste( false ),
241 m_addressBookConnected( false ),
242 m_lastSearchMode( false ),
243 m_searchExtended( false ),
244 m_useSemicolonAsSeparator( false )
246 m_delayedQueryTimer.setSingleShot(
true);
247 connect( &m_delayedQueryTimer, SIGNAL(timeout()), q, SLOT(slotTriggerDelayedQueries()) );
251 void startLoadingLDAPEntries();
252 void stopLDAPLookup();
253 void setCompletedItems(
const QStringList &items,
bool autoSuggest );
254 void addCompletionItem(
const QString &
string,
int weight,
int source,
255 const QStringList *keyWords = 0 );
256 const QStringList adjustedCompletionItems(
bool fullSearch );
257 void updateSearchString();
258 void startSearches();
259 void akonadiPerformSearch();
260 void akonadiListAllContacts();
261 void akonadiHandlePending();
262 void akonadiHandleItems(
const Akonadi::Item::List &items );
263 void doCompletion(
bool ctrlT );
265 void slotCompletion();
266 void slotPopupCompletion(
const QString & );
267 void slotReturnPressed(
const QString & );
268 void slotStartLDAPLookup();
270 void slotEditCompletionOrder();
271 void slotUserCancelled(
const QString & );
272 void slotAkonadiSearchResult(
KJob * );
273 void slotAkonadiSearchDbResult(
KJob* );
274 void slotAkonadiCollectionsReceived(
const Akonadi::Collection::List & );
275 void startNepomukSearch();
276 void stopNepomukSearch();
278 void slotNepomukSearchFinished();
279 void slotTriggerDelayedQueries();
280 static KCompletion::CompOrder completionOrder();
283 QString m_previousAddresses;
284 QString m_searchString;
285 bool m_useCompletion;
286 bool m_completionInitialized;
288 bool m_addressBookConnected;
289 bool m_lastSearchMode;
290 bool m_searchExtended;
291 bool m_useSemicolonAsSeparator;
292 QTimer m_delayedQueryTimer;
295 void AddresseeLineEdit::Private::init()
297 if ( !s_static.exists() ) {
298 s_static->completion->setOrder( completionOrder() );
299 s_static->completion->setIgnoreCase(
true );
302 if ( m_useCompletion ) {
303 if ( !s_static->ldapTimer ) {
304 s_static->ldapTimer =
new QTimer;
309 if ( !s_static->nepomukSearchClient ) {
310 s_static->nepomukSearchClient =
new Nepomuk2::Query::QueryServiceClient;
311 s_static->nepomukCompletionSource = q->addCompletionSource( i18nc(
"@title:group",
"Contacts found in your data"), -1 );
314 s_static->updateLDAPWeights();
316 if ( !m_completionInitialized ) {
317 q->setCompletionObject( s_static->completion,
false );
318 q->connect( q, SIGNAL(completion(QString)),
319 q, SLOT(slotCompletion()) );
320 q->connect( q, SIGNAL(returnPressed(QString)),
321 q, SLOT(slotReturnPressed(QString)) );
323 KCompletionBox *box = q->completionBox();
324 q->connect( box, SIGNAL(activated(QString)),
325 q, SLOT(slotPopupCompletion(QString)) );
326 q->connect( box, SIGNAL(userCancelled(QString)),
327 q, SLOT(slotUserCancelled(QString)) );
329 q->connect( s_static->ldapTimer, SIGNAL(timeout()), SLOT(slotStartLDAPLookup()) );
335 q->connect( s_static->nepomukSearchClient, SIGNAL(finishedListing()), q, SLOT(slotNepomukSearchFinished()));
336 m_completionInitialized =
true;
341 void AddresseeLineEdit::Private::startLoadingLDAPEntries()
343 QString text( s_static->ldapText );
347 const int index = text.lastIndexOf( QLatin1Char(
',' ) );
349 prevAddr = text.left( index + 1 ) + QLatin1Char(
' ' );
350 text = text.mid( index + 1, 255 ).trimmed();
353 if ( text.isEmpty() ) {
357 s_static->ldapSearch->startSearch( text );
360 void AddresseeLineEdit::Private::stopLDAPLookup()
362 s_static->ldapSearch->cancelSearch();
363 s_static->ldapLineEdit = 0;
368 "select distinct ?email ?fullname where {"
370 "?r nco:hasEmailAddress ?em ."
371 "?em nco:emailAddress ?email ."
373 "FILTER( ?p in (nco:fullname, nco:nameFamily, nco:nameGiven, nco:nickname, nco:nameAdditional) )."
374 "FILTER( bif:contains(?fullname, \"'%1*'\") )."
376 "?r nco:hasEmailAddress ?em ."
377 "?em nco:emailAddress ?email ."
378 "FILTER( bif:contains(?email, \"'%1*'\") )."
379 "?r nco:fullname ?fullname ."
383 void AddresseeLineEdit::Private::startNepomukSearch()
388 if ( m_searchString.size() <= 3 || !s_static->useNepomukCompletion ) {
391 const QString query = QString::fromLatin1(
sparqlquery ).arg( m_searchString );
392 Nepomuk2::Query::RequestPropertyMap requestPropertyMap;
393 requestPropertyMap.insert( QLatin1String(
"email"), Nepomuk2::Vocabulary::NCO::hasEmailAddress() );
394 requestPropertyMap.insert( QLatin1String(
"fullname"), Nepomuk2::Vocabulary::NCO::fullname() );
395 const bool result = s_static->nepomukSearchClient->sparqlQuery( query, requestPropertyMap );
397 kDebug() <<
"Starting the nepomuk lookup failed. Search string: " << m_searchString;
401 void AddresseeLineEdit::Private::stopNepomukSearch()
403 s_static->nepomukSearchClient->close();
408 if ( results.isEmpty() || ( !q->hasFocus() && !q->completionBox()->hasFocus() ) ) {
412 Q_FOREACH(
const Nepomuk2::Query::Result& result, results) {
413 Soprano::Node node = result.requestProperty( Nepomuk2::Vocabulary::NCO::hasEmailAddress() );
414 if ( node.isValid() && node.isLiteral() ) {
415 const QString fullEmail = node.literal().toString();
416 Q_ASSERT(!fullEmail.isEmpty());
417 QString formattedName;
419 Soprano::Node nodeName = result.requestProperty( Nepomuk2::Vocabulary::NCO::fullname() );
420 if ( nodeName.isValid() && nodeName.isLiteral() ) {
421 formattedName = nodeName.literal().toString();
425 if ( formattedName.isEmpty() ) {
426 addCompletionItem( fullEmail, 1, s_static->nepomukCompletionSource );
428 const QString byFirstName = formattedName + QLatin1String(
" <") + fullEmail + QLatin1Char(
'>');
429 addCompletionItem( byFirstName, 1, s_static->nepomukCompletionSource );
435 void AddresseeLineEdit::Private::slotNepomukSearchFinished()
437 if ( q->hasFocus() || q->completionBox()->hasFocus() ) {
440 const QListWidgetItem *current = q->completionBox()->currentItem();
441 if ( !current || m_searchString.trimmed() != current->text().trimmed() ) {
442 doCompletion( m_lastSearchMode );
447 void AddresseeLineEdit::Private::setCompletedItems(
const QStringList &items,
bool autoSuggest )
449 KCompletionBox *completionBox = q->completionBox();
451 if ( !items.isEmpty() &&
452 !( items.count() == 1 && m_searchString == items.first() ) ) {
454 completionBox->clear();
455 const int numberOfItems( items.count() );
456 for (
int i = 0; i< numberOfItems; ++i )
458 QListWidgetItem *item =
new QListWidgetItem(items.at( i ), completionBox);
460 item->setFlags( item->flags()&~Qt::ItemIsSelectable );
461 completionBox->addItem( item );
463 if ( !completionBox->isVisible() ) {
464 if ( !m_searchString.isEmpty() ) {
465 completionBox->setCancelledText( m_searchString );
467 completionBox->popup();
471 if ( s_static->completion->order() == KCompletion::Weighted ) {
472 qApp->installEventFilter( q );
476 QListWidgetItem *item = completionBox->item( 1 );
478 completionBox->blockSignals(
true );
479 completionBox->setCurrentItem( item );
480 item->setSelected(
true );
481 completionBox->blockSignals(
false );
485 const int index = items.first().indexOf( m_searchString );
486 const QString newText = items.first().mid( index );
487 q->setUserSelection(
false );
488 q->setCompletedText( newText,
true );
491 if ( completionBox && completionBox->isVisible() ) {
492 completionBox->hide();
493 completionBox->setItems( QStringList() );
498 void AddresseeLineEdit::Private::addCompletionItem(
const QString &
string,
int weight,
499 int completionItemSource,
500 const QStringList *keyWords )
506 CompletionItemsMap::iterator it = s_static->completionItemMap.find(
string );
507 if ( it != s_static->completionItemMap.end() ) {
508 weight = qMax( ( *it ).first, weight );
509 ( *it ).first = weight;
511 s_static->completionItemMap.insert(
string, qMakePair( weight, completionItemSource ) );
514 s_static->completion->addItem(
string, weight);
515 if (keyWords && !keyWords->isEmpty())
516 s_static->completion->addItemWithKeys(
string, weight, keyWords);
519 const QStringList KPIM::AddresseeLineEdit::Private::adjustedCompletionItems(
bool fullSearch )
521 QStringList items = fullSearch ?
522 s_static->completion->allMatches( m_searchString ) :
523 s_static->completion->substringCompletion( m_searchString );
537 int lastSourceIndex = -1;
542 QMap<int, QStringList> sections;
543 QStringList sortedItems;
544 for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
545 CompletionItemsMap::const_iterator cit = s_static->completionItemMap.constFind( *it );
546 if ( cit == s_static->completionItemMap.constEnd() ) {
550 const int index = (*cit).second;
552 if ( s_static->completion->order() == KCompletion::Weighted ) {
553 if ( lastSourceIndex == -1 || lastSourceIndex != index ) {
554 const QString sourceLabel( s_static->completionSources[ index ] );
555 if ( sections.find( index ) == sections.end() ) {
556 it = items.insert( it, sourceLabel );
559 lastSourceIndex = index;
564 (*it).replace( QLatin1String(
" <" ), QLatin1String(
" <" ) );
566 sections[ index ].append( *it );
568 if ( s_static->completion->order() == KCompletion::Sorted ) {
569 sortedItems.append( *it );
573 if ( s_static->completion->order() == KCompletion::Weighted ) {
577 const int numberOfCompletionSources(s_static->completionSources.size());
578 for (
int i = 0; i < numberOfCompletionSources; ++i ) {
579 SourceWithWeight sww;
580 sww.sourceName = s_static->completionSources[i];
581 sww.weight = s_static->completionSourceWeights[sww.sourceName];
583 sourcesAndWeights.append( sww );
585 qSort( sourcesAndWeights.begin(), sourcesAndWeights.end() );
588 const int numberOfSources(sourcesAndWeights.size());
589 for (
int i = 0; i < numberOfSources; ++i ) {
590 const QStringList sectionItems = sections[sourcesAndWeights[i].index];
591 if ( !sectionItems.isEmpty() ) {
592 sortedItems.append( sourcesAndWeights[i].sourceName );
593 foreach (
const QString &itemInSection, sectionItems ) {
594 sortedItems.append( itemInSection );
605 void AddresseeLineEdit::Private::updateSearchString()
607 m_searchString = q->text();
610 bool inQuote =
false;
611 uint searchStringLength = m_searchString.length();
612 for ( uint i = 0; i < searchStringLength; ++i ) {
613 const QChar searchChar = m_searchString[ i ];
614 if ( searchChar == QLatin1Char(
'"' ) ) {
618 if ( searchChar == QLatin1Char(
'\\') &&
619 ( i + 1 ) < searchStringLength && m_searchString[ i + 1 ] == QLatin1Char(
'"' ) ) {
627 if ( i < searchStringLength &&
628 ( searchChar == QLatin1Char(
',') ||
629 ( m_useSemicolonAsSeparator && searchChar == QLatin1Char(
';') ) ) ) {
637 const int len = m_searchString.length();
640 while ( n < len && m_searchString[ n ].isSpace() ) {
644 m_previousAddresses = m_searchString.left( n );
645 m_searchString = m_searchString.mid( n ).trimmed();
647 m_previousAddresses.clear();
651 void AddresseeLineEdit::Private::slotTriggerDelayedQueries()
653 if (m_searchString.isEmpty())
659 akonadiPerformSearch();
662 startNepomukSearch();
665 void AddresseeLineEdit::Private::startSearches()
667 if ( Nepomuk2::ResourceManager::instance()->initialized() ) {
668 if (!m_delayedQueryTimer.isActive())
669 m_delayedQueryTimer.start(500);
680 if ( !s_static->contactsListed ) {
681 akonadiListAllContacts();
682 s_static->contactsListed =
true;
684 doCompletion(
true );
689 void AddresseeLineEdit::Private::akonadiPerformSearch()
692 if ( m_searchString.size() <= 2 ) {
695 kDebug() <<
"searching akonadi with:" << m_searchString;
698 Q_FOREACH( QWeakPointer<Akonadi::Job> job, s_static->akonadiJobsInFlight ) {
699 if ( !job.isNull() ) {
703 s_static->akonadiJobsInFlight.clear();
706 Akonadi::ContactSearchJob *contactJob =
new Akonadi::ContactSearchJob( s_static->akonadiSession );
707 Akonadi::ContactGroupSearchJob *groupJob =
new Akonadi::ContactGroupSearchJob( s_static->akonadiSession );
708 contactJob->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent );
709 groupJob->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent );
710 contactJob->setQuery( Akonadi::ContactSearchJob::NameOrEmail, m_searchString,
711 Akonadi::ContactSearchJob::ContainsWordBoundaryMatch );
712 groupJob->setQuery( Akonadi::ContactGroupSearchJob::Name, m_searchString,
713 Akonadi::ContactGroupSearchJob::StartsWithMatch );
717 q->connect( contactJob, SIGNAL(result(
KJob*)),
718 q, SLOT(slotAkonadiSearchResult(
KJob*)) );
719 q->connect( groupJob, SIGNAL(result(
KJob*)),
720 q, SLOT(slotAkonadiSearchResult(
KJob*)) );
721 s_static->akonadiJobsInFlight.append( contactJob );
722 s_static->akonadiJobsInFlight.append( groupJob );
723 akonadiHandlePending();
726 void AddresseeLineEdit::Private::akonadiListAllContacts()
728 kDebug() <<
"listing all contacts in Akonadi";
729 Akonadi::RecursiveItemFetchJob *job =
730 new Akonadi::RecursiveItemFetchJob( Akonadi::Collection::root(),
731 QStringList() << KABC::Addressee::mimeType() << KABC::ContactGroup::mimeType(),
732 s_static->akonadiSession );
733 job->fetchScope().fetchFullPayload();
734 job->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent );
735 q->connect( job, SIGNAL(result(
KJob*)),
736 q, SLOT(slotAkonadiSearchDbResult(
KJob*)) );
738 akonadiHandlePending();
741 void AddresseeLineEdit::Private::akonadiHandlePending()
743 kDebug() <<
"Pending items: " << s_static->akonadiPendingItems.size();
744 Akonadi::Item::List::iterator it = s_static->akonadiPendingItems.begin();
745 while ( it != s_static->akonadiPendingItems.end() ) {
746 const Akonadi::Item item = *it;
748 const int sourceIndex =
749 s_static->akonadiCollectionToCompletionSourceMap.value( item.parentCollection().id(), -1 );
750 if ( sourceIndex >= 0 ) {
751 kDebug() <<
"identified collection: " << s_static->completionSources[sourceIndex];
752 q->addItem( item, 1, sourceIndex );
755 it = s_static->akonadiPendingItems.erase( it );
762 void AddresseeLineEdit::Private::doCompletion(
bool ctrlT )
764 m_lastSearchMode = ctrlT;
766 const KGlobalSettings::Completion mode = q->completionMode();
768 if ( mode == KGlobalSettings::CompletionNone ) {
772 s_static->completion->setOrder( completionOrder() );
776 const QStringList completions = adjustedCompletionItems(
false );
778 if ( completions.count() > 1 ) {
780 }
else if ( completions.count() == 1 ) {
781 q->setText( m_previousAddresses + completions.first().trimmed() );
785 setCompletedItems( completions,
true );
788 q->setCompletionMode( mode );
793 case KGlobalSettings::CompletionPopupAuto:
795 if ( m_searchString.isEmpty() ) {
801 case KGlobalSettings::CompletionPopup:
803 const QStringList items = adjustedCompletionItems(
false );
804 setCompletedItems( items,
false );
808 case KGlobalSettings::CompletionShell:
810 const QString match = s_static->completion->makeCompletion( m_searchString );
811 if ( !match.isNull() && match != m_searchString ) {
812 q->setText( m_previousAddresses + match );
813 q->setModified(
true );
819 case KGlobalSettings::CompletionMan:
820 case KGlobalSettings::CompletionAuto:
823 q->setCompletionMode( q->completionMode() );
825 if ( !m_searchString.isEmpty() ) {
828 if ( m_searchExtended && m_searchString == QLatin1String(
"\"" ) ) {
829 m_searchExtended =
false;
830 m_searchString.clear();
831 q->setText( m_previousAddresses );
835 QString match = s_static->completion->makeCompletion( m_searchString );
837 if ( !match.isEmpty() ) {
838 if ( match != m_searchString ) {
839 QString adds = m_previousAddresses + match;
840 q->setCompletedText( adds );
843 if ( !m_searchString.startsWith( QLatin1Char(
'\"' ) ) ) {
845 match = s_static->completion->makeCompletion( QLatin1String(
"\"" ) + m_searchString );
846 if ( !match.isEmpty() && match != m_searchString ) {
847 m_searchString = QLatin1String(
"\"" ) + m_searchString;
848 m_searchExtended =
true;
849 q->setText( m_previousAddresses + m_searchString );
850 q->setCompletedText( m_previousAddresses + match );
852 }
else if ( m_searchExtended ) {
854 m_searchString = m_searchString.mid( 1 );
855 m_searchExtended =
false;
856 q->setText( m_previousAddresses + m_searchString );
858 match = s_static->completion->makeCompletion( m_searchString );
859 if ( !match.isEmpty() && match != m_searchString ) {
860 const QString adds = m_previousAddresses + match;
861 q->setCompletedText( adds );
869 case KGlobalSettings::CompletionNone:
875 void AddresseeLineEdit::Private::slotCompletion()
882 updateSearchString();
883 if ( q->completionBox() ) {
884 q->completionBox()->setCancelledText( m_searchString );
888 doCompletion(
false );
891 void AddresseeLineEdit::Private::slotPopupCompletion(
const QString &completion )
893 q->setText( m_previousAddresses + completion.trimmed() );
895 updateSearchString();
896 q->emitTextCompleted();
899 void AddresseeLineEdit::Private::slotReturnPressed(
const QString & )
901 if ( !q->completionBox()->selectedItems().isEmpty() ) {
902 slotPopupCompletion( q->completionBox()->selectedItems().first()->text() );
906 void AddresseeLineEdit::Private::slotStartLDAPLookup()
908 if ( Solid::Networking::status() == Solid::Networking::Unconnected ) {
912 const KGlobalSettings::Completion mode = q->completionMode();
914 if ( mode == KGlobalSettings::CompletionNone ) {
918 if ( !s_static->ldapSearch->isAvailable() ) {
922 if ( s_static->ldapLineEdit != q ) {
926 startLoadingLDAPEntries();
931 if ( results.isEmpty() || s_static->ldapLineEdit != q ) {
936 KABC::Addressee contact;
937 contact.setNameFromString( result.
name );
938 contact.setEmails( result.
email );
940 if ( !s_static->ldapClientToCompletionSourceMap.contains( result.
clientNumber ) ) {
941 s_static->updateLDAPWeights();
945 s_static->ldapClientToCompletionSourceMap[ result.
clientNumber ] );
948 if ( ( q->hasFocus() || q->completionBox()->hasFocus() ) &&
949 q->completionMode() != KGlobalSettings::CompletionNone &&
950 q->completionMode() != KGlobalSettings::CompletionShell ) {
951 q->setText( m_previousAddresses + m_searchString );
954 const QListWidgetItem *current = q->completionBox()->currentItem();
955 if ( !current || m_searchString.trimmed() != current->text().trimmed() ) {
956 doCompletion( m_lastSearchMode );
961 void AddresseeLineEdit::Private::slotEditCompletionOrder()
966 s_static->slotEditCompletionOrder();
971 void AddresseeLineEdit::Private::slotUserCancelled(
const QString &cancelText )
973 if ( s_static->ldapSearch && s_static->ldapLineEdit == q ) {
977 if ( s_static->nepomukSearchClient ) {
980 q->userCancelled( m_previousAddresses + cancelText );
983 void AddresseeLineEdit::Private::akonadiHandleItems(
const Akonadi::Item::List &items )
987 foreach (
const Akonadi::Item &item, items ) {
990 const int sourceIndex =
991 s_static->akonadiCollectionToCompletionSourceMap.value( item.parentCollection().id(), -1 );
992 if ( sourceIndex == -1 ) {
993 kDebug() <<
"Fetching New collection: " << item.parentCollection().id();
995 Akonadi::CollectionFetchJob *collectionJob =
996 new Akonadi::CollectionFetchJob( item.parentCollection(),
997 Akonadi::CollectionFetchJob::Base,
998 s_static->akonadiSession );
999 connect( collectionJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
1000 q, SLOT(slotAkonadiCollectionsReceived(Akonadi::Collection::List)) );
1003 s_static->akonadiCollectionToCompletionSourceMap.insert( item.parentCollection().id(), -2 );
1004 s_static->akonadiPendingItems.append( item );
1005 }
else if ( sourceIndex == -2 ) {
1008 s_static->akonadiPendingItems.append( item );
1010 q->addItem( item, 1, sourceIndex );
1014 if ( !items.isEmpty() ) {
1015 const QListWidgetItem *current = q->completionBox()->currentItem();
1016 if ( !current || m_searchString.trimmed() != current->text().trimmed() ) {
1017 doCompletion( m_lastSearchMode );
1022 void AddresseeLineEdit::Private::slotAkonadiSearchResult(
KJob *job )
1024 const int index = s_static->akonadiJobsInFlight.indexOf( qobject_cast<Akonadi::Job*>( job ) );
1026 s_static->akonadiJobsInFlight.remove( index );
1027 const Akonadi::ContactSearchJob *contactJob =
1028 qobject_cast<Akonadi::ContactSearchJob*>( job );
1029 const Akonadi::ContactGroupSearchJob *groupJob =
1030 qobject_cast<Akonadi::ContactGroupSearchJob*>( job );
1032 Akonadi::Item::List items;
1034 items += contactJob->items();
1035 kDebug() <<
"Found" << contactJob->contacts().size() <<
"contacts";
1036 }
else if ( groupJob ) {
1037 items += groupJob->items();
1038 kDebug() <<
"Found" << groupJob->contactGroups().size() <<
"groups";
1040 akonadiHandleItems( items );
1043 void AddresseeLineEdit::Private::slotAkonadiSearchDbResult(
KJob *job )
1045 const Akonadi::RecursiveItemFetchJob *contactJob =
1046 qobject_cast<Akonadi::RecursiveItemFetchJob*>( job );
1047 Akonadi::Item::List items;
1049 items += contactJob->items();
1050 kDebug() <<
"Found in DB directly:" << contactJob->items().size() <<
"contacts";
1052 akonadiHandleItems( items );
1055 void AddresseeLineEdit::Private::slotAkonadiCollectionsReceived(
1056 const Akonadi::Collection::List &collections )
1058 foreach (
const Akonadi::Collection &collection, collections ) {
1059 if ( collection.isValid() ) {
1060 const QString sourceString = collection.displayName();
1061 const int index = q->addCompletionSource( sourceString, 1 );
1062 kDebug() <<
"\treceived: " << sourceString <<
"index: " << index;
1063 s_static->akonadiCollectionToCompletionSourceMap.insert( collection.id(), index );
1068 akonadiHandlePending();
1070 const QListWidgetItem *current = q->completionBox()->currentItem();
1071 if ( !current || m_searchString.trimmed() != current->text().trimmed() ) {
1072 doCompletion( m_lastSearchMode );
1077 KCompletion::CompOrder AddresseeLineEdit::Private::completionOrder()
1079 KConfig _config( QLatin1String(
"kpimcompletionorder" ) );
1080 const KConfigGroup config( &_config, QLatin1String(
"General" ) );
1081 const QString order =
1082 config.readEntry( QLatin1String(
"CompletionOrder" ), QString::fromLatin1(
"Weighted" ) );
1084 if ( order == QLatin1String(
"Weighted" ) ) {
1085 return KCompletion::Weighted;
1087 return KCompletion::Sorted;
1092 :
KLineEdit( parent ), d( new Private( this, enableCompletion ) )
1095 setClickMessage( QString() );
1102 if ( s_static->ldapSearch && s_static->ldapLineEdit ==
this ) {
1103 d->stopLDAPLookup();
1105 if( s_static->nepomukSearchClient ) {
1106 d->stopNepomukSearch();
1113 KLineEdit::setFont( font );
1115 if ( d->m_useCompletion ) {
1116 completionBox()->setFont( font );
1122 d->m_useSemicolonAsSeparator = useSemicolonAsSeparator;
1127 bool accept =
false;
1129 const int key =
event->key() |
event->modifiers();
1131 if ( KStandardShortcut::shortcut( KStandardShortcut::SubstringCompletion ).contains( key ) ) {
1133 d->updateSearchString();
1135 d->doCompletion(
true );
1137 }
else if ( KStandardShortcut::shortcut( KStandardShortcut::TextCompletion ).contains( key ) ) {
1138 const int len = text().length();
1140 if ( len == cursorPosition() ) {
1141 d->updateSearchString();
1143 d->doCompletion(
true );
1148 const QString oldContent = text();
1150 KLineEdit::keyPressEvent( event );
1155 if ( oldContent == text() ) {
1159 if ( event->isAccepted() ) {
1160 d->updateSearchString();
1162 QString searchString( d->m_searchString );
1164 if ( d->m_searchExtended ) {
1165 searchString = d->m_searchString.mid( 1 );
1168 if ( d->m_useCompletion && s_static->ldapTimer ) {
1169 if ( s_static->ldapText != searchString || s_static->ldapLineEdit !=
this ) {
1170 d->stopLDAPLookup();
1173 s_static->ldapText = searchString;
1174 s_static->ldapLineEdit =
this;
1175 s_static->ldapTimer->setSingleShot(
true );
1176 s_static->ldapTimer->start( 500 );
1183 if ( !d->m_smartPaste ) {
1184 KLineEdit::insert( t );
1188 QString newText = t.trimmed();
1189 if ( newText.isEmpty() ) {
1194 QStringList lines = newText.split( QRegExp( QLatin1String(
"\r?\n" ) ), QString::SkipEmptyParts );
1195 QStringList::iterator end( lines.end() );
1196 for ( QStringList::iterator it = lines.begin(); it != end; ++it ) {
1198 (*it).remove( QRegExp( QLatin1String(
",?\\s*$" ) ) );
1200 newText = lines.join( QLatin1String(
", " ) );
1202 if ( newText.startsWith( QLatin1String(
"mailto:" ) ) ) {
1203 const KUrl url( newText );
1204 newText = url.path();
1205 }
else if ( newText.indexOf( QLatin1String(
" at " ) ) != -1 ) {
1207 newText.replace( QLatin1String(
" at " ), QLatin1String(
"@" ) );
1208 newText.replace( QLatin1String(
" dot " ), QLatin1String(
"." ) );
1209 }
else if ( newText.indexOf( QLatin1String(
"(at)" ) ) != -1 ) {
1210 newText.replace( QRegExp( QLatin1String(
"\\s*\\(at\\)\\s*" ) ), QLatin1String(
"@" ) );
1213 QString contents = text();
1215 int pos = cursorPosition();
1217 if ( hasSelectedText() ) {
1219 start_sel = selectionStart();
1221 contents = contents.left( start_sel ) + contents.mid( start_sel + selectedText().length() );
1224 int eot = contents.length();
1225 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
1230 }
else if ( pos >= eot ) {
1231 if ( contents[ eot - 1 ] == QLatin1Char(
',' ) ) {
1234 contents.truncate( eot );
1235 contents += QLatin1String(
", " );
1239 contents = contents.left( pos ) + newText + contents.mid( pos );
1241 setModified(
true );
1242 setCursorPosition( pos + newText.length() );
1247 const int cursorPos = cursorPosition();
1248 KLineEdit::setText( text.trimmed() );
1249 setCursorPosition( cursorPos );
1254 if ( d->m_useCompletion ) {
1255 d->m_smartPaste =
true;
1259 d->m_smartPaste =
false;
1265 #ifndef QT_NO_CLIPBOARD
1266 if ( d->m_useCompletion &&
1267 QApplication::clipboard()->supportsSelection() &&
1269 event->button() == Qt::MidButton ) {
1270 d->m_smartPaste =
true;
1274 KLineEdit::mouseReleaseEvent( event );
1275 d->m_smartPaste =
false;
1278 #ifndef QT_NO_DRAGANDDROP
1281 if ( !isReadOnly() ) {
1282 const KUrl::List uriList = KUrl::List::fromMimeData( event->mimeData() );
1283 if ( !uriList.isEmpty() ) {
1284 QString contents = text();
1286 int eot = contents.length();
1287 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
1292 }
else if ( contents[ eot - 1 ] == QLatin1Char(
',') ) {
1294 contents.truncate( eot );
1296 bool mailtoURL =
false;
1298 foreach (
const KUrl &url, uriList ) {
1299 if ( url.protocol() == QLatin1String(
"mailto" ) ) {
1302 address = KUrl::fromPercentEncoding( url.path().toLatin1() );
1303 address = KMime::decodeRFC2047String( address.toLatin1() );
1304 if ( !contents.isEmpty() ) {
1305 contents.append( QLatin1String(
", " ) );
1307 contents.append( address );
1312 setModified(
true );
1317 const QString dropData = QString::fromUtf8( event->encodedData(
"text/plain" ) );
1318 const QStringList addrs = KPIMUtils::splitAddressList( dropData );
1319 if ( !addrs.isEmpty() ) {
1320 setText( KPIMUtils::normalizeAddressesAndDecodeIdn( dropData ) );
1321 setModified(
true );
1327 if ( d->m_useCompletion ) {
1328 d->m_smartPaste =
true;
1331 QLineEdit::dropEvent( event );
1332 d->m_smartPaste =
false;
1334 #endif // QT_NO_DRAGANDDROP
1338 setCursorPosition( text().length() );
1343 d->m_useCompletion = enable;
1348 if ( item.hasPayload<KABC::Addressee>() ) {
1349 addContact( item.payload<KABC::Addressee>(), weight, source );
1350 }
else if ( item.hasPayload<KABC::ContactGroup>() ) {
1351 addContactGroup( item.payload<KABC::ContactGroup>(), weight, source );
1357 d->addCompletionItem( group.name(), weight, source );
1362 const QStringList emails = addr.emails();
1363 QStringList::ConstIterator it;
1364 int isPrefEmail = 1;
1365 QStringList::ConstIterator end( emails.constEnd() );
1366 for ( it = emails.constBegin(); it != end; ++it ) {
1368 const QString email( (*it) );
1369 const QString givenName = addr.givenName();
1370 const QString familyName= addr.familyName();
1371 const QString nickName = addr.nickName();
1372 QString fullEmail = addr.fullEmail( email );
1375 QString fullName = givenName;
1376 if (!familyName.isEmpty()) {
1377 if (!fullName.isEmpty())
1378 fullName += QLatin1Char(
' ');
1379 fullName += familyName;
1383 if (!fullName.isEmpty()) {
1384 const QString address = KPIMUtils::normalizedAddress(fullName, email, QString());
1385 if (fullEmail != address) {
1388 d->addCompletionItem(address, weight + isPrefEmail, source);
1392 QStringList keyWords;
1393 if (!nickName.isEmpty()) {
1394 keyWords.append(nickName);
1397 d->addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords );
1403 #ifndef QT_NO_CONTEXTMENU
1408 menu->exec( event->globalPos() );
1416 setCompletionModeDisabled( KGlobalSettings::CompletionMan );
1417 setCompletionModeDisabled( KGlobalSettings::CompletionPopupAuto );
1419 QMenu *menu = KLineEdit::createStandardContextMenu();
1424 if ( d->m_useCompletion ) {
1425 menu->addAction( i18n(
"Configure Completion Order..." ),
1426 this, SLOT(slotEditCompletionOrder()) );
1434 s_static->removeCompletionSource(source);
1439 return s_static->addCompletionSource(source,weight);
1442 bool KPIM::AddresseeLineEdit::eventFilter(
QObject *
object, QEvent *event )
1444 if ( d->m_completionInitialized &&
1445 (
object == completionBox() ||
1446 completionBox()->findChild<QWidget*>( object->objectName() ) ==
object ) ) {
1447 if ( event->type() == QEvent::MouseButtonPress ||
1448 event->type() == QEvent::MouseMove ||
1449 event->type() == QEvent::MouseButtonRelease ||
1450 event->type() == QEvent::MouseButtonDblClick ) {
1452 const QMouseEvent* mouseEvent =
static_cast<QMouseEvent*
>( event );
1454 QListWidgetItem *item = completionBox()->itemAt( mouseEvent->pos() );
1458 bool eat =
event->type() == QEvent::MouseMove;
1463 const Qt::MouseButtons buttons = mouseEvent->buttons();
1464 if ( event->type() == QEvent::MouseButtonPress ||
1465 event->type() == QEvent::MouseButtonDblClick ||
1466 buttons & Qt::LeftButton || buttons & Qt::MidButton ||
1467 buttons & Qt::RightButton ) {
1474 completionBox()->setCurrentItem( item );
1475 item->setSelected(
true );
1476 if ( event->type() == QEvent::MouseMove ) {
1484 if ( (
object ==
this ) &&
1485 (
event->type() == QEvent::ShortcutOverride ) ) {
1486 QKeyEvent *keyEvent =
static_cast<QKeyEvent*
>( event );
1487 if ( keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down ||
1488 keyEvent->key() == Qt::Key_Tab ) {
1494 if ( (
object ==
this ) &&
1495 ( event->type() == QEvent::KeyPress ||
event->type() == QEvent::KeyRelease ) &&
1496 completionBox()->isVisible() ) {
1497 const QKeyEvent *keyEvent =
static_cast<QKeyEvent*
>( event );
1498 int currentIndex = completionBox()->currentRow();
1499 if ( currentIndex < 0 ) {
1502 if ( keyEvent->key() == Qt::Key_Up ) {
1506 const QListWidgetItem *itemAbove = completionBox()->item( currentIndex );
1510 if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) {
1512 completionBox()->setCurrentRow( currentIndex - 1 );
1513 completionBox()->item( currentIndex - 1 )->setSelected(
true );
1514 }
else if ( currentIndex == 0 ) {
1517 completionBox()->scrollToItem( completionBox()->item( 0 ) );
1518 QListWidgetItem *item = completionBox()->item( currentIndex );
1522 item = completionBox()->item( currentIndex );
1524 completionBox()->setCurrentItem( item );
1525 item->setSelected(
true );
1531 }
else if ( keyEvent->key() == Qt::Key_Down ) {
1534 const QListWidgetItem *itemBelow = completionBox()->item( currentIndex );
1536 if ( completionBox()->item( currentIndex + 1 ) ) {
1538 completionBox()->setCurrentRow( currentIndex + 1 );
1539 completionBox()->item( currentIndex + 1 )->setSelected(
true );
1542 QListWidgetItem *item = completionBox()->item( currentIndex );
1544 completionBox()->setCurrentItem( item );
1545 item->setSelected(
true );
1554 QListWidgetItem *item = completionBox()->item( currentIndex );
1556 completionBox()->setCurrentItem( item );
1557 item->setSelected(
true );
1559 }
else if ( event->type() == QEvent::KeyRelease &&
1560 ( keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab ) ) {
1562 QListWidgetItem *myHeader = 0;
1563 int myHeaderIndex = -1;
1564 const int iterationStep = keyEvent->key() == Qt::Key_Tab ? 1 : -1;
1565 int index = qMin( qMax( currentIndex - iterationStep, 0 ), completionBox()->count() - 1 );
1566 while ( index >= 0 ) {
1567 if (
itemIsHeader( completionBox()->item( index ) ) ) {
1568 myHeader = completionBox()->item( index );
1569 myHeaderIndex = index;
1575 Q_ASSERT( myHeader );
1578 QListWidgetItem *nextHeader = 0;
1583 if ( keyEvent->key() == Qt::Key_Tab ) {
1586 index = myHeaderIndex;
1588 j = completionBox()->count() - 1;
1590 j = ( index - 1 ) % completionBox()->count();
1593 while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
1597 j = ( j + iterationStep ) % completionBox()->count();
1600 if ( nextHeader && nextHeader != myHeader ) {
1601 QListWidgetItem *item = completionBox()->item( j + 1 );
1603 completionBox()->setCurrentItem( item );
1604 item->setSelected(
true );
1612 return KLineEdit::eventFilter(
object, event );
1615 void AddresseeLineEdit::emitTextCompleted()
1623 #include "addresseelineedit.moc"
static const char * sparqlquery
const KLDAP::LdapServer server() const
Returns the ldap server information that are used by this client.
virtual void contextMenuEvent(QContextMenuEvent *)
Reimplemented for internal reasons.
int clientNumber
The client the contact comes from (used for sorting in a ldap-only lookup).
virtual void dropEvent(QDropEvent *)
Reimplemented for smart insertion of dragged email addresses.
void allowSemicolonAsSeparator(bool allow)
Sets whether semicolons are allowed as separators.
int completionWeight() const
Returns the completion weight of this client.
static QString newLineEditObjectName()
Describes the result returned by an LdapClientSearch query.
void addContact(const KABC::Addressee &contact, int weight, int source=-1)
Adds a new contact to the completion with a given weight and source index.
QString name
The full name of the contact.
void addItem(const Akonadi::Item &item, int weight, int source=-1)
virtual ~AddresseeLineEdit()
Destroys the addressee line edit.
static const QString s_completionItemIndentString
virtual void keyPressEvent(QKeyEvent *)
Reimplemented for internal reasons.
void removeCompletionSource(const QString &source)
virtual QMenu * createStandardContextMenu()
Reimplemented for subclass access to menu.
virtual void insert(const QString &)
Reimplemented for smart insertion of email addresses.
virtual void setText(const QString &text)
Reimplemented for stripping whitespace after completion Danger: This is not virtual in the base class...
void cursorAtEnd()
Moves the cursor at the end of the line edit.
int addCompletionSource(const QString &name, int weight)
Adds the name of a completion source and its weight to the internal list of completion sources and re...
AddresseeLineEdit(QWidget *parent, bool enableCompletion=true)
Creates a new addressee line edit.
static bool itemIsHeader(const QListWidgetItem *item)
KMailCompletion allows lookup of email addresses by keyword.
virtual void mouseReleaseEvent(QMouseEvent *)
Reimplemented for smart insertion with middle mouse button.
virtual void paste()
Reimplemented for smart insertion of pasted email addresses.
void setFont(const QFont &font)
Reimplemented for setting the font for line edit and completion box.
QMap< QString, QPair< int, int > > CompletionItemsMap
QStringList email
The list of emails of the contact.
An object that represents a configured LDAP server.
void enableCompletion(bool enable)
Sets whether autocompletion shall be enabled.
int completionWeight
The weight of the contact (used for sorting in a completion list).
void addContactGroup(const KABC::ContactGroup &group, int weight, int source=-1)
Same as the above, but this time with contact groups.