diff --git a/kaddressbook/kabcore.h b/kaddressbook/kabcore.h index 8cb2bc4d56..e4632522f9 100644 --- a/kaddressbook/kabcore.h +++ b/kaddressbook/kabcore.h @@ -1,497 +1,499 @@ /* This file is part of KAddressbook. Copyright (c) 2003 Tobias Koenig This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #ifndef KABCORE_H #define KABCORE_H +#include // FOR KDEPIM_NEW_DISTRLISTS + #include #include #include #include #include "core.h" #include namespace KABC { class AddressBook; class Ticket; } namespace KPIM { class AddresseeView; class CategoryEditDialog; class CategorySelectDialog; } class KAboutData; class KAction; class KActionCollection; class KConfig; class KStatusBar; class KToggleAction; class KXMLGUIClient; class QSplitter; class QHBoxLayout; class QWidgetStack; class AddresseeEditorDialog; class ExtensionManager; class FilterSelectionWidget; class IncSearchWidget; class JumpButtonBar; class KAddressBookIface; class KAddressBookService; class KIMProxy; class LDAPSearchDialog; class ViewManager; class XXPortManager; namespace KAB { class DistributionListEntryView; } typedef struct { KABC::Ticket *ticket; int counter; } ResourceMapEntry; class KDE_EXPORT KABCore : public KAB::Core { Q_OBJECT public: KABCore( KXMLGUIClient *client, bool readWrite, QWidget *parent, const QString &file = QString::null, const char *name = 0 ); ~KABCore(); /** Restores the global settings. */ void restoreSettings(); /** Saves the global settings. */ void saveSettings(); /** Returns a pointer to the StdAddressBook of the application. */ KABC::AddressBook *addressBook() const; /** Returns a pointer to the KConfig object of the application. */ KConfig *config() const; /** Returns a pointer to the global KActionCollection object. So other classes can register their actions easily. */ KActionCollection *actionCollection() const; /** Returns the current sort field of the view. */ KABC::Field *currentSortField() const; /** Returns the uid list of the currently selected contacts. */ QStringList selectedUIDs() const; /** Displays the ResourceSelectDialog and returns the selected resource or a null pointer if no resource was selected by the user. */ KABC::Resource *requestResource( QWidget *parent ); /** Returns the parent widget. */ QWidget *widget() const; static KAboutData *createAboutData(); void setStatusBar( KStatusBar *statusBar ); KStatusBar *statusBar() const; KAB::SearchManager *searchManager() const { return mSearchManager; } KCommandHistory *commandHistory() const { return mCommandHistory; } #ifdef KDEPIM_NEW_DISTRLISTS /** Returns all the distribution lists. */ virtual KPIM::DistributionList::List distributionLists() const; /** Returns the name of all the distribution lists. */ virtual QStringList distributionListNames() const; /** sets the distribution list to display. If null, the regular address book is to be displayed. */ virtual void setSelectedDistributionList( const QString &name ); #endif public slots: /** Is called whenever a contact is selected in the view. */ void setContactSelected( const QString &uid ); /** Opens the preferred mail composer with all selected contacts as arguments. */ void sendMail(); /** Opens the preferred mail composer with the given contacts as arguments. */ void sendMail( const QString& email ); void mailVCard(); void mailVCard(const QStringList& uids); /** * Start an Instant Messaging chat with the selected contacts */ void startChat(); /** Starts the preferred web browser with the given URL as argument. */ void browse( const QString& url ); /** Select all contacts in the view. */ void selectAllContacts(); /** Deletes all selected contacts from the address book. */ void deleteContacts(); /** Deletes given contacts from the address book. @param uids The uids of the contacts, which shall be deleted. */ void deleteContacts( const QStringList &uids ); /** Deletes given distribution lists from the address book. @param uids The names of the distribution lists which shall be deleted. */ void deleteDistributionLists( const QStringList &names ); /** Copys the selected contacts into clipboard for later pasting. */ void copyContacts(); /** Cuts the selected contacts and stores them for later pasting. */ void cutContacts(); /** Paste contacts from clipboard into the address book. */ void pasteContacts(); /** Paste given contacts into the address book. @param list The list of addressee, which shall be pasted. */ void pasteContacts( KABC::Addressee::List &list ); /** Merge the selected contacts in a single one. */ void mergeContacts(); /** Sets the whoAmI contact, that is used by many other programs to get personal information about the current user. */ void setWhoAmI(); /** Displays the category dialog and applies the result to all selected contacts. */ void setCategories(); /** Search with the current search field for a contact, that matches the given text, and selects it in the view. */ void incrementalTextSearch( const QString& text ); void incrementalJumpButtonSearch( const QString& characters ); /** Marks the address book as modified. */ void setModified(); /** Marks the address book as modified concerning the argument. */ void setModified( bool modified ); /** Returns whether the address book is modified. */ bool modified() const; /** Called whenever an contact is modified in the contact editor dialog or the quick edit. */ void contactModified( const KABC::Addressee &addr ); /** DCOP METHOD: Adds the given email address to address book. */ virtual void addEmail( const QString& addr ); /** DCOP METHOD: Imports the vCard, located at the given url. */ virtual void importVCard( const KURL& url ); /** DCOP METHOD: Imports the given vCard. */ virtual void importVCardFromData( const QString& vCard ); /** DCOP METHOD: Opens contact editor to input a new contact. */ virtual void newContact(); /** DCOP METHOD: Opens distribution list editor to create a new distribution list */ virtual void newDistributionList(); /** DCOP METHOD: Returns the name of the contact, that matches the given phone number. */ virtual QString getNameByPhone( const QString& phone ); /** DCOP METHOD: Handle command line arguments, return true if handled and false if no args was given. The iface is either the mainwin or the part. */ bool handleCommandLine( KAddressBookIface* iface ); /** Saves the contents of the AddressBook back to disk. */ void save(); /** Shows the edit dialog for the given uid. If the uid is QString::null, the method will try to find a selected addressee in the view. */ void editContact( const QString &uid = QString::null ); /** * Let the user chose a different resource for the selected contacts. * If the adding to the new resource is successfull, the contact is * removed from the old one, unless the Copy flag is given. */ void storeContactIn( const QString &uid = QString::null, bool copy = false ); /** * Lets the user chose a different resource for the selected contacts and * copies it there. */ void copySelectedContactToResource(); /** * Lets the user chose a different resource for the selected contacts and * moves it there. */ void moveSelectedContactToResource(); /** Launches the ldap search dialog. */ void openLDAPDialog(); /** Opens the settings dialog. */ void configure(); /** Creates a KAddressBookPrinter, which will display the print dialog and do the printing. */ void print(); void detailsHighlighted( const QString& ); void showContactsAddress( const QString &uid ); void configurationChanged(); bool queryClose(); /** Is called whenever the xmlgui has to be rebuild after a part switch. */ void reinitXMLGUI(); private: #ifdef KDEPIM_NEW_DISTRLISTS void editDistributionList( const KPIM::DistributionList &list ); void showDistributionListEntry( const QString &uid ); #endif private slots: void setJumpButtonBarVisible( bool visible ); void setDetailsVisible( bool visible ); void extensionModified( const KABC::Addressee::List &list ); void extensionDeleted( const QStringList &uidList ); void clipboardDataChanged(); void updateIncSearchWidget(); void slotEditorDestroyed( const QString &uid ); void delayedAddressBookChanged(); void addressBookChanged(); void categoriesSelected( const QStringList& ); void editCategories(); void slotClearSearchBar(); void slotContactsUpdated(); void activateDetailsWidget( QWidget *widget ); void deactivateDetailsWidget( QWidget *widget ); void editDistributionList( const QString &name ); void removeSelectedContactsFromDistList(); void editSelectedDistributionList(); void sendMailToDistributionList( const QString &id ); private: void initGUI(); void createJumpButtonBar(); void initActions(); void updateCategories(); QStringList allCategories() const; AddresseeEditorDialog *createAddresseeEditorDialog( QWidget *parent, const char *name = 0 ); QWidget *mWidget; KABC::AddressBook *mAddressBook; KStatusBar *mStatusBar; ViewManager *mViewManager; QLabel *mViewHeaderLabel; #ifdef KDEPIM_NEW_DISTRLISTS QString mSelectedDistributionList; QWidget *mDistListButtonWidget; #endif ExtensionManager *mExtensionManager; XXPortManager *mXXPortManager; JumpButtonBar *mJumpButtonBar; FilterSelectionWidget *mFilterSelectionWidget; IncSearchWidget *mIncSearchWidget; KAB::DistributionListEntryView* mDistListEntryView; KPIM::AddresseeView *mDetailsViewer; KPIM::CategorySelectDialog *mCategorySelectDialog; KPIM::CategoryEditDialog *mCategoryEditDialog; QWidget *mDetailsPage; QWidget *mDetailsWidget; QHBoxLayout *mDetailsLayout; QSplitter *mDetailsSplitter; QSplitter *mLeftSplitter; QWidgetStack *mDetailsStack; LDAPSearchDialog *mLdapSearchDialog; QDict mEditorDict; bool mReadWrite; bool mModified; bool mIsPart; QTimer *mAddressBookChangedTimer; KAction *mActionPaste; KAction *mActionCut; KAction *mActionDelete; KAction *mActionCopy; KAction *mActionEditAddressee; KAction *mActionMoveAddresseeTo; KAction *mActionCopyAddresseeTo; KAction *mActionMerge; KAction *mActionMail; KAction *mActionMailVCard; KAction *mActionChat; KAction *mActionSave; KAction *mActionDeleteView; KAction *mActionWhoAmI; KAction *mActionCategories; KToggleAction *mActionJumpBar; KToggleAction *mActionDetails; KCommandHistory *mCommandHistory; KAddressBookService *mAddressBookService; KAB::SearchManager *mSearchManager; // KIMProxy provides access to up to date instant messaging presence data ::KIMProxy *mKIMProxy; class KABCorePrivate; KABCorePrivate *d; }; #endif diff --git a/kaddressbook/searchmanager.cpp b/kaddressbook/searchmanager.cpp index 1919dfea8a..a9b8ea7db6 100644 --- a/kaddressbook/searchmanager.cpp +++ b/kaddressbook/searchmanager.cpp @@ -1,198 +1,199 @@ /* This file is part of KAddressBook. Copyright (c) 2004 Tobias Koenig This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ +#include // FOR KDEPIM_NEW_DISTRLISTS #include #include #include "searchmanager.h" using namespace KAB; SearchManager::SearchManager( KABC::AddressBook *ab, QObject *parent, const char *name ) : QObject( parent, name ), mAddressBook( ab ) { } void SearchManager::search( const QString &pattern, const KABC::Field::List &fields, Type type ) { mPattern = pattern; mFields = fields; mType = type; KABC::Addressee::List allContacts; mContacts.clear(); #if KDE_VERSION >= 319 KABC::AddresseeList list( mAddressBook->allAddressees() ); if ( !fields.isEmpty() ) list.sortByField( fields.first() ); allContacts = list; #else KABC::AddressBook::ConstIterator abIt( mAddressBook->begin() ); const KABC::AddressBook::ConstIterator abEndIt( mAddressBook->end() ); for ( ; abIt != abEndIt; ++abIt ) allContacts.append( *abIt ); #endif #ifdef KDEPIM_NEW_DISTRLISTS // Extract distribution lists from allContacts mDistributionLists.clear(); KABC::Addressee::List::Iterator rmIt( allContacts.begin() ); const KABC::Addressee::List::Iterator rmEndIt( allContacts.end() ); while ( rmIt != rmEndIt ) { if ( KPIM::DistributionList::isDistributionList( *rmIt ) ) { mDistributionLists.append( static_cast( *rmIt ) ); rmIt = allContacts.remove( rmIt ); } else ++rmIt; } typedef KPIM::DistributionList::Entry Entry; if ( !mSelectedDistributionList.isNull() ) { const KPIM::DistributionList dl = KPIM::DistributionList::findByName( mAddressBook, mSelectedDistributionList ); if ( !dl.isEmpty() ) { allContacts.clear(); const Entry::List entries = dl.entries( mAddressBook ); const Entry::List::ConstIterator end = entries.end(); for ( Entry::List::ConstIterator it = entries.begin(); it != end; ++it ) { allContacts.append( (*it).addressee ); } } } #endif if ( mPattern.isEmpty() ) { // no pattern, return all mContacts = allContacts; emit contactsUpdated(); return; } const KABC::Field::List fieldList = !mFields.isEmpty() ? mFields : KABC::Field::allFields(); KABC::Addressee::List::ConstIterator it( allContacts.begin() ); const KABC::Addressee::List::ConstIterator endIt( allContacts.end() ); for ( ; it != endIt; ++it ) { #ifdef KDEPIM_NEW_DISTRLISTS if ( KPIM::DistributionList::isDistributionList( *it ) ) continue; #endif bool found = false; // search over all fields KABC::Field::List::ConstIterator fieldIt( fieldList.begin() ); const KABC::Field::List::ConstIterator fieldEndIt( fieldList.end() ); for ( ; fieldIt != fieldEndIt; ++fieldIt ) { if ( type == StartsWith && (*fieldIt)->value( *it ).startsWith( pattern, false ) ) { mContacts.append( *it ); found = true; break; } else if ( type == EndsWith && (*fieldIt)->value( *it ).endsWith( pattern, false ) ) { mContacts.append( *it ); found = true; break; } else if ( type == Contains && (*fieldIt)->value( *it ).find( pattern, 0, false ) != -1 ) { mContacts.append( *it ); found = true; break; } else if ( type == Equals && (*fieldIt)->value( *it ).localeAwareCompare( pattern ) == 0 ) { mContacts.append( *it ); found = true; break; } } if ( !found ) { // search over custom fields const QStringList customs = (*it).customs(); QStringList::ConstIterator customIt( customs.begin() ); const QStringList::ConstIterator customEndIt( customs.end() ); for ( ; customIt != customEndIt; ++customIt ) { int pos = (*customIt).find( ':' ); if ( pos != -1 ) { const QString value = (*customIt).mid( pos + 1 ); if ( type == StartsWith && value.startsWith( pattern, false ) ) { mContacts.append( *it ); break; } else if ( type == EndsWith && value.endsWith( pattern, false ) ) { mContacts.append( *it ); break; } else if ( type == Contains && value.find( pattern, 0, false ) != -1 ) { mContacts.append( *it ); break; } else if ( type == Equals && value.localeAwareCompare( pattern ) == 0 ) { mContacts.append( *it ); break; } } } } } emit contactsUpdated(); } KABC::Addressee::List SearchManager::contacts() const { return mContacts; } void SearchManager::reload() { search( mPattern, mFields, mType ); } #ifdef KDEPIM_NEW_DISTRLISTS void KAB::SearchManager::setSelectedDistributionList( const QString &name ) { if ( mSelectedDistributionList == name ) return; mSelectedDistributionList = name; reload(); } KPIM::DistributionList::List KAB::SearchManager::distributionLists() const { return mDistributionLists; } QStringList KAB::SearchManager::distributionListNames() const { QStringList lst; KPIM::DistributionList::List::ConstIterator it( mDistributionLists.begin() ); const KPIM::DistributionList::List::ConstIterator endIt( mDistributionLists.end() ); for ( ; it != endIt; ++it ) { lst.append( (*it).formattedName() ); } return lst; } #endif #include "searchmanager.moc" diff --git a/kmail/folderdiaacltab.cpp b/kmail/folderdiaacltab.cpp index 681f5bc77c..6f36978f43 100644 --- a/kmail/folderdiaacltab.cpp +++ b/kmail/folderdiaacltab.cpp @@ -1,817 +1,817 @@ // -*- mode: C++; c-file-style: "gnu" -*- /** * folderdiaacltab.cpp * * Copyright (c) 2004 David Faure * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ -#include +#include // FOR KDEPIM_NEW_DISTRLISTS #include "folderdiaacltab.h" #include "acljobs.h" #include "kmfolderimap.h" #include "kmfoldercachedimap.h" #include "kmacctcachedimap.h" #include "kmfolder.h" #include #include #ifdef KDEPIM_NEW_DISTRLISTS #include // libkdepim #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KMail; // In case your kdelibs is < 3.3 #ifndef I18N_NOOP2 #define I18N_NOOP2( comment,x ) x #endif // The set of standard permission sets static const struct { unsigned int permissions; const char* userString; } standardPermissions[] = { { 0, I18N_NOOP2( "Permissions", "None" ) }, { ACLJobs::List | ACLJobs::Read | ACLJobs::WriteSeenFlag, I18N_NOOP2( "Permissions", "Read" ) }, { ACLJobs::List | ACLJobs::Read | ACLJobs::WriteSeenFlag | ACLJobs::Insert | ACLJobs::Post, I18N_NOOP2( "Permissions", "Append" ) }, { ACLJobs::AllWrite, I18N_NOOP2( "Permissions", "Write" ) }, { ACLJobs::All, I18N_NOOP2( "Permissions", "All" ) } }; KMail::ACLEntryDialog::ACLEntryDialog( IMAPUserIdFormat userIdFormat, const QString& caption, QWidget* parent, const char* name ) : KDialogBase( parent, name, true /*modal*/, caption, KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true /*sep*/ ) , mUserIdFormat( userIdFormat ) { QWidget *page = new QWidget( this ); setMainWidget(page); QGridLayout *topLayout = new QGridLayout( page, 3 /*rows*/, 3 /*cols*/, 0, spacingHint() ); QLabel *label = new QLabel( i18n( "&User identifier:" ), page ); topLayout->addWidget( label, 0, 0 ); mUserIdLineEdit = new KLineEdit( page ); topLayout->addWidget( mUserIdLineEdit, 0, 1 ); label->setBuddy( mUserIdLineEdit ); QWhatsThis::add( mUserIdLineEdit, i18n( "The User Identifier is the login of the user on the IMAP server. This can be a simple user name or the full email address of the user; the login for your own account on the server will tell you which one it is." ) ); QPushButton* kabBtn = new QPushButton( "...", page ); topLayout->addWidget( kabBtn, 0, 2 ); mButtonGroup = new QVButtonGroup( i18n( "Permissions" ), page ); topLayout->addMultiCellWidget( mButtonGroup, 1, 1, 0, 2 ); for ( unsigned int i = 0; i < sizeof( standardPermissions ) / sizeof( *standardPermissions ); ++i ) { QRadioButton* cb = new QRadioButton( i18n( "Permissions", standardPermissions[i].userString ), mButtonGroup ); // We store the permission value (bitfield) as the id of the radiobutton in the group mButtonGroup->insert( cb, standardPermissions[i].permissions ); } topLayout->setRowStretch(2, 10); connect( mUserIdLineEdit, SIGNAL( textChanged( const QString& ) ), SLOT( slotChanged() ) ); connect( kabBtn, SIGNAL( clicked() ), SLOT( slotSelectAddresses() ) ); connect( mButtonGroup, SIGNAL( clicked( int ) ), SLOT( slotChanged() ) ); enableButtonOK( false ); mUserIdLineEdit->setFocus(); // Ensure the lineedit is rather wide so that email addresses can be read in it incInitialSize( QSize( 200, 0 ) ); } void KMail::ACLEntryDialog::slotChanged() { enableButtonOK( !mUserIdLineEdit->text().isEmpty() && mButtonGroup->selected() != 0 ); } static QString addresseeToUserId( const KABC::Addressee& addr, IMAPUserIdFormat userIdFormat ) { QString email = addr.preferredEmail(); if ( userIdFormat == FullEmail ) return email; else { // mUserIdFormat == UserName email.truncate( email.find( '@' ) ); return email; } } void KMail::ACLEntryDialog::slotSelectAddresses() { KPIM::AddressesDialog dlg( this ); dlg.setShowCC( false ); dlg.setShowBCC( false ); if ( mUserIdFormat == FullEmail ) // otherwise we have no way to go back from userid to email dlg.setSelectedTo( userIds() ); if ( dlg.exec() != QDialog::Accepted ) return; const QStringList distrLists = dlg.toDistributionLists(); QString txt = distrLists.join( ", " ); const KABC::Addressee::List lst = dlg.toAddresses(); if ( !lst.isEmpty() ) { for( QValueList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { if ( !txt.isEmpty() ) txt += ", "; txt += addresseeToUserId( *it, mUserIdFormat ); } } mUserIdLineEdit->setText( txt ); } void KMail::ACLEntryDialog::setValues( const QString& userId, unsigned int permissions ) { mUserIdLineEdit->setText( userId ); mButtonGroup->setButton( permissions ); enableButtonOK( !userId.isEmpty() ); } QString KMail::ACLEntryDialog::userId() const { return mUserIdLineEdit->text(); } QStringList KMail::ACLEntryDialog::userIds() const { QStringList lst = QStringList::split( ",", mUserIdLineEdit->text() ); for( QStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) { // Strip white space (in particular, due to ", ") *it = (*it).stripWhiteSpace(); } return lst; } unsigned int KMail::ACLEntryDialog::permissions() const { return mButtonGroup->selectedId(); } // class KMail::FolderDiaACLTab::ListView : public KListView // { // public: // ListView( QWidget* parent, const char* name = 0 ) : KListView( parent, name ) {} // }; class KMail::FolderDiaACLTab::ListViewItem : public KListViewItem { public: ListViewItem( QListView* listview ) : KListViewItem( listview, listview->lastItem() ), mModified( false ), mNew( false ) {} void load( const ACLListEntry& entry ); void save( ACLList& list, #ifdef KDEPIM_NEW_DISTRLISTS KABC::AddressBook* abook, #else KABC::DistributionListManager& manager, #endif IMAPUserIdFormat userIdFormat ); QString userId() const { return text( 0 ); } void setUserId( const QString& userId ) { setText( 0, userId ); } unsigned int permissions() const { return mPermissions; } void setPermissions( unsigned int permissions ); bool isModified() const { return mModified; } void setModified( bool b ) { mModified = b; } // The fact that an item is new doesn't matter much. // This bool is only used to handle deletion differently bool isNew() const { return mNew; } void setNew( bool b ) { mNew = b; } private: unsigned int mPermissions; QString mInternalRightsList; ///< protocol-dependent string (e.g. IMAP rights list) bool mModified; bool mNew; }; // internalRightsList is only used if permissions doesn't match the standard set static QString permissionsToUserString( unsigned int permissions, const QString& internalRightsList ) { for ( unsigned int i = 0; i < sizeof( standardPermissions ) / sizeof( *standardPermissions ); ++i ) { if ( permissions == standardPermissions[i].permissions ) return i18n( "Permissions", standardPermissions[i].userString ); } if ( internalRightsList.isEmpty() ) return i18n( "Custom Permissions" ); // not very helpful, but shouldn't happen else return i18n( "Custom Permissions (%1)" ).arg( internalRightsList ); } void KMail::FolderDiaACLTab::ListViewItem::setPermissions( unsigned int permissions ) { mPermissions = permissions; setText( 1, permissionsToUserString( permissions, QString::null ) ); } void KMail::FolderDiaACLTab::ListViewItem::load( const ACLListEntry& entry ) { // Don't allow spaces in userids. If you need this, fix the slave->app communication, // since it uses space as a separator (imap4.cc, look for GETACL) // It's ok in distribution list names though, that's why this check is only done here // and also why there's no validator on the lineedit. if ( entry.userId.contains( ' ' ) ) kdWarning(5006) << "Userid contains a space!!! '" << entry.userId << "'" << endl; setUserId( entry.userId ); mPermissions = entry.permissions; mInternalRightsList = entry.internalRightsList; setText( 1, permissionsToUserString( entry.permissions, entry.internalRightsList ) ); mModified = entry.changed; // for dimap, so that earlier changes are still marked as changes } void KMail::FolderDiaACLTab::ListViewItem::save( ACLList& aclList, #ifdef KDEPIM_NEW_DISTRLISTS KABC::AddressBook* addressBook, #else KABC::DistributionListManager& manager, #endif IMAPUserIdFormat userIdFormat ) { // expand distribution lists #ifdef KDEPIM_NEW_DISTRLISTS KPIM::DistributionList list = KPIM::DistributionList::findByName( addressBook, userId(), false ); if ( !list.isEmpty() ) { Q_ASSERT( mModified ); // it has to be new, it couldn't be stored as a distr list name.... KPIM::DistributionList::Entry::List entryList = list.entries(addressBook); KPIM::DistributionList::Entry::List::ConstIterator it; // (we share for loop with the old-distrlist-code) #else // kaddrbook.cpp has a strange two-pass case-insensitive lookup; is it ok to be case sensitive? KABC::DistributionList* list = manager.list( userId() ); if ( list ) { Q_ASSERT( mModified ); // it has to be new, it couldn't be stored as a distr list name.... KABC::DistributionList::Entry::List entryList = list->entries(); KABC::DistributionList::Entry::List::ConstIterator it; // nice number of "::"! #endif for( it = entryList.begin(); it != entryList.end(); ++it ) { QString email = (*it).email; if ( email.isEmpty() ) email = addresseeToUserId( (*it).addressee, userIdFormat ); ACLListEntry entry( email, QString::null, mPermissions ); entry.changed = true; aclList.append( entry ); } } else { // it wasn't a distribution list ACLListEntry entry( userId(), mInternalRightsList, mPermissions ); if ( mModified ) { entry.internalRightsList = QString::null; entry.changed = true; } aclList.append( entry ); } } //// KMail::FolderDiaACLTab::FolderDiaACLTab( KMFolderDialog* dlg, QWidget* parent, const char* name ) : FolderDiaTab( parent, name ), mImapAccount( 0 ), mUserRights( 0 ), mDlg( dlg ), mChanged( false ), mAccepting( false ), mSaving( false ) { QVBoxLayout* topLayout = new QVBoxLayout( this ); // We need a widget stack to show either a label ("no acl support", "please wait"...) // or a listview. mStack = new QWidgetStack( this ); topLayout->addWidget( mStack ); mLabel = new QLabel( mStack ); mLabel->setAlignment( AlignHCenter | AlignVCenter | WordBreak ); mStack->addWidget( mLabel ); mACLWidget = new QHBox( mStack ); mACLWidget->setSpacing( KDialog::spacingHint() ); mListView = new KListView( mACLWidget ); mListView->setAllColumnsShowFocus( true ); mStack->addWidget( mACLWidget ); mListView->addColumn( i18n( "User Id" ) ); mListView->addColumn( i18n( "Permissions" ) ); connect( mListView, SIGNAL(doubleClicked(QListViewItem*,const QPoint&,int)), SLOT(slotEditACL(QListViewItem*)) ); connect( mListView, SIGNAL(returnPressed(QListViewItem*)), SLOT(slotEditACL(QListViewItem*)) ); connect( mListView, SIGNAL(currentChanged(QListViewItem*)), SLOT(slotSelectionChanged(QListViewItem*)) ); QVBox* buttonBox = new QVBox( mACLWidget ); buttonBox->setSpacing( KDialog::spacingHint() ); mAddACL = new KPushButton( i18n( "Add Entry..." ), buttonBox ); mEditACL = new KPushButton( i18n( "Modify Entry..." ), buttonBox ); mRemoveACL = new KPushButton( i18n( "Remove Entry" ), buttonBox ); QWidget *spacer = new QWidget( buttonBox ); spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Expanding ); connect( mAddACL, SIGNAL( clicked() ), SLOT( slotAddACL() ) ); connect( mEditACL, SIGNAL( clicked() ), SLOT( slotEditACL() ) ); connect( mRemoveACL, SIGNAL( clicked() ), SLOT( slotRemoveACL() ) ); mEditACL->setEnabled( false ); mRemoveACL->setEnabled( false ); connect( this, SIGNAL( changed(bool) ), SLOT( slotChanged(bool) ) ); } // Warning before save() this will return the url of the _parent_ folder, when creating a new one KURL KMail::FolderDiaACLTab::imapURL() const { KURL url = mImapAccount->getUrl(); url.setPath( mImapPath ); return url; } void KMail::FolderDiaACLTab::initializeWithValuesFromFolder( KMFolder* folder ) { // This can be simplified once KMFolderImap and KMFolderCachedImap have a common base class mFolderType = folder->folderType(); if ( mFolderType == KMFolderTypeImap ) { KMFolderImap* folderImap = static_cast( folder->storage() ); mImapPath = folderImap->imapPath(); mImapAccount = folderImap->account(); mUserRights = folderImap->userRights(); } else if ( mFolderType == KMFolderTypeCachedImap ) { KMFolderCachedImap* folderImap = static_cast( folder->storage() ); mImapPath = folderImap->imapPath(); mImapAccount = folderImap->account(); mUserRights = folderImap->userRights(); } else assert( 0 ); // see KMFolderDialog constructor } void KMail::FolderDiaACLTab::load() { if ( mDlg->folder() ) { // existing folder initializeWithValuesFromFolder( mDlg->folder() ); } else if ( mDlg->parentFolder() ) { // new folder initializeWithValuesFromFolder( mDlg->parentFolder() ); mChanged = true; // ensure that saving happens } // KABC knows email addresses. // We want LDAP userids. // Depending on the IMAP server setup, the userid can be the full email address, // or just the username part of it. // To know which one it is, we currently have a hidden config option, // but the default value is determined from the current user's own id. QString defaultFormat = "fullemail"; // warning mImapAccount can be 0 if creating a subsubsubfolder with dimap... (bug?) if ( mImapAccount && mImapAccount->login().find('@') == -1 ) defaultFormat = "username"; // no @ found, so we assume it's just the username KConfigGroup configGroup( kmkernel->config(), "IMAP" ); QString str = configGroup.readEntry( "UserIdFormat", defaultFormat ); mUserIdFormat = FullEmail; if ( str == "username" ) mUserIdFormat = UserName; if ( mFolderType == KMFolderTypeCachedImap ) { KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder(); KMFolderCachedImap* folderImap = static_cast( folder->storage() ); if ( mUserRights == -1 ) { // error mLabel->setText( i18n( "Error retrieving user permissions." ) ); } else if ( mUserRights == 0 /* can't happen anymore*/ || folderImap->aclList().isEmpty() ) { /* We either synced, or we read user rights from the config, so we can assume the server supports acls and an empty list means we haven't synced yet. */ mLabel->setText( i18n( "Information not retrieved from server, you need to use \"Check Mail\" and have administrative privileges on the folder.")); } else { loadFinished( folderImap->aclList() ); } return; } // Loading, for online IMAP, consists of four steps: // 1) connect // 2) get user rights // 3) load ACLs // First ensure we are connected mStack->raiseWidget( mLabel ); if ( !mImapAccount ) { // hmmm? mLabel->setText( i18n( "Error: no IMAP account defined for this folder" ) ); return; } KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder(); if ( folder && folder->storage() == mImapAccount->rootFolder() ) return; // nothing to be done for the (virtual) account folder mLabel->setText( i18n( "Connecting to server %1, please wait..." ).arg( mImapAccount->host() ) ); ImapAccountBase::ConnectionState state = mImapAccount->makeConnection(); if ( state == ImapAccountBase::Error ) { // Cancelled by user, or slave can't start slotConnectionResult( -1, QString::null ); } else if ( state == ImapAccountBase::Connecting ) { connect( mImapAccount, SIGNAL( connectionResult(int, const QString&) ), this, SLOT( slotConnectionResult(int, const QString&) ) ); } else { // Connected slotConnectionResult( 0, QString::null ); } } void KMail::FolderDiaACLTab::slotConnectionResult( int errorCode, const QString& errorMsg ) { disconnect( mImapAccount, SIGNAL( connectionResult(int, const QString&) ), this, SLOT( slotConnectionResult(int, const QString&) ) ); if ( errorCode ) { if ( errorCode == -1 ) // unspecified error mLabel->setText( i18n( "Error connecting to server %1" ).arg( mImapAccount->host() ) ); else // Connection error (error message box already shown by the account) mLabel->setText( KIO::buildErrorString( errorCode, errorMsg ) ); return; } if ( mUserRights == 0 ) { connect( mImapAccount, SIGNAL( receivedUserRights( KMFolder* ) ), this, SLOT( slotReceivedUserRights( KMFolder* ) ) ); KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder(); mImapAccount->getUserRights( folder, mImapPath ); } else startListing(); } void KMail::FolderDiaACLTab::slotReceivedUserRights( KMFolder* folder ) { if ( !mImapAccount->hasACLSupport() ) { mLabel->setText( i18n( "This IMAP server does not have support for access control lists (ACL)" ) ); return; } if ( folder == mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) { KMFolderImap* folderImap = static_cast( folder->storage() ); mUserRights = folderImap->userRights(); startListing(); } } void KMail::FolderDiaACLTab::startListing() { // List ACLs of folder - or its parent, if creating a new folder mImapAccount->getACL( mDlg->folder() ? mDlg->folder() : mDlg->parentFolder(), mImapPath ); connect( mImapAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )), this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) ); } void KMail::FolderDiaACLTab::slotReceivedACL( KMFolder* folder, KIO::Job* job, const KMail::ACLList& aclList ) { if ( folder == ( mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) ) { disconnect( mImapAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )), this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) ); if ( job && job->error() ) { if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) mLabel->setText( i18n( "This IMAP server does not have support for access control lists (ACL)" ) ); else mLabel->setText( i18n( "Error retrieving access control list (ACL) from server\n%1" ).arg( job->errorString() ) ); return; } loadFinished( aclList ); } } void KMail::FolderDiaACLTab::loadListView( const ACLList& aclList ) { mListView->clear(); for( ACLList::const_iterator it = aclList.begin(); it != aclList.end(); ++it ) { // -1 means deleted (for cachedimap), don't show those if ( (*it).permissions > -1 ) { ListViewItem* item = new ListViewItem( mListView ); item->load( *it ); if ( !mDlg->folder() ) // new folder? everything is new then item->setModified( true ); } } } void KMail::FolderDiaACLTab::loadFinished( const ACLList& aclList ) { loadListView( aclList ); if ( mDlg->folder() ) // not when creating a new folder mInitialACLList = aclList; mStack->raiseWidget( mACLWidget ); slotSelectionChanged( mListView->selectedItem() ); } void KMail::FolderDiaACLTab::slotEditACL(QListViewItem* item) { if ( !item ) return; bool canAdmin = ( mUserRights & ACLJobs::Administer ); // Same logic as in slotSelectionChanged, but this is also needed for double-click IIRC if ( canAdmin && mImapAccount && item ) { // Don't allow users to remove their own admin permissions - there's no way back ListViewItem* ACLitem = static_cast( item ); if ( mImapAccount->login() == ACLitem->userId() && ACLitem->permissions() == ACLJobs::All ) canAdmin = false; } if ( !canAdmin ) return; ListViewItem* ACLitem = static_cast( mListView->currentItem() ); ACLEntryDialog dlg( mUserIdFormat, i18n( "Modify Permissions" ), this ); dlg.setValues( ACLitem->userId(), ACLitem->permissions() ); if ( dlg.exec() == QDialog::Accepted ) { QStringList userIds = dlg.userIds(); Q_ASSERT( !userIds.isEmpty() ); // impossible, the OK button is disabled in that case ACLitem->setUserId( dlg.userIds().front() ); ACLitem->setPermissions( dlg.permissions() ); ACLitem->setModified( true ); emit changed(true); if ( userIds.count() > 1 ) { // more emails were added, append them userIds.pop_front(); addACLs( userIds, dlg.permissions() ); } } } void KMail::FolderDiaACLTab::slotEditACL() { slotEditACL( mListView->currentItem() ); } void KMail::FolderDiaACLTab::addACLs( const QStringList& userIds, unsigned int permissions ) { for( QStringList::const_iterator it = userIds.begin(); it != userIds.end(); ++it ) { ListViewItem* ACLitem = new ListViewItem( mListView ); ACLitem->setUserId( *it ); ACLitem->setPermissions( permissions ); ACLitem->setModified( true ); ACLitem->setNew( true ); } } void KMail::FolderDiaACLTab::slotAddACL() { ACLEntryDialog dlg( mUserIdFormat, i18n( "Add Permissions" ), this ); if ( dlg.exec() == QDialog::Accepted ) { const QStringList userIds = dlg.userIds(); addACLs( dlg.userIds(), dlg.permissions() ); emit changed(true); } } void KMail::FolderDiaACLTab::slotSelectionChanged(QListViewItem* item) { bool canAdmin = ( mUserRights & ACLJobs::Administer ); bool canAdminThisItem = canAdmin; if ( canAdmin && mImapAccount && item ) { // Don't allow users to remove their own admin permissions - there's no way back ListViewItem* ACLitem = static_cast( item ); if ( mImapAccount->login() == ACLitem->userId() && ACLitem->permissions() == ACLJobs::All ) canAdminThisItem = false; } bool lvVisible = mStack->visibleWidget() == mACLWidget; mAddACL->setEnabled( lvVisible && canAdmin && !mSaving ); mEditACL->setEnabled( item && lvVisible && canAdminThisItem && !mSaving ); mRemoveACL->setEnabled( item && lvVisible && canAdminThisItem && !mSaving ); } void KMail::FolderDiaACLTab::slotRemoveACL() { ListViewItem* ACLitem = static_cast( mListView->currentItem() ); if ( !ACLitem ) return; if ( !ACLitem->isNew() ) { if ( mImapAccount && mImapAccount->login() == ACLitem->userId() ) { if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel( topLevelWidget(), i18n( "Do you really want to remove your own permissions for this folder? You will not be able to access it afterwards." ), i18n( "Remove" ) ) ) return; } mRemovedACLs.append( ACLitem->userId() ); } delete ACLitem; emit changed(true); } KMail::FolderDiaTab::AcceptStatus KMail::FolderDiaACLTab::accept() { if ( !mChanged || !mImapAccount ) return Accepted; // (no change made), ok for accepting the dialog immediately // If there were changes, we need to apply them first (which is async) save(); if ( mFolderType == KMFolderTypeCachedImap ) return Accepted; // cached imap: changes saved immediately into the folder // disconnected imap: async job[s] running mAccepting = true; return Delayed; } bool KMail::FolderDiaACLTab::save() { if ( !mChanged || !mImapAccount ) // no changes return true; assert( mDlg->folder() ); // should have been created already // Expand distribution lists. This is necessary because after Apply // we would otherwise be able to "modify" the permissions for a distr list, // which wouldn't work since the ACLList and the server only know about the // individual addresses. // slotACLChanged would have trouble matching the item too. // After reloading we'd see the list expanded anyway, // so this is more consistent. // But we do it now and not when inserting it, because this allows to // immediately remove a wrongly inserted distr list without having to // remove 100 items. // Now, how to expand them? Playing with listviewitem iterators and inserting // listviewitems at the same time sounds dangerous, so let's just save into // ACLList and reload that. KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true ); #ifndef KDEPIM_NEW_DISTRLISTS KABC::DistributionListManager manager( addressBook ); manager.load(); #endif ACLList aclList; for ( QListViewItem* item = mListView->firstChild(); item; item = item->nextSibling() ) { ListViewItem* ACLitem = static_cast( item ); ACLitem->save( aclList, #ifdef KDEPIM_NEW_DISTRLISTS addressBook, #else manager, #endif mUserIdFormat ); } loadListView( aclList ); // Now compare with the initial ACLList, because if the user renamed a userid // we have to add the old userid to the "to be deleted" list. for( ACLList::ConstIterator init = mInitialACLList.begin(); init != mInitialACLList.end(); ++init ) { bool isInNewList = false; QString uid = (*init).userId; for( ACLList::ConstIterator it = aclList.begin(); it != aclList.end() && !isInNewList; ++it ) isInNewList = uid == (*it).userId; if ( !isInNewList && !mRemovedACLs.contains(uid) ) mRemovedACLs.append( uid ); } for ( QStringList::ConstIterator rit = mRemovedACLs.begin(); rit != mRemovedACLs.end(); ++rit ) { // We use permissions == -1 to signify deleting. At least on cyrus, setacl(0) or deleteacl are the same, // but I'm not sure if that's true for all servers. ACLListEntry entry( *rit, QString::null, -1 ); entry.changed = true; aclList.append( entry ); } // aclList is finally ready. We can save it (dimap) or apply it (imap). if ( mFolderType == KMFolderTypeCachedImap ) { // Apply the changes to the aclList stored in the folder. // We have to do this now and not before, so that cancel really cancels. KMFolderCachedImap* folderImap = static_cast( mDlg->folder()->storage() ); folderImap->setACLList( aclList ); return true; } mACLList = aclList; KMFolderImap* parentImap = mDlg->parentFolder() ? static_cast( mDlg->parentFolder()->storage() ) : 0; if ( mDlg->isNewFolder() ) { // The folder isn't created yet, wait for it // It's a two-step process (mkdir+listDir) so we wait for the dir listing to be complete connect( parentImap, SIGNAL( directoryListingFinished(KMFolderImap*) ), this, SLOT( slotDirectoryListingFinished(KMFolderImap*) ) ); } else { slotDirectoryListingFinished( parentImap ); } return true; } void KMail::FolderDiaACLTab::slotDirectoryListingFinished(KMFolderImap* f) { if ( !f || f != static_cast( mDlg->parentFolder()->storage() ) || !mDlg->folder() || !mDlg->folder()->storage() ) { emit readyForAccept(); return; } // When creating a new folder with online imap, update mImapPath KMFolderImap* folderImap = static_cast( mDlg->folder()->storage() ); if ( !folderImap || folderImap->imapPath().isEmpty() ) return; mImapPath = folderImap->imapPath(); KIO::Job* job = ACLJobs::multiSetACL( mImapAccount->slave(), imapURL(), mACLList ); ImapAccountBase::jobData jd; jd.total = 1; jd.done = 0; jd.parent = 0; mImapAccount->insertJob(job, jd); connect(job, SIGNAL(result(KIO::Job *)), SLOT(slotMultiSetACLResult(KIO::Job *))); connect(job, SIGNAL(aclChanged( const QString&, int )), SLOT(slotACLChanged( const QString&, int )) ); } void KMail::FolderDiaACLTab::slotMultiSetACLResult(KIO::Job* job) { ImapAccountBase::JobIterator it = mImapAccount->findJob( job ); if ( it == mImapAccount->jobsEnd() ) return; mImapAccount->removeJob( it ); if ( job->error() ) { job->showErrorDialog( this ); if ( mAccepting ) { emit cancelAccept(); mAccepting = false; // don't emit readyForAccept anymore } } else { if ( mAccepting ) emit readyForAccept(); } } void KMail::FolderDiaACLTab::slotACLChanged( const QString& userId, int permissions ) { // The job indicates success in changing the permissions for this user // -> we note that it's been done. bool ok = false; if ( permissions > -1 ) { for ( QListViewItem* item = mListView->firstChild(); item; item = item->nextSibling() ) { ListViewItem* ACLitem = static_cast( item ); if ( ACLitem->userId() == userId ) { ACLitem->setModified( false ); ACLitem->setNew( false ); ok = true; break; } } } else { uint nr = mRemovedACLs.remove( userId ); ok = ( nr > 0 ); } if ( !ok ) kdWarning(5006) << k_funcinfo << " no item found for userId " << userId << endl; } void KMail::FolderDiaACLTab::slotChanged( bool b ) { mChanged = b; } bool KMail::FolderDiaACLTab::supports( KMFolder* refFolder ) { ImapAccountBase* imapAccount = 0; if ( refFolder->folderType() == KMFolderTypeImap ) imapAccount = static_cast( refFolder->storage() )->account(); else imapAccount = static_cast( refFolder->storage() )->account(); return imapAccount && imapAccount->hasACLSupport(); // support for ACLs (or not tried connecting yet) } #include "folderdiaacltab.moc" diff --git a/kmail/kmlineeditspell.cpp b/kmail/kmlineeditspell.cpp index ca315dea11..b49924ddf6 100644 --- a/kmail/kmlineeditspell.cpp +++ b/kmail/kmlineeditspell.cpp @@ -1,216 +1,216 @@ // -*- mode: C++; c-file-style: "gnu" -*- // kmcomposewin.cpp // Author: Markus Wuebben // This code is published under the GPL. #include "kmlineeditspell.h" #include "recentaddresses.h" #include "kmkernel.h" #include "globalsettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include KMLineEdit::KMLineEdit(bool useCompletion, QWidget *parent, const char *name) : KPIM::AddresseeLineEdit(parent,useCompletion,name) { allowSemiColonAsSeparator( GlobalSettings::allowSemicolonAsAddressSeparator() ); } //----------------------------------------------------------------------------- void KMLineEdit::keyPressEvent(QKeyEvent *e) { if ((e->key() == Key_Enter || e->key() == Key_Return) && !completionBox()->isVisible()) { emit focusDown(); AddresseeLineEdit::keyPressEvent(e); return; } if (e->key() == Key_Up) { emit focusUp(); return; } if (e->key() == Key_Down) { emit focusDown(); return; } AddresseeLineEdit::keyPressEvent(e); } void KMLineEdit::insertEmails( const QStringList & emails ) { if ( emails.empty() ) return; QString contents = text(); if ( !contents.isEmpty() ) contents += ','; // only one address, don't need kpopup to choose if ( emails.size() == 1 ) { setText( contents + emails.front() ); return; } //multiple emails, let the user choose one KPopupMenu menu( this, "Addresschooser" ); for ( QStringList::const_iterator it = emails.begin(), end = emails.end() ; it != end; ++it ) menu.insertItem( *it ); const int result = menu.exec( QCursor::pos() ); if ( result < 0 ) return; setText( contents + menu.text( result ) ); } void KMLineEdit::dropEvent(QDropEvent *event) { QString vcards; KVCardDrag::decode( event, vcards ); if ( !vcards.isEmpty() ) { KABC::VCardConverter converter; KABC::Addressee::List list = converter.parseVCards( vcards ); KABC::Addressee::List::Iterator ait; for ( ait = list.begin(); ait != list.end(); ++ait ){ insertEmails( (*ait).emails() ); } } else { KURL::List urls; if ( KURLDrag::decode( event, urls) ) { //kdDebug(5006) << "urlList" << endl; KURL::List::Iterator it = urls.begin(); KABC::VCardConverter converter; KABC::Addressee::List list; QString fileName; QString caption( i18n( "vCard Import Failed" ) ); for ( it = urls.begin(); it != urls.end(); ++it ) { if ( KIO::NetAccess::download( *it, fileName, parentWidget() ) ) { QFile file( fileName ); file.open( IO_ReadOnly ); QByteArray rawData = file.readAll(); file.close(); QString data = QString::fromUtf8( rawData.data(), rawData.size() + 1 ); list += converter.parseVCards( data ); KIO::NetAccess::removeTempFile( fileName ); } else { QString text = i18n( "Unable to access %1." ); KMessageBox::error( parentWidget(), text.arg( (*it).url() ), caption ); } KABC::Addressee::List::Iterator ait; for ( ait = list.begin(); ait != list.end(); ++ait ) insertEmails((*ait).emails()); } } else { KPIM::AddresseeLineEdit::dropEvent( event ); } } } QPopupMenu *KMLineEdit::createPopupMenu() { QPopupMenu *menu = KPIM::AddresseeLineEdit::createPopupMenu(); if ( !menu ) return 0; menu->insertSeparator(); menu->insertItem( i18n( "Edit Recent Addresses..." ), this, SLOT( editRecentAddresses() ) ); return menu; } void KMLineEdit::editRecentAddresses() { KRecentAddress::RecentAddressDialog dlg( this ); dlg.setAddresses( KRecentAddress::RecentAddresses::self( KMKernel::config() )->addresses() ); if ( !dlg.exec() ) return; KRecentAddress::RecentAddresses::self( KMKernel::config() )->clear(); const QStringList addrList = dlg.addresses(); for ( QStringList::const_iterator it = addrList.begin(), end = addrList.end() ; it != end ; ++it ) KRecentAddress::RecentAddresses::self( KMKernel::config() )->add( *it ); loadContacts(); } //----------------------------------------------------------------------------- void KMLineEdit::loadContacts() { - // was: KABC::AddressLineEdit::loadAddresses() AddresseeLineEdit::loadContacts(); if ( GlobalSettings::self()->showRecentAddressesInComposer() ){ if ( KMKernel::self() ) { QStringList recent = KRecentAddress::RecentAddresses::self( KMKernel::config() )->addresses(); QStringList::Iterator it = recent.begin(); QString name, email; - int idx = addCompletionSource( i18n( "Recent Addresses" ) ); + // FIXME: Make the 120 configureable. This is also hardcoded somewhere else! + int idx = addCompletionSource( i18n( "Recent Addresses" ), 120 ); for ( ; it != recent.end(); ++it ) { KABC::Addressee addr; KPIM::getNameAndMail(*it, name, email); addr.setNameFromString( KPIM::quoteNameIfNecessary( name )); addr.insertEmail( email, true ); addContact( addr, 120, idx ); // more weight than kabc entries and more than ldap results } } } } KMLineEditSpell::KMLineEditSpell(bool useCompletion, QWidget *parent, const char *name) : KMLineEdit(useCompletion,parent,name) { } void KMLineEditSpell::highLightWord( unsigned int length, unsigned int pos ) { setSelection ( pos, length ); } void KMLineEditSpell::spellCheckDone( const QString &s ) { if( s != text() ) setText( s ); } void KMLineEditSpell::spellCheckerMisspelling( const QString &_text, const QStringList&, unsigned int pos) { highLightWord( _text.length(),pos ); } void KMLineEditSpell::spellCheckerCorrected( const QString &old, const QString &corr, unsigned int pos) { if( old!= corr ) { setSelection ( pos, old.length() ); insert( corr ); setSelection ( pos, corr.length() ); emit subjectTextSpellChecked(); } } #include "kmlineeditspell.moc" diff --git a/libkdepim/addresseelineedit.cpp b/libkdepim/addresseelineedit.cpp index 2dd2f3ec0f..12b7defa29 100644 --- a/libkdepim/addresseelineedit.cpp +++ b/libkdepim/addresseelineedit.cpp @@ -1,1140 +1,1212 @@ /* This file is part of libkdepim. Copyright (c) 2002 Helge Deller 2002 Lubos Lunak 2001,2003 Carsten Pfeiffer 2001 Waldo Bastian 2004 Daniel Molkentin 2004 Karl-Heinz Zimmer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "addresseelineedit.h" #include "resourceabc.h" #include "completionordereditor.h" #include "ldapclient.h" #include #ifdef KDEPIM_NEW_DISTRLISTS #include "distributionlist.h" #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPIM; KMailCompletion * AddresseeLineEdit::s_completion = 0L; KPIM::CompletionItemsMap* AddresseeLineEdit::s_completionItemMap = 0L; QStringList* AddresseeLineEdit::s_completionSources = 0L; bool AddresseeLineEdit::s_addressesDirty = false; QTimer* AddresseeLineEdit::s_LDAPTimer = 0L; KPIM::LdapSearch* AddresseeLineEdit::s_LDAPSearch = 0L; QString* AddresseeLineEdit::s_LDAPText = 0L; AddresseeLineEdit* AddresseeLineEdit::s_LDAPLineEdit = 0L; +// The weights associated with the completion sources in s_completionSources. +// Both are maintained by addCompletionSource(), don't attempt to modifiy those yourself. +QMap* s_completionSourceWeights; + static KStaticDeleter completionDeleter; static KStaticDeleter completionItemsDeleter; static KStaticDeleter ldapTimerDeleter; static KStaticDeleter ldapSearchDeleter; static KStaticDeleter ldapTextDeleter; static KStaticDeleter completionSourcesDeleter; +static KStaticDeleter > completionSourceWeightsDeleter; // needs to be unique, but the actual name doesn't matter much static QCString newLineEditDCOPObjectName() { static int s_count = 0; QCString name( "KPIM::AddresseeLineEdit" ); if ( s_count++ ) { name += '-'; name += QCString().setNum( s_count ); } return name; } static const QString s_completionItemIndentString = " "; static bool itemIsHeader( const QListBoxItem* item ) { return item && !item->text().startsWith( s_completionItemIndentString ); } AddresseeLineEdit::AddresseeLineEdit( QWidget* parent, bool useCompletion, const char *name ) : ClickLineEdit( parent, QString::null, name ), DCOPObject( newLineEditDCOPObjectName() ) { m_useCompletion = useCompletion; m_completionInitialized = false; m_smartPaste = false; m_addressBookConnected = false; m_searchExtended = false; init(); if ( m_useCompletion ) s_addressesDirty = true; } +void AddresseeLineEdit::updateLDAPWeights() +{ + /* Add completion sources for all ldap server, 0 to n. Added first so + * that they map to the ldapclient::clientNumber() */ + s_LDAPSearch->updateCompletionWeights(); + QValueList< LdapClient* > clients = s_LDAPSearch->clients(); + for ( QValueList::iterator it = clients.begin(); it != clients.end(); ++it ) { + addCompletionSource( "LDAP server: " + (*it)->server().host(), (*it)->completionWeight() ); + } +} void AddresseeLineEdit::init() { if ( !s_completion ) { completionDeleter.setObject( s_completion, new KMailCompletion() ); s_completion->setOrder( completionOrder() ); s_completion->setIgnoreCase( true ); completionItemsDeleter.setObject( s_completionItemMap, new KPIM::CompletionItemsMap() ); completionSourcesDeleter.setObject( s_completionSources, new QStringList() ); + completionSourceWeightsDeleter.setObject( s_completionSourceWeights, new QMap ); } - // connect( s_completion, SIGNAL( match( const QString& ) ), // this, SLOT( slotMatched( const QString& ) ) ); if ( m_useCompletion ) { if ( !s_LDAPTimer ) { ldapTimerDeleter.setObject( s_LDAPTimer, new QTimer( 0, "ldapTimerDeleter" ) ); ldapSearchDeleter.setObject( s_LDAPSearch, new KPIM::LdapSearch ); ldapTextDeleter.setObject( s_LDAPText, new QString ); - - /* Add completion sources for all ldap server, 0 to n. Added first so - * that they map to the ldapclient::clientNumber() */ - QValueList< LdapClient* > clients = s_LDAPSearch->clients(); - for ( QValueList::iterator it = clients.begin(); it != clients.end(); ++it ) { - addCompletionSource( "LDAP server: " + (*it)->server().host() ); - } } + + updateLDAPWeights(); + if ( !m_completionInitialized ) { setCompletionObject( s_completion, false ); connect( this, SIGNAL( completion( const QString& ) ), this, SLOT( slotCompletion() ) ); connect( this, SIGNAL( returnPressed( const QString& ) ), this, SLOT( slotReturnPressed( const QString& ) ) ); KCompletionBox *box = completionBox(); connect( box, SIGNAL( highlighted( const QString& ) ), this, SLOT( slotPopupCompletion( const QString& ) ) ); connect( box, SIGNAL( userCancelled( const QString& ) ), SLOT( slotUserCancelled( const QString& ) ) ); // The emitter is always called KPIM::IMAPCompletionOrder by contract if ( !connectDCOPSignal( 0, "KPIM::IMAPCompletionOrder", "orderChanged()", "slotIMAPCompletionOrderChanged()", false ) ) kdError() << "AddresseeLineEdit: connection to orderChanged() failed" << endl; connect( s_LDAPTimer, SIGNAL( timeout() ), SLOT( slotStartLDAPLookup() ) ); connect( s_LDAPSearch, SIGNAL( searchData( const KPIM::LdapResultList& ) ), SLOT( slotLDAPSearchData( const KPIM::LdapResultList& ) ) ); m_completionInitialized = true; } } } AddresseeLineEdit::~AddresseeLineEdit() { if ( s_LDAPSearch && s_LDAPLineEdit == this ) stopLDAPLookup(); } void AddresseeLineEdit::setFont( const QFont& font ) { KLineEdit::setFont( font ); if ( m_useCompletion ) completionBox()->setFont( font ); } void AddresseeLineEdit::allowSemiColonAsSeparator( bool useSemiColonAsSeparator ) { m_useSemiColonAsSeparator = useSemiColonAsSeparator; } void AddresseeLineEdit::keyPressEvent( QKeyEvent *e ) { bool accept = false; if ( KStdAccel::shortcut( KStdAccel::SubstringCompletion ).contains( KKey( e ) ) ) { //TODO: add LDAP substring lookup, when it becomes available in KPIM::LDAPSearch updateSearchString(); doCompletion( true ); accept = true; } else if ( KStdAccel::shortcut( KStdAccel::TextCompletion ).contains( KKey( e ) ) ) { int len = text().length(); if ( len == cursorPosition() ) { // at End? updateSearchString(); doCompletion( true ); accept = true; } } if ( !accept ) KLineEdit::keyPressEvent( e ); if ( e->isAccepted() ) { updateSearchString(); QString searchString( m_searchString ); //LDAP does not know about our string manipulation, remove it if ( m_searchExtended ) searchString = m_searchString.mid( 1 ); if ( m_useCompletion && s_LDAPTimer != NULL ) { if ( *s_LDAPText != searchString || s_LDAPLineEdit != this ) stopLDAPLookup(); *s_LDAPText = searchString; s_LDAPLineEdit = this; s_LDAPTimer->start( 500, true ); } } } void AddresseeLineEdit::insert( const QString &t ) { if ( !m_smartPaste ) { KLineEdit::insert( t ); return; } //kdDebug(5300) << " AddresseeLineEdit::insert( \"" << t << "\" )" << endl; QString newText = t.stripWhiteSpace(); if ( newText.isEmpty() ) return; // remove newlines in the to-be-pasted string QStringList lines = QStringList::split( QRegExp("\r?\n"), newText, false ); for ( QStringList::iterator it = lines.begin(); it != lines.end(); ++it ) { // remove trailing commas and whitespace (*it).remove( QRegExp(",?\\s*$") ); } newText = lines.join( ", " ); if ( newText.startsWith("mailto:") ) { KURL url( newText ); newText = url.path(); } else if ( newText.find(" at ") != -1 ) { // Anti-spam stuff newText.replace( " at ", "@" ); newText.replace( " dot ", "." ); } else if ( newText.find("(at)") != -1 ) { newText.replace( QRegExp("\\s*\\(at\\)\\s*"), "@" ); } QString contents = text(); int start_sel = 0; int end_sel = 0; int pos = cursorPosition( ); if ( getSelection( &start_sel, &end_sel ) ) { // Cut away the selection. if ( pos > end_sel ) pos -= (end_sel - start_sel); else if ( pos > start_sel ) pos = start_sel; contents = contents.left( start_sel ) + contents.right( end_sel + 1 ); } int eot = contents.length(); while ((eot > 0) && contents[ eot - 1 ].isSpace() ) eot--; if ( eot == 0 ) contents = QString::null; else if ( pos >= eot ) { if ( contents[ eot - 1 ] == ',' ) eot--; contents.truncate( eot ); contents += ", "; pos = eot + 2; } contents = contents.left( pos ) + newText + contents.mid( pos ); setText( contents ); setEdited( true ); setCursorPosition( pos + newText.length() ); } void AddresseeLineEdit::setText( const QString & text ) { ClickLineEdit::setText( text.stripWhiteSpace() ); } void AddresseeLineEdit::paste() { if ( m_useCompletion ) m_smartPaste = true; KLineEdit::paste(); m_smartPaste = false; } void AddresseeLineEdit::mouseReleaseEvent( QMouseEvent *e ) { // reimplemented from QLineEdit::mouseReleaseEvent() if ( m_useCompletion && QApplication::clipboard()->supportsSelection() && !isReadOnly() && e->button() == MidButton ) { m_smartPaste = true; } KLineEdit::mouseReleaseEvent( e ); m_smartPaste = false; } void AddresseeLineEdit::dropEvent( QDropEvent *e ) { KURL::List uriList; if ( !isReadOnly() && KURLDrag::canDecode(e) && KURLDrag::decode( e, uriList ) ) { QString contents = text(); // remove trailing white space and comma int eot = contents.length(); while ( ( eot > 0 ) && contents[ eot - 1 ].isSpace() ) eot--; if ( eot == 0 ) contents = QString::null; else if ( contents[ eot - 1 ] == ',' ) { eot--; contents.truncate( eot ); } bool mailtoURL = false; // append the mailto URLs for ( KURL::List::Iterator it = uriList.begin(); it != uriList.end(); ++it ) { if ( !contents.isEmpty() ) contents.append( ", " ); KURL u( *it ); if ( u.protocol() == "mailto" ) { mailtoURL = true; contents.append( (*it).path() ); } } if ( mailtoURL ) { setText( contents ); setEdited( true ); return; } } if ( m_useCompletion ) m_smartPaste = true; QLineEdit::dropEvent( e ); m_smartPaste = false; } void AddresseeLineEdit::cursorAtEnd() { setCursorPosition( text().length() ); } void AddresseeLineEdit::enableCompletion( bool enable ) { m_useCompletion = enable; } void AddresseeLineEdit::doCompletion( bool ctrlT ) { m_lastSearchMode = ctrlT; KGlobalSettings::Completion mode = completionMode(); if ( mode == KGlobalSettings::CompletionNone ) return; if ( s_addressesDirty ) { loadContacts(); // read from local address book s_completion->setOrder( completionOrder() ); } // cursor at end of string - or Ctrl+T pressed for substring completion? if ( ctrlT ) { const QStringList completions = getAdjustedCompletionItems( false ); if ( completions.count() > 1 ) ; //m_previousAddresses = prevAddr; else if ( completions.count() == 1 ) setText( m_previousAddresses + completions.first().stripWhiteSpace() ); setCompletedItems( completions, true ); // this makes sure the completion popup is closed if no matching items were found cursorAtEnd(); setCompletionMode( mode ); //set back to previous mode return; } switch ( mode ) { case KGlobalSettings::CompletionPopupAuto: { if ( m_searchString.isEmpty() ) break; } case KGlobalSettings::CompletionPopup: { const QStringList items = getAdjustedCompletionItems( true ); setCompletedItems( items, false ); break; } case KGlobalSettings::CompletionShell: { QString match = s_completion->makeCompletion( m_searchString ); if ( !match.isNull() && match != m_searchString ) { setText( m_previousAddresses + match ); setEdited( true ); cursorAtEnd(); } break; } case KGlobalSettings::CompletionMan: // Short-Auto in fact case KGlobalSettings::CompletionAuto: { //force autoSuggest in KLineEdit::keyPressed or setCompletedText will have no effect setCompletionMode( completionMode() ); if ( !m_searchString.isEmpty() ) { //if only our \" is left, remove it since user has not typed it either if ( m_searchExtended && m_searchString == "\"" ){ m_searchExtended = false; m_searchString = QString::null; setText( m_previousAddresses ); break; } QString match = s_completion->makeCompletion( m_searchString ); if ( !match.isEmpty() ) { if ( match != m_searchString ) { QString adds = m_previousAddresses + match; setCompletedText( adds ); } } else { if ( !m_searchString.startsWith( "\"" ) ) { //try with quoted text, if user has not type one already match = s_completion->makeCompletion( "\"" + m_searchString ); if ( !match.isEmpty() && match != m_searchString ) { m_searchString = "\"" + m_searchString; m_searchExtended = true; setText( m_previousAddresses + m_searchString ); setCompletedText( m_previousAddresses + match ); } } else if ( m_searchExtended ) { //our added \" does not work anymore, remove it m_searchString = m_searchString.mid( 1 ); m_searchExtended = false; setText( m_previousAddresses + m_searchString ); //now try again match = s_completion->makeCompletion( m_searchString ); if ( !match.isEmpty() && match != m_searchString ) { QString adds = m_previousAddresses + match; setCompletedText( adds ); } } } } break; } case KGlobalSettings::CompletionNone: default: // fall through break; } } void AddresseeLineEdit::slotPopupCompletion( const QString& completion ) { setText( m_previousAddresses + completion.stripWhiteSpace() ); cursorAtEnd(); // slotMatched( m_previousAddresses + completion ); updateSearchString(); } void AddresseeLineEdit::slotReturnPressed( const QString& item ) { Q_UNUSED( item ); QListBoxItem* i = completionBox()->selectedItem(); if ( i != 0 ) slotPopupCompletion( i->text() ); } void AddresseeLineEdit::loadContacts() { s_completion->clear(); s_completionItemMap->clear(); s_addressesDirty = false; //m_contactMap.clear(); QApplication::setOverrideCursor( KCursor::waitCursor() ); // loading might take a while KConfig config( "kpimcompletionorder" ); // The weights for non-imap kabc resources is there. config.setGroup( "CompletionWeights" ); KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true ); // Can't just use the addressbook's iterator, we need to know which subresource // is behind which contact. QPtrList resources( addressBook->resources() ); for( QPtrListIterator resit( resources ); *resit; ++resit ) { KABC::Resource* resource = *resit; KPIM::ResourceABC* resabc = dynamic_cast( resource ); if ( resabc ) { // IMAP KABC resource; need to associate each contact with the subresource const QMap uidToResourceMap = resabc->uidToResourceMap(); KABC::Resource::Iterator it; for ( it = resource->begin(); it != resource->end(); ++it ) { QString uid = (*it).uid(); QMap::const_iterator wit = uidToResourceMap.find( uid ); const QString subresourceLabel = resabc->subresourceLabel( *wit ); - int idx = s_completionSources->findIndex( subresourceLabel ); - if ( idx == -1 ) { - s_completionSources->append( subresourceLabel ); - idx = s_completionSources->size() -1; - } - int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80; + const int weight = ( wit != uidToResourceMap.end() ) ? resabc->subresourceCompletionWeight( *wit ) : 80; + const int idx = addCompletionSource( subresourceLabel, weight ); + //kdDebug(5300) << (*it).fullEmail() << " subres=" << *wit << " weight=" << weight << endl; addContact( *it, weight, idx ); } } else { // KABC non-imap resource int weight = config.readNumEntry( resource->identifier(), 60 ); - s_completionSources->append( resource->resourceName() ); + int sourceIndex = addCompletionSource( resource->resourceName(), weight ); KABC::Resource::Iterator it; - for ( it = resource->begin(); it != resource->end(); ++it ) - addContact( *it, weight, s_completionSources->size()-1 ); + for ( it = resource->begin(); it != resource->end(); ++it ) { + addContact( *it, weight, sourceIndex ); + } } } #ifndef KDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, already done above int weight = config.readNumEntry( "DistributionLists", 60 ); KABC::DistributionListManager manager( addressBook ); manager.load(); const QStringList distLists = manager.listNames(); QStringList::const_iterator listIt; int idx = addCompletionSource( i18n( "Distribution Lists" ) ); for ( listIt = distLists.begin(); listIt != distLists.end(); ++listIt ) { //for KGlobalSettings::CompletionAuto addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx ); //for CompletionShell, CompletionPopup QStringList sl( (*listIt).simplifyWhiteSpace() ); addCompletionItem( (*listIt).simplifyWhiteSpace(), weight, idx, &sl ); } #endif QApplication::restoreOverrideCursor(); if ( !m_addressBookConnected ) { connect( addressBook, SIGNAL( addressBookChanged( AddressBook* ) ), SLOT( loadContacts() ) ); m_addressBookConnected = true; } } void AddresseeLineEdit::addContact( const KABC::Addressee& addr, int weight, int source ) { #ifdef KDEPIM_NEW_DISTRLISTS if ( KPIM::DistributionList::isDistributionList( addr ) ) { //kdDebug(5300) << "AddresseeLineEdit::addContact() distribution list \"" << addr.formattedName() << "\" weight=" << weight << endl; //for CompletionAuto addCompletionItem( addr.formattedName(), weight, source ); //for CompletionShell, CompletionPopup QStringList sl( addr.formattedName() ); addCompletionItem( addr.formattedName(), weight, source, &sl ); return; } #endif //m_contactMap.insert( addr.realName(), addr ); const QStringList emails = addr.emails(); QStringList::ConstIterator it; const int prefEmailWeight = 1; //increment weight by prefEmailWeight int isPrefEmail = prefEmailWeight; //first in list is preferredEmail for ( it = emails.begin(); it != emails.end(); ++it ) { //TODO: highlight preferredEmail const QString email( (*it) ); const QString givenName = addr.givenName(); const QString familyName= addr.familyName(); const QString nickName = addr.nickName(); const QString domain = email.mid( email.find( '@' ) + 1 ); QString fullEmail = addr.fullEmail( email ); //TODO: let user decide what fields to use in lookup, e.g. company, city, ... //for CompletionAuto if ( givenName.isEmpty() && familyName.isEmpty() ) { addCompletionItem( fullEmail, weight + isPrefEmail, source ); // use whatever is there } else { const QString byFirstName= "\"" + givenName + " " + familyName + "\" <" + email + ">"; const QString byLastName = "\"" + familyName + ", " + givenName + "\" <" + email + ">"; addCompletionItem( byFirstName, weight + isPrefEmail, source ); addCompletionItem( byLastName, weight + isPrefEmail, source ); } addCompletionItem( email, weight + isPrefEmail, source ); if ( !nickName.isEmpty() ){ const QString byNick = "\"" + nickName + "\" <" + email + ">"; addCompletionItem( byNick, weight + isPrefEmail, source ); } if ( !domain.isEmpty() ){ const QString byDomain = "\"" + domain + " " + familyName + " " + givenName + "\" <" + email + ">"; addCompletionItem( byDomain, weight + isPrefEmail, source ); } //for CompletionShell, CompletionPopup QStringList keyWords; const QString realName = addr.realName(); if ( !givenName.isEmpty() && !familyName.isEmpty() ) { keyWords.append( givenName + " " + familyName ); keyWords.append( familyName + " " + givenName ); keyWords.append( familyName + ", " + givenName); }else if ( !givenName.isEmpty() ) keyWords.append( givenName ); else if ( !familyName.isEmpty() ) keyWords.append( familyName ); if ( !nickName.isEmpty() ) keyWords.append( nickName ); if ( !realName.isEmpty() ) keyWords.append( realName ); if ( !domain.isEmpty() ) keyWords.append( domain ); keyWords.append( email ); /* KMailCompletion does not have knowledge about identities, it stores emails and * keywords for each email. KMailCompletion::allMatches does a lookup on the * keywords and returns an ordered list of emails. In order to get the preferred * email before others for each identity we use this little trick. * We remove the in getAdjustedCompletionItems. */ if ( isPrefEmail == prefEmailWeight ) fullEmail.replace( " <", " <" ); addCompletionItem( fullEmail, weight + isPrefEmail, source, &keyWords ); isPrefEmail = 0; #if 0 int len = (*it).length(); if ( len == 0 ) continue; if( '\0' == (*it)[len-1] ) --len; const QString tmp = (*it).left( len ); const QString fullEmail = addr.fullEmail( tmp ); //kdDebug(5300) << "AddresseeLineEdit::addContact() \"" << fullEmail << "\" weight=" << weight << endl; addCompletionItem( fullEmail.simplifyWhiteSpace(), weight, source ); // Try to guess the last name: if found, we add an extra // entry to the list to make sure completion works even // if the user starts by typing in the last name. QString name( addr.realName().simplifyWhiteSpace() ); if( name.endsWith("\"") ) name.truncate( name.length()-1 ); if( name.startsWith("\"") ) name = name.mid( 1 ); // While we're here also add "email (full name)" for completion on the email if ( !name.isEmpty() ) addCompletionItem( addr.preferredEmail() + " (" + name + ")", weight, source ); bool bDone = false; int i = -1; while( ( i = name.findRev(' ') ) > 1 && !bDone ) { QString sLastName( name.mid( i+1 ) ); if( ! sLastName.isEmpty() && 2 <= sLastName.length() && // last names must be at least 2 chars long ! sLastName.endsWith(".") ) { // last names must not end with a dot (like "Jr." or "Sr.") name.truncate( i ); if( !name.isEmpty() ){ sLastName.prepend( "\"" ); sLastName.append( ", " + name + "\" <" ); } QString sExtraEntry( sLastName ); sExtraEntry.append( tmp.isEmpty() ? addr.preferredEmail() : tmp ); sExtraEntry.append( ">" ); //kdDebug(5300) << "AddresseeLineEdit::addContact() added extra \"" << sExtraEntry.simplifyWhiteSpace() << "\" weight=" << weight << endl; addCompletionItem( sExtraEntry.simplifyWhiteSpace(), weight, source ); bDone = true; } if( !bDone ) { name.truncate( i ); if( name.endsWith("\"") ) name.truncate( name.length()-1 ); } } #endif } } void AddresseeLineEdit::addCompletionItem( const QString& string, int weight, int completionItemSource, const QStringList * keyWords ) { // Check if there is an exact match for item already, and use the max weight if so. // Since there's no way to get the information from KCompletion, we have to keep our own QMap CompletionItemsMap::iterator it = s_completionItemMap->find( string ); if ( it != s_completionItemMap->end() ) { weight = QMAX( ( *it ).first, weight ); ( *it ).first = weight; } else { s_completionItemMap->insert( string, qMakePair( weight, completionItemSource ) ); } if ( keyWords == 0 ) s_completion->addItem( string, weight ); else s_completion->addItemWithKeys( string, weight, keyWords ); } void AddresseeLineEdit::slotStartLDAPLookup() { KGlobalSettings::Completion mode = completionMode(); if ( mode == KGlobalSettings::CompletionNone ) return; if ( !s_LDAPSearch->isAvailable() ) { return; } if ( s_LDAPLineEdit != this ) return; startLoadingLDAPEntries(); } void AddresseeLineEdit::stopLDAPLookup() { s_LDAPSearch->cancelSearch(); s_LDAPLineEdit = NULL; } void AddresseeLineEdit::startLoadingLDAPEntries() { QString s( *s_LDAPText ); // TODO cache last? QString prevAddr; int n = s.findRev( ',' ); if ( n >= 0 ) { prevAddr = s.left( n + 1 ) + ' '; s = s.mid( n + 1, 255 ).stripWhiteSpace(); } if ( s.isEmpty() ) return; //loadContacts(); // TODO reuse these? s_LDAPSearch->startSearch( s ); } void AddresseeLineEdit::slotLDAPSearchData( const KPIM::LdapResultList& adrs ) { if ( s_LDAPLineEdit != this ) return; for ( KPIM::LdapResultList::ConstIterator it = adrs.begin(); it != adrs.end(); ++it ) { KABC::Addressee addr; addr.setNameFromString( (*it).name ); addr.setEmails( (*it).email ); addContact( addr, (*it).completionWeight, (*it ).clientNumber ); } } void AddresseeLineEdit::setCompletedItems( const QStringList& items, bool autoSuggest ) { KCompletionBox* completionBox = this->completionBox(); if ( !items.isEmpty() && !(items.count() == 1 && m_searchString == items.first()) ) { QString oldCurrentText = completionBox->currentText(); QListBoxItem *itemUnderMouse = completionBox->itemAt( completionBox->viewport()->mapFromGlobal(QCursor::pos()) ); QString oldTextUnderMouse; QPoint oldPosOfItemUnderMouse; if ( itemUnderMouse ) { oldTextUnderMouse = itemUnderMouse->text(); oldPosOfItemUnderMouse = completionBox->itemRect( itemUnderMouse ).topLeft(); } completionBox->setItems( items ); if ( !completionBox->isVisible() ) { if ( !m_searchString.isEmpty() ) completionBox->setCancelledText( m_searchString ); completionBox->popup(); // we have to install the event filter after popup(), since that // calls show(), and that's where KCompletionBox installs its filter. // We want to be first, though, so do it now. if ( s_completion->order() == KCompletion::Weighted ) qApp->installEventFilter( this ); } // Try to re-select what was selected before, otherrwise use the first // item, if there is one QListBoxItem* item = 0; if ( oldCurrentText.isEmpty() || ( item = completionBox->findItem( oldCurrentText ) ) == 0 ) { item = completionBox->item( 1 ); } if ( item ) { if ( itemUnderMouse ) { QListBoxItem *newItemUnderMouse = completionBox->findItem( oldTextUnderMouse ); // if the mouse was over an item, before, but now that's elsewhere, // move the cursor, so folks don't accidently click the wrong item if ( newItemUnderMouse ) { QRect r = completionBox->itemRect( newItemUnderMouse ); QPoint target = r.topLeft(); if ( oldPosOfItemUnderMouse != target ) { target.setX( target.x() + r.width()/2 ); QCursor::setPos( completionBox->viewport()->mapToGlobal(target) ); } } } completionBox->blockSignals( true ); completionBox->setSelected( item, true ); completionBox->setCurrentItem( item ); completionBox->ensureCurrentVisible(); completionBox->blockSignals( false ); } if ( autoSuggest ) { int index = items.first().find( m_searchString ); QString newText = items.first().mid( index ); setUserSelection(false); setCompletedText(newText,true); } } else { if ( completionBox && completionBox->isVisible() ) { completionBox->hide(); completionBox->setItems( QStringList() ); } } } QPopupMenu* AddresseeLineEdit::createPopupMenu() { QPopupMenu *menu = KLineEdit::createPopupMenu(); if ( !menu ) return 0; if ( m_useCompletion ){ menu->setItemVisible( ShortAutoCompletion, false ); menu->setItemVisible( PopupAutoCompletion, false ); menu->insertItem( i18n( "Configure Completion Order..." ), this, SLOT( slotEditCompletionOrder() ) ); } return menu; } void AddresseeLineEdit::slotEditCompletionOrder() { init(); // for s_LDAPSearch CompletionOrderEditor editor( s_LDAPSearch, this ); editor.exec(); + if ( m_useCompletion ) { + updateLDAPWeights(); + s_addressesDirty = true; + } } void KPIM::AddresseeLineEdit::slotIMAPCompletionOrderChanged() { if ( m_useCompletion ) s_addressesDirty = true; } void KPIM::AddresseeLineEdit::slotUserCancelled( const QString& cancelText ) { if ( s_LDAPSearch && s_LDAPLineEdit == this ) stopLDAPLookup(); userCancelled( m_previousAddresses + cancelText ); // in KLineEdit } void AddresseeLineEdit::updateSearchString() { m_searchString = text(); int n = -1; bool inQuote = false; for ( uint i = 0; i < m_searchString.length(); ++i ) { if ( m_searchString[ i ] == '"' ) inQuote = !inQuote; if ( m_searchString[ i ] == '\\' && (i + 1) < m_searchString.length() && m_searchString[ i + 1 ] == '"' ) ++i; if ( inQuote ) continue; if ( m_searchString[ i ] == ',' || ( m_useSemiColonAsSeparator && m_searchString[ i ] == ';' ) ) n = i; } if ( n >= 0 ) { ++n; // Go past the "," int len = m_searchString.length(); // Increment past any whitespace... while ( n < len && m_searchString[ n ].isSpace() ) ++n; m_previousAddresses = m_searchString.left( n ); m_searchString = m_searchString.mid( n ).stripWhiteSpace(); } else { m_previousAddresses = QString::null; } } void KPIM::AddresseeLineEdit::slotCompletion() { // Called by KLineEdit's keyPressEvent for CompletionModes Auto,Popup -> new text, update search string // not called for CompletionShell, this is been taken care of in AddresseeLineEdit::keyPressEvent updateSearchString(); if ( completionBox() ) completionBox()->setCancelledText( m_searchString ); doCompletion( false ); } // not cached, to make sure we get an up-to-date value when it changes KCompletion::CompOrder KPIM::AddresseeLineEdit::completionOrder() { KConfig config( "kpimcompletionorder" ); config.setGroup( "General" ); const QString order = config.readEntry( "CompletionOrder", "Weighted" ); if ( order == "Weighted" ) return KCompletion::Weighted; else return KCompletion::Sorted; } -int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source ) +int KPIM::AddresseeLineEdit::addCompletionSource( const QString &source, int weight ) { - s_completionSources->append( source ); - return s_completionSources->size()-1; + QMap::iterator it = s_completionSourceWeights->find( source ); + if ( it == s_completionSourceWeights->end() ) + s_completionSourceWeights->insert( source, weight ); + else + (*s_completionSourceWeights)[source] = weight; + + int sourceIndex = s_completionSources->findIndex( source ); + if ( sourceIndex == -1 ) { + s_completionSources->append( source ); + return s_completionSources->size() - 1; + } + else + return sourceIndex; } bool KPIM::AddresseeLineEdit::eventFilter(QObject *obj, QEvent *e) { if ( obj == completionBox() ) { if ( e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseMove || e->type() == QEvent::MouseButtonRelease ) { QMouseEvent* me = static_cast( e ); // find list box item at the event position QListBoxItem *item = completionBox()->itemAt( me->pos() ); if ( !item ) { // In the case of a mouse move outside of the box we don't want // the parent to fuzzy select a header by mistake. bool eat = e->type() == QEvent::MouseMove; return eat; } // avoid selection of headers on button press, or move or release while // a button is pressed if ( e->type() == QEvent::MouseButtonPress || me->state() & LeftButton || me->state() & MidButton || me->state() & RightButton ) { if ( itemIsHeader(item) ) { return true; // eat the event, we don't want anything to happen } else { // if we are not on one of the group heading, make sure the item // below or above is selected, not the heading, inadvertedly, due // to fuzzy auto-selection from QListBox completionBox()->setCurrentItem( item ); completionBox()->setSelected( completionBox()->index( item ), true ); if ( e->type() == QEvent::MouseMove ) return true; // avoid fuzzy selection behavior } } } } if ( ( obj == this ) && ( e->type() == QEvent::AccelOverride ) ) { QKeyEvent *ke = static_cast( e ); if ( ke->key() == Key_Up || ke->key() == Key_Down || ke->key() == Key_Tab ) { ke->accept(); return true; } } if ( ( obj == this ) && ( e->type() == QEvent::KeyPress ) && completionBox()->isVisible() ) { QKeyEvent *ke = static_cast( e ); unsigned int currentIndex = completionBox()->currentItem(); if ( ke->key() == Key_Up ) { //kdDebug() << "EVENTFILTER: Key_Up currentIndex=" << currentIndex << endl; // figure out if the item we would be moving to is one we want // to ignore. If so, go one further QListBoxItem *itemAbove = completionBox()->item( currentIndex - 1 ); if ( itemAbove && itemIsHeader(itemAbove) ) { // there is a header above us, check if there is even further up // and if so go one up, so it'll be selected if ( currentIndex > 1 && completionBox()->item( currentIndex - 2 ) ) { //kdDebug() << "EVENTFILTER: Key_Up -> skipping " << currentIndex - 1 << endl; completionBox()->setCurrentItem( itemAbove->prev() ); completionBox()->setSelected( currentIndex - 2, true ); } else if ( currentIndex == 1 ) { // nothing to skip to, let's stay where we are, but make sure the // first header becomes visible, if we are the first real entry completionBox()->ensureVisible( 0, 0 ); //Kolab issue 2941: be sure to add email even if it's the only element. completionBox()->setCurrentItem( itemAbove ); completionBox()->setSelected( currentIndex, true ); } return true; } } else if ( ke->key() == Key_Down ) { // same strategy for downwards //kdDebug() << "EVENTFILTER: Key_Down. currentIndex=" << currentIndex << endl; QListBoxItem *itemBelow = completionBox()->item( currentIndex + 1 ); if ( itemBelow && itemIsHeader( itemBelow ) ) { if ( completionBox()->item( currentIndex + 2 ) ) { //kdDebug() << "EVENTFILTER: Key_Down -> skipping " << currentIndex+1 << endl; completionBox()->setCurrentItem( itemBelow->next() ); completionBox()->setSelected( currentIndex + 2, true ); } else { // nothing to skip to, let's stay where we are completionBox()->setSelected( currentIndex, true ); } return true; } // special case of the last and only item in the list needing selection if ( !itemBelow && currentIndex == 1 ) { completionBox()->setSelected( currentIndex, true ); } // special case of the initial selection, which is unfortunately a header. // Setting it to selected tricks KCompletionBox into not treating is special // and selecting making it current, instead of the one below. QListBoxItem *item = completionBox()->item( currentIndex ); if ( item && itemIsHeader(item) ) { completionBox()->setSelected( currentIndex, true ); } } else if ( ke->key() == Key_Tab || ke->key() == Key_Backtab ) { /// first, find the header of teh current section QListBoxItem *myHeader = 0; int i = currentIndex; while ( i>=0 ) { if ( itemIsHeader( completionBox()->item(i) ) ) { myHeader = completionBox()->item( i ); break; } i--; } Q_ASSERT( myHeader ); // we should always be able to find a header // find the next header (searching backwards, for Key_Backtab QListBoxItem *nextHeader = 0; const int iterationstep = ke->key() == Key_Tab ? 1 : -1; // when iterating forward, start at the currentindex, when backwards, // one up from our header, or at the end uint j = ke->key() == Key_Tab ? currentIndex : i==0 ? completionBox()->count()-1 : (i-1) % completionBox()->count(); while ( ( nextHeader = completionBox()->item( j ) ) && nextHeader != myHeader ) { if ( itemIsHeader(nextHeader) ) { break; } j = (j + iterationstep) % completionBox()->count(); } if ( nextHeader && nextHeader != myHeader ) { QListBoxItem *item = completionBox()->item( j + 1 ); if ( item && !itemIsHeader(item) ) { completionBox()->setSelected( j+1, true ); completionBox()->setCurrentItem( item ); completionBox()->ensureCurrentVisible(); } } return true; } } return ClickLineEdit::eventFilter( obj, e ); } +class SourceWithWeight { + public: + int weight; // the weight of the source + QString sourceName; // the name of the source, e.g. "LDAP Server" + int index; // index into s_completionSources + + bool operator< ( const SourceWithWeight &other ) { + if ( weight > other.weight ) + return true; + if ( weight < other.weight ) + return false; + return sourceName < other.sourceName; + } +}; + const QStringList KPIM::AddresseeLineEdit::getAdjustedCompletionItems( bool fullSearch ) { QStringList items = fullSearch ? s_completion->allMatches( m_searchString ) : s_completion->substringCompletion( m_searchString ); + // For weighted mode, the algorithm is the following: + // In the first loop, we add each item to its section (there is one section per completion source) + // We also add spaces in front of the items. + // The sections are appended to the items list. + // In the second loop, we then walk through the sections and add all the items in there to the + // sorted item list, which is the final result. + // + // The algo for non-weighted mode is different. + int lastSourceIndex = -1; unsigned int i = 0; + + // Maps indices of the items list, which are section headers/source items, + // to a QStringList which are the items of that section/source. QMap sections; QStringList sortedItems; for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i ) { CompletionItemsMap::const_iterator cit = s_completionItemMap->find(*it); - if ( cit == s_completionItemMap->end() )continue; + if ( cit == s_completionItemMap->end() ) + continue; int idx = (*cit).second; + if ( s_completion->order() == KCompletion::Weighted ) { if ( lastSourceIndex == -1 || lastSourceIndex != idx ) { const QString sourceLabel( (*s_completionSources)[idx] ); if ( sections.find(idx) == sections.end() ) { items.insert( it, sourceLabel ); } lastSourceIndex = idx; } (*it) = (*it).prepend( s_completionItemIndentString ); // remove preferred email sort added in addContact() (*it).replace( " <", " <" ); } sections[idx].append( *it ); if ( s_completion->order() == KCompletion::Sorted ) { sortedItems.append( *it ); } } + if ( s_completion->order() == KCompletion::Weighted ) { - for ( QMap::Iterator it( sections.begin() ), end( sections.end() ); it != end; ++it ) { - sortedItems.append( (*s_completionSources)[it.key()] ); - for ( QStringList::Iterator sit( (*it).begin() ), send( (*it).end() ); sit != send; ++sit ) { - sortedItems.append( *sit ); + + // Sort the sections + QValueList sourcesAndWeights; + for ( uint i = 0; i < s_completionSources->size(); i++ ) { + SourceWithWeight sww; + sww.sourceName = (*s_completionSources)[i]; + sww.weight = (*s_completionSourceWeights)[sww.sourceName]; + sww.index = i; + sourcesAndWeights.append( sww ); + } + qHeapSort( sourcesAndWeights ); + + // Add the sections and their items to the final sortedItems result list + for( uint i = 0; i < sourcesAndWeights.size(); i++ ) { + QStringList sectionItems = sections[sourcesAndWeights[i].index]; + if ( !sectionItems.isEmpty() ) { + sortedItems.append( sourcesAndWeights[i].sourceName ); + QStringList sectionItems = sections[sourcesAndWeights[i].index]; + for ( QStringList::Iterator sit( sectionItems.begin() ), send( sectionItems.end() ); + sit != send; ++sit ) { + sortedItems.append( *sit ); + } } } } else { sortedItems.sort(); } return sortedItems; } #include "addresseelineedit.moc" diff --git a/libkdepim/addresseelineedit.h b/libkdepim/addresseelineedit.h index dacfd96a31..0066ce3482 100644 --- a/libkdepim/addresseelineedit.h +++ b/libkdepim/addresseelineedit.h @@ -1,171 +1,174 @@ /* This file is part of libkdepim. Copyright (c) 2002 Helge Deller 2002 Lubos Lunak 2001,2003 Carsten Pfeiffer 2001 Waldo Bastian 2004 Daniel Molkentin 2004 Karl-Heinz Zimmer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ADDRESSEELINEEDIT_H #define ADDRESSEELINEEDIT_H #include #include #include #include #include #include #include "clicklineedit.h" #include "kmailcompletion.h" #include #include class KConfig; namespace KPIM { class LdapSearch; class LdapResult; typedef QValueList LdapResultList; typedef QMap< QString, QPair > CompletionItemsMap; } namespace KPIM { class KDE_EXPORT AddresseeLineEdit : public ClickLineEdit, public DCOPObject { K_DCOP Q_OBJECT public: AddresseeLineEdit( QWidget* parent, bool useCompletion = true, const char *name = 0L); virtual ~AddresseeLineEdit(); virtual void setFont( const QFont& ); void allowSemiColonAsSeparator( bool ); public slots: void cursorAtEnd(); void enableCompletion( bool enable ); /** Reimplemented for stripping whitespace after completion */ virtual void setText( const QString& txt ); protected slots: virtual void loadContacts(); protected: void addContact( const KABC::Addressee&, int weight, int source = -1 ); virtual void keyPressEvent( QKeyEvent* ); /** * Reimplemented for smart insertion of email addresses. * Features: * - Automatically adds ',' if necessary to separate email addresses * - Correctly decodes mailto URLs * - Recognizes email addresses which are protected against address * harvesters, i.e. "name at kde dot org" and "name(at)kde.org" */ virtual void insert( const QString &text ); /** Reimplemented for smart insertion of pasted email addresses. */ virtual void paste(); /** Reimplemented for smart insertion with middle mouse button. */ virtual void mouseReleaseEvent( QMouseEvent *e ); /** Reimplemented for smart insertion of dragged email addresses. */ virtual void dropEvent( QDropEvent *e ); void doCompletion( bool ctrlT ); virtual QPopupMenu *createPopupMenu(); /** * Adds the name of a completion source to the internal list of * such sources and returns its index, such that that can be used * for insertion of items associated with that source. + * + * If the source already exists, the weight will be updated. */ - int addCompletionSource( const QString& ); + int addCompletionSource( const QString&, int weight ); /** return whether we are using sorted or weighted display */ static KCompletion::CompOrder completionOrder(); k_dcop: // Connected to the DCOP signal void slotIMAPCompletionOrderChanged(); private slots: void slotCompletion(); void slotPopupCompletion( const QString& ); void slotReturnPressed( const QString& ); void slotStartLDAPLookup(); void slotLDAPSearchData( const KPIM::LdapResultList& ); void slotEditCompletionOrder(); void slotUserCancelled( const QString& ); private: virtual bool eventFilter(QObject *o, QEvent *e); void init(); void startLoadingLDAPEntries(); void stopLDAPLookup(); + void updateLDAPWeights(); void setCompletedItems( const QStringList& items, bool autoSuggest ); void addCompletionItem( const QString& string, int weight, int source, const QStringList * keyWords=0 ); QString completionSearchText( QString& ); const QStringList getAdjustedCompletionItems( bool fullSearch ); void updateSearchString(); QString m_previousAddresses; QString m_searchString; bool m_useCompletion; bool m_completionInitialized; bool m_smartPaste; bool m_addressBookConnected; bool m_lastSearchMode; bool m_searchExtended; //has \" been added? bool m_useSemiColonAsSeparator; //QMap m_contactMap; static bool s_addressesDirty; static KMailCompletion *s_completion; static CompletionItemsMap* s_completionItemMap; static QTimer *s_LDAPTimer; static KPIM::LdapSearch *s_LDAPSearch; static QString *s_LDAPText; static AddresseeLineEdit *s_LDAPLineEdit; static QStringList *s_completionSources; class AddresseeLineEditPrivate; AddresseeLineEditPrivate *d; //until MenuID moves into protected in KLineEdit, we keep a copy here //Constants that represent the ID's of the popup menu. enum MenuID { Default = 42, NoCompletion, AutoCompletion, ShellCompletion, PopupCompletion, ShortAutoCompletion, PopupAutoCompletion }; }; } #endif diff --git a/libkdepim/completionordereditor.cpp b/libkdepim/completionordereditor.cpp index 4f797c2c15..d544bda3d7 100644 --- a/libkdepim/completionordereditor.cpp +++ b/libkdepim/completionordereditor.cpp @@ -1,304 +1,305 @@ /** -*- c++ -*- * completionordereditor.cpp * * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ +#include // FOR KDEPIM_NEW_DISTRLISTS #include "completionordereditor.h" #include "ldapclient.h" #include "resourceabc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* Several items are used in addresseelineedit's completion object: LDAP servers, KABC resources (imap and non-imap), Recent addresses (in kmail only). The default completion weights are as follow: LDAP: 50, 49, 48 etc. (see ldapclient.cpp) KABC non-imap resources: 60 (see addresseelineedit.cpp and SimpleCompletionItem here) Distribution lists: 60 (see addresseelineedit.cpp and SimpleCompletionItem here) KABC imap resources: 80 (see kresources/imap/kabc/resourceimap.cpp) Recent addresses (kmail) : 120 (see kmail/kmcomposewin.cpp) This dialog allows to change those weights, by showing one item per: - LDAP server - KABC non-imap resource - KABC imap subresource plus one item for Distribution Lists. Maybe 'recent addresses' should be configurable too, but first it might be better to add support for them in korganizer too. */ using namespace KPIM; namespace KPIM { int CompletionItemList::compareItems( QPtrCollection::Item s1, QPtrCollection::Item s2 ) { int w1 = ( (CompletionItem*)s1 )->completionWeight(); int w2 = ( (CompletionItem*)s2 )->completionWeight(); // s1 < s2 if it has a higher completion value, i.e. w1 > w2. return w2 - w1; } class LDAPCompletionItem : public CompletionItem { public: LDAPCompletionItem( LdapClient* ldapClient ) : mLdapClient( ldapClient ) {} virtual QString label() const { return i18n( "LDAP server %1" ).arg( mLdapClient->server().host() ); } virtual int completionWeight() const { return mLdapClient->completionWeight(); } virtual void save( CompletionOrderEditor* ); protected: virtual void setCompletionWeight( int weight ) { mWeight = weight; } private: LdapClient* mLdapClient; int mWeight; }; void LDAPCompletionItem::save( CompletionOrderEditor* ) { - KConfig config( "kabldaprc" ); - config.setGroup( "LDAP" ); - config.writeEntry( QString( "SelectedCompletionWeight%1" ).arg( mLdapClient->clientNumber() ), - mWeight ); - config.sync(); + KConfig * config = LdapSearch::config(); + config->setGroup( "LDAP" ); + config->writeEntry( QString( "SelectedCompletionWeight%1" ).arg( mLdapClient->clientNumber() ), + mWeight ); + config->sync(); } // A simple item saved into kpimcompletionorder (no subresources, just name/identifier/weight) class SimpleCompletionItem : public CompletionItem { public: SimpleCompletionItem( CompletionOrderEditor* editor, const QString& label, const QString& identifier ) : mLabel( label ), mIdentifier( identifier ) { KConfigGroup group( editor->configFile(), "CompletionWeights" ); mWeight = group.readNumEntry( mIdentifier, 60 ); } virtual QString label() const { return mLabel; } virtual int completionWeight() const { return mWeight; } virtual void save( CompletionOrderEditor* ); protected: virtual void setCompletionWeight( int weight ) { mWeight = weight; } private: QString mLabel, mIdentifier; int mWeight; }; void SimpleCompletionItem::save( CompletionOrderEditor* editor ) { // Maybe KABC::Resource could have a completionWeight setting (for readConfig/writeConfig) // But for kdelibs-3.2 compat purposes I can't do that. KConfigGroup group( editor->configFile(), "CompletionWeights" ); group.writeEntry( mIdentifier, mWeight ); } // An imap subresource for kabc class KABCImapSubResCompletionItem : public CompletionItem { public: KABCImapSubResCompletionItem( ResourceABC* resource, const QString& subResource ) : mResource( resource ), mSubResource( subResource ), mWeight( completionWeight() ) {} virtual QString label() const { return QString( "%1 %2" ).arg( mResource->resourceName() ).arg( mResource->subresourceLabel( mSubResource ) ); } virtual int completionWeight() const { return mResource->subresourceCompletionWeight( mSubResource ); } virtual void setCompletionWeight( int weight ) { mWeight = weight; } virtual void save( CompletionOrderEditor* ) { mResource->setSubresourceCompletionWeight( mSubResource, mWeight ); } private: ResourceABC* mResource; QString mSubResource; int mWeight; }; ///////// class CompletionViewItem : public QListViewItem { public: CompletionViewItem( QListView* lv, CompletionItem* item ) : QListViewItem( lv, lv->lastItem(), item->label() ), mItem( item ) {} CompletionItem* item() const { return mItem; } void setItem( CompletionItem* i ) { mItem = i; setText( 0, mItem->label() ); } private: CompletionItem* mItem; }; CompletionOrderEditor::CompletionOrderEditor( KPIM::LdapSearch* ldapSearch, QWidget* parent, const char* name ) : KDialogBase( parent, name, true, i18n("Edit Completion Order"), Ok|Cancel, Ok, true ), mConfig( "kpimcompletionorder" ), mDirty( false ) { mItems.setAutoDelete( true ); // The first step is to gather all the data, creating CompletionItem objects QValueList< LdapClient* > ldapClients = ldapSearch->clients(); for( QValueList::const_iterator it = ldapClients.begin(); it != ldapClients.end(); ++it ) { //kdDebug(5300) << "LDAP: host " << (*it)->host() << " weight " << (*it)->completionWeight() << endl; mItems.append( new LDAPCompletionItem( *it ) ); } KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true ); QPtrList resources = addressBook->resources(); for( QPtrListIterator resit( resources ); *resit; ++resit ) { //kdDebug(5300) << "KABC Resource: " << (*resit)->className() << endl; ResourceABC* res = dynamic_cast( *resit ); if ( res ) { // IMAP KABC resource const QStringList subresources = res->subresources(); for( QStringList::const_iterator it = subresources.begin(); it != subresources.end(); ++it ) { mItems.append( new KABCImapSubResCompletionItem( res, *it ) ); } } else { // non-IMAP KABC resource mItems.append( new SimpleCompletionItem( this, (*resit)->resourceName(), (*resit)->identifier() ) ); } } #ifndef KDEPIM_NEW_DISTRLISTS // new distr lists are normal contact, so no separate item if using them // Add an item for distribution lists mItems.append( new SimpleCompletionItem( this, i18n( "Distribution Lists" ), "DistributionLists" ) ); #endif // Now sort the items, then create the GUI mItems.sort(); QHBox* page = makeHBoxMainWidget(); mListView = new KListView( page ); mListView->setSorting( -1 ); mListView->addColumn( QString::null ); mListView->header()->hide(); for( QPtrListIterator compit( mItems ); *compit; ++compit ) { new CompletionViewItem( mListView, *compit ); kdDebug(5300) << " " << (*compit)->label() << " " << (*compit)->completionWeight() << endl; } QVBox* upDownBox = new QVBox( page ); mUpButton = new KPushButton( upDownBox, "mUpButton" ); mUpButton->setIconSet( BarIconSet( "up", KIcon::SizeSmall ) ); mUpButton->setEnabled( false ); // b/c no item is selected yet mUpButton->setFocusPolicy( StrongFocus ); mDownButton = new KPushButton( upDownBox, "mDownButton" ); mDownButton->setIconSet( BarIconSet( "down", KIcon::SizeSmall ) ); mDownButton->setEnabled( false ); // b/c no item is selected yet mDownButton->setFocusPolicy( StrongFocus ); QWidget* spacer = new QWidget( upDownBox ); upDownBox->setStretchFactor( spacer, 100 ); connect( mListView, SIGNAL( selectionChanged( QListViewItem* ) ), SLOT( slotSelectionChanged( QListViewItem* ) ) ); connect( mUpButton, SIGNAL( clicked() ), this, SLOT( slotMoveUp() ) ); connect( mDownButton, SIGNAL( clicked() ), this, SLOT( slotMoveDown() ) ); } CompletionOrderEditor::~CompletionOrderEditor() { } void CompletionOrderEditor::slotSelectionChanged( QListViewItem *item ) { mDownButton->setEnabled( item && item->itemBelow() ); mUpButton->setEnabled( item && item->itemAbove() ); } static void swapItems( CompletionViewItem *one, CompletionViewItem *other ) { CompletionItem* i = one->item(); one->setItem( other->item() ); other->setItem( i ); } void CompletionOrderEditor::slotMoveUp() { CompletionViewItem *item = static_cast( mListView->selectedItem() ); if ( !item ) return; CompletionViewItem *above = static_cast( item->itemAbove() ); if ( !above ) return; swapItems( item, above ); mListView->setCurrentItem( above ); mListView->setSelected( above, true ); mDirty = true; } void CompletionOrderEditor::slotMoveDown() { CompletionViewItem *item = static_cast( mListView->selectedItem() ); if ( !item ) return; CompletionViewItem *below = static_cast( item->itemBelow() ); if ( !below ) return; swapItems( item, below ); mListView->setCurrentItem( below ); mListView->setSelected( below, true ); mDirty = true; } void CompletionOrderEditor::slotOk() { if ( mDirty ) { int w = 100; for ( QListViewItem* it = mListView->firstChild(); it; it = it->nextSibling() ) { CompletionViewItem *item = static_cast( it ); item->item()->setCompletionWeight( w ); item->item()->save( this ); kdDebug(5300) << "slotOk: " << item->item()->label() << " " << w << endl; --w; } // Emit DCOP signal // The emitter is always set to KPIM::IMAPCompletionOrder, so that the connect works // This is why we can't use k_dcop_signals here, but need to use emitDCOPSignal kapp->dcopClient()->emitDCOPSignal( "KPIM::IMAPCompletionOrder", "orderChanged()", QByteArray() ); } KDialogBase::slotOk(); } } // namespace KPIM #include "completionordereditor.moc" diff --git a/libkdepim/ldapclient.cpp b/libkdepim/ldapclient.cpp index 34e8044fb7..31fbf529dc 100644 --- a/libkdepim/ldapclient.cpp +++ b/libkdepim/ldapclient.cpp @@ -1,599 +1,613 @@ /* kldapclient.cpp - LDAP access - * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2002 Klar�lvdalens Datakonsult AB * * Author: Steffen Hansen * * Ported to KABC by Daniel Molkentin * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ldapclient.h" using namespace KPIM; KConfig *KPIM::LdapSearch::s_config = 0L; static KStaticDeleter configDeleter; QString LdapObject::toString() const { QString result = QString::fromLatin1( "\ndn: %1\n" ).arg( dn ); for ( LdapAttrMap::ConstIterator it = attrs.begin(); it != attrs.end(); ++it ) { QString attr = it.key(); for ( LdapAttrValue::ConstIterator it2 = (*it).begin(); it2 != (*it).end(); ++it2 ) { result += QString::fromUtf8( KABC::LDIF::assembleLine( attr, *it2, 76 ) ) + "\n"; } } return result; } void LdapObject::clear() { dn = QString::null; objectClass = QString::null; attrs.clear(); } void LdapObject::assign( const LdapObject& that ) { if ( &that != this ) { dn = that.dn; attrs = that.attrs; client = that.client; } } LdapClient::LdapClient( int clientNumber, QObject* parent, const char* name ) : QObject( parent, name ), mJob( 0 ), mActive( false ), mReportObjectClass( false ) { // d = new LdapClientPrivate; mClientNumber = clientNumber; mCompletionWeight = 50 - mClientNumber; } LdapClient::~LdapClient() { cancelQuery(); // delete d; d = 0; } void LdapClient::setAttrs( const QStringList& attrs ) { mAttrs = attrs; for ( QStringList::Iterator it = mAttrs.begin(); it != mAttrs.end(); ++it ) if( (*it).lower() == "objectclass" ){ mReportObjectClass = true; return; } mAttrs << "objectClass"; // via objectClass we detect distribution lists mReportObjectClass = false; } void LdapClient::startQuery( const QString& filter ) { cancelQuery(); KABC::LDAPUrl url; url.setProtocol( ( mServer.security() == LdapServer::SSL ) ? "ldaps" : "ldap" ); if ( mServer.auth() != LdapServer::Anonymous ) { url.setUser( mServer.user() ); url.setPass( mServer.pwdBindDN() ); } url.setHost( mServer.host() ); url.setPort( mServer.port() ); url.setExtension( "x-ver", QString::number( mServer.version() ) ); url.setDn( mServer.baseDN() ); url.setDn( mServer.baseDN() ); if ( mServer.security() == LdapServer::TLS ) url.setExtension( "x-tls","" ); if ( mServer.auth() == LdapServer::SASL ) { url.setExtension( "x-sasl","" ); if ( !mServer.bindDN().isEmpty() ) url.setExtension( "x-bindname", mServer.bindDN() ); if ( !mServer.mech().isEmpty() ) url.setExtension( "x-mech", mServer.mech() ); } if ( mServer.timeLimit() != 0 ) url.setExtension( "x-timelimit", QString::number( mServer.timeLimit() ) ); if ( mServer.sizeLimit() != 0 ) url.setExtension( "x-sizelimit", QString::number( mServer.sizeLimit() ) ); url.setAttributes( mAttrs ); url.setScope( mScope == "one" ? KABC::LDAPUrl::One : KABC::LDAPUrl::Sub ); url.setFilter( "("+filter+")" ); kdDebug(5300) << "LdapClient: Doing query: " << url.prettyURL() << endl; startParseLDIF(); mActive = true; mJob = KIO::get( url, false, false ); connect( mJob, SIGNAL( data( KIO::Job*, const QByteArray& ) ), this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) ); connect( mJob, SIGNAL( infoMessage( KIO::Job*, const QString& ) ), this, SLOT( slotInfoMessage( KIO::Job*, const QString& ) ) ); connect( mJob, SIGNAL( result( KIO::Job* ) ), this, SLOT( slotDone() ) ); } void LdapClient::cancelQuery() { if ( mJob ) { mJob->kill(); mJob = 0; } mActive = false; } void LdapClient::slotData( KIO::Job*, const QByteArray& data ) { parseLDIF( data ); } void LdapClient::slotInfoMessage( KIO::Job*, const QString & ) { //qDebug("Job said \"%s\"", info.latin1()); } void LdapClient::slotDone() { endParseLDIF(); mActive = false; #if 0 for ( QValueList::Iterator it = mObjects.begin(); it != mObjects.end(); ++it ) { qDebug( (*it).toString().latin1() ); } #endif int err = mJob->error(); if ( err && err != KIO::ERR_USER_CANCELED ) { emit error( mJob->errorString() ); } emit done(); } void LdapClient::startParseLDIF() { mCurrentObject.clear(); mLdif.startParsing(); } void LdapClient::endParseLDIF() { } void LdapClient::finishCurrentObject() { mCurrentObject.dn = mLdif.dn(); const QString sClass( mCurrentObject.objectClass.lower() ); if( sClass == "groupofnames" || sClass == "kolabgroupofnames" ){ LdapAttrMap::ConstIterator it = mCurrentObject.attrs.find("mail"); if( it == mCurrentObject.attrs.end() ){ // No explicit mail address found so far? // Fine, then we use the address stored in the DN. QString sMail; QStringList lMail = QStringList::split(",dc=", mCurrentObject.dn); const int n = lMail.count(); if( n ){ if( lMail.first().lower().startsWith("cn=") ){ sMail = lMail.first().simplifyWhiteSpace().mid(3); if( 1 < n ) sMail.append('@'); for( int i=1; ireadEntry( prefix + QString( "Host%1" ).arg( j ), "" ).stripWhiteSpace(); if ( !host.isEmpty() ) server.setHost( host ); int port = config->readNumEntry( prefix + QString( "Port%1" ).arg( j ), 389 ); server.setPort( port ); QString base = config->readEntry( prefix + QString( "Base%1" ).arg( j ), "" ).stripWhiteSpace(); if ( !base.isEmpty() ) server.setBaseDN( base ); QString user = config->readEntry( prefix + QString( "User%1" ).arg( j ) ).stripWhiteSpace(); if ( !user.isEmpty() ) server.setUser( user ); QString bindDN = config->readEntry( prefix + QString( "Bind%1" ).arg( j ) ).stripWhiteSpace(); if ( !bindDN.isEmpty() ) server.setBindDN( bindDN ); QString pwdBindDN = config->readEntry( prefix + QString( "PwdBind%1" ).arg( j ) ); if ( !pwdBindDN.isEmpty() ) server.setPwdBindDN( pwdBindDN ); server.setTimeLimit( config->readNumEntry( prefix + QString( "TimeLimit%1" ).arg( j ) ) ); server.setSizeLimit( config->readNumEntry( prefix + QString( "SizeLimit%1" ).arg( j ) ) ); server.setVersion( config->readNumEntry( prefix + QString( "Version%1" ).arg( j ), 3 ) ); server.setSecurity( config->readNumEntry( prefix + QString( "Security%1" ).arg( j ) ) ); server.setAuth( config->readNumEntry( prefix + QString( "Auth%1" ).arg( j ) ) ); server.setMech( config->readEntry( prefix + QString( "Mech%1" ).arg( j ) ) ); } void LdapSearch::writeConfig( const LdapServer &server, KConfig *config, int j, bool active ) { QString prefix; if ( active ) prefix = "Selected"; config->writeEntry( prefix + QString( "Host%1" ).arg( j ), server.host() ); config->writeEntry( prefix + QString( "Port%1" ).arg( j ), server.port() ); config->writeEntry( prefix + QString( "Base%1" ).arg( j ), server.baseDN() ); config->writeEntry( prefix + QString( "User%1" ).arg( j ), server.user() ); config->writeEntry( prefix + QString( "Bind%1" ).arg( j ), server.bindDN() ); config->writeEntry( prefix + QString( "PwdBind%1" ).arg( j ), server.pwdBindDN() ); config->writeEntry( prefix + QString( "TimeLimit%1" ).arg( j ), server.timeLimit() ); config->writeEntry( prefix + QString( "SizeLimit%1" ).arg( j ), server.sizeLimit() ); config->writeEntry( prefix + QString( "Version%1" ).arg( j ), server.version() ); config->writeEntry( prefix + QString( "Security%1" ).arg( j ), server.security() ); config->writeEntry( prefix + QString( "Auth%1" ).arg( j ), server.auth() ); config->writeEntry( prefix + QString( "Mech%1" ).arg( j ), server.mech() ); } KConfig* LdapSearch::config() { if ( !s_config ) configDeleter.setObject( s_config, new KConfig( "kabldaprc", false, false ) ); // Open read-write, no kdeglobals return s_config; } LdapSearch::LdapSearch() : mActiveClients( 0 ), mNoLDAPLookup( false ) { if ( !KProtocolInfo::isKnownProtocol( KURL("ldap://localhost") ) ) { mNoLDAPLookup = true; return; } readConfig(); connect(KDirWatch::self(), SIGNAL(dirty (const QString&)),this, SLOT(slotFileChanged(const QString&))); } +void LdapSearch::readWeighForClient( LdapClient *client, KConfig *config, int clientNumber ) +{ + const int completionWeight = config->readNumEntry( QString( "SelectedCompletionWeight%1" ).arg( clientNumber ), -1 ); + if ( completionWeight != -1 ) + client->setCompletionWeight( completionWeight ); +} + +void LdapSearch::updateCompletionWeights() +{ + KConfig *config = KPIM::LdapSearch::config(); + config->setGroup( "LDAP" ); + for ( uint i = 0; i < mClients.size(); i++ ) { + readWeighForClient( mClients[i], config, i ); + } +} + void LdapSearch::readConfig() { cancelSearch(); QValueList< LdapClient* >::Iterator it; for ( it = mClients.begin(); it != mClients.end(); ++it ) delete *it; mClients.clear(); // stolen from KAddressBook KConfig *config = KPIM::LdapSearch::config(); config->setGroup( "LDAP" ); int numHosts = config->readUnsignedNumEntry( "NumSelectedHosts"); if ( !numHosts ) { mNoLDAPLookup = true; } else { for ( int j = 0; j < numHosts; j++ ) { LdapClient* ldapClient = new LdapClient( j, this ); LdapServer server; readConfig( server, config, j, true ); if ( !server.host().isEmpty() ) mNoLDAPLookup = false; ldapClient->setServer( server ); - int completionWeight = config->readNumEntry( QString( "SelectedCompletionWeight%1" ).arg( j ), -1 ); - if ( completionWeight != -1 ) - ldapClient->setCompletionWeight( completionWeight ); + readWeighForClient( ldapClient, config, j ); QStringList attrs; // note: we need "objectClass" to detect distribution lists attrs << "cn" << "mail" << "givenname" << "sn" << "objectClass"; ldapClient->setAttrs( attrs ); connect( ldapClient, SIGNAL( result( const KPIM::LdapObject& ) ), this, SLOT( slotLDAPResult( const KPIM::LdapObject& ) ) ); connect( ldapClient, SIGNAL( done() ), this, SLOT( slotLDAPDone() ) ); connect( ldapClient, SIGNAL( error( const QString& ) ), this, SLOT( slotLDAPError( const QString& ) ) ); mClients.append( ldapClient ); } connect( &mDataTimer, SIGNAL( timeout() ), SLOT( slotDataTimer() ) ); } mConfigFile = locateLocal( "config", "kabldaprc" ); KDirWatch::self()->addFile( mConfigFile ); } void LdapSearch::slotFileChanged( const QString& file ) { if ( file == mConfigFile ) readConfig(); } void LdapSearch::startSearch( const QString& txt ) { if ( mNoLDAPLookup ) return; cancelSearch(); int pos = txt.find( '\"' ); if( pos >= 0 ) { ++pos; int pos2 = txt.find( '\"', pos ); if( pos2 >= 0 ) mSearchText = txt.mid( pos , pos2 - pos ); else mSearchText = txt.mid( pos ); } else mSearchText = txt; /* The reasoning behind this filter is: * If it's a person, or a distlist, show it, even if it doesn't have an email address. * If it's not a person, or a distlist, only show it if it has an email attribute. * This allows both resource accounts with an email address which are not a person and * person entries without an email address to show up, while still not showing things * like structural entries in the ldap tree. */ QString filter = QString( "&(|(objectclass=person)(objectclass=groupOfNames)(mail=*))(|(cn=%1*)(mail=%2*)(mail=*@%3*)(givenName=%4*)(sn=%5*))" ) .arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText ).arg( mSearchText ); QValueList< LdapClient* >::Iterator it; for ( it = mClients.begin(); it != mClients.end(); ++it ) { (*it)->startQuery( filter ); kdDebug(5300) << "LdapSearch::startSearch() " << filter << endl; ++mActiveClients; } } void LdapSearch::cancelSearch() { QValueList< LdapClient* >::Iterator it; for ( it = mClients.begin(); it != mClients.end(); ++it ) (*it)->cancelQuery(); mActiveClients = 0; mResults.clear(); } void LdapSearch::slotLDAPResult( const KPIM::LdapObject& obj ) { mResults.append( obj ); if ( !mDataTimer.isActive() ) mDataTimer.start( 500, true ); } void LdapSearch::slotLDAPError( const QString& ) { slotLDAPDone(); } void LdapSearch::slotLDAPDone() { if ( --mActiveClients > 0 ) return; finish(); } void LdapSearch::slotDataTimer() { QStringList lst; LdapResultList reslist; makeSearchData( lst, reslist ); if ( !lst.isEmpty() ) emit searchData( lst ); if ( !reslist.isEmpty() ) emit searchData( reslist ); } void LdapSearch::finish() { mDataTimer.stop(); slotDataTimer(); // emit final bunch of data emit searchDone(); } void LdapSearch::makeSearchData( QStringList& ret, LdapResultList& resList ) { QString search_text_upper = mSearchText.upper(); QValueList< KPIM::LdapObject >::ConstIterator it1; for ( it1 = mResults.begin(); it1 != mResults.end(); ++it1 ) { QString name, mail, givenname, sn; QStringList mails; bool isDistributionList = false; bool wasCN = false; bool wasDC = false; - kdDebug(5300) << "\n\nLdapSearch::makeSearchData()\n\n" << endl; + //kdDebug(5300) << "\n\nLdapSearch::makeSearchData()\n\n" << endl; LdapAttrMap::ConstIterator it2; for ( it2 = (*it1).attrs.begin(); it2 != (*it1).attrs.end(); ++it2 ) { QByteArray val = (*it2).first(); int len = val.size(); if( len > 0 && '\0' == val[len-1] ) --len; const QString tmp = QString::fromUtf8( val, len ); - kdDebug(5300) << " key: \"" << it2.key() << "\" value: \"" << tmp << "\"" << endl; + //kdDebug(5300) << " key: \"" << it2.key() << "\" value: \"" << tmp << "\"" << endl; if ( it2.key() == "cn" ) { name = tmp; if( mail.isEmpty() ) mail = tmp; else{ if( wasCN ) mail.prepend( "." ); else mail.prepend( "@" ); mail.prepend( tmp ); } wasCN = true; } else if ( it2.key() == "dc" ) { if( mail.isEmpty() ) mail = tmp; else{ if( wasDC ) mail.append( "." ); else mail.append( "@" ); mail.append( tmp ); } wasDC = true; } else if( it2.key() == "mail" ) { mail = tmp; LdapAttrValue::ConstIterator it3 = it2.data().begin(); for ( ; it3 != it2.data().end(); ++it3 ) { mails.append( QString::fromUtf8( (*it3).data(), (*it3).size() ) ); } } else if( it2.key() == "givenName" ) givenname = tmp; else if( it2.key() == "sn" ) sn = tmp; else if( it2.key() == "objectClass" && (tmp == "groupOfNames" || tmp == "kolabGroupOfNames") ) { isDistributionList = true; } } if( mails.isEmpty()) { if ( !mail.isEmpty() ) mails.append( mail ); if( isDistributionList ) { - kdDebug(5300) << "\n\nLdapSearch::makeSearchData() found a list: " << name << "\n\n" << endl; + //kdDebug(5300) << "\n\nLdapSearch::makeSearchData() found a list: " << name << "\n\n" << endl; ret.append( name ); // following lines commented out for bugfixing kolab issue #177: // // Unlike we thought previously we may NOT append the server name here. // // The right server is found by the SMTP server instead: Kolab users // must use the correct SMTP server, by definition. // //mail = (*it1).client->base().simplifyWhiteSpace(); //mail.replace( ",dc=", ".", false ); //if( mail.startsWith("dc=", false) ) // mail.remove(0, 3); //mail.prepend( '@' ); //mail.prepend( name ); //mail = name; } else { - kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl; + //kdDebug(5300) << "LdapSearch::makeSearchData() found BAD ENTRY: \"" << name << "\"" << endl; continue; // nothing, bad entry } } else if ( name.isEmpty() ) { - kdDebug(5300) << "LdapSearch::makeSearchData() mail: \"" << mail << "\"" << endl; + //kdDebug(5300) << "LdapSearch::makeSearchData() mail: \"" << mail << "\"" << endl; ret.append( mail ); } else { - kdDebug(5300) << "LdapSearch::makeSearchData() name: \"" << name << "\" mail: \"" << mail << "\"" << endl; + //kdDebug(5300) << "LdapSearch::makeSearchData() name: \"" << name << "\" mail: \"" << mail << "\"" << endl; ret.append( QString( "%1 <%2>" ).arg( name ).arg( mail ) ); } LdapResult sr; sr.clientNumber = (*it1).client->clientNumber(); sr.completionWeight = (*it1).client->completionWeight(); sr.name = name; sr.email = mails; resList.append( sr ); } mResults.clear(); } bool LdapSearch::isAvailable() const { return !mNoLDAPLookup; } #include "ldapclient.moc" diff --git a/libkdepim/ldapclient.h b/libkdepim/ldapclient.h index 5be00e85cb..b058f6e318 100644 --- a/libkdepim/ldapclient.h +++ b/libkdepim/ldapclient.h @@ -1,296 +1,298 @@ /* kldapclient.h - LDAP access * Copyright (C) 2002 Klar�vdalens Datakonsult AB * * Author: Steffen Hansen * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #ifndef KPIM_LDAPCLIENT_H #define KPIM_LDAPCLIENT_H #include #include #include #include #include #include #include #include #include #include #include namespace KPIM { class LdapClient; typedef QValueList LdapAttrValue; typedef QMap LdapAttrMap; class LdapServer { public: LdapServer() : mPort( 389 ), mTimeLimit(0), mSizeLimit(0), mVersion(2), mSecurity(Sec_None), mAuth( LdapServer::Anonymous ) {} enum Security{ Sec_None, TLS, SSL }; enum Auth{ Anonymous, Simple, SASL }; QString host() const { return mHost; } int port() const { return mPort; } const QString &baseDN() const { return mBaseDN; } const QString &user() const { return mUser; } const QString &bindDN() const { return mBindDN; } const QString &pwdBindDN() const { return mPwdBindDN; } int timeLimit() const { return mTimeLimit; } int sizeLimit() const { return mSizeLimit; } int version() const { return mVersion; } int security() const { return mSecurity; } int auth() const { return mAuth; } const QString &mech() const { return mMech; } void setHost( const QString &host ) { mHost = host; } void setPort( int port ) { mPort = port; } void setBaseDN( const QString &baseDN ) { mBaseDN = baseDN; } void setUser( const QString &user ) { mUser = user; } void setBindDN( const QString &bindDN ) { mBindDN = bindDN; } void setPwdBindDN( const QString &pwdBindDN ) { mPwdBindDN = pwdBindDN; } void setTimeLimit( int timelimit ) { mTimeLimit = timelimit; } void setSizeLimit( int sizelimit ) { mSizeLimit = sizelimit; } void setVersion( int version ) { mVersion = version; } void setSecurity( int security ) { mSecurity = security; } //0-No, 1-TLS, 2-SSL - KDE4: add an enum to Lda void setAuth( int auth ) { mAuth = auth; } //0-Anonymous, 1-simple, 2-SASL - KDE4: add an enum to LdapCon void setMech( const QString &mech ) { mMech = mech; } private: QString mHost; int mPort; QString mBaseDN; QString mUser; QString mBindDN; QString mPwdBindDN; QString mMech; int mTimeLimit, mSizeLimit, mVersion, mSecurity, mAuth; }; /** * This class is internal. Binary compatibiliy might be broken any time * without notification. Do not use it. * * We mean it! * */ class LdapObject { public: LdapObject() : dn( QString::null ), client( 0 ) {} explicit LdapObject( const QString& _dn, LdapClient* _cl ) : dn( _dn ), client( _cl ) {} LdapObject( const LdapObject& that ) { assign( that ); } LdapObject& operator=( const LdapObject& that ) { assign( that ); return *this; } QString toString() const; void clear(); QString dn; QString objectClass; LdapAttrMap attrs; LdapClient* client; protected: void assign( const LdapObject& that ); private: //class LdapObjectPrivate* d; }; /** * This class is internal. Binary compatibility might be broken any time * without notification. Do not use it. * * We mean it! * */ class KDE_EXPORT LdapClient : public QObject { Q_OBJECT public: LdapClient( int clientNumber, QObject* parent = 0, const char* name = 0 ); virtual ~LdapClient(); /*! returns true if there is a query running */ bool isActive() const { return mActive; } int clientNumber() const; int completionWeight() const; void setCompletionWeight( int ); const LdapServer& server() { return mServer; } void setServer( const LdapServer &server ) { mServer = server; } /*! Return the attributes that should be * returned, or an empty list if * all attributes are wanted */ QStringList attrs() const { return mAttrs; } signals: /*! Emitted when the query is done */ void done(); /*! Emitted in case of error */ void error( const QString& ); /*! Emitted once for each object returned * from the query */ void result( const KPIM::LdapObject& ); public slots: // why are those slots? /*! Set the attributes that should be * returned, or an empty list if * all attributes are wanted */ void setAttrs( const QStringList& attrs ); void setScope( const QString scope ) { mScope = scope; } /*! * Start the query with filter filter */ void startQuery( const QString& filter ); /*! * Abort a running query */ void cancelQuery(); protected slots: void slotData( KIO::Job*, const QByteArray &data ); void slotInfoMessage( KIO::Job*, const QString &info ); void slotDone(); protected: void startParseLDIF(); void parseLDIF( const QByteArray& data ); void endParseLDIF(); void finishCurrentObject(); LdapServer mServer; QString mScope; QStringList mAttrs; QGuardedPtr mJob; bool mActive; bool mReportObjectClass; LdapObject mCurrentObject; private: KABC::LDIF mLdif; int mClientNumber; int mCompletionWeight; // class LdapClientPrivate; // LdapClientPrivate* d; }; /** * Structure describing one result returned by a LDAP query */ struct LdapResult { QString name; ///< full name QStringList email; ///< emails int clientNumber; ///< for sorting in a ldap-only lookup int completionWeight; ///< for sorting in a completion list }; typedef QValueList LdapResultList; /** * This class is internal. Binary compatibiliy might be broken any time * without notification. Do not use it. * * We mean it! * */ class KDE_EXPORT LdapSearch : public QObject { Q_OBJECT public: LdapSearch(); static KConfig *config(); static void readConfig( LdapServer &server, KConfig *config, int num, bool active ); static void writeConfig( const LdapServer &server, KConfig *config, int j, bool active ); void startSearch( const QString& txt ); void cancelSearch(); bool isAvailable() const; + void updateCompletionWeights(); QValueList< LdapClient* > clients() const { return mClients; } signals: /// Results, assembled as "Full Name " /// (This signal can be emitted many times) void searchData( const QStringList& ); /// Another form for the results, with separate fields /// (This signal can be emitted many times) void searchData( const KPIM::LdapResultList& ); void searchDone(); private slots: void slotLDAPResult( const KPIM::LdapObject& ); void slotLDAPError( const QString& ); void slotLDAPDone(); void slotDataTimer(); void slotFileChanged( const QString& ); private: + void readWeighForClient( LdapClient *client, KConfig *config, int clientNumber ); void readConfig(); void finish(); void makeSearchData( QStringList& ret, LdapResultList& resList ); QValueList< LdapClient* > mClients; QString mSearchText; QTimer mDataTimer; int mActiveClients; bool mNoLDAPLookup; QValueList< LdapObject > mResults; QString mConfigFile; private: static KConfig *s_config; class LdapSearchPrivate* d; }; } #endif // KPIM_LDAPCLIENT_H