libtdepim

addresseelineedit.cpp
00001 /*
00002     This file is part of libtdepim.
00003     Copyright (c) 2002 Helge Deller <deller@gmx.de>
00004                   2002 Lubos Lunak <llunak@suse.cz>
00005                   2001,2003 Carsten Pfeiffer <pfeiffer@kde.org>
00006                   2001 Waldo Bastian <bastian@kde.org>
00007                   2004 Daniel Molkentin <danimo@klaralvdalens-datakonsult.se>
00008                   2004 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se>
00009 
00010     This library is free software; you can redistribute it and/or
00011     modify it under the terms of the GNU Library General Public
00012     License as published by the Free Software Foundation; either
00013     version 2 of the License, or (at your option) any later version.
00014 
00015     This library is distributed in the hope that it will be useful,
00016     but WITHOUT ANY WARRANTY; without even the implied warranty of
00017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00018     Library General Public License for more details.
00019 
00020     You should have received a copy of the GNU Library General Public License
00021     along with this library; see the file COPYING.LIB.  If not, write to
00022     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00023     Boston, MA 02110-1301, USA.
00024 */
00025 
00026 #include "addresseelineedit.h"
00027 
00028 #include "resourceabc.h"
00029 #include "completionordereditor.h"
00030 #include "ldapclient.h"
00031 
00032 #include <config.h>
00033 
00034 #ifdef TDEPIM_NEW_DISTRLISTS
00035 #include "distributionlist.h"
00036 #else
00037 #include <tdeabc/distributionlist.h>
00038 #endif
00039 
00040 #include <tdeabc/stdaddressbook.h>
00041 #include <tdeabc/resource.h>
00042 #include <libemailfunctions/email.h>
00043 
00044 #include <tdecompletionbox.h>
00045 #include <kcursor.h>
00046 #include <kdebug.h>
00047 #include <kstandarddirs.h>
00048 #include <kstaticdeleter.h>
00049 #include <tdestdaccel.h>
00050 #include <kurldrag.h>
00051 #include <tdelocale.h>
00052 
00053 #include <tqpopupmenu.h>
00054 #include <tqapplication.h>
00055 #include <tqobject.h>
00056 #include <tqptrlist.h>
00057 #include <tqregexp.h>
00058 #include <tqevent.h>
00059 #include <tqdragobject.h>
00060 #include <tqclipboard.h>
00061 
00062 using namespace KPIM;
00063 
00064 KMailCompletion * AddresseeLineEdit::s_completion = 0L;
00065 KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L;
00066 TQStringList* AddresseeLineEdit::s_completionSources = 0L;
00067 bool AddresseeLineEdit::s_addressesDirty = false;
00068 TQTimer* AddresseeLineEdit::s_LDAPTimer = 0L;
00069 KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L;
00070 TQString* AddresseeLineEdit::s_LDAPText = 0L;
00071 AddresseeLineEdit* AddresseeLineEdit::s_LDAPLineEdit = 0L;
00072 
00073 // The weights associated with the completion sources in s_completionSources.
00074 // Both are maintained by addCompletionSource(), don't attempt to modifiy those yourself.
00075 TQMap<TQString,int>* s_completionSourceWeights = 0;
00076 
00077 // maps LDAP client indices to completion source indices
00078 // the assumption that they are always the first n indices in s_completion
00079 // does not hold when clients are added later on
00080 TQMap<int, int>* AddresseeLineEdit::s_ldapClientToCompletionSourceMap = 0;
00081 
00082 static KStaticDeleter<KMailCompletion> completionDeleter;
00083 static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
00084 static KStaticDeleter<TQTimer> ldapTimerDeleter;
00085 static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
00086 static KStaticDeleter<TQString> ldapTextDeleter;
00087 static KStaticDeleter<TQStringList> completionSourcesDeleter;
00088 static KStaticDeleter<TQMap<TQString,int> > completionSourceWeightsDeleter;
00089 static KStaticDeleter<TQMap<int, int> > ldapClientToCompletionSourceMapDeleter;
00090 
00091 // needs to be unique, but the actual name doesn't matter much
00092 static TQCString newLineEditDCOPObjectName()
00093 {
00094     static int s_count = 0;
00095     TQCString name( "KPIM::AddresseeLineEdit" );
00096     if ( s_count++ ) {
00097       name += '-';
00098       name += TQCString().setNum( s_count );
00099     }
00100     return name;
00101 }
00102 
00103 static const TQString s_completionItemIndentString = "     ";
00104 
00105 static bool itemIsHeader( const TQListBoxItem* item )
00106 {
00107   return item && !item->text().startsWith( s_completionItemIndentString );
00108 }
00109 
00110 
00111 
00112 AddresseeLineEdit::AddresseeLineEdit( TQWidget* parent, bool useCompletion,
00113                                       const char *name )
00114   : ClickLineEdit( parent, TQString(), name ), DCOPObject( newLineEditDCOPObjectName() ),
00115     m_useSemiColonAsSeparator( false ), m_allowDistLists( true )
00116 {
00117   m_useCompletion = useCompletion;
00118   m_completionInitialized = false;
00119   m_smartPaste = false;
00120   m_addressBookConnected = false;
00121   m_searchExtended = false;
00122 
00123   init();
00124 
00125   if ( m_useCompletion )
00126     s_addressesDirty = true;
00127 }
00128 
00129 void AddresseeLineEdit::updateLDAPWeights()
00130 {
00131   /* Add completion sources for all ldap server, 0 to n. Added first so
00132    * that they map to the ldapclient::clientNumber() */
00133   s_LDAPSearch->updateCompletionWeights();
00134   TQValueList< LdapClient* > clients =  s_LDAPSearch->clients();
00135   int clientIndex = 0;
00136   for ( TQValueList<LdapClient*>::iterator it = clients.begin(); it != clients.end(); ++it, ++clientIndex ) {
00137     const int sourceIndex = addCompletionSource( "LDAP server: " + (*it)->server().host(), (*it)->completionWeight() );
00138     s_ldapClientToCompletionSourceMap->insert( clientIndex, sourceIndex );
00139   }
00140 }
00141 
00142 void AddresseeLineEdit::init()
00143 {
00144   if ( !s_completion ) {
00145     completionDeleter.setObject( s_completion, new KMailCompletion() );
00146     s_completion->setOrder( completionOrder() );
00147     s_completion->setIgnoreCase( true );
00148 
00149     completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() );
00150     completionSourcesDeleter.setObject( s_completionSources, new TQStringList() );
00151     completionSourceWeightsDeleter.setObject( s_completionSourceWeights, new TQMap<TQString,int> );
00152     ldapClientToCompletionSourceMapDeleter.setObject( s_ldapClientToCompletionSourceMap, new TQMap<int,int> );
00153   }
00154 //  connect( s_completion, TQT_SIGNAL( match( const TQString& ) ),
00155 //           this, TQT_SLOT( slotMatched( const TQString& ) ) );
00156 
00157   if ( m_useCompletion ) {
00158     if ( !s_LDAPTimer ) {
00159       ldapTimerDeleter.setObject( s_LDAPTimer, new TQTimer( 0, "ldapTimerDeleter" ) );
00160       ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch );
00161       ldapTextDeleter.setObject( s_LDAPText, new TQString );
00162     }
00163 
00164     updateLDAPWeights();
00165 
00166     if ( !m_completionInitialized ) {
00167       setCompletionObject( s_completion, false );
00168       connect( this, TQT_SIGNAL( completion( const TQString& ) ),
00169           this, TQT_SLOT( slotCompletion() ) );
00170       connect( this, TQT_SIGNAL( returnPressed( const TQString& ) ),
00171           this, TQT_SLOT( slotReturnPressed( const TQString& ) ) );
00172 
00173       TDECompletionBox *box = completionBox();
00174       connect( box, TQT_SIGNAL( highlighted( const TQString& ) ),
00175           this, TQT_SLOT( slotPopupCompletion( const TQString& ) ) );
00176       connect( box, TQT_SIGNAL( userCancelled( const TQString& ) ),
00177           TQT_SLOT( slotUserCancelled( const TQString& ) ) );
00178 
00179       // The emitter is always called KPIM::IMAPCompletionOrder by contract
00180       if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()",
00181             "slotIMAPCompletionOrderChanged()", false ) )
00182         kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl;
00183 
00184       connect( s_LDAPTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( slotStartLDAPLookup() ) );
00185       connect( s_LDAPSearch, TQT_SIGNAL( searchData( const KPIM::LdapResultList& ) ),
00186           TQT_SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) );
00187 
00188       m_completionInitialized = true;
00189     }
00190   }
00191 }
00192 
00193 AddresseeLineEdit::~AddresseeLineEdit()
00194 {
00195   if ( s_LDAPSearch && s_LDAPLineEdit == this )
00196     stopLDAPLookup();
00197 }
00198 
00199 void AddresseeLineEdit::setFont( const TQFont& font )
00200 {
00201   KLineEdit::setFont( font );
00202   if ( m_useCompletion )
00203     completionBox()->setFont( font );
00204 }
00205 
00206 void AddresseeLineEdit::allowSemiColonAsSeparator( bool useSemiColonAsSeparator )
00207 {
00208   m_useSemiColonAsSeparator = useSemiColonAsSeparator;
00209 }
00210 
00211 void AddresseeLineEdit::allowDistributionLists( bool allowDistLists )
00212 {
00213   m_allowDistLists = allowDistLists;
00214 }
00215 
00216 void AddresseeLineEdit::keyPressEvent( TQKeyEvent *e )
00217 {
00218   bool accept = false;
00219 
00220   if ( TDEStdAccel::shortcut( TDEStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) {
00221     //TODO: add LDAP substring lookup, when it becomes available in KPIM::LDAPSearch
00222     updateSearchString();
00223     doCompletion( true );
00224     accept = true;
00225   } else if ( TDEStdAccel::shortcut( TDEStdAccel::TextCompletion ).contains( KKey( e ) ) ) {
00226     int len = text().length();
00227 
00228     if ( len == cursorPosition() ) { // at End?
00229       updateSearchString();
00230       doCompletion( true );
00231       accept = true;
00232     }
00233   }
00234 
00235   const TQString oldContent = text();
00236   if ( !accept )
00237     KLineEdit::keyPressEvent( e );
00238 
00239   // if the text didn't change (eg. because a cursor navigation key was pressed)
00240   // we don't need to trigger a new search
00241   if ( oldContent == text() )
00242     return;
00243 
00244   if ( e->isAccepted() ) {
00245     updateSearchString();
00246     TQString searchString( m_searchString );
00247     //LDAP does not know about our string manipulation, remove it
00248     if ( m_searchExtended )
00249         searchString = m_searchString.mid( 1 );
00250 
00251     if ( m_useCompletion && s_LDAPTimer != NULL ) {
00252       if ( *s_LDAPText != searchString || s_LDAPLineEdit != this )
00253         stopLDAPLookup();
00254 
00255       *s_LDAPText = searchString;
00256       s_LDAPLineEdit = this;
00257       s_LDAPTimer->start( 500, true );
00258     }
00259   }
00260 }
00261 
00262 void AddresseeLineEdit::insert( const TQString &t )
00263 {
00264   if ( !m_smartPaste ) {
00265     KLineEdit::insert( t );
00266     return;
00267   }
00268 
00269   //kdDebug(5300) << "     AddresseeLineEdit::insert( \"" << t << "\" )" << endl;
00270 
00271   TQString newText = t.stripWhiteSpace();
00272   if ( newText.isEmpty() )
00273     return;
00274 
00275   // remove newlines in the to-be-pasted string
00276   TQStringList lines = TQStringList::split( TQRegExp("\r?\n"), newText, false );
00277   for ( TQStringList::iterator it = lines.begin();
00278        it != lines.end(); ++it ) {
00279     // remove trailing commas and whitespace
00280     (*it).remove( TQRegExp(",?\\s*$") );
00281   }
00282   newText = lines.join( ", " );
00283 
00284   if ( newText.startsWith("mailto:") ) {
00285     KURL url( newText );
00286     newText = url.path();
00287   }
00288   else if ( newText.find(" at ") != -1 ) {
00289     // Anti-spam stuff
00290     newText.replace( " at ", "@" );
00291     newText.replace( " dot ", "." );
00292   }
00293   else if ( newText.find("(at)") != -1 ) {
00294     newText.replace( TQRegExp("\\s*\\(at\\)\\s*"), "@" );
00295   }
00296 
00297   TQString contents = text();
00298   int start_sel = 0;
00299   int pos = cursorPosition( );
00300 
00301   if ( hasSelectedText() ) {
00302     // Cut away the selection.
00303     start_sel = selectionStart();
00304     pos = start_sel;
00305     contents = contents.left( start_sel ) + contents.mid( start_sel + selectedText().length() );
00306   }
00307 
00308   int eot = contents.length();
00309   while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
00310     eot--;
00311   }
00312   if ( eot == 0 ) {
00313     contents = TQString();
00314   } else if ( pos >= eot ) {
00315     if ( contents[ eot - 1 ] == ',' ) {
00316       eot--;
00317     }
00318     contents.truncate( eot );
00319     contents += ", ";
00320     pos = eot + 2;
00321   }
00322 
00323   contents = contents.left( pos ) + newText + contents.mid( pos );
00324   setText( contents );
00325   setEdited( true );
00326   setCursorPosition( pos + newText.length() );
00327 }
00328 
00329 void AddresseeLineEdit::setText( const TQString & text )
00330 {
00331   ClickLineEdit::setText( text.stripWhiteSpace() );
00332 }
00333 
00334 void AddresseeLineEdit::paste()
00335 {
00336   if ( m_useCompletion )
00337     m_smartPaste = true;
00338 
00339   KLineEdit::paste();
00340   m_smartPaste = false;
00341 }
00342 
00343 void AddresseeLineEdit::mouseReleaseEvent( TQMouseEvent *e )
00344 {
00345   // reimplemented from TQLineEdit::mouseReleaseEvent()
00346   if ( m_useCompletion
00347        && TQApplication::clipboard()->supportsSelection()
00348        && !isReadOnly()
00349        && e->button() == Qt::MidButton ) {
00350     m_smartPaste = true;
00351   }
00352 
00353   KLineEdit::mouseReleaseEvent( e );
00354   m_smartPaste = false;
00355 }
00356 
00357 void AddresseeLineEdit::dropEvent( TQDropEvent *e )
00358 {
00359   KURL::List uriList;
00360   if ( !isReadOnly() ) {
00361     if ( KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) {
00362       TQString contents = text();
00363       // remove trailing white space and comma
00364       int eot = contents.length();
00365       while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() )
00366         eot--;
00367       if ( eot == 0 )
00368         contents = TQString();
00369       else if ( contents[ eot - 1 ] == ',' ) {
00370         eot--;
00371         contents.truncate( eot );
00372       }
00373       bool mailtoURL = false;
00374       // append the mailto URLs
00375       for ( KURL::List::Iterator it = uriList.begin();
00376             it != uriList.end(); ++it ) {
00377         if ( !contents.isEmpty() )
00378           contents.append( ", " );
00379         KURL u( *it );
00380         if ( u.protocol() == "mailto" ) {
00381           mailtoURL = true;
00382           contents.append( (*it).path() );
00383         }
00384       }
00385       if ( mailtoURL ) {
00386         setText( contents );
00387         setEdited( true );
00388         return;
00389       }
00390     } else {
00391       // Let's see if this drop contains a comma separated list of emails
00392       TQString dropData = TQString::fromUtf8( e->encodedData( "text/plain" ) );
00393       TQStringList addrs = splitEmailAddrList( dropData );
00394       if ( addrs.count() > 0 ) {
00395         setText( normalizeAddressesAndDecodeIDNs( dropData ) );
00396         setEdited( true );
00397         return;
00398       }
00399     }
00400   }
00401 
00402   if ( m_useCompletion )
00403     m_smartPaste = true;
00404   TQLineEdit::dropEvent( e );
00405   m_smartPaste = false;
00406 }
00407 
00408 void AddresseeLineEdit::cursorAtEnd()
00409 {
00410   setCursorPosition( text().length() );
00411 }
00412 
00413 void AddresseeLineEdit::enableCompletion( bool enable )
00414 {
00415   m_useCompletion = enable;
00416 }
00417 
00418 void AddresseeLineEdit::doCompletion( bool ctrlT )
00419 {
00420   m_lastSearchMode = ctrlT;
00421 
00422   TDEGlobalSettings::Completion  mode = completionMode();
00423 
00424   if ( mode == TDEGlobalSettings::CompletionNone  )
00425     return;
00426 
00427   if ( s_addressesDirty ) {
00428     loadContacts(); // read from local address book
00429     s_completion->setOrder( completionOrder() );
00430   }
00431 
00432   // cursor at end of string - or Ctrl+T pressed for substring completion?
00433   if ( ctrlT ) {
00434     const TQStringList completions = getAdjustedCompletionItems( false );
00435 
00436     if ( completions.count() > 1 )
00437       ; //m_previousAddresses = prevAddr;
00438     else if ( completions.count() == 1 )
00439       setText( m_previousAddresses + completions.first().stripWhiteSpace() );
00440 
00441     setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found
00442 
00443     cursorAtEnd();
00444     setCompletionMode( mode ); //set back to previous mode
00445     return;
00446   }
00447 
00448 
00449   switch ( mode ) {
00450     case TDEGlobalSettings::CompletionPopupAuto:
00451     {
00452       if ( m_searchString.isEmpty() )
00453         break;
00454     }
00455 
00456     case TDEGlobalSettings::CompletionPopup:
00457     {
00458       const TQStringList items = getAdjustedCompletionItems( true );
00459       setCompletedItems( items, false );
00460       break;
00461     }
00462 
00463     case TDEGlobalSettings::CompletionShell:
00464     {
00465       TQString match = s_completion->makeCompletion( m_searchString );
00466       if ( !match.isNull() && match != m_searchString ) {
00467         setText( m_previousAddresses + match );
00468         setEdited( true );
00469         cursorAtEnd();
00470       }
00471       break;
00472     }
00473 
00474     case TDEGlobalSettings::CompletionMan: // Short-Auto in fact
00475     case TDEGlobalSettings::CompletionAuto:
00476     {
00477       //force autoSuggest in KLineEdit::keyPressed or setCompletedText will have no effect
00478       setCompletionMode( completionMode() );
00479 
00480       if ( !m_searchString.isEmpty() ) {
00481 
00482         //if only our \" is left, remove it since user has not typed it either
00483         if ( m_searchExtended && m_searchString == "\"" ){
00484           m_searchExtended = false;
00485           m_searchString = TQString();
00486           setText( m_previousAddresses );
00487           break;
00488         }
00489 
00490         TQString match = s_completion->makeCompletion( m_searchString );
00491 
00492         if ( !match.isEmpty() ) {
00493           if ( match != m_searchString ) {
00494             TQString adds = m_previousAddresses + match;
00495             setCompletedText( adds );
00496           }
00497         } else {
00498           if ( !m_searchString.startsWith( "\"" ) ) {
00499             //try with quoted text, if user has not type one already
00500             match = s_completion->makeCompletion( "\"" + m_searchString );
00501             if ( !match.isEmpty() && match != m_searchString ) {
00502               m_searchString = "\"" + m_searchString;
00503               m_searchExtended = true;
00504               setText( m_previousAddresses + m_searchString );
00505               setCompletedText( m_previousAddresses + match );
00506             }
00507           } else if ( m_searchExtended ) {
00508             //our added \" does not work anymore, remove it
00509             m_searchString = m_searchString.mid( 1 );
00510             m_searchExtended = false;
00511             setText( m_previousAddresses + m_searchString );
00512             //now try again
00513             match = s_completion->makeCompletion( m_searchString );
00514             if ( !match.isEmpty() && match != m_searchString ) {
00515               TQString adds = m_previousAddresses + match;
00516               setCompletedText( adds );
00517             }
00518           }
00519         }
00520       }
00521       break;
00522     }
00523 
00524     case TDEGlobalSettings::CompletionNone:
00525     default: // fall through
00526       break;
00527   }
00528 }
00529 
00530 void AddresseeLineEdit::slotPopupCompletion( const TQString& completion )
00531 {
00532   setText( m_previousAddresses + completion.stripWhiteSpace() );
00533   cursorAtEnd();
00534 //  slotMatched( m_previousAddresses + completion );
00535   updateSearchString();
00536 }
00537 
00538 void AddresseeLineEdit::slotReturnPressed( const TQString& item )
00539 {
00540   Q_UNUSED( item );
00541   TQListBoxItem* i = completionBox()->selectedItem();
00542   if ( i != 0 )
00543     slotPopupCompletion( i->text() );
00544 }
00545 
00546 void AddresseeLineEdit::loadContacts()
00547 {
00548   s_completion->clear();
00549   s_completionItemMap->clear();
00550   s_addressesDirty = false;
00551   //m_contactMap.clear();
00552 
00553   TQApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while
00554 
00555   TDEConfig config( "kpimcompletionorder" ); // The weights for non-imap tdeabc resources is there.
00556   config.setGroup( "CompletionWeights" );
00557 
00558   TDEABC::AddressBook *addressBook = TDEABC::StdAddressBook::self( true );
00559   // Can't just use the addressbook's iterator, we need to know which subresource
00560   // is behind which contact.
00561   TQPtrList<TDEABC::Resource> resources( addressBook->resources() );
00562   for( TQPtrListIterator<TDEABC::Resource> resit( resources ); *resit; ++resit ) {
00563     TDEABC::Resource* resource = *resit;
00564     KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
00565     if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource
00566       const TQMap<TQString, TQString> uidToResourceMap = resabc->uidToResourceMap();
00567       TDEABC::Resource::Iterator it;
00568       for ( it = resource->begin(); it != resource->end(); ++it ) {
00569         TQString uid = (*it).uid();
00570         TQMap<TQString, TQString>::const_iterator wit = uidToResourceMap.find( uid );
00571         const TQString subresourceLabel = resabc->subresourceLabel( *wit );
00572         const int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
00573         const int idx = addCompletionSource( subresourceLabel, weight );
00574 
00575         //kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl;
00576         addContact( *it, weight, idx );
00577       }
00578     } else { // KABC non-imap resource
00579       int weight = config.readNumEntry( resource->identifier(), 60 );
00580       int sourceIndex = addCompletionSource( resource->resourceName(), weight );
00581       TDEABC::Resource::Iterator it;
00582       for ( it = resource->begin(); it != resource->end(); ++it ) {
00583         addContact( *it, weight, sourceIndex );
00584       }
00585     }
00586   }
00587 
00588 #ifndef TDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above
00589   int weight = config.readNumEntry( "DistributionLists", 60 );
00590   TDEABC::DistributionListManager manager( addressBook );
00591   manager.load();
00592   const TQStringList distLists = manager.listNames();
00593   TQStringList::const_iterator listIt;
00594   int idx = addCompletionSource( i18n( "Distribution Lists" ) );
00595   for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
00596 
00597     //for TDEGlobalSettings::CompletionAuto
00598     addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx );
00599 
00600     //for CompletionShell, CompletionPopup
00601     TQStringList sl( (*listIt).simplifyWhiteSpace() );
00602     addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx, &sl );
00603 
00604   }
00605 #endif
00606 
00607   TQApplication::restoreOverrideCursor();
00608 
00609   if ( !m_addressBookConnected ) {
00610     connect( addressBook, TQT_SIGNAL( addressBookChanged( AddressBook* ) ), TQT_SLOT( loadContacts() ) );
00611     m_addressBookConnected = true;
00612   }
00613 }
00614 
00615 void AddresseeLineEdit::addContact( const TDEABC::Addressee& addr, int weight, int source )
00616 {
00617 #ifdef TDEPIM_NEW_DISTRLISTS
00618   if ( KPIM::DistributionList::isDistributionList( addr ) ) {
00619     //kdDebug(5300) << "AddresseeLineEdit::addContact() distribution list \"" << addr.formattedName() << "\" weight=" << weight << endl;
00620 
00621     if ( m_allowDistLists ) {
00622       //for CompletionAuto
00623       addCompletionItem( addr.formattedName(), weight, source );
00624 
00625       //for CompletionShell, CompletionPopup
00626       TQStringList sl( addr.formattedName() );
00627       addCompletionItem( addr.formattedName(), weight, source, &sl );
00628     }
00629 
00630     return;
00631   }
00632 #endif
00633   //m_contactMap.insert( addr.realName(), addr );
00634   const TQStringList emails = addr.emails();
00635   TQStringList::ConstIterator it;
00636   const int prefEmailWeight = 1;     //increment weight by prefEmailWeight
00637   int isPrefEmail = prefEmailWeight; //first in list is preferredEmail
00638   for ( it = emails.begin(); it != emails.end(); ++it ) {
00639     //TODO: highlight preferredEmail
00640     const TQString email( (*it) );
00641     const TQString givenName = addr.givenName();
00642     const TQString familyName= addr.familyName();
00643     const TQString nickName  = addr.nickName();
00644     const TQString domain    = email.mid( email.find( '@' ) + 1 );
00645     TQString fullEmail = addr.fullEmail( email );
00646     //TODO: let user decide what fields to use in lookup, e.g. company, city, ...
00647 
00648     //for CompletionAuto
00649     if ( givenName.isEmpty() && familyName.isEmpty() ) {
00650       addCompletionItem( fullEmail, weight + isPrefEmail, source ); // use whatever is there
00651     } else {
00652       const TQString byFirstName=  "\"" + givenName + " " + familyName + "\" <" + email + ">";
00653       const TQString byLastName =  "\"" + familyName + ", " + givenName + "\" <" + email + ">";
00654       addCompletionItem( byFirstName, weight + isPrefEmail, source );
00655       addCompletionItem( byLastName, weight + isPrefEmail, source );
00656     }
00657 
00658     addCompletionItem( email, weight + isPrefEmail, source );
00659 
00660     if ( !nickName.isEmpty() ){
00661       const TQString byNick     =  "\"" + nickName + "\" <" + email + ">";
00662       addCompletionItem( byNick, weight + isPrefEmail, source );
00663     }
00664 
00665     if ( !domain.isEmpty() ){
00666       const TQString byDomain   =  "\"" + domain + " " + familyName + " " + givenName + "\" <" + email + ">";
00667       addCompletionItem( byDomain, weight + isPrefEmail, source );
00668     }
00669 
00670     //for CompletionShell, CompletionPopup
00671     TQStringList keyWords;
00672     const TQString realName  = addr.realName();
00673 
00674     if ( !givenName.isEmpty() && !familyName.isEmpty() ) {
00675       keyWords.append( givenName  + " "  + familyName );
00676       keyWords.append( familyName + " "  + givenName );
00677       keyWords.append( familyName + ", " + givenName);
00678     }else if ( !givenName.isEmpty() )
00679       keyWords.append( givenName );
00680     else if ( !familyName.isEmpty() )
00681       keyWords.append( familyName );
00682 
00683     if ( !nickName.isEmpty() )
00684       keyWords.append( nickName );
00685 
00686     if ( !realName.isEmpty() )
00687       keyWords.append( realName );
00688 
00689     if ( !domain.isEmpty() )
00690       keyWords.append( domain );
00691 
00692     keyWords.append( email );
00693 
00694     /* KMailCompletion does not have knowledge about identities, it stores emails and
00695      * keywords for each email. KMailCompletion::allMatches does a lookup on the
00696      * keywords and returns an ordered list of emails. In order to get the preferred
00697      * email before others for each identity we use this little trick.
00698      * We remove the <blank> in getAdjustedCompletionItems.
00699      */
00700     if ( isPrefEmail == prefEmailWeight )
00701       fullEmail.replace( " <", "  <" );
00702 
00703     addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords );
00704     isPrefEmail = 0;
00705 
00706 #if 0
00707     int len = (*it).length();
00708     if ( len == 0 ) continue;
00709     if( '\0' == (*it)[len-1] )
00710       --len;
00711     const TQString tmp = (*it).left( len );
00712     const TQString fullEmail = addr.fullEmail( tmp );
00713     //kdDebug(5300) << "AddresseeLineEdit::addContact() \"" << fullEmail << "\" weight=" << weight << endl;
00714     addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source );
00715     // Try to guess the last name: if found, we add an extra
00716     // entry to the list to make sure completion works even
00717     // if the user starts by typing in the last name.
00718     TQString name( addr.realName().simplifyWhiteSpace() );
00719     if( name.endsWith("\"") )
00720       name.truncate( name.length()-1 );
00721     if( name.startsWith("\"") )
00722       name = name.mid( 1 );
00723 
00724     // While we're here also add "email (full name)" for completion on the email
00725     if ( !name.isEmpty() )
00726       addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source );
00727 
00728     bool bDone = false;
00729     int i = -1;
00730     while( ( i = name.findRev(' ') ) > 1 && !bDone ) {
00731       TQString sLastName( name.mid( i+1 ) );
00732       if( ! sLastName.isEmpty() &&
00733             2 <= sLastName.length() &&   // last names must be at least 2 chars long
00734           ! sLastName.endsWith(".") ) { // last names must not end with a dot (like "Jr." or "Sr.")
00735         name.truncate( i );
00736         if( !name.isEmpty() ){
00737           sLastName.prepend( "\"" );
00738           sLastName.append( ", " + name + "\" <" );
00739         }
00740         TQString sExtraEntry( sLastName );
00741         sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
00742         sExtraEntry.append( ">" );
00743         //kdDebug(5300) << "AddresseeLineEdit::addContact() added extra \"" << sExtraEntry.simplifyWhiteSpace() << "\" weight=" << weight << endl;
00744         addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source );
00745         bDone = true;
00746       }
00747       if( !bDone ) {
00748         name.truncate( i );
00749         if( name.endsWith("\"") )
00750           name.truncate( name.length()-1 );
00751       }
00752     }
00753 #endif
00754   }
00755 }
00756 
00757 void AddresseeLineEdit::addCompletionItem( const TQString& string, int weight, int completionItemSource, const TQStringList * keyWords )
00758 {
00759   // Check if there is an exact match for item already, and use the max weight if so.
00760   // Since there's no way to get the information from TDECompletion, we have to keep our own TQMap
00761   CompletionItemsMap::iterator it = s_completionItemMap->find( string );
00762   if ( it != s_completionItemMap->end() ) {
00763     weight = TQMAX( ( *it ).first, weight );
00764     ( *it ).first = weight;
00765   } else {
00766     s_completionItemMap->insert( string, tqMakePair( weight, completionItemSource ) );
00767   }
00768   if ( keyWords == 0 )
00769     s_completion->addItem( string, weight );
00770   else
00771     s_completion->addItemWithKeys( string, weight, keyWords );
00772 }
00773 
00774 void AddresseeLineEdit::slotStartLDAPLookup()
00775 {
00776   TDEGlobalSettings::Completion  mode = completionMode();
00777 
00778   if ( mode == TDEGlobalSettings::CompletionNone  )
00779     return;
00780 
00781   if ( !s_LDAPSearch->isAvailable() ) {
00782     return;
00783   }
00784   if (  s_LDAPLineEdit != this )
00785     return;
00786 
00787   startLoadingLDAPEntries();
00788 }
00789 
00790 void AddresseeLineEdit::stopLDAPLookup()
00791 {
00792   s_LDAPSearch->cancelSearch();
00793   s_LDAPLineEdit = NULL;
00794 }
00795 
00796 void AddresseeLineEdit::startLoadingLDAPEntries()
00797 {
00798   TQString s( *s_LDAPText );
00799   // TODO cache last?
00800   TQString prevAddr;
00801   int n = s.findRev( ',' );
00802   if ( n >= 0 ) {
00803     prevAddr = s.left( n + 1 ) + ' ';
00804     s = s.mid( n + 1, 255 ).stripWhiteSpace();
00805   }
00806 
00807   if ( s.isEmpty() )
00808     return;
00809 
00810   //loadContacts(); // TODO reuse these?
00811   s_LDAPSearch->startSearch( s );
00812 }
00813 
00814 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
00815 {
00816   if ( adrs.isEmpty() || s_LDAPLineEdit != this )
00817     return;
00818 
00819   for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
00820     TDEABC::Addressee addr;
00821     addr.setNameFromString( (*it).name );
00822     addr.setEmails( (*it).email );
00823 
00824     if ( !s_ldapClientToCompletionSourceMap->contains( (*it).clientNumber ) )
00825       updateLDAPWeights(); // we got results from a new source, so update the completion sources
00826 
00827     addContact( addr, (*it).completionWeight, (*s_ldapClientToCompletionSourceMap)[ (*it ).clientNumber ]  );
00828   }
00829 
00830   if ( (hasFocus() || completionBox()->hasFocus() )
00831        && completionMode() != TDEGlobalSettings::CompletionNone
00832        && completionMode() != TDEGlobalSettings::CompletionShell ) {
00833     setText( m_previousAddresses + m_searchString );
00834     // only complete again if the user didn't change the selection while we were waiting
00835     // otherwise the completion box will be closed
00836     if ( m_searchString.stripWhiteSpace() != completionBox()->currentText().stripWhiteSpace() )
00837       doCompletion( m_lastSearchMode );
00838   }
00839 }
00840 
00841 void AddresseeLineEdit::setCompletedItems( const TQStringList& items, bool autoSuggest )
00842 {
00843     TDECompletionBox* completionBox = this->completionBox();
00844 
00845     if ( !items.isEmpty() &&
00846          !(items.count() == 1 && m_searchString == items.first()) )
00847     {
00848         TQString oldCurrentText = completionBox->currentText();
00849         TQListBoxItem *itemUnderMouse = completionBox->itemAt(
00850             completionBox->viewport()->mapFromGlobal(TQCursor::pos()) );
00851         TQString oldTextUnderMouse;
00852         TQPoint oldPosOfItemUnderMouse;
00853         if ( itemUnderMouse ) {
00854             oldTextUnderMouse = itemUnderMouse->text();
00855             oldPosOfItemUnderMouse = completionBox->itemRect( itemUnderMouse ).topLeft();
00856         }
00857 
00858         completionBox->setItems( items );
00859 
00860         if ( !completionBox->isVisible() ) {
00861           if ( !m_searchString.isEmpty() )
00862             completionBox->setCancelledText( m_searchString );
00863           completionBox->popup();
00864           // we have to install the event filter after popup(), since that
00865           // calls show(), and that's where TDECompletionBox installs its filter.
00866           // We want to be first, though, so do it now.
00867           if ( s_completion->order() == TDECompletion::Weighted )
00868             tqApp->installEventFilter( this );
00869         }
00870 
00871         // Try to re-select what was selected before, otherrwise use the first
00872         // item, if there is one
00873         TQListBoxItem* item = 0;
00874         if ( oldCurrentText.isEmpty()
00875            || ( item = completionBox->findItem( oldCurrentText ) ) == 0 ) {
00876             item = completionBox->item( 1 );
00877         }
00878         if ( item )
00879         {
00880           if ( itemUnderMouse ) {
00881               TQListBoxItem *newItemUnderMouse = completionBox->findItem( oldTextUnderMouse );
00882               // if the mouse was over an item, before, but now that's elsewhere,
00883               // move the cursor, so folks don't accidently click the wrong item
00884               if ( newItemUnderMouse ) {
00885                   TQRect r = completionBox->itemRect( newItemUnderMouse );
00886                   TQPoint target = r.topLeft();
00887                   if ( oldPosOfItemUnderMouse != target ) {
00888                       target.setX( target.x() + r.width()/2 );
00889                       TQCursor::setPos( completionBox->viewport()->mapToGlobal(target) );
00890                   }
00891               }
00892           }
00893           completionBox->blockSignals( true );
00894           completionBox->setSelected( item, true );
00895           completionBox->setCurrentItem( item );
00896           completionBox->ensureCurrentVisible();
00897 
00898           completionBox->blockSignals( false );
00899         }
00900 
00901         if ( autoSuggest )
00902         {
00903             int index = items.first().find( m_searchString );
00904             TQString newText = items.first().mid( index );
00905             setUserSelection(false);
00906             setCompletedText(newText,true);
00907         }
00908     }
00909     else
00910     {
00911         if ( completionBox && completionBox->isVisible() ) {
00912             completionBox->hide();
00913             completionBox->setItems( TQStringList() );
00914         }
00915     }
00916 }
00917 
00918 TQPopupMenu* AddresseeLineEdit::createPopupMenu()
00919 {
00920   TQPopupMenu *menu = KLineEdit::createPopupMenu();
00921   if ( !menu )
00922     return 0;
00923 
00924   if ( m_useCompletion ){
00925     menu->setItemVisible( ShortAutoCompletion, false );
00926     menu->setItemVisible( PopupAutoCompletion, false );
00927     menu->insertItem( i18n( "Configure Completion Order..." ),
00928                       this, TQT_SLOT( slotEditCompletionOrder() ) );
00929   }
00930   return menu;
00931 }
00932 
00933 void AddresseeLineEdit::slotEditCompletionOrder()
00934 {
00935   init(); // for s_LDAPSearch
00936   CompletionOrderEditor editor( s_LDAPSearch, this );
00937   editor.exec();
00938   if ( m_useCompletion ) {
00939     updateLDAPWeights();
00940     s_addressesDirty = true;
00941   }
00942 }
00943 
00944 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
00945 {
00946   if ( m_useCompletion )
00947     s_addressesDirty = true;
00948 }
00949 
00950 void KPIM::AddresseeLineEdit::slotUserCancelled( const TQString& cancelText )
00951 {
00952   if ( s_LDAPSearch && s_LDAPLineEdit == this )
00953     stopLDAPLookup();
00954   userCancelled( m_previousAddresses + cancelText ); // in KLineEdit
00955 }
00956 
00957 void AddresseeLineEdit::updateSearchString()
00958 {
00959   m_searchString = text();
00960 
00961   int n = -1;
00962   bool inQuote = false;
00963   uint searchStringLength = m_searchString.length();
00964   for ( uint i = 0; i < searchStringLength; ++i ) {
00965     if ( m_searchString[ i ] == '"' ) {
00966       inQuote = !inQuote;
00967     }
00968     if ( m_searchString[ i ] == '\\' &&
00969          (i + 1) < searchStringLength && m_searchString[ i + 1 ] == '"' ) {
00970       ++i;
00971     }
00972     if ( inQuote ) {
00973       continue;
00974     }
00975     if ( i < searchStringLength &&
00976          ( m_searchString[ i ] == ',' ||
00977            ( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) ) ) {
00978       n = i;
00979     }
00980   }
00981 
00982   if ( n >= 0 ) {
00983     ++n; // Go past the ","
00984 
00985     int len = m_searchString.length();
00986 
00987     // Increment past any whitespace...
00988     while ( n < len && m_searchString[ n ].isSpace() )
00989       ++n;
00990 
00991     m_previousAddresses = m_searchString.left( n );
00992     m_searchString = m_searchString.mid( n ).stripWhiteSpace();
00993   } else {
00994     m_previousAddresses = TQString();
00995   }
00996 }
00997 
00998 void KPIM::AddresseeLineEdit::slotCompletion()
00999 {
01000   // Called by KLineEdit's keyPressEvent for CompletionModes Auto,Popup -> new text, update search string
01001   // not called for CompletionShell, this is been taken care of in AddresseeLineEdit::keyPressEvent
01002   updateSearchString();
01003   if ( completionBox() )
01004     completionBox()->setCancelledText( m_searchString );
01005   doCompletion( false );
01006 }
01007 
01008 // not cached, to make sure we get an up-to-date value when it changes
01009 TDECompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
01010 {
01011   TDEConfig config( "kpimcompletionorder" );
01012   config.setGroup( "General" );
01013   const TQString order = config.readEntry( "CompletionOrder", "Weighted" );
01014 
01015   if ( order == "Weighted" )
01016     return TDECompletion::Weighted;
01017   else
01018     return TDECompletion::Sorted;
01019 }
01020 
01021 int KPIM::AddresseeLineEdit::addCompletionSource( const TQString &source, int weight )
01022 {
01023   TQMap<TQString,int>::iterator it = s_completionSourceWeights->find( source );
01024   if ( it == s_completionSourceWeights->end() )
01025     s_completionSourceWeights->insert( source, weight );
01026   else
01027     (*s_completionSourceWeights)[source] = weight;
01028 
01029   int sourceIndex = s_completionSources->findIndex( source );
01030   if ( sourceIndex == -1 ) {
01031     s_completionSources->append( source );
01032     return s_completionSources->size() - 1;
01033   }
01034   else
01035     return sourceIndex;
01036 }
01037 
01038 bool KPIM::AddresseeLineEdit::eventFilter(TQObject *obj, TQEvent *e)
01039 {
01040   if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(completionBox()) ) {
01041     if ( e->type() == TQEvent::MouseButtonPress ||
01042          e->type() == TQEvent::MouseMove ||
01043          e->type() == TQEvent::MouseButtonRelease ||
01044          e->type() == TQEvent::MouseButtonDblClick ) {
01045       TQMouseEvent* me = TQT_TQMOUSEEVENT( e );
01046       // find list box item at the event position
01047       TQListBoxItem *item = completionBox()->itemAt( me->pos() );
01048       if ( !item ) {
01049         // In the case of a mouse move outside of the box we don't want
01050         // the parent to fuzzy select a header by mistake.
01051         bool eat = e->type() == TQEvent::MouseMove;
01052         return eat;
01053       }
01054       // avoid selection of headers on button press, or move or release while
01055       // a button is pressed
01056       if ( e->type() == TQEvent::MouseButtonPress
01057           || me->state() & Qt::LeftButton || me->state() & Qt::MidButton
01058           || me->state() & Qt::RightButton ) {
01059         if ( itemIsHeader(item) ) {
01060           return true; // eat the event, we don't want anything to happen
01061         } else {
01062           // if we are not on one of the group heading, make sure the item
01063           // below or above is selected, not the heading, inadvertedly, due
01064           // to fuzzy auto-selection from TQListBox
01065           completionBox()->setCurrentItem( item );
01066           completionBox()->setSelected( completionBox()->index( item ), true );
01067           if ( e->type() == TQEvent::MouseMove )
01068             return true; // avoid fuzzy selection behavior
01069         }
01070       }
01071     }
01072   }
01073   if ( ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(this) ) &&
01074      ( e->type() == TQEvent::AccelOverride ) ) {
01075     TQKeyEvent *ke = TQT_TQKEYEVENT( e );
01076     if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
01077       ke->accept();
01078       return true;
01079     }
01080   }
01081   if ( ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(this) ) &&
01082        ( e->type() == TQEvent::KeyPress || e->type() == TQEvent::KeyRelease ) &&
01083        completionBox()->isVisible() ) {
01084     TQKeyEvent *ke = TQT_TQKEYEVENT( e );
01085     int currentIndex = completionBox()->currentItem();
01086     if ( currentIndex < 0 ) {
01087       return true;
01088     }
01089 
01090     if ( ke->key() == Key_Up ) {
01091       //kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl;
01092       // figure out if the item we would be moving to is one we want
01093       // to ignore. If so, go one further
01094       TQListBoxItem *itemAbove = completionBox()->item( currentIndex );
01095       if ( itemAbove && itemIsHeader(itemAbove) ) {
01096         // there is a header above us, check if there is even further up
01097         // and if so go one up, so it'll be selected
01098         if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) {
01099           //kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl;
01100           completionBox()->setCurrentItem( itemAbove->prev() );
01101           completionBox()->setSelected( currentIndex - 1, true );
01102         } else if ( currentIndex == 0 ) {
01103             // nothing to skip to, let's stay where we are, but make sure the
01104             // first header becomes visible, if we are the first real entry
01105             completionBox()->ensureVisible( 0, 0 );
01106             //Kolab issue 2941: be sure to add email even if it's the only element.
01107             if ( itemIsHeader( completionBox()->item( currentIndex ) ) ) {
01108               currentIndex++;
01109             }
01110             completionBox()->setCurrentItem( itemAbove );
01111             completionBox()->setSelected( currentIndex, true );
01112         }
01113         return true;
01114       }
01115     } else if ( ke->key() == Key_Down  ) {
01116       // same strategy for downwards
01117       //kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl;
01118       TQListBoxItem *itemBelow = completionBox()->item( currentIndex );
01119       if ( itemBelow && itemIsHeader( itemBelow ) ) {
01120         if ( completionBox()->item( currentIndex + 1 ) ) {
01121           //kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl;
01122           completionBox()->setCurrentItem( itemBelow->next() );
01123           completionBox()->setSelected( currentIndex + 1, true );
01124         } else {
01125           // nothing to skip to, let's stay where we are
01126           completionBox()->setCurrentItem( itemBelow );
01127           completionBox()->setSelected( currentIndex, true );
01128         }
01129         return true;
01130       }
01131       // special case of the last and only item in the list needing selection
01132       if ( !itemBelow && currentIndex == 1 ) {
01133         completionBox()->setSelected( currentIndex, true );
01134       }
01135       // special case of the initial selection, which is unfortunately a header.
01136       // Setting it to selected tricks TDECompletionBox into not treating is special
01137       // and selecting making it current, instead of the one below.
01138       TQListBoxItem *item = completionBox()->item( currentIndex );
01139       if ( item && itemIsHeader(item) ) {
01140         completionBox()->setSelected( currentIndex, true );
01141        }
01142     } else if ( e->type() == TQEvent::KeyRelease &&
01143                 ( ke->key() == Key_Tab || ke->key() == Key_Backtab ) ) {
01144       //kdDebug() << "EVENTFILTER: Key_Tab. currentIndex=" << currentIndex << endl;
01146       TQListBoxItem *myHeader = 0;
01147       const int iterationstep = ke->key() == Key_Tab ?  1 : -1;
01148       int i = TQMIN( TQMAX( currentIndex - iterationstep, 0 ), completionBox()->count() - 1 );
01149       while ( i>=0 ) {
01150         if ( itemIsHeader( completionBox()->item(i) ) ) {
01151           myHeader = completionBox()->item( i );
01152           break;
01153         }
01154         i--;
01155       }
01156       Q_ASSERT( myHeader ); // we should always be able to find a header
01157 
01158       // find the next header (searching backwards, for Key_Backtab)
01159       TQListBoxItem *nextHeader = 0;
01160       // when iterating forward, start at the currentindex, when backwards,
01161       // one up from our header, or at the end
01162       uint j;
01163       if ( ke->key() == Key_Tab ) {
01164         j = currentIndex;
01165       } else {
01166         i = completionBox()->index( myHeader );
01167         if ( i == 0 ) {
01168           j = completionBox()->count() - 1;
01169         } else {
01170           j = ( i - 1 ) % completionBox()->count();
01171         }
01172       }
01173       while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
01174           if ( itemIsHeader(nextHeader) ) {
01175             break;
01176           }
01177           j = (j + iterationstep) % completionBox()->count();
01178       }
01179       if ( nextHeader && nextHeader != myHeader ) {
01180         TQListBoxItem *item = completionBox()->item( j + 1 );
01181         if ( item && !itemIsHeader(item) ) {
01182           completionBox()->setSelected( item, true );
01183           completionBox()->setCurrentItem( item );
01184           completionBox()->ensureCurrentVisible();
01185         }
01186       }
01187       return true;
01188     }
01189   }
01190   return ClickLineEdit::eventFilter( obj, e );
01191 }
01192 
01193 class SourceWithWeight {
01194   public:
01195     int weight;           // the weight of the source
01196     TQString sourceName;   // the name of the source, e.g. "LDAP Server"
01197     int index;            // index into s_completionSources
01198 
01199     bool operator< ( const SourceWithWeight &other ) {
01200       if ( weight > other.weight )
01201         return true;
01202       if ( weight < other.weight )
01203         return false;
01204       return sourceName < other.sourceName;
01205     }
01206 };
01207 
01208 const TQStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
01209 {
01210   TQStringList items = fullSearch ?
01211     s_completion->allMatches( m_searchString )
01212     : s_completion->substringCompletion( m_searchString );
01213 
01214   // For weighted mode, the algorithm is the following:
01215   // In the first loop, we add each item to its section (there is one section per completion source)
01216   // We also add spaces in front of the items.
01217   // The sections are appended to the items list.
01218   // In the second loop, we then walk through the sections and add all the items in there to the
01219   // sorted item list, which is the final result.
01220   //
01221   // The algo for non-weighted mode is different.
01222 
01223   int lastSourceIndex = -1;
01224   unsigned int i = 0;
01225 
01226   // Maps indices of the items list, which are section headers/source items,
01227   // to a TQStringList which are the items of that section/source.
01228   TQMap<int, TQStringList> sections;
01229   TQStringList sortedItems;
01230   for ( TQStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
01231     CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it);
01232     if ( cit == s_completionItemMap->end() )
01233       continue;
01234     int idx = (*cit).second;
01235 
01236     if ( s_completion->order() == TDECompletion::Weighted ) {
01237       if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
01238         const TQString sourceLabel(  (*s_completionSources)[idx] );
01239         if ( sections.find(idx) == sections.end() ) {
01240           items.insert( it, sourceLabel );
01241         }
01242         lastSourceIndex = idx;
01243       }
01244       (*it) = (*it).prepend( s_completionItemIndentString );
01245       // remove preferred email sort <blank> added in  addContact()
01246       (*it).replace( "  <", " <" );
01247     }
01248     sections[idx].append( *it );
01249 
01250     if ( s_completion->order() == TDECompletion::Sorted ) {
01251       sortedItems.append( *it );
01252     }
01253   }
01254 
01255   if ( s_completion->order() == TDECompletion::Weighted ) {
01256 
01257     // Sort the sections
01258     TQValueList<SourceWithWeight> sourcesAndWeights;
01259     for ( uint i = 0; i < s_completionSources->size(); i++ ) {
01260       SourceWithWeight sww;
01261       sww.sourceName = (*s_completionSources)[i];
01262       sww.weight = (*s_completionSourceWeights)[sww.sourceName];
01263       sww.index = i;
01264       sourcesAndWeights.append( sww );
01265     }
01266     qHeapSort( sourcesAndWeights );
01267 
01268     // Add the sections and their items to the final sortedItems result list
01269     for( uint i = 0; i < sourcesAndWeights.size(); i++ ) {
01270       TQStringList sectionItems = sections[sourcesAndWeights[i].index];
01271       if ( !sectionItems.isEmpty() ) {
01272         sortedItems.append( sourcesAndWeights[i].sourceName );
01273         TQStringList sectionItems = sections[sourcesAndWeights[i].index];
01274         for ( TQStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() );
01275               sit != send; ++sit ) {
01276           sortedItems.append( *sit );
01277         }
01278       }
01279     }
01280   } else {
01281     sortedItems.sort();
01282   }
01283   return sortedItems;
01284 }
01285 #include "addresseelineedit.moc"