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