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