libkdepim

addresseelineedit.cpp

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