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