kabc Library API Documentation

addresslineedit.cpp

00001 /*
00002     This file is part of libkabc.
00003     Copyright (c) 2002 Helge Deller <deller@gmx.de>
00004           2002 Lubos Lunak <llunak@suse.cz>
00005                   2001 Carsten Pfeiffer <pfeiffer@kde.org>
00006                   2001 Waldo Bastian <bastian@kde.org>
00007 
00008     This library is free software; you can redistribute it and/or
00009     modify it under the terms of the GNU Library General Public
00010     License as published by the Free Software Foundation; either
00011     version 2 of the License, or (at your option) any later version.
00012 
00013     This library is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016     Library General Public License for more details.
00017 
00018     You should have received a copy of the GNU Library General Public License
00019     along with this library; see the file COPYING.LIB.  If not, write to
00020     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00021     Boston, MA 02111-1307, USA.
00022 */
00023 
00024 // $Id: addresslineedit.cpp,v 1.6.2.5 2003/07/22 06:28:05 deller Exp $
00025 
00026 #include "addresslineedit.h"
00027 
00028 #include <qobject.h>
00029 #include <qptrlist.h>
00030 #include <qregexp.h>
00031 #include <qevent.h>
00032 #include <qdragobject.h>
00033 
00034 #include <kcompletionbox.h>
00035 #include <kstdaccel.h>
00036 #include <kurldrag.h>
00037 
00038 #include <kabc/stdaddressbook.h>
00039 #include <kabc/distributionlist.h>
00040 #include <kurldrag.h>
00041 #include "ldapclient.h"
00042 
00043 #include <kdebug.h>
00044 
00045 //=============================================================================
00046 //
00047 //   Class  AddressLineEdit
00048 //
00049 //=============================================================================
00050 
00051 
00052 using namespace KABC;
00053 
00054 KCompletion * AddressLineEdit::s_completion = 0L;
00055 bool AddressLineEdit::s_addressesDirty = false;
00056 QTimer* AddressLineEdit::s_LDAPTimer = 0L;
00057 LdapSearch* AddressLineEdit::s_LDAPSearch = 0L;
00058 QString* AddressLineEdit::s_LDAPText = 0L;
00059 AddressLineEdit* AddressLineEdit::s_LDAPLineEdit = 0L;
00060 
00061 AddressLineEdit::AddressLineEdit(QWidget* parent,
00062         bool useCompletion,
00063         const char *name)
00064     : KLineEdit(parent,name)
00065 {
00066   m_useCompletion = useCompletion;
00067   m_completionInitialized = false;
00068   m_smartPaste = false;
00069 
00070   init();
00071 
00072   // Whenever a new AddressLineEdit is created (== a new composer is created),
00073   // we set a dirty flag to reload the addresses upon the first completion.
00074   // The address completions are shared between all AddressLineEdits.
00075   // Is there a signal that tells us about addressbook updates?
00076   if (m_useCompletion)
00077     s_addressesDirty = true;
00078 }
00079 
00080 
00081 //-----------------------------------------------------------------------------
00082 void AddressLineEdit::init()
00083 {
00084   if ( !s_completion ) {
00085       s_completion = new KCompletion();
00086       s_completion->setOrder( KCompletion::Sorted );
00087       s_completion->setIgnoreCase( true );
00088   }
00089 
00090   if( m_useCompletion ) {
00091       if( !s_LDAPTimer ) {
00092         s_LDAPTimer = new QTimer;
00093         s_LDAPSearch = new LdapSearch;
00094         s_LDAPText = new QString;
00095       }
00096       connect( s_LDAPTimer, SIGNAL( timeout()), SLOT( slotStartLDAPLookup()));
00097       connect( s_LDAPSearch, SIGNAL( searchData( const QStringList& )),
00098         SLOT( slotLDAPSearchData( const QStringList& )));
00099   }
00100 
00101   if ( m_useCompletion && !m_completionInitialized )
00102   {
00103       setCompletionObject( s_completion, false ); // we handle it ourself
00104       connect( this, SIGNAL( completion(const QString&)),
00105                this, SLOT(slotCompletion() ));
00106 
00107       KCompletionBox *box = completionBox();
00108       connect( box, SIGNAL( highlighted( const QString& )),
00109                this, SLOT( slotPopupCompletion( const QString& ) ));
00110       connect( box, SIGNAL( userCancelled( const QString& )),
00111                SLOT( slotSetTextAsEdited( const QString& )));
00112 
00113       m_completionInitialized = true; // don't connect muliple times. That's
00114                                       // ugly, tho, better have completionBox()
00115                                       // virtual in KDE 4
00116       // Why? This is only called once. Why should this be called more
00117       // than once? And why was this protected?
00118       // And while I'm at it: who deletes all those static objects? (pfeiffer)
00119   }
00120 }
00121 
00122 //-----------------------------------------------------------------------------
00123 AddressLineEdit::~AddressLineEdit()
00124 {
00125 }
00126 
00127 //-----------------------------------------------------------------------------
00128 void AddressLineEdit::setFont( const QFont& font )
00129 {
00130     KLineEdit::setFont( font );
00131     if ( m_useCompletion )
00132         completionBox()->setFont( font );
00133 }
00134 
00135 //-----------------------------------------------------------------------------
00136 void AddressLineEdit::keyPressEvent(QKeyEvent *e)
00137 {
00138     bool accept = false;
00139 
00140     if (KStdAccel::shortcut(KStdAccel::SubstringCompletion).contains(KKey(e)))
00141     {
00142       doCompletion(true);
00143       accept = true;
00144     }
00145     else if (e->state()==ControlButton && e->key() == Key_Right)
00146     {
00147       if ((int)text().length() == cursorPosition()) // at End?
00148       {
00149         doCompletion(true);
00150     accept = true;
00151       }
00152     }
00153     else if (e->state()==ControlButton && e->key() == Key_V)
00154     {
00155       if (m_useCompletion)
00156          m_smartPaste = true;
00157       paste();
00158       m_smartPaste = false;
00159       accept = true;
00160     }
00161 
00162     if( !accept )
00163         KLineEdit::keyPressEvent( e );
00164 
00165     if( e->isAccepted())
00166     {
00167         if( m_useCompletion && s_LDAPTimer != NULL )
00168     {
00169             if( *s_LDAPText != text())
00170                 stopLDAPLookup();
00171         *s_LDAPText = text();
00172         s_LDAPLineEdit = this;
00173         s_LDAPTimer->start( 500, true );
00174     }
00175     }
00176 }
00177 
00178 void AddressLineEdit::mouseReleaseEvent( QMouseEvent * e )
00179 {
00180    if (m_useCompletion && (e->button() == MidButton))
00181    {
00182       m_smartPaste = true;
00183       KLineEdit::mouseReleaseEvent(e);
00184       m_smartPaste = false;
00185       return;
00186    }
00187    KLineEdit::mouseReleaseEvent(e);
00188 }
00189 
00190 void AddressLineEdit::insert(const QString &t)
00191 {
00192     if (!m_smartPaste)
00193     {
00194        KLineEdit::insert(t);
00195        return;
00196     }
00197     QString newText = t.stripWhiteSpace();
00198     if (newText.isEmpty())
00199        return;
00200 
00201     // remove newlines in the to-be-pasted string as well as an eventual
00202     // mailto: protocol
00203     newText.replace( QRegExp("\r?\n"), " " );
00204     if ( newText.startsWith( "mailto:" ) )
00205         newText.remove( 0, 7 );
00206     else if (newText.contains(" at "))
00207     {
00208        // Anti-spam stuff
00209        newText.replace( QRegExp(" at "), "@" );
00210        newText.replace( QRegExp(" dot "), "." );
00211     }
00212     else if (newText.contains("(at)"))
00213     {
00214       newText.replace( QRegExp("\\s*\\(at\\)\\s*"), "@" );
00215     }
00216 
00217     QString contents = text();
00218     int start_sel = 0;
00219     int end_sel = 0;
00220     int pos = cursorPosition();
00221     if (getSelection(&start_sel, &end_sel))
00222     {
00223        // Cut away the selection.
00224        if (pos > end_sel)
00225           pos -= (end_sel - start_sel);
00226        else if (pos > start_sel)
00227           pos = start_sel;
00228        contents = contents.left(start_sel) + contents.right(end_sel+1);
00229     }
00230 
00231     int eot = contents.length();
00232     while ((eot > 0) && contents[eot-1].isSpace()) eot--;
00233     if (eot == 0)
00234     {
00235        contents = QString::null;
00236     }
00237     else if (pos >= eot)
00238     {
00239        if (contents[eot-1] == ',')
00240           eot--;
00241        contents.truncate(eot);
00242        contents += ", ";
00243        pos = eot+2;
00244     }
00245 
00246     contents = contents.left(pos)+newText+contents.mid(pos);
00247     slotSetTextAsEdited(contents);
00248     setCursorPosition(pos+newText.length());
00249 }
00250 
00251 void AddressLineEdit::paste()
00252 {
00253     if (m_useCompletion)
00254        m_smartPaste = true;
00255     KLineEdit::paste();
00256     m_smartPaste = false;
00257 }
00258 
00259 //-----------------------------------------------------------------------------
00260 void AddressLineEdit::cursorAtEnd()
00261 {
00262     setCursorPosition( text().length() );
00263 }
00264 
00265 //-----------------------------------------------------------------------------
00266 void AddressLineEdit::enableCompletion(bool enable)
00267 {
00268   m_useCompletion = enable;
00269 }
00270 
00271 //-----------------------------------------------------------------------------
00272 void AddressLineEdit::doCompletion(bool ctrlT)
00273 {
00274     if ( !m_useCompletion )
00275         return;
00276 
00277     QString s(text());
00278     QString prevAddr;
00279     int n = s.findRev(',');
00280     if (n>= 0)
00281     {
00282         prevAddr = s.left(n+1) + ' ';
00283         s = s.mid(n+1,255).stripWhiteSpace();
00284     }
00285 
00286     KCompletionBox *box = completionBox();
00287 
00288     if ( s.isEmpty() )
00289     {
00290         box->hide();
00291         return;
00292     }
00293 
00294     KGlobalSettings::Completion  mode = completionMode();
00295 
00296     if ( s_addressesDirty )
00297         loadAddresses();
00298 
00299     if ( ctrlT )
00300     {
00301         QStringList completions = s_completion->substringCompletion( s );
00302         if (completions.count() > 1) {
00303             m_previousAddresses = prevAddr;
00304             box->setItems( completions );
00305             box->setCancelledText( text() );
00306             box->popup();
00307         }
00308         else if (completions.count() == 1)
00309             slotSetTextAsEdited(prevAddr + completions.first());
00310         else
00311             box->hide();
00312 
00313         cursorAtEnd();
00314         return;
00315     }
00316 
00317     switch ( mode )
00318     {
00319         case KGlobalSettings::CompletionPopup:
00320         {
00321                 m_previousAddresses = prevAddr;
00322         QStringList items = s_completion->allMatches( s );
00323                 items += s_completion->allMatches( "\"" + s );
00324         items += s_completion->substringCompletion( '<' + s );
00325             uint beforeDollarCompletionCount = items.count();
00326             
00327         if( !s.contains( ' ' )) // one word, possibly given name
00328             items += s_completion->allMatches( "$$" + s );
00329             
00330             if ( items.isEmpty() )
00331                 box->hide();
00332             else
00333             {
00334                 if ( items.count() > beforeDollarCompletionCount )
00335                 {
00336                     // remove the '$$whatever$' part                    
00337             for( QStringList::Iterator it = items.begin();
00338              it != items.end();
00339              ++it )
00340                     { 
00341             int pos = (*it).find( '$', 2 );
00342             if( pos < 0 ) // ???
00343                 continue;
00344             (*it)=(*it).mid( pos + 1 );
00345         }
00346                 }
00347                 
00348         items = removeMailDupes( items );
00349         box->setItems( items );
00350                 box->setCancelledText( text() );
00351                 box->popup();
00352             }
00353 
00354             break;
00355         }
00356 
00357         case KGlobalSettings::CompletionShell:
00358         {
00359             QString match = s_completion->makeCompletion( s );
00360             if ( !match.isNull() && match != s )
00361             {
00362                 slotSetTextAsEdited( prevAddr + match );
00363         cursorAtEnd();
00364             }
00365             break;
00366         }
00367 
00368         case KGlobalSettings::CompletionMan: // Short-Auto in fact
00369         case KGlobalSettings::CompletionAuto:
00370         {
00371             QString match = s_completion->makeCompletion( s );
00372             if ( !match.isNull() && match != s )
00373             {
00374                 QString adds = prevAddr + match;
00375                 int curPos = cursorPosition();
00376                 validateAndSet( adds, curPos, curPos, adds.length() );
00377             }
00378             break;
00379         }
00380 
00381         default: // fall through
00382         case KGlobalSettings::CompletionNone:
00383             break;
00384     }
00385 }
00386 
00387 //-----------------------------------------------------------------------------
00388 void AddressLineEdit::slotPopupCompletion( const QString& completion )
00389 {
00390     slotSetTextAsEdited( m_previousAddresses + completion );
00391     cursorAtEnd();
00392 }
00393 
00394 //-----------------------------------------------------------------------------
00395 void AddressLineEdit::loadAddresses()
00396 {
00397     s_completion->clear();
00398     s_addressesDirty = false;
00399 
00400     QStringList adrs = addresses();
00401     for( QStringList::ConstIterator it = adrs.begin();
00402      it != adrs.end();
00403      ++it)
00404         addAddress( *it );
00405 }
00406 
00407 void AddressLineEdit::addAddress( const QString& adr )
00408 {
00409     s_completion->addItem( adr );
00410     int pos = adr.find( '<' );
00411     if( pos >= 0 )
00412     {
00413         ++pos;
00414         int pos2 = adr.find( pos, '>' );
00415         if( pos2 >= 0 )
00416             s_completion->addItem( adr.mid( pos, pos2 - pos ));
00417     }
00418 }
00419 
00420 void AddressLineEdit::slotStartLDAPLookup()
00421 {
00422     if( !s_LDAPSearch->isAvailable() || s_LDAPLineEdit != this )
00423     return;
00424     startLoadingLDAPEntries();
00425 }
00426 
00427 void AddressLineEdit::stopLDAPLookup()
00428 {
00429     s_LDAPSearch->cancelSearch();
00430     s_LDAPLineEdit = NULL;
00431 }
00432 
00433 void AddressLineEdit::startLoadingLDAPEntries()
00434 {
00435     QString s( *s_LDAPText );
00436     // TODO cache last?
00437     QString prevAddr;
00438     int n = s.findRev(',');
00439     if (n>= 0)
00440     {
00441         prevAddr = s.left(n+1) + ' ';
00442         s = s.mid(n+1,255).stripWhiteSpace();
00443     }
00444     if( s.length() == 0 )
00445     return;
00446     loadAddresses(); // TODO reuse these?
00447     s_LDAPSearch->startSearch( s );
00448 }
00449 
00450 void AddressLineEdit::slotLDAPSearchData( const QStringList& adrs )
00451 {
00452     if( s_LDAPLineEdit != this )
00453         return;
00454     for( QStringList::ConstIterator it = adrs.begin();
00455      it != adrs.end();
00456      ++it ) {
00457     QString name(*it);
00458     int pos = name.find( " <" );
00459     int pos_comma = name.find( ',' );
00460     // put name in quotes, if we have a comma in the name
00461     if (pos>0 && pos_comma>0 && pos_comma<pos) {
00462         name.insert(pos, '\"');
00463         name.prepend('\"');
00464     }
00465     addAddress( name );
00466     }
00467     if( hasFocus() || completionBox()->hasFocus())
00468     {
00469         if( completionMode() != KGlobalSettings::CompletionNone )
00470         {
00471             doCompletion( false );
00472         }
00473     }
00474 }
00475 
00476 void AddressLineEdit::slotSetTextAsEdited( const QString& text )
00477 {
00478     setText( text );
00479     setEdited( true );
00480 }
00481 
00482 QStringList AddressLineEdit::removeMailDupes( const QStringList& adrs )
00483 {
00484     QStringList src = adrs;
00485     qHeapSort( src );
00486     QString last;
00487     for( QStringList::Iterator it = src.begin();
00488      it != src.end();
00489      )
00490     {
00491     if( *it == last )
00492     {
00493         it = src.remove( it );
00494         continue; // dupe
00495     }
00496     last = *it;
00497     ++it;
00498     }
00499     return src;
00500 }
00501 
00502 //-----------------------------------------------------------------------------
00503 void AddressLineEdit::dropEvent(QDropEvent *e)
00504 {
00505   KURL::List uriList;
00506   if(KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ))
00507   {
00508     QString ct = text();
00509     KURL::List::Iterator it = uriList.begin();
00510     for (; it != uriList.end(); ++it)
00511     {
00512       if (!ct.isEmpty()) ct.append(", ");
00513       KURL u(*it);
00514       if ((*it).protocol() == "mailto")
00515           ct.append( (*it).path() );
00516       else
00517           ct.append( (*it).url() );
00518     }
00519     setText(ct);
00520     setEdited( true );
00521   }
00522   else {
00523     if (m_useCompletion)
00524        m_smartPaste = true;
00525     QLineEdit::dropEvent(e);
00526     m_smartPaste = false;
00527   }
00528 }
00529 
00530 
00531 QStringList AddressLineEdit::addresses()
00532 {
00533   QStringList result;
00534 
00535   KABC::AddressBook *addressBook = KABC::StdAddressBook::self();
00536   KABC::AddressBook::Iterator it;
00537   for( it = addressBook->begin(); it != addressBook->end(); ++it ) {
00538     QStringList emails = (*it).emails();
00539     QString n = (*it).prefix() + " " +
00540         (*it).givenName() + " " +
00541         (*it).additionalName() + " " +
00542             (*it).familyName() + " " +
00543         (*it).suffix();
00544     n = n.simplifyWhiteSpace();
00545 
00546     QRegExp needQuotes("[^ 0-9A-Za-z\\x0080-\\xFFFF]");
00547     QString endQuote("\" ");
00548     QString empty = "";
00549     QStringList::ConstIterator mit;
00550     QString addr, email;
00551 
00552     for ( mit = emails.begin(); mit != emails.end(); ++mit ) {
00553       email = *mit;
00554       if (!email.isEmpty()) {
00555     if (n.isEmpty() || (email.find( '<' ) != -1))
00556       addr = empty;
00557     else { /* do we really need quotes around this name ? */
00558           if (n.find(needQuotes) != -1)
00559         addr = '"' + n + endQuote;
00560       else
00561         addr = n + ' ';
00562     }
00563 
00564     if (!addr.isEmpty() && (email.find( '<' ) == -1)
00565         && (email.find( '>' ) == -1)
00566         && (email.find( ',' ) == -1))
00567       addr += '<' + email + '>';
00568     else
00569       addr += email;
00570     addr = addr.stripWhiteSpace();
00571     result.append( addr );
00572       }
00573     }
00574   }
00575   KABC::DistributionListManager manager( addressBook );
00576   manager.load();
00577 
00578   QStringList names = manager.listNames();
00579   QStringList::Iterator jt;
00580   for ( jt = names.begin(); jt != names.end(); ++jt)
00581     result.append( *jt );
00582   result.sort();
00583 
00584   return result;
00585 }
00586 
00587 #include "addresslineedit.moc"
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.4.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Sun Feb 27 22:16:07 2005 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001