libtdepim

addresseelineedit.cpp
1 /*
2  This file is part of libtdepim.
3  Copyright (c) 2002 Helge Deller <deller@gmx.de>
4  2002 Lubos Lunak <llunak@suse.cz>
5  2001,2003 Carsten Pfeiffer <pfeiffer@kde.org>
6  2001 Waldo Bastian <bastian@kde.org>
7  2004 Daniel Molkentin <danimo@klaralvdalens-datakonsult.se>
8  2004 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se>
9 
10  This library is free software; you can redistribute it and/or
11  modify it under the terms of the GNU Library General Public
12  License as published by the Free Software Foundation; either
13  version 2 of the License, or (at your option) any later version.
14 
15  This library is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  Library General Public License for more details.
19 
20  You should have received a copy of the GNU Library General Public License
21  along with this library; see the file COPYING.LIB. If not, write to
22  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  Boston, MA 02110-1301, USA.
24 */
25 
26 #include "addresseelineedit.h"
27 
28 #include "resourceabc.h"
29 #include "completionordereditor.h"
30 #include "ldapclient.h"
31 
32 #include <config.h>
33 
34 #ifdef TDEPIM_NEW_DISTRLISTS
35 #include "distributionlist.h"
36 #else
37 #include <tdeabc/distributionlist.h>
38 #endif
39 
40 #include <tdeabc/stdaddressbook.h>
41 #include <tdeabc/resource.h>
42 #include <libemailfunctions/email.h>
43 
44 #include <tdecompletionbox.h>
45 #include <kcursor.h>
46 #include <kdebug.h>
47 #include <kstandarddirs.h>
48 #include <kstaticdeleter.h>
49 #include <tdestdaccel.h>
50 #include <kurldrag.h>
51 #include <tdelocale.h>
52 
53 #include <tqpopupmenu.h>
54 #include <tqapplication.h>
55 #include <tqobject.h>
56 #include <tqptrlist.h>
57 #include <tqregexp.h>
58 #include <tqevent.h>
59 #include <tqdragobject.h>
60 #include <tqclipboard.h>
61 
62 using namespace KPIM;
63 
64 KMailCompletion * AddresseeLineEdit::s_completion = 0L;
65 KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L;
66 TQStringList* AddresseeLineEdit::s_completionSources = 0L;
67 bool AddresseeLineEdit::s_addressesDirty = false;
68 TQTimer* AddresseeLineEdit::s_LDAPTimer = 0L;
69 KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L;
70 TQString* AddresseeLineEdit::s_LDAPText = 0L;
71 AddresseeLineEdit* AddresseeLineEdit::s_LDAPLineEdit = 0L;
72 
73 // The weights associated with the completion sources in s_completionSources.
74 // Both are maintained by addCompletionSource(), don't attempt to modifiy those yourself.
75 TQMap<TQString,int>* s_completionSourceWeights = 0;
76 
77 // maps LDAP client indices to completion source indices
78 // the assumption that they are always the first n indices in s_completion
79 // does not hold when clients are added later on
80 TQMap<int, int>* AddresseeLineEdit::s_ldapClientToCompletionSourceMap = 0;
81 
82 static KStaticDeleter<KMailCompletion> completionDeleter;
83 static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
84 static KStaticDeleter<TQTimer> ldapTimerDeleter;
85 static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
86 static KStaticDeleter<TQString> ldapTextDeleter;
87 static KStaticDeleter<TQStringList> completionSourcesDeleter;
88 static KStaticDeleter<TQMap<TQString,int> > completionSourceWeightsDeleter;
89 static KStaticDeleter<TQMap<int, int> > ldapClientToCompletionSourceMapDeleter;
90 
91 // needs to be unique, but the actual name doesn't matter much
92 static TQCString newLineEditDCOPObjectName()
93 {
94  static int s_count = 0;
95  TQCString name( "KPIM::AddresseeLineEdit" );
96  if ( s_count++ ) {
97  name += '-';
98  name += TQCString().setNum( s_count );
99  }
100  return name;
101 }
102 
103 static const TQString s_completionItemIndentString = " ";
104 
105 static bool itemIsHeader( const TQListBoxItem* item )
106 {
107  return item && !item->text().startsWith( s_completionItemIndentString );
108 }
109 
110 
111 
112 AddresseeLineEdit::AddresseeLineEdit( TQWidget* parent, bool useCompletion,
113  const char *name )
114  : ClickLineEdit( parent, TQString(), name ), DCOPObject( newLineEditDCOPObjectName() ),
115  m_useSemiColonAsSeparator( false ), m_allowDistLists( true )
116 {
117  m_useCompletion = useCompletion;
118  m_completionInitialized = false;
119  m_smartPaste = false;
120  m_addressBookConnected = false;
121  m_searchExtended = false;
122 
123  init();
124 
125  if ( m_useCompletion )
126  s_addressesDirty = true;
127 }
128 
129 void AddresseeLineEdit::updateLDAPWeights()
130 {
131  /* Add completion sources for all ldap server, 0 to n. Added first so
132  * that they map to the ldapclient::clientNumber() */
133  s_LDAPSearch->updateCompletionWeights();
134  TQValueList< LdapClient* > clients = s_LDAPSearch->clients();
135  int clientIndex = 0;
136  for ( TQValueList<LdapClient*>::iterator it = clients.begin(); it != clients.end(); ++it, ++clientIndex ) {
137  const int sourceIndex = addCompletionSource( "LDAP server: " + (*it)->server().host(), (*it)->completionWeight() );
138  s_ldapClientToCompletionSourceMap->insert( clientIndex, sourceIndex );
139  }
140 }
141 
142 void AddresseeLineEdit::init()
143 {
144  if ( !s_completion ) {
145  completionDeleter.setObject( s_completion, new KMailCompletion() );
146  s_completion->setOrder( completionOrder() );
147  s_completion->setIgnoreCase( true );
148 
149  completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() );
150  completionSourcesDeleter.setObject( s_completionSources, new TQStringList() );
151  completionSourceWeightsDeleter.setObject( s_completionSourceWeights, new TQMap<TQString,int> );
152  ldapClientToCompletionSourceMapDeleter.setObject( s_ldapClientToCompletionSourceMap, new TQMap<int,int> );
153  }
154 // connect( s_completion, TQ_SIGNAL( match( const TQString& ) ),
155 // this, TQ_SLOT( slotMatched( const TQString& ) ) );
156 
157  if ( m_useCompletion ) {
158  if ( !s_LDAPTimer ) {
159  ldapTimerDeleter.setObject( s_LDAPTimer, new TQTimer( 0, "ldapTimerDeleter" ) );
160  ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch );
161  ldapTextDeleter.setObject( s_LDAPText, new TQString );
162  }
163 
164  updateLDAPWeights();
165 
166  if ( !m_completionInitialized ) {
167  setCompletionObject( s_completion, false );
168  connect( this, TQ_SIGNAL( completion( const TQString& ) ),
169  this, TQ_SLOT( slotCompletion() ) );
170  connect( this, TQ_SIGNAL( returnPressed( const TQString& ) ),
171  this, TQ_SLOT( slotReturnPressed( const TQString& ) ) );
172 
173  TDECompletionBox *box = completionBox();
174  connect( box, TQ_SIGNAL( highlighted( const TQString& ) ),
175  this, TQ_SLOT( slotPopupCompletion( const TQString& ) ) );
176  connect( box, TQ_SIGNAL( userCancelled( const TQString& ) ),
177  TQ_SLOT( slotUserCancelled( const TQString& ) ) );
178 
179  // The emitter is always called KPIM::IMAPCompletionOrder by contract
180  if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()",
181  "slotIMAPCompletionOrderChanged()", false ) )
182  kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl;
183 
184  connect( s_LDAPTimer, TQ_SIGNAL( timeout() ), TQ_SLOT( slotStartLDAPLookup() ) );
185  connect( s_LDAPSearch, TQ_SIGNAL( searchData( const KPIM::LdapResultList& ) ),
186  TQ_SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) );
187 
188  m_completionInitialized = true;
189  }
190  }
191 }
192 
193 AddresseeLineEdit::~AddresseeLineEdit()
194 {
195  if ( s_LDAPSearch && s_LDAPLineEdit == this )
196  stopLDAPLookup();
197 }
198 
199 void AddresseeLineEdit::setFont( const TQFont& font )
200 {
201  KLineEdit::setFont( font );
202  if ( m_useCompletion )
203  completionBox()->setFont( font );
204 }
205 
206 void AddresseeLineEdit::allowSemiColonAsSeparator( bool useSemiColonAsSeparator )
207 {
208  m_useSemiColonAsSeparator = useSemiColonAsSeparator;
209 }
210 
211 void AddresseeLineEdit::allowDistributionLists( bool allowDistLists )
212 {
213  m_allowDistLists = allowDistLists;
214 }
215 
216 void AddresseeLineEdit::keyPressEvent( TQKeyEvent *e )
217 {
218  bool accept = false;
219 
220  if ( TDEStdAccel::shortcut( TDEStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) {
221  //TODO: add LDAP substring lookup, when it becomes available in KPIM::LDAPSearch
222  updateSearchString();
223  doCompletion( true );
224  accept = true;
225  } else if ( TDEStdAccel::shortcut( TDEStdAccel::TextCompletion ).contains( KKey( e ) ) ) {
226  int len = text().length();
227 
228  if ( len == cursorPosition() ) { // at End?
229  updateSearchString();
230  doCompletion( true );
231  accept = true;
232  }
233  }
234 
235  const TQString oldContent = text();
236  if ( !accept )
237  KLineEdit::keyPressEvent( e );
238 
239  // if the text didn't change (eg. because a cursor navigation key was pressed)
240  // we don't need to trigger a new search
241  if ( oldContent == text() )
242  return;
243 
244  if ( e->isAccepted() ) {
245  updateSearchString();
246  TQString searchString( m_searchString );
247  //LDAP does not know about our string manipulation, remove it
248  if ( m_searchExtended )
249  searchString = m_searchString.mid( 1 );
250 
251  if ( m_useCompletion && s_LDAPTimer != NULL ) {
252  if ( *s_LDAPText != searchString || s_LDAPLineEdit != this )
253  stopLDAPLookup();
254 
255  *s_LDAPText = searchString;
256  s_LDAPLineEdit = this;
257  s_LDAPTimer->start( 500, true );
258  }
259  }
260 }
261 
262 void AddresseeLineEdit::insert( const TQString &t )
263 {
264  if ( !m_smartPaste ) {
265  KLineEdit::insert( t );
266  return;
267  }
268 
269  //kdDebug(5300) << " AddresseeLineEdit::insert( \"" << t << "\" )" << endl;
270 
271  TQString newText = t.stripWhiteSpace();
272  if ( newText.isEmpty() )
273  return;
274 
275  // remove newlines in the to-be-pasted string
276  TQStringList lines = TQStringList::split( TQRegExp("\r?\n"), newText, false );
277  for ( TQStringList::iterator it = lines.begin();
278  it != lines.end(); ++it ) {
279  // remove trailing commas and whitespace
280  (*it).remove( TQRegExp(",?\\s*$") );
281  }
282  newText = lines.join( ", " );
283 
284  if ( newText.startsWith("mailto:") ) {
285  KURL url( newText );
286  newText = url.path();
287  }
288  else if ( newText.find(" at ") != -1 ) {
289  // Anti-spam stuff
290  newText.replace( " at ", "@" );
291  newText.replace( " dot ", "." );
292  }
293  else if ( newText.find("(at)") != -1 ) {
294  newText.replace( TQRegExp("\\s*\\(at\\)\\s*"), "@" );
295  }
296 
297  TQString contents = text();
298  int start_sel = 0;
299  int pos = cursorPosition( );
300 
301  if ( hasSelectedText() ) {
302  // Cut away the selection.
303  start_sel = selectionStart();
304  pos = start_sel;
305  contents = contents.left( start_sel ) + contents.mid( start_sel + selectedText().length() );
306  }
307 
308  int eot = contents.length();
309  while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
310  eot--;
311  }
312  if ( eot == 0 ) {
313  contents = TQString();
314  } else if ( pos >= eot ) {
315  if ( contents[ eot - 1 ] == ',' ) {
316  eot--;
317  }
318  contents.truncate( eot );
319  contents += ", ";
320  pos = eot + 2;
321  }
322 
323  contents = contents.left( pos ) + newText + contents.mid( pos );
324  setText( contents );
325  setEdited( true );
326  setCursorPosition( pos + newText.length() );
327 }
328 
329 void AddresseeLineEdit::setText( const TQString & text )
330 {
331  ClickLineEdit::setText( text.stripWhiteSpace() );
332 }
333 
334 void AddresseeLineEdit::paste()
335 {
336  if ( m_useCompletion )
337  m_smartPaste = true;
338 
339  KLineEdit::paste();
340  m_smartPaste = false;
341 }
342 
343 void AddresseeLineEdit::mouseReleaseEvent( TQMouseEvent *e )
344 {
345  // reimplemented from TQLineEdit::mouseReleaseEvent()
346  if ( m_useCompletion
347  && TQApplication::clipboard()->supportsSelection()
348  && !isReadOnly()
349  && e->button() == TQt::MidButton ) {
350  m_smartPaste = true;
351  }
352 
353  KLineEdit::mouseReleaseEvent( e );
354  m_smartPaste = false;
355 }
356 
357 void AddresseeLineEdit::dropEvent( TQDropEvent *e )
358 {
359  KURL::List uriList;
360  if ( !isReadOnly() ) {
361  if ( KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) {
362  TQString contents = text();
363  // remove trailing white space and comma
364  int eot = contents.length();
365  while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() )
366  eot--;
367  if ( eot == 0 )
368  contents = TQString();
369  else if ( contents[ eot - 1 ] == ',' ) {
370  eot--;
371  contents.truncate( eot );
372  }
373  bool mailtoURL = false;
374  // append the mailto URLs
375  for ( KURL::List::Iterator it = uriList.begin();
376  it != uriList.end(); ++it ) {
377  if ( !contents.isEmpty() )
378  contents.append( ", " );
379  KURL u( *it );
380  if ( u.protocol() == "mailto" ) {
381  mailtoURL = true;
382  contents.append( (*it).path() );
383  }
384  }
385  if ( mailtoURL ) {
386  setText( contents );
387  setEdited( true );
388  return;
389  }
390  } else {
391  // Let's see if this drop contains a comma separated list of emails
392  TQString dropData = TQString::fromUtf8( e->encodedData( "text/plain" ) );
393  TQStringList addrs = splitEmailAddrList( dropData );
394  if ( addrs.count() > 0 ) {
395  setText( normalizeAddressesAndDecodeIDNs( dropData ) );
396  setEdited( true );
397  return;
398  }
399  }
400  }
401 
402  if ( m_useCompletion )
403  m_smartPaste = true;
404  TQLineEdit::dropEvent( e );
405  m_smartPaste = false;
406 }
407 
408 void AddresseeLineEdit::cursorAtEnd()
409 {
410  setCursorPosition( text().length() );
411 }
412 
413 void AddresseeLineEdit::enableCompletion( bool enable )
414 {
415  m_useCompletion = enable;
416 }
417 
418 void AddresseeLineEdit::doCompletion( bool ctrlT )
419 {
420  m_lastSearchMode = ctrlT;
421 
422  TDEGlobalSettings::Completion mode = completionMode();
423 
424  if ( mode == TDEGlobalSettings::CompletionNone )
425  return;
426 
427  if ( s_addressesDirty ) {
428  loadContacts(); // read from local address book
429  s_completion->setOrder( completionOrder() );
430  }
431 
432  // cursor at end of string - or Ctrl+T pressed for substring completion?
433  if ( ctrlT ) {
434  const TQStringList completions = getAdjustedCompletionItems( false );
435 
436  if ( completions.count() > 1 )
437  ; //m_previousAddresses = prevAddr;
438  else if ( completions.count() == 1 )
439  setText( m_previousAddresses + completions.first().stripWhiteSpace() );
440 
441  setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found
442 
443  cursorAtEnd();
444  setCompletionMode( mode ); //set back to previous mode
445  return;
446  }
447 
448 
449  switch ( mode ) {
450  case TDEGlobalSettings::CompletionPopupAuto:
451  {
452  if ( m_searchString.isEmpty() )
453  break;
454  }
455 
456  case TDEGlobalSettings::CompletionPopup:
457  {
458  const TQStringList items = getAdjustedCompletionItems( true );
459  setCompletedItems( items, false );
460  break;
461  }
462 
463  case TDEGlobalSettings::CompletionShell:
464  {
465  TQString match = s_completion->makeCompletion( m_searchString );
466  if ( !match.isNull() && match != m_searchString ) {
467  setText( m_previousAddresses + match );
468  setEdited( true );
469  cursorAtEnd();
470  }
471  break;
472  }
473 
474  case TDEGlobalSettings::CompletionMan: // Short-Auto in fact
475  case TDEGlobalSettings::CompletionAuto:
476  {
477  //force autoSuggest in KLineEdit::keyPressed or setCompletedText will have no effect
478  setCompletionMode( completionMode() );
479 
480  if ( !m_searchString.isEmpty() ) {
481 
482  //if only our \" is left, remove it since user has not typed it either
483  if ( m_searchExtended && m_searchString == "\"" ){
484  m_searchExtended = false;
485  m_searchString = TQString();
486  setText( m_previousAddresses );
487  break;
488  }
489 
490  TQString match = s_completion->makeCompletion( m_searchString );
491 
492  if ( !match.isEmpty() ) {
493  if ( match != m_searchString ) {
494  TQString adds = m_previousAddresses + match;
495  setCompletedText( adds );
496  }
497  } else {
498  if ( !m_searchString.startsWith( "\"" ) ) {
499  //try with quoted text, if user has not type one already
500  match = s_completion->makeCompletion( "\"" + m_searchString );
501  if ( !match.isEmpty() && match != m_searchString ) {
502  m_searchString = "\"" + m_searchString;
503  m_searchExtended = true;
504  setText( m_previousAddresses + m_searchString );
505  setCompletedText( m_previousAddresses + match );
506  }
507  } else if ( m_searchExtended ) {
508  //our added \" does not work anymore, remove it
509  m_searchString = m_searchString.mid( 1 );
510  m_searchExtended = false;
511  setText( m_previousAddresses + m_searchString );
512  //now try again
513  match = s_completion->makeCompletion( m_searchString );
514  if ( !match.isEmpty() && match != m_searchString ) {
515  TQString adds = m_previousAddresses + match;
516  setCompletedText( adds );
517  }
518  }
519  }
520  }
521  break;
522  }
523 
524  case TDEGlobalSettings::CompletionNone:
525  default: // fall through
526  break;
527  }
528 }
529 
530 void AddresseeLineEdit::slotPopupCompletion( const TQString& completion )
531 {
532  setText( m_previousAddresses + completion.stripWhiteSpace() );
533  cursorAtEnd();
534 // slotMatched( m_previousAddresses + completion );
535  updateSearchString();
536 }
537 
538 void AddresseeLineEdit::slotReturnPressed( const TQString& item )
539 {
540  Q_UNUSED( item );
541  TQListBoxItem* i = completionBox()->selectedItem();
542  if ( i != 0 )
543  slotPopupCompletion( i->text() );
544 }
545 
546 void AddresseeLineEdit::loadContacts()
547 {
548  s_completion->clear();
549  s_completionItemMap->clear();
550  s_addressesDirty = false;
551  //m_contactMap.clear();
552 
553  TQApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while
554 
555  TDEConfig config( "kpimcompletionorder" ); // The weights for non-imap tdeabc resources is there.
556  config.setGroup( "CompletionWeights" );
557 
558  TDEABC::AddressBook *addressBook = TDEABC::StdAddressBook::self( true );
559  // Can't just use the addressbook's iterator, we need to know which subresource
560  // is behind which contact.
561  TQPtrList<TDEABC::Resource> resources( addressBook->resources() );
562  for( TQPtrListIterator<TDEABC::Resource> resit( resources ); *resit; ++resit ) {
563  TDEABC::Resource* resource = *resit;
564  KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
565  if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource
566  const TQMap<TQString, TQString> uidToResourceMap = resabc->uidToResourceMap();
567  TDEABC::Resource::Iterator it;
568  for ( it = resource->begin(); it != resource->end(); ++it ) {
569  TQString uid = (*it).uid();
570  TQMap<TQString, TQString>::const_iterator wit = uidToResourceMap.find( uid );
571  const TQString subresourceLabel = resabc->subresourceLabel( *wit );
572  const int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
573  const int idx = addCompletionSource( subresourceLabel, weight );
574 
575  //kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl;
576  addContact( *it, weight, idx );
577  }
578  } else { // KABC non-imap resource
579  int weight = config.readNumEntry( resource->identifier(), 60 );
580  int sourceIndex = addCompletionSource( resource->resourceName(), weight );
581  TDEABC::Resource::Iterator it;
582  for ( it = resource->begin(); it != resource->end(); ++it ) {
583  addContact( *it, weight, sourceIndex );
584  }
585  }
586  }
587 
588 #ifndef TDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above
589  int weight = config.readNumEntry( "DistributionLists", 60 );
590  TDEABC::DistributionListManager manager( addressBook );
591  manager.load();
592  const TQStringList distLists = manager.listNames();
593  TQStringList::const_iterator listIt;
594  int idx = addCompletionSource( i18n( "Distribution Lists" ) );
595  for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
596 
597  //for TDEGlobalSettings::CompletionAuto
598  addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx );
599 
600  //for CompletionShell, CompletionPopup
601  TQStringList sl( (*listIt).simplifyWhiteSpace() );
602  addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx, &sl );
603 
604  }
605 #endif
606 
607  TQApplication::restoreOverrideCursor();
608 
609  if ( !m_addressBookConnected ) {
610  connect( addressBook, TQ_SIGNAL( addressBookChanged( AddressBook* ) ), TQ_SLOT( loadContacts() ) );
611  m_addressBookConnected = true;
612  }
613 }
614 
615 void AddresseeLineEdit::addContact( const TDEABC::Addressee& addr, int weight, int source )
616 {
617 #ifdef TDEPIM_NEW_DISTRLISTS
618  if ( KPIM::DistributionList::isDistributionList( addr ) ) {
619  //kdDebug(5300) << "AddresseeLineEdit::addContact() distribution list \"" << addr.formattedName() << "\" weight=" << weight << endl;
620 
621  if ( m_allowDistLists ) {
622  //for CompletionAuto
623  addCompletionItem( addr.formattedName(), weight, source );
624 
625  //for CompletionShell, CompletionPopup
626  TQStringList sl( addr.formattedName() );
627  addCompletionItem( addr.formattedName(), weight, source, &sl );
628  }
629 
630  return;
631  }
632 #endif
633  //m_contactMap.insert( addr.realName(), addr );
634  const TQStringList emails = addr.emails();
635  TQStringList::ConstIterator it;
636  const int prefEmailWeight = 1; //increment weight by prefEmailWeight
637  int isPrefEmail = prefEmailWeight; //first in list is preferredEmail
638  for ( it = emails.begin(); it != emails.end(); ++it ) {
639  //TODO: highlight preferredEmail
640  const TQString email( (*it) );
641  const TQString givenName = addr.givenName();
642  const TQString familyName= addr.familyName();
643  const TQString nickName = addr.nickName();
644  const TQString domain = email.mid( email.find( '@' ) + 1 );
645  TQString fullEmail = addr.fullEmail( email );
646  //TODO: let user decide what fields to use in lookup, e.g. company, city, ...
647 
648  //for CompletionAuto
649  if ( givenName.isEmpty() && familyName.isEmpty() ) {
650  addCompletionItem( fullEmail, weight + isPrefEmail, source ); // use whatever is there
651  } else {
652  const TQString byFirstName= "\"" + givenName + " " + familyName + "\" <" + email + ">";
653  const TQString byLastName = "\"" + familyName + ", " + givenName + "\" <" + email + ">";
654  addCompletionItem( byFirstName, weight + isPrefEmail, source );
655  addCompletionItem( byLastName, weight + isPrefEmail, source );
656  }
657 
658  addCompletionItem( email, weight + isPrefEmail, source );
659 
660  if ( !nickName.isEmpty() ){
661  const TQString byNick = "\"" + nickName + "\" <" + email + ">";
662  addCompletionItem( byNick, weight + isPrefEmail, source );
663  }
664 
665  if ( !domain.isEmpty() ){
666  const TQString byDomain = "\"" + domain + " " + familyName + " " + givenName + "\" <" + email + ">";
667  addCompletionItem( byDomain, weight + isPrefEmail, source );
668  }
669 
670  //for CompletionShell, CompletionPopup
671  TQStringList keyWords;
672  const TQString realName = addr.realName();
673 
674  if ( !givenName.isEmpty() && !familyName.isEmpty() ) {
675  keyWords.append( givenName + " " + familyName );
676  keyWords.append( familyName + " " + givenName );
677  keyWords.append( familyName + ", " + givenName);
678  }else if ( !givenName.isEmpty() )
679  keyWords.append( givenName );
680  else if ( !familyName.isEmpty() )
681  keyWords.append( familyName );
682 
683  if ( !nickName.isEmpty() )
684  keyWords.append( nickName );
685 
686  if ( !realName.isEmpty() )
687  keyWords.append( realName );
688 
689  if ( !domain.isEmpty() )
690  keyWords.append( domain );
691 
692  keyWords.append( email );
693 
694  /* KMailCompletion does not have knowledge about identities, it stores emails and
695  * keywords for each email. KMailCompletion::allMatches does a lookup on the
696  * keywords and returns an ordered list of emails. In order to get the preferred
697  * email before others for each identity we use this little trick.
698  * We remove the <blank> in getAdjustedCompletionItems.
699  */
700  if ( isPrefEmail == prefEmailWeight )
701  fullEmail.replace( " <", " <" );
702 
703  addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords );
704  isPrefEmail = 0;
705 
706 #if 0
707  int len = (*it).length();
708  if ( len == 0 ) continue;
709  if( '\0' == (*it)[len-1] )
710  --len;
711  const TQString tmp = (*it).left( len );
712  const TQString fullEmail = addr.fullEmail( tmp );
713  //kdDebug(5300) << "AddresseeLineEdit::addContact() \"" << fullEmail << "\" weight=" << weight << endl;
714  addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source );
715  // Try to guess the last name: if found, we add an extra
716  // entry to the list to make sure completion works even
717  // if the user starts by typing in the last name.
718  TQString name( addr.realName().simplifyWhiteSpace() );
719  if( name.endsWith("\"") )
720  name.truncate( name.length()-1 );
721  if( name.startsWith("\"") )
722  name = name.mid( 1 );
723 
724  // While we're here also add "email (full name)" for completion on the email
725  if ( !name.isEmpty() )
726  addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source );
727 
728  bool bDone = false;
729  int i = -1;
730  while( ( i = name.findRev(' ') ) > 1 && !bDone ) {
731  TQString sLastName( name.mid( i+1 ) );
732  if( ! sLastName.isEmpty() &&
733  2 <= sLastName.length() && // last names must be at least 2 chars long
734  ! sLastName.endsWith(".") ) { // last names must not end with a dot (like "Jr." or "Sr.")
735  name.truncate( i );
736  if( !name.isEmpty() ){
737  sLastName.prepend( "\"" );
738  sLastName.append( ", " + name + "\" <" );
739  }
740  TQString sExtraEntry( sLastName );
741  sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
742  sExtraEntry.append( ">" );
743  //kdDebug(5300) << "AddresseeLineEdit::addContact() added extra \"" << sExtraEntry.simplifyWhiteSpace() << "\" weight=" << weight << endl;
744  addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source );
745  bDone = true;
746  }
747  if( !bDone ) {
748  name.truncate( i );
749  if( name.endsWith("\"") )
750  name.truncate( name.length()-1 );
751  }
752  }
753 #endif
754  }
755 }
756 
757 void AddresseeLineEdit::addCompletionItem( const TQString& string, int weight, int completionItemSource, const TQStringList * keyWords )
758 {
759  // Check if there is an exact match for item already, and use the max weight if so.
760  // Since there's no way to get the information from TDECompletion, we have to keep our own TQMap
761  CompletionItemsMap::iterator it = s_completionItemMap->find( string );
762  if ( it != s_completionItemMap->end() ) {
763  weight = TQMAX( ( *it ).first, weight );
764  ( *it ).first = weight;
765  } else {
766  s_completionItemMap->insert( string, qMakePair( weight, completionItemSource ) );
767  }
768  if ( keyWords == 0 )
769  s_completion->addItem( string, weight );
770  else
771  s_completion->addItemWithKeys( string, weight, keyWords );
772 }
773 
774 void AddresseeLineEdit::slotStartLDAPLookup()
775 {
776  TDEGlobalSettings::Completion mode = completionMode();
777 
778  if ( mode == TDEGlobalSettings::CompletionNone )
779  return;
780 
781  if ( !s_LDAPSearch->isAvailable() ) {
782  return;
783  }
784  if ( s_LDAPLineEdit != this )
785  return;
786 
787  startLoadingLDAPEntries();
788 }
789 
790 void AddresseeLineEdit::stopLDAPLookup()
791 {
792  s_LDAPSearch->cancelSearch();
793  s_LDAPLineEdit = NULL;
794 }
795 
796 void AddresseeLineEdit::startLoadingLDAPEntries()
797 {
798  TQString s( *s_LDAPText );
799  // TODO cache last?
800  TQString prevAddr;
801  int n = s.findRev( ',' );
802  if ( n >= 0 ) {
803  prevAddr = s.left( n + 1 ) + ' ';
804  s = s.mid( n + 1, 255 ).stripWhiteSpace();
805  }
806 
807  if ( s.isEmpty() )
808  return;
809 
810  //loadContacts(); // TODO reuse these?
811  s_LDAPSearch->startSearch( s );
812 }
813 
814 void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
815 {
816  if ( adrs.isEmpty() || s_LDAPLineEdit != this )
817  return;
818 
819  for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
820  TDEABC::Addressee addr;
821  addr.setNameFromString( (*it).name );
822  addr.setEmails( (*it).email );
823 
824  if ( !s_ldapClientToCompletionSourceMap->contains( (*it).clientNumber ) )
825  updateLDAPWeights(); // we got results from a new source, so update the completion sources
826 
827  addContact( addr, (*it).completionWeight, (*s_ldapClientToCompletionSourceMap)[ (*it ).clientNumber ] );
828  }
829 
830  if ( (hasFocus() || completionBox()->hasFocus() )
831  && completionMode() != TDEGlobalSettings::CompletionNone
832  && completionMode() != TDEGlobalSettings::CompletionShell ) {
833  setText( m_previousAddresses + m_searchString );
834  // only complete again if the user didn't change the selection while we were waiting
835  // otherwise the completion box will be closed
836  if ( m_searchString.stripWhiteSpace() != completionBox()->currentText().stripWhiteSpace() )
837  doCompletion( m_lastSearchMode );
838  }
839 }
840 
841 void AddresseeLineEdit::setCompletedItems( const TQStringList& items, bool autoSuggest )
842 {
843  TDECompletionBox* completionBox = this->completionBox();
844 
845  if ( !items.isEmpty() &&
846  !(items.count() == 1 && m_searchString == items.first()) )
847  {
848  TQString oldCurrentText = completionBox->currentText();
849  TQListBoxItem *itemUnderMouse = completionBox->itemAt(
850  completionBox->viewport()->mapFromGlobal(TQCursor::pos()) );
851  TQString oldTextUnderMouse;
852  TQPoint oldPosOfItemUnderMouse;
853  if ( itemUnderMouse ) {
854  oldTextUnderMouse = itemUnderMouse->text();
855  oldPosOfItemUnderMouse = completionBox->itemRect( itemUnderMouse ).topLeft();
856  }
857 
858  completionBox->setItems( items );
859 
860  if ( !completionBox->isVisible() ) {
861  if ( !m_searchString.isEmpty() )
862  completionBox->setCancelledText( m_searchString );
863  completionBox->popup();
864  // we have to install the event filter after popup(), since that
865  // calls show(), and that's where TDECompletionBox installs its filter.
866  // We want to be first, though, so do it now.
867  if ( s_completion->order() == TDECompletion::Weighted )
868  tqApp->installEventFilter( this );
869  }
870 
871  // Try to re-select what was selected before, otherrwise use the first
872  // item, if there is one
873  TQListBoxItem* item = 0;
874  if ( oldCurrentText.isEmpty()
875  || ( item = completionBox->findItem( oldCurrentText ) ) == 0 ) {
876  item = completionBox->item( 1 );
877  }
878  if ( item )
879  {
880  if ( itemUnderMouse ) {
881  TQListBoxItem *newItemUnderMouse = completionBox->findItem( oldTextUnderMouse );
882  // if the mouse was over an item, before, but now that's elsewhere,
883  // move the cursor, so folks don't accidently click the wrong item
884  if ( newItemUnderMouse ) {
885  TQRect r = completionBox->itemRect( newItemUnderMouse );
886  TQPoint target = r.topLeft();
887  if ( oldPosOfItemUnderMouse != target ) {
888  target.setX( target.x() + r.width()/2 );
889  TQCursor::setPos( completionBox->viewport()->mapToGlobal(target) );
890  }
891  }
892  }
893  completionBox->blockSignals( true );
894  completionBox->setSelected( item, true );
895  completionBox->setCurrentItem( item );
896  completionBox->ensureCurrentVisible();
897 
898  completionBox->blockSignals( false );
899  }
900 
901  if ( autoSuggest )
902  {
903  int index = items.first().find( m_searchString );
904  TQString newText = items.first().mid( index );
905  setUserSelection(false);
906  setCompletedText(newText,true);
907  }
908  }
909  else
910  {
911  if ( completionBox && completionBox->isVisible() ) {
912  completionBox->hide();
913  completionBox->setItems( TQStringList() );
914  }
915  }
916 }
917 
918 TQPopupMenu* AddresseeLineEdit::createPopupMenu()
919 {
920  TQPopupMenu *menu = KLineEdit::createPopupMenu();
921  if ( !menu )
922  return 0;
923 
924  if ( m_useCompletion ){
925  menu->setItemVisible( ShortAutoCompletion, false );
926  menu->setItemVisible( PopupAutoCompletion, false );
927  menu->insertItem( i18n( "Configure Completion Order..." ),
928  this, TQ_SLOT( slotEditCompletionOrder() ) );
929  }
930  return menu;
931 }
932 
933 void AddresseeLineEdit::slotEditCompletionOrder()
934 {
935  init(); // for s_LDAPSearch
936  CompletionOrderEditor editor( s_LDAPSearch, this );
937  editor.exec();
938  if ( m_useCompletion ) {
939  updateLDAPWeights();
940  s_addressesDirty = true;
941  }
942 }
943 
944 void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
945 {
946  if ( m_useCompletion )
947  s_addressesDirty = true;
948 }
949 
950 void KPIM::AddresseeLineEdit::slotUserCancelled( const TQString& cancelText )
951 {
952  if ( s_LDAPSearch && s_LDAPLineEdit == this )
953  stopLDAPLookup();
954  userCancelled( m_previousAddresses + cancelText ); // in KLineEdit
955 }
956 
957 void AddresseeLineEdit::updateSearchString()
958 {
959  m_searchString = text();
960 
961  int n = -1;
962  bool inQuote = false;
963  uint searchStringLength = m_searchString.length();
964  for ( uint i = 0; i < searchStringLength; ++i ) {
965  if ( m_searchString[ i ] == '"' ) {
966  inQuote = !inQuote;
967  }
968  if ( m_searchString[ i ] == '\\' &&
969  (i + 1) < searchStringLength && m_searchString[ i + 1 ] == '"' ) {
970  ++i;
971  }
972  if ( inQuote ) {
973  continue;
974  }
975  if ( i < searchStringLength &&
976  ( m_searchString[ i ] == ',' ||
977  ( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) ) ) {
978  n = i;
979  }
980  }
981 
982  if ( n >= 0 ) {
983  ++n; // Go past the ","
984 
985  int len = m_searchString.length();
986 
987  // Increment past any whitespace...
988  while ( n < len && m_searchString[ n ].isSpace() )
989  ++n;
990 
991  m_previousAddresses = m_searchString.left( n );
992  m_searchString = m_searchString.mid( n ).stripWhiteSpace();
993  } else {
994  m_previousAddresses = TQString();
995  }
996 }
997 
998 void KPIM::AddresseeLineEdit::slotCompletion()
999 {
1000  // Called by KLineEdit's keyPressEvent for CompletionModes Auto,Popup -> new text, update search string
1001  // not called for CompletionShell, this is been taken care of in AddresseeLineEdit::keyPressEvent
1002  updateSearchString();
1003  if ( completionBox() )
1004  completionBox()->setCancelledText( m_searchString );
1005  doCompletion( false );
1006 }
1007 
1008 // not cached, to make sure we get an up-to-date value when it changes
1009 TDECompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
1010 {
1011  TDEConfig config( "kpimcompletionorder" );
1012  config.setGroup( "General" );
1013  const TQString order = config.readEntry( "CompletionOrder", "Weighted" );
1014 
1015  if ( order == "Weighted" )
1016  return TDECompletion::Weighted;
1017  else
1018  return TDECompletion::Sorted;
1019 }
1020 
1021 int KPIM::AddresseeLineEdit::addCompletionSource( const TQString &source, int weight )
1022 {
1023  TQMap<TQString,int>::iterator it = s_completionSourceWeights->find( source );
1024  if ( it == s_completionSourceWeights->end() )
1025  s_completionSourceWeights->insert( source, weight );
1026  else
1027  (*s_completionSourceWeights)[source] = weight;
1028 
1029  int sourceIndex = s_completionSources->findIndex( source );
1030  if ( sourceIndex == -1 ) {
1031  s_completionSources->append( source );
1032  return s_completionSources->size() - 1;
1033  }
1034  else
1035  return sourceIndex;
1036 }
1037 
1038 bool KPIM::AddresseeLineEdit::eventFilter(TQObject *obj, TQEvent *e)
1039 {
1040  if ( obj == completionBox() ) {
1041  if ( e->type() == TQEvent::MouseButtonPress ||
1042  e->type() == TQEvent::MouseMove ||
1043  e->type() == TQEvent::MouseButtonRelease ||
1044  e->type() == TQEvent::MouseButtonDblClick ) {
1045  TQMouseEvent* me = static_cast<TQMouseEvent*>( e );
1046  // find list box item at the event position
1047  TQListBoxItem *item = completionBox()->itemAt( me->pos() );
1048  if ( !item ) {
1049  // In the case of a mouse move outside of the box we don't want
1050  // the parent to fuzzy select a header by mistake.
1051  bool eat = e->type() == TQEvent::MouseMove;
1052  return eat;
1053  }
1054  // avoid selection of headers on button press, or move or release while
1055  // a button is pressed
1056  if ( e->type() == TQEvent::MouseButtonPress
1057  || me->state() & TQt::LeftButton || me->state() & TQt::MidButton
1058  || me->state() & TQt::RightButton ) {
1059  if ( itemIsHeader(item) ) {
1060  return true; // eat the event, we don't want anything to happen
1061  } else {
1062  // if we are not on one of the group heading, make sure the item
1063  // below or above is selected, not the heading, inadvertedly, due
1064  // to fuzzy auto-selection from TQListBox
1065  completionBox()->setCurrentItem( item );
1066  completionBox()->setSelected( completionBox()->index( item ), true );
1067  if ( e->type() == TQEvent::MouseMove )
1068  return true; // avoid fuzzy selection behavior
1069  }
1070  }
1071  }
1072  }
1073  if ( ( obj == this ) &&
1074  ( e->type() == TQEvent::AccelOverride ) ) {
1075  TQKeyEvent *ke = static_cast<TQKeyEvent*>( e );
1076  if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
1077  ke->accept();
1078  return true;
1079  }
1080  }
1081  if ( ( obj == this ) &&
1082  ( e->type() == TQEvent::KeyPress || e->type() == TQEvent::KeyRelease ) &&
1083  completionBox()->isVisible() ) {
1084  TQKeyEvent *ke = static_cast<TQKeyEvent*>( e );
1085  int currentIndex = completionBox()->currentItem();
1086  if ( currentIndex < 0 ) {
1087  return true;
1088  }
1089 
1090  if ( ke->key() == Key_Up ) {
1091  //kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl;
1092  // figure out if the item we would be moving to is one we want
1093  // to ignore. If so, go one further
1094  TQListBoxItem *itemAbove = completionBox()->item( currentIndex );
1095  if ( itemAbove && itemIsHeader(itemAbove) ) {
1096  // there is a header above us, check if there is even further up
1097  // and if so go one up, so it'll be selected
1098  if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) {
1099  //kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl;
1100  completionBox()->setCurrentItem( itemAbove->prev() );
1101  completionBox()->setSelected( currentIndex - 1, true );
1102  } else if ( currentIndex == 0 ) {
1103  // nothing to skip to, let's stay where we are, but make sure the
1104  // first header becomes visible, if we are the first real entry
1105  completionBox()->ensureVisible( 0, 0 );
1106  //Kolab issue 2941: be sure to add email even if it's the only element.
1107  if ( itemIsHeader( completionBox()->item( currentIndex ) ) ) {
1108  currentIndex++;
1109  }
1110  completionBox()->setCurrentItem( itemAbove );
1111  completionBox()->setSelected( currentIndex, true );
1112  }
1113  return true;
1114  }
1115  } else if ( ke->key() == Key_Down ) {
1116  // same strategy for downwards
1117  //kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl;
1118  TQListBoxItem *itemBelow = completionBox()->item( currentIndex );
1119  if ( itemBelow && itemIsHeader( itemBelow ) ) {
1120  if ( completionBox()->item( currentIndex + 1 ) ) {
1121  //kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl;
1122  completionBox()->setCurrentItem( itemBelow->next() );
1123  completionBox()->setSelected( currentIndex + 1, true );
1124  } else {
1125  // nothing to skip to, let's stay where we are
1126  completionBox()->setCurrentItem( itemBelow );
1127  completionBox()->setSelected( currentIndex, true );
1128  }
1129  return true;
1130  }
1131  // special case of the last and only item in the list needing selection
1132  if ( !itemBelow && currentIndex == 1 ) {
1133  completionBox()->setSelected( currentIndex, true );
1134  }
1135  // special case of the initial selection, which is unfortunately a header.
1136  // Setting it to selected tricks TDECompletionBox into not treating is special
1137  // and selecting making it current, instead of the one below.
1138  TQListBoxItem *item = completionBox()->item( currentIndex );
1139  if ( item && itemIsHeader(item) ) {
1140  completionBox()->setSelected( currentIndex, true );
1141  }
1142  } else if ( e->type() == TQEvent::KeyRelease &&
1143  ( ke->key() == Key_Tab || ke->key() == Key_Backtab ) ) {
1144  //kdDebug() << "EVENTFILTER: Key_Tab. currentIndex=" << currentIndex << endl;
1146  TQListBoxItem *myHeader = 0;
1147  const int iterationstep = ke->key() == Key_Tab ? 1 : -1;
1148  int i = TQMIN( TQMAX( currentIndex - iterationstep, 0 ), completionBox()->count() - 1 );
1149  while ( i>=0 ) {
1150  if ( itemIsHeader( completionBox()->item(i) ) ) {
1151  myHeader = completionBox()->item( i );
1152  break;
1153  }
1154  i--;
1155  }
1156  Q_ASSERT( myHeader ); // we should always be able to find a header
1157 
1158  // find the next header (searching backwards, for Key_Backtab)
1159  TQListBoxItem *nextHeader = 0;
1160  // when iterating forward, start at the currentindex, when backwards,
1161  // one up from our header, or at the end
1162  uint j;
1163  if ( ke->key() == Key_Tab ) {
1164  j = currentIndex;
1165  } else {
1166  i = completionBox()->index( myHeader );
1167  if ( i == 0 ) {
1168  j = completionBox()->count() - 1;
1169  } else {
1170  j = ( i - 1 ) % completionBox()->count();
1171  }
1172  }
1173  while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
1174  if ( itemIsHeader(nextHeader) ) {
1175  break;
1176  }
1177  j = (j + iterationstep) % completionBox()->count();
1178  }
1179  if ( nextHeader && nextHeader != myHeader ) {
1180  TQListBoxItem *item = completionBox()->item( j + 1 );
1181  if ( item && !itemIsHeader(item) ) {
1182  completionBox()->setSelected( item, true );
1183  completionBox()->setCurrentItem( item );
1184  completionBox()->ensureCurrentVisible();
1185  }
1186  }
1187  return true;
1188  }
1189  }
1190  return ClickLineEdit::eventFilter( obj, e );
1191 }
1192 
1193 class SourceWithWeight {
1194  public:
1195  int weight; // the weight of the source
1196  TQString sourceName; // the name of the source, e.g. "LDAP Server"
1197  int index; // index into s_completionSources
1198 
1199  bool operator< ( const SourceWithWeight &other ) {
1200  if ( weight > other.weight )
1201  return true;
1202  if ( weight < other.weight )
1203  return false;
1204  return sourceName < other.sourceName;
1205  }
1206 };
1207 
1208 const TQStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
1209 {
1210  TQStringList items = fullSearch ?
1211  s_completion->allMatches( m_searchString )
1212  : s_completion->substringCompletion( m_searchString );
1213 
1214  // For weighted mode, the algorithm is the following:
1215  // In the first loop, we add each item to its section (there is one section per completion source)
1216  // We also add spaces in front of the items.
1217  // The sections are appended to the items list.
1218  // In the second loop, we then walk through the sections and add all the items in there to the
1219  // sorted item list, which is the final result.
1220  //
1221  // The algo for non-weighted mode is different.
1222 
1223  int lastSourceIndex = -1;
1224  unsigned int i = 0;
1225 
1226  // Maps indices of the items list, which are section headers/source items,
1227  // to a TQStringList which are the items of that section/source.
1228  TQMap<int, TQStringList> sections;
1229  TQStringList sortedItems;
1230  for ( TQStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
1231  CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it);
1232  if ( cit == s_completionItemMap->end() )
1233  continue;
1234  int idx = (*cit).second;
1235 
1236  if ( s_completion->order() == TDECompletion::Weighted ) {
1237  if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
1238  const TQString sourceLabel( (*s_completionSources)[idx] );
1239  if ( sections.find(idx) == sections.end() ) {
1240  items.insert( it, sourceLabel );
1241  }
1242  lastSourceIndex = idx;
1243  }
1244  (*it) = (*it).prepend( s_completionItemIndentString );
1245  // remove preferred email sort <blank> added in addContact()
1246  (*it).replace( " <", " <" );
1247  }
1248  sections[idx].append( *it );
1249 
1250  if ( s_completion->order() == TDECompletion::Sorted ) {
1251  sortedItems.append( *it );
1252  }
1253  }
1254 
1255  if ( s_completion->order() == TDECompletion::Weighted ) {
1256 
1257  // Sort the sections
1258  TQValueList<SourceWithWeight> sourcesAndWeights;
1259  for ( uint i = 0; i < s_completionSources->size(); i++ ) {
1260  SourceWithWeight sww;
1261  sww.sourceName = (*s_completionSources)[i];
1262  sww.weight = (*s_completionSourceWeights)[sww.sourceName];
1263  sww.index = i;
1264  sourcesAndWeights.append( sww );
1265  }
1266  qHeapSort( sourcesAndWeights );
1267 
1268  // Add the sections and their items to the final sortedItems result list
1269  for( uint i = 0; i < sourcesAndWeights.size(); i++ ) {
1270  TQStringList sectionItems = sections[sourcesAndWeights[i].index];
1271  if ( !sectionItems.isEmpty() ) {
1272  sortedItems.append( sourcesAndWeights[i].sourceName );
1273  TQStringList sectionItems = sections[sourcesAndWeights[i].index];
1274  for ( TQStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() );
1275  sit != send; ++sit ) {
1276  sortedItems.append( *sit );
1277  }
1278  }
1279  }
1280  } else {
1281  sortedItems.sort();
1282  }
1283  return sortedItems;
1284 }
1285 #include "addresseelineedit.moc"
This class provides a KLineEdit which contains a greyed-out hinting text as long as the user didn't e...
Definition: clicklineedit.h:37
KMailCompletion allows lookup of email addresses by keyword.
This class is internal.
Definition: ldapclient.h:247
This class is the implementation of subfolder resources for KABC.
Definition: resourceabc.h:45
virtual TQString subresourceLabel(const TQString &) const =0
Label for a given subresource.
virtual int subresourceCompletionWeight(const TQString &) const =0
Completion weight for a given subresource.
virtual TQMap< TQString, TQString > uidToResourceMap() const =0
Get the UID to subresource map.
TDEPIM classes for drag and drop of mails.