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., 51 Franklin Street, Fifth Floor, 00023 Boston, MA 02110-1301, USA. 00024 */ 00025 00026 #include "addresseelineedit.h" 00027 00028 #include "resourceabc.h" 00029 #include "completionordereditor.h" 00030 #include "ldapclient.h" 00031 00032 #include <config.h> 00033 00034 #ifdef KDEPIM_NEW_DISTRLISTS 00035 #include "distributionlist.h" 00036 #else 00037 #include <kabc/distributionlist.h> 00038 #endif 00039 00040 #include <kabc/stdaddressbook.h> 00041 #include <kabc/resource.h> 00042 #include <libemailfunctions/email.h> 00043 00044 #include <kcompletionbox.h> 00045 #include <kcursor.h> 00046 #include <kdebug.h> 00047 #include <kstandarddirs.h> 00048 #include <kstaticdeleter.h> 00049 #include <kstdaccel.h> 00050 #include <kurldrag.h> 00051 #include <klocale.h> 00052 00053 #include <tqpopupmenu.h> 00054 #include <tqapplication.h> 00055 #include <tqobject.h> 00056 #include <tqptrlist.h> 00057 #include <tqregexp.h> 00058 #include <tqevent.h> 00059 #include <tqdragobject.h> 00060 #include <tqclipboard.h> 00061 00062 using namespace KPIM; 00063 00064 KMailCompletion * AddresseeLineEdit::s_completion = 0L; 00065 KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L; 00066 TQStringList* AddresseeLineEdit::s_completionSources = 0L; 00067 bool AddresseeLineEdit::s_addressesDirty = false; 00068 TQTimer* AddresseeLineEdit::s_LDAPTimer = 0L; 00069 KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L; 00070 TQString* AddresseeLineEdit::s_LDAPText = 0L; 00071 AddresseeLineEdit* AddresseeLineEdit::s_LDAPLineEdit = 0L; 00072 00073 // The weights associated with the completion sources in s_completionSources. 00074 // Both are maintained by addCompletionSource(), don't attempt to modifiy those yourself. 00075 TQMap<TQString,int>* s_completionSourceWeights = 0; 00076 00077 // maps LDAP client indices to completion source indices 00078 // the assumption that they are always the first n indices in s_completion 00079 // does not hold when clients are added later on 00080 TQMap<int, int>* AddresseeLineEdit::s_ldapClientToCompletionSourceMap = 0; 00081 00082 static KStaticDeleter<KMailCompletion> completionDeleter; 00083 static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter; 00084 static KStaticDeleter<TQTimer> ldapTimerDeleter; 00085 static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter; 00086 static KStaticDeleter<TQString> ldapTextDeleter; 00087 static KStaticDeleter<TQStringList> completionSourcesDeleter; 00088 static KStaticDeleter<TQMap<TQString,int> > completionSourceWeightsDeleter; 00089 static KStaticDeleter<TQMap<int, int> > ldapClientToCompletionSourceMapDeleter; 00090 00091 // needs to be unique, but the actual name doesn't matter much 00092 static TQCString newLineEditDCOPObjectName() 00093 { 00094 static int s_count = 0; 00095 TQCString name( "KPIM::AddresseeLineEdit" ); 00096 if ( s_count++ ) { 00097 name += '-'; 00098 name += TQCString().setNum( s_count ); 00099 } 00100 return name; 00101 } 00102 00103 static const TQString s_completionItemIndentString = " "; 00104 00105 static bool itemIsHeader( const TQListBoxItem* item ) 00106 { 00107 return item && !item->text().startsWith( s_completionItemIndentString ); 00108 } 00109 00110 00111 00112 AddresseeLineEdit::AddresseeLineEdit( TQWidget* parent, bool useCompletion, 00113 const char *name ) 00114 : ClickLineEdit( parent, TQString(), name ), DCOPObject( newLineEditDCOPObjectName() ), 00115 m_useSemiColonAsSeparator( false ), m_allowDistLists( true ) 00116 { 00117 m_useCompletion = useCompletion; 00118 m_completionInitialized = false; 00119 m_smartPaste = false; 00120 m_addressBookConnected = false; 00121 m_searchExtended = false; 00122 00123 init(); 00124 00125 if ( m_useCompletion ) 00126 s_addressesDirty = true; 00127 } 00128 00129 void AddresseeLineEdit::updateLDAPWeights() 00130 { 00131 /* Add completion sources for all ldap server, 0 to n. Added first so 00132 * that they map to the ldapclient::clientNumber() */ 00133 s_LDAPSearch->updateCompletionWeights(); 00134 TQValueList< LdapClient* > clients = s_LDAPSearch->clients(); 00135 int clientIndex = 0; 00136 for ( TQValueList<LdapClient*>::iterator it = clients.begin(); it != clients.end(); ++it, ++clientIndex ) { 00137 const int sourceIndex = addCompletionSource( "LDAP server: " + (*it)->server().host(), (*it)->completionWeight() ); 00138 s_ldapClientToCompletionSourceMap->insert( clientIndex, sourceIndex ); 00139 } 00140 } 00141 00142 void AddresseeLineEdit::init() 00143 { 00144 if ( !s_completion ) { 00145 completionDeleter.setObject( s_completion, new KMailCompletion() ); 00146 s_completion->setOrder( completionOrder() ); 00147 s_completion->setIgnoreCase( true ); 00148 00149 completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() ); 00150 completionSourcesDeleter.setObject( s_completionSources, new TQStringList() ); 00151 completionSourceWeightsDeleter.setObject( s_completionSourceWeights, new TQMap<TQString,int> ); 00152 ldapClientToCompletionSourceMapDeleter.setObject( s_ldapClientToCompletionSourceMap, new TQMap<int,int> ); 00153 } 00154 // connect( s_completion, TQT_SIGNAL( match( const TQString& ) ), 00155 // this, TQT_SLOT( slotMatched( const TQString& ) ) ); 00156 00157 if ( m_useCompletion ) { 00158 if ( !s_LDAPTimer ) { 00159 ldapTimerDeleter.setObject( s_LDAPTimer, new TQTimer( 0, "ldapTimerDeleter" ) ); 00160 ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch ); 00161 ldapTextDeleter.setObject( s_LDAPText, new TQString ); 00162 } 00163 00164 updateLDAPWeights(); 00165 00166 if ( !m_completionInitialized ) { 00167 setCompletionObject( s_completion, false ); 00168 connect( this, TQT_SIGNAL( completion( const TQString& ) ), 00169 this, TQT_SLOT( slotCompletion() ) ); 00170 connect( this, TQT_SIGNAL( returnPressed( const TQString& ) ), 00171 this, TQT_SLOT( slotReturnPressed( const TQString& ) ) ); 00172 00173 KCompletionBox *box = completionBox(); 00174 connect( box, TQT_SIGNAL( highlighted( const TQString& ) ), 00175 this, TQT_SLOT( slotPopupCompletion( const TQString& ) ) ); 00176 connect( box, TQT_SIGNAL( userCancelled( const TQString& ) ), 00177 TQT_SLOT( slotUserCancelled( const TQString& ) ) ); 00178 00179 // The emitter is always called KPIM::IMAPCompletionOrder by contract 00180 if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()", 00181 "slotIMAPCompletionOrderChanged()", false ) ) 00182 kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl; 00183 00184 connect( s_LDAPTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( slotStartLDAPLookup() ) ); 00185 connect( s_LDAPSearch, TQT_SIGNAL( searchData( const KPIM::LdapResultList& ) ), 00186 TQT_SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) ); 00187 00188 m_completionInitialized = true; 00189 } 00190 } 00191 } 00192 00193 AddresseeLineEdit::~AddresseeLineEdit() 00194 { 00195 if ( s_LDAPSearch && s_LDAPLineEdit == this ) 00196 stopLDAPLookup(); 00197 } 00198 00199 void AddresseeLineEdit::setFont( const TQFont& font ) 00200 { 00201 KLineEdit::setFont( font ); 00202 if ( m_useCompletion ) 00203 completionBox()->setFont( font ); 00204 } 00205 00206 void AddresseeLineEdit::allowSemiColonAsSeparator( bool useSemiColonAsSeparator ) 00207 { 00208 m_useSemiColonAsSeparator = useSemiColonAsSeparator; 00209 } 00210 00211 void AddresseeLineEdit::allowDistributionLists( bool allowDistLists ) 00212 { 00213 m_allowDistLists = allowDistLists; 00214 } 00215 00216 void AddresseeLineEdit::keyPressEvent( TQKeyEvent *e ) 00217 { 00218 bool accept = false; 00219 00220 if ( KStdAccel::shortcut( KStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) { 00221 //TODO: add LDAP substring lookup, when it becomes available in KPIM::LDAPSearch 00222 updateSearchString(); 00223 doCompletion( true ); 00224 accept = true; 00225 } else if ( KStdAccel::shortcut( KStdAccel::TextCompletion ).contains( KKey( e ) ) ) { 00226 int len = text().length(); 00227 00228 if ( len == cursorPosition() ) { // at End? 00229 updateSearchString(); 00230 doCompletion( true ); 00231 accept = true; 00232 } 00233 } 00234 00235 const TQString oldContent = text(); 00236 if ( !accept ) 00237 KLineEdit::keyPressEvent( e ); 00238 00239 // if the text didn't change (eg. because a cursor navigation key was pressed) 00240 // we don't need to trigger a new search 00241 if ( oldContent == text() ) 00242 return; 00243 00244 if ( e->isAccepted() ) { 00245 updateSearchString(); 00246 TQString searchString( m_searchString ); 00247 //LDAP does not know about our string manipulation, remove it 00248 if ( m_searchExtended ) 00249 searchString = m_searchString.mid( 1 ); 00250 00251 if ( m_useCompletion && s_LDAPTimer != NULL ) { 00252 if ( *s_LDAPText != searchString || s_LDAPLineEdit != this ) 00253 stopLDAPLookup(); 00254 00255 *s_LDAPText = searchString; 00256 s_LDAPLineEdit = this; 00257 s_LDAPTimer->start( 500, true ); 00258 } 00259 } 00260 } 00261 00262 void AddresseeLineEdit::insert( const TQString &t ) 00263 { 00264 if ( !m_smartPaste ) { 00265 KLineEdit::insert( t ); 00266 return; 00267 } 00268 00269 //kdDebug(5300) << " AddresseeLineEdit::insert( \"" << t << "\" )" << endl; 00270 00271 TQString newText = t.stripWhiteSpace(); 00272 if ( newText.isEmpty() ) 00273 return; 00274 00275 // remove newlines in the to-be-pasted string 00276 TQStringList lines = TQStringList::split( TQRegExp("\r?\n"), newText, false ); 00277 for ( TQStringList::iterator it = lines.begin(); 00278 it != lines.end(); ++it ) { 00279 // remove trailing commas and whitespace 00280 (*it).remove( TQRegExp(",?\\s*$") ); 00281 } 00282 newText = lines.join( ", " ); 00283 00284 if ( newText.startsWith("mailto:") ) { 00285 KURL url( newText ); 00286 newText = url.path(); 00287 } 00288 else if ( newText.find(" at ") != -1 ) { 00289 // Anti-spam stuff 00290 newText.replace( " at ", "@" ); 00291 newText.replace( " dot ", "." ); 00292 } 00293 else if ( newText.find("(at)") != -1 ) { 00294 newText.replace( TQRegExp("\\s*\\(at\\)\\s*"), "@" ); 00295 } 00296 00297 TQString contents = text(); 00298 int start_sel = 0; 00299 int pos = cursorPosition( ); 00300 00301 if ( hasSelectedText() ) { 00302 // Cut away the selection. 00303 start_sel = selectionStart(); 00304 pos = start_sel; 00305 contents = contents.left( start_sel ) + contents.mid( start_sel + selectedText().length() ); 00306 } 00307 00308 int eot = contents.length(); 00309 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) { 00310 eot--; 00311 } 00312 if ( eot == 0 ) { 00313 contents = TQString(); 00314 } else if ( pos >= eot ) { 00315 if ( contents[ eot - 1 ] == ',' ) { 00316 eot--; 00317 } 00318 contents.truncate( eot ); 00319 contents += ", "; 00320 pos = eot + 2; 00321 } 00322 00323 contents = contents.left( pos ) + newText + contents.mid( pos ); 00324 setText( contents ); 00325 setEdited( true ); 00326 setCursorPosition( pos + newText.length() ); 00327 } 00328 00329 void AddresseeLineEdit::setText( const TQString & text ) 00330 { 00331 ClickLineEdit::setText( text.stripWhiteSpace() ); 00332 } 00333 00334 void AddresseeLineEdit::paste() 00335 { 00336 if ( m_useCompletion ) 00337 m_smartPaste = true; 00338 00339 KLineEdit::paste(); 00340 m_smartPaste = false; 00341 } 00342 00343 void AddresseeLineEdit::mouseReleaseEvent( TQMouseEvent *e ) 00344 { 00345 // reimplemented from TQLineEdit::mouseReleaseEvent() 00346 if ( m_useCompletion 00347 && TQApplication::clipboard()->supportsSelection() 00348 && !isReadOnly() 00349 && e->button() == Qt::MidButton ) { 00350 m_smartPaste = true; 00351 } 00352 00353 KLineEdit::mouseReleaseEvent( e ); 00354 m_smartPaste = false; 00355 } 00356 00357 void AddresseeLineEdit::dropEvent( TQDropEvent *e ) 00358 { 00359 KURL::List uriList; 00360 if ( !isReadOnly() ) { 00361 if ( KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) { 00362 TQString contents = text(); 00363 // remove trailing white space and comma 00364 int eot = contents.length(); 00365 while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) 00366 eot--; 00367 if ( eot == 0 ) 00368 contents = TQString(); 00369 else if ( contents[ eot - 1 ] == ',' ) { 00370 eot--; 00371 contents.truncate( eot ); 00372 } 00373 bool mailtoURL = false; 00374 // append the mailto URLs 00375 for ( KURL::List::Iterator it = uriList.begin(); 00376 it != uriList.end(); ++it ) { 00377 if ( !contents.isEmpty() ) 00378 contents.append( ", " ); 00379 KURL u( *it ); 00380 if ( u.protocol() == "mailto" ) { 00381 mailtoURL = true; 00382 contents.append( (*it).path() ); 00383 } 00384 } 00385 if ( mailtoURL ) { 00386 setText( contents ); 00387 setEdited( true ); 00388 return; 00389 } 00390 } else { 00391 // Let's see if this drop contains a comma separated list of emails 00392 TQString dropData = TQString::fromUtf8( e->encodedData( "text/plain" ) ); 00393 TQStringList addrs = splitEmailAddrList( dropData ); 00394 if ( addrs.count() > 0 ) { 00395 setText( normalizeAddressesAndDecodeIDNs( dropData ) ); 00396 setEdited( true ); 00397 return; 00398 } 00399 } 00400 } 00401 00402 if ( m_useCompletion ) 00403 m_smartPaste = true; 00404 TQLineEdit::dropEvent( e ); 00405 m_smartPaste = false; 00406 } 00407 00408 void AddresseeLineEdit::cursorAtEnd() 00409 { 00410 setCursorPosition( text().length() ); 00411 } 00412 00413 void AddresseeLineEdit::enableCompletion( bool enable ) 00414 { 00415 m_useCompletion = enable; 00416 } 00417 00418 void AddresseeLineEdit::doCompletion( bool ctrlT ) 00419 { 00420 m_lastSearchMode = ctrlT; 00421 00422 KGlobalSettings::Completion mode = completionMode(); 00423 00424 if ( mode == KGlobalSettings::CompletionNone ) 00425 return; 00426 00427 if ( s_addressesDirty ) { 00428 loadContacts(); // read from local address book 00429 s_completion->setOrder( completionOrder() ); 00430 } 00431 00432 // cursor at end of string - or Ctrl+T pressed for substring completion? 00433 if ( ctrlT ) { 00434 const TQStringList completions = getAdjustedCompletionItems( false ); 00435 00436 if ( completions.count() > 1 ) 00437 ; //m_previousAddresses = prevAddr; 00438 else if ( completions.count() == 1 ) 00439 setText( m_previousAddresses + completions.first().stripWhiteSpace() ); 00440 00441 setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found 00442 00443 cursorAtEnd(); 00444 setCompletionMode( mode ); //set back to previous mode 00445 return; 00446 } 00447 00448 00449 switch ( mode ) { 00450 case KGlobalSettings::CompletionPopupAuto: 00451 { 00452 if ( m_searchString.isEmpty() ) 00453 break; 00454 } 00455 00456 case KGlobalSettings::CompletionPopup: 00457 { 00458 const TQStringList items = getAdjustedCompletionItems( true ); 00459 setCompletedItems( items, false ); 00460 break; 00461 } 00462 00463 case KGlobalSettings::CompletionShell: 00464 { 00465 TQString match = s_completion->makeCompletion( m_searchString ); 00466 if ( !match.isNull() && match != m_searchString ) { 00467 setText( m_previousAddresses + match ); 00468 setEdited( true ); 00469 cursorAtEnd(); 00470 } 00471 break; 00472 } 00473 00474 case KGlobalSettings::CompletionMan: // Short-Auto in fact 00475 case KGlobalSettings::CompletionAuto: 00476 { 00477 //force autoSuggest in KLineEdit::keyPressed or setCompletedText will have no effect 00478 setCompletionMode( completionMode() ); 00479 00480 if ( !m_searchString.isEmpty() ) { 00481 00482 //if only our \" is left, remove it since user has not typed it either 00483 if ( m_searchExtended && m_searchString == "\"" ){ 00484 m_searchExtended = false; 00485 m_searchString = TQString(); 00486 setText( m_previousAddresses ); 00487 break; 00488 } 00489 00490 TQString match = s_completion->makeCompletion( m_searchString ); 00491 00492 if ( !match.isEmpty() ) { 00493 if ( match != m_searchString ) { 00494 TQString adds = m_previousAddresses + match; 00495 setCompletedText( adds ); 00496 } 00497 } else { 00498 if ( !m_searchString.startsWith( "\"" ) ) { 00499 //try with quoted text, if user has not type one already 00500 match = s_completion->makeCompletion( "\"" + m_searchString ); 00501 if ( !match.isEmpty() && match != m_searchString ) { 00502 m_searchString = "\"" + m_searchString; 00503 m_searchExtended = true; 00504 setText( m_previousAddresses + m_searchString ); 00505 setCompletedText( m_previousAddresses + match ); 00506 } 00507 } else if ( m_searchExtended ) { 00508 //our added \" does not work anymore, remove it 00509 m_searchString = m_searchString.mid( 1 ); 00510 m_searchExtended = false; 00511 setText( m_previousAddresses + m_searchString ); 00512 //now try again 00513 match = s_completion->makeCompletion( m_searchString ); 00514 if ( !match.isEmpty() && match != m_searchString ) { 00515 TQString adds = m_previousAddresses + match; 00516 setCompletedText( adds ); 00517 } 00518 } 00519 } 00520 } 00521 break; 00522 } 00523 00524 case KGlobalSettings::CompletionNone: 00525 default: // fall through 00526 break; 00527 } 00528 } 00529 00530 void AddresseeLineEdit::slotPopupCompletion( const TQString& completion ) 00531 { 00532 setText( m_previousAddresses + completion.stripWhiteSpace() ); 00533 cursorAtEnd(); 00534 // slotMatched( m_previousAddresses + completion ); 00535 updateSearchString(); 00536 } 00537 00538 void AddresseeLineEdit::slotReturnPressed( const TQString& item ) 00539 { 00540 Q_UNUSED( item ); 00541 TQListBoxItem* i = completionBox()->selectedItem(); 00542 if ( i != 0 ) 00543 slotPopupCompletion( i->text() ); 00544 } 00545 00546 void AddresseeLineEdit::loadContacts() 00547 { 00548 s_completion->clear(); 00549 s_completionItemMap->clear(); 00550 s_addressesDirty = false; 00551 //m_contactMap.clear(); 00552 00553 TQApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while 00554 00555 KConfig config( "kpimcompletionorder" ); // The weights for non-imap kabc resources is there. 00556 config.setGroup( "CompletionWeights" ); 00557 00558 KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true ); 00559 // Can't just use the addressbook's iterator, we need to know which subresource 00560 // is behind which contact. 00561 TQPtrList<KABC::Resource> resources( addressBook->resources() ); 00562 for( TQPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) { 00563 KABC::Resource* resource = *resit; 00564 KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource ); 00565 if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource 00566 const TQMap<TQString, TQString> uidToResourceMap = resabc->uidToResourceMap(); 00567 KABC::Resource::Iterator it; 00568 for ( it = resource->begin(); it != resource->end(); ++it ) { 00569 TQString uid = (*it).uid(); 00570 TQMap<TQString, TQString>::const_iterator wit = uidToResourceMap.find( uid ); 00571 const TQString subresourceLabel = resabc->subresourceLabel( *wit ); 00572 const int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80; 00573 const int idx = addCompletionSource( subresourceLabel, weight ); 00574 00575 //kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl; 00576 addContact( *it, weight, idx ); 00577 } 00578 } else { // KABC non-imap resource 00579 int weight = config.readNumEntry( resource->identifier(), 60 ); 00580 int sourceIndex = addCompletionSource( resource->resourceName(), weight ); 00581 KABC::Resource::Iterator it; 00582 for ( it = resource->begin(); it != resource->end(); ++it ) { 00583 addContact( *it, weight, sourceIndex ); 00584 } 00585 } 00586 } 00587 00588 #ifndef KDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above 00589 int weight = config.readNumEntry( "DistributionLists", 60 ); 00590 KABC::DistributionListManager manager( addressBook ); 00591 manager.load(); 00592 const TQStringList distLists = manager.listNames(); 00593 TQStringList::const_iterator listIt; 00594 int idx = addCompletionSource( i18n( "Distribution Lists" ) ); 00595 for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) { 00596 00597 //for KGlobalSettings::CompletionAuto 00598 addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx ); 00599 00600 //for CompletionShell, CompletionPopup 00601 TQStringList sl( (*listIt).simplifyWhiteSpace() ); 00602 addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx, &sl ); 00603 00604 } 00605 #endif 00606 00607 TQApplication::restoreOverrideCursor(); 00608 00609 if ( !m_addressBookConnected ) { 00610 connect( addressBook, TQT_SIGNAL( addressBookChanged( AddressBook* ) ), TQT_SLOT( loadContacts() ) ); 00611 m_addressBookConnected = true; 00612 } 00613 } 00614 00615 void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight, int source ) 00616 { 00617 #ifdef KDEPIM_NEW_DISTRLISTS 00618 if ( KPIM::DistributionList::isDistributionList( addr ) ) { 00619 //kdDebug(5300) << "AddresseeLineEdit::addContact() distribution list \"" << addr.formattedName() << "\" weight=" << weight << endl; 00620 00621 if ( m_allowDistLists ) { 00622 //for CompletionAuto 00623 addCompletionItem( addr.formattedName(), weight, source ); 00624 00625 //for CompletionShell, CompletionPopup 00626 TQStringList sl( addr.formattedName() ); 00627 addCompletionItem( addr.formattedName(), weight, source, &sl ); 00628 } 00629 00630 return; 00631 } 00632 #endif 00633 //m_contactMap.insert( addr.realName(), addr ); 00634 const TQStringList emails = addr.emails(); 00635 TQStringList::ConstIterator it; 00636 const int prefEmailWeight = 1; //increment weight by prefEmailWeight 00637 int isPrefEmail = prefEmailWeight; //first in list is preferredEmail 00638 for ( it = emails.begin(); it != emails.end(); ++it ) { 00639 //TODO: highlight preferredEmail 00640 const TQString email( (*it) ); 00641 const TQString givenName = addr.givenName(); 00642 const TQString familyName= addr.familyName(); 00643 const TQString nickName = addr.nickName(); 00644 const TQString domain = email.mid( email.find( '@' ) + 1 ); 00645 TQString fullEmail = addr.fullEmail( email ); 00646 //TODO: let user decide what fields to use in lookup, e.g. company, city, ... 00647 00648 //for CompletionAuto 00649 if ( givenName.isEmpty() && familyName.isEmpty() ) { 00650 addCompletionItem( fullEmail, weight + isPrefEmail, source ); // use whatever is there 00651 } else { 00652 const TQString byFirstName= "\"" + givenName + " " + familyName + "\" <" + email + ">"; 00653 const TQString byLastName = "\"" + familyName + ", " + givenName + "\" <" + email + ">"; 00654 addCompletionItem( byFirstName, weight + isPrefEmail, source ); 00655 addCompletionItem( byLastName, weight + isPrefEmail, source ); 00656 } 00657 00658 addCompletionItem( email, weight + isPrefEmail, source ); 00659 00660 if ( !nickName.isEmpty() ){ 00661 const TQString byNick = "\"" + nickName + "\" <" + email + ">"; 00662 addCompletionItem( byNick, weight + isPrefEmail, source ); 00663 } 00664 00665 if ( !domain.isEmpty() ){ 00666 const TQString byDomain = "\"" + domain + " " + familyName + " " + givenName + "\" <" + email + ">"; 00667 addCompletionItem( byDomain, weight + isPrefEmail, source ); 00668 } 00669 00670 //for CompletionShell, CompletionPopup 00671 TQStringList keyWords; 00672 const TQString realName = addr.realName(); 00673 00674 if ( !givenName.isEmpty() && !familyName.isEmpty() ) { 00675 keyWords.append( givenName + " " + familyName ); 00676 keyWords.append( familyName + " " + givenName ); 00677 keyWords.append( familyName + ", " + givenName); 00678 }else if ( !givenName.isEmpty() ) 00679 keyWords.append( givenName ); 00680 else if ( !familyName.isEmpty() ) 00681 keyWords.append( familyName ); 00682 00683 if ( !nickName.isEmpty() ) 00684 keyWords.append( nickName ); 00685 00686 if ( !realName.isEmpty() ) 00687 keyWords.append( realName ); 00688 00689 if ( !domain.isEmpty() ) 00690 keyWords.append( domain ); 00691 00692 keyWords.append( email ); 00693 00694 /* KMailCompletion does not have knowledge about identities, it stores emails and 00695 * keywords for each email. KMailCompletion::allMatches does a lookup on the 00696 * keywords and returns an ordered list of emails. In order to get the preferred 00697 * email before others for each identity we use this little trick. 00698 * We remove the <blank> in getAdjustedCompletionItems. 00699 */ 00700 if ( isPrefEmail == prefEmailWeight ) 00701 fullEmail.replace( " <", " <" ); 00702 00703 addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords ); 00704 isPrefEmail = 0; 00705 00706 #if 0 00707 int len = (*it).length(); 00708 if ( len == 0 ) continue; 00709 if( '\0' == (*it)[len-1] ) 00710 --len; 00711 const TQString tmp = (*it).left( len ); 00712 const TQString fullEmail = addr.fullEmail( tmp ); 00713 //kdDebug(5300) << "AddresseeLineEdit::addContact() \"" << fullEmail << "\" weight=" << weight << endl; 00714 addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source ); 00715 // Try to guess the last name: if found, we add an extra 00716 // entry to the list to make sure completion works even 00717 // if the user starts by typing in the last name. 00718 TQString name( addr.realName().simplifyWhiteSpace() ); 00719 if( name.endsWith("\"") ) 00720 name.truncate( name.length()-1 ); 00721 if( name.startsWith("\"") ) 00722 name = name.mid( 1 ); 00723 00724 // While we're here also add "email (full name)" for completion on the email 00725 if ( !name.isEmpty() ) 00726 addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source ); 00727 00728 bool bDone = false; 00729 int i = -1; 00730 while( ( i = name.findRev(' ') ) > 1 && !bDone ) { 00731 TQString sLastName( name.mid( i+1 ) ); 00732 if( ! sLastName.isEmpty() && 00733 2 <= sLastName.length() && // last names must be at least 2 chars long 00734 ! sLastName.endsWith(".") ) { // last names must not end with a dot (like "Jr." or "Sr.") 00735 name.truncate( i ); 00736 if( !name.isEmpty() ){ 00737 sLastName.prepend( "\"" ); 00738 sLastName.append( ", " + name + "\" <" ); 00739 } 00740 TQString sExtraEntry( sLastName ); 00741 sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp ); 00742 sExtraEntry.append( ">" ); 00743 //kdDebug(5300) << "AddresseeLineEdit::addContact() added extra \"" << sExtraEntry.simplifyWhiteSpace() << "\" weight=" << weight << endl; 00744 addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source ); 00745 bDone = true; 00746 } 00747 if( !bDone ) { 00748 name.truncate( i ); 00749 if( name.endsWith("\"") ) 00750 name.truncate( name.length()-1 ); 00751 } 00752 } 00753 #endif 00754 } 00755 } 00756 00757 void AddresseeLineEdit::addCompletionItem( const TQString& string, int weight, int completionItemSource, const TQStringList * keyWords ) 00758 { 00759 // Check if there is an exact match for item already, and use the max weight if so. 00760 // Since there's no way to get the information from KCompletion, we have to keep our own TQMap 00761 CompletionItemsMap::iterator it = s_completionItemMap->find( string ); 00762 if ( it != s_completionItemMap->end() ) { 00763 weight = TQMAX( ( *it ).first, weight ); 00764 ( *it ).first = weight; 00765 } else { 00766 s_completionItemMap->insert( string, tqMakePair( weight, completionItemSource ) ); 00767 } 00768 if ( keyWords == 0 ) 00769 s_completion->addItem( string, weight ); 00770 else 00771 s_completion->addItemWithKeys( string, weight, keyWords ); 00772 } 00773 00774 void AddresseeLineEdit::slotStartLDAPLookup() 00775 { 00776 KGlobalSettings::Completion mode = completionMode(); 00777 00778 if ( mode == KGlobalSettings::CompletionNone ) 00779 return; 00780 00781 if ( !s_LDAPSearch->isAvailable() ) { 00782 return; 00783 } 00784 if ( s_LDAPLineEdit != this ) 00785 return; 00786 00787 startLoadingLDAPEntries(); 00788 } 00789 00790 void AddresseeLineEdit::stopLDAPLookup() 00791 { 00792 s_LDAPSearch->cancelSearch(); 00793 s_LDAPLineEdit = NULL; 00794 } 00795 00796 void AddresseeLineEdit::startLoadingLDAPEntries() 00797 { 00798 TQString s( *s_LDAPText ); 00799 // TODO cache last? 00800 TQString prevAddr; 00801 int n = s.findRev( ',' ); 00802 if ( n >= 0 ) { 00803 prevAddr = s.left( n + 1 ) + ' '; 00804 s = s.mid( n + 1, 255 ).stripWhiteSpace(); 00805 } 00806 00807 if ( s.isEmpty() ) 00808 return; 00809 00810 //loadContacts(); // TODO reuse these? 00811 s_LDAPSearch->startSearch( s ); 00812 } 00813 00814 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs ) 00815 { 00816 if ( adrs.isEmpty() || s_LDAPLineEdit != this ) 00817 return; 00818 00819 for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) { 00820 KABC::Addressee addr; 00821 addr.setNameFromString( (*it).name ); 00822 addr.setEmails( (*it).email ); 00823 00824 if ( !s_ldapClientToCompletionSourceMap->contains( (*it).clientNumber ) ) 00825 updateLDAPWeights(); // we got results from a new source, so update the completion sources 00826 00827 addContact( addr, (*it).completionWeight, (*s_ldapClientToCompletionSourceMap)[ (*it ).clientNumber ] ); 00828 } 00829 00830 if ( (hasFocus() || completionBox()->hasFocus() ) 00831 && completionMode() != KGlobalSettings::CompletionNone 00832 && completionMode() != KGlobalSettings::CompletionShell ) { 00833 setText( m_previousAddresses + m_searchString ); 00834 // only complete again if the user didn't change the selection while we were waiting 00835 // otherwise the completion box will be closed 00836 if ( m_searchString.stripWhiteSpace() != completionBox()->currentText().stripWhiteSpace() ) 00837 doCompletion( m_lastSearchMode ); 00838 } 00839 } 00840 00841 void AddresseeLineEdit::setCompletedItems( const TQStringList& items, bool autoSuggest ) 00842 { 00843 KCompletionBox* completionBox = this->completionBox(); 00844 00845 if ( !items.isEmpty() && 00846 !(items.count() == 1 && m_searchString == items.first()) ) 00847 { 00848 TQString oldCurrentText = completionBox->currentText(); 00849 TQListBoxItem *itemUnderMouse = completionBox->itemAt( 00850 completionBox->viewport()->mapFromGlobal(TQCursor::pos()) ); 00851 TQString oldTextUnderMouse; 00852 TQPoint oldPosOfItemUnderMouse; 00853 if ( itemUnderMouse ) { 00854 oldTextUnderMouse = itemUnderMouse->text(); 00855 oldPosOfItemUnderMouse = completionBox->itemRect( itemUnderMouse ).topLeft(); 00856 } 00857 00858 completionBox->setItems( items ); 00859 00860 if ( !completionBox->isVisible() ) { 00861 if ( !m_searchString.isEmpty() ) 00862 completionBox->setCancelledText( m_searchString ); 00863 completionBox->popup(); 00864 // we have to install the event filter after popup(), since that 00865 // calls show(), and that's where KCompletionBox installs its filter. 00866 // We want to be first, though, so do it now. 00867 if ( s_completion->order() == KCompletion::Weighted ) 00868 tqApp->installEventFilter( this ); 00869 } 00870 00871 // Try to re-select what was selected before, otherrwise use the first 00872 // item, if there is one 00873 TQListBoxItem* item = 0; 00874 if ( oldCurrentText.isEmpty() 00875 || ( item = completionBox->findItem( oldCurrentText ) ) == 0 ) { 00876 item = completionBox->item( 1 ); 00877 } 00878 if ( item ) 00879 { 00880 if ( itemUnderMouse ) { 00881 TQListBoxItem *newItemUnderMouse = completionBox->findItem( oldTextUnderMouse ); 00882 // if the mouse was over an item, before, but now that's elsewhere, 00883 // move the cursor, so folks don't accidently click the wrong item 00884 if ( newItemUnderMouse ) { 00885 TQRect r = completionBox->itemRect( newItemUnderMouse ); 00886 TQPoint target = r.topLeft(); 00887 if ( oldPosOfItemUnderMouse != target ) { 00888 target.setX( target.x() + r.width()/2 ); 00889 TQCursor::setPos( completionBox->viewport()->mapToGlobal(target) ); 00890 } 00891 } 00892 } 00893 completionBox->blockSignals( true ); 00894 completionBox->setSelected( item, true ); 00895 completionBox->setCurrentItem( item ); 00896 completionBox->ensureCurrentVisible(); 00897 00898 completionBox->blockSignals( false ); 00899 } 00900 00901 if ( autoSuggest ) 00902 { 00903 int index = items.first().find( m_searchString ); 00904 TQString newText = items.first().mid( index ); 00905 setUserSelection(false); 00906 setCompletedText(newText,true); 00907 } 00908 } 00909 else 00910 { 00911 if ( completionBox && completionBox->isVisible() ) { 00912 completionBox->hide(); 00913 completionBox->setItems( TQStringList() ); 00914 } 00915 } 00916 } 00917 00918 TQPopupMenu* AddresseeLineEdit::createPopupMenu() 00919 { 00920 TQPopupMenu *menu = KLineEdit::createPopupMenu(); 00921 if ( !menu ) 00922 return 0; 00923 00924 if ( m_useCompletion ){ 00925 menu->setItemVisible( ShortAutoCompletion, false ); 00926 menu->setItemVisible( PopupAutoCompletion, false ); 00927 menu->insertItem( i18n( "Configure Completion Order..." ), 00928 this, TQT_SLOT( slotEditCompletionOrder() ) ); 00929 } 00930 return menu; 00931 } 00932 00933 void AddresseeLineEdit::slotEditCompletionOrder() 00934 { 00935 init(); // for s_LDAPSearch 00936 CompletionOrderEditor editor( s_LDAPSearch, this ); 00937 editor.exec(); 00938 if ( m_useCompletion ) { 00939 updateLDAPWeights(); 00940 s_addressesDirty = true; 00941 } 00942 } 00943 00944 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged() 00945 { 00946 if ( m_useCompletion ) 00947 s_addressesDirty = true; 00948 } 00949 00950 void KPIM::AddresseeLineEdit::slotUserCancelled( const TQString& cancelText ) 00951 { 00952 if ( s_LDAPSearch && s_LDAPLineEdit == this ) 00953 stopLDAPLookup(); 00954 userCancelled( m_previousAddresses + cancelText ); // in KLineEdit 00955 } 00956 00957 void AddresseeLineEdit::updateSearchString() 00958 { 00959 m_searchString = text(); 00960 00961 int n = -1; 00962 bool inQuote = false; 00963 uint searchStringLength = m_searchString.length(); 00964 for ( uint i = 0; i < searchStringLength; ++i ) { 00965 if ( m_searchString[ i ] == '"' ) { 00966 inQuote = !inQuote; 00967 } 00968 if ( m_searchString[ i ] == '\\' && 00969 (i + 1) < searchStringLength && m_searchString[ i + 1 ] == '"' ) { 00970 ++i; 00971 } 00972 if ( inQuote ) { 00973 continue; 00974 } 00975 if ( i < searchStringLength && 00976 ( m_searchString[ i ] == ',' || 00977 ( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) ) ) { 00978 n = i; 00979 } 00980 } 00981 00982 if ( n >= 0 ) { 00983 ++n; // Go past the "," 00984 00985 int len = m_searchString.length(); 00986 00987 // Increment past any whitespace... 00988 while ( n < len && m_searchString[ n ].isSpace() ) 00989 ++n; 00990 00991 m_previousAddresses = m_searchString.left( n ); 00992 m_searchString = m_searchString.mid( n ).stripWhiteSpace(); 00993 } else { 00994 m_previousAddresses = TQString(); 00995 } 00996 } 00997 00998 void KPIM::AddresseeLineEdit::slotCompletion() 00999 { 01000 // Called by KLineEdit's keyPressEvent for CompletionModes Auto,Popup -> new text, update search string 01001 // not called for CompletionShell, this is been taken care of in AddresseeLineEdit::keyPressEvent 01002 updateSearchString(); 01003 if ( completionBox() ) 01004 completionBox()->setCancelledText( m_searchString ); 01005 doCompletion( false ); 01006 } 01007 01008 // not cached, to make sure we get an up-to-date value when it changes 01009 KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder() 01010 { 01011 KConfig config( "kpimcompletionorder" ); 01012 config.setGroup( "General" ); 01013 const TQString order = config.readEntry( "CompletionOrder", "Weighted" ); 01014 01015 if ( order == "Weighted" ) 01016 return KCompletion::Weighted; 01017 else 01018 return KCompletion::Sorted; 01019 } 01020 01021 int KPIM::AddresseeLineEdit::addCompletionSource( const TQString &source, int weight ) 01022 { 01023 TQMap<TQString,int>::iterator it = s_completionSourceWeights->find( source ); 01024 if ( it == s_completionSourceWeights->end() ) 01025 s_completionSourceWeights->insert( source, weight ); 01026 else 01027 (*s_completionSourceWeights)[source] = weight; 01028 01029 int sourceIndex = s_completionSources->findIndex( source ); 01030 if ( sourceIndex == -1 ) { 01031 s_completionSources->append( source ); 01032 return s_completionSources->size() - 1; 01033 } 01034 else 01035 return sourceIndex; 01036 } 01037 01038 bool KPIM::AddresseeLineEdit::eventFilter(TQObject *obj, TQEvent *e) 01039 { 01040 if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(completionBox()) ) { 01041 if ( e->type() == TQEvent::MouseButtonPress || 01042 e->type() == TQEvent::MouseMove || 01043 e->type() == TQEvent::MouseButtonRelease || 01044 e->type() == TQEvent::MouseButtonDblClick ) { 01045 TQMouseEvent* me = TQT_TQMOUSEEVENT( e ); 01046 // find list box item at the event position 01047 TQListBoxItem *item = completionBox()->itemAt( me->pos() ); 01048 if ( !item ) { 01049 // In the case of a mouse move outside of the box we don't want 01050 // the parent to fuzzy select a header by mistake. 01051 bool eat = e->type() == TQEvent::MouseMove; 01052 return eat; 01053 } 01054 // avoid selection of headers on button press, or move or release while 01055 // a button is pressed 01056 if ( e->type() == TQEvent::MouseButtonPress 01057 || me->state() & Qt::LeftButton || me->state() & Qt::MidButton 01058 || me->state() & Qt::RightButton ) { 01059 if ( itemIsHeader(item) ) { 01060 return true; // eat the event, we don't want anything to happen 01061 } else { 01062 // if we are not on one of the group heading, make sure the item 01063 // below or above is selected, not the heading, inadvertedly, due 01064 // to fuzzy auto-selection from TQListBox 01065 completionBox()->setCurrentItem( item ); 01066 completionBox()->setSelected( completionBox()->index( item ), true ); 01067 if ( e->type() == TQEvent::MouseMove ) 01068 return true; // avoid fuzzy selection behavior 01069 } 01070 } 01071 } 01072 } 01073 if ( ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(this) ) && 01074 ( e->type() == TQEvent::AccelOverride ) ) { 01075 TQKeyEvent *ke = TQT_TQKEYEVENT( e ); 01076 if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) { 01077 ke->accept(); 01078 return true; 01079 } 01080 } 01081 if ( ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(this) ) && 01082 ( e->type() == TQEvent::KeyPress || e->type() == TQEvent::KeyRelease ) && 01083 completionBox()->isVisible() ) { 01084 TQKeyEvent *ke = TQT_TQKEYEVENT( e ); 01085 int currentIndex = completionBox()->currentItem(); 01086 if ( currentIndex < 0 ) { 01087 return true; 01088 } 01089 01090 if ( ke->key() == Key_Up ) { 01091 //kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl; 01092 // figure out if the item we would be moving to is one we want 01093 // to ignore. If so, go one further 01094 TQListBoxItem *itemAbove = completionBox()->item( currentIndex ); 01095 if ( itemAbove && itemIsHeader(itemAbove) ) { 01096 // there is a header above us, check if there is even further up 01097 // and if so go one up, so it'll be selected 01098 if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) { 01099 //kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl; 01100 completionBox()->setCurrentItem( itemAbove->prev() ); 01101 completionBox()->setSelected( currentIndex - 1, true ); 01102 } else if ( currentIndex == 0 ) { 01103 // nothing to skip to, let's stay where we are, but make sure the 01104 // first header becomes visible, if we are the first real entry 01105 completionBox()->ensureVisible( 0, 0 ); 01106 //Kolab issue 2941: be sure to add email even if it's the only element. 01107 if ( itemIsHeader( completionBox()->item( currentIndex ) ) ) { 01108 currentIndex++; 01109 } 01110 completionBox()->setCurrentItem( itemAbove ); 01111 completionBox()->setSelected( currentIndex, true ); 01112 } 01113 return true; 01114 } 01115 } else if ( ke->key() == Key_Down ) { 01116 // same strategy for downwards 01117 //kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl; 01118 TQListBoxItem *itemBelow = completionBox()->item( currentIndex ); 01119 if ( itemBelow && itemIsHeader( itemBelow ) ) { 01120 if ( completionBox()->item( currentIndex + 1 ) ) { 01121 //kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl; 01122 completionBox()->setCurrentItem( itemBelow->next() ); 01123 completionBox()->setSelected( currentIndex + 1, true ); 01124 } else { 01125 // nothing to skip to, let's stay where we are 01126 completionBox()->setCurrentItem( itemBelow ); 01127 completionBox()->setSelected( currentIndex, true ); 01128 } 01129 return true; 01130 } 01131 // special case of the last and only item in the list needing selection 01132 if ( !itemBelow && currentIndex == 1 ) { 01133 completionBox()->setSelected( currentIndex, true ); 01134 } 01135 // special case of the initial selection, which is unfortunately a header. 01136 // Setting it to selected tricks KCompletionBox into not treating is special 01137 // and selecting making it current, instead of the one below. 01138 TQListBoxItem *item = completionBox()->item( currentIndex ); 01139 if ( item && itemIsHeader(item) ) { 01140 completionBox()->setSelected( currentIndex, true ); 01141 } 01142 } else if ( e->type() == TQEvent::KeyRelease && 01143 ( ke->key() == Key_Tab || ke->key() == Key_Backtab ) ) { 01144 //kdDebug() << "EVENTFILTER: Key_Tab. currentIndex=" << currentIndex << endl; 01146 TQListBoxItem *myHeader = 0; 01147 const int iterationstep = ke->key() == Key_Tab ? 1 : -1; 01148 int i = TQMIN( TQMAX( currentIndex - iterationstep, 0 ), completionBox()->count() - 1 ); 01149 while ( i>=0 ) { 01150 if ( itemIsHeader( completionBox()->item(i) ) ) { 01151 myHeader = completionBox()->item( i ); 01152 break; 01153 } 01154 i--; 01155 } 01156 Q_ASSERT( myHeader ); // we should always be able to find a header 01157 01158 // find the next header (searching backwards, for Key_Backtab) 01159 TQListBoxItem *nextHeader = 0; 01160 // when iterating forward, start at the currentindex, when backwards, 01161 // one up from our header, or at the end 01162 uint j; 01163 if ( ke->key() == Key_Tab ) { 01164 j = currentIndex; 01165 } else { 01166 i = completionBox()->index( myHeader ); 01167 if ( i == 0 ) { 01168 j = completionBox()->count() - 1; 01169 } else { 01170 j = ( i - 1 ) % completionBox()->count(); 01171 } 01172 } 01173 while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) { 01174 if ( itemIsHeader(nextHeader) ) { 01175 break; 01176 } 01177 j = (j + iterationstep) % completionBox()->count(); 01178 } 01179 if ( nextHeader && nextHeader != myHeader ) { 01180 TQListBoxItem *item = completionBox()->item( j + 1 ); 01181 if ( item && !itemIsHeader(item) ) { 01182 completionBox()->setSelected( item, true ); 01183 completionBox()->setCurrentItem( item ); 01184 completionBox()->ensureCurrentVisible(); 01185 } 01186 } 01187 return true; 01188 } 01189 } 01190 return ClickLineEdit::eventFilter( obj, e ); 01191 } 01192 01193 class SourceWithWeight { 01194 public: 01195 int weight; // the weight of the source 01196 TQString sourceName; // the name of the source, e.g. "LDAP Server" 01197 int index; // index into s_completionSources 01198 01199 bool operator< ( const SourceWithWeight &other ) { 01200 if ( weight > other.weight ) 01201 return true; 01202 if ( weight < other.weight ) 01203 return false; 01204 return sourceName < other.sourceName; 01205 } 01206 }; 01207 01208 const TQStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch ) 01209 { 01210 TQStringList items = fullSearch ? 01211 s_completion->allMatches( m_searchString ) 01212 : s_completion->substringCompletion( m_searchString ); 01213 01214 // For weighted mode, the algorithm is the following: 01215 // In the first loop, we add each item to its section (there is one section per completion source) 01216 // We also add spaces in front of the items. 01217 // The sections are appended to the items list. 01218 // In the second loop, we then walk through the sections and add all the items in there to the 01219 // sorted item list, which is the final result. 01220 // 01221 // The algo for non-weighted mode is different. 01222 01223 int lastSourceIndex = -1; 01224 unsigned int i = 0; 01225 01226 // Maps indices of the items list, which are section headers/source items, 01227 // to a TQStringList which are the items of that section/source. 01228 TQMap<int, TQStringList> sections; 01229 TQStringList sortedItems; 01230 for ( TQStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) { 01231 CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it); 01232 if ( cit == s_completionItemMap->end() ) 01233 continue; 01234 int idx = (*cit).second; 01235 01236 if ( s_completion->order() == KCompletion::Weighted ) { 01237 if ( lastSourceIndex == -1 || lastSourceIndex != idx ) { 01238 const TQString sourceLabel( (*s_completionSources)[idx] ); 01239 if ( sections.find(idx) == sections.end() ) { 01240 items.insert( it, sourceLabel ); 01241 } 01242 lastSourceIndex = idx; 01243 } 01244 (*it) = (*it).prepend( s_completionItemIndentString ); 01245 // remove preferred email sort <blank> added in addContact() 01246 (*it).replace( " <", " <" ); 01247 } 01248 sections[idx].append( *it ); 01249 01250 if ( s_completion->order() == KCompletion::Sorted ) { 01251 sortedItems.append( *it ); 01252 } 01253 } 01254 01255 if ( s_completion->order() == KCompletion::Weighted ) { 01256 01257 // Sort the sections 01258 TQValueList<SourceWithWeight> sourcesAndWeights; 01259 for ( uint i = 0; i < s_completionSources->size(); i++ ) { 01260 SourceWithWeight sww; 01261 sww.sourceName = (*s_completionSources)[i]; 01262 sww.weight = (*s_completionSourceWeights)[sww.sourceName]; 01263 sww.index = i; 01264 sourcesAndWeights.append( sww ); 01265 } 01266 qHeapSort( sourcesAndWeights ); 01267 01268 // Add the sections and their items to the final sortedItems result list 01269 for( uint i = 0; i < sourcesAndWeights.size(); i++ ) { 01270 TQStringList sectionItems = sections[sourcesAndWeights[i].index]; 01271 if ( !sectionItems.isEmpty() ) { 01272 sortedItems.append( sourcesAndWeights[i].sourceName ); 01273 TQStringList sectionItems = sections[sourcesAndWeights[i].index]; 01274 for ( TQStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() ); 01275 sit != send; ++sit ) { 01276 sortedItems.append( *sit ); 01277 } 01278 } 01279 } 01280 } else { 01281 sortedItems.sort(); 01282 } 01283 return sortedItems; 01284 } 01285 #include "addresseelineedit.moc"