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