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 if ( 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 } else {
00391
00392 QStringList addrs = splitEmailAddrList( QString::fromUtf8( e->encodedData( "text/plain" ) ) );
00393 if ( addrs.count() > 0 ) {
00394 QStringList::ConstIterator it;
00395 QStringList emails;
00396 for ( it = addrs.begin(); it != addrs.end(); ++it ) {
00397 QString name, mail;
00398 if ( getNameAndMail( (*it), name, mail ) ) {
00399 emails.append( mail );
00400 } else {
00401 continue;
00402 }
00403 }
00404 if ( emails.count() > 0 ) {
00405 setText( emails.join( "," ) );
00406 setEdited( true );
00407 return;
00408 }
00409 }
00410 }
00411 }
00412
00413 if ( m_useCompletion )
00414 m_smartPaste = true;
00415 QLineEdit::dropEvent( e );
00416 m_smartPaste = false;
00417 }
00418
00419 void AddresseeLineEdit::cursorAtEnd()
00420 {
00421 setCursorPosition( text().length() );
00422 }
00423
00424 void AddresseeLineEdit::enableCompletion( bool enable )
00425 {
00426 m_useCompletion = enable;
00427 }
00428
00429 void AddresseeLineEdit::doCompletion( bool ctrlT )
00430 {
00431 m_lastSearchMode = ctrlT;
00432
00433 KGlobalSettings::Completion mode = completionMode();
00434
00435 if ( mode == KGlobalSettings::CompletionNone )
00436 return;
00437
00438 if ( s_addressesDirty ) {
00439 loadContacts();
00440 s_completion->setOrder( completionOrder() );
00441 }
00442
00443
00444 if ( ctrlT ) {
00445 const QStringList completions = getAdjustedCompletionItems( false );
00446
00447 if ( completions.count() > 1 )
00448 ;
00449 else if ( completions.count() == 1 )
00450 setText( m_previousAddresses + completions.first().stripWhiteSpace() );
00451
00452 setCompletedItems( completions, true );
00453
00454 cursorAtEnd();
00455 setCompletionMode( mode );
00456 return;
00457 }
00458
00459
00460 switch ( mode ) {
00461 case KGlobalSettings::CompletionPopupAuto:
00462 {
00463 if ( m_searchString.isEmpty() )
00464 break;
00465 }
00466
00467 case KGlobalSettings::CompletionPopup:
00468 {
00469 const QStringList items = getAdjustedCompletionItems( true );
00470 setCompletedItems( items, false );
00471 break;
00472 }
00473
00474 case KGlobalSettings::CompletionShell:
00475 {
00476 QString match = s_completion->makeCompletion( m_searchString );
00477 if ( !match.isNull() && match != m_searchString ) {
00478 setText( m_previousAddresses + match );
00479 setEdited( true );
00480 cursorAtEnd();
00481 }
00482 break;
00483 }
00484
00485 case KGlobalSettings::CompletionMan:
00486 case KGlobalSettings::CompletionAuto:
00487 {
00488
00489 setCompletionMode( completionMode() );
00490
00491 if ( !m_searchString.isEmpty() ) {
00492
00493
00494 if ( m_searchExtended && m_searchString == "\"" ){
00495 m_searchExtended = false;
00496 m_searchString = QString::null;
00497 setText( m_previousAddresses );
00498 break;
00499 }
00500
00501 QString match = s_completion->makeCompletion( m_searchString );
00502
00503 if ( !match.isEmpty() ) {
00504 if ( match != m_searchString ) {
00505 QString adds = m_previousAddresses + match;
00506 setCompletedText( adds );
00507 }
00508 } else {
00509 if ( !m_searchString.startsWith( "\"" ) ) {
00510
00511 match = s_completion->makeCompletion( "\"" + m_searchString );
00512 if ( !match.isEmpty() && match != m_searchString ) {
00513 m_searchString = "\"" + m_searchString;
00514 m_searchExtended = true;
00515 setText( m_previousAddresses + m_searchString );
00516 setCompletedText( m_previousAddresses + match );
00517 }
00518 } else if ( m_searchExtended ) {
00519
00520 m_searchString = m_searchString.mid( 1 );
00521 m_searchExtended = false;
00522 setText( m_previousAddresses + m_searchString );
00523
00524 match = s_completion->makeCompletion( m_searchString );
00525 if ( !match.isEmpty() && match != m_searchString ) {
00526 QString adds = m_previousAddresses + match;
00527 setCompletedText( adds );
00528 }
00529 }
00530 }
00531 }
00532 break;
00533 }
00534
00535 case KGlobalSettings::CompletionNone:
00536 default:
00537 break;
00538 }
00539 }
00540
00541 void AddresseeLineEdit::slotPopupCompletion( const QString& completion )
00542 {
00543 setText( m_previousAddresses + completion.stripWhiteSpace() );
00544 cursorAtEnd();
00545
00546 updateSearchString();
00547 }
00548
00549 void AddresseeLineEdit::slotReturnPressed( const QString& item )
00550 {
00551 Q_UNUSED( item );
00552 QListBoxItem* i = completionBox()->selectedItem();
00553 if ( i != 0 )
00554 slotPopupCompletion( i->text() );
00555 }
00556
00557 void AddresseeLineEdit::loadContacts()
00558 {
00559 s_completion->clear();
00560 s_completionItemMap->clear();
00561 s_addressesDirty = false;
00562
00563
00564 QApplication::setOverrideCursor( KCursor::waitCursor() );
00565
00566 KConfig config( "kpimcompletionorder" );
00567 config.setGroup( "CompletionWeights" );
00568
00569 KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
00570
00571
00572 QPtrList<KABC::Resource> resources( addressBook->resources() );
00573 for( QPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) {
00574 KABC::Resource* resource = *resit;
00575 KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
00576 if ( resabc ) {
00577 const QMap<QString, QString> uidToResourceMap = resabc->uidToResourceMap();
00578 KABC::Resource::Iterator it;
00579 for ( it = resource->begin(); it != resource->end(); ++it ) {
00580 QString uid = (*it).uid();
00581 QMap<QString, QString>::const_iterator wit = uidToResourceMap.find( uid );
00582 const QString subresourceLabel = resabc->subresourceLabel( *wit );
00583 const int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
00584 const int idx = addCompletionSource( subresourceLabel, weight );
00585
00586
00587 addContact( *it, weight, idx );
00588 }
00589 } else {
00590 int weight = config.readNumEntry( resource->identifier(), 60 );
00591 int sourceIndex = addCompletionSource( resource->resourceName(), weight );
00592 KABC::Resource::Iterator it;
00593 for ( it = resource->begin(); it != resource->end(); ++it ) {
00594 addContact( *it, weight, sourceIndex );
00595 }
00596 }
00597 }
00598
00599 #ifndef KDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above
00600 int weight = config.readNumEntry( "DistributionLists", 60 );
00601 KABC::DistributionListManager manager( addressBook );
00602 manager.load();
00603 const QStringList distLists = manager.listNames();
00604 QStringList::const_iterator listIt;
00605 int idx = addCompletionSource( i18n( "Distribution Lists" ) );
00606 for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
00607
00608
00609 addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx );
00610
00611
00612 QStringList sl( (*listIt).simplifyWhiteSpace() );
00613 addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx, &sl );
00614
00615 }
00616 #endif
00617
00618 QApplication::restoreOverrideCursor();
00619
00620 if ( !m_addressBookConnected ) {
00621 connect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( loadContacts() ) );
00622 m_addressBookConnected = true;
00623 }
00624 }
00625
00626 void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight, int source )
00627 {
00628 #ifdef KDEPIM_NEW_DISTRLISTS
00629 if ( KPIM::DistributionList::isDistributionList( addr ) ) {
00630
00631
00632 if ( m_allowDistLists ) {
00633
00634 addCompletionItem( addr.formattedName(), weight, source );
00635
00636
00637 QStringList sl( addr.formattedName() );
00638 addCompletionItem( addr.formattedName(), weight, source, &sl );
00639 }
00640
00641 return;
00642 }
00643 #endif
00644
00645 const QStringList emails = addr.emails();
00646 QStringList::ConstIterator it;
00647 const int prefEmailWeight = 1;
00648 int isPrefEmail = prefEmailWeight;
00649 for ( it = emails.begin(); it != emails.end(); ++it ) {
00650
00651 const QString email( (*it) );
00652 const QString givenName = addr.givenName();
00653 const QString familyName= addr.familyName();
00654 const QString nickName = addr.nickName();
00655 const QString domain = email.mid( email.find( '@' ) + 1 );
00656 QString fullEmail = addr.fullEmail( email );
00657
00658
00659
00660 if ( givenName.isEmpty() && familyName.isEmpty() ) {
00661 addCompletionItem( fullEmail, weight + isPrefEmail, source );
00662 } else {
00663 const QString byFirstName= "\"" + givenName + " " + familyName + "\" <" + email + ">";
00664 const QString byLastName = "\"" + familyName + ", " + givenName + "\" <" + email + ">";
00665 addCompletionItem( byFirstName, weight + isPrefEmail, source );
00666 addCompletionItem( byLastName, weight + isPrefEmail, source );
00667 }
00668
00669 addCompletionItem( email, weight + isPrefEmail, source );
00670
00671 if ( !nickName.isEmpty() ){
00672 const QString byNick = "\"" + nickName + "\" <" + email + ">";
00673 addCompletionItem( byNick, weight + isPrefEmail, source );
00674 }
00675
00676 if ( !domain.isEmpty() ){
00677 const QString byDomain = "\"" + domain + " " + familyName + " " + givenName + "\" <" + email + ">";
00678 addCompletionItem( byDomain, weight + isPrefEmail, source );
00679 }
00680
00681
00682 QStringList keyWords;
00683 const QString realName = addr.realName();
00684
00685 if ( !givenName.isEmpty() && !familyName.isEmpty() ) {
00686 keyWords.append( givenName + " " + familyName );
00687 keyWords.append( familyName + " " + givenName );
00688 keyWords.append( familyName + ", " + givenName);
00689 }else if ( !givenName.isEmpty() )
00690 keyWords.append( givenName );
00691 else if ( !familyName.isEmpty() )
00692 keyWords.append( familyName );
00693
00694 if ( !nickName.isEmpty() )
00695 keyWords.append( nickName );
00696
00697 if ( !realName.isEmpty() )
00698 keyWords.append( realName );
00699
00700 if ( !domain.isEmpty() )
00701 keyWords.append( domain );
00702
00703 keyWords.append( email );
00704
00705
00706
00707
00708
00709
00710
00711 if ( isPrefEmail == prefEmailWeight )
00712 fullEmail.replace( " <", " <" );
00713
00714 addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords );
00715 isPrefEmail = 0;
00716
00717 #if 0
00718 int len = (*it).length();
00719 if ( len == 0 ) continue;
00720 if( '\0' == (*it)[len-1] )
00721 --len;
00722 const QString tmp = (*it).left( len );
00723 const QString fullEmail = addr.fullEmail( tmp );
00724
00725 addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source );
00726
00727
00728
00729 QString name( addr.realName().simplifyWhiteSpace() );
00730 if( name.endsWith("\"") )
00731 name.truncate( name.length()-1 );
00732 if( name.startsWith("\"") )
00733 name = name.mid( 1 );
00734
00735
00736 if ( !name.isEmpty() )
00737 addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source );
00738
00739 bool bDone = false;
00740 int i = -1;
00741 while( ( i = name.findRev(' ') ) > 1 && !bDone ) {
00742 QString sLastName( name.mid( i+1 ) );
00743 if( ! sLastName.isEmpty() &&
00744 2 <= sLastName.length() &&
00745 ! sLastName.endsWith(".") ) {
00746 name.truncate( i );
00747 if( !name.isEmpty() ){
00748 sLastName.prepend( "\"" );
00749 sLastName.append( ", " + name + "\" <" );
00750 }
00751 QString sExtraEntry( sLastName );
00752 sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
00753 sExtraEntry.append( ">" );
00754
00755 addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source );
00756 bDone = true;
00757 }
00758 if( !bDone ) {
00759 name.truncate( i );
00760 if( name.endsWith("\"") )
00761 name.truncate( name.length()-1 );
00762 }
00763 }
00764 #endif
00765 }
00766 }
00767
00768 void AddresseeLineEdit::addCompletionItem( const QString& string, int weight, int completionItemSource, const QStringList * keyWords )
00769 {
00770
00771
00772 CompletionItemsMap::iterator it = s_completionItemMap->find( string );
00773 if ( it != s_completionItemMap->end() ) {
00774 weight = QMAX( ( *it ).first, weight );
00775 ( *it ).first = weight;
00776 } else {
00777 s_completionItemMap->insert( string, qMakePair( weight, completionItemSource ) );
00778 }
00779 if ( keyWords == 0 )
00780 s_completion->addItem( string, weight );
00781 else
00782 s_completion->addItemWithKeys( string, weight, keyWords );
00783 }
00784
00785 void AddresseeLineEdit::slotStartLDAPLookup()
00786 {
00787 KGlobalSettings::Completion mode = completionMode();
00788
00789 if ( mode == KGlobalSettings::CompletionNone )
00790 return;
00791
00792 if ( !s_LDAPSearch->isAvailable() ) {
00793 return;
00794 }
00795 if ( s_LDAPLineEdit != this )
00796 return;
00797
00798 startLoadingLDAPEntries();
00799 }
00800
00801 void AddresseeLineEdit::stopLDAPLookup()
00802 {
00803 s_LDAPSearch->cancelSearch();
00804 s_LDAPLineEdit = NULL;
00805 }
00806
00807 void AddresseeLineEdit::startLoadingLDAPEntries()
00808 {
00809 QString s( *s_LDAPText );
00810
00811 QString prevAddr;
00812 int n = s.findRev( ',' );
00813 if ( n >= 0 ) {
00814 prevAddr = s.left( n + 1 ) + ' ';
00815 s = s.mid( n + 1, 255 ).stripWhiteSpace();
00816 }
00817
00818 if ( s.isEmpty() )
00819 return;
00820
00821
00822 s_LDAPSearch->startSearch( s );
00823 }
00824
00825 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
00826 {
00827 if ( adrs.isEmpty() || s_LDAPLineEdit != this )
00828 return;
00829
00830 for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
00831 KABC::Addressee addr;
00832 addr.setNameFromString( (*it).name );
00833 addr.setEmails( (*it).email );
00834
00835 if ( !s_ldapClientToCompletionSourceMap->contains( (*it).clientNumber ) )
00836 updateLDAPWeights();
00837
00838 addContact( addr, (*it).completionWeight, (*s_ldapClientToCompletionSourceMap)[ (*it ).clientNumber ] );
00839 }
00840
00841 if ( (hasFocus() || completionBox()->hasFocus() )
00842 && completionMode() != KGlobalSettings::CompletionNone
00843 && completionMode() != KGlobalSettings::CompletionShell ) {
00844 setText( m_previousAddresses + m_searchString );
00845
00846
00847 if ( m_searchString.stripWhiteSpace() != completionBox()->currentText().stripWhiteSpace() )
00848 doCompletion( m_lastSearchMode );
00849 }
00850 }
00851
00852 void AddresseeLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest )
00853 {
00854 KCompletionBox* completionBox = this->completionBox();
00855
00856 if ( !items.isEmpty() &&
00857 !(items.count() == 1 && m_searchString == items.first()) )
00858 {
00859 QString oldCurrentText = completionBox->currentText();
00860 QListBoxItem *itemUnderMouse = completionBox->itemAt(
00861 completionBox->viewport()->mapFromGlobal(QCursor::pos()) );
00862 QString oldTextUnderMouse;
00863 QPoint oldPosOfItemUnderMouse;
00864 if ( itemUnderMouse ) {
00865 oldTextUnderMouse = itemUnderMouse->text();
00866 oldPosOfItemUnderMouse = completionBox->itemRect( itemUnderMouse ).topLeft();
00867 }
00868
00869 completionBox->setItems( items );
00870
00871 if ( !completionBox->isVisible() ) {
00872 if ( !m_searchString.isEmpty() )
00873 completionBox->setCancelledText( m_searchString );
00874 completionBox->popup();
00875
00876
00877
00878 if ( s_completion->order() == KCompletion::Weighted )
00879 qApp->installEventFilter( this );
00880 }
00881
00882
00883
00884 QListBoxItem* item = 0;
00885 if ( oldCurrentText.isEmpty()
00886 || ( item = completionBox->findItem( oldCurrentText ) ) == 0 ) {
00887 item = completionBox->item( 1 );
00888 }
00889 if ( item )
00890 {
00891 if ( itemUnderMouse ) {
00892 QListBoxItem *newItemUnderMouse = completionBox->findItem( oldTextUnderMouse );
00893
00894
00895 if ( newItemUnderMouse ) {
00896 QRect r = completionBox->itemRect( newItemUnderMouse );
00897 QPoint target = r.topLeft();
00898 if ( oldPosOfItemUnderMouse != target ) {
00899 target.setX( target.x() + r.width()/2 );
00900 QCursor::setPos( completionBox->viewport()->mapToGlobal(target) );
00901 }
00902 }
00903 }
00904 completionBox->blockSignals( true );
00905 completionBox->setSelected( item, true );
00906 completionBox->setCurrentItem( item );
00907 completionBox->ensureCurrentVisible();
00908
00909 completionBox->blockSignals( false );
00910 }
00911
00912 if ( autoSuggest )
00913 {
00914 int index = items.first().find( m_searchString );
00915 QString newText = items.first().mid( index );
00916 setUserSelection(false);
00917 setCompletedText(newText,true);
00918 }
00919 }
00920 else
00921 {
00922 if ( completionBox && completionBox->isVisible() ) {
00923 completionBox->hide();
00924 completionBox->setItems( QStringList() );
00925 }
00926 }
00927 }
00928
00929 QPopupMenu* AddresseeLineEdit::createPopupMenu()
00930 {
00931 QPopupMenu *menu = KLineEdit::createPopupMenu();
00932 if ( !menu )
00933 return 0;
00934
00935 if ( m_useCompletion ){
00936 menu->setItemVisible( ShortAutoCompletion, false );
00937 menu->setItemVisible( PopupAutoCompletion, false );
00938 menu->insertItem( i18n( "Configure Completion Order..." ),
00939 this, SLOT( slotEditCompletionOrder() ) );
00940 }
00941 return menu;
00942 }
00943
00944 void AddresseeLineEdit::slotEditCompletionOrder()
00945 {
00946 init();
00947 CompletionOrderEditor editor( s_LDAPSearch, this );
00948 editor.exec();
00949 if ( m_useCompletion ) {
00950 updateLDAPWeights();
00951 s_addressesDirty = true;
00952 }
00953 }
00954
00955 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
00956 {
00957 if ( m_useCompletion )
00958 s_addressesDirty = true;
00959 }
00960
00961 void KPIM::AddresseeLineEdit::slotUserCancelled( const QString& cancelText )
00962 {
00963 if ( s_LDAPSearch && s_LDAPLineEdit == this )
00964 stopLDAPLookup();
00965 userCancelled( m_previousAddresses + cancelText );
00966 }
00967
00968 void AddresseeLineEdit::updateSearchString()
00969 {
00970 m_searchString = text();
00971
00972 int n = -1;
00973 bool inQuote = false;
00974 uint searchStringLength = m_searchString.length();
00975 for ( uint i = 0; i < searchStringLength; ++i ) {
00976 if ( m_searchString[ i ] == '"' ) {
00977 inQuote = !inQuote;
00978 }
00979 if ( m_searchString[ i ] == '\\' &&
00980 (i + 1) < searchStringLength && m_searchString[ i + 1 ] == '"' ) {
00981 ++i;
00982 }
00983 if ( inQuote ) {
00984 continue;
00985 }
00986 if ( i < searchStringLength &&
00987 ( m_searchString[ i ] == ',' ||
00988 ( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) ) ) {
00989 n = i;
00990 }
00991 }
00992
00993 if ( n >= 0 ) {
00994 ++n;
00995
00996 int len = m_searchString.length();
00997
00998
00999 while ( n < len && m_searchString[ n ].isSpace() )
01000 ++n;
01001
01002 m_previousAddresses = m_searchString.left( n );
01003 m_searchString = m_searchString.mid( n ).stripWhiteSpace();
01004 } else {
01005 m_previousAddresses = QString::null;
01006 }
01007 }
01008
01009 void KPIM::AddresseeLineEdit::slotCompletion()
01010 {
01011
01012
01013 updateSearchString();
01014 if ( completionBox() )
01015 completionBox()->setCancelledText( m_searchString );
01016 doCompletion( false );
01017 }
01018
01019
01020 KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
01021 {
01022 KConfig config( "kpimcompletionorder" );
01023 config.setGroup( "General" );
01024 const QString order = config.readEntry( "CompletionOrder", "Weighted" );
01025
01026 if ( order == "Weighted" )
01027 return KCompletion::Weighted;
01028 else
01029 return KCompletion::Sorted;
01030 }
01031
01032 int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source, int weight )
01033 {
01034 QMap<QString,int>::iterator it = s_completionSourceWeights->find( source );
01035 if ( it == s_completionSourceWeights->end() )
01036 s_completionSourceWeights->insert( source, weight );
01037 else
01038 (*s_completionSourceWeights)[source] = weight;
01039
01040 int sourceIndex = s_completionSources->findIndex( source );
01041 if ( sourceIndex == -1 ) {
01042 s_completionSources->append( source );
01043 return s_completionSources->size() - 1;
01044 }
01045 else
01046 return sourceIndex;
01047 }
01048
01049 bool KPIM::AddresseeLineEdit::eventFilter(QObject *obj, QEvent *e)
01050 {
01051 if ( obj == completionBox() ) {
01052 if ( e->type() == QEvent::MouseButtonPress ||
01053 e->type() == QEvent::MouseMove ||
01054 e->type() == QEvent::MouseButtonRelease ||
01055 e->type() == QEvent::MouseButtonDblClick ) {
01056 QMouseEvent* me = static_cast<QMouseEvent*>( e );
01057
01058 QListBoxItem *item = completionBox()->itemAt( me->pos() );
01059 if ( !item ) {
01060
01061
01062 bool eat = e->type() == QEvent::MouseMove;
01063 return eat;
01064 }
01065
01066
01067 if ( e->type() == QEvent::MouseButtonPress
01068 || me->state() & LeftButton || me->state() & MidButton
01069 || me->state() & RightButton ) {
01070 if ( itemIsHeader(item) ) {
01071 return true;
01072 } else {
01073
01074
01075
01076 completionBox()->setCurrentItem( item );
01077 completionBox()->setSelected( completionBox()->index( item ), true );
01078 if ( e->type() == QEvent::MouseMove )
01079 return true;
01080 }
01081 }
01082 }
01083 }
01084 if ( ( obj == this ) &&
01085 ( e->type() == QEvent::AccelOverride ) ) {
01086 QKeyEvent *ke = static_cast<QKeyEvent*>( e );
01087 if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
01088 ke->accept();
01089 return true;
01090 }
01091 }
01092 if ( ( obj == this ) &&
01093 ( e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease ) &&
01094 completionBox()->isVisible() ) {
01095 QKeyEvent *ke = static_cast<QKeyEvent*>( e );
01096 int currentIndex = completionBox()->currentItem();
01097 if ( currentIndex < 0 ) {
01098 return true;
01099 }
01100
01101 if ( ke->key() == Key_Up ) {
01102
01103
01104
01105 QListBoxItem *itemAbove = completionBox()->item( currentIndex );
01106 if ( itemAbove && itemIsHeader(itemAbove) ) {
01107
01108
01109 if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) {
01110
01111 completionBox()->setCurrentItem( itemAbove->prev() );
01112 completionBox()->setSelected( currentIndex - 1, true );
01113 } else if ( currentIndex == 0 ) {
01114
01115
01116 completionBox()->ensureVisible( 0, 0 );
01117
01118 if ( itemIsHeader( completionBox()->item( currentIndex ) ) ) {
01119 currentIndex++;
01120 }
01121 completionBox()->setCurrentItem( itemAbove );
01122 completionBox()->setSelected( currentIndex, true );
01123 }
01124 return true;
01125 }
01126 } else if ( ke->key() == Key_Down ) {
01127
01128
01129 QListBoxItem *itemBelow = completionBox()->item( currentIndex );
01130 if ( itemBelow && itemIsHeader( itemBelow ) ) {
01131 if ( completionBox()->item( currentIndex + 1 ) ) {
01132
01133 completionBox()->setCurrentItem( itemBelow->next() );
01134 completionBox()->setSelected( currentIndex + 1, true );
01135 } else {
01136
01137 completionBox()->setCurrentItem( itemBelow );
01138 completionBox()->setSelected( currentIndex, true );
01139 }
01140 return true;
01141 }
01142
01143 if ( !itemBelow && currentIndex == 1 ) {
01144 completionBox()->setSelected( currentIndex, true );
01145 }
01146
01147
01148
01149 QListBoxItem *item = completionBox()->item( currentIndex );
01150 if ( item && itemIsHeader(item) ) {
01151 completionBox()->setSelected( currentIndex, true );
01152 }
01153 } else if ( e->type() == QEvent::KeyRelease &&
01154 ( ke->key() == Key_Tab || ke->key() == Key_Backtab ) ) {
01155
01157 QListBoxItem *myHeader = 0;
01158 const int iterationstep = ke->key() == Key_Tab ? 1 : -1;
01159 int i = QMIN( QMAX( currentIndex - iterationstep, 0 ), completionBox()->count() - 1 );
01160 while ( i>=0 ) {
01161 if ( itemIsHeader( completionBox()->item(i) ) ) {
01162 myHeader = completionBox()->item( i );
01163 break;
01164 }
01165 i--;
01166 }
01167 Q_ASSERT( myHeader );
01168
01169
01170 QListBoxItem *nextHeader = 0;
01171
01172
01173 uint j;
01174 if ( ke->key() == Key_Tab ) {
01175 j = currentIndex;
01176 } else {
01177 i = completionBox()->index( myHeader );
01178 if ( i == 0 ) {
01179 j = completionBox()->count() - 1;
01180 } else {
01181 j = ( i - 1 ) % completionBox()->count();
01182 }
01183 }
01184 while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
01185 if ( itemIsHeader(nextHeader) ) {
01186 break;
01187 }
01188 j = (j + iterationstep) % completionBox()->count();
01189 }
01190 if ( nextHeader && nextHeader != myHeader ) {
01191 QListBoxItem *item = completionBox()->item( j + 1 );
01192 if ( item && !itemIsHeader(item) ) {
01193 completionBox()->setSelected( item, true );
01194 completionBox()->setCurrentItem( item );
01195 completionBox()->ensureCurrentVisible();
01196 }
01197 }
01198 return true;
01199 }
01200 }
01201 return ClickLineEdit::eventFilter( obj, e );
01202 }
01203
01204 class SourceWithWeight {
01205 public:
01206 int weight;
01207 QString sourceName;
01208 int index;
01209
01210 bool operator< ( const SourceWithWeight &other ) {
01211 if ( weight > other.weight )
01212 return true;
01213 if ( weight < other.weight )
01214 return false;
01215 return sourceName < other.sourceName;
01216 }
01217 };
01218
01219 const QStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
01220 {
01221 QStringList items = fullSearch ?
01222 s_completion->allMatches( m_searchString )
01223 : s_completion->substringCompletion( m_searchString );
01224
01225
01226
01227
01228
01229
01230
01231
01232
01233
01234 int lastSourceIndex = -1;
01235 unsigned int i = 0;
01236
01237
01238
01239 QMap<int, QStringList> sections;
01240 QStringList sortedItems;
01241 for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
01242 CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it);
01243 if ( cit == s_completionItemMap->end() )
01244 continue;
01245 int idx = (*cit).second;
01246
01247 if ( s_completion->order() == KCompletion::Weighted ) {
01248 if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
01249 const QString sourceLabel( (*s_completionSources)[idx] );
01250 if ( sections.find(idx) == sections.end() ) {
01251 items.insert( it, sourceLabel );
01252 }
01253 lastSourceIndex = idx;
01254 }
01255 (*it) = (*it).prepend( s_completionItemIndentString );
01256
01257 (*it).replace( " <", " <" );
01258 }
01259 sections[idx].append( *it );
01260
01261 if ( s_completion->order() == KCompletion::Sorted ) {
01262 sortedItems.append( *it );
01263 }
01264 }
01265
01266 if ( s_completion->order() == KCompletion::Weighted ) {
01267
01268
01269 QValueList<SourceWithWeight> sourcesAndWeights;
01270 for ( uint i = 0; i < s_completionSources->size(); i++ ) {
01271 SourceWithWeight sww;
01272 sww.sourceName = (*s_completionSources)[i];
01273 sww.weight = (*s_completionSourceWeights)[sww.sourceName];
01274 sww.index = i;
01275 sourcesAndWeights.append( sww );
01276 }
01277 qHeapSort( sourcesAndWeights );
01278
01279
01280 for( uint i = 0; i < sourcesAndWeights.size(); i++ ) {
01281 QStringList sectionItems = sections[sourcesAndWeights[i].index];
01282 if ( !sectionItems.isEmpty() ) {
01283 sortedItems.append( sourcesAndWeights[i].sourceName );
01284 QStringList sectionItems = sections[sourcesAndWeights[i].index];
01285 for ( QStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() );
01286 sit != send; ++sit ) {
01287 sortedItems.append( *sit );
01288 }
01289 }
01290 }
01291 } else {
01292 sortedItems.sort();
01293 }
01294 return sortedItems;
01295 }
01296 #include "addresseelineedit.moc"