Add KMessageWidget-based inline warning about disabled Nepomuk.
[kdepim.git] / libkdepim / addresseelineedit.cpp
blob3868a019dc01cbd9dfc6438f91a07c6e339a44f5
1 /*
2 This file is part of libkdepim.
4 Copyright (c) 2002 Helge Deller <deller@gmx.de>
5 Copyright (c) 2002 Lubos Lunak <llunak@suse.cz>
6 Copyright (c) 2001,2003 Carsten Pfeiffer <pfeiffer@kde.org>
7 Copyright (c) 2001 Waldo Bastian <bastian@kde.org>
8 Copyright (c) 2004 Daniel Molkentin <danimo@klaralvdalens-datakonsult.se>
9 Copyright (c) 2004 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se>
11 This library is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Library General Public
13 License as published by the Free Software Foundation; either
14 version 2 of the License, or (at your option) any later version.
16 This library is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Library General Public License for more details.
21 You should have received a copy of the GNU Library General Public License
22 along with this library; see the file COPYING.LIB. If not, write to
23 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 Boston, MA 02110-1301, USA.
27 #include "addresseelineedit.h"
29 #ifndef Q_OS_WINCE
30 #include "completionordereditor.h"
31 #endif
33 #include <Akonadi/Contact/ContactSearchJob>
34 #include <Akonadi/Contact/ContactGroupSearchJob>
35 #include <Akonadi/CollectionFetchJob>
36 #include <Akonadi/EntityDisplayAttribute>
37 #include <Akonadi/ItemFetchScope>
39 #include <KPIMUtils/Email>
41 #include <KLDAP/LdapServer>
43 #include <KMime/Util>
45 #include <KConfigGroup>
46 #include <KCompletionBox>
47 #include <KDebug>
48 #include <KLocale>
49 #include <KStandardDirs>
50 #include <KStandardShortcut>
51 #include <KUrl>
53 #include <solid/networking.h>
55 #include <QApplication>
56 #include <QCursor>
57 #include <QObject>
58 #include <QRegExp>
59 #include <QEvent>
60 #include <QClipboard>
61 #include <QKeyEvent>
62 #include <QDropEvent>
63 #include <QMouseEvent>
64 #include <QMenu>
65 #include <QTimer>
66 #include <QtDBus/QDBusConnection>
68 using namespace KPIM;
70 class AddresseeLineEditStatic
72 public:
74 AddresseeLineEditStatic()
75 : completion( new KMailCompletion ),
76 ldapTimer( 0 ),
77 ldapSearch( 0 ),
78 ldapLineEdit( 0 )
82 ~AddresseeLineEditStatic()
84 delete completion;
85 delete ldapTimer;
86 delete ldapSearch;
89 KMailCompletion *completion;
90 KPIM::CompletionItemsMap completionItemMap;
91 QStringList completionSources;
92 QTimer *ldapTimer;
93 KLDAP::LdapClientSearch *ldapSearch;
94 QString ldapText;
95 AddresseeLineEdit *ldapLineEdit;
96 // The weights associated with the completion sources in s_static->completionSources.
97 // Both are maintained by addCompletionSource(), don't attempt to modifiy those yourself.
98 QMap<QString, int> completionSourceWeights;
99 // maps LDAP client indices to completion source indices
100 // the assumption that they are always the first n indices in s_static->completion
101 // does not hold when clients are added later on
102 QMap<int, int> ldapClientToCompletionSourceMap;
103 // holds the cached mapping from akonadi collection id to the completion source index
104 QMap<Akonadi::Collection::Id, int> akonadiCollectionToCompletionSourceMap;
105 // a list of akonadi items (contacts) that have not had their collection fetched yet
106 Akonadi::Item::List akonadiPendingItems;
109 K_GLOBAL_STATIC( AddresseeLineEditStatic, s_static )
111 // needs to be unique, but the actual name doesn't matter much
112 static QByteArray newLineEditObjectName()
114 static int s_count = 0;
115 QByteArray name( "KPIM::AddresseeLineEdit" );
116 if ( s_count++ ) {
117 name += '-';
118 name += QByteArray().setNum( s_count );
120 return name;
123 static const QString s_completionItemIndentString = " ";
125 static bool itemIsHeader( const QListWidgetItem *item )
127 return item && !item->text().startsWith( s_completionItemIndentString );
130 class SourceWithWeight
132 public:
133 int weight; // the weight of the source
134 QString sourceName; // the name of the source, e.g. "LDAP Server"
135 int index; // index into s_static->completionSources
137 bool operator< ( const SourceWithWeight &other ) const
139 if ( weight > other.weight ) {
140 return true;
143 if ( weight < other.weight ) {
144 return false;
147 return sourceName < other.sourceName;
151 class AddresseeLineEdit::Private
153 public:
154 Private( AddresseeLineEdit *qq, bool enableCompletion )
155 : q( qq ),
156 m_useCompletion( enableCompletion ),
157 m_completionInitialized( false ),
158 m_smartPaste( false ),
159 m_addressBookConnected( false ),
160 m_searchExtended( false ),
161 m_useSemicolonAsSeparator( false )
165 void init();
166 void startLoadingLDAPEntries();
167 void stopLDAPLookup();
168 void updateLDAPWeights();
169 void setCompletedItems( const QStringList &items, bool autoSuggest );
170 void addCompletionItem( const QString &string, int weight, int source,
171 const QStringList *keyWords = 0 );
172 const QStringList adjustedCompletionItems( bool fullSearch );
173 void updateSearchString();
174 void akonadiPerformSearch();
175 void akonadiHandlePending();
176 void doCompletion( bool ctrlT );
178 void slotCompletion();
179 void slotPopupCompletion( const QString & );
180 void slotReturnPressed( const QString & );
181 void slotStartLDAPLookup();
182 void slotLDAPSearchData( const KLDAP::LdapResult::List & );
183 void slotEditCompletionOrder();
184 void slotUserCancelled( const QString & );
185 void slotAkonadiSearchResult( KJob * );
186 void slotAkonadiCollectionsReceived( const Akonadi::Collection::List & );
188 static KCompletion::CompOrder completionOrder();
190 AddresseeLineEdit *q;
191 QString m_previousAddresses;
192 QString m_searchString;
193 bool m_useCompletion;
194 bool m_completionInitialized;
195 bool m_smartPaste;
196 bool m_addressBookConnected;
197 bool m_lastSearchMode;
198 bool m_searchExtended; //has \" been added?
199 bool m_useSemicolonAsSeparator;
202 void AddresseeLineEdit::Private::init()
204 if ( !s_static.exists() ) {
205 s_static->completion->setOrder( completionOrder() );
206 s_static->completion->setIgnoreCase( true );
209 if ( m_useCompletion ) {
210 if ( !s_static->ldapTimer ) {
211 s_static->ldapTimer = new QTimer;
212 s_static->ldapSearch = new KLDAP::LdapClientSearch;
215 updateLDAPWeights();
217 if ( !m_completionInitialized ) {
218 q->setCompletionObject( s_static->completion, false );
219 q->connect( q, SIGNAL(completion(QString)),
220 q, SLOT(slotCompletion()) );
221 q->connect( q, SIGNAL(returnPressed(QString)),
222 q, SLOT(slotReturnPressed(QString)) );
224 KCompletionBox *box = q->completionBox();
225 q->connect( box, SIGNAL(activated(QString)),
226 q, SLOT(slotPopupCompletion(QString)) );
227 q->connect( box, SIGNAL(userCancelled(QString)),
228 q, SLOT(slotUserCancelled(QString)) );
230 q->connect( s_static->ldapTimer, SIGNAL(timeout()), SLOT(slotStartLDAPLookup()) );
231 q->connect( s_static->ldapSearch, SIGNAL(searchData(KLDAP::LdapResult::List)),
232 SLOT(slotLDAPSearchData(KLDAP::LdapResult::List)) );
234 m_completionInitialized = true;
239 void AddresseeLineEdit::Private::startLoadingLDAPEntries()
241 QString text( s_static->ldapText );
243 // TODO cache last?
244 QString prevAddr;
245 const int index = text.lastIndexOf( QLatin1Char( ',' ) );
246 if ( index >= 0 ) {
247 prevAddr = text.left( index + 1 ) + QLatin1Char( ' ' );
248 text = text.mid( index + 1, 255 ).trimmed();
251 if ( text.isEmpty() ) {
252 return;
255 s_static->ldapSearch->startSearch( text );
258 void AddresseeLineEdit::Private::stopLDAPLookup()
260 s_static->ldapSearch->cancelSearch();
261 s_static->ldapLineEdit = 0;
264 void AddresseeLineEdit::Private::updateLDAPWeights()
266 /* Add completion sources for all ldap server, 0 to n. Added first so
267 * that they map to the LdapClient::clientNumber() */
268 s_static->ldapSearch->updateCompletionWeights();
269 int clientIndex = 0;
270 foreach ( const KLDAP::LdapClient *client, s_static->ldapSearch->clients() ) {
271 const int sourceIndex =
272 q->addCompletionSource( QLatin1String( "LDAP server: " ) + client->server().host(),
273 client->completionWeight() );
275 s_static->ldapClientToCompletionSourceMap.insert( clientIndex, sourceIndex );
277 clientIndex++;
281 void AddresseeLineEdit::Private::setCompletedItems( const QStringList &items, bool autoSuggest )
283 KCompletionBox *completionBox = q->completionBox();
285 if ( !items.isEmpty() &&
286 !( items.count() == 1 && m_searchString == items.first() ) ) {
288 completionBox->setItems( items );
290 if ( !completionBox->isVisible() ) {
291 if ( !m_searchString.isEmpty() ) {
292 completionBox->setCancelledText( m_searchString );
294 completionBox->popup();
295 // we have to install the event filter after popup(), since that
296 // calls show(), and that's where KCompletionBox installs its filter.
297 // We want to be first, though, so do it now.
298 if ( s_static->completion->order() == KCompletion::Weighted ) {
299 qApp->installEventFilter( q );
303 QListWidgetItem *item = completionBox->item( 1 );
304 if ( item ) {
305 completionBox->blockSignals( true );
306 completionBox->setCurrentItem( item );
307 item->setSelected( true );
308 completionBox->blockSignals( false );
311 if ( autoSuggest ) {
312 const int index = items.first().indexOf( m_searchString );
313 const QString newText = items.first().mid( index );
314 q->setUserSelection( false );
315 q->setCompletedText( newText, true );
317 } else {
318 if ( completionBox && completionBox->isVisible() ) {
319 completionBox->hide();
320 completionBox->setItems( QStringList() );
325 void AddresseeLineEdit::Private::addCompletionItem( const QString &string, int weight,
326 int completionItemSource,
327 const QStringList *keyWords )
329 // Check if there is an exact match for item already, and use the
330 // maximum weight if so. Since there's no way to get the information
331 // from KCompletion, we have to keep our own QMap.
333 CompletionItemsMap::iterator it = s_static->completionItemMap.find( string );
334 if ( it != s_static->completionItemMap.end() ) {
335 weight = qMax( ( *it ).first, weight );
336 ( *it ).first = weight;
337 } else {
338 s_static->completionItemMap.insert( string, qMakePair( weight, completionItemSource ) );
341 if ( keyWords == 0 ) {
342 s_static->completion->addItem( string, weight );
343 } else {
344 s_static->completion->addItemWithKeys( string, weight, keyWords );
348 const QStringList KPIM::AddresseeLineEdit::Private::adjustedCompletionItems( bool fullSearch )
350 QStringList items = fullSearch ?
351 s_static->completion->allMatches( m_searchString ) :
352 s_static->completion->substringCompletion( m_searchString );
354 //force items to be sorted by email
355 items.sort();
357 // For weighted mode, the algorithm is the following:
358 // In the first loop, we add each item to its section (there is one section per completion source)
359 // We also add spaces in front of the items.
360 // The sections are appended to the items list.
361 // In the second loop, we then walk through the sections and add all the items in there to the
362 // sorted item list, which is the final result.
364 // The algo for non-weighted mode is different.
366 int lastSourceIndex = -1;
367 unsigned int i = 0;
369 // Maps indices of the items list, which are section headers/source items,
370 // to a QStringList which are the items of that section/source.
371 QMap<int, QStringList> sections;
372 QStringList sortedItems;
373 for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
374 CompletionItemsMap::const_iterator cit = s_static->completionItemMap.constFind( *it );
375 if ( cit == s_static->completionItemMap.constEnd() ) {
376 continue;
379 const int index = (*cit).second;
381 if ( s_static->completion->order() == KCompletion::Weighted ) {
382 if ( lastSourceIndex == -1 || lastSourceIndex != index ) {
383 const QString sourceLabel( s_static->completionSources[ index ] );
384 if ( sections.find( index ) == sections.end() ) {
385 it = items.insert( it, sourceLabel );
386 ++it; //skip new item
388 lastSourceIndex = index;
391 (*it) = (*it).prepend( s_completionItemIndentString );
392 // remove preferred email sort <blank> added in addContact()
393 (*it).replace( QLatin1String( " <" ), QLatin1String( " <" ) );
395 sections[ index ].append( *it );
397 if ( s_static->completion->order() == KCompletion::Sorted ) {
398 sortedItems.append( *it );
402 if ( s_static->completion->order() == KCompletion::Weighted ) {
404 // Sort the sections
405 QList<SourceWithWeight> sourcesAndWeights;
406 for ( int i = 0; i < s_static->completionSources.size(); i++ ) {
407 SourceWithWeight sww;
408 sww.sourceName = s_static->completionSources[i];
409 sww.weight = s_static->completionSourceWeights[sww.sourceName];
410 sww.index = i;
411 sourcesAndWeights.append( sww );
413 qSort( sourcesAndWeights.begin(), sourcesAndWeights.end() );
415 // Add the sections and their items to the final sortedItems result list
416 for ( int i = 0; i < sourcesAndWeights.size(); i++ ) {
417 const QStringList sectionItems = sections[sourcesAndWeights[i].index];
418 if ( !sectionItems.isEmpty() ) {
419 sortedItems.append( sourcesAndWeights[i].sourceName );
420 foreach ( const QString &itemInSection, sectionItems ) {
421 sortedItems.append( itemInSection );
425 } else {
426 sortedItems.sort();
429 return sortedItems;
432 void AddresseeLineEdit::Private::updateSearchString()
434 m_searchString = q->text();
436 int n = -1;
437 bool inQuote = false;
438 uint searchStringLength = m_searchString.length();
439 for ( uint i = 0; i < searchStringLength; ++i ) {
440 if ( m_searchString[ i ] == QLatin1Char( '"' ) ) {
441 inQuote = !inQuote;
444 if ( m_searchString[ i ] == '\\' &&
445 ( i + 1 ) < searchStringLength && m_searchString[ i + 1 ] == QLatin1Char( '"' ) ) {
446 ++i;
449 if ( inQuote ) {
450 continue;
453 if ( i < searchStringLength &&
454 ( m_searchString[ i ] == ',' ||
455 ( m_useSemicolonAsSeparator && m_searchString[ i ] == ';' ) ) ) {
456 n = i;
460 if ( n >= 0 ) {
461 ++n; // Go past the ","
463 const int len = m_searchString.length();
465 // Increment past any whitespace...
466 while ( n < len && m_searchString[ n ].isSpace() ) {
467 ++n;
470 m_previousAddresses = m_searchString.left( n );
471 m_searchString = m_searchString.mid( n ).trimmed();
472 } else {
473 m_previousAddresses.clear();
477 void AddresseeLineEdit::Private::akonadiPerformSearch()
479 kDebug() << "searching akonadi with:" << m_searchString;
480 Akonadi::ContactSearchJob *contactJob = new Akonadi::ContactSearchJob();
481 Akonadi::ContactGroupSearchJob *groupJob = new Akonadi::ContactGroupSearchJob();
482 contactJob->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent );
483 groupJob->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent );
484 contactJob->setQuery( Akonadi::ContactSearchJob::NameOrEmail, m_searchString,
485 Akonadi::ContactSearchJob::ContainsMatch );
486 groupJob->setQuery( Akonadi::ContactGroupSearchJob::Name, m_searchString,
487 Akonadi::ContactGroupSearchJob::StartsWithMatch );
488 // FIXME: ContainsMatch is broken, even though it creates the correct SPARQL query, so use
489 // StartsWith for now
490 //Akonadi::ContactGroupSearchJob::ContainsMatch );
491 q->connect( contactJob, SIGNAL(result(KJob*)),
492 q, SLOT(slotAkonadiSearchResult(KJob*)) );
493 q->connect( groupJob, SIGNAL(result(KJob*)),
494 q, SLOT(slotAkonadiSearchResult(KJob*)) );
495 akonadiHandlePending();
498 void AddresseeLineEdit::Private::akonadiHandlePending()
500 kDebug() << "Pending items: " << s_static->akonadiPendingItems.size();
501 Akonadi::Item::List::iterator it = s_static->akonadiPendingItems.begin();
502 while ( it != s_static->akonadiPendingItems.end() ) {
503 const Akonadi::Item item = *it;
505 const int sourceIndex =
506 s_static->akonadiCollectionToCompletionSourceMap.value( item.parentCollection().id(), -1 );
507 if ( sourceIndex >= 0 ) {
508 kDebug() << "identified collection: " << s_static->completionSources[sourceIndex];
509 q->addItem( item, 1, sourceIndex );
511 // remove from the pending
512 it = s_static->akonadiPendingItems.erase( it );
513 } else {
514 ++it;
519 void AddresseeLineEdit::Private::doCompletion( bool ctrlT )
521 m_lastSearchMode = ctrlT;
523 const KGlobalSettings::Completion mode = q->completionMode();
525 if ( mode == KGlobalSettings::CompletionNone ) {
526 return;
529 s_static->completion->setOrder( completionOrder() );
531 // cursor at end of string - or Ctrl+T pressed for substring completion?
532 if ( ctrlT ) {
533 const QStringList completions = adjustedCompletionItems( false );
535 if ( completions.count() > 1 ) {
536 ; //m_previousAddresses = prevAddr;
537 } else if ( completions.count() == 1 ) {
538 q->setText( m_previousAddresses + completions.first().trimmed() );
541 // Make sure the completion popup is closed if no matching items were found
542 setCompletedItems( completions, true );
544 q->cursorAtEnd();
545 q->setCompletionMode( mode ); //set back to previous mode
546 return;
549 switch ( mode ) {
550 case KGlobalSettings::CompletionPopupAuto:
552 if ( m_searchString.isEmpty() ) {
553 break;
555 //else: fall-through to the CompletionPopup case
558 case KGlobalSettings::CompletionPopup:
560 const QStringList items = adjustedCompletionItems( true );
561 setCompletedItems( items, false );
563 break;
565 case KGlobalSettings::CompletionShell:
567 const QString match = s_static->completion->makeCompletion( m_searchString );
568 if ( !match.isNull() && match != m_searchString ) {
569 q->setText( m_previousAddresses + match );
570 q->setModified( true );
571 q->cursorAtEnd();
574 break;
576 case KGlobalSettings::CompletionMan: // Short-Auto in fact
577 case KGlobalSettings::CompletionAuto:
579 //force autoSuggest in KLineEdit::keyPressed or setCompletedText will have no effect
580 q->setCompletionMode( q->completionMode() );
582 if ( !m_searchString.isEmpty() ) {
584 //if only our \" is left, remove it since user has not typed it either
585 if ( m_searchExtended && m_searchString == QLatin1String( "\"" ) ) {
586 m_searchExtended = false;
587 m_searchString.clear();
588 q->setText( m_previousAddresses );
589 break;
592 QString match = s_static->completion->makeCompletion( m_searchString );
594 if ( !match.isEmpty() ) {
595 if ( match != m_searchString ) {
596 QString adds = m_previousAddresses + match;
597 q->setCompletedText( adds );
599 } else {
600 if ( !m_searchString.startsWith( QLatin1Char( '\"' ) ) ) {
601 //try with quoted text, if user has not type one already
602 match = s_static->completion->makeCompletion( QLatin1String( "\"" ) + m_searchString );
603 if ( !match.isEmpty() && match != m_searchString ) {
604 m_searchString = QLatin1String( "\"" ) + m_searchString;
605 m_searchExtended = true;
606 q->setText( m_previousAddresses + m_searchString );
607 q->setCompletedText( m_previousAddresses + match );
609 } else if ( m_searchExtended ) {
610 //our added \" does not work anymore, remove it
611 m_searchString = m_searchString.mid( 1 );
612 m_searchExtended = false;
613 q->setText( m_previousAddresses + m_searchString );
614 //now try again
615 match = s_static->completion->makeCompletion( m_searchString );
616 if ( !match.isEmpty() && match != m_searchString ) {
617 const QString adds = m_previousAddresses + match;
618 q->setCompletedText( adds );
624 break;
626 case KGlobalSettings::CompletionNone:
627 default: // fall through
628 break;
632 void AddresseeLineEdit::Private::slotCompletion()
634 // Called by KLineEdit's keyPressEvent for CompletionModes
635 // Auto,Popup -> new text, update search string.
636 // not called for CompletionShell, this is been taken care of
637 // in AddresseeLineEdit::keyPressEvent
639 updateSearchString();
640 if ( q->completionBox() ) {
641 q->completionBox()->setCancelledText( m_searchString );
644 akonadiPerformSearch();
645 doCompletion( false );
648 void AddresseeLineEdit::Private::slotPopupCompletion( const QString &completion )
650 q->setText( m_previousAddresses + completion.trimmed() );
651 q->cursorAtEnd();
652 updateSearchString();
653 q->emitTextCompleted();
656 void AddresseeLineEdit::Private::slotReturnPressed( const QString & )
658 if ( !q->completionBox()->selectedItems().isEmpty() ) {
659 slotPopupCompletion( q->completionBox()->selectedItems().first()->text() );
663 void AddresseeLineEdit::Private::slotStartLDAPLookup()
665 if ( Solid::Networking::status() == Solid::Networking::Unconnected ) {
666 return;
669 const KGlobalSettings::Completion mode = q->completionMode();
671 if ( mode == KGlobalSettings::CompletionNone ) {
672 return;
675 if ( !s_static->ldapSearch->isAvailable() ) {
676 return;
679 if ( s_static->ldapLineEdit != q ) {
680 return;
683 startLoadingLDAPEntries();
686 void AddresseeLineEdit::Private::slotLDAPSearchData( const KLDAP::LdapResult::List &results )
688 if ( results.isEmpty() || s_static->ldapLineEdit != q ) {
689 return;
692 foreach ( const KLDAP::LdapResult &result, results ) {
693 KABC::Addressee contact;
694 contact.setNameFromString( result.name );
695 contact.setEmails( result.email );
697 if ( !s_static->ldapClientToCompletionSourceMap.contains( result.clientNumber ) ) {
698 updateLDAPWeights(); // we got results from a new source, so update the completion sources
701 q->addContact( contact, result.completionWeight,
702 s_static->ldapClientToCompletionSourceMap[ result.clientNumber ] );
705 if ( ( q->hasFocus() || q->completionBox()->hasFocus() ) &&
706 q->completionMode() != KGlobalSettings::CompletionNone &&
707 q->completionMode() != KGlobalSettings::CompletionShell ) {
708 q->setText( m_previousAddresses + m_searchString );
709 // only complete again if the user didn't change the selection while
710 // we were waiting; otherwise the completion box will be closed
711 const QListWidgetItem *current = q->completionBox()->currentItem();
712 if ( !current || m_searchString.trimmed() != current->text().trimmed() ) {
713 doCompletion( m_lastSearchMode );
718 void AddresseeLineEdit::Private::slotEditCompletionOrder()
720 init(); // for s_static->ldapSearch
721 #ifndef Q_OS_WINCE
722 CompletionOrderEditor editor( s_static->ldapSearch, q );
723 editor.exec();
724 #endif
725 if ( m_useCompletion ) {
726 updateLDAPWeights();
730 void AddresseeLineEdit::Private::slotUserCancelled( const QString &cancelText )
732 if ( s_static->ldapSearch && s_static->ldapLineEdit == q ) {
733 stopLDAPLookup();
736 q->userCancelled( m_previousAddresses + cancelText ); // in KLineEdit
739 void AddresseeLineEdit::Private::slotAkonadiSearchResult( KJob *job )
741 const Akonadi::ContactSearchJob *contactJob =
742 qobject_cast<Akonadi::ContactSearchJob*>( job );
743 const Akonadi::ContactGroupSearchJob *groupJob =
744 qobject_cast<Akonadi::ContactGroupSearchJob*>( job );
746 if ( contactJob ) {
747 kDebug() << "Found" << contactJob->contacts().size() << "contacts";
748 } else if ( groupJob ) {
749 kDebug() << "Found" << groupJob->contactGroups().size() << "groups";
752 /* We have to fetch the collections of the items, so that
753 the source name can be correctly labeled.*/
754 Akonadi::Item::List items;
755 if ( contactJob ) items += contactJob->items();
756 if ( groupJob ) items += groupJob->items();
757 foreach ( const Akonadi::Item &item, items ) {
759 // check the local cache of collections
760 const int sourceIndex =
761 s_static->akonadiCollectionToCompletionSourceMap.value( item.parentCollection().id(), -1 );
762 if ( sourceIndex == -1 ) {
763 kDebug() << "Fetching New collection: " << item.parentCollection().id();
764 // the collection isn't there, start the fetch job.
765 Akonadi::CollectionFetchJob *collectionJob =
766 new Akonadi::CollectionFetchJob( item.parentCollection(),
767 Akonadi::CollectionFetchJob::Base, q );
768 connect( collectionJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
769 q, SLOT(slotAkonadiCollectionsReceived(Akonadi::Collection::List)) );
770 /* we don't want to start multiple fetch jobs for the same collection,
771 so insert the collection with an index value of -2 */
772 s_static->akonadiCollectionToCompletionSourceMap.insert( item.parentCollection().id(), -2 );
773 s_static->akonadiPendingItems.append( item );
774 } else if ( sourceIndex == -2 ) {
775 /* fetch job already started, don't need to start another one,
776 so just append the item as pending */
777 s_static->akonadiPendingItems.append( item );
778 } else {
779 q->addItem( item, 1, sourceIndex );
783 if ( ( contactJob && contactJob->contacts().size() > 0 ) ||
784 ( groupJob && groupJob->contactGroups().size() > 0 ) ) {
785 const QListWidgetItem *current = q->completionBox()->currentItem();
786 if ( !current || m_searchString.trimmed() != current->text().trimmed() ) {
787 doCompletion( m_lastSearchMode );
792 void AddresseeLineEdit::Private::slotAkonadiCollectionsReceived(
793 const Akonadi::Collection::List &collections )
795 foreach ( const Akonadi::Collection &collection, collections ) {
796 if ( collection.isValid() ) {
797 const Akonadi::EntityDisplayAttribute *attribute =
798 collection.attribute<Akonadi::EntityDisplayAttribute>();
800 QString sourceString = collection.name();
801 if ( attribute && !attribute->displayName().isEmpty() )
802 sourceString = attribute->displayName();
804 const int index = q->addCompletionSource( sourceString, 1 );
805 kDebug() << "\treceived: " << sourceString << "index: " << index;
806 s_static->akonadiCollectionToCompletionSourceMap.insert( collection.id(), index );
810 // now that we have added the new collections, recheck our list of pending contacts
811 akonadiHandlePending();
812 // do completion
813 const QListWidgetItem *current = q->completionBox()->currentItem();
814 if ( !current || m_searchString.trimmed() != current->text().trimmed() ) {
815 doCompletion( m_lastSearchMode );
819 // not cached, to make sure we get an up-to-date value when it changes
820 KCompletion::CompOrder AddresseeLineEdit::Private::completionOrder()
822 KConfig _config( QLatin1String( "kpimcompletionorder" ) );
823 const KConfigGroup config( &_config, QLatin1String( "General" ) );
824 const QString order =
825 config.readEntry( QLatin1String( "CompletionOrder" ), QString::fromLatin1( "Weighted" ) );
827 if ( order == QLatin1String( "Weighted" ) ) {
828 return KCompletion::Weighted;
829 } else {
830 return KCompletion::Sorted;
834 AddresseeLineEdit::AddresseeLineEdit( QWidget *parent, bool enableCompletion )
835 : KLineEdit( parent ), d( new Private( this, enableCompletion ) )
837 setObjectName( newLineEditObjectName() );
838 setClickMessage( QString() );
840 d->init();
843 AddresseeLineEdit::~AddresseeLineEdit()
845 if ( s_static->ldapSearch && s_static->ldapLineEdit == this ) {
846 d->stopLDAPLookup();
849 delete d;
852 void AddresseeLineEdit::setFont( const QFont &font )
854 KLineEdit::setFont( font );
856 if ( d->m_useCompletion ) {
857 completionBox()->setFont( font );
861 void AddresseeLineEdit::allowSemicolonAsSeparator( bool useSemicolonAsSeparator )
863 d->m_useSemicolonAsSeparator = useSemicolonAsSeparator;
866 void AddresseeLineEdit::keyPressEvent( QKeyEvent *event )
868 bool accept = false;
870 const int key = event->key() | event->modifiers();
872 if ( KStandardShortcut::shortcut( KStandardShortcut::SubstringCompletion ).contains( key ) ) {
873 //TODO: add LDAP substring lookup, when it becomes available in KPIM::LDAPSearch
874 d->updateSearchString();
875 d->akonadiPerformSearch();
876 d->doCompletion( true );
877 accept = true;
878 } else if ( KStandardShortcut::shortcut( KStandardShortcut::TextCompletion ).contains( key ) ) {
879 const int len = text().length();
881 if ( len == cursorPosition() ) { // at End?
882 d->updateSearchString();
883 d->akonadiPerformSearch();
884 d->doCompletion( true );
885 accept = true;
889 const QString oldContent = text();
890 if ( !accept ) {
891 KLineEdit::keyPressEvent( event );
894 // if the text didn't change (eg. because a cursor navigation key was pressed)
895 // we don't need to trigger a new search
896 if ( oldContent == text() ) {
897 return;
900 if ( event->isAccepted() ) {
901 d->updateSearchString();
903 QString searchString( d->m_searchString );
904 //LDAP does not know about our string manipulation, remove it
905 if ( d->m_searchExtended ) {
906 searchString = d->m_searchString.mid( 1 );
909 if ( d->m_useCompletion && s_static->ldapTimer ) {
910 if ( s_static->ldapText != searchString || s_static->ldapLineEdit != this ) {
911 d->stopLDAPLookup();
914 s_static->ldapText = searchString;
915 s_static->ldapLineEdit = this;
916 s_static->ldapTimer->setSingleShot( true );
917 s_static->ldapTimer->start( 500 );
922 void AddresseeLineEdit::insert( const QString &t )
924 if ( !d->m_smartPaste ) {
925 KLineEdit::insert( t );
926 return;
929 QString newText = t.trimmed();
930 if ( newText.isEmpty() ) {
931 return;
934 // remove newlines in the to-be-pasted string
935 QStringList lines = newText.split( QRegExp( QLatin1String( "\r?\n" ) ), QString::SkipEmptyParts );
936 for ( QStringList::iterator it = lines.begin(); it != lines.end(); ++it ) {
937 // remove trailing commas and whitespace
938 (*it).remove( QRegExp( QLatin1String( ",?\\s*$" ) ) );
940 newText = lines.join( QLatin1String( ", " ) );
942 if ( newText.startsWith( QLatin1String( "mailto:" ) ) ) {
943 const KUrl url( newText );
944 newText = url.path();
945 } else if ( newText.indexOf( QLatin1String( " at " ) ) != -1 ) {
946 // Anti-spam stuff
947 newText.replace( QLatin1String( " at " ), QLatin1String( "@" ) );
948 newText.replace( QLatin1String( " dot " ), QLatin1String( "." ) );
949 } else if ( newText.indexOf( QLatin1String( "(at)" ) ) != -1 ) {
950 newText.replace( QRegExp( QLatin1String( "\\s*\\(at\\)\\s*" ) ), QLatin1String( "@" ) );
953 QString contents = text();
954 int start_sel = 0;
955 int pos = cursorPosition();
957 if ( hasSelectedText() ) {
958 // Cut away the selection.
959 start_sel = selectionStart();
960 pos = start_sel;
961 contents = contents.left( start_sel ) + contents.mid( start_sel + selectedText().length() );
964 int eot = contents.length();
965 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
966 eot--;
968 if ( eot == 0 ) {
969 contents.clear();
970 } else if ( pos >= eot ) {
971 if ( contents[ eot - 1 ] == QLatin1Char( ',' ) ) {
972 eot--;
974 contents.truncate( eot );
975 contents += QLatin1String( ", " );
976 pos = eot + 2;
979 contents = contents.left( pos ) + newText + contents.mid( pos );
980 setText( contents );
981 setModified( true );
982 setCursorPosition( pos + newText.length() );
985 void AddresseeLineEdit::setText( const QString & text )
987 KLineEdit::setText( text.trimmed() );
990 void AddresseeLineEdit::paste()
992 if ( d->m_useCompletion ) {
993 d->m_smartPaste = true;
996 #ifndef QT_NO_CLIPBOARD
997 KLineEdit::paste();
998 #endif
999 d->m_smartPaste = false;
1002 void AddresseeLineEdit::mouseReleaseEvent( QMouseEvent *event )
1004 // reimplemented from QLineEdit::mouseReleaseEvent()
1005 #ifndef QT_NO_CLIPBOARD
1006 if ( d->m_useCompletion &&
1007 QApplication::clipboard()->supportsSelection() &&
1008 !isReadOnly() &&
1009 event->button() == Qt::MidButton ) {
1010 d->m_smartPaste = true;
1012 #endif
1014 KLineEdit::mouseReleaseEvent( event );
1015 d->m_smartPaste = false;
1018 #ifndef QT_NO_DRAGANDDROP
1019 void AddresseeLineEdit::dropEvent( QDropEvent *event )
1021 if ( !isReadOnly() ) {
1022 const KUrl::List uriList = KUrl::List::fromMimeData( event->mimeData() );
1023 if ( !uriList.isEmpty() ) {
1024 QString contents = text();
1025 // remove trailing white space and comma
1026 int eot = contents.length();
1027 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
1028 eot--;
1030 if ( eot == 0 ) {
1031 contents.clear();
1032 } else if ( contents[ eot - 1 ] == ',' ) {
1033 eot--;
1034 contents.truncate( eot );
1036 bool mailtoURL = false;
1037 // append the mailto URLs
1038 foreach ( const KUrl &url, uriList ) {
1039 if ( url.protocol() == QLatin1String( "mailto" ) ) {
1040 mailtoURL = true;
1041 QString address;
1042 address = KUrl::fromPercentEncoding( url.path().toLatin1() );
1043 address = KMime::decodeRFC2047String( address.toAscii() );
1044 if ( !contents.isEmpty() ) {
1045 contents.append( QLatin1String( ", " ) );
1047 contents.append( address );
1050 if ( mailtoURL ) {
1051 setText( contents );
1052 setModified( true );
1053 return;
1055 } else {
1056 // Let's see if this drop contains a comma separated list of emails
1057 QString dropData = QString::fromUtf8( event->encodedData( "text/plain" ) );
1058 QStringList addrs = KPIMUtils::splitAddressList( dropData );
1059 if ( addrs.count() > 0 ) {
1060 setText( KPIMUtils::normalizeAddressesAndDecodeIdn( dropData ) );
1061 setModified( true );
1062 return;
1067 if ( d->m_useCompletion ) {
1068 d->m_smartPaste = true;
1071 QLineEdit::dropEvent( event );
1072 d->m_smartPaste = false;
1074 #endif // QT_NO_DRAGANDDROP
1076 void AddresseeLineEdit::cursorAtEnd()
1078 setCursorPosition( text().length() );
1081 void AddresseeLineEdit::enableCompletion( bool enable )
1083 d->m_useCompletion = enable;
1086 void AddresseeLineEdit::addItem( const Akonadi::Item &item, int weight, int source )
1088 if ( item.hasPayload<KABC::Addressee>() ) {
1089 addContact( item.payload<KABC::Addressee>(), weight, source );
1090 } else if ( item.hasPayload<KABC::ContactGroup>() ) {
1091 addContactGroup( item.payload<KABC::ContactGroup>(), weight, source );
1095 void AddresseeLineEdit::addContactGroup( const KABC::ContactGroup &group, int weight, int source )
1097 d->addCompletionItem( group.name(), weight, source );
1098 QStringList keyWords;
1099 keyWords.append( group.name() );
1100 d->addCompletionItem( group.name(), weight, source, &keyWords );
1103 void AddresseeLineEdit::addContact( const KABC::Addressee &addr, int weight, int source )
1105 const QStringList emails = addr.emails();
1106 QStringList::ConstIterator it;
1107 const int prefEmailWeight = 1; //increment weight by prefEmailWeight
1108 int isPrefEmail = prefEmailWeight; //first in list is preferredEmail
1109 for ( it = emails.begin(); it != emails.end(); ++it ) {
1110 //TODO: highlight preferredEmail
1111 const QString email( (*it) );
1112 const QString givenName = addr.givenName();
1113 const QString familyName= addr.familyName();
1114 const QString nickName = addr.nickName();
1115 const QString domain = email.mid( email.indexOf( '@' ) + 1 );
1116 QString fullEmail = addr.fullEmail( email );
1117 //TODO: let user decide what fields to use in lookup, e.g. company, city, ...
1118 //for CompletionAuto
1119 if ( givenName.isEmpty() && familyName.isEmpty() ) {
1120 d->addCompletionItem( fullEmail, weight + isPrefEmail, source ); // use whatever is there
1121 } else {
1122 const QString byFirstName = QLatin1Char( '"' ) + givenName +
1123 QLatin1Char( ' ' ) + familyName +
1124 QLatin1String( "\" <" ) + email + QLatin1Char( '>' );
1125 const QString byLastName = QLatin1Char( '"' ) + familyName +
1126 QLatin1String( ", " ) + givenName +
1127 QLatin1String( "\" <" ) + email + QLatin1Char( '>' );
1128 d->addCompletionItem( byFirstName, weight + isPrefEmail, source );
1129 d->addCompletionItem( byLastName, weight + isPrefEmail, source );
1132 d->addCompletionItem( email, weight + isPrefEmail, source );
1134 if ( !nickName.isEmpty() ) {
1135 const QString byNick = QLatin1Char( '"' ) + nickName +
1136 QLatin1String( "\" <" ) + email + QLatin1Char( '>' );
1137 d->addCompletionItem( byNick, weight + isPrefEmail, source );
1140 if ( !domain.isEmpty() ) {
1141 const QString byDomain = QLatin1Char( '"' ) + domain + QLatin1Char( ' ' ) +
1142 familyName + QLatin1Char( ' ' ) + givenName +
1143 QLatin1String( "\" <" ) + email + QLatin1Char( '>' );
1144 d->addCompletionItem( byDomain, weight + isPrefEmail, source );
1147 //for CompletionShell, CompletionPopup
1148 QStringList keyWords;
1149 const QString realName = addr.realName();
1151 if ( !givenName.isEmpty() && !familyName.isEmpty() ) {
1152 keyWords.append( givenName + QLatin1Char( ' ' ) + familyName );
1153 keyWords.append( familyName + QLatin1Char( ' ' ) + givenName );
1154 keyWords.append( familyName + QLatin1String( ", " ) + givenName );
1155 } else if ( !givenName.isEmpty() ) {
1156 keyWords.append( givenName );
1157 } else if ( !familyName.isEmpty() ) {
1158 keyWords.append( familyName );
1161 if ( !nickName.isEmpty() ) {
1162 keyWords.append( nickName );
1165 if ( !realName.isEmpty() ) {
1166 keyWords.append( realName );
1169 if ( !domain.isEmpty() ) {
1170 keyWords.append( domain );
1173 keyWords.append( email );
1175 /* KMailCompletion does not have knowledge about identities, it stores emails and
1176 * keywords for each email. KMailCompletion::allMatches does a lookup on the
1177 * keywords and returns an ordered list of emails. In order to get the preferred
1178 * email before others for each identity we use this little trick.
1179 * We remove the <blank> in adjustedCompletionItems.
1181 if ( isPrefEmail == prefEmailWeight ) {
1182 fullEmail.replace( QLatin1String( " <" ), QLatin1String( " <" ) );
1185 d->addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords );
1186 isPrefEmail = 0;
1190 #ifndef QT_NO_CONTEXTMENU
1191 void AddresseeLineEdit::contextMenuEvent( QContextMenuEvent *event )
1193 QMenu *menu = createStandardContextMenu();
1194 if ( menu ) { // can be 0 on platforms with only a touch interface
1195 menu->exec( event->globalPos() );
1196 delete menu;
1200 QMenu *AddresseeLineEdit::createStandardContextMenu()
1202 // disable modes not supported by KMailCompletion
1203 setCompletionModeDisabled( KGlobalSettings::CompletionMan );
1204 setCompletionModeDisabled( KGlobalSettings::CompletionPopupAuto );
1206 QMenu *menu = KLineEdit::createStandardContextMenu();
1207 if ( !menu ) {
1208 return 0;
1211 if ( d->m_useCompletion ) {
1212 menu->addAction( i18n( "Configure Completion Order..." ),
1213 this, SLOT(slotEditCompletionOrder()) );
1215 return menu;
1217 #endif
1219 int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source, int weight )
1221 QMap<QString,int>::iterator it = s_static->completionSourceWeights.find( source );
1222 if ( it == s_static->completionSourceWeights.end() ) {
1223 s_static->completionSourceWeights.insert( source, weight );
1224 } else {
1225 s_static->completionSourceWeights[source] = weight;
1228 const int sourceIndex = s_static->completionSources.indexOf( source );
1229 if ( sourceIndex == -1 ) {
1230 s_static->completionSources.append( source );
1231 return s_static->completionSources.size() - 1;
1232 } else {
1233 return sourceIndex;
1237 bool KPIM::AddresseeLineEdit::eventFilter( QObject *object, QEvent *event )
1239 if ( d->m_completionInitialized &&
1240 ( object == completionBox() ||
1241 completionBox()->findChild<QWidget*>( object->objectName() ) == object ) ) {
1242 if ( event->type() == QEvent::MouseButtonPress ||
1243 event->type() == QEvent::MouseMove ||
1244 event->type() == QEvent::MouseButtonRelease ||
1245 event->type() == QEvent::MouseButtonDblClick ) {
1247 const QMouseEvent* mouseEvent = static_cast<QMouseEvent*>( event );
1248 // find list box item at the event position
1249 QListWidgetItem *item = completionBox()->itemAt( mouseEvent->pos() );
1250 if ( !item ) {
1251 // In the case of a mouse move outside of the box we don't want
1252 // the parent to fuzzy select a header by mistake.
1253 bool eat = event->type() == QEvent::MouseMove;
1254 return eat;
1256 // avoid selection of headers on button press, or move or release while
1257 // a button is pressed
1258 const Qt::MouseButtons buttons = mouseEvent->buttons();
1259 if ( event->type() == QEvent::MouseButtonPress ||
1260 event->type() == QEvent::MouseButtonDblClick ||
1261 buttons & Qt::LeftButton || buttons & Qt::MidButton ||
1262 buttons & Qt::RightButton ) {
1263 if ( itemIsHeader( item ) ) {
1264 return true; // eat the event, we don't want anything to happen
1265 } else {
1266 // if we are not on one of the group heading, make sure the item
1267 // below or above is selected, not the heading, inadvertedly, due
1268 // to fuzzy auto-selection from QListBox
1269 completionBox()->setCurrentItem( item );
1270 item->setSelected( true );
1271 if ( event->type() == QEvent::MouseMove ) {
1272 return true; // avoid fuzzy selection behavior
1279 if ( ( object == this ) &&
1280 ( event->type() == QEvent::ShortcutOverride ) ) {
1281 QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
1282 if ( keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down ||
1283 keyEvent->key() == Qt::Key_Tab ) {
1284 keyEvent->accept();
1285 return true;
1289 if ( ( object == this ) &&
1290 ( event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease ) &&
1291 completionBox()->isVisible() ) {
1292 const QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
1293 int currentIndex = completionBox()->currentRow();
1294 if ( currentIndex < 0 ) {
1295 return true;
1297 if ( keyEvent->key() == Qt::Key_Up ) {
1298 //kDebug() <<"EVENTFILTER: Qt::Key_Up currentIndex=" << currentIndex;
1299 // figure out if the item we would be moving to is one we want
1300 // to ignore. If so, go one further
1301 const QListWidgetItem *itemAbove = completionBox()->item( currentIndex );
1302 if ( itemAbove && itemIsHeader( itemAbove ) ) {
1303 // there is a header above is, check if there is even further up
1304 // and if so go one up, so it'll be selected
1305 if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) {
1306 //kDebug() <<"EVENTFILTER: Qt::Key_Up -> skipping" << currentIndex - 1;
1307 completionBox()->setCurrentRow( currentIndex - 1 );
1308 completionBox()->item( currentIndex - 1 )->setSelected( true );
1309 } else if ( currentIndex == 0 ) {
1310 // nothing to skip to, let's stay where we are, but make sure the
1311 // first header becomes visible, if we are the first real entry
1312 completionBox()->scrollToItem( completionBox()->item( 0 ) );
1313 QListWidgetItem *item = completionBox()->item( currentIndex );
1314 if ( item ) {
1315 if ( itemIsHeader( item ) ) {
1316 currentIndex++;
1317 item = completionBox()->item( currentIndex );
1319 completionBox()->setCurrentItem( item );
1320 item->setSelected( true );
1324 return true;
1326 } else if ( keyEvent->key() == Qt::Key_Down ) {
1327 // same strategy for downwards
1328 //kDebug() <<"EVENTFILTER: Qt::Key_Down. currentIndex=" << currentIndex;
1329 const QListWidgetItem *itemBelow = completionBox()->item( currentIndex );
1330 if ( itemBelow && itemIsHeader( itemBelow ) ) {
1331 if ( completionBox()->item( currentIndex + 1 ) ) {
1332 //kDebug() <<"EVENTFILTER: Qt::Key_Down -> skipping" << currentIndex+1;
1333 completionBox()->setCurrentRow( currentIndex + 1 );
1334 completionBox()->item( currentIndex + 1 )->setSelected( true );
1335 } else {
1336 // nothing to skip to, let's stay where we are
1337 QListWidgetItem *item = completionBox()->item( currentIndex );
1338 if ( item ) {
1339 completionBox()->setCurrentItem( item );
1340 item->setSelected( true );
1344 return true;
1346 // special case of the initial selection, which is unfortunately a header.
1347 // Setting it to selected tricks KCompletionBox into not treating is special
1348 // and selecting making it current, instead of the one below.
1349 QListWidgetItem *item = completionBox()->item( currentIndex );
1350 if ( item && itemIsHeader( item ) ) {
1351 completionBox()->setCurrentItem( item );
1352 item->setSelected( true );
1354 } else if ( event->type() == QEvent::KeyRelease &&
1355 ( keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab ) ) {
1356 /// first, find the header of the current section
1357 QListWidgetItem *myHeader = 0;
1358 int myHeaderIndex = -1;
1359 const int iterationStep = keyEvent->key() == Qt::Key_Tab ? 1 : -1;
1360 int index = qMin( qMax( currentIndex - iterationStep, 0 ), completionBox()->count() - 1 );
1361 while ( index >= 0 ) {
1362 if ( itemIsHeader( completionBox()->item( index ) ) ) {
1363 myHeader = completionBox()->item( index );
1364 myHeaderIndex = index;
1365 break;
1368 index--;
1370 Q_ASSERT( myHeader ); // we should always be able to find a header
1372 // find the next header (searching backwards, for Qt::Key_Backtab)
1373 QListWidgetItem *nextHeader = 0;
1375 // when iterating forward, start at the currentindex, when backwards,
1376 // one up from our header, or at the end
1377 uint j;
1378 if ( keyEvent->key() == Qt::Key_Tab ) {
1379 j = currentIndex;
1380 } else {
1381 index = myHeaderIndex;
1382 if ( index == 0 ) {
1383 j = completionBox()->count() - 1;
1384 } else {
1385 j = ( index - 1 ) % completionBox()->count();
1388 while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
1389 if ( itemIsHeader( nextHeader ) ) {
1390 break;
1392 j = ( j + iterationStep ) % completionBox()->count();
1395 if ( nextHeader && nextHeader != myHeader ) {
1396 QListWidgetItem *item = completionBox()->item( j + 1 );
1397 if ( item && !itemIsHeader( item ) ) {
1398 completionBox()->setCurrentItem( item );
1399 item->setSelected( true );
1403 return true;
1407 return KLineEdit::eventFilter( object, event );
1410 void AddresseeLineEdit::emitTextCompleted()
1412 emit textCompleted();
1416 #include "addresseelineedit.moc"