libkdepim Library API Documentation

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                   2005 Till Adam <till@klaralvdalens-datakonsult.se>
00010 
00011     This library is free software; you can redistribute it and/or
00012     modify it under the terms of the GNU Library General Public
00013     License as published by the Free Software Foundation; either
00014     version 2 of the License, or (at your option) any later version.
00015 
00016     This library is distributed in the hope that it will be useful,
00017     but WITHOUT ANY WARRANTY; without even the implied warranty of
00018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00019     Library General Public License for more details.
00020 
00021     You should have received a copy of the GNU Library General Public License
00022     along with this library; see the file COPYING.LIB.  If not, write to
00023     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00024     Boston, MA 02111-1307, USA.
00025 */
00026 
00027 #include "addresseelineedit.h"
00028 
00029 #include <kabc/distributionlist.h>
00030 #include <kabc/stdaddressbook.h>
00031 #include <kabc/resource.h>
00032 
00033 #include <kcompletionbox.h>
00034 #include <kcursor.h>
00035 #include <kdebug.h>
00036 #include <kstandarddirs.h>
00037 #include <kstaticdeleter.h>
00038 #include <kstdaccel.h>
00039 #include <kurldrag.h>
00040 #include <klocale.h>
00041 
00042 #include "completionordereditor.h"
00043 #include "ldapclient.h"
00044 
00045 #include <qpopupmenu.h>
00046 #include <qapplication.h>
00047 #include <qobject.h>
00048 #include <qptrlist.h>
00049 #include <qregexp.h>
00050 #include <qevent.h>
00051 #include <qdragobject.h>
00052 #include <qclipboard.h>
00053 #include "resourceabc.h"
00054 #include "distributionlist.h"
00055 
00056 using namespace KPIM;
00057 
00058 KCompletion * AddresseeLineEdit::s_completion = 0L;
00059 KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L;
00060 QStringList* AddresseeLineEdit::s_completionSources = 0L;
00061 bool AddresseeLineEdit::s_addressesDirty = false;
00062 QTimer* AddresseeLineEdit::s_LDAPTimer = 0L;
00063 KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L;
00064 QString* AddresseeLineEdit::s_LDAPText = 0L;
00065 AddresseeLineEdit* AddresseeLineEdit::s_LDAPLineEdit = 0L;
00066 //KConfig *AddresseeLineEdit::s_config = 0L;
00067 
00068 static KStaticDeleter<KCompletion> completionDeleter;
00069 static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
00070 static KStaticDeleter<QTimer> ldapTimerDeleter;
00071 static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
00072 static KStaticDeleter<QString> ldapTextDeleter;
00073 static KStaticDeleter<KConfig> configDeleter;
00074 static KStaticDeleter<QStringList> completionSourcesDeleter;
00075 
00076 // needs to be unique, but the actual name doesn't matter much
00077 static QCString newLineEditDCOPObjectName()
00078 {
00079     static int s_count = 0;
00080     QCString name( "KPIM::AddresseeLineEdit" );
00081     if ( s_count++ ) {
00082       name += '-';
00083       name += QCString().setNum( s_count );
00084     }
00085     return name;
00086 }
00087 
00088 static const QString s_completionItemIndentString = "     ";
00089 
00090 AddresseeLineEdit::AddresseeLineEdit( QWidget* parent, bool useCompletion,
00091                                       const char *name )
00092   : ClickLineEdit( parent, QString::null, name ), DCOPObject( newLineEditDCOPObjectName() )
00093 {
00094   m_useCompletion = useCompletion;
00095   m_completionInitialized = false;
00096   m_smartPaste = false;
00097   m_addressBookConnected = false;
00098   init();
00099 
00100   if ( m_useCompletion )
00101     s_addressesDirty = true;
00102 }
00103 
00104 
00105 void AddresseeLineEdit::init()
00106 {
00107   if ( !s_completion ) {
00108     completionDeleter.setObject( s_completion, new KCompletion() );
00109     s_completion->setOrder( completionOrder() );
00110     s_completion->setIgnoreCase( true );
00111 
00112     completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() );
00113     completionSourcesDeleter.setObject( s_completionSources, new QStringList() );
00114   }
00115 
00116 //  connect( s_completion, SIGNAL( match( const QString& ) ),
00117 //           this, SLOT( slotMatched( const QString& ) ) );
00118 
00119   if ( m_useCompletion ) {
00120     if ( !s_LDAPTimer ) {
00121       ldapTimerDeleter.setObject( s_LDAPTimer, new QTimer );
00122       ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch );
00123       ldapTextDeleter.setObject( s_LDAPText, new QString );
00124 
00125       /* Add completion sources for all ldap server, 0 to n. Added first so
00126        * that they map to the ldapclient::clientNumber() */
00127       QValueList< LdapClient* > clients =  s_LDAPSearch->clients();
00128       for ( QValueList<LdapClient*>::iterator it = clients.begin(); it != clients.end(); ++it ) {
00129         addCompletionSource( "LDAP server: " + (*it)->host() );
00130       }
00131     }
00132     if ( !m_completionInitialized ) {
00133       setCompletionObject( s_completion, false );
00134       connect( this, SIGNAL( completion( const QString& ) ),
00135           this, SLOT( slotCompletion() ) );
00136 
00137       KCompletionBox *box = completionBox();
00138       connect( box, SIGNAL( highlighted( const QString& ) ),
00139           this, SLOT( slotPopupCompletion( const QString& ) ) );
00140       connect( box, SIGNAL( userCancelled( const QString& ) ),
00141           SLOT( slotUserCancelled( const QString& ) ) );
00142 
00143       // The emitter is always called KPIM::IMAPCompletionOrder by contract
00144       if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()",
00145             "slotIMAPCompletionOrderChanged()", false ) )
00146         kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl;
00147 
00148       connect( s_LDAPTimer, SIGNAL( timeout() ), SLOT( slotStartLDAPLookup() ) );
00149       connect( s_LDAPSearch, SIGNAL( searchData( const KPIM::LdapResultList& ) ),
00150           SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) );
00151 
00152       m_completionInitialized = true;
00153     }
00154   }
00155 }
00156 
00157 AddresseeLineEdit::~AddresseeLineEdit()
00158 {
00159   if ( s_LDAPSearch && s_LDAPLineEdit == this )
00160     stopLDAPLookup();
00161 }
00162 
00163 void AddresseeLineEdit::setFont( const QFont& font )
00164 {
00165   KLineEdit::setFont( font );
00166   if ( m_useCompletion )
00167     completionBox()->setFont( font );
00168 }
00169 
00170 void AddresseeLineEdit::allowSemiColonAsSeparator( bool useSemiColonAsSeparator )
00171 {
00172   m_useSemiColonAsSeparator = useSemiColonAsSeparator;
00173 }
00174 
00175 void AddresseeLineEdit::keyPressEvent( QKeyEvent *e )
00176 {
00177   bool accept = false;
00178 
00179   if ( KStdAccel::shortcut( KStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) {
00180     doCompletion( true );
00181     accept = true;
00182   } else if ( KStdAccel::shortcut( KStdAccel::TextCompletion ).contains( KKey( e ) ) ) {
00183     int len = text().length();
00184 
00185     if ( len == cursorPosition() ) { // at End?
00186       doCompletion( true );
00187       accept = true;
00188     }
00189   }
00190 
00191   if ( !accept )
00192     KLineEdit::keyPressEvent( e );
00193 
00194   if ( e->isAccepted() ) {
00195     if ( m_useCompletion && s_LDAPTimer != NULL ) {
00196       if ( *s_LDAPText != text() || s_LDAPLineEdit != this )
00197         stopLDAPLookup();
00198 
00199       *s_LDAPText = text();
00200       s_LDAPLineEdit = this;
00201       s_LDAPTimer->start( 500, true );
00202     }
00203   }
00204 }
00205 
00206 void AddresseeLineEdit::insert( const QString &t )
00207 {
00208   if ( !m_smartPaste ) {
00209     KLineEdit::insert( t );
00210     return;
00211   }
00212 
00213   kdDebug(5300) << "     AddresseeLineEdit::insert( \"" << t << "\" )" << endl;
00214 
00215   QString newText = t.stripWhiteSpace();
00216   if ( newText.isEmpty() )
00217     return;
00218 
00219   // remove newlines in the to-be-pasted string
00220   QStringList lines = QStringList::split( QRegExp("\r?\n"), newText, false );
00221   for ( QStringList::iterator it = lines.begin();
00222        it != lines.end(); ++it ) {
00223     // remove trailing commas and whitespace
00224     (*it).remove( QRegExp(",?\\s*$") );
00225   }
00226   newText = lines.join( ", " );
00227 
00228   if ( newText.lower().startsWith("mailto:") ) {
00229     KURL url( newText );
00230     newText = url.path();
00231   }
00232   else if ( newText.find(" at ") != -1 ) {
00233     // Anti-spam stuff
00234     newText.replace( " at ", "@" );
00235     newText.replace( " dot ", "." );
00236   }
00237   else if ( newText.find("(at)") != -1 ) {
00238     newText.replace( QRegExp("\\s*\\(at\\)\\s*"), "@" );
00239   }
00240 
00241   QString contents = text();
00242   int start_sel = 0;
00243   int end_sel = 0;
00244   int pos = cursorPosition();
00245   if ( getSelection( &start_sel, &end_sel ) ) {
00246     // Cut away the selection.
00247     if ( pos > end_sel )
00248       pos -= (end_sel - start_sel);
00249     else if ( pos > start_sel )
00250       pos = start_sel;
00251     contents = contents.left( start_sel ) + contents.right( end_sel + 1 );
00252   }
00253 
00254   int eot = contents.length();
00255   while ((eot > 0) && contents[ eot - 1 ].isSpace() ) eot--;
00256   if ( eot == 0 )
00257     contents = QString::null;
00258   else if ( pos >= eot ) {
00259     if ( contents[ eot - 1 ] == ',' )
00260       eot--;
00261     contents.truncate( eot );
00262     contents += ", ";
00263     pos = eot + 2;
00264   }
00265 
00266   contents = contents.left( pos ) + newText + contents.mid( pos );
00267   setText( contents );
00268   setCursorPosition( pos + newText.length() );
00269 }
00270 
00271 void AddresseeLineEdit::setText( const QString & text )
00272 {
00273   ClickLineEdit::setText( text.stripWhiteSpace() );
00274 }
00275 
00276 void AddresseeLineEdit::paste()
00277 {
00278   if ( m_useCompletion )
00279     m_smartPaste = true;
00280 
00281   KLineEdit::paste();
00282   m_smartPaste = false;
00283 }
00284 
00285 void AddresseeLineEdit::mouseReleaseEvent( QMouseEvent *e )
00286 {
00287   // reimplemented from QLineEdit::mouseReleaseEvent()
00288   if ( m_useCompletion
00289        && QApplication::clipboard()->supportsSelection()
00290        && !isReadOnly()
00291        && e->button() == MidButton ) {
00292     m_smartPaste = true;
00293   }
00294 
00295   KLineEdit::mouseReleaseEvent( e );
00296   m_smartPaste = false;
00297 }
00298 
00299 void AddresseeLineEdit::dropEvent( QDropEvent *e )
00300 {
00301   KURL::List uriList;
00302   if ( !isReadOnly()
00303        && KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) {
00304     QString contents = text();
00305     // remove trailing white space and comma
00306     int eot = contents.length();
00307     while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() )
00308       eot--;
00309     if ( eot == 0 )
00310       contents = QString::null;
00311     else if ( contents[ eot - 1 ] == ',' ) {
00312       eot--;
00313       contents.truncate( eot );
00314     }
00315     bool mailtoURL = false;
00316     // append the mailto URLs
00317     for ( KURL::List::Iterator it = uriList.begin();
00318           it != uriList.end(); ++it ) {
00319       if ( !contents.isEmpty() )
00320         contents.append( ", " );
00321       KURL u( *it );
00322       if ( u.protocol() == "mailto" ) {
00323         mailtoURL = true;
00324         contents.append( (*it).path() );
00325       }
00326     }
00327     if ( mailtoURL ) {
00328       setText( contents );
00329       setEdited( true );
00330       return;
00331     }
00332   }
00333 
00334   if ( m_useCompletion )
00335     m_smartPaste = true;
00336   QLineEdit::dropEvent( e );
00337   m_smartPaste = false;
00338 }
00339 
00340 void AddresseeLineEdit::cursorAtEnd()
00341 {
00342   setCursorPosition( text().length() );
00343 }
00344 
00345 void AddresseeLineEdit::enableCompletion( bool enable )
00346 {
00347   m_useCompletion = enable;
00348 }
00349 
00350 void AddresseeLineEdit::doCompletion( bool ctrlT )
00351 {
00352   if ( !m_useCompletion )
00353     return;
00354 
00355   if ( s_addressesDirty ) {
00356     loadContacts(); // read from local address book
00357     s_completion->setOrder( completionOrder() );
00358   }
00359 
00360   // cursor at end of string - or Ctrl+T pressed for substring completion?
00361   if ( ctrlT ) {
00362     const QStringList completions = getAdjustedCompletionItems( false );
00363 
00364     if ( completions.count() > 1 )
00365       ; //m_previousAddresses = prevAddr;
00366     else if ( completions.count() == 1 )
00367       setText( m_previousAddresses + completions.first().stripWhiteSpace() );
00368 
00369     setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found
00370 
00371     cursorAtEnd();
00372     return;
00373   }
00374 
00375   KGlobalSettings::Completion  mode = completionMode();
00376 
00377   switch ( mode ) {
00378     case KGlobalSettings::CompletionPopupAuto:
00379     {
00380       if ( m_searchString.isEmpty() )
00381         break;
00382     }
00383 
00384     case KGlobalSettings::CompletionPopup:
00385     {
00386       const QStringList items = getAdjustedCompletionItems( true );
00387       bool autoSuggest = !items.isEmpty() && (mode != KGlobalSettings::CompletionPopupAuto);
00388       setCompletedItems( items, autoSuggest );
00389 
00390       if ( !autoSuggest ) {
00391         int index = items.first().find( m_searchString );
00392         QString newText = m_previousAddresses + items.first().mid( index ).stripWhiteSpace();
00393         setUserSelection( false );
00394         setCompletedText( newText, true );
00395       }
00396       break;
00397     }
00398 
00399     case KGlobalSettings::CompletionShell:
00400     {
00401       QString match = s_completion->makeCompletion( m_searchString );
00402       if ( !match.isNull() && match != m_searchString ) {
00403         setText( m_previousAddresses + match );
00404         cursorAtEnd();
00405       }
00406       break;
00407     }
00408 
00409     case KGlobalSettings::CompletionMan: // Short-Auto in fact
00410     case KGlobalSettings::CompletionAuto:
00411     {
00412       if ( !m_searchString.isEmpty() ) {
00413         QString match = s_completion->makeCompletion( m_searchString );
00414         if ( !match.isNull() && match != m_searchString ) {
00415           QString adds = m_previousAddresses + match;
00416           setCompletedText( adds );
00417         }
00418         break;
00419       }
00420     }
00421 
00422     case KGlobalSettings::CompletionNone:
00423     default: // fall through
00424       break;
00425   }
00426 }
00427 
00428 void AddresseeLineEdit::slotPopupCompletion( const QString& completion )
00429 {
00430   setText( m_previousAddresses + completion.stripWhiteSpace() );
00431   cursorAtEnd();
00432 //  slotMatched( m_previousAddresses + completion );
00433 }
00434 
00435 void AddresseeLineEdit::loadContacts()
00436 {
00437   s_completion->clear();
00438   s_completionItemMap->clear();
00439   s_addressesDirty = false;
00440   //m_contactMap.clear();
00441 
00442   QApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while
00443 
00444   KConfig config( "kpimcompletionorder" ); // The weights for non-imap kabc resources is there.
00445   config.setGroup( "CompletionWeights" );
00446 
00447   KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
00448   // Can't just use the addressbook's iterator, we need to know which subresource
00449   // is behind which contact.
00450   QPtrList<KABC::Resource> resources( addressBook->resources() );
00451   for( QPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) {
00452     KABC::Resource* resource = *resit;
00453     KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
00454     if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource
00455       const QMap<QString, QString> uidToResourceMap = resabc->uidToResourceMap();
00456       KABC::Resource::Iterator it;
00457       for ( it = resource->begin(); it != resource->end(); ++it ) {
00458         QString uid = (*it).uid();
00459         QMap<QString, QString>::const_iterator wit = uidToResourceMap.find( uid );
00460         const QString subresourceLabel = resabc->subresourceLabel( *wit );
00461         int idx = s_completionSources->findIndex( subresourceLabel );
00462         if ( idx == -1 ) {
00463           s_completionSources->append( subresourceLabel );
00464           idx = s_completionSources->size() -1;
00465         }
00466         int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
00467         //kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl;
00468         addContact( *it, weight, idx );
00469       }
00470     } else { // KABC non-imap resource
00471       int weight = config.readNumEntry( resource->identifier(), 60 );
00472       s_completionSources->append( resource->resourceName() );
00473       KABC::Resource::Iterator it;
00474       for ( it = resource->begin(); it != resource->end(); ++it )
00475         addContact( *it, weight, s_completionSources->size()-1 );
00476     }
00477   }
00478 
00479 #if 0 // now done as part of the normal contacts
00480   int weight = config.readNumEntry( "DistributionLists", 60 );
00481   KABC::DistributionListManager manager( addressBook );
00482   manager.load();
00483   const QStringList distLists = manager.listNames();
00484   QStringList::const_iterator listIt;
00485   for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
00486     s_completion->addItem( (*listIt).simplifyWhiteSpace(), weight );
00487   }
00488 #endif
00489 
00490   QApplication::restoreOverrideCursor();
00491 
00492   if ( !m_addressBookConnected ) {
00493     connect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( loadContacts() ) );
00494     m_addressBookConnected = true;
00495   }
00496 }
00497 
00498 void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight, int source )
00499 {
00500   if ( KPIM::DistributionList::isDistributionList( addr ) ) {
00501     //kdDebug(5300) << "AddresseeLineEdit::addContact() distribution list \"" << addr.formattedName() << "\" weight=" << weight << endl;
00502     addCompletionItem( addr.formattedName(), weight, source );
00503     return;
00504   }
00505   //m_contactMap.insert( addr.realName(), addr );
00506   const QStringList emails = addr.emails();
00507   QStringList::ConstIterator it;
00508   for ( it = emails.begin(); it != emails.end(); ++it ) {
00509     int len = (*it).length();
00510     if ( len == 0 ) continue;
00511     if( '\0' == (*it)[len-1] )
00512       --len;
00513     const QString tmp = (*it).left( len );
00514     const QString fullEmail = addr.fullEmail( tmp );
00515     //kdDebug(5300) << "AddresseeLineEdit::addContact() \"" << fullEmail << "\" weight=" << weight << endl;
00516     addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source );
00517     // Try to guess the last name: if found, we add an extra
00518     // entry to the list to make sure completion works even
00519     // if the user starts by typing in the last name.
00520     QString name( addr.realName().simplifyWhiteSpace() );
00521     if( name.endsWith("\"") )
00522       name.truncate( name.length()-1 );
00523     if( name.startsWith("\"") )
00524       name = name.mid( 1 );
00525 
00526     // While we're here also add "email (full name)" for completion on the email
00527     if ( !name.isEmpty() )
00528       addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source );
00529 
00530     bool bDone = false;
00531     int i = 1;
00532     do{
00533       i = name.findRev(' ');
00534       if( 1 < i ){
00535         QString sLastName( name.mid(i+1) );
00536         if( ! sLastName.isEmpty() &&
00537             2 <= sLastName.length() &&   // last names must be at least 2 chars long
00538             ! sLastName.endsWith(".") ){ // last names must not end with a dot (like "Jr." or "Sr.")
00539           name.truncate( i );
00540           if( !name.isEmpty() ){
00541             sLastName.prepend( "\"" );
00542             sLastName.append( ", " + name + "\" <" );
00543           }
00544           QString sExtraEntry( sLastName );
00545           sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
00546           sExtraEntry.append( ">" );
00547           //kdDebug(5300) << "AddresseeLineEdit::addContact() added extra \"" << sExtraEntry.simplifyWhiteSpace() << "\" weight=" << weight << endl;
00548           addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source );
00549           bDone = true;
00550         }
00551       }
00552       if( !bDone ){
00553         name.truncate( i );
00554         if( name.endsWith("\"") )
00555           name.truncate( name.length()-1 );
00556       }
00557     }while( 1 < i && !bDone );
00558   }
00559 }
00560 
00561 void AddresseeLineEdit::addCompletionItem( const QString& string, int weight, int completionItemSource )
00562 {
00563   // Check if there is an exact match for item already, and use the max weight if so.
00564   // Since there's no way to get the information from KCompletion, we have to keep our own QMap
00565   CompletionItemsMap::iterator it = s_completionItemMap->find( string );
00566   if ( it != s_completionItemMap->end() ) {
00567     weight = QMAX( ( *it ).first, weight );
00568     ( *it ).first = weight;
00569   } else {
00570     s_completionItemMap->insert( string, qMakePair( weight, completionItemSource ) );
00571   }
00572   s_completion->addItem( string, weight );
00573 }
00574 
00575 void AddresseeLineEdit::slotStartLDAPLookup()
00576 {
00577   if ( !s_LDAPSearch->isAvailable() ) {
00578     return;
00579   }
00580   if (  s_LDAPLineEdit != this )
00581     return;
00582 
00583   startLoadingLDAPEntries();
00584 }
00585 
00586 void AddresseeLineEdit::stopLDAPLookup()
00587 {
00588   s_LDAPSearch->cancelSearch();
00589   s_LDAPLineEdit = NULL;
00590 }
00591 
00592 void AddresseeLineEdit::startLoadingLDAPEntries()
00593 {
00594   QString s( *s_LDAPText );
00595   // TODO cache last?
00596   QString prevAddr;
00597   int n = s.findRev( ',' );
00598   if ( n >= 0 ) {
00599     prevAddr = s.left( n + 1 ) + ' ';
00600     s = s.mid( n + 1, 255 ).stripWhiteSpace();
00601   }
00602 
00603   if ( s.isEmpty() )
00604     return;
00605 
00606   loadContacts(); // TODO reuse these?
00607   s_LDAPSearch->startSearch( s );
00608 }
00609 
00610 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
00611 {
00612   if ( s_LDAPLineEdit != this )
00613     return;
00614 
00615   for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
00616     KABC::Addressee addr;
00617     addr.setNameFromString( (*it).name );
00618     addr.setEmails( (*it).email );
00619 
00620     addContact( addr, (*it).completionWeight, (*it ).clientNumber  );
00621   }
00622 
00623   if ( hasFocus() || completionBox()->hasFocus() )
00624     if ( completionMode() != KGlobalSettings::CompletionNone )
00625       doCompletion( false );
00626 }
00627 
00628 // Workaround for bug in kdelibs < 3.4.beta2: KCompletionBox didn't resize
00629 // when calling setItems
00630 class KCompletionBoxHack : public KCompletionBox
00631 {
00632 public:
00633   KCompletionBoxHack() : KCompletionBox( 0 ) {}
00634   void sizeAndPosition() { KCompletionBox::sizeAndPosition(); }
00635 };
00636 
00637 void AddresseeLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest )
00638 {
00639     KCompletionBox* completionBox = this->completionBox();
00640 
00641     if ( !items.isEmpty() &&
00642          !(items.count() == 1 && m_searchString == items.first()) )
00643     {
00644         if ( completionBox->isVisible() )
00645         {
00646           bool wasSelected = completionBox->isSelected( completionBox->currentItem() );
00647           const QString currentSelection = completionBox->currentText();
00648           completionBox->setItems( items );
00649           QListBoxItem* item = completionBox->findItem( currentSelection, Qt::ExactMatch );
00650           if ( item )
00651           {
00652             completionBox->blockSignals( true );
00653             completionBox->setCurrentItem( item );
00654             completionBox->setSelected( item, wasSelected );
00655             completionBox->blockSignals( false );
00656           } else {
00657             completionBox->clearSelection();
00658           }
00659           // Workaround for bug in kdelibs < 3.4.beta2: KCompletionBox didn't resize
00660           // when calling setItems
00661           KCompletionBoxHack* hack = static_cast<KCompletionBoxHack *>( completionBox );
00662           hack->sizeAndPosition();
00663         }
00664         else // completion box not visible yet -> show it
00665         {
00666           if ( !m_searchString.isEmpty() )
00667             completionBox->setCancelledText( m_searchString );
00668           completionBox->setItems( items );
00669           completionBox->popup();
00670           // we have to install the event filter after popup(), since that
00671           // calls show(), and that's where KCompletionBox installs its filter.
00672           // We want to be first, though, so do it now.
00673           if ( s_completion->order() == KCompletion::Weighted )
00674             qApp->installEventFilter( this );
00675         }
00676         if ( autoSuggest )
00677         {
00678             int index = items.first().find( m_searchString );
00679             QString newText = items.first().mid( index );
00680             setUserSelection(false);
00681             setCompletedText(newText,true);
00682         }
00683     }
00684     else
00685     {
00686         if ( completionBox && completionBox->isVisible() ) {
00687             completionBox->hide();
00688         }
00689     }
00690 }
00691 
00692 QPopupMenu* AddresseeLineEdit::createPopupMenu()
00693 {
00694   QPopupMenu *menu = KLineEdit::createPopupMenu();
00695   if ( !menu )
00696     return 0;
00697 
00698   if ( m_useCompletion )
00699     menu->insertItem( i18n( "Configure Completion Order..." ),
00700                       this, SLOT( slotEditCompletionOrder() ) );
00701   return menu;
00702 }
00703 
00704 void AddresseeLineEdit::slotEditCompletionOrder()
00705 {
00706   init(); // for s_LDAPSearch
00707   CompletionOrderEditor editor( s_LDAPSearch, this );
00708   editor.exec();
00709 }
00710 
00711 #if 0
00712 KConfig* AddresseeLineEdit::config()
00713 {
00714   if ( !s_config )
00715     configDeleter.setObject( s_config, new KConfig( locateLocal( "config",
00716                              "kabldaprc" ) ) );
00717 
00718   return s_config;
00719 }
00720 #endif
00721 
00722 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
00723 {
00724   if ( m_useCompletion )
00725     s_addressesDirty = true;
00726 }
00727 
00728 void KPIM::AddresseeLineEdit::slotUserCancelled( const QString& cancelText )
00729 {
00730   if ( s_LDAPSearch && s_LDAPLineEdit == this )
00731     stopLDAPLookup();
00732   userCancelled( cancelText ); // in KLineEdit
00733 }
00734 
00735 void KPIM::AddresseeLineEdit::slotCompletion()
00736 {
00737   // Called by KLineEdit's keyPressEvent -> new text, update search string
00738   m_searchString = text();
00739 
00740   int n = m_searchString.findRev(',');
00741   if( m_useSemiColonAsSeparator )
00742     n = QMAX( n, m_searchString.findRev(';') );
00743 
00744   if ( n >= 0 ) {
00745     ++n; // Go past the ","
00746 
00747     int len = m_searchString.length();
00748 
00749     // Increment past any whitespace...
00750     while ( n < len && m_searchString[ n ].isSpace() )
00751       ++n;
00752 
00753     m_previousAddresses = m_searchString.left( n );
00754     m_searchString = m_searchString.mid( n ).stripWhiteSpace();
00755   }
00756   else
00757   {
00758     m_previousAddresses = QString::null;
00759   }
00760   if ( completionBox() )
00761     completionBox()->setCancelledText( m_searchString );
00762   doCompletion( false );
00763 }
00764 
00765 // not cached, to make sure we get an up-to-date value when it changes
00766 KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
00767 {
00768   KConfig config( "kpimcompletionorder" );
00769   config.setGroup( "General" );
00770   const QString order = config.readEntry( "CompletionOrder", "Weighted" );
00771 
00772   if ( order == "Weighted" )
00773     return KCompletion::Weighted;
00774   else
00775     return KCompletion::Sorted;
00776 }
00777 
00778 int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source )
00779 {
00780   s_completionSources->append( source );
00781   return s_completionSources->size()-1;
00782 }
00783 
00784 bool KPIM::AddresseeLineEdit::eventFilter(QObject *obj, QEvent *e)
00785 {
00786   if ( obj == completionBox() ) {
00787     if ( e->type() == QEvent::MouseButtonPress
00788       || e->type() == QEvent::MouseMove
00789       || e->type() == QEvent::MouseButtonRelease ) {
00790       QMouseEvent* me = static_cast<QMouseEvent*>( e );
00791       // find list box item at the event position
00792       QListBoxItem *item = completionBox()->itemAt( me->pos() );
00793       if ( !item ) {
00794         // In the case of a mouse move outside of the box we don't want
00795         // the parent to fuzzy select a header by mistake.
00796         bool eat = e->type() == QEvent::MouseMove;
00797         return eat;
00798       }
00799       // avoid selection of headers on button press, or move or release while
00800       // a button is pressed
00801       if ( e->type() == QEvent::MouseButtonPress
00802           || me->state() & LeftButton || me->state() & MidButton
00803           || me->state() & RightButton ) {
00804         if ( !item->text().startsWith( s_completionItemIndentString ) ) {
00805           return true; // eat the event, we don't want anything to happen
00806         } else {
00807           // if we are not on one of the group heading, make sure the item
00808           // below or above is selected, not the heading, inadvertedly, due
00809           // to fuzzy auto-selection from QListBox
00810           completionBox()->setCurrentItem( item );
00811           completionBox()->setSelected( completionBox()->index( item ), true );
00812           if ( e->type() == QEvent::MouseMove )
00813             return true; // avoid fuzzy selection behavior
00814         }
00815       }
00816     }
00817   }
00818   if ( ( obj == this ) &&
00819      ( e->type() == QEvent::AccelOverride ) ) {
00820     QKeyEvent *ke = static_cast<QKeyEvent*>( e );
00821     if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
00822       ke->accept();
00823       return true;
00824     }
00825   }
00826   if ( ( obj == this ) &&
00827       ( e->type() == QEvent::KeyPress ) &&
00828       completionBox()->isVisible() ) {
00829     QKeyEvent *ke = static_cast<QKeyEvent*>( e );
00830     unsigned int currentIndex = completionBox()->currentItem();
00831     if ( ke->key() == Key_Up || ke->key() == Key_Backtab ) {
00832       //kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl;
00833       // figure out if the item we would be moving to is one we want
00834       // to ignore. If so, go one further
00835       QListBoxItem *itemAbove = completionBox()->item( currentIndex - 1 );
00836       if ( itemAbove && !itemAbove->text().startsWith( s_completionItemIndentString ) ) {
00837         // there is a header above us, check if there is even further up
00838         // and if so go one up, so it'll be selected
00839         if ( currentIndex > 1 && completionBox()->item( currentIndex - 2 ) ) {
00840           //kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl;
00841           completionBox()->setCurrentItem( itemAbove->prev() );
00842           completionBox()->setSelected( currentIndex - 2, true );
00843         } else if ( currentIndex == 1 ) {
00844             // nothing to skip to, let's stay where we are, but make sure the
00845             // first header becomes visible, if we are the first real entry
00846             completionBox()->ensureVisible( 0, 0 );
00847             completionBox()->setSelected( currentIndex, true );
00848         }
00849         return true;
00850       }
00851     } else if ( ke->key() == Key_Down || ke->key() == Key_Tab ) {
00852       // same strategy for downwards
00853       //kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl;
00854       QListBoxItem *itemBelow = completionBox()->item( currentIndex + 1 );
00855       if ( itemBelow && !itemBelow->text().startsWith( s_completionItemIndentString ) ) {
00856         if ( completionBox()->item( currentIndex + 2 ) ) {
00857           //kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl;
00858           completionBox()->setCurrentItem( itemBelow->next() );
00859           completionBox()->setSelected( currentIndex + 2, true );
00860         } else {
00861           // nothing to skip to, let's stay where we are
00862           completionBox()->setSelected( currentIndex, true );
00863         }
00864         return true;
00865       }
00866       // special case of the last and only item in the list needing selection
00867       if ( !itemBelow && currentIndex == 1 ) {
00868         completionBox()->setSelected( currentIndex, true );
00869       }
00870       // special case of the initial selection, which is unfortunately a header.
00871       // Setting it to selected tricks KCompletionBox into not treating is special
00872       // and selecting making it current, instead of the one below.
00873       QListBoxItem *item = completionBox()->item( currentIndex );
00874       if ( item && !item->text().startsWith( s_completionItemIndentString )  ) {
00875         completionBox()->setSelected( currentIndex, true );
00876       }
00877     }
00878   }
00879   return ClickLineEdit::eventFilter( obj, e );
00880 }
00881 
00882 const QStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
00883 {
00884   QStringList items = fullSearch ?
00885     s_completion->allMatches( m_searchString )
00886     : s_completion->substringCompletion( m_searchString );
00887   if ( fullSearch )
00888     items += s_completion->allMatches( "\"" + m_searchString );
00889   unsigned int beforeDollarCompletionCount = items.count();
00890 
00891   if ( fullSearch && m_searchString.find( ' ' ) == -1 ) // one word, possibly given name
00892     items += s_completion->allMatches( "$$" + m_searchString );
00893 
00894   // kdDebug(5300) << "     AddresseeLineEdit::doCompletion() found: " << items.join(" AND ") << endl;
00895 
00896   int lastSourceIndex = -1;
00897   unsigned int i = 0;
00898   QMap<int, QStringList> sections;
00899   QStringList sortedItems;
00900   for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
00901     CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it);
00902     if ( cit == s_completionItemMap->end() )continue;
00903     int idx = (*cit).second;
00904     if ( s_completion->order() == KCompletion::Weighted ) {
00905       if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
00906         const QString sourceLabel(  (*s_completionSources)[idx] );
00907         if ( sections.find(idx) == sections.end() ) {
00908           items.insert( it, sourceLabel );
00909         }
00910         lastSourceIndex = idx;
00911       }
00912       (*it) = (*it).prepend( s_completionItemIndentString );
00913     }
00914     sections[idx].append( *it );
00915 
00916     if ( i > beforeDollarCompletionCount ) {
00917       // remove the '$$whatever$' part
00918       int pos = (*it).find( '$', 2 );
00919       if ( pos < 0 ) // ???
00920         continue;
00921       (*it) = (*it).mid( pos + 1 );
00922     }
00923     if ( s_completion->order() == KCompletion::Sorted ) {
00924       sortedItems.append( *it );
00925     }
00926   }
00927   if ( s_completion->order() == KCompletion::Weighted ) {
00928     for ( QMap<int, QStringList>::Iterator it( sections.begin() ), end( sections.end() ); it != end; ++it ) {
00929       sortedItems.append( (*s_completionSources)[it.key()] );
00930       for ( QStringList::Iterator sit( (*it).begin() ), send( (*it).end() ); sit != send; ++sit ) {
00931         sortedItems.append( *sit );
00932       }
00933     }
00934   } else {
00935     sortedItems.sort();
00936   }
00937   return sortedItems;
00938 }
00939 #include "addresseelineedit.moc"
KDE Logo
This file is part of the documentation for libkdepim Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu May 3 20:20:03 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003