libkdepim Library API Documentation

addresseelineedit.cpp

00001 /* 00002 This file is part of libkdepim. 00003 Copyright (c) 2002 Helge Deller <deller@gmx.de> 00004 2002 Lubos Lunak <llunak@suse.cz> 00005 2001,2003 Carsten Pfeiffer <pfeiffer@kde.org> 00006 2001 Waldo Bastian <bastian@kde.org> 00007 2004 Daniel Molkentin <danimo@klaralvdalens-datakonsult.se> 00008 2004 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se> 00009 00010 This library is free software; you can redistribute it and/or 00011 modify it under the terms of the GNU Library General Public 00012 License as published by the Free Software Foundation; either 00013 version 2 of the License, or (at your option) any later version. 00014 00015 This library is distributed in the hope that it will be useful, 00016 but WITHOUT ANY WARRANTY; without even the implied warranty of 00017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00018 Library General Public License for more details. 00019 00020 You should have received a copy of the GNU Library General Public License 00021 along with this library; see the file COPYING.LIB. If not, write to 00022 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00023 Boston, MA 02111-1307, USA. 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 // needs to be unique, but the actual name doesn't matter much 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 00090 init(); 00091 00092 if ( m_useCompletion ) 00093 s_addressesDirty = true; 00094 } 00095 00096 00097 void AddresseeLineEdit::init() 00098 { 00099 if ( !s_completion ) { 00100 completionDeleter.setObject( s_completion, new KCompletion() ); 00101 s_completion->setOrder( KCompletion::Weighted ); 00102 s_completion->setIgnoreCase( true ); 00103 } 00104 00105 // connect( s_completion, SIGNAL( match( const QString& ) ), 00106 // this, SLOT( slotMatched( const QString& ) ) ); 00107 00108 if ( m_useCompletion && !s_LDAPTimer ) { 00109 ldapTimerDeleter.setObject( s_LDAPTimer, new QTimer ); 00110 ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch ); 00111 ldapTextDeleter.setObject( s_LDAPText, new QString ); 00112 connect( s_LDAPTimer, SIGNAL( timeout() ), SLOT( slotStartLDAPLookup() ) ); 00113 connect( s_LDAPSearch, SIGNAL( searchData( const KPIM::LdapResultList& ) ), 00114 SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) ); 00115 } 00116 00117 if ( m_useCompletion && !m_completionInitialized ) { 00118 setCompletionObject( s_completion, false ); 00119 connect( this, SIGNAL( completion( const QString& ) ), 00120 this, SLOT( slotCompletion() ) ); 00121 00122 KCompletionBox *box = completionBox(); 00123 connect( box, SIGNAL( highlighted( const QString& ) ), 00124 this, SLOT( slotPopupCompletion( const QString& ) ) ); 00125 connect( box, SIGNAL( userCancelled( const QString& ) ), 00126 SLOT( userCancelled( const QString& ) ) ); 00127 00128 // The emitter is always called KPIM::IMAPCompletionOrder by contract 00129 if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()", 00130 "slotIMAPCompletionOrderChanged()", false ) ) 00131 kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl; 00132 00133 m_completionInitialized = true; 00134 } 00135 } 00136 00137 AddresseeLineEdit::~AddresseeLineEdit() 00138 { 00139 } 00140 00141 void AddresseeLineEdit::setFont( const QFont& font ) 00142 { 00143 KLineEdit::setFont( font ); 00144 if ( m_useCompletion ) 00145 completionBox()->setFont( font ); 00146 } 00147 00148 void AddresseeLineEdit::keyPressEvent( QKeyEvent *e ) 00149 { 00150 bool accept = false; 00151 00152 if ( KStdAccel::shortcut( KStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) { 00153 doCompletion( true ); 00154 accept = true; 00155 } else if ( KStdAccel::shortcut( KStdAccel::TextCompletion ).contains( KKey( e ) ) ) { 00156 int len = text().length(); 00157 00158 if ( len == cursorPosition() ) { // at End? 00159 doCompletion( true ); 00160 accept = true; 00161 } 00162 } 00163 00164 if ( !accept ) 00165 KLineEdit::keyPressEvent( e ); 00166 00167 if ( e->isAccepted() ) { 00168 if ( m_useCompletion && s_LDAPTimer != NULL ) { 00169 if ( *s_LDAPText != text() ) 00170 stopLDAPLookup(); 00171 00172 *s_LDAPText = text(); 00173 s_LDAPLineEdit = this; 00174 s_LDAPTimer->start( 500, true ); 00175 } 00176 } 00177 } 00178 00179 void AddresseeLineEdit::insert( const QString &t ) 00180 { 00181 if ( !m_smartPaste ) { 00182 KLineEdit::insert( t ); 00183 return; 00184 } 00185 00186 //kdDebug(5300) << " AddresseeLineEdit::insert( \"" << t << "\" )" << endl; 00187 00188 QString newText = t.stripWhiteSpace(); 00189 if ( newText.isEmpty() ) 00190 return; 00191 00192 // remove newlines in the to-be-pasted string as well as an eventual 00193 // mailto: protocol 00194 newText.replace( QRegExp("\r?\n"), ", " ); 00195 00196 if ( newText.startsWith("mailto:") ) { 00197 KURL url( newText ); 00198 newText = url.path(); 00199 } 00200 else if ( newText.find(" at ") != -1 ) { 00201 // Anti-spam stuff 00202 newText.replace( " at ", "@" ); 00203 newText.replace( " dot ", "." ); 00204 } 00205 else if ( newText.find("(at)") != -1 ) { 00206 newText.replace( QRegExp("\\s*\\(at\\)\\s*"), "@" ); 00207 } 00208 00209 QString contents = text(); 00210 int start_sel = 0; 00211 int end_sel = 0; 00212 int pos = cursorPosition(); 00213 if ( getSelection( &start_sel, &end_sel ) ) { 00214 // Cut away the selection. 00215 if ( pos > end_sel ) 00216 pos -= (end_sel - start_sel); 00217 else if ( pos > start_sel ) 00218 pos = start_sel; 00219 contents = contents.left( start_sel ) + contents.right( end_sel + 1 ); 00220 } 00221 00222 int eot = contents.length(); 00223 while ((eot > 0) && contents[ eot - 1 ].isSpace() ) eot--; 00224 if ( eot == 0 ) 00225 contents = QString::null; 00226 else if ( pos >= eot ) { 00227 if ( contents[ eot - 1 ] == ',' ) 00228 eot--; 00229 contents.truncate( eot ); 00230 contents += ", "; 00231 pos = eot + 2; 00232 } 00233 00234 contents = contents.left( pos ) + newText + contents.mid( pos ); 00235 setText( contents ); 00236 setCursorPosition( pos + newText.length() ); 00237 } 00238 00239 void AddresseeLineEdit::paste() 00240 { 00241 if ( m_useCompletion ) 00242 m_smartPaste = true; 00243 00244 KLineEdit::paste(); 00245 m_smartPaste = false; 00246 } 00247 00248 void AddresseeLineEdit::mouseReleaseEvent( QMouseEvent *e ) 00249 { 00250 // reimplemented from QLineEdit::mouseReleaseEvent() 00251 if ( m_useCompletion 00252 && QApplication::clipboard()->supportsSelection() 00253 && !isReadOnly() 00254 && e->button() == MidButton ) { 00255 m_smartPaste = true; 00256 } 00257 00258 KLineEdit::mouseReleaseEvent( e ); 00259 m_smartPaste = false; 00260 } 00261 00262 void AddresseeLineEdit::dropEvent( QDropEvent *e ) 00263 { 00264 KURL::List uriList; 00265 if ( !isReadOnly() 00266 && KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) { 00267 QString contents = text(); 00268 // remove trailing white space and comma 00269 int eot = contents.length(); 00270 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) 00271 eot--; 00272 if ( eot == 0 ) 00273 contents = QString::null; 00274 else if ( contents[ eot - 1 ] == ',' ) { 00275 eot--; 00276 contents.truncate( eot ); 00277 } 00278 // append the mailto URLs 00279 for ( KURL::List::Iterator it = uriList.begin(); 00280 it != uriList.end(); ++it ) { 00281 if ( !contents.isEmpty() ) 00282 contents.append( ", " ); 00283 KURL u( *it ); 00284 contents.append( (*it).path() ); 00285 } 00286 setText( contents ); 00287 setEdited( true ); 00288 } 00289 else { 00290 if ( m_useCompletion ) 00291 m_smartPaste = true; 00292 QLineEdit::dropEvent( e ); 00293 m_smartPaste = false; 00294 } 00295 } 00296 00297 void AddresseeLineEdit::cursorAtEnd() 00298 { 00299 setCursorPosition( text().length() ); 00300 } 00301 00302 void AddresseeLineEdit::enableCompletion( bool enable ) 00303 { 00304 m_useCompletion = enable; 00305 } 00306 00307 void AddresseeLineEdit::doCompletion( bool ctrlT ) 00308 { 00309 if ( !m_useCompletion ) 00310 return; 00311 00312 QString prevAddr; 00313 00314 QString s( text() ); 00315 int n = s.findRev(','); 00316 00317 if ( n >= 0 ) { 00318 n++; // Go past the "," 00319 00320 int len = s.length(); 00321 00322 // Increment past any whitespace... 00323 while ( n < len && s[ n ].isSpace() ) 00324 n++; 00325 00326 prevAddr = s.left( n ); 00327 s = s.mid( n, 255 ).stripWhiteSpace(); 00328 } 00329 00330 if ( s_addressesDirty ) 00331 loadContacts(); // read from local address book 00332 00333 // cursor at end of string - or Ctrl+T pressed for substring completion? 00334 if ( ctrlT ) { 00335 const QStringList completions = s_completion->substringCompletion( s ); 00336 00337 if ( completions.count() > 1 ) 00338 m_previousAddresses = prevAddr; 00339 else if ( completions.count() == 1 ) 00340 setText( prevAddr + completions.first() ); 00341 00342 setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found 00343 00344 cursorAtEnd(); 00345 return; 00346 } 00347 00348 KGlobalSettings::Completion mode = completionMode(); 00349 00350 switch ( mode ) { 00351 case KGlobalSettings::CompletionPopupAuto: 00352 { 00353 if ( s.isEmpty() ) 00354 break; 00355 } 00356 00357 case KGlobalSettings::CompletionPopup: 00358 { 00359 m_previousAddresses = prevAddr; 00360 QStringList items = s_completion->allMatches( s ); 00361 items += s_completion->allMatches( "\"" + s ); 00362 uint beforeDollarCompletionCount = items.count(); 00363 00364 if ( s.find( ' ' ) == -1 ) // one word, possibly given name 00365 items += s_completion->allMatches( "$$" + s ); 00366 00367 if ( items.isEmpty() ) { 00368 setCompletedItems( items, false ); 00369 } else { 00370 if ( items.count() > beforeDollarCompletionCount ) { 00371 // remove the '$$whatever$' part 00372 for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it ) { 00373 int pos = (*it).find( '$', 2 ); 00374 if ( pos < 0 ) // ??? 00375 continue; 00376 (*it) = (*it).mid( pos + 1 ); 00377 } 00378 } 00379 00380 bool autoSuggest = (mode != KGlobalSettings::CompletionPopupAuto); 00381 setCompletedItems( items, autoSuggest ); 00382 00383 if ( !autoSuggest ) { 00384 int index = items.first().find( s ); 00385 QString newText = prevAddr + items.first().mid( index ); 00386 setUserSelection( false ); 00387 setCompletedText( newText, true ); 00388 } 00389 } 00390 00391 break; 00392 } 00393 00394 case KGlobalSettings::CompletionShell: 00395 { 00396 QString match = s_completion->makeCompletion( s ); 00397 if ( !match.isNull() && match != s ) { 00398 setText( prevAddr + match ); 00399 cursorAtEnd(); 00400 } 00401 break; 00402 } 00403 00404 case KGlobalSettings::CompletionMan: // Short-Auto in fact 00405 case KGlobalSettings::CompletionAuto: 00406 { 00407 if ( !s.isEmpty() ) { 00408 QString match = s_completion->makeCompletion( s ); 00409 if ( !match.isNull() && match != s ) { 00410 QString adds = prevAddr + match; 00411 setCompletedText( adds ); 00412 } 00413 break; 00414 } 00415 } 00416 00417 case KGlobalSettings::CompletionNone: 00418 default: // fall through 00419 break; 00420 } 00421 } 00422 00423 void AddresseeLineEdit::slotPopupCompletion( const QString& completion ) 00424 { 00425 setText( m_previousAddresses + completion ); 00426 cursorAtEnd(); 00427 // slotMatched( m_previousAddresses + completion ); 00428 } 00429 00430 void AddresseeLineEdit::loadContacts() 00431 { 00432 s_completion->clear(); 00433 s_addressesDirty = false; 00434 //m_contactMap.clear(); 00435 00436 QApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while 00437 00438 KConfig config( "kpimcompletionorder" ); // The weights for non-imap kabc resources is there. 00439 config.setGroup( "CompletionWeights" ); 00440 00441 KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true ); 00442 // Can't just use the addressbook's iterator, we need to know which subresource 00443 // is behind which contact. 00444 QPtrList<KABC::Resource> resources( addressBook->resources() ); 00445 for( QPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) { 00446 KABC::Resource* resource = *resit; 00447 KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource ); 00448 if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource 00449 const QMap<QString, QString> uidToResourceMap = resabc->uidToResourceMap(); 00450 KABC::Resource::Iterator it; 00451 for ( it = resource->begin(); it != resource->end(); ++it ) { 00452 QString uid = (*it).uid(); 00453 QMap<QString, QString>::const_iterator wit = uidToResourceMap.find( uid ); 00454 int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80; 00455 //kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl; 00456 addContact( *it, weight ); 00457 } 00458 } else { // KABC non-imap resource 00459 int weight = config.readNumEntry( resource->identifier(), 60 ); 00460 KABC::Resource::Iterator it; 00461 for ( it = resource->begin(); it != resource->end(); ++it ) 00462 addContact( *it, weight ); 00463 } 00464 } 00465 00466 int weight = config.readNumEntry( "DistributionLists", 60 ); 00467 KABC::DistributionListManager manager( addressBook ); 00468 manager.load(); 00469 const QStringList distLists = manager.listNames(); 00470 QStringList::const_iterator listIt; 00471 for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) { 00472 s_completion->addItem( (*listIt).simplifyWhiteSpace(), weight ); 00473 } 00474 00475 QApplication::restoreOverrideCursor(); 00476 00477 disconnect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), 00478 this, SLOT( loadContacts() ) ); 00479 00480 connect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( loadContacts() ) ); 00481 } 00482 00483 void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight ) 00484 { 00485 //m_contactMap.insert( addr.realName(), addr ); 00486 const QStringList emails = addr.emails(); 00487 QStringList::ConstIterator it; 00488 for ( it = emails.begin(); it != emails.end(); ++it ) { 00489 int len = (*it).length(); 00490 if( '\0' == (*it)[len-1] ) 00491 --len; 00492 const QString tmp = (*it).left( len ); 00493 //kdDebug(5300) << " AddresseeLineEdit::addContact() \"" << tmp << "\"" << endl; 00494 QString fullEmail = addr.fullEmail( tmp ); 00495 //kdDebug(5300) << " \"" << fullEmail << "\"" << endl; 00496 s_completion->addItem( fullEmail.simplifyWhiteSpace(), weight ); 00497 } 00498 } 00499 00500 void AddresseeLineEdit::slotStartLDAPLookup() 00501 { 00502 if ( !s_LDAPSearch->isAvailable() || s_LDAPLineEdit != this ) 00503 return; 00504 00505 startLoadingLDAPEntries(); 00506 } 00507 00508 void AddresseeLineEdit::stopLDAPLookup() 00509 { 00510 s_LDAPSearch->cancelSearch(); 00511 s_LDAPLineEdit = NULL; 00512 } 00513 00514 void AddresseeLineEdit::startLoadingLDAPEntries() 00515 { 00516 QString s( *s_LDAPText ); 00517 // TODO cache last? 00518 QString prevAddr; 00519 int n = s.findRev( ',' ); 00520 if ( n >= 0 ) { 00521 prevAddr = s.left( n + 1 ) + ' '; 00522 s = s.mid( n + 1, 255 ).stripWhiteSpace(); 00523 } 00524 00525 if ( s.isEmpty() ) 00526 return; 00527 00528 loadContacts(); // TODO reuse these? 00529 s_LDAPSearch->startSearch( s ); 00530 } 00531 00532 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs ) 00533 { 00534 if ( s_LDAPLineEdit != this ) 00535 return; 00536 00537 for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) { 00538 KABC::Addressee addr; 00539 addr.setNameFromString( (*it).name ); 00540 addr.setEmails( (*it).email ); 00541 addContact( addr, (*it).completionWeight ); 00542 } 00543 00544 if ( hasFocus() || completionBox()->hasFocus() ) 00545 if ( completionMode() != KGlobalSettings::CompletionNone ) 00546 doCompletion( false ); 00547 } 00548 00549 void AddresseeLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest ) 00550 { 00551 QString txt = text(); 00552 00553 if ( !items.isEmpty() && 00554 !(items.count() == 1 && txt == items.first()) ) 00555 { 00556 if ( !txt.isEmpty() ) 00557 completionBox()->setCancelledText( txt ); 00558 00559 completionBox()->setItems( items ); 00560 completionBox()->popup(); 00561 00562 if ( autoSuggest ) 00563 { 00564 int index = items.first().find( txt ); 00565 QString newText = items.first().mid( index ); 00566 setUserSelection(false); 00567 setCompletedText(newText,true); 00568 } 00569 } 00570 else 00571 { 00572 if ( completionBox() && completionBox()->isVisible() ) 00573 completionBox()->hide(); 00574 } 00575 } 00576 00577 QPopupMenu* AddresseeLineEdit::createPopupMenu() 00578 { 00579 QPopupMenu *menu = KLineEdit::createPopupMenu(); 00580 if ( !menu ) 00581 return 0; 00582 00583 if ( m_useCompletion ) 00584 menu->insertItem( i18n( "Edit Completion Order..." ), 00585 this, SLOT( slotEditCompletionOrder() ) ); 00586 return menu; 00587 } 00588 00589 void AddresseeLineEdit::slotEditCompletionOrder() 00590 { 00591 init(); // for s_LDAPSearch 00592 CompletionOrderEditor editor( s_LDAPSearch, this ); 00593 editor.exec(); 00594 } 00595 00596 KConfig* AddresseeLineEdit::config() 00597 { 00598 if ( !s_config ) 00599 configDeleter.setObject( s_config, new KConfig( locateLocal( "config", 00600 "kabldaprc" ) ) ); 00601 00602 return s_config; 00603 } 00604 00605 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged() 00606 { 00607 if ( m_useCompletion ) 00608 s_addressesDirty = true; 00609 } 00610 00611 #include "addresseelineedit.moc"
KDE Logo
This file is part of the documentation for libkdepim Library Version 3.3.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 21 19:46:29 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003