addresslineedit.cpp
00001 /* 00002 This file is part of libtdeabc. 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 00008 This library is free software; you can redistribute it and/or 00009 modify it under the terms of the GNU Library General Public 00010 License as published by the Free Software Foundation; either 00011 version 2 of the License, or (at your option) any later version. 00012 00013 This library is distributed in the hope that it will be useful, 00014 but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 Library General Public License for more details. 00017 00018 You should have received a copy of the GNU Library General Public License 00019 along with this library; see the file COPYING.LIB. If not, write to 00020 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00021 Boston, MA 02110-1301, USA. 00022 */ 00023 00024 // $Id$ 00025 00026 #include "addresslineedit.h" 00027 00028 #include <tqapplication.h> 00029 #include <tqobject.h> 00030 #include <tqptrlist.h> 00031 #include <tqregexp.h> 00032 #include <tqevent.h> 00033 #include <tqdragobject.h> 00034 00035 #include <tdecompletionbox.h> 00036 #include <tdeconfig.h> 00037 #include <kcursor.h> 00038 #include <kstandarddirs.h> 00039 #include <kstaticdeleter.h> 00040 #include <tdestdaccel.h> 00041 #include <kurldrag.h> 00042 00043 #include <tdeabc/stdaddressbook.h> 00044 #include <tdeabc/distributionlist.h> 00045 #include "ldapclient.h" 00046 00047 #include <kdebug.h> 00048 00049 //============================================================================= 00050 // 00051 // Class AddressLineEdit 00052 // 00053 //============================================================================= 00054 00055 00056 using namespace TDEABC; 00057 00058 TDECompletion * AddressLineEdit::s_completion = 0L; 00059 bool AddressLineEdit::s_addressesDirty = false; 00060 TQTimer* AddressLineEdit::s_LDAPTimer = 0L; 00061 LdapSearch* AddressLineEdit::s_LDAPSearch = 0L; 00062 TQString* AddressLineEdit::s_LDAPText = 0L; 00063 AddressLineEdit* AddressLineEdit::s_LDAPLineEdit = 0L; 00064 TDEConfig *AddressLineEdit::s_config = 0L; 00065 00066 static KStaticDeleter<TDECompletion> completionDeleter; 00067 static KStaticDeleter<TQTimer> ldapTimerDeleter; 00068 static KStaticDeleter<LdapSearch> ldapSearchDeleter; 00069 static KStaticDeleter<TQString> ldapTextDeleter; 00070 static KStaticDeleter<TDEConfig> configDeleter; 00071 00072 AddressLineEdit::AddressLineEdit(TQWidget* parent, 00073 bool useCompletion, 00074 const char *name) 00075 : KLineEdit(parent,name) 00076 { 00077 m_useCompletion = useCompletion; 00078 m_completionInitialized = false; 00079 m_smartPaste = false; 00080 00081 init(); 00082 00083 // Whenever a new AddressLineEdit is created (== a new composer is created), 00084 // we set a dirty flag to reload the addresses upon the first completion. 00085 // The address completions are shared between all AddressLineEdits. 00086 // Is there a signal that tells us about addressbook updates? 00087 if (m_useCompletion) 00088 s_addressesDirty = true; 00089 } 00090 00091 00092 //----------------------------------------------------------------------------- 00093 void AddressLineEdit::init() 00094 { 00095 if ( !s_completion ) { 00096 completionDeleter.setObject( s_completion, new TDECompletion() ); 00097 s_completion->setOrder( TDECompletion::Sorted ); 00098 s_completion->setIgnoreCase( true ); 00099 } 00100 00101 if( m_useCompletion ) { 00102 if( !s_LDAPTimer ) { 00103 ldapTimerDeleter.setObject( s_LDAPTimer, new TQTimer ); 00104 ldapSearchDeleter.setObject( s_LDAPSearch, new LdapSearch ); 00105 ldapTextDeleter.setObject( s_LDAPText, new TQString ); 00106 } 00107 connect( s_LDAPTimer, TQT_SIGNAL( timeout()), TQT_SLOT( slotStartLDAPLookup())); 00108 connect( s_LDAPSearch, TQT_SIGNAL( searchData( const TQStringList& )), 00109 TQT_SLOT( slotLDAPSearchData( const TQStringList& ))); 00110 } 00111 00112 if ( m_useCompletion && !m_completionInitialized ) 00113 { 00114 setCompletionObject( s_completion, false ); // we handle it ourself 00115 connect( this, TQT_SIGNAL( completion(const TQString&)), 00116 this, TQT_SLOT(slotCompletion() )); 00117 00118 TDECompletionBox *box = completionBox(); 00119 connect( box, TQT_SIGNAL( highlighted( const TQString& )), 00120 this, TQT_SLOT( slotPopupCompletion( const TQString& ) )); 00121 connect( box, TQT_SIGNAL( userCancelled( const TQString& )), 00122 TQT_SLOT( userCancelled( const TQString& ))); 00123 00124 m_completionInitialized = true; // don't connect muliple times. That's 00125 // ugly, tho, better have completionBox() 00126 // virtual in KDE 4 00127 // Why? This is only called once. Why should this be called more 00128 // than once? And why was this protected? 00129 } 00130 } 00131 00132 //----------------------------------------------------------------------------- 00133 AddressLineEdit::~AddressLineEdit() 00134 { 00135 } 00136 00137 //----------------------------------------------------------------------------- 00138 00139 TDEConfig* AddressLineEdit::config() 00140 { 00141 if ( !s_config ) 00142 configDeleter.setObject( s_config, new TDEConfig( "kabldaprc", false, false ) ); // Open read-write, no kdeglobals 00143 00144 return s_config; 00145 } 00146 00147 void AddressLineEdit::setFont( const TQFont& font ) 00148 { 00149 KLineEdit::setFont( font ); 00150 if ( m_useCompletion ) 00151 completionBox()->setFont( font ); 00152 } 00153 00154 //----------------------------------------------------------------------------- 00155 void AddressLineEdit::keyPressEvent(TQKeyEvent *e) 00156 { 00157 bool accept = false; 00158 00159 if (TDEStdAccel::shortcut(TDEStdAccel::SubstringCompletion).contains(KKey(e))) 00160 { 00161 doCompletion(true); 00162 accept = true; 00163 } 00164 else if (TDEStdAccel::shortcut(TDEStdAccel::TextCompletion).contains(KKey(e))) 00165 { 00166 int len = text().length(); 00167 00168 if (len == cursorPosition()) // at End? 00169 { 00170 doCompletion(true); 00171 accept = true; 00172 } 00173 } 00174 00175 if( !accept ) 00176 KLineEdit::keyPressEvent( e ); 00177 00178 if( e->isAccepted()) 00179 { 00180 if( m_useCompletion && s_LDAPTimer != NULL ) 00181 { 00182 if( *s_LDAPText != text()) 00183 stopLDAPLookup(); 00184 *s_LDAPText = text(); 00185 s_LDAPLineEdit = this; 00186 s_LDAPTimer->start( 500, true ); 00187 } 00188 } 00189 } 00190 00191 void AddressLineEdit::mouseReleaseEvent( TQMouseEvent * e ) 00192 { 00193 if (m_useCompletion && (e->button() == Qt::MidButton)) 00194 { 00195 m_smartPaste = true; 00196 KLineEdit::mouseReleaseEvent(e); 00197 m_smartPaste = false; 00198 return; 00199 } 00200 KLineEdit::mouseReleaseEvent(e); 00201 } 00202 00203 void AddressLineEdit::insert(const TQString &t) 00204 { 00205 if (!m_smartPaste) 00206 { 00207 KLineEdit::insert(t); 00208 return; 00209 } 00210 TQString newText = t.stripWhiteSpace(); 00211 if (newText.isEmpty()) 00212 return; 00213 00214 // remove newlines in the to-be-pasted string as well as an eventual 00215 // mailto: protocol 00216 newText.replace( TQRegExp("\r?\n"), ", " ); 00217 if ( newText.startsWith( "mailto:" ) ) 00218 { 00219 KURL u(newText); 00220 newText = u.path(); 00221 } 00222 else if (newText.find(" at ") != -1) 00223 { 00224 // Anti-spam stuff 00225 newText.replace( " at ", "@" ); 00226 newText.replace( " dot ", "." ); 00227 } 00228 else if (newText.find("(at)") != -1) 00229 { 00230 newText.replace( TQRegExp("\\s*\\(at\\)\\s*"), "@" ); 00231 } 00232 00233 TQString contents = text(); 00234 int start_sel = 0; 00235 int end_sel = 0; 00236 int pos = cursorPosition(); 00237 if (getSelection(&start_sel, &end_sel)) 00238 { 00239 // Cut away the selection. 00240 if (pos > end_sel) 00241 pos -= (end_sel - start_sel); 00242 else if (pos > start_sel) 00243 pos = start_sel; 00244 contents = contents.left(start_sel) + contents.right(end_sel+1); 00245 } 00246 00247 int eot = contents.length(); 00248 while ((eot > 0) && contents[eot-1].isSpace()) eot--; 00249 if (eot == 0) 00250 { 00251 contents = TQString::null; 00252 } 00253 else if (pos >= eot) 00254 { 00255 if (contents[eot-1] == ',') 00256 eot--; 00257 contents.truncate(eot); 00258 contents += ", "; 00259 pos = eot+2; 00260 } 00261 00262 contents = contents.left(pos)+newText+contents.mid(pos); 00263 setText(contents); 00264 setCursorPosition(pos+newText.length()); 00265 } 00266 00267 void AddressLineEdit::paste() 00268 { 00269 if (m_useCompletion) 00270 m_smartPaste = true; 00271 KLineEdit::paste(); 00272 m_smartPaste = false; 00273 } 00274 00275 //----------------------------------------------------------------------------- 00276 void AddressLineEdit::cursorAtEnd() 00277 { 00278 setCursorPosition( text().length() ); 00279 } 00280 00281 //----------------------------------------------------------------------------- 00282 void AddressLineEdit::enableCompletion(bool enable) 00283 { 00284 m_useCompletion = enable; 00285 } 00286 00287 //----------------------------------------------------------------------------- 00288 void AddressLineEdit::doCompletion(bool ctrlT) 00289 { 00290 if ( !m_useCompletion ) 00291 return; 00292 00293 TQString prevAddr; 00294 00295 TQString s(text()); 00296 int n = s.findRev(','); 00297 00298 if (n >= 0) 00299 { 00300 n++; // Go past the "," 00301 00302 int len = s.length(); 00303 00304 // Increment past any whitespace... 00305 while( n < len && s[n].isSpace() ) 00306 n++; 00307 00308 prevAddr = s.left(n); 00309 s = s.mid(n,255).stripWhiteSpace(); 00310 } 00311 00312 if ( s_addressesDirty ) 00313 loadAddresses(); 00314 00315 if ( ctrlT ) 00316 { 00317 TQStringList completions = s_completion->substringCompletion( s ); 00318 if (completions.count() > 1) { 00319 m_previousAddresses = prevAddr; 00320 setCompletedItems( completions ); 00321 } 00322 else if (completions.count() == 1) 00323 setText(prevAddr + completions.first()); 00324 00325 cursorAtEnd(); 00326 return; 00327 } 00328 00329 TDEGlobalSettings::Completion mode = completionMode(); 00330 00331 switch ( mode ) 00332 { 00333 case TDEGlobalSettings::CompletionPopupAuto: 00334 { 00335 if (s.isEmpty()) 00336 break; 00337 } 00338 case TDEGlobalSettings::CompletionPopup: 00339 { 00340 m_previousAddresses = prevAddr; 00341 TQStringList items = s_completion->allMatches( s ); 00342 items += s_completion->allMatches( "\"" + s ); 00343 items += s_completion->substringCompletion( '<' + s ); 00344 uint beforeDollarCompletionCount = items.count(); 00345 00346 if( s.find( ' ' ) == -1 ) // one word, possibly given name 00347 items += s_completion->allMatches( "$$" + s ); 00348 00349 if ( !items.isEmpty() ) 00350 { 00351 if ( items.count() > beforeDollarCompletionCount ) 00352 { 00353 // remove the '$$whatever$' part 00354 for( TQStringList::Iterator it = items.begin(); 00355 it != items.end(); 00356 ++it ) 00357 { 00358 int pos = (*it).find( '$', 2 ); 00359 if( pos < 0 ) // ??? 00360 continue; 00361 (*it)=(*it).mid( pos + 1 ); 00362 } 00363 } 00364 00365 items = removeMailDupes( items ); 00366 00367 // We do not want KLineEdit::setCompletedItems to perform text 00368 // completion (suggestion) since it does not know how to deal 00369 // with providing proper completions for different items on the 00370 // same line, e.g. comma-separated list of email addresses. 00371 bool autoSuggest = (mode != TDEGlobalSettings::CompletionPopupAuto); 00372 setCompletedItems( items, autoSuggest ); 00373 00374 if (!autoSuggest) 00375 { 00376 int index = items.first().find( s ); 00377 TQString newText = prevAddr + items.first().mid( index ); 00378 //kdDebug() << "OLD TEXT: " << text() << endl; 00379 //kdDebug() << "NEW TEXT: " << newText << endl; 00380 setUserSelection(false); 00381 setCompletedText(newText,true); 00382 } 00383 } 00384 00385 break; 00386 } 00387 00388 case TDEGlobalSettings::CompletionShell: 00389 { 00390 TQString match = s_completion->makeCompletion( s ); 00391 if ( !match.isNull() && match != s ) 00392 { 00393 setText( prevAddr + match ); 00394 cursorAtEnd(); 00395 } 00396 break; 00397 } 00398 00399 case TDEGlobalSettings::CompletionMan: // Short-Auto in fact 00400 case TDEGlobalSettings::CompletionAuto: 00401 { 00402 if (!s.isEmpty()) 00403 { 00404 TQString match = s_completion->makeCompletion( s ); 00405 if ( !match.isNull() && match != s ) 00406 { 00407 TQString adds = prevAddr + match; 00408 setCompletedText( adds ); 00409 } 00410 break; 00411 } 00412 } 00413 case TDEGlobalSettings::CompletionNone: 00414 default: // fall through 00415 break; 00416 } 00417 } 00418 00419 //----------------------------------------------------------------------------- 00420 void AddressLineEdit::slotPopupCompletion( const TQString& completion ) 00421 { 00422 setText( m_previousAddresses + completion ); 00423 cursorAtEnd(); 00424 } 00425 00426 //----------------------------------------------------------------------------- 00427 void AddressLineEdit::loadAddresses() 00428 { 00429 s_completion->clear(); 00430 s_addressesDirty = false; 00431 00432 TQStringList adrs = addresses(); 00433 for( TQStringList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it) 00434 addAddress( *it ); 00435 } 00436 00437 void AddressLineEdit::addAddress( const TQString& adr ) 00438 { 00439 s_completion->addItem( adr ); 00440 int pos = adr.find( '<' ); 00441 if( pos >= 0 ) 00442 { 00443 ++pos; 00444 int pos2 = adr.find( pos, '>' ); 00445 if( pos2 >= 0 ) 00446 s_completion->addItem( adr.mid( pos, pos2 - pos )); 00447 } 00448 } 00449 00450 void AddressLineEdit::slotStartLDAPLookup() 00451 { 00452 if( !s_LDAPSearch->isAvailable() || s_LDAPLineEdit != this ) 00453 return; 00454 startLoadingLDAPEntries(); 00455 } 00456 00457 void AddressLineEdit::stopLDAPLookup() 00458 { 00459 s_LDAPSearch->cancelSearch(); 00460 s_LDAPLineEdit = NULL; 00461 } 00462 00463 void AddressLineEdit::startLoadingLDAPEntries() 00464 { 00465 TQString s( *s_LDAPText ); 00466 // TODO cache last? 00467 TQString prevAddr; 00468 int n = s.findRev(','); 00469 if (n>= 0) 00470 { 00471 prevAddr = s.left(n+1) + ' '; 00472 s = s.mid(n+1,255).stripWhiteSpace(); 00473 } 00474 if( s.length() == 0 ) 00475 return; 00476 00477 loadAddresses(); // TODO reuse these? 00478 s_LDAPSearch->startSearch( s ); 00479 } 00480 00481 void AddressLineEdit::slotLDAPSearchData( const TQStringList& adrs ) 00482 { 00483 if( s_LDAPLineEdit != this ) 00484 return; 00485 for( TQStringList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) { 00486 TQString name(*it); 00487 int pos = name.find( " <" ); 00488 int pos_comma = name.find( ',' ); 00489 // put name in quotes, if we have a comma in the name 00490 if (pos>0 && pos_comma>0 && pos_comma<pos) { 00491 name.insert(pos, '\"'); 00492 name.prepend('\"'); 00493 } 00494 addAddress( name ); 00495 } 00496 00497 if( hasFocus() || completionBox()->hasFocus()) 00498 { 00499 if( completionMode() != TDEGlobalSettings::CompletionNone ) 00500 { 00501 doCompletion( false ); 00502 } 00503 } 00504 } 00505 00506 TQStringList AddressLineEdit::removeMailDupes( const TQStringList& adrs ) 00507 { 00508 TQStringList src = adrs; 00509 qHeapSort( src ); 00510 TQString last; 00511 for( TQStringList::Iterator it = src.begin(); it != src.end(); ) { 00512 if( *it == last ) 00513 { 00514 it = src.remove( it ); 00515 continue; // dupe 00516 } 00517 last = *it; 00518 ++it; 00519 } 00520 return src; 00521 } 00522 00523 //----------------------------------------------------------------------------- 00524 void AddressLineEdit::dropEvent(TQDropEvent *e) 00525 { 00526 KURL::List uriList; 00527 if(KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList )) 00528 { 00529 TQString ct = text(); 00530 KURL::List::Iterator it = uriList.begin(); 00531 for (; it != uriList.end(); ++it) 00532 { 00533 if (!ct.isEmpty()) ct.append(", "); 00534 KURL u(*it); 00535 if ((*it).protocol() == "mailto") 00536 ct.append( (*it).path() ); 00537 else 00538 ct.append( (*it).url() ); 00539 } 00540 setText(ct); 00541 setEdited( true ); 00542 } 00543 else { 00544 if (m_useCompletion) 00545 m_smartPaste = true; 00546 TQLineEdit::dropEvent(e); 00547 m_smartPaste = false; 00548 } 00549 } 00550 00551 00552 TQStringList AddressLineEdit::addresses() 00553 { 00554 TQApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while 00555 00556 TQStringList result; 00557 TQString space(" "); 00558 TQRegExp needQuotes("[^ 0-9A-Za-z\\x0080-\\xFFFF]"); 00559 TQString endQuote("\" "); 00560 TQString addr, email; 00561 00562 TDEABC::AddressBook *addressBook = TDEABC::StdAddressBook::self(); 00563 TDEABC::AddressBook::Iterator it; 00564 for( it = addressBook->begin(); it != addressBook->end(); ++it ) { 00565 TQStringList emails = (*it).emails(); 00566 00567 TQString n = (*it).prefix() + space + 00568 (*it).givenName() + space + 00569 (*it).additionalName() + space + 00570 (*it).familyName() + space + 00571 (*it).suffix(); 00572 00573 n = n.simplifyWhiteSpace(); 00574 00575 TQStringList::ConstIterator mit; 00576 00577 for ( mit = emails.begin(); mit != emails.end(); ++mit ) { 00578 email = *mit; 00579 if (!email.isEmpty()) { 00580 if (n.isEmpty() || (email.find( '<' ) != -1)) 00581 addr = TQString::null; 00582 else { /* do we really need quotes around this name ? */ 00583 if (n.find(needQuotes) != -1) 00584 addr = '"' + n + endQuote; 00585 else 00586 addr = n + space; 00587 } 00588 00589 if (!addr.isEmpty() && (email.find( '<' ) == -1) 00590 && (email.find( '>' ) == -1) 00591 && (email.find( ',' ) == -1)) 00592 addr += '<' + email + '>'; 00593 else 00594 addr += email; 00595 addr = addr.stripWhiteSpace(); 00596 result.append( addr ); 00597 } 00598 } 00599 } 00600 00601 TDEABC::DistributionListManager manager( addressBook ); 00602 manager.load(); 00603 result += manager.listNames(); 00604 00605 TQApplication::restoreOverrideCursor(); 00606 00607 return result; 00608 } 00609 00610 #include "addresslineedit.moc"