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