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 <tdestandarddirs.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
62using namespace KPIM;
63
64KMailCompletion * AddresseeLineEdit::s_completion = 0L;
65KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L;
66TQStringList* AddresseeLineEdit::s_completionSources = 0L;
67bool AddresseeLineEdit::s_addressesDirty = false;
68TQTimer* AddresseeLineEdit::s_LDAPTimer = 0L;
69KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L;
70TQString* AddresseeLineEdit::s_LDAPText = 0L;
71AddresseeLineEdit* 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.
75TQMap<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
80TQMap<int, int>* AddresseeLineEdit::s_ldapClientToCompletionSourceMap = 0;
81
82static KStaticDeleter<KMailCompletion> completionDeleter;
83static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
84static KStaticDeleter<TQTimer> ldapTimerDeleter;
85static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
86static KStaticDeleter<TQString> ldapTextDeleter;
87static KStaticDeleter<TQStringList> completionSourcesDeleter;
88static KStaticDeleter<TQMap<TQString,int> > completionSourceWeightsDeleter;
89static KStaticDeleter<TQMap<int, int> > ldapClientToCompletionSourceMapDeleter;
90
91// needs to be unique, but the actual name doesn't matter much
92static 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
103static const TQString s_completionItemIndentString = " ";
104
105static bool itemIsHeader( const TQListBoxItem* item )
106{
107 return item && !item->text().startsWith( s_completionItemIndentString );
108}
109
110
111
112AddresseeLineEdit::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
129void 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
142void 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
193AddresseeLineEdit::~AddresseeLineEdit()
194{
195 if ( s_LDAPSearch && s_LDAPLineEdit == this )
196 stopLDAPLookup();
197}
198
199void AddresseeLineEdit::setFont( const TQFont& font )
200{
201 KLineEdit::setFont( font );
202 if ( m_useCompletion )
203 completionBox()->setFont( font );
204}
205
206void AddresseeLineEdit::allowSemiColonAsSeparator( bool useSemiColonAsSeparator )
207{
208 m_useSemiColonAsSeparator = useSemiColonAsSeparator;
209}
210
211void AddresseeLineEdit::allowDistributionLists( bool allowDistLists )
212{
213 m_allowDistLists = allowDistLists;
214}
215
216void 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
262void 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
329void AddresseeLineEdit::setText( const TQString & text )
330{
331 ClickLineEdit::setText( text.stripWhiteSpace() );
332}
333
334void AddresseeLineEdit::paste()
335{
336 if ( m_useCompletion )
337 m_smartPaste = true;
338
339 KLineEdit::paste();
340 m_smartPaste = false;
341}
342
343void 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
357void 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
408void AddresseeLineEdit::cursorAtEnd()
409{
410 setCursorPosition( text().length() );
411}
412
413void AddresseeLineEdit::enableCompletion( bool enable )
414{
415 m_useCompletion = enable;
416}
417
418void 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
530void AddresseeLineEdit::slotPopupCompletion( const TQString& completion )
531{
532 setText( m_previousAddresses + completion.stripWhiteSpace() );
533 cursorAtEnd();
534// slotMatched( m_previousAddresses + completion );
535 updateSearchString();
536}
537
538void 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
546void 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
615void 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
757void 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
774void 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
790void AddresseeLineEdit::stopLDAPLookup()
791{
792 s_LDAPSearch->cancelSearch();
793 s_LDAPLineEdit = NULL;
794}
795
796void 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
814void 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
841void 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
918TQPopupMenu* 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
933void 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
944void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
945{
946 if ( m_useCompletion )
947 s_addressesDirty = true;
948}
949
950void 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
957void 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
998void 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
1009TDECompletion::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
1021int 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
1038bool 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
1193class 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
1208const 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 TQMap< TQString, TQString > uidToResourceMap() const =0
Get the UID to subresource map.
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.
TDEPIM classes for drag and drop of mails.