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