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 KCompletion * 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 static KStaticDeleter<KCompletion> completionDeleter;
00074 static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
00075 static KStaticDeleter<QTimer> ldapTimerDeleter;
00076 static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
00077 static KStaticDeleter<QString> ldapTextDeleter;
00078 static KStaticDeleter<QStringList> completionSourcesDeleter;
00079 
00080 // needs to be unique, but the actual name doesn't matter much
00081 static QCString newLineEditDCOPObjectName()
00082 {
00083     static int s_count = 0;
00084     QCString name( "KPIM::AddresseeLineEdit" );
00085     if ( s_count++ ) {
00086       name += '-';
00087       name += QCString().setNum( s_count );
00088     }
00089     return name;
00090 }
00091 
00092 static const QString s_completionItemIndentString = "     ";
00093 
00094 AddresseeLineEdit::AddresseeLineEdit( QWidget* parent, bool useCompletion,
00095                                       const char *name )
00096   : ClickLineEdit( parent, QString::null, name ), DCOPObject( newLineEditDCOPObjectName() )
00097 {
00098   m_useCompletion = useCompletion;
00099   m_completionInitialized = false;
00100   m_smartPaste = false;
00101   m_addressBookConnected = false;
00102 
00103   init();
00104 
00105   if ( m_useCompletion )
00106     s_addressesDirty = true;
00107 }
00108 
00109 
00110 void AddresseeLineEdit::init()
00111 {
00112   if ( !s_completion ) {
00113     completionDeleter.setObject( s_completion, new KCompletion() );
00114     s_completion->setOrder( completionOrder() );
00115     s_completion->setIgnoreCase( true );
00116 
00117     completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() );
00118     completionSourcesDeleter.setObject( s_completionSources, new QStringList() );
00119   }
00120 
00121 //  connect( s_completion, SIGNAL( match( const QString& ) ),
00122 //           this, SLOT( slotMatched( const QString& ) ) );
00123 
00124   if ( m_useCompletion ) {
00125     if ( !s_LDAPTimer ) {
00126       ldapTimerDeleter.setObject( s_LDAPTimer, new QTimer );
00127       ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch );
00128       ldapTextDeleter.setObject( s_LDAPText, new QString );
00129 
00130       /* Add completion sources for all ldap server, 0 to n. Added first so 
00131        * that they map to the ldapclient::clientNumber() */
00132       QValueList< LdapClient* > clients =  s_LDAPSearch->clients();
00133       for ( QValueList<LdapClient*>::iterator it = clients.begin(); it != clients.end(); ++it ) {
00134         addCompletionSource( "LDAP server: " + (*it)->server().host() );
00135       }
00136     }
00137     if ( !m_completionInitialized ) {
00138       setCompletionObject( s_completion, false );
00139       connect( this, SIGNAL( completion( const QString& ) ),
00140           this, SLOT( slotCompletion() ) );
00141 
00142       KCompletionBox *box = completionBox();
00143       connect( box, SIGNAL( highlighted( const QString& ) ),
00144           this, SLOT( slotPopupCompletion( const QString& ) ) );
00145       connect( box, SIGNAL( userCancelled( const QString& ) ),
00146           SLOT( slotUserCancelled( const QString& ) ) );
00147 
00148       // The emitter is always called KPIM::IMAPCompletionOrder by contract
00149       if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()",
00150             "slotIMAPCompletionOrderChanged()", false ) )
00151         kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl;
00152 
00153       connect( s_LDAPTimer, SIGNAL( timeout() ), SLOT( slotStartLDAPLookup() ) );
00154       connect( s_LDAPSearch, SIGNAL( searchData( const KPIM::LdapResultList& ) ),
00155           SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) );
00156 
00157       m_completionInitialized = true;
00158     }
00159   }
00160 }
00161 
00162 AddresseeLineEdit::~AddresseeLineEdit()
00163 {
00164   if ( s_LDAPSearch && s_LDAPLineEdit == this )
00165     stopLDAPLookup();
00166 }
00167 
00168 void AddresseeLineEdit::setFont( const QFont& font )
00169 {
00170   KLineEdit::setFont( font );
00171   if ( m_useCompletion )
00172     completionBox()->setFont( font );
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.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   setEdited( true );
00269   setCursorPosition( pos + newText.length() );
00270 }
00271 
00272 void AddresseeLineEdit::setText( const QString & text )
00273 {
00274   ClickLineEdit::setText( text.stripWhiteSpace() );
00275 }
00276 
00277 void AddresseeLineEdit::paste()
00278 {
00279   if ( m_useCompletion )
00280     m_smartPaste = true;
00281 
00282   KLineEdit::paste();
00283   m_smartPaste = false;
00284 }
00285 
00286 void AddresseeLineEdit::mouseReleaseEvent( QMouseEvent *e )
00287 {
00288   // reimplemented from QLineEdit::mouseReleaseEvent()
00289   if ( m_useCompletion
00290        && QApplication::clipboard()->supportsSelection()
00291        && !isReadOnly()
00292        && e->button() == MidButton ) {
00293     m_smartPaste = true;
00294   }
00295 
00296   KLineEdit::mouseReleaseEvent( e );
00297   m_smartPaste = false;
00298 }
00299 
00300 void AddresseeLineEdit::dropEvent( QDropEvent *e )
00301 {
00302   KURL::List uriList;
00303   if ( !isReadOnly()
00304        && KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) {
00305     QString contents = text();
00306     // remove trailing white space and comma
00307     int eot = contents.length();
00308     while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() )
00309       eot--;
00310     if ( eot == 0 )
00311       contents = QString::null;
00312     else if ( contents[ eot - 1 ] == ',' ) {
00313       eot--;
00314       contents.truncate( eot );
00315     }
00316     bool mailtoURL = false;
00317     // append the mailto URLs
00318     for ( KURL::List::Iterator it = uriList.begin();
00319           it != uriList.end(); ++it ) {
00320       if ( !contents.isEmpty() )
00321         contents.append( ", " );
00322       KURL u( *it );
00323       if ( u.protocol() == "mailto" ) {
00324         mailtoURL = true;
00325         contents.append( (*it).path() );
00326       }
00327     }
00328     if ( mailtoURL ) {
00329       setText( contents );
00330       setEdited( true );
00331       return;
00332     }
00333   }
00334 
00335   if ( m_useCompletion )
00336     m_smartPaste = true;
00337   QLineEdit::dropEvent( e );
00338   m_smartPaste = false;
00339 }
00340 
00341 void AddresseeLineEdit::cursorAtEnd()
00342 {
00343   setCursorPosition( text().length() );
00344 }
00345 
00346 void AddresseeLineEdit::enableCompletion( bool enable )
00347 {
00348   m_useCompletion = enable;
00349 }
00350 
00351 void AddresseeLineEdit::doCompletion( bool ctrlT )
00352 {
00353   if ( !m_useCompletion )
00354     return;
00355 
00356   if ( s_addressesDirty ) {
00357     loadContacts(); // read from local address book
00358     s_completion->setOrder( completionOrder() );
00359   }
00360 
00361   // cursor at end of string - or Ctrl+T pressed for substring completion?
00362   if ( ctrlT ) {
00363     const QStringList completions = getAdjustedCompletionItems( false );
00364 
00365     if ( completions.count() > 1 )
00366       ; //m_previousAddresses = prevAddr;
00367     else if ( completions.count() == 1 )
00368       setText( m_previousAddresses + completions.first().stripWhiteSpace() );
00369 
00370     setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found
00371 
00372     cursorAtEnd();
00373     return;
00374   }
00375 
00376   KGlobalSettings::Completion  mode = completionMode();
00377 
00378   switch ( mode ) {
00379     case KGlobalSettings::CompletionPopupAuto:
00380     {
00381       if ( m_searchString.isEmpty() )
00382         break;
00383     }
00384 
00385     case KGlobalSettings::CompletionPopup:
00386     {
00387       const QStringList items = getAdjustedCompletionItems( true );
00388       bool autoSuggest = !items.isEmpty() && (mode != KGlobalSettings::CompletionPopupAuto);
00389       setCompletedItems( items, autoSuggest );
00390 
00391       if ( !autoSuggest ) {
00392         int index = items.first().find( m_searchString );
00393         QString newText = m_previousAddresses + items.first().mid( index ).stripWhiteSpace();
00394         setUserSelection( false );
00395         setCompletedText( newText, true );
00396       }
00397       break;
00398     }
00399 
00400     case KGlobalSettings::CompletionShell:
00401     {
00402       QString match = s_completion->makeCompletion( m_searchString );
00403       if ( !match.isNull() && match != m_searchString ) {
00404         setText( m_previousAddresses + match );
00405         setEdited( true );
00406         cursorAtEnd();
00407       }
00408       break;
00409     }
00410 
00411     case KGlobalSettings::CompletionMan: // Short-Auto in fact
00412     case KGlobalSettings::CompletionAuto:
00413     {
00414       if ( !m_searchString.isEmpty() ) {
00415         QString match = s_completion->makeCompletion( m_searchString );
00416         if ( !match.isNull() && match != m_searchString ) {
00417           QString adds = m_previousAddresses + match;
00418           setCompletedText( adds );
00419         }
00420         break;
00421       }
00422     }
00423 
00424     case KGlobalSettings::CompletionNone:
00425     default: // fall through
00426       break;
00427   }
00428 }
00429 
00430 void AddresseeLineEdit::slotPopupCompletion( const QString& completion )
00431 {
00432   setText( m_previousAddresses + completion.stripWhiteSpace() );
00433   cursorAtEnd();
00434 //  slotMatched( m_previousAddresses + completion );
00435 }
00436 
00437 void AddresseeLineEdit::loadContacts()
00438 {
00439   s_completion->clear();
00440   s_completionItemMap->clear();
00441   s_addressesDirty = false;
00442   //m_contactMap.clear();
00443 
00444   QApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while
00445 
00446   KConfig config( "kpimcompletionorder" ); // The weights for non-imap kabc resources is there.
00447   config.setGroup( "CompletionWeights" );
00448 
00449   KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
00450   // Can't just use the addressbook's iterator, we need to know which subresource
00451   // is behind which contact.
00452   QPtrList<KABC::Resource> resources( addressBook->resources() );
00453   for( QPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) {
00454     KABC::Resource* resource = *resit;
00455     KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
00456     if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource
00457       const QMap<QString, QString> uidToResourceMap = resabc->uidToResourceMap();
00458       KABC::Resource::Iterator it;
00459       for ( it = resource->begin(); it != resource->end(); ++it ) {
00460         QString uid = (*it).uid();
00461         QMap<QString, QString>::const_iterator wit = uidToResourceMap.find( uid );
00462         const QString subresourceLabel = resabc->subresourceLabel( *wit );
00463         int idx = s_completionSources->findIndex( subresourceLabel );
00464         if ( idx == -1 ) {
00465           s_completionSources->append( subresourceLabel );
00466           idx = s_completionSources->size() -1;
00467         }
00468         int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
00469         //kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl;
00470         addContact( *it, weight, idx );
00471       }
00472     } else { // KABC non-imap resource
00473       int weight = config.readNumEntry( resource->identifier(), 60 );
00474       s_completionSources->append( resource->resourceName() );
00475       KABC::Resource::Iterator it;
00476       for ( it = resource->begin(); it != resource->end(); ++it )
00477         addContact( *it, weight, s_completionSources->size()-1 );
00478     }
00479   }
00480 
00481 #ifndef KDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above
00482   int weight = config.readNumEntry( "DistributionLists", 60 );
00483   KABC::DistributionListManager manager( addressBook );
00484   manager.load();
00485   const QStringList distLists = manager.listNames();
00486   QStringList::const_iterator listIt;
00487   int idx = addCompletionSource( i18n( "Distribution Lists" ) );
00488   for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
00489     addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx );
00490   }
00491 #endif
00492 
00493   QApplication::restoreOverrideCursor();
00494 
00495   if ( !m_addressBookConnected ) {
00496     connect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( loadContacts() ) );
00497     m_addressBookConnected = true;
00498   }
00499 }
00500 
00501 void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight, int source )
00502 {
00503 #ifdef KDEPIM_NEW_DISTRLISTS
00504   if ( KPIM::DistributionList::isDistributionList( addr ) ) {
00505     //kdDebug(5300) << "AddresseeLineEdit::addContact() distribution list \"" << addr.formattedName() << "\" weight=" << weight << endl;
00506     addCompletionItem( addr.formattedName(), weight, source );
00507     return;
00508   }
00509 #endif
00510   //m_contactMap.insert( addr.realName(), addr );
00511   const QStringList emails = addr.emails();
00512   QStringList::ConstIterator it;
00513   for ( it = emails.begin(); it != emails.end(); ++it ) {
00514 
00515     if ( addr.givenName().isEmpty() && addr.familyName().isEmpty() ) {
00516       addCompletionItem( addr.fullEmail( (*it) ), weight, source ); // use whatever is there
00517     } else {
00518       const QString byFirstName= KPIM::quoteNameIfNecessary( addr.givenName() + " " + addr.familyName() ) + " <" + (*it) + ">";
00519       const QString byLastName= "\"" + addr.familyName() + ", " + addr.givenName() + "\" "  + "<" + (*it) + ">";
00520       const QString byEmail= (*it) + " (" + KPIM::quoteNameIfNecessary( addr.realName() ) + ")";
00521       addCompletionItem( byFirstName, weight, source );
00522       addCompletionItem( byLastName, weight, source );
00523       addCompletionItem( byEmail, weight, source );
00524     }
00525 
00526 #if 0
00527     int len = (*it).length();
00528     if ( len == 0 ) continue;
00529     if( '\0' == (*it)[len-1] )
00530       --len;
00531     const QString tmp = (*it).left( len );
00532     const QString fullEmail = addr.fullEmail( tmp );
00533     //kdDebug(5300) << "AddresseeLineEdit::addContact() \"" << fullEmail << "\" weight=" << weight << endl;
00534     addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source );
00535     // Try to guess the last name: if found, we add an extra
00536     // entry to the list to make sure completion works even
00537     // if the user starts by typing in the last name.
00538     QString name( addr.realName().simplifyWhiteSpace() );
00539     if( name.endsWith("\"") )
00540       name.truncate( name.length()-1 );
00541     if( name.startsWith("\"") )
00542       name = name.mid( 1 );
00543 
00544     // While we're here also add "email (full name)" for completion on the email
00545     if ( !name.isEmpty() )
00546       addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source );
00547 
00548     bool bDone = false;
00549     int i = -1;
00550     while( ( i = name.findRev(' ') ) > 1 && !bDone ) {
00551       QString sLastName( name.mid( i+1 ) );
00552       if( ! sLastName.isEmpty() &&
00553             2 <= sLastName.length() &&   // last names must be at least 2 chars long
00554           ! sLastName.endsWith(".") ) { // last names must not end with a dot (like "Jr." or "Sr.")
00555         name.truncate( i );
00556         if( !name.isEmpty() ){
00557           sLastName.prepend( "\"" );
00558           sLastName.append( ", " + name + "\" <" );
00559         }
00560         QString sExtraEntry( sLastName );
00561         sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
00562         sExtraEntry.append( ">" );
00563         //kdDebug(5300) << "AddresseeLineEdit::addContact() added extra \"" << sExtraEntry.simplifyWhiteSpace() << "\" weight=" << weight << endl;
00564         addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source );
00565         bDone = true;
00566       }
00567       if( !bDone ) {
00568         name.truncate( i );
00569         if( name.endsWith("\"") )
00570           name.truncate( name.length()-1 );
00571       }
00572     }
00573 #endif
00574   }
00575 }
00576 
00577 void AddresseeLineEdit::addCompletionItem( const QString& string, int weight, int completionItemSource )
00578 {
00579   // Check if there is an exact match for item already, and use the max weight if so.
00580   // Since there's no way to get the information from KCompletion, we have to keep our own QMap
00581   CompletionItemsMap::iterator it = s_completionItemMap->find( string );
00582   if ( it != s_completionItemMap->end() ) {
00583     weight = QMAX( ( *it ).first, weight );
00584     ( *it ).first = weight;
00585   } else {
00586     s_completionItemMap->insert( string, qMakePair( weight, completionItemSource ) );
00587   }
00588   s_completion->addItem( string, weight );
00589 }
00590 
00591 void AddresseeLineEdit::slotStartLDAPLookup()
00592 {
00593   if ( !s_LDAPSearch->isAvailable() ) {
00594     return;
00595   }
00596   if (  s_LDAPLineEdit != this )
00597     return;
00598 
00599   startLoadingLDAPEntries();
00600 }
00601 
00602 void AddresseeLineEdit::stopLDAPLookup()
00603 {
00604   s_LDAPSearch->cancelSearch();
00605   s_LDAPLineEdit = NULL;
00606 }
00607 
00608 void AddresseeLineEdit::startLoadingLDAPEntries()
00609 {
00610   QString s( *s_LDAPText );
00611   // TODO cache last?
00612   QString prevAddr;
00613   int n = s.findRev( ',' );
00614   if ( n >= 0 ) {
00615     prevAddr = s.left( n + 1 ) + ' ';
00616     s = s.mid( n + 1, 255 ).stripWhiteSpace();
00617   }
00618 
00619   if ( s.isEmpty() )
00620     return;
00621 
00622   loadContacts(); // TODO reuse these?
00623   s_LDAPSearch->startSearch( s );
00624 }
00625 
00626 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
00627 {
00628   if ( s_LDAPLineEdit != this )
00629     return;
00630 
00631   for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
00632     KABC::Addressee addr;
00633     addr.setNameFromString( (*it).name );
00634     addr.setEmails( (*it).email );
00635     
00636     addContact( addr, (*it).completionWeight, (*it ).clientNumber  );
00637   }
00638 
00639   if ( hasFocus() || completionBox()->hasFocus() )
00640     if ( completionMode() != KGlobalSettings::CompletionNone )
00641       doCompletion( false );
00642 }
00643 
00644 void AddresseeLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest )
00645 {
00646     KCompletionBox* completionBox = this->completionBox();
00647 
00648     if ( !items.isEmpty() &&
00649          !(items.count() == 1 && m_searchString == items.first()) )
00650     {
00651         if ( completionBox->isVisible() )
00652         {
00653           bool wasSelected = completionBox->isSelected( completionBox->currentItem() );
00654           const QString currentSelection = completionBox->currentText();
00655           completionBox->setItems( items );
00656           QListBoxItem* item = completionBox->findItem( currentSelection, Qt::ExactMatch );
00657           if ( item )
00658           {
00659             completionBox->blockSignals( true );
00660             completionBox->setCurrentItem( item );
00661             completionBox->setSelected( item, wasSelected );
00662             completionBox->blockSignals( false );
00663           }
00664         }
00665         else // completion box not visible yet -> show it
00666         {
00667           if ( !m_searchString.isEmpty() )
00668             completionBox->setCancelledText( m_searchString );
00669           completionBox->setItems( items );
00670           completionBox->popup();
00671           // we have to install the event filter after popup(), since that 
00672           // calls show(), and that's where KCompletionBox installs its filter.
00673           // We want to be first, though, so do it now.
00674           if ( s_completion->order() == KCompletion::Weighted )
00675             qApp->installEventFilter( this );
00676         }
00677 
00678         if ( autoSuggest )
00679         {
00680             int index = items.first().find( m_searchString );
00681             QString newText = items.first().mid( index );
00682             setUserSelection(false);
00683             setCompletedText(newText,true);
00684         }
00685     }
00686     else
00687     {
00688         if ( completionBox && completionBox->isVisible() ) {
00689             completionBox->hide();
00690         }
00691     }
00692 }
00693 
00694 QPopupMenu* AddresseeLineEdit::createPopupMenu()
00695 {
00696   QPopupMenu *menu = KLineEdit::createPopupMenu();
00697   if ( !menu )
00698     return 0;
00699 
00700   if ( m_useCompletion )
00701     menu->insertItem( i18n( "Configure Completion Order..." ),
00702                       this, SLOT( slotEditCompletionOrder() ) );
00703   return menu;
00704 }
00705 
00706 void AddresseeLineEdit::slotEditCompletionOrder()
00707 {
00708   init(); // for s_LDAPSearch
00709   CompletionOrderEditor editor( s_LDAPSearch, this );
00710   editor.exec();
00711 }
00712 
00713 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
00714 {
00715   if ( m_useCompletion )
00716     s_addressesDirty = true;
00717 }
00718 
00719 void KPIM::AddresseeLineEdit::slotUserCancelled( const QString& cancelText )
00720 {
00721   if ( s_LDAPSearch && s_LDAPLineEdit == this )
00722     stopLDAPLookup();
00723   userCancelled( cancelText ); // in KLineEdit
00724 }
00725 
00726 void KPIM::AddresseeLineEdit::slotCompletion()
00727 {
00728   // Called by KLineEdit's keyPressEvent -> new text, update search string
00729   m_searchString = text();
00730   int n = m_searchString.findRev(',');
00731   if ( n >= 0 ) {
00732     ++n; // Go past the ","
00733 
00734     int len = m_searchString.length();
00735 
00736     // Increment past any whitespace...
00737     while ( n < len && m_searchString[ n ].isSpace() )
00738       ++n;
00739 
00740     m_previousAddresses = m_searchString.left( n );
00741     m_searchString = m_searchString.mid( n ).stripWhiteSpace();
00742   }
00743   else
00744   {
00745     m_previousAddresses = QString::null;
00746   }
00747   if ( completionBox() )
00748     completionBox()->setCancelledText( m_searchString );
00749   doCompletion( false );
00750 }
00751 
00752 // not cached, to make sure we get an up-to-date value when it changes
00753 KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
00754 {
00755   KConfig config( "kpimcompletionorder" );
00756   config.setGroup( "General" );
00757   const QString order = config.readEntry( "CompletionOrder", "Weighted" );
00758 
00759   if ( order == "Weighted" )
00760     return KCompletion::Weighted;
00761   else
00762     return KCompletion::Sorted;
00763 }
00764 
00765 int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source )
00766 {
00767   s_completionSources->append( source );
00768   return s_completionSources->size()-1;
00769 }
00770 
00771 bool KPIM::AddresseeLineEdit::eventFilter(QObject *obj, QEvent *e)
00772 {
00773   if ( obj == completionBox() ) {
00774     if ( e->type() == QEvent::MouseButtonPress
00775       || e->type() == QEvent::MouseMove
00776       || e->type() == QEvent::MouseButtonRelease ) {
00777       QMouseEvent* me = static_cast<QMouseEvent*>( e );
00778       // find list box item at the event position
00779       QListBoxItem *item = completionBox()->itemAt( me->pos() );
00780       if ( !item ) {
00781         // In the case of a mouse move outside of the box we don't want
00782         // the parent to fuzzy select a header by mistake.
00783         bool eat = e->type() == QEvent::MouseMove;
00784         return eat;
00785       }
00786       // avoid selection of headers on button press, or move or release while
00787       // a button is pressed
00788       if ( e->type() == QEvent::MouseButtonPress 
00789           || me->state() & LeftButton || me->state() & MidButton 
00790           || me->state() & RightButton ) {
00791         if ( !item->text().startsWith( s_completionItemIndentString ) ) {
00792           return true; // eat the event, we don't want anything to happen
00793         } else {
00794           // if we are not on one of the group heading, make sure the item
00795           // below or above is selected, not the heading, inadvertedly, due
00796           // to fuzzy auto-selection from QListBox
00797           completionBox()->setCurrentItem( item );
00798           completionBox()->setSelected( completionBox()->index( item ), true );
00799           if ( e->type() == QEvent::MouseMove )
00800             return true; // avoid fuzzy selection behavior
00801         }
00802       }
00803     }
00804   }
00805   if ( ( obj == this ) &&
00806      ( e->type() == QEvent::AccelOverride ) ) {
00807     QKeyEvent *ke = static_cast<QKeyEvent*>( e );
00808     if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
00809       ke->accept();
00810       return true;
00811     }
00812   }
00813   if ( ( obj == this ) &&
00814       ( e->type() == QEvent::KeyPress ) &&
00815       completionBox()->isVisible() ) {
00816     QKeyEvent *ke = static_cast<QKeyEvent*>( e );
00817     unsigned int currentIndex = completionBox()->currentItem();
00818     if ( ke->key() == Key_Up || ke->key() == Key_Backtab ) {
00819       //kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl;
00820       // figure out if the item we would be moving to is one we want
00821       // to ignore. If so, go one further
00822       QListBoxItem *itemAbove = completionBox()->item( currentIndex - 1 );
00823       if ( itemAbove && !itemAbove->text().startsWith( s_completionItemIndentString ) ) {
00824         // there is a header above is, check if there is even further up
00825         // and if so go one up, so it'll be selected
00826         if ( currentIndex > 1 && completionBox()->item( currentIndex - 2 ) ) {
00827           //kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl;
00828           completionBox()->setCurrentItem( itemAbove->prev() );
00829           completionBox()->setSelected( currentIndex - 2, true );
00830         } else if ( currentIndex == 1 ) {
00831             // nothing to skip to, let's stay where we are, but make sure the
00832             // first header becomes visible, if we are the first real entry
00833             completionBox()->ensureVisible( 0, 0 );
00834         }
00835         return true;
00836       }
00837     } else if ( ke->key() == Key_Down || ke->key() == Key_Tab ) {
00838       // same strategy for downwards
00839       //kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl;
00840       QListBoxItem *itemBelow = completionBox()->item( currentIndex + 1 );
00841       if ( itemBelow && !itemBelow->text().startsWith( s_completionItemIndentString ) ) {
00842         if ( completionBox()->item( currentIndex + 2 ) ) {
00843           //kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl;
00844           completionBox()->setCurrentItem( itemBelow->next() );
00845           completionBox()->setSelected( currentIndex + 2, true );
00846         } else {
00847           // nothing to skip to, let's stay where we are
00848         }
00849         return true;
00850       }
00851       // special case of the initial selection, which is unfortunately a header.
00852       // Setting it to selected tricks KCompletionBox into not treating is special
00853       // and selecting making it current, instead of the one below.
00854       QListBoxItem *item = completionBox()->item( currentIndex );
00855       if ( item && !item->text().startsWith( s_completionItemIndentString )  ) {
00856         completionBox()->setSelected( currentIndex, true );
00857       }
00858     }
00859   }
00860   return ClickLineEdit::eventFilter( obj, e );
00861 }
00862 
00863 const QStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
00864 {
00865   QStringList items = fullSearch ?
00866     s_completion->allMatches( m_searchString )
00867     : s_completion->substringCompletion( m_searchString );
00868   if ( fullSearch )
00869     items += s_completion->allMatches( "\"" + m_searchString );
00870   unsigned int beforeDollarCompletionCount = items.count();
00871 
00872   if ( fullSearch && m_searchString.find( ' ' ) == -1 ) // one word, possibly given name
00873     items += s_completion->allMatches( "$$" + m_searchString );
00874 
00875   // kdDebug(5300) << "     AddresseeLineEdit::doCompletion() found: " << items.join(" AND ") << endl;
00876 
00877   int lastSourceIndex = -1;
00878   unsigned int i = 0;
00879   QMap<int, QStringList> sections;
00880   QStringList sortedItems;
00881   for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
00882     CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it);
00883     if ( cit == s_completionItemMap->end() )continue;
00884     int idx = (*cit).second;
00885     if ( s_completion->order() == KCompletion::Weighted ) {
00886       if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
00887         const QString sourceLabel(  (*s_completionSources)[idx] );
00888         if ( sections.find(idx) == sections.end() ) {
00889           items.insert( it, sourceLabel );
00890         }
00891         lastSourceIndex = idx;
00892       }
00893       (*it) = (*it).prepend( s_completionItemIndentString );
00894     }
00895     sections[idx].append( *it );
00896 
00897       if ( i > beforeDollarCompletionCount ) { 
00898       // remove the '$$whatever$' part
00899       int pos = (*it).find( '$', 2 );
00900       if ( pos < 0 ) // ???
00901         continue;
00902       (*it) = (*it).mid( pos + 1 );
00903     }
00904     if ( s_completion->order() == KCompletion::Sorted ) {
00905       sortedItems.append( *it );
00906     }
00907   }
00908   if ( s_completion->order() == KCompletion::Weighted ) {
00909     for ( QMap<int, QStringList>::Iterator it( sections.begin() ), end( sections.end() ); it != end; ++it ) {
00910       sortedItems.append( (*s_completionSources)[it.key()] );
00911       for ( QStringList::Iterator sit( (*it).begin() ), send( (*it).end() ); sit != send; ++sit ) {
00912         sortedItems.append( *sit );
00913       }
00914     }
00915   } else {
00916     sortedItems.sort();
00917   }
00918   return sortedItems;
00919 }
00920 #include "addresseelineedit.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys