libkdepim

ldapsearchdialog.cpp

00001 /* ldapsearchdialogimpl.cpp - LDAP access
00002  *      Copyright (C) 2002 Klar�vdalens Datakonsult AB
00003  *
00004  *      Author: Steffen Hansen <hansen@kde.org>
00005  *
00006  * This file is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This file is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
00019  */
00020 
00021 #include "ldapsearchdialog.h"
00022 #include "ldapclient.h"
00023 
00024 #include <qcheckbox.h>
00025 #include <qgroupbox.h>
00026 #include <qheader.h>
00027 #include <qlabel.h>
00028 #include <qlayout.h>
00029 #include <qlistview.h>
00030 #include <qpushbutton.h>
00031 
00032 #include <kabc/addresslineedit.h>
00033 #include <kapplication.h>
00034 #include <kcombobox.h>
00035 #include <kconfig.h>
00036 #include <klineedit.h>
00037 #include <klocale.h>
00038 #include <kmessagebox.h>
00039 
00040 using namespace KPIM;
00041 
00042 static QString asUtf8( const QByteArray &val )
00043 {
00044   if ( val.isEmpty() )
00045     return QString::null;
00046 
00047   const char *data = val.data();
00048 
00049   //QString::fromUtf8() bug workaround
00050   if ( data[ val.size() - 1 ] == '\0' )
00051     return QString::fromUtf8( data, val.size() - 1 );
00052   else
00053     return QString::fromUtf8( data, val.size() );
00054 }
00055 
00056 static QString join( const KPIM::LdapAttrValue& lst, const QString& sep )
00057 {
00058   QString res;
00059   bool alredy = false;
00060   for ( KPIM::LdapAttrValue::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
00061     if ( alredy )
00062       res += sep;
00063     alredy = TRUE;
00064     res += asUtf8( *it );
00065   }
00066   return res;
00067 }
00068 
00069 static QMap<QString, QString>& adrbookattr2ldap()
00070 {
00071   static QMap<QString, QString> keys;
00072 
00073   if ( keys.isEmpty() ) {
00074     keys[ i18n( "Title" ) ] = "title";
00075     keys[ i18n( "Full Name" ) ] = "cn";
00076     keys[ i18n( "Email" ) ] = "mail";
00077     keys[ i18n( "Home Number" ) ] = "homePhone";
00078     keys[ i18n( "Work Number" ) ] = "telephoneNumber";
00079     keys[ i18n( "Mobile Number" ) ] = "mobile";
00080     keys[ i18n( "Fax Number" ) ] = "facsimileTelephoneNumber";
00081     keys[ i18n( "Pager" ) ] = "pager";
00082     keys[ i18n( "Street") ] = "street";
00083     keys[ i18n( "State" ) ] = "st";
00084     keys[ i18n( "Country" ) ] = "co";
00085     keys[ i18n( "City" ) ] = "l";
00086     keys[ i18n( "Organization" ) ] = "o";
00087     keys[ i18n( "Company" ) ] = "Company";
00088     keys[ i18n( "Department" ) ] = "department";
00089     keys[ i18n( "Zip Code" ) ] = "postalCode";
00090     keys[ i18n( "Postal Address" ) ] = "postalAddress";
00091     keys[ i18n( "Description" ) ] = "description";
00092     keys[ i18n( "User ID" ) ] = "uid";
00093   }
00094   return keys;
00095 }
00096 
00097 namespace KPIM {
00098 
00099 class ContactListItem : public QListViewItem
00100 {
00101   public:
00102     ContactListItem( QListView* parent, const KPIM::LdapAttrMap& attrs )
00103       : QListViewItem( parent ), mAttrs( attrs )
00104     {
00105       const KPIM::LdapAttrValue &mailAttrs = attrs[ "mail" ];
00106       if ( mailAttrs.isEmpty() ) {
00107         setSelectable( false );
00108         setEnabled( false );
00109       }
00110     }
00111 
00112     KPIM::LdapAttrMap mAttrs;
00113 
00114     virtual QString text( int col ) const
00115     {
00116       // Look up a suitable attribute for column col
00117       const QString colName = listView()->columnText( col );
00118       const QString ldapAttrName = adrbookattr2ldap()[ colName ];
00119       return join( mAttrs[ ldapAttrName ], ", " );
00120     }
00121 };
00122 
00123 }
00124 
00125 LDAPSearchDialog::LDAPSearchDialog( QWidget* parent, const char* name )
00126   : KDialogBase( Plain, i18n( "Search for Addresses in Directory" ), Help | User1 |
00127     User2 | User3 | Cancel, Default, parent, name, false, true )
00128 {
00129   setButtonCancel( KStdGuiItem::close() );
00130   QFrame *page = plainPage();
00131   QVBoxLayout *topLayout = new QVBoxLayout( page, marginHint(), spacingHint() );
00132 
00133   QGroupBox *groupBox = new QGroupBox( i18n( "Search for Addresses in Directory" ),
00134                                        page );
00135   groupBox->setFrameShape( QGroupBox::Box );
00136   groupBox->setFrameShadow( QGroupBox::Sunken );
00137   groupBox->setColumnLayout( 0, Qt::Vertical );
00138   QGridLayout *boxLayout = new QGridLayout( groupBox->layout(), 2,
00139                                             5, spacingHint() );
00140   boxLayout->setColStretch( 1, 1 );
00141 
00142   QLabel *label = new QLabel( i18n( "Search for:" ), groupBox );
00143   boxLayout->addWidget( label, 0, 0 );
00144 
00145   mSearchEdit = new KLineEdit( groupBox );
00146   boxLayout->addWidget( mSearchEdit, 0, 1 );
00147   label->setBuddy( mSearchEdit );
00148 
00149   label = new QLabel( i18n( "in" ), groupBox );
00150   boxLayout->addWidget( label, 0, 2 );
00151 
00152   mFilterCombo = new KComboBox( groupBox );
00153   mFilterCombo->insertItem( i18n( "Name" ) );
00154   mFilterCombo->insertItem( i18n( "Email" ) );
00155   mFilterCombo->insertItem( i18n( "Home Number" ) );
00156   mFilterCombo->insertItem( i18n( "Work Number" ) );
00157   boxLayout->addWidget( mFilterCombo, 0, 3 );
00158 
00159   QSize buttonSize;
00160   mSearchButton = new QPushButton( i18n( "Stop" ), groupBox );
00161   buttonSize = mSearchButton->sizeHint();
00162   mSearchButton->setText( i18n( "Search" ) );
00163   if ( buttonSize.width() < mSearchButton->sizeHint().width() )
00164     buttonSize = mSearchButton->sizeHint();
00165   mSearchButton->setFixedWidth( buttonSize.width() );
00166 
00167   mSearchButton->setDefault( true );
00168   boxLayout->addWidget( mSearchButton, 0, 4 );
00169 
00170   mRecursiveCheckbox = new QCheckBox( i18n( "Recursive search" ), groupBox  );
00171   mRecursiveCheckbox->setChecked( true );
00172   boxLayout->addMultiCellWidget( mRecursiveCheckbox, 1, 1, 0, 4 );
00173 
00174   mSearchType = new KComboBox( groupBox );
00175   mSearchType->insertItem( i18n( "Contains" ) );
00176   mSearchType->insertItem( i18n( "Starts With" ) );
00177   boxLayout->addMultiCellWidget( mSearchType, 1, 1, 3, 4 );
00178 
00179   topLayout->addWidget( groupBox );
00180 
00181   mResultListView = new QListView( page );
00182   mResultListView->setSelectionMode( QListView::Multi );
00183   mResultListView->setAllColumnsShowFocus( true );
00184   mResultListView->setShowSortIndicator( true );
00185   topLayout->addWidget( mResultListView );
00186 
00187   resize( QSize( 600, 400).expandedTo( minimumSizeHint() ) );
00188 
00189   setButtonText( User1, i18n( "Unselect All" ) );
00190   setButtonText( User2, i18n( "Select All" ) );
00191   setButtonText( User3, i18n( "Add Selected" ) );
00192 
00193   mNumHosts = 0;
00194   mIsOK = false;
00195 
00196   connect( mRecursiveCheckbox, SIGNAL( toggled( bool ) ),
00197        this, SLOT( slotSetScope( bool ) ) );
00198   connect( mSearchButton, SIGNAL( clicked() ),
00199        this, SLOT( slotStartSearch() ) );
00200 
00201   setTabOrder(mSearchEdit, mFilterCombo);
00202   setTabOrder(mFilterCombo, mSearchButton);
00203   mSearchEdit->setFocus();
00204 
00205   restoreSettings();
00206 }
00207 
00208 LDAPSearchDialog::~LDAPSearchDialog()
00209 {
00210   saveSettings();
00211 }
00212 
00213 void LDAPSearchDialog::restoreSettings()
00214 {
00215   // Create one KPIM::LdapClient per selected server and configure it.
00216 
00217   // First clean the list to make sure it is empty at
00218   // the beginning of the process
00219   mLdapClientList.setAutoDelete( true );
00220   mLdapClientList.clear();
00221 
00222   KConfig kabConfig( "kaddressbookrc" );
00223   kabConfig.setGroup( "LDAPSearch" );
00224   mSearchType->setCurrentItem( kabConfig.readNumEntry( "SearchType", 0 ) );
00225 
00226   // then read the config file and register all selected
00227   // server in the list
00228   KConfig* config = KABC::AddressLineEdit::config(); // singleton kabldaprc config object
00229   KConfigGroupSaver saver( config, "LDAP" );
00230   mNumHosts = config->readUnsignedNumEntry( "NumSelectedHosts" );
00231   if ( !mNumHosts ) {
00232     KMessageBox::error( this, i18n( "You must select a LDAP server before searching.\nYou can do this from the menu Settings/Configure KAddressBook." ) );
00233     mIsOK = false;
00234   } else {
00235     mIsOK = true;
00236     for ( int j = 0; j < mNumHosts; ++j ) {
00237         KPIM::LdapServer ldapServer;
00238 
00239       QString host = config->readEntry( QString( "SelectedHost%1" ).arg( j ), "" );
00240       if ( !host.isEmpty() )
00241           ldapServer.setHost( host );
00242 
00243       int port = config->readUnsignedNumEntry( QString( "SelectedPort%1" ).arg( j ) );
00244       if ( port )
00245         ldapServer.setPort( port );
00246 
00247       QString base = config->readEntry( QString( "SelectedBase%1" ).arg( j ), "" );
00248       if ( !base.isEmpty() )
00249         ldapServer.setBaseDN( base );
00250 
00251       QString bindDN = config->readEntry( QString( "SelectedBind%1" ).arg( j ), "" );
00252       if ( !bindDN.isEmpty() )
00253         ldapServer.setBindDN( bindDN );
00254 
00255       QString pwdBindDN = config->readEntry( QString( "SelectedPwdBind%1" ).arg( j ), "" );
00256       if ( !pwdBindDN.isEmpty() )
00257         ldapServer.setPwdBindDN( pwdBindDN );
00258 
00259       KPIM::LdapClient* ldapClient = new KPIM::LdapClient( 0, this, "ldapclient" );
00260       ldapClient->setServer( ldapServer );
00261       
00262       QStringList attrs;
00263 
00264       for ( QMap<QString,QString>::Iterator it = adrbookattr2ldap().begin(); it != adrbookattr2ldap().end(); ++it )
00265         attrs << *it;
00266 
00267       ldapClient->setAttrs( attrs );
00268 
00269       connect( ldapClient, SIGNAL( result( const KPIM::LdapObject& ) ),
00270            this, SLOT( slotAddResult( const KPIM::LdapObject& ) ) );
00271       connect( ldapClient, SIGNAL( done() ),
00272            this, SLOT( slotSearchDone() ) );
00273       connect( ldapClient, SIGNAL( error( const QString& ) ),
00274            this, SLOT( slotError( const QString& ) ) );
00275 
00276       mLdapClientList.append( ldapClient );
00277     }
00278 
00280     while ( mResultListView->header()->count() > 0 ) {
00281       mResultListView->removeColumn(0);
00282     }
00283 
00284     mResultListView->addColumn( i18n( "Full Name" ) );
00285     mResultListView->addColumn( i18n( "Email" ) );
00286     mResultListView->addColumn( i18n( "Home Number" ) );
00287     mResultListView->addColumn( i18n( "Work Number" ) );
00288     mResultListView->addColumn( i18n( "Mobile Number" ) );
00289     mResultListView->addColumn( i18n( "Fax Number" ) );
00290     mResultListView->addColumn( i18n( "Company" ) );
00291     mResultListView->addColumn( i18n( "Organization" ) );
00292     mResultListView->addColumn( i18n( "Street" ) );
00293     mResultListView->addColumn( i18n( "State" ) );
00294     mResultListView->addColumn( i18n( "Country" ) );
00295     mResultListView->addColumn( i18n( "Zip Code" ) );
00296     mResultListView->addColumn( i18n( "Postal Address" ) );
00297     mResultListView->addColumn( i18n( "City" ) );
00298     mResultListView->addColumn( i18n( "Department" ) );
00299     mResultListView->addColumn( i18n( "Description" ) );
00300     mResultListView->addColumn( i18n( "User ID" ) );
00301     mResultListView->addColumn( i18n( "Title" ) );
00302 
00303     mResultListView->clear();
00304   }
00305 }
00306 
00307 void LDAPSearchDialog::saveSettings()
00308 {
00309   KConfig config( "kaddressbookrc" );
00310   config.setGroup( "LDAPSearch" );
00311   config.writeEntry( "SearchType", mSearchType->currentItem() );
00312   config.sync();
00313 }
00314 
00315 void LDAPSearchDialog::cancelQuery()
00316 {
00317   for ( KPIM::LdapClient* client = mLdapClientList.first(); client; client = mLdapClientList.next() ) {
00318     client->cancelQuery();
00319   }
00320 }
00321 
00322 void LDAPSearchDialog::slotAddResult( const KPIM::LdapObject& obj )
00323 {
00324   new ContactListItem( mResultListView, obj.attrs );
00325 }
00326 
00327 void LDAPSearchDialog::slotSetScope( bool rec )
00328 {
00329   for ( KPIM::LdapClient* client = mLdapClientList.first(); client; client = mLdapClientList.next() ) {
00330     if ( rec )
00331       client->setScope( "sub" );
00332     else
00333       client->setScope( "one" );
00334   }
00335 }
00336 
00337 QString LDAPSearchDialog::makeFilter( const QString& query, const QString& attr,
00338                                       bool startsWith )
00339 {
00340   /* The reasoning behind this filter is:
00341    * If it's a person, or a distlist, show it, even if it doesn't have an email address.
00342    * If it's not a person, or a distlist, only show it if it has an email attribute.
00343    * This allows both resource accounts with an email address which are not a person and
00344    * person entries without an email address to show up, while still not showing things
00345    * like structural entries in the ldap tree. */
00346   QString result( "&(|(objectclass=person)(objectclass=groupofnames)(mail=*))(" );
00347   if( query.isEmpty() )
00348     // Return a filter that matches everything
00349     return result + "|(cn=*)(sn=*)" + ")";
00350 
00351   if ( attr == i18n( "Name" ) ) {
00352     result += startsWith ? "|(cn=%1*)(sn=%2*)" : "|(cn=*%1*)(sn=*%2*)";
00353     result = result.arg( query ).arg( query );
00354   } else {
00355     result += (startsWith ? "%1=%2*" : "%1=*%2*");
00356     if ( attr == i18n( "Email" ) ) {
00357       result = result.arg( "mail" ).arg( query );
00358     } else if ( attr == i18n( "Home Number" ) ) {
00359       result = result.arg( "homePhone" ).arg( query );
00360     } else if ( attr == i18n( "Work Number" ) ) {
00361       result = result.arg( "telephoneNumber" ).arg( query );
00362     } else {
00363       // Error?
00364       result = QString::null;
00365       return result;
00366     }
00367   }
00368   result += ")";
00369   return result;
00370 }
00371 
00372 void LDAPSearchDialog::slotStartSearch()
00373 {
00374   cancelQuery();
00375 
00376   QApplication::setOverrideCursor( Qt::waitCursor );
00377   mSearchButton->setText( i18n( "Stop" ) );
00378 
00379   disconnect( mSearchButton, SIGNAL( clicked() ),
00380               this, SLOT( slotStartSearch() ) );
00381   connect( mSearchButton, SIGNAL( clicked() ),
00382            this, SLOT( slotStopSearch() ) );
00383 
00384   bool startsWith = (mSearchType->currentItem() == 1);
00385 
00386   QString filter = makeFilter( mSearchEdit->text().stripWhiteSpace(), mFilterCombo->currentText(), startsWith );
00387 
00388    // loop in the list and run the KPIM::LdapClients
00389   mResultListView->clear();
00390   for( KPIM::LdapClient* client = mLdapClientList.first(); client; client = mLdapClientList.next() ) {
00391     client->startQuery( filter );
00392   }
00393 
00394   saveSettings();
00395 }
00396 
00397 void LDAPSearchDialog::slotStopSearch()
00398 {
00399   cancelQuery();
00400   slotSearchDone();
00401 }
00402 
00403 void LDAPSearchDialog::slotSearchDone()
00404 {
00405   // If there are no more active clients, we are done.
00406   for ( KPIM::LdapClient* client = mLdapClientList.first(); client; client = mLdapClientList.next() ) {
00407     if ( client->isActive() )
00408       return;
00409   }
00410 
00411   disconnect( mSearchButton, SIGNAL( clicked() ),
00412               this, SLOT( slotStopSearch() ) );
00413   connect( mSearchButton, SIGNAL( clicked() ),
00414            this, SLOT( slotStartSearch() ) );
00415 
00416   mSearchButton->setText( i18n( "Search" ) );
00417   QApplication::restoreOverrideCursor();
00418 }
00419 
00420 void LDAPSearchDialog::slotError( const QString& error )
00421 {
00422   QApplication::restoreOverrideCursor();
00423   KMessageBox::error( this, error );
00424 }
00425 
00426 void LDAPSearchDialog::closeEvent( QCloseEvent* e )
00427 {
00428   slotStopSearch();
00429   e->accept();
00430 }
00431 
00436 QString LDAPSearchDialog::selectedEMails() const
00437 {
00438   QStringList result;
00439   ContactListItem* cli = static_cast<ContactListItem*>( mResultListView->firstChild() );
00440   while ( cli ) {
00441     if ( cli->isSelected() ) {
00442       QString email = asUtf8( cli->mAttrs[ "mail" ].first() ).stripWhiteSpace();
00443       if ( !email.isEmpty() ) {
00444         QString name = asUtf8( cli->mAttrs[ "cn" ].first() ).stripWhiteSpace();
00445         if ( name.isEmpty() ) {
00446           result << email;
00447         } else {
00448           result << name + " <" + email + ">";
00449         }
00450       }
00451     }
00452     cli = static_cast<ContactListItem*>( cli->nextSibling() );
00453   }
00454 
00455   return result.join( ", " );
00456 }
00457 
00458 void LDAPSearchDialog::slotHelp()
00459 {
00460   kapp->invokeHelp( "ldap-queries" );
00461 }
00462 
00463 void LDAPSearchDialog::slotUser1()
00464 {
00465   mResultListView->selectAll( false );
00466 }
00467 
00468 void LDAPSearchDialog::slotUser2()
00469 {
00470   mResultListView->selectAll( true );
00471 }
00472 
00473 void LDAPSearchDialog::slotUser3()
00474 {
00475   emit addresseesAdded();
00476 }
00477 
00478 #include "ldapsearchdialog.moc"