kde-playground/kdepimlibs/kabc/addresslineedit.cpp
2015-04-14 21:49:29 +00:00

527 lines
15 KiB
C++

/*
This file is part of libkabc.
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>
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 "addresslineedit.h"
#include <QApplication>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QtCore/QObject>
#include <QtCore/QRegExp>
#include <kcompletionbox.h>
#include <kconfig.h>
#include <kcursor.h>
#include <kdebug.h>
#include <kstandarddirs.h>
#include <kstandardshortcut.h>
#include "stdaddressbook.h"
//=============================================================================
//
// Class AddressLineEdit
//
//=============================================================================
using namespace KABC;
class AddressLineEdit::Private
{
public:
Private( AddressLineEdit *parent )
: mParent( parent ),
mCompletionInitialized( false ),
mSmartPaste( false )
{
init();
}
void init();
QStringList addresses();
QStringList removeMailDupes( const QStringList &adrs );
void slotCompletion() { mParent->doCompletion( false ); }
void slotPopupCompletion( const QString &completion );
AddressLineEdit *mParent;
QString mPreviousAddresses;
bool mUseCompletion;
bool mCompletionInitialized;
bool mSmartPaste;
static bool sAddressesDirty;
static bool initialized;
};
bool AddressLineEdit::Private::initialized = false;
K_GLOBAL_STATIC( KCompletion, sCompletion )
void AddressLineEdit::Private::init()
{
if ( !Private::initialized ) {
Private::initialized = true;
sCompletion->setOrder( KCompletion::Sorted );
sCompletion->setIgnoreCase( true );
}
if ( mUseCompletion && !mCompletionInitialized ) {
mParent->setCompletionObject( sCompletion, false ); // we handle it ourself
mParent->connect( mParent, SIGNAL(completion(QString)),
mParent, SLOT(slotCompletion()) );
KCompletionBox *box = mParent->completionBox();
mParent->connect( box, SIGNAL(currentTextChanged(QString)),
mParent, SLOT(slotPopupCompletion(QString)) );
mParent->connect( box, SIGNAL(userCancelled(QString)),
SLOT(userCancelled(QString)) );
mCompletionInitialized = true; // don't connect muliple times. That's
// ugly, tho, better have completionBox()
// virtual in KDE 4
// Why? This is only called once. Why should this be called more
// than once? And why was this protected?
}
}
QStringList AddressLineEdit::Private::addresses()
{
QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) ); // loading might take a while
QStringList result;
QLatin1String space( " " );
QRegExp needQuotes( QLatin1String( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) );
QLatin1String endQuote( "\" " );
QString addr, email;
KABC::AddressBook *addressBook = KABC::StdAddressBook::self();
KABC::AddressBook::Iterator it;
for ( it = addressBook->begin(); it != addressBook->end(); ++it ) {
QStringList emails = ( *it ).emails();
QString n = ( *it ).prefix() + space +
( *it ).givenName() + space +
( *it ).additionalName() + space +
( *it ).familyName() + space +
( *it ).suffix();
n = n.simplified();
QStringList::ConstIterator mit;
QStringList::ConstIterator end( emails.constEnd() );
for ( mit = emails.constBegin(); mit != end; ++mit ) {
email = *mit;
if ( !email.isEmpty() ) {
if ( n.isEmpty() || ( email.indexOf( QLatin1Char( '<' ) ) != -1 ) ) {
addr.clear();
} else { /* do we really need quotes around this name ? */
if ( n.indexOf( needQuotes ) != -1 ) {
addr = QLatin1Char( '"' ) + n + endQuote;
} else {
addr = n + space;
}
}
if ( !addr.isEmpty() &&
( email.indexOf( QLatin1Char( '<' ) ) == -1 ) &&
( email.indexOf( QLatin1Char( '>' ) ) == -1 ) &&
( email.indexOf( QLatin1Char( ',' ) ) == -1 ) ) {
addr += QLatin1Char( '<' ) + email + QLatin1Char( '>' );
} else {
addr += email;
}
addr = addr.trimmed();
result.append( addr );
}
}
}
result += addressBook->allDistributionListNames();
QApplication::restoreOverrideCursor();
return result;
}
QStringList AddressLineEdit::Private::removeMailDupes( const QStringList &addrs )
{
QStringList src( addrs );
qSort( src );
QString last;
for ( QStringList::Iterator it = src.begin(); it != src.end(); ) {
if ( *it == last ) {
it = src.erase( it );
continue; // dupe
}
last = *it;
++it;
}
return src;
}
void AddressLineEdit::Private::slotPopupCompletion( const QString &completion )
{
mParent->setText( mPreviousAddresses + completion );
mParent->cursorAtEnd();
}
bool AddressLineEdit::Private::sAddressesDirty = false;
AddressLineEdit::AddressLineEdit( QWidget *parent, bool useCompletion )
: KLineEdit( parent ), d( new Private( this ) )
{
d->mUseCompletion = useCompletion;
// Whenever a new AddressLineEdit is created (== a new composer is created),
// we set a dirty flag to reload the addresses upon the first completion.
// The address completions are shared between all AddressLineEdits.
// Is there a signal that tells us about addressbook updates?
if ( d->mUseCompletion ) {
d->sAddressesDirty = true;
}
}
//-----------------------------------------------------------------------------
AddressLineEdit::~AddressLineEdit()
{
delete d;
}
//-----------------------------------------------------------------------------
void AddressLineEdit::setFont( const QFont &font )
{
KLineEdit::setFont( font );
if ( d->mUseCompletion ) {
completionBox()->setFont( font );
}
}
//-----------------------------------------------------------------------------
void AddressLineEdit::keyPressEvent( QKeyEvent *event )
{
bool accept = false;
if ( KStandardShortcut::shortcut( KStandardShortcut::SubstringCompletion ).
contains( event->key() | event->modifiers() ) ) {
doCompletion( true );
accept = true;
} else if ( KStandardShortcut::shortcut( KStandardShortcut::TextCompletion ).
contains( event->key() | event->modifiers() ) ) {
int len = text().length();
if ( len == cursorPosition() ) { // at End?
doCompletion( true );
accept = true;
}
}
if ( !accept ) {
KLineEdit::keyPressEvent( event );
}
}
void AddressLineEdit::mouseReleaseEvent( QMouseEvent *event )
{
if ( d->mUseCompletion && ( event->button() == Qt::MidButton ) ) {
d->mSmartPaste = true;
KLineEdit::mouseReleaseEvent( event );
d->mSmartPaste = false;
return;
}
KLineEdit::mouseReleaseEvent( event );
}
void AddressLineEdit::insert( const QString &oldText )
{
if ( !d->mSmartPaste ) {
KLineEdit::insert( oldText );
return;
}
QString newText = oldText.trimmed();
if ( newText.isEmpty() ) {
return;
}
// remove newlines in the to-be-pasted string as well as an eventual
// mailto: protocol
newText.replace( QRegExp( QLatin1String( "\r?\n" ) ), QLatin1String( ", " ) );
if ( newText.startsWith( QLatin1String( "mailto:" ) ) ) {
KUrl u( newText );
newText = u.path();
} else if ( newText.indexOf( QLatin1String( " at " ) ) != -1 ) {
// Anti-spam stuff
newText.replace( QLatin1String( " at " ), QLatin1String( "@" ) );
newText.replace( QLatin1String( " dot " ), QLatin1String( "." ) );
} else if ( newText.indexOf( QLatin1String( "(at)" ) ) != -1 ) {
newText.replace( QRegExp( QLatin1String( "\\s*\\(at\\)\\s*" ) ), QLatin1String( "@" ) );
}
QString contents = text();
int start_sel = 0;
int end_sel = 0;
int pos = cursorPosition();
if ( !selectedText().isEmpty() ) {
// Cut away the selection.
if ( pos > end_sel ) {
pos -= ( end_sel - start_sel );
} else if ( pos > start_sel ) {
pos = start_sel;
}
contents = contents.left( start_sel ) + contents.right( end_sel + 1 );
}
int eot = contents.length();
while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) {
eot--;
}
if ( eot == 0 ) {
contents.clear();
} else if ( pos >= eot ) {
if ( contents[ eot - 1 ] == QLatin1Char( ',' ) ) {
eot--;
}
contents.truncate( eot );
contents += QLatin1String( ", " );
pos = eot+2;
}
contents = contents.left( pos ) + newText + contents.mid( pos );
setText( contents );
setCursorPosition( pos + newText.length() );
}
void AddressLineEdit::paste()
{
if ( d->mUseCompletion ) {
d->mSmartPaste = true;
}
KLineEdit::paste();
d->mSmartPaste = false;
}
//-----------------------------------------------------------------------------
void AddressLineEdit::cursorAtEnd()
{
setCursorPosition( text().length() );
}
//-----------------------------------------------------------------------------
void AddressLineEdit::enableCompletion( bool enable )
{
d->mUseCompletion = enable;
}
//-----------------------------------------------------------------------------
void AddressLineEdit::doCompletion( bool ctrlT )
{
if ( !d->mUseCompletion ) {
return;
}
QString prevAddr;
QString s( text() );
int n = s.lastIndexOf( QLatin1Char( ',' ) );
if ( n >= 0 ) {
n++; // Go past the ","
int len = s.length();
// Increment past any whitespace...
while ( n < len && s[ n ].isSpace() ) {
n++;
}
prevAddr = s.left( n );
s = s.mid( n, 255 ).trimmed();
}
if ( d->sAddressesDirty ) {
loadAddresses();
}
if ( ctrlT ) {
QStringList completions = sCompletion->substringCompletion( s );
if ( completions.count() > 1 ) {
d->mPreviousAddresses = prevAddr;
setCompletedItems( completions );
} else if ( completions.count() == 1 ) {
setText( prevAddr + completions.first() );
}
cursorAtEnd();
return;
}
KGlobalSettings::Completion mode = completionMode();
switch ( mode ) {
case KGlobalSettings::CompletionPopupAuto:
{
if ( s.isEmpty() ) {
break;
}
}
case KGlobalSettings::CompletionPopup:
{
d->mPreviousAddresses = prevAddr;
QStringList items = sCompletion->allMatches( s );
items += sCompletion->allMatches( QLatin1String( "\"" ) + s );
items += sCompletion->substringCompletion( QLatin1Char( '<' ) + s );
int beforeDollarCompletionCount = items.count();
if ( s.indexOf( QLatin1Char( ' ' ) ) == -1 ) { // one word, possibly given name
items += sCompletion->allMatches( QLatin1String( "$$" ) + s );
}
if ( !items.isEmpty() ) {
if ( items.count() > beforeDollarCompletionCount ) {
// remove the '$$whatever$' part
for ( QStringList::Iterator it = items.begin();
it != items.end(); ++it ) {
int pos = ( *it ).indexOf( QLatin1Char( '$' ), 2 );
if ( pos < 0 ) { // ???
continue;
}
( *it ) = ( *it ).mid( pos + 1 );
}
}
items = d->removeMailDupes( items );
// We do not want KLineEdit::setCompletedItems to perform text
// completion (suggestion) since it does not know how to deal
// with providing proper completions for different items on the
// same line, e.g. comma-separated list of email addresses.
bool autoSuggest = ( mode != KGlobalSettings::CompletionPopupAuto );
setCompletedItems( items, autoSuggest );
if ( !autoSuggest ) {
int index = items.first().indexOf( s );
QString newText = prevAddr + items.first().mid( index );
//kDebug() << "OLD TEXT:" << text();
//kDebug() << "NEW TEXT:" << newText;
setUserSelection( false );
setCompletedText( newText, true );
}
}
break;
}
case KGlobalSettings::CompletionShell:
{
QString match = sCompletion->makeCompletion( s );
if ( !match.isNull() && match != s ) {
setText( prevAddr + match );
cursorAtEnd();
}
break;
}
case KGlobalSettings::CompletionMan: // Short-Auto in fact
case KGlobalSettings::CompletionAuto:
{
if ( !s.isEmpty() ) {
QString match = sCompletion->makeCompletion( s );
if ( !match.isNull() && match != s ) {
setCompletedText( prevAddr + match );
}
break;
}
}
case KGlobalSettings::CompletionNone:
default: // fall through
break;
}
}
//-----------------------------------------------------------------------------
void AddressLineEdit::loadAddresses()
{
sCompletion->clear();
d->sAddressesDirty = false;
const QStringList addrs = d->addresses();
QStringList::ConstIterator end( addrs.end() );
for ( QStringList::ConstIterator it = addrs.begin(); it != end; ++it ) {
addAddress( *it );
}
}
void AddressLineEdit::addAddress( const QString &addr )
{
sCompletion->addItem( addr );
int pos = addr.indexOf( QLatin1Char( '<' ) );
if ( pos >= 0 ) {
++pos;
int pos2 = addr.indexOf( QLatin1Char( '>' ), pos );
if ( pos2 >= 0 ) {
sCompletion->addItem( addr.mid( pos, pos2 - pos ) );
}
}
}
//-----------------------------------------------------------------------------
void AddressLineEdit::dropEvent( QDropEvent *event )
{
const KUrl::List uriList = KUrl::List::fromMimeData( event->mimeData() );
if ( !uriList.isEmpty() ) {
QString ct = text();
KUrl::List::ConstIterator it = uriList.begin();
for ( ; it != uriList.end(); ++it ) {
if ( !ct.isEmpty() ) {
ct.append( QLatin1String( ", " ) );
}
KUrl u( *it );
if ( ( *it ).protocol() == QLatin1String( "mailto" ) ) {
ct.append( ( *it ).path() );
} else {
ct.append( ( *it ).url() );
}
}
setText( ct );
setModified( true );
} else {
if ( d->mUseCompletion ) {
d->mSmartPaste = true;
}
KLineEdit::dropEvent( event );
d->mSmartPaste = false;
}
}
#include "moc_addresslineedit.cpp"