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