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 <kabc/distributionlist.h>
00029 #include <kabc/stdaddressbook.h>
00030 #include <kabc/resource.h>
00031
00032 #include <kcompletionbox.h>
00033 #include <kcursor.h>
00034 #include <kdebug.h>
00035 #include <kstandarddirs.h>
00036 #include <kstaticdeleter.h>
00037 #include <kstdaccel.h>
00038 #include <kurldrag.h>
00039 #include <klocale.h>
00040
00041 #include "completionordereditor.h"
00042 #include "ldapclient.h"
00043
00044 #include <qpopupmenu.h>
00045 #include <qapplication.h>
00046 #include <qobject.h>
00047 #include <qptrlist.h>
00048 #include <qregexp.h>
00049 #include <qevent.h>
00050 #include <qdragobject.h>
00051 #include <qclipboard.h>
00052 #include "resourceabc.h"
00053
00054 using namespace KPIM;
00055
00056 KCompletion * AddresseeLineEdit::s_completion = 0L;
00057 KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L;
00058 bool AddresseeLineEdit::s_addressesDirty = false;
00059 QTimer* AddresseeLineEdit::s_LDAPTimer = 0L;
00060 KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L;
00061 QString* AddresseeLineEdit::s_LDAPText = 0L;
00062 AddresseeLineEdit* AddresseeLineEdit::s_LDAPLineEdit = 0L;
00063 KConfig *AddresseeLineEdit::s_config = 0L;
00064
00065 static KStaticDeleter<KCompletion> completionDeleter;
00066 static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
00067 static KStaticDeleter<QTimer> ldapTimerDeleter;
00068 static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
00069 static KStaticDeleter<QString> ldapTextDeleter;
00070 static KStaticDeleter<KConfig> configDeleter;
00071
00072
00073 static QCString newLineEditDCOPObjectName()
00074 {
00075 static int s_count = 0;
00076 QCString name( "KPIM::AddresseeLineEdit" );
00077 if ( s_count++ ) {
00078 name += '-';
00079 name += QCString().setNum( s_count );
00080 }
00081 return name;
00082 }
00083
00084 AddresseeLineEdit::AddresseeLineEdit( QWidget* parent, bool useCompletion,
00085 const char *name )
00086 : ClickLineEdit( parent, QString::null, name ), DCOPObject( newLineEditDCOPObjectName() )
00087 {
00088 m_useCompletion = useCompletion;
00089 m_completionInitialized = false;
00090 m_smartPaste = false;
00091 m_addressBookConnected = false;
00092
00093 init();
00094
00095 if ( m_useCompletion )
00096 s_addressesDirty = true;
00097 }
00098
00099
00100 void AddresseeLineEdit::init()
00101 {
00102 if ( !s_completion ) {
00103 completionDeleter.setObject( s_completion, new KCompletion() );
00104 s_completion->setOrder( KCompletion::Weighted );
00105 s_completion->setIgnoreCase( true );
00106
00107 completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() );
00108 }
00109
00110
00111
00112
00113 if ( m_useCompletion ) {
00114 if ( !s_LDAPTimer ) {
00115 ldapTimerDeleter.setObject( s_LDAPTimer, new QTimer );
00116 ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch );
00117 ldapTextDeleter.setObject( s_LDAPText, new QString );
00118 }
00119 if ( !m_completionInitialized ) {
00120 setCompletionObject( s_completion, false );
00121 connect( this, SIGNAL( completion( const QString& ) ),
00122 this, SLOT( slotCompletion() ) );
00123
00124 KCompletionBox *box = completionBox();
00125 connect( box, SIGNAL( highlighted( const QString& ) ),
00126 this, SLOT( slotPopupCompletion( const QString& ) ) );
00127 connect( box, SIGNAL( userCancelled( const QString& ) ),
00128 SLOT( slotUserCancelled( const QString& ) ) );
00129
00130
00131 if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()",
00132 "slotIMAPCompletionOrderChanged()", false ) )
00133 kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl;
00134
00135 connect( s_LDAPTimer, SIGNAL( timeout() ), SLOT( slotStartLDAPLookup() ) );
00136 connect( s_LDAPSearch, SIGNAL( searchData( const KPIM::LdapResultList& ) ),
00137 SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) );
00138
00139 m_completionInitialized = true;
00140 }
00141 }
00142 }
00143
00144 AddresseeLineEdit::~AddresseeLineEdit()
00145 {
00146 if ( s_LDAPSearch && s_LDAPLineEdit == this )
00147 stopLDAPLookup();
00148 }
00149
00150 void AddresseeLineEdit::setFont( const QFont& font )
00151 {
00152 KLineEdit::setFont( font );
00153 if ( m_useCompletion )
00154 completionBox()->setFont( font );
00155 }
00156
00157 void AddresseeLineEdit::keyPressEvent( QKeyEvent *e )
00158 {
00159 bool accept = false;
00160
00161 if ( KStdAccel::shortcut( KStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) {
00162 doCompletion( true );
00163 accept = true;
00164 } else if ( KStdAccel::shortcut( KStdAccel::TextCompletion ).contains( KKey( e ) ) ) {
00165 int len = text().length();
00166
00167 if ( len == cursorPosition() ) {
00168 doCompletion( true );
00169 accept = true;
00170 }
00171 }
00172
00173 if ( !accept )
00174 KLineEdit::keyPressEvent( e );
00175
00176 if ( e->isAccepted() ) {
00177 if ( m_useCompletion && s_LDAPTimer != NULL ) {
00178 if ( *s_LDAPText != text() || s_LDAPLineEdit != this )
00179 stopLDAPLookup();
00180
00181 *s_LDAPText = text();
00182 s_LDAPLineEdit = this;
00183 s_LDAPTimer->start( 500, true );
00184 }
00185 }
00186 }
00187
00188 void AddresseeLineEdit::insert( const QString &t )
00189 {
00190 if ( !m_smartPaste ) {
00191 KLineEdit::insert( t );
00192 return;
00193 }
00194
00195
00196
00197 QString newText = t.stripWhiteSpace();
00198 if ( newText.isEmpty() )
00199 return;
00200
00201
00202
00203 newText.replace( QRegExp("\r?\n"), ", " );
00204
00205 if ( newText.startsWith("mailto:") ) {
00206 KURL url( newText );
00207 newText = url.path();
00208 }
00209 else if ( newText.find(" at ") != -1 ) {
00210
00211 newText.replace( " at ", "@" );
00212 newText.replace( " dot ", "." );
00213 }
00214 else if ( newText.find("(at)") != -1 ) {
00215 newText.replace( QRegExp("\\s*\\(at\\)\\s*"), "@" );
00216 }
00217
00218 QString contents = text();
00219 int start_sel = 0;
00220 int end_sel = 0;
00221 int pos = cursorPosition();
00222 if ( getSelection( &start_sel, &end_sel ) ) {
00223
00224 if ( pos > end_sel )
00225 pos -= (end_sel - start_sel);
00226 else if ( pos > start_sel )
00227 pos = start_sel;
00228 contents = contents.left( start_sel ) + contents.right( end_sel + 1 );
00229 }
00230
00231 int eot = contents.length();
00232 while ((eot > 0) && contents[ eot - 1 ].isSpace() ) eot--;
00233 if ( eot == 0 )
00234 contents = QString::null;
00235 else if ( pos >= eot ) {
00236 if ( contents[ eot - 1 ] == ',' )
00237 eot--;
00238 contents.truncate( eot );
00239 contents += ", ";
00240 pos = eot + 2;
00241 }
00242
00243 contents = contents.left( pos ) + newText + contents.mid( pos );
00244 setText( contents );
00245 setCursorPosition( pos + newText.length() );
00246 }
00247
00248 void AddresseeLineEdit::paste()
00249 {
00250 if ( m_useCompletion )
00251 m_smartPaste = true;
00252
00253 KLineEdit::paste();
00254 m_smartPaste = false;
00255 }
00256
00257 void AddresseeLineEdit::mouseReleaseEvent( QMouseEvent *e )
00258 {
00259
00260 if ( m_useCompletion
00261 && QApplication::clipboard()->supportsSelection()
00262 && !isReadOnly()
00263 && e->button() == MidButton ) {
00264 m_smartPaste = true;
00265 }
00266
00267 KLineEdit::mouseReleaseEvent( e );
00268 m_smartPaste = false;
00269 }
00270
00271 void AddresseeLineEdit::dropEvent( QDropEvent *e )
00272 {
00273 KURL::List uriList;
00274 if ( !isReadOnly()
00275 && KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) {
00276 QString contents = text();
00277
00278 int eot = contents.length();
00279 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() )
00280 eot--;
00281 if ( eot == 0 )
00282 contents = QString::null;
00283 else if ( contents[ eot - 1 ] == ',' ) {
00284 eot--;
00285 contents.truncate( eot );
00286 }
00287 bool mailtoURL = false;
00288
00289 for ( KURL::List::Iterator it = uriList.begin();
00290 it != uriList.end(); ++it ) {
00291 if ( !contents.isEmpty() )
00292 contents.append( ", " );
00293 KURL u( *it );
00294 if ( u.protocol() == "mailto" ) {
00295 mailtoURL = true;
00296 contents.append( (*it).path() );
00297 }
00298 }
00299 if ( mailtoURL ) {
00300 setText( contents );
00301 setEdited( true );
00302 return;
00303 }
00304 }
00305
00306 if ( m_useCompletion )
00307 m_smartPaste = true;
00308 QLineEdit::dropEvent( e );
00309 m_smartPaste = false;
00310 }
00311
00312 void AddresseeLineEdit::cursorAtEnd()
00313 {
00314 setCursorPosition( text().length() );
00315 }
00316
00317 void AddresseeLineEdit::enableCompletion( bool enable )
00318 {
00319 m_useCompletion = enable;
00320 }
00321
00322 void AddresseeLineEdit::doCompletion( bool ctrlT )
00323 {
00324 if ( !m_useCompletion )
00325 return;
00326
00327 if ( s_addressesDirty )
00328 loadContacts();
00329
00330
00331 if ( ctrlT ) {
00332 const QStringList completions = s_completion->substringCompletion( m_searchString );
00333
00334 if ( completions.count() > 1 )
00335 ;
00336 else if ( completions.count() == 1 )
00337 setText( m_previousAddresses + completions.first() );
00338
00339 setCompletedItems( completions, true );
00340
00341 cursorAtEnd();
00342 return;
00343 }
00344
00345 KGlobalSettings::Completion mode = completionMode();
00346
00347 switch ( mode ) {
00348 case KGlobalSettings::CompletionPopupAuto:
00349 {
00350 if ( m_searchString.isEmpty() )
00351 break;
00352 }
00353
00354 case KGlobalSettings::CompletionPopup:
00355 {
00356
00357 QStringList items = s_completion->allMatches( m_searchString );
00358 items += s_completion->allMatches( "\"" + m_searchString );
00359 uint beforeDollarCompletionCount = items.count();
00360
00361 if ( m_searchString.find( ' ' ) == -1 )
00362 items += s_completion->allMatches( "$$" + m_searchString );
00363
00364 if ( items.isEmpty() ) {
00365 setCompletedItems( items, false );
00366 } else {
00367 if ( items.count() > beforeDollarCompletionCount ) {
00368
00369 for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it ) {
00370 int pos = (*it).find( '$', 2 );
00371 if ( pos < 0 )
00372 continue;
00373 (*it) = (*it).mid( pos + 1 );
00374 }
00375 }
00376
00377 bool autoSuggest = (mode != KGlobalSettings::CompletionPopupAuto);
00378 setCompletedItems( items, autoSuggest );
00379
00380 if ( !autoSuggest ) {
00381 int index = items.first().find( m_searchString );
00382 QString newText = m_previousAddresses + items.first().mid( index );
00383 setUserSelection( false );
00384 setCompletedText( newText, true );
00385 }
00386 }
00387
00388 break;
00389 }
00390
00391 case KGlobalSettings::CompletionShell:
00392 {
00393 QString match = s_completion->makeCompletion( m_searchString );
00394 if ( !match.isNull() && match != m_searchString ) {
00395 setText( m_previousAddresses + match );
00396 cursorAtEnd();
00397 }
00398 break;
00399 }
00400
00401 case KGlobalSettings::CompletionMan:
00402 case KGlobalSettings::CompletionAuto:
00403 {
00404 if ( !m_searchString.isEmpty() ) {
00405 QString match = s_completion->makeCompletion( m_searchString );
00406 if ( !match.isNull() && match != m_searchString ) {
00407 QString adds = m_previousAddresses + match;
00408 setCompletedText( adds );
00409 }
00410 break;
00411 }
00412 }
00413
00414 case KGlobalSettings::CompletionNone:
00415 default:
00416 break;
00417 }
00418 }
00419
00420 void AddresseeLineEdit::slotPopupCompletion( const QString& completion )
00421 {
00422 setText( m_previousAddresses + completion );
00423 cursorAtEnd();
00424
00425 }
00426
00427 void AddresseeLineEdit::loadContacts()
00428 {
00429 s_completion->clear();
00430 s_addressesDirty = false;
00431
00432
00433 QApplication::setOverrideCursor( KCursor::waitCursor() );
00434
00435 KConfig config( "kpimcompletionorder" );
00436 config.setGroup( "CompletionWeights" );
00437
00438 KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
00439
00440
00441 QPtrList<KABC::Resource> resources( addressBook->resources() );
00442 for( QPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) {
00443 KABC::Resource* resource = *resit;
00444 KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
00445 if ( resabc ) {
00446 const QMap<QString, QString> uidToResourceMap = resabc->uidToResourceMap();
00447 KABC::Resource::Iterator it;
00448 for ( it = resource->begin(); it != resource->end(); ++it ) {
00449 QString uid = (*it).uid();
00450 QMap<QString, QString>::const_iterator wit = uidToResourceMap.find( uid );
00451 int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
00452
00453 addContact( *it, weight );
00454 }
00455 } else {
00456 int weight = config.readNumEntry( resource->identifier(), 60 );
00457 KABC::Resource::Iterator it;
00458 for ( it = resource->begin(); it != resource->end(); ++it )
00459 addContact( *it, weight );
00460 }
00461 }
00462
00463 int weight = config.readNumEntry( "DistributionLists", 60 );
00464 KABC::DistributionListManager manager( addressBook );
00465 manager.load();
00466 const QStringList distLists = manager.listNames();
00467 QStringList::const_iterator listIt;
00468 for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
00469 s_completion->addItem( (*listIt).simplifyWhiteSpace(), weight );
00470 }
00471
00472 QApplication::restoreOverrideCursor();
00473
00474 if ( !m_addressBookConnected ) {
00475 connect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( loadContacts() ) );
00476 m_addressBookConnected = true;
00477 }
00478 }
00479
00480 void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight )
00481 {
00482
00483 const QStringList emails = addr.emails();
00484 QStringList::ConstIterator it;
00485 for ( it = emails.begin(); it != emails.end(); ++it ) {
00486 int len = (*it).length();
00487 if ( len == 0 ) continue;
00488 if( '\0' == (*it)[len-1] )
00489 --len;
00490 const QString tmp = (*it).left( len );
00491 const QString fullEmail = addr.fullEmail( tmp );
00492
00493 addCompletionItem( fullEmail.simplifyWhiteSpace(), weight );
00494
00495
00496
00497 QString name( addr.realName().simplifyWhiteSpace() );
00498 if( name.endsWith("\"") )
00499 name.truncate( name.length()-1 );
00500 if( name.startsWith("\"") )
00501 name = name.mid( 1 );
00502
00503
00504 if ( !name.isEmpty() )
00505 addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight );
00506
00507 bool bDone = false;
00508 int i = 1;
00509 do{
00510 i = name.findRev(' ');
00511 if( 1 < i ){
00512 QString sLastName( name.mid(i+1) );
00513 if( ! sLastName.isEmpty() &&
00514 2 <= sLastName.length() &&
00515 ! sLastName.endsWith(".") ){
00516 name.truncate( i );
00517 if( !name.isEmpty() ){
00518 sLastName.prepend( "\"" );
00519 sLastName.append( ", " + name + "\" <" );
00520 }
00521 QString sExtraEntry( sLastName );
00522 sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
00523 sExtraEntry.append( ">" );
00524
00525 addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight );
00526 bDone = true;
00527 }
00528 }
00529 if( !bDone ){
00530 name.truncate( i );
00531 if( name.endsWith("\"") )
00532 name.truncate( name.length()-1 );
00533 }
00534 }while( 1 < i && !bDone );
00535 }
00536 }
00537
00538 void AddresseeLineEdit::addCompletionItem( const QString& string, int weight )
00539 {
00540
00541
00542 CompletionItemsMap::iterator it = s_completionItemMap->begin();
00543 if ( it != s_completionItemMap->end() ) {
00544 weight = QMAX( *it, weight );
00545 *it = weight;
00546 } else {
00547 s_completionItemMap->insert( string, weight );
00548 }
00549
00550 s_completion->addItem( string, weight );
00551 }
00552
00553 void AddresseeLineEdit::slotStartLDAPLookup()
00554 {
00555 if ( !s_LDAPSearch->isAvailable() || s_LDAPLineEdit != this )
00556 return;
00557
00558 startLoadingLDAPEntries();
00559 }
00560
00561 void AddresseeLineEdit::stopLDAPLookup()
00562 {
00563 s_LDAPSearch->cancelSearch();
00564 s_LDAPLineEdit = NULL;
00565 s_completionItemMap->clear();
00566 }
00567
00568 void AddresseeLineEdit::startLoadingLDAPEntries()
00569 {
00570 QString s( *s_LDAPText );
00571
00572 QString prevAddr;
00573 int n = s.findRev( ',' );
00574 if ( n >= 0 ) {
00575 prevAddr = s.left( n + 1 ) + ' ';
00576 s = s.mid( n + 1, 255 ).stripWhiteSpace();
00577 }
00578
00579 if ( s.isEmpty() )
00580 return;
00581
00582 loadContacts();
00583 s_LDAPSearch->startSearch( s );
00584 }
00585
00586 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
00587 {
00588 if ( s_LDAPLineEdit != this )
00589 return;
00590
00591 for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
00592 KABC::Addressee addr;
00593 addr.setNameFromString( (*it).name );
00594 addr.setEmails( (*it).email );
00595 addContact( addr, (*it).completionWeight );
00596 }
00597
00598 if ( hasFocus() || completionBox()->hasFocus() )
00599 if ( completionMode() != KGlobalSettings::CompletionNone )
00600 doCompletion( false );
00601 }
00602
00603 void AddresseeLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest )
00604 {
00605 KCompletionBox* completionBox = this->completionBox();
00606
00607 if ( !items.isEmpty() &&
00608 !(items.count() == 1 && m_searchString == items.first()) )
00609 {
00610 if ( completionBox->isVisible() )
00611 {
00612 bool wasSelected = completionBox->isSelected( completionBox->currentItem() );
00613 const QString currentSelection = completionBox->currentText();
00614 completionBox->setItems( items );
00615 QListBoxItem* item = completionBox->findItem( currentSelection, Qt::ExactMatch );
00616 if ( item )
00617 {
00618 completionBox->blockSignals( true );
00619 completionBox->setCurrentItem( item );
00620 completionBox->setSelected( item, wasSelected );
00621 completionBox->blockSignals( false );
00622 }
00623 }
00624 else
00625 {
00626 if ( !m_searchString.isEmpty() )
00627 completionBox->setCancelledText( m_searchString );
00628 completionBox->setItems( items );
00629 completionBox->popup();
00630 }
00631
00632 if ( autoSuggest )
00633 {
00634 int index = items.first().find( m_searchString );
00635 QString newText = items.first().mid( index );
00636 setUserSelection(false);
00637 setCompletedText(newText,true);
00638 }
00639 }
00640 else
00641 {
00642 if ( completionBox && completionBox->isVisible() )
00643 completionBox->hide();
00644 }
00645 }
00646
00647 QPopupMenu* AddresseeLineEdit::createPopupMenu()
00648 {
00649 QPopupMenu *menu = KLineEdit::createPopupMenu();
00650 if ( !menu )
00651 return 0;
00652
00653 if ( m_useCompletion )
00654 menu->insertItem( i18n( "Edit Completion Order..." ),
00655 this, SLOT( slotEditCompletionOrder() ) );
00656 return menu;
00657 }
00658
00659 void AddresseeLineEdit::slotEditCompletionOrder()
00660 {
00661 init();
00662 CompletionOrderEditor editor( s_LDAPSearch, this );
00663 editor.exec();
00664 }
00665
00666 KConfig* AddresseeLineEdit::config()
00667 {
00668 if ( !s_config )
00669 configDeleter.setObject( s_config, new KConfig( locateLocal( "config",
00670 "kabldaprc" ) ) );
00671
00672 return s_config;
00673 }
00674
00675 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
00676 {
00677 if ( m_useCompletion )
00678 s_addressesDirty = true;
00679 }
00680
00681 void KPIM::AddresseeLineEdit::slotUserCancelled( const QString& cancelText )
00682 {
00683 if ( s_LDAPSearch && s_LDAPLineEdit == this )
00684 stopLDAPLookup();
00685 userCancelled( cancelText );
00686 }
00687
00688 void KPIM::AddresseeLineEdit::slotCompletion()
00689 {
00690
00691 m_searchString = text();
00692 int n = m_searchString.findRev(',');
00693 if ( n >= 0 ) {
00694 ++n;
00695
00696 int len = m_searchString.length();
00697
00698
00699 while ( n < len && m_searchString[ n ].isSpace() )
00700 ++n;
00701
00702 m_previousAddresses = m_searchString.left( n );
00703 m_searchString = m_searchString.mid( n ).stripWhiteSpace();
00704 }
00705 else
00706 {
00707 m_previousAddresses = QString::null;
00708 }
00709 doCompletion( false );
00710 }
00711
00712 #include "addresseelineedit.moc"