00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
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
00074
00075 QMap<QString,int>* s_completionSourceWeights;
00076
00077 static KStaticDeleter<KMailCompletion> completionDeleter;
00078 static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
00079 static KStaticDeleter<QTimer> ldapTimerDeleter;
00080 static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
00081 static KStaticDeleter<QString> ldapTextDeleter;
00082 static KStaticDeleter<QStringList> completionSourcesDeleter;
00083 static KStaticDeleter<QMap<QString,int> > completionSourceWeightsDeleter;
00084
00085
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
00125
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
00145
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
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
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() ) {
00214 updateSearchString();
00215 doCompletion( true );
00216 accept = true;
00217 }
00218 }
00219
00220 const QString oldContent = text();
00221 if ( !accept )
00222 KLineEdit::keyPressEvent( e );
00223
00224
00225
00226 if ( oldContent == text() )
00227 return;
00228
00229 if ( e->isAccepted() ) {
00230 updateSearchString();
00231 QString searchString( m_searchString );
00232
00233 if ( m_searchExtended )
00234 searchString = m_searchString.mid( 1 );
00235
00236 if ( m_useCompletion && s_LDAPTimer != NULL ) {
00237 if ( *s_LDAPText != searchString || s_LDAPLineEdit != this )
00238 stopLDAPLookup();
00239
00240 *s_LDAPText = searchString;
00241 s_LDAPLineEdit = this;
00242 s_LDAPTimer->start( 500, true );
00243 }
00244 }
00245 }
00246
00247 void AddresseeLineEdit::insert( const QString &t )
00248 {
00249 if ( !m_smartPaste ) {
00250 KLineEdit::insert( t );
00251 return;
00252 }
00253
00254
00255
00256 QString newText = t.stripWhiteSpace();
00257 if ( newText.isEmpty() )
00258 return;
00259
00260
00261 QStringList lines = QStringList::split( QRegExp("\r?\n"), newText, false );
00262 for ( QStringList::iterator it = lines.begin();
00263 it != lines.end(); ++it ) {
00264
00265 (*it).remove( QRegExp(",?\\s*$") );
00266 }
00267 newText = lines.join( ", " );
00268
00269 if ( newText.startsWith("mailto:") ) {
00270 KURL url( newText );
00271 newText = url.path();
00272 }
00273 else if ( newText.find(" at ") != -1 ) {
00274
00275 newText.replace( " at ", "@" );
00276 newText.replace( " dot ", "." );
00277 }
00278 else if ( newText.find("(at)") != -1 ) {
00279 newText.replace( QRegExp("\\s*\\(at\\)\\s*"), "@" );
00280 }
00281
00282 QString contents = text();
00283 int start_sel = 0;
00284 int end_sel = 0;
00285 int pos = cursorPosition( );
00286 if ( getSelection( &start_sel, &end_sel ) ) {
00287
00288 if ( pos > end_sel )
00289 pos -= (end_sel - start_sel);
00290 else if ( pos > start_sel )
00291 pos = start_sel;
00292 contents = contents.left( start_sel ) + contents.right( end_sel + 1 );
00293 }
00294
00295 int eot = contents.length();
00296 while ((eot > 0) && contents[ eot - 1 ].isSpace() ) eot--;
00297 if ( eot == 0 )
00298 contents = QString::null;
00299 else if ( pos >= eot ) {
00300 if ( contents[ eot - 1 ] == ',' )
00301 eot--;
00302 contents.truncate( eot );
00303 contents += ", ";
00304 pos = eot + 2;
00305 }
00306
00307 contents = contents.left( pos ) + newText + contents.mid( pos );
00308 setText( contents );
00309 setEdited( true );
00310 setCursorPosition( pos + newText.length() );
00311 }
00312
00313 void AddresseeLineEdit::setText( const QString & text )
00314 {
00315 ClickLineEdit::setText( text.stripWhiteSpace() );
00316 }
00317
00318 void AddresseeLineEdit::paste()
00319 {
00320 if ( m_useCompletion )
00321 m_smartPaste = true;
00322
00323 KLineEdit::paste();
00324 m_smartPaste = false;
00325 }
00326
00327 void AddresseeLineEdit::mouseReleaseEvent( QMouseEvent *e )
00328 {
00329
00330 if ( m_useCompletion
00331 && QApplication::clipboard()->supportsSelection()
00332 && !isReadOnly()
00333 && e->button() == MidButton ) {
00334 m_smartPaste = true;
00335 }
00336
00337 KLineEdit::mouseReleaseEvent( e );
00338 m_smartPaste = false;
00339 }
00340
00341 void AddresseeLineEdit::dropEvent( QDropEvent *e )
00342 {
00343 KURL::List uriList;
00344 if ( !isReadOnly()
00345 && KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) {
00346 QString contents = text();
00347
00348 int eot = contents.length();
00349 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() )
00350 eot--;
00351 if ( eot == 0 )
00352 contents = QString::null;
00353 else if ( contents[ eot - 1 ] == ',' ) {
00354 eot--;
00355 contents.truncate( eot );
00356 }
00357 bool mailtoURL = false;
00358
00359 for ( KURL::List::Iterator it = uriList.begin();
00360 it != uriList.end(); ++it ) {
00361 if ( !contents.isEmpty() )
00362 contents.append( ", " );
00363 KURL u( *it );
00364 if ( u.protocol() == "mailto" ) {
00365 mailtoURL = true;
00366 contents.append( (*it).path() );
00367 }
00368 }
00369 if ( mailtoURL ) {
00370 setText( contents );
00371 setEdited( true );
00372 return;
00373 }
00374 }
00375
00376 if ( m_useCompletion )
00377 m_smartPaste = true;
00378 QLineEdit::dropEvent( e );
00379 m_smartPaste = false;
00380 }
00381
00382 void AddresseeLineEdit::cursorAtEnd()
00383 {
00384 setCursorPosition( text().length() );
00385 }
00386
00387 void AddresseeLineEdit::enableCompletion( bool enable )
00388 {
00389 m_useCompletion = enable;
00390 }
00391
00392 void AddresseeLineEdit::doCompletion( bool ctrlT )
00393 {
00394 m_lastSearchMode = ctrlT;
00395
00396 KGlobalSettings::Completion mode = completionMode();
00397
00398 if ( mode == KGlobalSettings::CompletionNone )
00399 return;
00400
00401 if ( s_addressesDirty ) {
00402 loadContacts();
00403 s_completion->setOrder( completionOrder() );
00404 }
00405
00406
00407 if ( ctrlT ) {
00408 const QStringList completions = getAdjustedCompletionItems( false );
00409
00410 if ( completions.count() > 1 )
00411 ;
00412 else if ( completions.count() == 1 )
00413 setText( m_previousAddresses + completions.first().stripWhiteSpace() );
00414
00415 setCompletedItems( completions, true );
00416
00417 cursorAtEnd();
00418 setCompletionMode( mode );
00419 return;
00420 }
00421
00422
00423 switch ( mode ) {
00424 case KGlobalSettings::CompletionPopupAuto:
00425 {
00426 if ( m_searchString.isEmpty() )
00427 break;
00428 }
00429
00430 case KGlobalSettings::CompletionPopup:
00431 {
00432 const QStringList items = getAdjustedCompletionItems( true );
00433 setCompletedItems( items, false );
00434 break;
00435 }
00436
00437 case KGlobalSettings::CompletionShell:
00438 {
00439 QString match = s_completion->makeCompletion( m_searchString );
00440 if ( !match.isNull() && match != m_searchString ) {
00441 setText( m_previousAddresses + match );
00442 setEdited( true );
00443 cursorAtEnd();
00444 }
00445 break;
00446 }
00447
00448 case KGlobalSettings::CompletionMan:
00449 case KGlobalSettings::CompletionAuto:
00450 {
00451
00452 setCompletionMode( completionMode() );
00453
00454 if ( !m_searchString.isEmpty() ) {
00455
00456
00457 if ( m_searchExtended && m_searchString == "\"" ){
00458 m_searchExtended = false;
00459 m_searchString = QString::null;
00460 setText( m_previousAddresses );
00461 break;
00462 }
00463
00464 QString match = s_completion->makeCompletion( m_searchString );
00465
00466 if ( !match.isEmpty() ) {
00467 if ( match != m_searchString ) {
00468 QString adds = m_previousAddresses + match;
00469 setCompletedText( adds );
00470 }
00471 } else {
00472 if ( !m_searchString.startsWith( "\"" ) ) {
00473
00474 match = s_completion->makeCompletion( "\"" + m_searchString );
00475 if ( !match.isEmpty() && match != m_searchString ) {
00476 m_searchString = "\"" + m_searchString;
00477 m_searchExtended = true;
00478 setText( m_previousAddresses + m_searchString );
00479 setCompletedText( m_previousAddresses + match );
00480 }
00481 } else if ( m_searchExtended ) {
00482
00483 m_searchString = m_searchString.mid( 1 );
00484 m_searchExtended = false;
00485 setText( m_previousAddresses + m_searchString );
00486
00487 match = s_completion->makeCompletion( m_searchString );
00488 if ( !match.isEmpty() && match != m_searchString ) {
00489 QString adds = m_previousAddresses + match;
00490 setCompletedText( adds );
00491 }
00492 }
00493 }
00494 }
00495 break;
00496 }
00497
00498 case KGlobalSettings::CompletionNone:
00499 default:
00500 break;
00501 }
00502 }
00503
00504 void AddresseeLineEdit::slotPopupCompletion( const QString& completion )
00505 {
00506 setText( m_previousAddresses + completion.stripWhiteSpace() );
00507 cursorAtEnd();
00508
00509 updateSearchString();
00510 }
00511
00512 void AddresseeLineEdit::slotReturnPressed( const QString& item )
00513 {
00514 Q_UNUSED( item );
00515 QListBoxItem* i = completionBox()->selectedItem();
00516 if ( i != 0 )
00517 slotPopupCompletion( i->text() );
00518 }
00519
00520 void AddresseeLineEdit::loadContacts()
00521 {
00522 s_completion->clear();
00523 s_completionItemMap->clear();
00524 s_addressesDirty = false;
00525
00526
00527 QApplication::setOverrideCursor( KCursor::waitCursor() );
00528
00529 KConfig config( "kpimcompletionorder" );
00530 config.setGroup( "CompletionWeights" );
00531
00532 KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
00533
00534
00535 QPtrList<KABC::Resource> resources( addressBook->resources() );
00536 for( QPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) {
00537 KABC::Resource* resource = *resit;
00538 KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
00539 if ( resabc ) {
00540 const QMap<QString, QString> uidToResourceMap = resabc->uidToResourceMap();
00541 KABC::Resource::Iterator it;
00542 for ( it = resource->begin(); it != resource->end(); ++it ) {
00543 QString uid = (*it).uid();
00544 QMap<QString, QString>::const_iterator wit = uidToResourceMap.find( uid );
00545 const QString subresourceLabel = resabc->subresourceLabel( *wit );
00546 const int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
00547 const int idx = addCompletionSource( subresourceLabel, weight );
00548
00549
00550 addContact( *it, weight, idx );
00551 }
00552 } else {
00553 int weight = config.readNumEntry( resource->identifier(), 60 );
00554 int sourceIndex = addCompletionSource( resource->resourceName(), weight );
00555 KABC::Resource::Iterator it;
00556 for ( it = resource->begin(); it != resource->end(); ++it ) {
00557 addContact( *it, weight, sourceIndex );
00558 }
00559 }
00560 }
00561
00562 #ifndef KDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above
00563 int weight = config.readNumEntry( "DistributionLists", 60 );
00564 KABC::DistributionListManager manager( addressBook );
00565 manager.load();
00566 const QStringList distLists = manager.listNames();
00567 QStringList::const_iterator listIt;
00568 int idx = addCompletionSource( i18n( "Distribution Lists" ) );
00569 for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
00570
00571
00572 addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx );
00573
00574
00575 QStringList sl( (*listIt).simplifyWhiteSpace() );
00576 addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx, &sl );
00577
00578 }
00579 #endif
00580
00581 QApplication::restoreOverrideCursor();
00582
00583 if ( !m_addressBookConnected ) {
00584 connect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( loadContacts() ) );
00585 m_addressBookConnected = true;
00586 }
00587 }
00588
00589 void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight, int source )
00590 {
00591 #ifdef KDEPIM_NEW_DISTRLISTS
00592 if ( KPIM::DistributionList::isDistributionList( addr ) ) {
00593
00594
00595
00596 addCompletionItem( addr.formattedName(), weight, source );
00597
00598
00599 QStringList sl( addr.formattedName() );
00600 addCompletionItem( addr.formattedName(), weight, source, &sl );
00601
00602 return;
00603 }
00604 #endif
00605
00606 const QStringList emails = addr.emails();
00607 QStringList::ConstIterator it;
00608 const int prefEmailWeight = 1;
00609 int isPrefEmail = prefEmailWeight;
00610 for ( it = emails.begin(); it != emails.end(); ++it ) {
00611
00612 const QString email( (*it) );
00613 const QString givenName = addr.givenName();
00614 const QString familyName= addr.familyName();
00615 const QString nickName = addr.nickName();
00616 const QString domain = email.mid( email.find( '@' ) + 1 );
00617 QString fullEmail = addr.fullEmail( email );
00618
00619
00620
00621 if ( givenName.isEmpty() && familyName.isEmpty() ) {
00622 addCompletionItem( fullEmail, weight + isPrefEmail, source );
00623 } else {
00624 const QString byFirstName= "\"" + givenName + " " + familyName + "\" <" + email + ">";
00625 const QString byLastName = "\"" + familyName + ", " + givenName + "\" <" + email + ">";
00626 addCompletionItem( byFirstName, weight + isPrefEmail, source );
00627 addCompletionItem( byLastName, weight + isPrefEmail, source );
00628 }
00629
00630 addCompletionItem( email, weight + isPrefEmail, source );
00631
00632 if ( !nickName.isEmpty() ){
00633 const QString byNick = "\"" + nickName + "\" <" + email + ">";
00634 addCompletionItem( byNick, weight + isPrefEmail, source );
00635 }
00636
00637 if ( !domain.isEmpty() ){
00638 const QString byDomain = "\"" + domain + " " + familyName + " " + givenName + "\" <" + email + ">";
00639 addCompletionItem( byDomain, weight + isPrefEmail, source );
00640 }
00641
00642
00643 QStringList keyWords;
00644 const QString realName = addr.realName();
00645
00646 if ( !givenName.isEmpty() && !familyName.isEmpty() ) {
00647 keyWords.append( givenName + " " + familyName );
00648 keyWords.append( familyName + " " + givenName );
00649 keyWords.append( familyName + ", " + givenName);
00650 }else if ( !givenName.isEmpty() )
00651 keyWords.append( givenName );
00652 else if ( !familyName.isEmpty() )
00653 keyWords.append( familyName );
00654
00655 if ( !nickName.isEmpty() )
00656 keyWords.append( nickName );
00657
00658 if ( !realName.isEmpty() )
00659 keyWords.append( realName );
00660
00661 if ( !domain.isEmpty() )
00662 keyWords.append( domain );
00663
00664 keyWords.append( email );
00665
00666
00667
00668
00669
00670
00671
00672 if ( isPrefEmail == prefEmailWeight )
00673 fullEmail.replace( " <", " <" );
00674
00675 addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords );
00676 isPrefEmail = 0;
00677
00678 #if 0
00679 int len = (*it).length();
00680 if ( len == 0 ) continue;
00681 if( '\0' == (*it)[len-1] )
00682 --len;
00683 const QString tmp = (*it).left( len );
00684 const QString fullEmail = addr.fullEmail( tmp );
00685
00686 addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source );
00687
00688
00689
00690 QString name( addr.realName().simplifyWhiteSpace() );
00691 if( name.endsWith("\"") )
00692 name.truncate( name.length()-1 );
00693 if( name.startsWith("\"") )
00694 name = name.mid( 1 );
00695
00696
00697 if ( !name.isEmpty() )
00698 addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source );
00699
00700 bool bDone = false;
00701 int i = -1;
00702 while( ( i = name.findRev(' ') ) > 1 && !bDone ) {
00703 QString sLastName( name.mid( i+1 ) );
00704 if( ! sLastName.isEmpty() &&
00705 2 <= sLastName.length() &&
00706 ! sLastName.endsWith(".") ) {
00707 name.truncate( i );
00708 if( !name.isEmpty() ){
00709 sLastName.prepend( "\"" );
00710 sLastName.append( ", " + name + "\" <" );
00711 }
00712 QString sExtraEntry( sLastName );
00713 sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
00714 sExtraEntry.append( ">" );
00715
00716 addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source );
00717 bDone = true;
00718 }
00719 if( !bDone ) {
00720 name.truncate( i );
00721 if( name.endsWith("\"") )
00722 name.truncate( name.length()-1 );
00723 }
00724 }
00725 #endif
00726 }
00727 }
00728
00729 void AddresseeLineEdit::addCompletionItem( const QString& string, int weight, int completionItemSource, const QStringList * keyWords )
00730 {
00731
00732
00733 CompletionItemsMap::iterator it = s_completionItemMap->find( string );
00734 if ( it != s_completionItemMap->end() ) {
00735 weight = QMAX( ( *it ).first, weight );
00736 ( *it ).first = weight;
00737 } else {
00738 s_completionItemMap->insert( string, qMakePair( weight, completionItemSource ) );
00739 }
00740 if ( keyWords == 0 )
00741 s_completion->addItem( string, weight );
00742 else
00743 s_completion->addItemWithKeys( string, weight, keyWords );
00744 }
00745
00746 void AddresseeLineEdit::slotStartLDAPLookup()
00747 {
00748 KGlobalSettings::Completion mode = completionMode();
00749
00750 if ( mode == KGlobalSettings::CompletionNone )
00751 return;
00752
00753 if ( !s_LDAPSearch->isAvailable() ) {
00754 return;
00755 }
00756 if ( s_LDAPLineEdit != this )
00757 return;
00758
00759 startLoadingLDAPEntries();
00760 }
00761
00762 void AddresseeLineEdit::stopLDAPLookup()
00763 {
00764 s_LDAPSearch->cancelSearch();
00765 s_LDAPLineEdit = NULL;
00766 }
00767
00768 void AddresseeLineEdit::startLoadingLDAPEntries()
00769 {
00770 QString s( *s_LDAPText );
00771
00772 QString prevAddr;
00773 int n = s.findRev( ',' );
00774 if ( n >= 0 ) {
00775 prevAddr = s.left( n + 1 ) + ' ';
00776 s = s.mid( n + 1, 255 ).stripWhiteSpace();
00777 }
00778
00779 if ( s.isEmpty() )
00780 return;
00781
00782
00783 s_LDAPSearch->startSearch( s );
00784 }
00785
00786 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
00787 {
00788 if ( s_LDAPLineEdit != this )
00789 return;
00790
00791 for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
00792 KABC::Addressee addr;
00793 addr.setNameFromString( (*it).name );
00794 addr.setEmails( (*it).email );
00795
00796 addContact( addr, (*it).completionWeight, (*it ).clientNumber );
00797 }
00798
00799 if ( (hasFocus() || completionBox()->hasFocus() )
00800 && completionMode() != KGlobalSettings::CompletionNone
00801 && completionMode() != KGlobalSettings::CompletionShell ) {
00802 setText( m_previousAddresses + m_searchString );
00803
00804
00805 if ( m_searchString.stripWhiteSpace() != completionBox()->currentText().stripWhiteSpace() )
00806 doCompletion( m_lastSearchMode );
00807 }
00808 }
00809
00810 void AddresseeLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest )
00811 {
00812 KCompletionBox* completionBox = this->completionBox();
00813
00814 if ( !items.isEmpty() &&
00815 !(items.count() == 1 && m_searchString == items.first()) )
00816 {
00817 QString oldCurrentText = completionBox->currentText();
00818 QListBoxItem *itemUnderMouse = completionBox->itemAt(
00819 completionBox->viewport()->mapFromGlobal(QCursor::pos()) );
00820 QString oldTextUnderMouse;
00821 QPoint oldPosOfItemUnderMouse;
00822 if ( itemUnderMouse ) {
00823 oldTextUnderMouse = itemUnderMouse->text();
00824 oldPosOfItemUnderMouse = completionBox->itemRect( itemUnderMouse ).topLeft();
00825 }
00826
00827 completionBox->setItems( items );
00828
00829 if ( !completionBox->isVisible() ) {
00830 if ( !m_searchString.isEmpty() )
00831 completionBox->setCancelledText( m_searchString );
00832 completionBox->popup();
00833
00834
00835
00836 if ( s_completion->order() == KCompletion::Weighted )
00837 qApp->installEventFilter( this );
00838 }
00839
00840
00841
00842 QListBoxItem* item = 0;
00843 if ( oldCurrentText.isEmpty()
00844 || ( item = completionBox->findItem( oldCurrentText ) ) == 0 ) {
00845 item = completionBox->item( 1 );
00846 }
00847 if ( item )
00848 {
00849 if ( itemUnderMouse ) {
00850 QListBoxItem *newItemUnderMouse = completionBox->findItem( oldTextUnderMouse );
00851
00852
00853 if ( newItemUnderMouse ) {
00854 QRect r = completionBox->itemRect( newItemUnderMouse );
00855 QPoint target = r.topLeft();
00856 if ( oldPosOfItemUnderMouse != target ) {
00857 target.setX( target.x() + r.width()/2 );
00858 QCursor::setPos( completionBox->viewport()->mapToGlobal(target) );
00859 }
00860 }
00861 }
00862 completionBox->blockSignals( true );
00863 completionBox->setSelected( item, true );
00864 completionBox->setCurrentItem( item );
00865 completionBox->ensureCurrentVisible();
00866
00867 completionBox->blockSignals( false );
00868 }
00869
00870 if ( autoSuggest )
00871 {
00872 int index = items.first().find( m_searchString );
00873 QString newText = items.first().mid( index );
00874 setUserSelection(false);
00875 setCompletedText(newText,true);
00876 }
00877 }
00878 else
00879 {
00880 if ( completionBox && completionBox->isVisible() ) {
00881 completionBox->hide();
00882 completionBox->setItems( QStringList() );
00883 }
00884 }
00885 }
00886
00887 QPopupMenu* AddresseeLineEdit::createPopupMenu()
00888 {
00889 QPopupMenu *menu = KLineEdit::createPopupMenu();
00890 if ( !menu )
00891 return 0;
00892
00893 if ( m_useCompletion ){
00894 menu->setItemVisible( ShortAutoCompletion, false );
00895 menu->setItemVisible( PopupAutoCompletion, false );
00896 menu->insertItem( i18n( "Configure Completion Order..." ),
00897 this, SLOT( slotEditCompletionOrder() ) );
00898 }
00899 return menu;
00900 }
00901
00902 void AddresseeLineEdit::slotEditCompletionOrder()
00903 {
00904 init();
00905 CompletionOrderEditor editor( s_LDAPSearch, this );
00906 editor.exec();
00907 if ( m_useCompletion ) {
00908 updateLDAPWeights();
00909 s_addressesDirty = true;
00910 }
00911 }
00912
00913 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
00914 {
00915 if ( m_useCompletion )
00916 s_addressesDirty = true;
00917 }
00918
00919 void KPIM::AddresseeLineEdit::slotUserCancelled( const QString& cancelText )
00920 {
00921 if ( s_LDAPSearch && s_LDAPLineEdit == this )
00922 stopLDAPLookup();
00923 userCancelled( m_previousAddresses + cancelText );
00924 }
00925
00926 void AddresseeLineEdit::updateSearchString()
00927 {
00928 m_searchString = text();
00929
00930 int n = -1;
00931 bool inQuote = false;
00932 for ( uint i = 0; i < m_searchString.length(); ++i ) {
00933 if ( m_searchString[ i ] == '"' )
00934 inQuote = !inQuote;
00935 if ( m_searchString[ i ] == '\\' && (i + 1) < m_searchString.length() && m_searchString[ i + 1 ] == '"' )
00936 ++i;
00937 if ( inQuote )
00938 continue;
00939 if ( m_searchString[ i ] == ',' || ( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) )
00940 n = i;
00941 }
00942
00943 if ( n >= 0 ) {
00944 ++n;
00945
00946 int len = m_searchString.length();
00947
00948
00949 while ( n < len && m_searchString[ n ].isSpace() )
00950 ++n;
00951
00952 m_previousAddresses = m_searchString.left( n );
00953 m_searchString = m_searchString.mid( n ).stripWhiteSpace();
00954 }
00955 else
00956 {
00957 m_previousAddresses = QString::null;
00958 }
00959 }
00960
00961 void KPIM::AddresseeLineEdit::slotCompletion()
00962 {
00963
00964
00965 updateSearchString();
00966 if ( completionBox() )
00967 completionBox()->setCancelledText( m_searchString );
00968 doCompletion( false );
00969 }
00970
00971
00972 KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
00973 {
00974 KConfig config( "kpimcompletionorder" );
00975 config.setGroup( "General" );
00976 const QString order = config.readEntry( "CompletionOrder", "Weighted" );
00977
00978 if ( order == "Weighted" )
00979 return KCompletion::Weighted;
00980 else
00981 return KCompletion::Sorted;
00982 }
00983
00984 int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source, int weight )
00985 {
00986 QMap<QString,int>::iterator it = s_completionSourceWeights->find( source );
00987 if ( it == s_completionSourceWeights->end() )
00988 s_completionSourceWeights->insert( source, weight );
00989 else
00990 (*s_completionSourceWeights)[source] = weight;
00991
00992 int sourceIndex = s_completionSources->findIndex( source );
00993 if ( sourceIndex == -1 ) {
00994 s_completionSources->append( source );
00995 return s_completionSources->size() - 1;
00996 }
00997 else
00998 return sourceIndex;
00999 }
01000
01001 bool KPIM::AddresseeLineEdit::eventFilter(QObject *obj, QEvent *e)
01002 {
01003 if ( obj == completionBox() ) {
01004 if ( e->type() == QEvent::MouseButtonPress
01005 || e->type() == QEvent::MouseMove
01006 || e->type() == QEvent::MouseButtonRelease ) {
01007 QMouseEvent* me = static_cast<QMouseEvent*>( e );
01008
01009 QListBoxItem *item = completionBox()->itemAt( me->pos() );
01010 if ( !item ) {
01011
01012
01013 bool eat = e->type() == QEvent::MouseMove;
01014 return eat;
01015 }
01016
01017
01018 if ( e->type() == QEvent::MouseButtonPress
01019 || me->state() & LeftButton || me->state() & MidButton
01020 || me->state() & RightButton ) {
01021 if ( itemIsHeader(item) ) {
01022 return true;
01023 } else {
01024
01025
01026
01027 completionBox()->setCurrentItem( item );
01028 completionBox()->setSelected( completionBox()->index( item ), true );
01029 if ( e->type() == QEvent::MouseMove )
01030 return true;
01031 }
01032 }
01033 }
01034 }
01035 if ( ( obj == this ) &&
01036 ( e->type() == QEvent::AccelOverride ) ) {
01037 QKeyEvent *ke = static_cast<QKeyEvent*>( e );
01038 if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
01039 ke->accept();
01040 return true;
01041 }
01042 }
01043 if ( ( obj == this ) &&
01044 ( e->type() == QEvent::KeyPress ) &&
01045 completionBox()->isVisible() ) {
01046 QKeyEvent *ke = static_cast<QKeyEvent*>( e );
01047 unsigned int currentIndex = completionBox()->currentItem();
01048 if ( ke->key() == Key_Up ) {
01049
01050
01051
01052 QListBoxItem *itemAbove = completionBox()->item( currentIndex - 1 );
01053 if ( itemAbove && itemIsHeader(itemAbove) ) {
01054
01055
01056 if ( currentIndex > 1 && completionBox()->item( currentIndex - 2 ) ) {
01057
01058 completionBox()->setCurrentItem( itemAbove->prev() );
01059 completionBox()->setSelected( currentIndex - 2, true );
01060 } else if ( currentIndex == 1 ) {
01061
01062
01063 completionBox()->ensureVisible( 0, 0 );
01064
01065 completionBox()->setCurrentItem( itemAbove );
01066 completionBox()->setSelected( currentIndex, true );
01067 }
01068 return true;
01069 }
01070 } else if ( ke->key() == Key_Down ) {
01071
01072
01073 QListBoxItem *itemBelow = completionBox()->item( currentIndex + 1 );
01074 if ( itemBelow && itemIsHeader( itemBelow ) ) {
01075 if ( completionBox()->item( currentIndex + 2 ) ) {
01076
01077 completionBox()->setCurrentItem( itemBelow->next() );
01078 completionBox()->setSelected( currentIndex + 2, true );
01079 } else {
01080
01081 completionBox()->setSelected( currentIndex, true );
01082 }
01083 return true;
01084 }
01085
01086 if ( !itemBelow && currentIndex == 1 ) {
01087 completionBox()->setSelected( currentIndex, true );
01088 }
01089
01090
01091
01092 QListBoxItem *item = completionBox()->item( currentIndex );
01093 if ( item && itemIsHeader(item) ) {
01094 completionBox()->setSelected( currentIndex, true );
01095 }
01096 } else if ( ke->key() == Key_Tab || ke->key() == Key_Backtab ) {
01098 QListBoxItem *myHeader = 0;
01099 int i = currentIndex;
01100 while ( i>=0 ) {
01101 if ( itemIsHeader( completionBox()->item(i) ) ) {
01102 myHeader = completionBox()->item( i );
01103 break;
01104 }
01105 i--;
01106 }
01107 Q_ASSERT( myHeader );
01108
01109
01110 QListBoxItem *nextHeader = 0;
01111 const int iterationstep = ke->key() == Key_Tab ? 1 : -1;
01112
01113
01114 uint j = ke->key() == Key_Tab ? currentIndex : i==0 ? completionBox()->count()-1 : (i-1) % completionBox()->count();
01115 while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
01116 if ( itemIsHeader(nextHeader) ) {
01117 break;
01118 }
01119 j = (j + iterationstep) % completionBox()->count();
01120 }
01121 if ( nextHeader && nextHeader != myHeader ) {
01122 QListBoxItem *item = completionBox()->item( j + 1 );
01123 if ( item && !itemIsHeader(item) ) {
01124 completionBox()->setSelected( j+1, true );
01125 completionBox()->setCurrentItem( item );
01126 completionBox()->ensureCurrentVisible();
01127 }
01128 }
01129 return true;
01130 }
01131 }
01132 return ClickLineEdit::eventFilter( obj, e );
01133 }
01134
01135 class SourceWithWeight {
01136 public:
01137 int weight;
01138 QString sourceName;
01139 int index;
01140
01141 bool operator< ( const SourceWithWeight &other ) {
01142 if ( weight > other.weight )
01143 return true;
01144 if ( weight < other.weight )
01145 return false;
01146 return sourceName < other.sourceName;
01147 }
01148 };
01149
01150 const QStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
01151 {
01152 QStringList items = fullSearch ?
01153 s_completion->allMatches( m_searchString )
01154 : s_completion->substringCompletion( m_searchString );
01155
01156
01157
01158
01159
01160
01161
01162
01163
01164
01165 int lastSourceIndex = -1;
01166 unsigned int i = 0;
01167
01168
01169
01170 QMap<int, QStringList> sections;
01171 QStringList sortedItems;
01172 for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
01173 CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it);
01174 if ( cit == s_completionItemMap->end() )
01175 continue;
01176 int idx = (*cit).second;
01177
01178 if ( s_completion->order() == KCompletion::Weighted ) {
01179 if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
01180 const QString sourceLabel( (*s_completionSources)[idx] );
01181 if ( sections.find(idx) == sections.end() ) {
01182 items.insert( it, sourceLabel );
01183 }
01184 lastSourceIndex = idx;
01185 }
01186 (*it) = (*it).prepend( s_completionItemIndentString );
01187
01188 (*it).replace( " <", " <" );
01189 }
01190 sections[idx].append( *it );
01191
01192 if ( s_completion->order() == KCompletion::Sorted ) {
01193 sortedItems.append( *it );
01194 }
01195 }
01196
01197 if ( s_completion->order() == KCompletion::Weighted ) {
01198
01199
01200 QValueList<SourceWithWeight> sourcesAndWeights;
01201 for ( uint i = 0; i < s_completionSources->size(); i++ ) {
01202 SourceWithWeight sww;
01203 sww.sourceName = (*s_completionSources)[i];
01204 sww.weight = (*s_completionSourceWeights)[sww.sourceName];
01205 sww.index = i;
01206 sourcesAndWeights.append( sww );
01207 }
01208 qHeapSort( sourcesAndWeights );
01209
01210
01211 for( uint i = 0; i < sourcesAndWeights.size(); i++ ) {
01212 QStringList sectionItems = sections[sourcesAndWeights[i].index];
01213 if ( !sectionItems.isEmpty() ) {
01214 sortedItems.append( sourcesAndWeights[i].sourceName );
01215 QStringList sectionItems = sections[sourcesAndWeights[i].index];
01216 for ( QStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() );
01217 sit != send; ++sit ) {
01218 sortedItems.append( *sit );
01219 }
01220 }
01221 }
01222 } else {
01223 sortedItems.sort();
01224 }
01225 return sortedItems;
01226 }
01227 #include "addresseelineedit.moc"