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,2003 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., 51 Franklin Street, Fifth Floor,
00021     Boston, MA 02110-1301, USA.
00022 */
00023 
00024 // $Id: addresslineedit.cpp 465272 2005-09-29 09:47:40Z mueller $
00025 
00026 #include "addresslineedit.h"
00027 
00028 #include <qapplication.h>
00029 #include <qobject.h>
00030 #include <qptrlist.h>
00031 #include <qregexp.h>
00032 #include <qevent.h>
00033 #include <qdragobject.h>
00034 
00035 #include <kcompletionbox.h>
00036 #include <kconfig.h>
00037 #include <kcursor.h>
00038 #include <kstandarddirs.h>
00039 #include <kstaticdeleter.h>
00040 #include <kstdaccel.h>
00041 #include <kurldrag.h>
00042 
00043 #include <kabc/stdaddressbook.h>
00044 #include <kabc/distributionlist.h>
00045 #include "ldapclient.h"
00046 
00047 #include <kdebug.h>
00048 
00049 //=============================================================================
00050 //
00051 //   Class  AddressLineEdit
00052 //
00053 //=============================================================================
00054 
00055 
00056 using namespace KABC;
00057 
00058 KCompletion * AddressLineEdit::s_completion = 0L;
00059 bool AddressLineEdit::s_addressesDirty = false;
00060 QTimer* AddressLineEdit::s_LDAPTimer = 0L;
00061 LdapSearch* AddressLineEdit::s_LDAPSearch = 0L;
00062 QString* AddressLineEdit::s_LDAPText = 0L;
00063 AddressLineEdit* AddressLineEdit::s_LDAPLineEdit = 0L;
00064 KConfig *AddressLineEdit::s_config = 0L;
00065 
00066 static KStaticDeleter<KCompletion> completionDeleter;
00067 static KStaticDeleter<QTimer> ldapTimerDeleter;
00068 static KStaticDeleter<LdapSearch> ldapSearchDeleter;
00069 static KStaticDeleter<QString> ldapTextDeleter;
00070 static KStaticDeleter<KConfig> configDeleter;
00071 
00072 AddressLineEdit::AddressLineEdit(QWidget* parent,
00073         bool useCompletion,
00074         const char *name)
00075     : KLineEdit(parent,name)
00076 {
00077   m_useCompletion = useCompletion;
00078   m_completionInitialized = false;
00079   m_smartPaste = false;
00080 
00081   init();
00082 
00083   // Whenever a new AddressLineEdit is created (== a new composer is created),
00084   // we set a dirty flag to reload the addresses upon the first completion.
00085   // The address completions are shared between all AddressLineEdits.
00086   // Is there a signal that tells us about addressbook updates?
00087   if (m_useCompletion)
00088     s_addressesDirty = true;
00089 }
00090 
00091 
00092 //-----------------------------------------------------------------------------
00093 void AddressLineEdit::init()
00094 {
00095   if ( !s_completion ) {
00096       completionDeleter.setObject( s_completion, new KCompletion() );
00097       s_completion->setOrder( KCompletion::Sorted );
00098       s_completion->setIgnoreCase( true );
00099   }
00100 
00101   if( m_useCompletion ) {
00102       if( !s_LDAPTimer ) {
00103         ldapTimerDeleter.setObject( s_LDAPTimer, new QTimer );
00104         ldapSearchDeleter.setObject( s_LDAPSearch, new LdapSearch );
00105         ldapTextDeleter.setObject( s_LDAPText, new QString );
00106       }
00107       connect( s_LDAPTimer, SIGNAL( timeout()), SLOT( slotStartLDAPLookup()));
00108       connect( s_LDAPSearch, SIGNAL( searchData( const QStringList& )),
00109         SLOT( slotLDAPSearchData( const QStringList& )));
00110   }
00111 
00112   if ( m_useCompletion && !m_completionInitialized )
00113   {
00114       setCompletionObject( s_completion, false ); // we handle it ourself
00115       connect( this, SIGNAL( completion(const QString&)),
00116                this, SLOT(slotCompletion() ));
00117       
00118       KCompletionBox *box = completionBox();
00119       connect( box, SIGNAL( highlighted( const QString& )),
00120                this, SLOT( slotPopupCompletion( const QString& ) ));
00121       connect( box, SIGNAL( userCancelled( const QString& )),
00122                SLOT( userCancelled( const QString& )));
00123 
00124       m_completionInitialized = true; // don't connect muliple times. That's
00125                                       // ugly, tho, better have completionBox()
00126                                       // virtual in KDE 4
00127       // Why? This is only called once. Why should this be called more
00128       // than once? And why was this protected?
00129   }
00130 }
00131 
00132 //-----------------------------------------------------------------------------
00133 AddressLineEdit::~AddressLineEdit()
00134 {
00135 }
00136 
00137 //-----------------------------------------------------------------------------
00138 
00139 KConfig* AddressLineEdit::config()
00140 {
00141   if ( !s_config )
00142     configDeleter.setObject( s_config, new KConfig( "kabldaprc", false, false ) ); // Open read-write, no kdeglobals
00143 
00144   return s_config;
00145 }
00146 
00147 void AddressLineEdit::setFont( const QFont& font )
00148 {
00149     KLineEdit::setFont( font );
00150     if ( m_useCompletion )
00151         completionBox()->setFont( font );
00152 }
00153 
00154 //-----------------------------------------------------------------------------
00155 void AddressLineEdit::keyPressEvent(QKeyEvent *e)
00156 {
00157     bool accept = false;
00158 
00159     if (KStdAccel::shortcut(KStdAccel::SubstringCompletion).contains(KKey(e)))
00160     {
00161         doCompletion(true);
00162         accept = true;
00163     }
00164     else if (KStdAccel::shortcut(KStdAccel::TextCompletion).contains(KKey(e)))
00165     {
00166         int len = text().length();
00167         
00168         if (len == cursorPosition()) // at End?
00169         {
00170             doCompletion(true);
00171             accept = true;
00172         }
00173     }
00174 
00175     if( !accept )
00176         KLineEdit::keyPressEvent( e );
00177 
00178     if( e->isAccepted())
00179     {
00180         if( m_useCompletion && s_LDAPTimer != NULL )
00181         {
00182             if( *s_LDAPText != text())
00183                 stopLDAPLookup();
00184             *s_LDAPText = text();
00185             s_LDAPLineEdit = this;
00186             s_LDAPTimer->start( 500, true );
00187         }
00188     }
00189 }
00190 
00191 void AddressLineEdit::mouseReleaseEvent( QMouseEvent * e )
00192 {
00193    if (m_useCompletion && (e->button() == MidButton))
00194    {
00195       m_smartPaste = true;
00196       KLineEdit::mouseReleaseEvent(e);
00197       m_smartPaste = false;
00198       return;
00199    }
00200    KLineEdit::mouseReleaseEvent(e);
00201 }
00202 
00203 void AddressLineEdit::insert(const QString &t)
00204 {
00205     if (!m_smartPaste)
00206     {
00207        KLineEdit::insert(t);
00208        return;
00209     }
00210     QString newText = t.stripWhiteSpace();
00211     if (newText.isEmpty())
00212        return;
00213 
00214     // remove newlines in the to-be-pasted string as well as an eventual
00215     // mailto: protocol
00216     newText.replace( QRegExp("\r?\n"), ", " );
00217     if ( newText.startsWith( "mailto:" ) )
00218     {
00219       KURL u(newText);
00220       newText = u.path();
00221     }
00222     else if (newText.find(" at ") != -1)
00223     {
00224        // Anti-spam stuff
00225        newText.replace( " at ", "@" );
00226        newText.replace( " dot ", "." );
00227     }
00228     else if (newText.find("(at)") != -1)
00229     {
00230       newText.replace( QRegExp("\\s*\\(at\\)\\s*"), "@" );
00231     }
00232 
00233     QString contents = text();
00234     int start_sel = 0;
00235     int end_sel = 0;
00236     int pos = cursorPosition();
00237     if (getSelection(&start_sel, &end_sel))
00238     {
00239        // Cut away the selection.
00240        if (pos > end_sel)
00241           pos -= (end_sel - start_sel);
00242        else if (pos > start_sel)
00243           pos = start_sel;
00244        contents = contents.left(start_sel) + contents.right(end_sel+1);
00245     }
00246 
00247     int eot = contents.length();
00248     while ((eot > 0) && contents[eot-1].isSpace()) eot--;
00249     if (eot == 0)
00250     {
00251        contents = QString::null;
00252     }
00253     else if (pos >= eot)
00254     {
00255        if (contents[eot-1] == ',')
00256           eot--;
00257        contents.truncate(eot);
00258        contents += ", ";
00259        pos = eot+2;
00260     }
00261 
00262     contents = contents.left(pos)+newText+contents.mid(pos);
00263     setText(contents);
00264     setCursorPosition(pos+newText.length());
00265 }
00266 
00267 void AddressLineEdit::paste()
00268 {
00269     if (m_useCompletion)
00270        m_smartPaste = true;
00271     KLineEdit::paste();
00272     m_smartPaste = false;
00273 }
00274 
00275 //-----------------------------------------------------------------------------
00276 void AddressLineEdit::cursorAtEnd()
00277 {
00278     setCursorPosition( text().length() );
00279 }
00280 
00281 //-----------------------------------------------------------------------------
00282 void AddressLineEdit::enableCompletion(bool enable)
00283 {
00284   m_useCompletion = enable;
00285 }
00286 
00287 //-----------------------------------------------------------------------------
00288 void AddressLineEdit::doCompletion(bool ctrlT)
00289 {
00290     if ( !m_useCompletion )
00291         return;
00292 
00293     QString prevAddr;    
00294     
00295     QString s(text());    
00296     int n = s.findRev(',');
00297         
00298     if (n >= 0)
00299     {
00300         n++; // Go past the ","
00301         
00302         int len = s.length();
00303         
00304         // Increment past any whitespace...
00305         while( n < len && s[n].isSpace() )
00306           n++;
00307                   
00308         prevAddr = s.left(n);
00309         s = s.mid(n,255).stripWhiteSpace();
00310     }
00311 
00312     if ( s_addressesDirty )
00313         loadAddresses();
00314 
00315     if ( ctrlT )
00316     {
00317         QStringList completions = s_completion->substringCompletion( s );
00318         if (completions.count() > 1) {
00319             m_previousAddresses = prevAddr;
00320             setCompletedItems( completions );
00321         }
00322         else if (completions.count() == 1)
00323             setText(prevAddr + completions.first());
00324 
00325         cursorAtEnd();
00326         return;
00327     }
00328     
00329     KGlobalSettings::Completion  mode = completionMode();
00330     
00331     switch ( mode )
00332     {
00333         case KGlobalSettings::CompletionPopupAuto:
00334         {
00335             if (s.isEmpty())
00336                 break;
00337         }
00338         case KGlobalSettings::CompletionPopup:        
00339         {
00340             m_previousAddresses = prevAddr;
00341             QStringList items = s_completion->allMatches( s );
00342             items += s_completion->allMatches( "\"" + s );
00343             items += s_completion->substringCompletion( '<' + s );
00344             uint beforeDollarCompletionCount = items.count();
00345 
00346             if( s.find( ' ' ) == -1 ) // one word, possibly given name
00347                 items += s_completion->allMatches( "$$" + s );
00348 
00349             if ( !items.isEmpty() )
00350             {
00351                 if ( items.count() > beforeDollarCompletionCount )
00352                 {
00353                     // remove the '$$whatever$' part                    
00354                     for( QStringList::Iterator it = items.begin();
00355                          it != items.end();
00356                          ++it )
00357                     { 
00358                         int pos = (*it).find( '$', 2 );
00359                         if( pos < 0 ) // ???
00360                             continue;
00361                         (*it)=(*it).mid( pos + 1 );
00362                     }
00363                 }
00364 
00365                 items = removeMailDupes( items );
00366                 
00367                 // We do not want KLineEdit::setCompletedItems to perform text
00368                 // completion (suggestion) since it does not know how to deal
00369                 // with providing proper completions for different items on the 
00370                 // same line, e.g. comma-separated list of email addresses.
00371                 bool autoSuggest = (mode != KGlobalSettings::CompletionPopupAuto);                                
00372                 setCompletedItems( items, autoSuggest );
00373                 
00374                 if (!autoSuggest)
00375                 {
00376                     int index = items.first().find( s );
00377                     QString newText = prevAddr + items.first().mid( index );
00378                     //kdDebug() << "OLD TEXT: " << text() << endl;
00379                     //kdDebug() << "NEW TEXT: " << newText << endl;
00380                     setUserSelection(false);
00381                     setCompletedText(newText,true);
00382                 }
00383             }
00384 
00385             break;
00386         }
00387 
00388         case KGlobalSettings::CompletionShell:
00389         {
00390             QString match = s_completion->makeCompletion( s );
00391             if ( !match.isNull() && match != s )
00392             {
00393                 setText( prevAddr + match );
00394                 cursorAtEnd();
00395             }
00396             break;
00397         }
00398 
00399         case KGlobalSettings::CompletionMan: // Short-Auto in fact
00400         case KGlobalSettings::CompletionAuto:
00401         {
00402             if (!s.isEmpty())
00403             {              
00404                 QString match = s_completion->makeCompletion( s );
00405                 if ( !match.isNull() && match != s )
00406                 {
00407                   QString adds = prevAddr + match;
00408                   setCompletedText( adds );
00409                 }
00410                 break;
00411             }
00412         }        
00413         case KGlobalSettings::CompletionNone:
00414         default: // fall through        
00415             break;
00416     }
00417 }
00418 
00419 //-----------------------------------------------------------------------------
00420 void AddressLineEdit::slotPopupCompletion( const QString& completion )
00421 {
00422     setText( m_previousAddresses + completion );
00423     cursorAtEnd();
00424 }
00425 
00426 //-----------------------------------------------------------------------------
00427 void AddressLineEdit::loadAddresses()
00428 {
00429     s_completion->clear();
00430     s_addressesDirty = false;
00431 
00432     QStringList adrs = addresses();
00433     for( QStringList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it)
00434         addAddress( *it );
00435 }
00436 
00437 void AddressLineEdit::addAddress( const QString& adr )
00438 {
00439     s_completion->addItem( adr );
00440     int pos = adr.find( '<' );
00441     if( pos >= 0 )
00442     {
00443         ++pos;
00444         int pos2 = adr.find( pos, '>' );
00445         if( pos2 >= 0 )
00446             s_completion->addItem( adr.mid( pos, pos2 - pos ));
00447     }
00448 }
00449 
00450 void AddressLineEdit::slotStartLDAPLookup()
00451 {
00452     if( !s_LDAPSearch->isAvailable() || s_LDAPLineEdit != this )
00453         return;
00454     startLoadingLDAPEntries();
00455 }
00456 
00457 void AddressLineEdit::stopLDAPLookup()
00458 {
00459     s_LDAPSearch->cancelSearch();
00460     s_LDAPLineEdit = NULL;
00461 }
00462 
00463 void AddressLineEdit::startLoadingLDAPEntries()
00464 {
00465     QString s( *s_LDAPText );
00466     // TODO cache last?
00467     QString prevAddr;
00468     int n = s.findRev(',');
00469     if (n>= 0)
00470     {
00471         prevAddr = s.left(n+1) + ' ';
00472         s = s.mid(n+1,255).stripWhiteSpace();
00473     }
00474     if( s.length() == 0 )
00475         return;
00476     
00477     loadAddresses(); // TODO reuse these?
00478     s_LDAPSearch->startSearch( s );
00479 }
00480 
00481 void AddressLineEdit::slotLDAPSearchData( const QStringList& adrs )
00482 {
00483     if( s_LDAPLineEdit != this )
00484         return;
00485     for( QStringList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) {
00486         QString name(*it);
00487         int pos = name.find( " <" );
00488         int pos_comma = name.find( ',' );
00489         // put name in quotes, if we have a comma in the name
00490         if (pos>0 && pos_comma>0 && pos_comma<pos) {
00491           name.insert(pos, '\"');
00492           name.prepend('\"');
00493         }
00494         addAddress( name );
00495     }
00496     
00497     if( hasFocus() || completionBox()->hasFocus())
00498     {
00499         if( completionMode() != KGlobalSettings::CompletionNone )
00500         {
00501             doCompletion( false );
00502         }
00503     }
00504 }
00505 
00506 QStringList AddressLineEdit::removeMailDupes( const QStringList& adrs )
00507 {
00508     QStringList src = adrs;
00509     qHeapSort( src );
00510     QString last;
00511     for( QStringList::Iterator it = src.begin(); it != src.end(); ) {
00512         if( *it == last )
00513         {
00514             it = src.remove( it );
00515             continue; // dupe
00516         }
00517         last = *it;
00518         ++it;
00519     }
00520     return src;
00521 }
00522 
00523 //-----------------------------------------------------------------------------
00524 void AddressLineEdit::dropEvent(QDropEvent *e)
00525 {
00526   KURL::List uriList;
00527   if(KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ))
00528   {
00529     QString ct = text();
00530     KURL::List::Iterator it = uriList.begin();
00531     for (; it != uriList.end(); ++it)
00532     {
00533       if (!ct.isEmpty()) ct.append(", ");
00534       KURL u(*it);
00535       if ((*it).protocol() == "mailto")
00536           ct.append( (*it).path() );
00537       else
00538           ct.append( (*it).url() );
00539     }
00540     setText(ct);
00541     setEdited( true );
00542   }
00543   else {
00544     if (m_useCompletion)
00545        m_smartPaste = true;
00546     QLineEdit::dropEvent(e);
00547     m_smartPaste = false;
00548   }
00549 }
00550 
00551 
00552 QStringList AddressLineEdit::addresses()
00553 {
00554   QApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while
00555 
00556   QStringList result;
00557   QString space(" ");
00558   QRegExp needQuotes("[^ 0-9A-Za-z\\x0080-\\xFFFF]");
00559   QString endQuote("\" ");
00560   QString addr, email;
00561 
00562   KABC::AddressBook *addressBook = KABC::StdAddressBook::self();
00563   KABC::AddressBook::Iterator it;
00564   for( it = addressBook->begin(); it != addressBook->end(); ++it ) {
00565     QStringList emails = (*it).emails();
00566     
00567     QString n = (*it).prefix() + space +
00568                 (*it).givenName() + space +
00569                 (*it).additionalName() + space +
00570                 (*it).familyName() + space +
00571                 (*it).suffix();
00572     
00573     n = n.simplifyWhiteSpace();
00574 
00575     QStringList::ConstIterator mit;
00576 
00577     for ( mit = emails.begin(); mit != emails.end(); ++mit ) {
00578       email = *mit;
00579       if (!email.isEmpty()) {
00580         if (n.isEmpty() || (email.find( '<' ) != -1))
00581           addr = QString::null;
00582         else { /* do we really need quotes around this name ? */
00583                 if (n.find(needQuotes) != -1)
00584             addr = '"' + n + endQuote;
00585           else
00586             addr = n + space;
00587         }
00588 
00589         if (!addr.isEmpty() && (email.find( '<' ) == -1)
00590             && (email.find( '>' ) == -1)
00591             && (email.find( ',' ) == -1))
00592           addr += '<' + email + '>';
00593         else
00594           addr += email;
00595         addr = addr.stripWhiteSpace();
00596         result.append( addr );
00597       }
00598     }
00599   }
00600   
00601   KABC::DistributionListManager manager( addressBook );
00602   manager.load();
00603   result += manager.listNames();
00604 
00605   QApplication::restoreOverrideCursor();
00606 
00607   return result;
00608 }
00609 
00610 #include "addresslineedit.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys