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.2 2002/11/28 15:00:52 lunakl 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     QString match;
00300     int curPos = cursorPosition();
00301     if ( mode != KGlobalSettings::CompletionNone )
00302     {
00303         match = s_completion->makeCompletion( s );
00304         if (match.isNull() && mode == KGlobalSettings::CompletionPopup)
00305           match = s_completion->makeCompletion( "\"" + s );
00306         if (match.isNull() && mode == KGlobalSettings::CompletionPopup)
00307           match = s_completion->makeCompletion( "$$" + s );
00308     }
00309 
00310     kdDebug() << "** completion for: " << s << " : " << match << endl;
00311 
00312     if ( ctrlT )
00313     {
00314         QStringList addresses = s_completion->items();
00315         QStringList::Iterator it = addresses.begin();
00316         QStringList completions;
00317         for (; it != addresses.end(); ++it)
00318         {
00319             if ((*it).find(s,0,false) >= 0)
00320                 completions.append( *it );
00321         }
00322 
00323         if (completions.count() > 1) {
00324             m_previousAddresses = prevAddr;
00325             box->setItems( completions );
00326             box->setCancelledText( text() );
00327             box->popup();
00328         }
00329         else if (completions.count() == 1)
00330             slotSetTextAsEdited(prevAddr + completions.first());
00331         else
00332             box->hide();
00333 
00334         cursorAtEnd();
00335         return;
00336     }
00337 
00338     switch ( mode )
00339     {
00340         case KGlobalSettings::CompletionPopup:
00341         {
00342             if ( !match.isNull() )
00343             {
00344                 m_previousAddresses = prevAddr;
00345         QStringList items = s_completion->allMatches( s );
00346                 items += s_completion->allMatches( "\"" + s );
00347         items += s_completion->substringCompletion( '<' + s );
00348         if( !s.contains( ' ' )) // one word, possibly given name
00349             items += s_completion->allMatches( "$$" + s );
00350             for( QStringList::Iterator it = items.begin();
00351              it != items.end();
00352              ++it )
00353         { // remove the '$$whatever$' part
00354             int pos = (*it).find( '$', 2 );
00355             if( pos < 0 ) // ???
00356                 continue;
00357             (*it)=(*it).mid( pos + 1 );
00358         }
00359         items = removeMailDupes( items );
00360         box->setItems( items );
00361                 box->setCancelledText( text() );
00362                 box->popup();
00363             }
00364             else
00365                 box->hide();
00366 
00367             break;
00368         }
00369 
00370         case KGlobalSettings::CompletionShell:
00371         {
00372             if ( !match.isNull() && match != s )
00373             {
00374                 slotSetTextAsEdited( prevAddr + match );
00375         cursorAtEnd();
00376             }
00377             break;
00378         }
00379 
00380         case KGlobalSettings::CompletionMan: // Short-Auto in fact
00381         case KGlobalSettings::CompletionAuto:
00382         {
00383             if ( !match.isNull() && match != s )
00384             {
00385                 QString adds = prevAddr + match;
00386                 validateAndSet( adds, curPos, curPos, adds.length() );
00387             }
00388             break;
00389         }
00390 
00391         default: // fall through
00392         case KGlobalSettings::CompletionNone:
00393             break;
00394     }
00395 }
00396 
00397 //-----------------------------------------------------------------------------
00398 void AddressLineEdit::slotPopupCompletion( const QString& completion )
00399 {
00400     slotSetTextAsEdited( m_previousAddresses + completion );
00401     cursorAtEnd();
00402 }
00403 
00404 //-----------------------------------------------------------------------------
00405 void AddressLineEdit::loadAddresses()
00406 {
00407     s_completion->clear();
00408     s_addressesDirty = false;
00409 
00410     QStringList adrs = addresses();
00411     for( QStringList::ConstIterator it = adrs.begin();
00412      it != adrs.end();
00413      ++it)
00414         addAddress( *it );
00415 }
00416 
00417 void AddressLineEdit::addAddress( const QString& adr )
00418 {
00419     s_completion->addItem( adr );
00420     int pos = adr.find( '<' );
00421     if( pos >= 0 )
00422     {
00423         ++pos;
00424         int pos2 = adr.find( pos, '>' );
00425         if( pos2 >= 0 )
00426             s_completion->addItem( adr.mid( pos, pos2 - pos ));
00427     }
00428 }
00429 
00430 void AddressLineEdit::slotStartLDAPLookup()
00431 {
00432     if( !s_LDAPSearch->isAvailable() || s_LDAPLineEdit != this )
00433     return;
00434     startLoadingLDAPEntries();
00435 }
00436 
00437 void AddressLineEdit::stopLDAPLookup()
00438 {
00439     s_LDAPSearch->cancelSearch();
00440     s_LDAPLineEdit = NULL;
00441 }
00442 
00443 void AddressLineEdit::startLoadingLDAPEntries()
00444 {
00445     QString s( *s_LDAPText );
00446     // TODO cache last?
00447     QString prevAddr;
00448     int n = s.findRev(',');
00449     if (n>= 0)
00450     {
00451         prevAddr = s.left(n+1) + ' ';
00452         s = s.mid(n+1,255).stripWhiteSpace();
00453     }
00454     if( s.length() == 0 )
00455     return;
00456     loadAddresses(); // TODO reuse these?
00457     s_LDAPSearch->startSearch( s );
00458 }
00459 
00460 void AddressLineEdit::slotLDAPSearchData( const QStringList& adrs )
00461 {
00462     if( s_LDAPLineEdit != this )
00463         return;
00464     for( QStringList::ConstIterator it = adrs.begin();
00465      it != adrs.end();
00466      ++it )
00467     addAddress( *it );
00468     if( hasFocus() || completionBox()->hasFocus())
00469     {
00470         // avoid ShellCompletion too, as it would set the first
00471         // match as the edited text, not only offer it
00472         if( completionMode() != KGlobalSettings::CompletionShell
00473             && completionMode() != KGlobalSettings::CompletionNone )
00474         {
00475             doCompletion( false );
00476         }
00477     }
00478 }
00479 
00480 void AddressLineEdit::slotSetTextAsEdited( const QString& text )
00481 {
00482     setText( text );
00483     setEdited( true );
00484 }
00485 
00486 QStringList AddressLineEdit::removeMailDupes( const QStringList& adrs )
00487 {
00488     QStringList src = adrs;
00489     qHeapSort( src );
00490     QString last;
00491     for( QStringList::Iterator it = src.begin();
00492      it != src.end();
00493      )
00494     {
00495     if( *it == last )
00496     {
00497         it = src.remove( it );
00498         continue; // dupe
00499     }
00500     last = *it;
00501     ++it;
00502     }
00503     return src;
00504 }
00505 
00506 //-----------------------------------------------------------------------------
00507 void AddressLineEdit::dropEvent(QDropEvent *e)
00508 {
00509   KURL::List uriList;
00510   if(KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ))
00511   {
00512     QString ct = text();
00513     KURL::List::Iterator it = uriList.begin();
00514     for (; it != uriList.end(); ++it)
00515     {
00516       if (!ct.isEmpty()) ct.append(", ");
00517       KURL u(*it);
00518       if ((*it).protocol() == "mailto")
00519           ct.append( (*it).path() );
00520       else
00521           ct.append( (*it).url() );
00522     }
00523     setText(ct);
00524     setEdited( true );
00525   }
00526   else {
00527     if (m_useCompletion)
00528        m_smartPaste = true;
00529     QLineEdit::dropEvent(e);
00530     m_smartPaste = false;
00531   }
00532 }
00533 
00534 
00535 QStringList AddressLineEdit::addresses()
00536 {
00537   QStringList result;
00538 
00539   KABC::AddressBook *addressBook = KABC::StdAddressBook::self();
00540   KABC::AddressBook::Iterator it;
00541   for( it = addressBook->begin(); it != addressBook->end(); ++it ) {
00542     QStringList emails = (*it).emails();
00543     QString n = (*it).prefix() + " " +
00544         (*it).givenName() + " " +
00545         (*it).additionalName() + " " +
00546             (*it).familyName() + " " +
00547         (*it).suffix();
00548     n = n.simplifyWhiteSpace();
00549 
00550     QRegExp needQuotes("[^ 0-9A-Za-z\\x0080-\\xFFFF]");
00551     QString endQuote = "\" ";
00552     QString empty = "";
00553     QStringList::ConstIterator mit;
00554     QString addr, email;
00555 
00556     for ( mit = emails.begin(); mit != emails.end(); ++mit ) {
00557       email = *mit;
00558       if (!email.isEmpty()) {
00559     if (n.isEmpty() || (email.find( '<' ) != -1))
00560       addr = empty;
00561     else { /* do we really need quotes around this name ? */
00562           if (n.find(needQuotes) != -1)
00563         addr = '"' + n + endQuote;
00564       else
00565         addr = n + ' ';
00566     }
00567 
00568     if (!addr.isEmpty() && (email.find( '<' ) == -1)
00569         && (email.find( '>' ) == -1)
00570         && (email.find( ',' ) == -1))
00571       addr += '<' + email + '>';
00572     else
00573       addr += email;
00574     addr = addr.stripWhiteSpace();
00575     result.append( addr );
00576       }
00577     }
00578   }
00579   KABC::DistributionListManager manager( addressBook );
00580   manager.load();
00581 
00582   QStringList names = manager.listNames();
00583   QStringList::Iterator jt;
00584   for ( jt = names.begin(); jt != names.end(); ++jt)
00585     result.append( *jt );
00586   result.sort();
00587 
00588   return result;
00589 }
00590 
00591 #include "addresslineedit.moc"
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.0.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Oct 8 12:22:07 2003 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2001