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