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