|
|
|
/*
|
|
|
|
This file is part of libkdepim.
|
|
|
|
Copyright (c) 2002 Helge Deller <deller@gmx.de>
|
|
|
|
2002 Lubos Lunak <llunak@suse.cz>
|
|
|
|
2001,2003 Carsten Pfeiffer <pfeiffer@kde.org>
|
|
|
|
2001 Waldo Bastian <bastian@kde.org>
|
|
|
|
2004 Daniel Molkentin <danimo@klaralvdalens-datakonsult.se>
|
|
|
|
2004 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se>
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "addresseelineedit.h"
|
|
|
|
|
|
|
|
#include "resourceabc.h"
|
|
|
|
#include "completionordereditor.h"
|
|
|
|
#include "ldapclient.h"
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#ifdef KDEPIM_NEW_DISTRLISTS
|
|
|
|
#include "distributionlist.h"
|
|
|
|
#else
|
|
|
|
#include <kabc/distributionlist.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <kabc/stdaddressbook.h>
|
|
|
|
#include <kabc/resource.h>
|
|
|
|
#include <libemailfunctions/email.h>
|
|
|
|
|
|
|
|
#include <kcompletionbox.h>
|
|
|
|
#include <kcursor.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kstandarddirs.h>
|
|
|
|
#include <kstaticdeleter.h>
|
|
|
|
#include <kstdaccel.h>
|
|
|
|
#include <kurldrag.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
|
|
|
|
#include <tqpopupmenu.h>
|
|
|
|
#include <tqapplication.h>
|
|
|
|
#include <tqobject.h>
|
|
|
|
#include <tqptrlist.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqevent.h>
|
|
|
|
#include <tqdragobject.h>
|
|
|
|
#include <tqclipboard.h>
|
|
|
|
|
|
|
|
using namespace KPIM;
|
|
|
|
|
|
|
|
KMailCompletion * AddresseeLineEdit::s_completion = 0L;
|
|
|
|
KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L;
|
|
|
|
TQStringList* AddresseeLineEdit::s_completionSources = 0L;
|
|
|
|
bool AddresseeLineEdit::s_addressesDirty = false;
|
|
|
|
TQTimer* AddresseeLineEdit::s_LDAPTimer = 0L;
|
|
|
|
KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L;
|
|
|
|
TQString* AddresseeLineEdit::s_LDAPText = 0L;
|
|
|
|
AddresseeLineEdit* AddresseeLineEdit::s_LDAPLineEdit = 0L;
|
|
|
|
|
|
|
|
// The weights associated with the completion sources in s_completionSources.
|
|
|
|
// Both are maintained by addCompletionSource(), don't attempt to modifiy those yourself.
|
|
|
|
TQMap<TQString,int>* s_completionSourceWeights = 0;
|
|
|
|
|
|
|
|
// maps LDAP client indices to completion source indices
|
|
|
|
// the assumption that they are always the first n indices in s_completion
|
|
|
|
// does not hold when clients are added later on
|
|
|
|
TQMap<int, int>* AddresseeLineEdit::s_ldapClientToCompletionSourceMap = 0;
|
|
|
|
|
|
|
|
static KStaticDeleter<KMailCompletion> completionDeleter;
|
|
|
|
static KStaticDeleter<KPIM::CompletionItemsMap> completionItemsDeleter;
|
|
|
|
static KStaticDeleter<TQTimer> ldapTimerDeleter;
|
|
|
|
static KStaticDeleter<KPIM::LdapSearch> ldapSearchDeleter;
|
|
|
|
static KStaticDeleter<TQString> ldapTextDeleter;
|
|
|
|
static KStaticDeleter<TQStringList> completionSourcesDeleter;
|
|
|
|
static KStaticDeleter<TQMap<TQString,int> > completionSourceWeightsDeleter;
|
|
|
|
static KStaticDeleter<TQMap<int, int> > ldapClientToCompletionSourceMapDeleter;
|
|
|
|
|
|
|
|
// needs to be unique, but the actual name doesn't matter much
|
|
|
|
static TQCString newLineEditDCOPObjectName()
|
|
|
|
{
|
|
|
|
static int s_count = 0;
|
|
|
|
TQCString name( "KPIM::AddresseeLineEdit" );
|
|
|
|
if ( s_count++ ) {
|
|
|
|
name += '-';
|
|
|
|
name += TQCString().setNum( s_count );
|
|
|
|
}
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const TQString s_completionItemIndentString = " ";
|
|
|
|
|
|
|
|
static bool itemIsHeader( const TQListBoxItem* item )
|
|
|
|
{
|
|
|
|
return item && !item->text().startsWith( s_completionItemIndentString );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AddresseeLineEdit::AddresseeLineEdit( TQWidget* tqparent, bool useCompletion,
|
|
|
|
const char *name )
|
|
|
|
: ClickLineEdit( tqparent, TQString(), name ), DCOPObject( newLineEditDCOPObjectName() ),
|
|
|
|
m_useSemiColonAsSeparator( false ), m_allowDistLists( true )
|
|
|
|
{
|
|
|
|
m_useCompletion = useCompletion;
|
|
|
|
m_completionInitialized = false;
|
|
|
|
m_smartPaste = false;
|
|
|
|
m_addressBookConnected = false;
|
|
|
|
m_searchExtended = false;
|
|
|
|
|
|
|
|
init();
|
|
|
|
|
|
|
|
if ( m_useCompletion )
|
|
|
|
s_addressesDirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::updateLDAPWeights()
|
|
|
|
{
|
|
|
|
/* Add completion sources for all ldap server, 0 to n. Added first so
|
|
|
|
* that they map to the ldapclient::clientNumber() */
|
|
|
|
s_LDAPSearch->updateCompletionWeights();
|
|
|
|
TQValueList< LdapClient* > clients = s_LDAPSearch->clients();
|
|
|
|
int clientIndex = 0;
|
|
|
|
for ( TQValueList<LdapClient*>::iterator it = clients.begin(); it != clients.end(); ++it, ++clientIndex ) {
|
|
|
|
const int sourceIndex = addCompletionSource( "LDAP server: " + (*it)->server().host(), (*it)->completionWeight() );
|
|
|
|
s_ldapClientToCompletionSourceMap->insert( clientIndex, sourceIndex );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::init()
|
|
|
|
{
|
|
|
|
if ( !s_completion ) {
|
|
|
|
completionDeleter.setObject( s_completion, new KMailCompletion() );
|
|
|
|
s_completion->setOrder( completionOrder() );
|
|
|
|
s_completion->setIgnoreCase( true );
|
|
|
|
|
|
|
|
completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() );
|
|
|
|
completionSourcesDeleter.setObject( s_completionSources, new TQStringList() );
|
|
|
|
completionSourceWeightsDeleter.setObject( s_completionSourceWeights, new TQMap<TQString,int> );
|
|
|
|
ldapClientToCompletionSourceMapDeleter.setObject( s_ldapClientToCompletionSourceMap, new TQMap<int,int> );
|
|
|
|
}
|
|
|
|
// connect( s_completion, TQT_SIGNAL( match( const TQString& ) ),
|
|
|
|
// this, TQT_SLOT( slotMatched( const TQString& ) ) );
|
|
|
|
|
|
|
|
if ( m_useCompletion ) {
|
|
|
|
if ( !s_LDAPTimer ) {
|
|
|
|
ldapTimerDeleter.setObject( s_LDAPTimer, new TQTimer( 0, "ldapTimerDeleter" ) );
|
|
|
|
ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch );
|
|
|
|
ldapTextDeleter.setObject( s_LDAPText, new TQString );
|
|
|
|
}
|
|
|
|
|
|
|
|
updateLDAPWeights();
|
|
|
|
|
|
|
|
if ( !m_completionInitialized ) {
|
|
|
|
setCompletionObject( s_completion, false );
|
|
|
|
connect( this, TQT_SIGNAL( completion( const TQString& ) ),
|
|
|
|
this, TQT_SLOT( slotCompletion() ) );
|
|
|
|
connect( this, TQT_SIGNAL( returnPressed( const TQString& ) ),
|
|
|
|
this, TQT_SLOT( slotReturnPressed( const TQString& ) ) );
|
|
|
|
|
|
|
|
KCompletionBox *box = completionBox();
|
|
|
|
connect( box, TQT_SIGNAL( highlighted( const TQString& ) ),
|
|
|
|
this, TQT_SLOT( slotPopupCompletion( const TQString& ) ) );
|
|
|
|
connect( box, TQT_SIGNAL( userCancelled( const TQString& ) ),
|
|
|
|
TQT_SLOT( slotUserCancelled( const TQString& ) ) );
|
|
|
|
|
|
|
|
// The emitter is always called KPIM::IMAPCompletionOrder by contract
|
|
|
|
if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()",
|
|
|
|
"slotIMAPCompletionOrderChanged()", false ) )
|
|
|
|
kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl;
|
|
|
|
|
|
|
|
connect( s_LDAPTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( slotStartLDAPLookup() ) );
|
|
|
|
connect( s_LDAPSearch, TQT_SIGNAL( searchData( const KPIM::LdapResultList& ) ),
|
|
|
|
TQT_SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) );
|
|
|
|
|
|
|
|
m_completionInitialized = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AddresseeLineEdit::~AddresseeLineEdit()
|
|
|
|
{
|
|
|
|
if ( s_LDAPSearch && s_LDAPLineEdit == this )
|
|
|
|
stopLDAPLookup();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::setFont( const TQFont& font )
|
|
|
|
{
|
|
|
|
KLineEdit::setFont( font );
|
|
|
|
if ( m_useCompletion )
|
|
|
|
completionBox()->setFont( font );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::allowSemiColonAsSeparator( bool useSemiColonAsSeparator )
|
|
|
|
{
|
|
|
|
m_useSemiColonAsSeparator = useSemiColonAsSeparator;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::allowDistributionLists( bool allowDistLists )
|
|
|
|
{
|
|
|
|
m_allowDistLists = allowDistLists;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::keyPressEvent( TQKeyEvent *e )
|
|
|
|
{
|
|
|
|
bool accept = false;
|
|
|
|
|
|
|
|
if ( KStdAccel::shortcut( KStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) {
|
|
|
|
//TODO: add LDAP substring lookup, when it becomes available in KPIM::LDAPSearch
|
|
|
|
updateSearchString();
|
|
|
|
doCompletion( true );
|
|
|
|
accept = true;
|
|
|
|
} else if ( KStdAccel::shortcut( KStdAccel::TextCompletion ).contains( KKey( e ) ) ) {
|
|
|
|
int len = text().length();
|
|
|
|
|
|
|
|
if ( len == cursorPosition() ) { // at End?
|
|
|
|
updateSearchString();
|
|
|
|
doCompletion( true );
|
|
|
|
accept = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const TQString oldContent = text();
|
|
|
|
if ( !accept )
|
|
|
|
KLineEdit::keyPressEvent( e );
|
|
|
|
|
|
|
|
// if the text didn't change (eg. because a cursor navigation key was pressed)
|
|
|
|
// we don't need to trigger a new search
|
|
|
|
if ( oldContent == text() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( e->isAccepted() ) {
|
|
|
|
updateSearchString();
|
|
|
|
TQString searchString( m_searchString );
|
|
|
|
//LDAP does not know about our string manipulation, remove it
|
|
|
|
if ( m_searchExtended )
|
|
|
|
searchString = m_searchString.mid( 1 );
|
|
|
|
|
|
|
|
if ( m_useCompletion && s_LDAPTimer != NULL ) {
|
|
|
|
if ( *s_LDAPText != searchString || s_LDAPLineEdit != this )
|
|
|
|
stopLDAPLookup();
|
|
|
|
|
|
|
|
*s_LDAPText = searchString;
|
|
|
|
s_LDAPLineEdit = this;
|
|
|
|
s_LDAPTimer->start( 500, true );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::insert( const TQString &t )
|
|
|
|
{
|
|
|
|
if ( !m_smartPaste ) {
|
|
|
|
KLineEdit::insert( t );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//kdDebug(5300) << " AddresseeLineEdit::insert( \"" << t << "\" )" << endl;
|
|
|
|
|
|
|
|
TQString newText = t.stripWhiteSpace();
|
|
|
|
if ( newText.isEmpty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// remove newlines in the to-be-pasted string
|
|
|
|
TQStringList lines = TQStringList::split( TQRegExp("\r?\n"), newText, false );
|
|
|
|
for ( TQStringList::iterator it = lines.begin();
|
|
|
|
it != lines.end(); ++it ) {
|
|
|
|
// remove trailing commas and whitespace
|
|
|
|
(*it).remove( TQRegExp(",?\\s*$") );
|
|
|
|
}
|
|
|
|
newText = lines.join( ", " );
|
|
|
|
|
|
|
|
if ( newText.startsWith("mailto:") ) {
|
|
|
|
KURL url( newText );
|
|
|
|
newText = url.path();
|
|
|
|
}
|
|
|
|
else if ( newText.tqfind(" at ") != -1 ) {
|
|
|
|
// Anti-spam stuff
|
|
|
|
newText.tqreplace( " at ", "@" );
|
|
|
|
newText.tqreplace( " dot ", "." );
|
|
|
|
}
|
|
|
|
else if ( newText.tqfind("(at)") != -1 ) {
|
|
|
|
newText.tqreplace( TQRegExp("\\s*\\(at\\)\\s*"), "@" );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString contents = text();
|
|
|
|
int start_sel = 0;
|
|
|
|
int pos = cursorPosition( );
|
|
|
|
|
|
|
|
if ( hasSelectedText() ) {
|
|
|
|
// Cut away the selection.
|
|
|
|
start_sel = selectionStart();
|
|
|
|
pos = start_sel;
|
|
|
|
contents = contents.left( start_sel ) + contents.mid( start_sel + selectedText().length() );
|
|
|
|
}
|
|
|
|
|
|
|
|
int eot = contents.length();
|
|
|
|
while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
|
|
|
|
eot--;
|
|
|
|
}
|
|
|
|
if ( eot == 0 ) {
|
|
|
|
contents = TQString();
|
|
|
|
} else if ( pos >= eot ) {
|
|
|
|
if ( contents[ eot - 1 ] == ',' ) {
|
|
|
|
eot--;
|
|
|
|
}
|
|
|
|
contents.truncate( eot );
|
|
|
|
contents += ", ";
|
|
|
|
pos = eot + 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
contents = contents.left( pos ) + newText + contents.mid( pos );
|
|
|
|
setText( contents );
|
|
|
|
setEdited( true );
|
|
|
|
setCursorPosition( pos + newText.length() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::setText( const TQString & text )
|
|
|
|
{
|
|
|
|
ClickLineEdit::setText( text.stripWhiteSpace() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::paste()
|
|
|
|
{
|
|
|
|
if ( m_useCompletion )
|
|
|
|
m_smartPaste = true;
|
|
|
|
|
|
|
|
KLineEdit::paste();
|
|
|
|
m_smartPaste = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::mouseReleaseEvent( TQMouseEvent *e )
|
|
|
|
{
|
|
|
|
// reimplemented from TQLineEdit::mouseReleaseEvent()
|
|
|
|
if ( m_useCompletion
|
|
|
|
&& TQApplication::tqclipboard()->supportsSelection()
|
|
|
|
&& !isReadOnly()
|
|
|
|
&& e->button() == Qt::MidButton ) {
|
|
|
|
m_smartPaste = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
KLineEdit::mouseReleaseEvent( e );
|
|
|
|
m_smartPaste = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::dropEvent( TQDropEvent *e )
|
|
|
|
{
|
|
|
|
KURL::List uriList;
|
|
|
|
if ( !isReadOnly() ) {
|
|
|
|
if ( KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) {
|
|
|
|
TQString contents = text();
|
|
|
|
// remove trailing white space and comma
|
|
|
|
int eot = contents.length();
|
|
|
|
while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() )
|
|
|
|
eot--;
|
|
|
|
if ( eot == 0 )
|
|
|
|
contents = TQString();
|
|
|
|
else if ( contents[ eot - 1 ] == ',' ) {
|
|
|
|
eot--;
|
|
|
|
contents.truncate( eot );
|
|
|
|
}
|
|
|
|
bool mailtoURL = false;
|
|
|
|
// append the mailto URLs
|
|
|
|
for ( KURL::List::Iterator it = uriList.begin();
|
|
|
|
it != uriList.end(); ++it ) {
|
|
|
|
if ( !contents.isEmpty() )
|
|
|
|
contents.append( ", " );
|
|
|
|
KURL u( *it );
|
|
|
|
if ( u.protocol() == "mailto" ) {
|
|
|
|
mailtoURL = true;
|
|
|
|
contents.append( (*it).path() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( mailtoURL ) {
|
|
|
|
setText( contents );
|
|
|
|
setEdited( true );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Let's see if this drop contains a comma separated list of emails
|
|
|
|
TQString dropData = TQString::fromUtf8( e->tqencodedData( "text/plain" ) );
|
|
|
|
TQStringList addrs = splitEmailAddrList( dropData );
|
|
|
|
if ( addrs.count() > 0 ) {
|
|
|
|
setText( normalizeAddressesAndDecodeIDNs( dropData ) );
|
|
|
|
setEdited( true );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( m_useCompletion )
|
|
|
|
m_smartPaste = true;
|
|
|
|
TQLineEdit::dropEvent( e );
|
|
|
|
m_smartPaste = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::cursorAtEnd()
|
|
|
|
{
|
|
|
|
setCursorPosition( text().length() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::enableCompletion( bool enable )
|
|
|
|
{
|
|
|
|
m_useCompletion = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::doCompletion( bool ctrlT )
|
|
|
|
{
|
|
|
|
m_lastSearchMode = ctrlT;
|
|
|
|
|
|
|
|
KGlobalSettings::Completion mode = completionMode();
|
|
|
|
|
|
|
|
if ( mode == KGlobalSettings::CompletionNone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( s_addressesDirty ) {
|
|
|
|
loadContacts(); // read from local address book
|
|
|
|
s_completion->setOrder( completionOrder() );
|
|
|
|
}
|
|
|
|
|
|
|
|
// cursor at end of string - or Ctrl+T pressed for substring completion?
|
|
|
|
if ( ctrlT ) {
|
|
|
|
const TQStringList completions = getAdjustedCompletionItems( false );
|
|
|
|
|
|
|
|
if ( completions.count() > 1 )
|
|
|
|
; //m_previousAddresses = prevAddr;
|
|
|
|
else if ( completions.count() == 1 )
|
|
|
|
setText( m_previousAddresses + completions.first().stripWhiteSpace() );
|
|
|
|
|
|
|
|
setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found
|
|
|
|
|
|
|
|
cursorAtEnd();
|
|
|
|
setCompletionMode( mode ); //set back to previous mode
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch ( mode ) {
|
|
|
|
case KGlobalSettings::CompletionPopupAuto:
|
|
|
|
{
|
|
|
|
if ( m_searchString.isEmpty() )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case KGlobalSettings::CompletionPopup:
|
|
|
|
{
|
|
|
|
const TQStringList items = getAdjustedCompletionItems( true );
|
|
|
|
setCompletedItems( items, false );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case KGlobalSettings::CompletionShell:
|
|
|
|
{
|
|
|
|
TQString match = s_completion->makeCompletion( m_searchString );
|
|
|
|
if ( !match.isNull() && match != m_searchString ) {
|
|
|
|
setText( m_previousAddresses + match );
|
|
|
|
setEdited( true );
|
|
|
|
cursorAtEnd();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case KGlobalSettings::CompletionMan: // Short-Auto in fact
|
|
|
|
case KGlobalSettings::CompletionAuto:
|
|
|
|
{
|
|
|
|
//force autoSuggest in KLineEdit::keyPressed or setCompletedText will have no effect
|
|
|
|
setCompletionMode( completionMode() );
|
|
|
|
|
|
|
|
if ( !m_searchString.isEmpty() ) {
|
|
|
|
|
|
|
|
//if only our \" is left, remove it since user has not typed it either
|
|
|
|
if ( m_searchExtended && m_searchString == "\"" ){
|
|
|
|
m_searchExtended = false;
|
|
|
|
m_searchString = TQString();
|
|
|
|
setText( m_previousAddresses );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString match = s_completion->makeCompletion( m_searchString );
|
|
|
|
|
|
|
|
if ( !match.isEmpty() ) {
|
|
|
|
if ( match != m_searchString ) {
|
|
|
|
TQString adds = m_previousAddresses + match;
|
|
|
|
setCompletedText( adds );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( !m_searchString.startsWith( "\"" ) ) {
|
|
|
|
//try with quoted text, if user has not type one already
|
|
|
|
match = s_completion->makeCompletion( "\"" + m_searchString );
|
|
|
|
if ( !match.isEmpty() && match != m_searchString ) {
|
|
|
|
m_searchString = "\"" + m_searchString;
|
|
|
|
m_searchExtended = true;
|
|
|
|
setText( m_previousAddresses + m_searchString );
|
|
|
|
setCompletedText( m_previousAddresses + match );
|
|
|
|
}
|
|
|
|
} else if ( m_searchExtended ) {
|
|
|
|
//our added \" does not work anymore, remove it
|
|
|
|
m_searchString = m_searchString.mid( 1 );
|
|
|
|
m_searchExtended = false;
|
|
|
|
setText( m_previousAddresses + m_searchString );
|
|
|
|
//now try again
|
|
|
|
match = s_completion->makeCompletion( m_searchString );
|
|
|
|
if ( !match.isEmpty() && match != m_searchString ) {
|
|
|
|
TQString adds = m_previousAddresses + match;
|
|
|
|
setCompletedText( adds );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case KGlobalSettings::CompletionNone:
|
|
|
|
default: // fall through
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::slotPopupCompletion( const TQString& completion )
|
|
|
|
{
|
|
|
|
setText( m_previousAddresses + completion.stripWhiteSpace() );
|
|
|
|
cursorAtEnd();
|
|
|
|
// slotMatched( m_previousAddresses + completion );
|
|
|
|
updateSearchString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::slotReturnPressed( const TQString& item )
|
|
|
|
{
|
|
|
|
Q_UNUSED( item );
|
|
|
|
TQListBoxItem* i = completionBox()->selectedItem();
|
|
|
|
if ( i != 0 )
|
|
|
|
slotPopupCompletion( i->text() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::loadContacts()
|
|
|
|
{
|
|
|
|
s_completion->clear();
|
|
|
|
s_completionItemMap->clear();
|
|
|
|
s_addressesDirty = false;
|
|
|
|
//m_contactMap.clear();
|
|
|
|
|
|
|
|
TQApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while
|
|
|
|
|
|
|
|
KConfig config( "kpimcompletionorder" ); // The weights for non-imap kabc resources is there.
|
|
|
|
config.setGroup( "CompletionWeights" );
|
|
|
|
|
|
|
|
KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
|
|
|
|
// Can't just use the addressbook's iterator, we need to know which subresource
|
|
|
|
// is behind which contact.
|
|
|
|
TQPtrList<KABC::Resource> resources( addressBook->resources() );
|
|
|
|
for( TQPtrListIterator<KABC::Resource> resit( resources ); *resit; ++resit ) {
|
|
|
|
KABC::Resource* resource = *resit;
|
|
|
|
KPIM::ResourceABC* resabc = dynamic_cast<ResourceABC *>( resource );
|
|
|
|
if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource
|
|
|
|
const TQMap<TQString, TQString> uidToResourceMap = resabc->uidToResourceMap();
|
|
|
|
KABC::Resource::Iterator it;
|
|
|
|
for ( it = resource->begin(); it != resource->end(); ++it ) {
|
|
|
|
TQString uid = (*it).uid();
|
|
|
|
TQMap<TQString, TQString>::const_iterator wit = uidToResourceMap.tqfind( uid );
|
|
|
|
const TQString subresourceLabel = resabc->subresourceLabel( *wit );
|
|
|
|
const int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80;
|
|
|
|
const int idx = addCompletionSource( subresourceLabel, weight );
|
|
|
|
|
|
|
|
//kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl;
|
|
|
|
addContact( *it, weight, idx );
|
|
|
|
}
|
|
|
|
} else { // KABC non-imap resource
|
|
|
|
int weight = config.readNumEntry( resource->identifier(), 60 );
|
|
|
|
int sourceIndex = addCompletionSource( resource->resourceName(), weight );
|
|
|
|
KABC::Resource::Iterator it;
|
|
|
|
for ( it = resource->begin(); it != resource->end(); ++it ) {
|
|
|
|
addContact( *it, weight, sourceIndex );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef KDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above
|
|
|
|
int weight = config.readNumEntry( "DistributionLists", 60 );
|
|
|
|
KABC::DistributionListManager manager( addressBook );
|
|
|
|
manager.load();
|
|
|
|
const TQStringList distLists = manager.listNames();
|
|
|
|
TQStringList::const_iterator listIt;
|
|
|
|
int idx = addCompletionSource( i18n( "Distribution Lists" ) );
|
|
|
|
for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) {
|
|
|
|
|
|
|
|
//for KGlobalSettings::CompletionAuto
|
|
|
|
addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx );
|
|
|
|
|
|
|
|
//for CompletionShell, CompletionPopup
|
|
|
|
TQStringList sl( (*listIt).simplifyWhiteSpace() );
|
|
|
|
addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx, &sl );
|
|
|
|
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
TQApplication::restoreOverrideCursor();
|
|
|
|
|
|
|
|
if ( !m_addressBookConnected ) {
|
|
|
|
connect( addressBook, TQT_SIGNAL( addressBookChanged( AddressBook* ) ), TQT_SLOT( loadContacts() ) );
|
|
|
|
m_addressBookConnected = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight, int source )
|
|
|
|
{
|
|
|
|
#ifdef KDEPIM_NEW_DISTRLISTS
|
|
|
|
if ( KPIM::DistributionList::isDistributionList( addr ) ) {
|
|
|
|
//kdDebug(5300) << "AddresseeLineEdit::addContact() distribution list \"" << addr.formattedName() << "\" weight=" << weight << endl;
|
|
|
|
|
|
|
|
if ( m_allowDistLists ) {
|
|
|
|
//for CompletionAuto
|
|
|
|
addCompletionItem( addr.formattedName(), weight, source );
|
|
|
|
|
|
|
|
//for CompletionShell, CompletionPopup
|
|
|
|
TQStringList sl( addr.formattedName() );
|
|
|
|
addCompletionItem( addr.formattedName(), weight, source, &sl );
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
//m_contactMap.insert( addr.realName(), addr );
|
|
|
|
const TQStringList emails = addr.emails();
|
|
|
|
TQStringList::ConstIterator it;
|
|
|
|
const int prefEmailWeight = 1; //increment weight by prefEmailWeight
|
|
|
|
int isPrefEmail = prefEmailWeight; //first in list is preferredEmail
|
|
|
|
for ( it = emails.begin(); it != emails.end(); ++it ) {
|
|
|
|
//TODO: highlight preferredEmail
|
|
|
|
const TQString email( (*it) );
|
|
|
|
const TQString givenName = addr.givenName();
|
|
|
|
const TQString familyName= addr.familyName();
|
|
|
|
const TQString nickName = addr.nickName();
|
|
|
|
const TQString domain = email.mid( email.tqfind( '@' ) + 1 );
|
|
|
|
TQString fullEmail = addr.fullEmail( email );
|
|
|
|
//TODO: let user decide what fields to use in lookup, e.g. company, city, ...
|
|
|
|
|
|
|
|
//for CompletionAuto
|
|
|
|
if ( givenName.isEmpty() && familyName.isEmpty() ) {
|
|
|
|
addCompletionItem( fullEmail, weight + isPrefEmail, source ); // use whatever is there
|
|
|
|
} else {
|
|
|
|
const TQString byFirstName= "\"" + givenName + " " + familyName + "\" <" + email + ">";
|
|
|
|
const TQString byLastName = "\"" + familyName + ", " + givenName + "\" <" + email + ">";
|
|
|
|
addCompletionItem( byFirstName, weight + isPrefEmail, source );
|
|
|
|
addCompletionItem( byLastName, weight + isPrefEmail, source );
|
|
|
|
}
|
|
|
|
|
|
|
|
addCompletionItem( email, weight + isPrefEmail, source );
|
|
|
|
|
|
|
|
if ( !nickName.isEmpty() ){
|
|
|
|
const TQString byNick = "\"" + nickName + "\" <" + email + ">";
|
|
|
|
addCompletionItem( byNick, weight + isPrefEmail, source );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !domain.isEmpty() ){
|
|
|
|
const TQString byDomain = "\"" + domain + " " + familyName + " " + givenName + "\" <" + email + ">";
|
|
|
|
addCompletionItem( byDomain, weight + isPrefEmail, source );
|
|
|
|
}
|
|
|
|
|
|
|
|
//for CompletionShell, CompletionPopup
|
|
|
|
TQStringList keyWords;
|
|
|
|
const TQString realName = addr.realName();
|
|
|
|
|
|
|
|
if ( !givenName.isEmpty() && !familyName.isEmpty() ) {
|
|
|
|
keyWords.append( givenName + " " + familyName );
|
|
|
|
keyWords.append( familyName + " " + givenName );
|
|
|
|
keyWords.append( familyName + ", " + givenName);
|
|
|
|
}else if ( !givenName.isEmpty() )
|
|
|
|
keyWords.append( givenName );
|
|
|
|
else if ( !familyName.isEmpty() )
|
|
|
|
keyWords.append( familyName );
|
|
|
|
|
|
|
|
if ( !nickName.isEmpty() )
|
|
|
|
keyWords.append( nickName );
|
|
|
|
|
|
|
|
if ( !realName.isEmpty() )
|
|
|
|
keyWords.append( realName );
|
|
|
|
|
|
|
|
if ( !domain.isEmpty() )
|
|
|
|
keyWords.append( domain );
|
|
|
|
|
|
|
|
keyWords.append( email );
|
|
|
|
|
|
|
|
/* KMailCompletion does not have knowledge about identities, it stores emails and
|
|
|
|
* keywords for each email. KMailCompletion::allMatches does a lookup on the
|
|
|
|
* keywords and returns an ordered list of emails. In order to get the preferred
|
|
|
|
* email before others for each identity we use this little trick.
|
|
|
|
* We remove the <blank> in getAdjustedCompletionItems.
|
|
|
|
*/
|
|
|
|
if ( isPrefEmail == prefEmailWeight )
|
|
|
|
fullEmail.tqreplace( " <", " <" );
|
|
|
|
|
|
|
|
addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords );
|
|
|
|
isPrefEmail = 0;
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
int len = (*it).length();
|
|
|
|
if ( len == 0 ) continue;
|
|
|
|
if( '\0' == (*it)[len-1] )
|
|
|
|
--len;
|
|
|
|
const TQString tmp = (*it).left( len );
|
|
|
|
const TQString fullEmail = addr.fullEmail( tmp );
|
|
|
|
//kdDebug(5300) << "AddresseeLineEdit::addContact() \"" << fullEmail << "\" weight=" << weight << endl;
|
|
|
|
addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source );
|
|
|
|
// Try to guess the last name: if found, we add an extra
|
|
|
|
// entry to the list to make sure completion works even
|
|
|
|
// if the user starts by typing in the last name.
|
|
|
|
TQString name( addr.realName().simplifyWhiteSpace() );
|
|
|
|
if( name.endsWith("\"") )
|
|
|
|
name.truncate( name.length()-1 );
|
|
|
|
if( name.startsWith("\"") )
|
|
|
|
name = name.mid( 1 );
|
|
|
|
|
|
|
|
// While we're here also add "email (full name)" for completion on the email
|
|
|
|
if ( !name.isEmpty() )
|
|
|
|
addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source );
|
|
|
|
|
|
|
|
bool bDone = false;
|
|
|
|
int i = -1;
|
|
|
|
while( ( i = name.tqfindRev(' ') ) > 1 && !bDone ) {
|
|
|
|
TQString sLastName( name.mid( i+1 ) );
|
|
|
|
if( ! sLastName.isEmpty() &&
|
|
|
|
2 <= sLastName.length() && // last names must be at least 2 chars long
|
|
|
|
! sLastName.endsWith(".") ) { // last names must not end with a dot (like "Jr." or "Sr.")
|
|
|
|
name.truncate( i );
|
|
|
|
if( !name.isEmpty() ){
|
|
|
|
sLastName.prepend( "\"" );
|
|
|
|
sLastName.append( ", " + name + "\" <" );
|
|
|
|
}
|
|
|
|
TQString sExtraEntry( sLastName );
|
|
|
|
sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp );
|
|
|
|
sExtraEntry.append( ">" );
|
|
|
|
//kdDebug(5300) << "AddresseeLineEdit::addContact() added extra \"" << sExtraEntry.simplifyWhiteSpace() << "\" weight=" << weight << endl;
|
|
|
|
addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source );
|
|
|
|
bDone = true;
|
|
|
|
}
|
|
|
|
if( !bDone ) {
|
|
|
|
name.truncate( i );
|
|
|
|
if( name.endsWith("\"") )
|
|
|
|
name.truncate( name.length()-1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::addCompletionItem( const TQString& string, int weight, int completionItemSource, const TQStringList * keyWords )
|
|
|
|
{
|
|
|
|
// Check if there is an exact match for item already, and use the max weight if so.
|
|
|
|
// Since there's no way to get the information from KCompletion, we have to keep our own TQMap
|
|
|
|
CompletionItemsMap::iterator it = s_completionItemMap->tqfind( string );
|
|
|
|
if ( it != s_completionItemMap->end() ) {
|
|
|
|
weight = TQMAX( ( *it ).first, weight );
|
|
|
|
( *it ).first = weight;
|
|
|
|
} else {
|
|
|
|
s_completionItemMap->insert( string, tqMakePair( weight, completionItemSource ) );
|
|
|
|
}
|
|
|
|
if ( keyWords == 0 )
|
|
|
|
s_completion->addItem( string, weight );
|
|
|
|
else
|
|
|
|
s_completion->addItemWithKeys( string, weight, keyWords );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::slotStartLDAPLookup()
|
|
|
|
{
|
|
|
|
KGlobalSettings::Completion mode = completionMode();
|
|
|
|
|
|
|
|
if ( mode == KGlobalSettings::CompletionNone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( !s_LDAPSearch->isAvailable() ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( s_LDAPLineEdit != this )
|
|
|
|
return;
|
|
|
|
|
|
|
|
startLoadingLDAPEntries();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::stopLDAPLookup()
|
|
|
|
{
|
|
|
|
s_LDAPSearch->cancelSearch();
|
|
|
|
s_LDAPLineEdit = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::startLoadingLDAPEntries()
|
|
|
|
{
|
|
|
|
TQString s( *s_LDAPText );
|
|
|
|
// TODO cache last?
|
|
|
|
TQString prevAddr;
|
|
|
|
int n = s.tqfindRev( ',' );
|
|
|
|
if ( n >= 0 ) {
|
|
|
|
prevAddr = s.left( n + 1 ) + ' ';
|
|
|
|
s = s.mid( n + 1, 255 ).stripWhiteSpace();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( s.isEmpty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
//loadContacts(); // TODO reuse these?
|
|
|
|
s_LDAPSearch->startSearch( s );
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs )
|
|
|
|
{
|
|
|
|
if ( adrs.isEmpty() || s_LDAPLineEdit != this )
|
|
|
|
return;
|
|
|
|
|
|
|
|
for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
|
|
|
|
KABC::Addressee addr;
|
|
|
|
addr.setNameFromString( (*it).name );
|
|
|
|
addr.setEmails( (*it).email );
|
|
|
|
|
|
|
|
if ( !s_ldapClientToCompletionSourceMap->tqcontains( (*it).clientNumber ) )
|
|
|
|
updateLDAPWeights(); // we got results from a new source, so update the completion sources
|
|
|
|
|
|
|
|
addContact( addr, (*it).completionWeight, (*s_ldapClientToCompletionSourceMap)[ (*it ).clientNumber ] );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( (hasFocus() || completionBox()->hasFocus() )
|
|
|
|
&& completionMode() != KGlobalSettings::CompletionNone
|
|
|
|
&& completionMode() != KGlobalSettings::CompletionShell ) {
|
|
|
|
setText( m_previousAddresses + m_searchString );
|
|
|
|
// only complete again if the user didn't change the selection while we were waiting
|
|
|
|
// otherwise the completion box will be closed
|
|
|
|
if ( m_searchString.stripWhiteSpace() != completionBox()->currentText().stripWhiteSpace() )
|
|
|
|
doCompletion( m_lastSearchMode );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::setCompletedItems( const TQStringList& items, bool autoSuggest )
|
|
|
|
{
|
|
|
|
KCompletionBox* completionBox = this->completionBox();
|
|
|
|
|
|
|
|
if ( !items.isEmpty() &&
|
|
|
|
!(items.count() == 1 && m_searchString == items.first()) )
|
|
|
|
{
|
|
|
|
TQString oldCurrentText = completionBox->currentText();
|
|
|
|
TQListBoxItem *itemUnderMouse = completionBox->itemAt(
|
|
|
|
completionBox->viewport()->mapFromGlobal(TQCursor::pos()) );
|
|
|
|
TQString oldTextUnderMouse;
|
|
|
|
TQPoint oldPosOfItemUnderMouse;
|
|
|
|
if ( itemUnderMouse ) {
|
|
|
|
oldTextUnderMouse = itemUnderMouse->text();
|
|
|
|
oldPosOfItemUnderMouse = completionBox->tqitemRect( itemUnderMouse ).topLeft();
|
|
|
|
}
|
|
|
|
|
|
|
|
completionBox->setItems( items );
|
|
|
|
|
|
|
|
if ( !completionBox->isVisible() ) {
|
|
|
|
if ( !m_searchString.isEmpty() )
|
|
|
|
completionBox->setCancelledText( m_searchString );
|
|
|
|
completionBox->popup();
|
|
|
|
// we have to install the event filter after popup(), since that
|
|
|
|
// calls show(), and that's where KCompletionBox installs its filter.
|
|
|
|
// We want to be first, though, so do it now.
|
|
|
|
if ( s_completion->order() == KCompletion::Weighted )
|
|
|
|
tqApp->installEventFilter( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to re-select what was selected before, otherrwise use the first
|
|
|
|
// item, if there is one
|
|
|
|
TQListBoxItem* item = 0;
|
|
|
|
if ( oldCurrentText.isEmpty()
|
|
|
|
|| ( item = completionBox->tqfindItem( oldCurrentText ) ) == 0 ) {
|
|
|
|
item = completionBox->item( 1 );
|
|
|
|
}
|
|
|
|
if ( item )
|
|
|
|
{
|
|
|
|
if ( itemUnderMouse ) {
|
|
|
|
TQListBoxItem *newItemUnderMouse = completionBox->tqfindItem( oldTextUnderMouse );
|
|
|
|
// if the mouse was over an item, before, but now that's elsewhere,
|
|
|
|
// move the cursor, so folks don't accidently click the wrong item
|
|
|
|
if ( newItemUnderMouse ) {
|
|
|
|
TQRect r = completionBox->tqitemRect( newItemUnderMouse );
|
|
|
|
TQPoint target = r.topLeft();
|
|
|
|
if ( oldPosOfItemUnderMouse != target ) {
|
|
|
|
target.setX( target.x() + r.width()/2 );
|
|
|
|
TQCursor::setPos( completionBox->viewport()->mapToGlobal(target) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
completionBox->blockSignals( true );
|
|
|
|
completionBox->setSelected( item, true );
|
|
|
|
completionBox->setCurrentItem( item );
|
|
|
|
completionBox->ensureCurrentVisible();
|
|
|
|
|
|
|
|
completionBox->blockSignals( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( autoSuggest )
|
|
|
|
{
|
|
|
|
int index = items.first().tqfind( m_searchString );
|
|
|
|
TQString newText = items.first().mid( index );
|
|
|
|
setUserSelection(false);
|
|
|
|
setCompletedText(newText,true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ( completionBox && completionBox->isVisible() ) {
|
|
|
|
completionBox->hide();
|
|
|
|
completionBox->setItems( TQStringList() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPopupMenu* AddresseeLineEdit::createPopupMenu()
|
|
|
|
{
|
|
|
|
TQPopupMenu *menu = KLineEdit::createPopupMenu();
|
|
|
|
if ( !menu )
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ( m_useCompletion ){
|
|
|
|
menu->setItemVisible( ShortAutoCompletion, false );
|
|
|
|
menu->setItemVisible( PopupAutoCompletion, false );
|
|
|
|
menu->insertItem( i18n( "Configure Completion Order..." ),
|
|
|
|
this, TQT_SLOT( slotEditCompletionOrder() ) );
|
|
|
|
}
|
|
|
|
return menu;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::slotEditCompletionOrder()
|
|
|
|
{
|
|
|
|
init(); // for s_LDAPSearch
|
|
|
|
CompletionOrderEditor editor( s_LDAPSearch, this );
|
|
|
|
editor.exec();
|
|
|
|
if ( m_useCompletion ) {
|
|
|
|
updateLDAPWeights();
|
|
|
|
s_addressesDirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged()
|
|
|
|
{
|
|
|
|
if ( m_useCompletion )
|
|
|
|
s_addressesDirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KPIM::AddresseeLineEdit::slotUserCancelled( const TQString& cancelText )
|
|
|
|
{
|
|
|
|
if ( s_LDAPSearch && s_LDAPLineEdit == this )
|
|
|
|
stopLDAPLookup();
|
|
|
|
userCancelled( m_previousAddresses + cancelText ); // in KLineEdit
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddresseeLineEdit::updateSearchString()
|
|
|
|
{
|
|
|
|
m_searchString = text();
|
|
|
|
|
|
|
|
int n = -1;
|
|
|
|
bool inQuote = false;
|
|
|
|
uint searchStringLength = m_searchString.length();
|
|
|
|
for ( uint i = 0; i < searchStringLength; ++i ) {
|
|
|
|
if ( m_searchString[ i ] == '"' ) {
|
|
|
|
inQuote = !inQuote;
|
|
|
|
}
|
|
|
|
if ( m_searchString[ i ] == '\\' &&
|
|
|
|
(i + 1) < searchStringLength && m_searchString[ i + 1 ] == '"' ) {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
if ( inQuote ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( i < searchStringLength &&
|
|
|
|
( m_searchString[ i ] == ',' ||
|
|
|
|
( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) ) ) {
|
|
|
|
n = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( n >= 0 ) {
|
|
|
|
++n; // Go past the ","
|
|
|
|
|
|
|
|
int len = m_searchString.length();
|
|
|
|
|
|
|
|
// Increment past any whitespace...
|
|
|
|
while ( n < len && m_searchString[ n ].isSpace() )
|
|
|
|
++n;
|
|
|
|
|
|
|
|
m_previousAddresses = m_searchString.left( n );
|
|
|
|
m_searchString = m_searchString.mid( n ).stripWhiteSpace();
|
|
|
|
} else {
|
|
|
|
m_previousAddresses = TQString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KPIM::AddresseeLineEdit::slotCompletion()
|
|
|
|
{
|
|
|
|
// Called by KLineEdit's keyPressEvent for CompletionModes Auto,Popup -> new text, update search string
|
|
|
|
// not called for CompletionShell, this is been taken care of in AddresseeLineEdit::keyPressEvent
|
|
|
|
updateSearchString();
|
|
|
|
if ( completionBox() )
|
|
|
|
completionBox()->setCancelledText( m_searchString );
|
|
|
|
doCompletion( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
// not cached, to make sure we get an up-to-date value when it changes
|
|
|
|
KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder()
|
|
|
|
{
|
|
|
|
KConfig config( "kpimcompletionorder" );
|
|
|
|
config.setGroup( "General" );
|
|
|
|
const TQString order = config.readEntry( "CompletionOrder", "Weighted" );
|
|
|
|
|
|
|
|
if ( order == "Weighted" )
|
|
|
|
return KCompletion::Weighted;
|
|
|
|
else
|
|
|
|
return KCompletion::Sorted;
|
|
|
|
}
|
|
|
|
|
|
|
|
int KPIM::AddresseeLineEdit::addCompletionSource( const TQString &source, int weight )
|
|
|
|
{
|
|
|
|
TQMap<TQString,int>::iterator it = s_completionSourceWeights->tqfind( source );
|
|
|
|
if ( it == s_completionSourceWeights->end() )
|
|
|
|
s_completionSourceWeights->insert( source, weight );
|
|
|
|
else
|
|
|
|
(*s_completionSourceWeights)[source] = weight;
|
|
|
|
|
|
|
|
int sourceIndex = s_completionSources->tqfindIndex( source );
|
|
|
|
if ( sourceIndex == -1 ) {
|
|
|
|
s_completionSources->append( source );
|
|
|
|
return s_completionSources->size() - 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return sourceIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KPIM::AddresseeLineEdit::eventFilter(TQObject *obj, TQEvent *e)
|
|
|
|
{
|
|
|
|
if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(completionBox()) ) {
|
|
|
|
if ( e->type() == TQEvent::MouseButtonPress ||
|
|
|
|
e->type() == TQEvent::MouseMove ||
|
|
|
|
e->type() == TQEvent::MouseButtonRelease ||
|
|
|
|
e->type() == TQEvent::MouseButtonDblClick ) {
|
|
|
|
TQMouseEvent* me = TQT_TQMOUSEEVENT( e );
|
|
|
|
// find list box item at the event position
|
|
|
|
TQListBoxItem *item = completionBox()->itemAt( me->pos() );
|
|
|
|
if ( !item ) {
|
|
|
|
// In the case of a mouse move outside of the box we don't want
|
|
|
|
// the tqparent to fuzzy select a header by mistake.
|
|
|
|
bool eat = e->type() == TQEvent::MouseMove;
|
|
|
|
return eat;
|
|
|
|
}
|
|
|
|
// avoid selection of headers on button press, or move or release while
|
|
|
|
// a button is pressed
|
|
|
|
if ( e->type() == TQEvent::MouseButtonPress
|
|
|
|
|| me->state() & Qt::LeftButton || me->state() & Qt::MidButton
|
|
|
|
|| me->state() & Qt::RightButton ) {
|
|
|
|
if ( itemIsHeader(item) ) {
|
|
|
|
return true; // eat the event, we don't want anything to happen
|
|
|
|
} else {
|
|
|
|
// if we are not on one of the group heading, make sure the item
|
|
|
|
// below or above is selected, not the heading, inadvertedly, due
|
|
|
|
// to fuzzy auto-selection from TQListBox
|
|
|
|
completionBox()->setCurrentItem( item );
|
|
|
|
completionBox()->setSelected( completionBox()->index( item ), true );
|
|
|
|
if ( e->type() == TQEvent::MouseMove )
|
|
|
|
return true; // avoid fuzzy selection behavior
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(this) ) &&
|
|
|
|
( e->type() == TQEvent::AccelOverride ) ) {
|
|
|
|
TQKeyEvent *ke = TQT_TQKEYEVENT( e );
|
|
|
|
if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) {
|
|
|
|
ke->accept();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(this) ) &&
|
|
|
|
( e->type() == TQEvent::KeyPress || e->type() == TQEvent::KeyRelease ) &&
|
|
|
|
completionBox()->isVisible() ) {
|
|
|
|
TQKeyEvent *ke = TQT_TQKEYEVENT( e );
|
|
|
|
int currentIndex = completionBox()->currentItem();
|
|
|
|
if ( currentIndex < 0 ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ke->key() == Key_Up ) {
|
|
|
|
//kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl;
|
|
|
|
// figure out if the item we would be moving to is one we want
|
|
|
|
// to ignore. If so, go one further
|
|
|
|
TQListBoxItem *itemAbove = completionBox()->item( currentIndex );
|
|
|
|
if ( itemAbove && itemIsHeader(itemAbove) ) {
|
|
|
|
// there is a header above us, check if there is even further up
|
|
|
|
// and if so go one up, so it'll be selected
|
|
|
|
if ( currentIndex > 0 && completionBox()->item( currentIndex - 1 ) ) {
|
|
|
|
//kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl;
|
|
|
|
completionBox()->setCurrentItem( itemAbove->prev() );
|
|
|
|
completionBox()->setSelected( currentIndex - 1, true );
|
|
|
|
} else if ( currentIndex == 0 ) {
|
|
|
|
// nothing to skip to, let's stay where we are, but make sure the
|
|
|
|
// first header becomes visible, if we are the first real entry
|
|
|
|
completionBox()->ensureVisible( 0, 0 );
|
|
|
|
//Kolab issue 2941: be sure to add email even if it's the only element.
|
|
|
|
if ( itemIsHeader( completionBox()->item( currentIndex ) ) ) {
|
|
|
|
currentIndex++;
|
|
|
|
}
|
|
|
|
completionBox()->setCurrentItem( itemAbove );
|
|
|
|
completionBox()->setSelected( currentIndex, true );
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
} else if ( ke->key() == Key_Down ) {
|
|
|
|
// same strategy for downwards
|
|
|
|
//kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl;
|
|
|
|
TQListBoxItem *itemBelow = completionBox()->item( currentIndex );
|
|
|
|
if ( itemBelow && itemIsHeader( itemBelow ) ) {
|
|
|
|
if ( completionBox()->item( currentIndex + 1 ) ) {
|
|
|
|
//kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl;
|
|
|
|
completionBox()->setCurrentItem( itemBelow->next() );
|
|
|
|
completionBox()->setSelected( currentIndex + 1, true );
|
|
|
|
} else {
|
|
|
|
// nothing to skip to, let's stay where we are
|
|
|
|
completionBox()->setCurrentItem( itemBelow );
|
|
|
|
completionBox()->setSelected( currentIndex, true );
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// special case of the last and only item in the list needing selection
|
|
|
|
if ( !itemBelow && currentIndex == 1 ) {
|
|
|
|
completionBox()->setSelected( currentIndex, true );
|
|
|
|
}
|
|
|
|
// special case of the initial selection, which is unfortunately a header.
|
|
|
|
// Setting it to selected tricks KCompletionBox into not treating is special
|
|
|
|
// and selecting making it current, instead of the one below.
|
|
|
|
TQListBoxItem *item = completionBox()->item( currentIndex );
|
|
|
|
if ( item && itemIsHeader(item) ) {
|
|
|
|
completionBox()->setSelected( currentIndex, true );
|
|
|
|
}
|
|
|
|
} else if ( e->type() == TQEvent::KeyRelease &&
|
|
|
|
( ke->key() == Key_Tab || ke->key() == Key_Backtab ) ) {
|
|
|
|
//kdDebug() << "EVENTFILTER: Key_Tab. currentIndex=" << currentIndex << endl;
|
|
|
|
/// first, find the header of the current section
|
|
|
|
TQListBoxItem *myHeader = 0;
|
|
|
|
const int iterationstep = ke->key() == Key_Tab ? 1 : -1;
|
|
|
|
int i = TQMIN( TQMAX( currentIndex - iterationstep, 0 ), completionBox()->count() - 1 );
|
|
|
|
while ( i>=0 ) {
|
|
|
|
if ( itemIsHeader( completionBox()->item(i) ) ) {
|
|
|
|
myHeader = completionBox()->item( i );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
Q_ASSERT( myHeader ); // we should always be able to find a header
|
|
|
|
|
|
|
|
// find the next header (searching backwards, for Key_Backtab)
|
|
|
|
TQListBoxItem *nextHeader = 0;
|
|
|
|
// when iterating forward, start at the currentindex, when backwards,
|
|
|
|
// one up from our header, or at the end
|
|
|
|
uint j;
|
|
|
|
if ( ke->key() == Key_Tab ) {
|
|
|
|
j = currentIndex;
|
|
|
|
} else {
|
|
|
|
i = completionBox()->index( myHeader );
|
|
|
|
if ( i == 0 ) {
|
|
|
|
j = completionBox()->count() - 1;
|
|
|
|
} else {
|
|
|
|
j = ( i - 1 ) % completionBox()->count();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) {
|
|
|
|
if ( itemIsHeader(nextHeader) ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
j = (j + iterationstep) % completionBox()->count();
|
|
|
|
}
|
|
|
|
if ( nextHeader && nextHeader != myHeader ) {
|
|
|
|
TQListBoxItem *item = completionBox()->item( j + 1 );
|
|
|
|
if ( item && !itemIsHeader(item) ) {
|
|
|
|
completionBox()->setSelected( item, true );
|
|
|
|
completionBox()->setCurrentItem( item );
|
|
|
|
completionBox()->ensureCurrentVisible();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ClickLineEdit::eventFilter( obj, e );
|
|
|
|
}
|
|
|
|
|
|
|
|
class SourceWithWeight {
|
|
|
|
public:
|
|
|
|
int weight; // the weight of the source
|
|
|
|
TQString sourceName; // the name of the source, e.g. "LDAP Server"
|
|
|
|
int index; // index into s_completionSources
|
|
|
|
|
|
|
|
bool operator< ( const SourceWithWeight &other ) {
|
|
|
|
if ( weight > other.weight )
|
|
|
|
return true;
|
|
|
|
if ( weight < other.weight )
|
|
|
|
return false;
|
|
|
|
return sourceName < other.sourceName;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const TQStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch )
|
|
|
|
{
|
|
|
|
TQStringList items = fullSearch ?
|
|
|
|
s_completion->allMatches( m_searchString )
|
|
|
|
: s_completion->substringCompletion( m_searchString );
|
|
|
|
|
|
|
|
// For weighted mode, the algorithm is the following:
|
|
|
|
// In the first loop, we add each item to its section (there is one section per completion source)
|
|
|
|
// We also add spaces in front of the items.
|
|
|
|
// The sections are appended to the items list.
|
|
|
|
// In the second loop, we then walk through the sections and add all the items in there to the
|
|
|
|
// sorted item list, which is the final result.
|
|
|
|
//
|
|
|
|
// The algo for non-weighted mode is different.
|
|
|
|
|
|
|
|
int lastSourceIndex = -1;
|
|
|
|
unsigned int i = 0;
|
|
|
|
|
|
|
|
// Maps indices of the items list, which are section headers/source items,
|
|
|
|
// to a TQStringList which are the items of that section/source.
|
|
|
|
TQMap<int, TQStringList> sections;
|
|
|
|
TQStringList sortedItems;
|
|
|
|
for ( TQStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) {
|
|
|
|
CompletionItemsMap::const_iterator cit = s_completionItemMap->tqfind(*it);
|
|
|
|
if ( cit == s_completionItemMap->end() )
|
|
|
|
continue;
|
|
|
|
int idx = (*cit).second;
|
|
|
|
|
|
|
|
if ( s_completion->order() == KCompletion::Weighted ) {
|
|
|
|
if ( lastSourceIndex == -1 || lastSourceIndex != idx ) {
|
|
|
|
const TQString sourceLabel( (*s_completionSources)[idx] );
|
|
|
|
if ( sections.tqfind(idx) == sections.end() ) {
|
|
|
|
items.insert( it, sourceLabel );
|
|
|
|
}
|
|
|
|
lastSourceIndex = idx;
|
|
|
|
}
|
|
|
|
(*it) = (*it).prepend( s_completionItemIndentString );
|
|
|
|
// remove preferred email sort <blank> added in addContact()
|
|
|
|
(*it).tqreplace( " <", " <" );
|
|
|
|
}
|
|
|
|
sections[idx].append( *it );
|
|
|
|
|
|
|
|
if ( s_completion->order() == KCompletion::Sorted ) {
|
|
|
|
sortedItems.append( *it );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( s_completion->order() == KCompletion::Weighted ) {
|
|
|
|
|
|
|
|
// Sort the sections
|
|
|
|
TQValueList<SourceWithWeight> sourcesAndWeights;
|
|
|
|
for ( uint i = 0; i < s_completionSources->size(); i++ ) {
|
|
|
|
SourceWithWeight sww;
|
|
|
|
sww.sourceName = (*s_completionSources)[i];
|
|
|
|
sww.weight = (*s_completionSourceWeights)[sww.sourceName];
|
|
|
|
sww.index = i;
|
|
|
|
sourcesAndWeights.append( sww );
|
|
|
|
}
|
|
|
|
qHeapSort( sourcesAndWeights );
|
|
|
|
|
|
|
|
// Add the sections and their items to the final sortedItems result list
|
|
|
|
for( uint i = 0; i < sourcesAndWeights.size(); i++ ) {
|
|
|
|
TQStringList sectionItems = sections[sourcesAndWeights[i].index];
|
|
|
|
if ( !sectionItems.isEmpty() ) {
|
|
|
|
sortedItems.append( sourcesAndWeights[i].sourceName );
|
|
|
|
TQStringList sectionItems = sections[sourcesAndWeights[i].index];
|
|
|
|
for ( TQStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() );
|
|
|
|
sit != send; ++sit ) {
|
|
|
|
sortedItems.append( *sit );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sortedItems.sort();
|
|
|
|
}
|
|
|
|
return sortedItems;
|
|
|
|
}
|
|
|
|
#include "addresseelineedit.moc"
|