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