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