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_useSemiColonAsSeparator( false ), m_allowDistLists( true )
00116 {
00117   m_useCompletion = useCompletion;
00118   m_completionInitialized = false;
00119   m_smartPaste = false;
00120   m_addressBookConnected = false;
00121   m_searchExtended = false;
00122 
00123   init();
00124 
00125   if ( m_useCompletion )
00126     s_addressesDirty = true;
00127 }
00128 
00129 void AddresseeLineEdit::updateLDAPWeights()
00130 {
00131   /* Add completion sources for all ldap server, 0 to n. Added first so
00132    * that they map to the ldapclient::clientNumber() */
00133   s_LDAPSearch->updateCompletionWeights();
00134   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   uint searchStringLength = m_searchString.length();
00954   for ( uint i = 0; i < searchStringLength; ++i ) {
00955     if ( m_searchString[ i ] == '"' ) {
00956       inQuote = !inQuote;
00957     }
00958     if ( m_searchString[ i ] == '\\' &&
00959          (i + 1) < searchStringLength && m_searchString[ i + 1 ] == '"' ) {
00960       ++i;
00961     }
00962     if ( inQuote ) {
00963       continue;
00964     }
00965     if ( i < searchStringLength &&
00966          ( m_searchString[ i ] == ',' ||
00967            ( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) ) ) {
00968       n = i;
00969     }
00970   }
00971 
00972   if ( n >= 0 ) {
00973     ++n; // Go past the ","
00974 
00975     int len = m_searchString.length();
00976 
00977     // Increment past any whitespace...
00978     while ( n < len && m_searchString[ n ].isSpace() )
00979       ++n;
00980 
00981     m_previousAddresses = m_searchString.left( n );
00982     m_searchString = m_searchString.mid( n ).stripWhiteSpace();
00983   } else {
00984     m_previousAddresses = QString::null;
00985   }
00986 }
00987 
00988 void KPIM::AddresseeLineEdit::slotCompletion()
00989 {
00990   // Called by KLineEdit's keyPressEvent for CompletionModes Auto,Popup -> new text, update search string
00991   // not called for CompletionShell, this is been taken care of in AddresseeLineEdit::keyPressEvent
00992   updateSearchString();
00993   if ( completionBox() )
00994     completionBox()->setCancelledText( m_searchString );
00995   doCompletion( false );
00996 }
00997 
00998 // not cached, to make sure we get an up-to-date value when it changes
00999 KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
01000 {
01001   KConfig config( "kpimcompletionorder" );
01002   config.setGroup( "General" );
01003   const QString order = config.readEntry( "CompletionOrder", "Weighted" );
01004 
01005   if ( order == "Weighted" )
01006     return KCompletion::Weighted;
01007   else
01008     return KCompletion::Sorted;
01009 }
01010 
01011 int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source, int weight )
01012 {
01013   QMap<QString,int>::iterator it = s_completionSourceWeights->find( source );
01014   if ( it == s_completionSourceWeights->end() )
01015     s_completionSourceWeights->insert( source, weight );
01016   else
01017     (*s_completionSourceWeights)[source] = weight;
01018 
01019   int sourceIndex = s_completionSources->findIndex( source );
01020   if ( sourceIndex == -1 ) {
01021     s_completionSources->append( source );
01022     return s_completionSources->size() - 1;
01023   }
01024   else
01025     return sourceIndex;
01026 }
01027 
01028 bool KPIM::AddresseeLineEdit::eventFilter(QObject *obj, QEvent *e)
01029 {
01030   if ( obj == completionBox() ) {
01031     if ( e->type() == QEvent::MouseButtonPress ||
01032          e->type() == QEvent::MouseMove ||
01033          e->type() == QEvent::MouseButtonRelease ||
01034          e->type() == QEvent::MouseButtonDblClick ) {
01035       QMouseEvent* me = static_cast<QMouseEvent*>( e );
01036       // find list box item at the event position
01037       QListBoxItem *item = completionBox()->itemAt( me->pos() );
01038       if ( !item ) {
01039         // In the case of a mouse move outside of the box we don't want
01040         // the parent to fuzzy select a header by mistake.
01041         bool eat = e->type() == QEvent::MouseMove;
01042         return eat;
01043       }
01044       // avoid selection of headers on button press, or move or release while
01045       // a button is pressed
01046       if ( e->type() == QEvent::MouseButtonPress
01047           || me->state() & LeftButton || me->state() & MidButton
01048           || me->state() & RightButton ) {
01049         if ( itemIsHeader(item) ) {
01050           return true; // eat the event, we don't want anything to happen
01051         } else {
01052           // if we are not on one of the group heading, make sure the item
01053           // below or above is selected, not the heading, inadvertedly, due
01054           // to fuzzy auto-selection from QListBox
01055           completionBox()->setCurrentItem( item );
01056           completionBox()->setSelected( completionBox()->index( item ), true );
01057           if ( e->type() == QEvent::MouseMove )
01058             return true; // avoid fuzzy selection behavior
01059         }
01060       }
01061     }
01062   }
01063   if ( ( obj == this ) &&
01064      ( e->type() == QEvent::AccelOverride ) ) {
01065     QKeyEvent *ke = static_cast<QKeyEvent*>( e );
01066     if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
01067       ke->accept();
01068       return true;
01069     }
01070   }
01071   if ( ( obj == this ) &&
01072        ( e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease ) &&
01073        completionBox()->isVisible() ) {
01074     QKeyEvent *ke = static_cast<QKeyEvent*>( e );
01075     int currentIndex = completionBox()->currentItem();
01076     if ( currentIndex < 0 ) {
01077       return true;
01078     }
01079 
01080     if ( ke->key() == Key_Up ) {
01081       //kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl;
01082       // figure out if the item we would be moving to is one we want
01083       // to ignore. If so, go one further
01084       QListBoxItem *itemAbove = completionBox()->item( currentIndex );
01085       if ( itemAbove && itemIsHeader(itemAbove) ) {
01086         // there is a header above us, check if there is even further up
01087         // and if so go one up, so it'll be selected
01088         if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) {
01089           //kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl;
01090           completionBox()->setCurrentItem( itemAbove->prev() );
01091           completionBox()->setSelected( currentIndex - 1, true );
01092         } else if ( currentIndex == 0 ) {
01093             // nothing to skip to, let's stay where we are, but make sure the
01094             // first header becomes visible, if we are the first real entry
01095             completionBox()->ensureVisible( 0, 0 );
01096             //Kolab issue 2941: be sure to add email even if it's the only element.
01097             if ( itemIsHeader( completionBox()->item( currentIndex ) ) ) {
01098               currentIndex++;
01099             }
01100             completionBox()->setCurrentItem( itemAbove );
01101             completionBox()->setSelected( currentIndex, true );
01102         }
01103         return true;
01104       }
01105     } else if ( ke->key() == Key_Down  ) {
01106       // same strategy for downwards
01107       //kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl;
01108       QListBoxItem *itemBelow = completionBox()->item( currentIndex );
01109       if ( itemBelow && itemIsHeader( itemBelow ) ) {
01110         if ( completionBox()->item( currentIndex + 1 ) ) {
01111           //kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl;
01112           completionBox()->setCurrentItem( itemBelow->next() );
01113           completionBox()->setSelected( currentIndex + 1, true );
01114         } else {
01115           // nothing to skip to, let's stay where we are
01116           completionBox()->setCurrentItem( itemBelow );
01117           completionBox()->setSelected( currentIndex, true );
01118         }
01119         return true;
01120       }
01121       // special case of the last and only item in the list needing selection
01122       if ( !itemBelow && currentIndex == 1 ) {
01123         completionBox()->setSelected( currentIndex, true );
01124       }
01125       // special case of the initial selection, which is unfortunately a header.
01126       // Setting it to selected tricks KCompletionBox into not treating is special
01127       // and selecting making it current, instead of the one below.
01128       QListBoxItem *item = completionBox()->item( currentIndex );
01129       if ( item && itemIsHeader(item) ) {
01130         completionBox()->setSelected( currentIndex, true );
01131        }
01132     } else if ( e->type() == QEvent::KeyRelease &&
01133                 ( ke->key() == Key_Tab || ke->key() == Key_Backtab ) ) {
01134       //kdDebug() << "EVENTFILTER: Key_Tab. currentIndex=" << currentIndex << endl;
01136       QListBoxItem *myHeader = 0;
01137       const int iterationstep = ke->key() == Key_Tab ?  1 : -1;
01138       int i = QMIN( QMAX( currentIndex - iterationstep, 0 ), completionBox()->count() - 1 );
01139       while ( i>=0 ) {
01140         if ( itemIsHeader( completionBox()->item(i) ) ) {
01141           myHeader = completionBox()->item( i );
01142           break;
01143         }
01144         i--;
01145       }
01146       Q_ASSERT( myHeader ); // we should always be able to find a header
01147 
01148       // find the next header (searching backwards, for Key_Backtab)
01149       QListBoxItem *nextHeader = 0;
01150       // when iterating forward, start at the currentindex, when backwards,
01151       // one up from our header, or at the end
01152       uint j;
01153       if ( ke->key() == Key_Tab ) {
01154         j = currentIndex;
01155       } else {
01156         i = completionBox()->index( myHeader );
01157         if ( i == 0 ) {
01158           j = completionBox()->count() - 1;
01159         } else {
01160           j = ( i - 1 ) % completionBox()->count();
01161         }
01162       }
01163       while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
01164           if ( itemIsHeader(nextHeader) ) {
01165             break;
01166           }
01167           j = (j + iterationstep) % completionBox()->count();
01168       }
01169       if ( nextHeader && nextHeader != myHeader ) {
01170         QListBoxItem *item = completionBox()->item( j + 1 );
01171         if ( item && !itemIsHeader(item) ) {
01172           completionBox()->setSelected( item, true );
01173           completionBox()->setCurrentItem( item );
01174           completionBox()->ensureCurrentVisible();
01175         }
01176       }
01177       return true;
01178     }
01179   }
01180   return ClickLineEdit::eventFilter( obj, e );
01181 }
01182 
01183 class SourceWithWeight {
01184   public:
01185     int weight;           // the weight of the source
01186     QString sourceName;   // the name of the source, e.g. "LDAP Server"
01187     int index;            // index into s_completionSources
01188 
01189     bool operator< ( const SourceWithWeight &other ) {
01190       if ( weight > other.weight )
01191         return true;
01192       if ( weight < other.weight )
01193         return false;
01194       return sourceName < other.sourceName;
01195     }
01196 };
01197 
01198 const QStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
01199 {
01200   QStringList items = fullSearch ?
01201     s_completion->allMatches( m_searchString )
01202     : s_completion->substringCompletion( m_searchString );
01203 
01204   // For weighted mode, the algorithm is the following:
01205   // In the first loop, we add each item to its section (there is one section per completion source)
01206   // We also add spaces in front of the items.
01207   // The sections are appended to the items list.
01208   // In the second loop, we then walk through the sections and add all the items in there to the
01209   // sorted item list, which is the final result.
01210   //
01211   // The algo for non-weighted mode is different.
01212 
01213   int lastSourceIndex = -1;
01214   unsigned int i = 0;
01215 
01216   // Maps indices of the items list, which are section headers/source items,
01217   // to a QStringList which are the items of that section/source.
01218   QMap<int, QStringList> sections;
01219   QStringList sortedItems;
01220   for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
01221     CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it);
01222     if ( cit == s_completionItemMap->end() )
01223       continue;
01224     int idx = (*cit).second;
01225 
01226     if ( s_completion->order() == KCompletion::Weighted ) {
01227       if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
01228         const QString sourceLabel(  (*s_completionSources)[idx] );
01229         if ( sections.find(idx) == sections.end() ) {
01230           items.insert( it, sourceLabel );
01231         }
01232         lastSourceIndex = idx;
01233       }
01234       (*it) = (*it).prepend( s_completionItemIndentString );
01235       // remove preferred email sort <blank> added in  addContact()
01236       (*it).replace( "  <", " <" );
01237     }
01238     sections[idx].append( *it );
01239 
01240     if ( s_completion->order() == KCompletion::Sorted ) {
01241       sortedItems.append( *it );
01242     }
01243   }
01244 
01245   if ( s_completion->order() == KCompletion::Weighted ) {
01246 
01247     // Sort the sections
01248     QValueList<SourceWithWeight> sourcesAndWeights;
01249     for ( uint i = 0; i < s_completionSources->size(); i++ ) {
01250       SourceWithWeight sww;
01251       sww.sourceName = (*s_completionSources)[i];
01252       sww.weight = (*s_completionSourceWeights)[sww.sourceName];
01253       sww.index = i;
01254       sourcesAndWeights.append( sww );
01255     }
01256     qHeapSort( sourcesAndWeights );
01257 
01258     // Add the sections and their items to the final sortedItems result list
01259     for( uint i = 0; i < sourcesAndWeights.size(); i++ ) {
01260       QStringList sectionItems = sections[sourcesAndWeights[i].index];
01261       if ( !sectionItems.isEmpty() ) {
01262         sortedItems.append( sourcesAndWeights[i].sourceName );
01263         QStringList sectionItems = sections[sourcesAndWeights[i].index];
01264         for ( QStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() );
01265               sit != send; ++sit ) {
01266           sortedItems.append( *sit );
01267         }
01268       }
01269     }
01270   } else {
01271     sortedItems.sort();
01272   }
01273   return sortedItems;
01274 }
01275 #include "addresseelineedit.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys